use super::{CodeGen, CodeGenError, Emission};
use crate::{
Result,
codegen::{Callee, FnCall},
ensure,
masm::{IntCmpKind, IntScratch, MacroAssembler, OperandSize, RegImm},
reg::{Reg, writable},
stack::{TypedReg, Val},
};
use cranelift_codegen::MachLabel;
use wasmtime_cranelift::{TRAP_ALLOCATION_TOO_LARGE, TRAP_GC_HEAP_CORRUPT};
use wasmtime_environ::{
Collector, GcTypeLayouts, I31_DISCRIMINANT, ModuleInternedTypeIndex, PtrSize,
VM_GC_HEADER_KIND_OFFSET, VM_GC_HEADER_TYPE_INDEX_OFFSET, VMGcKind,
copying::CopyingTypeLayouts, drc::DrcTypeLayouts, null::NullTypeLayouts,
};
#[derive(Clone, Copy)]
pub(crate) struct GcCodegenConfig {
collector: Collector,
layouts: &'static dyn GcTypeLayouts,
}
impl GcCodegenConfig {
pub(super) fn new(collector: Collector) -> Self {
let layouts = match collector {
Collector::DeferredReferenceCounting => &DrcTypeLayouts as &dyn GcTypeLayouts,
Collector::Null => &NullTypeLayouts,
Collector::Copying => &CopyingTypeLayouts,
};
Self { collector, layouts }
}
pub(crate) fn collector(self) -> Collector {
self.collector
}
pub(crate) fn layouts(self) -> &'static dyn GcTypeLayouts {
self.layouts
}
}
impl<'a, 'translation, 'data, M> CodeGen<'a, 'translation, 'data, M, Emission>
where
M: MacroAssembler,
{
pub(crate) fn require_gc_codegen_config(&self) -> GcCodegenConfig {
self.gc_codegen_config.expect(
"attempted GC code generation when GC support was not enabled at configuration time",
)
}
pub(crate) fn emit_gc_raw_alloc(
&mut self,
kind: VMGcKind,
interned: ModuleInternedTypeIndex,
layout: &core::alloc::Layout,
reserved_bits: u32,
) -> Result<(TypedReg, Reg)> {
let kind = kind.as_u32() | reserved_bits;
let size =
u32::try_from(layout.size()).map_err(|_| CodeGenError::allocation_too_large())?;
let align =
u32::try_from(layout.align()).map_err(|_| CodeGenError::allocation_too_large())?;
match self.require_gc_codegen_config().collector() {
Collector::Null => self.emit_null_gc_raw_alloc(kind, interned, size, align),
Collector::DeferredReferenceCounting | Collector::Copying => {
self.emit_builtin_gc_raw_alloc(kind, interned, size, align)
}
}
}
fn emit_null_gc_raw_alloc(
&mut self,
kind: u32,
interned: ModuleInternedTypeIndex,
size: u32,
align: u32,
) -> Result<(TypedReg, Reg)> {
let heap_data_offset = self.env.vmoffsets.ptr.vmctx().gc_heap_data();
let bump = self.context.any_gpr(self.masm)?;
self.masm.with_scratch::<IntScratch, _>(|masm, heap_data| {
masm.load_ptr(
masm.address_at_vmctx(u32::from(heap_data_offset))?,
heap_data.writable(),
)?;
masm.load(
masm.address_at_reg(heap_data.inner(), 0)?,
writable!(bump),
OperandSize::S32,
)
})?;
debug_assert!(align.is_power_of_two());
let align_minus_one = align - 1;
self.masm.checked_uadd(
writable!(bump),
bump,
RegImm::i32(align_minus_one.cast_signed()),
OperandSize::S32,
TRAP_ALLOCATION_TOO_LARGE,
)?;
self.masm.and(
writable!(bump),
bump,
RegImm::i32(!align_minus_one.cast_signed()),
OperandSize::S32,
)?;
let end = self.context.any_gpr(self.masm)?;
self.masm
.mov(writable!(end), bump.into(), OperandSize::S32)?;
self.masm.checked_uadd(
writable!(end),
end,
RegImm::i32(size.cast_signed()),
OperandSize::S32,
TRAP_ALLOCATION_TOO_LARGE,
)?;
self.context.stack.push(TypedReg::i32(bump).into());
self.context.stack.push(TypedReg::i32(end).into());
self.context.spill(self.masm)?;
let end = self.context.pop_to_reg(self.masm, None)?;
let (heap_reg, heap_bound) = self.emit_load_gc_heap_base_and_bound()?;
self.context.free_reg(heap_reg);
let in_bounds = self.masm.get_label()?;
self.masm.branch(
IntCmpKind::LeU,
end.reg,
heap_bound.into(),
in_bounds,
OperandSize::S64,
)?;
self.masm.sub(
writable!(end.reg),
end.reg,
heap_bound.into(),
OperandSize::S64,
)?;
self.context.free_reg(heap_bound);
self.context.stack.push(TypedReg::i64(end.reg).into());
let grow_gc_heap = self.env.builtins.grow_gc_heap::<M::ABI>()?;
FnCall::emit::<M>(
&mut self.env,
self.masm,
&mut self.context,
Callee::Builtin(grow_gc_heap),
)?;
self.context.pop_and_free(self.masm)?;
self.masm.bind(in_bounds)?;
let aligned = self.context.pop_to_reg(self.masm, None)?;
let (heap_reg, heap_bound) = self.emit_load_gc_heap_base_and_bound()?;
self.context.free_reg(heap_bound);
let object_addr = self.emit_gc_ref_addr(aligned.reg, heap_reg)?;
self.context.free_reg(heap_reg);
let kind_and_size = kind | size;
ensure!(
VMGcKind::value_fits_in_unused_bits(size),
CodeGenError::allocation_too_large()
);
self.masm.store(
RegImm::i32(kind_and_size.cast_signed()),
self.masm
.address_at_reg(object_addr, VM_GC_HEADER_KIND_OFFSET)?,
OperandSize::S32,
)?;
let shared_ty_size = self.env.vmoffsets.size_of_vmshared_type_index();
let shared_ty_offset = self.env.shared_type_index_offset(interned);
let type_ids_offset = self.env.vmoffsets.ptr.vmctx().type_ids();
self.masm.with_scratch::<IntScratch, _>(|masm, shared_ty| {
masm.load_ptr(
masm.address_at_vmctx(type_ids_offset.into())?,
shared_ty.writable(),
)?;
masm.load(
masm.address_at_reg(shared_ty.inner(), shared_ty_offset)?,
shared_ty.writable(),
OperandSize::from_bytes(shared_ty_size),
)?;
masm.store(
shared_ty.inner().into(),
masm.address_at_reg(object_addr, VM_GC_HEADER_TYPE_INDEX_OFFSET)?,
OperandSize::from_bytes(shared_ty_size),
)
})?;
let heap_data = self.context.any_gpr(self.masm)?;
self.masm.load_ptr(
self.masm.address_at_vmctx(u32::from(heap_data_offset))?,
writable!(heap_data),
)?;
self.masm.with_scratch::<IntScratch, _>(|masm, end| {
masm.mov(end.writable(), aligned.reg.into(), OperandSize::S32)?;
masm.add(
end.writable(),
end.inner(),
RegImm::i32(size.cast_signed()),
OperandSize::S32,
)?;
masm.store(
end.inner().into(),
masm.address_at_reg(heap_data, 0)?,
OperandSize::S32,
)
})?;
self.context.free_reg(heap_data);
Ok((aligned, object_addr))
}
fn emit_builtin_gc_raw_alloc(
&mut self,
kind: u32,
interned: ModuleInternedTypeIndex,
size: u32,
align: u32,
) -> Result<(TypedReg, Reg)> {
let shared_ty_size = self.env.vmoffsets.size_of_vmshared_type_index();
let shared_ty_offset = self.env.shared_type_index_offset(interned);
let shared_ty = self.context.any_gpr(self.masm)?;
self.masm.load_ptr(
self.masm
.address_at_vmctx(self.env.vmoffsets.ptr.vmctx().type_ids().into())?,
writable!(shared_ty),
)?;
self.masm.load(
self.masm.address_at_reg(shared_ty, shared_ty_offset)?,
writable!(shared_ty),
OperandSize::from_bytes(shared_ty_size),
)?;
self.context.stack.push(Val::i32(kind.cast_signed()));
self.context.stack.push(TypedReg::i32(shared_ty).into());
self.context.stack.push(Val::i32(size.cast_signed()));
self.context.stack.push(Val::i32(align.cast_signed()));
let gc_alloc_raw = self.env.builtins.gc_alloc_raw::<M::ABI>()?;
FnCall::emit::<M>(
&mut self.env,
self.masm,
&mut self.context,
Callee::Builtin(gc_alloc_raw),
)?;
let gc_ref = self.context.pop_to_reg(self.masm, None)?;
let (heap_base, heap_bound) = self.emit_load_gc_heap_base_and_bound()?;
self.context.free_reg(heap_bound);
let object_addr = self.emit_gc_ref_addr(gc_ref.reg, heap_base)?;
self.context.free_reg(heap_base);
Ok((gc_ref, object_addr))
}
pub(super) fn emit_skip_if_gc_ref_is_null_or_i31(
&mut self,
gc_ref: Reg,
skip: MachLabel,
) -> Result<()> {
self.masm.branch(
IntCmpKind::Eq,
gc_ref,
gc_ref.into(),
skip,
OperandSize::S32,
)?;
self.masm.with_scratch::<IntScratch, _>(|masm, scratch| {
masm.mov(scratch.writable(), gc_ref.into(), OperandSize::S32)?;
masm.and(
scratch.writable(),
scratch.inner(),
RegImm::i32(I31_DISCRIMINANT as i32),
OperandSize::S32,
)?;
masm.branch(
IntCmpKind::Ne,
scratch.inner(),
scratch.inner().into(),
skip,
OperandSize::S32,
)
})
}
pub(crate) fn emit_load_gc_heap_base_and_bound(&mut self) -> Result<(Reg, Reg)> {
self.needs_gc_heap = true;
let store_context_offset = self.env.vmoffsets.ptr.vmctx().store_context();
let gc_heap_base_offset = self.env.vmoffsets.ptr.vm_store_context().gc_heap_base();
let gc_heap_len_offset = self
.env
.vmoffsets
.ptr
.vm_store_context()
.gc_heap_current_length();
let heap_base = self.context.any_gpr(self.masm)?;
let heap_bound = self.context.any_gpr(self.masm)?;
self.masm.load_ptr(
self.masm
.address_at_vmctx(u32::from(store_context_offset))?,
writable!(heap_base),
)?;
self.masm.load(
self.masm
.address_at_reg(heap_base, u32::from(gc_heap_len_offset))?,
writable!(heap_bound),
OperandSize::S64,
)?;
self.masm.load_ptr(
self.masm
.address_at_reg(heap_base, u32::from(gc_heap_base_offset))?,
writable!(heap_base),
)?;
Ok((heap_base, heap_bound))
}
pub(super) fn emit_gc_ref_bounds_check(
&mut self,
gc_ref: Reg,
heap_bound: Reg,
access_extent: i64,
) -> Result<()> {
self.masm.with_scratch::<IntScratch, _>(|masm, scratch| {
masm.mov(scratch.writable(), gc_ref.into(), OperandSize::S64)?;
masm.add(
scratch.writable(),
scratch.inner(),
RegImm::i64(access_extent),
OperandSize::S64,
)?;
masm.cmp(scratch.inner(), heap_bound.into(), OperandSize::S64)?;
masm.trapif(IntCmpKind::GtU, TRAP_GC_HEAP_CORRUPT)
})
}
pub(crate) fn emit_gc_ref_addr(&mut self, gc_ref: Reg, heap_base: Reg) -> Result<Reg> {
let dst = self.context.any_gpr(self.masm)?;
self.masm
.mov(writable!(dst), heap_base.into(), OperandSize::S64)?;
self.masm
.add(writable!(dst), dst, gc_ref.into(), OperandSize::S64)?;
Ok(dst)
}
}