Path: blob/main/crates/cranelift/src/func_environ/gc/enabled.rs
3090 views
use super::{ArrayInit, GcCompiler};1use crate::bounds_checks::BoundsCheck;2use crate::func_environ::{Extension, FuncEnvironment};3use crate::translate::{Heap, HeapData, StructFieldsVec, TargetEnvironment};4use crate::{Reachability, TRAP_INTERNAL_ASSERT};5use cranelift_codegen::ir::immediates::Offset32;6use cranelift_codegen::ir::{BlockArg, ExceptionTableData, ExceptionTableItem};7use cranelift_codegen::{8cursor::FuncCursor,9ir::{self, InstBuilder, condcodes::IntCC},10};11use cranelift_entity::packed_option::ReservedValue;12use cranelift_frontend::FunctionBuilder;13use smallvec::{SmallVec, smallvec};14use wasmtime_environ::{15Collector, GcArrayLayout, GcLayout, GcStructLayout, I31_DISCRIMINANT, ModuleInternedTypeIndex,16PtrSize, TagIndex, TypeIndex, VMGcKind, WasmCompositeInnerType, WasmHeapTopType, WasmHeapType,17WasmRefType, WasmResult, WasmStorageType, WasmValType, wasm_unsupported,18};1920#[cfg(feature = "gc-drc")]21mod drc;22#[cfg(feature = "gc-null")]23mod null;2425/// Get the default GC compiler.26pub fn gc_compiler(func_env: &mut FuncEnvironment<'_>) -> WasmResult<Box<dyn GcCompiler>> {27// If this function requires a GC compiler, that is not too bad of an28// over-approximation for it requiring a GC heap.29func_env.needs_gc_heap = true;3031match func_env.tunables.collector {32#[cfg(feature = "gc-drc")]33Some(Collector::DeferredReferenceCounting) => Ok(Box::new(drc::DrcCompiler::default())),34#[cfg(not(feature = "gc-drc"))]35Some(Collector::DeferredReferenceCounting) => Err(wasm_unsupported!(36"the DRC collector is unavailable because the `gc-drc` feature \37was disabled at compile time",38)),3940#[cfg(feature = "gc-null")]41Some(Collector::Null) => Ok(Box::new(null::NullCompiler::default())),42#[cfg(not(feature = "gc-null"))]43Some(Collector::Null) => Err(wasm_unsupported!(44"the null collector is unavailable because the `gc-null` feature \45was disabled at compile time",46)),4748#[cfg(any(feature = "gc-drc", feature = "gc-null"))]49None => Err(wasm_unsupported!(50"support for GC types disabled at configuration time"51)),52#[cfg(not(any(feature = "gc-drc", feature = "gc-null")))]53None => Err(wasm_unsupported!(54"support for GC types disabled because no collector implementation \55was selected at compile time; enable one of the `gc-drc` or \56`gc-null` features",57)),58}59}6061#[cfg_attr(62not(feature = "gc-drc"),63expect(dead_code, reason = "easier to define")64)]65fn unbarriered_load_gc_ref(66builder: &mut FunctionBuilder,67ty: WasmHeapType,68ptr_to_gc_ref: ir::Value,69flags: ir::MemFlags,70) -> WasmResult<ir::Value> {71debug_assert!(ty.is_vmgcref_type());72let gc_ref = builder.ins().load(ir::types::I32, flags, ptr_to_gc_ref, 0);73if ty != WasmHeapType::I31 {74builder.declare_value_needs_stack_map(gc_ref);75}76Ok(gc_ref)77}7879#[cfg_attr(80not(any(feature = "gc-drc", feature = "gc-null")),81expect(dead_code, reason = "easier to define")82)]83fn unbarriered_store_gc_ref(84builder: &mut FunctionBuilder,85ty: WasmHeapType,86dst: ir::Value,87gc_ref: ir::Value,88flags: ir::MemFlags,89) -> WasmResult<()> {90debug_assert!(ty.is_vmgcref_type());91builder.ins().store(flags, gc_ref, dst, 0);92Ok(())93}9495/// Emit code to read a struct field or array element from its raw address in96/// the GC heap.97///98/// The given address MUST have already been bounds-checked via99/// `prepare_gc_ref_access`.100fn read_field_at_addr(101func_env: &mut FuncEnvironment<'_>,102builder: &mut FunctionBuilder<'_>,103ty: WasmStorageType,104addr: ir::Value,105extension: Option<Extension>,106) -> WasmResult<ir::Value> {107assert_eq!(extension.is_none(), matches!(ty, WasmStorageType::Val(_)));108assert_eq!(109extension.is_some(),110matches!(ty, WasmStorageType::I8 | WasmStorageType::I16)111);112113// Data inside GC objects is always little endian.114let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);115116let value = match ty {117WasmStorageType::I8 => builder.ins().load(ir::types::I8, flags, addr, 0),118WasmStorageType::I16 => builder.ins().load(ir::types::I16, flags, addr, 0),119WasmStorageType::Val(v) => match v {120WasmValType::I32 => builder.ins().load(ir::types::I32, flags, addr, 0),121WasmValType::I64 => builder.ins().load(ir::types::I64, flags, addr, 0),122WasmValType::F32 => builder.ins().load(ir::types::F32, flags, addr, 0),123WasmValType::F64 => builder.ins().load(ir::types::F64, flags, addr, 0),124WasmValType::V128 => builder.ins().load(ir::types::I8X16, flags, addr, 0),125WasmValType::Ref(r) => match r.heap_type.top() {126WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => {127gc_compiler(func_env)?128.translate_read_gc_reference(func_env, builder, r, addr, flags)?129}130WasmHeapTopType::Func => {131let expected_ty = match r.heap_type {132WasmHeapType::Func => ModuleInternedTypeIndex::reserved_value(),133WasmHeapType::ConcreteFunc(ty) => ty.unwrap_module_type_index(),134WasmHeapType::NoFunc => {135let null = builder.ins().iconst(func_env.pointer_type(), 0);136if !r.nullable {137// Because `nofunc` is uninhabited, and this138// reference is non-null, this is unreachable139// code. Unconditionally trap via conditional140// trap instructions to avoid inserting block141// terminators in the middle of this block.142builder.ins().trapz(null, TRAP_INTERNAL_ASSERT);143}144return Ok(null);145}146_ => unreachable!("not a function heap type"),147};148let expected_ty = builder149.ins()150.iconst(ir::types::I32, i64::from(expected_ty.as_bits()));151152let vmctx = func_env.vmctx_val(&mut builder.cursor());153154let func_ref_id = builder.ins().load(ir::types::I32, flags, addr, 0);155let get_interned_func_ref = func_env156.builtin_functions157.get_interned_func_ref(builder.func);158159let call_inst = builder160.ins()161.call(get_interned_func_ref, &[vmctx, func_ref_id, expected_ty]);162builder.func.dfg.first_result(call_inst)163}164WasmHeapTopType::Cont => {165// TODO(#10248) GC integration for stack switching166return Err(wasmtime_environ::WasmError::Unsupported(167"Stack switching feature not compatible with GC, yet".to_string(),168));169}170},171},172};173174let value = match extension {175Some(Extension::Sign) => builder.ins().sextend(ir::types::I32, value),176Some(Extension::Zero) => builder.ins().uextend(ir::types::I32, value),177None => value,178};179180Ok(value)181}182183fn write_func_ref_at_addr(184func_env: &mut FuncEnvironment<'_>,185builder: &mut FunctionBuilder<'_>,186ref_type: WasmRefType,187flags: ir::MemFlags,188field_addr: ir::Value,189func_ref: ir::Value,190) -> WasmResult<()> {191assert_eq!(ref_type.heap_type.top(), WasmHeapTopType::Func);192193let vmctx = func_env.vmctx_val(&mut builder.cursor());194195let intern_func_ref_for_gc_heap = func_env196.builtin_functions197.intern_func_ref_for_gc_heap(builder.func);198199let func_ref = if ref_type.heap_type == WasmHeapType::NoFunc {200let null = builder.ins().iconst(func_env.pointer_type(), 0);201if !ref_type.nullable {202// Because `nofunc` is uninhabited, and this reference is203// non-null, this is unreachable code. Unconditionally trap204// via conditional trap instructions to avoid inserting205// block terminators in the middle of this block.206builder.ins().trapz(null, TRAP_INTERNAL_ASSERT);207}208null209} else {210func_ref211};212213// Convert the raw `funcref` into a `FuncRefTableId` for use in the214// GC heap.215let call_inst = builder216.ins()217.call(intern_func_ref_for_gc_heap, &[vmctx, func_ref]);218let func_ref_id = builder.func.dfg.first_result(call_inst);219let func_ref_id = builder.ins().ireduce(ir::types::I32, func_ref_id);220221// Store the id in the field.222builder.ins().store(flags, func_ref_id, field_addr, 0);223224Ok(())225}226227fn write_field_at_addr(228func_env: &mut FuncEnvironment<'_>,229builder: &mut FunctionBuilder<'_>,230field_ty: WasmStorageType,231field_addr: ir::Value,232new_val: ir::Value,233) -> WasmResult<()> {234// Data inside GC objects is always little endian.235let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);236237match field_ty {238WasmStorageType::I8 => {239builder.ins().istore8(flags, new_val, field_addr, 0);240}241WasmStorageType::I16 => {242builder.ins().istore16(flags, new_val, field_addr, 0);243}244WasmStorageType::Val(WasmValType::Ref(r)) if r.heap_type.top() == WasmHeapTopType::Func => {245write_func_ref_at_addr(func_env, builder, r, flags, field_addr, new_val)?;246}247WasmStorageType::Val(WasmValType::Ref(r)) => {248gc_compiler(func_env)?249.translate_write_gc_reference(func_env, builder, r, field_addr, new_val, flags)?;250}251WasmStorageType::Val(_) => {252assert_eq!(253builder.func.dfg.value_type(new_val).bytes(),254wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty)255);256builder.ins().store(flags, new_val, field_addr, 0);257}258}259Ok(())260}261262pub fn translate_struct_new(263func_env: &mut FuncEnvironment<'_>,264builder: &mut FunctionBuilder<'_>,265struct_type_index: TypeIndex,266fields: &[ir::Value],267) -> WasmResult<ir::Value> {268gc_compiler(func_env)?.alloc_struct(func_env, builder, struct_type_index, &fields)269}270271fn default_value(272cursor: &mut FuncCursor,273func_env: &FuncEnvironment<'_>,274ty: &WasmStorageType,275) -> ir::Value {276match ty {277WasmStorageType::I8 | WasmStorageType::I16 => cursor.ins().iconst(ir::types::I32, 0),278WasmStorageType::Val(v) => match v {279WasmValType::I32 => cursor.ins().iconst(ir::types::I32, 0),280WasmValType::I64 => cursor.ins().iconst(ir::types::I64, 0),281WasmValType::F32 => cursor.ins().f32const(0.0),282WasmValType::F64 => cursor.ins().f64const(0.0),283WasmValType::V128 => {284let c = cursor.func.dfg.constants.insert(vec![0; 16].into());285cursor.ins().vconst(ir::types::I8X16, c)286}287WasmValType::Ref(r) => {288assert!(r.nullable);289let (ty, needs_stack_map) = func_env.reference_type(r.heap_type);290291// NB: The collector doesn't need to know about null references.292let _ = needs_stack_map;293294cursor.ins().iconst(ty, 0)295}296},297}298}299300pub fn translate_struct_new_default(301func_env: &mut FuncEnvironment<'_>,302builder: &mut FunctionBuilder<'_>,303struct_type_index: TypeIndex,304) -> WasmResult<ir::Value> {305let interned_ty = func_env.module.types[struct_type_index].unwrap_module_type_index();306let struct_ty = func_env.types.unwrap_struct(interned_ty)?;307let fields = struct_ty308.fields309.iter()310.map(|f| default_value(&mut builder.cursor(), func_env, &f.element_type))311.collect::<StructFieldsVec>();312gc_compiler(func_env)?.alloc_struct(func_env, builder, struct_type_index, &fields)313}314315pub fn translate_struct_get(316func_env: &mut FuncEnvironment<'_>,317builder: &mut FunctionBuilder<'_>,318struct_type_index: TypeIndex,319field_index: u32,320struct_ref: ir::Value,321extension: Option<Extension>,322) -> WasmResult<ir::Value> {323log::trace!(324"translate_struct_get({struct_type_index:?}, {field_index:?}, {struct_ref:?}, {extension:?})"325);326327// TODO: If we know we have a `(ref $my_struct)` here, instead of maybe a328// `(ref null $my_struct)`, we could omit the `trapz`. But plumbing that329// type info from `wasmparser` and through to here is a bit funky.330func_env.trapz(builder, struct_ref, crate::TRAP_NULL_REFERENCE);331332let field_index = usize::try_from(field_index).unwrap();333let interned_type_index = func_env.module.types[struct_type_index].unwrap_module_type_index();334335let struct_layout = func_env.struct_or_exn_layout(interned_type_index);336let struct_size = struct_layout.size;337338let field_offset = struct_layout.fields[field_index].offset;339let field_ty = &func_env.types.unwrap_struct(interned_type_index)?.fields[field_index];340let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty.element_type);341assert!(field_offset + field_size <= struct_size);342343let field_addr = func_env.prepare_gc_ref_access(344builder,345struct_ref,346BoundsCheck::StaticObjectField {347offset: field_offset,348access_size: u8::try_from(field_size).unwrap(),349object_size: struct_size,350},351);352353let result = read_field_at_addr(354func_env,355builder,356field_ty.element_type,357field_addr,358extension,359);360log::trace!("translate_struct_get(..) -> {result:?}");361result362}363364pub fn translate_struct_set(365func_env: &mut FuncEnvironment<'_>,366builder: &mut FunctionBuilder<'_>,367struct_type_index: TypeIndex,368field_index: u32,369struct_ref: ir::Value,370new_val: ir::Value,371) -> WasmResult<()> {372log::trace!(373"translate_struct_set({struct_type_index:?}, {field_index:?}, struct_ref: {struct_ref:?}, new_val: {new_val:?})"374);375376// TODO: See comment in `translate_struct_get` about the `trapz`.377func_env.trapz(builder, struct_ref, crate::TRAP_NULL_REFERENCE);378379let field_index = usize::try_from(field_index).unwrap();380let interned_type_index = func_env.module.types[struct_type_index].unwrap_module_type_index();381382let struct_layout = func_env.struct_or_exn_layout(interned_type_index);383let struct_size = struct_layout.size;384385let field_offset = struct_layout.fields[field_index].offset;386let field_ty = &func_env.types.unwrap_struct(interned_type_index)?.fields[field_index];387let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty.element_type);388assert!(field_offset + field_size <= struct_size);389390let field_addr = func_env.prepare_gc_ref_access(391builder,392struct_ref,393BoundsCheck::StaticObjectField {394offset: field_offset,395access_size: u8::try_from(field_size).unwrap(),396object_size: struct_size,397},398);399400write_field_at_addr(401func_env,402builder,403field_ty.element_type,404field_addr,405new_val,406)?;407408log::trace!("translate_struct_set: finished");409Ok(())410}411412pub fn translate_exn_unbox(413func_env: &mut FuncEnvironment<'_>,414builder: &mut FunctionBuilder<'_>,415tag_index: TagIndex,416exn_ref: ir::Value,417) -> WasmResult<SmallVec<[ir::Value; 4]>> {418log::trace!("translate_exn_unbox({tag_index:?}, {exn_ref:?})");419420// We know that the `exn_ref` is not null because we reach this421// operation only in catch blocks, and throws are initiated from422// runtime code that checks for nulls first.423424// Get the GcExceptionLayout associated with this tag's425// function type, and generate loads for each field.426let exception_ty_idx = func_env427.exception_type_from_tag(tag_index)428.unwrap_module_type_index();429let exception_ty = func_env.types.unwrap_exn(exception_ty_idx)?;430let exn_layout = func_env.struct_or_exn_layout(exception_ty_idx);431let exn_size = exn_layout.size;432433// Gather accesses first because these require a borrow on434// `func_env`, which we later mutate below via435// `prepare_gc_ref_access()`.436let mut accesses: SmallVec<[_; 4]> = smallvec![];437for (field_ty, field_layout) in exception_ty.fields.iter().zip(exn_layout.fields.iter()) {438accesses.push((field_layout.offset, field_ty.element_type));439}440441let mut result = smallvec![];442for (field_offset, field_ty) in accesses {443let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty);444assert!(field_offset + field_size <= exn_size);445let field_addr = func_env.prepare_gc_ref_access(446builder,447exn_ref,448BoundsCheck::StaticObjectField {449offset: field_offset,450access_size: u8::try_from(field_size).unwrap(),451object_size: exn_size,452},453);454455let value = read_field_at_addr(func_env, builder, field_ty, field_addr, None)?;456result.push(value);457}458459log::trace!("translate_exn_unbox(..) -> {result:?}");460Ok(result)461}462463pub fn translate_exn_throw(464func_env: &mut FuncEnvironment<'_>,465builder: &mut FunctionBuilder<'_>,466tag_index: TagIndex,467args: &[ir::Value],468) -> WasmResult<()> {469let (instance_id, defined_tag_id) = func_env.get_instance_and_tag(builder, tag_index);470let exnref = gc_compiler(func_env)?.alloc_exn(471func_env,472builder,473tag_index,474args,475instance_id,476defined_tag_id,477)?;478translate_exn_throw_ref(func_env, builder, exnref)479}480481pub fn translate_exn_throw_ref(482func_env: &mut FuncEnvironment<'_>,483builder: &mut FunctionBuilder<'_>,484exnref: ir::Value,485) -> WasmResult<()> {486let builtin = func_env.builtin_functions.throw_ref(builder.func);487let sig = builder.func.dfg.ext_funcs[builtin].signature;488let vmctx = func_env.vmctx_val(&mut builder.cursor());489490// Generate a `try_call` with handlers from the current491// stack. This libcall is unique among libcall implementations of492// opcodes: we know the others will not throw, but `throw_ref`'s493// entire purpose is to throw. So if there are any handlers in the494// local function body, we need to attach them to this callsite495// like any other.496let continuation = builder.create_block();497let current_block = builder.current_block().unwrap();498builder.insert_block_after(continuation, current_block);499let continuation_call = builder.func.dfg.block_call(continuation, &[]);500let mut table_items = vec![ExceptionTableItem::Context(vmctx)];501for (tag, block) in func_env.stacks.handlers.handlers() {502let block_call = builder503.func504.dfg505.block_call(block, &[BlockArg::TryCallExn(0)]);506table_items.push(match tag {507Some(tag) => ExceptionTableItem::Tag(tag, block_call),508None => ExceptionTableItem::Default(block_call),509});510}511let etd = ExceptionTableData::new(sig, continuation_call, table_items);512let et = builder.func.dfg.exception_tables.push(etd);513514builder.ins().try_call(builtin, &[vmctx, exnref], et);515516builder.switch_to_block(continuation);517builder.seal_block(continuation);518func_env.trap(builder, crate::TRAP_UNREACHABLE);519520Ok(())521}522523pub fn translate_array_new(524func_env: &mut FuncEnvironment<'_>,525builder: &mut FunctionBuilder,526array_type_index: TypeIndex,527elem: ir::Value,528len: ir::Value,529) -> WasmResult<ir::Value> {530log::trace!("translate_array_new({array_type_index:?}, {elem:?}, {len:?})");531let result = gc_compiler(func_env)?.alloc_array(532func_env,533builder,534array_type_index,535ArrayInit::Fill { elem, len },536)?;537log::trace!("translate_array_new(..) -> {result:?}");538Ok(result)539}540541pub fn translate_array_new_default(542func_env: &mut FuncEnvironment<'_>,543builder: &mut FunctionBuilder,544array_type_index: TypeIndex,545len: ir::Value,546) -> WasmResult<ir::Value> {547log::trace!("translate_array_new_default({array_type_index:?}, {len:?})");548549let interned_ty = func_env.module.types[array_type_index].unwrap_module_type_index();550let array_ty = func_env.types.unwrap_array(interned_ty)?;551let elem = default_value(&mut builder.cursor(), func_env, &array_ty.0.element_type);552let result = gc_compiler(func_env)?.alloc_array(553func_env,554builder,555array_type_index,556ArrayInit::Fill { elem, len },557)?;558log::trace!("translate_array_new_default(..) -> {result:?}");559Ok(result)560}561562pub fn translate_array_new_fixed(563func_env: &mut FuncEnvironment<'_>,564builder: &mut FunctionBuilder,565array_type_index: TypeIndex,566elems: &[ir::Value],567) -> WasmResult<ir::Value> {568log::trace!("translate_array_new_fixed({array_type_index:?}, {elems:?})");569let result = gc_compiler(func_env)?.alloc_array(570func_env,571builder,572array_type_index,573ArrayInit::Elems(elems),574)?;575log::trace!("translate_array_new_fixed(..) -> {result:?}");576Ok(result)577}578579impl ArrayInit<'_> {580/// Get the length (as an `i32`-typed `ir::Value`) of these array elements.581#[cfg_attr(582not(any(feature = "gc-drc", feature = "gc-null")),583expect(dead_code, reason = "easier to define")584)]585fn len(self, pos: &mut FuncCursor) -> ir::Value {586match self {587ArrayInit::Fill { len, .. } => len,588ArrayInit::Elems(e) => {589let len = u32::try_from(e.len()).unwrap();590pos.ins().iconst(ir::types::I32, i64::from(len))591}592}593}594595/// Initialize a newly-allocated array's elements.596#[cfg_attr(597not(any(feature = "gc-drc", feature = "gc-null")),598expect(dead_code, reason = "easier to define")599)]600fn initialize(601self,602func_env: &mut FuncEnvironment<'_>,603builder: &mut FunctionBuilder<'_>,604interned_type_index: ModuleInternedTypeIndex,605base_size: u32,606size: ir::Value,607elems_addr: ir::Value,608mut init_field: impl FnMut(609&mut FuncEnvironment<'_>,610&mut FunctionBuilder<'_>,611WasmStorageType,612ir::Value,613ir::Value,614) -> WasmResult<()>,615) -> WasmResult<()> {616log::trace!(617"initialize_array({interned_type_index:?}, {base_size:?}, {size:?}, {elems_addr:?})"618);619620assert!(!func_env.types[interned_type_index].composite_type.shared);621let array_ty = func_env.types[interned_type_index]622.composite_type623.inner624.unwrap_array();625let elem_ty = array_ty.0.element_type;626let elem_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&elem_ty);627let pointer_type = func_env.pointer_type();628let elem_size = builder.ins().iconst(pointer_type, i64::from(elem_size));629match self {630ArrayInit::Elems(elems) => {631let mut elem_addr = elems_addr;632for val in elems {633init_field(func_env, builder, elem_ty, elem_addr, *val)?;634elem_addr = builder.ins().iadd(elem_addr, elem_size);635}636}637ArrayInit::Fill { elem, len: _ } => {638// Compute the end address of the elements.639let base_size = builder.ins().iconst(pointer_type, i64::from(base_size));640let array_addr = builder.ins().isub(elems_addr, base_size);641let size = uextend_i32_to_pointer_type(builder, pointer_type, size);642let elems_end = builder.ins().iadd(array_addr, size);643644emit_array_fill_impl(645func_env,646builder,647elems_addr,648elem_size,649elems_end,650|func_env, builder, elem_addr| {651init_field(func_env, builder, elem_ty, elem_addr, elem)652},653)?;654}655}656log::trace!("initialize_array: finished");657Ok(())658}659}660661fn emit_array_fill_impl(662func_env: &mut FuncEnvironment<'_>,663builder: &mut FunctionBuilder<'_>,664elem_addr: ir::Value,665elem_size: ir::Value,666fill_end: ir::Value,667mut emit_elem_write: impl FnMut(668&mut FuncEnvironment<'_>,669&mut FunctionBuilder<'_>,670ir::Value,671) -> WasmResult<()>,672) -> WasmResult<()> {673log::trace!(674"emit_array_fill_impl(elem_addr: {elem_addr:?}, elem_size: {elem_size:?}, fill_end: {fill_end:?})"675);676677let pointer_ty = func_env.pointer_type();678679assert_eq!(builder.func.dfg.value_type(elem_addr), pointer_ty);680assert_eq!(builder.func.dfg.value_type(elem_size), pointer_ty);681assert_eq!(builder.func.dfg.value_type(fill_end), pointer_ty);682683// Loop to fill the elements, emitting the equivalent of the following684// pseudo-CLIF:685//686// current_block:687// ...688// jump loop_header_block(elem_addr)689//690// loop_header_block(elem_addr: i32):691// done = icmp eq elem_addr, fill_end692// brif done, continue_block, loop_body_block693//694// loop_body_block:695// emit_elem_write()696// next_elem_addr = iadd elem_addr, elem_size697// jump loop_header_block(next_elem_addr)698//699// continue_block:700// ...701702let current_block = builder.current_block().unwrap();703let loop_header_block = builder.create_block();704let loop_body_block = builder.create_block();705let continue_block = builder.create_block();706707builder.ensure_inserted_block();708builder.insert_block_after(loop_header_block, current_block);709builder.insert_block_after(loop_body_block, loop_header_block);710builder.insert_block_after(continue_block, loop_body_block);711712// Current block: jump to the loop header block with the first element's713// address.714builder.ins().jump(loop_header_block, &[elem_addr.into()]);715716// Loop header block: check if we're done, then jump to either the continue717// block or the loop body block.718builder.switch_to_block(loop_header_block);719builder.append_block_param(loop_header_block, pointer_ty);720log::trace!("emit_array_fill_impl: loop header");721func_env.translate_loop_header(builder)?;722let elem_addr = builder.block_params(loop_header_block)[0];723let done = builder.ins().icmp(IntCC::Equal, elem_addr, fill_end);724builder725.ins()726.brif(done, continue_block, &[], loop_body_block, &[]);727728// Loop body block: write the value to the current element, compute the next729// element's address, and then jump back to the loop header block.730builder.switch_to_block(loop_body_block);731log::trace!("emit_array_fill_impl: loop body");732emit_elem_write(func_env, builder, elem_addr)?;733let next_elem_addr = builder.ins().iadd(elem_addr, elem_size);734builder735.ins()736.jump(loop_header_block, &[next_elem_addr.into()]);737738// Continue...739builder.switch_to_block(continue_block);740log::trace!("emit_array_fill_impl: finished");741builder.seal_block(loop_header_block);742builder.seal_block(loop_body_block);743builder.seal_block(continue_block);744Ok(())745}746747pub fn translate_array_fill(748func_env: &mut FuncEnvironment<'_>,749builder: &mut FunctionBuilder<'_>,750array_type_index: TypeIndex,751array_ref: ir::Value,752index: ir::Value,753value: ir::Value,754n: ir::Value,755) -> WasmResult<()> {756log::trace!(757"translate_array_fill({array_type_index:?}, {array_ref:?}, {index:?}, {value:?}, {n:?})"758);759760let len = translate_array_len(func_env, builder, array_ref)?;761762// Check that the full range of elements we want to fill is within bounds.763let end_index = func_env.uadd_overflow_trap(builder, index, n, crate::TRAP_ARRAY_OUT_OF_BOUNDS);764let out_of_bounds = builder765.ins()766.icmp(IntCC::UnsignedGreaterThan, end_index, len);767func_env.trapnz(builder, out_of_bounds, crate::TRAP_ARRAY_OUT_OF_BOUNDS);768769// Get the address of the first element we want to fill.770let interned_type_index = func_env.module.types[array_type_index].unwrap_module_type_index();771let ArraySizeInfo {772obj_size,773one_elem_size,774base_size,775} = emit_array_size_info(func_env, builder, interned_type_index, len);776let offset_in_elems = builder.ins().imul(index, one_elem_size);777let obj_offset = builder.ins().iadd(base_size, offset_in_elems);778let elem_addr = func_env.prepare_gc_ref_access(779builder,780array_ref,781BoundsCheck::DynamicObjectField {782offset: obj_offset,783object_size: obj_size,784},785);786787// Calculate the end address, just after the filled region.788let fill_size = builder.ins().imul(n, one_elem_size);789let fill_size = uextend_i32_to_pointer_type(builder, func_env.pointer_type(), fill_size);790let fill_end = builder.ins().iadd(elem_addr, fill_size);791792let one_elem_size =793uextend_i32_to_pointer_type(builder, func_env.pointer_type(), one_elem_size);794795let result = emit_array_fill_impl(796func_env,797builder,798elem_addr,799one_elem_size,800fill_end,801|func_env, builder, elem_addr| {802let elem_ty = func_env803.types804.unwrap_array(interned_type_index)?805.0806.element_type;807write_field_at_addr(func_env, builder, elem_ty, elem_addr, value)808},809);810log::trace!("translate_array_fill(..) -> {result:?}");811result812}813814pub fn translate_array_len(815func_env: &mut FuncEnvironment<'_>,816builder: &mut FunctionBuilder,817array_ref: ir::Value,818) -> WasmResult<ir::Value> {819log::trace!("translate_array_len({array_ref:?})");820821func_env.trapz(builder, array_ref, crate::TRAP_NULL_REFERENCE);822823let len_offset = gc_compiler(func_env)?.layouts().array_length_field_offset();824let len_field = func_env.prepare_gc_ref_access(825builder,826array_ref,827// Note: We can't bounds check the whole array object's size because we828// don't know its length yet. Chicken and egg problem.829BoundsCheck::StaticOffset {830offset: len_offset,831access_size: u8::try_from(ir::types::I32.bytes()).unwrap(),832},833);834let result = builder.ins().load(835ir::types::I32,836ir::MemFlags::trusted().with_readonly(),837len_field,8380,839);840log::trace!("translate_array_len(..) -> {result:?}");841Ok(result)842}843844struct ArraySizeInfo {845/// The `i32` size of the whole array object, in bytes.846obj_size: ir::Value,847848/// The `i32` size of each one of the array's elements, in bytes.849one_elem_size: ir::Value,850851/// The `i32` size of the array's base object, in bytes. This is also the852/// offset from the start of the array object to its elements.853base_size: ir::Value,854}855856/// Emit code to get the dynamic size (in bytes) of a whole array object, along857/// with some other related bits.858fn emit_array_size_info(859func_env: &mut FuncEnvironment<'_>,860builder: &mut FunctionBuilder<'_>,861array_type_index: ModuleInternedTypeIndex,862// `i32` value containing the array's length.863array_len: ir::Value,864) -> ArraySizeInfo {865let array_layout = func_env.array_layout(array_type_index);866867// Note that we check for overflow below because we can't trust the array's868// length: it came from inside the GC heap.869//870// We check for 32-bit multiplication overflow by performing a 64-bit871// multiplication and testing the high bits.872let one_elem_size = builder873.ins()874.iconst(ir::types::I64, i64::from(array_layout.elem_size));875let array_len = builder.ins().uextend(ir::types::I64, array_len);876let all_elems_size = builder.ins().imul(one_elem_size, array_len);877878let high_bits = builder.ins().ushr_imm(all_elems_size, 32);879builder.ins().trapnz(high_bits, TRAP_INTERNAL_ASSERT);880881let all_elems_size = builder.ins().ireduce(ir::types::I32, all_elems_size);882let base_size = builder883.ins()884.iconst(ir::types::I32, i64::from(array_layout.base_size));885let obj_size =886builder887.ins()888.uadd_overflow_trap(all_elems_size, base_size, TRAP_INTERNAL_ASSERT);889890let one_elem_size = builder.ins().ireduce(ir::types::I32, one_elem_size);891892ArraySizeInfo {893obj_size,894one_elem_size,895base_size,896}897}898899/// Get the bounds-checked address of an element in an array.900///901/// The emitted code will trap if `index >= array.length`.902///903/// Returns the `ir::Value` containing the address of the `index`th element in904/// the array. You may read or write a value of the array's element type at this905/// address. You may not use it for any other kind of access, nor reuse this906/// value across GC safepoints.907fn array_elem_addr(908func_env: &mut FuncEnvironment<'_>,909builder: &mut FunctionBuilder<'_>,910array_type_index: ModuleInternedTypeIndex,911array_ref: ir::Value,912index: ir::Value,913) -> ir::Value {914// First, assert that `index < array.length`.915//916// This check is visible at the Wasm-semantics level.917//918// TODO: We should emit spectre-safe bounds checks for array accesses (if919// configured) but we don't currently have a great way to do that here. The920// proper solution is to use linear memories to back GC heaps and reuse the921// code in `bounds_check.rs` to implement these bounds checks. That is all922// planned, but not yet implemented.923924let len = translate_array_len(func_env, builder, array_ref).unwrap();925926let in_bounds = builder.ins().icmp(IntCC::UnsignedLessThan, index, len);927func_env.trapz(builder, in_bounds, crate::TRAP_ARRAY_OUT_OF_BOUNDS);928929// Compute the size (in bytes) of the whole array object.930let ArraySizeInfo {931obj_size,932one_elem_size,933base_size,934} = emit_array_size_info(func_env, builder, array_type_index, len);935936// Compute the offset of the `index`th element within the array object.937//938// NB: no need to check for overflow here, since at this point we know that939// `len * elem_size + base_size` did not overflow and `i < len`.940let offset_in_elems = builder.ins().imul(index, one_elem_size);941let offset_in_array = builder.ins().iadd(offset_in_elems, base_size);942943// Finally, use the object size and element offset we just computed to944// perform our implementation-internal bounds checks.945//946// Checking the whole object's size, rather than the `index`th element's947// size allows these bounds checks to be deduplicated across repeated948// accesses to the same array at different indices.949//950// This check should not be visible to Wasm, and serve to protect us from951// our own implementation bugs. The goal is to keep any potential widgets952// confined within the GC heap, and turn what would otherwise be a security953// vulnerability into a simple bug.954//955// TODO: Ideally we should fold the first Wasm-visible bounds check into956// this internal bounds check, so that we aren't performing multiple,957// redundant bounds checks. But we should figure out how to do this in a way958// that doesn't defeat the object-size bounds checking's deduplication959// mentioned above.960func_env.prepare_gc_ref_access(961builder,962array_ref,963BoundsCheck::DynamicObjectField {964offset: offset_in_array,965object_size: obj_size,966},967)968}969970pub fn translate_array_get(971func_env: &mut FuncEnvironment<'_>,972builder: &mut FunctionBuilder,973array_type_index: TypeIndex,974array_ref: ir::Value,975index: ir::Value,976extension: Option<Extension>,977) -> WasmResult<ir::Value> {978log::trace!("translate_array_get({array_type_index:?}, {array_ref:?}, {index:?})");979980let array_type_index = func_env.module.types[array_type_index].unwrap_module_type_index();981let elem_addr = array_elem_addr(func_env, builder, array_type_index, array_ref, index);982983let array_ty = func_env.types.unwrap_array(array_type_index)?;984let elem_ty = array_ty.0.element_type;985986let result = read_field_at_addr(func_env, builder, elem_ty, elem_addr, extension)?;987log::trace!("translate_array_get(..) -> {result:?}");988Ok(result)989}990991pub fn translate_array_set(992func_env: &mut FuncEnvironment<'_>,993builder: &mut FunctionBuilder,994array_type_index: TypeIndex,995array_ref: ir::Value,996index: ir::Value,997value: ir::Value,998) -> WasmResult<()> {999log::trace!("translate_array_set({array_type_index:?}, {array_ref:?}, {index:?}, {value:?})");10001001let array_type_index = func_env.module.types[array_type_index].unwrap_module_type_index();1002let elem_addr = array_elem_addr(func_env, builder, array_type_index, array_ref, index);10031004let array_ty = func_env.types.unwrap_array(array_type_index)?;1005let elem_ty = array_ty.0.element_type;10061007write_field_at_addr(func_env, builder, elem_ty, elem_addr, value)?;10081009log::trace!("translate_array_set: finished");1010Ok(())1011}10121013pub fn translate_ref_test(1014func_env: &mut FuncEnvironment<'_>,1015builder: &mut FunctionBuilder<'_>,1016test_ty: WasmRefType,1017val: ir::Value,1018val_ty: WasmRefType,1019) -> WasmResult<ir::Value> {1020log::trace!("translate_ref_test({test_ty:?}, {val:?})");10211022// First special case: testing for references to bottom types.1023if test_ty.heap_type.is_bottom() {1024let result = if test_ty.nullable {1025// All null references (within the same type hierarchy) match null1026// references to the bottom type.1027func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?1028} else {1029// `ref.test` is always false for non-nullable bottom types, as the1030// bottom types are uninhabited.1031builder.ins().iconst(ir::types::I32, 0)1032};1033log::trace!("translate_ref_test(..) -> {result:?}");1034return Ok(result);1035}10361037// And because `ref.test heap_ty` is only valid on operands whose type is in1038// the same type hierarchy as `heap_ty`, if `heap_ty` is its hierarchy's top1039// type, we only need to worry about whether we are testing for nullability1040// or not.1041if test_ty.heap_type.is_top() {1042let result = if test_ty.nullable {1043builder.ins().iconst(ir::types::I32, 1)1044} else {1045let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;1046let zero = builder.ins().iconst(ir::types::I32, 0);1047let one = builder.ins().iconst(ir::types::I32, 1);1048builder.ins().select(is_null, zero, one)1049};1050log::trace!("translate_ref_test(..) -> {result:?}");1051return Ok(result);1052}10531054// `i31ref`s are a little interesting because they don't point to GC1055// objects; we test the bit pattern of the reference itself.1056if test_ty.heap_type == WasmHeapType::I31 {1057let i31_mask = builder.ins().iconst(1058ir::types::I32,1059i64::from(wasmtime_environ::I31_DISCRIMINANT),1060);1061let is_i31 = builder.ins().band(val, i31_mask);1062let result = if test_ty.nullable {1063let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;1064builder.ins().bor(is_null, is_i31)1065} else {1066is_i311067};1068log::trace!("translate_ref_test(..) -> {result:?}");1069return Ok(result);1070}10711072// Otherwise, in the general case, we need to inspect our given object's1073// actual type, which also requires null-checking and i31-checking it.10741075let is_any_hierarchy = test_ty.heap_type.top() == WasmHeapTopType::Any;10761077let non_null_block = builder.create_block();1078let non_null_non_i31_block = builder.create_block();1079let continue_block = builder.create_block();10801081// Current block: check if the reference is null and branch appropriately.1082let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;1083let result_when_is_null = builder1084.ins()1085.iconst(ir::types::I32, test_ty.nullable as i64);1086builder.ins().brif(1087is_null,1088continue_block,1089&[result_when_is_null.into()],1090non_null_block,1091&[],1092);10931094// Non-null block: We know the GC ref is non-null, but we need to also check1095// for `i31` references that don't point to GC objects.1096builder.switch_to_block(non_null_block);1097log::trace!("translate_ref_test: non-null ref block");1098if is_any_hierarchy {1099let i31_mask = builder.ins().iconst(1100ir::types::I32,1101i64::from(wasmtime_environ::I31_DISCRIMINANT),1102);1103let is_i31 = builder.ins().band(val, i31_mask);1104// If it is an `i31`, then create the result value based on whether we1105// want `i31`s to pass the test or not.1106let result_when_is_i31 = builder.ins().iconst(1107ir::types::I32,1108matches!(1109test_ty.heap_type,1110WasmHeapType::Any | WasmHeapType::Eq | WasmHeapType::I311111) as i64,1112);1113builder.ins().brif(1114is_i31,1115continue_block,1116&[result_when_is_i31.into()],1117non_null_non_i31_block,1118&[],1119);1120} else {1121// If we aren't testing the `any` hierarchy, the reference cannot be an1122// `i31ref`. Jump directly to the non-null and non-i31 block; rely on1123// branch folding during lowering to clean this up.1124builder.ins().jump(non_null_non_i31_block, &[]);1125}11261127// Non-null and non-i31 block: Read the actual `VMGcKind` or1128// `VMSharedTypeIndex` out of the object's header and check whether it1129// matches the expected type.1130builder.switch_to_block(non_null_non_i31_block);1131log::trace!("translate_ref_test: non-null and non-i31 ref block");1132let check_header_kind = |func_env: &mut FuncEnvironment<'_>,1133builder: &mut FunctionBuilder,1134val: ir::Value,1135expected_kind: VMGcKind|1136-> ir::Value {1137let kind_addr = func_env.prepare_gc_ref_access(1138builder,1139val,1140BoundsCheck::StaticObjectField {1141offset: wasmtime_environ::VM_GC_HEADER_KIND_OFFSET,1142access_size: wasmtime_environ::VM_GC_KIND_SIZE,1143object_size: wasmtime_environ::VM_GC_HEADER_SIZE,1144},1145);1146let actual_kind = builder.ins().load(1147ir::types::I32,1148ir::MemFlags::trusted().with_readonly(),1149kind_addr,11500,1151);1152let expected_kind = builder1153.ins()1154.iconst(ir::types::I32, i64::from(expected_kind.as_u32()));1155// Inline version of `VMGcKind::matches`.1156let and = builder.ins().band(actual_kind, expected_kind);1157let kind_matches = builder1158.ins()1159.icmp(ir::condcodes::IntCC::Equal, and, expected_kind);1160builder.ins().uextend(ir::types::I32, kind_matches)1161};1162let result = match test_ty.heap_type {1163WasmHeapType::Any1164| WasmHeapType::None1165| WasmHeapType::Extern1166| WasmHeapType::NoExtern1167| WasmHeapType::Func1168| WasmHeapType::NoFunc1169| WasmHeapType::Cont1170| WasmHeapType::NoCont1171| WasmHeapType::Exn1172| WasmHeapType::NoExn1173| WasmHeapType::I31 => unreachable!("handled top, bottom, and i31 types above"),11741175// For these abstract but non-top and non-bottom types, we check the1176// `VMGcKind` that is in the object's header.1177WasmHeapType::Eq => check_header_kind(func_env, builder, val, VMGcKind::EqRef),1178WasmHeapType::Struct => check_header_kind(func_env, builder, val, VMGcKind::StructRef),1179WasmHeapType::Array => check_header_kind(func_env, builder, val, VMGcKind::ArrayRef),11801181// For concrete types, we need to do a full subtype check between the1182// `VMSharedTypeIndex` in the object's header and the1183// `ModuleInternedTypeIndex` we have here.1184//1185// TODO: This check should ideally be done inline, but we don't have a1186// good way to access the `TypeRegistry`'s supertypes arrays from Wasm1187// code at the moment.1188WasmHeapType::ConcreteArray(ty)1189| WasmHeapType::ConcreteStruct(ty)1190| WasmHeapType::ConcreteExn(ty) => {1191let expected_interned_ty = ty.unwrap_module_type_index();1192let expected_shared_ty =1193func_env.module_interned_to_shared_ty(&mut builder.cursor(), expected_interned_ty);11941195let ty_addr = func_env.prepare_gc_ref_access(1196builder,1197val,1198BoundsCheck::StaticOffset {1199offset: wasmtime_environ::VM_GC_HEADER_TYPE_INDEX_OFFSET,1200access_size: func_env.offsets.size_of_vmshared_type_index(),1201},1202);1203let actual_shared_ty = builder.ins().load(1204ir::types::I32,1205ir::MemFlags::trusted().with_readonly(),1206ty_addr,12070,1208);12091210func_env.is_subtype(builder, actual_shared_ty, expected_shared_ty)1211}12121213// Same as for concrete arrays and structs except that a `VMFuncRef`1214// doesn't begin with a `VMGcHeader` and is a raw pointer rather than GC1215// heap index.1216WasmHeapType::ConcreteFunc(ty) => {1217let expected_interned_ty = ty.unwrap_module_type_index();1218let expected_shared_ty =1219func_env.module_interned_to_shared_ty(&mut builder.cursor(), expected_interned_ty);12201221let actual_shared_ty = func_env.load_funcref_type_index(1222&mut builder.cursor(),1223ir::MemFlags::trusted().with_readonly(),1224val,1225);12261227func_env.is_subtype(builder, actual_shared_ty, expected_shared_ty)1228}1229WasmHeapType::ConcreteCont(_) => {1230// TODO(#10248) GC integration for stack switching1231return Err(wasmtime_environ::WasmError::Unsupported(1232"Stack switching feature not compatible with GC, yet".to_string(),1233));1234}1235};1236builder.ins().jump(continue_block, &[result.into()]);12371238// Control flow join point with the result.1239builder.switch_to_block(continue_block);1240let result = builder.append_block_param(continue_block, ir::types::I32);1241log::trace!("translate_ref_test(..) -> {result:?}");12421243builder.seal_block(non_null_block);1244builder.seal_block(non_null_non_i31_block);1245builder.seal_block(continue_block);12461247Ok(result)1248}12491250fn uextend_i32_to_pointer_type(1251builder: &mut FunctionBuilder,1252pointer_type: ir::Type,1253value: ir::Value,1254) -> ir::Value {1255assert_eq!(builder.func.dfg.value_type(value), ir::types::I32);1256match pointer_type {1257ir::types::I32 => value,1258ir::types::I64 => builder.ins().uextend(ir::types::I64, value),1259_ => unreachable!(),1260}1261}12621263/// Emit CLIF to compute an array object's total size, given the dynamic length1264/// in its initialization.1265///1266/// Traps if the size overflows.1267#[cfg_attr(1268not(any(feature = "gc-drc", feature = "gc-null")),1269expect(dead_code, reason = "easier to define")1270)]1271fn emit_array_size(1272func_env: &mut FuncEnvironment<'_>,1273builder: &mut FunctionBuilder<'_>,1274array_layout: &GcArrayLayout,1275len: ir::Value,1276) -> ir::Value {1277let base_size = builder1278.ins()1279.iconst(ir::types::I32, i64::from(array_layout.base_size));12801281// `elems_size = len * elem_size`1282//1283// Check for multiplication overflow and trap if it occurs, since that1284// means Wasm is attempting to allocate an array that is larger than our1285// implementation limits. (Note: there is no standard implementation1286// limit for array length beyond `u32::MAX`.)1287//1288// We implement this check by encoding our logically-32-bit operands as1289// i64 values, doing a 64-bit multiplication, and then checking the high1290// 32 bits of the multiplication's result. If the high 32 bits are not1291// all zeros, then the multiplication overflowed.1292debug_assert_eq!(builder.func.dfg.value_type(len), ir::types::I32);1293let len = builder.ins().uextend(ir::types::I64, len);1294let elems_size_64 = builder1295.ins()1296.imul_imm(len, i64::from(array_layout.elem_size));1297let high_bits = builder.ins().ushr_imm(elems_size_64, 32);1298func_env.trapnz(builder, high_bits, crate::TRAP_ALLOCATION_TOO_LARGE);1299let elems_size = builder.ins().ireduce(ir::types::I32, elems_size_64);13001301// And if adding the base size and elements size overflows, then the1302// allocation is too large.1303let size = func_env.uadd_overflow_trap(1304builder,1305base_size,1306elems_size,1307crate::TRAP_ALLOCATION_TOO_LARGE,1308);13091310size1311}13121313/// Common helper for struct-field initialization that can be reused across1314/// collectors.1315#[cfg_attr(1316not(any(feature = "gc-drc", feature = "gc-null")),1317expect(dead_code, reason = "easier to define")1318)]1319fn initialize_struct_fields(1320func_env: &mut FuncEnvironment<'_>,1321builder: &mut FunctionBuilder<'_>,1322struct_ty: ModuleInternedTypeIndex,1323raw_ptr_to_struct: ir::Value,1324field_values: &[ir::Value],1325mut init_field: impl FnMut(1326&mut FuncEnvironment<'_>,1327&mut FunctionBuilder<'_>,1328WasmStorageType,1329ir::Value,1330ir::Value,1331) -> WasmResult<()>,1332) -> WasmResult<()> {1333let struct_layout = func_env.struct_or_exn_layout(struct_ty);1334let struct_size = struct_layout.size;1335let field_offsets: SmallVec<[_; 8]> = struct_layout.fields.iter().map(|f| f.offset).collect();1336assert_eq!(field_offsets.len(), field_values.len());13371338assert!(!func_env.types[struct_ty].composite_type.shared);1339let fields = match &func_env.types[struct_ty].composite_type.inner {1340WasmCompositeInnerType::Struct(s) => &s.fields,1341WasmCompositeInnerType::Exn(e) => &e.fields,1342_ => panic!("Not a struct or exception type"),1343};13441345let field_types: SmallVec<[_; 8]> = fields.iter().cloned().collect();1346assert_eq!(field_types.len(), field_values.len());13471348for ((ty, val), offset) in field_types.into_iter().zip(field_values).zip(field_offsets) {1349let size_of_access = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&ty.element_type);1350assert!(offset + size_of_access <= struct_size);1351let field_addr = builder.ins().iadd_imm(raw_ptr_to_struct, i64::from(offset));1352init_field(func_env, builder, ty.element_type, field_addr, *val)?;1353}13541355Ok(())1356}13571358impl FuncEnvironment<'_> {1359fn gc_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcLayout {1360// Lazily compute and cache the layout.1361if !self.ty_to_gc_layout.contains_key(&type_index) {1362let ty = &self.types[type_index].composite_type;1363let layout = gc_compiler(self)1364.unwrap()1365.layouts()1366.gc_layout(ty)1367.expect("should only call `FuncEnvironment::gc_layout` for GC types");1368self.ty_to_gc_layout.insert(type_index, layout);1369}13701371self.ty_to_gc_layout.get(&type_index).unwrap()1372}13731374/// Get the `GcArrayLayout` for the array type at the given `type_index`.1375fn array_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcArrayLayout {1376self.gc_layout(type_index).unwrap_array()1377}13781379/// Get the `GcStructLayout` for the struct or exception type at the given `type_index`.1380fn struct_or_exn_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcStructLayout {1381let result = self.gc_layout(type_index).unwrap_struct();1382result1383}13841385/// Get or create the global for our GC heap's base pointer.1386fn get_gc_heap_base_global(&mut self, func: &mut ir::Function) -> ir::GlobalValue {1387if let Some(base) = self.gc_heap_base {1388return base;1389}13901391let store_context_ptr = self.get_vmstore_context_ptr_global(func);1392let offset = self.offsets.ptr.vmstore_context_gc_heap_base();13931394let mut flags = ir::MemFlags::trusted();1395if !self1396.tunables1397.gc_heap_memory_type()1398.memory_may_move(self.tunables)1399{1400flags.set_readonly();1401flags.set_can_move();1402}14031404let base = func.create_global_value(ir::GlobalValueData::Load {1405base: store_context_ptr,1406offset: Offset32::new(offset.into()),1407global_type: self.pointer_type(),1408flags,1409});14101411self.gc_heap_base = Some(base);1412base1413}14141415/// Get the GC heap's base.1416#[cfg(any(feature = "gc-null", feature = "gc-drc"))]1417fn get_gc_heap_base(&mut self, builder: &mut FunctionBuilder) -> ir::Value {1418let global = self.get_gc_heap_base_global(&mut builder.func);1419builder.ins().global_value(self.pointer_type(), global)1420}14211422fn get_gc_heap_bound_global(&mut self, func: &mut ir::Function) -> ir::GlobalValue {1423if let Some(bound) = self.gc_heap_bound {1424return bound;1425}1426let store_context_ptr = self.get_vmstore_context_ptr_global(func);1427let offset = self.offsets.ptr.vmstore_context_gc_heap_current_length();1428let bound = func.create_global_value(ir::GlobalValueData::Load {1429base: store_context_ptr,1430offset: Offset32::new(offset.into()),1431global_type: self.pointer_type(),1432flags: ir::MemFlags::trusted(),1433});1434self.gc_heap_bound = Some(bound);1435bound1436}14371438/// Get the GC heap's bound.1439#[cfg(feature = "gc-null")]1440fn get_gc_heap_bound(&mut self, builder: &mut FunctionBuilder) -> ir::Value {1441let global = self.get_gc_heap_bound_global(&mut builder.func);1442builder.ins().global_value(self.pointer_type(), global)1443}14441445/// Get or create the `Heap` for our GC heap.1446fn get_gc_heap(&mut self, func: &mut ir::Function) -> Heap {1447if let Some(heap) = self.gc_heap {1448return heap;1449}14501451let base = self.get_gc_heap_base_global(func);1452let bound = self.get_gc_heap_bound_global(func);1453let memory = self.tunables.gc_heap_memory_type();1454let heap = self.heaps.push(HeapData {1455base,1456bound,1457pcc_memory_type: None,1458memory,1459});1460self.gc_heap = Some(heap);1461heap1462}14631464/// Get the raw pointer of `gc_ref[offset]` bounds checked for an access of1465/// `size` bytes.1466///1467/// The given `gc_ref` must be a non-null, non-i31 GC reference.1468///1469/// If `check` is a `BoundsCheck::Object`, then it is the callers1470/// responsibility to ensure that `offset + access_size <= object_size`.1471///1472/// Returns a raw pointer to `gc_ref[offset]` -- not a raw pointer to the GC1473/// object itself (unless `offset` happens to be `0`). This raw pointer may1474/// be used to read or write up to as many bytes as described by `bound`. Do1475/// NOT attempt accesses bytes outside of `bound`; that may lead to1476/// unchecked out-of-bounds accesses.1477///1478/// This method is collector-agnostic.1479fn prepare_gc_ref_access(1480&mut self,1481builder: &mut FunctionBuilder,1482gc_ref: ir::Value,1483bounds_check: BoundsCheck,1484) -> ir::Value {1485log::trace!("prepare_gc_ref_access({gc_ref:?}, {bounds_check:?})");1486assert_eq!(builder.func.dfg.value_type(gc_ref), ir::types::I32);14871488let gc_heap = self.get_gc_heap(&mut builder.func);1489let gc_heap = self.heaps[gc_heap].clone();1490let result = match crate::bounds_checks::bounds_check_and_compute_addr(1491builder,1492self,1493&gc_heap,1494gc_ref,1495bounds_check,1496crate::TRAP_INTERNAL_ASSERT,1497) {1498Reachability::Reachable(v) => v,1499Reachability::Unreachable => {1500// We are now in unreachable code, but we don't want to plumb1501// through a bunch of `Reachability` through all of our callers,1502// so just assert we won't reach here and return `null`1503let null = builder.ins().iconst(self.pointer_type(), 0);1504builder.ins().trapz(null, crate::TRAP_INTERNAL_ASSERT);1505null1506}1507};1508log::trace!("prepare_gc_ref_access(..) -> {result:?}");1509result1510}15111512/// Emit checks (if necessary) for whether the given `gc_ref` is null or is1513/// an `i31ref`.1514///1515/// Takes advantage of static information based on `ty` as to whether the GC1516/// reference is nullable or can ever be an `i31`.1517///1518/// Returns an `ir::Value` that is an `i32` will be non-zero if the GC1519/// reference is null or is an `i31ref`; otherwise, it will be zero.1520///1521/// This method is collector-agnostic.1522#[cfg_attr(1523not(feature = "gc-drc"),1524expect(dead_code, reason = "easier to define")1525)]1526fn gc_ref_is_null_or_i31(1527&mut self,1528builder: &mut FunctionBuilder,1529ty: WasmRefType,1530gc_ref: ir::Value,1531) -> ir::Value {1532assert_eq!(builder.func.dfg.value_type(gc_ref), ir::types::I32);1533assert!(ty.is_vmgcref_type_and_not_i31());15341535let might_be_i31 = match ty.heap_type {1536// If we are definitely dealing with an i31, we shouldn't be1537// emitting dynamic checks for it, and the caller shouldn't call1538// this function. Should have been caught by the assertion at the1539// start of the function.1540WasmHeapType::I31 => unreachable!(),15411542// Could potentially be an i31.1543WasmHeapType::Any | WasmHeapType::Eq => true,15441545// If it is definitely a struct, array, or uninhabited type, then it1546// is definitely not an i31.1547WasmHeapType::Array1548| WasmHeapType::ConcreteArray(_)1549| WasmHeapType::Struct1550| WasmHeapType::ConcreteStruct(_)1551| WasmHeapType::None => false,15521553// Despite being a different type hierarchy, this *could* be an1554// `i31` if it is the result of1555//1556// (extern.convert_any (ref.i31 ...))1557WasmHeapType::Extern => true,15581559// Can only ever be `null`.1560WasmHeapType::NoExtern => false,15611562WasmHeapType::Exn | WasmHeapType::ConcreteExn(_) | WasmHeapType::NoExn => false,15631564// Wrong type hierarchy, and also funcrefs are not GC-managed1565// types. Should have been caught by the assertion at the start of1566// the function.1567WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {1568unreachable!()1569}1570WasmHeapType::Cont | WasmHeapType::ConcreteCont(_) | WasmHeapType::NoCont => {1571unreachable!()1572}1573};15741575match (ty.nullable, might_be_i31) {1576// This GC reference statically cannot be null nor an i31. (Let1577// Cranelift's optimizer const-propagate this value and erase any1578// unnecessary control flow resulting from branching on this value.)1579(false, false) => builder.ins().iconst(ir::types::I32, 0),15801581// This GC reference is always non-null, but might be an i31.1582(false, true) => builder.ins().band_imm(gc_ref, i64::from(I31_DISCRIMINANT)),15831584// This GC reference might be null, but can never be an i31.1585(true, false) => builder.ins().icmp_imm(IntCC::Equal, gc_ref, 0),15861587// Fully general case: this GC reference could be either null or an1588// i31.1589(true, true) => {1590let is_i31 = builder.ins().band_imm(gc_ref, i64::from(I31_DISCRIMINANT));1591let is_null = builder.ins().icmp_imm(IntCC::Equal, gc_ref, 0);1592let is_null = builder.ins().uextend(ir::types::I32, is_null);1593builder.ins().bor(is_i31, is_null)1594}1595}1596}15971598// Emit code to check whether `a <: b` for two `VMSharedTypeIndex`es.1599pub(crate) fn is_subtype(1600&mut self,1601builder: &mut FunctionBuilder<'_>,1602a: ir::Value,1603b: ir::Value,1604) -> ir::Value {1605log::trace!("is_subtype({a:?}, {b:?})");16061607let diff_tys_block = builder.create_block();1608let continue_block = builder.create_block();16091610// Current block: fast path for when `a == b`.1611log::trace!("is_subtype: fast path check for exact same types");1612let same_ty = builder.ins().icmp(IntCC::Equal, a, b);1613let same_ty = builder.ins().uextend(ir::types::I32, same_ty);1614builder.ins().brif(1615same_ty,1616continue_block,1617&[same_ty.into()],1618diff_tys_block,1619&[],1620);16211622// Different types block: fall back to the `is_subtype` libcall.1623builder.switch_to_block(diff_tys_block);1624log::trace!("is_subtype: slow path to do full `is_subtype` libcall");1625let is_subtype = self.builtin_functions.is_subtype(builder.func);1626let vmctx = self.vmctx_val(&mut builder.cursor());1627let call_inst = builder.ins().call(is_subtype, &[vmctx, a, b]);1628let result = builder.func.dfg.first_result(call_inst);1629builder.ins().jump(continue_block, &[result.into()]);16301631// Continue block: join point for the result.1632builder.switch_to_block(continue_block);1633let result = builder.append_block_param(continue_block, ir::types::I32);1634log::trace!("is_subtype(..) -> {result:?}");16351636builder.seal_block(diff_tys_block);1637builder.seal_block(continue_block);16381639result1640}1641}164216431644