Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 3b71eca

Browse files
committed
Bug 1910880 - Use a real JIT exit frame for Wasm JitEntry stub. r=rhunt
JS JIT callers can call Wasm code either directly from Ion or through the JitEntry stub. In the JitEntry case, we didn't have a real JS JIT exit frame. To make JS frame iteration still work in this case, a special `JSJitToWasm` frame type was used to transition from Wasm frame iteration to JIT frame iteration. Now that we can unwind Wasm and JS frames directly, this caused some issues because there were places where we were expecting an actual JIT exit frame. This patch changes the JitEntry stub to initialize a JIT exit frame. To do this, we just need to store an `ExitFooterFrame` below the frame pointer. This is similar to what we were already doing in `GenerateJitEntryThrow`. This lets us remove the `JSJitToWasm` frame type and simplify frame iteration a bit. Differential Revision: https://phabricator.services.mozilla.com/D218346
1 parent 28c2dc5 commit 3b71eca

File tree

10 files changed

+94
-84
lines changed

10 files changed

+94
-84
lines changed

js/src/gdb/mozilla/unwind.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def debug(something):
4949
"FrameType::BaselineStub": "BaselineStubFrameLayout",
5050
"FrameType::CppToJSJit": "JitFrameLayout",
5151
"FrameType::WasmToJSJit": "JitFrameLayout",
52-
"FrameType::JSJitToWasm": "JitFrameLayout",
5352
"FrameType::Rectifier": "RectifierFrameLayout",
5453
"FrameType::IonAccessorIC": "IonAccessorICFrameLayout",
5554
"FrameType::IonICCall": "IonICCallFrameLayout",
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// |jit-test| --fast-warmup; skip-if: !wasmIsSupported()
2+
3+
gczeal(2);
4+
5+
function wasmEvalText(str) {
6+
var bin = wasmTextToBinary(str);
7+
var m = new WebAssembly.Module(bin);
8+
return new WebAssembly.Instance(m);
9+
}
10+
function test() {
11+
var instance = wasmEvalText(`
12+
(module (type $a (array (mut i32)))
13+
(func (export "createDefault") (param i32) (result eqref)
14+
local.get 0
15+
array.new_default $a
16+
)
17+
)
18+
`);
19+
var createDefault = instance.exports.createDefault;
20+
21+
var g = newGlobal({newCompartment: true});
22+
g.debuggeeGlobal = this;
23+
g.eval("(" + function () {
24+
var dbg = new Debugger(debuggeeGlobal);
25+
dbg.onExceptionUnwind = function () {
26+
throw new Error("x");
27+
};
28+
} + ")();");
29+
30+
for (var i = 0; i < 8; i++) {
31+
try {
32+
createDefault(-1);
33+
} catch (e) {
34+
}
35+
}
36+
}
37+
test();
38+
quit(0); // Ensure exit code is 0, not 3.

js/src/jit/Ion.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2325,20 +2325,15 @@ static void InvalidateActivation(JS::GCContext* gcx,
23252325

23262326
for (OnlyJSJitFrameIter iter(activations); !iter.done(); ++iter, ++frameno) {
23272327
const JSJitFrameIter& frame = iter.frame();
2328-
MOZ_ASSERT_IF(frameno == 1, frame.isExitFrame() ||
2329-
frame.type() == FrameType::Bailout ||
2330-
frame.type() == FrameType::JSJitToWasm);
2328+
MOZ_ASSERT_IF(frameno == 1,
2329+
frame.isExitFrame() || frame.type() == FrameType::Bailout);
23312330

23322331
#ifdef JS_JITSPEW
23332332
switch (frame.type()) {
23342333
case FrameType::Exit:
23352334
JitSpew(JitSpew_IonInvalidate, "#%zu exit frame @ %p", frameno,
23362335
frame.fp());
23372336
break;
2338-
case FrameType::JSJitToWasm:
2339-
JitSpew(JitSpew_IonInvalidate, "#%zu wasm exit frame @ %p", frameno,
2340-
frame.fp());
2341-
break;
23422337
case FrameType::BaselineJS:
23432338
case FrameType::IonJS:
23442339
case FrameType::Bailout: {

js/src/jit/JSJitFrameIter.cpp

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,16 @@ JSJitFrameIter::JSJitFrameIter(const JitActivation* activation)
3838
MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI);
3939
}
4040

41-
JSJitFrameIter::JSJitFrameIter(const JitActivation* activation,
42-
FrameType frameType, uint8_t* fp, bool unwinding)
43-
: current_(fp), type_(frameType), activation_(activation) {
41+
JSJitFrameIter::JSJitFrameIter(const JitActivation* activation, uint8_t* fp,
42+
bool unwinding)
43+
: current_(fp), type_(FrameType::Exit), activation_(activation) {
4444
// This constructor is only used when resuming iteration after iterating Wasm
4545
// frames in the same JitActivation so ignore activation_->bailoutData().
46-
//
47-
// Note: FrameType::JSJitToWasm is used for JIT => Wasm calls through the Wasm
48-
// JIT entry trampoline. FrameType::Exit is used for direct Ion => Wasm calls.
4946
if (unwinding) {
5047
MOZ_ASSERT(fp == activation->jsExitFP());
5148
} else {
5249
MOZ_ASSERT(fp > activation->jsOrWasmExitFP());
5350
}
54-
MOZ_ASSERT(type_ == FrameType::JSJitToWasm || type_ == FrameType::Exit);
5551
MOZ_ASSERT(!TlsContext.get()->inUnsafeCallWithABI);
5652
}
5753

@@ -409,9 +405,6 @@ void JSJitFrameIter::dump() const {
409405
case FrameType::Exit:
410406
fprintf(stderr, " Exit frame\n");
411407
break;
412-
case FrameType::JSJitToWasm:
413-
fprintf(stderr, " Wasm exit frame\n");
414-
break;
415408
};
416409
fputc('\n', stderr);
417410
}
@@ -815,7 +808,6 @@ void JSJitProfilingFrameIterator::moveToNextFrame(CommonFrameLayout* frame) {
815808
case FrameType::TrampolineNative:
816809
case FrameType::Exit:
817810
case FrameType::Bailout:
818-
case FrameType::JSJitToWasm:
819811
// Rectifier and Baseline Interpreter entry frames are handled before
820812
// this switch. The other frame types can't call JS functions directly.
821813
break;

js/src/jit/JSJitFrameIter.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,6 @@ enum class FrameType {
6969
// point of view of JS JITs, this is just another kind of entry frame.
7070
WasmToJSJit,
7171

72-
// A JS to wasm frame is constructed during fast calls from any JS jits to
73-
// wasm, and is a special kind of exit frame that doesn't have the exit
74-
// footer. From the point of view of the jit, it can be skipped as an exit.
75-
JSJitToWasm,
76-
7772
// Frame for a TrampolineNative, a JS builtin implemented with a JIT
7873
// trampoline. See jit/TrampolineNatives.h.
7974
TrampolineNative,
@@ -133,8 +128,7 @@ class JSJitFrameIter {
133128

134129
// A constructor specialized for jit->wasm frames, which starts at a
135130
// specific FP.
136-
JSJitFrameIter(const JitActivation* activation, FrameType frameType,
137-
uint8_t* fp, bool unwinding);
131+
JSJitFrameIter(const JitActivation* activation, uint8_t* fp, bool unwinding);
138132

139133
void setResumePCInCurrentFrame(uint8_t* newAddr) {
140134
resumePCinCurrentFrame_ = newAddr;

js/src/jit/JitFrames.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,14 +1429,6 @@ static void TraceRectifierFrame(JSTracer* trc, const JSJitFrameIter& frame) {
14291429
TraceRoot(trc, &layout->thisv(), "rectifier-thisv");
14301430
}
14311431

1432-
static void TraceJSJitToWasmFrame(JSTracer* trc, const JSJitFrameIter& frame) {
1433-
// This is doing a subset of TraceIonJSFrame, since the callee doesn't
1434-
// have a script.
1435-
JitFrameLayout* layout = (JitFrameLayout*)frame.fp();
1436-
layout->replaceCalleeToken(TraceCalleeToken(trc, layout->calleeToken()));
1437-
TraceThisAndArguments(trc, frame, layout);
1438-
}
1439-
14401432
static void TraceTrampolineNativeFrame(JSTracer* trc,
14411433
const JSJitFrameIter& frame) {
14421434
auto* layout = (TrampolineNativeFrameLayout*)frame.fp();
@@ -1507,9 +1499,6 @@ static void TraceJitActivation(JSTracer* trc, JitActivation* activation) {
15071499
// JitFrameIter know the frame above is a wasm frame, handled
15081500
// in the next iteration.
15091501
break;
1510-
case FrameType::JSJitToWasm:
1511-
TraceJSJitToWasmFrame(trc, jitFrame);
1512-
break;
15131502
default:
15141503
MOZ_CRASH("unexpected frame type");
15151504
}

js/src/vm/FrameIter.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,15 +202,13 @@ void JitFrameIter::settle() {
202202

203203
MOZ_ASSERT(wasmFrame.done());
204204
uint8_t* prevFP = wasmFrame.unwoundCallerFP();
205-
jit::FrameType prevFrameType = wasmFrame.unwoundJitFrameType();
206205

207206
if (mustUnwindActivation_) {
208207
act_->setJSExitFP(prevFP);
209208
}
210209

211210
iter_.destroy();
212-
iter_.construct<jit::JSJitFrameIter>(act_, prevFrameType, prevFP,
213-
mustUnwindActivation_);
211+
iter_.construct<jit::JSJitFrameIter>(act_, prevFP, mustUnwindActivation_);
214212
MOZ_ASSERT(!asJSJit().done());
215213
return;
216214
}

js/src/wasm/WasmFrameIter.cpp

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -180,17 +180,21 @@ void WasmFrameIter::operator++() {
180180
popFrame();
181181
}
182182

183-
static inline void AssertDirectJitCall(const void* fp) {
184-
// Called via an inlined fast JIT to wasm call: in this case, FP is
185-
// pointing in the middle of the exit frame, right before the exit
186-
// footer; ensure the exit frame type is the expected one.
183+
static inline void AssertJitExitFrame(const void* fp,
184+
jit::ExitFrameType expected) {
185+
// Called via a JIT to wasm call: in this case, FP is pointing in the middle
186+
// of the exit frame, right before the exit footer; ensure the exit frame type
187+
// is the expected one.
187188
#ifdef DEBUG
188189
auto* jitCaller = (ExitFrameLayout*)fp;
189-
MOZ_ASSERT(jitCaller->footer()->type() ==
190-
jit::ExitFrameType::DirectWasmJitCall);
190+
MOZ_ASSERT(jitCaller->footer()->type() == expected);
191191
#endif
192192
}
193193

194+
static inline void AssertDirectJitCall(const void* fp) {
195+
AssertJitExitFrame(fp, jit::ExitFrameType::DirectWasmJitCall);
196+
}
197+
194198
void WasmFrameIter::popFrame() {
195199
uint8_t* returnAddress = fp_->returnAddress();
196200
code_ = LookupCode(returnAddress, &codeRange_);
@@ -215,7 +219,7 @@ void WasmFrameIter::popFrame() {
215219
AssertDirectJitCall(fp_->jitEntryCaller());
216220

217221
unwoundCallerFP_ = fp_->jitEntryCaller();
218-
unwoundJitFrameType_.emplace(FrameType::Exit);
222+
hasUnwoundJitFrame_ = true;
219223

220224
if (unwind_ == Unwind::True) {
221225
activation_->setJSExitFP(unwoundCallerFP());
@@ -239,6 +243,7 @@ void WasmFrameIter::popFrame() {
239243
if (codeRange_->isInterpEntry()) {
240244
// Interpreter entry has a simple frame, record FP from it.
241245
unwoundCallerFP_ = reinterpret_cast<uint8_t*>(fp_);
246+
MOZ_ASSERT(!hasUnwoundJitFrame_);
242247

243248
fp_ = nullptr;
244249
code_ = nullptr;
@@ -266,10 +271,12 @@ void WasmFrameIter::popFrame() {
266271
// | WASM FRAME | (already unwound)
267272
// |---------------------|
268273
//
269-
// The next value of FP is just a regular jit frame used as a marker to
270-
// know that we should transition to a JSJit frame iterator.
274+
// The next value of FP is a jit exit frame with type WasmGenericJitEntry.
275+
// This lets us transition to a JSJit frame iterator.
271276
unwoundCallerFP_ = reinterpret_cast<uint8_t*>(fp_);
272-
unwoundJitFrameType_.emplace(FrameType::JSJitToWasm);
277+
hasUnwoundJitFrame_ = true;
278+
AssertJitExitFrame(unwoundCallerFP_,
279+
jit::ExitFrameType::WasmGenericJitEntry);
273280

274281
fp_ = nullptr;
275282
code_ = nullptr;
@@ -411,13 +418,8 @@ DebugFrame* WasmFrameIter::debugFrame() const {
411418
}
412419

413420
bool WasmFrameIter::hasUnwoundJitFrame() const {
414-
return unwoundCallerFP_ && unwoundJitFrameType_.isSome();
415-
}
416-
417-
jit::FrameType WasmFrameIter::unwoundJitFrameType() const {
418-
MOZ_ASSERT(unwoundCallerFP_);
419-
MOZ_ASSERT(unwoundJitFrameType_.isSome());
420-
return *unwoundJitFrameType_;
421+
MOZ_ASSERT_IF(hasUnwoundJitFrame_, unwoundCallerFP_);
422+
return hasUnwoundJitFrame_;
421423
}
422424

423425
uint8_t* WasmFrameIter::resumePCinCurrentFrame() const {

js/src/wasm/WasmFrameIter.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ class WasmFrameIter {
6969
Frame* fp_;
7070
Instance* instance_;
7171
uint8_t* unwoundCallerFP_;
72-
mozilla::Maybe<jit::FrameType> unwoundJitFrameType_;
72+
// Whether unwoundCallerFP_ is a JS JIT exit frame.
73+
bool hasUnwoundJitFrame_ = false;
7374
Unwind unwind_;
7475
void** unwoundAddressOfReturnAddress_;
7576
uint8_t* resumePCinCurrentFrame_;
@@ -98,7 +99,6 @@ class WasmFrameIter {
9899
void** unwoundAddressOfReturnAddress() const;
99100
bool debugEnabled() const;
100101
DebugFrame* debugFrame() const;
101-
jit::FrameType unwoundJitFrameType() const;
102102
bool hasUnwoundJitFrame() const;
103103
uint8_t* unwoundCallerFP() const { return unwoundCallerFP_; }
104104
Frame* frame() const { return fp_; }

js/src/wasm/WasmStubs.cpp

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -932,29 +932,40 @@ static bool GenerateJitEntry(MacroAssembler& masm, size_t funcExportIndex,
932932
//
933933
// GenerateJitEntryPrologue has additionally pushed the caller's frame
934934
// pointer. The stack pointer is now JitStackAlignment-aligned.
935+
//
936+
// We initialize an ExitFooterFrame (with ExitFrameType::WasmGenericJitEntry)
937+
// immediately below the frame pointer to ensure FP is a valid JS JIT exit
938+
// frame.
935939

936940
MOZ_ASSERT(masm.framePushed() == 0);
937941

938-
unsigned normalBytesNeeded = StackArgBytesForWasmABI(funcType);
942+
unsigned normalBytesNeeded =
943+
ExitFooterFrame::Size() + StackArgBytesForWasmABI(funcType);
939944

940945
MIRTypeVector coerceArgTypes;
941946
MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Int32));
942947
MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Pointer));
943948
MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Pointer));
944-
unsigned oolBytesNeeded = StackArgBytesForWasmABI(coerceArgTypes);
949+
unsigned oolBytesNeeded =
950+
ExitFooterFrame::Size() + StackArgBytesForWasmABI(coerceArgTypes);
945951

946952
unsigned bytesNeeded = std::max(normalBytesNeeded, oolBytesNeeded);
947953

948954
// Note the jit caller ensures the stack is aligned *after* the call
949955
// instruction.
950-
unsigned frameSizeExclFP = StackDecrementForCall(
951-
WasmStackAlignment, masm.framePushed(), bytesNeeded);
956+
unsigned frameSize = StackDecrementForCall(WasmStackAlignment,
957+
masm.framePushed(), bytesNeeded);
952958

953959
// Reserve stack space for wasm ABI arguments, set up like this:
954960
// <-- ABI args | padding
955-
masm.reserveStack(frameSizeExclFP);
961+
masm.reserveStack(frameSize);
962+
963+
MOZ_ASSERT(masm.framePushed() == frameSize);
956964

957-
uint32_t frameSize = masm.framePushed();
965+
// Initialize the ExitFooterFrame.
966+
static_assert(ExitFooterFrame::Size() == sizeof(uintptr_t));
967+
masm.storePtr(ImmWord(uint32_t(ExitFrameType::WasmGenericJitEntry)),
968+
Address(FramePointer, -int32_t(ExitFooterFrame::Size())));
958969

959970
GenerateJitEntryLoadInstance(masm);
960971

@@ -1198,9 +1209,6 @@ static bool GenerateJitEntry(MacroAssembler& masm, size_t funcExportIndex,
11981209
CallFuncExport(masm, fe, funcPtr);
11991210
masm.assertStackAlignment(WasmStackAlignment);
12001211

1201-
// Pop arguments.
1202-
masm.freeStackTo(frameSize - frameSizeExclFP);
1203-
12041212
GenPrintf(DebugChannel::Function, masm, "wasm-function[%d]; returns ",
12051213
fe.funcIndex());
12061214

@@ -1234,18 +1242,11 @@ static bool GenerateJitEntry(MacroAssembler& masm, size_t funcExportIndex,
12341242
break;
12351243
}
12361244
case ValType::I64: {
1237-
Label fail, done;
12381245
GenPrintI64(DebugChannel::Function, masm, ReturnReg64);
1239-
GenerateBigIntInitialization(masm, 0, ReturnReg64, scratchG, fe, &fail);
1246+
MOZ_ASSERT(masm.framePushed() == frameSize);
1247+
GenerateBigIntInitialization(masm, 0, ReturnReg64, scratchG, fe,
1248+
&exception);
12401249
masm.boxNonDouble(JSVAL_TYPE_BIGINT, scratchG, JSReturnOperand);
1241-
masm.jump(&done);
1242-
masm.bind(&fail);
1243-
// Fixup the stack for the exception tail so that we can share it.
1244-
masm.reserveStack(frameSizeExclFP);
1245-
masm.jump(&exception);
1246-
masm.bind(&done);
1247-
// Un-fixup the stack for the benefit of the assertion below.
1248-
masm.setFramePushed(0);
12491250
break;
12501251
}
12511252
case ValType::V128: {
@@ -1263,9 +1264,11 @@ static bool GenerateJitEntry(MacroAssembler& masm, size_t funcExportIndex,
12631264
}
12641265
}
12651266

1266-
GenPrintf(DebugChannel::Function, masm, "\n");
1267+
// Pop frame.
1268+
masm.moveToStackPtr(FramePointer);
1269+
masm.setFramePushed(0);
12671270

1268-
MOZ_ASSERT(masm.framePushed() == 0);
1271+
GenPrintf(DebugChannel::Function, masm, "\n");
12691272

12701273
AssertExpectedSP(masm);
12711274
GenerateJitEntryEpilogue(masm, offsets);
@@ -1318,14 +1321,14 @@ static bool GenerateJitEntry(MacroAssembler& masm, size_t funcExportIndex,
13181321
// No widening is required, as the return value is used as a bool.
13191322
masm.branchTest32(Assembler::NonZero, ReturnReg, ReturnReg,
13201323
&rejoinBeforeCall);
1324+
1325+
MOZ_ASSERT(masm.framePushed() == frameSize);
13211326
hasFallThroughForException = true;
13221327
}
13231328

1324-
// Prepare to throw: reload InstanceReg from the frame.
1325-
masm.bind(&exception);
1326-
masm.setFramePushed(frameSize);
13271329
if (exception.used() || hasFallThroughForException) {
1328-
masm.freeStackTo(frameSize);
1330+
masm.bind(&exception);
1331+
masm.setFramePushed(frameSize);
13291332
GenerateJitEntryThrow(masm, frameSize);
13301333
}
13311334

0 commit comments

Comments
 (0)