Skip to content

Commit c4f8ccd

Browse files
committed
[JSC][WASM][Debugger] Implement interleaved WASM/JS call stack collection
https://bugs.webkit.org/show_bug.cgi?id=310437 rdar://173077900 Reviewed by Yusuke Suzuki (OOPS\!). Replace the two duplicate WASM-only stack walk loops in WasmQueryHandler and WasmExecutionHandler (both marked FIXME for lacking JS interleaving support) with a shared collectCallStack function. collectCallStack uses a Step A/B/C loop: Step A emits the current frame if it is a JS frame (one entry per inline depth for DFG/FTL inlined frames, one otherwise); Step B advances to the caller; Step C dispatches on the caller type — JS (advance), IPIntMode (WASM->WASM, emit return PC from savedWasmToWasmPC), JSToWasm*/WasmBuiltinMode (skip trampoline), WasmToJS (recover outer WASM call-site PC from WasmToJSIPIntReturnPCSlot of the WasmToJS trampoline frame). Add GPRInfo::wasmIPIntPCRegister as a named alias for the IPInt PC register on all four supported platforms, distinct from jitDataRegister to make the calling-convention intent explicit. Add savedWasmToWasmPC(CallFrame*) as the single reader of the cfr-8 slot written by IPInt's saveIPIntRegisters() for WASM->WASM calls. Add savedWasmToJSPC(CallFrame*) as the reader of WasmToJSIPIntReturnPCSlot. Simplify getWasmReturnPC to call savedWasmToWasmPC once and pass the result directly to toVirtual. Add JS_FRAME_BASE constant to VirtualAddress and inferredNameWithHash() to InlineCallFrame for verbose logging in the DFG inlined frame path. Add five test resource files and matching test classes covering all interleaved topologies: JSTests/wasm/debugger/resources/wasm/js-js-wasm-js-js-wasm.js JSTests/wasm/debugger/resources/wasm/js-wasm-js-wasm.js JSTests/wasm/debugger/resources/wasm/wasm-js-wasm-js-wasm.js JSTests/wasm/debugger/resources/wasm/wasm-wasm-js-wasm-wasm.js JSTests/wasm/debugger/resources/wasm/wasm-wasm-wasm.js Canonical link: https://commits.webkit.org/309803@main
1 parent 7300baf commit c4f8ccd

17 files changed

Lines changed: 815 additions & 41 deletions
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Test case for deeply interleaved JS->JS->WASM->JS->JS->WASM call stack.
2+
// Exercises multiple JS frames on each side (DFG inlined + non-inlined) together
3+
// with the WasmToJS trampoline peek and JSToWasm trampoline skip.
4+
//
5+
// Call chain: JS main -> js_a -> js_b -> test[WASM] -> callback -> js_c -> wasm_inner[WASM]
6+
//
7+
// Function index mapping:
8+
// 0: imported js.callback (JS)
9+
// 1: test (WASM, exported) — calls js.callback
10+
// 2: wasm_inner (WASM, exported) — nop, breakpoint target
11+
//
12+
// Expected bt at 0x4000000000000045:
13+
// frame #0: 0x4000000000000045 (wasm_inner nop — breakpoint)
14+
// frame #1: 0xc000000000000000 (js_c)
15+
// frame #2: 0xc000000000000000 (callback)
16+
// frame #3: 0x4000000000000042 (test call-site return PC — after 'call 0' at 0x40)
17+
// frame #4: 0xc000000000000000 (js_b)
18+
// frame #5: 0xc000000000000000 (js_a)
19+
// frame #6: 0xc000000000000000 (JS main loop)
20+
var wasm = new Uint8Array([
21+
// [0x00] WASM header
22+
0x00, 0x61, 0x73, 0x6d, // magic
23+
0x01, 0x00, 0x00, 0x00, // version
24+
25+
// [0x08] Type section: 1 type
26+
0x01, 0x04, // section id=1, size=4
27+
0x01, // 1 type
28+
0x60, 0x00, 0x00, // type 0: (func [] -> [])
29+
30+
// [0x0e] Import section: js.callback
31+
0x02, 0x0f, // section id=2, size=15
32+
0x01, // 1 import
33+
0x02, 0x6a, 0x73, // module name "js" (length=2)
34+
0x08, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, // field name "callback" (length=8)
35+
0x00, 0x00, // import kind: function, type index 0
36+
37+
// [0x1f] Function section: 2 WASM functions (both type 0)
38+
0x03, 0x03, // section id=3, size=3
39+
0x02, // 2 functions
40+
0x00, 0x00, // both type 0
41+
42+
// [0x24] Export section: test (func 1), wasm_inner (func 2)
43+
0x07, 0x15, // section id=7, size=21
44+
0x02, // 2 exports
45+
0x04, 0x74, 0x65, 0x73, 0x74, // name "test" (length=4)
46+
0x00, 0x01, // kind: function, index 1
47+
0x0a, 0x77, 0x61, 0x73, 0x6d, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, // name "wasm_inner" (length=10)
48+
0x00, 0x02, // kind: function, index 2
49+
50+
// [0x3b] Code section
51+
0x0a, 0x0a, // section id=10, size=10
52+
0x02, // 2 functions
53+
54+
// [0x3e] Function 1 (test): calls js.callback
55+
0x04, // body size=4
56+
0x00, // 0 local declarations
57+
0x10, 0x00, // [0x40] call 0 (js.callback)
58+
0x0b, // [0x42] end
59+
60+
// [0x43] Function 2 (wasm_inner): breakpoint target
61+
0x03, // body size=3
62+
0x00, // 0 local declarations
63+
0x01, // [0x45] nop <-- breakpoint
64+
0x0b, // [0x46] end
65+
]);
66+
67+
var wasm_inner_func;
68+
69+
function js_c() {
70+
wasm_inner_func();
71+
}
72+
73+
function callback() {
74+
js_c();
75+
}
76+
77+
function js_b() {
78+
test();
79+
}
80+
81+
function js_a() {
82+
js_b();
83+
}
84+
85+
var module = new WebAssembly.Module(wasm);
86+
var instance = new WebAssembly.Instance(module, { js: { callback } });
87+
88+
wasm_inner_func = instance.exports.wasm_inner;
89+
let test = instance.exports.test;
90+
91+
let iteration = 0;
92+
for (;;) {
93+
js_a();
94+
iteration += 1;
95+
if (iteration % 1e6 == 0)
96+
print("iteration=", iteration);
97+
}
98+
99+
// b 0x4000000000000045
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Test case for a JS->WASM->JS->WASM interleaved call stack.
2+
// Exercises the WasmToJS trampoline peek and JSToWasm trampoline skip.
3+
//
4+
// Function index mapping:
5+
// 0: imported js.callback (JS)
6+
// 1: test (WASM, exported) — calls js.callback
7+
// 2: wasm_inner (WASM, exported) — nop, breakpoint target
8+
//
9+
// Expected bt at 0x4000000000000045:
10+
// frame #0: 0x4000000000000045 (wasm_inner nop — breakpoint)
11+
// frame #1: 0xc000000000000000 (JS callback)
12+
// frame #2: 0x4000000000000042 (test call-site return PC — after 'call 0' at 0x40)
13+
// frame #3: 0xc000000000000000 (JS main loop)
14+
var wasm = new Uint8Array([
15+
// [0x00] WASM header
16+
0x00, 0x61, 0x73, 0x6d, // magic
17+
0x01, 0x00, 0x00, 0x00, // version
18+
19+
// [0x08] Type section: 1 type
20+
0x01, 0x04, // section id=1, size=4
21+
0x01, // 1 type
22+
0x60, 0x00, 0x00, // type 0: (func [] -> [])
23+
24+
// [0x0e] Import section: js.callback
25+
0x02, 0x0f, // section id=2, size=15
26+
0x01, // 1 import
27+
0x02, 0x6a, 0x73, // module name "js" (length=2)
28+
0x08, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, // field name "callback" (length=8)
29+
0x00, 0x00, // import kind: function, type index 0
30+
31+
// [0x1f] Function section: 2 WASM functions (both type 0)
32+
0x03, 0x03, // section id=3, size=3
33+
0x02, // 2 functions
34+
0x00, 0x00, // both type 0
35+
36+
// [0x24] Export section: test (func 1), wasm_inner (func 2)
37+
0x07, 0x15, // section id=7, size=21
38+
0x02, // 2 exports
39+
0x04, 0x74, 0x65, 0x73, 0x74, // name "test" (length=4)
40+
0x00, 0x01, // kind: function, index 1
41+
0x0a, 0x77, 0x61, 0x73, 0x6d, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, // name "wasm_inner" (length=10)
42+
0x00, 0x02, // kind: function, index 2
43+
44+
// [0x3b] Code section
45+
0x0a, 0x0a, // section id=10, size=10
46+
0x02, // 2 functions
47+
48+
// [0x3e] Function 1 (test): calls js.callback
49+
0x04, // body size=4
50+
0x00, // 0 local declarations
51+
0x10, 0x00, // [0x40] call 0 (js.callback)
52+
0x0b, // [0x42] end
53+
54+
// [0x43] Function 2 (wasm_inner): breakpoint target
55+
0x03, // body size=3
56+
0x00, // 0 local declarations
57+
0x01, // [0x45] nop <-- breakpoint
58+
0x0b, // [0x46] end
59+
]);
60+
61+
var wasm_inner_func;
62+
63+
function callback() {
64+
wasm_inner_func();
65+
}
66+
67+
var module = new WebAssembly.Module(wasm);
68+
var instance = new WebAssembly.Instance(module, { js: { callback } });
69+
70+
wasm_inner_func = instance.exports.wasm_inner;
71+
let test = instance.exports.test;
72+
73+
let iteration = 0;
74+
for (;;) {
75+
test();
76+
iteration += 1;
77+
if (iteration % 1e6 == 0)
78+
print("iteration=", iteration);
79+
}
80+
81+
// b 0x4000000000000045
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Test case for WASM→JS→WASM→JS→WASM triple-interleaved call stack.
2+
// Exercises two WasmToJS crossings, verifying that WasmToJSIPIntReturnPCSlot is read
3+
// from the correct WasmToJS trampoline frame at each crossing.
4+
//
5+
// Call chain: JS main → test_outer[W] → js_mid_outer[JS] → test_mid[W] → js_mid_inner[JS] → wasm_inner[W]
6+
//
7+
// Function index mapping:
8+
// 0: imported js.mid_outer (JS)
9+
// 1: imported js.mid_inner (JS)
10+
// 2: test_outer (WASM, exported) — calls mid_outer (import 0)
11+
// 3: test_mid (WASM, exported) — calls mid_inner (import 1)
12+
// 4: wasm_inner (WASM, exported) — nop, breakpoint target
13+
//
14+
// Expected bt at 0x400000000000006c:
15+
// frame #0: 0x400000000000006c (wasm_inner nop — breakpoint)
16+
// frame #1: 0xc000000000000000 (JS js_mid_inner frame)
17+
// frame #2: 0x4000000000000069 (test_mid return PC, after 'call mid_inner' at 0x67 — from WasmToJSIPIntReturnPCSlot in WasmToJS frame)
18+
// frame #3: 0xc000000000000000 (JS js_mid_outer frame)
19+
// frame #4: 0x4000000000000064 (test_outer return PC, after 'call mid_outer' at 0x62 — from WasmToJSIPIntReturnPCSlot in WasmToJS frame)
20+
// frame #5: 0xc000000000000000 (JS main loop)
21+
var wasm = new Uint8Array([
22+
// [0x00] WASM header
23+
0x00, 0x61, 0x73, 0x6d, // magic
24+
0x01, 0x00, 0x00, 0x00, // version
25+
26+
// [0x08] Type section: 1 type (func [] -> [])
27+
0x01, 0x04, // section id=1, size=4
28+
0x01, // 1 type
29+
0x60, 0x00, 0x00, // type 0: (func [] -> [])
30+
31+
// [0x0e] Import section: js.mid_outer, js.mid_inner
32+
0x02, 0x1f, // section id=2, size=31
33+
0x02, // 2 imports
34+
0x02, 0x6a, 0x73, // module name "js" (length=2)
35+
0x09, 0x6d, 0x69, 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, // field "mid_outer" (length=9)
36+
0x00, 0x00, // import kind: function, type index 0
37+
0x02, 0x6a, 0x73, // module name "js" (length=2)
38+
0x09, 0x6d, 0x69, 0x64, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, // field "mid_inner" (length=9)
39+
0x00, 0x00, // import kind: function, type index 0
40+
41+
// [0x2f] Function section: 3 WASM functions (indices 2–4, all type 0)
42+
0x03, 0x04, // section id=3, size=4
43+
0x03, // 3 functions
44+
0x00, 0x00, 0x00, // all type 0
45+
46+
// [0x35] Export section: test_outer (func 2), test_mid (func 3), wasm_inner (func 4)
47+
0x07, 0x26, // section id=7, size=38
48+
0x03, // 3 exports
49+
0x0a, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, // name "test_outer" (length=10)
50+
0x00, 0x02, // kind: function, index 2
51+
0x08, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x69, 0x64, // name "test_mid" (length=8)
52+
0x00, 0x03, // kind: function, index 3
53+
0x0a, 0x77, 0x61, 0x73, 0x6d, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, // name "wasm_inner" (length=10)
54+
0x00, 0x04, // kind: function, index 4
55+
56+
// [0x5d] Code section
57+
0x0a, 0x0f, // section id=10, size=15
58+
0x03, // 3 function bodies
59+
60+
// [0x60] Function 2 (test_outer): calls mid_outer (import 0)
61+
0x04, // body size=4
62+
0x00, // 0 local declarations
63+
0x10, 0x00, // [0x62] call 0 (mid_outer)
64+
0x0b, // [0x64] end ← return PC in test_outer
65+
66+
// [0x65] Function 3 (test_mid): calls mid_inner (import 1)
67+
0x04, // body size=4
68+
0x00, // 0 local declarations
69+
0x10, 0x01, // [0x67] call 1 (mid_inner)
70+
0x0b, // [0x69] end ← return PC in test_mid
71+
72+
// [0x6a] Function 4 (wasm_inner): breakpoint target
73+
0x03, // body size=3
74+
0x00, // 0 local declarations
75+
0x01, // [0x6c] nop <-- breakpoint
76+
0x0b, // [0x6d] end
77+
]);
78+
79+
var test_mid_func;
80+
var wasm_inner_func;
81+
82+
function js_mid_inner() {
83+
wasm_inner_func();
84+
}
85+
86+
function js_mid_outer() {
87+
test_mid_func();
88+
}
89+
90+
var module = new WebAssembly.Module(wasm);
91+
var instance = new WebAssembly.Instance(module, { js: { mid_outer: js_mid_outer, mid_inner: js_mid_inner } });
92+
93+
test_mid_func = instance.exports.test_mid;
94+
wasm_inner_func = instance.exports.wasm_inner;
95+
let test_outer = instance.exports.test_outer;
96+
97+
let iteration = 0;
98+
for (;;) {
99+
test_outer();
100+
iteration += 1;
101+
if (iteration % 1e6 == 0)
102+
print("iteration=", iteration);
103+
}
104+
105+
// b 0x400000000000006c
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Test case for WASM→WASM→JS→WASM→WASM interleaved call stack.
2+
// Exercises IPInt callee tracking across a WASM→WASM hop on each side of a JS boundary.
3+
//
4+
// Call chain: JS main → test_a[W] → test_b[W] → mid_callback[JS] → test_c[W] → wasm_leaf[W]
5+
//
6+
// Function index mapping:
7+
// 0: imported js.mid_callback (JS)
8+
// 1: test_a (WASM, exported) — calls test_b (func 2)
9+
// 2: test_b (WASM) — calls mid_callback (import 0)
10+
// 3: test_c (WASM, exported) — calls wasm_leaf (func 4)
11+
// 4: wasm_leaf (WASM, exported) — nop, breakpoint target
12+
//
13+
// Expected bt at 0x400000000000005f:
14+
// frame #0: 0x400000000000005f (wasm_leaf nop — breakpoint)
15+
// frame #1: 0x400000000000005c (test_c return PC, after 'call wasm_leaf' at 0x5a)
16+
// frame #2: 0xc000000000000000 (JS mid_callback frame)
17+
// frame #3: 0x4000000000000057 (test_b return PC, after 'call mid_callback' at 0x55 — from WasmToJSIPIntReturnPCSlot in WasmToJS frame)
18+
// frame #4: 0x4000000000000052 (test_a return PC, after 'call test_b' at 0x50 — read from test_b−8)
19+
// frame #5: 0xc000000000000000 (JS main loop)
20+
var wasm = new Uint8Array([
21+
// [0x00] WASM header
22+
0x00, 0x61, 0x73, 0x6d, // magic
23+
0x01, 0x00, 0x00, 0x00, // version
24+
25+
// [0x08] Type section: 1 type (func [] -> [])
26+
0x01, 0x04, // section id=1, size=4
27+
0x01, // 1 type
28+
0x60, 0x00, 0x00, // type 0: (func [] -> [])
29+
30+
// [0x0e] Import section: js.mid_callback
31+
0x02, 0x13, // section id=2, size=19
32+
0x01, // 1 import
33+
0x02, 0x6a, 0x73, // module name "js" (length=2)
34+
0x0c, 0x6d, 0x69, 0x64, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, // field "mid_callback" (length=12)
35+
0x00, 0x00, // import kind: function, type index 0
36+
37+
// [0x23] Function section: 4 WASM functions (indices 1–4, all type 0)
38+
0x03, 0x05, // section id=3, size=5
39+
0x04, // 4 functions
40+
0x00, 0x00, 0x00, 0x00, // all type 0
41+
42+
// [0x2a] Export section: test_a (func 1), test_c (func 3), wasm_leaf (func 4)
43+
0x07, 0x1f, // section id=7, size=31
44+
0x03, // 3 exports
45+
0x06, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x61, // name "test_a" (length=6)
46+
0x00, 0x01, // kind: function, index 1
47+
0x06, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, // name "test_c" (length=6)
48+
0x00, 0x03, // kind: function, index 3
49+
0x09, 0x77, 0x61, 0x73, 0x6d, 0x5f, 0x6c, 0x65, 0x61, 0x66, // name "wasm_leaf" (length=9)
50+
0x00, 0x04, // kind: function, index 4
51+
52+
// [0x4b] Code section
53+
0x0a, 0x14, // section id=10, size=20
54+
0x04, // 4 function bodies
55+
56+
// [0x4e] Function 1 (test_a): calls test_b (func 2)
57+
0x04, // body size=4
58+
0x00, // 0 local declarations
59+
0x10, 0x02, // [0x50] call 2 (test_b)
60+
0x0b, // [0x52] end ← return PC in test_a
61+
62+
// [0x53] Function 2 (test_b): calls mid_callback (import 0)
63+
0x04, // body size=4
64+
0x00, // 0 local declarations
65+
0x10, 0x00, // [0x55] call 0 (mid_callback)
66+
0x0b, // [0x57] end ← return PC in test_b
67+
68+
// [0x58] Function 3 (test_c): calls wasm_leaf (func 4)
69+
0x04, // body size=4
70+
0x00, // 0 local declarations
71+
0x10, 0x04, // [0x5a] call 4 (wasm_leaf)
72+
0x0b, // [0x5c] end ← return PC in test_c
73+
74+
// [0x5d] Function 4 (wasm_leaf): breakpoint target
75+
0x03, // body size=3
76+
0x00, // 0 local declarations
77+
0x01, // [0x5f] nop <-- breakpoint
78+
0x0b, // [0x60] end
79+
]);
80+
81+
var test_c_func;
82+
83+
function mid_callback() {
84+
test_c_func();
85+
}
86+
87+
var module = new WebAssembly.Module(wasm);
88+
var instance = new WebAssembly.Instance(module, { js: { mid_callback } });
89+
90+
test_c_func = instance.exports.test_c;
91+
let test_a = instance.exports.test_a;
92+
93+
let iteration = 0;
94+
for (;;) {
95+
test_a();
96+
iteration += 1;
97+
if (iteration % 1e6 == 0)
98+
print("iteration=", iteration);
99+
}
100+
101+
// b 0x400000000000005f

0 commit comments

Comments
 (0)