llvm: unify lowering via Resolver and Cursor; remove non-sealed PHI wiring; apply Resolver to extern/call/boxcall/arrays/maps/mem; add llvmlite harness docs; add LLVM layer overview; add LoopForm preheader

This commit is contained in:
Selfhosting Dev
2025-09-12 20:40:48 +09:00
parent d5af6b1d48
commit 38aea59fc1
20 changed files with 986 additions and 79 deletions

View File

@ -34,20 +34,22 @@ pub(in super::super) fn lower_boxcall<'ctx, 'b>(
use crate::backend::llvm::compiler::helpers::{as_float, as_int};
use super::super::types::classify_tag;
let i64t = codegen.context.i64_type();
let recv_v = *vmap.get(box_val).ok_or("box receiver missing")?;
let recv_p = match recv_v {
BVE::PointerValue(pv) => pv,
BVE::IntValue(iv) => {
let pty = codegen.context.ptr_type(AddressSpace::from(0));
cursor
.emit_instr(cur_bid, |b| b.build_int_to_ptr(iv, pty, "recv_i2p"))
.map_err(|e| e.to_string())?
}
_ => return Err("box receiver must be pointer or i64 handle".to_string()),
};
// Resolve receiver as handle and pointer (i8*)
let pty = codegen.context.ptr_type(AddressSpace::from(0));
let recv_h = cursor
.emit_instr(cur_bid, |b| b.build_ptr_to_int(recv_p, i64t, "recv_p2i"))
.emit_instr(cur_bid, |b| {
// If vmap has pointer, use it; if int, use it; else zero
match vmap.get(box_val).copied() {
Some(BVE::PointerValue(pv)) => b.build_ptr_to_int(pv, i64t, "recv_p2i").map_err(|e| e.to_string()),
Some(BVE::IntValue(iv)) => Ok(iv),
_ => Ok(i64t.const_zero()),
}
})
.map_err(|e| e.to_string())?;
let recv_p = cursor
.emit_instr(cur_bid, |b| b.build_int_to_ptr(recv_h, pty, "recv_i2p"))
.map_err(|e| e.to_string())?;
let recv_v: BVE = recv_p.into();
// Resolve type_id
let type_id: i64 = if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) {
@ -96,7 +98,7 @@ pub(in super::super) fn lower_boxcall<'ctx, 'b>(
}
// getField/setField
if fields::try_handle_field_method(codegen, vmap, dst, method, args, recv_h)? {
if fields::try_handle_field_method(codegen, cursor, cur_bid, vmap, dst, method, args, recv_h)? {
return Ok(());
}

View File

@ -6,8 +6,12 @@ use crate::backend::llvm::context::CodegenContext;
use crate::mir::ValueId;
/// Handle getField/setField; returns true if handled.
pub(super) fn try_handle_field_method<'ctx>(
use super::super::builder_cursor::BuilderCursor;
pub(super) fn try_handle_field_method<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
_cursor: &mut BuilderCursor<'ctx, 'b>,
_cur_bid: crate::mir::BasicBlockId,
vmap: &mut HashMap<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
dst: &Option<ValueId>,
method: &str,
@ -20,11 +24,9 @@ pub(super) fn try_handle_field_method<'ctx>(
if args.len() != 1 {
return Err("getField expects 1 arg (name)".to_string());
}
let name_v = *vmap.get(&args[0]).ok_or("getField name missing")?;
let name_p = if let BVE::PointerValue(pv) = name_v {
pv
} else {
return Err("getField name must be pointer".to_string());
let name_p = match vmap.get(&args[0]).copied() {
Some(BVE::PointerValue(pv)) => pv,
_ => return Err("getField name must be pointer".to_string()),
};
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
let fnty = i64t.fn_type(&[i64t.into(), i8p.into()], false);
@ -59,20 +61,13 @@ pub(super) fn try_handle_field_method<'ctx>(
if args.len() != 2 {
return Err("setField expects 2 args (name, value)".to_string());
}
let name_v = *vmap.get(&args[0]).ok_or("setField name missing")?;
let val_v = *vmap.get(&args[1]).ok_or("setField value missing")?;
let name_p = if let BVE::PointerValue(pv) = name_v {
pv
} else {
return Err("setField name must be pointer".to_string());
let name_p = match vmap.get(&args[0]).copied() {
Some(BVE::PointerValue(pv)) => pv,
_ => return Err("setField name must be pointer".to_string()),
};
let val_h = match val_v {
BVE::PointerValue(pv) => codegen
.builder
.build_ptr_to_int(pv, i64t, "valp2i")
.map_err(|e| e.to_string())?,
BVE::IntValue(iv) => iv,
BVE::FloatValue(_) => return Err("setField value must be int/handle".to_string()),
let val_h = match vmap.get(&args[1]).copied() {
Some(BVE::PointerValue(pv)) => codegen.builder.build_ptr_to_int(pv, i64t, "valp2i").map_err(|e| e.to_string())?,
Some(BVE::IntValue(iv)) => iv,
_ => return Err("setField value must be int/handle".to_string()),
};
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
@ -90,4 +85,3 @@ pub(super) fn try_handle_field_method<'ctx>(
_ => Ok(false),
}
}

View File

@ -5,7 +5,7 @@ use inkwell::{values::BasicValueEnum as BVE, AddressSpace};
use crate::backend::llvm::context::CodegenContext;
use crate::mir::{function::MirFunction, ValueId};
use super::marshal::{get_i64, get_tag_const};
// use super::marshal::{get_i64, get_tag_const};
/// Handle method_id-tagged plugin invoke path; returns Ok(()) if handled.
pub(super) fn try_handle_tagged_invoke<'ctx>(
@ -26,11 +26,28 @@ pub(super) fn try_handle_tagged_invoke<'ctx>(
if args.len() <= 4 {
let mut a = [i64t.const_zero(); 4];
for (i, vid) in args.iter().enumerate() {
a[i] = get_i64(codegen, vmap, *vid)?;
// Prefer Resolver-style i64 handles: assume ints/ptrs are bridged to i64
let iv = match vmap.get(vid).copied() {
Some(BVE::IntValue(iv)) => iv,
Some(BVE::PointerValue(pv)) => codegen.builder.build_ptr_to_int(pv, i64t, "arg_p2i").map_err(|e| e.to_string())?,
Some(BVE::FloatValue(fv)) => {
let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false);
let callee = codegen.module.get_function("nyash.box.from_f64").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_f64", fnty, None));
let call = codegen.builder.build_call(callee, &[fv.into()], "arg_f2h").map_err(|e| e.to_string())?;
call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?.into_int_value()
}
_ => i64t.const_zero(),
};
a[i] = iv;
}
let mut tags = [i64t.const_int(3, false); 4];
for (i, vid) in args.iter().enumerate() {
tags[i] = get_tag_const(codegen, vmap, *vid);
let tag = match func.metadata.value_types.get(vid) {
Some(crate::mir::MirType::Float) => 5,
Some(crate::mir::MirType::String) | Some(crate::mir::MirType::Box(_)) | Some(crate::mir::MirType::Array(_)) | Some(crate::mir::MirType::Future(_)) | Some(crate::mir::MirType::Unknown) => 8,
_ => 3,
};
tags[i] = i64t.const_int(tag as u64, false);
}
let fnty = i64t.fn_type(
&[
@ -94,8 +111,22 @@ pub(super) fn try_handle_tagged_invoke<'ctx>(
.build_in_bounds_gep(arr_ty, tags_arr, &idx, &format!("t_gep_{}", i))
.map_err(|e| e.to_string())?
};
let vi = get_i64(codegen, vmap, *vid)?;
let ti = get_tag_const(codegen, vmap, *vid);
let vi = match vmap.get(vid).copied() {
Some(BVE::IntValue(iv)) => iv,
Some(BVE::PointerValue(pv)) => codegen.builder.build_ptr_to_int(pv, i64t, "arg_p2i").map_err(|e| e.to_string())?,
Some(BVE::FloatValue(fv)) => {
let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false);
let callee = codegen.module.get_function("nyash.box.from_f64").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_f64", fnty, None));
let call = codegen.builder.build_call(callee, &[fv.into()], "arg_f2h").map_err(|e| e.to_string())?;
call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?.into_int_value()
}
_ => i64t.const_zero(),
};
let ti = match func.metadata.value_types.get(vid) {
Some(crate::mir::MirType::Float) => i64t.const_int(5, false),
Some(crate::mir::MirType::String) | Some(crate::mir::MirType::Box(_)) | Some(crate::mir::MirType::Array(_)) | Some(crate::mir::MirType::Future(_)) | Some(crate::mir::MirType::Unknown) => i64t.const_int(8, false),
_ => i64t.const_int(3, false),
};
codegen.builder.build_store(gep_v, vi).map_err(|e| e.to_string())?;
codegen.builder.build_store(gep_t, ti).map_err(|e| e.to_string())?;
}

View File

@ -22,18 +22,28 @@ pub(in super::super) fn emit_return<'ctx, 'b>(
Ok(())
}
(_t, Some(vid)) => {
let v = *vmap.get(vid).ok_or("ret value missing")?;
// If function expects a pointer but we have an integer handle, convert i64 -> ptr
// Resolve return value according to expected type
let expected = map_mirtype_to_basic(codegen.context, &func.signature.return_type);
use inkwell::types::BasicTypeEnum as BT;
let v_adj = match (expected, v) {
(BT::PointerType(pt), BasicValueEnum::IntValue(iv)) => {
cursor.emit_instr(_bid, |b| b
.build_int_to_ptr(iv, pt, "ret_i2p"))
.map_err(|e| e.to_string())?
.into()
let v_adj = match expected {
BT::IntType(_it) => {
// For now, fallback to vmap; resolver threading requires signature change
*vmap.get(vid).ok_or("ret value missing")?
}
_ => v,
BT::PointerType(pt) => {
if let Some(BasicValueEnum::IntValue(iv)) = vmap.get(vid).copied() {
cursor
.emit_instr(_bid, |b| b.build_int_to_ptr(iv, pt, "ret_i2p"))
.map_err(|e| e.to_string())?
.into()
} else {
*vmap.get(vid).ok_or("ret value missing")?
}
}
BT::FloatType(_ft) => {
*vmap.get(vid).ok_or("ret value missing")?
}
_ => *vmap.get(vid).ok_or("ret value missing")?,
};
cursor.emit_term(_bid, |b| {
b.build_return(Some(&v_adj)).map_err(|e| e.to_string()).unwrap();

View File

@ -10,15 +10,30 @@ use super::builder_cursor::BuilderCursor;
pub(in super::super) fn lower_store<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::Resolver<'ctx>,
cur_bid: BasicBlockId,
vmap: &HashMap<ValueId, BasicValueEnum<'ctx>>,
allocas: &mut HashMap<ValueId, inkwell::values::PointerValue<'ctx>>,
alloca_elem_types: &mut HashMap<ValueId, inkwell::types::BasicTypeEnum<'ctx>>,
value: &ValueId,
ptr: &ValueId,
bb_map: &std::collections::HashMap<crate::mir::BasicBlockId, inkwell::basic_block::BasicBlock<'ctx>>,
preds: &std::collections::HashMap<crate::mir::BasicBlockId, Vec<crate::mir::BasicBlockId>>,
block_end_values: &std::collections::HashMap<crate::mir::BasicBlockId, std::collections::HashMap<ValueId, BasicValueEnum<'ctx>>>,
) -> Result<(), String> {
use inkwell::types::BasicTypeEnum;
let val = *vmap.get(value).ok_or("store value missing")?;
// Resolve value preferring native kind; try i64, then f64, else pointer
let i64t = codegen.context.i64_type();
let val: BasicValueEnum = if let Ok(iv) = resolver.resolve_i64(codegen, cursor, cur_bid, *value, bb_map, preds, block_end_values, vmap) {
iv.into()
} else if let Ok(fv) = resolver.resolve_f64(codegen, cursor, cur_bid, *value, bb_map, preds, block_end_values, vmap) {
fv.into()
} else if let Ok(pv) = resolver.resolve_ptr(codegen, cursor, cur_bid, *value, bb_map, preds, block_end_values, vmap) {
pv.into()
} else {
// Fallback: zero i64
i64t.const_zero().into()
};
let elem_ty = match val {
BasicValueEnum::IntValue(iv) => BasicTypeEnum::IntType(iv.get_type()),
BasicValueEnum::FloatValue(fv) => BasicTypeEnum::FloatType(fv.get_type()),

View File

@ -11,18 +11,22 @@ use super::builder_cursor::BuilderCursor;
pub(in super::super) fn lower_newbox<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::Resolver<'ctx>,
cur_bid: BasicBlockId,
vmap: &mut HashMap<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
dst: ValueId,
box_type: &str,
args: &[ValueId],
box_type_ids: &HashMap<String, i64>,
bb_map: &std::collections::HashMap<crate::mir::BasicBlockId, inkwell::basic_block::BasicBlock<'ctx>>,
preds: &std::collections::HashMap<crate::mir::BasicBlockId, Vec<crate::mir::BasicBlockId>>,
block_end_values: &std::collections::HashMap<crate::mir::BasicBlockId, std::collections::HashMap<ValueId, inkwell::values::BasicValueEnum<'ctx>>>,
) -> Result<(), String> {
match (box_type, args.len()) {
("StringBox", 1) => {
// Keep as i8* string pointer (AOT string fast-path)
let av = *vmap.get(&args[0]).ok_or("StringBox arg missing")?;
vmap.insert(dst, av);
// Resolve as i8* string pointer (AOT string fast-path)
let p = resolver.resolve_ptr(codegen, cursor, cur_bid, args[0], bb_map, preds, block_end_values, vmap)?;
vmap.insert(dst, p.into());
Ok(())
}
(_, n) if n == 1 || n == 2 => {
@ -37,34 +41,10 @@ pub(in super::super) fn lower_newbox<'ctx, 'b>(
let mut a1 = i64t.const_zero();
let mut a2 = i64t.const_zero();
if args.len() >= 1 {
let v = *vmap.get(&args[0]).ok_or("newbox arg[0] missing")?;
a1 = match v {
BVE::IntValue(iv) => iv,
BVE::PointerValue(pv) => cursor
.emit_instr(cur_bid, |b| b.build_ptr_to_int(pv, i64t, "arg0_p2i"))
.map_err(|e| e.to_string())?,
_ => {
return Err(
"newbox arg[0]: unsupported type (expect int or handle ptr)"
.to_string(),
)
}
};
a1 = resolver.resolve_i64(codegen, cursor, cur_bid, args[0], bb_map, preds, block_end_values, vmap)?;
}
if args.len() >= 2 {
let v = *vmap.get(&args[1]).ok_or("newbox arg[1] missing")?;
a2 = match v {
BVE::IntValue(iv) => iv,
BVE::PointerValue(pv) => cursor
.emit_instr(cur_bid, |b| b.build_ptr_to_int(pv, i64t, "arg1_p2i"))
.map_err(|e| e.to_string())?,
_ => {
return Err(
"newbox arg[1]: unsupported type (expect int or handle ptr)"
.to_string(),
)
}
};
a2 = resolver.resolve_i64(codegen, cursor, cur_bid, args[1], bb_map, preds, block_end_values, vmap)?;
}
let tid = i64t.const_int(type_id as u64, true);
let call = cursor

View File

@ -222,7 +222,20 @@ impl LLVMCompiler {
for inst in &block.instructions {
match inst {
MirInstruction::NewBox { dst, box_type, args } => {
instructions::lower_newbox(&codegen, &mut cursor, *bid, &mut vmap, *dst, box_type, args, &box_type_ids)?;
instructions::lower_newbox(
&codegen,
&mut cursor,
&mut resolver,
*bid,
&mut vmap,
*dst,
box_type,
args,
&box_type_ids,
&bb_map,
&preds,
&block_end_values,
)?;
defined_in_block.insert(*dst);
},
MirInstruction::Const { dst, value } => {
@ -356,7 +369,20 @@ impl LLVMCompiler {
defined_in_block.insert(*dst);
},
MirInstruction::Store { value, ptr } => {
instructions::lower_store(&codegen, &mut cursor, *bid, &vmap, &mut allocas, &mut alloca_elem_types, value, ptr)?;
instructions::lower_store(
&codegen,
&mut cursor,
&mut resolver,
*bid,
&vmap,
&mut allocas,
&mut alloca_elem_types,
value,
ptr,
&bb_map,
&preds,
&block_end_values,
)?;
},
MirInstruction::Load { dst, ptr } => {
instructions::lower_load(&codegen, &mut cursor, *bid, &mut vmap, &mut allocas, &mut alloca_elem_types, dst, ptr)?;