Path: blob/main/crates/cranelift/src/func_environ/gc/enabled.rs
1693 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::{7Block, BlockArg, ExceptionTableData, ExceptionTableItem, ExceptionTag,8};9use cranelift_codegen::{10cursor::FuncCursor,11ir::{self, InstBuilder, condcodes::IntCC},12};13use cranelift_entity::packed_option::ReservedValue;14use cranelift_frontend::FunctionBuilder;15use smallvec::{SmallVec, smallvec};16use wasmtime_environ::{17Collector, GcArrayLayout, GcLayout, GcStructLayout, I31_DISCRIMINANT, ModuleInternedTypeIndex,18PtrSize, TagIndex, TypeIndex, VMGcKind, WasmCompositeInnerType, WasmHeapTopType, WasmHeapType,19WasmRefType, WasmResult, WasmStorageType, WasmValType, wasm_unsupported,20};2122#[cfg(feature = "gc-drc")]23mod drc;24#[cfg(feature = "gc-null")]25mod null;2627/// Get the default GC compiler.28pub fn gc_compiler(func_env: &mut FuncEnvironment<'_>) -> WasmResult<Box<dyn GcCompiler>> {29// If this function requires a GC compiler, that is not too bad of an30// over-approximation for it requiring a GC heap.31func_env.needs_gc_heap = true;3233match func_env.tunables.collector {34#[cfg(feature = "gc-drc")]35Some(Collector::DeferredReferenceCounting) => Ok(Box::new(drc::DrcCompiler::default())),36#[cfg(not(feature = "gc-drc"))]37Some(Collector::DeferredReferenceCounting) => Err(wasm_unsupported!(38"the DRC collector is unavailable because the `gc-drc` feature \39was disabled at compile time",40)),4142#[cfg(feature = "gc-null")]43Some(Collector::Null) => Ok(Box::new(null::NullCompiler::default())),44#[cfg(not(feature = "gc-null"))]45Some(Collector::Null) => Err(wasm_unsupported!(46"the null collector is unavailable because the `gc-null` feature \47was disabled at compile time",48)),4950#[cfg(any(feature = "gc-drc", feature = "gc-null"))]51None => Err(wasm_unsupported!(52"support for GC types disabled at configuration time"53)),54#[cfg(not(any(feature = "gc-drc", feature = "gc-null")))]55None => Err(wasm_unsupported!(56"support for GC types disabled because no collector implementation \57was selected at compile time; enable one of the `gc-drc` or \58`gc-null` features",59)),60}61}6263#[cfg_attr(64not(feature = "gc-drc"),65expect(dead_code, reason = "easier to define")66)]67fn unbarriered_load_gc_ref(68builder: &mut FunctionBuilder,69ty: WasmHeapType,70ptr_to_gc_ref: ir::Value,71flags: ir::MemFlags,72) -> WasmResult<ir::Value> {73debug_assert!(ty.is_vmgcref_type());74let gc_ref = builder.ins().load(ir::types::I32, flags, ptr_to_gc_ref, 0);75if ty != WasmHeapType::I31 {76builder.declare_value_needs_stack_map(gc_ref);77}78Ok(gc_ref)79}8081#[cfg_attr(82not(any(feature = "gc-drc", feature = "gc-null")),83expect(dead_code, reason = "easier to define")84)]85fn unbarriered_store_gc_ref(86builder: &mut FunctionBuilder,87ty: WasmHeapType,88dst: ir::Value,89gc_ref: ir::Value,90flags: ir::MemFlags,91) -> WasmResult<()> {92debug_assert!(ty.is_vmgcref_type());93builder.ins().store(flags, gc_ref, dst, 0);94Ok(())95}9697/// Emit code to read a struct field or array element from its raw address in98/// the GC heap.99///100/// The given address MUST have already been bounds-checked via101/// `prepare_gc_ref_access`.102fn read_field_at_addr(103func_env: &mut FuncEnvironment<'_>,104builder: &mut FunctionBuilder<'_>,105ty: WasmStorageType,106addr: ir::Value,107extension: Option<Extension>,108) -> WasmResult<ir::Value> {109assert_eq!(extension.is_none(), matches!(ty, WasmStorageType::Val(_)));110assert_eq!(111extension.is_some(),112matches!(ty, WasmStorageType::I8 | WasmStorageType::I16)113);114115// Data inside GC objects is always little endian.116let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);117118let value = match ty {119WasmStorageType::I8 => builder.ins().load(ir::types::I8, flags, addr, 0),120WasmStorageType::I16 => builder.ins().load(ir::types::I16, flags, addr, 0),121WasmStorageType::Val(v) => match v {122WasmValType::I32 => builder.ins().load(ir::types::I32, flags, addr, 0),123WasmValType::I64 => builder.ins().load(ir::types::I64, flags, addr, 0),124WasmValType::F32 => builder.ins().load(ir::types::F32, flags, addr, 0),125WasmValType::F64 => builder.ins().load(ir::types::F64, flags, addr, 0),126WasmValType::V128 => builder.ins().load(ir::types::I8X16, flags, addr, 0),127WasmValType::Ref(r) => match r.heap_type.top() {128WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => {129gc_compiler(func_env)?130.translate_read_gc_reference(func_env, builder, r, addr, flags)?131}132WasmHeapTopType::Func => {133let expected_ty = match r.heap_type {134WasmHeapType::Func => ModuleInternedTypeIndex::reserved_value(),135WasmHeapType::ConcreteFunc(ty) => ty.unwrap_module_type_index(),136WasmHeapType::NoFunc => {137let null = builder.ins().iconst(func_env.pointer_type(), 0);138if !r.nullable {139// Because `nofunc` is uninhabited, and this140// reference is non-null, this is unreachable141// code. Unconditionally trap via conditional142// trap instructions to avoid inserting block143// terminators in the middle of this block.144builder.ins().trapz(null, TRAP_INTERNAL_ASSERT);145}146return Ok(null);147}148_ => unreachable!("not a function heap type"),149};150let expected_ty = builder151.ins()152.iconst(ir::types::I32, i64::from(expected_ty.as_bits()));153154let vmctx = func_env.vmctx_val(&mut builder.cursor());155156let func_ref_id = builder.ins().load(ir::types::I32, flags, addr, 0);157let get_interned_func_ref = func_env158.builtin_functions159.get_interned_func_ref(builder.func);160161let call_inst = builder162.ins()163.call(get_interned_func_ref, &[vmctx, func_ref_id, expected_ty]);164builder.func.dfg.first_result(call_inst)165}166WasmHeapTopType::Cont => {167// TODO(#10248) GC integration for stack switching168return Err(wasmtime_environ::WasmError::Unsupported(169"Stack switching feature not compatible with GC, yet".to_string(),170));171}172},173},174};175176let value = match extension {177Some(Extension::Sign) => builder.ins().sextend(ir::types::I32, value),178Some(Extension::Zero) => builder.ins().uextend(ir::types::I32, value),179None => value,180};181182Ok(value)183}184185fn write_func_ref_at_addr(186func_env: &mut FuncEnvironment<'_>,187builder: &mut FunctionBuilder<'_>,188ref_type: WasmRefType,189flags: ir::MemFlags,190field_addr: ir::Value,191func_ref: ir::Value,192) -> WasmResult<()> {193assert_eq!(ref_type.heap_type.top(), WasmHeapTopType::Func);194195let vmctx = func_env.vmctx_val(&mut builder.cursor());196197let intern_func_ref_for_gc_heap = func_env198.builtin_functions199.intern_func_ref_for_gc_heap(builder.func);200201let func_ref = if ref_type.heap_type == WasmHeapType::NoFunc {202let null = builder.ins().iconst(func_env.pointer_type(), 0);203if !ref_type.nullable {204// Because `nofunc` is uninhabited, and this reference is205// non-null, this is unreachable code. Unconditionally trap206// via conditional trap instructions to avoid inserting207// block terminators in the middle of this block.208builder.ins().trapz(null, TRAP_INTERNAL_ASSERT);209}210null211} else {212func_ref213};214215// Convert the raw `funcref` into a `FuncRefTableId` for use in the216// GC heap.217let call_inst = builder218.ins()219.call(intern_func_ref_for_gc_heap, &[vmctx, func_ref]);220let func_ref_id = builder.func.dfg.first_result(call_inst);221let func_ref_id = builder.ins().ireduce(ir::types::I32, func_ref_id);222223// Store the id in the field.224builder.ins().store(flags, func_ref_id, field_addr, 0);225226Ok(())227}228229fn write_field_at_addr(230func_env: &mut FuncEnvironment<'_>,231builder: &mut FunctionBuilder<'_>,232field_ty: WasmStorageType,233field_addr: ir::Value,234new_val: ir::Value,235) -> WasmResult<()> {236// Data inside GC objects is always little endian.237let flags = ir::MemFlags::trusted().with_endianness(ir::Endianness::Little);238239match field_ty {240WasmStorageType::I8 => {241builder.ins().istore8(flags, new_val, field_addr, 0);242}243WasmStorageType::I16 => {244builder.ins().istore16(flags, new_val, field_addr, 0);245}246WasmStorageType::Val(WasmValType::Ref(r)) if r.heap_type.top() == WasmHeapTopType::Func => {247write_func_ref_at_addr(func_env, builder, r, flags, field_addr, new_val)?;248}249WasmStorageType::Val(WasmValType::Ref(r)) => {250gc_compiler(func_env)?251.translate_write_gc_reference(func_env, builder, r, field_addr, new_val, flags)?;252}253WasmStorageType::Val(_) => {254assert_eq!(255builder.func.dfg.value_type(new_val).bytes(),256wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty)257);258builder.ins().store(flags, new_val, field_addr, 0);259}260}261Ok(())262}263264pub fn translate_struct_new(265func_env: &mut FuncEnvironment<'_>,266builder: &mut FunctionBuilder<'_>,267struct_type_index: TypeIndex,268fields: &[ir::Value],269) -> WasmResult<ir::Value> {270gc_compiler(func_env)?.alloc_struct(func_env, builder, struct_type_index, &fields)271}272273fn default_value(274cursor: &mut FuncCursor,275func_env: &FuncEnvironment<'_>,276ty: &WasmStorageType,277) -> ir::Value {278match ty {279WasmStorageType::I8 | WasmStorageType::I16 => cursor.ins().iconst(ir::types::I32, 0),280WasmStorageType::Val(v) => match v {281WasmValType::I32 => cursor.ins().iconst(ir::types::I32, 0),282WasmValType::I64 => cursor.ins().iconst(ir::types::I64, 0),283WasmValType::F32 => cursor.ins().f32const(0.0),284WasmValType::F64 => cursor.ins().f64const(0.0),285WasmValType::V128 => {286let c = cursor.func.dfg.constants.insert(vec![0; 16].into());287cursor.ins().vconst(ir::types::I8X16, c)288}289WasmValType::Ref(r) => {290assert!(r.nullable);291let (ty, needs_stack_map) = func_env.reference_type(r.heap_type);292293// NB: The collector doesn't need to know about null references.294let _ = needs_stack_map;295296cursor.ins().iconst(ty, 0)297}298},299}300}301302pub fn translate_struct_new_default(303func_env: &mut FuncEnvironment<'_>,304builder: &mut FunctionBuilder<'_>,305struct_type_index: TypeIndex,306) -> WasmResult<ir::Value> {307let interned_ty = func_env.module.types[struct_type_index].unwrap_module_type_index();308let struct_ty = func_env.types.unwrap_struct(interned_ty)?;309let fields = struct_ty310.fields311.iter()312.map(|f| default_value(&mut builder.cursor(), func_env, &f.element_type))313.collect::<StructFieldsVec>();314gc_compiler(func_env)?.alloc_struct(func_env, builder, struct_type_index, &fields)315}316317pub fn translate_struct_get(318func_env: &mut FuncEnvironment<'_>,319builder: &mut FunctionBuilder<'_>,320struct_type_index: TypeIndex,321field_index: u32,322struct_ref: ir::Value,323extension: Option<Extension>,324) -> WasmResult<ir::Value> {325log::trace!(326"translate_struct_get({struct_type_index:?}, {field_index:?}, {struct_ref:?}, {extension:?})"327);328329// TODO: If we know we have a `(ref $my_struct)` here, instead of maybe a330// `(ref null $my_struct)`, we could omit the `trapz`. But plumbing that331// type info from `wasmparser` and through to here is a bit funky.332func_env.trapz(builder, struct_ref, crate::TRAP_NULL_REFERENCE);333334let field_index = usize::try_from(field_index).unwrap();335let interned_type_index = func_env.module.types[struct_type_index].unwrap_module_type_index();336337let struct_layout = func_env.struct_or_exn_layout(interned_type_index);338let struct_size = struct_layout.size;339340let field_offset = struct_layout.fields[field_index].offset;341let field_ty = &func_env.types.unwrap_struct(interned_type_index)?.fields[field_index];342let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty.element_type);343assert!(field_offset + field_size <= struct_size);344345let field_addr = func_env.prepare_gc_ref_access(346builder,347struct_ref,348BoundsCheck::StaticObjectField {349offset: field_offset,350access_size: u8::try_from(field_size).unwrap(),351object_size: struct_size,352},353);354355let result = read_field_at_addr(356func_env,357builder,358field_ty.element_type,359field_addr,360extension,361);362log::trace!("translate_struct_get(..) -> {result:?}");363result364}365366pub fn translate_struct_set(367func_env: &mut FuncEnvironment<'_>,368builder: &mut FunctionBuilder<'_>,369struct_type_index: TypeIndex,370field_index: u32,371struct_ref: ir::Value,372new_val: ir::Value,373) -> WasmResult<()> {374log::trace!(375"translate_struct_set({struct_type_index:?}, {field_index:?}, struct_ref: {struct_ref:?}, new_val: {new_val:?})"376);377378// TODO: See comment in `translate_struct_get` about the `trapz`.379func_env.trapz(builder, struct_ref, crate::TRAP_NULL_REFERENCE);380381let field_index = usize::try_from(field_index).unwrap();382let interned_type_index = func_env.module.types[struct_type_index].unwrap_module_type_index();383384let struct_layout = func_env.struct_or_exn_layout(interned_type_index);385let struct_size = struct_layout.size;386387let field_offset = struct_layout.fields[field_index].offset;388let field_ty = &func_env.types.unwrap_struct(interned_type_index)?.fields[field_index];389let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty.element_type);390assert!(field_offset + field_size <= struct_size);391392let field_addr = func_env.prepare_gc_ref_access(393builder,394struct_ref,395BoundsCheck::StaticObjectField {396offset: field_offset,397access_size: u8::try_from(field_size).unwrap(),398object_size: struct_size,399},400);401402write_field_at_addr(403func_env,404builder,405field_ty.element_type,406field_addr,407new_val,408)?;409410log::trace!("translate_struct_set: finished");411Ok(())412}413414pub fn translate_exn_unbox(415func_env: &mut FuncEnvironment<'_>,416builder: &mut FunctionBuilder<'_>,417tag_index: TagIndex,418exn_ref: ir::Value,419) -> WasmResult<SmallVec<[ir::Value; 4]>> {420log::trace!("translate_exn_unbox({tag_index:?}, {exn_ref:?})");421422// We know that the `exn_ref` is not null because we reach this423// operation only in catch blocks, and throws are initiated from424// runtime code that checks for nulls first.425426// Get the GcExceptionLayout associated with this tag's427// function type, and generate loads for each field.428let exception_ty_idx = func_env429.exception_type_from_tag(tag_index)430.unwrap_module_type_index();431let exception_ty = func_env.types.unwrap_exn(exception_ty_idx)?;432let exn_layout = func_env.struct_or_exn_layout(exception_ty_idx);433let exn_size = exn_layout.size;434435// Gather accesses first because these require a borrow on436// `func_env`, which we later mutate below via437// `prepare_gc_ref_access()`.438let mut accesses: SmallVec<[_; 4]> = smallvec![];439for (field_ty, field_layout) in exception_ty.fields.iter().zip(exn_layout.fields.iter()) {440accesses.push((field_layout.offset, field_ty.element_type));441}442443let mut result = smallvec![];444for (field_offset, field_ty) in accesses {445let field_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&field_ty);446assert!(field_offset + field_size <= exn_size);447let field_addr = func_env.prepare_gc_ref_access(448builder,449exn_ref,450BoundsCheck::StaticObjectField {451offset: field_offset,452access_size: u8::try_from(field_size).unwrap(),453object_size: exn_size,454},455);456457let value = read_field_at_addr(func_env, builder, field_ty, field_addr, None)?;458result.push(value);459}460461log::trace!("translate_exn_unbox(..) -> {result:?}");462Ok(result)463}464465pub fn translate_exn_throw(466func_env: &mut FuncEnvironment<'_>,467builder: &mut FunctionBuilder<'_>,468tag_index: TagIndex,469args: &[ir::Value],470handlers: impl IntoIterator<Item = (Option<ExceptionTag>, Block)>,471) -> WasmResult<()> {472let (instance_id, defined_tag_id) = func_env.get_instance_and_tag(builder, tag_index);473let exnref = gc_compiler(func_env)?.alloc_exn(474func_env,475builder,476tag_index,477args,478instance_id,479defined_tag_id,480)?;481translate_exn_throw_ref(func_env, builder, exnref, handlers)482}483484pub fn translate_exn_throw_ref(485func_env: &mut FuncEnvironment<'_>,486builder: &mut FunctionBuilder<'_>,487exnref: ir::Value,488handlers: impl IntoIterator<Item = (Option<ExceptionTag>, Block)>,489) -> WasmResult<()> {490let builtin = func_env.builtin_functions.throw_ref(builder.func);491let sig = builder.func.dfg.ext_funcs[builtin].signature;492let vmctx = func_env.vmctx_val(&mut builder.cursor());493494// Generate a `try_call` with handlers from the current495// stack. This libcall is unique among libcall implementations of496// opcodes: we know the others will not throw, but `throw_ref`'s497// entire purpose is to throw. So if there are any handlers in the498// local function body, we need to attach them to this callsite499// like any other.500let continuation = builder.create_block();501let current_block = builder.current_block().unwrap();502builder.insert_block_after(continuation, current_block);503let continuation_call = builder.func.dfg.block_call(continuation, &[]);504let mut table_items = vec![ExceptionTableItem::Context(vmctx)];505for (tag, block) in handlers {506let block_call = builder507.func508.dfg509.block_call(block, &[BlockArg::TryCallExn(0)]);510table_items.push(match tag {511Some(tag) => ExceptionTableItem::Tag(tag, block_call),512None => ExceptionTableItem::Default(block_call),513});514}515let etd = ExceptionTableData::new(sig, continuation_call, table_items);516let et = builder.func.dfg.exception_tables.push(etd);517518builder.ins().try_call(builtin, &[vmctx, exnref], et);519520builder.switch_to_block(continuation);521builder.seal_block(continuation);522func_env.trap(builder, crate::TRAP_UNREACHABLE);523524Ok(())525}526527pub fn translate_array_new(528func_env: &mut FuncEnvironment<'_>,529builder: &mut FunctionBuilder,530array_type_index: TypeIndex,531elem: ir::Value,532len: ir::Value,533) -> WasmResult<ir::Value> {534log::trace!("translate_array_new({array_type_index:?}, {elem:?}, {len:?})");535let result = gc_compiler(func_env)?.alloc_array(536func_env,537builder,538array_type_index,539ArrayInit::Fill { elem, len },540)?;541log::trace!("translate_array_new(..) -> {result:?}");542Ok(result)543}544545pub fn translate_array_new_default(546func_env: &mut FuncEnvironment<'_>,547builder: &mut FunctionBuilder,548array_type_index: TypeIndex,549len: ir::Value,550) -> WasmResult<ir::Value> {551log::trace!("translate_array_new_default({array_type_index:?}, {len:?})");552553let interned_ty = func_env.module.types[array_type_index].unwrap_module_type_index();554let array_ty = func_env.types.unwrap_array(interned_ty)?;555let elem = default_value(&mut builder.cursor(), func_env, &array_ty.0.element_type);556let result = gc_compiler(func_env)?.alloc_array(557func_env,558builder,559array_type_index,560ArrayInit::Fill { elem, len },561)?;562log::trace!("translate_array_new_default(..) -> {result:?}");563Ok(result)564}565566pub fn translate_array_new_fixed(567func_env: &mut FuncEnvironment<'_>,568builder: &mut FunctionBuilder,569array_type_index: TypeIndex,570elems: &[ir::Value],571) -> WasmResult<ir::Value> {572log::trace!("translate_array_new_fixed({array_type_index:?}, {elems:?})");573let result = gc_compiler(func_env)?.alloc_array(574func_env,575builder,576array_type_index,577ArrayInit::Elems(elems),578)?;579log::trace!("translate_array_new_fixed(..) -> {result:?}");580Ok(result)581}582583impl ArrayInit<'_> {584/// Get the length (as an `i32`-typed `ir::Value`) of these array elements.585#[cfg_attr(586not(any(feature = "gc-drc", feature = "gc-null")),587expect(dead_code, reason = "easier to define")588)]589fn len(self, pos: &mut FuncCursor) -> ir::Value {590match self {591ArrayInit::Fill { len, .. } => len,592ArrayInit::Elems(e) => {593let len = u32::try_from(e.len()).unwrap();594pos.ins().iconst(ir::types::I32, i64::from(len))595}596}597}598599/// Initialize a newly-allocated array's elements.600#[cfg_attr(601not(any(feature = "gc-drc", feature = "gc-null")),602expect(dead_code, reason = "easier to define")603)]604fn initialize(605self,606func_env: &mut FuncEnvironment<'_>,607builder: &mut FunctionBuilder<'_>,608interned_type_index: ModuleInternedTypeIndex,609base_size: u32,610size: ir::Value,611elems_addr: ir::Value,612mut init_field: impl FnMut(613&mut FuncEnvironment<'_>,614&mut FunctionBuilder<'_>,615WasmStorageType,616ir::Value,617ir::Value,618) -> WasmResult<()>,619) -> WasmResult<()> {620log::trace!(621"initialize_array({interned_type_index:?}, {base_size:?}, {size:?}, {elems_addr:?})"622);623624assert!(!func_env.types[interned_type_index].composite_type.shared);625let array_ty = func_env.types[interned_type_index]626.composite_type627.inner628.unwrap_array();629let elem_ty = array_ty.0.element_type;630let elem_size = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&elem_ty);631let pointer_type = func_env.pointer_type();632let elem_size = builder.ins().iconst(pointer_type, i64::from(elem_size));633match self {634ArrayInit::Elems(elems) => {635let mut elem_addr = elems_addr;636for val in elems {637init_field(func_env, builder, elem_ty, elem_addr, *val)?;638elem_addr = builder.ins().iadd(elem_addr, elem_size);639}640}641ArrayInit::Fill { elem, len: _ } => {642// Compute the end address of the elements.643let base_size = builder.ins().iconst(pointer_type, i64::from(base_size));644let array_addr = builder.ins().isub(elems_addr, base_size);645let size = uextend_i32_to_pointer_type(builder, pointer_type, size);646let elems_end = builder.ins().iadd(array_addr, size);647648emit_array_fill_impl(649func_env,650builder,651elems_addr,652elem_size,653elems_end,654|func_env, builder, elem_addr| {655init_field(func_env, builder, elem_ty, elem_addr, elem)656},657)?;658}659}660log::trace!("initialize_array: finished");661Ok(())662}663}664665fn emit_array_fill_impl(666func_env: &mut FuncEnvironment<'_>,667builder: &mut FunctionBuilder<'_>,668elem_addr: ir::Value,669elem_size: ir::Value,670fill_end: ir::Value,671mut emit_elem_write: impl FnMut(672&mut FuncEnvironment<'_>,673&mut FunctionBuilder<'_>,674ir::Value,675) -> WasmResult<()>,676) -> WasmResult<()> {677log::trace!(678"emit_array_fill_impl(elem_addr: {elem_addr:?}, elem_size: {elem_size:?}, fill_end: {fill_end:?})"679);680681let pointer_ty = func_env.pointer_type();682683assert_eq!(builder.func.dfg.value_type(elem_addr), pointer_ty);684assert_eq!(builder.func.dfg.value_type(elem_size), pointer_ty);685assert_eq!(builder.func.dfg.value_type(fill_end), pointer_ty);686687// Loop to fill the elements, emitting the equivalent of the following688// pseudo-CLIF:689//690// current_block:691// ...692// jump loop_header_block(elem_addr)693//694// loop_header_block(elem_addr: i32):695// done = icmp eq elem_addr, fill_end696// brif done, continue_block, loop_body_block697//698// loop_body_block:699// emit_elem_write()700// next_elem_addr = iadd elem_addr, elem_size701// jump loop_header_block(next_elem_addr)702//703// continue_block:704// ...705706let current_block = builder.current_block().unwrap();707let loop_header_block = builder.create_block();708let loop_body_block = builder.create_block();709let continue_block = builder.create_block();710711builder.ensure_inserted_block();712builder.insert_block_after(loop_header_block, current_block);713builder.insert_block_after(loop_body_block, loop_header_block);714builder.insert_block_after(continue_block, loop_body_block);715716// Current block: jump to the loop header block with the first element's717// address.718builder.ins().jump(loop_header_block, &[elem_addr.into()]);719720// Loop header block: check if we're done, then jump to either the continue721// block or the loop body block.722builder.switch_to_block(loop_header_block);723builder.append_block_param(loop_header_block, pointer_ty);724log::trace!("emit_array_fill_impl: loop header");725func_env.translate_loop_header(builder)?;726let elem_addr = builder.block_params(loop_header_block)[0];727let done = builder.ins().icmp(IntCC::Equal, elem_addr, fill_end);728builder729.ins()730.brif(done, continue_block, &[], loop_body_block, &[]);731732// Loop body block: write the value to the current element, compute the next733// element's address, and then jump back to the loop header block.734builder.switch_to_block(loop_body_block);735log::trace!("emit_array_fill_impl: loop body");736emit_elem_write(func_env, builder, elem_addr)?;737let next_elem_addr = builder.ins().iadd(elem_addr, elem_size);738builder739.ins()740.jump(loop_header_block, &[next_elem_addr.into()]);741742// Continue...743builder.switch_to_block(continue_block);744log::trace!("emit_array_fill_impl: finished");745builder.seal_block(loop_header_block);746builder.seal_block(loop_body_block);747builder.seal_block(continue_block);748Ok(())749}750751pub fn translate_array_fill(752func_env: &mut FuncEnvironment<'_>,753builder: &mut FunctionBuilder<'_>,754array_type_index: TypeIndex,755array_ref: ir::Value,756index: ir::Value,757value: ir::Value,758n: ir::Value,759) -> WasmResult<()> {760log::trace!(761"translate_array_fill({array_type_index:?}, {array_ref:?}, {index:?}, {value:?}, {n:?})"762);763764let len = translate_array_len(func_env, builder, array_ref)?;765766// Check that the full range of elements we want to fill is within bounds.767let end_index = func_env.uadd_overflow_trap(builder, index, n, crate::TRAP_ARRAY_OUT_OF_BOUNDS);768let out_of_bounds = builder769.ins()770.icmp(IntCC::UnsignedGreaterThan, end_index, len);771func_env.trapnz(builder, out_of_bounds, crate::TRAP_ARRAY_OUT_OF_BOUNDS);772773// Get the address of the first element we want to fill.774let interned_type_index = func_env.module.types[array_type_index].unwrap_module_type_index();775let ArraySizeInfo {776obj_size,777one_elem_size,778base_size,779} = emit_array_size_info(func_env, builder, interned_type_index, len);780let offset_in_elems = builder.ins().imul(index, one_elem_size);781let obj_offset = builder.ins().iadd(base_size, offset_in_elems);782let elem_addr = func_env.prepare_gc_ref_access(783builder,784array_ref,785BoundsCheck::DynamicObjectField {786offset: obj_offset,787object_size: obj_size,788},789);790791// Calculate the end address, just after the filled region.792let fill_size = builder.ins().imul(n, one_elem_size);793let fill_size = uextend_i32_to_pointer_type(builder, func_env.pointer_type(), fill_size);794let fill_end = builder.ins().iadd(elem_addr, fill_size);795796let one_elem_size =797uextend_i32_to_pointer_type(builder, func_env.pointer_type(), one_elem_size);798799let result = emit_array_fill_impl(800func_env,801builder,802elem_addr,803one_elem_size,804fill_end,805|func_env, builder, elem_addr| {806let elem_ty = func_env807.types808.unwrap_array(interned_type_index)?809.0810.element_type;811write_field_at_addr(func_env, builder, elem_ty, elem_addr, value)812},813);814log::trace!("translate_array_fill(..) -> {result:?}");815result816}817818pub fn translate_array_len(819func_env: &mut FuncEnvironment<'_>,820builder: &mut FunctionBuilder,821array_ref: ir::Value,822) -> WasmResult<ir::Value> {823log::trace!("translate_array_len({array_ref:?})");824825func_env.trapz(builder, array_ref, crate::TRAP_NULL_REFERENCE);826827let len_offset = gc_compiler(func_env)?.layouts().array_length_field_offset();828let len_field = func_env.prepare_gc_ref_access(829builder,830array_ref,831// Note: We can't bounds check the whole array object's size because we832// don't know its length yet. Chicken and egg problem.833BoundsCheck::StaticOffset {834offset: len_offset,835access_size: u8::try_from(ir::types::I32.bytes()).unwrap(),836},837);838let result = builder.ins().load(839ir::types::I32,840ir::MemFlags::trusted().with_readonly(),841len_field,8420,843);844log::trace!("translate_array_len(..) -> {result:?}");845Ok(result)846}847848struct ArraySizeInfo {849/// The `i32` size of the whole array object, in bytes.850obj_size: ir::Value,851852/// The `i32` size of each one of the array's elements, in bytes.853one_elem_size: ir::Value,854855/// The `i32` size of the array's base object, in bytes. This is also the856/// offset from the start of the array object to its elements.857base_size: ir::Value,858}859860/// Emit code to get the dynamic size (in bytes) of a whole array object, along861/// with some other related bits.862fn emit_array_size_info(863func_env: &mut FuncEnvironment<'_>,864builder: &mut FunctionBuilder<'_>,865array_type_index: ModuleInternedTypeIndex,866// `i32` value containing the array's length.867array_len: ir::Value,868) -> ArraySizeInfo {869let array_layout = func_env.array_layout(array_type_index);870871// Note that we check for overflow below because we can't trust the array's872// length: it came from inside the GC heap.873//874// We check for 32-bit multiplication overflow by performing a 64-bit875// multiplication and testing the high bits.876let one_elem_size = builder877.ins()878.iconst(ir::types::I64, i64::from(array_layout.elem_size));879let array_len = builder.ins().uextend(ir::types::I64, array_len);880let all_elems_size = builder.ins().imul(one_elem_size, array_len);881882let high_bits = builder.ins().ushr_imm(all_elems_size, 32);883builder.ins().trapnz(high_bits, TRAP_INTERNAL_ASSERT);884885let all_elems_size = builder.ins().ireduce(ir::types::I32, all_elems_size);886let base_size = builder887.ins()888.iconst(ir::types::I32, i64::from(array_layout.base_size));889let obj_size =890builder891.ins()892.uadd_overflow_trap(all_elems_size, base_size, TRAP_INTERNAL_ASSERT);893894let one_elem_size = builder.ins().ireduce(ir::types::I32, one_elem_size);895896ArraySizeInfo {897obj_size,898one_elem_size,899base_size,900}901}902903/// Get the bounds-checked address of an element in an array.904///905/// The emitted code will trap if `index >= array.length`.906///907/// Returns the `ir::Value` containing the address of the `index`th element in908/// the array. You may read or write a value of the array's element type at this909/// address. You may not use it for any other kind of access, nor reuse this910/// value across GC safepoints.911fn array_elem_addr(912func_env: &mut FuncEnvironment<'_>,913builder: &mut FunctionBuilder<'_>,914array_type_index: ModuleInternedTypeIndex,915array_ref: ir::Value,916index: ir::Value,917) -> ir::Value {918// First, assert that `index < array.length`.919//920// This check is visible at the Wasm-semantics level.921//922// TODO: We should emit spectre-safe bounds checks for array accesses (if923// configured) but we don't currently have a great way to do that here. The924// proper solution is to use linear memories to back GC heaps and reuse the925// code in `bounds_check.rs` to implement these bounds checks. That is all926// planned, but not yet implemented.927928let len = translate_array_len(func_env, builder, array_ref).unwrap();929930let in_bounds = builder.ins().icmp(IntCC::UnsignedLessThan, index, len);931func_env.trapz(builder, in_bounds, crate::TRAP_ARRAY_OUT_OF_BOUNDS);932933// Compute the size (in bytes) of the whole array object.934let ArraySizeInfo {935obj_size,936one_elem_size,937base_size,938} = emit_array_size_info(func_env, builder, array_type_index, len);939940// Compute the offset of the `index`th element within the array object.941//942// NB: no need to check for overflow here, since at this point we know that943// `len * elem_size + base_size` did not overflow and `i < len`.944let offset_in_elems = builder.ins().imul(index, one_elem_size);945let offset_in_array = builder.ins().iadd(offset_in_elems, base_size);946947// Finally, use the object size and element offset we just computed to948// perform our implementation-internal bounds checks.949//950// Checking the whole object's size, rather than the `index`th element's951// size allows these bounds checks to be deduplicated across repeated952// accesses to the same array at different indices.953//954// This check should not be visible to Wasm, and serve to protect us from955// our own implementation bugs. The goal is to keep any potential widgets956// confined within the GC heap, and turn what would otherwise be a security957// vulnerability into a simple bug.958//959// TODO: Ideally we should fold the first Wasm-visible bounds check into960// this internal bounds check, so that we aren't performing multiple,961// redundant bounds checks. But we should figure out how to do this in a way962// that doesn't defeat the object-size bounds checking's deduplication963// mentioned above.964func_env.prepare_gc_ref_access(965builder,966array_ref,967BoundsCheck::DynamicObjectField {968offset: offset_in_array,969object_size: obj_size,970},971)972}973974pub fn translate_array_get(975func_env: &mut FuncEnvironment<'_>,976builder: &mut FunctionBuilder,977array_type_index: TypeIndex,978array_ref: ir::Value,979index: ir::Value,980extension: Option<Extension>,981) -> WasmResult<ir::Value> {982log::trace!("translate_array_get({array_type_index:?}, {array_ref:?}, {index:?})");983984let array_type_index = func_env.module.types[array_type_index].unwrap_module_type_index();985let elem_addr = array_elem_addr(func_env, builder, array_type_index, array_ref, index);986987let array_ty = func_env.types.unwrap_array(array_type_index)?;988let elem_ty = array_ty.0.element_type;989990let result = read_field_at_addr(func_env, builder, elem_ty, elem_addr, extension)?;991log::trace!("translate_array_get(..) -> {result:?}");992Ok(result)993}994995pub fn translate_array_set(996func_env: &mut FuncEnvironment<'_>,997builder: &mut FunctionBuilder,998array_type_index: TypeIndex,999array_ref: ir::Value,1000index: ir::Value,1001value: ir::Value,1002) -> WasmResult<()> {1003log::trace!("translate_array_set({array_type_index:?}, {array_ref:?}, {index:?}, {value:?})");10041005let array_type_index = func_env.module.types[array_type_index].unwrap_module_type_index();1006let elem_addr = array_elem_addr(func_env, builder, array_type_index, array_ref, index);10071008let array_ty = func_env.types.unwrap_array(array_type_index)?;1009let elem_ty = array_ty.0.element_type;10101011write_field_at_addr(func_env, builder, elem_ty, elem_addr, value)?;10121013log::trace!("translate_array_set: finished");1014Ok(())1015}10161017pub fn translate_ref_test(1018func_env: &mut FuncEnvironment<'_>,1019builder: &mut FunctionBuilder<'_>,1020test_ty: WasmRefType,1021val: ir::Value,1022val_ty: WasmRefType,1023) -> WasmResult<ir::Value> {1024log::trace!("translate_ref_test({test_ty:?}, {val:?})");10251026// First special case: testing for references to bottom types.1027if test_ty.heap_type.is_bottom() {1028let result = if test_ty.nullable {1029// All null references (within the same type hierarchy) match null1030// references to the bottom type.1031func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?1032} else {1033// `ref.test` is always false for non-nullable bottom types, as the1034// bottom types are uninhabited.1035builder.ins().iconst(ir::types::I32, 0)1036};1037log::trace!("translate_ref_test(..) -> {result:?}");1038return Ok(result);1039}10401041// And because `ref.test heap_ty` is only valid on operands whose type is in1042// the same type hierarchy as `heap_ty`, if `heap_ty` is its hierarchy's top1043// type, we only need to worry about whether we are testing for nullability1044// or not.1045if test_ty.heap_type.is_top() {1046let result = if test_ty.nullable {1047builder.ins().iconst(ir::types::I32, 1)1048} else {1049let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;1050let zero = builder.ins().iconst(ir::types::I32, 0);1051let one = builder.ins().iconst(ir::types::I32, 1);1052builder.ins().select(is_null, zero, one)1053};1054log::trace!("translate_ref_test(..) -> {result:?}");1055return Ok(result);1056}10571058// `i31ref`s are a little interesting because they don't point to GC1059// objects; we test the bit pattern of the reference itself.1060if test_ty.heap_type == WasmHeapType::I31 {1061let i31_mask = builder.ins().iconst(1062ir::types::I32,1063i64::from(wasmtime_environ::I31_DISCRIMINANT),1064);1065let is_i31 = builder.ins().band(val, i31_mask);1066let result = if test_ty.nullable {1067let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;1068builder.ins().bor(is_null, is_i31)1069} else {1070is_i311071};1072log::trace!("translate_ref_test(..) -> {result:?}");1073return Ok(result);1074}10751076// Otherwise, in the general case, we need to inspect our given object's1077// actual type, which also requires null-checking and i31-checking it.10781079let is_any_hierarchy = test_ty.heap_type.top() == WasmHeapTopType::Any;10801081let non_null_block = builder.create_block();1082let non_null_non_i31_block = builder.create_block();1083let continue_block = builder.create_block();10841085// Current block: check if the reference is null and branch appropriately.1086let is_null = func_env.translate_ref_is_null(builder.cursor(), val, val_ty)?;1087let result_when_is_null = builder1088.ins()1089.iconst(ir::types::I32, test_ty.nullable as i64);1090builder.ins().brif(1091is_null,1092continue_block,1093&[result_when_is_null.into()],1094non_null_block,1095&[],1096);10971098// Non-null block: We know the GC ref is non-null, but we need to also check1099// for `i31` references that don't point to GC objects.1100builder.switch_to_block(non_null_block);1101log::trace!("translate_ref_test: non-null ref block");1102if is_any_hierarchy {1103let i31_mask = builder.ins().iconst(1104ir::types::I32,1105i64::from(wasmtime_environ::I31_DISCRIMINANT),1106);1107let is_i31 = builder.ins().band(val, i31_mask);1108// If it is an `i31`, then create the result value based on whether we1109// want `i31`s to pass the test or not.1110let result_when_is_i31 = builder.ins().iconst(1111ir::types::I32,1112matches!(1113test_ty.heap_type,1114WasmHeapType::Any | WasmHeapType::Eq | WasmHeapType::I311115) as i64,1116);1117builder.ins().brif(1118is_i31,1119continue_block,1120&[result_when_is_i31.into()],1121non_null_non_i31_block,1122&[],1123);1124} else {1125// If we aren't testing the `any` hierarchy, the reference cannot be an1126// `i31ref`. Jump directly to the non-null and non-i31 block; rely on1127// branch folding during lowering to clean this up.1128builder.ins().jump(non_null_non_i31_block, &[]);1129}11301131// Non-null and non-i31 block: Read the actual `VMGcKind` or1132// `VMSharedTypeIndex` out of the object's header and check whether it1133// matches the expected type.1134builder.switch_to_block(non_null_non_i31_block);1135log::trace!("translate_ref_test: non-null and non-i31 ref block");1136let check_header_kind = |func_env: &mut FuncEnvironment<'_>,1137builder: &mut FunctionBuilder,1138val: ir::Value,1139expected_kind: VMGcKind|1140-> ir::Value {1141let kind_addr = func_env.prepare_gc_ref_access(1142builder,1143val,1144BoundsCheck::StaticObjectField {1145offset: wasmtime_environ::VM_GC_HEADER_KIND_OFFSET,1146access_size: wasmtime_environ::VM_GC_KIND_SIZE,1147object_size: wasmtime_environ::VM_GC_HEADER_SIZE,1148},1149);1150let actual_kind = builder.ins().load(1151ir::types::I32,1152ir::MemFlags::trusted().with_readonly(),1153kind_addr,11540,1155);1156let expected_kind = builder1157.ins()1158.iconst(ir::types::I32, i64::from(expected_kind.as_u32()));1159// Inline version of `VMGcKind::matches`.1160let and = builder.ins().band(actual_kind, expected_kind);1161let kind_matches = builder1162.ins()1163.icmp(ir::condcodes::IntCC::Equal, and, expected_kind);1164builder.ins().uextend(ir::types::I32, kind_matches)1165};1166let result = match test_ty.heap_type {1167WasmHeapType::Any1168| WasmHeapType::None1169| WasmHeapType::Extern1170| WasmHeapType::NoExtern1171| WasmHeapType::Func1172| WasmHeapType::NoFunc1173| WasmHeapType::Cont1174| WasmHeapType::NoCont1175| WasmHeapType::Exn1176| WasmHeapType::NoExn1177| WasmHeapType::I31 => unreachable!("handled top, bottom, and i31 types above"),11781179// For these abstract but non-top and non-bottom types, we check the1180// `VMGcKind` that is in the object's header.1181WasmHeapType::Eq => check_header_kind(func_env, builder, val, VMGcKind::EqRef),1182WasmHeapType::Struct => check_header_kind(func_env, builder, val, VMGcKind::StructRef),1183WasmHeapType::Array => check_header_kind(func_env, builder, val, VMGcKind::ArrayRef),11841185// For concrete types, we need to do a full subtype check between the1186// `VMSharedTypeIndex` in the object's header and the1187// `ModuleInternedTypeIndex` we have here.1188//1189// TODO: This check should ideally be done inline, but we don't have a1190// good way to access the `TypeRegistry`'s supertypes arrays from Wasm1191// code at the moment.1192WasmHeapType::ConcreteArray(ty)1193| WasmHeapType::ConcreteStruct(ty)1194| WasmHeapType::ConcreteExn(ty) => {1195let expected_interned_ty = ty.unwrap_module_type_index();1196let expected_shared_ty =1197func_env.module_interned_to_shared_ty(&mut builder.cursor(), expected_interned_ty);11981199let ty_addr = func_env.prepare_gc_ref_access(1200builder,1201val,1202BoundsCheck::StaticOffset {1203offset: wasmtime_environ::VM_GC_HEADER_TYPE_INDEX_OFFSET,1204access_size: func_env.offsets.size_of_vmshared_type_index(),1205},1206);1207let actual_shared_ty = builder.ins().load(1208ir::types::I32,1209ir::MemFlags::trusted().with_readonly(),1210ty_addr,12110,1212);12131214func_env.is_subtype(builder, actual_shared_ty, expected_shared_ty)1215}12161217// Same as for concrete arrays and structs except that a `VMFuncRef`1218// doesn't begin with a `VMGcHeader` and is a raw pointer rather than GC1219// heap index.1220WasmHeapType::ConcreteFunc(ty) => {1221let expected_interned_ty = ty.unwrap_module_type_index();1222let expected_shared_ty =1223func_env.module_interned_to_shared_ty(&mut builder.cursor(), expected_interned_ty);12241225let actual_shared_ty = func_env.load_funcref_type_index(1226&mut builder.cursor(),1227ir::MemFlags::trusted().with_readonly(),1228val,1229);12301231func_env.is_subtype(builder, actual_shared_ty, expected_shared_ty)1232}1233WasmHeapType::ConcreteCont(_) => {1234// TODO(#10248) GC integration for stack switching1235return Err(wasmtime_environ::WasmError::Unsupported(1236"Stack switching feature not compatible with GC, yet".to_string(),1237));1238}1239};1240builder.ins().jump(continue_block, &[result.into()]);12411242// Control flow join point with the result.1243builder.switch_to_block(continue_block);1244let result = builder.append_block_param(continue_block, ir::types::I32);1245log::trace!("translate_ref_test(..) -> {result:?}");12461247builder.seal_block(non_null_block);1248builder.seal_block(non_null_non_i31_block);1249builder.seal_block(continue_block);12501251Ok(result)1252}12531254fn uextend_i32_to_pointer_type(1255builder: &mut FunctionBuilder,1256pointer_type: ir::Type,1257value: ir::Value,1258) -> ir::Value {1259assert_eq!(builder.func.dfg.value_type(value), ir::types::I32);1260match pointer_type {1261ir::types::I32 => value,1262ir::types::I64 => builder.ins().uextend(ir::types::I64, value),1263_ => unreachable!(),1264}1265}12661267/// Emit CLIF to compute an array object's total size, given the dynamic length1268/// in its initialization.1269///1270/// Traps if the size overflows.1271#[cfg_attr(1272not(any(feature = "gc-drc", feature = "gc-null")),1273expect(dead_code, reason = "easier to define")1274)]1275fn emit_array_size(1276func_env: &mut FuncEnvironment<'_>,1277builder: &mut FunctionBuilder<'_>,1278array_layout: &GcArrayLayout,1279len: ir::Value,1280) -> ir::Value {1281let base_size = builder1282.ins()1283.iconst(ir::types::I32, i64::from(array_layout.base_size));12841285// `elems_size = len * elem_size`1286//1287// Check for multiplication overflow and trap if it occurs, since that1288// means Wasm is attempting to allocate an array that is larger than our1289// implementation limits. (Note: there is no standard implementation1290// limit for array length beyond `u32::MAX`.)1291//1292// We implement this check by encoding our logically-32-bit operands as1293// i64 values, doing a 64-bit multiplication, and then checking the high1294// 32 bits of the multiplication's result. If the high 32 bits are not1295// all zeros, then the multiplication overflowed.1296debug_assert_eq!(builder.func.dfg.value_type(len), ir::types::I32);1297let len = builder.ins().uextend(ir::types::I64, len);1298let elems_size_64 = builder1299.ins()1300.imul_imm(len, i64::from(array_layout.elem_size));1301let high_bits = builder.ins().ushr_imm(elems_size_64, 32);1302func_env.trapnz(builder, high_bits, crate::TRAP_ALLOCATION_TOO_LARGE);1303let elems_size = builder.ins().ireduce(ir::types::I32, elems_size_64);13041305// And if adding the base size and elements size overflows, then the1306// allocation is too large.1307let size = func_env.uadd_overflow_trap(1308builder,1309base_size,1310elems_size,1311crate::TRAP_ALLOCATION_TOO_LARGE,1312);13131314size1315}13161317/// Common helper for struct-field initialization that can be reused across1318/// collectors.1319#[cfg_attr(1320not(any(feature = "gc-drc", feature = "gc-null")),1321expect(dead_code, reason = "easier to define")1322)]1323fn initialize_struct_fields(1324func_env: &mut FuncEnvironment<'_>,1325builder: &mut FunctionBuilder<'_>,1326struct_ty: ModuleInternedTypeIndex,1327raw_ptr_to_struct: ir::Value,1328field_values: &[ir::Value],1329mut init_field: impl FnMut(1330&mut FuncEnvironment<'_>,1331&mut FunctionBuilder<'_>,1332WasmStorageType,1333ir::Value,1334ir::Value,1335) -> WasmResult<()>,1336) -> WasmResult<()> {1337let struct_layout = func_env.struct_or_exn_layout(struct_ty);1338let struct_size = struct_layout.size;1339let field_offsets: SmallVec<[_; 8]> = struct_layout.fields.iter().map(|f| f.offset).collect();1340assert_eq!(field_offsets.len(), field_values.len());13411342assert!(!func_env.types[struct_ty].composite_type.shared);1343let fields = match &func_env.types[struct_ty].composite_type.inner {1344WasmCompositeInnerType::Struct(s) => &s.fields,1345WasmCompositeInnerType::Exn(e) => &e.fields,1346_ => panic!("Not a struct or exception type"),1347};13481349let field_types: SmallVec<[_; 8]> = fields.iter().cloned().collect();1350assert_eq!(field_types.len(), field_values.len());13511352for ((ty, val), offset) in field_types.into_iter().zip(field_values).zip(field_offsets) {1353let size_of_access = wasmtime_environ::byte_size_of_wasm_ty_in_gc_heap(&ty.element_type);1354assert!(offset + size_of_access <= struct_size);1355let field_addr = builder.ins().iadd_imm(raw_ptr_to_struct, i64::from(offset));1356init_field(func_env, builder, ty.element_type, field_addr, *val)?;1357}13581359Ok(())1360}13611362impl FuncEnvironment<'_> {1363fn gc_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcLayout {1364// Lazily compute and cache the layout.1365if !self.ty_to_gc_layout.contains_key(&type_index) {1366let ty = &self.types[type_index].composite_type;1367let layout = gc_compiler(self)1368.unwrap()1369.layouts()1370.gc_layout(ty)1371.expect("should only call `FuncEnvironment::gc_layout` for GC types");1372self.ty_to_gc_layout.insert(type_index, layout);1373}13741375self.ty_to_gc_layout.get(&type_index).unwrap()1376}13771378/// Get the `GcArrayLayout` for the array type at the given `type_index`.1379fn array_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcArrayLayout {1380self.gc_layout(type_index).unwrap_array()1381}13821383/// Get the `GcStructLayout` for the struct or exception type at the given `type_index`.1384fn struct_or_exn_layout(&mut self, type_index: ModuleInternedTypeIndex) -> &GcStructLayout {1385let result = self.gc_layout(type_index).unwrap_struct();1386result1387}13881389/// Get or create the global for our GC heap's base pointer.1390fn get_gc_heap_base_global(&mut self, func: &mut ir::Function) -> ir::GlobalValue {1391if let Some(base) = self.gc_heap_base {1392return base;1393}13941395let store_context_ptr = self.get_vmstore_context_ptr_global(func);1396let offset = self.offsets.ptr.vmstore_context_gc_heap_base();13971398let mut flags = ir::MemFlags::trusted();1399if !self1400.tunables1401.gc_heap_memory_type()1402.memory_may_move(self.tunables)1403{1404flags.set_readonly();1405flags.set_can_move();1406}14071408let base = func.create_global_value(ir::GlobalValueData::Load {1409base: store_context_ptr,1410offset: Offset32::new(offset.into()),1411global_type: self.pointer_type(),1412flags,1413});14141415self.gc_heap_base = Some(base);1416base1417}14181419/// Get the GC heap's base.1420#[cfg(any(feature = "gc-null", feature = "gc-drc"))]1421fn get_gc_heap_base(&mut self, builder: &mut FunctionBuilder) -> ir::Value {1422let global = self.get_gc_heap_base_global(&mut builder.func);1423builder.ins().global_value(self.pointer_type(), global)1424}14251426fn get_gc_heap_bound_global(&mut self, func: &mut ir::Function) -> ir::GlobalValue {1427if let Some(bound) = self.gc_heap_bound {1428return bound;1429}1430let store_context_ptr = self.get_vmstore_context_ptr_global(func);1431let offset = self.offsets.ptr.vmstore_context_gc_heap_current_length();1432let bound = func.create_global_value(ir::GlobalValueData::Load {1433base: store_context_ptr,1434offset: Offset32::new(offset.into()),1435global_type: self.pointer_type(),1436flags: ir::MemFlags::trusted(),1437});1438self.gc_heap_bound = Some(bound);1439bound1440}14411442/// Get the GC heap's bound.1443#[cfg(feature = "gc-null")]1444fn get_gc_heap_bound(&mut self, builder: &mut FunctionBuilder) -> ir::Value {1445let global = self.get_gc_heap_bound_global(&mut builder.func);1446builder.ins().global_value(self.pointer_type(), global)1447}14481449/// Get or create the `Heap` for our GC heap.1450fn get_gc_heap(&mut self, func: &mut ir::Function) -> Heap {1451if let Some(heap) = self.gc_heap {1452return heap;1453}14541455let base = self.get_gc_heap_base_global(func);1456let bound = self.get_gc_heap_bound_global(func);1457let memory = self.tunables.gc_heap_memory_type();1458let heap = self.heaps.push(HeapData {1459base,1460bound,1461pcc_memory_type: None,1462memory,1463});1464self.gc_heap = Some(heap);1465heap1466}14671468/// Get the raw pointer of `gc_ref[offset]` bounds checked for an access of1469/// `size` bytes.1470///1471/// The given `gc_ref` must be a non-null, non-i31 GC reference.1472///1473/// If `check` is a `BoundsCheck::Object`, then it is the callers1474/// responsibility to ensure that `offset + access_size <= object_size`.1475///1476/// Returns a raw pointer to `gc_ref[offset]` -- not a raw pointer to the GC1477/// object itself (unless `offset` happens to be `0`). This raw pointer may1478/// be used to read or write up to as many bytes as described by `bound`. Do1479/// NOT attempt accesses bytes outside of `bound`; that may lead to1480/// unchecked out-of-bounds accesses.1481///1482/// This method is collector-agnostic.1483fn prepare_gc_ref_access(1484&mut self,1485builder: &mut FunctionBuilder,1486gc_ref: ir::Value,1487bounds_check: BoundsCheck,1488) -> ir::Value {1489log::trace!("prepare_gc_ref_access({gc_ref:?}, {bounds_check:?})");1490assert_eq!(builder.func.dfg.value_type(gc_ref), ir::types::I32);14911492let gc_heap = self.get_gc_heap(&mut builder.func);1493let gc_heap = self.heaps[gc_heap].clone();1494let result = match crate::bounds_checks::bounds_check_and_compute_addr(1495builder,1496self,1497&gc_heap,1498gc_ref,1499bounds_check,1500crate::TRAP_INTERNAL_ASSERT,1501) {1502Reachability::Reachable(v) => v,1503Reachability::Unreachable => {1504// We are now in unreachable code, but we don't want to plumb1505// through a bunch of `Reachability` through all of our callers,1506// so just assert we won't reach here and return `null`1507let null = builder.ins().iconst(self.pointer_type(), 0);1508builder.ins().trapz(null, crate::TRAP_INTERNAL_ASSERT);1509null1510}1511};1512log::trace!("prepare_gc_ref_access(..) -> {result:?}");1513result1514}15151516/// Emit checks (if necessary) for whether the given `gc_ref` is null or is1517/// an `i31ref`.1518///1519/// Takes advantage of static information based on `ty` as to whether the GC1520/// reference is nullable or can ever be an `i31`.1521///1522/// Returns an `ir::Value` that is an `i32` will be non-zero if the GC1523/// reference is null or is an `i31ref`; otherwise, it will be zero.1524///1525/// This method is collector-agnostic.1526#[cfg_attr(1527not(feature = "gc-drc"),1528expect(dead_code, reason = "easier to define")1529)]1530fn gc_ref_is_null_or_i31(1531&mut self,1532builder: &mut FunctionBuilder,1533ty: WasmRefType,1534gc_ref: ir::Value,1535) -> ir::Value {1536assert_eq!(builder.func.dfg.value_type(gc_ref), ir::types::I32);1537assert!(ty.is_vmgcref_type_and_not_i31());15381539let might_be_i31 = match ty.heap_type {1540// If we are definitely dealing with an i31, we shouldn't be1541// emitting dynamic checks for it, and the caller shouldn't call1542// this function. Should have been caught by the assertion at the1543// start of the function.1544WasmHeapType::I31 => unreachable!(),15451546// Could potentially be an i31.1547WasmHeapType::Any | WasmHeapType::Eq => true,15481549// If it is definitely a struct, array, or uninhabited type, then it1550// is definitely not an i31.1551WasmHeapType::Array1552| WasmHeapType::ConcreteArray(_)1553| WasmHeapType::Struct1554| WasmHeapType::ConcreteStruct(_)1555| WasmHeapType::None => false,15561557// Despite being a different type hierarchy, this *could* be an1558// `i31` if it is the result of1559//1560// (extern.convert_any (ref.i31 ...))1561WasmHeapType::Extern => true,15621563// Can only ever be `null`.1564WasmHeapType::NoExtern => false,15651566WasmHeapType::Exn | WasmHeapType::ConcreteExn(_) | WasmHeapType::NoExn => false,15671568// Wrong type hierarchy, and also funcrefs are not GC-managed1569// types. Should have been caught by the assertion at the start of1570// the function.1571WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {1572unreachable!()1573}1574WasmHeapType::Cont | WasmHeapType::ConcreteCont(_) | WasmHeapType::NoCont => {1575unreachable!()1576}1577};15781579match (ty.nullable, might_be_i31) {1580// This GC reference statically cannot be null nor an i31. (Let1581// Cranelift's optimizer const-propagate this value and erase any1582// unnecessary control flow resulting from branching on this value.)1583(false, false) => builder.ins().iconst(ir::types::I32, 0),15841585// This GC reference is always non-null, but might be an i31.1586(false, true) => builder.ins().band_imm(gc_ref, i64::from(I31_DISCRIMINANT)),15871588// This GC reference might be null, but can never be an i31.1589(true, false) => builder.ins().icmp_imm(IntCC::Equal, gc_ref, 0),15901591// Fully general case: this GC reference could be either null or an1592// i31.1593(true, true) => {1594let is_i31 = builder.ins().band_imm(gc_ref, i64::from(I31_DISCRIMINANT));1595let is_null = builder.ins().icmp_imm(IntCC::Equal, gc_ref, 0);1596let is_null = builder.ins().uextend(ir::types::I32, is_null);1597builder.ins().bor(is_i31, is_null)1598}1599}1600}16011602// Emit code to check whether `a <: b` for two `VMSharedTypeIndex`es.1603pub(crate) fn is_subtype(1604&mut self,1605builder: &mut FunctionBuilder<'_>,1606a: ir::Value,1607b: ir::Value,1608) -> ir::Value {1609log::trace!("is_subtype({a:?}, {b:?})");16101611let diff_tys_block = builder.create_block();1612let continue_block = builder.create_block();16131614// Current block: fast path for when `a == b`.1615log::trace!("is_subtype: fast path check for exact same types");1616let same_ty = builder.ins().icmp(IntCC::Equal, a, b);1617let same_ty = builder.ins().uextend(ir::types::I32, same_ty);1618builder.ins().brif(1619same_ty,1620continue_block,1621&[same_ty.into()],1622diff_tys_block,1623&[],1624);16251626// Different types block: fall back to the `is_subtype` libcall.1627builder.switch_to_block(diff_tys_block);1628log::trace!("is_subtype: slow path to do full `is_subtype` libcall");1629let is_subtype = self.builtin_functions.is_subtype(builder.func);1630let vmctx = self.vmctx_val(&mut builder.cursor());1631let call_inst = builder.ins().call(is_subtype, &[vmctx, a, b]);1632let result = builder.func.dfg.first_result(call_inst);1633builder.ins().jump(continue_block, &[result.into()]);16341635// Continue block: join point for the result.1636builder.switch_to_block(continue_block);1637let result = builder.append_block_param(continue_block, ir::types::I32);1638log::trace!("is_subtype(..) -> {result:?}");16391640builder.seal_block(diff_tys_block);1641builder.seal_block(continue_block);16421643result1644}1645}164616471648