feat(llvm): Major refactor - BuilderCursor全域化 & Resolver API導入

Added:
- Resolver API (resolve_i64) for unified value resolution with per-block cache
- llvmlite harness (Python) for rapid PHI/SSA verification
- Comprehensive LLVM documentation suite:
  - LLVM_LAYER_OVERVIEW.md: Overall architecture and invariants
  - RESOLVER_API.md: Value resolution strategy
  - LLVM_HARNESS.md: Python verification harness

Updated:
- BuilderCursor applied to ALL lowering paths (externcall/newbox/arrays/maps/call)
- localize_to_i64 for dominance safety in strings/compare/flow
- NYASH_LLVM_DUMP_ON_FAIL=1 for debug IR output

Key insight: LoopForm didn't cause problems, it just exposed existing design flaws:
- Scattered value resolution (now unified via Resolver)
- Inconsistent type conversion placement
- Ambiguous PHI wiring responsibilities

Next: Wire Resolver throughout, achieve sealed=ON green for dep_tree_min_string
This commit is contained in:
Selfhosting Dev
2025-09-12 20:06:48 +09:00
parent 45f13cf7a8
commit c04b0c059d
16 changed files with 377 additions and 168 deletions

View File

@ -10,6 +10,7 @@ use super::builder_cursor::BuilderCursor;
pub(in super::super) fn lower_compare<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::Resolver<'ctx>,
cur_bid: BasicBlockId,
func: &MirFunction,
vmap: &HashMap<ValueId, BasicValueEnum<'ctx>>,
@ -104,9 +105,11 @@ pub(in super::super) fn lower_compare<'ctx, 'b>(
}
let out = if let (Some(_li0), Some(_ri0)) = (as_int(lv), as_int(rv)) {
// Localize integer operands into current block to satisfy dominance
let mut li = super::flow::localize_to_i64(codegen, cursor, cur_bid, *lhs, bb_map, preds, block_end_values, vmap)
let mut li = resolver
.resolve_i64(codegen, cursor, cur_bid, *lhs, bb_map, preds, block_end_values, vmap)
.unwrap_or_else(|_| as_int(lv).unwrap());
let mut ri = super::flow::localize_to_i64(codegen, cursor, cur_bid, *rhs, bb_map, preds, block_end_values, vmap)
let mut ri = resolver
.resolve_i64(codegen, cursor, cur_bid, *rhs, bb_map, preds, block_end_values, vmap)
.unwrap_or_else(|_| as_int(rv).unwrap());
// Normalize integer widths: extend the narrower to match the wider to satisfy LLVM
let lw = li.get_type().get_bit_width();

View File

@ -57,6 +57,7 @@ pub(in super::super) fn lower_unary<'ctx, 'b>(
pub(in super::super) fn lower_binop<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::Resolver<'ctx>,
cur_bid: BasicBlockId,
func: &MirFunction,
vmap: &mut HashMap<ValueId, BasicValueEnum<'ctx>>,
@ -64,6 +65,9 @@ pub(in super::super) fn lower_binop<'ctx, 'b>(
op: &BinaryOp,
lhs: &ValueId,
rhs: &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 crate::backend::llvm::compiler::helpers::{as_float, as_int};
use inkwell::values::BasicValueEnum as BVE;
@ -221,7 +225,12 @@ pub(in super::super) fn lower_binop<'ctx, 'b>(
return Ok(());
}
let out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) {
let out = if let (Some(_li0), Some(_ri0)) = (as_int(lv), as_int(rv)) {
// Localize integer operands into current block to satisfy dominance (normalize to i64)
let li = resolver.resolve_i64(codegen, cursor, cur_bid, *lhs, bb_map, preds, block_end_values, vmap)
.unwrap_or_else(|_| codegen.context.i64_type().const_zero());
let ri = resolver.resolve_i64(codegen, cursor, cur_bid, *rhs, bb_map, preds, block_end_values, vmap)
.unwrap_or_else(|_| codegen.context.i64_type().const_zero());
use BinaryOp as B;
match op {
B::Add => cursor.emit_instr(cur_bid, |b| b.build_int_add(li, ri, "iadd")).map_err(|e| e.to_string())?.into(),

View File

@ -16,6 +16,7 @@ use super::builder_cursor::BuilderCursor;
pub(in super::super) fn lower_boxcall<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::Resolver<'ctx>,
cur_bid: BasicBlockId,
func: &MirFunction,
vmap: &mut HashMap<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
@ -59,7 +60,7 @@ pub(in super::super) fn lower_boxcall<'ctx, 'b>(
// Delegate String methods
if super::strings::try_handle_string_method(
codegen, cursor, cur_bid, func, vmap, dst, box_val, method, args, recv_v,
codegen, cursor, resolver, cur_bid, func, vmap, dst, box_val, method, args, recv_v,
bb_map, preds, block_end_values,
)? {
return Ok(());
@ -77,7 +78,21 @@ pub(in super::super) fn lower_boxcall<'ctx, 'b>(
// Console convenience: treat println as env.console.log
if method == "println" {
return super::externcall::lower_externcall(codegen, cursor, cur_bid, func, vmap, dst, &"env.console".to_string(), &"log".to_string(), args);
return super::externcall::lower_externcall(
codegen,
cursor,
resolver,
cur_bid,
func,
vmap,
dst,
&"env.console".to_string(),
&"log".to_string(),
args,
bb_map,
preds,
block_end_values,
);
}
// getField/setField

View File

@ -10,12 +10,16 @@ use crate::backend::llvm::compiler::codegen::instructions::builder_cursor::Build
pub(super) fn lower_log_or_trace<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::super::Resolver<'ctx>,
cur_bid: BasicBlockId,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
dst: &Option<ValueId>,
iface_name: &str,
method_name: &str,
args: &[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, BVE<'ctx>>>,
) -> Result<(), String> {
if args.len() != 1 {
return Err(format!("{}.{} expects 1 arg", iface_name, method_name));
@ -49,23 +53,8 @@ pub(super) fn lower_log_or_trace<'ctx, 'b>(
}
// Otherwise, convert to i64 and call handle variant
_ => {
let arg_val = match av {
BVE::IntValue(iv) => {
if iv.get_type() == codegen.context.bool_type() {
cursor
.emit_instr(cur_bid, |b| b.build_int_z_extend(iv, codegen.context.i64_type(), "bool2i64"))
.map_err(|e| e.to_string())?
} else if iv.get_type() == codegen.context.i64_type() {
iv
} else {
cursor
.emit_instr(cur_bid, |b| b.build_int_s_extend(iv, codegen.context.i64_type(), "int2i64"))
.map_err(|e| e.to_string())?
}
}
BVE::PointerValue(_) => unreachable!(),
_ => return Err("console.log arg conversion failed".to_string()),
};
// Localize to i64 to satisfy dominance
let arg_val = resolver.resolve_i64(codegen, cursor, cur_bid, args[0], bb_map, preds, block_end_values, vmap)?;
let fnty = codegen
.context
.i64_type()

View File

@ -10,10 +10,14 @@ use crate::backend::llvm::compiler::codegen::instructions::builder_cursor::Build
pub(super) fn lower_future_spawn_instance<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::super::Resolver<'ctx>,
cur_bid: BasicBlockId,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
dst: &Option<ValueId>,
args: &[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, BVE<'ctx>>>,
) -> Result<(), String> {
if args.len() < 2 {
return Err("env.future.spawn_instance expects at least (recv, method_name)".to_string());
@ -22,10 +26,10 @@ pub(super) fn lower_future_spawn_instance<'ctx, 'b>(
let i8p = codegen.context.ptr_type(AddressSpace::from(0));
let recv_v = *vmap.get(&args[0]).ok_or("recv missing")?;
let recv_h = match recv_v {
BVE::IntValue(iv) => iv,
BVE::PointerValue(pv) => cursor
.emit_instr(cur_bid, |b| b.build_ptr_to_int(pv, i64t, "recv_p2i"))
.map_err(|e| e.to_string())?,
BVE::IntValue(_) | BVE::PointerValue(_) => {
// Localize to i64 to satisfy dominance; converts ptr→i64 if needed
resolver.resolve_i64(codegen, cursor, cur_bid, args[0], bb_map, preds, block_end_values, vmap)?
}
_ => return Err("spawn_instance recv must be int or ptr".to_string()),
};
let name_v = *vmap.get(&args[1]).ok_or("method name missing")?;
@ -54,11 +58,15 @@ pub(super) fn lower_future_spawn_instance<'ctx, 'b>(
pub(super) fn lower_local_get<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
_resolver: &mut super::super::Resolver<'ctx>,
cur_bid: BasicBlockId,
func: &MirFunction,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
dst: &Option<ValueId>,
args: &[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, BVE<'ctx>>>,
) -> Result<(), String> {
if args.len() != 1 {
return Err("env.local.get expects 1 arg".to_string());
@ -119,10 +127,14 @@ pub(super) fn lower_local_get<'ctx, 'b>(
pub(super) fn lower_box_new<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::super::Resolver<'ctx>,
cur_bid: BasicBlockId,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
dst: &Option<ValueId>,
args: &[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, BVE<'ctx>>>,
) -> Result<(), String> {
// Two variants: (name) and (argc, arg1, arg2, arg3, arg4) with optional ptr conversion
// Prefer the i64 birth when possible; else call env.box.new(name)
@ -186,7 +198,9 @@ pub(super) fn lower_box_new<'ctx, 'b>(
if args.len() >= 2 {
let bv = *vmap.get(&args[1]).ok_or("arg missing")?;
a1 = match bv {
BVE::IntValue(iv) => iv,
BVE::IntValue(_) | BVE::PointerValue(_) => {
resolver.resolve_i64(codegen, cursor, cur_bid, args[1], bb_map, preds, block_end_values, vmap)?
}
BVE::FloatValue(fv) => {
let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false);
let callee = codegen
@ -202,18 +216,7 @@ pub(super) fn lower_box_new<'ctx, 'b>(
.ok_or("from_f64 returned void".to_string())?;
if let BVE::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BVE::PointerValue(pv) => {
let fnty = i64t.fn_type(&[i8p.into()], false);
let callee = codegen
.module
.get_function("nyash.box.from_i8_string")
.unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty, None));
let call = cursor
.emit_instr(cur_bid, |b| b.build_call(callee, &[pv.into()], "arg1_i8_to_box"))
.map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?;
if let BVE::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
// Pointer handled above by resolve_i64
_ => return Err("unsupported arg value for env.box.new".to_string()),
};
}
@ -221,7 +224,9 @@ pub(super) fn lower_box_new<'ctx, 'b>(
if args.len() >= 3 {
let bv = *vmap.get(&args[2]).ok_or("arg missing")?;
a2 = match bv {
BVE::IntValue(iv) => iv,
BVE::IntValue(_) | BVE::PointerValue(_) => {
resolver.resolve_i64(codegen, cursor, cur_bid, args[2], bb_map, preds, block_end_values, vmap)?
}
BVE::FloatValue(fv) => {
let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false);
let callee = codegen
@ -237,18 +242,7 @@ pub(super) fn lower_box_new<'ctx, 'b>(
.ok_or("from_f64 returned void".to_string())?;
if let BVE::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BVE::PointerValue(pv) => {
let fnty = i64t.fn_type(&[i8p.into()], false);
let callee = codegen
.module
.get_function("nyash.box.from_i8_string")
.unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty, None));
let call = cursor
.emit_instr(cur_bid, |b| b.build_call(callee, &[pv.into()], "arg2_i8_to_box"))
.map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?;
if let BVE::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
// Pointer handled above by resolve_i64
_ => return Err("unsupported arg value for env.box.new".to_string()),
};
}

View File

@ -12,6 +12,7 @@ use crate::backend::llvm::compiler::codegen::instructions::builder_cursor::Build
pub(in super::super) fn lower_externcall<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::Resolver<'ctx>,
cur_bid: BasicBlockId,
func: &MirFunction,
vmap: &mut HashMap<ValueId, BVE<'ctx>>,
@ -19,13 +20,16 @@ pub(in super::super) fn lower_externcall<'ctx, 'b>(
iface_name: &str,
method_name: &str,
args: &[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, BVE<'ctx>>>,
) -> Result<(), String> {
// console/debug
if (iface_name == "env.console"
&& matches!(method_name, "log" | "warn" | "error"))
|| (iface_name == "env.debug" && method_name == "trace")
{
return console::lower_log_or_trace(codegen, cursor, cur_bid, vmap, dst, iface_name, method_name, args);
return console::lower_log_or_trace(codegen, cursor, resolver, cur_bid, vmap, dst, iface_name, method_name, args, bb_map, preds, block_end_values);
}
if iface_name == "env.console" && method_name == "readLine" {
return console::lower_readline(codegen, cursor, cur_bid, vmap, dst, args);
@ -33,13 +37,13 @@ pub(in super::super) fn lower_externcall<'ctx, 'b>(
// env.*
if iface_name == "env.future" && method_name == "spawn_instance" {
return env::lower_future_spawn_instance(codegen, cursor, cur_bid, vmap, dst, args);
return env::lower_future_spawn_instance(codegen, cursor, resolver, cur_bid, vmap, dst, args, bb_map, preds, block_end_values);
}
if iface_name == "env.local" && method_name == "get" {
return env::lower_local_get(codegen, cursor, cur_bid, func, vmap, dst, args);
return env::lower_local_get(codegen, cursor, resolver, cur_bid, func, vmap, dst, args, bb_map, preds, block_end_values);
}
if iface_name == "env.box" && method_name == "new" {
return env::lower_box_new(codegen, cursor, cur_bid, vmap, dst, args);
return env::lower_box_new(codegen, cursor, resolver, cur_bid, vmap, dst, args, bb_map, preds, block_end_values);
}
Err(format!(

View File

@ -56,38 +56,7 @@ pub(in super::super) fn emit_jump<'ctx, 'b>(
>,
vmap: &HashMap<ValueId, BasicValueEnum<'ctx>>,
) -> Result<(), String> {
let sealed = std::env::var("NYASH_LLVM_PHI_SEALED").ok().as_deref() == Some("1");
if !sealed {
if let Some(list) = phis_by_block.get(target) {
for (_dst, phi, inputs) in list {
if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) {
let mut val = *vmap.get(in_vid).ok_or("phi incoming value missing")?;
let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?;
// Coerce incoming to PHI type when needed
val = coerce_to_type(codegen, phi, val)?;
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
let tys = phi
.as_basic_value()
.get_type()
.print_to_string()
.to_string();
eprintln!(
"[PHI] incoming add pred_bb={} val={} ty={}",
bid.as_u32(),
in_vid.as_u32(),
tys
);
}
match val {
BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]),
BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]),
BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]),
_ => return Err("unsupported phi incoming value".to_string()),
}
}
}
}
}
// Non-sealed incoming wiring removed: rely on sealed snapshots and resolver-driven PHIs.
let tbb = *bb_map.get(target).ok_or("target bb missing")?;
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!("[LLVM] emit_jump: {} -> {}", bid.as_u32(), target.as_u32());
@ -101,6 +70,7 @@ pub(in super::super) fn emit_jump<'ctx, 'b>(
pub(in super::super) fn emit_branch<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut super::Resolver<'ctx>,
bid: BasicBlockId,
condition: &ValueId,
then_bb: &BasicBlockId,
@ -118,79 +88,13 @@ pub(in super::super) fn emit_branch<'ctx, 'b>(
let cond_v = *vmap.get(condition).ok_or("cond missing")?;
let b = match cond_v {
BasicValueEnum::IntValue(_) | BasicValueEnum::PointerValue(_) | BasicValueEnum::FloatValue(_) => {
let ci = localize_to_i64(codegen, cursor, bid, *condition, bb_map, preds, block_end_values, vmap)?;
let ci = resolver.resolve_i64(codegen, cursor, bid, *condition, bb_map, preds, block_end_values, vmap)?;
let zero = codegen.context.i64_type().const_zero();
codegen.builder.build_int_compare(inkwell::IntPredicate::NE, ci, zero, "cond_nez").map_err(|e| e.to_string())?
}
_ => to_bool(codegen.context, cond_v, &codegen.builder)?,
};
let sealed = std::env::var("NYASH_LLVM_PHI_SEALED").ok().as_deref() == Some("1");
// then
if !sealed {
if let Some(list) = phis_by_block.get(then_bb) {
for (_dst, phi, inputs) in list {
if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) {
let mut val = *vmap
.get(in_vid)
.ok_or("phi incoming (then) value missing")?;
let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?;
val = coerce_to_type(codegen, phi, val)?;
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
let tys = phi
.as_basic_value()
.get_type()
.print_to_string()
.to_string();
eprintln!(
"[PHI] incoming add (then) pred_bb={} val={} ty={}",
bid.as_u32(),
in_vid.as_u32(),
tys
);
}
match val {
BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]),
BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]),
BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]),
_ => return Err("unsupported phi incoming value (then)".to_string()),
}
}
}
}
}
// else
if !sealed {
if let Some(list) = phis_by_block.get(else_bb) {
for (_dst, phi, inputs) in list {
if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) {
let mut val = *vmap
.get(in_vid)
.ok_or("phi incoming (else) value missing")?;
let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?;
val = coerce_to_type(codegen, phi, val)?;
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
let tys = phi
.as_basic_value()
.get_type()
.print_to_string()
.to_string();
eprintln!(
"[PHI] incoming add (else) pred_bb={} val={} ty={}",
bid.as_u32(),
in_vid.as_u32(),
tys
);
}
match val {
BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]),
BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]),
BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]),
_ => return Err("unsupported phi incoming value (else)".to_string()),
}
}
}
}
}
// Non-sealed incoming wiring removed: rely on sealed snapshots and resolver-driven PHIs.
let tbb = *bb_map.get(then_bb).ok_or("then bb missing")?;
let ebb = *bb_map.get(else_bb).ok_or("else bb missing")?;
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {

View File

@ -13,6 +13,7 @@ mod maps;
mod arith_ops;
mod call;
mod loopform;
mod resolver;
pub(super) use blocks::{create_basic_blocks, precreate_phis};
pub(super) use flow::{emit_branch, emit_jump, emit_return};
@ -26,3 +27,4 @@ pub(super) use arith_ops::{lower_binop, lower_unary};
pub(super) use call::lower_call;
pub(super) use loopform::{LoopFormContext, lower_while_loopform};
pub(super) use loopform::normalize_header_phis_for_latch;
pub(super) use resolver::Resolver;

View File

@ -0,0 +1,43 @@
use std::collections::HashMap;
use inkwell::values::{BasicValueEnum as BVE, IntValue};
use crate::backend::llvm::context::CodegenContext;
use crate::mir::{BasicBlockId, ValueId};
use super::builder_cursor::BuilderCursor;
use super::flow::localize_to_i64;
/// Minimal per-function resolver caches. Caches localized i64 values per (block,value) to avoid
/// redundant PHIs and casts when multiple users in the same block request the same MIR value.
pub struct Resolver<'ctx> {
i64_locals: HashMap<(BasicBlockId, ValueId), IntValue<'ctx>>,
}
impl<'ctx> Resolver<'ctx> {
pub fn new() -> Self {
Self { i64_locals: HashMap::new() }
}
/// Resolve a MIR value as an i64 dominating the current block.
/// Strategy: if present in cache, return it; otherwise localize via sealed snapshots and cache.
pub fn resolve_i64<'b>(
&mut self,
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
cur_bid: BasicBlockId,
vid: ValueId,
bb_map: &std::collections::HashMap<BasicBlockId, inkwell::basic_block::BasicBlock<'ctx>>,
preds: &std::collections::HashMap<BasicBlockId, Vec<BasicBlockId>>,
block_end_values: &std::collections::HashMap<BasicBlockId, std::collections::HashMap<ValueId, BVE<'ctx>>>,
vmap: &std::collections::HashMap<ValueId, BVE<'ctx>>,
) -> Result<IntValue<'ctx>, String> {
if let Some(iv) = self.i64_locals.get(&(cur_bid, vid)).copied() {
return Ok(iv);
}
let iv = localize_to_i64(codegen, cursor, cur_bid, vid, bb_map, preds, block_end_values, vmap)?;
self.i64_locals.insert((cur_bid, vid), iv);
Ok(iv)
}
}

View File

@ -5,12 +5,13 @@ use inkwell::{values::BasicValueEnum as BVE, AddressSpace};
use crate::backend::llvm::context::CodegenContext;
use crate::mir::{function::MirFunction, BasicBlockId, ValueId};
use super::builder_cursor::BuilderCursor;
use super::flow::localize_to_i64;
use super::Resolver;
/// Handle String-specific methods. Returns true if handled, false to let caller continue.
pub(super) fn try_handle_string_method<'ctx, 'b>(
codegen: &CodegenContext<'ctx>,
cursor: &mut BuilderCursor<'ctx, 'b>,
resolver: &mut Resolver<'ctx>,
cur_bid: BasicBlockId,
func: &MirFunction,
vmap: &mut HashMap<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
@ -60,8 +61,8 @@ pub(super) fn try_handle_string_method<'ctx, 'b>(
}
(BVE::PointerValue(lp), BVE::IntValue(_ri)) => {
let i64t = codegen.context.i64_type();
// Localize rhs integer in current block
let ri = localize_to_i64(codegen, cursor, cur_bid, args[0], bb_map, preds, block_end_values, vmap)?;
// Localize rhs integer in current block via Resolver
let ri = resolver.resolve_i64(codegen, cursor, cur_bid, args[0], bb_map, preds, block_end_values, vmap)?;
let fnty = i8p.fn_type(&[i8p.into(), i64t.into()], false);
let callee = codegen
.module
@ -83,7 +84,7 @@ pub(super) fn try_handle_string_method<'ctx, 'b>(
(BVE::IntValue(_li), BVE::PointerValue(rp)) => {
let i64t = codegen.context.i64_type();
// Localize receiver integer in current block (box_val)
let li = localize_to_i64(codegen, cursor, cur_bid, *box_val, bb_map, preds, block_end_values, vmap)?;
let li = resolver.resolve_i64(codegen, cursor, cur_bid, *box_val, bb_map, preds, block_end_values, vmap)?;
let fnty = i8p.fn_type(&[i64t.into(), i8p.into()], false);
let callee = codegen
.module
@ -172,8 +173,8 @@ pub(super) fn try_handle_string_method<'ctx, 'b>(
_ => return Ok(false),
};
// Localize start/end indices to current block via sealed snapshots (i64)
let s = localize_to_i64(codegen, cursor, cur_bid, args[0], bb_map, preds, block_end_values, vmap)?;
let e = localize_to_i64(codegen, cursor, cur_bid, args[1], bb_map, preds, block_end_values, vmap)?;
let s = resolver.resolve_i64(codegen, cursor, cur_bid, args[0], bb_map, preds, block_end_values, vmap)?;
let e = resolver.resolve_i64(codegen, cursor, cur_bid, args[1], bb_map, preds, block_end_values, vmap)?;
let fnty = i8p.fn_type(&[i8p.into(), i64t.into(), i64t.into()], false);
let callee = codegen
.module