1 // Copyright 2017 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef V8_WASM_WASM_CODE_MANAGER_H_
6 #define V8_WASM_WASM_CODE_MANAGER_H_
7 
8 #include <functional>
9 #include <list>
10 #include <map>
11 #include <unordered_map>
12 
13 #include "src/base/macros.h"
14 #include "src/handles.h"
15 #include "src/trap-handler/trap-handler.h"
16 #include "src/vector.h"
17 #include "src/wasm/module-compiler.h"
18 
19 namespace v8 {
20 class Isolate;
21 namespace internal {
22 
23 struct CodeDesc;
24 class Code;
25 class Histogram;
26 class WasmCompiledModule;
27 
28 namespace wasm {
29 
30 class NativeModule;
31 class WasmCodeManager;
32 struct WasmModule;
33 
34 // Sorted, disjoint and non-overlapping memory ranges. A range is of the
35 // form [start, end). So there's no [start, end), [end, other_end),
36 // because that should have been reduced to [start, other_end).
37 using AddressRange = std::pair<Address, Address>;
38 class V8_EXPORT_PRIVATE DisjointAllocationPool final {
39  public:
40   enum ExtractionMode : bool { kAny = false, kContiguous = true };
DisjointAllocationPool()41   DisjointAllocationPool() {}
42 
43   explicit DisjointAllocationPool(Address, Address);
44 
45   DisjointAllocationPool(DisjointAllocationPool&& other) = default;
46   DisjointAllocationPool& operator=(DisjointAllocationPool&& other) = default;
47 
48   // Merge the ranges of the parameter into this object. Ordering is
49   // preserved. The assumption is that the passed parameter is
50   // not intersecting this object - for example, it was obtained
51   // from a previous Allocate{Pool}.
52   void Merge(DisjointAllocationPool&&);
53 
54   // Allocate a contiguous range of size {size}. Return an empty pool on
55   // failure.
Allocate(size_t size)56   DisjointAllocationPool Allocate(size_t size) {
57     return Extract(size, kContiguous);
58   }
59 
60   // Allocate a sub-pool of size {size}. Return an empty pool on failure.
AllocatePool(size_t size)61   DisjointAllocationPool AllocatePool(size_t size) {
62     return Extract(size, kAny);
63   }
64 
IsEmpty()65   bool IsEmpty() const { return ranges_.empty(); }
ranges()66   const std::list<AddressRange>& ranges() const { return ranges_; }
67 
68  private:
69   // Extract out a total of {size}. By default, the return may
70   // be more than one range. If kContiguous is passed, the return
71   // will be one range. If the operation fails, this object is
72   // unchanged, and the return {IsEmpty()}
73   DisjointAllocationPool Extract(size_t size, ExtractionMode mode);
74 
75   std::list<AddressRange> ranges_;
76 
77   DISALLOW_COPY_AND_ASSIGN(DisjointAllocationPool)
78 };
79 
80 using ProtectedInstructions =
81     std::vector<trap_handler::ProtectedInstructionData>;
82 
83 class V8_EXPORT_PRIVATE WasmCode final {
84  public:
85   enum Kind {
86     kFunction,
87     kWasmToJsWrapper,
88     kLazyStub,
89     kInterpreterEntry,
90     kTrampoline
91   };
92 
93   // kOther is used if we have WasmCode that is neither
94   // liftoff- nor turbofan-compiled, i.e. if Kind is
95   // not a kFunction.
96   enum Tier : int8_t { kLiftoff, kTurbofan, kOther };
97 
instructions()98   Vector<byte> instructions() const { return instructions_; }
instruction_start()99   Address instruction_start() const {
100     return reinterpret_cast<Address>(instructions_.start());
101   }
reloc_info()102   Vector<const byte> reloc_info() const {
103     return {reloc_info_.get(), reloc_size_};
104   }
source_positions()105   Vector<const byte> source_positions() const {
106     return {source_position_table_.get(), source_position_size_};
107   }
108 
index()109   uint32_t index() const { return index_.ToChecked(); }
110   // Anonymous functions are functions that don't carry an index, like
111   // trampolines.
IsAnonymous()112   bool IsAnonymous() const { return index_.IsNothing(); }
kind()113   Kind kind() const { return kind_; }
native_module()114   NativeModule* native_module() const { return native_module_; }
tier()115   Tier tier() const { return tier_; }
116   Address constant_pool() const;
constant_pool_offset()117   size_t constant_pool_offset() const { return constant_pool_offset_; }
safepoint_table_offset()118   size_t safepoint_table_offset() const { return safepoint_table_offset_; }
handler_table_offset()119   size_t handler_table_offset() const { return handler_table_offset_; }
stack_slots()120   uint32_t stack_slots() const { return stack_slots_; }
is_liftoff()121   bool is_liftoff() const { return tier_ == kLiftoff; }
contains(Address pc)122   bool contains(Address pc) const {
123     return reinterpret_cast<Address>(instructions_.start()) <= pc &&
124            pc < reinterpret_cast<Address>(instructions_.end());
125   }
126 
protected_instructions()127   const ProtectedInstructions& protected_instructions() const {
128     // TODO(mstarzinger): Code that doesn't have trapping instruction should
129     // not be required to have this vector, make it possible to be null.
130     DCHECK_NOT_NULL(protected_instructions_);
131     return *protected_instructions_.get();
132   }
133 
134   // Register protected instruction information with the trap handler. Sets
135   // trap_handler_index.
136   void RegisterTrapHandlerData();
137 
138   void Print(Isolate* isolate) const;
139   void Disassemble(const char* name, Isolate* isolate, std::ostream& os,
140                    Address current_pc = kNullAddress) const;
141 
142   static bool ShouldBeLogged(Isolate* isolate);
143   void LogCode(Isolate* isolate) const;
144 
145   ~WasmCode();
146 
147   enum FlushICache : bool { kFlushICache = true, kNoFlushICache = false };
148 
149   // Offset of {instructions_.start()}. It is used for tiering, when
150   // we check if optimized code is available during the prologue
151   // of Liftoff-compiled code.
152   static constexpr int kInstructionStartOffset = 0;
153 
154  private:
155   friend class NativeModule;
156 
WasmCode(Vector<byte> instructions,std::unique_ptr<const byte[]> reloc_info,size_t reloc_size,std::unique_ptr<const byte[]> source_pos,size_t source_pos_size,NativeModule * native_module,Maybe<uint32_t> index,Kind kind,size_t constant_pool_offset,uint32_t stack_slots,size_t safepoint_table_offset,size_t handler_table_offset,std::unique_ptr<ProtectedInstructions> protected_instructions,Tier tier)157   WasmCode(Vector<byte> instructions, std::unique_ptr<const byte[]> reloc_info,
158            size_t reloc_size, std::unique_ptr<const byte[]> source_pos,
159            size_t source_pos_size, NativeModule* native_module,
160            Maybe<uint32_t> index, Kind kind, size_t constant_pool_offset,
161            uint32_t stack_slots, size_t safepoint_table_offset,
162            size_t handler_table_offset,
163            std::unique_ptr<ProtectedInstructions> protected_instructions,
164            Tier tier)
165       : instructions_(instructions),
166         reloc_info_(std::move(reloc_info)),
167         reloc_size_(reloc_size),
168         source_position_table_(std::move(source_pos)),
169         source_position_size_(source_pos_size),
170         native_module_(native_module),
171         index_(index),
172         kind_(kind),
173         constant_pool_offset_(constant_pool_offset),
174         stack_slots_(stack_slots),
175         safepoint_table_offset_(safepoint_table_offset),
176         handler_table_offset_(handler_table_offset),
177         protected_instructions_(std::move(protected_instructions)),
178         tier_(tier) {
179     DCHECK_LE(safepoint_table_offset, instructions.size());
180     DCHECK_LE(constant_pool_offset, instructions.size());
181     DCHECK_LE(handler_table_offset, instructions.size());
182     DCHECK_EQ(kInstructionStartOffset, OFFSET_OF(WasmCode, instructions_));
183   }
184 
185   // Code objects that have been registered with the global trap handler within
186   // this process, will have a {trap_handler_index} associated with them.
187   size_t trap_handler_index() const;
188   void set_trap_handler_index(size_t);
189   bool HasTrapHandlerIndex() const;
190   void ResetTrapHandlerIndex();
191 
192   Vector<byte> instructions_;
193   std::unique_ptr<const byte[]> reloc_info_;
194   size_t reloc_size_ = 0;
195   std::unique_ptr<const byte[]> source_position_table_;
196   size_t source_position_size_ = 0;
197   NativeModule* native_module_ = nullptr;
198   Maybe<uint32_t> index_;
199   Kind kind_;
200   size_t constant_pool_offset_ = 0;
201   uint32_t stack_slots_ = 0;
202   // we care about safepoint data for wasm-to-js functions,
203   // since there may be stack/register tagged values for large number
204   // conversions.
205   size_t safepoint_table_offset_ = 0;
206   size_t handler_table_offset_ = 0;
207   intptr_t trap_handler_index_ = -1;
208   std::unique_ptr<ProtectedInstructions> protected_instructions_;
209   Tier tier_;
210 
211   DISALLOW_COPY_AND_ASSIGN(WasmCode);
212 };
213 
214 // Return a textual description of the kind.
215 const char* GetWasmCodeKindAsString(WasmCode::Kind);
216 
217 // Note that we currently need to add code on the main thread, because we may
218 // trigger a GC if we believe there's a chance the GC would clear up native
219 // modules. The code is ready for concurrency otherwise, we just need to be
220 // careful about this GC consideration. See WouldGCHelp and
221 // WasmCodeManager::Commit.
222 class V8_EXPORT_PRIVATE NativeModule final {
223  public:
224   WasmCode* AddCode(const CodeDesc& desc, uint32_t frame_count, uint32_t index,
225                     size_t safepoint_table_offset, size_t handler_table_offset,
226                     std::unique_ptr<ProtectedInstructions>,
227                     Handle<ByteArray> source_position_table,
228                     WasmCode::Tier tier);
229 
230   // A way to copy over JS-allocated code. This is because we compile
231   // certain wrappers using a different pipeline.
232   WasmCode* AddCodeCopy(Handle<Code> code, WasmCode::Kind kind, uint32_t index);
233 
234   // Add an interpreter entry. For the same reason as AddCodeCopy, we
235   // currently compile these using a different pipeline and we can't get a
236   // CodeDesc here. When adding interpreter wrappers, we do not insert them in
237   // the code_table, however, we let them self-identify as the {index} function
238   WasmCode* AddInterpreterEntry(Handle<Code> code, uint32_t index);
239 
240   // When starting lazy compilation, provide the WasmLazyCompile builtin by
241   // calling SetLazyBuiltin. It will initialize the code table with it. Copies
242   // of it might be cloned from them later when creating entries for exported
243   // functions and indirect callable functions, so that they may be identified
244   // by the runtime.
245   void SetLazyBuiltin(Handle<Code> code);
246 
247   // function_count is WasmModule::functions.size().
function_count()248   uint32_t function_count() const {
249     DCHECK_LE(code_table_.size(), std::numeric_limits<uint32_t>::max());
250     return static_cast<uint32_t>(code_table_.size());
251   }
252 
code(uint32_t index)253   WasmCode* code(uint32_t index) const {
254     DCHECK_LT(index, function_count());
255     DCHECK_LE(num_imported_functions(), index);
256     return code_table_[index];
257   }
258 
259   // TODO(clemensh): Remove this method once we have the jump table
260   // (crbug.com/v8/7758).
SetCodeForTesting(uint32_t index,WasmCode * code)261   void SetCodeForTesting(uint32_t index, WasmCode* code) {
262     DCHECK_LT(index, function_count());
263     DCHECK_LE(num_imported_functions(), index);
264     code_table_[index] = code;
265   }
266 
has_code(uint32_t index)267   bool has_code(uint32_t index) const {
268     DCHECK_LT(index, function_count());
269     return code_table_[index] != nullptr;
270   }
271 
272   // Register/release the protected instructions in all code objects with the
273   // global trap handler for this process.
274   void UnpackAndRegisterProtectedInstructions();
275   void ReleaseProtectedInstructions();
276 
277   // Returns the instruction start of code suitable for indirect or import calls
278   // for the given function index. If the code at the given index is the lazy
279   // compile stub, it will clone a non-anonymous lazy compile stub for the
280   // purpose. This will soon change to always return a jump table slot.
281   Address GetCallTargetForFunction(uint32_t index);
282 
283   bool SetExecutable(bool executable);
284 
285   // For cctests, where we build both WasmModule and the runtime objects
286   // on the fly, and bypass the instance builder pipeline.
287   void ResizeCodeTableForTesting(size_t num_functions, size_t max_functions);
288 
compilation_state()289   CompilationState* compilation_state() { return compilation_state_.get(); }
290 
291   // TODO(mstarzinger): The link to the {shared_module_data} is deprecated and
292   // all uses should vanish to make {NativeModule} independent of the Isolate.
293   WasmSharedModuleData* shared_module_data() const;
294   void SetSharedModuleData(Handle<WasmSharedModuleData>);
295 
num_imported_functions()296   uint32_t num_imported_functions() const { return num_imported_functions_; }
code_table()297   const std::vector<WasmCode*>& code_table() const { return code_table_; }
use_trap_handler()298   bool use_trap_handler() const { return use_trap_handler_; }
set_lazy_compile_frozen(bool frozen)299   void set_lazy_compile_frozen(bool frozen) { lazy_compile_frozen_ = frozen; }
lazy_compile_frozen()300   bool lazy_compile_frozen() const { return lazy_compile_frozen_; }
301 
302   const size_t instance_id = 0;
303   ~NativeModule();
304 
305  private:
306   friend class WasmCodeManager;
307   friend class NativeModuleSerializer;
308   friend class NativeModuleDeserializer;
309   friend class NativeModuleModificationScope;
310 
311   static base::AtomicNumber<size_t> next_id_;
312   NativeModule(uint32_t num_functions, uint32_t num_imports,
313                bool can_request_more, VirtualMemory* code_space,
314                WasmCodeManager* code_manager, ModuleEnv& env);
315 
316   WasmCode* AddAnonymousCode(Handle<Code>, WasmCode::Kind kind);
317   Address AllocateForCode(size_t size);
318 
319   // Primitive for adding code to the native module. All code added to a native
320   // module is owned by that module. Various callers get to decide on how the
321   // code is obtained (CodeDesc vs, as a point in time, Code*), the kind,
322   // whether it has an index or is anonymous, etc.
323   WasmCode* AddOwnedCode(Vector<const byte> orig_instructions,
324                          std::unique_ptr<const byte[]> reloc_info,
325                          size_t reloc_size,
326                          std::unique_ptr<const byte[]> source_pos,
327                          size_t source_pos_size, Maybe<uint32_t> index,
328                          WasmCode::Kind kind, size_t constant_pool_offset,
329                          uint32_t stack_slots, size_t safepoint_table_offset,
330                          size_t handler_table_offset,
331                          std::unique_ptr<ProtectedInstructions>, WasmCode::Tier,
332                          WasmCode::FlushICache);
333   WasmCode* CloneCode(const WasmCode*, WasmCode::FlushICache);
334   WasmCode* Lookup(Address);
335   Address GetLocalAddressFor(Handle<Code>);
336   Address CreateTrampolineTo(Handle<Code>);
337 
338   // Holds all allocated code objects, is maintained to be in ascending order
339   // according to the codes instruction start address to allow lookups.
340   std::vector<std::unique_ptr<WasmCode>> owned_code_;
341 
342   std::vector<WasmCode*> code_table_;
343   std::unique_ptr<std::vector<WasmCode*>> lazy_compile_stubs_;
344   uint32_t num_imported_functions_;
345 
346   // Maps from instruction start of an immovable code object to instruction
347   // start of the trampoline.
348   std::unordered_map<Address, Address> trampolines_;
349 
350   std::unique_ptr<CompilationState, CompilationStateDeleter> compilation_state_;
351 
352   // A phantom reference to the {WasmSharedModuleData}. It is intentionally not
353   // typed {Handle<WasmSharedModuleData>} because this location will be cleared
354   // when the phantom reference is cleared.
355   WasmSharedModuleData** shared_module_data_ = nullptr;
356 
357   DisjointAllocationPool free_code_space_;
358   DisjointAllocationPool allocated_code_space_;
359   std::list<VirtualMemory> owned_code_space_;
360 
361   WasmCodeManager* wasm_code_manager_;
362   base::Mutex allocation_mutex_;
363   size_t committed_code_space_ = 0;
364   int modification_scope_depth_ = 0;
365   bool can_request_more_memory_;
366   bool use_trap_handler_;
367   bool is_executable_ = false;
368   bool lazy_compile_frozen_ = false;
369 
370   DISALLOW_COPY_AND_ASSIGN(NativeModule);
371 };
372 
373 class V8_EXPORT_PRIVATE WasmCodeManager final {
374  public:
375   // The only reason we depend on Isolate is to report native memory used
376   // and held by a GC-ed object. We'll need to mitigate that when we
377   // start sharing wasm heaps.
378   WasmCodeManager(v8::Isolate*, size_t max_committed);
379   // Create a new NativeModule. The caller is responsible for its
380   // lifetime. The native module will be given some memory for code,
381   // which will be page size aligned. The size of the initial memory
382   // is determined with a heuristic based on the total size of wasm
383   // code. The native module may later request more memory.
384   std::unique_ptr<NativeModule> NewNativeModule(const WasmModule& module,
385                                                 ModuleEnv& env);
386   std::unique_ptr<NativeModule> NewNativeModule(size_t memory_estimate,
387                                                 uint32_t num_functions,
388                                                 uint32_t num_imported_functions,
389                                                 bool can_request_more,
390                                                 ModuleEnv& env);
391 
392   WasmCode* LookupCode(Address pc) const;
393   WasmCode* GetCodeFromStartAddress(Address pc) const;
394   size_t remaining_uncommitted_code_space() const;
395 
SetModuleCodeSizeHistogram(Histogram * histogram)396   void SetModuleCodeSizeHistogram(Histogram* histogram) {
397     module_code_size_mb_ = histogram;
398   }
399 
400  private:
401   friend class NativeModule;
402 
403   void TryAllocate(size_t size, VirtualMemory*, void* hint = nullptr);
404   bool Commit(Address, size_t);
405   // Currently, we uncommit a whole module, so all we need is account
406   // for the freed memory size. We do that in FreeNativeModule.
407   // There's no separate Uncommit.
408 
409   void FreeNativeModule(NativeModule*);
410   void Free(VirtualMemory* mem);
411   void AssignRanges(Address start, Address end, NativeModule*);
412   size_t GetAllocationChunk(const WasmModule& module);
413   bool WouldGCHelp() const;
414 
415   std::map<Address, std::pair<Address, NativeModule*>> lookup_map_;
416   // Count of NativeModules not yet collected. Helps determine if it's
417   // worth requesting a GC on memory pressure.
418   size_t active_ = 0;
419   std::atomic<size_t> remaining_uncommitted_code_space_;
420 
421   // TODO(mtrofin): remove the dependency on isolate.
422   v8::Isolate* isolate_;
423 
424   // Histogram to update with the maximum used code space for each NativeModule.
425   Histogram* module_code_size_mb_ = nullptr;
426 
427   DISALLOW_COPY_AND_ASSIGN(WasmCodeManager);
428 };
429 
430 // Within the scope, the native_module is writable and not executable.
431 // At the scope's destruction, the native_module is executable and not writable.
432 // The states inside the scope and at the scope termination are irrespective of
433 // native_module's state when entering the scope.
434 // We currently mark the entire module's memory W^X:
435 //  - for AOT, that's as efficient as it can be.
436 //  - for Lazy, we don't have a heuristic for functions that may need patching,
437 //    and even if we did, the resulting set of pages may be fragmented.
438 //    Currently, we try and keep the number of syscalls low.
439 // -  similar argument for debug time.
440 class NativeModuleModificationScope final {
441  public:
442   explicit NativeModuleModificationScope(NativeModule* native_module);
443   ~NativeModuleModificationScope();
444 
445  private:
446   NativeModule* native_module_;
447 };
448 
449 }  // namespace wasm
450 }  // namespace internal
451 }  // namespace v8
452 
453 #endif  // V8_WASM_WASM_CODE_MANAGER_H_
454