WasmModule.cpp (35666B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * 4 * Copyright 2015 Mozilla Foundation 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 #include "wasm/WasmModule.h" 20 21 #include <chrono> 22 23 #include "js/BuildId.h" // JS::BuildIdCharVector 24 #include "js/experimental/TypedData.h" // JS_NewUint8Array 25 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 26 #include "js/Printf.h" // JS_smprintf 27 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById 28 #include "js/StreamConsumer.h" 29 #include "threading/LockGuard.h" 30 #include "threading/Thread.h" 31 #include "util/DifferentialTesting.h" 32 #include "vm/HelperThreadState.h" // Tier2GeneratorTask 33 #include "vm/PlainObject.h" // js::PlainObject 34 #include "vm/Warnings.h" // WarnNumberASCII 35 #include "wasm/WasmBaselineCompile.h" 36 #include "wasm/WasmCompile.h" 37 #include "wasm/WasmDebug.h" 38 #include "wasm/WasmInstance.h" 39 #include "wasm/WasmIonCompile.h" 40 #include "wasm/WasmJS.h" 41 #include "wasm/WasmModuleTypes.h" 42 #include "wasm/WasmPI.h" 43 #include "wasm/WasmSerialize.h" 44 #include "wasm/WasmUtility.h" 45 46 #include "debugger/DebugAPI-inl.h" 47 #include "vm/ArrayBufferObject-inl.h" 48 #include "vm/JSAtomUtils-inl.h" // AtomToId 49 #include "wasm/WasmInstance-inl.h" 50 51 using namespace js; 52 using namespace js::jit; 53 using namespace js::wasm; 54 55 static UniqueChars Tier2ResultsContext(const ScriptedCaller& scriptedCaller) { 56 return scriptedCaller.filename 57 ? JS_smprintf("%s:%d", scriptedCaller.filename.get(), 58 scriptedCaller.line) 59 : UniqueChars(); 60 } 61 62 void js::wasm::ReportTier2ResultsOffThread(bool cancelled, bool success, 63 Maybe<uint32_t> maybeFuncIndex, 64 const ScriptedCaller& scriptedCaller, 65 const UniqueChars& error, 66 const UniqueCharsVector& warnings) { 67 // Get context to describe this tier-2 task. 68 UniqueChars context = Tier2ResultsContext(scriptedCaller); 69 const char* contextString = context ? context.get() : "unknown"; 70 71 // Display the main error, if any. 72 if (!success || cancelled) { 73 const char* errorString = error ? error.get() 74 : cancelled ? "compilation cancelled" 75 : "out of memory"; 76 if (maybeFuncIndex.isSome()) { 77 LogOffThread( 78 "'%s': wasm partial tier-2 (func index %u) failed with '%s'.\n", 79 contextString, maybeFuncIndex.value(), errorString); 80 } else { 81 LogOffThread("'%s': wasm complete tier-2 failed with '%s'.\n", 82 contextString, errorString); 83 } 84 } 85 86 // Display warnings as a follow-up, avoiding spamming the console. 87 size_t numWarnings = std::min<size_t>(warnings.length(), 3); 88 89 for (size_t i = 0; i < numWarnings; i++) { 90 // Per the assertion above, we won't get warnings for partial tier-2. 91 LogOffThread("'%s': wasm complete tier-2 warning: '%s'.\n'.", contextString, 92 warnings[i].get()); 93 } 94 if (warnings.length() > numWarnings) { 95 LogOffThread("'%s': other warnings suppressed.\n", contextString); 96 } 97 } 98 99 class Module::CompleteTier2GeneratorTaskImpl 100 : public CompleteTier2GeneratorTask { 101 SharedBytes codeSection_; 102 SharedModule module_; 103 mozilla::Atomic<bool> cancelled_; 104 105 public: 106 CompleteTier2GeneratorTaskImpl(const ShareableBytes* codeSection, 107 Module& module) 108 : codeSection_(codeSection), module_(&module), cancelled_(false) {} 109 110 ~CompleteTier2GeneratorTaskImpl() override { 111 module_->completeTier2Listener_ = nullptr; 112 module_->testingTier2Active_ = false; 113 } 114 115 void cancel() override { cancelled_ = true; } 116 117 void runHelperThreadTask(AutoLockHelperThreadState& locked) override { 118 { 119 AutoUnlockHelperThreadState unlock(locked); 120 121 // Compile complete tier-2 and report any warning/errors as long as it's 122 // not a cancellation. Encountering a warning/error during compilation 123 // and being cancelled may race with each other, but the only observable 124 // race should be being cancelled after a warning/error is set, and 125 // that's okay. 126 UniqueChars error; 127 UniqueCharsVector warnings; 128 bool success = CompileCompleteTier2(codeSection_, *module_, &error, 129 &warnings, &cancelled_); 130 if (!cancelled_) { 131 // We could try to dispatch a runnable to the thread that started this 132 // compilation, so as to report the warning/error using a JSContext*. 133 // For now we just report to stderr. 134 ReportTier2ResultsOffThread(cancelled_, success, mozilla::Nothing(), 135 module_->codeMeta().scriptedCaller(), error, 136 warnings); 137 } 138 } 139 140 // During shutdown the main thread will wait for any ongoing (cancelled) 141 // complete tier-2 generation to shut down normally. To do so, it waits on 142 // the HelperThreadState's condition variable for the count of finished 143 // generators to rise. 144 HelperThreadState().incWasmCompleteTier2GeneratorsFinished(locked); 145 146 // The task is finished, release it. 147 js_delete(this); 148 } 149 150 ThreadType threadType() override { 151 return ThreadType::THREAD_TYPE_WASM_GENERATOR_COMPLETE_TIER2; 152 } 153 }; 154 155 Module::~Module() { 156 // Note: Modules can be destroyed on any thread. 157 MOZ_ASSERT(!completeTier2Listener_); 158 MOZ_ASSERT(!testingTier2Active_); 159 } 160 161 void Module::startTier2(const ShareableBytes* codeSection, 162 JS::OptimizedEncodingListener* listener) { 163 MOZ_ASSERT(!testingTier2Active_); 164 MOZ_ASSERT_IF(codeMeta().codeSectionRange.isSome(), codeSection); 165 166 auto task = MakeUnique<CompleteTier2GeneratorTaskImpl>(codeSection, *this); 167 if (!task) { 168 return; 169 } 170 171 // These will be cleared asynchronously by ~CompleteTier2GeneratorTaskImpl() 172 // if not sooner by finishTier2(). 173 completeTier2Listener_ = listener; 174 testingTier2Active_ = true; 175 176 StartOffThreadWasmCompleteTier2Generator(std::move(task)); 177 } 178 179 bool Module::finishTier2(UniqueCodeBlock tier2CodeBlock, 180 UniqueLinkData tier2LinkData, 181 const CompileAndLinkStats& tier2Stats) const { 182 if (!code_->finishTier2(std::move(tier2CodeBlock), std::move(tier2LinkData), 183 tier2Stats)) { 184 return false; 185 } 186 187 // Tier-2 is done; let everyone know. Mark tier-2 active for testing 188 // purposes so that wasmHasTier2CompilationCompleted() only returns true 189 // after tier-2 has been fully cached. 190 191 if (completeTier2Listener_ && canSerialize()) { 192 Bytes bytes; 193 if (serialize(&bytes)) { 194 completeTier2Listener_->storeOptimizedEncoding(bytes.begin(), 195 bytes.length()); 196 } 197 completeTier2Listener_ = nullptr; 198 } 199 testingTier2Active_ = false; 200 201 return true; 202 } 203 204 void Module::testingBlockOnTier2Complete() const { 205 while (testingTier2Active_) { 206 ThisThread::SleepMilliseconds(1); 207 } 208 } 209 210 /* virtual */ 211 JSObject* Module::createObject(JSContext* cx) const { 212 if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly)) { 213 return nullptr; 214 } 215 216 JS::RootedVector<JSString*> parameterStrings(cx); 217 JS::RootedVector<Value> parameterArgs(cx); 218 bool canCompileStrings = false; 219 if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr, 220 JS::CompilationType::Undefined, 221 parameterStrings, nullptr, parameterArgs, 222 NullHandleValue, &canCompileStrings)) { 223 return nullptr; 224 } 225 if (!canCompileStrings) { 226 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 227 JSMSG_CSP_BLOCKED_WASM, "WebAssembly.Module"); 228 return nullptr; 229 } 230 231 RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule)); 232 return WasmModuleObject::create(cx, *this, proto); 233 } 234 235 /* virtual */ 236 JSObject* Module::createObjectForAsmJS(JSContext* cx) const { 237 // Use nullptr to get the default object prototype. These objects are never 238 // exposed to script for asm.js. 239 return WasmModuleObject::create(cx, *this, nullptr); 240 } 241 242 bool wasm::GetOptimizedEncodingBuildId(JS::BuildIdCharVector* buildId) { 243 // From a JS API perspective, the "build id" covers everything that can 244 // cause machine code to become invalid, so include both the actual build-id 245 // and cpu-id. 246 247 if (!GetBuildId || !GetBuildId(buildId)) { 248 return false; 249 } 250 251 uint32_t cpu = ObservedCPUFeatures(); 252 253 if (!buildId->reserve(buildId->length() + 254 13 /* "()" + 8 nibbles + "m[+-][+-]" */)) { 255 return false; 256 } 257 258 buildId->infallibleAppend('('); 259 while (cpu) { 260 buildId->infallibleAppend('0' + (cpu & 0xf)); 261 cpu >>= 4; 262 } 263 buildId->infallibleAppend(')'); 264 265 buildId->infallibleAppend('m'); 266 buildId->infallibleAppend( 267 wasm::IsHugeMemoryEnabled(AddressType::I32, PageSize::Standard) ? '+' 268 : '-'); 269 buildId->infallibleAppend( 270 wasm::IsHugeMemoryEnabled(AddressType::I64, PageSize::Standard) ? '+' 271 : '-'); 272 273 // We don't expect huge memory to be supported if custom page sizes are used. 274 #ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES 275 MOZ_RELEASE_ASSERT( 276 !wasm::IsHugeMemoryEnabled(AddressType::I32, PageSize::Tiny)); 277 MOZ_RELEASE_ASSERT( 278 !wasm::IsHugeMemoryEnabled(AddressType::I64, PageSize::Tiny)); 279 #endif 280 281 return true; 282 } 283 284 /* virtual */ 285 void Module::addSizeOfMisc(mozilla::MallocSizeOf mallocSizeOf, 286 CodeMetadata::SeenSet* seenCodeMeta, 287 CodeMetadataForAsmJS::SeenSet* seenCodeMetaForAsmJS, 288 Code::SeenSet* seenCode, size_t* code, 289 size_t* data) const { 290 code_->addSizeOfMiscIfNotSeen(mallocSizeOf, seenCodeMeta, 291 seenCodeMetaForAsmJS, seenCode, code, data); 292 *data += mallocSizeOf(this); 293 } 294 295 // Extracting machine code as JS object. The result has the "code" property, as 296 // a Uint8Array, and the "segments" property as array objects. The objects 297 // contain offsets in the "code" array and basic information about a code 298 // segment/function body. 299 bool Module::extractCode(JSContext* cx, Tier tier, 300 MutableHandleValue vp) const { 301 Rooted<PlainObject*> result(cx, NewPlainObject(cx)); 302 if (!result) { 303 return false; 304 } 305 306 // This function is only used for testing purposes so we can simply 307 // block on tiered compilation to complete. 308 testingBlockOnTier2Complete(); 309 310 if (!code_->hasCompleteTier(tier)) { 311 vp.setNull(); 312 return true; 313 } 314 315 const CodeBlock& codeBlock = code_->completeTierCodeBlock(tier); 316 const CodeSegment& codeSegment = *codeBlock.segment; 317 RootedObject codeObj(cx, JS_NewUint8Array(cx, codeSegment.lengthBytes())); 318 if (!codeObj) { 319 return false; 320 } 321 322 memcpy(codeObj->as<TypedArrayObject>().dataPointerUnshared(), 323 codeSegment.base(), codeSegment.lengthBytes()); 324 325 RootedValue value(cx, ObjectValue(*codeObj)); 326 if (!JS_DefineProperty(cx, result, "code", value, JSPROP_ENUMERATE)) { 327 return false; 328 } 329 330 RootedObject segments(cx, NewDenseEmptyArray(cx)); 331 if (!segments) { 332 return false; 333 } 334 335 for (const CodeRange& p : codeBlock.codeRanges) { 336 RootedObject segment(cx, NewPlainObjectWithProto(cx, nullptr)); 337 if (!segment) { 338 return false; 339 } 340 341 value.setNumber((uint32_t)p.begin()); 342 if (!JS_DefineProperty(cx, segment, "begin", value, JSPROP_ENUMERATE)) { 343 return false; 344 } 345 346 value.setNumber((uint32_t)p.end()); 347 if (!JS_DefineProperty(cx, segment, "end", value, JSPROP_ENUMERATE)) { 348 return false; 349 } 350 351 value.setNumber((uint32_t)p.kind()); 352 if (!JS_DefineProperty(cx, segment, "kind", value, JSPROP_ENUMERATE)) { 353 return false; 354 } 355 356 if (p.isFunction()) { 357 value.setNumber((uint32_t)p.funcIndex()); 358 if (!JS_DefineProperty(cx, segment, "funcIndex", value, 359 JSPROP_ENUMERATE)) { 360 return false; 361 } 362 363 value.setNumber((uint32_t)p.funcUncheckedCallEntry()); 364 if (!JS_DefineProperty(cx, segment, "funcBodyBegin", value, 365 JSPROP_ENUMERATE)) { 366 return false; 367 } 368 369 value.setNumber((uint32_t)p.end()); 370 if (!JS_DefineProperty(cx, segment, "funcBodyEnd", value, 371 JSPROP_ENUMERATE)) { 372 return false; 373 } 374 } 375 376 if (!NewbornArrayPush(cx, segments, ObjectValue(*segment))) { 377 return false; 378 } 379 } 380 381 value.setObject(*segments); 382 if (!JS_DefineProperty(cx, result, "segments", value, JSPROP_ENUMERATE)) { 383 return false; 384 } 385 386 vp.setObject(*result); 387 return true; 388 } 389 390 static const Import& FindImportFunction(const ImportVector& imports, 391 uint32_t funcImportIndex) { 392 for (const Import& import : imports) { 393 if (import.kind != DefinitionKind::Function) { 394 continue; 395 } 396 if (funcImportIndex == 0) { 397 return import; 398 } 399 funcImportIndex--; 400 } 401 MOZ_CRASH("ran out of imports"); 402 } 403 404 bool Module::instantiateFunctions(JSContext* cx, 405 const JSObjectVector& funcImports) const { 406 #ifdef DEBUG 407 MOZ_ASSERT(funcImports.length() == code().funcImports().length()); 408 #endif 409 410 if (codeMeta().isAsmJS()) { 411 return true; 412 } 413 414 for (size_t i = 0; i < code().funcImports().length(); i++) { 415 if (!funcImports[i]->is<JSFunction>()) { 416 continue; 417 } 418 419 JSFunction* f = &funcImports[i]->as<JSFunction>(); 420 if (!f->isWasm() || codeMeta().funcImportsAreJS) { 421 continue; 422 } 423 424 const TypeDef& exportFuncType = *f->wasmTypeDef(); 425 const TypeDef& importFuncType = code().codeMeta().getFuncTypeDef(i); 426 427 if (!TypeDef::isSubTypeOf(&exportFuncType, &importFuncType)) { 428 const Import& import = FindImportFunction(moduleMeta().imports, i); 429 UniqueChars importModuleName = import.module.toQuotedString(cx); 430 UniqueChars importFieldName = import.field.toQuotedString(cx); 431 if (!importFieldName || !importModuleName) { 432 ReportOutOfMemory(cx); 433 return false; 434 } 435 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 436 JSMSG_WASM_BAD_IMPORT_SIG, 437 importModuleName.get(), importFieldName.get()); 438 return false; 439 } 440 } 441 442 return true; 443 } 444 445 template <typename T> 446 static bool CheckLimits(JSContext* cx, T declaredMin, 447 const mozilla::Maybe<T>& declaredMax, T defaultMax, 448 T actualLength, const mozilla::Maybe<T>& actualMax, 449 bool isAsmJS, const char* kind) { 450 if (isAsmJS) { 451 MOZ_ASSERT(actualLength >= declaredMin); 452 MOZ_ASSERT(!declaredMax); 453 MOZ_ASSERT(actualLength == actualMax.value()); 454 return true; 455 } 456 457 if (actualLength < declaredMin || 458 actualLength > declaredMax.valueOr(defaultMax)) { 459 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 460 JSMSG_WASM_BAD_IMP_SIZE, kind); 461 return false; 462 } 463 464 if ((actualMax && declaredMax && *actualMax > *declaredMax) || 465 (!actualMax && declaredMax)) { 466 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 467 JSMSG_WASM_BAD_IMP_MAX, kind); 468 return false; 469 } 470 471 return true; 472 } 473 474 static bool CheckSharing(JSContext* cx, bool declaredShared, bool isShared) { 475 if (isShared && 476 !cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) { 477 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 478 JSMSG_WASM_NO_SHMEM_LINK); 479 return false; 480 } 481 482 if (declaredShared && !isShared) { 483 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 484 JSMSG_WASM_IMP_SHARED_REQD); 485 return false; 486 } 487 488 if (!declaredShared && isShared) { 489 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 490 JSMSG_WASM_IMP_SHARED_BANNED); 491 return false; 492 } 493 494 return true; 495 } 496 497 #ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES 498 static bool CheckPageSize(JSContext* cx, PageSize declaredPageSize, 499 PageSize actualPageSize) { 500 if (declaredPageSize != actualPageSize) { 501 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 502 JSMSG_WASM_BAD_IMP_PAGE_SIZE); 503 return false; 504 } 505 506 return true; 507 } 508 #endif 509 510 // asm.js module instantiation supplies its own buffer, but for wasm, create and 511 // initialize the buffer if one is requested. Either way, the buffer is wrapped 512 // in a WebAssembly.Memory object which is what the Instance stores. 513 bool Module::instantiateMemories( 514 JSContext* cx, const WasmMemoryObjectVector& memoryImports, 515 MutableHandle<WasmMemoryObjectVector> memoryObjs) const { 516 for (uint32_t memoryIndex = 0; memoryIndex < codeMeta().memories.length(); 517 memoryIndex++) { 518 const MemoryDesc& desc = codeMeta().memories[memoryIndex]; 519 520 Rooted<WasmMemoryObject*> memory(cx); 521 if (memoryIndex < memoryImports.length()) { 522 memory = memoryImports[memoryIndex]; 523 MOZ_ASSERT_IF(codeMeta().isAsmJS(), 524 memory->buffer().isPreparedForAsmJS()); 525 MOZ_ASSERT_IF(!codeMeta().isAsmJS(), memory->buffer().isWasm()); 526 527 if (memory->addressType() != desc.addressType()) { 528 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 529 JSMSG_WASM_BAD_IMP_ADDRESS, "memory", 530 ToString(memory->addressType())); 531 return false; 532 } 533 534 #ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES 535 // Page size needs to be checked first because comparisons between 536 // incompatible page sizes are invalid in CheckLimits. 537 if (!CheckPageSize(cx, desc.pageSize(), memory->pageSize())) { 538 return false; 539 } 540 #endif 541 542 if (!CheckLimits(cx, desc.initialPages(), desc.maximumPages(), 543 /* defaultMax */ 544 MaxMemoryPages(desc.addressType(), desc.pageSize()), 545 /* actualLength */ 546 memory->volatilePages(), memory->sourceMaxPages(), 547 codeMeta().isAsmJS(), "Memory")) { 548 return false; 549 } 550 551 if (!CheckSharing(cx, desc.isShared(), memory->isShared())) { 552 return false; 553 } 554 } else { 555 MOZ_ASSERT(!codeMeta().isAsmJS()); 556 557 if (desc.initialPages() > 558 MaxMemoryPages(desc.addressType(), desc.pageSize())) { 559 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 560 JSMSG_WASM_MEM_IMP_LIMIT); 561 return false; 562 } 563 564 Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, 565 CreateWasmBuffer(cx, desc)); 566 if (!buffer) { 567 return false; 568 } 569 570 RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmMemory)); 571 memory = WasmMemoryObject::create( 572 cx, buffer, IsHugeMemoryEnabled(desc.addressType(), desc.pageSize()), 573 proto); 574 if (!memory) { 575 return false; 576 } 577 } 578 579 MOZ_RELEASE_ASSERT( 580 codeMeta().isAsmJS() || 581 memory->isHuge() == 582 IsHugeMemoryEnabled(desc.addressType(), desc.pageSize())); 583 584 if (!memoryObjs.get().append(memory)) { 585 ReportOutOfMemory(cx); 586 return false; 587 } 588 } 589 return true; 590 } 591 592 bool Module::instantiateTags(JSContext* cx, 593 WasmTagObjectVector& tagObjs) const { 594 size_t tagLength = codeMeta().tags.length(); 595 if (tagLength == 0) { 596 return true; 597 } 598 size_t importedTagsLength = tagObjs.length(); 599 if (tagObjs.length() <= tagLength && !tagObjs.resize(tagLength)) { 600 ReportOutOfMemory(cx); 601 return false; 602 } 603 604 uint32_t tagIndex = 0; 605 RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTag)); 606 for (const TagDesc& desc : codeMeta().tags) { 607 if (tagIndex >= importedTagsLength) { 608 Rooted<WasmTagObject*> tagObj( 609 cx, WasmTagObject::create(cx, desc.type, proto)); 610 if (!tagObj) { 611 return false; 612 } 613 tagObjs[tagIndex] = tagObj; 614 } 615 tagIndex++; 616 } 617 return true; 618 } 619 620 bool Module::instantiateImportedTable(JSContext* cx, const TableDesc& td, 621 Handle<WasmTableObject*> tableObj, 622 WasmTableObjectVector* tableObjs, 623 SharedTableVector* tables) const { 624 MOZ_ASSERT(tableObj); 625 MOZ_ASSERT(!codeMeta().isAsmJS()); 626 627 Table& table = tableObj->table(); 628 if (table.addressType() != td.addressType()) { 629 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 630 JSMSG_WASM_BAD_IMP_ADDRESS, "table", 631 ToString(tableObj->table().addressType())); 632 return false; 633 } 634 if (!CheckLimits(cx, /*declaredMin=*/td.initialLength(), 635 /*declaredMax=*/td.maximumLength(), 636 /*defaultMax=*/MaxTableElemsValidation(td.addressType()), 637 /*actualLength=*/uint64_t(table.length()), 638 /*actualMax=*/table.maximum(), codeMeta().isAsmJS(), 639 "Table")) { 640 return false; 641 } 642 643 if (!tables->append(&table)) { 644 ReportOutOfMemory(cx); 645 return false; 646 } 647 648 if (!tableObjs->append(tableObj)) { 649 ReportOutOfMemory(cx); 650 return false; 651 } 652 653 return true; 654 } 655 656 bool Module::instantiateLocalTable(JSContext* cx, const TableDesc& td, 657 WasmTableObjectVector* tableObjs, 658 SharedTableVector* tables) const { 659 if (td.initialLength() > MaxTableElemsRuntime) { 660 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 661 JSMSG_WASM_TABLE_IMP_LIMIT); 662 return false; 663 } 664 665 SharedTable table; 666 Rooted<WasmTableObject*> tableObj(cx); 667 if (td.isExported) { 668 RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTable)); 669 tableObj.set(WasmTableObject::create(cx, td.limits, td.elemType, proto)); 670 if (!tableObj) { 671 return false; 672 } 673 table = &tableObj->table(); 674 } else { 675 table = Table::create(cx, td, /* Handle<WasmTableObject*> = */ nullptr); 676 if (!table) { 677 return false; 678 } 679 } 680 681 // Note, appending a null pointer for non-exported local tables. 682 if (!tableObjs->append(tableObj.get())) { 683 ReportOutOfMemory(cx); 684 return false; 685 } 686 687 if (!tables->emplaceBack(table)) { 688 ReportOutOfMemory(cx); 689 return false; 690 } 691 692 return true; 693 } 694 695 bool Module::instantiateTables(JSContext* cx, 696 const WasmTableObjectVector& tableImports, 697 MutableHandle<WasmTableObjectVector> tableObjs, 698 SharedTableVector* tables) const { 699 uint32_t tableIndex = 0; 700 for (const TableDesc& td : codeMeta().tables) { 701 if (tableIndex < tableImports.length()) { 702 Rooted<WasmTableObject*> tableObj(cx, tableImports[tableIndex]); 703 704 if (!instantiateImportedTable(cx, td, tableObj, &tableObjs.get(), 705 tables)) { 706 return false; 707 } 708 } else { 709 if (!instantiateLocalTable(cx, td, &tableObjs.get(), tables)) { 710 return false; 711 } 712 } 713 tableIndex++; 714 } 715 return true; 716 } 717 718 static bool EnsureExportedGlobalObject(JSContext* cx, 719 const ValVector& globalImportValues, 720 size_t globalIndex, 721 const GlobalDesc& global, 722 WasmGlobalObjectVector& globalObjs) { 723 if (globalIndex < globalObjs.length() && globalObjs[globalIndex]) { 724 return true; 725 } 726 727 RootedVal val(cx); 728 if (global.kind() == GlobalKind::Import) { 729 // If this is an import, then this must be a constant global that was 730 // provided without a global object. We must initialize it with the 731 // provided value while we still can differentiate this case. 732 MOZ_ASSERT(!global.isMutable()); 733 val.set(Val(globalImportValues[globalIndex])); 734 } else { 735 // If this is not an import, then the initial value will be set by 736 // Instance::init() for indirect globals or else by CreateExportObject(). 737 // In either case, we initialize with a default value here. 738 val.set(Val(global.type())); 739 } 740 741 RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmGlobal)); 742 Rooted<WasmGlobalObject*> go( 743 cx, WasmGlobalObject::create(cx, val, global.isMutable(), proto)); 744 if (!go) { 745 return false; 746 } 747 748 if (globalObjs.length() <= globalIndex && 749 !globalObjs.resize(globalIndex + 1)) { 750 ReportOutOfMemory(cx); 751 return false; 752 } 753 754 globalObjs[globalIndex] = go; 755 return true; 756 } 757 758 bool Module::instantiateGlobals(JSContext* cx, 759 const ValVector& globalImportValues, 760 WasmGlobalObjectVector& globalObjs) const { 761 // If there are exported globals that aren't in globalObjs because they 762 // originate in this module or because they were immutable imports that came 763 // in as primitive values then we must create cells in the globalObjs for 764 // them here, as WasmInstanceObject::create() and CreateExportObject() will 765 // need the cells to exist. 766 767 const GlobalDescVector& globals = codeMeta().globals; 768 769 for (const Export& exp : moduleMeta().exports) { 770 if (exp.kind() != DefinitionKind::Global) { 771 continue; 772 } 773 unsigned globalIndex = exp.globalIndex(); 774 const GlobalDesc& global = globals[globalIndex]; 775 if (!EnsureExportedGlobalObject(cx, globalImportValues, globalIndex, global, 776 globalObjs)) { 777 return false; 778 } 779 } 780 781 // Imported globals that are not re-exported may also have received only a 782 // primitive value; these globals are always immutable. Assert that we do 783 // not need to create any additional Global objects for such imports. 784 785 #ifdef DEBUG 786 size_t numGlobalImports = 0; 787 for (const Import& import : moduleMeta().imports) { 788 if (import.kind != DefinitionKind::Global) { 789 continue; 790 } 791 size_t globalIndex = numGlobalImports++; 792 const GlobalDesc& global = globals[globalIndex]; 793 MOZ_ASSERT(global.importIndex() == globalIndex); 794 MOZ_ASSERT_IF(global.isIndirect(), 795 globalIndex < globalObjs.length() || globalObjs[globalIndex]); 796 } 797 MOZ_ASSERT_IF(!codeMeta().isAsmJS(), 798 numGlobalImports == globals.length() || 799 !globals[numGlobalImports].isImport()); 800 #endif 801 return true; 802 } 803 804 static bool GetGlobalExport(JSContext* cx, 805 Handle<WasmInstanceObject*> instanceObj, 806 const JSObjectVector& funcImports, 807 const GlobalDesc& global, uint32_t globalIndex, 808 const ValVector& globalImportValues, 809 const WasmGlobalObjectVector& globalObjs, 810 MutableHandleValue val) { 811 // A global object for this index is guaranteed to exist by 812 // instantiateGlobals. 813 Rooted<WasmGlobalObject*> globalObj(cx, globalObjs[globalIndex]); 814 val.setObject(*globalObj); 815 816 // We are responsible to set the initial value of the global object here if 817 // it's not imported or indirect. Imported global objects have their initial 818 // value set by their defining module, or are set by 819 // EnsureExportedGlobalObject when a constant value is provided as an import. 820 // Indirect exported globals that are not imported, are initialized in 821 // Instance::init. 822 if (global.isIndirect() || global.isImport()) { 823 return true; 824 } 825 826 // This must be an exported immutable global defined in this module. The 827 // instance either has compiled the value into the code or has its own copy 828 // in its global data area. Either way, we must initialize the global object 829 // with the same initial value. 830 MOZ_ASSERT(!global.isMutable()); 831 MOZ_RELEASE_ASSERT(!global.isImport()); 832 RootedVal globalVal(cx); 833 instanceObj->instance().constantGlobalGet(globalIndex, &globalVal); 834 globalObj->setVal(globalVal); 835 return true; 836 } 837 838 static bool CreateExportObject( 839 JSContext* cx, Handle<WasmInstanceObject*> instanceObj, 840 const JSObjectVector& funcImports, const WasmTableObjectVector& tableObjs, 841 const WasmMemoryObjectVector& memoryObjs, 842 const WasmTagObjectVector& tagObjs, const ValVector& globalImportValues, 843 const WasmGlobalObjectVector& globalObjs, const ExportVector& exports) { 844 Instance& instance = instanceObj->instance(); 845 const CodeMetadata& codeMeta = instance.codeMeta(); 846 const GlobalDescVector& globals = codeMeta.globals; 847 848 if (codeMeta.isAsmJS() && exports.length() == 1 && 849 exports[0].fieldName().isEmpty()) { 850 RootedFunction func(cx); 851 if (!instance.getExportedFunction(cx, exports[0].funcIndex(), &func)) { 852 return false; 853 } 854 instanceObj->initExportsObj(*func.get()); 855 return true; 856 } 857 858 RootedObject exportObj(cx); 859 uint8_t propertyAttr = JSPROP_ENUMERATE; 860 861 if (codeMeta.isAsmJS()) { 862 exportObj = NewPlainObject(cx); 863 } else { 864 exportObj = NewPlainObjectWithProto(cx, nullptr); 865 propertyAttr |= JSPROP_READONLY | JSPROP_PERMANENT; 866 } 867 if (!exportObj) { 868 return false; 869 } 870 871 for (const Export& exp : exports) { 872 JSAtom* atom = exp.fieldName().toAtom(cx); 873 if (!atom) { 874 return false; 875 } 876 877 RootedId id(cx, AtomToId(atom)); 878 RootedValue val(cx); 879 switch (exp.kind()) { 880 case DefinitionKind::Function: { 881 RootedFunction func(cx); 882 if (!instance.getExportedFunction(cx, exp.funcIndex(), &func)) { 883 return false; 884 } 885 val = ObjectValue(*func); 886 break; 887 } 888 case DefinitionKind::Table: { 889 val = ObjectValue(*tableObjs[exp.tableIndex()]); 890 break; 891 } 892 case DefinitionKind::Memory: { 893 val = ObjectValue(*memoryObjs[exp.memoryIndex()]); 894 break; 895 } 896 case DefinitionKind::Global: { 897 const GlobalDesc& global = globals[exp.globalIndex()]; 898 if (!GetGlobalExport(cx, instanceObj, funcImports, global, 899 exp.globalIndex(), globalImportValues, globalObjs, 900 &val)) { 901 return false; 902 } 903 break; 904 } 905 case DefinitionKind::Tag: { 906 val = ObjectValue(*tagObjs[exp.tagIndex()]); 907 break; 908 } 909 } 910 911 if (!JS_DefinePropertyById(cx, exportObj, id, val, propertyAttr)) { 912 return false; 913 } 914 } 915 916 if (!codeMeta.isAsmJS()) { 917 if (!PreventExtensions(cx, exportObj)) { 918 return false; 919 } 920 } 921 922 instanceObj->initExportsObj(*exportObj); 923 return true; 924 } 925 926 bool Module::instantiate(JSContext* cx, ImportValues& imports, 927 HandleObject instanceProto, 928 MutableHandle<WasmInstanceObject*> instance) const { 929 MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers); 930 931 if (!instantiateFunctions(cx, imports.funcs)) { 932 return false; 933 } 934 935 Rooted<WasmMemoryObjectVector> memories(cx); 936 if (!instantiateMemories(cx, imports.memories, &memories)) { 937 return false; 938 } 939 940 // Note that the following will extend imports.exceptionObjs with wrappers for 941 // the local (non-imported) exceptions of the module. 942 // The resulting vector is sparse, i.e., it will be null in slots that contain 943 // exceptions that are neither exported or imported. 944 // On the contrary, all the slots of exceptionTags will be filled with 945 // unique tags. 946 947 if (!instantiateTags(cx, imports.tagObjs)) { 948 return false; 949 } 950 951 // Note that tableObjs is sparse: it will be null in slots that contain 952 // tables that are neither exported nor imported. 953 954 Rooted<WasmTableObjectVector> tableObjs(cx); 955 SharedTableVector tables; 956 if (!instantiateTables(cx, imports.tables, &tableObjs, &tables)) { 957 return false; 958 } 959 960 if (!instantiateGlobals(cx, imports.globalValues, imports.globalObjs)) { 961 return false; 962 } 963 964 UniqueDebugState maybeDebug; 965 if (code().debugEnabled()) { 966 maybeDebug = cx->make_unique<DebugState>(*code_, *this); 967 if (!maybeDebug) { 968 ReportOutOfMemory(cx); 969 return false; 970 } 971 } 972 973 instance.set(WasmInstanceObject::create( 974 cx, code_, moduleMeta().dataSegments, moduleMeta().elemSegments, 975 codeMeta().instanceDataLength, memories, std::move(tables), imports.funcs, 976 codeMeta().globals, imports.globalValues, imports.globalObjs, 977 imports.tagObjs, instanceProto, std::move(maybeDebug))); 978 if (!instance) { 979 return false; 980 } 981 982 if (!CreateExportObject(cx, instance, imports.funcs, tableObjs.get(), 983 memories.get(), imports.tagObjs, imports.globalValues, 984 imports.globalObjs, moduleMeta().exports)) { 985 return false; 986 } 987 988 // Register the instance with the Realm so that it can find out about global 989 // events like profiling being enabled in the realm. Registration does not 990 // require a fully-initialized instance and must precede initSegments as the 991 // final pre-requisite for a live instance. 992 993 if (!cx->realm()->wasm.registerInstance(cx, instance)) { 994 ReportOutOfMemory(cx); 995 return false; 996 } 997 998 // Perform initialization as the final step after the instance is fully 999 // constructed since this can make the instance live to content (even if the 1000 // start function fails). 1001 1002 if (!instance->instance().initSegments(cx, moduleMeta().dataSegments, 1003 moduleMeta().elemSegments)) { 1004 return false; 1005 } 1006 1007 // Now that the instance is fully live and initialized, the start function. 1008 // Note that failure may cause instantiation to throw, but the instance may 1009 // still be live via edges created by initSegments or the start function. 1010 1011 if (codeMeta().startFuncIndex) { 1012 FixedInvokeArgs<0> args(cx); 1013 if (!instance->instance().callExport(cx, *codeMeta().startFuncIndex, 1014 args)) { 1015 return false; 1016 } 1017 } 1018 1019 JSUseCounter useCounter = 1020 codeMeta().isAsmJS() ? JSUseCounter::ASMJS : JSUseCounter::WASM; 1021 cx->runtime()->setUseCounter(instance, useCounter); 1022 SetUseCountersForFeatureUsage(cx, instance, moduleMeta().featureUsage); 1023 1024 // Warn for deprecated features. Don't do this with differential testing as 1025 // that will think these warnings are significant. 1026 if (!js::SupportDifferentialTesting()) { 1027 // Warn if the user is using the legacy exceptions proposal. 1028 if (moduleMeta().featureUsage & FeatureUsage::LegacyExceptions) { 1029 if (!js::WarnNumberASCII(cx, JSMSG_WASM_LEGACY_EXCEPTIONS_DEPRECATED)) { 1030 if (cx->isExceptionPending()) { 1031 cx->clearPendingException(); 1032 } 1033 } 1034 } 1035 } 1036 1037 if (cx->options().testWasmAwaitTier2() && 1038 code().mode() != CompileMode::LazyTiering) { 1039 testingBlockOnTier2Complete(); 1040 } 1041 1042 return true; 1043 }