22// See LICENSE for details of Apache 2.0 license.
33
44// Zero-initialized reentry label used as a placeholder in test frames.
5+ // TODO: fuzz this for testing
56var DUMMY_REENTRY: TargetReentryLabel;
67
7- // A strategy adapter that erases the compressed stack type, providing a uniform
8- // roundtrip interface for testing all compression strategies.
8+ // An adapter that erases the compressed stack type to perform strategy-agnostic roundtrip tests.
99class CompressionStrategyAdapter {
1010 def roundtrip: Array<RelocatableFrame> -> Array<RelocatableFrame>;
1111 new(roundtrip) { }
@@ -26,7 +26,7 @@ def packedRoundtrip(frames: Array<RelocatableFrame>) -> Array<RelocatableFrame>
2626 return packed.decompress(packed.compress(frames));
2727}
2828
29- // Register all tests across all strategies.
29+ // Register all tests across all strategies: for each test, register it once for every strategy .
3030def X_ = registerAll([
3131 ("i32", test_i32),
3232 ("i64", test_i64),
@@ -118,7 +118,7 @@ class CompressionTester(t: Tester, adapter: CompressionStrategyAdapter) {
118118 }
119119}
120120
121- // === Part 1: Individual value type round-trips ===
121+ // ===== Compression roundtrip tests == ===
122122
123123def test_i32(t: CompressionTester) {
124124 t.assert_roundtrip_vals([Value.I32(0)]);
@@ -170,8 +170,6 @@ def test_ref(t: CompressionTester) {
170170 }
171171}
172172
173- // === Part 2: Edge cases and multi-frame ===
174-
175173def test_empty_frames(t: CompressionTester) {
176174 var got = t.roundtrip([]);
177175 t.assert_length(0, got.length, "frames");
@@ -224,8 +222,6 @@ def test_single_val_per_frame(t: CompressionTester) {
224222 t.assert_frames_eq(frames, got);
225223}
226224
227- // === Part 3: Continuation values ===
228-
229225def test_cont(t: CompressionTester) {
230226 var stack = Target.newWasmStack();
231227 var cont = Continuations.makeContinuation(stack);
@@ -242,8 +238,6 @@ def test_cont(t: CompressionTester) {
242238 }
243239}
244240
245- // === Part 4: Frame metadata preservation ===
246-
247241def test_frame_metadata(t: CompressionTester) {
248242 var func = t.getDummyFunc();
249243 var frame = RelocatableFrame(func, 42, DUMMY_REENTRY, DUMMY_REENTRY, [Value.I32(99)]);
@@ -254,8 +248,6 @@ def test_frame_metadata(t: CompressionTester) {
254248 t.assert_vals_eq([Value.I32(99)], got[0].vals);
255249}
256250
257- // === Part 5: Repeated values ===
258-
259251// Many identical values within a single frame.
260252def test_repeated_vals(t: CompressionTester) {
261253 var vals = Array<Value>.new(100);
@@ -281,8 +273,6 @@ def test_repeated_across_frames(t: CompressionTester) {
281273 t.assert_frames_eq(frames, got);
282274}
283275
284- // === Part 6: Many diverse values ===
285-
286276// Single frame with many values of varying types and magnitudes.
287277def test_many_diverse_vals(t: CompressionTester) {
288278 var vals = Vector<Value>.new();
@@ -317,8 +307,6 @@ def test_many_frames_diverse(t: CompressionTester) {
317307 t.assert_frames_eq(input, got);
318308}
319309
320- // === Part 7: Complex reference objects ===
321-
322310// Multiple distinct reference types in a single frame.
323311def test_refs_mixed(t: CompressionTester) {
324312 var heap_obj = HeapObject.new(null, []);
@@ -393,3 +381,71 @@ def test_large_uniform_frames(t: CompressionTester) {
393381 var got = t.roundtrip(frames);
394382 t.assert_frames_eq(frames, got);
395383}
384+
385+ // ===== Stack capture tests =====
386+
387+ // Host function callback. Set {stack} before resuming; {invoke} serves as the imported host function body.
388+ class FrameCapturer {
389+ var stack: WasmStack;
390+
391+ def invoke(args: Range<Value>) -> HostResult {
392+ return HostResult.Value0;
393+ }
394+ }
395+
396+ // Tester that builds Wasm functions and captures stack frames during execution.
397+ class StackCaptureTester extends ExeTester {
398+ var capturer = FrameCapturer.new();
399+ var import_index: int;
400+
401+ new(t: Tester, tiering: ExecutionStrategy) super(t, tiering) { }
402+
403+ // Adds an imported v_v host function to the module and sets up the import binding.
404+ // Must be called before codev/makeFunc.
405+ def addHostImport() {
406+ var import_sig = addSig(SigCache.v_v);
407+ var import_decl = FuncDecl.new(import_sig.heaptype_index);
408+ module.addImport("test", "capture", import_decl);
409+ import_index = import_decl.func_index;
410+ imports = [HostFunction.new("capture", import_sig, capturer.invoke)];
411+ }
412+
413+ // Builds a validated function that pushes all params onto the value stack and calls the imported v_v host function,
414+ // This ensures the value stack holds exactly the bound param values during the host call.
415+ def buildCaptureFunc(param_types: Array<ValueType>) -> Function {
416+ addHostImport();
417+ var main_sig = SigDecl.new(true, ValueTypes.NO_HEAPTYPES, param_types, SigCache.arr_v);
418+ sig(main_sig);
419+ var bc = BinBuilder.new();
420+ for (i < param_types.length) {
421+ bc.put(u8.!(Opcode.LOCAL_GET.code));
422+ bc.put_u32leb(u32.!(i));
423+ }
424+ bc.put(u8.!(Opcode.CALL.code));
425+ bc.put_u32leb(u32.!(import_index));
426+ for (i < param_types.length) {
427+ bc.put(u8.!(Opcode.DROP.code));
428+ }
429+ codev(bc.extract());
430+ return makeFunc();
431+ }
432+ }
433+
434+ // TODO: add more tests, after stack capture utils are done
435+ def S = StackCaptureTester.new;
436+ def unused_stack_ = TestTiers.addTests2([
437+ ("stack_capture_smoke", S, test_stack_capture_smoke)
438+ ]);
439+
440+ def test_stack_capture_smoke(t: StackCaptureTester) {
441+ var f = t.buildCaptureFunc([ValueType.I32, ValueType.I64]);
442+ if (f == null) return;
443+
444+ var s = Target.newWasmStack();
445+ s.reset(f);
446+ s.bind([Value.I32(42), Value.I64(99)]);
447+ var r = s.resume();
448+
449+ // The function should complete normally with no return values.
450+ t.assert_req(r, Result.Value(Values.NONE));
451+ }
0 commit comments