summaryrefslogtreecommitdiffstats
path: root/js/src/wasm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
commit59203c63bb777a3bacec32fb8830fba33540e809 (patch)
tree58298e711c0ff0575818c30485b44a2f21bf28a0 /js/src/wasm
parentAdding upstream version 126.0.1. (diff)
downloadfirefox-59203c63bb777a3bacec32fb8830fba33540e809.tar.xz
firefox-59203c63bb777a3bacec32fb8830fba33540e809.zip
Adding upstream version 127.0.upstream/127.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/wasm')
-rw-r--r--js/src/wasm/WasmBinary.h6
-rw-r--r--js/src/wasm/WasmBuiltinModule.yaml68
-rw-r--r--js/src/wasm/WasmBuiltins.cpp40
-rw-r--r--js/src/wasm/WasmBuiltins.h6
-rw-r--r--js/src/wasm/WasmCode.h8
-rw-r--r--js/src/wasm/WasmCodegenTypes.h4
-rw-r--r--js/src/wasm/WasmCompile.cpp10
-rw-r--r--js/src/wasm/WasmCompileArgs.h23
-rw-r--r--js/src/wasm/WasmConstants.h35
-rw-r--r--js/src/wasm/WasmContext.h48
-rw-r--r--js/src/wasm/WasmDump.cpp6
-rw-r--r--js/src/wasm/WasmFrameIter.cpp64
-rw-r--r--js/src/wasm/WasmFrameIter.h4
-rw-r--r--js/src/wasm/WasmGcObject.cpp9
-rw-r--r--js/src/wasm/WasmGcObject.h3
-rw-r--r--js/src/wasm/WasmGenerator.cpp2
-rw-r--r--js/src/wasm/WasmInstance.cpp49
-rw-r--r--js/src/wasm/WasmInstance.h18
-rw-r--r--js/src/wasm/WasmIonCompile.cpp133
-rw-r--r--js/src/wasm/WasmJS.cpp178
-rw-r--r--js/src/wasm/WasmJS.h24
-rw-r--r--js/src/wasm/WasmModule.cpp1
-rw-r--r--js/src/wasm/WasmModuleTypes.cpp2
-rw-r--r--js/src/wasm/WasmModuleTypes.h49
-rw-r--r--js/src/wasm/WasmOpIter.cpp2
-rw-r--r--js/src/wasm/WasmOpIter.h82
-rw-r--r--js/src/wasm/WasmPI.cpp1743
-rw-r--r--js/src/wasm/WasmPI.h170
-rw-r--r--js/src/wasm/WasmRealm.cpp19
-rw-r--r--js/src/wasm/WasmRealm.h6
-rw-r--r--js/src/wasm/WasmSerialize.cpp5
-rw-r--r--js/src/wasm/WasmStubs.cpp45
-rw-r--r--js/src/wasm/WasmTypeDef.h6
-rw-r--r--js/src/wasm/WasmValType.cpp13
-rw-r--r--js/src/wasm/WasmValType.h16
-rw-r--r--js/src/wasm/WasmValidate.cpp92
-rw-r--r--js/src/wasm/WasmValidate.h7
-rw-r--r--js/src/wasm/WasmValue.cpp37
-rw-r--r--js/src/wasm/WasmValue.h4
-rw-r--r--js/src/wasm/moz.build1
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(&params, &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",