Skip to content

Commit 0ef5bd9

Browse files
authored
[interp] Pop memory.copy/table.copy length using the minimum index type (WebAssembly#2800)
A valid module doing a mixed-width `table.copy` aborts wasm-interp in a debug build: $ wasm-interp --enable-memory64 --enable-multi-memory --run-all-exports t.wasm Assertion failed: (t == type || type == ValueType::Any), function CheckType, file interp.h, line 596. The module copies from an i64-indexed table into an i32-indexed one (`table.copy $t32 $t64`). Found it running the memory64 tests under `--run-all-exports`. `DoTableCopy`/`DoMemoryCopy` pop the length operand with the source's index type (`PopPtr(table_src)`/`PopPtr(mem_src)`). But the length's type is the minimum of the two index types, not the source's, so for a 64-bit source into a 32-bit destination the length is i32 while the interpreter reads it as i64. `TypeChecker::OnMemoryCopy`/`OnTableCopy` already spell out the min rule; the runtime side just didn't follow it. Debug builds trip the type assertion. In release the read only gives the right answer because the `Value` union happens to be zero-initialised. Pop the length as u64 only when both memories/tables are 64-bit. Same-width copies are byte-identical. Regression test added under test/interp.
1 parent 11602da commit 0ef5bd9

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

src/interp/interp.cc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2326,7 +2326,11 @@ RunResult Thread::DoDataDrop(Instr instr) {
23262326
RunResult Thread::DoMemoryCopy(Instr instr, Trap::Ptr* out_trap) {
23272327
Memory::Ptr mem_dst{store_, inst_->memories()[instr.imm_u32x2.fst]};
23282328
Memory::Ptr mem_src{store_, inst_->memories()[instr.imm_u32x2.snd]};
2329-
u64 size = PopPtr(mem_src);
2329+
// The size operand's type is the minimum of the two memory index types, so it
2330+
// is only 64-bit when both memories are (see TypeChecker::OnMemoryCopy).
2331+
bool size_is_64 =
2332+
mem_dst->type().limits.is_64 && mem_src->type().limits.is_64;
2333+
u64 size = size_is_64 ? Pop<u64>() : Pop<u32>();
23302334
u64 src = PopPtr(mem_src);
23312335
u64 dst = PopPtr(mem_dst);
23322336
// TODO: change to "out of bounds"
@@ -2364,7 +2368,11 @@ RunResult Thread::DoElemDrop(Instr instr) {
23642368
RunResult Thread::DoTableCopy(Instr instr, Trap::Ptr* out_trap) {
23652369
Table::Ptr table_dst{store_, inst_->tables()[instr.imm_u32x2.fst]};
23662370
Table::Ptr table_src{store_, inst_->tables()[instr.imm_u32x2.snd]};
2367-
u64 size = PopPtr(table_src);
2371+
// The size operand's type is the minimum of the two table index types, so it
2372+
// is only 64-bit when both tables are (see TypeChecker::OnTableCopy).
2373+
bool size_is_64 =
2374+
table_dst->type().limits.is_64 && table_src->type().limits.is_64;
2375+
u64 size = size_is_64 ? Pop<u64>() : Pop<u32>();
23682376
u64 src = PopPtr(table_src);
23692377
u64 dst = PopPtr(table_dst);
23702378
TRAP_IF(Failed(Table::Copy(store_, *table_dst, dst, *table_src, src, size)),

test/interp/bulk-copy-mixed64.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
;;; TOOL: run-interp
2+
;;; ARGS*: --enable-memory64 --enable-multi-memory
3+
(module
4+
(memory $m32 1 1)
5+
(memory $m64 i64 1 1)
6+
(data (memory $m64) (i64.const 0) "\11\22\33\44")
7+
8+
(table $t32 10 funcref)
9+
(table $t64 i64 10 funcref)
10+
(elem (table $t64) (i64.const 1) func $a $b)
11+
(type $r (func (result i32)))
12+
(func $a (result i32) (i32.const 111))
13+
(func $b (result i32) (i32.const 222))
14+
15+
;; memory.copy/table.copy from a 64-bit source into a 32-bit destination: the
16+
;; length operand's type is the minimum of the two index types (i32 here), so
17+
;; the interpreter must pop it as i32, not as the source's i64.
18+
(func (export "mem_copy_64to32")
19+
(memory.copy $m32 $m64 (i32.const 0) (i64.const 0) (i32.const 4)))
20+
(func (export "mem_read0") (result i32) (i32.load8_u $m32 (i32.const 0)))
21+
(func (export "mem_read3") (result i32) (i32.load8_u $m32 (i32.const 3)))
22+
23+
(func (export "tbl_copy_64to32")
24+
(table.copy $t32 $t64 (i32.const 5) (i64.const 1) (i32.const 2)))
25+
(func (export "tbl_call5") (result i32)
26+
(call_indirect $t32 (type $r) (i32.const 5)))
27+
(func (export "tbl_call6") (result i32)
28+
(call_indirect $t32 (type $r) (i32.const 6))))
29+
(;; STDOUT ;;;
30+
mem_copy_64to32() =>
31+
mem_read0() => i32:17
32+
mem_read3() => i32:68
33+
tbl_copy_64to32() =>
34+
tbl_call5() => i32:111
35+
tbl_call6() => i32:222
36+
;;; STDOUT ;;)

0 commit comments

Comments
 (0)