Skip to content

Commit

Permalink
Implement the ref.test Wasm GC instruction (#9401)
Browse files Browse the repository at this point in the history
* Implement the `ref.test` Wasm GC instruction

This commit implements the `ref.test` instruction, which tests whether a
reference is of a given type.

We implement inline fast paths for abstract types, but currently rely on an
out-of-line libcall for concrete types in the general case. This is known to be
suboptimal. (FWIW, we also emit a fast path in front of the libcall where we
first check the actual type and expected type for equality and skip the libcall
if they are equal.)

This implementation is expected to be improved in the future by exposing a
module's types' supertypes arrays to Wasm, so that the Wasm can do the O(1)
subtype checks inline. This will make the vast majority of all `ref.test`s
inlinable. After that, the only remaining case that would require out-of-line
libcalls would be when a module is given an instance of a type that it did not
itself define (but which could be a subtype of a type it defined, for example,
and which itself might not even have been defined until after this module's
instance was created!)

* fix no-gc build

* Re-add should-fail test, due to instructions that still aren't implemented

* check for the `any` type hierarchy via `.top()`

* Add a couple more small comments

* rename internal ref.test test to avoid should-pass vs should-fail confusion between similarly named spec test
  • Loading branch information
fitzgen authored Oct 8, 2024
1 parent 72ded10 commit bc6b0fe
Show file tree
Hide file tree
Showing 20 changed files with 1,494 additions and 76 deletions.
103 changes: 72 additions & 31 deletions crates/cranelift/src/func_environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ use std::mem;
use wasmparser::Operator;
use wasmtime_environ::{
BuiltinFunctionIndex, DataIndex, ElemIndex, EngineOrModuleTypeIndex, FuncIndex, GlobalIndex,
IndexType, Memory, MemoryIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation,
ModuleTypesBuilder, PtrSize, Table, TableIndex, TableStyle, Tunables, TypeConvert, TypeIndex,
VMOffsets, WasmCompositeType, WasmFuncType, WasmHeapTopType, WasmHeapType, WasmResult,
WasmValType,
IndexType, Memory, MemoryIndex, MemoryPlan, MemoryStyle, Module, ModuleInternedTypeIndex,
ModuleTranslation, ModuleTypesBuilder, PtrSize, Table, TableIndex, TableStyle, Tunables,
TypeConvert, TypeIndex, VMOffsets, WasmCompositeType, WasmFuncType, WasmHeapTopType,
WasmHeapType, WasmRefType, WasmResult, WasmValType,
};
use wasmtime_environ::{FUNCREF_INIT_BIT, FUNCREF_MASK};

Expand Down Expand Up @@ -1129,6 +1129,57 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
let call = builder.ins().call(libcall, &[vmctx, val]);
*builder.func.dfg.inst_results(call).first().unwrap()
}

fn vmshared_type_index_ty(&self) -> Type {
Type::int_with_byte_size(self.offsets.size_of_vmshared_type_index().into()).unwrap()
}

/// Given a `ModuleInternedTypeIndex`, emit code to get the corresponding
/// `VMSharedTypeIndex` at runtime.
pub(crate) fn module_interned_to_shared_ty(
&mut self,
pos: &mut FuncCursor,
interned_ty: ModuleInternedTypeIndex,
) -> ir::Value {
let vmctx = self.vmctx_val(pos);
let pointer_type = self.pointer_type();
let mem_flags = ir::MemFlags::trusted().with_readonly();

// Load the base pointer of the array of `VMSharedTypeIndex`es.
let shared_indices = pos.ins().load(
pointer_type,
mem_flags,
vmctx,
i32::from(self.offsets.ptr.vmctx_type_ids_array()),
);

// Calculate the offset in that array for this type's entry.
let ty = self.vmshared_type_index_ty();
let offset = i32::try_from(interned_ty.as_u32().checked_mul(ty.bytes()).unwrap()).unwrap();

// Load the`VMSharedTypeIndex` that this `ModuleInternedTypeIndex` is
// associated with at runtime from the array.
pos.ins().load(ty, mem_flags, shared_indices, offset)
}

/// Load the associated `VMSharedTypeIndex` from inside a `*const VMFuncRef`.
///
/// Does not check for null; just assumes that the `funcref` is a valid
/// pointer.
pub(crate) fn load_funcref_type_index(
&mut self,
pos: &mut FuncCursor,
mem_flags: ir::MemFlags,
funcref: ir::Value,
) -> ir::Value {
let ty = self.vmshared_type_index_ty();
pos.ins().load(
ty,
mem_flags,
funcref,
i32::from(self.offsets.ptr.vm_func_ref_type_index()),
)
}
}

struct Call<'a, 'func, 'module_env> {
Expand Down Expand Up @@ -1320,7 +1371,6 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
ty_index: TypeIndex,
funcref_ptr: ir::Value,
) -> CheckIndirectCallTypeSignature {
let pointer_type = self.env.pointer_type();
let table = &self.env.module.table_plans[table_index];
let sig_id_size = self.env.offsets.size_of_vmshared_type_index();
let sig_id_type = Type::int(u16::from(sig_id_size) * 8).unwrap();
Expand Down Expand Up @@ -1409,28 +1459,13 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
}
}

let vmctx = self.env.vmctx(self.builder.func);
let base = self.builder.ins().global_value(pointer_type, vmctx);

// Load the caller ID. This requires loading the `*mut VMFuncRef` base
// pointer from `VMContext` and then loading, based on `SignatureIndex`,
// the corresponding entry.
let mem_flags = ir::MemFlags::trusted().with_readonly();
let signatures = self.builder.ins().load(
pointer_type,
mem_flags,
base,
i32::from(self.env.offsets.ptr.vmctx_type_ids_array()),
);
let sig_index = self.env.module.types[ty_index];
let offset =
i32::try_from(sig_index.as_u32().checked_mul(sig_id_type.bytes()).unwrap()).unwrap();
// Load the caller's `VMSharedTypeIndex.
let interned_ty = self.env.module.types[ty_index];
let caller_sig_id = self
.builder
.ins()
.load(sig_id_type, mem_flags, signatures, offset);
.env
.module_interned_to_shared_ty(&mut self.builder.cursor(), interned_ty);

// Load the callee ID.
// Load the callee's `VMSharedTypeIndex`.
//
// Note that the callee may be null in which case this load may
// trap. If so use the `TRAP_INDIRECT_CALL_TO_NULL` trap code.
Expand All @@ -1441,12 +1476,9 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
self.env
.trapz(self.builder, funcref_ptr, crate::TRAP_INDIRECT_CALL_TO_NULL);
}
let callee_sig_id = self.builder.ins().load(
sig_id_type,
mem_flags,
funcref_ptr,
i32::from(self.env.offsets.ptr.vm_func_ref_type_index()),
);
let callee_sig_id =
self.env
.load_funcref_type_index(&mut self.builder.cursor(), mem_flags, funcref_ptr);

// Check that they match.
let cmp = self
Expand Down Expand Up @@ -2162,6 +2194,15 @@ impl<'module_environment> crate::translate::FuncEnvironment
gc::translate_array_set(self, builder, array_type_index, array, index, value)
}

fn translate_ref_test(
&mut self,
builder: &mut FunctionBuilder<'_>,
ref_ty: WasmRefType,
gc_ref: ir::Value,
) -> WasmResult<ir::Value> {
gc::translate_ref_test(self, builder, ref_ty, gc_ref)
}

fn translate_ref_null(
&mut self,
mut pos: cranelift_codegen::cursor::FuncCursor,
Expand Down
11 changes: 10 additions & 1 deletion crates/cranelift/src/gc/disabled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::GcCompiler;
use crate::func_environ::FuncEnvironment;
use cranelift_codegen::ir;
use cranelift_frontend::FunctionBuilder;
use wasmtime_environ::{wasm_unsupported, TypeIndex, WasmResult};
use wasmtime_environ::{wasm_unsupported, TypeIndex, WasmRefType, WasmResult};

fn disabled<T>() -> WasmResult<T> {
Err(wasm_unsupported!(
Expand Down Expand Up @@ -164,3 +164,12 @@ pub fn translate_array_set(
) -> WasmResult<()> {
disabled()
}

pub fn translate_ref_test(
_func_env: &mut FuncEnvironment<'_>,
_builder: &mut FunctionBuilder<'_>,
_ref_ty: WasmRefType,
_gc_ref: ir::Value,
) -> WasmResult<ir::Value> {
disabled()
}
Loading

0 comments on commit bc6b0fe

Please sign in to comment.