From 28dc12816c3fb9f46289de549d81403ba9550cb0 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 23 Oct 2026 19:07:23 -0700 Subject: [PATCH 1/2] [Literal] Respect PrintLimiter when printing cyclic data `operator<<(std::ostream&, Literal)` instantiated a `PrintLimiter` (incrementing `PrintLimiter::printed`), but never checked `limiter.stop()`, so would infinitely recurse when printing cyclic data. This was a regression from #9051. Check `limiter.stop()` at the entry of `operator<<(std::ostream&, Literal)` to print `[..]` and return early when the print limit is reached, and break out of the `GCData` element loop once `limiter.stop()` becomes true. Add tests for wasm-ctor-eval and --fuzz-exec showing that we no longer infinitely recurse. --- src/wasm/literal.cpp | 7 +++++ test/lit/ctor-eval/gc-cycle.wast | 47 ++++++++++++++++++++++++++++++++ test/lit/exec/gc-cycle-leak.wast | 10 ++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 5f20800ad3f..736e646d197 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -729,6 +729,10 @@ thread_local size_t PrintLimiter::printed = 0; std::ostream& operator<<(std::ostream& o, Literal literal) { PrintLimiter limiter; + if (limiter.stop()) { + return o << "[..]"; + } + prepareMinorColor(o); assert(literal.type.isSingle()); if (literal.type.isBasic()) { @@ -861,6 +865,9 @@ std::ostream& operator<<(std::ostream& o, Literal literal) { o << ' '; } o << literal.getElement(i); + if (limiter.stop()) { + break; + } } if (!data->desc.isNull()) { if (literal.getNumElements() > 0) { diff --git a/test/lit/ctor-eval/gc-cycle.wast b/test/lit/ctor-eval/gc-cycle.wast index 6d4f0726730..7eea1555c77 100644 --- a/test/lit/ctor-eval/gc-cycle.wast +++ b/test/lit/ctor-eval/gc-cycle.wast @@ -1347,4 +1347,51 @@ ) ) ) +(module + ;; CHECK: (type $A (struct (field i32) (field (mut (ref null $A))))) + (type $A (struct (field i32) (field (mut (ref null $A))))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $2 (func (param (ref $A)))) + + ;; CHECK: (global $ctor-eval$global_1 (ref (exact $A)) (struct.new $A + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (tag $tag (type $2) (param (ref $A))) + (tag $tag (param (ref $A))) + (func $test (export "test") + ;; Throwing a cyclic struct should not cause infinite recursion when + ;; formatting the exception message. + (local $a (ref $A)) + (local.set $a + (struct.new_default $A) + ) + (struct.set $A 1 + (local.get $a) + (local.get $a) + ) + (throw $tag + (local.get $a) + ) + ) +) +;; CHECK: (export "test" (func $test_2)) + +;; CHECK: (start $start) + +;; CHECK: (func $start (type $1) +;; CHECK-NEXT: (struct.set $A 1 +;; CHECK-NEXT: (global.get $ctor-eval$global_1) +;; CHECK-NEXT: (global.get $ctor-eval$global_1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_2 (type $1) +;; CHECK-NEXT: (throw $tag +;; CHECK-NEXT: (global.get $ctor-eval$global_1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) diff --git a/test/lit/exec/gc-cycle-leak.wast b/test/lit/exec/gc-cycle-leak.wast index f477f24010a..b6d2c89cc8b 100644 --- a/test/lit/exec/gc-cycle-leak.wast +++ b/test/lit/exec/gc-cycle-leak.wast @@ -5,14 +5,18 @@ (module (type $A (struct (field (mut (ref null $A))))) + (import "env" "log_execution" (func $log (param (ref $A)))) + ;; CHECK: [fuzz-exec] export test + ;; CHECK-NEXT: [LoggingExternalInterface log-execution [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [..]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] (func $test (export "test") (local $a (ref $A)) ;; This function makes a self-cycle where the local $a's ref field points to ;; itself. This test checks that we do not error, even in sanitizers, when ;; such cycles are created (at the time of creating this test, we represent ;; GC data using std::shared_ptr, which does not handle cycles, and so by - ;; default leak checks will error if not suppressed). + ;; default leak checks will error if not suppressed). Also log $a to test + ;; printing of recursive GC structures. (local.set $a (struct.new $A (ref.null $A) @@ -22,7 +26,11 @@ (local.get $a) (local.get $a) ) + (call $log + (local.get $a) + ) ) ) ;; CHECK: [fuzz-exec] export test +;; CHECK-NEXT: [LoggingExternalInterface log-execution [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [..]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] ;; CHECK-NEXT: [fuzz-exec] comparing test From f32cdaa10222fb3aa45b7b0aa26cc2f0ced99e0e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sat, 24 Oct 2026 00:12:33 -0700 Subject: [PATCH 2/2] [Literal] Print cycles using de Bruijn indices Instead of truncating Literal output after 100 elements with PrintLimiter, track active GCData objects on the printing stack and print cyclic references using de Bruijn indices (..0, ..1, etc.). --- src/wasm/literal.cpp | 354 +++++++++++++++---------------- test/lit/exec/gc-cycle-leak.wast | 33 ++- 2 files changed, 200 insertions(+), 187 deletions(-) diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 736e646d197..00fdd12f757 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "emscripten-optimizer/simple_ast.h" #include "ir/bits.h" @@ -699,215 +700,198 @@ void Literal::printVec128(std::ostream& o, const std::array& v) { o << std::dec; } -// Printing literals is mainly for debugging purposes, and they can be of -// massive size or even infinitely recursive, so abbreviate past some point. -// We do so by tracking how much we've printed so far, and whether we are the -// "toplevel" call (the entry point from somewhere else), and we reset the -// count when we leave the toplevel call. namespace { -struct PrintLimiter { - static const size_t PRINT_LIMIT = 100; - static thread_local size_t printed; - bool isTopLevel; - - PrintLimiter() : isTopLevel(printed == 0) { printed++; } - - ~PrintLimiter() { - if (isTopLevel) { - printed = 0; - } - } - - bool stop() { return printed >= PRINT_LIMIT; } -}; - -thread_local size_t PrintLimiter::printed = 0; - -} // namespace - -std::ostream& operator<<(std::ostream& o, Literal literal) { - PrintLimiter limiter; - - if (limiter.stop()) { - return o << "[..]"; - } - - prepareMinorColor(o); - assert(literal.type.isSingle()); - if (literal.type.isBasic()) { - switch (literal.type.getBasic()) { - case Type::none: - o << "?"; - break; - case Type::i32: - o << literal.geti32(); - break; - case Type::i64: - o << literal.geti64(); - break; - case Type::f32: - literal.printFloat(o, literal.getf32()); - break; - case Type::f64: - literal.printDouble(o, literal.getf64()); - break; - case Type::v128: - o << "i32x4 "; - literal.printVec128(o, literal.getv128()); - break; - case Type::unreachable: - WASM_UNREACHABLE("unexpected type"); - } - } else { - assert(literal.type.isRef()); - auto heapType = literal.type.getHeapType(); - if (heapType.isShared()) { - o << "shared "; - } - if (heapType.isBasic()) { - switch (heapType.getBasic(Unshared)) { - case HeapType::i31: - o << "i31ref(" << literal.geti31() << ")"; - break; - case HeapType::none: - o << "nullref"; - break; - case HeapType::noext: - o << "nullexternref"; +struct LiteralPrinter { + std::ostream& o; + // Track the depths of active GCData objects to detect cycles and print them + // using relative depths. + std::unordered_map depths; + + std::ostream& print(const Literal& literal) { + prepareMinorColor(o); + assert(literal.type.isSingle()); + if (literal.type.isBasic()) { + switch (literal.type.getBasic()) { + case Type::none: + o << "?"; break; - case HeapType::nofunc: - o << "nullfuncref"; + case Type::i32: + o << literal.geti32(); break; - case HeapType::noexn: - o << "nullexnref"; + case Type::i64: + o << literal.geti64(); break; - case HeapType::nocont: - o << "nullcontref"; + case Type::f32: + literal.printFloat(o, literal.getf32()); break; - case HeapType::nowaitqueue: - o << "nullwaitqueue"; + case Type::f64: + literal.printDouble(o, literal.getf64()); break; - case HeapType::any: { - auto data = literal.getGCData(); - assert(data->getLiterals().size() == 1); - o << "internalized " << data->getLiterals()[0]; + case Type::v128: + o << "i32x4 "; + literal.printVec128(o, literal.getv128()); break; - } - case HeapType::ext: { - if (literal.hasExternPayload()) { - // Externref payload - o << "externref(" << literal.getExternPayload() << ")"; - } else { - // Externalized internal reference. - o << "externalized " << literal.internalize(); + case Type::unreachable: + WASM_UNREACHABLE("unexpected type"); + } + } else { + assert(literal.type.isRef()); + auto heapType = literal.type.getHeapType(); + if (heapType.isShared()) { + o << "shared "; + } + if (heapType.isBasic()) { + switch (heapType.getBasic(Unshared)) { + case HeapType::i31: + o << "i31ref(" << literal.geti31() << ")"; + break; + case HeapType::none: + o << "nullref"; + break; + case HeapType::noext: + o << "nullexternref"; + break; + case HeapType::nofunc: + o << "nullfuncref"; + break; + case HeapType::noexn: + o << "nullexnref"; + break; + case HeapType::nocont: + o << "nullcontref"; + break; + case HeapType::nowaitqueue: + o << "nullwaitqueue"; + break; + case HeapType::any: { + auto data = literal.getGCData(); + assert(data->getLiterals().size() == 1); + o << "internalized "; + print(data->getLiterals()[0]); + break; } - break; - } - case HeapType::exn: - o << "exnref"; - break; - case HeapType::eq: - case HeapType::func: - case HeapType::cont: - case HeapType::struct_: - case HeapType::array: - WASM_UNREACHABLE("invalid type"); - case HeapType::string: { - auto data = literal.getGCData(); - if (!data) { - o << "nullstring"; - } else { - o << "string("; - // Convert WTF-16 literals to WTF-16 string. - std::stringstream wtf16; - for (auto c : data->getLiterals()) { - auto u = c.getInteger(); - assert(u < 0x10000); - wtf16 << uint8_t(u & 0xFF); - wtf16 << uint8_t(u >> 8); + case HeapType::ext: { + if (literal.hasExternPayload()) { + // Externref payload + o << "externref(" << literal.getExternPayload() << ")"; + } else { + // Externalized internal reference. + o << "externalized "; + print(literal.internalize()); } - // Escape to ensure we have valid unicode output and to make - // unprintable characters visible. - // TODO: Use wtf16.view() once we have C++20. - String::printEscapedJSON(o, wtf16.str()); - o << ")"; + break; + } + case HeapType::exn: + o << "exnref"; + break; + case HeapType::eq: + case HeapType::func: + case HeapType::cont: + case HeapType::struct_: + case HeapType::array: + WASM_UNREACHABLE("invalid type"); + case HeapType::string: { + auto data = literal.getGCData(); + if (!data) { + o << "nullstring"; + } else { + o << "string("; + // Convert WTF-16 literals to WTF-16 string. + std::stringstream wtf16; + for (auto c : data->getLiterals()) { + auto u = c.getInteger(); + assert(u < 0x10000); + wtf16 << uint8_t(u & 0xFF); + wtf16 << uint8_t(u >> 8); + } + // Escape to ensure we have valid unicode output and to make + // unprintable characters visible. + // TODO: Use wtf16.view() once we have C++20. + String::printEscapedJSON(o, wtf16.str()); + o << ")"; + } + break; + } + case HeapType::waitqueue: { + o << "waitqueue"; + break; } - break; } - case HeapType::waitqueue: { - o << "waitqueue"; - break; + } else if (heapType.isSignature()) { + o << "funcref(" << literal.getFunc() << ")"; + } else if (heapType.isContinuation()) { + auto data = literal.getContData(); + o << "cont(" << data->func << ' ' << data->type; + if (data->resumeExpr) { + o << " resumeExpr=" << reinterpret_cast(data->resumeExpr); } - } - } else if (heapType.isSignature()) { - o << "funcref(" << literal.getFunc() << ")"; - } else if (heapType.isContinuation()) { - auto data = literal.getContData(); - o << "cont(" << data->func << ' ' << data->type; - if (data->resumeExpr) { - o << " resumeExpr=" << reinterpret_cast(data->resumeExpr); - } - if (!data->resumeInfo.empty()) { - o << " |resumeInfo|=" << data->resumeInfo.size(); - } - if (!data->resumeArguments.empty()) { - o << " resumeArguments=" << data->resumeArguments; - } - o << " executed=" << data->executed << ')'; - } else { - assert(literal.isData()); - auto data = literal.getGCData(); - assert(data); - o << "[ref " << literal.type.getHeapType() << ' '; - for (size_t i = 0; i < literal.getNumElements(); i++) { - if (i > 0) { - o << ' '; + if (!data->resumeInfo.empty()) { + o << " |resumeInfo|=" << data->resumeInfo.size(); } - o << literal.getElement(i); - if (limiter.stop()) { - break; + if (!data->resumeArguments.empty()) { + o << " resumeArguments="; + print(data->resumeArguments); } - } - if (!data->desc.isNull()) { - if (literal.getNumElements() > 0) { - o << ", "; + o << " executed=" << data->executed << ')'; + } else { + assert(literal.isData()); + auto data = literal.getGCData(); + assert(data); + auto [it, inserted] = depths.insert({data.get(), depths.size()}); + if (!inserted) { + o << ".." << (depths.size() - 1 - it->second); + } else { + o << "[ref " << literal.type.getHeapType() << ' '; + for (size_t i = 0; i < literal.getNumElements(); i++) { + if (i > 0) { + o << ' '; + } + print(literal.getElement(i)); + } + if (!data->desc.isNull()) { + if (literal.getNumElements() > 0) { + o << ", "; + } + o << "desc="; + print(data->desc); + } + o << ']'; + depths.erase(data.get()); } - o << "desc=" << data->desc; } - o << ']'; } + restoreNormalColor(o); + return o; } - restoreNormalColor(o); - return o; -} -std::ostream& operator<<(std::ostream& o, wasm::Literals literals) { - PrintLimiter limiter; + std::ostream& print(const Literals& literals) { + if (literals.size() == 1) { + return print(literals[0]); + } - if (limiter.stop()) { - return o << "[..]"; + o << '('; + bool first = true; + for (auto& literal : literals) { + if (first) { + first = false; + } else { + o << ", "; + } + print(literal); + } + return o << ')'; } +}; - if (literals.size() == 1) { - return o << literals[0]; - } +} // namespace - o << '('; - bool first = true; - for (auto& literal : literals) { - if (limiter.stop()) { - o << "[..]"; - break; - } - if (first) { - first = false; - } else { - o << ", "; - } - o << literal; - } - return o << ')'; +std::ostream& operator<<(std::ostream& o, Literal literal) { + return LiteralPrinter{o, {}}.print(literal); +} + +std::ostream& operator<<(std::ostream& o, wasm::Literals literals) { + return LiteralPrinter{o, {}}.print(literals); } Literal Literal::countLeadingZeroes() const { diff --git a/test/lit/exec/gc-cycle-leak.wast b/test/lit/exec/gc-cycle-leak.wast index b6d2c89cc8b..f01bcba4096 100644 --- a/test/lit/exec/gc-cycle-leak.wast +++ b/test/lit/exec/gc-cycle-leak.wast @@ -8,7 +8,7 @@ (import "env" "log_execution" (func $log (param (ref $A)))) ;; CHECK: [fuzz-exec] export test - ;; CHECK-NEXT: [LoggingExternalInterface log-execution [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [..]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] + ;; CHECK-NEXT: [LoggingExternalInterface log-execution [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) ..0]] (func $test (export "test") (local $a (ref $A)) ;; This function makes a self-cycle where the local $a's ref field points to @@ -30,7 +30,36 @@ (local.get $a) ) ) + + ;; CHECK: [fuzz-exec] export cycle2 + ;; CHECK-NEXT: [LoggingExternalInterface log-execution [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) ..1]]] + (func $cycle2 (export "cycle2") + (local $a (ref $A)) + (local $b (ref $A)) + ;; A cycle of length 2 between $a and $b. + (local.set $a + (struct.new $A + (ref.null $A) + ) + ) + (local.set $b + (struct.new $A + (local.get $a) + ) + ) + (struct.set $A 0 + (local.get $a) + (local.get $b) + ) + (call $log + (local.get $a) + ) + ) ) ;; CHECK: [fuzz-exec] export test -;; CHECK-NEXT: [LoggingExternalInterface log-execution [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [..]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] +;; CHECK-NEXT: [LoggingExternalInterface log-execution [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) ..0]] + +;; CHECK: [fuzz-exec] export cycle2 +;; CHECK-NEXT: [LoggingExternalInterface log-execution [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) [ref (type $struct.0 (struct (field (mut (ref null $struct.0))))) ..1]]] +;; CHECK-NEXT: [fuzz-exec] comparing cycle2 ;; CHECK-NEXT: [fuzz-exec] comparing test