Skip to content

Commit a109d2a

Browse files
winch(x64): Add support for table instructions (#7155)
* winch(x64): Add support for table instructions This change adds support for the following table insructions: `elem.drop`, `table.copy`, `table.set`, `table.get`, `table.fill`, `table.grow`, `table.size`, `table.init`. This change also introduces partial support for the `Ref` WebAssembly type, more conretely the `Func` heap type, which means that all the table instructions above, only work this WebAssembly type as of this change. Finally, this change is also a small follow up to the primitives introduced in #7100, more concretely: * `FnCall::with_lib`: tracks the presence of a libcall and ensures that any result registers are freed right when the call is emitted. * `MacroAssembler::table_elem_addr` returns an address rather than the value of the address, making it convenient for other use cases like `table.set`. -- prtest:full * chore: Make stack functions take impl IntoIterator<..> * Update winch/codegen/src/codegen/call.rs Co-authored-by: Trevor Elliott <awesomelyawesome@gmail.com> * Remove a dangling `dbg!` * Add comment on branching --------- Co-authored-by: Trevor Elliott <awesomelyawesome@gmail.com>
1 parent b77b407 commit a109d2a

24 files changed

Lines changed: 1301 additions & 200 deletions

File tree

build.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,19 +205,34 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
205205
// We ignore tests that assert for traps on windows, given
206206
// that Winch doesn't encode unwind information for Windows, yet.
207207
if strategy == "Winch" {
208+
let assert_trap = [
209+
"i32",
210+
"i64",
211+
"call_indirect",
212+
"table_fill",
213+
"table_init",
214+
"table_copy",
215+
"table_set",
216+
"table_get",
217+
]
218+
.contains(&testname);
219+
220+
if assert_trap && env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() == "windows" {
221+
return true;
222+
}
223+
208224
if testsuite == "misc_testsuite" {
209225
// The misc/call_indirect is fully supported by Winch.
210-
if testname == "call_indirect" {
211-
return false;
226+
if testname != "call_indirect" {
227+
return true;
212228
}
213229
}
214-
if testsuite != "winch" {
215-
return true;
230+
if testsuite == "spec_testsuite" {
231+
// The official table init and table copy tests are now supported.
232+
return !["table_init", "table_copy"].contains(&testname);
216233
}
217234

218-
let assert_trap = ["i32", "i64", "call_indirect"].contains(&testname);
219-
220-
if assert_trap && env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() == "windows" {
235+
if testsuite != "winch" {
221236
return true;
222237
}
223238
}

fuzz/fuzz_targets/differential.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,15 @@ fn winch_supports_module(module: &[u8]) -> bool {
375375
| F64Abs { .. }
376376
| F32Neg { .. }
377377
| F64Neg { .. }
378-
| CallIndirect { .. } => {}
378+
| CallIndirect { .. }
379+
| ElemDrop { .. }
380+
| TableCopy { .. }
381+
| TableSet { .. }
382+
| TableGet { .. }
383+
| TableFill { .. }
384+
| TableGrow { .. }
385+
| TableSize { .. }
386+
| TableInit { .. } => {}
379387
_ => {
380388
supported = false;
381389
break 'main;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
(module
2+
(type $t0 (func))
3+
(func $f1 (type $t0))
4+
(func $f2 (type $t0))
5+
(func $f3 (type $t0))
6+
7+
;; Define two tables of funcref
8+
(table $t1 3 funcref)
9+
(table $t2 10 funcref)
10+
11+
;; Initialize table $t1 with functions $f1, $f2, $f3
12+
(elem (i32.const 0) $f1 $f2 $f3)
13+
14+
;; Function to fill table $t1 using a function reference from table $t2
15+
(func (export "fill") (param $i i32) (param $r i32) (param $n i32)
16+
(local $ref funcref)
17+
(local.set $ref (table.get $t1 (local.get $r)))
18+
(table.fill $t2 (local.get $i) (local.get $ref) (local.get $n))
19+
)
20+
21+
(func (export "get") (param $i i32) (result funcref)
22+
(table.get $t2 (local.get $i))
23+
)
24+
)
25+
26+
(assert_return (invoke "get" (i32.const 1)) (ref.null func))
27+
(assert_return (invoke "get" (i32.const 2)) (ref.null func))
28+
(assert_return (invoke "get" (i32.const 3)) (ref.null func))
29+
(assert_return (invoke "get" (i32.const 4)) (ref.null func))
30+
(assert_return (invoke "get" (i32.const 5)) (ref.null func))
31+
32+
(assert_return (invoke "fill" (i32.const 2) (i32.const 0) (i32.const 3)))
33+
(assert_return (invoke "get" (i32.const 1)) (ref.null func))
34+
(assert_return (invoke "get" (i32.const 2)) (ref.func 0))
35+
(assert_return (invoke "get" (i32.const 3)) (ref.func 0))
36+
(assert_return (invoke "get" (i32.const 4)) (ref.func 0))
37+
(assert_return (invoke "get" (i32.const 5)) (ref.null func))
38+
39+
(assert_return (invoke "fill" (i32.const 4) (i32.const 1) (i32.const 2)))
40+
(assert_return (invoke "get" (i32.const 3)) (ref.func 0))
41+
(assert_return (invoke "get" (i32.const 4)) (ref.func 1))
42+
(assert_return (invoke "get" (i32.const 5)) (ref.func 1))
43+
(assert_return (invoke "get" (i32.const 6)) (ref.null func))
44+
45+
(assert_return (invoke "fill" (i32.const 4) (i32.const 2) (i32.const 0)))
46+
(assert_return (invoke "get" (i32.const 3)) (ref.func 0))
47+
(assert_return (invoke "get" (i32.const 4)) (ref.func 1))
48+
(assert_return (invoke "get" (i32.const 5)) (ref.func 1))
49+
50+
(assert_return (invoke "fill" (i32.const 8) (i32.const 0) (i32.const 2)))
51+
(assert_return (invoke "get" (i32.const 7)) (ref.null func))
52+
(assert_return (invoke "get" (i32.const 8)) (ref.func 0))
53+
(assert_return (invoke "get" (i32.const 9)) (ref.func 0))
54+
55+
(assert_return (invoke "fill" (i32.const 9) (i32.const 2) (i32.const 1)))
56+
(assert_return (invoke "get" (i32.const 8)) (ref.func 0))
57+
(assert_return (invoke "get" (i32.const 9)) (ref.func 2))
58+
59+
(assert_return (invoke "fill" (i32.const 10) (i32.const 1) (i32.const 0)))
60+
(assert_return (invoke "get" (i32.const 9)) (ref.func 2))
61+
62+
(assert_trap
63+
(invoke "fill" (i32.const 8) (i32.const 0) (i32.const 3))
64+
"out of bounds table access"
65+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(module
2+
(table $t3 3 funcref)
3+
(elem (table $t3) (i32.const 1) func $dummy)
4+
(func $dummy)
5+
(func $f3 (export "get-funcref") (param $i i32) (result funcref)
6+
(table.get $t3 (local.get $i))
7+
)
8+
)
9+
10+
(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func))
11+
(assert_trap (invoke "get-funcref" (i32.const 3)) "out of bounds table access")
12+
(assert_trap (invoke "get-funcref" (i32.const -1)) "out of bounds table access")
13+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
(module
2+
(table $t1 0 funcref)
3+
4+
(func (export "grow-by-10") (param $r funcref) (result i32)
5+
(table.grow $t1 (local.get $r) (i32.const 10))
6+
)
7+
(func (export "grow-over") (param $r funcref) (result i32)
8+
(table.grow $t1 (local.get $r) (i32.const 0xffff_fff0))
9+
)
10+
11+
(func (export "size") (result i32)
12+
(table.size $t1))
13+
)
14+
15+
(assert_return (invoke "size") (i32.const 0))
16+
(assert_return (invoke "grow-by-10" (ref.null func)) (i32.const 0))
17+
(assert_return (invoke "size") (i32.const 10))
18+
19+
(module
20+
(table $t 0x10 funcref)
21+
(func $f (export "grow") (param $r funcref) (result i32)
22+
(table.grow $t (local.get $r) (i32.const 0xffff_fff0))
23+
)
24+
)
25+
26+
(assert_return (invoke "grow" (ref.null func)) (i32.const -1))
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
(module
2+
(table $t3 2 funcref)
3+
(elem (table $t3) (i32.const 1) func $dummy)
4+
(func $dummy)
5+
6+
(func $f3 (export "get-funcref") (param $i i32) (result funcref)
7+
(table.get $t3 (local.get $i))
8+
)
9+
10+
(func (export "set-funcref") (param $i i32) (param $r funcref)
11+
(table.set $t3 (local.get $i) (local.get $r))
12+
)
13+
(func (export "set-funcref-from") (param $i i32) (param $j i32)
14+
(table.set $t3 (local.get $i) (table.get $t3 (local.get $j)))
15+
)
16+
)
17+
18+
(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func))
19+
(assert_return (invoke "set-funcref-from" (i32.const 0) (i32.const 1)))
20+
(assert_return (invoke "set-funcref" (i32.const 0) (ref.null func)))
21+
(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func))
22+
23+
(assert_trap (invoke "set-funcref" (i32.const 3) (ref.null func)) "out of bounds table access")
24+
(assert_trap (invoke "set-funcref" (i32.const -1) (ref.null func)) "out of bounds table access")
25+
26+
(assert_trap (invoke "set-funcref-from" (i32.const 3) (i32.const 1)) "out of bounds table access")
27+
(assert_trap (invoke "set-funcref-from" (i32.const -1) (i32.const 1)) "out of bounds table access")

winch/codegen/src/abi/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use crate::isa::{reg::Reg, CallingConvention};
4747
use crate::masm::OperandSize;
4848
use smallvec::SmallVec;
4949
use std::ops::{Add, BitAnd, Not, Sub};
50-
use wasmtime_environ::{WasmFuncType, WasmType};
50+
use wasmtime_environ::{WasmFuncType, WasmHeapType, WasmType};
5151

5252
pub(crate) mod local;
5353
pub(crate) use local::*;
@@ -267,7 +267,15 @@ pub(crate) fn ty_size(ty: &WasmType) -> u32 {
267267
match *ty {
268268
WasmType::I32 | WasmType::F32 => 4,
269269
WasmType::I64 | WasmType::F64 => 8,
270-
_ => panic!(),
270+
WasmType::Ref(rt) => match rt.heap_type {
271+
// TODO: Similar to the comment in visitor.rs at impl From<WasmType> for
272+
// OperandSize, Once Wasmtime supports 32-bit architectures, this will
273+
// need to be updated to derive operand size from the target's pointer
274+
// size.
275+
WasmHeapType::Func => 8,
276+
ht => unimplemented!("Support for WasmHeapType: {ht}"),
277+
},
278+
t => unimplemented!("Support for WasmType: {t}"),
271279
}
272280
}
273281

winch/codegen/src/codegen/call.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Function call emission. For more details around the ABI and
22
//! calling convention, see [ABI].
33
use crate::{
4-
abi::{ABIArg, ABISig, ABI},
4+
abi::{ABIArg, ABIResult, ABISig, ABI},
55
codegen::{BuiltinFunction, CodeGenContext},
66
masm::{CalleeKind, MacroAssembler, OperandSize},
77
reg::Reg,
@@ -65,6 +65,8 @@ pub(crate) struct FnCall<'a> {
6565
arg_stack_space: u32,
6666
/// The ABI-specific signature of the callee.
6767
pub abi_sig: &'a ABISig,
68+
/// Whether this a built-in function call.
69+
lib: bool,
6870
}
6971

7072
impl<'a> FnCall<'a> {
@@ -74,6 +76,7 @@ impl<'a> FnCall<'a> {
7476
abi_sig: &callee_sig,
7577
arg_stack_space: callee_sig.stack_bytes,
7678
call_stack_space: None,
79+
lib: false,
7780
}
7881
}
7982

@@ -238,6 +241,7 @@ impl<'a> FnCall<'a> {
238241
) where
239242
F: FnMut(&mut CodeGenContext, &mut M, &mut Self, Reg),
240243
{
244+
self.lib = true;
241245
// When dealing with libcalls, we don't have all the information
242246
// upfront (all necessary arguments in the stack) in order to optimize
243247
// saving the live registers, so we save all the values available in
@@ -288,6 +292,26 @@ impl<'a> FnCall<'a> {
288292
regalloc.free(v.get_reg().into());
289293
}
290294
});
295+
296+
// When emitting built-calls we ensure that none of the registers
297+
// (params and results) used as part of the ABI signature are
298+
// allocatable throughout the lifetime of the `with_lib` callback, since
299+
// such registers will be used to assign arguments and hold results.
300+
// After executing the callback, it's only safe to free the param
301+
// registers, since depending on the signature, the caller
302+
// will push any result registers to the stack, keeping those registers allocated.
303+
// Here we ensure that any allocated result registers are correctly
304+
// freed before finalizing the function call and pushing any results to
305+
// the value stack.
306+
if self.lib {
307+
match self.abi_sig.result {
308+
ABIResult::Reg { reg, .. } => {
309+
assert!(!context.regalloc.reg_available(reg));
310+
context.free_reg(reg);
311+
}
312+
_ => {}
313+
}
314+
}
291315
context.push_abi_results(&self.abi_sig.result, masm);
292316
}
293317

winch/codegen/src/codegen/context.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use wasmtime_environ::WasmType;
1+
use wasmtime_environ::{WasmHeapType, WasmType};
22

33
use super::ControlStackFrame;
44
use crate::{
@@ -63,7 +63,11 @@ impl<'a> CodeGenContext<'a> {
6363
match ty {
6464
I32 | I64 => self.reg_for_class(RegClass::Int, masm),
6565
F32 | F64 => self.reg_for_class(RegClass::Float, masm),
66-
t => panic!("unsupported type {:?}", t),
66+
Ref(rt) => match rt.heap_type {
67+
WasmHeapType::Func => self.reg_for_class(RegClass::Int, masm),
68+
ht => unimplemented!("Support for WasmHeapType: {ht}"),
69+
},
70+
t => unimplemented!("Support for WasmType: {t}"),
6771
}
6872
}
6973

0 commit comments

Comments
 (0)