diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:37 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:37 +0000 |
commit | a90a5cba08fdf6c0ceb95101c275108a152a3aed (patch) | |
tree | 532507288f3defd7f4dcf1af49698bcb76034855 /js/src/wasm | |
parent | Adding debian version 126.0.1-1. (diff) | |
download | firefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.tar.xz firefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.zip |
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/wasm')
40 files changed, 2982 insertions, 56 deletions
diff --git a/js/src/wasm/WasmBinary.h b/js/src/wasm/WasmBinary.h index da17a0a864..4176b3d8c2 100644 --- a/js/src/wasm/WasmBinary.h +++ b/js/src/wasm/WasmBinary.h @@ -709,7 +709,8 @@ inline bool Decoder::readPackedType(const TypeContext& types, *type = RefType::fromTypeCode(TypeCode(code), true); return true; } - case uint8_t(TypeCode::ExnRef): { + case uint8_t(TypeCode::ExnRef): + case uint8_t(TypeCode::NullExnRef): { if (!features.exnref) { return fail("exnref not enabled"); } @@ -792,7 +793,8 @@ inline bool Decoder::readHeapType(const TypeContext& types, case uint8_t(TypeCode::ExternRef): *type = RefType::fromTypeCode(TypeCode(code), nullable); return true; - case uint8_t(TypeCode::ExnRef): { + case uint8_t(TypeCode::ExnRef): + case uint8_t(TypeCode::NullExnRef): { if (!features.exnref) { return fail("exnref not enabled"); } diff --git a/js/src/wasm/WasmBuiltinModule.yaml b/js/src/wasm/WasmBuiltinModule.yaml index 755e0e5e74..eb04ecc7cf 100644 --- a/js/src/wasm/WasmBuiltinModule.yaml +++ b/js/src/wasm/WasmBuiltinModule.yaml @@ -285,7 +285,9 @@ export: fromCharCode params: - 'i32' - result: 'externref' + result: + code: 'extern' + nullable: false fail_mode: FailOnNullPtr uses_memory: false @@ -297,7 +299,9 @@ export: fromCodePoint params: - 'i32' - result: 'externref' + result: + code: 'extern' + nullable: false fail_mode: FailOnNullPtr uses_memory: false @@ -348,7 +352,9 @@ params: - 'externref' - 'externref' - result: 'externref' + result: + code: 'extern' + nullable: false fail_mode: FailOnNullPtr uses_memory: false @@ -362,7 +368,9 @@ - 'externref' - 'i32' - 'i32' - result: 'externref' + result: + code: 'extern' + nullable: false fail_mode: FailOnNullPtr uses_memory: false @@ -393,3 +401,55 @@ uses_memory: false #endif // ENABLE_WASM_JS_STRING_BUILTINS + +#if defined(ENABLE_WASM_JSPI) + +- op: CheckSuspender + symbolic_address: + name: CheckSuspender + type: Args_General2 + entry: CheckSuspender + export: suspenderCheck + params: + - 'externref' + result: 'externref' + fail_mode: FailOnNullPtr + uses_memory: false + +- op: CurrentSuspender + symbolic_address: + name: CurrentSuspender + type: Args_General_GeneralInt32 + entry: CurrentSuspender + export: currentSuspender + params: + - 'i32' + result: 'externref' + fail_mode: FailOnNullPtr + uses_memory: false + +- op: GetSuspendingPromiseResult + symbolic_address: + name: GetSuspendingPromiseResult + type: Args_General2 + entry: GetSuspendingPromiseResult + export: getSuspendingPromiseResult + params: + - 'externref' + result: 'anyref' + fail_mode: FailOnNullPtr + uses_memory: false + +- op: SetPromisingPromiseResults + symbolic_address: + name: SetPromisingPromiseResults + type: Args_Int32_GeneralGeneralGeneral + entry: SetPromisingPromiseResults + export: setPromisingPromiseResults + params: + - 'externref' + - 'anyref' + fail_mode: FailOnNegI32 + uses_memory: false + +#endif // ENABLE_WASM_JSPI diff --git a/js/src/wasm/WasmBuiltins.cpp b/js/src/wasm/WasmBuiltins.cpp index f0773583ac..342f55598f 100644 --- a/js/src/wasm/WasmBuiltins.cpp +++ b/js/src/wasm/WasmBuiltins.cpp @@ -19,6 +19,7 @@ #include "wasm/WasmBuiltins.h" #include "mozilla/Atomics.h" +#include "mozilla/ScopeExit.h" #include "fdlibm.h" #include "jslibmath.h" @@ -42,6 +43,7 @@ #include "wasm/WasmDebugFrame.h" #include "wasm/WasmGcObject.h" #include "wasm/WasmInstance.h" +#include "wasm/WasmPI.h" #include "wasm/WasmStubs.h" #include "debugger/DebugAPI-inl.h" @@ -404,6 +406,15 @@ const SymbolicAddressSignature SASigArrayCopy = { FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC) #undef VISIT_BUILTIN_FUNC +#ifdef ENABLE_WASM_JSPI +const SymbolicAddressSignature SASigUpdateSuspenderState = { + SymbolicAddress::UpdateSuspenderState, + _VOID, + _Infallible, + 3, + {_PTR, _PTR, _I32, _END}}; +#endif + } // namespace wasm } // namespace js @@ -660,12 +671,21 @@ bool wasm::HandleThrow(JSContext* cx, WasmFrameIter& iter, // WasmFrameIter iterates down wasm frames in the activation starting at // JitActivation::wasmExitFP(). Calling WasmFrameIter::startUnwinding pops // JitActivation::wasmExitFP() once each time WasmFrameIter is incremented, - // ultimately leaving exit FP null when the WasmFrameIter is done(). This - // is necessary to prevent a DebugFrame from being observed again after we - // just called onLeaveFrame (which would lead to the frame being re-added + // ultimately leaving no wasm exit FP when the WasmFrameIter is done(). This + // is necessary to prevent a wasm::DebugFrame from being observed again after + // we just called onLeaveFrame (which would lead to the frame being re-added // to the map of live frames, right as it becomes trash). MOZ_ASSERT(CallingActivation(cx) == iter.activation()); +#ifdef DEBUG + auto onExit = mozilla::MakeScopeExit([cx] { + MOZ_ASSERT(!cx->activation()->asJit()->isWasmTrapping(), + "unwinding clears the trapping state"); + MOZ_ASSERT(!cx->activation()->asJit()->hasWasmExitFP(), + "unwinding leaves no wasm exit fp"); + }); +#endif + MOZ_ASSERT(!iter.done()); iter.setUnwind(WasmFrameIter::Unwind::True); @@ -722,6 +742,7 @@ bool wasm::HandleThrow(JSContext* cx, WasmFrameIter& iter, if (activation->isWasmTrapping()) { activation->finishWasmTrap(); } + activation->setWasmExitFP(nullptr); return true; } @@ -761,9 +782,6 @@ bool wasm::HandleThrow(JSContext* cx, WasmFrameIter& iter, frame->leave(cx); } - MOZ_ASSERT(!cx->activation()->asJit()->isWasmTrapping(), - "unwinding clears the trapping state"); - // Assert that any pending exception escaping to non-wasm code is not a // wrapper exception object #ifdef DEBUG @@ -1504,6 +1522,13 @@ void* wasm::AddressOf(SymbolicAddress imm, ABIFunctionType* abiType) { MOZ_ASSERT(*abiType == ToABIType(SASigThrowException)); return FuncCast(Instance::throwException, *abiType); +#ifdef ENABLE_WASM_JSPI + case SymbolicAddress::UpdateSuspenderState: + *abiType = Args_Int32_GeneralGeneralInt32; + MOZ_ASSERT(*abiType == ToABIType(SASigUpdateSuspenderState)); + return FuncCast(UpdateSuspenderState, *abiType); +#endif + #ifdef WASM_CODEGEN_DEBUG case SymbolicAddress::PrintI32: *abiType = Args_General1; @@ -1691,6 +1716,9 @@ bool wasm::NeedsBuiltinThunk(SymbolicAddress sym) { case SymbolicAddress::sa_name: FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC) #undef VISIT_BUILTIN_FUNC +#ifdef ENABLE_WASM_JSPI + case SymbolicAddress::UpdateSuspenderState: +#endif return true; case SymbolicAddress::Limit: diff --git a/js/src/wasm/WasmBuiltins.h b/js/src/wasm/WasmBuiltins.h index ea6de48327..55f9b55a9c 100644 --- a/js/src/wasm/WasmBuiltins.h +++ b/js/src/wasm/WasmBuiltins.h @@ -146,8 +146,11 @@ enum class SymbolicAddress { #define VISIT_BUILTIN_FUNC(op, export, sa_name, ...) sa_name, FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC) #undef VISIT_BUILTIN_FUNC +#ifdef ENABLE_WASM_JSPI + UpdateSuspenderState, +#endif #ifdef WASM_CODEGEN_DEBUG - PrintI32, + PrintI32, PrintPtr, PrintF32, PrintF64, @@ -280,6 +283,7 @@ extern const SymbolicAddressSignature SASigArrayNewElem; extern const SymbolicAddressSignature SASigArrayInitData; extern const SymbolicAddressSignature SASigArrayInitElem; extern const SymbolicAddressSignature SASigArrayCopy; +extern const SymbolicAddressSignature SASigUpdateSuspenderState; #define VISIT_BUILTIN_FUNC(op, export, sa_name, ...) \ extern const SymbolicAddressSignature SASig##sa_name; FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC) diff --git a/js/src/wasm/WasmCode.h b/js/src/wasm/WasmCode.h index e03a2f596e..002e4c3846 100644 --- a/js/src/wasm/WasmCode.h +++ b/js/src/wasm/WasmCode.h @@ -364,6 +364,7 @@ struct MetadataCacheablePod { BuiltinModuleIds builtinModules; FeatureUsage featureUsage; bool filenameIsURL; + bool parsedBranchHints; uint32_t typeDefsOffsetStart; uint32_t memoriesOffsetStart; uint32_t tablesOffsetStart; @@ -372,15 +373,16 @@ struct MetadataCacheablePod { WASM_CHECK_CACHEABLE_POD(kind, instanceDataLength, startFuncIndex, nameCustomSectionIndex, builtinModules, featureUsage, - filenameIsURL, typeDefsOffsetStart, - memoriesOffsetStart, tablesOffsetStart, - tagsOffsetStart) + filenameIsURL, parsedBranchHints, + typeDefsOffsetStart, memoriesOffsetStart, + tablesOffsetStart, tagsOffsetStart) explicit MetadataCacheablePod(ModuleKind kind) : kind(kind), instanceDataLength(0), featureUsage(FeatureUsage::None), filenameIsURL(false), + parsedBranchHints(false), typeDefsOffsetStart(UINT32_MAX), memoriesOffsetStart(UINT32_MAX), tablesOffsetStart(UINT32_MAX), diff --git a/js/src/wasm/WasmCodegenTypes.h b/js/src/wasm/WasmCodegenTypes.h index 7e54ad15f7..c01f1d4d37 100644 --- a/js/src/wasm/WasmCodegenTypes.h +++ b/js/src/wasm/WasmCodegenTypes.h @@ -554,6 +554,7 @@ class CallSiteDesc { EnterFrame, // call to a enter frame handler LeaveFrame, // call to a leave frame handler CollapseFrame, // call to a leave frame handler during tail call + StackSwitch, // stack switch point Breakpoint // call to instruction breakpoint }; CallSiteDesc() : lineOrBytecode_(0), kind_(0) {} @@ -576,9 +577,10 @@ class CallSiteDesc { bool isIndirectCall() const { return kind() == CallSiteDesc::Indirect; } bool isFuncRefCall() const { return kind() == CallSiteDesc::FuncRef; } bool isReturnStub() const { return kind() == CallSiteDesc::ReturnStub; } + bool isStackSwitch() const { return kind() == CallSiteDesc::StackSwitch; } bool mightBeCrossInstance() const { return isImportCall() || isIndirectCall() || isFuncRefCall() || - isReturnStub(); + isReturnStub() || isStackSwitch(); } }; diff --git a/js/src/wasm/WasmCompile.cpp b/js/src/wasm/WasmCompile.cpp index fbf4df3e71..516863744f 100644 --- a/js/src/wasm/WasmCompile.cpp +++ b/js/src/wasm/WasmCompile.cpp @@ -170,6 +170,16 @@ FeatureArgs FeatureArgs::build(JSContext* cx, const FeatureOptions& options) { if (features.jsStringBuiltins) { features.builtinModules.jsString = options.jsStringBuiltins; } +#ifdef ENABLE_WASM_GC + if (options.requireGC) { + features.gc = true; + } +#endif +#ifdef ENABLE_WASM_TAIL_CALLS + if (options.requireTailCalls) { + features.tailCalls = true; + } +#endif return features; } diff --git a/js/src/wasm/WasmCompileArgs.h b/js/src/wasm/WasmCompileArgs.h index af85026b93..74b03bfe03 100644 --- a/js/src/wasm/WasmCompileArgs.h +++ b/js/src/wasm/WasmCompileArgs.h @@ -74,7 +74,19 @@ class Tiers { // available under prefs.) struct FeatureOptions { - FeatureOptions() : isBuiltinModule(false), jsStringBuiltins(false) {} + FeatureOptions() + : isBuiltinModule(false), + jsStringBuiltins(false) +#ifdef ENABLE_WASM_GC + , + requireGC(false) +#endif +#ifdef ENABLE_WASM_TAIL_CALLS + , + requireTailCalls(false) +#endif + { + } // Enables builtin module opcodes, only set in WasmBuiltinModule.cpp. bool isBuiltinModule; @@ -82,6 +94,15 @@ struct FeatureOptions { // is also enabled. bool jsStringBuiltins; +#ifdef ENABLE_WASM_GC + // Enable GC support. + bool requireGC; +#endif +#ifdef ENABLE_WASM_TAIL_CALLS + // Enable tail-calls support. + bool requireTailCalls; +#endif + // Parse the compile options bag. [[nodiscard]] bool init(JSContext* cx, HandleValue val); }; diff --git a/js/src/wasm/WasmConstants.h b/js/src/wasm/WasmConstants.h index 0ce741b2d0..8d339d5cf3 100644 --- a/js/src/wasm/WasmConstants.h +++ b/js/src/wasm/WasmConstants.h @@ -94,6 +94,9 @@ enum class TypeCode { // A null reference in the func hierarchy. NullFuncRef = 0x73, // SLEB128(-0x0D) + // A null reference in the exn hierarchy. + NullExnRef = 0x74, // SLEB128(-0x0C) + // A reference to any struct value. StructRef = 0x6b, // SLEB128(-0x15) @@ -994,6 +997,19 @@ struct BuiltinModuleIds { WASM_DECLARE_CACHEABLE_POD(BuiltinModuleIds) +enum class StackSwitchKind { + SwitchToSuspendable, + SwitchToMain, + ContinueOnSuspendable, +}; + +enum class UpdateSuspenderStateAction { + Enter, + Suspend, + Resume, + Leave, +}; + enum class MozOp { // ------------------------------------------------------------------------ // These operators are emitted internally when compiling asm.js and are @@ -1041,6 +1057,8 @@ enum class MozOp { // particular operation id. See BuiltinModuleFuncId above. CallBuiltinModuleFunc, + StackSwitch, + Limit }; @@ -1092,6 +1110,7 @@ struct OpBytes { static const char NameSectionName[] = "name"; static const char SourceMappingURLSectionName[] = "sourceMappingURL"; +static const char BranchHintingSectionName[] = "metadata.code.branch_hint"; enum class NameType { Module = 0, Function = 1, Local = 2 }; @@ -1151,6 +1170,7 @@ static_assert(uint64_t(MaxArrayPayloadBytes) < static const unsigned MaxTryTableCatches = 10000; static const unsigned MaxBrTableElems = 1000000; static const unsigned MaxCodeSectionBytes = MaxModuleBytes; +static const unsigned MaxBranchHintValue = 2; // 512KiB should be enough, considering how Rabaldr uses the stack and // what the standard limits are: @@ -1163,6 +1183,21 @@ static const unsigned MaxCodeSectionBytes = MaxModuleBytes; static const unsigned MaxFrameSize = 512 * 1024; +// Limit for the amount of stacks present in the runtime. +static const size_t SuspendableStacksMaxCount = 100; + +// Max size of an allocated stack. +static const size_t SuspendableStackSize = 0x100000; + +// Size of additional space at the top of a suspendable stack. +// The space is allocated to C++ handlers such as error/trap handlers, +// or stack snapshots utilities. +static const size_t SuspendableRedZoneSize = 0x6000; + +// Total size of a suspendable stack to be reserved. +static constexpr size_t SuspendableStackPlusRedZoneSize = + SuspendableStackSize + SuspendableRedZoneSize; + // Asserted by Decoder::readVarU32. static const unsigned MaxVarU32DecodedBytes = 5; diff --git a/js/src/wasm/WasmContext.h b/js/src/wasm/WasmContext.h index 5064401a81..873f98f1a2 100644 --- a/js/src/wasm/WasmContext.h +++ b/js/src/wasm/WasmContext.h @@ -19,23 +19,61 @@ #ifndef wasm_context_h #define wasm_context_h -namespace js { -namespace wasm { +#include "mozilla/DoublyLinkedList.h" + +namespace js::wasm { + +#ifdef ENABLE_WASM_JSPI + +class SuspenderObject; +class SuspenderObjectData; + +class SuspenderContext { + private: + HeapPtr<SuspenderObject*> activeSuspender_; + // Using double-linked list to avoid allocation in the JIT code. + mozilla::DoublyLinkedList<SuspenderObjectData> suspendedStacks_; + + public: + SuspenderContext(); + ~SuspenderContext(); + SuspenderObject* activeSuspender(); + void setActiveSuspender(SuspenderObject* obj); + void trace(JSTracer* trc); + void traceRoots(JSTracer* trc); + + friend class SuspenderObject; +}; +#endif // ENABLE_WASM_JSPI // wasm::Context lives in JSContext and contains the wasm-related per-context // state. class Context { public: - Context() : triedToInstallSignalHandlers(false), haveSignalHandlers(false) {} + Context() + : triedToInstallSignalHandlers(false), + haveSignalHandlers(false) +#ifdef ENABLE_WASM_JSPI + , + suspendableStackLimit(JS::NativeStackLimitMin), + suspendableStacksCount(0) +#endif + { + } // Used by wasm::EnsureThreadSignalHandlers(cx) to install thread signal // handlers once per JSContext/thread. bool triedToInstallSignalHandlers; bool haveSignalHandlers; + +#ifdef ENABLE_WASM_JSPI + JS::NativeStackLimit suspendableStackLimit; + mozilla::Atomic<uint32_t> suspendableStacksCount; + SuspenderContext promiseIntegration; +#endif }; -} // namespace wasm -} // namespace js +} // namespace js::wasm #endif // wasm_context_h diff --git a/js/src/wasm/WasmDump.cpp b/js/src/wasm/WasmDump.cpp index 2af9ebece2..f92125077a 100644 --- a/js/src/wasm/WasmDump.cpp +++ b/js/src/wasm/WasmDump.cpp @@ -78,6 +78,9 @@ void wasm::Dump(RefType type, GenericPrinter& out) { case RefType::NoFunc: literal = "nullfuncref"; break; + case RefType::NoExn: + literal = "nullexn"; + break; case RefType::NoExtern: literal = "nullexternref"; break; @@ -122,6 +125,9 @@ void wasm::Dump(RefType type, GenericPrinter& out) { case RefType::NoFunc: heapType = "nofunc"; break; + case RefType::NoExn: + heapType = "noexn"; + break; case RefType::NoExtern: heapType = "noextern"; break; diff --git a/js/src/wasm/WasmFrameIter.cpp b/js/src/wasm/WasmFrameIter.cpp index 90555720da..e9f27bbda0 100644 --- a/js/src/wasm/WasmFrameIter.cpp +++ b/js/src/wasm/WasmFrameIter.cpp @@ -26,6 +26,7 @@ #include "wasm/WasmDebugFrame.h" #include "wasm/WasmInstance.h" #include "wasm/WasmInstanceData.h" +#include "wasm/WasmPI.h" #include "wasm/WasmStubs.h" #include "jit/MacroAssembler-inl.h" @@ -65,7 +66,8 @@ WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp) unwind_(Unwind::False), unwoundAddressOfReturnAddress_(nullptr), resumePCinCurrentFrame_(nullptr), - failedUnwindSignatureMismatch_(false) { + failedUnwindSignatureMismatch_(false), + stackSwitched_(false) { MOZ_ASSERT(fp_); instance_ = GetNearestEffectiveInstance(fp_); @@ -88,6 +90,19 @@ WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp) lineOrBytecode_ = trapData.bytecodeOffset; failedUnwindSignatureMismatch_ = trapData.failedUnwindSignatureMismatch; +#ifdef ENABLE_WASM_TAIL_CALLS + // The debugEnabled() relies on valid value of resumePCinCurrentFrame_ + // to identify DebugFrame. Normally this field is updated at popFrame(). + // The only case when this can happend is during IndirectCallBadSig + // trapping and stack unwinding. The top frame will never be at ReturnStub + // callsite, except during IndirectCallBadSig unwinding. + const CallSite* site = code_->lookupCallSite(unwoundPC); + if (site && site->kind() == CallSite::ReturnStub) { + MOZ_ASSERT(trapData.trap == Trap::IndirectCallBadSig); + resumePCinCurrentFrame_ = (uint8_t*)unwoundPC; + } +#endif + MOZ_ASSERT(!done()); return; } @@ -102,6 +117,39 @@ WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp) MOZ_ASSERT(!done() || unwoundCallerFP_); } +WasmFrameIter::WasmFrameIter(FrameWithInstances* fp, void* returnAddress) + : activation_(nullptr), + code_(nullptr), + codeRange_(nullptr), + lineOrBytecode_(0), + fp_(fp), + instance_(fp->calleeInstance()), + unwoundCallerFP_(nullptr), + unwind_(Unwind::False), + unwoundAddressOfReturnAddress_(nullptr), + resumePCinCurrentFrame_((uint8_t*)returnAddress), + failedUnwindSignatureMismatch_(false), + stackSwitched_(false) { + // Specialized implementation to avoid popFrame() interation. + // It is expected that the iterator starts at a callsite that is in + // the function body and has instance reference. + code_ = LookupCode(returnAddress, &codeRange_); + MOZ_ASSERT(code_ && codeRange_ && codeRange_->kind() == CodeRange::Function); + + const CallSite* callsite = code_->lookupCallSite(returnAddress); + MOZ_ASSERT(callsite && callsite->mightBeCrossInstance()); + +#ifdef ENABLE_WASM_JSPI + stackSwitched_ = callsite->isStackSwitch(); +#endif + + MOZ_ASSERT(code_ == &instance_->code()); + lineOrBytecode_ = callsite->lineOrBytecode(); + failedUnwindSignatureMismatch_ = false; + + MOZ_ASSERT(!done()); +} + bool WasmFrameIter::done() const { MOZ_ASSERT(!!fp_ == !!code_); MOZ_ASSERT(!!fp_ == !!codeRange_); @@ -145,6 +193,9 @@ static inline void AssertDirectJitCall(const void* fp) { void WasmFrameIter::popFrame() { uint8_t* returnAddress = fp_->returnAddress(); code_ = LookupCode(returnAddress, &codeRange_); +#ifdef ENABLE_WASM_JSPI + stackSwitched_ = false; +#endif if (!code_) { // This is a direct call from the jit into the wasm function's body. The @@ -241,6 +292,13 @@ void WasmFrameIter::popFrame() { instance_ = ExtractCallerInstanceFromFrameWithInstances(prevFP); } +#ifdef ENABLE_WASM_JSPI + stackSwitched_ = callsite->isStackSwitch(); + if (stackSwitched_ && unwind_ == Unwind::True) { + wasm::UnwindStackSwitch(activation_->cx()); + } +#endif + MOZ_ASSERT(code_ == &instance()->code()); lineOrBytecode_ = callsite->lineOrBytecode(); failedUnwindSignatureMismatch_ = false; @@ -1811,6 +1869,10 @@ static const char* ThunkedNativeToDescription(SymbolicAddress func) { return "call to native " #op " builtin (in wasm)"; FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC) #undef VISIT_BUILTIN_FUNC +#ifdef ENABLE_WASM_JSPI + case SymbolicAddress::UpdateSuspenderState: + return "call to native update suspender state util"; +#endif #ifdef WASM_CODEGEN_DEBUG case SymbolicAddress::PrintI32: case SymbolicAddress::PrintPtr: diff --git a/js/src/wasm/WasmFrameIter.h b/js/src/wasm/WasmFrameIter.h index 59590b1b2a..a6fda0b6ec 100644 --- a/js/src/wasm/WasmFrameIter.h +++ b/js/src/wasm/WasmFrameIter.h @@ -45,6 +45,7 @@ struct CallableOffsets; struct FuncOffsets; struct Offsets; class Frame; +class FrameWithInstances; using RegisterState = JS::ProfilingFrameIterator::RegisterState; @@ -73,12 +74,14 @@ class WasmFrameIter { uint8_t* resumePCinCurrentFrame_; // See wasm::TrapData for more information. bool failedUnwindSignatureMismatch_; + bool stackSwitched_; void popFrame(); public: // See comment above this class definition. explicit WasmFrameIter(jit::JitActivation* activation, Frame* fp = nullptr); + WasmFrameIter(FrameWithInstances* fp, void* returnAddress); const jit::JitActivation* activation() const { return activation_; } void setUnwind(Unwind unwind) { unwind_ = unwind; } void operator++(); @@ -99,6 +102,7 @@ class WasmFrameIter { uint8_t* unwoundCallerFP() const { return unwoundCallerFP_; } Frame* frame() const { return fp_; } Instance* instance() const { return instance_; } + bool stackSwitched() const { return stackSwitched_; } // Returns the address of the next instruction that will execute in this // frame, once control returns to this frame. diff --git a/js/src/wasm/WasmGcObject.cpp b/js/src/wasm/WasmGcObject.cpp index 3ccb08d381..4bf574aa12 100644 --- a/js/src/wasm/WasmGcObject.cpp +++ b/js/src/wasm/WasmGcObject.cpp @@ -516,6 +516,15 @@ js::gc::AllocKind js::WasmStructObject::allocKindForTypeDef( return gc::GetGCObjectKindForBytes(nbytes); } +bool WasmStructObject::getField(JSContext* cx, uint32_t index, + MutableHandle<Value> val) { + const StructType& resultType = typeDef().structType(); + MOZ_ASSERT(index <= resultType.fields_.length()); + const StructField& field = resultType.fields_[index]; + StorageType ty = field.type.storageType(); + return ToJSValue(cx, fieldOffsetToAddress(ty, field.offset), ty, val); +} + /* static */ void WasmStructObject::obj_trace(JSTracer* trc, JSObject* object) { WasmStructObject& structObj = object->as<WasmStructObject>(); diff --git a/js/src/wasm/WasmGcObject.h b/js/src/wasm/WasmGcObject.h index 45569c3061..d88061c238 100644 --- a/js/src/wasm/WasmGcObject.h +++ b/js/src/wasm/WasmGcObject.h @@ -408,6 +408,9 @@ class WasmStructObject : public WasmGcObject, inline uint8_t* fieldOffsetToAddress(StorageType fieldType, uint32_t fieldOffset); + // Gets JS Value of the structure field. + bool getField(JSContext* cx, uint32_t index, MutableHandle<Value> val); + // JIT accessors static const uint32_t inlineDataAlignment = 8; static constexpr size_t offsetOfOutlineData() { diff --git a/js/src/wasm/WasmGenerator.cpp b/js/src/wasm/WasmGenerator.cpp index 338812e1d6..d755c87519 100644 --- a/js/src/wasm/WasmGenerator.cpp +++ b/js/src/wasm/WasmGenerator.cpp @@ -470,6 +470,7 @@ bool ModuleGenerator::linkCallSites() { case CallSiteDesc::FuncRef: case CallSiteDesc::FuncRefFast: case CallSiteDesc::ReturnStub: + case CallSiteDesc::StackSwitch: break; case CallSiteDesc::ReturnFunc: case CallSiteDesc::Func: { @@ -1135,6 +1136,7 @@ SharedMetadata ModuleGenerator::finishMetadata(const Bytes& bytecode) { metadata_->nameCustomSectionIndex = moduleEnv_->nameCustomSectionIndex; metadata_->moduleName = moduleEnv_->moduleName; metadata_->funcNames = std::move(moduleEnv_->funcNames); + metadata_->parsedBranchHints = moduleEnv_->parsedBranchHints; // Copy over additional debug information. diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp index 606601581d..9183715ca7 100644 --- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -53,11 +53,13 @@ #include "wasm/WasmCode.h" #include "wasm/WasmDebug.h" #include "wasm/WasmDebugFrame.h" +#include "wasm/WasmFeatures.h" #include "wasm/WasmInitExpr.h" #include "wasm/WasmJS.h" #include "wasm/WasmMemory.h" #include "wasm/WasmModule.h" #include "wasm/WasmModuleTypes.h" +#include "wasm/WasmPI.h" #include "wasm/WasmStubs.h" #include "wasm/WasmTypeDef.h" #include "wasm/WasmValType.h" @@ -307,6 +309,13 @@ bool Instance::callImport(JSContext* cx, uint32_t funcImportIndex, return true; } +#ifdef ENABLE_WASM_JSPI + // Disable jit exit optimization when JSPI is enabled. + if (JSPromiseIntegrationAvailable(cx)) { + return true; + } +#endif + // The import may already have become optimized. for (auto t : code().tiers()) { void* jitExitCode = codeBase(t) + fi.jitExitCodeOffset(); @@ -346,6 +355,11 @@ bool Instance::callImport(JSContext* cx, uint32_t funcImportIndex, Instance::callImport_general(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv) { JSContext* cx = instance->cx(); +#ifdef ENABLE_WASM_JSPI + if (IsSuspendableStackActive(cx)) { + return CallImportOnMainThread(cx, instance, funcImportIndex, argc, argv); + } +#endif return instance->callImport(cx, funcImportIndex, argc, argv); } @@ -2373,6 +2387,23 @@ bool Instance::init(JSContext* cx, const JSObjectVector& funcImports, Tier callerTier = code_->bestTier(); for (size_t i = 0; i < metadata(callerTier).funcImports.length(); i++) { JSObject* f = funcImports[i]; + +#ifdef ENABLE_WASM_JSPI + if (JSObject* suspendingObject = MaybeUnwrapSuspendingObject(f)) { + // Compile suspending function Wasm wrapper. + const FuncImport& fi = metadata(callerTier).funcImports[i]; + const FuncType& funcType = metadata().getFuncImportType(fi); + RootedObject wrapped(cx, suspendingObject); + RootedFunction wrapper( + cx, WasmSuspendingFunctionCreate(cx, wrapped, funcType)); + if (!wrapper) { + return false; + } + MOZ_ASSERT(IsWasmExportedFunction(wrapper)); + f = wrapper; + } +#endif + MOZ_ASSERT(f->isCallable()); const FuncImport& fi = metadata(callerTier).funcImports[i]; const FuncType& funcType = metadata().getFuncImportType(fi); @@ -2617,9 +2648,27 @@ bool Instance::isInterrupted() const { void Instance::resetInterrupt(JSContext* cx) { interrupt_ = false; +#ifdef ENABLE_WASM_JSPI + if (cx->wasm().suspendableStackLimit != JS::NativeStackLimitMin) { + stackLimit_ = cx->wasm().suspendableStackLimit; + return; + } +#endif stackLimit_ = cx->stackLimitForJitCode(JS::StackForUntrustedScript); } +void Instance::setTemporaryStackLimit(JS::NativeStackLimit limit) { + if (!isInterrupted()) { + stackLimit_ = limit; + } +} + +void Instance::resetTemporaryStackLimit(JSContext* cx) { + if (!isInterrupted()) { + stackLimit_ = cx->stackLimitForJitCode(JS::StackForUntrustedScript); + } +} + bool Instance::debugFilter(uint32_t funcIndex) const { return (debugFilter_[funcIndex / 32] >> funcIndex % 32) & 1; } diff --git a/js/src/wasm/WasmInstance.h b/js/src/wasm/WasmInstance.h index 0e4f9745b7..0f9960133a 100644 --- a/js/src/wasm/WasmInstance.h +++ b/js/src/wasm/WasmInstance.h @@ -217,6 +217,21 @@ class alignas(16) Instance { TableInstanceData& tableInstanceData(uint32_t tableIndex) const; TagInstanceData& tagInstanceData(uint32_t tagIndex) const; +#ifdef ENABLE_WASM_JSPI + public: + struct WasmJSPICallImportData { + Instance* instance; + int32_t funcImportIndex; + int32_t argc; + uint64_t* argv; + static bool Call(WasmJSPICallImportData* data); + }; + + private: + bool isImportAllowedOnSuspendableStack(JSContext* cx, + int32_t funcImportIndex); +#endif + // Only WasmInstanceObject can call the private trace function. friend class js::WasmInstanceObject; void tracePrivate(JSTracer* trc); @@ -342,6 +357,9 @@ class alignas(16) Instance { bool isInterrupted() const; void resetInterrupt(JSContext* cx); + void setTemporaryStackLimit(JS::NativeStackLimit limit); + void resetTemporaryStackLimit(JSContext* cx); + bool debugFilter(uint32_t funcIndex) const; void setDebugFilter(uint32_t funcIndex, bool value); diff --git a/js/src/wasm/WasmIonCompile.cpp b/js/src/wasm/WasmIonCompile.cpp index 0568a95804..b8039792ae 100644 --- a/js/src/wasm/WasmIonCompile.cpp +++ b/js/src/wasm/WasmIonCompile.cpp @@ -235,11 +235,18 @@ class FunctionCompiler { }; using ControlFlowPatchVector = Vector<ControlFlowPatch, 0, SystemAllocPolicy>; - using ControlFlowPatchVectorVector = - Vector<ControlFlowPatchVector, 0, SystemAllocPolicy>; + + struct PendingBlockTarget { + ControlFlowPatchVector patches; + BranchHint hint = BranchHint::Invalid; + }; + + using PendingBlockTargetVector = + Vector<PendingBlockTarget, 0, SystemAllocPolicy>; const ModuleEnvironment& moduleEnv_; IonOpIter iter_; + uint32_t functionBodyOffset_; const FuncCompileInput& func_; const ValTypeVector& locals_; size_t lastReadCallSite_; @@ -254,7 +261,7 @@ class FunctionCompiler { uint32_t loopDepth_; uint32_t blockDepth_; - ControlFlowPatchVectorVector blockPatches_; + PendingBlockTargetVector pendingBlocks_; // Control flow patches created by `delegate` instructions that target the // outermost label of this function. These will be bound to a pad that will // do a rethrow in `emitBodyDelegateThrowPad`. @@ -276,6 +283,7 @@ class FunctionCompiler { MIRGenerator& mirGen, TryNoteVector& tryNotes) : moduleEnv_(moduleEnv), iter_(moduleEnv, decoder), + functionBodyOffset_(decoder.beginOffset()), func_(func), locals_(locals), lastReadCallSite_(0), @@ -294,6 +302,9 @@ class FunctionCompiler { const ModuleEnvironment& moduleEnv() const { return moduleEnv_; } IonOpIter& iter() { return iter_; } + uint32_t relativeBytecodeOffset() { + return readBytecodeOffset() - functionBodyOffset_; + } TempAllocator& alloc() const { return alloc_; } // FIXME(1401675): Replace with BlockType. uint32_t funcIndex() const { return func_.index; } @@ -301,6 +312,7 @@ class FunctionCompiler { return *moduleEnv_.funcs[func_.index].type; } + MBasicBlock* getCurBlock() const { return curBlock_; } BytecodeOffset bytecodeOffset() const { return iter_.bytecodeOffset(); } BytecodeOffset bytecodeIfNotAsmJS() const { return moduleEnv_.isAsmJS() ? BytecodeOffset() : iter_.bytecodeOffset(); @@ -384,8 +396,8 @@ class FunctionCompiler { MOZ_ASSERT(loopDepth_ == 0); MOZ_ASSERT(blockDepth_ == 0); #ifdef DEBUG - for (ControlFlowPatchVector& patches : blockPatches_) { - MOZ_ASSERT(patches.empty()); + for (PendingBlockTarget& targets : pendingBlocks_) { + MOZ_ASSERT(targets.patches.empty()); } #endif MOZ_ASSERT(inDeadCode()); @@ -2447,6 +2459,31 @@ class FunctionCompiler { return collectUnaryCallResult(builtin.retType, def); } + [[nodiscard]] bool stackSwitch(MDefinition* suspender, MDefinition* fn, + MDefinition* data, StackSwitchKind kind) { + MOZ_ASSERT(!inDeadCode()); + + MInstruction* ins; + switch (kind) { + case StackSwitchKind::SwitchToMain: + ins = MWasmStackSwitchToMain::New(alloc(), suspender, fn, data); + break; + case StackSwitchKind::SwitchToSuspendable: + ins = MWasmStackSwitchToSuspendable::New(alloc(), suspender, fn, data); + break; + case StackSwitchKind::ContinueOnSuspendable: + ins = MWasmStackContinueOnSuspendable::New(alloc(), suspender); + break; + } + if (!ins) { + return false; + } + + curBlock_->add(ins); + + return true; + } + #ifdef ENABLE_WASM_GC [[nodiscard]] bool callRef(const FuncType& funcType, MDefinition* ref, uint32_t lineOrBytecode, @@ -2682,8 +2719,8 @@ class FunctionCompiler { } [[nodiscard]] bool startBlock() { - MOZ_ASSERT_IF(blockDepth_ < blockPatches_.length(), - blockPatches_[blockDepth_].empty()); + MOZ_ASSERT_IF(blockDepth_ < pendingBlocks_.length(), + pendingBlocks_[blockDepth_].patches.empty()); blockDepth_++; return true; } @@ -2769,8 +2806,8 @@ class FunctionCompiler { } // Fix up phis stored in the slots Vector of pending blocks. - for (ControlFlowPatchVector& patches : blockPatches_) { - for (ControlFlowPatch& p : patches) { + for (PendingBlockTarget& pendingBlockTarget : pendingBlocks_) { + for (ControlFlowPatch& p : pendingBlockTarget.patches) { MBasicBlock* block = p.ins->block(); if (block->loopDepth() >= loopEntry->loopDepth()) { fixupRedundantPhis(block); @@ -2836,8 +2873,8 @@ class FunctionCompiler { if (!loopHeader) { MOZ_ASSERT(inDeadCode()); - MOZ_ASSERT(headerLabel >= blockPatches_.length() || - blockPatches_[headerLabel].empty()); + MOZ_ASSERT(headerLabel >= pendingBlocks_.length() || + pendingBlocks_[headerLabel].patches.empty()); blockDepth_--; loopDepth_--; return true; @@ -2896,17 +2933,20 @@ class FunctionCompiler { return inDeadCode() || popPushedDefs(loopResults); } - [[nodiscard]] bool addControlFlowPatch(MControlInstruction* ins, - uint32_t relative, uint32_t index) { + [[nodiscard]] bool addControlFlowPatch( + MControlInstruction* ins, uint32_t relative, uint32_t index, + BranchHint branchHint = BranchHint::Invalid) { MOZ_ASSERT(relative < blockDepth_); uint32_t absolute = blockDepth_ - 1 - relative; - if (absolute >= blockPatches_.length() && - !blockPatches_.resize(absolute + 1)) { + if (absolute >= pendingBlocks_.length() && + !pendingBlocks_.resize(absolute + 1)) { return false; } - return blockPatches_[absolute].append(ControlFlowPatch(ins, index)); + pendingBlocks_[absolute].hint = branchHint; + return pendingBlocks_[absolute].patches.append( + ControlFlowPatch(ins, index)); } [[nodiscard]] bool br(uint32_t relativeDepth, const DefVector& values) { @@ -2929,7 +2969,7 @@ class FunctionCompiler { } [[nodiscard]] bool brIf(uint32_t relativeDepth, const DefVector& values, - MDefinition* condition) { + MDefinition* condition, BranchHint branchHint) { if (inDeadCode()) { return true; } @@ -2940,7 +2980,8 @@ class FunctionCompiler { } MTest* test = MTest::New(alloc(), condition, nullptr, joinBlock); - if (!addControlFlowPatch(test, relativeDepth, MTest::TrueBranchIndex)) { + if (!addControlFlowPatch(test, relativeDepth, MTest::TrueBranchIndex, + branchHint)) { return false; } @@ -2950,6 +2991,7 @@ class FunctionCompiler { curBlock_->end(test); curBlock_ = joinBlock; + return true; } @@ -4785,11 +4827,12 @@ class FunctionCompiler { } [[nodiscard]] bool bindBranches(uint32_t absolute, DefVector* defs) { - if (absolute >= blockPatches_.length() || blockPatches_[absolute].empty()) { + if (absolute >= pendingBlocks_.length() || + pendingBlocks_[absolute].patches.empty()) { return inDeadCode() || popPushedDefs(defs); } - ControlFlowPatchVector& patches = blockPatches_[absolute]; + ControlFlowPatchVector& patches = pendingBlocks_[absolute].patches; MControlInstruction* ins = patches[0].ins; MBasicBlock* pred = ins->block(); @@ -4798,6 +4841,11 @@ class FunctionCompiler { return false; } + // Use branch hinting information if any. + if (pendingBlocks_[absolute].hint != BranchHint::Invalid) { + join->setBranchHinting(pendingBlocks_[absolute].hint); + } + pred->mark(); ins->replaceSuccessor(patches[0].index, join); @@ -4942,6 +4990,9 @@ static bool EmitLoop(FunctionCompiler& f) { } static bool EmitIf(FunctionCompiler& f) { + BranchHint branchHint = + f.iter().getBranchHint(f.funcIndex(), f.relativeBytecodeOffset()); + ResultType params; MDefinition* condition = nullptr; if (!f.iter().readIf(¶ms, &condition)) { @@ -4953,6 +5004,11 @@ static bool EmitIf(FunctionCompiler& f) { return false; } + // Store the branch hint in the basic block. + if (branchHint != BranchHint::Invalid) { + f.getCurBlock()->setBranchHinting(branchHint); + } + f.iter().controlItem().block = elseBlock; return true; } @@ -5088,11 +5144,15 @@ static bool EmitBrIf(FunctionCompiler& f) { ResultType type; DefVector values; MDefinition* condition; + + BranchHint branchHint = + f.iter().getBranchHint(f.funcIndex(), f.relativeBytecodeOffset()); + if (!f.iter().readBrIf(&relativeDepth, &type, &values, &condition)) { return false; } - return f.brIf(relativeDepth, values, condition); + return f.brIf(relativeDepth, values, condition, branchHint); } static bool EmitBrTable(FunctionCompiler& f) { @@ -5372,6 +5432,22 @@ static bool EmitCallIndirect(FunctionCompiler& f, bool oldStyle) { return true; } +#ifdef ENABLE_WASM_JSPI +static bool EmitStackSwitch(FunctionCompiler& f) { + StackSwitchKind kind; + MDefinition* suspender; + MDefinition* fn; + MDefinition* data; + if (!f.iter().readStackSwitch(&kind, &suspender, &fn, &data)) { + return false; + } + if (!f.stackSwitch(suspender, fn, data, kind)) { + return false; + } + return true; +} +#endif + #ifdef ENABLE_WASM_TAIL_CALLS static bool EmitReturnCall(FunctionCompiler& f) { uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode(); @@ -9153,6 +9229,15 @@ static bool EmitBodyExprs(FunctionCompiler& f) { } CHECK(EmitCallBuiltinModuleFunc(f)); } +#ifdef ENABLE_WASM_JSPI + if (op.b1 == uint32_t(MozOp::StackSwitch)) { + if (!f.moduleEnv().isBuiltinModule() || + !f.moduleEnv().jsPromiseIntegrationEnabled()) { + return f.iter().unrecognizedOpcode(&op); + } + CHECK(EmitStackSwitch(f)); + } +#endif if (!f.moduleEnv().isAsmJS()) { return f.iter().unrecognizedOpcode(&op); @@ -9326,6 +9411,12 @@ bool wasm::IonCompileFunctions(const ModuleEnvironment& moduleEnv, const JitCompileOptions options; MIRGraph graph(&alloc); CompileInfo compileInfo(locals.length()); + // Only activate branch hinting if the option is enabled and some hints were + // parsed. + if (moduleEnv.branchHintingEnabled() && !moduleEnv.branchHints.isEmpty()) { + compileInfo.setBranchHinting(true); + } + MIRGenerator mir(nullptr, options, &alloc, &graph, &compileInfo, IonOptimizations.get(OptimizationLevel::Wasm)); diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index d987ecec29..8a7e967a80 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -68,6 +68,7 @@ #include "wasm/WasmIonCompile.h" #include "wasm/WasmMemory.h" #include "wasm/WasmModule.h" +#include "wasm/WasmPI.h" #include "wasm/WasmProcess.h" #include "wasm/WasmSignalHandlers.h" #include "wasm/WasmStubs.h" @@ -139,6 +140,10 @@ static bool IsCallableNonCCW(const Value& v) { return IsCallable(v) && !IsCrossCompartmentWrapper(&v.toObject()); } +static bool IsWasmSuspendingWrapper(const Value& v) { + return v.isObject() && js::IsWasmSuspendingObject(&v.toObject()); +} + bool js::wasm::GetImports(JSContext* cx, const Module& module, HandleObject importObj, ImportValues* imports) { if (!module.imports().empty() && !importObj) { @@ -207,7 +212,8 @@ bool js::wasm::GetImports(JSContext* cx, const Module& module, switch (import.kind) { case DefinitionKind::Function: { - if (!IsCallableNonCCW(importFieldValue)) { + if (!IsCallableNonCCW(importFieldValue) && + !IsWasmSuspendingWrapper(importFieldValue)) { return ThrowBadImportType(cx, import.field, "Function"); } @@ -4225,6 +4231,66 @@ bool WasmFunctionConstruct(JSContext* cx, unsigned argc, Value* vp) { return false; } +# ifdef ENABLE_WASM_JSPI + // Check suspeding and promising + SuspenderArgPosition suspending = SuspenderArgPosition::None; + SuspenderArgPosition promising = SuspenderArgPosition::None; + if (wasm::JSPromiseIntegrationAvailable(cx) && args.length() > 2 && + args[2].isObject()) { + RootedObject usageObj(cx, &args[2].toObject()); + RootedValue val(cx); + if (!JS_GetProperty(cx, usageObj, "suspending", &val)) { + return false; + } + if (!ParseSuspendingPromisingString(cx, val, suspending)) { + return false; + } + if (!JS_GetProperty(cx, usageObj, "promising", &val)) { + return false; + } + if (!ParseSuspendingPromisingString(cx, val, promising)) { + return false; + } + } + + if (suspending > SuspenderArgPosition::None) { + if (!IsCallableNonCCW(args[1])) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_FUNCTION_VALUE); + return false; + } + + RootedObject func(cx, &args[1].toObject()); + RootedFunction suspend( + cx, WasmSuspendingFunctionCreate(cx, func, std::move(params), + std::move(results), suspending)); + if (!suspend) { + return false; + } + args.rval().setObject(*suspend); + + return true; + } + if (promising > SuspenderArgPosition::None) { + if (!IsWasmFunction(args[1])) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_FUNCTION_VALUE); + return false; + } + + RootedObject func(cx, &args[1].toObject()); + RootedFunction promise( + cx, WasmPromisingFunctionCreate(cx, func, std::move(params), + std::move(results), promising)); + if (!promise) { + return false; + } + args.rval().setObject(*promise); + + return true; + } +# endif // ENABLE_WASM_JSPI + // Get the target function if (!IsCallableNonCCW(args[1]) || IsWasmFunction(args[1])) { @@ -5291,6 +5357,91 @@ static bool WebAssembly_instantiateStreaming(JSContext* cx, unsigned argc, return true; } +#ifdef ENABLE_WASM_JSPI +const ClassSpec WasmSuspendingObject::classSpec_ = { + GenericCreateConstructor<construct, 1, gc::AllocKind::FUNCTION>, + GenericCreatePrototype<WasmSuspendingObject>, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + ClassSpec::DontDefineConstructor}; + +const JSClass WasmSuspendingObject::class_ = { + "Suspending", + JSCLASS_HAS_RESERVED_SLOTS(WasmSuspendingObject::RESERVED_SLOTS), + JS_NULL_CLASS_OPS, &classSpec_}; + +const JSClass& WasmSuspendingObject::protoClass_ = PlainObject::class_; + +/* static */ +bool WasmSuspendingObject::construct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "WebAssembly.Suspending", 1)) { + return false; + } + + if (!IsCallableNonCCW(args[0])) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_FUNCTION_VALUE); + return false; + } + + RootedObject callable(cx, &args[0].toObject()); + Rooted<WasmSuspendingObject*> suspending( + cx, NewBuiltinClassInstance<WasmSuspendingObject>(cx)); + if (!suspending) { + return false; + } + suspending->setWrappedFunction(callable); + args.rval().setObject(*suspending); + return true; +} + +static bool WebAssembly_promising(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "WebAssembly.promising", 1)) { + return false; + } + + if (!IsWasmFunction(args[0])) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_FUNCTION_VALUE); + return false; + } + + RootedObject func(cx, &args[0].toObject()); + RootedFunction promise( + cx, WasmPromisingFunctionCreate(cx, func, wasm::ValTypeVector(), + wasm::ValTypeVector(), + SuspenderArgPosition::None)); + if (!promise) { + return false; + } + args.rval().setObject(*promise); + return true; +} + +static const JSFunctionSpec WebAssembly_jspi_methods[] = { + JS_FN("promising", WebAssembly_promising, 1, JSPROP_ENUMERATE), JS_FS_END}; + +bool js::IsWasmSuspendingObject(JSObject* obj) { + return obj->is<WasmSuspendingObject>(); +} + +JSObject* js::MaybeUnwrapSuspendingObject(JSObject* wrapper) { + if (!wrapper->is<WasmSuspendingObject>()) { + return nullptr; + } + return wrapper->as<WasmSuspendingObject>().wrappedFunction(); +} +#else +bool js::IsWasmSuspendingObject(JSObject* obj) { return false; } +#endif // ENABLE_WASM_JSPI + #ifdef ENABLE_WASM_MOZ_INTGEMM static bool WebAssembly_mozIntGemm(JSContext* cx, unsigned argc, Value* vp) { @@ -5410,6 +5561,31 @@ static bool WebAssemblyClassFinish(JSContext* cx, HandleObject object, wasm->setWrappedJSValueTag(wrappedJSValueTagObject); + if (ExnRefAvailable(cx)) { + RootedId jsTagName(cx, NameToId(cx->names().jsTag)); + RootedValue jsTagValue(cx, ObjectValue(*wrappedJSValueTagObject)); + if (!DefineDataProperty(cx, wasm, jsTagName, jsTagValue, + JSPROP_READONLY | JSPROP_ENUMERATE)) { + return false; + } + } + +#ifdef ENABLE_WASM_JSPI + constexpr NameAndProtoKey jspiEntries[] = { + {"Suspending", JSProto_WasmSuspending}, + }; + if (JSPromiseIntegrationAvailable(cx)) { + if (!JS_DefineFunctions(cx, wasm, WebAssembly_jspi_methods)) { + return false; + } + for (const auto& entry : jspiEntries) { + if (!WebAssemblyDefineConstructor(cx, wasm, entry, &ctorValue, &id)) { + return false; + } + } + } +#endif + #ifdef ENABLE_WASM_MOZ_INTGEMM if (MozIntGemmAvailable(cx) && !JS_DefineFunctions(cx, wasm, WebAssembly_mozIntGemm_methods)) { diff --git a/js/src/wasm/WasmJS.h b/js/src/wasm/WasmJS.h index 27d49701a9..3ebc2d9705 100644 --- a/js/src/wasm/WasmJS.h +++ b/js/src/wasm/WasmJS.h @@ -503,6 +503,30 @@ class WasmNamespaceObject : public NativeObject { extern const JSClass WasmFunctionClass; +bool IsWasmSuspendingObject(JSObject* obj); + +#ifdef ENABLE_WASM_JSPI + +class WasmSuspendingObject : public NativeObject { + public: + static const ClassSpec classSpec_; + static const JSClass class_; + static const JSClass& protoClass_; + static const unsigned WRAPPED_FN_SLOT = 0; + static const unsigned RESERVED_SLOTS = 1; + static bool construct(JSContext*, unsigned, Value*); + + JSObject* wrappedFunction() const { + return getReservedSlot(WRAPPED_FN_SLOT).toObjectOrNull(); + } + void setWrappedFunction(HandleObject fn) { + return setReservedSlot(WRAPPED_FN_SLOT, ObjectValue(*fn)); + } +}; + +JSObject* MaybeUnwrapSuspendingObject(JSObject* wrapper); +#endif + } // namespace js #endif // wasm_js_h diff --git a/js/src/wasm/WasmModule.cpp b/js/src/wasm/WasmModule.cpp index 406c16462b..3609eeaf6e 100644 --- a/js/src/wasm/WasmModule.cpp +++ b/js/src/wasm/WasmModule.cpp @@ -38,6 +38,7 @@ #include "wasm/WasmIonCompile.h" #include "wasm/WasmJS.h" #include "wasm/WasmModuleTypes.h" +#include "wasm/WasmPI.h" #include "wasm/WasmSerialize.h" #include "wasm/WasmUtility.h" diff --git a/js/src/wasm/WasmModuleTypes.cpp b/js/src/wasm/WasmModuleTypes.cpp index fdd968a614..8fc4effb5f 100644 --- a/js/src/wasm/WasmModuleTypes.cpp +++ b/js/src/wasm/WasmModuleTypes.cpp @@ -49,6 +49,8 @@ bool CacheableName::fromUTF8Chars(const char* utf8Chars, CacheableName* name) { return true; } +BranchHintVector BranchHintCollection::invalidVector; + JSAtom* CacheableName::toAtom(JSContext* cx) const { return AtomizeUTF8Chars(cx, begin(), length()); } diff --git a/js/src/wasm/WasmModuleTypes.h b/js/src/wasm/WasmModuleTypes.h index 091619f598..c6cfabaca5 100644 --- a/js/src/wasm/WasmModuleTypes.h +++ b/js/src/wasm/WasmModuleTypes.h @@ -23,6 +23,7 @@ #include "mozilla/Span.h" #include "js/AllocPolicy.h" +#include "js/HashTable.h" #include "js/RefCounted.h" #include "js/Utility.h" #include "js/Vector.h" @@ -220,6 +221,54 @@ struct FuncDesc { using FuncDescVector = Vector<FuncDesc, 0, SystemAllocPolicy>; +enum class BranchHint : uint8_t { Unlikely = 0, Likely = 1, Invalid = 2 }; + +// Stores pairs of <BranchOffset, BranchHint> +struct BranchHintEntry { + uint32_t branchOffset; + BranchHint value; + + BranchHintEntry() = default; + BranchHintEntry(uint32_t branchOffset, BranchHint value) + : branchOffset(branchOffset), value(value) {} +}; + +// Branch hint sorted vector for a function, +// stores tuples of <BranchOffset, BranchHint> +using BranchHintVector = Vector<BranchHintEntry, 0, SystemAllocPolicy>; + +struct BranchHintCollection { + private: + // Map from function index to their collection of branch hints + HashMap<uint32_t, BranchHintVector, DefaultHasher<uint32_t>, + SystemAllocPolicy> + branchHintsMap; + + public: + // Used for lookups into the collection if a function + // doesn't contain any hints. + static BranchHintVector invalidVector; + + // Add all the branch hints for a function + [[nodiscard]] bool addHintsForFunc(uint32_t functionIndex, + BranchHintVector&& branchHints) { + return branchHintsMap.put(functionIndex, std::move(branchHints)); + } + + // Return the vector with branch hints for a funcIndex. + // If this function doesn't contain any hints, return an empty vector. + BranchHintVector& getHintVector(uint32_t funcIndex) const { + if (auto hintsVector = branchHintsMap.readonlyThreadsafeLookup(funcIndex)) { + return hintsVector->value(); + } + + // If not found, return the empty invalid Vector + return invalidVector; + } + + bool isEmpty() const { return branchHintsMap.empty(); } +}; + enum class GlobalKind { Import, Constant, Variable }; // A GlobalDesc describes a single global variable. diff --git a/js/src/wasm/WasmOpIter.cpp b/js/src/wasm/WasmOpIter.cpp index d60a87dc12..e35c6ce578 100644 --- a/js/src/wasm/WasmOpIter.cpp +++ b/js/src/wasm/WasmOpIter.cpp @@ -811,6 +811,8 @@ OpKind wasm::Classify(OpBytes op) { return OpKind::OldCallIndirect; case MozOp::CallBuiltinModuleFunc: return OpKind::CallBuiltinModuleFunc; + case MozOp::StackSwitch: + return OpKind::StackSwitch; } break; } diff --git a/js/src/wasm/WasmOpIter.h b/js/src/wasm/WasmOpIter.h index 59d494bfbf..9ea44bac92 100644 --- a/js/src/wasm/WasmOpIter.h +++ b/js/src/wasm/WasmOpIter.h @@ -234,6 +234,7 @@ enum class OpKind { Try, TryTable, CallBuiltinModuleFunc, + StackSwitch, }; // Return the OpKind for a given Op. This is used for sanity-checking that @@ -431,6 +432,8 @@ class MOZ_STACK_CLASS OpIter : private Policy { // immutable globals are allowed. uint32_t maxInitializedGlobalsIndexPlus1_; FeatureUsage featureUsage_; + uint32_t lastBranchHintIndex_; + BranchHintVector* branchHintVector_; #ifdef DEBUG OpBytes op_; @@ -548,6 +551,7 @@ class MOZ_STACK_CLASS OpIter : private Policy { env_(env), maxInitializedGlobalsIndexPlus1_(0), featureUsage_(FeatureUsage::None), + branchHintVector_(nullptr), op_(OpBytes(Op::Limit)), offsetOfLastReadOp_(0) {} #else @@ -598,6 +602,32 @@ class MOZ_STACK_CLASS OpIter : private Policy { return !controlStack_.empty() && controlStack_.back().polymorphicBase(); } + // If it exists, return the BranchHint value from a function index and a + // branch offset. + // Branch hints are stored in a sorted vector. Because code in compiled in + // order, we keep track of the most recently accessed index. + // Retrieving branch hints is also done in order inside a function. + BranchHint getBranchHint(uint32_t funcIndex, uint32_t branchOffset) { + if (!env_.branchHintingEnabled()) { + return BranchHint::Invalid; + } + + // Get the next hint in the collection + while (lastBranchHintIndex_ < branchHintVector_->length() && + (*branchHintVector_)[lastBranchHintIndex_].branchOffset < + branchOffset) { + lastBranchHintIndex_++; + } + + // No hint found for this branch. + if (lastBranchHintIndex_ >= branchHintVector_->length()) { + return BranchHint::Invalid; + } + + // The last index is saved, now return the hint. + return (*branchHintVector_)[lastBranchHintIndex_].value; + } + // ------------------------------------------------------------------------ // Decoding and validation interface. @@ -828,6 +858,11 @@ class MOZ_STACK_CLASS OpIter : private Policy { [[nodiscard]] bool readCallBuiltinModuleFunc( const BuiltinModuleFunc** builtinModuleFunc, ValueVector* params); +#ifdef ENABLE_WASM_JSPI + [[nodiscard]] bool readStackSwitch(StackSwitchKind* kind, Value* suspender, + Value* fn, Value* data); +#endif + // At a location where readOp is allowed, peek at the next opcode // without consuming it or updating any internal state. // Never fails: returns uint16_t(Op::Limit) in op->b0 if it can't read. @@ -1270,6 +1305,12 @@ inline bool OpIter<Policy>::startFunction(uint32_t funcIndex, MOZ_ASSERT(maxInitializedGlobalsIndexPlus1_ == 0); BlockType type = BlockType::FuncResults(*env_.funcs[funcIndex].type); + // Initialize information related to branch hinting. + lastBranchHintIndex_ = 0; + if (env_.branchHintingEnabled()) { + branchHintVector_ = &env_.branchHints.getHintVector(funcIndex); + } + size_t numArgs = env_.funcs[funcIndex].type->args().length(); if (!unsetLocals_.init(locals, numArgs)) { return false; @@ -1304,6 +1345,7 @@ inline bool OpIter<Policy>::startInitExpr(ValType expected) { MOZ_ASSERT(valueStack_.empty()); MOZ_ASSERT(controlStack_.empty()); MOZ_ASSERT(op_.b0 == uint16_t(Op::Limit)); + lastBranchHintIndex_ = 0; // GC allows accessing any previously defined global, not just those that are // imported and immutable. @@ -4219,6 +4261,46 @@ inline bool OpIter<Policy>::readStoreLane(uint32_t byteSize, #endif // ENABLE_WASM_SIMD +#ifdef ENABLE_WASM_JSPI +template <typename Policy> +inline bool OpIter<Policy>::readStackSwitch(StackSwitchKind* kind, + Value* suspender, Value* fn, + Value* data) { + MOZ_ASSERT(Classify(op_) == OpKind::StackSwitch); + MOZ_ASSERT(env_.jsPromiseIntegrationEnabled()); + uint32_t kind_; + if (!d_.readVarU32(&kind_)) { + return false; + } + *kind = StackSwitchKind(kind_); + if (!popWithType(ValType(RefType::any()), data)) { + return false; + } + StackType stackType; + if (!popWithType(ValType(RefType::func()), fn, &stackType)) { + return false; + } +# if DEBUG + // Verify that the function takes suspender and data as parameters and + // returns no values. + MOZ_ASSERT((*kind == StackSwitchKind::ContinueOnSuspendable) == + stackType.isNullableAsOperand()); + if (!stackType.isNullableAsOperand()) { + ValType valType = stackType.valType(); + MOZ_ASSERT(valType.isRefType() && valType.typeDef()->isFuncType()); + const FuncType& func = valType.typeDef()->funcType(); + MOZ_ASSERT(func.args().length() == 2 && func.results().empty() && + func.arg(0).isExternRef() && + ValType::isSubTypeOf(func.arg(1), RefType::any())); + } +# endif + if (!popWithType(ValType(RefType::extern_()), suspender)) { + return false; + } + return true; +} +#endif + template <typename Policy> inline bool OpIter<Policy>::readCallBuiltinModuleFunc( const BuiltinModuleFunc** builtinModuleFunc, ValueVector* params) { diff --git a/js/src/wasm/WasmPI.cpp b/js/src/wasm/WasmPI.cpp new file mode 100644 index 0000000000..d11db9022d --- /dev/null +++ b/js/src/wasm/WasmPI.cpp @@ -0,0 +1,1743 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * + * Copyright 2016 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wasm/WasmPI.h" + +#include "builtin/Promise.h" +#include "jit/MIRGenerator.h" +#include "js/CallAndConstruct.h" +#include "vm/JSContext.h" +#include "vm/JSObject.h" +#include "vm/NativeObject.h" +#include "vm/PromiseObject.h" +#include "wasm/WasmConstants.h" +#include "wasm/WasmContext.h" +#include "wasm/WasmFeatures.h" +#include "wasm/WasmGenerator.h" +#include "wasm/WasmValidate.h" + +#include "vm/JSObject-inl.h" +#include "wasm/WasmGcObject-inl.h" +#include "wasm/WasmInstance-inl.h" + +#ifdef XP_WIN +# include "util/WindowsWrapper.h" +#endif + +using namespace js; +using namespace js::jit; + +#ifdef ENABLE_WASM_JSPI +namespace js::wasm { + +SuspenderObjectData::SuspenderObjectData(void* stackMemory) + : stackMemory_(stackMemory), + suspendableFP_(nullptr), + suspendableSP_(static_cast<uint8_t*>(stackMemory) + + SuspendableStackPlusRedZoneSize), + state_(SuspenderState::Initial) {} + +# ifdef _WIN64 +// On WIN64, the Thread Information Block stack limits has to be modified to +// avoid failures on SP checks. +void SuspenderObjectData::updateTIBStackFields() { + _NT_TIB* tib = reinterpret_cast<_NT_TIB*>(::NtCurrentTeb()); + savedStackBase_ = tib->StackBase; + savedStackLimit_ = tib->StackLimit; + uintptr_t stack_limit = (uintptr_t)stackMemory_ + SuspendableRedZoneSize; + uintptr_t stack_base = stack_limit + SuspendableStackSize; + tib->StackBase = (void*)stack_base; + tib->StackLimit = (void*)stack_limit; +} + +void SuspenderObjectData::restoreTIBStackFields() { + _NT_TIB* tib = reinterpret_cast<_NT_TIB*>(::NtCurrentTeb()); + tib->StackBase = savedStackBase_; + tib->StackLimit = savedStackLimit_; +} +# endif + +// Slots that used in various JSFunctionExtended below. +const size_t SUSPENDER_SLOT = 0; +const size_t WRAPPED_FN_SLOT = 1; +const size_t CONTINUE_ON_SUSPENDABLE_SLOT = 1; + +SuspenderContext::SuspenderContext() + : activeSuspender_(nullptr), suspendedStacks_() {} + +SuspenderContext::~SuspenderContext() { + MOZ_ASSERT(activeSuspender_ == nullptr); +} + +SuspenderObject* SuspenderContext::activeSuspender() { + return activeSuspender_; +} + +void SuspenderContext::setActiveSuspender(SuspenderObject* obj) { + activeSuspender_.set(obj); +} + +void SuspenderContext::trace(JSTracer* trc) { + if (activeSuspender_) { + TraceEdge(trc, &activeSuspender_, "suspender"); + } +} + +void SuspenderContext::traceRoots(JSTracer* trc) { + for (const SuspenderObjectData& data : suspendedStacks_) { + void* startFP = data.suspendableFP(); + void* returnAddress = data.suspendedReturnAddress(); + void* exitFP = data.suspendableExitFP(); + MOZ_ASSERT(startFP != exitFP); + + WasmFrameIter iter(static_cast<FrameWithInstances*>(startFP), + returnAddress); + MOZ_ASSERT(iter.stackSwitched()); + uintptr_t highestByteVisitedInPrevWasmFrame = 0; + while (true) { + MOZ_ASSERT(!iter.done()); + uint8_t* nextPC = iter.resumePCinCurrentFrame(); + Instance* instance = iter.instance(); + TraceInstanceEdge(trc, instance, "WasmFrameIter instance"); + highestByteVisitedInPrevWasmFrame = instance->traceFrame( + trc, iter, nextPC, highestByteVisitedInPrevWasmFrame); + if (iter.frame() == exitFP) { + break; + } + ++iter; + if (iter.stackSwitched()) { + highestByteVisitedInPrevWasmFrame = 0; + } + } + } +} + +static_assert(JS_STACK_GROWTH_DIRECTION < 0, + "JS-PI implemented only for native stacks that grows towards 0"); + +static void DecrementSuspendableStacksCount(JSContext* cx) { + for (;;) { + uint32_t currentCount = cx->wasm().suspendableStacksCount; + MOZ_ASSERT(currentCount > 0); + if (cx->wasm().suspendableStacksCount.compareExchange(currentCount, + currentCount - 1)) { + break; + } + // Failed to decrement suspendableStacksCount, repeat. + } +} + +class SuspenderObject : public NativeObject { + public: + static const JSClass class_; + + enum { DataSlot, PromisingPromiseSlot, SuspendingPromiseSlot, SlotCount }; + + static SuspenderObject* create(JSContext* cx) { + for (;;) { + uint32_t currentCount = cx->wasm().suspendableStacksCount; + if (currentCount >= SuspendableStacksMaxCount) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_JSPI_SUSPENDER_LIMIT); + return nullptr; + } + if (cx->wasm().suspendableStacksCount.compareExchange(currentCount, + currentCount + 1)) { + break; + } + // Failed to increment suspendableStacksCount, repeat. + } + + Rooted<SuspenderObject*> suspender( + cx, NewBuiltinClassInstance<SuspenderObject>(cx)); + if (!suspender) { + DecrementSuspendableStacksCount(cx); + return nullptr; + } + + void* stackMemory = js_malloc(SuspendableStackPlusRedZoneSize); + if (!stackMemory) { + DecrementSuspendableStacksCount(cx); + ReportOutOfMemory(cx); + return nullptr; + } + + SuspenderObjectData* data = js_new<SuspenderObjectData>(stackMemory); + if (!data) { + js_free(stackMemory); + DecrementSuspendableStacksCount(cx); + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_RELEASE_ASSERT(data->state() != SuspenderState::Moribund); + + suspender->initReservedSlot(DataSlot, PrivateValue(data)); + suspender->initReservedSlot(PromisingPromiseSlot, NullValue()); + suspender->initReservedSlot(SuspendingPromiseSlot, NullValue()); + return suspender; + } + + PromiseObject* promisingPromise() const { + return &getReservedSlot(PromisingPromiseSlot) + .toObject() + .as<PromiseObject>(); + } + + void setPromisingPromise(Handle<PromiseObject*> promise) { + setReservedSlot(PromisingPromiseSlot, ObjectOrNullValue(promise)); + } + + PromiseObject* suspendingPromise() const { + return &getReservedSlot(SuspendingPromiseSlot) + .toObject() + .as<PromiseObject>(); + } + + void setSuspendingPromise(Handle<PromiseObject*> promise) { + setReservedSlot(SuspendingPromiseSlot, ObjectOrNullValue(promise)); + } + + JS::NativeStackLimit getStackMemoryLimit() { + return JS::NativeStackLimit(data()->stackMemory()) + SuspendableRedZoneSize; + } + + SuspenderState state() { return data()->state(); } + + inline bool hasData() { return !getReservedSlot(DataSlot).isUndefined(); } + + inline SuspenderObjectData* data() { + return static_cast<SuspenderObjectData*>( + getReservedSlot(DataSlot).toPrivate()); + } + + void setMoribund(JSContext* cx); + void setActive(JSContext* cx); + void setSuspended(JSContext* cx); + + void enter(JSContext* cx); + void suspend(JSContext* cx); + void resume(JSContext* cx); + void leave(JSContext* cx); + + private: + static const JSClassOps classOps_; + + static void finalize(JS::GCContext* gcx, JSObject* obj); +}; + +static_assert(SuspenderObjectDataSlot == SuspenderObject::DataSlot); + +const JSClass SuspenderObject::class_ = { + "SuspenderObject", + JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_BACKGROUND_FINALIZE, + &SuspenderObject::classOps_}; + +const JSClassOps SuspenderObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +/* static */ +void SuspenderObject::finalize(JS::GCContext* gcx, JSObject* obj) { + SuspenderObject& suspender = obj->as<SuspenderObject>(); + if (!suspender.hasData()) { + return; + } + SuspenderObjectData* data = suspender.data(); + MOZ_RELEASE_ASSERT(data->state() == SuspenderState::Moribund); + js_free(data->stackMemory()); + js_free(data); +} + +void SuspenderObject::setMoribund(JSContext* cx) { + // TODO make sense to free stackMemory at this point to reduce memory usage? + MOZ_ASSERT(state() == SuspenderState::Active); + ResetInstanceStackLimits(cx); +# ifdef _WIN64 + data()->restoreTIBStackFields(); +# endif + SuspenderObjectData* data = this->data(); + data->setState(SuspenderState::Moribund); + DecrementSuspendableStacksCount(cx); + MOZ_ASSERT( + !cx->wasm().promiseIntegration.suspendedStacks_.ElementProbablyInList( + data)); +} + +void SuspenderObject::setActive(JSContext* cx) { + data()->setState(SuspenderState::Active); + UpdateInstanceStackLimitsForSuspendableStack(cx, getStackMemoryLimit()); +# ifdef _WIN64 + data()->updateTIBStackFields(); +# endif +} + +void SuspenderObject::setSuspended(JSContext* cx) { + data()->setState(SuspenderState::Suspended); + ResetInstanceStackLimits(cx); +# ifdef _WIN64 + data()->restoreTIBStackFields(); +# endif +} + +void SuspenderObject::enter(JSContext* cx) { + MOZ_ASSERT(state() == SuspenderState::Initial); + cx->wasm().promiseIntegration.setActiveSuspender(this); + setActive(cx); +} + +void SuspenderObject::suspend(JSContext* cx) { + MOZ_ASSERT(state() == SuspenderState::Active); + setSuspended(cx); + cx->wasm().promiseIntegration.suspendedStacks_.pushFront(data()); + cx->wasm().promiseIntegration.setActiveSuspender(nullptr); +} + +void SuspenderObject::resume(JSContext* cx) { + MOZ_ASSERT(state() == SuspenderState::Suspended); + cx->wasm().promiseIntegration.setActiveSuspender(this); + setActive(cx); + cx->wasm().promiseIntegration.suspendedStacks_.remove(data()); +} + +void SuspenderObject::leave(JSContext* cx) { + cx->wasm().promiseIntegration.setActiveSuspender(nullptr); + // We are exiting alternative stack if state is active, + // otherwise the stack was just suspended. + switch (state()) { + case SuspenderState::Active: + setMoribund(cx); + break; + case SuspenderState::Suspended: + break; + case SuspenderState::Initial: + case SuspenderState::Moribund: + MOZ_CRASH(); + } +} + +bool ParseSuspendingPromisingString(JSContext* cx, HandleValue val, + SuspenderArgPosition& result) { + if (val.isNullOrUndefined()) { + result = SuspenderArgPosition::None; + return true; + } + + RootedString str(cx, ToString(cx, val)); + if (!str) { + return false; + } + Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); + if (!linear) { + return false; + } + + if (StringEqualsLiteral(linear, "first")) { + result = SuspenderArgPosition::First; + } else if (StringEqualsLiteral(linear, "last")) { + result = SuspenderArgPosition::Last; + } else { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_JSPI_ARG_POSITION); + return false; + } + return true; +} + +using CallImportData = Instance::WasmJSPICallImportData; + +/*static*/ +bool CallImportData::Call(CallImportData* data) { + Instance* instance = data->instance; + JSContext* cx = instance->cx(); + return instance->callImport(cx, data->funcImportIndex, data->argc, + data->argv); +} + +bool CallImportOnMainThread(JSContext* cx, Instance* instance, + int32_t funcImportIndex, int32_t argc, + uint64_t* argv) { + CallImportData data = {instance, funcImportIndex, argc, argv}; + Rooted<SuspenderObject*> suspender( + cx, cx->wasm().promiseIntegration.activeSuspender()); + SuspenderObjectData* stacks = suspender->data(); + + cx->wasm().promiseIntegration.setActiveSuspender(nullptr); + + MOZ_ASSERT(suspender->state() == SuspenderState::Active); + suspender->setSuspended(cx); + + // The platform specific code below inserts offsets as strings into inline + // assembly. CHECK_OFFSETS verifies the specified literals in macros below. +# define CHECK_OFFSETS(MAIN_FP_OFFSET, MAIN_SP_OFFSET, SUSPENDABLE_FP_OFFSET, \ + SUSPENDABLE_SP_OFFSET) \ + static_assert((MAIN_FP_OFFSET) == SuspenderObjectData::offsetOfMainFP() && \ + (MAIN_SP_OFFSET) == SuspenderObjectData::offsetOfMainSP() && \ + (SUSPENDABLE_FP_OFFSET) == \ + SuspenderObjectData::offsetOfSuspendableFP() && \ + (SUSPENDABLE_SP_OFFSET) == \ + SuspenderObjectData::offsetOfSuspendableSP()); + + // The following assembly code temporarily switches FP/SP pointers to be on + // main stack, while maintaining frames linking. After + // `CallImportData::Call` execution suspendable stack FP/SP will be restored. + // + // Because the assembly sequences contain a call, the trashed-register list + // must contain all the caller saved registers. They must also contain "cc" + // and "memory" since both of those state elements could be modified by the + // call. They also need a "volatile" qualifier to ensure that the they don't + // get optimised out or otherwise messed with by clang/gcc. + // + // `Registers::VolatileMask` (in the assembler complex) is useful in that it + // lists the caller-saved registers. + + uintptr_t res; + + // clang-format off +#if defined(_M_ARM64) || defined(__aarch64__) +# define CALLER_SAVED_REGS \ + "x0", "x1", "x2", "x3","x4", "x5", "x6", "x7", "x8", "x9", "x10", \ + "x11", "x12", "x13", "x14", "x15", "x16", "x17", /* "x18", */ \ + "x19", "x20", /* it's unclear who saves these two, so be safe */ \ + /* claim that all the vector regs are caller-saved, for safety */ \ + "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10", \ + "v11", "v12", "v13", "v14", "v15", "v16", "v17", "v18", "v19", \ + "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", \ + "v29", "v30", "v31" +# define INLINED_ASM(MAIN_FP, MAIN_SP, SUSPENDABLE_FP, SUSPENDABLE_SP) \ + CHECK_OFFSETS(MAIN_FP, MAIN_SP, SUSPENDABLE_FP, SUSPENDABLE_SP); \ + asm volatile( \ + "\n mov x0, %1" \ + "\n mov x27, sp " \ + "\n str x29, [x0, #" #SUSPENDABLE_FP "] " \ + "\n str x27, [x0, #" #SUSPENDABLE_SP "] " \ + \ + "\n ldr x29, [x0, #" #MAIN_FP "] " \ + "\n ldr x27, [x0, #" #MAIN_SP "] " \ + "\n mov sp, x27 " \ + \ + "\n stp x0, x27, [sp, #-16]! " \ + \ + "\n mov x0, %3" \ + "\n blr %2 " \ + \ + "\n ldp x3, x27, [sp], #16 " \ + \ + "\n mov x27, sp " \ + "\n str x29, [x3, #" #MAIN_FP "] " \ + "\n str x27, [x3, #" #MAIN_SP "] " \ + \ + "\n ldr x29, [x3, #" #SUSPENDABLE_FP "] " \ + "\n ldr x27, [x3, #" #SUSPENDABLE_SP "] " \ + "\n mov sp, x27 " \ + "\n mov %0, x0" \ + : "=r"(res) \ + : "r"(stacks), "r"(CallImportData::Call), "r"(&data) \ + : "x0", "x3", "x27", CALLER_SAVED_REGS, "cc", "memory") + INLINED_ASM(24, 32, 40, 48); + +# elif defined(_WIN64) && defined(_M_X64) +# define INLINED_ASM(MAIN_FP, MAIN_SP, SUSPENDABLE_FP, SUSPENDABLE_SP) \ + CHECK_OFFSETS(MAIN_FP, MAIN_SP, SUSPENDABLE_FP, SUSPENDABLE_SP); \ + asm("\n mov %1, %%rcx" \ + "\n mov %%rbp, " #SUSPENDABLE_FP "(%%rcx)" \ + "\n mov %%rsp, " #SUSPENDABLE_SP "(%%rcx)" \ + \ + "\n mov " #MAIN_FP "(%%rcx), %%rbp" \ + "\n mov " #MAIN_SP "(%%rcx), %%rsp" \ + \ + "\n push %%rcx" \ + "\n push %%rdx" \ + \ + "\n mov %3, %%rcx" \ + "\n call *%2" \ + \ + "\n pop %%rdx" \ + "\n pop %%rcx" \ + \ + "\n mov %%rbp, " #MAIN_FP "(%%rcx)" \ + "\n mov %%rsp, " #MAIN_SP "(%%rcx)" \ + \ + "\n mov " #SUSPENDABLE_FP "(%%rcx), %%rbp" \ + "\n mov " #SUSPENDABLE_SP "(%%rcx), %%rsp" \ + \ + "\n movq %%rax, %0" \ + : "=r"(res) \ + : "r"(stacks), "r"(CallImportData::Call), "r"(&data) \ + : "rcx", "rax") + INLINED_ASM(24, 32, 40, 48); + +# elif defined(__x86_64__) +# define INLINED_ASM(MAIN_FP, MAIN_SP, SUSPENDABLE_FP, SUSPENDABLE_SP) \ + CHECK_OFFSETS(MAIN_FP, MAIN_SP, SUSPENDABLE_FP, SUSPENDABLE_SP); \ + asm("\n mov %1, %%rdi" \ + "\n mov %%rbp, " #SUSPENDABLE_FP "(%%rdi)" \ + "\n mov %%rsp, " #SUSPENDABLE_SP "(%%rdi)" \ + \ + "\n mov " #MAIN_FP "(%%rdi), %%rbp" \ + "\n mov " #MAIN_SP "(%%rdi), %%rsp" \ + \ + "\n push %%rdi" \ + "\n push %%rdx" \ + \ + "\n mov %3, %%rdi" \ + "\n call *%2" \ + \ + "\n pop %%rdx" \ + "\n pop %%rdi" \ + \ + "\n mov %%rbp, " #MAIN_FP "(%%rdi)" \ + "\n mov %%rsp, " #MAIN_SP "(%%rdi)" \ + \ + "\n mov " #SUSPENDABLE_FP "(%%rdi), %%rbp" \ + "\n mov " #SUSPENDABLE_SP "(%%rdi), %%rsp" \ + \ + "\n movq %%rax, %0" \ + : "=r"(res) \ + : "r"(stacks), "r"(CallImportData::Call), "r"(&data) \ + : "rdi", "rax") + INLINED_ASM(24, 32, 40, 48); + +# else + MOZ_CRASH("Not supported for this platform"); +# endif + // clang-format on + + bool ok = res; + suspender->setActive(cx); + cx->wasm().promiseIntegration.setActiveSuspender(suspender); + +# undef INLINED_ASM +# undef CHECK_OFFSETS +# undef CALLER_SAVED_REGS + + return ok; +} + +void UnwindStackSwitch(JSContext* cx) { + SuspenderObject* suspender = cx->wasm().promiseIntegration.activeSuspender(); + MOZ_ASSERT(suspender); + cx->wasm().promiseIntegration.setActiveSuspender(nullptr); + suspender->setMoribund(cx); +} + +// Suspending + +// Builds a wasm module with following structure: +// (module +// (type $params (struct (field ..)*))) +// (type $results (struct (field ..)*))) +// (import "" "" (func $suspending.wrappedfn ..)) +// (import "" "" (func $suspending.add-promise-reactions ..)) +// (import "" "" (func $suspending.get-suspending-promise-result ..)) +// (func $suspending.exported .. ) +// (func $suspending.trampoline ..) +// (func $suspending.continue-on-suspendable ..) +// (export "" (func $suspending.exported)) +// ) +class SuspendingFunctionModuleFactory { + public: + enum TypeIdx { + ParamsTypeIndex, + ResultsTypeIndex, + }; + + enum FnIdx { + WrappedFnIndex, + GetSuspendingResultsFnIndex, + ExportedFnIndex, + TrampolineFnIndex, + ContinueOnSuspendableFnIndex + }; + + private: + // Builds function that will be imported to wasm module: + // (func $suspending.exported + // (param $suspender externref)? (param ..)* (param $suspender externref)? + // (result ..)* + // (local $suspender externref)? + // (local $results (ref $results)) + // ;; #if checkSuspender + // local.get $suspender + // call $builtin.check-suspender + // ;; #else + // call $builtin.current-suspender + // local.tee $suspender + // ;; #endif + // ref.func $suspending.trampoline + // local.get $i* + // stuct.new $param-type + // stack-switch SwitchToMain ;; <- (suspender,fn,data) + // local.get $suspender + // call $builtin.get-suspending-promise-result + // ref.cast $results-type + // local.set $results + // (struct.get $results (local.get $results))* + // ) + bool encodeExportedFunction(ModuleEnvironment& moduleEnv, uint32_t paramsSize, + uint32_t resultSize, uint32_t paramsOffset, + uint32_t suspenderIndex, bool checkSuspender, + RefType resultType, Bytes& bytecode) { + Encoder encoder(bytecode, *moduleEnv.types); + ValTypeVector locals; + if (!checkSuspender && !locals.emplaceBack(RefType::extern_())) { + return false; + } + if (!locals.emplaceBack(resultType)) { + return false; + } + if (!EncodeLocalEntries(encoder, locals)) { + return false; + } + + if (checkSuspender) { + if (!encoder.writeOp(Op::LocalGet) || + !encoder.writeVarU32(suspenderIndex)) { + return false; + } + if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || + !encoder.writeVarU32((uint32_t)BuiltinModuleFuncId::CheckSuspender)) { + return false; + } + } else { + if (!encoder.writeOp(Op::I32Const) || !encoder.writeVarU32(0)) { + return false; + } + if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || + !encoder.writeVarU32( + (uint32_t)BuiltinModuleFuncId::CurrentSuspender)) { + return false; + } + if (!encoder.writeOp(Op::LocalTee) || + !encoder.writeVarU32(suspenderIndex)) { + return false; + } + } + + // Results local is located after all params and suspender. + // Adding 1 to paramSize for checkSuspender and ! cases. + int resultsIndex = paramsSize + 1; + + if (!encoder.writeOp(Op::RefFunc) || + !encoder.writeVarU32(TrampolineFnIndex)) { + return false; + } + for (uint32_t i = 0; i < paramsSize; i++) { + if (!encoder.writeOp(Op::LocalGet) || + !encoder.writeVarU32(i + paramsOffset)) { + return false; + } + } + if (!encoder.writeOp(GcOp::StructNew) || + !encoder.writeVarU32(ParamsTypeIndex)) { + return false; + } + + if (!encoder.writeOp(MozOp::StackSwitch) || + !encoder.writeVarU32(uint32_t(StackSwitchKind::SwitchToMain))) { + return false; + } + + if (!encoder.writeOp(Op::LocalGet) || + !encoder.writeVarU32(suspenderIndex)) { + return false; + } + if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || + !encoder.writeVarU32( + (uint32_t)BuiltinModuleFuncId::GetSuspendingPromiseResult)) { + return false; + } + if (!encoder.writeOp(GcOp::RefCast) || + !encoder.writeVarU32(ResultsTypeIndex) || + !encoder.writeOp(Op::LocalSet) || !encoder.writeVarU32(resultsIndex)) { + return false; + } + for (uint32_t i = 0; i < resultSize; i++) { + if (!encoder.writeOp(Op::LocalGet) || + !encoder.writeVarU32(resultsIndex) || + !encoder.writeOp(GcOp::StructGet) || + !encoder.writeVarU32(ResultsTypeIndex) || !encoder.writeVarU32(i)) { + return false; + } + } + return encoder.writeOp(Op::End); + } + + // Builds function that is called on main stack: + // (func $suspending.trampoline + // (param $params (ref $suspender) (ref $param-type)) + // (result externref) + // local.get $suspender ;; for call $process-promise + // (struct.get $param-type $i (local.get $param))* + // call $suspending.wrappedfn + // ref.func $suspending.continue-on-suspendable + // call $suspending.add-promise-reactions + // ) + // The function calls suspending import and returns into the + // $promising.exported function because that was the top function + // on the main stack. + bool encodeTrampolineFunction(ModuleEnvironment& moduleEnv, + uint32_t paramsSize, Bytes& bytecode) { + Encoder encoder(bytecode, *moduleEnv.types); + if (!EncodeLocalEntries(encoder, ValTypeVector())) { + return false; + } + const uint32_t SuspenderIndex = 0; + const uint32_t ParamsIndex = 1; + + // For GetSuspendingResultsFnIndex call below. + if (!encoder.writeOp(Op::LocalGet) || + !encoder.writeVarU32(SuspenderIndex)) { + return false; + } + + for (uint32_t i = 0; i < paramsSize; i++) { + if (!encoder.writeOp(Op::LocalGet) || !encoder.writeVarU32(ParamsIndex)) { + return false; + } + if (!encoder.writeOp(GcOp::StructGet) || + !encoder.writeVarU32(ParamsTypeIndex) || !encoder.writeVarU32(i)) { + return false; + } + } + if (!encoder.writeOp(Op::Call) || !encoder.writeVarU32(WrappedFnIndex)) { + return false; + } + + if (!encoder.writeOp(Op::RefFunc) || + !encoder.writeVarU32(ContinueOnSuspendableFnIndex)) { + return false; + } + if (!encoder.writeOp(Op::Call) || + !encoder.writeVarU32(GetSuspendingResultsFnIndex)) { + return false; + } + + return encoder.writeOp(Op::End); + } + + // Builds function that is called on main stack: + // (func $suspending.continue-on-suspendable + // (param $params (ref $suspender)) + // (result externref) + // local.get $suspender + // ref.null funcref + // ref.null anyref + // stack-switch ContinueOnSuspendable + // ) + bool encodeContinueOnSuspendableFunction(ModuleEnvironment& moduleEnv, + uint32_t resultsSize, + Bytes& bytecode) { + Encoder encoder(bytecode, *moduleEnv.types); + if (!EncodeLocalEntries(encoder, ValTypeVector())) { + return false; + } + + const uint32_t SuspenderIndex = 0; + if (!encoder.writeOp(Op::LocalGet) || + !encoder.writeVarU32(SuspenderIndex)) { + return false; + } + if (!encoder.writeOp(Op::RefNull) || + !encoder.writeValType(ValType(RefType::func()))) { + return false; + } + if (!encoder.writeOp(Op::RefNull) || + !encoder.writeValType(ValType(RefType::any()))) { + return false; + } + + if (!encoder.writeOp(MozOp::StackSwitch) || + !encoder.writeVarU32( + uint32_t(StackSwitchKind::ContinueOnSuspendable))) { + return false; + } + + return encoder.writeOp(Op::End); + } + + public: + SharedModule build(JSContext* cx, HandleObject func, ValTypeVector&& params, + ValTypeVector&& results, + SuspenderArgPosition argPosition) { + FeatureOptions options; + options.isBuiltinModule = true; + options.requireGC = true; + options.requireTailCalls = true; + + ScriptedCaller scriptedCaller; + SharedCompileArgs compileArgs = + CompileArgs::buildAndReport(cx, std::move(scriptedCaller), options); + if (!compileArgs) { + return nullptr; + } + + ModuleEnvironment moduleEnv(compileArgs->features); + MOZ_ASSERT(IonAvailable(cx)); + CompilerEnvironment compilerEnv(CompileMode::Once, Tier::Optimized, + DebugEnabled::False); + compilerEnv.computeParameters(); + + if (!moduleEnv.init()) { + return nullptr; + } + + RefType suspenderType = RefType::extern_(); + RefType promiseType = RefType::extern_(); + + size_t paramsSize, paramsOffset, suspenderIndex; + size_t resultsSize = results.length(); + bool checkSuspender; + ValTypeVector paramsWithoutSuspender; + switch (argPosition) { + case SuspenderArgPosition::First: + paramsSize = params.length() - 1; + paramsOffset = 1; + suspenderIndex = 0; + if (!paramsWithoutSuspender.append(params.begin() + 1, params.end())) { + ReportOutOfMemory(cx); + return nullptr; + } + checkSuspender = true; + break; + case SuspenderArgPosition::Last: + paramsSize = params.length() - 1; + paramsOffset = 0; + suspenderIndex = paramsSize - 1; + if (!paramsWithoutSuspender.append(params.begin(), params.end() - 1)) { + ReportOutOfMemory(cx); + return nullptr; + } + checkSuspender = true; + break; + default: + paramsSize = params.length(); + paramsOffset = 0; + suspenderIndex = paramsSize; + if (!paramsWithoutSuspender.append(params.begin(), params.end())) { + ReportOutOfMemory(cx); + return nullptr; + } + checkSuspender = false; + break; + } + ValTypeVector resultsRef; + if (!resultsRef.emplaceBack(promiseType)) { + ReportOutOfMemory(cx); + return nullptr; + } + + StructType boxedParamsStruct; + if (!StructType::createImmutable(paramsWithoutSuspender, + &boxedParamsStruct)) { + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_ASSERT(moduleEnv.types->length() == ParamsTypeIndex); + if (!moduleEnv.types->addType(std::move(boxedParamsStruct))) { + return nullptr; + } + + StructType boxedResultType; + if (!StructType::createImmutable(results, &boxedResultType)) { + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_ASSERT(moduleEnv.types->length() == ResultsTypeIndex); + if (!moduleEnv.types->addType(std::move(boxedResultType))) { + return nullptr; + } + + MOZ_ASSERT(moduleEnv.funcs.length() == WrappedFnIndex); + if (!moduleEnv.addDefinedFunc(std::move(paramsWithoutSuspender), + std::move(resultsRef))) { + return nullptr; + } + + ValTypeVector paramsGetSuspendingResults, resultsGetSuspendingResults; + if (!paramsGetSuspendingResults.emplaceBack(suspenderType) || + !paramsGetSuspendingResults.emplaceBack(promiseType) || + !paramsGetSuspendingResults.emplaceBack(RefType::func())) { + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_ASSERT(moduleEnv.funcs.length() == GetSuspendingResultsFnIndex); + if (!moduleEnv.addDefinedFunc(std::move(paramsGetSuspendingResults), + std::move(resultsGetSuspendingResults))) { + return nullptr; + } + + // Imports names are not important, declare functions above as imports. + moduleEnv.numFuncImports = moduleEnv.funcs.length(); + + // We will be looking up and using the exports function by index so + // the name doesn't matter. + MOZ_ASSERT(moduleEnv.funcs.length() == ExportedFnIndex); + if (!moduleEnv.addDefinedFunc(std::move(params), std::move(results), + /*declareForRef = */ true, + mozilla::Some(CacheableName()))) { + return nullptr; + } + + ValTypeVector paramsTrampoline, resultsTrampoline; + if (!paramsTrampoline.emplaceBack(suspenderType) || + !paramsTrampoline.emplaceBack(RefType::fromTypeDef( + &(*moduleEnv.types)[ParamsTypeIndex], false))) { + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_ASSERT(moduleEnv.funcs.length() == TrampolineFnIndex); + if (!moduleEnv.addDefinedFunc(std::move(paramsTrampoline), + std::move(resultsTrampoline), + /*declareForRef = */ true)) { + return nullptr; + } + + ValTypeVector paramsContinueOnSuspendable, resultsContinueOnSuspendable; + if (!paramsContinueOnSuspendable.emplaceBack(suspenderType) || + !paramsContinueOnSuspendable.emplaceBack(RefType::extern_())) { + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_ASSERT(moduleEnv.funcs.length() == ContinueOnSuspendableFnIndex); + if (!moduleEnv.addDefinedFunc(std::move(paramsContinueOnSuspendable), + std::move(resultsContinueOnSuspendable), + /*declareForRef = */ true)) { + return nullptr; + } + + ModuleGenerator mg(*compileArgs, &moduleEnv, &compilerEnv, nullptr, nullptr, + nullptr); + if (!mg.init(nullptr)) { + return nullptr; + } + // Build functions and keep bytecodes around until the end. + Bytes bytecode; + if (!encodeExportedFunction( + moduleEnv, paramsSize, resultsSize, paramsOffset, suspenderIndex, + checkSuspender, + RefType::fromTypeDef(&(*moduleEnv.types)[ResultsTypeIndex], false), + bytecode)) { + ReportOutOfMemory(cx); + return nullptr; + } + if (!mg.compileFuncDef(ExportedFnIndex, 0, bytecode.begin(), + bytecode.begin() + bytecode.length())) { + return nullptr; + } + Bytes bytecode2; + if (!encodeTrampolineFunction(moduleEnv, paramsSize, bytecode2)) { + ReportOutOfMemory(cx); + return nullptr; + } + if (!mg.compileFuncDef(TrampolineFnIndex, 0, bytecode2.begin(), + bytecode2.begin() + bytecode2.length())) { + return nullptr; + } + Bytes bytecode3; + if (!encodeContinueOnSuspendableFunction(moduleEnv, paramsSize, + bytecode3)) { + ReportOutOfMemory(cx); + return nullptr; + } + if (!mg.compileFuncDef(ContinueOnSuspendableFnIndex, 0, bytecode3.begin(), + bytecode3.begin() + bytecode3.length())) { + return nullptr; + } + if (!mg.finishFuncDefs()) { + return nullptr; + } + + SharedBytes shareableBytes = js_new<ShareableBytes>(); + if (!shareableBytes) { + ReportOutOfMemory(cx); + return nullptr; + } + return mg.finishModule(*shareableBytes); + } +}; + +// Reaction on resolved/rejected suspending promise. +static bool WasmPISuspendTaskContinue(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<JSFunction*> callee(cx, &args.callee().as<JSFunction>()); + RootedValue suspender(cx, callee->getExtendedSlot(SUSPENDER_SLOT)); + + // Convert result of the promise into the parameters/arguments for the + // $suspending.continue-on-suspendable. + RootedFunction continueOnSuspendable( + cx, &callee->getExtendedSlot(CONTINUE_ON_SUSPENDABLE_SLOT) + .toObject() + .as<JSFunction>()); + RootedValueVector argv(cx); + if (!argv.emplaceBack(suspender)) { + ReportOutOfMemory(cx); + return false; + } + MOZ_ASSERT(args.length() > 0); + if (!argv.emplaceBack(args[0])) { + ReportOutOfMemory(cx); + return false; + } + + JS::Rooted<JS::Value> rval(cx); + if (Call(cx, UndefinedHandleValue, continueOnSuspendable, argv, &rval)) { + return true; + } + + if (cx->isThrowingOutOfMemory()) { + return false; + } + Rooted<PromiseObject*> promise( + cx, suspender.toObject().as<SuspenderObject>().promisingPromise()); + return RejectPromiseWithPendingError(cx, promise); +} + +// Collects returned suspending promising, and registers callbacks to +// react on it using WasmPISuspendTaskContinue. +// Seen as $suspending.add-promise-reactions in wasm. +static bool WasmPIAddPromiseReactions(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted<SuspenderObject*> suspenderObject( + cx, &args[0].toObject().as<SuspenderObject>()); + RootedValue rval(cx, args[1]); + RootedFunction fn(cx, &args[2].toObject().as<JSFunction>()); + + MOZ_ASSERT(rval.toObject().is<PromiseObject>(), + "WasmPIWrapSuspendingImport always returning a promise"); + Rooted<PromiseObject*> promise(cx, &rval.toObject().as<PromiseObject>()); + suspenderObject->setSuspendingPromise(promise); + + // Pass fn here + RootedFunction then_( + cx, NewNativeFunction(cx, WasmPISuspendTaskContinue, 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); + then_->initExtendedSlot(SUSPENDER_SLOT, ObjectValue(*suspenderObject)); + then_->initExtendedSlot(CONTINUE_ON_SUSPENDABLE_SLOT, ObjectValue(*fn)); + return AddPromiseReactions(cx, promise, then_, then_); +} + +// Wraps original import to catch all exceptions and convert result to a +// promise. +// Seen as $suspending.wrappedfn in wasm. +static bool WasmPIWrapSuspendingImport(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<JSFunction*> callee(cx, &args.callee().as<JSFunction>()); + RootedFunction originalImportFunc( + cx, + &callee->getExtendedSlot(WRAPPED_FN_SLOT).toObject().as<JSFunction>()); + + // Catching exceptions here. + RootedValue rval(cx); + if (Call(cx, UndefinedHandleValue, originalImportFunc, args, &rval)) { + // Convert the result to a resolved promise. + RootedObject promiseConstructor(cx, GetPromiseConstructor(cx)); + RootedObject promiseObj(cx, PromiseResolve(cx, promiseConstructor, rval)); + if (!promiseObj) { + return false; + } + args.rval().setObject(*promiseObj); + return true; + } + + if (cx->isThrowingOutOfMemory()) { + return false; + } + + // Convert failure to a rejected promise. + RootedObject promiseObject(cx, NewPromiseObject(cx, nullptr)); + if (!promiseObject) { + return false; + } + args.rval().setObject(*promiseObject); + + Rooted<PromiseObject*> promise(cx, &promiseObject->as<PromiseObject>()); + return RejectPromiseWithPendingError(cx, promise); +} + +JSFunction* WasmSuspendingFunctionCreate(JSContext* cx, HandleObject func, + ValTypeVector&& params, + ValTypeVector&& results, + SuspenderArgPosition argPosition) { + MOZ_ASSERT(IsCallable(ObjectValue(*func)) && + !IsCrossCompartmentWrapper(func)); + + if (argPosition != SuspenderArgPosition::None) { + if (params.length() < 1 || + params[argPosition == SuspenderArgPosition::Last ? params.length() - 1 + : 0] != + RefType::extern_()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_JSPI_EXPECTED_SUSPENDER); + return nullptr; + } + } + + SuspendingFunctionModuleFactory moduleFactory; + SharedModule module = moduleFactory.build(cx, func, std::move(params), + std::move(results), argPosition); + if (!module) { + return nullptr; + } + + // Instantiate the module. + Rooted<ImportValues> imports(cx); + + // Add $suspending.wrappedfn to imports. + RootedFunction funcWrapper( + cx, NewNativeFunction(cx, WasmPIWrapSuspendingImport, 0, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); + if (!funcWrapper) { + return nullptr; + } + funcWrapper->initExtendedSlot(WRAPPED_FN_SLOT, ObjectValue(*func)); + if (!imports.get().funcs.append(funcWrapper)) { + ReportOutOfMemory(cx); + return nullptr; + } + + // Add $suspending.add-promise-reactions to imports. + RootedFunction addPromiseReactions( + cx, NewNativeFunction(cx, WasmPIAddPromiseReactions, 3, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); + if (!addPromiseReactions) { + return nullptr; + } + if (!imports.get().funcs.append(addPromiseReactions)) { + ReportOutOfMemory(cx); + return nullptr; + } + + Rooted<WasmInstanceObject*> instance(cx); + if (!module->instantiate(cx, imports.get(), nullptr, &instance)) { + // Can also trap on invalid input function. + return nullptr; + } + + // Returns the $suspending.exported function. + RootedFunction wasmFunc(cx); + if (!WasmInstanceObject::getExportedFunction( + cx, instance, SuspendingFunctionModuleFactory::ExportedFnIndex, + &wasmFunc)) { + return nullptr; + } + return wasmFunc; +} + +JSFunction* WasmSuspendingFunctionCreate(JSContext* cx, HandleObject func, + const FuncType& type) { + ValTypeVector params, results; + if (!params.append(type.args().begin(), type.args().end()) || + !results.append(type.results().begin(), type.results().end())) { + ReportOutOfMemory(cx); + return nullptr; + } + return WasmSuspendingFunctionCreate(cx, func, std::move(params), + std::move(results), + SuspenderArgPosition::None); +} + +// Promising + +// Builds a wasm module with following structure: +// (module +// (type $params (struct (field ..)*))) +// (type $results (struct (field ..)*))) +// (import "" "" (func $promising.wrappedfn ..)) +// (import "" "" (func $promising.create-suspender ..)) +// (func $promising.exported .. ) +// (func $promising.trampoline ..) +// (export "" (func $promising.exported)) +// ) +class PromisingFunctionModuleFactory { + public: + enum TypeIdx { + ParamsTypeIndex, + ResultsTypeIndex, + }; + + enum FnIdx { + WrappedFnIndex, + CreateSuspenderFnIndex, + ExportedFnIndex, + TrampolineFnIndex, + }; + + private: + // Builds function that will be exported for JS: + // (func $promising.exported + // (param ..)* (result externref) + // (local $promise externref) + // call $promising.create-suspender ;; -> (suspender,promise) + // local.set $promise + // ref.func $promising.trampoline + // local.get $i* + // stuct.new $param-type + // stack-switch SwitchToSuspendable ;; <- (suspender,fn,data) + // local.get $promise + // ) + bool encodeExportedFunction(ModuleEnvironment& moduleEnv, uint32_t paramsSize, + Bytes& bytecode) { + Encoder encoder(bytecode, *moduleEnv.types); + ValTypeVector locals; + if (!locals.emplaceBack(RefType::extern_())) { + return false; + } + if (!EncodeLocalEntries(encoder, locals)) { + return false; + } + + const uint32_t PromiseIndex = paramsSize; + if (!encoder.writeOp(Op::Call) || + !encoder.writeVarU32(CreateSuspenderFnIndex)) { + return false; + } + if (!encoder.writeOp(Op::LocalSet) || !encoder.writeVarU32(PromiseIndex)) { + return false; + } + + if (!encoder.writeOp(Op::RefFunc) || + !encoder.writeVarU32(TrampolineFnIndex)) { + return false; + } + for (uint32_t i = 0; i < paramsSize; i++) { + if (!encoder.writeOp(Op::LocalGet) || !encoder.writeVarU32(i)) { + return false; + } + } + if (!encoder.writeOp(GcOp::StructNew) || + !encoder.writeVarU32(ParamsTypeIndex)) { + return false; + } + if (!encoder.writeOp(MozOp::StackSwitch) || + !encoder.writeVarU32(uint32_t(StackSwitchKind::SwitchToSuspendable))) { + return false; + } + + if (!encoder.writeOp(Op::LocalGet) || !encoder.writeVarU32(PromiseIndex)) { + return false; + } + return encoder.writeOp(Op::End); + } + + // Builds function that is called on alternative stack: + // (func $promising.trampoline + // (param $suspender externref) (param $params (ref $param-type)) + // (result externref) + // local.get $suspender ;; for call $set-results + // (local.get $suspender)? + // (struct.get $param-type $i (local.get $param))* + // (local.get $suspender)? + // call $promising.wrappedfn + // struct.new $result-type + // call $builtin.set-promising-promise-results + // ) + bool encodeTrampolineFunction(ModuleEnvironment& moduleEnv, + uint32_t paramsSize, + SuspenderArgPosition argPosition, + Bytes& bytecode) { + Encoder encoder(bytecode, *moduleEnv.types); + if (!EncodeLocalEntries(encoder, ValTypeVector())) { + return false; + } + const uint32_t SuspenderIndex = 0; + const uint32_t ParamsIndex = 1; + + // Reserved for SetResultsFnIndex call at the end + if (!encoder.writeOp(Op::LocalGet) || + !encoder.writeVarU32(SuspenderIndex)) { + return false; + } + + if (argPosition == SuspenderArgPosition::First) { + if (!encoder.writeOp(Op::LocalGet) || + !encoder.writeVarU32(SuspenderIndex)) { + return false; + } + } + for (uint32_t i = 0; i < paramsSize; i++) { + if (!encoder.writeOp(Op::LocalGet) || !encoder.writeVarU32(ParamsIndex)) { + return false; + } + if (!encoder.writeOp(GcOp::StructGet) || + !encoder.writeVarU32(ParamsTypeIndex) || !encoder.writeVarU32(i)) { + return false; + } + } + if (argPosition == SuspenderArgPosition::Last) { + if (!encoder.writeOp(Op::LocalGet) || + !encoder.writeVarU32(SuspenderIndex)) { + return false; + } + } + if (!encoder.writeOp(Op::Call) || !encoder.writeVarU32(WrappedFnIndex)) { + return false; + } + + if (!encoder.writeOp(GcOp::StructNew) || + !encoder.writeVarU32(ResultsTypeIndex)) { + return false; + } + if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc) || + !encoder.writeVarU32( + (uint32_t)BuiltinModuleFuncId::SetPromisingPromiseResults)) { + return false; + } + + return encoder.writeOp(Op::End); + } + + public: + SharedModule build(JSContext* cx, HandleFunction fn, ValTypeVector&& params, + ValTypeVector&& results, + SuspenderArgPosition argPosition) { + const FuncType& fnType = fn->wasmTypeDef()->funcType(); + size_t paramsSize = params.length(); + + RefType suspenderType = RefType::extern_(); + RefType promiseType = RefType::extern_(); + + FeatureOptions options; + options.isBuiltinModule = true; + options.requireGC = true; + options.requireTailCalls = true; + + ScriptedCaller scriptedCaller; + SharedCompileArgs compileArgs = + CompileArgs::buildAndReport(cx, std::move(scriptedCaller), options); + if (!compileArgs) { + return nullptr; + } + + ModuleEnvironment moduleEnv(compileArgs->features); + MOZ_ASSERT(IonAvailable(cx)); + CompilerEnvironment compilerEnv(CompileMode::Once, Tier::Optimized, + DebugEnabled::False); + compilerEnv.computeParameters(); + + if (!moduleEnv.init()) { + return nullptr; + } + + StructType boxedParamsStruct; + if (!StructType::createImmutable(params, &boxedParamsStruct)) { + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_ASSERT(moduleEnv.types->length() == ParamsTypeIndex); + if (!moduleEnv.types->addType(std::move(boxedParamsStruct))) { + return nullptr; + } + + StructType boxedResultType; + if (!StructType::createImmutable(fnType.results(), &boxedResultType)) { + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_ASSERT(moduleEnv.types->length() == ResultsTypeIndex); + if (!moduleEnv.types->addType(std::move(boxedResultType))) { + return nullptr; + } + + ValTypeVector paramsForWrapper, resultsForWrapper; + if (!paramsForWrapper.append(fnType.args().begin(), fnType.args().end()) || + !resultsForWrapper.append(fnType.results().begin(), + fnType.results().end())) { + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_ASSERT(moduleEnv.funcs.length() == WrappedFnIndex); + if (!moduleEnv.addDefinedFunc(std::move(paramsForWrapper), + std::move(resultsForWrapper))) { + return nullptr; + } + + ValTypeVector paramsCreateSuspender, resultsCreateSuspender; + if (!resultsCreateSuspender.emplaceBack(suspenderType) || + !resultsCreateSuspender.emplaceBack(promiseType)) { + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_ASSERT(moduleEnv.funcs.length() == CreateSuspenderFnIndex); + if (!moduleEnv.addDefinedFunc(std::move(paramsCreateSuspender), + std::move(resultsCreateSuspender))) { + return nullptr; + } + + // Imports names are not important, declare functions above as imports. + moduleEnv.numFuncImports = moduleEnv.funcs.length(); + + // We will be looking up and using the exports function by index so + // the name doesn't matter. + MOZ_ASSERT(moduleEnv.funcs.length() == ExportedFnIndex); + if (!moduleEnv.addDefinedFunc(std::move(params), std::move(results), + /* declareFoRef = */ true, + mozilla::Some(CacheableName()))) { + return nullptr; + } + + ValTypeVector paramsTrampoline, resultsTrampoline; + if (!paramsTrampoline.emplaceBack(suspenderType) || + !paramsTrampoline.emplaceBack(RefType::fromTypeDef( + &(*moduleEnv.types)[ParamsTypeIndex], false))) { + ReportOutOfMemory(cx); + return nullptr; + } + MOZ_ASSERT(moduleEnv.funcs.length() == TrampolineFnIndex); + if (!moduleEnv.addDefinedFunc(std::move(paramsTrampoline), + std::move(resultsTrampoline), + /* declareFoRef = */ true)) { + return nullptr; + } + + ModuleGenerator mg(*compileArgs, &moduleEnv, &compilerEnv, nullptr, nullptr, + nullptr); + if (!mg.init(nullptr)) { + return nullptr; + } + // Build functions and keep bytecodes around until the end. + Bytes bytecode; + if (!encodeExportedFunction(moduleEnv, paramsSize, bytecode)) { + ReportOutOfMemory(cx); + return nullptr; + } + if (!mg.compileFuncDef(ExportedFnIndex, 0, bytecode.begin(), + bytecode.begin() + bytecode.length())) { + return nullptr; + } + Bytes bytecode2; + if (!encodeTrampolineFunction(moduleEnv, paramsSize, argPosition, + bytecode2)) { + ReportOutOfMemory(cx); + return nullptr; + } + if (!mg.compileFuncDef(TrampolineFnIndex, 0, bytecode2.begin(), + bytecode2.begin() + bytecode2.length())) { + return nullptr; + } + if (!mg.finishFuncDefs()) { + return nullptr; + } + + SharedBytes shareableBytes = js_new<ShareableBytes>(); + if (!shareableBytes) { + ReportOutOfMemory(cx); + return nullptr; + } + return mg.finishModule(*shareableBytes); + } +}; + +// Creates a suspender and promise (that will be returned to JS code). +// Seen as $promising.create-suspender to wasm. +static bool WasmPICreateSuspender(JSContext* cx, unsigned argc, Value* vp) { + Rooted<SuspenderObject*> suspenderObject(cx, SuspenderObject::create(cx)); + RootedObject promiseObject(cx, NewPromiseObject(cx, nullptr)); + if (!promiseObject) { + return false; + } + + Rooted<PromiseObject*> promise(cx, &promiseObject->as<PromiseObject>()); + suspenderObject->setPromisingPromise(promise); + + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<ArrayObject*> results(cx, NewDenseEmptyArray(cx)); + if (!NewbornArrayPush(cx, results, ObjectValue(*suspenderObject))) { + return false; + } + if (!NewbornArrayPush(cx, results, ObjectValue(*promise))) { + return false; + } + args.rval().setObject(*results); + return true; +} + +// Wraps call to wasm $promising.exported function to catch an exception and +// return a promise instead. +static bool WasmPIPromisingFunction(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<JSFunction*> callee(cx, &args.callee().as<JSFunction>()); + RootedFunction fn( + cx, + &callee->getExtendedSlot(WRAPPED_FN_SLOT).toObject().as<JSFunction>()); + + // Catching exceptions here. + if (Call(cx, UndefinedHandleValue, fn, args, args.rval())) { + return true; + } + + if (cx->isThrowingOutOfMemory()) { + return false; + } + + RootedObject promiseObject(cx, NewPromiseObject(cx, nullptr)); + if (!promiseObject) { + return false; + } + args.rval().setObject(*promiseObject); + + Rooted<PromiseObject*> promise(cx, &promiseObject->as<PromiseObject>()); + return RejectPromiseWithPendingError(cx, promise); +} + +JSFunction* WasmPromisingFunctionCreate(JSContext* cx, HandleObject func, + ValTypeVector&& params, + ValTypeVector&& results, + SuspenderArgPosition argPosition) { + RootedFunction wrappedWasmFunc(cx, &func->as<JSFunction>()); + MOZ_ASSERT(wrappedWasmFunc->isWasm()); + const FuncType& wrappedWasmFuncType = + wrappedWasmFunc->wasmTypeDef()->funcType(); + + if (argPosition != SuspenderArgPosition::None) { + if (results.length() != 1 || results[0] != RefType::extern_()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_JSPI_EXPECTED_PROMISE); + return nullptr; + } + + size_t paramsOffset, suspenderIndex; + switch (argPosition) { + case SuspenderArgPosition::First: + paramsOffset = 1; + suspenderIndex = 0; + break; + case SuspenderArgPosition::Last: + paramsOffset = 0; + suspenderIndex = params.length(); + break; + default: + MOZ_CRASH(); + } + + if (wrappedWasmFuncType.args().length() != params.length() + 1 || + wrappedWasmFuncType.args()[suspenderIndex] != RefType::extern_()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_JSPI_EXPECTED_SUSPENDER); + return nullptr; + } + for (size_t i = 0; i < params.length(); i++) { + if (params[i] != wrappedWasmFuncType.args()[i + paramsOffset]) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_JSPI_SIGNATURE_MISMATCH); + return nullptr; + } + } + } else { + MOZ_ASSERT(results.length() == 0 && params.length() == 0); + if (!results.append(RefType::extern_())) { + ReportOutOfMemory(cx); + return nullptr; + } + if (!params.append(wrappedWasmFuncType.args().begin(), + wrappedWasmFuncType.args().end())) { + ReportOutOfMemory(cx); + return nullptr; + } + } + + PromisingFunctionModuleFactory moduleFactory; + SharedModule module = moduleFactory.build( + cx, wrappedWasmFunc, std::move(params), std::move(results), argPosition); + // Instantiate the module. + Rooted<ImportValues> imports(cx); + + // Add wrapped function ($promising.wrappedfn) to imports. + if (!imports.get().funcs.append(func)) { + ReportOutOfMemory(cx); + return nullptr; + } + + // Add $promising.create-suspender to imports. + RootedFunction createSuspenderFunc( + cx, NewNativeFunction(cx, WasmPICreateSuspender, 0, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); + if (!createSuspenderFunc) { + return nullptr; + } + if (!imports.get().funcs.append(createSuspenderFunc)) { + ReportOutOfMemory(cx); + return nullptr; + } + + Rooted<WasmInstanceObject*> instance(cx); + if (!module->instantiate(cx, imports.get(), nullptr, &instance)) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return nullptr; + } + + // Wrap $promising.exported function for exceptions/traps handling. + RootedFunction wasmFunc(cx); + if (!WasmInstanceObject::getExportedFunction( + cx, instance, PromisingFunctionModuleFactory::ExportedFnIndex, + &wasmFunc)) { + return nullptr; + } + + RootedFunction wasmFuncWrapper( + cx, NewNativeFunction(cx, WasmPIPromisingFunction, 0, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); + if (!wasmFuncWrapper) { + return nullptr; + } + wasmFuncWrapper->initExtendedSlot(WRAPPED_FN_SLOT, ObjectValue(*wasmFunc)); + return wasmFuncWrapper; +} + +// Gets active suspender. +// The reserved parameter is a workaround for limitation in the +// WasmBuiltinModule.yaml generator to always have params. +SuspenderObject* CurrentSuspender(Instance* instance, int32_t reserved) { + MOZ_ASSERT(SASigCurrentSuspender.failureMode == FailureMode::FailOnNullPtr); + JSContext* cx = instance->cx(); + SuspenderObject* suspender = cx->wasm().promiseIntegration.activeSuspender(); + if (!suspender) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_JSPI_INVALID_STATE); + return nullptr; + } + return suspender; +} + +// Checks suspender value. +SuspenderObject* CheckSuspender(Instance* instance, JSObject* maybeSuspender) { + MOZ_ASSERT(SASigCheckSuspender.failureMode == FailureMode::FailOnNullPtr); + JSContext* cx = instance->cx(); + if (!maybeSuspender || !maybeSuspender->is<SuspenderObject>() || + &maybeSuspender->as<SuspenderObject>() != + cx->wasm().promiseIntegration.activeSuspender()) { + // Wrong suspender + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_JSPI_INVALID_SUSPENDER); + return nullptr; + } + SuspenderObject* suspenderObject = &maybeSuspender->as<SuspenderObject>(); + if (suspenderObject->state() != SuspenderState::Active) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_JSPI_INVALID_STATE); + return nullptr; + } + return suspenderObject; +} + +// Converts promise results into actual function result, or exception/trap +// if rejected. +JSObject* GetSuspendingPromiseResult(Instance* instance, + SuspenderObject* suspender) { + MOZ_ASSERT(SASigGetSuspendingPromiseResult.failureMode == + FailureMode::FailOnNullPtr); + JSContext* cx = instance->cx(); + Rooted<SuspenderObject*> suspenderObject(cx, suspender); + + Rooted<PromiseObject*> promise(cx, suspenderObject->suspendingPromise()); + + if (promise->state() == JS::PromiseState::Rejected) { + RootedValue reason(cx, promise->reason()); + // Result is also the reason of promise rejection. + cx->setPendingException(reason, ShouldCaptureStack::Maybe); + return nullptr; + } + + // Construct the results object. + Rooted<WasmStructObject*> results( + cx, instance->constantStructNewDefault( + cx, SuspendingFunctionModuleFactory::ResultsTypeIndex)); + const StructFieldVector& fields = results->typeDef().structType().fields_; + + MOZ_ASSERT(fields.length() <= 1); + if (fields.length() == 1) { + RootedValue jsValue(cx, promise->value()); + + // The struct object is constructed based on returns of exported function. + // It is the only way we can get ValType for Val::fromJSValue call. + auto bestTier = instance->code().bestTier(); + const wasm::FuncExport& funcExport = + instance->metadata(bestTier).lookupFuncExport( + SuspendingFunctionModuleFactory::ExportedFnIndex); + const wasm::FuncType& sig = + instance->metadata().getFuncExportType(funcExport); + + RootedVal val(cx); + MOZ_ASSERT(sig.result(0).storageType() == fields[0].type); + if (!Val::fromJSValue(cx, sig.result(0), jsValue, &val)) { + return nullptr; + } + results->storeVal(val, 0); + } + return results; +} + +// Resolves the promise using results packed by wasm. +int32_t SetPromisingPromiseResults(Instance* instance, + SuspenderObject* suspender, + WasmStructObject* results) { + MOZ_ASSERT(SASigSetPromisingPromiseResults.failureMode == + FailureMode::FailOnNegI32); + JSContext* cx = instance->cx(); + Rooted<WasmStructObject*> res(cx, results); + Rooted<SuspenderObject*> suspenderObject(cx, suspender); + RootedObject promise(cx, suspenderObject->promisingPromise()); + + const StructType& resultType = res->typeDef().structType(); + RootedValue val(cx); + // Unbox the result value from the struct, if any. + if (resultType.fields_.length() > 0) { + MOZ_RELEASE_ASSERT(resultType.fields_.length() == 1); + if (!res->getField(cx, /*index=*/0, &val)) { + return -1; + } + } + ResolvePromise(cx, promise, val); + return 0; +} + +void UpdateSuspenderState(Instance* instance, SuspenderObject* suspender, + UpdateSuspenderStateAction action) { + MOZ_ASSERT(SASigUpdateSuspenderState.failureMode == FailureMode::Infallible); + + JSContext* cx = instance->cx(); + switch (action) { + case UpdateSuspenderStateAction::Enter: + suspender->enter(cx); + break; + case UpdateSuspenderStateAction::Suspend: + suspender->suspend(cx); + break; + case UpdateSuspenderStateAction::Resume: + suspender->resume(cx); + break; + case UpdateSuspenderStateAction::Leave: + suspender->leave(cx); + break; + default: + MOZ_CRASH(); + } +} + +} // namespace js::wasm +#endif // ENABLE_WASM_JSPI diff --git a/js/src/wasm/WasmPI.h b/js/src/wasm/WasmPI.h new file mode 100644 index 0000000000..a26740d540 --- /dev/null +++ b/js/src/wasm/WasmPI.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * + * Copyright 2016 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef wasm_pi_h +#define wasm_pi_h + +#include "mozilla/DoublyLinkedList.h" // for DoublyLinkedListElement + +#include "js/TypeDecls.h" +#include "wasm/WasmTypeDef.h" + +namespace js { + +class WasmStructObject; + +namespace wasm { + +class SuspenderObject; + +static const uint32_t SuspenderObjectDataSlot = 0; + +enum SuspenderArgPosition { + None = -1, + First = 0, + Last = 1, +}; + +enum SuspenderState { + Initial, + Moribund, + Active, + Suspended, +}; + +class SuspenderObjectData + : public mozilla::DoublyLinkedListElement<SuspenderObjectData> { + void* stackMemory_; + + // Stored main stack FP register. + void* mainFP_; + + // Stored main stack SP register. + void* mainSP_; + + // Stored suspendable stack FP register. + void* suspendableFP_; + + // Stored suspendable stack SP register. + void* suspendableSP_; + + // Stored suspendable stack exit/bottom frame pointer. + void* suspendableExitFP_; + + // Stored return address for return to suspendable stack. + void* suspendedReturnAddress_; + + SuspenderState state_; + +#ifdef _WIN64 + // The storage of main stack limits during stack switching. + // See updateTibFields and restoreTibFields below. + void* savedStackBase_; + void* savedStackLimit_; +#endif + + public: + explicit SuspenderObjectData(void* stackMemory); + + inline SuspenderState state() const { return state_; } + void setState(SuspenderState state) { state_ = state; } + + inline void* stackMemory() const { return stackMemory_; } + inline void* mainFP() const { return mainFP_; } + inline void* mainSP() const { return mainSP_; } + inline void* suspendableFP() const { return suspendableFP_; } + inline void* suspendableSP() const { return suspendableSP_; } + inline void* suspendableExitFP() const { return suspendableExitFP_; } + inline void* suspendedReturnAddress() const { + return suspendedReturnAddress_; + } + +#ifdef _WIN64 + void updateTIBStackFields(); + void restoreTIBStackFields(); +#endif + + static constexpr size_t offsetOfMainFP() { + return offsetof(SuspenderObjectData, mainFP_); + } + + static constexpr size_t offsetOfMainSP() { + return offsetof(SuspenderObjectData, mainSP_); + } + + static constexpr size_t offsetOfSuspendableFP() { + return offsetof(SuspenderObjectData, suspendableFP_); + } + + static constexpr size_t offsetOfSuspendableSP() { + return offsetof(SuspenderObjectData, suspendableSP_); + } + + static constexpr size_t offsetOfSuspendableExitFP() { + return offsetof(SuspenderObjectData, suspendableExitFP_); + } + + static constexpr size_t offsetOfSuspendedReturnAddress() { + return offsetof(SuspenderObjectData, suspendedReturnAddress_); + } +}; + +#ifdef ENABLE_WASM_JSPI + +bool ParseSuspendingPromisingString(JSContext* cx, JS::HandleValue val, + SuspenderArgPosition& result); + +bool CallImportOnMainThread(JSContext* cx, Instance* instance, + int32_t funcImportIndex, int32_t argc, + uint64_t* argv); + +void UnwindStackSwitch(JSContext* cx); + +JSFunction* WasmSuspendingFunctionCreate(JSContext* cx, HandleObject func, + wasm::ValTypeVector&& params, + wasm::ValTypeVector&& results, + SuspenderArgPosition argPosition); + +JSFunction* WasmSuspendingFunctionCreate(JSContext* cx, HandleObject func, + const FuncType& type); + +JSFunction* WasmPromisingFunctionCreate(JSContext* cx, HandleObject func, + wasm::ValTypeVector&& params, + wasm::ValTypeVector&& results, + SuspenderArgPosition argPosition); + +SuspenderObject* CurrentSuspender(Instance* instance, int reserved); + +SuspenderObject* CheckSuspender(Instance* instance, JSObject* maybeSuspender); + +JSObject* GetSuspendingPromiseResult(Instance* instance, + SuspenderObject* suspender); + +int32_t SetPromisingPromiseResults(Instance* instance, + SuspenderObject* suspender, + WasmStructObject* results); + +void UpdateSuspenderState(Instance* instance, SuspenderObject* suspender, + UpdateSuspenderStateAction action); + +#endif // ENABLE_WASM_JSPI + +} // namespace wasm +} // namespace js + +#endif // wasm_pi_h diff --git a/js/src/wasm/WasmRealm.cpp b/js/src/wasm/WasmRealm.cpp index 8907715c8f..61d8b96a44 100644 --- a/js/src/wasm/WasmRealm.cpp +++ b/js/src/wasm/WasmRealm.cpp @@ -150,3 +150,22 @@ void wasm::ResetInterruptState(JSContext* cx) { instance->resetInterrupt(cx); } } + +#ifdef ENABLE_WASM_JSPI +void wasm::UpdateInstanceStackLimitsForSuspendableStack( + JSContext* cx, JS::NativeStackLimit limit) { + auto runtimeInstances = cx->runtime()->wasmInstances.lock(); + cx->wasm().suspendableStackLimit = limit; + for (Instance* instance : runtimeInstances.get()) { + instance->setTemporaryStackLimit(limit); + } +} + +void wasm::ResetInstanceStackLimits(JSContext* cx) { + auto runtimeInstances = cx->runtime()->wasmInstances.lock(); + cx->wasm().suspendableStackLimit = JS::NativeStackLimitMin; + for (Instance* instance : runtimeInstances.get()) { + instance->resetTemporaryStackLimit(cx); + } +} +#endif // ENABLE_WASM_JSPI diff --git a/js/src/wasm/WasmRealm.h b/js/src/wasm/WasmRealm.h index 6ad7c435b9..46267456f1 100644 --- a/js/src/wasm/WasmRealm.h +++ b/js/src/wasm/WasmRealm.h @@ -78,6 +78,12 @@ extern void InterruptRunningCode(JSContext* cx); void ResetInterruptState(JSContext* cx); +#ifdef ENABLE_WASM_JSPI +void UpdateInstanceStackLimitsForSuspendableStack(JSContext* cx, + JS::NativeStackLimit limit); +void ResetInstanceStackLimits(JSContext* cx); +#endif + } // namespace wasm } // namespace js diff --git a/js/src/wasm/WasmSerialize.cpp b/js/src/wasm/WasmSerialize.cpp index 35f437688c..e8cf904064 100644 --- a/js/src/wasm/WasmSerialize.cpp +++ b/js/src/wasm/WasmSerialize.cpp @@ -957,7 +957,10 @@ CoderResult CodeSymbolicLinkArray( template <CoderMode mode> CoderResult CodeLinkData(Coder<mode>& coder, CoderArg<mode, wasm::LinkData> item) { - WASM_VERIFY_SERIALIZATION_FOR_SIZE(wasm::LinkData, 8976); + // SymbolicLinkArray depends on SymbolicAddress::Limit, which is changed + // often. Exclude symbolicLinks field from trip wire value calculation. + WASM_VERIFY_SERIALIZATION_FOR_SIZE( + wasm::LinkData, 48 + sizeof(wasm::LinkData::SymbolicLinkArray)); if constexpr (mode == MODE_ENCODE) { MOZ_ASSERT(item->tier == Tier::Serialized); } diff --git a/js/src/wasm/WasmStubs.cpp b/js/src/wasm/WasmStubs.cpp index dfaa898744..76f015d34b 100644 --- a/js/src/wasm/WasmStubs.cpp +++ b/js/src/wasm/WasmStubs.cpp @@ -1862,6 +1862,39 @@ static void FillArgumentArrayForJitExit(MacroAssembler& masm, Register instance, GenPrintf(DebugChannel::Import, masm, "\n"); } +static bool AddStackCheckForImportFunctionEntry(jit::MacroAssembler& masm, + unsigned reserve, + const FuncType& funcType, + StackMaps* stackMaps) { + std::pair<CodeOffset, uint32_t> pair = + masm.wasmReserveStackChecked(reserve, BytecodeOffset(0)); + + // Attempt to create stack maps for masm.wasmReserveStackChecked. + ArgTypeVector argTypes(funcType); + RegisterOffsets trapExitLayout; + size_t trapExitLayoutNumWords; + GenerateTrapExitRegisterOffsets(&trapExitLayout, &trapExitLayoutNumWords); + CodeOffset trapInsnOffset = pair.first; + size_t nBytesReservedBeforeTrap = pair.second; + size_t nInboundStackArgBytes = StackArgAreaSizeUnaligned(argTypes); + wasm::StackMap* stackMap = nullptr; + if (!CreateStackMapForFunctionEntryTrap( + argTypes, trapExitLayout, trapExitLayoutNumWords, + nBytesReservedBeforeTrap, nInboundStackArgBytes, &stackMap)) { + return false; + } + + // In debug builds, we'll always have a stack map, even if there are no + // refs to track. + MOZ_ASSERT(stackMap); + if (stackMap && + !stackMaps->add((uint8_t*)(uintptr_t)trapInsnOffset.offset(), stackMap)) { + stackMap->destroy(); + return false; + } + return true; +} + // Generate a wrapper function with the standard intra-wasm call ABI which // simply calls an import. This wrapper function allows any import to be treated // like a normal wasm function for the purposes of exports and table calls. In @@ -1873,7 +1906,7 @@ static bool GenerateImportFunction(jit::MacroAssembler& masm, const FuncImport& fi, const FuncType& funcType, CallIndirectId callIndirectId, - FuncOffsets* offsets) { + FuncOffsets* offsets, StackMaps* stackMaps) { AutoCreatedBy acb(masm, "wasm::GenerateImportFunction"); AssertExpectedSP(masm); @@ -1886,7 +1919,12 @@ static bool GenerateImportFunction(jit::MacroAssembler& masm, WasmStackAlignment, sizeof(Frame), // pushed by prologue StackArgBytesForWasmABI(funcType) + sizeOfInstanceSlot); - masm.wasmReserveStackChecked(framePushed, BytecodeOffset(0)); + + if (!AddStackCheckForImportFunctionEntry(masm, framePushed, funcType, + stackMaps)) { + return false; + } + MOZ_ASSERT(masm.framePushed() == framePushed); masm.storePtr(InstanceReg, Address(masm.getStackPointer(), @@ -1950,7 +1988,8 @@ bool wasm::GenerateImportFunctions(const ModuleEnvironment& env, CallIndirectId callIndirectId = CallIndirectId::forFunc(env, funcIndex); FuncOffsets offsets; - if (!GenerateImportFunction(masm, fi, funcType, callIndirectId, &offsets)) { + if (!GenerateImportFunction(masm, fi, funcType, callIndirectId, &offsets, + &code->stackMaps)) { return false; } if (!code->codeRanges.emplaceBack(funcIndex, /* bytecodeOffset = */ 0, diff --git a/js/src/wasm/WasmTypeDef.h b/js/src/wasm/WasmTypeDef.h index a0d44e647b..135d58e613 100644 --- a/js/src/wasm/WasmTypeDef.h +++ b/js/src/wasm/WasmTypeDef.h @@ -1355,6 +1355,7 @@ inline RefTypeHierarchy RefType::hierarchy() const { case RefType::NoExtern: return RefTypeHierarchy::Extern; case RefType::Exn: + case RefType::NoExn: return RefTypeHierarchy::Exn; case RefType::Any: case RefType::None: @@ -1476,6 +1477,11 @@ inline bool RefType::isSubTypeOf(RefType subType, RefType superType) { return true; } + // No exn is the bottom type of the exn hierarchy + if (subType.isNoExn() && superType.hierarchy() == RefTypeHierarchy::Exn) { + return true; + } + return false; } diff --git a/js/src/wasm/WasmValType.cpp b/js/src/wasm/WasmValType.cpp index 64ef8ff85a..0d9e1b24fb 100644 --- a/js/src/wasm/WasmValType.cpp +++ b/js/src/wasm/WasmValType.cpp @@ -52,6 +52,7 @@ RefType RefType::topType() const { case RefType::NoExtern: return RefType::extern_(); case RefType::Exn: + case RefType::NoExn: return RefType::exn(); case RefType::TypeRef: switch (typeDef()->kind()) { @@ -94,14 +95,12 @@ static bool ToRefType(JSContext* cx, JSLinearString* typeLinearStr, *out = RefType::extern_(); return true; } -#ifdef ENABLE_WASM_EXNREF if (ExnRefAvailable(cx)) { if (StringEqualsLiteral(typeLinearStr, "exnref")) { *out = RefType::exn(); return true; } } -#endif #ifdef ENABLE_WASM_GC if (GcAvailable(cx)) { if (StringEqualsLiteral(typeLinearStr, "anyref")) { @@ -132,6 +131,10 @@ static bool ToRefType(JSContext* cx, JSLinearString* typeLinearStr, *out = RefType::noextern(); return true; } + if (StringEqualsLiteral(typeLinearStr, "nullexnref")) { + *out = RefType::noexn(); + return true; + } if (StringEqualsLiteral(typeLinearStr, "nullref")) { *out = RefType::none(); return true; @@ -220,6 +223,9 @@ UniqueChars wasm::ToString(RefType type, const TypeContext* types) { case RefType::NoFunc: literal = "nullfuncref"; break; + case RefType::NoExn: + literal = "nullexnref"; + break; case RefType::NoExtern: literal = "nullexternref"; break; @@ -263,6 +269,9 @@ UniqueChars wasm::ToString(RefType type, const TypeContext* types) { case RefType::NoFunc: heapType = "nofunc"; break; + case RefType::NoExn: + heapType = "noexn"; + break; case RefType::NoExtern: heapType = "noextern"; break; diff --git a/js/src/wasm/WasmValType.h b/js/src/wasm/WasmValType.h index c98eda28dd..b194b78113 100644 --- a/js/src/wasm/WasmValType.h +++ b/js/src/wasm/WasmValType.h @@ -316,6 +316,7 @@ class RefType { Any = uint8_t(TypeCode::AnyRef), NoFunc = uint8_t(TypeCode::NullFuncRef), NoExtern = uint8_t(TypeCode::NullExternRef), + NoExn = uint8_t(TypeCode::NullExnRef), None = uint8_t(TypeCode::NullAnyRef), Eq = uint8_t(TypeCode::EqRef), I31 = uint8_t(TypeCode::I31Ref), @@ -373,6 +374,7 @@ class RefType { case TypeCode::ArrayRef: case TypeCode::NullFuncRef: case TypeCode::NullExternRef: + case TypeCode::NullExnRef: case TypeCode::NullAnyRef: case AbstractTypeRefCode: return true; @@ -388,6 +390,7 @@ class RefType { static RefType any() { return RefType(Any, true); } static RefType nofunc() { return RefType(NoFunc, true); } static RefType noextern() { return RefType(NoExtern, true); } + static RefType noexn() { return RefType(NoExn, true); } static RefType none() { return RefType(None, true); } static RefType eq() { return RefType(Eq, true); } static RefType i31() { return RefType(I31, true); } @@ -399,6 +402,7 @@ class RefType { bool isAny() const { return kind() == RefType::Any; } bool isNoFunc() const { return kind() == RefType::NoFunc; } bool isNoExtern() const { return kind() == RefType::NoExtern; } + bool isNoExn() const { return kind() == RefType::NoExn; } bool isNone() const { return kind() == RefType::None; } bool isEq() const { return kind() == RefType::Eq; } bool isI31() const { return kind() == RefType::I31; } @@ -412,7 +416,9 @@ class RefType { return RefType(ptc_.withIsNullable(nullable)); } - bool isRefBottom() const { return isNone() || isNoFunc() || isNoExtern(); } + bool isRefBottom() const { + return isNone() || isNoFunc() || isNoExtern() || isNoExn(); + } // These methods are defined in WasmTypeDef.h to avoid a cycle while allowing // inlining. @@ -469,6 +475,7 @@ class StorageTypeTraits { case TypeCode::FuncRef: case TypeCode::ExternRef: case TypeCode::ExnRef: + case TypeCode::NullExnRef: #ifdef ENABLE_WASM_GC case TypeCode::AnyRef: case TypeCode::EqRef: @@ -547,6 +554,7 @@ class ValTypeTraits { case TypeCode::FuncRef: case TypeCode::ExternRef: case TypeCode::ExnRef: + case TypeCode::NullExnRef: #ifdef ENABLE_WASM_GC case TypeCode::AnyRef: case TypeCode::EqRef: @@ -725,6 +733,8 @@ class PackedType : public T { bool isNoExtern() const { return tc_.typeCode() == TypeCode::NullExternRef; } + bool isNoExn() const { return tc_.typeCode() == TypeCode::NullExnRef; } + bool isNone() const { return tc_.typeCode() == TypeCode::NullAnyRef; } bool isEqRef() const { return tc_.typeCode() == TypeCode::EqRef; } @@ -745,9 +755,9 @@ class PackedType : public T { // Returns whether the type has a representation in JS. bool isExposable() const { #if defined(ENABLE_WASM_SIMD) - return kind() != Kind::V128 && !isExnRef(); + return kind() != Kind::V128 && !isExnRef() && !isNoExn(); #else - return !isExnRef(); + return !isExnRef() && !isNoExn(); #endif } diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp index d67967fa41..3a0f865d46 100644 --- a/js/src/wasm/WasmValidate.cpp +++ b/js/src/wasm/WasmValidate.cpp @@ -2929,6 +2929,92 @@ bool wasm::StartsCodeSection(const uint8_t* begin, const uint8_t* end, return false; } +#ifdef ENABLE_WASM_BRANCH_HINTING +static bool ParseBranchHintingSection(Decoder& d, ModuleEnvironment* env) { + uint32_t functionCount; + if (!d.readVarU32(&functionCount)) { + return d.fail("failed to read function count"); + } + + for (uint32_t i = 0; i < functionCount; i++) { + uint32_t functionIndex; + if (!d.readVarU32(&functionIndex)) { + return d.fail("failed to read function index"); + } + + // Disallow branch hints on imported functions. + if ((functionIndex >= env->funcs.length()) || + (functionIndex < env->numFuncImports)) { + return d.fail("invalid function index in branch hint"); + } + + uint32_t hintCount; + if (!d.readVarU32(&hintCount)) { + return d.fail("failed to read hint count"); + } + + BranchHintVector hintVector; + if (!hintVector.reserve(hintCount)) { + return false; + } + + // Branch hint offsets must appear in increasing byte offset order, at most + // once for each offset. + uint32_t prevOffsetPlus1 = 0; + for (uint32_t hintIndex = 0; hintIndex < hintCount; hintIndex++) { + uint32_t branchOffset; + if (!d.readVarU32(&branchOffset)) { + return d.fail("failed to read branch offset"); + } + if (branchOffset <= prevOffsetPlus1) { + return d.fail("Invalid offset in code hint"); + } + + uint32_t reserved; + if (!d.readVarU32(&reserved) || (reserved != 1)) { + return d.fail("Invalid reserved value for code hint"); + } + + uint32_t branchHintValue; + if (!d.readVarU32(&branchHintValue) || + (branchHintValue >= MaxBranchHintValue)) { + return d.fail("Invalid branch hint value"); + } + + BranchHint branchHint = static_cast<BranchHint>(branchHintValue); + BranchHintEntry entry(branchOffset, branchHint); + hintVector.infallibleAppend(entry); + + prevOffsetPlus1 = branchOffset; + } + + // Save this collection in the module + if (!env->branchHints.addHintsForFunc(functionIndex, + std::move(hintVector))) { + return false; + } + } + + return true; +} + +static bool DecodeBranchHintingSection(Decoder& d, ModuleEnvironment* env) { + MaybeSectionRange range; + if (!d.startCustomSection(BranchHintingSectionName, env, &range)) { + return false; + } + if (!range) { + return true; + } + + // Skip this custom section if errors are encountered during parsing. + env->parsedBranchHints = ParseBranchHintingSection(d, env); + + d.finishCustomSection(BranchHintingSectionName, *range); + return true; +} +#endif + bool wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env) { if (!DecodePreamble(d)) { return false; @@ -2984,6 +3070,12 @@ bool wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env) { return false; } +#ifdef ENABLE_WASM_BRANCH_HINTING + if (env->branchHintingEnabled() && !DecodeBranchHintingSection(d, env)) { + return false; + } +#endif + if (!d.startSection(SectionId::Code, env, &env->codeSection, "code")) { return false; } diff --git a/js/src/wasm/WasmValidate.h b/js/src/wasm/WasmValidate.h index f8d712b3b3..4dab19416c 100644 --- a/js/src/wasm/WasmValidate.h +++ b/js/src/wasm/WasmValidate.h @@ -58,6 +58,7 @@ struct ModuleEnvironment { MemoryDescVector memories; MutableTypeContext types; FuncDescVector funcs; + BranchHintCollection branchHints; uint32_t numFuncImports; uint32_t numGlobalImports; GlobalDescVector globals; @@ -93,6 +94,9 @@ struct ModuleEnvironment { Maybe<Name> moduleName; NameVector funcNames; + // Indicates whether the branch hint section was successfully parsed. + bool parsedBranchHints; + explicit ModuleEnvironment(FeatureArgs features, ModuleKind kind = ModuleKind::Wasm) : kind(kind), @@ -103,7 +107,8 @@ struct ModuleEnvironment { typeDefsOffsetStart(UINT32_MAX), memoriesOffsetStart(UINT32_MAX), tablesOffsetStart(UINT32_MAX), - tagsOffsetStart(UINT32_MAX) {} + tagsOffsetStart(UINT32_MAX), + parsedBranchHints(false) {} [[nodiscard]] bool init() { types = js_new<TypeContext>(features); diff --git a/js/src/wasm/WasmValue.cpp b/js/src/wasm/WasmValue.cpp index 6039b00517..824cc0e2cc 100644 --- a/js/src/wasm/WasmValue.cpp +++ b/js/src/wasm/WasmValue.cpp @@ -135,6 +135,8 @@ bool wasm::CheckRefType(JSContext* cx, RefType targetType, HandleValue v, return CheckAnyRefValue(cx, v, refval); case RefType::NoFunc: return CheckNullFuncRefValue(cx, v, fnval); + case RefType::NoExn: + return CheckNullExnRefValue(cx, v, refval); case RefType::NoExtern: return CheckNullExternRefValue(cx, v, refval); case RefType::None: @@ -200,6 +202,18 @@ bool wasm::CheckNullFuncRefValue(JSContext* cx, HandleValue v, return true; } +bool wasm::CheckNullExnRefValue(JSContext* cx, HandleValue v, + MutableHandleAnyRef vp) { + if (!v.isNull()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_NULL_EXNREF_VALUE); + return false; + } + + vp.set(AnyRef::null()); + return true; +} + bool wasm::CheckNullExternRefValue(JSContext* cx, HandleValue v, MutableHandleAnyRef vp) { if (!v.isNull()) { @@ -436,6 +450,23 @@ bool ToWebAssemblyValue_externref(JSContext* cx, HandleValue val, void** loc, } template <typename Debug = NoDebug> +bool ToWebAssemblyValue_nullexnref(JSContext* cx, HandleValue val, void** loc, + bool mustWrite64) { + RootedAnyRef result(cx, AnyRef::null()); + if (!CheckNullExnRefValue(cx, val, &result)) { + return false; + } + loc[0] = result.get().forCompiledCode(); +#ifndef JS_64BIT + if (mustWrite64) { + loc[1] = nullptr; + } +#endif + Debug::print(*loc); + return true; +} + +template <typename Debug = NoDebug> bool ToWebAssemblyValue_nullexternref(JSContext* cx, HandleValue val, void** loc, bool mustWrite64) { RootedAnyRef result(cx, AnyRef::null()); @@ -667,6 +698,9 @@ bool wasm::ToWebAssemblyValue(JSContext* cx, HandleValue val, ValType type, case RefType::NoFunc: return ToWebAssemblyValue_nullfuncref<Debug>(cx, val, (void**)loc, mustWrite64); + case RefType::NoExn: + return ToWebAssemblyValue_nullexnref<Debug>(cx, val, (void**)loc, + mustWrite64); case RefType::NoExtern: return ToWebAssemblyValue_nullexternref<Debug>(cx, val, (void**)loc, mustWrite64); @@ -775,6 +809,9 @@ bool ToJSValue_lossless(JSContext* cx, const void* src, MutableHandleValue dst, cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmGlobal)); Rooted<WasmGlobalObject*> srcGlobal( cx, WasmGlobalObject::create(cx, srcVal, false, prototype)); + if (!srcGlobal) { + return false; + } dst.set(ObjectValue(*srcGlobal.get())); return true; } diff --git a/js/src/wasm/WasmValue.h b/js/src/wasm/WasmValue.h index 9a5442fc75..ddb2463883 100644 --- a/js/src/wasm/WasmValue.h +++ b/js/src/wasm/WasmValue.h @@ -371,6 +371,10 @@ using RootedValVectorN = Rooted<ValVectorN<N>>; [[nodiscard]] extern bool CheckAnyRefValue(JSContext* cx, HandleValue v, MutableHandleAnyRef vp); +// The same as above for when the target type is 'nullexnref'. +[[nodiscard]] extern bool CheckNullExnRefValue(JSContext* cx, HandleValue v, + MutableHandleAnyRef vp); + // The same as above for when the target type is 'nullexternref'. [[nodiscard]] extern bool CheckNullExternRefValue(JSContext* cx, HandleValue v, MutableHandleAnyRef vp); diff --git a/js/src/wasm/moz.build b/js/src/wasm/moz.build index 83fea3b81b..e8bf00a9d6 100644 --- a/js/src/wasm/moz.build +++ b/js/src/wasm/moz.build @@ -41,6 +41,7 @@ UNIFIED_SOURCES += [ "WasmModule.cpp", "WasmModuleTypes.cpp", "WasmOpIter.cpp", + "WasmPI.cpp", "WasmProcess.cpp", "WasmRealm.cpp", "WasmSerialize.cpp", |