From ad15af6b13870d9f2541782bdf5ed033193f5dfa Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Fri, 13 Feb 2026 07:53:23 +0800 Subject: [PATCH 1/3] Fix Memory64Lowering table.grow return value for -1 failure visitTableGrow used a plain i64.extend_i32_u on the table.grow return value, but table.grow returns -1 on failure. i64.extend_i32_u(0xFFFFFFFF) produces 0x00000000FFFFFFFF (4294967295), not i64 -1 (0xFFFFFFFFFFFFFFFF). Code checking result == -1 would fail to detect the failure. Apply the same if/else -1 check pattern already used by visitMemoryGrow. --- src/passes/Memory64Lowering.cpp | 27 +++++++++++- .../passes/memory64-lowering-table-grow.wast | 43 +++++++++++++++++++ test/lit/passes/memory64-lowering.wast | 24 ++++++++--- 3 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 test/lit/passes/memory64-lowering-table-grow.wast diff --git a/src/passes/Memory64Lowering.cpp b/src/passes/Memory64Lowering.cpp index 9d7a2a4252c..dd0fa09d7fa 100644 --- a/src/passes/Memory64Lowering.cpp +++ b/src/passes/Memory64Lowering.cpp @@ -224,8 +224,31 @@ struct Memory64Lowering : public WalkerPass> { if (table->is64()) { wrapTableAddress64(curr->delta, curr->table); auto* size = static_cast(curr); - extendTableAddress64(size, curr->table); - replaceCurrent(size); + // TableGrow returns -1 in case of failure. We cannot just use + // extend_32_u in this case so we handle it the same way as MemoryGrow: + // + // (if (result i64) + // (i32.eq (i32.const -1) (local.tee $tmp (table.grow X))) + // (then + // (i64.const -1) + // ) + // (else + // (i64.extend_i32_u (local.get $tmp)) + // ) + // ) + Builder builder(module); + auto tmp = builder.addVar(getFunction(), Type::i32); + Expression* isMinusOne = + builder.makeBinary(EqInt32, + builder.makeConst(int32_t(-1)), + builder.makeLocalTee(tmp, size, Type::i32)); + auto* newSize = builder.makeLocalGet(tmp, Type::i32); + Expression* ifExp = + builder.makeIf(isMinusOne, + builder.makeConst(int64_t(-1)), + builder.makeUnary(UnaryOp::ExtendUInt32, newSize)); + curr->type = Type::i32; + replaceCurrent(ifExp); } } diff --git a/test/lit/passes/memory64-lowering-table-grow.wast b/test/lit/passes/memory64-lowering-table-grow.wast new file mode 100644 index 00000000000..8c8f628f7de --- /dev/null +++ b/test/lit/passes/memory64-lowering-table-grow.wast @@ -0,0 +1,43 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --memory64-lowering --enable-memory64 --enable-reference-types -S -o - | filecheck %s + +;; Test that table.grow on a 64-bit table correctly handles the -1 failure +;; return value, matching the behavior of memory.grow lowering. +(module + ;; CHECK: (type $0 (func (result i64))) + + ;; CHECK: (table $t64 0 funcref) + (table $t64 i64 0 funcref) + + ;; CHECK: (func $table-grow-minus-one (result i64) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (if (result i64) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (table.grow $t64 + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i64.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-grow-minus-one (result i64) + ;; table.grow returns -1 on failure. The lowering must check for -1 and + ;; return i64(-1) instead of i64.extend_i32_u(i32(-1)) which would be + ;; 4294967295. + (table.grow $t64 (ref.null func) (i64.const 10)) + ) +) diff --git a/test/lit/passes/memory64-lowering.wast b/test/lit/passes/memory64-lowering.wast index a7d904d9f95..4f02b2243ec 100644 --- a/test/lit/passes/memory64-lowering.wast +++ b/test/lit/passes/memory64-lowering.wast @@ -399,11 +399,25 @@ ) ;; CHECK: (func $test_table_grow (result i64) - ;; CHECK-NEXT: (i64.extend_i32_u - ;; CHECK-NEXT: (table.grow $t64 - ;; CHECK-NEXT: (ref.null nofunc) - ;; CHECK-NEXT: (i32.wrap_i64 - ;; CHECK-NEXT: (i64.const 10) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (if (result i64) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (table.grow $t64 + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i64.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From 13c042c1e16bab15911c12c7d23513dce257a2a5 Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Sat, 14 Feb 2026 01:06:52 +0800 Subject: [PATCH 2/3] Address review feedback: add code comment and consolidate tests - Add comment explaining why curr->type is set to i32 (the table.grow node is reused as a child of local.tee storing to an i32 local) - Remove duplicate test file memory64-lowering-table-grow.wast - Move the -1 failure explanation comment to the existing test_table_grow function in memory64-lowering.wast --- src/passes/Memory64Lowering.cpp | 2 + .../passes/memory64-lowering-table-grow.wast | 43 ------------------- test/lit/passes/memory64-lowering.wast | 3 ++ 3 files changed, 5 insertions(+), 43 deletions(-) delete mode 100644 test/lit/passes/memory64-lowering-table-grow.wast diff --git a/src/passes/Memory64Lowering.cpp b/src/passes/Memory64Lowering.cpp index dd0fa09d7fa..a20cb4c0950 100644 --- a/src/passes/Memory64Lowering.cpp +++ b/src/passes/Memory64Lowering.cpp @@ -247,6 +247,8 @@ struct Memory64Lowering : public WalkerPass> { builder.makeIf(isMinusOne, builder.makeConst(int64_t(-1)), builder.makeUnary(UnaryOp::ExtendUInt32, newSize)); + // The table.grow node is reused as a child of local.tee which stores + // to an i32 local, so its type must be i32 to match the lowered table. curr->type = Type::i32; replaceCurrent(ifExp); } diff --git a/test/lit/passes/memory64-lowering-table-grow.wast b/test/lit/passes/memory64-lowering-table-grow.wast deleted file mode 100644 index 8c8f628f7de..00000000000 --- a/test/lit/passes/memory64-lowering-table-grow.wast +++ /dev/null @@ -1,43 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: foreach %s %t wasm-opt --memory64-lowering --enable-memory64 --enable-reference-types -S -o - | filecheck %s - -;; Test that table.grow on a 64-bit table correctly handles the -1 failure -;; return value, matching the behavior of memory.grow lowering. -(module - ;; CHECK: (type $0 (func (result i64))) - - ;; CHECK: (table $t64 0 funcref) - (table $t64 i64 0 funcref) - - ;; CHECK: (func $table-grow-minus-one (result i64) - ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (if (result i64) - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (i32.const -1) - ;; CHECK-NEXT: (local.tee $0 - ;; CHECK-NEXT: (table.grow $t64 - ;; CHECK-NEXT: (ref.null nofunc) - ;; CHECK-NEXT: (i32.wrap_i64 - ;; CHECK-NEXT: (i64.const 10) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (i64.const -1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (i64.extend_i32_u - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $table-grow-minus-one (result i64) - ;; table.grow returns -1 on failure. The lowering must check for -1 and - ;; return i64(-1) instead of i64.extend_i32_u(i32(-1)) which would be - ;; 4294967295. - (table.grow $t64 (ref.null func) (i64.const 10)) - ) -) diff --git a/test/lit/passes/memory64-lowering.wast b/test/lit/passes/memory64-lowering.wast index 4f02b2243ec..2745c04b729 100644 --- a/test/lit/passes/memory64-lowering.wast +++ b/test/lit/passes/memory64-lowering.wast @@ -423,6 +423,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test_table_grow (result i64) + ;; table.grow returns -1 on failure. The lowering must check for -1 and + ;; return i64(-1) instead of i64.extend_i32_u(i32(-1)) which would be + ;; 4294967295. (table.grow $t64 (ref.null func) (i64.const 10)) ) From 1185062a348edc38901d356ca991cf53c9ef7d00 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 13 Feb 2026 09:41:04 -0800 Subject: [PATCH 3/3] Update src/passes/Memory64Lowering.cpp --- src/passes/Memory64Lowering.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/passes/Memory64Lowering.cpp b/src/passes/Memory64Lowering.cpp index a20cb4c0950..dd0fa09d7fa 100644 --- a/src/passes/Memory64Lowering.cpp +++ b/src/passes/Memory64Lowering.cpp @@ -247,8 +247,6 @@ struct Memory64Lowering : public WalkerPass> { builder.makeIf(isMinusOne, builder.makeConst(int64_t(-1)), builder.makeUnary(UnaryOp::ExtendUInt32, newSize)); - // The table.grow node is reused as a child of local.tee which stores - // to an i32 local, so its type must be i32 to match the lowered table. curr->type = Type::i32; replaceCurrent(ifExp); }