use inkwell::values::{IntValue, PointerValue};
use relon_ir::ir::{IrType, Op};
use crate::error::LlvmError;
use crate::state::ARENA_STATE_OFFSET_TAIL_CURSOR;
use super::*;
impl<'ctx, 'b, 'cp> Emit<'ctx, 'b, 'cp> {
pub(crate) fn lower_collections_rest(
&mut self,
ip: usize,
ip_hint: &str,
op: &Op,
) -> Result<(), LlvmError> {
match op {
Op::ConstListInt { idx, .. } => self.emit_const_list(*idx, IrType::ListInt),
Op::ConstListFloat { idx, .. } => self.emit_const_list(*idx, IrType::ListFloat),
Op::ConstListBool { idx, .. } => self.emit_const_list(*idx, IrType::ListBool),
Op::ConstListString { idx, .. } => self.emit_const_list(*idx, IrType::ListString),
Op::ConstDict { idx, .. } => self.emit_const_list(*idx, IrType::Dict),
Op::AllocSubRecord {
record_local_idx,
root_size,
root_align,
} => self.emit_alloc_sub_record(*record_local_idx, *root_size, *root_align),
Op::AllocScratchRecord {
record_local_idx,
root_size,
root_align,
} => {
self.emit_alloc_scratch_record(ip_hint, *record_local_idx, *root_size, *root_align)
}
Op::PushRecordBase { record_local_idx } => {
self.emit_push_record_base(*record_local_idx)
}
Op::PushRecordBaseAbsolute { record_local_idx } => {
self.emit_push_record_base_absolute(*record_local_idx)
}
Op::StoreFieldAtRecordAbsolute {
record_local_idx,
offset,
ty,
} => self.emit_store_field_at_record_absolute(ip_hint, *record_local_idx, *offset, *ty),
Op::EmitTailRecordFromAbsoluteAddr { ty } => {
self.emit_tail_record_from_absolute(ip_hint, *ty)
}
Op::BuildVariantRecord {
tag,
record_size,
record_align,
payload_offset,
payload_ty,
} => self.emit_build_variant_record(
ip_hint,
*tag,
*record_size,
*record_align,
*payload_offset,
*payload_ty,
),
Op::BuildVariantRecordScratch {
tag,
record_size,
record_align,
payload_offset,
payload_ty,
} => self.emit_build_variant_record_scratch(
ip_hint,
*tag,
*record_size,
*record_align,
*payload_offset,
*payload_ty,
),
Op::BuildPointerList { len } => self.emit_build_pointer_list(ip_hint, *len),
_ => Err(LlvmError::Codegen(format!(
"unsupported op (Phase 0b collections seam): {op:?} at ip={ip}"
))),
}
}
fn emit_const_list(&mut self, idx: u32, ty: IrType) -> Result<(), LlvmError> {
let (offset, label) = match ty {
IrType::ListInt => (self.const_pool.list_int_offsets.get(&idx), "ConstListInt"),
IrType::ListFloat => (
self.const_pool.list_float_offsets.get(&idx),
"ConstListFloat",
),
IrType::ListBool => (self.const_pool.list_bool_offsets.get(&idx), "ConstListBool"),
IrType::ListString => (
self.const_pool.list_string_offsets.get(&idx),
"ConstListString",
),
IrType::Dict => (self.const_pool.dict_offsets.get(&idx), "ConstDict"),
other => {
return Err(LlvmError::Codegen(format!(
"emit_const_list: unexpected list type {other:?}"
)));
}
};
let off = offset.copied().ok_or_else(|| {
LlvmError::Codegen(format!("{label} idx {idx} not in pre-computed const pool"))
})?;
let c = self.ctx.i32_type().const_int(u64::from(off), false);
self.push(c, ty);
Ok(())
}
pub(crate) fn get_or_create_record_local(
&mut self,
idx: u32,
) -> Result<PointerValue<'ctx>, LlvmError> {
if let Some(p) = self.record_locals.get(&idx).copied() {
return Ok(p);
}
let i32_t = self.ctx.i32_type();
let name = self.next_name("record_local");
let slot = self
.builder
.build_alloca(i32_t, &name)
.map_err(|e| LlvmError::Codegen(format!("record_local alloca: {e}")))?;
self.record_locals.insert(idx, slot);
Ok(slot)
}
pub(crate) fn emit_alloc_root_record(&mut self, idx: u32) -> Result<(), LlvmError> {
if self.fast_path.is_some() {
let _ = idx;
return Ok(());
}
let slot = self.get_or_create_record_local(idx)?;
let zero = self.ctx.i32_type().const_zero();
self.builder
.build_store(slot, zero)
.map_err(|e| LlvmError::Codegen(format!("AllocRootRecord store: {e}")))?;
Ok(())
}
fn emit_tail_alloc(
&mut self,
record_size: IntValue<'ctx>,
align: u32,
) -> Result<IntValue<'ctx>, LlvmError> {
let state_ptr = self.state_ptr.ok_or_else(|| {
LlvmError::Codegen(
"tail alloc outside buffer-protocol entry shape (no state ptr)".into(),
)
})?;
let i32_t = self.ctx.i32_type();
let i8_t = self.ctx.i8_type();
let tail_gep = unsafe {
self.builder
.build_in_bounds_gep(
i8_t,
state_ptr,
&[i32_t.const_int(u64::from(ARENA_STATE_OFFSET_TAIL_CURSOR), false)],
"tail_cursor_gep",
)
.map_err(|e| LlvmError::Codegen(format!("tail_cursor GEP: {e}")))?
};
let cur = self
.builder
.build_load(i32_t, tail_gep, "tail_cursor_pre")
.map_err(|e| LlvmError::Codegen(format!("tail_cursor load: {e}")))?
.into_int_value();
let aligned = if align <= 1 {
cur
} else {
let add = i32_t.const_int(u64::from(align - 1), false);
let mask = i32_t.const_int(u64::from(!(align - 1)), false);
let sum = self
.builder
.build_int_add(cur, add, "tail_align_sum")
.map_err(|e| LlvmError::Codegen(format!("tail align add: {e}")))?;
self.builder
.build_and(sum, mask, "tail_align_and")
.map_err(|e| LlvmError::Codegen(format!("tail align and: {e}")))?
};
let new_cur = self
.builder
.build_int_add(aligned, record_size, "tail_cursor_post")
.map_err(|e| LlvmError::Codegen(format!("tail cur bump: {e}")))?;
self.builder
.build_store(tail_gep, new_cur)
.map_err(|e| LlvmError::Codegen(format!("tail cursor store: {e}")))?;
self.needs_tail_cursor = true;
Ok(aligned)
}
pub(crate) fn emit_alloc_sub_record(
&mut self,
idx: u32,
root_size: u32,
root_align: u32,
) -> Result<(), LlvmError> {
let size_v = self.ctx.i32_type().const_int(u64::from(root_size), false);
let pre_cursor = self.emit_tail_alloc(size_v, root_align)?;
let slot = self.get_or_create_record_local(idx)?;
self.builder
.build_store(slot, pre_cursor)
.map_err(|e| LlvmError::Codegen(format!("AllocSubRecord store: {e}")))?;
Ok(())
}
pub(crate) fn emit_push_record_base(&mut self, idx: u32) -> Result<(), LlvmError> {
let slot = self.record_locals.get(&idx).copied().ok_or_else(|| {
LlvmError::Codegen(format!(
"PushRecordBase({idx}) before matching AllocRootRecord / AllocSubRecord"
))
})?;
let v = self
.builder
.build_load(self.ctx.i32_type(), slot, "record_base")
.map_err(|e| LlvmError::Codegen(format!("PushRecordBase load: {e}")))?
.into_int_value();
self.push(v, IrType::I32);
Ok(())
}
pub(crate) fn emit_alloc_scratch_record(
&mut self,
ip_hint: &str,
idx: u32,
root_size: u32,
root_align: u32,
) -> Result<(), LlvmError> {
let alloc_size = root_size
.checked_add(root_align.saturating_sub(1))
.ok_or_else(|| LlvmError::Codegen("AllocScratchRecord size overflow".into()))?;
let i32_t = self.ctx.i32_type();
self.emit_alloc_scratch_common(i32_t.const_int(u64::from(alloc_size), false))?;
let raw = self.pop_int(ip_hint)?;
let aligned = if root_align <= 1 {
raw
} else {
let add = i32_t.const_int(u64::from(root_align - 1), false);
let mask = i32_t.const_int(u64::from(!(root_align - 1)), false);
let sum = self
.builder
.build_int_add(raw, add, "scratch_record_align_sum")
.map_err(|e| LlvmError::Codegen(format!("AllocScratchRecord align add: {e}")))?;
self.builder
.build_and(sum, mask, "scratch_record_align")
.map_err(|e| LlvmError::Codegen(format!("AllocScratchRecord align and: {e}")))?
};
let slot = self.get_or_create_record_local(idx)?;
self.builder
.build_store(slot, aligned)
.map_err(|e| LlvmError::Codegen(format!("AllocScratchRecord store: {e}")))?;
Ok(())
}
pub(crate) fn emit_push_record_base_absolute(&mut self, idx: u32) -> Result<(), LlvmError> {
let slot = self.record_locals.get(&idx).copied().ok_or_else(|| {
LlvmError::Codegen(format!(
"PushRecordBaseAbsolute({idx}) before matching AllocScratchRecord"
))
})?;
let v = self
.builder
.build_load(self.ctx.i32_type(), slot, "scratch_record_base")
.map_err(|e| LlvmError::Codegen(format!("PushRecordBaseAbsolute load: {e}")))?
.into_int_value();
self.push(v, IrType::I32);
Ok(())
}
pub(crate) fn emit_tail_record_from_absolute(
&mut self,
ip_hint: &str,
ty: IrType,
) -> Result<(), LlvmError> {
if matches!(ty, IrType::ListString) {
let header_off = self.pop_int(ip_hint)?;
let new_header = self.copy_list_string_block(header_off)?;
self.push(new_header, IrType::I32);
return Ok(());
}
let src_off_i32 = self.pop_int(ip_hint)?;
let i32_t = self.ctx.i32_type();
let src_abs =
self.arena_addr_i32_checked_const(src_off_i32, 4, "EmitTailRecord src len")?;
let len_i32 = self
.builder
.build_load(i32_t, src_abs, "tail_rec_len")
.map_err(|e| LlvmError::Codegen(format!("EmitTailRecord len load: {e}")))?
.into_int_value();
let record_size = match ty {
IrType::String | IrType::ListBool => {
let four = i32_t.const_int(4, false);
self.builder
.build_int_add(len_i32, four, "tail_rec_size4")
.map_err(|e| LlvmError::Codegen(format!("EmitTailRecord size4: {e}")))?
}
IrType::ListInt | IrType::ListFloat => {
let three = i32_t.const_int(3, false);
let shifted = self
.builder
.build_left_shift(len_i32, three, "tail_rec_shl")
.map_err(|e| LlvmError::Codegen(format!("EmitTailRecord shl: {e}")))?;
let eight = i32_t.const_int(8, false);
self.builder
.build_int_add(shifted, eight, "tail_rec_size8")
.map_err(|e| LlvmError::Codegen(format!("EmitTailRecord size8: {e}")))?
}
IrType::ListSchema | IrType::ListList => {
return Err(LlvmError::Codegen(format!(
"EmitTailRecordFromAbsoluteAddr {ty:?} (pointer-array) not yet supported"
)));
}
_ => {
return Err(LlvmError::Codegen(format!(
"EmitTailRecordFromAbsoluteAddr unsupported {ty:?}"
)));
}
};
let align: u32 = match ty {
IrType::String | IrType::ListBool => 4,
IrType::ListInt | IrType::ListFloat => 8,
_ => unreachable!("record_size match already rejected non-pointer-indirect types"),
};
let pre_cursor = self.emit_tail_alloc(record_size, align)?;
let out_ptr_i32 = self.lookup_param(2)?; let dst_off = self
.builder
.build_int_add(out_ptr_i32, pre_cursor, "tail_rec_dst_off")
.map_err(|e| LlvmError::Codegen(format!("EmitTailRecord dst off: {e}")))?;
let dst_ptr = self.arena_addr_i32_checked(dst_off, record_size, "EmitTailRecord dst")?;
let src_ptr =
self.arena_addr_i32_checked(src_off_i32, record_size, "EmitTailRecord src")?;
let i64_t = self.ctx.i64_type();
let rec64 = self
.builder
.build_int_z_extend(record_size, i64_t, "tail_rec_size64")
.map_err(|e| LlvmError::Codegen(format!("EmitTailRecord size zext: {e}")))?;
self.builder
.build_memcpy(dst_ptr, align, src_ptr, 1, rec64)
.map_err(|e| LlvmError::Codegen(format!("EmitTailRecord memcpy: {e}")))?;
self.push(dst_off, IrType::I32);
Ok(())
}
pub(crate) fn emit_build_variant_record(
&mut self,
ip_hint: &str,
tag: u8,
record_size: u32,
record_align: u32,
payload_offset: Option<u32>,
payload_ty: Option<IrType>,
) -> Result<(), LlvmError> {
let payload = match payload_ty {
Some(_) => Some(self.pop_int(ip_hint)?),
None => None,
};
if payload.is_some() != payload_offset.is_some() {
return Err(LlvmError::Codegen(
"BuildVariantRecord payload metadata mismatch".into(),
));
}
let i32_t = self.ctx.i32_type();
let i8_t = self.ctx.i8_type();
let size_v = i32_t.const_int(u64::from(record_size), false);
let base_rel = self.emit_tail_alloc(size_v, record_align)?;
let out_ptr_i32 = self.lookup_param(2)?;
let record_abs = self
.builder
.build_int_add(out_ptr_i32, base_rel, "variant_record_abs")
.map_err(|e| LlvmError::Codegen(format!("BuildVariantRecord abs add: {e}")))?;
let tag_addr =
self.arena_addr_i32_checked_const(record_abs, 1, "BuildVariantRecord tag")?;
let tag_v = i8_t.const_int(u64::from(tag), false);
self.builder
.build_store(tag_addr, tag_v)
.map_err(|e| LlvmError::Codegen(format!("BuildVariantRecord tag store: {e}")))?;
if let (Some(payload), Some(offset), Some(ty)) = (payload, payload_offset, payload_ty) {
let slot_off = self
.builder
.build_int_add(
record_abs,
i32_t.const_int(u64::from(offset), false),
"variant_payload_off",
)
.map_err(|e| LlvmError::Codegen(format!("BuildVariantRecord slot off: {e}")))?;
let access_size = self.field_access_size(ty)?;
let slot_addr = self.arena_addr_i32_checked_const(
slot_off,
access_size,
"BuildVariantRecord slot",
)?;
match ty {
IrType::I64 => {
self.builder.build_store(slot_addr, payload).map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecord I64 store: {e}"))
})?;
}
IrType::F64 => {
let f = self
.builder
.build_bit_cast(payload, self.ctx.f64_type(), "variant_f64_bitcast")
.map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecord F64 bitcast: {e}"))
})?;
self.builder.build_store(slot_addr, f).map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecord F64 store: {e}"))
})?;
}
IrType::I32
| IrType::String
| IrType::ListInt
| IrType::ListFloat
| IrType::ListBool
| IrType::ListString
| IrType::ListSchema
| IrType::ListList
| IrType::Closure
| IrType::Dict => {
self.builder.build_store(slot_addr, payload).map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecord I32 store: {e}"))
})?;
}
IrType::Bool | IrType::Unit => {
let v8 = self
.builder
.build_int_truncate(payload, i8_t, "variant_bool_trunc")
.map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecord Bool trunc: {e}"))
})?;
self.builder.build_store(slot_addr, v8).map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecord Bool store: {e}"))
})?;
}
}
}
self.push(record_abs, IrType::I32);
Ok(())
}
pub(crate) fn emit_build_variant_record_scratch(
&mut self,
ip_hint: &str,
tag: u8,
record_size: u32,
record_align: u32,
payload_offset: Option<u32>,
payload_ty: Option<IrType>,
) -> Result<(), LlvmError> {
let payload = match payload_ty {
Some(_) => Some(self.pop_int(ip_hint)?),
None => None,
};
if payload.is_some() != payload_offset.is_some() {
return Err(LlvmError::Codegen(
"BuildVariantRecordScratch payload metadata mismatch".into(),
));
}
let i32_t = self.ctx.i32_type();
let i8_t = self.ctx.i8_type();
let alloc_size = record_size
.checked_add(record_align.saturating_sub(1))
.ok_or_else(|| LlvmError::Codegen("BuildVariantRecordScratch size overflow".into()))?;
self.emit_alloc_scratch_common(i32_t.const_int(u64::from(alloc_size), false))?;
let raw = self.pop_int(ip_hint)?;
let record_abs = if record_align <= 1 {
raw
} else {
let add = i32_t.const_int(u64::from(record_align - 1), false);
let mask = i32_t.const_int(u64::from(!(record_align - 1)), false);
let sum = self
.builder
.build_int_add(raw, add, "variant_scratch_align_sum")
.map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecordScratch align add: {e}"))
})?;
self.builder
.build_and(sum, mask, "variant_scratch_align")
.map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecordScratch align and: {e}"))
})?
};
let tag_addr =
self.arena_addr_i32_checked_const(record_abs, 1, "BuildVariantRecordScratch tag")?;
let tag_v = i8_t.const_int(u64::from(tag), false);
self.builder
.build_store(tag_addr, tag_v)
.map_err(|e| LlvmError::Codegen(format!("BuildVariantRecordScratch tag store: {e}")))?;
if let (Some(payload), Some(offset), Some(ty)) = (payload, payload_offset, payload_ty) {
let slot_off = self
.builder
.build_int_add(
record_abs,
i32_t.const_int(u64::from(offset), false),
"variant_scratch_payload_off",
)
.map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecordScratch slot off: {e}"))
})?;
let access_size = self.field_access_size(ty)?;
let slot_addr = self.arena_addr_i32_checked_const(
slot_off,
access_size,
"BuildVariantRecordScratch slot",
)?;
match ty {
IrType::I64 => {
self.builder.build_store(slot_addr, payload).map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecordScratch I64 store: {e}"))
})?;
}
IrType::F64 => {
let f = self
.builder
.build_bit_cast(payload, self.ctx.f64_type(), "variant_scratch_f64_bitcast")
.map_err(|e| {
LlvmError::Codegen(format!(
"BuildVariantRecordScratch F64 bitcast: {e}"
))
})?;
self.builder.build_store(slot_addr, f).map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecordScratch F64 store: {e}"))
})?;
}
IrType::I32
| IrType::String
| IrType::ListInt
| IrType::ListFloat
| IrType::ListBool
| IrType::ListString
| IrType::ListSchema
| IrType::ListList
| IrType::Closure
| IrType::Dict => {
self.builder.build_store(slot_addr, payload).map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecordScratch I32 store: {e}"))
})?;
}
IrType::Bool | IrType::Unit => {
let v8 = self
.builder
.build_int_truncate(payload, i8_t, "variant_scratch_bool_trunc")
.map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecordScratch Bool trunc: {e}"))
})?;
self.builder.build_store(slot_addr, v8).map_err(|e| {
LlvmError::Codegen(format!("BuildVariantRecordScratch Bool store: {e}"))
})?;
}
}
}
self.push(record_abs, IrType::I32);
Ok(())
}
pub(crate) fn emit_build_pointer_list(
&mut self,
ip_hint: &str,
len: u32,
) -> Result<(), LlvmError> {
let mut elems = Vec::with_capacity(len as usize);
for _ in 0..len {
elems.push(self.pop_int(ip_hint)?);
}
elems.reverse();
let size = 4u32
.checked_add(
len.checked_mul(4)
.ok_or_else(|| LlvmError::Codegen("BuildPointerList length overflow".into()))?,
)
.ok_or_else(|| LlvmError::Codegen("BuildPointerList size overflow".into()))?;
let i32_t = self.ctx.i32_type();
let size_v = i32_t.const_int(u64::from(size), false);
let base_rel = self.emit_tail_alloc(size_v, 4)?;
let out_ptr_i32 = self.lookup_param(2)?;
let header_abs = self
.builder
.build_int_add(out_ptr_i32, base_rel, "ptr_list_abs")
.map_err(|e| LlvmError::Codegen(format!("BuildPointerList abs add: {e}")))?;
let header_addr =
self.arena_addr_i32_checked_const(header_abs, 4, "BuildPointerList len")?;
self.builder
.build_store(header_addr, i32_t.const_int(u64::from(len), false))
.map_err(|e| LlvmError::Codegen(format!("BuildPointerList len store: {e}")))?;
for (idx, elem) in elems.into_iter().enumerate() {
let off = 4u32 + (idx as u32) * 4;
let slot_off = self
.builder
.build_int_add(
header_abs,
i32_t.const_int(u64::from(off), false),
"ptr_list_slot",
)
.map_err(|e| LlvmError::Codegen(format!("BuildPointerList slot off: {e}")))?;
let slot_addr =
self.arena_addr_i32_checked_const(slot_off, 4, "BuildPointerList elem")?;
self.builder
.build_store(slot_addr, elem)
.map_err(|e| LlvmError::Codegen(format!("BuildPointerList elem store: {e}")))?;
}
self.push(header_abs, IrType::ListList);
Ok(())
}
pub(crate) fn emit_store_field_at_record_absolute(
&mut self,
ip_hint: &str,
idx: u32,
offset: u32,
ty: IrType,
) -> Result<(), LlvmError> {
let value = self.pop_int(ip_hint)?;
let slot = self.record_locals.get(&idx).copied().ok_or_else(|| {
LlvmError::Codegen(format!(
"StoreFieldAtRecordAbsolute({idx}) before matching AllocScratchRecord"
))
})?;
let i32_t = self.ctx.i32_type();
let i8_t = self.ctx.i8_type();
let record_base = self
.builder
.build_load(i32_t, slot, "scratch_record_base")
.map_err(|e| LlvmError::Codegen(format!("scratch record base load: {e}")))?
.into_int_value();
let slot_off = self
.builder
.build_int_add(
record_base,
i32_t.const_int(u64::from(offset), false),
"scratch_record_slot_off",
)
.map_err(|e| LlvmError::Codegen(format!("scratch record slot off: {e}")))?;
let access_size = self.field_access_size(ty)?;
let addr =
self.arena_addr_i32_checked_const(slot_off, access_size, "StoreFieldAtRecordAbsolute")?;
match ty {
IrType::I64 => {
self.builder.build_store(addr, value).map_err(|e| {
LlvmError::Codegen(format!("StoreFieldAtRecordAbsolute I64: {e}"))
})?;
}
IrType::F64 => {
let f = self
.builder
.build_bit_cast(value, self.ctx.f64_type(), "scratch_record_f64_bitcast")
.map_err(|e| {
LlvmError::Codegen(format!("StoreFieldAtRecordAbsolute F64 bitcast: {e}"))
})?;
self.builder.build_store(addr, f).map_err(|e| {
LlvmError::Codegen(format!("StoreFieldAtRecordAbsolute F64: {e}"))
})?;
}
IrType::I32
| IrType::String
| IrType::ListInt
| IrType::ListFloat
| IrType::ListBool
| IrType::ListString
| IrType::ListSchema
| IrType::ListList
| IrType::Closure
| IrType::Dict => {
self.builder.build_store(addr, value).map_err(|e| {
LlvmError::Codegen(format!("StoreFieldAtRecordAbsolute I32: {e}"))
})?;
}
IrType::Bool | IrType::Unit => {
let v8 = self
.builder
.build_int_truncate(value, i8_t, "scratch_record_bool_trunc")
.map_err(|e| {
LlvmError::Codegen(format!("StoreFieldAtRecordAbsolute Bool trunc: {e}"))
})?;
self.builder.build_store(addr, v8).map_err(|e| {
LlvmError::Codegen(format!("StoreFieldAtRecordAbsolute Bool: {e}"))
})?;
}
}
Ok(())
}
pub(crate) fn emit_store_field_at_record(
&mut self,
ip_hint: &str,
idx: u32,
offset: u32,
ty: IrType,
) -> Result<(), LlvmError> {
if let Some(fast) = self.fast_path.clone() {
let _ = idx;
if ty != IrType::I64 {
return Err(LlvmError::Codegen(format!(
"fast-path StoreFieldAtRecord: only I64 returns supported, got {ty:?}"
)));
}
if offset != fast.profile.ret_offset {
return Err(LlvmError::Codegen(format!(
"fast-path StoreFieldAtRecord: offset {offset} != profile.ret_offset {}",
fast.profile.ret_offset
)));
}
let v = self.pop_int(ip_hint)?;
self.builder.build_store(fast.ret_slot, v).map_err(|e| {
LlvmError::Codegen(format!("fast StoreFieldAtRecord ret_slot: {e}"))
})?;
return Ok(());
}
let arena_base_ptr = self.arena_base_ptr.ok_or_else(|| {
LlvmError::Codegen("StoreFieldAtRecord outside buffer-protocol entry".into())
})?;
let value = self.pop_int(ip_hint)?;
let slot = self.record_locals.get(&idx).copied().ok_or_else(|| {
LlvmError::Codegen(format!(
"StoreFieldAtRecord({idx}) before matching AllocRootRecord"
))
})?;
let i32_t = self.ctx.i32_type();
let i8_t = self.ctx.i8_type();
let record_base = self
.builder
.build_load(i32_t, slot, "record_base")
.map_err(|e| LlvmError::Codegen(format!("record_base load: {e}")))?
.into_int_value();
let i64_t = self.ctx.i64_type();
let record_base64 = self
.builder
.build_int_z_extend(record_base, i64_t, "record_base64")
.map_err(|e| LlvmError::Codegen(format!("record_base64: {e}")))?;
let off_const = i64_t.const_int(u64::from(offset), false);
let slot_off = self
.builder
.build_int_add(record_base64, off_const, "record_slot_off")
.map_err(|e| LlvmError::Codegen(format!("record_slot_off: {e}")))?;
let out_ptr_i32 = self.lookup_param(2)?; let out_ptr64 = self
.builder
.build_int_z_extend(out_ptr_i32, i64_t, "record_out_ptr64")
.map_err(|e| LlvmError::Codegen(format!("record_out_ptr64: {e}")))?;
let total_off = self
.builder
.build_int_add(out_ptr64, slot_off, "record_total_off")
.map_err(|e| LlvmError::Codegen(format!("record_total_off: {e}")))?;
let access_size = self.field_access_size(ty)?;
self.emit_arena_bounds_check_const(total_off, access_size, "StoreFieldAtRecord")?;
let addr = self.arena_addr_from_base_offset(arena_base_ptr, total_off, "abs")?;
match ty {
IrType::I64 => {
self.builder
.build_store(addr, value)
.map_err(|e| LlvmError::Codegen(format!("StoreFieldAtRecord I64: {e}")))?;
}
IrType::F64 => {
let f = self
.builder
.build_bit_cast(value, self.ctx.f64_type(), "record_f64_bitcast")
.map_err(|e| LlvmError::Codegen(format!("F64 bitcast: {e}")))?;
self.builder
.build_store(addr, f)
.map_err(|e| LlvmError::Codegen(format!("StoreFieldAtRecord F64: {e}")))?;
}
IrType::I32
| IrType::String
| IrType::ListInt
| IrType::ListFloat
| IrType::ListBool
| IrType::ListString
| IrType::ListSchema
| IrType::ListList
| IrType::Closure
| IrType::Dict => {
self.builder
.build_store(addr, value)
.map_err(|e| LlvmError::Codegen(format!("StoreFieldAtRecord I32: {e}")))?;
}
IrType::Bool | IrType::Unit => {
let v8 = self
.builder
.build_int_truncate(value, i8_t, "record_bool_trunc")
.map_err(|e| {
LlvmError::Codegen(format!("StoreFieldAtRecord Bool trunc: {e}"))
})?;
self.builder
.build_store(addr, v8)
.map_err(|e| LlvmError::Codegen(format!("StoreFieldAtRecord Bool: {e}")))?;
}
}
Ok(())
}
}