Path: blob/main/crates/cranelift/src/func_environ/gc/enabled/null.rs
1693 views
//! Compiler for the null collector.1//!2//! Note that we don't need to mark any value as requiring inclusion in stack3//! maps inside this module, because the null collector doesn't ever collect4//! anything.56use super::*;7use crate::func_environ::FuncEnvironment;8use cranelift_codegen::ir::{self, InstBuilder};9use cranelift_frontend::FunctionBuilder;10use wasmtime_environ::VMSharedTypeIndex;11use wasmtime_environ::null::{EXCEPTION_TAG_DEFINED_OFFSET, EXCEPTION_TAG_INSTANCE_OFFSET};12use wasmtime_environ::{13GcTypeLayouts, ModuleInternedTypeIndex, PtrSize, TypeIndex, VMGcKind, WasmRefType, WasmResult,14null::NullTypeLayouts,15};1617#[derive(Default)]18pub struct NullCompiler {19layouts: NullTypeLayouts,20}2122impl NullCompiler {23/// Emit code to perform an allocation inline.24///25/// `kind` may be `VMGcKind::ExternRef` iff `ty` is `None`.26///27/// `size` must be greater than or equal to `size_of(VMGcHeader)`.28///29/// `align` must be greater than or equal to `align_of(VMGcHeader)` and a30/// power of two.31///32/// The resulting values are33///34/// 1. The `VMGcRef` indexing into the GC heap.35///36/// 2. The raw pointer to the start of the object inside the GC heap. This37/// may be used to access up to `size` bytes.38fn emit_inline_alloc(39&mut self,40func_env: &mut FuncEnvironment<'_>,41builder: &mut FunctionBuilder,42kind: VMGcKind,43ty: Option<ModuleInternedTypeIndex>,44size: ir::Value,45align: ir::Value,46) -> (ir::Value, ir::Value) {47log::trace!("emit_inline_alloc(kind={kind:?}, ty={ty:?}, size={size}, align={align})");4849assert_eq!(builder.func.dfg.value_type(size), ir::types::I32);50assert_eq!(builder.func.dfg.value_type(align), ir::types::I32);5152let current_block = builder.current_block().unwrap();53let continue_block = builder.create_block();54let grow_block = builder.create_block();5556builder.ensure_inserted_block();57builder.insert_block_after(continue_block, current_block);58builder.insert_block_after(grow_block, continue_block);5960// Check that the size fits in the unused bits of a `VMGcKind`, since61// the null collector stores the object's size there.62let mask = builder63.ins()64.iconst(ir::types::I32, i64::from(VMGcKind::MASK));65let masked = builder.ins().band(size, mask);66func_env.trapnz(builder, masked, crate::TRAP_ALLOCATION_TOO_LARGE);6768// Load the bump "pointer" (it is actually an index into the GC heap,69// not a raw pointer).70let pointer_type = func_env.pointer_type();71let vmctx = func_env.vmctx_val(&mut builder.cursor());72let ptr_to_next = builder.ins().load(73pointer_type,74ir::MemFlags::trusted().with_readonly(),75vmctx,76i32::from(func_env.offsets.ptr.vmctx_gc_heap_data()),77);78let next = builder79.ins()80.load(ir::types::I32, ir::MemFlags::trusted(), ptr_to_next, 0);8182// Increment the bump "pointer" to the requested alignment:83//84// next + (align - 1) & !(align - 1)85//86// Overflow means that the alignment is too large to satisfy, so trap87// accordingly. Note that `align - 1` can't overflow because `align` is88// a power of two.89let minus_one = builder.ins().iconst(ir::types::I32, -1);90let align_minus_one = builder.ins().iadd(align, minus_one);91let next_plus_align_minus_one = func_env.uadd_overflow_trap(92builder,93next,94align_minus_one,95crate::TRAP_ALLOCATION_TOO_LARGE,96);97let not_align_minus_one = builder.ins().bnot(align_minus_one);98let aligned = builder99.ins()100.band(next_plus_align_minus_one, not_align_minus_one);101102// Check whether the allocation fits in the heap space we have left.103let end_of_object =104func_env.uadd_overflow_trap(builder, aligned, size, crate::TRAP_ALLOCATION_TOO_LARGE);105let uext_end_of_object = uextend_i32_to_pointer_type(builder, pointer_type, end_of_object);106let bound = func_env.get_gc_heap_bound(builder);107let is_in_bounds = builder.ins().icmp(108ir::condcodes::IntCC::UnsignedLessThanOrEqual,109uext_end_of_object,110bound,111);112builder113.ins()114.brif(is_in_bounds, continue_block, &[], grow_block, &[]);115116log::trace!("emit_inline_alloc: grow_block");117builder.switch_to_block(grow_block);118builder.seal_block(grow_block);119builder.set_cold_block(grow_block);120let grow_gc_heap_builtin = func_env.builtin_functions.grow_gc_heap(builder.func);121let vmctx = func_env.vmctx_val(&mut builder.cursor());122let bytes_needed = builder.ins().isub(uext_end_of_object, bound);123let bytes_needed = match func_env.pointer_type() {124ir::types::I32 => builder.ins().uextend(ir::types::I64, bytes_needed),125ir::types::I64 => bytes_needed,126_ => unreachable!(),127};128builder129.ins()130.call(grow_gc_heap_builtin, &[vmctx, bytes_needed]);131builder.ins().jump(continue_block, &[]);132133// Write the header, update the bump "pointer", and return the newly134// allocated object.135log::trace!("emit_inline_alloc: continue_block");136builder.switch_to_block(continue_block);137builder.seal_block(continue_block);138139// TODO: Ideally we would use a single `i64` store to write both the140// header and the type index, but that requires generating different141// code for big-endian architectures, and I haven't bothered doing that142// yet.143let base = func_env.get_gc_heap_base(builder);144let uext_aligned = uextend_i32_to_pointer_type(builder, pointer_type, aligned);145let ptr_to_object = builder.ins().iadd(base, uext_aligned);146let kind = builder147.ins()148.iconst(ir::types::I32, i64::from(kind.as_u32()));149let kind_and_size = builder.ins().bor(kind, size);150let ty = match ty {151Some(ty) => func_env.module_interned_to_shared_ty(&mut builder.cursor(), ty),152None => builder.ins().iconst(153func_env.vmshared_type_index_ty(),154i64::from(VMSharedTypeIndex::reserved_value().as_bits()),155),156};157builder.ins().store(158ir::MemFlags::trusted(),159kind_and_size,160ptr_to_object,161i32::try_from(wasmtime_environ::VM_GC_HEADER_KIND_OFFSET).unwrap(),162);163builder.ins().store(164ir::MemFlags::trusted(),165ty,166ptr_to_object,167i32::try_from(wasmtime_environ::VM_GC_HEADER_TYPE_INDEX_OFFSET).unwrap(),168);169builder170.ins()171.store(ir::MemFlags::trusted(), end_of_object, ptr_to_next, 0);172173log::trace!("emit_inline_alloc(..) -> ({aligned}, {ptr_to_object})");174(aligned, ptr_to_object)175}176}177178impl GcCompiler for NullCompiler {179fn layouts(&self) -> &dyn GcTypeLayouts {180&self.layouts181}182183fn alloc_array(184&mut self,185func_env: &mut FuncEnvironment<'_>,186builder: &mut FunctionBuilder<'_>,187array_type_index: TypeIndex,188init: super::ArrayInit<'_>,189) -> WasmResult<ir::Value> {190let interned_type_index =191func_env.module.types[array_type_index].unwrap_module_type_index();192let ptr_ty = func_env.pointer_type();193194let len_offset = gc_compiler(func_env)?.layouts().array_length_field_offset();195let array_layout = func_env.array_layout(interned_type_index).clone();196let base_size = array_layout.base_size;197let align = array_layout.align;198let len_to_elems_delta = base_size.checked_sub(len_offset).unwrap();199200// First, compute the array's total size from its base size, element201// size, and length.202let len = init.len(&mut builder.cursor());203let size = emit_array_size(func_env, builder, &array_layout, len);204205// Next, allocate the array.206assert!(align.is_power_of_two());207let align = builder.ins().iconst(ir::types::I32, i64::from(align));208let (gc_ref, ptr_to_object) = self.emit_inline_alloc(209func_env,210builder,211VMGcKind::ArrayRef,212Some(interned_type_index),213size,214align,215);216217// Write the array's length into its field.218//219// Note: we don't need to bounds-check the GC ref access here, because220// the result of the inline allocation is trusted and we aren't reading221// any pointers or offsets out from the (untrusted) GC heap.222let len_addr = builder.ins().iadd_imm(ptr_to_object, i64::from(len_offset));223let len = init.len(&mut builder.cursor());224builder225.ins()226.store(ir::MemFlags::trusted(), len, len_addr, 0);227228// Finally, initialize the elements.229let len_to_elems_delta = builder.ins().iconst(ptr_ty, i64::from(len_to_elems_delta));230let elems_addr = builder.ins().iadd(len_addr, len_to_elems_delta);231init.initialize(232func_env,233builder,234interned_type_index,235base_size,236size,237elems_addr,238|func_env, builder, elem_ty, elem_addr, val| {239write_field_at_addr(func_env, builder, elem_ty, elem_addr, val)240},241)?;242243Ok(gc_ref)244}245246fn alloc_struct(247&mut self,248func_env: &mut FuncEnvironment<'_>,249builder: &mut FunctionBuilder<'_>,250struct_type_index: TypeIndex,251field_vals: &[ir::Value],252) -> WasmResult<ir::Value> {253let interned_type_index =254func_env.module.types[struct_type_index].unwrap_module_type_index();255let struct_layout = func_env.struct_or_exn_layout(interned_type_index);256257// Copy some stuff out of the struct layout to avoid borrowing issues.258let struct_size = struct_layout.size;259let struct_align = struct_layout.align;260261assert_eq!(VMGcKind::MASK & struct_size, 0);262assert_eq!(VMGcKind::UNUSED_MASK & struct_size, struct_size);263let struct_size_val = builder.ins().iconst(ir::types::I32, i64::from(struct_size));264265let align = builder266.ins()267.iconst(ir::types::I32, i64::from(struct_align));268269let (struct_ref, raw_struct_pointer) = self.emit_inline_alloc(270func_env,271builder,272VMGcKind::StructRef,273Some(interned_type_index),274struct_size_val,275align,276);277278// Initialize the struct's fields.279//280// Note: we don't need to bounds-check the GC ref access here, because281// the result of the inline allocation is trusted and we aren't reading282// any pointers or offsets out from the (untrusted) GC heap.283initialize_struct_fields(284func_env,285builder,286interned_type_index,287raw_struct_pointer,288field_vals,289|func_env, builder, ty, field_addr, val| {290write_field_at_addr(func_env, builder, ty, field_addr, val)291},292)?;293294Ok(struct_ref)295}296297fn alloc_exn(298&mut self,299func_env: &mut FuncEnvironment<'_>,300builder: &mut FunctionBuilder<'_>,301tag_index: TagIndex,302field_vals: &[ir::Value],303instance_id: ir::Value,304tag: ir::Value,305) -> WasmResult<ir::Value> {306let interned_type_index = func_env.module.tags[tag_index]307.exception308.unwrap_module_type_index();309let exn_layout = func_env.struct_or_exn_layout(interned_type_index);310311// Copy some stuff out of the exception layout to avoid borrowing issues.312let exn_size = exn_layout.size;313let exn_align = exn_layout.align;314315assert_eq!(VMGcKind::MASK & exn_size, 0);316assert_eq!(VMGcKind::UNUSED_MASK & exn_size, exn_size);317let exn_size_val = builder.ins().iconst(ir::types::I32, i64::from(exn_size));318319let align = builder.ins().iconst(ir::types::I32, i64::from(exn_align));320321let (exn_ref, raw_exn_pointer) = self.emit_inline_alloc(322func_env,323builder,324VMGcKind::ExnRef,325Some(interned_type_index),326exn_size_val,327align,328);329330// Initialize the exception object's fields.331//332// Note: we don't need to bounds-check the GC ref access here, because333// the result of the inline allocation is trusted and we aren't reading334// any pointers or offsets out from the (untrusted) GC heap.335initialize_struct_fields(336func_env,337builder,338interned_type_index,339raw_exn_pointer,340field_vals,341|func_env, builder, ty, field_addr, val| {342write_field_at_addr(func_env, builder, ty, field_addr, val)343},344)?;345346// Initialize the tag fields.347let instance_id_addr = builder348.ins()349.iadd_imm(raw_exn_pointer, i64::from(EXCEPTION_TAG_INSTANCE_OFFSET));350write_field_at_addr(351func_env,352builder,353WasmStorageType::Val(WasmValType::I32),354instance_id_addr,355instance_id,356)?;357let tag_addr = builder358.ins()359.iadd_imm(raw_exn_pointer, i64::from(EXCEPTION_TAG_DEFINED_OFFSET));360write_field_at_addr(361func_env,362builder,363WasmStorageType::Val(WasmValType::I32),364tag_addr,365tag,366)?;367368Ok(exn_ref)369}370371fn translate_read_gc_reference(372&mut self,373_func_env: &mut FuncEnvironment<'_>,374builder: &mut FunctionBuilder,375_ty: WasmRefType,376src: ir::Value,377flags: ir::MemFlags,378) -> WasmResult<ir::Value> {379// NB: Don't use `unbarriered_load_gc_ref` here because we don't need to380// mark the value as requiring inclusion in stack maps.381Ok(builder.ins().load(ir::types::I32, flags, src, 0))382}383384fn translate_write_gc_reference(385&mut self,386_func_env: &mut FuncEnvironment<'_>,387builder: &mut FunctionBuilder,388ty: WasmRefType,389dst: ir::Value,390new_val: ir::Value,391flags: ir::MemFlags,392) -> WasmResult<()> {393unbarriered_store_gc_ref(builder, ty.heap_type, dst, new_val, flags)394}395}396397398