use formalang::ast::PrimitiveType;
use formalang::ir::{IrEnum, IrEnumVariant, IrExpr, IrField, IrModule, IrStruct, ResolvedType};
use wasm_encoder::{InstructionSink, MemArg, ValType};
use super::{LowerContext, LowerError, lower_expr};
use crate::layout::{
ARRAY_HEADER_ALIGN, ARRAY_HEADER_CAP_OFFSET, ARRAY_HEADER_LEN_OFFSET, ARRAY_HEADER_PTR_OFFSET,
ArrayLayout, ENUM_TAG_ALIGN, FieldLayout, LayoutError, RangeLayout, StructLayout,
VariantLayout, plan_array, plan_enum, plan_range, plan_struct,
};
use crate::module::MEMORY_INDEX;
pub fn lower_struct_inst(
expr: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let IrExpr::StructInst {
struct_id,
fields,
ty,
..
} = expr
else {
return Err(LowerError::NotYetImplemented {
what: "lower_struct_inst called with non-StructInst expression".to_owned(),
});
};
let id = crate::compound::struct_id_of(ty)
.or(*struct_id)
.ok_or(LowerError::ExternalStructInst)?;
let module = ctx.module()?;
let s = module
.structs
.get(id.0 as usize)
.ok_or(LowerError::UnknownStruct(id))?;
let layout = plan_struct(s, module)?;
let base_local = allocate_aggregate(layout.size, sink, ctx)?;
for (name, _idx, value_expr) in fields {
let (field_layout, field_def) = lookup_field_by_name(s, &layout.fields, name)?;
sink.local_get(base_local);
store_aggregate_field(value_expr, &field_def.ty, *field_layout, sink, ctx)?;
}
sink.local_get(base_local);
Ok(())
}
pub(super) fn store_aggregate_field(
value_expr: &IrExpr,
field_ty: &ResolvedType,
field_layout: FieldLayout,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let module = ctx.module()?;
if matches!(
crate::compound::Compound::of(field_ty, module),
crate::compound::Compound::Optional(_)
) {
super::optional::lower_coerced(value_expr, field_ty, sink, ctx)?;
sink.i32_store(field_mem_arg(field_layout));
return Ok(());
}
match field_ty {
ResolvedType::Primitive(p) => {
super::optional::lower_coerced(value_expr, field_ty, sink, ctx)?;
store_primitive(*p, field_layout, sink);
Ok(())
}
ResolvedType::Struct(_)
| ResolvedType::Enum(_)
| ResolvedType::Tuple(_)
| ResolvedType::Closure { .. }
| ResolvedType::Trait(_)
| ResolvedType::Generic { .. } => {
super::optional::lower_coerced(value_expr, field_ty, sink, ctx)?;
sink.i32_store(field_mem_arg(field_layout));
Ok(())
}
ResolvedType::TypeParam(_) | ResolvedType::External { .. } | ResolvedType::Error => {
Err(LowerError::FieldAccessOnNonAggregate {
ty: field_ty.clone(),
})
}
}
}
pub(super) fn allocate_aggregate(
size: u32,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<u32, LowerError> {
let alloc_idx = ctx.bump_allocator()?;
let size_i32 = i32::try_from(size).map_err(|_| {
LowerError::Layout(crate::layout::LayoutError::SizeOverflow {
name: "<aggregate>".to_owned(),
})
})?;
let base_local = ctx.next_scratch_local(ValType::I32)?;
sink.i32_const(size_i32);
sink.call(alloc_idx);
sink.local_set(base_local);
Ok(base_local)
}
pub(super) fn store_primitive(
p: PrimitiveType,
layout: FieldLayout,
sink: &mut InstructionSink<'_>,
) {
let mem_arg = field_mem_arg(layout);
match p {
PrimitiveType::Boolean => {
sink.i32_store8(mem_arg);
}
PrimitiveType::I32 | PrimitiveType::String | PrimitiveType::Path | PrimitiveType::Regex => {
sink.i32_store(mem_arg);
}
PrimitiveType::I64 => {
sink.i64_store(mem_arg);
}
PrimitiveType::F32 => {
sink.f32_store(mem_arg);
}
PrimitiveType::F64 => {
sink.f64_store(mem_arg);
}
PrimitiveType::Never | _ => {
sink.unreachable();
}
}
}
pub(super) fn load_primitive(
p: PrimitiveType,
layout: FieldLayout,
sink: &mut InstructionSink<'_>,
) {
let mem_arg = field_mem_arg(layout);
match p {
PrimitiveType::Boolean => {
sink.i32_load8_u(mem_arg);
}
PrimitiveType::I32 | PrimitiveType::String | PrimitiveType::Path | PrimitiveType::Regex => {
sink.i32_load(mem_arg);
}
PrimitiveType::I64 => {
sink.i64_load(mem_arg);
}
PrimitiveType::F32 => {
sink.f32_load(mem_arg);
}
PrimitiveType::F64 => {
sink.f64_load(mem_arg);
}
PrimitiveType::Never | _ => {
sink.unreachable();
}
}
}
pub fn lower_closure_ref(
expr: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let IrExpr::ClosureRef {
funcref,
env_struct,
..
} = expr
else {
return Err(LowerError::NotYetImplemented {
what: "lower_closure_ref called with non-ClosureRef expression".to_owned(),
});
};
let module = ctx.module()?;
let last = funcref
.last()
.ok_or_else(|| LowerError::NotYetImplemented {
what: "ClosureRef carries an empty funcref path".to_owned(),
})?;
let func_idx_u32 = module
.functions
.iter()
.enumerate()
.find(|(_, f)| &f.name == last)
.map(|(i, _)| i)
.ok_or_else(|| LowerError::NotYetImplemented {
what: format!("ClosureRef target function '{last}' is not in module.functions"),
})?;
let func_idx_raw = u32::try_from(func_idx_u32).map_err(|_| LowerError::NotYetImplemented {
what: "ClosureRef target index exceeds u32::MAX".to_owned(),
})?;
let funcref_table_idx = ctx.closure_funcref_index(formalang::ir::FunctionId(func_idx_raw))?;
let funcref_signed =
i32::try_from(funcref_table_idx).map_err(|_| LowerError::NotYetImplemented {
what: "ClosureRef target table index exceeds i32::MAX".to_owned(),
})?;
let base_local = allocate_aggregate(crate::types::CLOSURE_VALUE_SIZE, sink, ctx)?;
sink.local_get(base_local);
sink.i32_const(funcref_signed);
sink.i32_store(MemArg {
offset: u64::from(crate::types::CLOSURE_FUNCREF_OFFSET),
align: 2, memory_index: MEMORY_INDEX,
});
sink.local_get(base_local);
lower_expr(env_struct, sink, ctx)?;
sink.i32_store(MemArg {
offset: u64::from(crate::types::CLOSURE_ENV_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
});
sink.local_get(base_local);
Ok(())
}
pub fn lower_enum_inst(
expr: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let IrExpr::EnumInst {
enum_id,
variant,
variant_idx,
fields,
ty,
..
} = expr
else {
return Err(LowerError::NotYetImplemented {
what: "lower_enum_inst called with non-EnumInst expression".to_owned(),
});
};
let id = enum_id.ok_or(LowerError::ExternalEnumInst)?;
let module = ctx.module()?;
let e_decl = module
.enums
.get(id.0 as usize)
.ok_or(LowerError::UnknownEnum(id))?;
let type_args = crate::compound::generic_args_for_enum(ty, id);
let e_owned = crate::compound::substitute_enum(e_decl, &e_decl.generic_params, type_args);
let e = &e_owned;
let layout = plan_enum(e, module)?;
let (variant_layout, variant_def) =
resolve_variant(e, &layout.variants, variant_idx.0, variant)?;
let base_local = allocate_aggregate(layout.size, sink, ctx)?;
sink.local_get(base_local);
let tag_signed = i32::try_from(variant_layout.tag).unwrap_or(i32::MAX);
sink.i32_const(tag_signed);
sink.i32_store(MemArg {
offset: u64::from(layout.tag_offset),
align: align_to_log2(ENUM_TAG_ALIGN),
memory_index: MEMORY_INDEX,
});
for (field_name, _idx, value_expr) in fields {
let (field_layout, field_def) =
lookup_variant_field_by_name(variant_def, &variant_layout.fields, field_name)?;
sink.local_get(base_local);
store_aggregate_field(value_expr, &field_def.ty, *field_layout, sink, ctx)?;
}
sink.local_get(base_local);
Ok(())
}
fn resolve_variant<'a>(
e: &'a IrEnum,
variants: &'a [VariantLayout],
idx: u32,
name: &str,
) -> Result<(&'a VariantLayout, &'a IrEnumVariant), LowerError> {
let i = idx as usize;
if let Some(vl) = variants.get(i)
&& let Some(vd) = e.variants.get(i)
&& vl.name == vd.name
{
return Ok((vl, vd));
}
for (vl, vd) in variants.iter().zip(e.variants.iter()) {
if vd.name == name {
return Ok((vl, vd));
}
}
Err(LowerError::UnknownVariant {
enum_name: e.name.clone(),
variant: name.to_owned(),
})
}
fn lookup_variant_field_by_name<'a>(
variant_def: &'a IrEnumVariant,
field_layouts: &'a [FieldLayout],
name: &str,
) -> Result<(&'a FieldLayout, &'a IrField), LowerError> {
for (i, f) in variant_def.fields.iter().enumerate() {
if f.name == name {
let fl = field_layouts
.get(i)
.ok_or_else(|| LowerError::FieldIndexOutOfRange {
struct_name: variant_def.name.clone(),
field_count: variant_def.fields.len(),
field_idx: u32::try_from(i).unwrap_or(u32::MAX),
})?;
return Ok((fl, f));
}
}
Err(LowerError::FieldIndexOutOfRange {
struct_name: variant_def.name.clone(),
field_count: variant_def.fields.len(),
field_idx: u32::MAX,
})
}
pub fn lower_tuple(
expr: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let IrExpr::Tuple { fields, ty, .. } = expr else {
return Err(LowerError::NotYetImplemented {
what: "lower_tuple called with non-Tuple expression".to_owned(),
});
};
let module = ctx.module()?;
let synthetic = synthetic_struct_for_tuple(ty)?;
let layout = plan_struct(&synthetic, module)?;
let base_local = allocate_aggregate(layout.size, sink, ctx)?;
for (name, value_expr) in fields {
let (field_layout, field_def) =
lookup_field_by_name_with_meta(&synthetic.fields, &layout.fields, name, "__tuple")?;
sink.local_get(base_local);
store_aggregate_field(value_expr, &field_def.ty, *field_layout, sink, ctx)?;
}
sink.local_get(base_local);
Ok(())
}
pub fn lower_array(
expr: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let IrExpr::Array { elements, ty, .. } = expr else {
return Err(LowerError::NotYetImplemented {
what: "lower_array called with non-Array expression".to_owned(),
});
};
let module = ctx.module()?;
let elem_ty =
crate::compound::array_elem(ty, module).ok_or_else(|| LowerError::NotYetImplemented {
what: format!("Array literal carrying non-Array type {ty:?}"),
})?;
let layout = plan_array(elem_ty, module)?;
let len_u32 = u32::try_from(elements.len()).map_err(|_| LowerError::NotYetImplemented {
what: "array literal with more than u32::MAX elements".to_owned(),
})?;
let len_signed = i32::try_from(len_u32).map_err(|_| LowerError::NotYetImplemented {
what: "array literal length exceeds i32::MAX".to_owned(),
})?;
let buffer_size =
layout
.element_size
.checked_mul(len_u32)
.ok_or_else(|| LayoutError::SizeOverflow {
name: "<array buffer>".to_owned(),
})?;
let buf_local = allocate_aggregate(buffer_size, sink, ctx)?;
for (i, element) in elements.iter().enumerate() {
let i_u32 = u32::try_from(i).unwrap_or(u32::MAX);
let offset =
layout
.element_size
.checked_mul(i_u32)
.ok_or_else(|| LayoutError::SizeOverflow {
name: "<array element offset>".to_owned(),
})?;
sink.local_get(buf_local);
super::optional::lower_coerced(element, elem_ty, sink, ctx)?;
store_array_element(elem_ty, layout, offset, sink)?;
}
let header_local = allocate_aggregate(layout.header_size, sink, ctx)?;
finalize_array_header(header_local, buf_local, HeaderLen::Const(len_signed), sink);
Ok(())
}
#[derive(Debug, Clone, Copy)]
pub(super) enum HeaderLen {
Const(i32),
Local(u32),
}
pub(super) fn finalize_array_header(
header_local: u32,
buf_local: u32,
len: HeaderLen,
sink: &mut InstructionSink<'_>,
) {
let mem_arg = |offset: u64| MemArg {
offset,
align: align_to_log2(ARRAY_HEADER_ALIGN),
memory_index: MEMORY_INDEX,
};
let push_len = |sink: &mut InstructionSink<'_>| match len {
HeaderLen::Const(c) => {
sink.i32_const(c);
}
HeaderLen::Local(idx) => {
sink.local_get(idx);
}
};
sink.local_get(header_local);
sink.local_get(buf_local);
sink.i32_store(mem_arg(u64::from(ARRAY_HEADER_PTR_OFFSET)));
sink.local_get(header_local);
push_len(sink);
sink.i32_store(mem_arg(u64::from(ARRAY_HEADER_LEN_OFFSET)));
sink.local_get(header_local);
push_len(sink);
sink.i32_store(mem_arg(u64::from(ARRAY_HEADER_CAP_OFFSET)));
sink.local_get(header_local);
}
fn store_array_element(
elem_ty: &ResolvedType,
layout: ArrayLayout,
offset: u32,
sink: &mut InstructionSink<'_>,
) -> Result<(), LowerError> {
let field_layout = FieldLayout {
offset,
size: layout.element_size,
align: layout.element_align,
};
match elem_ty {
ResolvedType::Primitive(p) => {
store_primitive(*p, field_layout, sink);
Ok(())
}
ResolvedType::Struct(_)
| ResolvedType::Enum(_)
| ResolvedType::Tuple(_)
| ResolvedType::Generic { .. } => {
sink.i32_store(field_mem_arg(field_layout));
Ok(())
}
ResolvedType::Closure { .. }
| ResolvedType::Trait(_)
| ResolvedType::TypeParam(_)
| ResolvedType::External { .. }
| ResolvedType::Error => Err(LowerError::NotYetImplemented {
what: format!("array element of type {elem_ty:?}"),
}),
}
}
pub fn lower_dict_literal(
expr: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let IrExpr::DictLiteral { entries, ty, .. } = expr else {
return Err(LowerError::NotYetImplemented {
what: "lower_dict_literal called with non-DictLiteral expression".to_owned(),
});
};
let module = ctx.module()?;
let (key_ty, value_ty) = crate::compound::dictionary_kv(ty, module).ok_or_else(|| {
LowerError::NotYetImplemented {
what: format!("DictLiteral carrying non-Dictionary type {ty:?}"),
}
})?;
let pair_struct = dict_pair_struct(key_ty, value_ty);
let pair_layout = plan_struct(&pair_struct, module)?;
let key_field_layout =
pair_layout
.fields
.first()
.copied()
.ok_or_else(|| LowerError::NotYetImplemented {
what: "dict pair tuple has no fields".to_owned(),
})?;
let value_field_layout =
pair_layout
.fields
.get(1)
.copied()
.ok_or_else(|| LowerError::NotYetImplemented {
what: "dict pair tuple has fewer than two fields".to_owned(),
})?;
let len_u32 = u32::try_from(entries.len()).map_err(|_| LowerError::NotYetImplemented {
what: "dict literal with more than u32::MAX entries".to_owned(),
})?;
let len_signed = i32::try_from(len_u32).map_err(|_| LowerError::NotYetImplemented {
what: "dict literal length exceeds i32::MAX".to_owned(),
})?;
let buffer_size =
len_u32
.checked_mul(POINTER_SIZE_CONST)
.ok_or_else(|| LayoutError::SizeOverflow {
name: "<dict buffer>".to_owned(),
})?;
let buf_local = allocate_aggregate(buffer_size, sink, ctx)?;
for (i, (k_expr, v_expr)) in entries.iter().enumerate() {
let i_u32 = u32::try_from(i).unwrap_or(u32::MAX);
let slot_offset =
i_u32
.checked_mul(POINTER_SIZE_CONST)
.ok_or_else(|| LayoutError::SizeOverflow {
name: "<dict slot offset>".to_owned(),
})?;
let pair_local = allocate_aggregate(pair_layout.size, sink, ctx)?;
let key_prim = primitive_of(key_ty)?;
sink.local_get(pair_local);
super::optional::lower_coerced(k_expr, key_ty, sink, ctx)?;
store_primitive(key_prim, key_field_layout, sink);
let value_prim = primitive_of(value_ty)?;
sink.local_get(pair_local);
super::optional::lower_coerced(v_expr, value_ty, sink, ctx)?;
store_primitive(value_prim, value_field_layout, sink);
sink.local_get(buf_local);
sink.local_get(pair_local);
sink.i32_store(MemArg {
offset: u64::from(slot_offset),
align: 2,
memory_index: MEMORY_INDEX,
});
}
let header_local = allocate_aggregate(crate::layout::DICTIONARY_HEADER_SIZE, sink, ctx)?;
finalize_array_header(header_local, buf_local, HeaderLen::Const(len_signed), sink);
Ok(())
}
const POINTER_SIZE_CONST: u32 = 4;
pub fn lower_dict_access(
expr: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let IrExpr::DictAccess { dict, key, .. } = expr else {
return Err(LowerError::NotYetImplemented {
what: "lower_dict_access called with non-DictAccess expression".to_owned(),
});
};
let coll_ty = dict.ty();
let module = ctx.module()?;
if let Some(elem_ty) = crate::compound::array_elem(coll_ty, module) {
return lower_array_index(dict, key, elem_ty, sink, ctx);
}
if let Some((key_ty, value_ty)) = crate::compound::dictionary_kv(coll_ty, module) {
return lower_dict_lookup(dict, key, key_ty, value_ty, sink, ctx);
}
match coll_ty {
ResolvedType::Primitive(_)
| ResolvedType::Struct(_)
| ResolvedType::Trait(_)
| ResolvedType::Enum(_)
| ResolvedType::Tuple(_)
| ResolvedType::Generic { .. }
| ResolvedType::TypeParam(_)
| ResolvedType::External { .. }
| ResolvedType::Closure { .. }
| ResolvedType::Error => Err(LowerError::NotYetImplemented {
what: format!("DictAccess on collection type {coll_ty:?}"),
}),
}
}
fn dict_pair_struct(key_ty: &ResolvedType, value_ty: &ResolvedType) -> IrStruct {
use formalang::ast::ParamConvention;
use formalang::ast::Visibility;
use formalang::ir::IrSpan;
let field = |name: &str, ty: ResolvedType| IrField {
name: name.to_owned(),
ty,
mutable: false,
optional: false,
default: None,
doc: None,
convention: ParamConvention::Let,
span: IrSpan::default(),
};
IrStruct {
name: "__dict_pair".to_owned(),
visibility: Visibility::Private,
traits: Vec::new(),
fields: vec![field("k", key_ty.clone()), field("v", value_ty.clone())],
generic_params: Vec::new(),
doc: None,
span: IrSpan::default(),
}
}
#[expect(
clippy::too_many_lines,
reason = "single self-contained linear-scan loop; splitting hides the block / loop / br_if structure"
)]
fn lower_dict_lookup(
dict: &IrExpr,
key: &IrExpr,
key_ty: &ResolvedType,
value_ty: &ResolvedType,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
use formalang::ast::PrimitiveType;
use wasm_encoder::BlockType;
if !matches!(key_ty, ResolvedType::Primitive(PrimitiveType::String)) {
return Err(LowerError::NotYetImplemented {
what: format!("Dictionary lookup with key type {key_ty:?} (Phase 2 v1: String only)"),
});
}
let value_prim = primitive_of(value_ty).map_err(|_| LowerError::NotYetImplemented {
what: format!("Dictionary value type {value_ty:?} (Phase 2 v1: primitive only)"),
})?;
if matches!(value_prim, PrimitiveType::Never) {
return Err(LowerError::NotYetImplemented {
what: "Dictionary value type Never".to_owned(),
});
}
let module = ctx.module()?;
let pair_struct = dict_pair_struct(key_ty, value_ty);
let pair_layout = plan_struct(&pair_struct, module)?;
let value_field_layout =
pair_layout
.fields
.get(1)
.copied()
.ok_or_else(|| LowerError::NotYetImplemented {
what: "dict pair tuple has fewer than two fields".to_owned(),
})?;
let value_block_ty = match value_prim {
PrimitiveType::Boolean
| PrimitiveType::I32
| PrimitiveType::String
| PrimitiveType::Path
| PrimitiveType::Regex => BlockType::Result(ValType::I32),
PrimitiveType::I64 => BlockType::Result(ValType::I64),
PrimitiveType::F32 => BlockType::Result(ValType::F32),
PrimitiveType::F64 => BlockType::Result(ValType::F64),
PrimitiveType::Never | _ => {
return Err(LowerError::NotYetImplemented {
what: format!("Dictionary value primitive {value_prim:?}"),
});
}
};
let str_eq_idx = ctx.str_eq_index()?;
let dict_local = ctx.next_scratch_local(ValType::I32)?;
let buf_local = ctx.next_scratch_local(ValType::I32)?;
let len_local = ctx.next_scratch_local(ValType::I32)?;
let i_local = ctx.next_scratch_local(ValType::I32)?;
let target_local = ctx.next_scratch_local(ValType::I32)?;
let pair_local = ctx.next_scratch_local(ValType::I32)?;
lower_expr(dict, sink, ctx)?;
sink.local_set(dict_local);
sink.local_get(dict_local);
sink.i32_load(MemArg {
offset: u64::from(ARRAY_HEADER_PTR_OFFSET),
align: align_to_log2(ARRAY_HEADER_ALIGN),
memory_index: MEMORY_INDEX,
});
sink.local_set(buf_local);
sink.local_get(dict_local);
sink.i32_load(MemArg {
offset: u64::from(ARRAY_HEADER_LEN_OFFSET),
align: align_to_log2(ARRAY_HEADER_ALIGN),
memory_index: MEMORY_INDEX,
});
sink.local_set(len_local);
lower_expr(key, sink, ctx)?;
sink.local_set(target_local);
sink.i32_const(0);
sink.local_set(i_local);
sink.block(value_block_ty);
sink.loop_(BlockType::Empty);
sink.local_get(i_local);
sink.local_get(len_local);
sink.i32_ge_u();
sink.if_(BlockType::Empty);
sink.unreachable();
sink.end();
sink.local_get(buf_local);
sink.local_get(i_local);
sink.i32_const(4);
sink.i32_mul();
sink.i32_add();
sink.i32_load(MemArg {
offset: 0,
align: 2,
memory_index: MEMORY_INDEX,
});
sink.local_set(pair_local);
sink.local_get(pair_local);
sink.i32_load(MemArg {
offset: 0, align: 2,
memory_index: MEMORY_INDEX,
});
sink.local_get(target_local);
sink.call(str_eq_idx);
sink.if_(BlockType::Empty);
sink.local_get(pair_local);
load_primitive(value_prim, value_field_layout, sink);
sink.br(2);
sink.end();
sink.local_get(i_local);
sink.i32_const(1);
sink.i32_add();
sink.local_set(i_local);
sink.br(0);
sink.end(); sink.unreachable();
sink.end(); Ok(())
}
fn lower_array_index(
dict: &IrExpr,
key: &IrExpr,
elem_ty: &ResolvedType,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let module = ctx.module()?;
let layout = plan_array(elem_ty, module)?;
let elem_size_signed =
i32::try_from(layout.element_size).map_err(|_| LayoutError::SizeOverflow {
name: "<array element>".to_owned(),
})?;
lower_expr(dict, sink, ctx)?;
sink.i32_load(MemArg {
offset: u64::from(ARRAY_HEADER_PTR_OFFSET),
align: align_to_log2(ARRAY_HEADER_ALIGN),
memory_index: MEMORY_INDEX,
});
lower_expr(key, sink, ctx)?;
sink.i32_const(elem_size_signed);
sink.i32_mul();
sink.i32_add();
let field_layout = FieldLayout {
offset: 0,
size: layout.element_size,
align: layout.element_align,
};
load_array_element(elem_ty, field_layout, sink)
}
fn load_array_element(
elem_ty: &ResolvedType,
field_layout: FieldLayout,
sink: &mut InstructionSink<'_>,
) -> Result<(), LowerError> {
match elem_ty {
ResolvedType::Primitive(p) => {
load_primitive(*p, field_layout, sink);
Ok(())
}
ResolvedType::Struct(_)
| ResolvedType::Enum(_)
| ResolvedType::Tuple(_)
| ResolvedType::Generic { .. } => {
sink.i32_load(field_mem_arg(field_layout));
Ok(())
}
ResolvedType::Closure { .. }
| ResolvedType::Trait(_)
| ResolvedType::TypeParam(_)
| ResolvedType::External { .. }
| ResolvedType::Error => Err(LowerError::NotYetImplemented {
what: format!("array element of type {elem_ty:?}"),
}),
}
}
pub fn lower_range(
elem_ty: &ResolvedType,
left: &IrExpr,
right: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let module = ctx.module()?;
let layout = plan_range(elem_ty, module)?;
let primitive = primitive_of(elem_ty)?;
let base_local = allocate_aggregate(layout.size, sink, ctx)?;
sink.local_get(base_local);
lower_expr(left, sink, ctx)?;
store_primitive(primitive, range_field_layout(layout, 0), sink);
sink.local_get(base_local);
lower_expr(right, sink, ctx)?;
store_primitive(
primitive,
range_field_layout(layout, layout.end_offset),
sink,
);
sink.local_get(base_local);
Ok(())
}
const fn range_field_layout(layout: RangeLayout, offset: u32) -> FieldLayout {
FieldLayout {
offset,
size: layout.bound_size,
align: layout.bound_align,
}
}
pub fn lower_self_field_ref(
expr: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let IrExpr::SelfFieldRef { field, .. } = expr else {
return Err(LowerError::NotYetImplemented {
what: "lower_self_field_ref called with non-SelfFieldRef expression".to_owned(),
});
};
let struct_id = ctx.self_struct_id.ok_or(LowerError::MissingSelfStruct)?;
let module = ctx.module()?;
let s = module
.structs
.get(struct_id.0 as usize)
.ok_or(LowerError::UnknownStruct(struct_id))?;
let layout = plan_struct(s, module)?;
let (field_layout, field_def) = lookup_field_by_name(s, &layout.fields, field)?;
let primitive = primitive_of(&field_def.ty)?;
sink.local_get(0);
load_primitive(primitive, *field_layout, sink);
Ok(())
}
pub fn lower_field_access(
expr: &IrExpr,
sink: &mut InstructionSink<'_>,
ctx: &LowerContext<'_>,
) -> Result<(), LowerError> {
let IrExpr::FieldAccess { object, field, .. } = expr else {
return Err(LowerError::NotYetImplemented {
what: "lower_field_access called with non-FieldAccess expression".to_owned(),
});
};
let module = ctx.module()?;
let (layout, fields_meta) = layout_for_aggregate(object.ty(), module)?;
let (field_layout, field_def) = lookup_field_by_name_with_meta(
&fields_meta,
&layout.fields,
field,
&type_tag(object.ty()),
)?;
lower_expr(object, sink, ctx)?;
if let Ok(prim) = primitive_of(&field_def.ty) {
load_primitive(prim, *field_layout, sink);
} else {
sink.i32_load(field_mem_arg(*field_layout));
}
Ok(())
}
pub(super) fn layout_for_aggregate(
ty: &ResolvedType,
module: &IrModule,
) -> Result<(StructLayout, Vec<IrField>), LowerError> {
if let Some(id) = crate::compound::struct_id_of(ty) {
let s = module
.structs
.get(id.0 as usize)
.ok_or(LowerError::UnknownStruct(id))?;
let layout = plan_struct(s, module)?;
return Ok((layout, s.fields.clone()));
}
match ty {
ResolvedType::Tuple(_) => {
let synthetic = synthetic_struct_for_tuple(ty)?;
let layout = plan_struct(&synthetic, module)?;
Ok((layout, synthetic.fields))
}
ResolvedType::Primitive(_)
| ResolvedType::Trait(_)
| ResolvedType::Enum(_)
| ResolvedType::Struct(_)
| ResolvedType::Generic { .. }
| ResolvedType::TypeParam(_)
| ResolvedType::External { .. }
| ResolvedType::Closure { .. }
| ResolvedType::Error => Err(LowerError::FieldAccessOnNonAggregate { ty: ty.clone() }),
}
}
pub(crate) fn synthetic_struct_for_tuple(ty: &ResolvedType) -> Result<IrStruct, LowerError> {
let ResolvedType::Tuple(fields) = ty else {
return Err(LowerError::FieldAccessOnNonAggregate { ty: ty.clone() });
};
Ok(IrStruct {
name: "__tuple".to_owned(),
visibility: formalang::ast::Visibility::Private,
traits: Vec::new(),
fields: fields
.iter()
.map(|(field_name, field_ty)| IrField {
name: field_name.clone(),
ty: field_ty.clone(),
mutable: false,
optional: false,
default: None,
doc: None,
convention: formalang::ast::ParamConvention::Let,
span: formalang::ir::IrSpan::default(),
})
.collect(),
generic_params: Vec::new(),
doc: None,
span: formalang::ir::IrSpan::default(),
})
}
pub(super) fn lookup_field_by_name_with_meta<'a>(
fields_meta: &'a [IrField],
field_layouts: &'a [FieldLayout],
name: &str,
aggregate_tag: &str,
) -> Result<(&'a FieldLayout, &'a IrField), LowerError> {
for (i, f) in fields_meta.iter().enumerate() {
if f.name == name {
let fl = field_layouts
.get(i)
.ok_or_else(|| LowerError::FieldIndexOutOfRange {
struct_name: aggregate_tag.to_owned(),
field_count: fields_meta.len(),
field_idx: u32::try_from(i).unwrap_or(u32::MAX),
})?;
return Ok((fl, f));
}
}
Err(LowerError::FieldIndexOutOfRange {
struct_name: aggregate_tag.to_owned(),
field_count: fields_meta.len(),
field_idx: u32::MAX,
})
}
fn type_tag(ty: &ResolvedType) -> String {
match ty {
ResolvedType::Struct(_) => "<struct>".to_owned(),
ResolvedType::Tuple(_) => "__tuple".to_owned(),
ResolvedType::Primitive(_)
| ResolvedType::Trait(_)
| ResolvedType::Enum(_)
| ResolvedType::Generic { .. }
| ResolvedType::TypeParam(_)
| ResolvedType::External { .. }
| ResolvedType::Closure { .. }
| ResolvedType::Error => "<non-aggregate>".to_owned(),
}
}
pub(super) fn field_mem_arg(layout: FieldLayout) -> MemArg {
MemArg {
offset: u64::from(layout.offset),
align: align_to_log2(layout.align),
memory_index: MEMORY_INDEX,
}
}
const fn align_to_log2(align: u32) -> u32 {
match align {
2 => 1,
4 => 2,
8 => 3,
_ => 0,
}
}
pub(super) fn primitive_of(ty: &ResolvedType) -> Result<PrimitiveType, LowerError> {
match ty {
ResolvedType::Primitive(p) => Ok(*p),
ResolvedType::Struct(_)
| ResolvedType::Trait(_)
| ResolvedType::Enum(_)
| ResolvedType::Tuple(_)
| ResolvedType::Generic { .. }
| ResolvedType::TypeParam(_)
| ResolvedType::External { .. }
| ResolvedType::Closure { .. }
| ResolvedType::Error => Err(LowerError::FieldAccessOnNonAggregate { ty: ty.clone() }),
}
}
pub(super) fn lookup_field_by_name<'a>(
s: &'a IrStruct,
field_layouts: &'a [FieldLayout],
name: &str,
) -> Result<(&'a FieldLayout, &'a formalang::ir::IrField), LowerError> {
for (i, f) in s.fields.iter().enumerate() {
if f.name == name {
let fl = field_layouts
.get(i)
.ok_or_else(|| LowerError::FieldIndexOutOfRange {
struct_name: s.name.clone(),
field_count: s.fields.len(),
field_idx: u32::try_from(i).unwrap_or(u32::MAX),
})?;
return Ok((fl, f));
}
}
Err(LowerError::FieldIndexOutOfRange {
struct_name: s.name.clone(),
field_count: s.fields.len(),
field_idx: u32::MAX,
})
}