Skip to content

Commit 0dcc3fa

Browse files
committed
Pass operand types in to Wasm-to-CLIF translation
This lets us generate better CLIF based on what we know of the Wasm types. There is a bunch we can do here, but this commit just lays down the initial infrastructure and plumbs the type info through to the `ref.is_null` implementation. We will now avoid actually performing null check when the input to a `ref.is_null` instruction is a non-nullable ref type.
1 parent a8e0d07 commit 0dcc3fa

File tree

11 files changed

+192
-40
lines changed

11 files changed

+192
-40
lines changed

Cargo.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,3 +610,6 @@ debug = "line-tables-only"
610610
inherits = "release"
611611
codegen-units = 1
612612
lto = true
613+
614+
[patch.crates-io]
615+
wasmparser = { path = "../wasm-tools/crates/wasmparser" }

crates/cranelift/src/func_environ.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2250,10 +2250,11 @@ impl FuncEnvironment<'_> {
22502250
pub fn translate_ref_test(
22512251
&mut self,
22522252
builder: &mut FunctionBuilder<'_>,
2253-
ref_ty: WasmRefType,
2253+
test_ty: WasmRefType,
22542254
gc_ref: ir::Value,
2255+
gc_ref_ty: WasmRefType,
22552256
) -> WasmResult<ir::Value> {
2256-
gc::translate_ref_test(self, builder, ref_ty, gc_ref)
2257+
gc::translate_ref_test(self, builder, test_ty, gc_ref, gc_ref_ty)
22572258
}
22582259

22592260
pub fn translate_ref_null(
@@ -2273,7 +2274,14 @@ impl FuncEnvironment<'_> {
22732274
&mut self,
22742275
mut pos: cranelift_codegen::cursor::FuncCursor,
22752276
value: ir::Value,
2277+
ty: WasmRefType,
22762278
) -> WasmResult<ir::Value> {
2279+
// If we know the type is not nullable, then we don't actually need to
2280+
// check for null.
2281+
if !ty.nullable {
2282+
return Ok(pos.ins().iconst(ir::types::I32, 0));
2283+
}
2284+
22772285
let byte_is_null =
22782286
pos.ins()
22792287
.icmp_imm(cranelift_codegen::ir::condcodes::IntCC::Equal, value, 0);
@@ -3121,6 +3129,7 @@ impl FuncEnvironment<'_> {
31213129
pub fn before_translate_operator(
31223130
&mut self,
31233131
op: &Operator,
3132+
_operand_types: Option<&[WasmValType]>,
31243133
builder: &mut FunctionBuilder,
31253134
state: &FuncTranslationState,
31263135
) -> WasmResult<()> {
@@ -3133,6 +3142,7 @@ impl FuncEnvironment<'_> {
31333142
pub fn after_translate_operator(
31343143
&mut self,
31353144
op: &Operator,
3145+
_operand_types: Option<&[WasmValType]>,
31363146
builder: &mut FunctionBuilder,
31373147
state: &FuncTranslationState,
31383148
) -> WasmResult<()> {

crates/cranelift/src/func_environ/gc/disabled.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,9 @@ pub fn translate_array_set(
130130
pub fn translate_ref_test(
131131
_func_env: &mut FuncEnvironment<'_>,
132132
_builder: &mut FunctionBuilder<'_>,
133-
_ref_ty: WasmRefType,
134-
_gc_ref: ir::Value,
133+
_test_ty: WasmRefType,
134+
_val: ir::Value,
135+
_val_ty: WasmRefType,
135136
) -> WasmResult<ir::Value> {
136137
disabled()
137138
}

crates/cranelift/src/func_environ/gc/enabled.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -887,17 +887,18 @@ pub fn translate_array_set(
887887
pub fn translate_ref_test(
888888
func_env: &mut FuncEnvironment<'_>,
889889
builder: &mut FunctionBuilder<'_>,
890-
ref_ty: WasmRefType,
890+
test_ty: WasmRefType,
891891
val: ir::Value,
892+
val_ty: WasmRefType,
892893
) -> WasmResult<ir::Value> {
893-
log::trace!("translate_ref_test({ref_ty:?}, {val:?})");
894+
log::trace!("translate_ref_test({test_ty:?}, {val:?})");
894895

895896
// First special case: testing for references to bottom types.
896-
if ref_ty.heap_type.is_bottom() {
897-
let result = if ref_ty.nullable {
897+
if test_ty.heap_type.is_bottom() {
898+
let result = if test_ty.nullable {
898899
// All null references (within the same type hierarchy) match null
899900
// references to the bottom type.
900-
func_env.translate_ref_is_null(builder.cursor(), val)?
901+
func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?
901902
} else {
902903
// `ref.test` is always false for non-nullable bottom types, as the
903904
// bottom types are uninhabited.
@@ -911,11 +912,11 @@ pub fn translate_ref_test(
911912
// the same type hierarchy as `heap_ty`, if `heap_ty` is its hierarchy's top
912913
// type, we only need to worry about whether we are testing for nullability
913914
// or not.
914-
if ref_ty.heap_type.is_top() {
915-
let result = if ref_ty.nullable {
915+
if test_ty.heap_type.is_top() {
916+
let result = if test_ty.nullable {
916917
builder.ins().iconst(ir::types::I32, 1)
917918
} else {
918-
let is_null = func_env.translate_ref_is_null(builder.cursor(), val)?;
919+
let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;
919920
let zero = builder.ins().iconst(ir::types::I32, 0);
920921
let one = builder.ins().iconst(ir::types::I32, 1);
921922
builder.ins().select(is_null, zero, one)
@@ -926,14 +927,14 @@ pub fn translate_ref_test(
926927

927928
// `i31ref`s are a little interesting because they don't point to GC
928929
// objects; we test the bit pattern of the reference itself.
929-
if ref_ty.heap_type == WasmHeapType::I31 {
930+
if test_ty.heap_type == WasmHeapType::I31 {
930931
let i31_mask = builder.ins().iconst(
931932
ir::types::I32,
932933
i64::from(wasmtime_environ::I31_DISCRIMINANT),
933934
);
934935
let is_i31 = builder.ins().band(val, i31_mask);
935-
let result = if ref_ty.nullable {
936-
let is_null = func_env.translate_ref_is_null(builder.cursor(), val)?;
936+
let result = if test_ty.nullable {
937+
let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;
937938
builder.ins().bor(is_null, is_i31)
938939
} else {
939940
is_i31
@@ -945,15 +946,17 @@ pub fn translate_ref_test(
945946
// Otherwise, in the general case, we need to inspect our given object's
946947
// actual type, which also requires null-checking and i31-checking it.
947948

948-
let is_any_hierarchy = ref_ty.heap_type.top() == WasmHeapTopType::Any;
949+
let is_any_hierarchy = test_ty.heap_type.top() == WasmHeapTopType::Any;
949950

950951
let non_null_block = builder.create_block();
951952
let non_null_non_i31_block = builder.create_block();
952953
let continue_block = builder.create_block();
953954

954955
// Current block: check if the reference is null and branch appropriately.
955-
let is_null = func_env.translate_ref_is_null(builder.cursor(), val)?;
956-
let result_when_is_null = builder.ins().iconst(ir::types::I32, ref_ty.nullable as i64);
956+
let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;
957+
let result_when_is_null = builder
958+
.ins()
959+
.iconst(ir::types::I32, test_ty.nullable as i64);
957960
builder.ins().brif(
958961
is_null,
959962
continue_block,
@@ -977,7 +980,7 @@ pub fn translate_ref_test(
977980
let result_when_is_i31 = builder.ins().iconst(
978981
ir::types::I32,
979982
matches!(
980-
ref_ty.heap_type,
983+
test_ty.heap_type,
981984
WasmHeapType::Any | WasmHeapType::Eq | WasmHeapType::I31
982985
) as i64,
983986
);
@@ -1030,7 +1033,7 @@ pub fn translate_ref_test(
10301033
.icmp(ir::condcodes::IntCC::Equal, and, expected_kind);
10311034
builder.ins().uextend(ir::types::I32, kind_matches)
10321035
};
1033-
let result = match ref_ty.heap_type {
1036+
let result = match test_ty.heap_type {
10341037
WasmHeapType::Any
10351038
| WasmHeapType::None
10361039
| WasmHeapType::Extern

crates/cranelift/src/translate/code_translator.rs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ use std::vec::Vec;
9494
use wasmparser::{FuncValidator, MemArg, Operator, WasmModuleResources};
9595
use wasmtime_environ::{
9696
DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, Signed, TableIndex, TypeConvert,
97-
TypeIndex, Unsigned, WasmRefType, WasmResult, wasm_unsupported,
97+
TypeIndex, Unsigned, WasmRefType, WasmResult, WasmValType, wasm_unsupported,
9898
};
9999

100100
/// Given a `Reachability<T>`, unwrap the inner `T` or, when unreachable, set
@@ -119,6 +119,7 @@ macro_rules! unwrap_or_return_unreachable_state {
119119
pub fn translate_operator(
120120
validator: &mut FuncValidator<impl WasmModuleResources>,
121121
op: &Operator,
122+
operand_types: Option<&[WasmValType]>,
122123
builder: &mut FunctionBuilder,
123124
state: &mut FuncTranslationState,
124125
environ: &mut FuncEnvironment<'_>,
@@ -128,6 +129,10 @@ pub fn translate_operator(
128129
return Ok(());
129130
}
130131

132+
let operand_types = operand_types.unwrap_or_else(|| {
133+
panic!("should always have operand types available for valid, reachable ops; op = {op:?}")
134+
});
135+
131136
// Given that we believe the current block is reachable, the FunctionBuilder ought to agree.
132137
debug_assert!(!builder.is_unreachable());
133138

@@ -1258,7 +1263,10 @@ pub fn translate_operator(
12581263
}
12591264
Operator::RefIsNull => {
12601265
let value = state.pop1();
1261-
state.push1(environ.translate_ref_is_null(builder.cursor(), value)?);
1266+
let [WasmValType::Ref(ty)] = operand_types else {
1267+
unreachable!("validation")
1268+
};
1269+
state.push1(environ.translate_ref_is_null(builder.cursor(), value, *ty)?);
12621270
}
12631271
Operator::RefFunc { function_index } => {
12641272
let index = FuncIndex::from_u32(*function_index);
@@ -2448,8 +2456,11 @@ pub fn translate_operator(
24482456

24492457
Operator::BrOnNull { relative_depth } => {
24502458
let r = state.pop1();
2459+
let [.., WasmValType::Ref(r_ty)] = operand_types else {
2460+
unreachable!("validation")
2461+
};
24512462
let (br_destination, inputs) = translate_br_if_args(*relative_depth, state);
2452-
let is_null = environ.translate_ref_is_null(builder.cursor(), r)?;
2463+
let is_null = environ.translate_ref_is_null(builder.cursor(), r, *r_ty)?;
24532464
let else_block = builder.create_block();
24542465
canonicalise_brif(builder, is_null, br_destination, inputs, else_block, &[]);
24552466

@@ -2464,7 +2475,11 @@ pub fn translate_operator(
24642475
// Peek the value val from the stack.
24652476
// If val is ref.null ht, then: pop the value val from the stack.
24662477
// Else: Execute the instruction (br relative_depth).
2467-
let is_null = environ.translate_ref_is_null(builder.cursor(), state.peek1())?;
2478+
let r = state.peek1();
2479+
let [.., WasmValType::Ref(r_ty)] = operand_types else {
2480+
unreachable!("validation")
2481+
};
2482+
let is_null = environ.translate_ref_is_null(builder.cursor(), r, *r_ty)?;
24682483
let (br_destination, inputs) = translate_br_if_args(*relative_depth, state);
24692484
let else_block = builder.create_block();
24702485
canonicalise_brif(builder, is_null, else_block, &[], br_destination, inputs);
@@ -2503,7 +2518,10 @@ pub fn translate_operator(
25032518
}
25042519
Operator::RefAsNonNull => {
25052520
let r = state.pop1();
2506-
let is_null = environ.translate_ref_is_null(builder.cursor(), r)?;
2521+
let [.., WasmValType::Ref(r_ty)] = operand_types else {
2522+
unreachable!("validation")
2523+
};
2524+
let is_null = environ.translate_ref_is_null(builder.cursor(), r, *r_ty)?;
25072525
environ.trapnz(builder, is_null, crate::TRAP_NULL_REFERENCE);
25082526
state.push1(r);
25092527
}
@@ -2770,6 +2788,9 @@ pub fn translate_operator(
27702788
}
27712789
Operator::RefTestNonNull { hty } => {
27722790
let r = state.pop1();
2791+
let [.., WasmValType::Ref(r_ty)] = operand_types else {
2792+
unreachable!("validation")
2793+
};
27732794
let heap_type = environ.convert_heap_type(*hty)?;
27742795
let result = environ.translate_ref_test(
27752796
builder,
@@ -2778,11 +2799,15 @@ pub fn translate_operator(
27782799
nullable: false,
27792800
},
27802801
r,
2802+
*r_ty,
27812803
)?;
27822804
state.push1(result);
27832805
}
27842806
Operator::RefTestNullable { hty } => {
27852807
let r = state.pop1();
2808+
let [.., WasmValType::Ref(r_ty)] = operand_types else {
2809+
unreachable!("validation")
2810+
};
27862811
let heap_type = environ.convert_heap_type(*hty)?;
27872812
let result = environ.translate_ref_test(
27882813
builder,
@@ -2791,11 +2816,15 @@ pub fn translate_operator(
27912816
nullable: true,
27922817
},
27932818
r,
2819+
*r_ty,
27942820
)?;
27952821
state.push1(result);
27962822
}
27972823
Operator::RefCastNonNull { hty } => {
27982824
let r = state.pop1();
2825+
let [.., WasmValType::Ref(r_ty)] = operand_types else {
2826+
unreachable!("validation")
2827+
};
27992828
let heap_type = environ.convert_heap_type(*hty)?;
28002829
let cast_okay = environ.translate_ref_test(
28012830
builder,
@@ -2804,12 +2833,16 @@ pub fn translate_operator(
28042833
nullable: false,
28052834
},
28062835
r,
2836+
*r_ty,
28072837
)?;
28082838
environ.trapz(builder, cast_okay, crate::TRAP_CAST_FAILURE);
28092839
state.push1(r);
28102840
}
28112841
Operator::RefCastNullable { hty } => {
28122842
let r = state.pop1();
2843+
let [.., WasmValType::Ref(r_ty)] = operand_types else {
2844+
unreachable!("validation")
2845+
};
28132846
let heap_type = environ.convert_heap_type(*hty)?;
28142847
let cast_okay = environ.translate_ref_test(
28152848
builder,
@@ -2818,6 +2851,7 @@ pub fn translate_operator(
28182851
nullable: true,
28192852
},
28202853
r,
2854+
*r_ty,
28212855
)?;
28222856
environ.trapz(builder, cast_okay, crate::TRAP_CAST_FAILURE);
28232857
state.push1(r);
@@ -2830,9 +2864,12 @@ pub fn translate_operator(
28302864
from_ref_type: _,
28312865
} => {
28322866
let r = state.peek1();
2867+
let [.., WasmValType::Ref(r_ty)] = operand_types else {
2868+
unreachable!("validation")
2869+
};
28332870

28342871
let to_ref_type = environ.convert_ref_type(*to_ref_type)?;
2835-
let cast_is_okay = environ.translate_ref_test(builder, to_ref_type, r)?;
2872+
let cast_is_okay = environ.translate_ref_test(builder, to_ref_type, r, *r_ty)?;
28362873

28372874
let (cast_succeeds_block, inputs) = translate_br_if_args(*relative_depth, state);
28382875
let cast_fails_block = builder.create_block();
@@ -2863,9 +2900,12 @@ pub fn translate_operator(
28632900
from_ref_type: _,
28642901
} => {
28652902
let r = state.peek1();
2903+
let [.., WasmValType::Ref(r_ty)] = operand_types else {
2904+
unreachable!("validation")
2905+
};
28662906

28672907
let to_ref_type = environ.convert_ref_type(*to_ref_type)?;
2868-
let cast_is_okay = environ.translate_ref_test(builder, to_ref_type, r)?;
2908+
let cast_is_okay = environ.translate_ref_test(builder, to_ref_type, r, *r_ty)?;
28692909

28702910
let (cast_fails_block, inputs) = translate_br_if_args(*relative_depth, state);
28712911
let cast_succeeds_block = builder.create_block();

crates/cranelift/src/translate/func_translator.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,15 +241,34 @@ fn parse_function_body(
241241
debug_assert_eq!(state.control_stack.len(), 1, "State not initialized");
242242

243243
environ.before_translate_function(builder, state)?;
244+
244245
let mut reader = OperatorsReader::new(reader);
246+
let mut operand_types = vec![];
247+
245248
while !reader.eof() {
246249
let pos = reader.original_position();
247250
builder.set_srcloc(cur_srcloc(&reader.get_binary_reader()));
251+
248252
let op = reader.read()?;
253+
254+
// Get the operand types for this operator.
255+
let arity = op.operator_arity(&*validator);
256+
operand_types.clear();
257+
let operand_types = arity.and_then(|(operand_arity, _result_arity)| {
258+
for i in (0..operand_arity).rev() {
259+
let i = usize::try_from(i).unwrap();
260+
let ty = validator.get_operand_type(i)??;
261+
let ty = environ.convert_valtype(ty).ok()?;
262+
operand_types.push(ty);
263+
}
264+
Some(&operand_types[..])
265+
});
266+
249267
validator.op(pos, &op)?;
250-
environ.before_translate_operator(&op, builder, state)?;
251-
translate_operator(validator, &op, builder, state, environ)?;
252-
environ.after_translate_operator(&op, builder, state)?;
268+
269+
environ.before_translate_operator(&op, operand_types, builder, state)?;
270+
translate_operator(validator, &op, operand_types, builder, state, environ)?;
271+
environ.after_translate_operator(&op, operand_types, builder, state)?;
253272
}
254273
environ.after_translate_function(builder, state)?;
255274
reader.finish()?;

0 commit comments

Comments
 (0)