WasmPI.cpp (51583B)
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 2016 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/WasmPI.h" 20 21 #include "builtin/Promise.h" 22 #include "debugger/DebugAPI.h" 23 #include "debugger/Debugger.h" 24 #include "jit/MIRGenerator.h" 25 #include "js/CallAndConstruct.h" 26 #include "js/Printf.h" 27 #include "vm/Iteration.h" 28 #include "vm/JSContext.h" 29 #include "vm/JSObject.h" 30 #include "vm/NativeObject.h" 31 #include "vm/PromiseObject.h" 32 #include "wasm/WasmConstants.h" 33 #include "wasm/WasmContext.h" 34 #include "wasm/WasmFeatures.h" 35 #include "wasm/WasmGenerator.h" 36 #include "wasm/WasmIonCompile.h" // IonPlatformSupport 37 #include "wasm/WasmValidate.h" 38 39 #include "vm/JSObject-inl.h" 40 #include "wasm/WasmGcObject-inl.h" 41 #include "wasm/WasmInstance-inl.h" 42 43 using namespace js; 44 using namespace js::jit; 45 46 #ifdef ENABLE_WASM_JSPI 47 namespace js::wasm { 48 49 void SuspenderObject::releaseStackMemory() { 50 void* memory = stackMemory(); 51 MOZ_ASSERT(isMoribund() == !memory); 52 if (memory) { 53 js_free(memory); 54 setStackMemory(nullptr); 55 setState(SuspenderState::Moribund); 56 } 57 } 58 59 // Slots that used in various JSFunctionExtended below. 60 const size_t SUSPENDER_SLOT = 0; 61 const size_t WRAPPED_FN_SLOT = 1; 62 const size_t CONTINUE_ON_SUSPENDABLE_SLOT = 1; 63 const size_t PROMISE_SLOT = 2; 64 65 static JitActivation* FindSuspendableStackActivation( 66 JSTracer* trc, SuspenderObject* suspender) { 67 // The jitActivation.refNoCheck() can be used since during trace/marking 68 // the main thread will be paused. 69 JitActivation* activation = 70 trc->runtime()->mainContextFromAnyThread()->jitActivation.refNoCheck(); 71 while (activation) { 72 // Skip activations without Wasm exit FP -- they are mostly debugger 73 // related. 74 if (activation->hasWasmExitFP()) { 75 // Scan all JitActivations to find one that starts with suspended stack 76 // frame pointer. 77 WasmFrameIter iter(activation); 78 if (!iter.done() && suspender->hasStackAddress(iter.frame())) { 79 return activation; 80 } 81 } 82 activation = activation->prevJitActivation(); 83 } 84 MOZ_CRASH("Suspendable stack activation not found"); 85 } 86 87 void TraceSuspendableStack(JSTracer* trc, SuspenderObject* suspender) { 88 MOZ_ASSERT(suspender->isTraceable()); 89 void* exitFP = suspender->suspendableExitFP(); 90 91 // Create and iterator for wasm frames: 92 // - If a stack entry for suspended stack exists, the 93 // suspender->suspendableFP() 94 // and suspender->suspendedReturnAddress() provide start of the frames. 95 // - Otherwise, the stack is the part of the main stack, the context 96 // JitActivation frames will be used to trace. 97 WasmFrameIter iter = 98 suspender->isSuspended() 99 ? WasmFrameIter( 100 static_cast<FrameWithInstances*>(suspender->suspendableFP()), 101 suspender->suspendedReturnAddress()) 102 : WasmFrameIter(FindSuspendableStackActivation(trc, suspender)); 103 MOZ_ASSERT_IF(suspender->isSuspended(), iter.currentFrameStackSwitched()); 104 uintptr_t highestByteVisitedInPrevWasmFrame = 0; 105 while (true) { 106 MOZ_ASSERT(!iter.done()); 107 uint8_t* nextPC = iter.resumePCinCurrentFrame(); 108 Instance* instance = iter.instance(); 109 TraceInstanceEdge(trc, instance, "WasmFrameIter instance"); 110 highestByteVisitedInPrevWasmFrame = instance->traceFrame( 111 trc, iter, nextPC, highestByteVisitedInPrevWasmFrame); 112 if (iter.frame() == exitFP) { 113 break; 114 } 115 ++iter; 116 if (iter.currentFrameStackSwitched()) { 117 highestByteVisitedInPrevWasmFrame = 0; 118 } 119 } 120 } 121 122 static_assert(JS_STACK_GROWTH_DIRECTION < 0, 123 "JS-PI implemented only for native stacks that grows towards 0"); 124 125 SuspenderObject* SuspenderObject::create(JSContext* cx) { 126 if (cx->wasm().suspenders_.count() >= SuspendableStacksMaxCount) { 127 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 128 JSMSG_JSPI_SUSPENDER_LIMIT); 129 return nullptr; 130 } 131 132 Rooted<SuspenderObject*> suspender( 133 cx, NewBuiltinClassInstance<SuspenderObject>(cx)); 134 if (!suspender) { 135 return nullptr; 136 } 137 138 // Initialize all of the slots 139 suspender->initFixedSlot(StateSlot, Int32Value(SuspenderState::Moribund)); 140 suspender->initFixedSlot(PromisingPromiseSlot, NullValue()); 141 suspender->initFixedSlot(SuspendingReturnTypeSlot, 142 Int32Value(int32_t(ReturnType::Unknown))); 143 suspender->initFixedSlot(StackMemorySlot, PrivateValue(nullptr)); 144 suspender->initFixedSlot(MainFPSlot, PrivateValue(nullptr)); 145 suspender->initFixedSlot(MainSPSlot, PrivateValue(nullptr)); 146 suspender->initFixedSlot(SuspendableFPSlot, PrivateValue(nullptr)); 147 suspender->initFixedSlot(SuspendableSPSlot, PrivateValue(nullptr)); 148 suspender->initFixedSlot(SuspendableExitFPSlot, PrivateValue(nullptr)); 149 suspender->initFixedSlot(SuspendedRASlot, PrivateValue(nullptr)); 150 suspender->initFixedSlot(MainExitFPSlot, PrivateValue(nullptr)); 151 152 void* stackMemory = js_malloc(SuspendableStackPlusRedZoneSize); 153 if (!stackMemory) { 154 ReportOutOfMemory(cx); 155 return nullptr; 156 } 157 158 if (!cx->wasm().suspenders_.putNew(suspender)) { 159 js_free(stackMemory); 160 ReportOutOfMemory(cx); 161 return nullptr; 162 } 163 164 // We are now fully constructed and can transition states 165 suspender->setStackMemory(stackMemory); 166 suspender->setFixedSlot(SuspendableSPSlot, 167 PrivateValue(static_cast<uint8_t*>(stackMemory) + 168 SuspendableStackPlusRedZoneSize)); 169 suspender->setState(SuspenderState::Initial); 170 171 return suspender; 172 } 173 174 const JSClass SuspenderObject::class_ = { 175 "SuspenderObject", 176 JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE, 177 &SuspenderObject::classOps_, 178 nullptr, 179 &SuspenderObject::classExt_, 180 }; 181 182 const JSClassOps SuspenderObject::classOps_ = { 183 nullptr, // addProperty 184 nullptr, // delProperty 185 nullptr, // enumerate 186 nullptr, // newEnumerate 187 nullptr, // resolve 188 nullptr, // mayResolve 189 finalize, // finalize 190 nullptr, // call 191 nullptr, // construct 192 trace, // trace 193 }; 194 195 const ClassExtension SuspenderObject::classExt_ = { 196 .objectMovedOp = SuspenderObject::moved, 197 }; 198 199 /* static */ 200 void SuspenderObject::finalize(JS::GCContext* gcx, JSObject* obj) { 201 SuspenderObject& suspender = obj->as<SuspenderObject>(); 202 if (!suspender.isMoribund()) { 203 gcx->runtime()->mainContextFromOwnThread()->wasm().suspenders_.remove( 204 &suspender); 205 } 206 suspender.releaseStackMemory(); 207 MOZ_ASSERT(suspender.isMoribund()); 208 } 209 210 /* static */ 211 void SuspenderObject::trace(JSTracer* trc, JSObject* obj) { 212 SuspenderObject& suspender = obj->as<SuspenderObject>(); 213 // The SuspenderObject refers stacks frames that need to be traced 214 // only during major GC to determine if SuspenderObject content is 215 // reachable from JS. 216 if (!suspender.isTraceable() || trc->isTenuringTracer()) { 217 return; 218 } 219 TraceSuspendableStack(trc, &suspender); 220 } 221 222 /* static */ 223 size_t SuspenderObject::moved(JSObject* obj, JSObject* old) { 224 wasm::Context& context = 225 obj->runtimeFromMainThread()->mainContextFromOwnThread()->wasm(); 226 context.suspenders_.rekeyIfMoved(&old->as<SuspenderObject>(), 227 &obj->as<SuspenderObject>()); 228 return 0; 229 } 230 231 void SuspenderObject::setMoribund(JSContext* cx) { 232 MOZ_ASSERT(state() == SuspenderState::Active); 233 cx->wasm().leaveSuspendableStack(cx); 234 if (!this->isMoribund()) { 235 cx->wasm().suspenders_.remove(this); 236 } 237 this->releaseStackMemory(); 238 MOZ_ASSERT(this->isMoribund()); 239 } 240 241 void SuspenderObject::setActive(JSContext* cx) { 242 this->setState(SuspenderState::Active); 243 cx->wasm().enterSuspendableStack(cx, this); 244 } 245 246 void SuspenderObject::setSuspended(JSContext* cx) { 247 this->setState(SuspenderState::Suspended); 248 cx->wasm().leaveSuspendableStack(cx); 249 } 250 251 void SuspenderObject::enter(JSContext* cx) { 252 // We can enter a suspender normally from Initial, or through unwinding when 253 // are in the 'CalledOnMain' or 'Suspended' states. 254 MOZ_ASSERT(state() == SuspenderState::Initial || 255 state() == SuspenderState::CalledOnMain || 256 state() == SuspenderState::Suspended); 257 setActive(cx); 258 } 259 260 void SuspenderObject::suspend(JSContext* cx) { 261 MOZ_ASSERT(state() == SuspenderState::Active); 262 setSuspended(cx); 263 264 if (cx->realm()->isDebuggee()) { 265 WasmFrameIter iter(cx->activation()->asJit()); 266 while (true) { 267 MOZ_ASSERT(!iter.done()); 268 if (iter.debugEnabled()) { 269 DebugAPI::onSuspendWasmFrame(cx, iter.debugFrame()); 270 } 271 ++iter; 272 if (iter.currentFrameStackSwitched()) { 273 break; 274 } 275 } 276 } 277 } 278 279 void SuspenderObject::resume(JSContext* cx) { 280 MOZ_ASSERT(state() == SuspenderState::Suspended); 281 setActive(cx); 282 // Use barrier because object is being removed from the suspendable stack 283 // from roots. 284 gc::PreWriteBarrier(this); 285 286 if (cx->realm()->isDebuggee()) { 287 for (FrameIter iter(cx);; ++iter) { 288 MOZ_RELEASE_ASSERT(!iter.done(), "expecting stackSwitched()"); 289 if (iter.isWasm()) { 290 WasmFrameIter& wasmIter = iter.wasmFrame(); 291 if (wasmIter.currentFrameStackSwitched()) { 292 break; 293 } 294 if (wasmIter.debugEnabled()) { 295 DebugAPI::onResumeWasmFrame(cx, iter); 296 } 297 } 298 } 299 } 300 } 301 302 void SuspenderObject::leave(JSContext* cx) { 303 // We are exiting suspended stack if state is active, 304 // otherwise the stack was just suspended. 305 switch (state()) { 306 case SuspenderState::Active: { 307 setMoribund(cx); 308 break; 309 } 310 case SuspenderState::Suspended: { 311 MOZ_ASSERT(!cx->wasm().onSuspendableStack()); 312 break; 313 } 314 case SuspenderState::Initial: 315 case SuspenderState::Moribund: 316 case SuspenderState::CalledOnMain: 317 MOZ_CRASH(); 318 } 319 } 320 321 void SuspenderObject::unwind(JSContext* cx) { 322 switch (state()) { 323 case SuspenderState::Suspended: 324 case SuspenderState::CalledOnMain: { 325 cx->wasm().suspenders_.remove(this); 326 this->releaseStackMemory(); 327 MOZ_ASSERT(this->isMoribund()); 328 break; 329 } 330 case SuspenderState::Active: 331 case SuspenderState::Initial: 332 case SuspenderState::Moribund: 333 MOZ_CRASH(); 334 } 335 } 336 337 void SuspenderObject::forwardToSuspendable() { 338 // Injecting suspendable stack back into main one at the exit frame. 339 uint8_t* mainExitFP = (uint8_t*)this->mainExitFP(); 340 *reinterpret_cast<void**>(mainExitFP + Frame::callerFPOffset()) = 341 this->suspendableFP(); 342 *reinterpret_cast<void**>(mainExitFP + Frame::returnAddressOffset()) = 343 this->suspendedReturnAddress(); 344 } 345 346 // Suspending 347 348 // Builds a wasm module with following structure: 349 // (module 350 // (type $params (struct (field ..)*))) 351 // (type $results (struct (field ..)*))) 352 // (import "" "" (func $suspending.wrappedfn ..)) 353 // (func $suspending.exported .. ) 354 // (func $suspending.trampoline ..) 355 // (func $suspending.continue-on-suspendable ..) 356 // (export "" (func $suspending.exported)) 357 // ) 358 // 359 // The module provides logic for the state transitions (see the SMDOC): 360 // - Invoke Suspending Import via $suspending.exported 361 // - Suspending Function Returns a Promise via $suspending.trampoline 362 // - Promise Resolved transitions via $suspending.continue-on-suspendable 363 // 364 class SuspendingFunctionModuleFactory { 365 public: 366 enum TypeIdx { 367 ParamsTypeIndex, 368 ResultsTypeIndex, 369 }; 370 371 enum FnIdx { 372 WrappedFnIndex, 373 ExportedFnIndex, 374 TrampolineFnIndex, 375 ContinueOnSuspendableFnIndex 376 }; 377 378 private: 379 // Builds function that will be imported to wasm module: 380 // (func $suspending.exported 381 // (param ..)* (result ..)* 382 // (local $suspender externref) 383 // (local $results (ref $results)) 384 // call $builtin.current-suspender 385 // local.tee $suspender 386 // ref.func $suspending.trampoline 387 // local.get $i* 388 // stuct.new $param-type 389 // stack-switch SwitchToMain ;; <- (suspender,fn,data) 390 // local.get $suspender 391 // call $builtin.get-suspending-promise-result 392 // ref.cast $results-type 393 // local.set $results 394 // (struct.get $results (local.get $results))* 395 // ) 396 bool encodeExportedFunction(CodeMetadata& codeMeta, uint32_t paramsSize, 397 uint32_t resultSize, uint32_t paramsOffset, 398 RefType resultType, Bytes& bytecode) { 399 Encoder encoder(bytecode, *codeMeta.types); 400 ValTypeVector locals; 401 if (!locals.emplaceBack(RefType::extern_())) { 402 return false; 403 } 404 if (!locals.emplaceBack(resultType)) { 405 return false; 406 } 407 if (!EncodeLocalEntries(encoder, locals)) { 408 return false; 409 } 410 411 const int suspenderIndex = paramsSize; 412 if (!encoder.writeOp(Op::I32Const) || !encoder.writeVarU32(0)) { 413 return false; 414 } 415 if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || 416 !encoder.writeVarU32((uint32_t)BuiltinModuleFuncId::CurrentSuspender)) { 417 return false; 418 } 419 if (!encoder.writeOp(Op::LocalTee) || 420 !encoder.writeVarU32(suspenderIndex)) { 421 return false; 422 } 423 424 // Results local is located after all params and suspender. 425 const int resultsIndex = paramsSize + 1; 426 427 if (!encoder.writeOp(Op::RefFunc) || 428 !encoder.writeVarU32(TrampolineFnIndex)) { 429 return false; 430 } 431 for (uint32_t i = 0; i < paramsSize; i++) { 432 if (!encoder.writeOp(Op::LocalGet) || 433 !encoder.writeVarU32(i + paramsOffset)) { 434 return false; 435 } 436 } 437 if (!encoder.writeOp(GcOp::StructNew) || 438 !encoder.writeVarU32(ParamsTypeIndex)) { 439 return false; 440 } 441 442 if (!encoder.writeOp(MozOp::StackSwitch) || 443 !encoder.writeVarU32(uint32_t(StackSwitchKind::SwitchToMain))) { 444 return false; 445 } 446 447 if (!encoder.writeOp(Op::LocalGet) || 448 !encoder.writeVarU32(suspenderIndex)) { 449 return false; 450 } 451 if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || 452 !encoder.writeVarU32( 453 (uint32_t)BuiltinModuleFuncId::GetSuspendingPromiseResult)) { 454 return false; 455 } 456 if (!encoder.writeOp(GcOp::RefCast) || 457 !encoder.writeVarU32(ResultsTypeIndex) || 458 !encoder.writeOp(Op::LocalSet) || !encoder.writeVarU32(resultsIndex)) { 459 return false; 460 } 461 for (uint32_t i = 0; i < resultSize; i++) { 462 if (!encoder.writeOp(Op::LocalGet) || 463 !encoder.writeVarU32(resultsIndex) || 464 !encoder.writeOp(GcOp::StructGet) || 465 !encoder.writeVarU32(ResultsTypeIndex) || !encoder.writeVarU32(i)) { 466 return false; 467 } 468 } 469 return encoder.writeOp(Op::End); 470 } 471 472 // Builds function that is called on main stack: 473 // (func $suspending.trampoline 474 // (param $params (ref $suspender)) (param $param (ref $param-type)) 475 // (result anyref) 476 // local.get $suspender ;; for $builtin.forward-exn-to-suspended below 477 // block (result exnref) 478 // try_table (catch_all_ref 0) 479 // local.get $suspender ;; for call $add-promise-reactions 480 // (struct.get $param-type $i (local.get $param))* 481 // call $suspending.wrappedfn 482 // ref.func $suspending.continue-on-suspendable 483 // call $builtin.add-promise-reactions 484 // return 485 // end 486 // unreachable 487 // end 488 // call $builtin.forward-exn-to-suspended 489 // ) 490 // The function calls suspending import and returns into the 491 // $promising.exported function because that was the top function 492 // on the main stack. 493 bool encodeTrampolineFunction(CodeMetadata& codeMeta, uint32_t paramsSize, 494 Bytes& bytecode) { 495 Encoder encoder(bytecode, *codeMeta.types); 496 if (!EncodeLocalEntries(encoder, ValTypeVector())) { 497 return false; 498 } 499 const uint32_t SuspenderIndex = 0; 500 const uint32_t ParamsIndex = 1; 501 502 if (!encoder.writeOp(Op::LocalGet) || 503 !encoder.writeVarU32(SuspenderIndex)) { 504 return false; 505 } 506 507 if (!encoder.writeOp(Op::Block) || 508 !encoder.writeFixedU8(uint8_t(TypeCode::ExnRef))) { 509 return false; 510 } 511 512 if (!encoder.writeOp(Op::TryTable) || 513 !encoder.writeFixedU8(uint8_t(TypeCode::BlockVoid)) || 514 !encoder.writeVarU32(1) || 515 !encoder.writeFixedU8(/* catch_all_ref = */ 0x03) || 516 !encoder.writeVarU32(0)) { 517 return false; 518 } 519 520 // For AddPromiseReactions call below. 521 if (!encoder.writeOp(Op::LocalGet) || 522 !encoder.writeVarU32(SuspenderIndex)) { 523 return false; 524 } 525 526 for (uint32_t i = 0; i < paramsSize; i++) { 527 if (!encoder.writeOp(Op::LocalGet) || !encoder.writeVarU32(ParamsIndex)) { 528 return false; 529 } 530 if (!encoder.writeOp(GcOp::StructGet) || 531 !encoder.writeVarU32(ParamsTypeIndex) || !encoder.writeVarU32(i)) { 532 return false; 533 } 534 } 535 if (!encoder.writeOp(Op::Call) || !encoder.writeVarU32(WrappedFnIndex)) { 536 return false; 537 } 538 if (!encoder.writeOp(Op::RefFunc) || 539 !encoder.writeVarU32(ContinueOnSuspendableFnIndex)) { 540 return false; 541 } 542 543 if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || 544 !encoder.writeVarU32( 545 (uint32_t)BuiltinModuleFuncId::AddPromiseReactions)) { 546 return false; 547 } 548 549 if (!encoder.writeOp(Op::Return) || !encoder.writeOp(Op::End) || 550 !encoder.writeOp(Op::Unreachable) || !encoder.writeOp(Op::End)) { 551 return false; 552 } 553 554 if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || 555 !encoder.writeVarU32( 556 (uint32_t)BuiltinModuleFuncId::ForwardExceptionToSuspended)) { 557 return false; 558 } 559 560 return encoder.writeOp(Op::End); 561 } 562 563 // Builds function that is called on main stack: 564 // (func $suspending.continue-on-suspendable 565 // (param $params (ref $suspender)) (param $results externref) 566 // (result externref) 567 // local.get $suspender 568 // ref.null funcref 569 // local.get $results 570 // any.convert_extern 571 // stack-switch ContinueOnSuspendable 572 // ) 573 bool encodeContinueOnSuspendableFunction(CodeMetadata& codeMeta, 574 uint32_t resultsSize, 575 Bytes& bytecode) { 576 Encoder encoder(bytecode, *codeMeta.types); 577 if (!EncodeLocalEntries(encoder, ValTypeVector())) { 578 return false; 579 } 580 581 const uint32_t SuspenderIndex = 0; 582 const uint32_t ResultsIndex = 1; 583 584 if (!encoder.writeOp(Op::LocalGet) || 585 !encoder.writeVarU32(SuspenderIndex)) { 586 return false; 587 } 588 if (!encoder.writeOp(Op::RefNull) || 589 !encoder.writeValType(ValType(RefType::func()))) { 590 return false; 591 } 592 if (!encoder.writeOp(Op::LocalGet) || !encoder.writeVarU32(ResultsIndex) || 593 !encoder.writeOp(GcOp::AnyConvertExtern)) { 594 return false; 595 } 596 597 if (!encoder.writeOp(MozOp::StackSwitch) || 598 !encoder.writeVarU32( 599 uint32_t(StackSwitchKind::ContinueOnSuspendable))) { 600 return false; 601 } 602 603 return encoder.writeOp(Op::End); 604 } 605 606 public: 607 SharedModule build(JSContext* cx, HandleObject func, ValTypeVector&& params, 608 ValTypeVector&& results) { 609 FeatureOptions options; 610 options.isBuiltinModule = true; 611 612 ScriptedCaller scriptedCaller; 613 SharedCompileArgs compileArgs = 614 CompileArgs::buildAndReport(cx, std::move(scriptedCaller), options); 615 if (!compileArgs) { 616 return nullptr; 617 } 618 619 MutableModuleMetadata moduleMeta = js_new<ModuleMetadata>(); 620 if (!moduleMeta || !moduleMeta->init(*compileArgs)) { 621 return nullptr; 622 } 623 MutableCodeMetadata codeMeta = moduleMeta->codeMeta; 624 625 MOZ_ASSERT(IonPlatformSupport()); 626 CompilerEnvironment compilerEnv(CompileMode::Once, Tier::Optimized, 627 DebugEnabled::False); 628 compilerEnv.computeParameters(); 629 630 RefType suspenderType = RefType::extern_(); 631 RefType promiseType = RefType::extern_(); 632 633 ValTypeVector paramsWithoutSuspender; 634 635 const size_t resultsSize = results.length(); 636 const size_t paramsSize = params.length(); 637 const size_t paramsOffset = 0; 638 if (!paramsWithoutSuspender.append(params.begin(), params.end())) { 639 ReportOutOfMemory(cx); 640 return nullptr; 641 } 642 643 ValTypeVector resultsRef; 644 if (!resultsRef.emplaceBack(promiseType)) { 645 ReportOutOfMemory(cx); 646 return nullptr; 647 } 648 649 StructType boxedParamsStruct; 650 if (!StructType::createImmutable(paramsWithoutSuspender, 651 &boxedParamsStruct)) { 652 ReportOutOfMemory(cx); 653 return nullptr; 654 } 655 MOZ_ASSERT(codeMeta->types->length() == ParamsTypeIndex); 656 if (!codeMeta->types->addType(std::move(boxedParamsStruct))) { 657 return nullptr; 658 } 659 660 StructType boxedResultType; 661 if (!StructType::createImmutable(results, &boxedResultType)) { 662 ReportOutOfMemory(cx); 663 return nullptr; 664 } 665 MOZ_ASSERT(codeMeta->types->length() == ResultsTypeIndex); 666 if (!codeMeta->types->addType(std::move(boxedResultType))) { 667 return nullptr; 668 } 669 670 MOZ_ASSERT(codeMeta->funcs.length() == WrappedFnIndex); 671 if (!moduleMeta->addDefinedFunc(std::move(paramsWithoutSuspender), 672 std::move(resultsRef))) { 673 return nullptr; 674 } 675 676 // Imports names are not important, declare functions above as imports. 677 codeMeta->numFuncImports = codeMeta->funcs.length(); 678 679 // We will be looking up and using the exports function by index so 680 // the name doesn't matter. 681 MOZ_ASSERT(codeMeta->funcs.length() == ExportedFnIndex); 682 if (!moduleMeta->addDefinedFunc(std::move(params), std::move(results), 683 /*declareForRef = */ true, 684 mozilla::Some(CacheableName()))) { 685 return nullptr; 686 } 687 688 ValTypeVector paramsTrampoline, resultsTrampoline; 689 if (!paramsTrampoline.emplaceBack(suspenderType) || 690 !paramsTrampoline.emplaceBack(RefType::fromTypeDef( 691 &(*codeMeta->types)[ParamsTypeIndex], false)) || 692 !resultsTrampoline.emplaceBack(RefType::any())) { 693 ReportOutOfMemory(cx); 694 return nullptr; 695 } 696 MOZ_ASSERT(codeMeta->funcs.length() == TrampolineFnIndex); 697 if (!moduleMeta->addDefinedFunc(std::move(paramsTrampoline), 698 std::move(resultsTrampoline), 699 /*declareForRef = */ true)) { 700 return nullptr; 701 } 702 703 ValTypeVector paramsContinueOnSuspendable, resultsContinueOnSuspendable; 704 if (!paramsContinueOnSuspendable.emplaceBack(suspenderType) || 705 !paramsContinueOnSuspendable.emplaceBack(RefType::extern_())) { 706 ReportOutOfMemory(cx); 707 return nullptr; 708 } 709 MOZ_ASSERT(codeMeta->funcs.length() == ContinueOnSuspendableFnIndex); 710 if (!moduleMeta->addDefinedFunc(std::move(paramsContinueOnSuspendable), 711 std::move(resultsContinueOnSuspendable), 712 /*declareForRef = */ true)) { 713 return nullptr; 714 } 715 716 if (!moduleMeta->prepareForCompile(compilerEnv.mode())) { 717 return nullptr; 718 } 719 720 ModuleGenerator mg(*codeMeta, compilerEnv, compilerEnv.initialState(), 721 nullptr, nullptr, nullptr); 722 if (!mg.initializeCompleteTier()) { 723 return nullptr; 724 } 725 // Build functions and keep bytecodes around until the end. 726 uint32_t funcBytecodeOffset = CallSite::FIRST_VALID_BYTECODE_OFFSET; 727 Bytes bytecode; 728 if (!encodeExportedFunction( 729 *codeMeta, paramsSize, resultsSize, paramsOffset, 730 RefType::fromTypeDef(&(*codeMeta->types)[ResultsTypeIndex], false), 731 bytecode)) { 732 ReportOutOfMemory(cx); 733 return nullptr; 734 } 735 if (!mg.compileFuncDef(ExportedFnIndex, funcBytecodeOffset, 736 bytecode.begin(), 737 bytecode.begin() + bytecode.length())) { 738 return nullptr; 739 } 740 funcBytecodeOffset += bytecode.length(); 741 742 Bytes bytecode2; 743 if (!encodeTrampolineFunction(*codeMeta, paramsSize, bytecode2)) { 744 ReportOutOfMemory(cx); 745 return nullptr; 746 } 747 if (!mg.compileFuncDef(TrampolineFnIndex, funcBytecodeOffset, 748 bytecode2.begin(), 749 bytecode2.begin() + bytecode2.length())) { 750 return nullptr; 751 } 752 funcBytecodeOffset += bytecode2.length(); 753 754 Bytes bytecode3; 755 if (!encodeContinueOnSuspendableFunction(*codeMeta, paramsSize, 756 bytecode3)) { 757 ReportOutOfMemory(cx); 758 return nullptr; 759 } 760 if (!mg.compileFuncDef(ContinueOnSuspendableFnIndex, funcBytecodeOffset, 761 bytecode3.begin(), 762 bytecode3.begin() + bytecode3.length())) { 763 return nullptr; 764 } 765 funcBytecodeOffset += bytecode3.length(); 766 767 if (!mg.finishFuncDefs()) { 768 return nullptr; 769 } 770 771 return mg.finishModule(BytecodeBufferOrSource(), *moduleMeta, 772 /*maybeCompleteTier2Listener=*/nullptr); 773 } 774 }; 775 776 // Reaction on resolved/rejected suspending promise. 777 static bool WasmPISuspendTaskContinue(JSContext* cx, unsigned argc, Value* vp) { 778 CallArgs args = CallArgsFromVp(argc, vp); 779 // The arg[0] has result of resolved promise, or rejection reason. 780 Rooted<JSFunction*> callee(cx, &args.callee().as<JSFunction>()); 781 RootedValue suspender(cx, callee->getExtendedSlot(SUSPENDER_SLOT)); 782 RootedValue suspendingPromise(cx, callee->getExtendedSlot(PROMISE_SLOT)); 783 784 // Convert result of the promise into the parameters/arguments for the 785 // $suspending.continue-on-suspendable. 786 RootedFunction continueOnSuspendable( 787 cx, &callee->getExtendedSlot(CONTINUE_ON_SUSPENDABLE_SLOT) 788 .toObject() 789 .as<JSFunction>()); 790 JS::RootedValueArray<2> argv(cx); 791 argv[0].set(suspender); 792 argv[1].set(suspendingPromise); 793 794 JS::Rooted<JS::Value> rval(cx); 795 if (Call(cx, UndefinedHandleValue, continueOnSuspendable, argv, &rval)) { 796 return true; 797 } 798 799 // The stack was unwound during exception. 800 MOZ_RELEASE_ASSERT(!cx->wasm().activeSuspender()); 801 MOZ_RELEASE_ASSERT( 802 suspender.toObject().as<wasm::SuspenderObject>().isMoribund()); 803 804 if (cx->isThrowingOutOfMemory()) { 805 return false; 806 } 807 Rooted<PromiseObject*> promise( 808 cx, suspender.toObject().as<SuspenderObject>().promisingPromise()); 809 return RejectPromiseWithPendingError(cx, promise); 810 } 811 812 // Wraps original import to catch all exceptions and convert result to a 813 // promise. 814 // Seen as $suspending.wrappedfn in wasm. 815 static bool WasmPIWrapSuspendingImport(JSContext* cx, unsigned argc, 816 Value* vp) { 817 CallArgs args = CallArgsFromVp(argc, vp); 818 Rooted<JSFunction*> callee(cx, &args.callee().as<JSFunction>()); 819 RootedValue originalImportFunc(cx, callee->getExtendedSlot(WRAPPED_FN_SLOT)); 820 821 // Catching exceptions here. 822 RootedValue rval(cx); 823 if (Call(cx, UndefinedHandleValue, originalImportFunc, args, &rval)) { 824 // Convert the result to a resolved promise later in AddPromiseReactions. 825 args.rval().set(rval); 826 return true; 827 } 828 829 // Deferring pending exception to the handler in the 830 // $suspending.trampoline. 831 return false; 832 } 833 834 JSFunction* WasmSuspendingFunctionCreate(JSContext* cx, HandleObject func, 835 ValTypeVector&& params, 836 ValTypeVector&& results) { 837 MOZ_ASSERT(IsCallable(ObjectValue(*func)) && 838 !IsCrossCompartmentWrapper(func)); 839 840 SuspendingFunctionModuleFactory moduleFactory; 841 SharedModule module = 842 moduleFactory.build(cx, func, std::move(params), std::move(results)); 843 if (!module) { 844 return nullptr; 845 } 846 847 // Instantiate the module. 848 Rooted<ImportValues> imports(cx); 849 850 // Add $suspending.wrappedfn to imports. 851 RootedFunction funcWrapper( 852 cx, NewNativeFunction(cx, WasmPIWrapSuspendingImport, 0, nullptr, 853 gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); 854 if (!funcWrapper) { 855 return nullptr; 856 } 857 funcWrapper->initExtendedSlot(WRAPPED_FN_SLOT, ObjectValue(*func)); 858 if (!imports.get().funcs.append(funcWrapper)) { 859 ReportOutOfMemory(cx); 860 return nullptr; 861 } 862 863 Rooted<WasmInstanceObject*> instance(cx); 864 if (!module->instantiate(cx, imports.get(), nullptr, &instance)) { 865 // Can also trap on invalid input function. 866 return nullptr; 867 } 868 869 // Returns the $suspending.exported function. 870 RootedFunction wasmFunc(cx); 871 if (!WasmInstanceObject::getExportedFunction( 872 cx, instance, SuspendingFunctionModuleFactory::ExportedFnIndex, 873 &wasmFunc)) { 874 return nullptr; 875 } 876 return wasmFunc; 877 } 878 879 JSFunction* WasmSuspendingFunctionCreate(JSContext* cx, HandleObject func, 880 const FuncType& type) { 881 ValTypeVector params, results; 882 if (!params.append(type.args().begin(), type.args().end()) || 883 !results.append(type.results().begin(), type.results().end())) { 884 ReportOutOfMemory(cx); 885 return nullptr; 886 } 887 return WasmSuspendingFunctionCreate(cx, func, std::move(params), 888 std::move(results)); 889 } 890 891 // Promising 892 893 // Builds a wasm module with following structure: 894 // (module 895 // (type $params (struct (field ..)*)) 896 // (type $results (struct (field ..)*)) 897 // (type $create-suspender-result (struct (field externref externref))) 898 // (import "" "" (func $promising.wrappedfn ..)) 899 // (func $promising.exported .. ) 900 // (func $promising.trampoline ..) 901 // (export "" (func $promising.exported)) 902 // ) 903 // 904 // The module provides logic for the Invoke Promising Import state transition 905 // via $promising.exported and $promising.trampoline (see the SMDOC). 906 // 907 class PromisingFunctionModuleFactory { 908 public: 909 enum TypeIdx { 910 ParamsTypeIndex, 911 ResultsTypeIndex, 912 }; 913 914 enum FnIdx { 915 WrappedFnIndex, 916 ExportedFnIndex, 917 TrampolineFnIndex, 918 }; 919 920 private: 921 // Builds function that will be exported for JS: 922 // (func $promising.exported 923 // (param ..)* (result externref) 924 // (local $suspender externref) 925 // call $builtin.create-suspender 926 // local.tee $suspender 927 // call $builtin.create-promising-promise ;; -> (promise) 928 // local.get $suspender 929 // ref.func $promising.trampoline 930 // local.get $i* 931 // stuct.new $param-type 932 // stack-switch SwitchToSuspendable ;; <- (suspender,fn,data) 933 // ) 934 bool encodeExportedFunction(CodeMetadata& codeMeta, uint32_t paramsSize, 935 Bytes& bytecode) { 936 Encoder encoder(bytecode, *codeMeta.types); 937 ValTypeVector locals; 938 if (!locals.emplaceBack(RefType::extern_())) { 939 return false; 940 } 941 if (!EncodeLocalEntries(encoder, locals)) { 942 return false; 943 } 944 945 const uint32_t SuspenderIndex = paramsSize; 946 if (!encoder.writeOp(Op::I32Const) || !encoder.writeVarU32(0)) { 947 return false; 948 } 949 if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || 950 !encoder.writeVarU32((uint32_t)BuiltinModuleFuncId::CreateSuspender)) { 951 return false; 952 } 953 954 if (!encoder.writeOp(Op::LocalTee) || 955 !encoder.writeVarU32(SuspenderIndex)) { 956 return false; 957 } 958 if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || 959 !encoder.writeVarU32( 960 (uint32_t)BuiltinModuleFuncId::CreatePromisingPromise)) { 961 return false; 962 } 963 964 if (!encoder.writeOp(Op::LocalGet) || 965 !encoder.writeVarU32(SuspenderIndex)) { 966 return false; 967 } 968 if (!encoder.writeOp(Op::RefFunc) || 969 !encoder.writeVarU32(TrampolineFnIndex)) { 970 return false; 971 } 972 for (uint32_t i = 0; i < paramsSize; i++) { 973 if (!encoder.writeOp(Op::LocalGet) || !encoder.writeVarU32(i)) { 974 return false; 975 } 976 } 977 if (!encoder.writeOp(GcOp::StructNew) || 978 !encoder.writeVarU32(ParamsTypeIndex)) { 979 return false; 980 } 981 if (!encoder.writeOp(MozOp::StackSwitch) || 982 !encoder.writeVarU32(uint32_t(StackSwitchKind::SwitchToSuspendable))) { 983 return false; 984 } 985 986 return encoder.writeOp(Op::End); 987 } 988 989 // Builds function that is called on alternative stack: 990 // (func $promising.trampoline 991 // (param $suspender externref) (param $params (ref $param-type)) 992 // (result externref) 993 // local.get $suspender ;; for call $set-results 994 // (local.get $suspender)? 995 // (struct.get $param-type $i (local.get $param))* 996 // (local.get $suspender)? 997 // call $promising.wrappedfn 998 // struct.new $result-type 999 // call $builtin.set-promising-promise-results 1000 // ) 1001 bool encodeTrampolineFunction(CodeMetadata& codeMeta, uint32_t paramsSize, 1002 Bytes& bytecode) { 1003 Encoder encoder(bytecode, *codeMeta.types); 1004 if (!EncodeLocalEntries(encoder, ValTypeVector())) { 1005 return false; 1006 } 1007 const uint32_t SuspenderIndex = 0; 1008 const uint32_t ParamsIndex = 1; 1009 1010 // Reserved for SetResultsFnIndex call at the end 1011 if (!encoder.writeOp(Op::LocalGet) || 1012 !encoder.writeVarU32(SuspenderIndex)) { 1013 return false; 1014 } 1015 1016 for (uint32_t i = 0; i < paramsSize; i++) { 1017 if (!encoder.writeOp(Op::LocalGet) || !encoder.writeVarU32(ParamsIndex)) { 1018 return false; 1019 } 1020 if (!encoder.writeOp(GcOp::StructGet) || 1021 !encoder.writeVarU32(ParamsTypeIndex) || !encoder.writeVarU32(i)) { 1022 return false; 1023 } 1024 } 1025 if (!encoder.writeOp(Op::Call) || !encoder.writeVarU32(WrappedFnIndex)) { 1026 return false; 1027 } 1028 1029 if (!encoder.writeOp(GcOp::StructNew) || 1030 !encoder.writeVarU32(ResultsTypeIndex)) { 1031 return false; 1032 } 1033 if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || 1034 !encoder.writeVarU32( 1035 (uint32_t)BuiltinModuleFuncId::SetPromisingPromiseResults)) { 1036 return false; 1037 } 1038 1039 return encoder.writeOp(Op::End); 1040 } 1041 1042 public: 1043 SharedModule build(JSContext* cx, HandleFunction fn, ValTypeVector&& params, 1044 ValTypeVector&& results) { 1045 const FuncType& fnType = fn->wasmTypeDef()->funcType(); 1046 size_t paramsSize = params.length(); 1047 1048 RefType suspenderType = RefType::extern_(); 1049 1050 FeatureOptions options; 1051 options.isBuiltinModule = true; 1052 1053 ScriptedCaller scriptedCaller; 1054 SharedCompileArgs compileArgs = 1055 CompileArgs::buildAndReport(cx, std::move(scriptedCaller), options); 1056 if (!compileArgs) { 1057 return nullptr; 1058 } 1059 1060 MutableModuleMetadata moduleMeta = js_new<ModuleMetadata>(); 1061 if (!moduleMeta || !moduleMeta->init(*compileArgs)) { 1062 return nullptr; 1063 } 1064 MutableCodeMetadata codeMeta = moduleMeta->codeMeta; 1065 1066 MOZ_ASSERT(IonPlatformSupport()); 1067 CompilerEnvironment compilerEnv(CompileMode::Once, Tier::Optimized, 1068 DebugEnabled::False); 1069 compilerEnv.computeParameters(); 1070 1071 StructType boxedParamsStruct; 1072 if (!StructType::createImmutable(params, &boxedParamsStruct)) { 1073 ReportOutOfMemory(cx); 1074 return nullptr; 1075 } 1076 MOZ_ASSERT(codeMeta->types->length() == ParamsTypeIndex); 1077 if (!codeMeta->types->addType(std::move(boxedParamsStruct))) { 1078 return nullptr; 1079 } 1080 1081 StructType boxedResultType; 1082 if (!StructType::createImmutable(fnType.results(), &boxedResultType)) { 1083 ReportOutOfMemory(cx); 1084 return nullptr; 1085 } 1086 MOZ_ASSERT(codeMeta->types->length() == ResultsTypeIndex); 1087 if (!codeMeta->types->addType(std::move(boxedResultType))) { 1088 return nullptr; 1089 } 1090 1091 ValTypeVector paramsForWrapper, resultsForWrapper; 1092 if (!paramsForWrapper.append(fnType.args().begin(), fnType.args().end()) || 1093 !resultsForWrapper.append(fnType.results().begin(), 1094 fnType.results().end())) { 1095 ReportOutOfMemory(cx); 1096 return nullptr; 1097 } 1098 MOZ_ASSERT(codeMeta->funcs.length() == WrappedFnIndex); 1099 if (!moduleMeta->addDefinedFunc(std::move(paramsForWrapper), 1100 std::move(resultsForWrapper))) { 1101 return nullptr; 1102 } 1103 1104 // Imports names are not important, declare functions above as imports. 1105 codeMeta->numFuncImports = codeMeta->funcs.length(); 1106 1107 // We will be looking up and using the exports function by index so 1108 // the name doesn't matter. 1109 MOZ_ASSERT(codeMeta->funcs.length() == ExportedFnIndex); 1110 if (!moduleMeta->addDefinedFunc(std::move(params), std::move(results), 1111 /* declareFoRef = */ true, 1112 mozilla::Some(CacheableName()))) { 1113 return nullptr; 1114 } 1115 1116 ValTypeVector paramsTrampoline, resultsTrampoline; 1117 if (!paramsTrampoline.emplaceBack(suspenderType) || 1118 !paramsTrampoline.emplaceBack(RefType::fromTypeDef( 1119 &(*codeMeta->types)[ParamsTypeIndex], false))) { 1120 ReportOutOfMemory(cx); 1121 return nullptr; 1122 } 1123 MOZ_ASSERT(codeMeta->funcs.length() == TrampolineFnIndex); 1124 if (!moduleMeta->addDefinedFunc(std::move(paramsTrampoline), 1125 std::move(resultsTrampoline), 1126 /* declareFoRef = */ true)) { 1127 return nullptr; 1128 } 1129 1130 if (!moduleMeta->prepareForCompile(compilerEnv.mode())) { 1131 return nullptr; 1132 } 1133 1134 ModuleGenerator mg(*codeMeta, compilerEnv, compilerEnv.initialState(), 1135 nullptr, nullptr, nullptr); 1136 if (!mg.initializeCompleteTier()) { 1137 return nullptr; 1138 } 1139 // Build functions and keep bytecodes around until the end. 1140 Bytes bytecode; 1141 uint32_t funcBytecodeOffset = CallSite::FIRST_VALID_BYTECODE_OFFSET; 1142 if (!encodeExportedFunction(*codeMeta, paramsSize, bytecode)) { 1143 ReportOutOfMemory(cx); 1144 return nullptr; 1145 } 1146 if (!mg.compileFuncDef(ExportedFnIndex, funcBytecodeOffset, 1147 bytecode.begin(), 1148 bytecode.begin() + bytecode.length())) { 1149 return nullptr; 1150 } 1151 funcBytecodeOffset += bytecode.length(); 1152 1153 Bytes bytecode2; 1154 if (!encodeTrampolineFunction(*codeMeta, paramsSize, bytecode2)) { 1155 ReportOutOfMemory(cx); 1156 return nullptr; 1157 } 1158 if (!mg.compileFuncDef(TrampolineFnIndex, funcBytecodeOffset, 1159 bytecode2.begin(), 1160 bytecode2.begin() + bytecode2.length())) { 1161 return nullptr; 1162 } 1163 funcBytecodeOffset += bytecode2.length(); 1164 1165 if (!mg.finishFuncDefs()) { 1166 return nullptr; 1167 } 1168 1169 return mg.finishModule(BytecodeBufferOrSource(), *moduleMeta, 1170 /*maybeCompleteTier2Listener=*/nullptr); 1171 } 1172 }; 1173 1174 // Wraps call to wasm $promising.exported function to catch an exception and 1175 // return a promise instead. 1176 static bool WasmPIPromisingFunction(JSContext* cx, unsigned argc, Value* vp) { 1177 CallArgs args = CallArgsFromVp(argc, vp); 1178 Rooted<JSFunction*> callee(cx, &args.callee().as<JSFunction>()); 1179 RootedFunction fn( 1180 cx, 1181 &callee->getExtendedSlot(WRAPPED_FN_SLOT).toObject().as<JSFunction>()); 1182 1183 // Catching exceptions here. 1184 if (Call(cx, UndefinedHandleValue, fn, args, args.rval())) { 1185 return true; 1186 } 1187 1188 // The stack was unwound during exception. There should be no active 1189 // suspender. 1190 MOZ_RELEASE_ASSERT(!cx->wasm().activeSuspender()); 1191 1192 if (cx->isThrowingOutOfMemory()) { 1193 return false; 1194 } 1195 1196 RootedObject promiseObject(cx, NewPromiseObject(cx, nullptr)); 1197 if (!promiseObject) { 1198 return false; 1199 } 1200 args.rval().setObject(*promiseObject); 1201 1202 Rooted<PromiseObject*> promise(cx, &promiseObject->as<PromiseObject>()); 1203 return RejectPromiseWithPendingError(cx, promise); 1204 } 1205 1206 JSFunction* WasmPromisingFunctionCreate(JSContext* cx, HandleObject func, 1207 ValTypeVector&& params, 1208 ValTypeVector&& results) { 1209 RootedFunction wrappedWasmFunc(cx, &func->as<JSFunction>()); 1210 MOZ_ASSERT(wrappedWasmFunc->isWasm()); 1211 const FuncType& wrappedWasmFuncType = 1212 wrappedWasmFunc->wasmTypeDef()->funcType(); 1213 1214 MOZ_ASSERT(results.length() == 0 && params.length() == 0); 1215 if (!results.append(RefType::extern_())) { 1216 ReportOutOfMemory(cx); 1217 return nullptr; 1218 } 1219 if (!params.append(wrappedWasmFuncType.args().begin(), 1220 wrappedWasmFuncType.args().end())) { 1221 ReportOutOfMemory(cx); 1222 return nullptr; 1223 } 1224 1225 PromisingFunctionModuleFactory moduleFactory; 1226 SharedModule module = moduleFactory.build( 1227 cx, wrappedWasmFunc, std::move(params), std::move(results)); 1228 // Instantiate the module. 1229 Rooted<ImportValues> imports(cx); 1230 1231 // Add wrapped function ($promising.wrappedfn) to imports. 1232 if (!imports.get().funcs.append(func)) { 1233 ReportOutOfMemory(cx); 1234 return nullptr; 1235 } 1236 1237 Rooted<WasmInstanceObject*> instance(cx); 1238 if (!module->instantiate(cx, imports.get(), nullptr, &instance)) { 1239 MOZ_ASSERT(cx->isThrowingOutOfMemory()); 1240 return nullptr; 1241 } 1242 1243 // Wrap $promising.exported function for exceptions/traps handling. 1244 RootedFunction wasmFunc(cx); 1245 if (!WasmInstanceObject::getExportedFunction( 1246 cx, instance, PromisingFunctionModuleFactory::ExportedFnIndex, 1247 &wasmFunc)) { 1248 return nullptr; 1249 } 1250 1251 RootedFunction wasmFuncWrapper( 1252 cx, NewNativeFunction(cx, WasmPIPromisingFunction, 0, nullptr, 1253 gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); 1254 if (!wasmFuncWrapper) { 1255 return nullptr; 1256 } 1257 wasmFuncWrapper->initExtendedSlot(WRAPPED_FN_SLOT, ObjectValue(*wasmFunc)); 1258 return wasmFuncWrapper; 1259 } 1260 1261 // Gets active suspender. 1262 // The reserved parameter is a workaround for limitation in the 1263 // WasmBuiltinModule.yaml generator to always have params. 1264 // Seen as $builtin.current-suspender to wasm. 1265 SuspenderObject* CurrentSuspender(Instance* instance, int32_t reserved) { 1266 MOZ_ASSERT(SASigCurrentSuspender.failureMode == FailureMode::FailOnNullPtr); 1267 JSContext* cx = instance->cx(); 1268 SuspenderObject* suspender = cx->wasm().activeSuspender(); 1269 if (!suspender) { 1270 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1271 JSMSG_JSPI_INVALID_STATE); 1272 return nullptr; 1273 } 1274 return suspender; 1275 } 1276 1277 // Creates a suspender and promise (that will be returned to JS code). 1278 // Seen as $builtin.create-suspender to wasm. 1279 SuspenderObject* CreateSuspender(Instance* instance, int32_t reserved) { 1280 MOZ_ASSERT(SASigCreateSuspender.failureMode == FailureMode::FailOnNullPtr); 1281 JSContext* cx = instance->cx(); 1282 return SuspenderObject::create(cx); 1283 } 1284 1285 // Creates a promise that will be returned at promising call. 1286 // Seen as $builtin.create-promising-promise to wasm. 1287 PromiseObject* CreatePromisingPromise(Instance* instance, 1288 SuspenderObject* suspender) { 1289 MOZ_ASSERT(SASigCreatePromisingPromise.failureMode == 1290 FailureMode::FailOnNullPtr); 1291 JSContext* cx = instance->cx(); 1292 1293 Rooted<SuspenderObject*> suspenderObject(cx, suspender); 1294 RootedObject promiseObject(cx, NewPromiseObject(cx, nullptr)); 1295 if (!promiseObject) { 1296 return nullptr; 1297 } 1298 1299 Rooted<PromiseObject*> promise(cx, &promiseObject->as<PromiseObject>()); 1300 suspenderObject->setPromisingPromise(promise); 1301 return promise.get(); 1302 } 1303 1304 // Converts promise results into actual function result, or exception/trap 1305 // if rejected. 1306 // Seen as $builtin.get-suspending-promise-result to wasm. 1307 JSObject* GetSuspendingPromiseResult(Instance* instance, void* result, 1308 SuspenderObject* suspender) { 1309 MOZ_ASSERT(SASigGetSuspendingPromiseResult.failureMode == 1310 FailureMode::FailOnNullPtr); 1311 JSContext* cx = instance->cx(); 1312 Rooted<SuspenderObject*> suspenderObject(cx, suspender); 1313 RootedAnyRef resultRef(cx, AnyRef::fromCompiledCode(result)); 1314 1315 SuspenderObject::ReturnType returnType = 1316 suspenderObject->suspendingReturnType(); 1317 MOZ_ASSERT(returnType != SuspenderObject::ReturnType::Unknown); 1318 Rooted<PromiseObject*> promise( 1319 cx, returnType == SuspenderObject::ReturnType::Promise 1320 ? &resultRef.toJSObject().as<PromiseObject>() 1321 : nullptr); 1322 1323 # ifdef DEBUG 1324 auto resetReturnType = mozilla::MakeScopeExit([&suspenderObject]() { 1325 suspenderObject->setSuspendingReturnType( 1326 SuspenderObject::ReturnType::Unknown); 1327 }); 1328 # endif 1329 1330 if (promise ? promise->state() == JS::PromiseState::Rejected 1331 : returnType == SuspenderObject::ReturnType::Exception) { 1332 // Promise was rejected or an exception was thrown, set pending exception 1333 // and fail. 1334 RootedValue reason( 1335 cx, promise ? promise->reason() : resultRef.get().toJSValue()); 1336 cx->setPendingException(reason, ShouldCaptureStack::Maybe); 1337 return nullptr; 1338 } 1339 1340 // The exception and rejection are handled above -- expect resolved promise. 1341 MOZ_ASSERT(promise->state() == JS::PromiseState::Fulfilled); 1342 RootedValue jsValue(cx, promise->value()); 1343 1344 // Construct the results object. 1345 Rooted<WasmStructObject*> results( 1346 cx, instance->constantStructNewDefault( 1347 cx, SuspendingFunctionModuleFactory::ResultsTypeIndex)); 1348 const FieldTypeVector& fields = results->typeDef().structType().fields_; 1349 1350 if (fields.length() > 0) { 1351 // The struct object is constructed based on returns of exported function. 1352 // It is the only way we can get ValType for Val::fromJSValue call. 1353 const wasm::FuncType& sig = instance->codeMeta().getFuncType( 1354 SuspendingFunctionModuleFactory::ExportedFnIndex); 1355 1356 if (fields.length() == 1) { 1357 RootedVal val(cx); 1358 MOZ_ASSERT(sig.result(0).storageType() == fields[0].type); 1359 if (!Val::fromJSValue(cx, sig.result(0), jsValue, &val)) { 1360 return nullptr; 1361 } 1362 results->storeVal(val, 0); 1363 } else { 1364 // The multi-value result is wrapped into ArrayObject/Iterable. 1365 Rooted<ArrayObject*> array(cx); 1366 if (!IterableToArray(cx, jsValue, &array)) { 1367 return nullptr; 1368 } 1369 if (fields.length() != array->length()) { 1370 UniqueChars expected(JS_smprintf("%zu", fields.length())); 1371 UniqueChars got(JS_smprintf("%u", array->length())); 1372 if (!expected || !got) { 1373 ReportOutOfMemory(cx); 1374 return nullptr; 1375 } 1376 1377 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 1378 JSMSG_WASM_WRONG_NUMBER_OF_VALUES, 1379 expected.get(), got.get()); 1380 return nullptr; 1381 } 1382 1383 for (size_t i = 0; i < fields.length(); i++) { 1384 RootedVal val(cx); 1385 RootedValue v(cx, array->getDenseElement(i)); 1386 MOZ_ASSERT(sig.result(i).storageType() == fields[i].type); 1387 if (!Val::fromJSValue(cx, sig.result(i), v, &val)) { 1388 return nullptr; 1389 } 1390 results->storeVal(val, i); 1391 } 1392 } 1393 } 1394 return results; 1395 } 1396 1397 // Collects returned suspending promising, and registers callbacks to 1398 // react on it using WasmPISuspendTaskContinue. 1399 // Seen as $builtin.add-promise-reactions to wasm. 1400 void* AddPromiseReactions(Instance* instance, SuspenderObject* suspender, 1401 void* result, JSFunction* continueOnSuspendable) { 1402 MOZ_ASSERT(SASigAddPromiseReactions.failureMode == 1403 FailureMode::FailOnInvalidRef); 1404 JSContext* cx = instance->cx(); 1405 1406 RootedAnyRef resultRef(cx, AnyRef::fromCompiledCode(result)); 1407 RootedValue resultValue(cx, resultRef.get().toJSValue()); 1408 Rooted<SuspenderObject*> suspenderObject(cx, suspender); 1409 RootedFunction fn(cx, continueOnSuspendable); 1410 1411 // Wrap a promise. 1412 RootedObject promiseConstructor(cx, GetPromiseConstructor(cx)); 1413 RootedObject promiseObj(cx, 1414 PromiseResolve(cx, promiseConstructor, resultValue)); 1415 if (!promiseObj) { 1416 return AnyRef::invalid().forCompiledCode(); 1417 } 1418 Rooted<PromiseObject*> promiseObject(cx, &promiseObj->as<PromiseObject>()); 1419 1420 suspenderObject->setSuspendingReturnType( 1421 SuspenderObject::ReturnType::Promise); 1422 1423 // Add promise reactions 1424 RootedFunction then_( 1425 cx, NewNativeFunction(cx, WasmPISuspendTaskContinue, 1, nullptr, 1426 gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); 1427 then_->initExtendedSlot(SUSPENDER_SLOT, ObjectValue(*suspenderObject)); 1428 then_->initExtendedSlot(CONTINUE_ON_SUSPENDABLE_SLOT, ObjectValue(*fn)); 1429 then_->initExtendedSlot(PROMISE_SLOT, ObjectValue(*promiseObject)); 1430 if (!JS::AddPromiseReactions(cx, promiseObject, then_, then_)) { 1431 return AnyRef::invalid().forCompiledCode(); 1432 } 1433 return AnyRef::fromJSObject(*promiseObject).forCompiledCode(); 1434 } 1435 1436 // Changes exit stack frame pointers to suspendable stack and recast exception 1437 // to wasm reference. Seen as $builtin.forward-exn-to-suspended to wasm. 1438 void* ForwardExceptionToSuspended(Instance* instance, 1439 SuspenderObject* suspender, void* exception) { 1440 MOZ_ASSERT(SASigForwardExceptionToSuspended.failureMode == 1441 FailureMode::Infallible); 1442 1443 suspender->forwardToSuspendable(); 1444 suspender->setSuspendingReturnType(SuspenderObject::ReturnType::Exception); 1445 return exception; 1446 } 1447 1448 // Resolves the promise using results packed by wasm. 1449 // Seen as $builtin.set-promising-promise-results to wasm. 1450 int32_t SetPromisingPromiseResults(Instance* instance, 1451 SuspenderObject* suspender, 1452 WasmStructObject* results) { 1453 MOZ_ASSERT(SASigSetPromisingPromiseResults.failureMode == 1454 FailureMode::FailOnNegI32); 1455 JSContext* cx = instance->cx(); 1456 Rooted<WasmStructObject*> res(cx, results); 1457 Rooted<SuspenderObject*> suspenderObject(cx, suspender); 1458 RootedObject promise(cx, suspenderObject->promisingPromise()); 1459 1460 const StructType& resultType = res->typeDef().structType(); 1461 RootedValue val(cx); 1462 // Unbox the result value from the struct, if any. 1463 switch (resultType.fields_.length()) { 1464 case 0: 1465 break; 1466 case 1: { 1467 if (!res->getField(cx, /*index=*/0, &val)) { 1468 return false; 1469 } 1470 } break; 1471 default: { 1472 Rooted<ArrayObject*> array(cx, NewDenseEmptyArray(cx)); 1473 if (!array) { 1474 return false; 1475 } 1476 for (size_t i = 0; i < resultType.fields_.length(); i++) { 1477 RootedValue item(cx); 1478 if (!res->getField(cx, i, &item)) { 1479 return false; 1480 } 1481 if (!NewbornArrayPush(cx, array, item)) { 1482 return false; 1483 } 1484 } 1485 val.setObject(*array); 1486 } break; 1487 } 1488 ResolvePromise(cx, promise, val); 1489 return 0; 1490 } 1491 1492 void UpdateSuspenderState(Instance* instance, SuspenderObject* suspender, 1493 UpdateSuspenderStateAction action) { 1494 MOZ_ASSERT(SASigUpdateSuspenderState.failureMode == FailureMode::Infallible); 1495 1496 JSContext* cx = instance->cx(); 1497 switch (action) { 1498 case UpdateSuspenderStateAction::Enter: 1499 suspender->enter(cx); 1500 break; 1501 case UpdateSuspenderStateAction::Suspend: 1502 suspender->suspend(cx); 1503 break; 1504 case UpdateSuspenderStateAction::Resume: 1505 suspender->resume(cx); 1506 break; 1507 case UpdateSuspenderStateAction::Leave: 1508 suspender->leave(cx); 1509 break; 1510 default: 1511 MOZ_CRASH(); 1512 } 1513 } 1514 1515 } // namespace js::wasm 1516 #endif // ENABLE_WASM_JSPI