Skip to content

Commit f01be81

Browse files
feat: add gc references
- keep references in the 32-bit stack lane - add i31 operations and reference casts - implement runtime reference subtyping - add tagged reference values - simplify reference table storage Signed-off-by: Henry <mail@henrygressmann.de>
1 parent c3638a8 commit f01be81

17 files changed

Lines changed: 441 additions & 196 deletions

File tree

crates/cli/src/wast_runner.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,9 @@ enum ExpectedValue {
791791
RefNull,
792792
RefFunc,
793793
RefExtern,
794+
RefAny,
795+
RefEq,
796+
RefI31,
794797
}
795798

796799
impl ExpectedValue {
@@ -800,6 +803,9 @@ impl ExpectedValue {
800803
Self::RefNull => matches!(value, WasmValue::Ref(RefValue::Null)),
801804
Self::RefFunc => matches!(value, WasmValue::Ref(RefValue::Func(_))),
802805
Self::RefExtern => matches!(value, WasmValue::Ref(RefValue::Extern(_))),
806+
Self::RefAny => matches!(value, WasmValue::Ref(RefValue::Any(_))),
807+
Self::RefEq => matches!(value, WasmValue::Ref(RefValue::Any(value)) if value.as_i31().is_some()),
808+
Self::RefI31 => matches!(value, WasmValue::Ref(RefValue::Any(value)) if value.as_i31().is_some()),
803809
}
804810
}
805811
}
@@ -817,7 +823,9 @@ fn wastret2tinywasmvalues(ret: wast::WastRet) -> Result<Vec<ExpectedValue>> {
817823
}
818824

819825
fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result<ExpectedValue> {
820-
use wast::core::WastRetCore::{F32, F64, I32, I64, RefExtern, RefFunc, RefNull, V128};
826+
use wast::core::WastRetCore::{
827+
F32, F64, I32, I64, RefAny, RefEq, RefExtern, RefFunc, RefI31, RefI31Shared, RefNull, V128,
828+
};
821829
Ok(match ret {
822830
F32(f) => ExpectedValue::Exact(nanpattern2tinywasmvalue(f)?),
823831
F64(f) => ExpectedValue::Exact(nanpattern2tinywasmvalue(f)?),
@@ -832,6 +840,9 @@ fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result<ExpectedVal
832840
RefFunc(v) => {
833841
bail!("unsupported arg type: reffunc: {:?}", v);
834842
}
843+
RefAny => ExpectedValue::RefAny,
844+
RefEq => ExpectedValue::RefEq,
845+
RefI31 | RefI31Shared => ExpectedValue::RefI31,
835846
a => {
836847
bail!("unsupported arg type {:?}", a);
837848
}

crates/parser/src/conversion.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,9 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result<Box<[C
324324
wasmparser::Operator::RefFunc { function_index } => {
325325
ConstInstruction::Ref(RefValue::Func(FuncRef::new(function_index)))
326326
}
327+
wasmparser::Operator::RefI31 => ConstInstruction::RefI31,
328+
wasmparser::Operator::AnyConvertExtern => ConstInstruction::AnyConvertExtern,
329+
wasmparser::Operator::ExternConvertAny => ConstInstruction::ExternConvertAny,
327330
wasmparser::Operator::I32Const { value } => ConstInstruction::I32Const(value),
328331
wasmparser::Operator::I64Const { value } => ConstInstruction::I64Const(value),
329332
wasmparser::Operator::F32Const { value } => ConstInstruction::F32Const(f32::from_bits(value.bits())),

crates/parser/src/macros.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@ pub(crate) mod visit {
3737
$(lowering_ops!(@effect $inputs => $outputs $visit);)*
3838
lowering_ops!($($rest)*);
3939
};
40+
(unsupported $args:tt { $($visit:ident),* $(,)? } $($rest:tt)*) => {
41+
$(lowering_ops!(@unsupported $args $visit);)*
42+
lowering_ops!($($rest)*);
43+
};
44+
(heap $nullable:literal $inputs:tt => $outputs:tt {
45+
$($visit:ident => $instr:ident),* $(,)?
46+
} $($rest:tt)*) => {
47+
$(
48+
fn $visit(&mut self, heap_type: wasmparser::HeapType) -> Self::Output {
49+
let ty = convert_heap_type(heap_type, $nullable)?;
50+
lowering_ops!(@emit self fixed $inputs => $outputs Instruction::$instr(ty))
51+
}
52+
)*
53+
lowering_ops!($($rest)*);
54+
};
55+
56+
(@unsupported [$($argty:ty),*] $visit:ident) => {
57+
fn $visit(&mut self $(, _: $argty)*) -> Self::Output {
58+
Err(crate::ParseError::UnsupportedOperator(stringify!($visit).to_string()))
59+
}
60+
};
4061

4162
(@fixed [$($input:ident),*] => [$($output:ident),*]
4263
$visit:ident $(($($arg:ident: $ty:ty),+))? => $instr:ident
@@ -128,6 +149,7 @@ pub(crate) mod visit {
128149
(@@relaxed_simd $($rest:tt)* ) => {};
129150
(@@tail_call $($rest:tt)* ) => {};
130151
(@@function_references $($rest:tt)* ) => {};
152+
(@@gc $($rest:tt)* ) => {};
131153

132154
(@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => {
133155
fn $visit(&mut self $($(,_: $argty)*)?) -> Self::Output {

crates/parser/src/optimize.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,7 @@ fn instruction_target_mut(instr: &mut Instruction) -> Option<&mut u32> {
858858
| Instruction::JumpIfNonZero64(ip)
859859
| Instruction::JumpIfRefNull(ip)
860860
| Instruction::JumpIfRefNonNull(ip)
861+
| Instruction::BrOnCast(ip, _, _)
861862
| Instruction::JumpCmpStackConst32 { target_ip: ip, .. }
862863
| Instruction::JumpCmpStackConst64 { target_ip: ip, .. }
863864
| Instruction::JumpIfLocalZero32 { target_ip: ip, .. }

crates/parser/src/visit.rs

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,18 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> {
343343
fixed [] => [] { visit_data_drop(segment: u32) => DataDrop, visit_elem_drop(segment: u32) => ElemDrop }
344344
fixed [] => [S32] { visit_i32_const(value: i32) => Const32, visit_ref_func(function: u32) => RefFunc }
345345
fixed [] => [S64] { visit_i64_const(value: i64) => Const64 }
346+
heap false [] => [S32] { visit_ref_null => RefNull }
347+
heap false [S32] => [S32] {
348+
visit_ref_test_non_null => RefTest, visit_ref_cast_non_null => RefCast,
349+
}
350+
heap true [S32] => [S32] {
351+
visit_ref_test_nullable => RefTest, visit_ref_cast_nullable => RefCast,
352+
}
346353
fixed [S32] => [S32] {
347-
visit_i32_eqz => I32Eqz, visit_ref_is_null => RefIsNull, visit_i32_clz => I32Clz,
354+
visit_ref_is_null => RefIsNull, visit_ref_as_non_null => RefAsNonNull, visit_ref_i31 => RefI31,
355+
visit_i31_get_s => I31GetS, visit_i31_get_u => I31GetU,
356+
visit_any_convert_extern => AnyConvertExtern, visit_extern_convert_any => ExternConvertAny,
357+
visit_i32_eqz => I32Eqz, visit_i32_clz => I32Clz,
348358
visit_i32_ctz => I32Ctz, visit_i32_popcnt => I32Popcnt, visit_i32_extend8_s => I32Extend8S,
349359
visit_i32_extend16_s => I32Extend16S, visit_i32_trunc_f32_s => I32TruncF32S,
350360
visit_i32_trunc_f32_u => I32TruncF32U, visit_f32_convert_i32_s => F32ConvertI32S,
@@ -377,7 +387,8 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> {
377387
visit_i64_trunc_sat_f32_u => I64TruncSatF32U,
378388
}
379389
fixed [S32, S32] => [S32] {
380-
visit_i32_eq => I32Eq, visit_i32_ne => I32Ne, visit_i32_lt_s => I32LtS, visit_i32_lt_u => I32LtU,
390+
visit_ref_eq => RefEq, visit_i32_eq => I32Eq, visit_i32_ne => I32Ne,
391+
visit_i32_lt_s => I32LtS, visit_i32_lt_u => I32LtU,
381392
visit_i32_gt_s => I32GtS, visit_i32_gt_u => I32GtU, visit_i32_le_s => I32LeS,
382393
visit_i32_le_u => I32LeU, visit_i32_ge_s => I32GeS, visit_i32_ge_u => I32GeU,
383394
visit_f32_eq => F32Eq, visit_f32_ne => F32Ne, visit_f32_lt => F32Lt, visit_f32_gt => F32Gt,
@@ -424,6 +435,16 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> {
424435
table [S32, Addr] => [Addr] { visit_table_grow(table: u32) => TableGrow }
425436
table [Addr, S32, Addr] => [] { visit_table_fill(table: u32) => TableFill }
426437
table [Addr, S32, S32] => [] { visit_table_init(elem_index: u32, table: u32) => TableInit }
438+
unsupported [] { visit_array_len }
439+
unsupported [u32] {
440+
visit_struct_new, visit_struct_new_default, visit_array_new, visit_array_new_default,
441+
visit_array_get, visit_array_get_s, visit_array_get_u, visit_array_set, visit_array_fill,
442+
}
443+
unsupported [u32, u32] {
444+
visit_struct_get, visit_struct_get_s, visit_struct_get_u, visit_struct_set,
445+
visit_array_new_fixed, visit_array_new_data, visit_array_new_elem, visit_array_copy,
446+
visit_array_init_data, visit_array_init_elem,
447+
}
427448
}
428449

429450
fn visit_call(&mut self, function_index: u32) -> Self::Output {
@@ -722,14 +743,22 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> {
722743
self.emit(&[dst, src, len], &[], Instruction::MemoryCopy { dst_mem, src_mem })
723744
}
724745

725-
// Reference Types
726-
fn visit_ref_null(&mut self, ty: wasmparser::HeapType) -> Self::Output {
727-
let instruction = Instruction::RefNull(convert_heap_type(ty, false)?);
728-
self.emit(&[], &[OperandSize::S32], instruction)
746+
fn visit_br_on_cast(
747+
&mut self,
748+
relative_depth: u32,
749+
_from_ref_type: wasmparser::RefType,
750+
to_ref_type: wasmparser::RefType,
751+
) -> Self::Output {
752+
self.emit_cast_branch(relative_depth, to_ref_type, false)
729753
}
730754

731-
fn visit_ref_as_non_null(&mut self) -> Self::Output {
732-
self.emit(&[OperandSize::S32], &[OperandSize::S32], Instruction::RefAsNonNull)
755+
fn visit_br_on_cast_fail(
756+
&mut self,
757+
relative_depth: u32,
758+
_from_ref_type: wasmparser::RefType,
759+
to_ref_type: wasmparser::RefType,
760+
) -> Self::Output {
761+
self.emit_cast_branch(relative_depth, to_ref_type, true)
733762
}
734763

735764
fn visit_br_on_null(&mut self, relative_depth: u32) -> Self::Output {
@@ -975,6 +1004,23 @@ impl wasmparser::VisitSimdOperator<'_> for FunctionBuilder<'_> {
9751004
}
9761005

9771006
impl FunctionBuilder<'_> {
1007+
fn emit_cast_branch(
1008+
&mut self,
1009+
relative_depth: u32,
1010+
target: wasmparser::RefType,
1011+
branch_on_fail: bool,
1012+
) -> Result<()> {
1013+
self.pop_expect(OperandSize::S32)?;
1014+
let target = convert_heap_type(target.heap_type(), target.is_nullable())?;
1015+
let conditional_ip = self.instructions.len();
1016+
self.instructions.push(Instruction::BrOnCast(0, target, branch_on_fail));
1017+
self.push_sizes(&[OperandSize::S32])?;
1018+
self.emit_dropkeep_to_label(relative_depth)?;
1019+
self.emit_branch_jump_or_return(relative_depth)?;
1020+
self.patch_jump(conditional_ip, self.instructions.len());
1021+
Ok(())
1022+
}
1023+
9781024
fn is_unreachable(&self) -> bool {
9791025
self.control_stack.last().is_none_or(|frame| frame.unreachable)
9801026
}
@@ -1112,7 +1158,8 @@ impl FunctionBuilder<'_> {
11121158
| Instruction::JumpIfZero32(ip)
11131159
| Instruction::JumpIfNonZero32(ip)
11141160
| Instruction::JumpIfRefNull(ip)
1115-
| Instruction::JumpIfRefNonNull(ip) => {
1161+
| Instruction::JumpIfRefNonNull(ip)
1162+
| Instruction::BrOnCast(ip, _, _) => {
11161163
*ip = target as u32;
11171164
}
11181165
_ => {}

crates/tinywasm/src/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ pub enum Trap {
172172
/// A null function reference was called.
173173
NullFunctionReference,
174174

175+
/// A null i31 reference was unwrapped.
176+
NullI31Reference,
177+
178+
/// A reference cast failed.
179+
CastFailure,
180+
175181
/// Indirect call type mismatch
176182
IndirectCallTypeMismatch {
177183
/// The expected type
@@ -201,6 +207,8 @@ impl Trap {
201207
Self::UninitializedElement { .. } => "uninitialized element",
202208
Self::NullReference => "null reference",
203209
Self::NullFunctionReference => "null function reference",
210+
Self::NullI31Reference => "null i31 reference",
211+
Self::CastFailure => "cast failure",
204212
Self::IndirectCallTypeMismatch { .. } => "indirect call type mismatch",
205213
Self::HostFunction(_) => "host function trap",
206214
Self::InvalidStore => "invalid store",
@@ -303,6 +311,8 @@ impl Display for Trap {
303311
}
304312
Self::NullReference => write!(f, "null reference"),
305313
Self::NullFunctionReference => write!(f, "null function reference"),
314+
Self::NullI31Reference => write!(f, "null i31 reference"),
315+
Self::CastFailure => write!(f, "cast failure"),
306316
Self::InvalidStore => write!(f, "invalid store"),
307317
#[cfg(feature = "debug")]
308318
Self::IndirectCallTypeMismatch { expected, actual } => {

crates/tinywasm/src/imports.rs

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -157,45 +157,9 @@ impl Imports {
157157
Ok(())
158158
}
159159

160-
fn ref_subtype(state: &crate::store::State, actual: RefType, expected: RefType) -> bool {
161-
if actual.is_nullable() && !expected.is_nullable() {
162-
return false;
163-
}
164-
match (actual.type_index(), expected.type_index()) {
165-
(Some(actual), Some(expected)) => actual == expected,
166-
(Some(actual), None) => match expected.abstract_heap_type() {
167-
Some(AbstractHeapType::Func) => state.get_type(actual).as_func().is_some(),
168-
Some(AbstractHeapType::Struct) => state.get_type(actual).as_struct().is_some(),
169-
Some(AbstractHeapType::Array) => state.get_type(actual).as_array().is_some(),
170-
_ => false,
171-
},
172-
(None, Some(_)) => matches!(actual.abstract_heap_type(), Some(AbstractHeapType::NoFunc)),
173-
(None, None) => {
174-
actual.abstract_heap_type() == expected.abstract_heap_type()
175-
|| actual.is_func() && matches!(expected.abstract_heap_type(), Some(AbstractHeapType::Func))
176-
|| actual.is_extern() && matches!(expected.abstract_heap_type(), Some(AbstractHeapType::Extern))
177-
|| actual.is_exn() && matches!(expected.abstract_heap_type(), Some(AbstractHeapType::Exn))
178-
}
179-
}
180-
}
181-
182-
fn value_subtype(state: &crate::store::State, actual: WasmType, expected: WasmType) -> bool {
183-
match (actual, expected) {
184-
(WasmType::Ref(actual), WasmType::Ref(expected)) => Self::ref_subtype(state, actual, expected),
185-
_ => actual == expected,
186-
}
187-
}
188-
189-
fn compare_table_types(
190-
state: &crate::store::State,
191-
import: &Import,
192-
actual: &TableType,
193-
expected: &TableType,
194-
) -> Result<()> {
160+
fn compare_table_types(import: &Import, actual: &TableType, expected: &TableType) -> Result<()> {
195161
Self::compare_types(import, &actual.arch(), &expected.arch())?;
196-
if !Self::ref_subtype(state, actual.element_type, expected.element_type)
197-
|| !Self::ref_subtype(state, expected.element_type, actual.element_type)
198-
{
162+
if actual.element_type != expected.element_type {
199163
return Err(LinkingError::incompatible_import_type(import).into());
200164
}
201165
if actual.size_initial < expected.size_initial {
@@ -285,8 +249,8 @@ impl Imports {
285249
let global = store.state.get_global(global_addr);
286250
let expected = ty.with_ty(crate::store::canonicalize_value_type(ty.ty, type_addrs));
287251
let compatible = global.ty.mutable == ty.mutable
288-
&& Self::value_subtype(&store.state, global.ty.ty, expected.ty)
289-
&& (!ty.mutable || Self::value_subtype(&store.state, expected.ty, global.ty.ty));
252+
&& store.state.value_type_is_subtype(global.ty.ty, expected.ty)
253+
&& (!ty.mutable || store.state.value_type_is_subtype(expected.ty, global.ty.ty));
290254
if !compatible {
291255
cold_path();
292256
return Err(LinkingError::incompatible_import_type(import).into());
@@ -302,7 +266,7 @@ impl Imports {
302266
MemoryArch::I32 => TableType::new(element_type, ty.size_initial, ty.size_max),
303267
MemoryArch::I64 => TableType::new64(element_type, ty.size_initial, ty.size_max),
304268
};
305-
Self::compare_table_types(&store.state, import, &kind, &expected)?;
269+
Self::compare_table_types(import, &kind, &expected)?;
306270
imports.tables.push(table_addr);
307271
}
308272
(ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => {
@@ -316,7 +280,8 @@ impl Imports {
316280
if let Some(func) = &func_handle {
317281
func.item.validate_store(store)?;
318282
}
319-
if store.state.get_func(func_addr).type_addr != *expected_type_addr {
283+
if !store.state.type_addr_is_subtype(store.state.get_func(func_addr).type_addr, *expected_type_addr)
284+
{
320285
cold_path();
321286
return Err(LinkingError::incompatible_import_type(import).into());
322287
}

0 commit comments

Comments
 (0)