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