From 38c5f277522e35a911166ee09a89fbdedf53e47c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 15 Apr 2026 15:22:16 -0700 Subject: [PATCH 1/2] Use fewer scratch locals in IRBuilder When popping children for an expression, greedily pop none-typed expressions below the last value-producing expression in the stack, packaging all the popped instructions into a new block. This avoids leaving the none-typed expressions on top of the stack, which is good because having them on top of the stack would force the use of a scratch local when the next operand is popped. Besides producing better IR with fewer scratch locals, this also has the benefit of better round-tripping IR through binaries. The binary writer has an optimization where it will elide unnamed blocks because they cannot possibly be branch targets, but this could create "stacky" code that would previously introduce a scratch local when it was parsed back into IR. Now IRBuilder just recreates exactly the unnamed block that had been present in the IR in first place. While we don't generally guarantee that we can perfectly round-trip IR through binaries, this reduces the number of cases where round-trips lead to increased code size. Fixes #8413. --- src/wasm-ir-builder.h | 11 +- src/wasm/wasm-ir-builder.cpp | 52 +++++++--- test/lit/basic/extra-branch-values.wast | 44 ++++---- test/lit/passes/roundtrip-gc.wast | 44 -------- test/lit/string.as_wtf16.wast | 132 ++++++++++-------------- test/lit/wat-kitchen-sink.wast | 40 +++---- test/stacky.wasm.fromBinary | 8 +- 7 files changed, 133 insertions(+), 198 deletions(-) delete mode 100644 test/lit/passes/roundtrip-gc.wast diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 44b4ba0b270..7abe73a36b5 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -713,15 +713,20 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result addScratchLocal(Type); struct HoistedVal { - // The index in the stack of the original value-producing expression. + // The index in the stack of the deepest expression to be popped. This can + // be the original value-producing expression, or if we are popping + // greedily, it might be the deepest none-typed expression under the + // value-producing expression. Index valIndex; // The local.get placed on the stack, if any. LocalGet* get; }; // Find the last value-producing expression, if any, and hoist its value to - // the top of the stack using a scratch local if necessary. - MaybeResult hoistLastValue(); + // the top of the stack using a scratch local if necessary. If `greedy`, then + // also include none-typed expressions and the value-producing expression in + // the hoisted range of expressions. + MaybeResult hoistLastValue(bool greedy = false); // Transform the stack as necessary such that the original producer of the // hoisted value will be popped along with the final expression that produces // the value, if they are different. May only be called directly after diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 61aff65f24d..f7c14f79f12 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -61,29 +61,37 @@ Result IRBuilder::addScratchLocal(Type type) { return Builder::addVar(func, name, type); } -MaybeResult IRBuilder::hoistLastValue() { +MaybeResult IRBuilder::hoistLastValue(bool greedy) { auto& stack = getScope().exprStack; - int index = stack.size() - 1; - for (; index >= 0; --index) { - if (stack[index]->type != Type::none) { + int valIndex = stack.size() - 1; + for (; valIndex >= 0; --valIndex) { + if (stack[valIndex]->type != Type::none) { break; } } - if (index < 0) { + if (valIndex < 0) { // There is no value-producing or unreachable expression. return {}; } - if (unsigned(index) == stack.size() - 1) { + + int hoistIndex = valIndex; + if (greedy) { + while (hoistIndex > 0 && stack[hoistIndex - 1]->type == Type::none) { + --hoistIndex; + } + } + + if (unsigned(valIndex) == stack.size() - 1) { // Value-producing expression already on top of the stack. - return HoistedVal{Index(index), nullptr}; + return HoistedVal{Index(hoistIndex), nullptr}; } - auto*& expr = stack[index]; + auto*& expr = stack[valIndex]; if (expr->type == Type::unreachable) { // Make sure the top of the stack also has an unreachable expression. if (stack.back()->type != Type::unreachable) { pushSynthetic(builder.makeUnreachable()); } - return HoistedVal{Index(index), nullptr}; + return HoistedVal{Index(hoistIndex), nullptr}; } // Hoist with a scratch local. Normally the scratch local is the same type as // the hoisted expression, but we may need to adjust it given the enabled @@ -99,7 +107,7 @@ MaybeResult IRBuilder::hoistLastValue() { expr = builder.makeLocalSet(*scratchIdx, expr); auto* get = builder.makeLocalGet(*scratchIdx, type); pushSynthetic(get); - return HoistedVal{Index(index), get}; + return HoistedVal{Index(hoistIndex), get}; } Result<> IRBuilder::packageHoistedValue(const HoistedVal& hoisted, @@ -123,7 +131,7 @@ Result<> IRBuilder::packageHoistedValue(const HoistedVal& hoisted, auto type = scope.exprStack.back()->type; if (type.size() == sizeHint || type.size() <= 1) { - if (hoisted.get) { + if (hoisted.valIndex < scope.exprStack.size() - 1) { packageAsBlock(type); } return Ok{}; @@ -379,8 +387,10 @@ struct IRBuilder::ChildPopper continue; } - // Pop a child normally. - auto val = pop(children[i].constraint.size()); + // Pop a child normally. Pop greedily for children other than the first + // (i.e. the last to be popped). + bool greedy = i > 0; + auto val = pop(children[i].constraint.size(), greedy); CHECK_ERR(val); *children[i].childp = *val; } @@ -458,12 +468,22 @@ struct IRBuilder::ChildPopper return false; } - Result pop(size_t size) { + // If `greedy`, then we will pop additional none-typed expressions that come + // before the value-producing expression. The additional expressions will be + // packaged into a block with the value-producing expression. This is better + // than leaving them on top of the stack, where they will force the use of a + // scratch local when the next operand is popped. `greedy` should be used when + // popping all children of an expression except the first (i.e. the + // last child to be popped). Not being greedy for the last popped child defers + // the creation of a block to hold its none-typed predecessors. It may turn + // out that such a block is not necessary, for example when the none-typed + // expressions can be included directly into a parent block scope. + Result pop(size_t size, bool greedy = false) { assert(size >= 1); auto& scope = builder.getScope(); // Find the suffix of expressions that do not produce values. - auto hoisted = builder.hoistLastValue(); + auto hoisted = builder.hoistLastValue(greedy); CHECK_ERR(hoisted); if (!hoisted) { // There are no expressions that produce values. @@ -489,7 +509,7 @@ struct IRBuilder::ChildPopper std::vector elems; elems.resize(size); for (int i = size - 1; i >= 0; --i) { - auto elem = pop(1); + auto elem = pop(1, greedy || i > 0); CHECK_ERR(elem); elems[i] = *elem; } diff --git a/test/lit/basic/extra-branch-values.wast b/test/lit/basic/extra-branch-values.wast index ca840036818..953ca7d634f 100644 --- a/test/lit/basic/extra-branch-values.wast +++ b/test/lit/basic/extra-branch-values.wast @@ -2383,17 +2383,15 @@ ;; CHECK-NEXT: (local $scratch_6 i32) ;; CHECK-NEXT: (local $scratch_7 i32) ;; CHECK-NEXT: (local $scratch_8 (ref any)) - ;; CHECK-NEXT: (local $scratch_9 i32) - ;; CHECK-NEXT: (local $scratch_10 anyref) - ;; CHECK-NEXT: (local $scratch_11 i32) - ;; CHECK-NEXT: (local $scratch_12 (ref any)) - ;; CHECK-NEXT: (local $scratch_13 i32) - ;; CHECK-NEXT: (local $scratch_14 eqref) + ;; CHECK-NEXT: (local $scratch_9 anyref) + ;; CHECK-NEXT: (local $scratch_10 i32) + ;; CHECK-NEXT: (local $scratch_11 (ref any)) + ;; CHECK-NEXT: (local $scratch_12 eqref) ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block $label (type $0) (result i32 eqref) - ;; CHECK-NEXT: (local.set $scratch_14 + ;; CHECK-NEXT: (local.set $scratch_12 ;; CHECK-NEXT: (block $label0 (result eqref) ;; CHECK-NEXT: (br $label ;; CHECK-NEXT: (if (type $0) (result i32 eqref) @@ -2416,46 +2414,40 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $scratch_9 - ;; CHECK-NEXT: (local.get $scratch_6) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: (block (result (ref eq)) ;; CHECK-NEXT: (global.set $any ;; CHECK-NEXT: (local.get $scratch_8) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $scratch_9) + ;; CHECK-NEXT: (global.get $eq) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (global.get $eq) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (local.set $scratch_6 ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $scratch_11 + ;; CHECK-NEXT: (local.set $scratch_10 ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $scratch_10 + ;; CHECK-NEXT: (local.set $scratch_9 ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $scratch_11) + ;; CHECK-NEXT: (local.get $scratch_10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $scratch_12 + ;; CHECK-NEXT: (local.set $scratch_11 ;; CHECK-NEXT: (br_on_cast $label0 anyref eqref - ;; CHECK-NEXT: (local.get $scratch_10) + ;; CHECK-NEXT: (local.get $scratch_9) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $scratch_13 - ;; CHECK-NEXT: (local.get $scratch_6) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_6) + ;; CHECK-NEXT: (block (result eqref) ;; CHECK-NEXT: (global.set $any - ;; CHECK-NEXT: (local.get $scratch_12) + ;; CHECK-NEXT: (local.get $scratch_11) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $scratch_13) + ;; CHECK-NEXT: (global.get $eqref) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (global.get $eqref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2464,7 +2456,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (tuple.make 2 ;; CHECK-NEXT: (local.get $scratch_6) - ;; CHECK-NEXT: (local.get $scratch_14) + ;; CHECK-NEXT: (local.get $scratch_12) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/roundtrip-gc.wast b/test/lit/passes/roundtrip-gc.wast deleted file mode 100644 index 805d477b8a2..00000000000 --- a/test/lit/passes/roundtrip-gc.wast +++ /dev/null @@ -1,44 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s -all --generate-stack-ir --optimize-stack-ir --roundtrip -S -o - | filecheck %s - -(module - (type $"{i32}" (struct (field i32))) - ;; CHECK: (export "export" (func $test)) - (export "export" (func $test)) - ;; CHECK: (func $test (type $1) - ;; CHECK-NEXT: (local $scratch (ref (exact $\7bi32\7d))) - ;; CHECK-NEXT: (call $help - ;; CHECK-NEXT: (block (result (ref (exact $\7bi32\7d))) - ;; CHECK-NEXT: (local.set $scratch - ;; CHECK-NEXT: (struct.new_default $\7bi32\7d) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $other) - ;; CHECK-NEXT: (local.get $scratch) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test - (call $help - (struct.new_default $"{i32}") - ;; Stack IR optimizations can remove this block, leaving a call in an odd - ;; "stacky" location. On load, we will use a local to work around that. It - ;; is fine for the local to be non-nullable since the get is later in that - ;; same block. - (block $block (result i32) - (call $other) - (i32.const 1) - ) - ) - ) - ;; CHECK: (func $help (type $2) (param $3 (ref $\7bi32\7d)) (param $4 i32) - ;; CHECK-NEXT: ) - (func $help (param $3 (ref $"{i32}")) (param $4 i32) - (nop) - ) - - ;; CHECK: (func $other (type $1) - ;; CHECK-NEXT: ) - (func $other - ) -) diff --git a/test/lit/string.as_wtf16.wast b/test/lit/string.as_wtf16.wast index 74bbc97eaff..db5cf6c746a 100644 --- a/test/lit/string.as_wtf16.wast +++ b/test/lit/string.as_wtf16.wast @@ -26,35 +26,27 @@ ;; CHECK-NEXT: ) ;; RTRIP: (func $codeunit (type $1) (result i32) ;; RTRIP-NEXT: (local $0 i32) - ;; RTRIP-NEXT: (local $scratch (ref string)) ;; RTRIP-NEXT: (stringview_wtf16.get_codeunit - ;; RTRIP-NEXT: (block (result (ref string)) - ;; RTRIP-NEXT: (local.set $scratch - ;; RTRIP-NEXT: (string.const "abc") - ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (string.const "abc") + ;; RTRIP-NEXT: (block (result i32) ;; RTRIP-NEXT: (local.set $0 ;; RTRIP-NEXT: (i32.const 0) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $scratch) + ;; RTRIP-NEXT: (local.get $0) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $0) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) ;; RRTRP: (func $codeunit (type $1) (result i32) ;; RRTRP-NEXT: (local $0 i32) - ;; RRTRP-NEXT: (local $scratch (ref string)) - ;; RRTRP-NEXT: (local $scratch_2 (ref string)) + ;; RRTRP-NEXT: (local $1 i32) ;; RRTRP-NEXT: (stringview_wtf16.get_codeunit - ;; RRTRP-NEXT: (block (result (ref string)) - ;; RRTRP-NEXT: (local.set $scratch_2 - ;; RRTRP-NEXT: (string.const "abc") - ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.set $0 + ;; RRTRP-NEXT: (string.const "abc") + ;; RRTRP-NEXT: (block (result i32) + ;; RRTRP-NEXT: (local.set $1 ;; RRTRP-NEXT: (i32.const 0) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $scratch_2) + ;; RRTRP-NEXT: (local.get $1) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $0) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: ) (func $codeunit (result i32) @@ -109,12 +101,9 @@ ;; RTRIP-NEXT: (local $0 i32) ;; RTRIP-NEXT: (local $1 i32) ;; RTRIP-NEXT: (local $scratch i32) - ;; RTRIP-NEXT: (local $scratch_3 (ref string)) ;; RTRIP-NEXT: (stringview_wtf16.slice - ;; RTRIP-NEXT: (block (result (ref string)) - ;; RTRIP-NEXT: (local.set $scratch_3 - ;; RTRIP-NEXT: (string.const "abc") - ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (string.const "abc") + ;; RTRIP-NEXT: (block (result i32) ;; RTRIP-NEXT: (local.set $0 ;; RTRIP-NEXT: (block (result i32) ;; RTRIP-NEXT: (local.set $scratch @@ -126,9 +115,8 @@ ;; RTRIP-NEXT: (local.get $scratch) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $scratch_3) + ;; RTRIP-NEXT: (local.get $0) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $0) ;; RTRIP-NEXT: (local.get $1) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) @@ -136,29 +124,29 @@ ;; RRTRP-NEXT: (local $0 i32) ;; RRTRP-NEXT: (local $1 i32) ;; RRTRP-NEXT: (local $scratch i32) - ;; RRTRP-NEXT: (local $scratch_3 (ref string)) - ;; RRTRP-NEXT: (local $scratch_4 i32) - ;; RRTRP-NEXT: (local $scratch_5 (ref string)) + ;; RRTRP-NEXT: (local $3 i32) + ;; RRTRP-NEXT: (local $4 i32) + ;; RRTRP-NEXT: (local $scratch_5 i32) ;; RRTRP-NEXT: (stringview_wtf16.slice - ;; RRTRP-NEXT: (block (result (ref string)) - ;; RRTRP-NEXT: (local.set $scratch_5 - ;; RRTRP-NEXT: (string.const "abc") - ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.set $0 + ;; RRTRP-NEXT: (string.const "abc") + ;; RRTRP-NEXT: (block (result i32) + ;; RRTRP-NEXT: (local.set $3 ;; RRTRP-NEXT: (block (result i32) - ;; RRTRP-NEXT: (local.set $scratch_4 + ;; RRTRP-NEXT: (local.set $scratch_5 ;; RRTRP-NEXT: (i32.const 1) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.set $1 ;; RRTRP-NEXT: (i32.const 2) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $scratch_4) + ;; RRTRP-NEXT: (local.set $4 + ;; RRTRP-NEXT: (local.get $1) + ;; RRTRP-NEXT: ) + ;; RRTRP-NEXT: (local.get $scratch_5) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $scratch_5) + ;; RRTRP-NEXT: (local.get $3) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $0) - ;; RRTRP-NEXT: (local.get $1) + ;; RRTRP-NEXT: (local.get $4) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: ) (func $slice (result stringref) @@ -186,12 +174,9 @@ ;; RTRIP-NEXT: (local $1 i32) ;; RTRIP-NEXT: (local $2 i32) ;; RTRIP-NEXT: (local $scratch i32) - ;; RTRIP-NEXT: (local $scratch_4 (ref string)) ;; RTRIP-NEXT: (stringview_wtf16.slice - ;; RTRIP-NEXT: (block (result (ref string)) - ;; RTRIP-NEXT: (local.set $scratch_4 - ;; RTRIP-NEXT: (string.const "abc") - ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (string.const "abc") + ;; RTRIP-NEXT: (block (result i32) ;; RTRIP-NEXT: (local.set $1 ;; RTRIP-NEXT: (block (result i32) ;; RTRIP-NEXT: (local.set $scratch @@ -203,9 +188,8 @@ ;; RTRIP-NEXT: (local.get $scratch) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $scratch_4) + ;; RTRIP-NEXT: (local.get $1) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $1) ;; RTRIP-NEXT: (local.get $2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) @@ -214,29 +198,29 @@ ;; RRTRP-NEXT: (local $1 i32) ;; RRTRP-NEXT: (local $2 i32) ;; RRTRP-NEXT: (local $scratch i32) - ;; RRTRP-NEXT: (local $scratch_4 (ref string)) - ;; RRTRP-NEXT: (local $scratch_5 i32) - ;; RRTRP-NEXT: (local $scratch_6 (ref string)) + ;; RRTRP-NEXT: (local $4 i32) + ;; RRTRP-NEXT: (local $5 i32) + ;; RRTRP-NEXT: (local $scratch_6 i32) ;; RRTRP-NEXT: (stringview_wtf16.slice - ;; RRTRP-NEXT: (block (result (ref string)) - ;; RRTRP-NEXT: (local.set $scratch_6 - ;; RRTRP-NEXT: (string.const "abc") - ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.set $1 + ;; RRTRP-NEXT: (string.const "abc") + ;; RRTRP-NEXT: (block (result i32) + ;; RRTRP-NEXT: (local.set $4 ;; RRTRP-NEXT: (block (result i32) - ;; RRTRP-NEXT: (local.set $scratch_5 + ;; RRTRP-NEXT: (local.set $scratch_6 ;; RRTRP-NEXT: (local.get $start) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.set $2 ;; RRTRP-NEXT: (i32.const 2) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $scratch_5) + ;; RRTRP-NEXT: (local.set $5 + ;; RRTRP-NEXT: (local.get $2) + ;; RRTRP-NEXT: ) + ;; RRTRP-NEXT: (local.get $scratch_6) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $scratch_6) + ;; RRTRP-NEXT: (local.get $4) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $1) - ;; RRTRP-NEXT: (local.get $2) + ;; RRTRP-NEXT: (local.get $5) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: ) (func $slice-start-get (result stringref) @@ -262,12 +246,9 @@ ;; RTRIP-NEXT: (local $1 i32) ;; RTRIP-NEXT: (local $2 i32) ;; RTRIP-NEXT: (local $scratch i32) - ;; RTRIP-NEXT: (local $scratch_4 (ref string)) ;; RTRIP-NEXT: (stringview_wtf16.slice - ;; RTRIP-NEXT: (block (result (ref string)) - ;; RTRIP-NEXT: (local.set $scratch_4 - ;; RTRIP-NEXT: (string.const "abc") - ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (string.const "abc") + ;; RTRIP-NEXT: (block (result i32) ;; RTRIP-NEXT: (local.set $1 ;; RTRIP-NEXT: (block (result i32) ;; RTRIP-NEXT: (local.set $scratch @@ -279,9 +260,8 @@ ;; RTRIP-NEXT: (local.get $scratch) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $scratch_4) + ;; RTRIP-NEXT: (local.get $1) ;; RTRIP-NEXT: ) - ;; RTRIP-NEXT: (local.get $1) ;; RTRIP-NEXT: (local.get $2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) @@ -290,29 +270,29 @@ ;; RRTRP-NEXT: (local $1 i32) ;; RRTRP-NEXT: (local $2 i32) ;; RRTRP-NEXT: (local $scratch i32) - ;; RRTRP-NEXT: (local $scratch_4 (ref string)) - ;; RRTRP-NEXT: (local $scratch_5 i32) - ;; RRTRP-NEXT: (local $scratch_6 (ref string)) + ;; RRTRP-NEXT: (local $4 i32) + ;; RRTRP-NEXT: (local $5 i32) + ;; RRTRP-NEXT: (local $scratch_6 i32) ;; RRTRP-NEXT: (stringview_wtf16.slice - ;; RRTRP-NEXT: (block (result (ref string)) - ;; RRTRP-NEXT: (local.set $scratch_6 - ;; RRTRP-NEXT: (string.const "abc") - ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.set $1 + ;; RRTRP-NEXT: (string.const "abc") + ;; RRTRP-NEXT: (block (result i32) + ;; RRTRP-NEXT: (local.set $4 ;; RRTRP-NEXT: (block (result i32) - ;; RRTRP-NEXT: (local.set $scratch_5 + ;; RRTRP-NEXT: (local.set $scratch_6 ;; RRTRP-NEXT: (i32.const 1) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: (local.set $2 ;; RRTRP-NEXT: (local.get $end) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $scratch_5) + ;; RRTRP-NEXT: (local.set $5 + ;; RRTRP-NEXT: (local.get $2) + ;; RRTRP-NEXT: ) + ;; RRTRP-NEXT: (local.get $scratch_6) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $scratch_6) + ;; RRTRP-NEXT: (local.get $4) ;; RRTRP-NEXT: ) - ;; RRTRP-NEXT: (local.get $1) - ;; RRTRP-NEXT: (local.get $2) + ;; RRTRP-NEXT: (local.get $5) ;; RRTRP-NEXT: ) ;; RRTRP-NEXT: ) (func $slice-end-get (result stringref) diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast index 9e3c6171f4b..4b355c464f1 100644 --- a/test/lit/wat-kitchen-sink.wast +++ b/test/lit/wat-kitchen-sink.wast @@ -586,16 +586,12 @@ ) ;; CHECK: (func $add-stacky (type $1) (result i32) - ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $scratch - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $add-stacky (result i32) @@ -646,17 +642,11 @@ ;; CHECK: (func $add-stacky-4 (type $1) (result i32) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (local $scratch_1 i32) - ;; CHECK-NEXT: (local $scratch_2 i32) - ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (local.set $scratch_1 ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $scratch_1 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (local.get $scratch_1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -666,7 +656,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: (local.get $scratch_1) ;; CHECK-NEXT: ) (func $add-stacky-4 (result i32) i32.const 1 @@ -738,21 +728,17 @@ ) ;; CHECK: (func $add-twice-stacky (type $ret2) (result i32 i32) - ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $scratch - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (local.get $scratch) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/stacky.wasm.fromBinary b/test/stacky.wasm.fromBinary index d7c85091e1d..8543fca0a4c 100644 --- a/test/stacky.wasm.fromBinary +++ b/test/stacky.wasm.fromBinary @@ -3,18 +3,14 @@ (memory $0 256 256) (export "add" (func $0)) (func $0 (param $0 i32) (param $1 i32) (result i32) - (local $scratch i32) (i32.add + (local.get $0) (block (result i32) - (local.set $scratch - (local.get $0) - ) (local.set $0 (i32.const 100) ) - (local.get $scratch) + (local.get $1) ) - (local.get $1) ) ) ) From 1a3544bf4151ca438cc75ad2497bf407318a6e18 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 15 Apr 2026 16:31:14 -0700 Subject: [PATCH 2/2] valIndex => hoistIndex --- src/wasm-ir-builder.h | 2 +- src/wasm/wasm-ir-builder.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 7abe73a36b5..92b8a8acfb1 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -717,7 +717,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { // be the original value-producing expression, or if we are popping // greedily, it might be the deepest none-typed expression under the // value-producing expression. - Index valIndex; + Index hoistIndex; // The local.get placed on the stack, if any. LocalGet* get; }; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index f7c14f79f12..55ef512b103 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -121,17 +121,17 @@ Result<> IRBuilder::packageHoistedValue(const HoistedVal& hoisted, // we are synthesizing a block to help us determine later whether we need to // run the nested pop fixup. scopeStack[0].noteSyntheticBlock(); - std::vector exprs(scope.exprStack.begin() + hoisted.valIndex, + std::vector exprs(scope.exprStack.begin() + hoisted.hoistIndex, scope.exprStack.end()); auto* block = builder.makeBlock(exprs, type); - scope.exprStack.resize(hoisted.valIndex); + scope.exprStack.resize(hoisted.hoistIndex); pushSynthetic(block); }; auto type = scope.exprStack.back()->type; if (type.size() == sizeHint || type.size() <= 1) { - if (hoisted.valIndex < scope.exprStack.size() - 1) { + if (hoisted.hoistIndex < scope.exprStack.size() - 1) { packageAsBlock(type); } return Ok{};