diff --git a/src/passes/Memory64Lowering.cpp b/src/passes/Memory64Lowering.cpp index 9d7a2a4252c..dd5d250c51a 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); } } @@ -237,7 +260,12 @@ struct Memory64Lowering : public WalkerPass> { void visitTableCopy(TableCopy* curr) { wrapTableAddress64(curr->dest, curr->destTable); wrapTableAddress64(curr->source, curr->sourceTable); - wrapTableAddress64(curr->size, curr->destTable); + // The size type is i64 only when both tables are 64-bit. + auto& module = *getModule(); + if (module.getTable(curr->destTable)->is64() && + module.getTable(curr->sourceTable)->is64()) { + wrapAddress64(curr->size, curr->destTable, true); + } } void visitTableInit(TableInit* curr) { diff --git a/test/lit/passes/memory64-lowering-table-fixes.wast b/test/lit/passes/memory64-lowering-table-fixes.wast new file mode 100644 index 00000000000..b5a412a25e1 --- /dev/null +++ b/test/lit/passes/memory64-lowering-table-fixes.wast @@ -0,0 +1,103 @@ +;; 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 --enable-bulk-memory -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)) + ) +) + +;; Test that table.copy with mixed 32/64-bit tables correctly handles the +;; size operand type. +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (table $t64 10 funcref) + (table $t64 i64 10 funcref) + ;; CHECK: (table $t32 10 funcref) + (table $t32 10 funcref) + + ;; CHECK: (func $table-copy-mixed-64-to-32 + ;; CHECK-NEXT: (table.copy $t32 $t64 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-copy-mixed-64-to-32 + ;; Copy from 64-bit table to 32-bit table. The size is i32 because not + ;; both tables are 64-bit, so no wrapping of size should occur. + (table.copy $t32 $t64 (i32.const 0) (i64.const 0) (i32.const 5)) + ) + + ;; CHECK: (func $table-copy-mixed-32-to-64 + ;; CHECK-NEXT: (table.copy $t64 $t32 + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-copy-mixed-32-to-64 + ;; Copy from 32-bit table to 64-bit table. The size is i32 because not + ;; both tables are 64-bit, so no wrapping of size should occur. + (table.copy $t64 $t32 (i64.const 0) (i32.const 0) (i32.const 5)) + ) + + ;; CHECK: (func $table-copy-both-64 + ;; CHECK-NEXT: (table.copy $t64 $t64 + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-copy-both-64 + ;; Copy between same 64-bit table. All operands including size are i64 + ;; and should be wrapped. + (table.copy $t64 $t64 (i64.const 0) (i64.const 5) (i64.const 3)) + ) +) 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: )