1 // Copyright 2015 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 #include "src/macro-assembler.h" 6 #include "src/objects.h" 7 #include "src/v8.h" 8 9 #include "src/simulator.h" 10 11 #include "src/wasm/ast-decoder.h" 12 #include "src/wasm/module-decoder.h" 13 #include "src/wasm/wasm-module.h" 14 #include "src/wasm/wasm-result.h" 15 16 #include "src/compiler/wasm-compiler.h" 17 18 namespace v8 { 19 namespace internal { 20 namespace wasm { 21 22 std::ostream& operator<<(std::ostream& os, const WasmModule& module) { 23 os << "WASM module with "; 24 os << (1 << module.min_mem_size_log2) << " min mem"; 25 os << (1 << module.max_mem_size_log2) << " max mem"; 26 if (module.functions) os << module.functions->size() << " functions"; 27 if (module.globals) os << module.functions->size() << " globals"; 28 if (module.data_segments) os << module.functions->size() << " data segments"; 29 return os; 30 } 31 32 33 std::ostream& operator<<(std::ostream& os, const WasmFunction& function) { 34 os << "WASM function with signature "; 35 36 // TODO(titzer): factor out rendering of signatures. 37 if (function.sig->return_count() == 0) os << "v"; 38 for (size_t i = 0; i < function.sig->return_count(); i++) { 39 os << WasmOpcodes::ShortNameOf(function.sig->GetReturn(i)); 40 } 41 os << "_"; 42 if (function.sig->parameter_count() == 0) os << "v"; 43 for (size_t i = 0; i < function.sig->parameter_count(); i++) { 44 os << WasmOpcodes::ShortNameOf(function.sig->GetParam(i)); 45 } 46 os << " locals: "; 47 if (function.local_int32_count) 48 os << function.local_int32_count << " int32s "; 49 if (function.local_int64_count) 50 os << function.local_int64_count << " int64s "; 51 if (function.local_float32_count) 52 os << function.local_float32_count << " float32s "; 53 if (function.local_float64_count) 54 os << function.local_float64_count << " float64s "; 55 56 os << " code bytes: " 57 << (function.code_end_offset - function.code_start_offset); 58 return os; 59 } 60 61 62 // A helper class for compiling multiple wasm functions that offers 63 // placeholder code objects for calling functions that are not yet compiled. 64 class WasmLinker { 65 public: 66 WasmLinker(Isolate* isolate, size_t size) 67 : isolate_(isolate), placeholder_code_(size), function_code_(size) {} 68 69 // Get the code object for a function, allocating a placeholder if it has 70 // not yet been compiled. 71 Handle<Code> GetFunctionCode(uint32_t index) { 72 DCHECK(index < function_code_.size()); 73 if (function_code_[index].is_null()) { 74 // Create a placeholder code object and encode the corresponding index in 75 // the {constant_pool_offset} field of the code object. 76 // TODO(titzer): placeholder code objects are somewhat dangerous. 77 Handle<Code> self(nullptr, isolate_); 78 byte buffer[] = {0, 0, 0, 0, 0, 0, 0, 0}; // fake instructions. 79 CodeDesc desc = {buffer, 8, 8, 0, 0, nullptr}; 80 Handle<Code> code = isolate_->factory()->NewCode( 81 desc, Code::KindField::encode(Code::WASM_FUNCTION), self); 82 code->set_constant_pool_offset(index + kPlaceholderMarker); 83 placeholder_code_[index] = code; 84 function_code_[index] = code; 85 } 86 return function_code_[index]; 87 } 88 89 void Finish(uint32_t index, Handle<Code> code) { 90 DCHECK(index < function_code_.size()); 91 function_code_[index] = code; 92 } 93 94 void Link(Handle<FixedArray> function_table, 95 std::vector<uint16_t>* functions) { 96 for (size_t i = 0; i < function_code_.size(); i++) { 97 LinkFunction(function_code_[i]); 98 } 99 if (functions && !function_table.is_null()) { 100 int table_size = static_cast<int>(functions->size()); 101 DCHECK_EQ(function_table->length(), table_size * 2); 102 for (int i = 0; i < table_size; i++) { 103 function_table->set(i + table_size, *function_code_[functions->at(i)]); 104 } 105 } 106 } 107 108 private: 109 static const int kPlaceholderMarker = 1000000000; 110 111 Isolate* isolate_; 112 std::vector<Handle<Code>> placeholder_code_; 113 std::vector<Handle<Code>> function_code_; 114 115 void LinkFunction(Handle<Code> code) { 116 bool modified = false; 117 int mode_mask = RelocInfo::kCodeTargetMask; 118 AllowDeferredHandleDereference embedding_raw_address; 119 for (RelocIterator it(*code, mode_mask); !it.done(); it.next()) { 120 RelocInfo::Mode mode = it.rinfo()->rmode(); 121 if (RelocInfo::IsCodeTarget(mode)) { 122 Code* target = 123 Code::GetCodeFromTargetAddress(it.rinfo()->target_address()); 124 if (target->kind() == Code::WASM_FUNCTION && 125 target->constant_pool_offset() >= kPlaceholderMarker) { 126 // Patch direct calls to placeholder code objects. 127 uint32_t index = target->constant_pool_offset() - kPlaceholderMarker; 128 CHECK(index < function_code_.size()); 129 Handle<Code> new_target = function_code_[index]; 130 if (target != *new_target) { 131 CHECK_EQ(*placeholder_code_[index], target); 132 it.rinfo()->set_target_address(new_target->instruction_start(), 133 SKIP_WRITE_BARRIER, 134 SKIP_ICACHE_FLUSH); 135 modified = true; 136 } 137 } 138 } 139 } 140 if (modified) { 141 Assembler::FlushICache(isolate_, code->instruction_start(), 142 code->instruction_size()); 143 } 144 } 145 }; 146 147 namespace { 148 // Internal constants for the layout of the module object. 149 const int kWasmModuleInternalFieldCount = 4; 150 const int kWasmModuleFunctionTable = 0; 151 const int kWasmModuleCodeTable = 1; 152 const int kWasmMemArrayBuffer = 2; 153 const int kWasmGlobalsArrayBuffer = 3; 154 155 156 size_t AllocateGlobalsOffsets(std::vector<WasmGlobal>* globals) { 157 uint32_t offset = 0; 158 if (!globals) return 0; 159 for (WasmGlobal& global : *globals) { 160 byte size = WasmOpcodes::MemSize(global.type); 161 offset = (offset + size - 1) & ~(size - 1); // align 162 global.offset = offset; 163 offset += size; 164 } 165 return offset; 166 } 167 168 169 void LoadDataSegments(WasmModule* module, byte* mem_addr, size_t mem_size) { 170 for (const WasmDataSegment& segment : *module->data_segments) { 171 if (!segment.init) continue; 172 CHECK_LT(segment.dest_addr, mem_size); 173 CHECK_LE(segment.source_size, mem_size); 174 CHECK_LE(segment.dest_addr + segment.source_size, mem_size); 175 byte* addr = mem_addr + segment.dest_addr; 176 memcpy(addr, module->module_start + segment.source_offset, 177 segment.source_size); 178 } 179 } 180 181 182 Handle<FixedArray> BuildFunctionTable(Isolate* isolate, WasmModule* module) { 183 if (!module->function_table || module->function_table->size() == 0) { 184 return Handle<FixedArray>::null(); 185 } 186 int table_size = static_cast<int>(module->function_table->size()); 187 Handle<FixedArray> fixed = isolate->factory()->NewFixedArray(2 * table_size); 188 for (int i = 0; i < table_size; i++) { 189 WasmFunction* function = 190 &module->functions->at(module->function_table->at(i)); 191 fixed->set(i, Smi::FromInt(function->sig_index)); 192 } 193 return fixed; 194 } 195 196 197 Handle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, int size, 198 byte** backing_store) { 199 void* memory = isolate->array_buffer_allocator()->Allocate(size); 200 if (!memory) return Handle<JSArrayBuffer>::null(); 201 *backing_store = reinterpret_cast<byte*>(memory); 202 203 #if DEBUG 204 // Double check the API allocator actually zero-initialized the memory. 205 for (int i = 0; i < size; i++) { 206 DCHECK_EQ(0, (*backing_store)[i]); 207 } 208 #endif 209 210 Handle<JSArrayBuffer> buffer = isolate->factory()->NewJSArrayBuffer(); 211 JSArrayBuffer::Setup(buffer, isolate, false, memory, size); 212 buffer->set_is_neuterable(false); 213 return buffer; 214 } 215 } // namespace 216 217 218 WasmModule::WasmModule() 219 : globals(nullptr), 220 signatures(nullptr), 221 functions(nullptr), 222 data_segments(nullptr), 223 function_table(nullptr) {} 224 225 226 WasmModule::~WasmModule() { 227 if (globals) delete globals; 228 if (signatures) delete signatures; 229 if (functions) delete functions; 230 if (data_segments) delete data_segments; 231 if (function_table) delete function_table; 232 } 233 234 235 // Instantiates a wasm module as a JSObject. 236 // * allocates a backing store of {mem_size} bytes. 237 // * installs a named property "memory" for that buffer if exported 238 // * installs named properties on the object for exported functions 239 // * compiles wasm code to machine code 240 MaybeHandle<JSObject> WasmModule::Instantiate(Isolate* isolate, 241 Handle<JSObject> ffi, 242 Handle<JSArrayBuffer> memory) { 243 this->shared_isolate = isolate; // TODO(titzer): have a real shared isolate. 244 ErrorThrower thrower(isolate, "WasmModule::Instantiate()"); 245 246 Factory* factory = isolate->factory(); 247 // Memory is bigger than maximum supported size. 248 if (memory.is_null() && min_mem_size_log2 > kMaxMemSize) { 249 thrower.Error("Out of memory: wasm memory too large"); 250 return MaybeHandle<JSObject>(); 251 } 252 253 Handle<Map> map = factory->NewMap( 254 JS_OBJECT_TYPE, 255 JSObject::kHeaderSize + kWasmModuleInternalFieldCount * kPointerSize); 256 257 //------------------------------------------------------------------------- 258 // Allocate the module object. 259 //------------------------------------------------------------------------- 260 Handle<JSObject> module = factory->NewJSObjectFromMap(map, TENURED); 261 Handle<FixedArray> code_table = 262 factory->NewFixedArray(static_cast<int>(functions->size()), TENURED); 263 264 //------------------------------------------------------------------------- 265 // Allocate the linear memory. 266 //------------------------------------------------------------------------- 267 uint32_t mem_size = 1 << min_mem_size_log2; 268 byte* mem_addr = nullptr; 269 Handle<JSArrayBuffer> mem_buffer; 270 if (!memory.is_null()) { 271 memory->set_is_neuterable(false); 272 mem_addr = reinterpret_cast<byte*>(memory->backing_store()); 273 mem_size = memory->byte_length()->Number(); 274 mem_buffer = memory; 275 } else { 276 mem_buffer = NewArrayBuffer(isolate, mem_size, &mem_addr); 277 if (!mem_addr) { 278 // Not enough space for backing store of memory 279 thrower.Error("Out of memory: wasm memory"); 280 return MaybeHandle<JSObject>(); 281 } 282 } 283 284 // Load initialized data segments. 285 LoadDataSegments(this, mem_addr, mem_size); 286 287 module->SetInternalField(kWasmMemArrayBuffer, *mem_buffer); 288 289 if (mem_export) { 290 // Export the memory as a named property. 291 Handle<String> name = factory->InternalizeUtf8String("memory"); 292 JSObject::AddProperty(module, name, mem_buffer, READ_ONLY); 293 } 294 295 //------------------------------------------------------------------------- 296 // Allocate the globals area if necessary. 297 //------------------------------------------------------------------------- 298 size_t globals_size = AllocateGlobalsOffsets(globals); 299 byte* globals_addr = nullptr; 300 if (globals_size > 0) { 301 Handle<JSArrayBuffer> globals_buffer = 302 NewArrayBuffer(isolate, mem_size, &globals_addr); 303 if (!globals_addr) { 304 // Not enough space for backing store of globals. 305 thrower.Error("Out of memory: wasm globals"); 306 return MaybeHandle<JSObject>(); 307 } 308 309 module->SetInternalField(kWasmGlobalsArrayBuffer, *globals_buffer); 310 } else { 311 module->SetInternalField(kWasmGlobalsArrayBuffer, Smi::FromInt(0)); 312 } 313 314 //------------------------------------------------------------------------- 315 // Compile all functions in the module. 316 //------------------------------------------------------------------------- 317 int index = 0; 318 WasmLinker linker(isolate, functions->size()); 319 ModuleEnv module_env; 320 module_env.module = this; 321 module_env.mem_start = reinterpret_cast<uintptr_t>(mem_addr); 322 module_env.mem_end = reinterpret_cast<uintptr_t>(mem_addr) + mem_size; 323 module_env.globals_area = reinterpret_cast<uintptr_t>(globals_addr); 324 module_env.linker = &linker; 325 module_env.function_code = nullptr; 326 module_env.function_table = BuildFunctionTable(isolate, this); 327 module_env.memory = memory; 328 module_env.context = isolate->native_context(); 329 module_env.asm_js = false; 330 331 // First pass: compile each function and initialize the code table. 332 for (const WasmFunction& func : *functions) { 333 if (thrower.error()) break; 334 335 const char* cstr = GetName(func.name_offset); 336 Handle<String> name = factory->InternalizeUtf8String(cstr); 337 Handle<Code> code = Handle<Code>::null(); 338 Handle<JSFunction> function = Handle<JSFunction>::null(); 339 if (func.external) { 340 // Lookup external function in FFI object. 341 if (!ffi.is_null()) { 342 MaybeHandle<Object> result = Object::GetProperty(ffi, name); 343 if (!result.is_null()) { 344 Handle<Object> obj = result.ToHandleChecked(); 345 if (obj->IsJSFunction()) { 346 function = Handle<JSFunction>::cast(obj); 347 code = compiler::CompileWasmToJSWrapper(isolate, &module_env, 348 function, index); 349 } else { 350 thrower.Error("FFI function #%d:%s is not a JSFunction.", index, 351 cstr); 352 return MaybeHandle<JSObject>(); 353 } 354 } else { 355 thrower.Error("FFI function #%d:%s not found.", index, cstr); 356 return MaybeHandle<JSObject>(); 357 } 358 } else { 359 thrower.Error("FFI table is not an object."); 360 return MaybeHandle<JSObject>(); 361 } 362 } else { 363 // Compile the function. 364 code = compiler::CompileWasmFunction(thrower, isolate, &module_env, func, 365 index); 366 if (code.is_null()) { 367 thrower.Error("Compilation of #%d:%s failed.", index, cstr); 368 return MaybeHandle<JSObject>(); 369 } 370 if (func.exported) { 371 function = compiler::CompileJSToWasmWrapper(isolate, &module_env, name, 372 code, module, index); 373 } 374 } 375 if (!code.is_null()) { 376 // Install the code into the linker table. 377 linker.Finish(index, code); 378 code_table->set(index, *code); 379 } 380 if (func.exported) { 381 // Exported functions are installed as read-only properties on the module. 382 JSObject::AddProperty(module, name, function, READ_ONLY); 383 } 384 index++; 385 } 386 387 // Second pass: patch all direct call sites. 388 linker.Link(module_env.function_table, this->function_table); 389 390 module->SetInternalField(kWasmModuleFunctionTable, Smi::FromInt(0)); 391 module->SetInternalField(kWasmModuleCodeTable, *code_table); 392 return module; 393 } 394 395 396 Handle<Code> ModuleEnv::GetFunctionCode(uint32_t index) { 397 DCHECK(IsValidFunction(index)); 398 if (linker) return linker->GetFunctionCode(index); 399 if (function_code) return function_code->at(index); 400 return Handle<Code>::null(); 401 } 402 403 404 compiler::CallDescriptor* ModuleEnv::GetCallDescriptor(Zone* zone, 405 uint32_t index) { 406 DCHECK(IsValidFunction(index)); 407 // Always make a direct call to whatever is in the table at that location. 408 // A wrapper will be generated for FFI calls. 409 WasmFunction* function = &module->functions->at(index); 410 return GetWasmCallDescriptor(zone, function->sig); 411 } 412 413 414 int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start, 415 const byte* module_end, bool asm_js) { 416 HandleScope scope(isolate); 417 Zone zone; 418 // Decode the module, but don't verify function bodies, since we'll 419 // be compiling them anyway. 420 ModuleResult result = 421 DecodeWasmModule(isolate, &zone, module_start, module_end, false, false); 422 if (result.failed()) { 423 // Module verification failed. throw. 424 std::ostringstream str; 425 str << "WASM.compileRun() failed: " << result; 426 isolate->Throw( 427 *isolate->factory()->NewStringFromAsciiChecked(str.str().c_str())); 428 return -1; 429 } 430 431 int32_t retval = CompileAndRunWasmModule(isolate, result.val); 432 delete result.val; 433 return retval; 434 } 435 436 437 int32_t CompileAndRunWasmModule(Isolate* isolate, WasmModule* module) { 438 ErrorThrower thrower(isolate, "CompileAndRunWasmModule"); 439 440 // Allocate temporary linear memory and globals. 441 size_t mem_size = 1 << module->min_mem_size_log2; 442 size_t globals_size = AllocateGlobalsOffsets(module->globals); 443 444 base::SmartArrayPointer<byte> mem_addr(new byte[mem_size]); 445 base::SmartArrayPointer<byte> globals_addr(new byte[globals_size]); 446 447 memset(mem_addr.get(), 0, mem_size); 448 memset(globals_addr.get(), 0, globals_size); 449 450 // Create module environment. 451 WasmLinker linker(isolate, module->functions->size()); 452 ModuleEnv module_env; 453 module_env.module = module; 454 module_env.mem_start = reinterpret_cast<uintptr_t>(mem_addr.get()); 455 module_env.mem_end = reinterpret_cast<uintptr_t>(mem_addr.get()) + mem_size; 456 module_env.globals_area = reinterpret_cast<uintptr_t>(globals_addr.get()); 457 module_env.linker = &linker; 458 module_env.function_code = nullptr; 459 module_env.function_table = BuildFunctionTable(isolate, module); 460 module_env.asm_js = false; 461 462 // Load data segments. 463 // TODO(titzer): throw instead of crashing if segments don't fit in memory? 464 LoadDataSegments(module, mem_addr.get(), mem_size); 465 466 // Compile all functions. 467 Handle<Code> main_code = Handle<Code>::null(); // record last code. 468 int index = 0; 469 for (const WasmFunction& func : *module->functions) { 470 if (!func.external) { 471 // Compile the function and install it in the code table. 472 Handle<Code> code = compiler::CompileWasmFunction( 473 thrower, isolate, &module_env, func, index); 474 if (!code.is_null()) { 475 if (func.exported) main_code = code; 476 linker.Finish(index, code); 477 } 478 if (thrower.error()) return -1; 479 } 480 index++; 481 } 482 483 if (!main_code.is_null()) { 484 linker.Link(module_env.function_table, module->function_table); 485 #if USE_SIMULATOR && V8_TARGET_ARCH_ARM64 486 // Run the main code on arm64 simulator. 487 Simulator* simulator = Simulator::current(isolate); 488 Simulator::CallArgument args[] = {Simulator::CallArgument(0), 489 Simulator::CallArgument::End()}; 490 return static_cast<int32_t>(simulator->CallInt64(main_code->entry(), args)); 491 #elif USE_SIMULATOR 492 // Run the main code on simulator. 493 Simulator* simulator = Simulator::current(isolate); 494 return static_cast<int32_t>( 495 simulator->Call(main_code->entry(), 4, 0, 0, 0, 0)); 496 #else 497 // Run the main code as raw machine code. 498 int32_t (*raw_func)() = reinterpret_cast<int32_t (*)()>( 499 reinterpret_cast<uintptr_t>(main_code->entry())); 500 return raw_func(); 501 #endif 502 } else { 503 // No main code was found. 504 isolate->Throw(*isolate->factory()->NewStringFromStaticChars( 505 "WASM.compileRun() failed: no valid main code produced.")); 506 } 507 return -1; 508 } 509 } // namespace wasm 510 } // namespace internal 511 } // namespace v8 512