From a3eb99c65e301a998914dda92d59f60ed832aa0b Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Wed, 25 Feb 2026 09:14:37 +0800 Subject: [PATCH 1/2] Fix i32 sign extension in MemoryPacking pass Use getUnsigned() to properly zero-extend i32 values when computing memory.init offset and size. Previously, geti32() returned a signed int32_t that sign-extended when stored as uint64_t, causing values >= 0x80000000 to produce incorrect overflow detection and range calculations. Also clean up existing uint32_t() casts in the same function to use getUnsigned() consistently. --- src/passes/MemoryPacking.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/passes/MemoryPacking.cpp b/src/passes/MemoryPacking.cpp index b9acb32f92e..1cd8d1bd3db 100644 --- a/src/passes/MemoryPacking.cpp +++ b/src/passes/MemoryPacking.cpp @@ -455,15 +455,15 @@ void MemoryPacking::optimizeSegmentOps(Module* module) { bool mustTrap = false; auto* offset = curr->offset->dynCast(); auto* size = curr->size->dynCast(); - if (offset && uint32_t(offset->value.geti32()) > maxRuntimeSize) { + if (offset && offset->value.getUnsigned() > maxRuntimeSize) { mustTrap = true; } - if (size && uint32_t(size->value.geti32()) > maxRuntimeSize) { + if (size && size->value.getUnsigned() > maxRuntimeSize) { mustTrap = true; } if (offset && size) { - uint64_t offsetVal(offset->value.geti32()); - uint64_t sizeVal(size->value.geti32()); + auto offsetVal = offset->value.getUnsigned(); + auto sizeVal = size->value.getUnsigned(); if (offsetVal + sizeVal > maxRuntimeSize) { mustTrap = true; } else if (offsetVal == 0 && sizeVal == 0) { @@ -710,8 +710,8 @@ void MemoryPacking::createReplacements(Module* module, } // Nonconstant offsets or sizes will have inhibited splitting - size_t start = init->offset->cast()->value.geti32(); - size_t end = start + init->size->cast()->value.geti32(); + size_t start = init->offset->cast()->value.getUnsigned(); + size_t end = start + init->size->cast()->value.getUnsigned(); // Index in `segments` of the segment used in emitted memory.init // instructions From bd38334539c34f5bbe7e1584caa8e76cbaea9ea6 Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Thu, 26 Feb 2026 13:35:49 +0800 Subject: [PATCH 2/2] Add regression test for memory64 high-bit offsets in MemoryPacking Add a test covering memory64 data segment offsets with the high bit set (>= 0x80000000) to verify they are treated as unsigned values and not sign-extended to 64 bits. --- .../memory-packing_memory64-high-addr.wast | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 test/lit/passes/memory-packing_memory64-high-addr.wast diff --git a/test/lit/passes/memory-packing_memory64-high-addr.wast b/test/lit/passes/memory-packing_memory64-high-addr.wast new file mode 100644 index 00000000000..196727ca4c8 --- /dev/null +++ b/test/lit/passes/memory-packing_memory64-high-addr.wast @@ -0,0 +1,139 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --memory-packing -all --zero-filled-memory -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --memory-packing -all --zero-filled-memory -tnh -S -o - | filecheck %s --check-prefix=TNH__ + +;; Test that memory-packing correctly handles memory64 data segment offsets +;; with the high bit set (>= 0x80000000). These must be treated as unsigned +;; values, not sign-extended to 64 bits. + +(module + ;; An active data segment at offset 0x80000000 on a memory64 with only 2 + ;; pages. The offset far exceeds the memory size, so this should trap and the + ;; segment must be preserved. + ;; CHECK: (memory $memory i64 2 2) + ;; TNH__: (memory $memory i64 2 2) + (memory $memory i64 2 2) + ;; CHECK: (data $data (i64.const 2147483648) "\00") + (data $data (i64.const 0x80000000) "\00") +) + +(module + ;; Similar to above but with a multi-byte segment that should be shortened to + ;; a single byte at the highest address to preserve the trap. + ;; CHECK: (memory $memory i64 2 2) + ;; TNH__: (memory $memory i64 2 2) + (memory $memory i64 2 2) + ;; CHECK: (data $data (i64.const 2147483650) "\00") + (data $data (i64.const 0x80000000) "\00\00\00") +) + +(module + ;; Offset 0xFFFFFFFF (4GB - 1) on memory64. This is a valid i64 offset that + ;; must not be confused with -1 in i32. The segment should trap. + ;; CHECK: (memory $memory i64 2 2) + ;; TNH__: (memory $memory i64 2 2) + (memory $memory i64 2 2) + ;; CHECK: (data $data (i64.const 4294967295) "\00") + (data $data (i64.const 0xFFFFFFFF) "\00") +) + +(module + ;; A memory64 with enough pages that offset 0x80000000 is in bounds (0x80000000 + ;; = 2GB, which is 32768 pages). The segment should be removable since it's all + ;; zeroes and in bounds. + ;; CHECK: (memory $memory i64 32769 32769) + ;; TNH__: (memory $memory i64 32769 32769) + (memory $memory i64 32769 32769) + (data $data (i64.const 0x80000000) "\00") +) + +(module + ;; A passive segment used with memory.init. The memory.init offset into the + ;; segment is 0x80000000 (as i32), which exceeds the segment data size and + ;; should cause a trap. Ensure the i32 offset is treated as unsigned (2147483648) + ;; not sign-extended to a negative value. + ;; CHECK: (type $0 (func)) + + ;; CHECK: (memory $memory i64 2 2) + ;; TNH__: (type $0 (func)) + + ;; TNH__: (memory $memory i64 2 2) + (memory $memory i64 2 2) + (data $data "hello") + ;; CHECK: (func $test (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -2147483648) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; TNH__: (func $test (type $0) + ;; TNH__-NEXT: (drop + ;; TNH__-NEXT: (i64.const 0) + ;; TNH__-NEXT: ) + ;; TNH__-NEXT: (drop + ;; TNH__-NEXT: (i32.const -2147483648) + ;; TNH__-NEXT: ) + ;; TNH__-NEXT: (drop + ;; TNH__-NEXT: (i32.const 1) + ;; TNH__-NEXT: ) + ;; TNH__-NEXT: (unreachable) + ;; TNH__-NEXT: ) + (func $test + (memory.init $data + (i64.const 0) + (i32.const 0x80000000) + (i32.const 1) + ) + ) +) + +(module + ;; A passive segment where memory.init size has the high bit set. + ;; Size 0x80000000 (2147483648) exceeds the segment data size (5) and should + ;; trap. Ensure the i32 size is treated as unsigned. + ;; CHECK: (type $0 (func)) + + ;; CHECK: (memory $memory i64 2 2) + ;; TNH__: (type $0 (func)) + + ;; TNH__: (memory $memory i64 2 2) + (memory $memory i64 2 2) + (data $data "hello") + ;; CHECK: (func $test (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const -2147483648) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; TNH__: (func $test (type $0) + ;; TNH__-NEXT: (drop + ;; TNH__-NEXT: (i64.const 0) + ;; TNH__-NEXT: ) + ;; TNH__-NEXT: (drop + ;; TNH__-NEXT: (i32.const 0) + ;; TNH__-NEXT: ) + ;; TNH__-NEXT: (drop + ;; TNH__-NEXT: (i32.const -2147483648) + ;; TNH__-NEXT: ) + ;; TNH__-NEXT: (unreachable) + ;; TNH__-NEXT: ) + (func $test + (memory.init $data + (i64.const 0) + (i32.const 0) + (i32.const 0x80000000) + ) + ) +)