• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include <unordered_set>
13 
14 #include "src/base/macros.h"
15 #include "src/builtins/builtins-definitions.h"
16 #include "src/handles.h"
17 #include "src/trap-handler/trap-handler.h"
18 #include "src/vector.h"
19 #include "src/wasm/module-compiler.h"
20 #include "src/wasm/wasm-features.h"
21 
22 namespace v8 {
23 namespace internal {
24 
25 struct CodeDesc;
26 class Code;
27 
28 namespace wasm {
29 
30 class NativeModule;
31 class WasmCodeManager;
32 class WasmMemoryTracker;
33 struct WasmModule;
34 
35 struct AddressRange {
36   Address start;
37   Address end;
38 
AddressRangeAddressRange39   AddressRange(Address s, Address e) : start(s), end(e) {
40     DCHECK_LE(start, end);
41     DCHECK_IMPLIES(start == kNullAddress, end == kNullAddress);
42   }
AddressRangeAddressRange43   AddressRange() : AddressRange(kNullAddress, kNullAddress) {}
44 
sizeAddressRange45   size_t size() const { return static_cast<size_t>(end - start); }
is_emptyAddressRange46   bool is_empty() const { return start == end; }
47   operator bool() const { return start == kNullAddress; }
48 };
49 
50 // Sorted, disjoint and non-overlapping memory ranges. A range is of the
51 // form [start, end). So there's no [start, end), [end, other_end),
52 // because that should have been reduced to [start, other_end).
53 class V8_EXPORT_PRIVATE DisjointAllocationPool final {
54  public:
55   DisjointAllocationPool() = default;
56 
DisjointAllocationPool(AddressRange range)57   explicit DisjointAllocationPool(AddressRange range) : ranges_({range}) {}
58 
59   DisjointAllocationPool(DisjointAllocationPool&& other) = default;
60   DisjointAllocationPool& operator=(DisjointAllocationPool&& other) = default;
61 
62   // Merge the parameter range into this object while preserving ordering of the
63   // ranges. The assumption is that the passed parameter is not intersecting
64   // this object - for example, it was obtained from a previous Allocate.
65   void Merge(AddressRange);
66 
67   // Allocate a contiguous range of size {size}. Return an empty pool on
68   // failure.
69   AddressRange Allocate(size_t size);
70 
IsEmpty()71   bool IsEmpty() const { return ranges_.empty(); }
ranges()72   const std::list<AddressRange>& ranges() const { return ranges_; }
73 
74  private:
75   std::list<AddressRange> ranges_;
76 
77   DISALLOW_COPY_AND_ASSIGN(DisjointAllocationPool)
78 };
79 
80 class V8_EXPORT_PRIVATE WasmCode final {
81  public:
82   enum Kind {
83     kFunction,
84     kWasmToJsWrapper,
85     kLazyStub,
86     kRuntimeStub,
87     kInterpreterEntry,
88     kJumpTable
89   };
90 
91   // Each runtime stub is identified by an id. This id is used to reference the
92   // stub via {RelocInfo::WASM_STUB_CALL} and gets resolved during relocation.
93   enum RuntimeStubId {
94 #define DEF_ENUM(Name) k##Name,
95 #define DEF_ENUM_TRAP(Name) kThrowWasm##Name,
96     WASM_RUNTIME_STUB_LIST(DEF_ENUM, DEF_ENUM_TRAP)
97 #undef DEF_ENUM_TRAP
98 #undef DEF_ENUM
99         kRuntimeStubCount
100   };
101 
102   // kOther is used if we have WasmCode that is neither
103   // liftoff- nor turbofan-compiled, i.e. if Kind is
104   // not a kFunction.
105   enum Tier : int8_t { kLiftoff, kTurbofan, kOther };
106 
instructions()107   Vector<byte> instructions() const { return instructions_; }
instruction_start()108   Address instruction_start() const {
109     return reinterpret_cast<Address>(instructions_.start());
110   }
reloc_info()111   Vector<const byte> reloc_info() const { return reloc_info_.as_vector(); }
source_positions()112   Vector<const byte> source_positions() const {
113     return source_position_table_.as_vector();
114   }
115 
index()116   uint32_t index() const { return index_.ToChecked(); }
117   // Anonymous functions are functions that don't carry an index.
IsAnonymous()118   bool IsAnonymous() const { return index_.IsNothing(); }
kind()119   Kind kind() const { return kind_; }
native_module()120   NativeModule* native_module() const { return native_module_; }
tier()121   Tier tier() const { return tier_; }
122   Address constant_pool() const;
constant_pool_offset()123   size_t constant_pool_offset() const { return constant_pool_offset_; }
safepoint_table_offset()124   size_t safepoint_table_offset() const { return safepoint_table_offset_; }
handler_table_offset()125   size_t handler_table_offset() const { return handler_table_offset_; }
stack_slots()126   uint32_t stack_slots() const { return stack_slots_; }
is_liftoff()127   bool is_liftoff() const { return tier_ == kLiftoff; }
contains(Address pc)128   bool contains(Address pc) const {
129     return reinterpret_cast<Address>(instructions_.start()) <= pc &&
130            pc < reinterpret_cast<Address>(instructions_.end());
131   }
132 
protected_instructions()133   Vector<trap_handler::ProtectedInstructionData> protected_instructions()
134       const {
135     return protected_instructions_.as_vector();
136   }
137 
138   void Validate() const;
139   void Print(const char* name = nullptr) const;
140   void Disassemble(const char* name, std::ostream& os,
141                    Address current_pc = kNullAddress) const;
142 
143   static bool ShouldBeLogged(Isolate* isolate);
144   void LogCode(Isolate* isolate) const;
145 
146   ~WasmCode();
147 
148   enum FlushICache : bool { kFlushICache = true, kNoFlushICache = false };
149 
150  private:
151   friend class NativeModule;
152 
WasmCode(NativeModule * native_module,Maybe<uint32_t> index,Vector<byte> instructions,uint32_t stack_slots,size_t safepoint_table_offset,size_t handler_table_offset,size_t constant_pool_offset,OwnedVector<trap_handler::ProtectedInstructionData> protected_instructions,OwnedVector<const byte> reloc_info,OwnedVector<const byte> source_position_table,Kind kind,Tier tier)153   WasmCode(NativeModule* native_module, Maybe<uint32_t> index,
154            Vector<byte> instructions, uint32_t stack_slots,
155            size_t safepoint_table_offset, size_t handler_table_offset,
156            size_t constant_pool_offset,
157            OwnedVector<trap_handler::ProtectedInstructionData>
158                protected_instructions,
159            OwnedVector<const byte> reloc_info,
160            OwnedVector<const byte> source_position_table, Kind kind, Tier tier)
161       : instructions_(instructions),
162         reloc_info_(std::move(reloc_info)),
163         source_position_table_(std::move(source_position_table)),
164         native_module_(native_module),
165         index_(index),
166         kind_(kind),
167         constant_pool_offset_(constant_pool_offset),
168         stack_slots_(stack_slots),
169         safepoint_table_offset_(safepoint_table_offset),
170         handler_table_offset_(handler_table_offset),
171         protected_instructions_(std::move(protected_instructions)),
172         tier_(tier) {
173     DCHECK_LE(safepoint_table_offset, instructions.size());
174     DCHECK_LE(constant_pool_offset, instructions.size());
175     DCHECK_LE(handler_table_offset, instructions.size());
176   }
177 
178   // Code objects that have been registered with the global trap handler within
179   // this process, will have a {trap_handler_index} associated with them.
180   size_t trap_handler_index() const;
181   void set_trap_handler_index(size_t);
182   bool HasTrapHandlerIndex() const;
183 
184   // Register protected instruction information with the trap handler. Sets
185   // trap_handler_index.
186   void RegisterTrapHandlerData();
187 
188   Vector<byte> instructions_;
189   OwnedVector<const byte> reloc_info_;
190   OwnedVector<const byte> source_position_table_;
191   NativeModule* native_module_ = nullptr;
192   Maybe<uint32_t> index_;
193   Kind kind_;
194   size_t constant_pool_offset_ = 0;
195   uint32_t stack_slots_ = 0;
196   // we care about safepoint data for wasm-to-js functions,
197   // since there may be stack/register tagged values for large number
198   // conversions.
199   size_t safepoint_table_offset_ = 0;
200   size_t handler_table_offset_ = 0;
201   intptr_t trap_handler_index_ = -1;
202   OwnedVector<trap_handler::ProtectedInstructionData> protected_instructions_;
203   Tier tier_;
204 
205   DISALLOW_COPY_AND_ASSIGN(WasmCode);
206 };
207 
208 // Return a textual description of the kind.
209 const char* GetWasmCodeKindAsString(WasmCode::Kind);
210 
211 class V8_EXPORT_PRIVATE NativeModule final {
212  public:
213 #if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_S390X || V8_TARGET_ARCH_ARM64
214   static constexpr bool kCanAllocateMoreMemory = false;
215 #else
216   static constexpr bool kCanAllocateMoreMemory = true;
217 #endif
218 
219   // {AddCode} is thread safe w.r.t. other calls to {AddCode} or {AddCodeCopy},
220   // i.e. it can be called concurrently from background threads.
221   WasmCode* AddCode(uint32_t index, const CodeDesc& desc, uint32_t stack_slots,
222                     size_t safepoint_table_offset, size_t handler_table_offset,
223                     OwnedVector<trap_handler::ProtectedInstructionData>
224                         protected_instructions,
225                     OwnedVector<const byte> source_position_table,
226                     WasmCode::Tier tier);
227 
228   WasmCode* AddDeserializedCode(
229       uint32_t index, Vector<const byte> instructions, uint32_t stack_slots,
230       size_t safepoint_table_offset, size_t handler_table_offset,
231       size_t constant_pool_offset,
232       OwnedVector<trap_handler::ProtectedInstructionData>
233           protected_instructions,
234       OwnedVector<const byte> reloc_info,
235       OwnedVector<const byte> source_position_table, WasmCode::Tier tier);
236 
237   // A way to copy over JS-allocated code. This is because we compile
238   // certain wrappers using a different pipeline.
239   WasmCode* AddCodeCopy(Handle<Code> code, WasmCode::Kind kind, uint32_t index);
240 
241   // Add an interpreter entry. For the same reason as AddCodeCopy, we
242   // currently compile these using a different pipeline and we can't get a
243   // CodeDesc here. When adding interpreter wrappers, we do not insert them in
244   // the code_table, however, we let them self-identify as the {index} function.
245   WasmCode* AddInterpreterEntry(Handle<Code> code, uint32_t index);
246 
247   // When starting lazy compilation, provide the WasmLazyCompile builtin by
248   // calling SetLazyBuiltin. It will be copied into this NativeModule and the
249   // jump table will be populated with that copy.
250   void SetLazyBuiltin(Handle<Code> code);
251 
252   // Initializes all runtime stubs by copying them over from the JS-allocated
253   // heap into this native module. It must be called exactly once per native
254   // module before adding other WasmCode so that runtime stub ids can be
255   // resolved during relocation.
256   void SetRuntimeStubs(Isolate* isolate);
257 
258   // Makes the code available to the system (by entering it into the code table
259   // and patching the jump table). Callers have to take care not to race with
260   // threads executing the old code.
261   void PublishCode(WasmCode* code);
262 
263   // Creates a snapshot of the current state of the code table. This is useful
264   // to get a consistent view of the table (e.g. used by the serializer).
265   std::vector<WasmCode*> SnapshotCodeTable() const;
266 
code(uint32_t index)267   WasmCode* code(uint32_t index) const {
268     DCHECK_LT(index, num_functions());
269     DCHECK_LE(module_->num_imported_functions, index);
270     return code_table_[index - module_->num_imported_functions];
271   }
272 
has_code(uint32_t index)273   bool has_code(uint32_t index) const { return code(index) != nullptr; }
274 
runtime_stub(WasmCode::RuntimeStubId index)275   WasmCode* runtime_stub(WasmCode::RuntimeStubId index) const {
276     DCHECK_LT(index, WasmCode::kRuntimeStubCount);
277     WasmCode* code = runtime_stub_table_[index];
278     DCHECK_NOT_NULL(code);
279     return code;
280   }
281 
jump_table_start()282   Address jump_table_start() const {
283     return jump_table_ ? jump_table_->instruction_start() : kNullAddress;
284   }
285 
jump_table_offset(uint32_t func_index)286   ptrdiff_t jump_table_offset(uint32_t func_index) const {
287     DCHECK_GE(func_index, num_imported_functions());
288     return GetCallTargetForFunction(func_index) - jump_table_start();
289   }
290 
is_jump_table_slot(Address address)291   bool is_jump_table_slot(Address address) const {
292     return jump_table_->contains(address);
293   }
294 
295   // Transition this module from code relying on trap handlers (i.e. without
296   // explicit memory bounds checks) to code that does not require trap handlers
297   // (i.e. code with explicit bounds checks).
298   // This method must only be called if {use_trap_handler()} is true (it will be
299   // false afterwards). All code in this {NativeModule} needs to be re-added
300   // after calling this method.
301   void DisableTrapHandler();
302 
303   // Returns the target to call for the given function (returns a jump table
304   // slot within {jump_table_}).
305   Address GetCallTargetForFunction(uint32_t func_index) const;
306 
307   // Reverse lookup from a given call target (i.e. a jump table slot as the
308   // above {GetCallTargetForFunction} returns) to a function index.
309   uint32_t GetFunctionIndexFromJumpTableSlot(Address slot_address) const;
310 
311   bool SetExecutable(bool executable);
312 
313   // For cctests, where we build both WasmModule and the runtime objects
314   // on the fly, and bypass the instance builder pipeline.
315   void ReserveCodeTableForTesting(uint32_t max_functions);
316 
317   void LogWasmCodes(Isolate* isolate);
318 
compilation_state()319   CompilationState* compilation_state() { return compilation_state_.get(); }
320 
num_functions()321   uint32_t num_functions() const {
322     return module_->num_declared_functions + module_->num_imported_functions;
323   }
num_imported_functions()324   uint32_t num_imported_functions() const {
325     return module_->num_imported_functions;
326   }
use_trap_handler()327   bool use_trap_handler() const { return use_trap_handler_; }
set_lazy_compile_frozen(bool frozen)328   void set_lazy_compile_frozen(bool frozen) { lazy_compile_frozen_ = frozen; }
lazy_compile_frozen()329   bool lazy_compile_frozen() const { return lazy_compile_frozen_; }
wire_bytes()330   Vector<const byte> wire_bytes() const { return wire_bytes_.as_vector(); }
set_wire_bytes(OwnedVector<const byte> wire_bytes)331   void set_wire_bytes(OwnedVector<const byte> wire_bytes) {
332     wire_bytes_ = std::move(wire_bytes);
333   }
module()334   const WasmModule* module() const { return module_.get(); }
code_manager()335   WasmCodeManager* code_manager() const { return wasm_code_manager_; }
336 
337   WasmCode* Lookup(Address) const;
338 
339   ~NativeModule();
340 
enabled_features()341   const WasmFeatures& enabled_features() const { return enabled_features_; }
342 
343  private:
344   friend class WasmCode;
345   friend class WasmCodeManager;
346   friend class NativeModuleModificationScope;
347 
348   NativeModule(Isolate* isolate, const WasmFeatures& enabled_features,
349                bool can_request_more, VirtualMemory* code_space,
350                WasmCodeManager* code_manager,
351                std::shared_ptr<const WasmModule> module, const ModuleEnv& env);
352 
353   WasmCode* AddAnonymousCode(Handle<Code>, WasmCode::Kind kind);
354   Address AllocateForCode(size_t size);
355 
356   // Primitive for adding code to the native module. All code added to a native
357   // module is owned by that module. Various callers get to decide on how the
358   // code is obtained (CodeDesc vs, as a point in time, Code*), the kind,
359   // whether it has an index or is anonymous, etc.
360   WasmCode* AddOwnedCode(Maybe<uint32_t> index, Vector<const byte> instructions,
361                          uint32_t stack_slots, size_t safepoint_table_offset,
362                          size_t handler_table_offset,
363                          size_t constant_pool_offset,
364                          OwnedVector<trap_handler::ProtectedInstructionData>,
365                          OwnedVector<const byte> reloc_info,
366                          OwnedVector<const byte> source_position_table,
367                          WasmCode::Kind, WasmCode::Tier);
368 
369   WasmCode* CreateEmptyJumpTable(uint32_t num_wasm_functions);
370 
371   void PatchJumpTable(uint32_t func_index, Address target,
372                       WasmCode::FlushICache);
373 
code_table()374   Vector<WasmCode*> code_table() const {
375     return {code_table_.get(), module_->num_declared_functions};
376   }
set_code(uint32_t index,WasmCode * code)377   void set_code(uint32_t index, WasmCode* code) {
378     DCHECK_LT(index, num_functions());
379     DCHECK_LE(module_->num_imported_functions, index);
380     DCHECK_EQ(code->index(), index);
381     code_table_[index - module_->num_imported_functions] = code;
382   }
383 
384   // Features enabled for this module. We keep a copy of the features that
385   // were enabled at the time of the creation of this native module,
386   // to be consistent across asynchronous compilations later.
387   const WasmFeatures enabled_features_;
388 
389   // TODO(clemensh): Make this a unique_ptr (requires refactoring
390   // AsyncCompileJob).
391   std::shared_ptr<const WasmModule> module_;
392 
393   // Holds all allocated code objects, is maintained to be in ascending order
394   // according to the codes instruction start address to allow lookups.
395   std::vector<std::unique_ptr<WasmCode>> owned_code_;
396 
397   std::unique_ptr<WasmCode* []> code_table_;
398 
399   OwnedVector<const byte> wire_bytes_;
400 
401   WasmCode* runtime_stub_table_[WasmCode::kRuntimeStubCount] = {nullptr};
402 
403   // Jump table used to easily redirect wasm function calls.
404   WasmCode* jump_table_ = nullptr;
405 
406   // The compilation state keeps track of compilation tasks for this module.
407   // Note that its destructor blocks until all tasks are finished/aborted and
408   // hence needs to be destructed first when this native module dies.
409   std::unique_ptr<CompilationState, CompilationStateDeleter> compilation_state_;
410 
411   // This mutex protects concurrent calls to {AddCode} and {AddCodeCopy}.
412   mutable base::Mutex allocation_mutex_;
413 
414   DisjointAllocationPool free_code_space_;
415   DisjointAllocationPool allocated_code_space_;
416   std::list<VirtualMemory> owned_code_space_;
417 
418   WasmCodeManager* wasm_code_manager_;
419   std::atomic<size_t> committed_code_space_{0};
420   int modification_scope_depth_ = 0;
421   bool can_request_more_memory_;
422   bool use_trap_handler_ = false;
423   bool is_executable_ = false;
424   bool lazy_compile_frozen_ = false;
425 
426   DISALLOW_COPY_AND_ASSIGN(NativeModule);
427 };
428 
429 class V8_EXPORT_PRIVATE WasmCodeManager final {
430  public:
431   explicit WasmCodeManager(WasmMemoryTracker* memory_tracker,
432                            size_t max_committed);
433   // Create a new NativeModule. The caller is responsible for its
434   // lifetime. The native module will be given some memory for code,
435   // which will be page size aligned. The size of the initial memory
436   // is determined with a heuristic based on the total size of wasm
437   // code. The native module may later request more memory.
438   // TODO(titzer): isolate is only required here for CompilationState.
439   std::unique_ptr<NativeModule> NewNativeModule(
440       Isolate* isolate, const WasmFeatures& enabled_features,
441       size_t memory_estimate, bool can_request_more,
442       std::shared_ptr<const WasmModule> module, const ModuleEnv& env);
443 
444   NativeModule* LookupNativeModule(Address pc) const;
445   WasmCode* LookupCode(Address pc) const;
446   WasmCode* GetCodeFromStartAddress(Address pc) const;
447   size_t remaining_uncommitted_code_space() const;
448 
449   // Add a sample of all module sizes.
450   void SampleModuleSizes(Isolate* isolate) const;
451 
452   // TODO(v8:7424): For now we sample module sizes in a GC callback. This will
453   // bias samples towards apps with high memory pressure. We should switch to
454   // using sampling based on regular intervals independent of the GC.
455   static void InstallSamplingGCCallback(Isolate* isolate);
456 
457   static size_t EstimateNativeModuleSize(const WasmModule* module);
458 
459  private:
460   friend class NativeModule;
461 
462   void TryAllocate(size_t size, VirtualMemory*, void* hint = nullptr);
463   bool Commit(Address, size_t);
464   // Currently, we uncommit a whole module, so all we need is account
465   // for the freed memory size. We do that in FreeNativeModule.
466   // There's no separate Uncommit.
467 
468   void FreeNativeModule(NativeModule*);
469   void Free(VirtualMemory* mem);
470   void AssignRanges(Address start, Address end, NativeModule*);
471   bool ShouldForceCriticalMemoryPressureNotification();
472 
473   WasmMemoryTracker* const memory_tracker_;
474   mutable base::Mutex native_modules_mutex_;
475   std::map<Address, std::pair<Address, NativeModule*>> lookup_map_;
476   std::unordered_set<NativeModule*> native_modules_;
477   std::atomic<size_t> remaining_uncommitted_code_space_;
478 
479   DISALLOW_COPY_AND_ASSIGN(WasmCodeManager);
480 };
481 
482 // Within the scope, the native_module is writable and not executable.
483 // At the scope's destruction, the native_module is executable and not writable.
484 // The states inside the scope and at the scope termination are irrespective of
485 // native_module's state when entering the scope.
486 // We currently mark the entire module's memory W^X:
487 //  - for AOT, that's as efficient as it can be.
488 //  - for Lazy, we don't have a heuristic for functions that may need patching,
489 //    and even if we did, the resulting set of pages may be fragmented.
490 //    Currently, we try and keep the number of syscalls low.
491 // -  similar argument for debug time.
492 class NativeModuleModificationScope final {
493  public:
494   explicit NativeModuleModificationScope(NativeModule* native_module);
495   ~NativeModuleModificationScope();
496 
497  private:
498   NativeModule* native_module_;
499 };
500 
501 }  // namespace wasm
502 }  // namespace internal
503 }  // namespace v8
504 
505 #endif  // V8_WASM_WASM_CODE_MANAGER_H_
506