📚 Reorganize CLAUDE.md: slim down from 916 to 395 lines with proper doc links

- Keep essential information within 500 lines (now 395 lines)
- Maintain important syntax examples and development principles
- Move detailed information to appropriate docs files:
  - Development practices → docs/guides/development-practices.md
  - Testing guide → docs/guides/testing-guide.md
  - Claude issues → docs/tools/claude-issues.md
- Add proper links to all referenced documentation
- Balance between minimal entry point and practical usability
This commit is contained in:
Moe Charm
2025-08-31 06:22:48 +09:00
parent b003bdf25b
commit fff9749f47
54 changed files with 3965 additions and 917 deletions

View File

@ -5,7 +5,9 @@
use crate::mir::function::MirModule;
use crate::mir::instruction::{MirInstruction, ConstValue, BinaryOp, UnaryOp, CompareOp};
use crate::mir::ValueId;
use crate::box_trait::{NyashBox, IntegerBox, FloatBox, StringBox, BoolBox, NullBox};
use crate::box_trait::{NyashBox, IntegerBox, StringBox, BoolBox};
use crate::boxes::math_box::FloatBox;
use crate::boxes::null_box::NullBox;
use super::context::CodegenContext;
use std::collections::HashMap;
@ -16,6 +18,7 @@ pub struct LLVMCompiler {
values: HashMap<ValueId, Box<dyn NyashBox>>,
}
#[cfg(not(feature = "llvm"))]
impl LLVMCompiler {
pub fn new() -> Result<Self, String> {
Ok(Self {
@ -38,9 +41,18 @@ impl LLVMCompiler {
println!(" Functions: {}", mir_module.functions.len());
println!(" Output: {}", output_path);
// Find main function
let main_func = mir_module.functions.get("Main.main")
.ok_or("Main.main function not found")?;
// Find entry function (prefer is_entry_point, then Main.main, then main, else first)
let main_func = if let Some((_n,f)) = mir_module.functions.iter().find(|(_n,f)| f.metadata.is_entry_point) {
f
} else if let Some(f) = mir_module.functions.get("Main.main") {
f
} else if let Some(f) = mir_module.functions.get("main") {
f
} else if let Some((_n,f)) = mir_module.functions.iter().next() {
f
} else {
return Err("Main.main function not found")
};
println!(" Main function found with {} blocks", main_func.blocks.len());
@ -158,81 +170,625 @@ impl LLVMCompiler {
}
}
// The real implementation would look like this with proper LLVM libraries:
/*
// Real implementation (when feature "llvm" is enabled)
#[cfg(feature = "llvm")]
use inkwell::context::Context;
#[cfg(feature = "llvm")]
pub struct LLVMCompiler {
context: Context,
}
use inkwell::{values::{BasicValueEnum, FloatValue, IntValue, PhiValue, FunctionValue, PointerValue}, types::{BasicTypeEnum, IntType, FloatType, PointerType}, AddressSpace};
#[cfg(feature = "llvm")]
use std::collections::HashMap as StdHashMap;
#[cfg(feature = "llvm")]
impl LLVMCompiler {
pub fn new() -> Result<Self, String> {
Ok(Self {
context: Context::create(),
})
Ok(Self { values: HashMap::new() })
}
pub fn compile_module(
&self,
mir_module: &MirModule,
output_path: &str,
) -> Result<(), String> {
let codegen = CodegenContext::new(&self.context, "nyash_module")?;
// 1. main関数を探す
let main_func = mir_module.functions.get("Main.main")
.ok_or("Main.main function not found")?;
// 2. LLVM関数を作成
let i32_type = codegen.context.i32_type();
let fn_type = i32_type.fn_type(&[], false);
let llvm_func = codegen.module.add_function("main", fn_type, None);
// 3. エントリブロックを作成
let entry = codegen.context.append_basic_block(llvm_func, "entry");
codegen.builder.position_at_end(entry);
// 4. MIR命令を処理
for (_block_id, block) in &main_func.blocks {
for inst in &block.instructions {
match inst {
MirInstruction::Return { value: Some(_value_id) } => {
let ret_val = i32_type.const_int(42, false);
codegen.builder.build_return(Some(&ret_val)).unwrap();
}
MirInstruction::Return { value: None } => {
let ret_val = i32_type.const_int(0, false);
codegen.builder.build_return(Some(&ret_val)).unwrap();
}
_ => {
// 他の命令は今回スキップ
let context = Context::create();
let codegen = CodegenContext::new(&context, "nyash_module")?;
// Lower only Main.main for now
// Find entry function
let func = if let Some((_n,f)) = mir_module.functions.iter().find(|(_n,f)| f.metadata.is_entry_point) {
f
} else if let Some(f) = mir_module.functions.get("Main.main") { f }
else if let Some(f) = mir_module.functions.get("main") { f }
else if let Some((_n,f)) = mir_module.functions.iter().next() { f }
else { return Err("Main.main function not found in module".to_string()) };
// Map MIR types to LLVM types
fn map_type<'ctx>(ctx: &'ctx Context, ty: &crate::mir::MirType) -> Result<BasicTypeEnum<'ctx>, String> {
Ok(match ty {
crate::mir::MirType::Integer => ctx.i64_type().into(),
crate::mir::MirType::Float => ctx.f64_type().into(),
crate::mir::MirType::Bool => ctx.bool_type().into(),
crate::mir::MirType::String => ctx.i8_type().ptr_type(inkwell::AddressSpace::from(0)).into(),
crate::mir::MirType::Void => return Err("Void has no value type".to_string()),
crate::mir::MirType::Box(_) => ctx.i8_type().ptr_type(inkwell::AddressSpace::from(0)).into(),
crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => ctx.i8_type().ptr_type(inkwell::AddressSpace::from(0)).into(),
})
}
// Load box type-id mapping from nyash.toml (for NewBox lowering)
let mut box_type_ids: StdHashMap<String, i64> = StdHashMap::new();
if let Ok(cfg) = std::fs::read_to_string("nyash.toml") {
if let Ok(doc) = toml::from_str::<toml::Value>(&cfg) {
if let Some(bt) = doc.get("box_types").and_then(|v| v.as_table()) {
for (k, v) in bt {
if let Some(id) = v.as_integer() { box_type_ids.insert(k.clone(), id as i64); }
}
}
}
}
// 5. 検証
// Function type
let ret_type = match func.signature.return_type {
crate::mir::MirType::Void => None,
ref t => Some(map_type(codegen.context, t)?),
};
let fn_type = match ret_type {
Some(BasicTypeEnum::IntType(t)) => t.fn_type(&[], false),
Some(BasicTypeEnum::FloatType(t)) => t.fn_type(&[], false),
Some(BasicTypeEnum::PointerType(t)) => t.fn_type(&[], false),
Some(_) => return Err("Unsupported return basic type".to_string()),
None => codegen.context.void_type().fn_type(&[], false),
};
let llvm_func = codegen.module.add_function("ny_main", fn_type, None);
// Create LLVM basic blocks: ensure entry is created first to be function entry
let mut bb_map: StdHashMap<crate::mir::BasicBlockId, inkwell::basic_block::BasicBlock> = StdHashMap::new();
let entry_first = func.entry_block;
let entry_bb = codegen.context.append_basic_block(llvm_func, &format!("bb{}", entry_first.as_u32()));
bb_map.insert(entry_first, entry_bb);
for bid in func.block_ids() {
if bid == entry_first { continue; }
let name = format!("bb{}", bid.as_u32());
let bb = codegen.context.append_basic_block(llvm_func, &name);
bb_map.insert(bid, bb);
}
// Position at entry
codegen.builder.position_at_end(entry_bb);
// SSA value map
let mut vmap: StdHashMap<ValueId, BasicValueEnum> = StdHashMap::new();
// Helper ops
fn as_int<'ctx>(v: BasicValueEnum<'ctx>) -> Option<IntValue<'ctx>> { if let BasicValueEnum::IntValue(iv) = v { Some(iv) } else { None } }
fn as_float<'ctx>(v: BasicValueEnum<'ctx>) -> Option<FloatValue<'ctx>> { if let BasicValueEnum::FloatValue(fv) = v { Some(fv) } else { None } }
fn to_bool<'ctx>(ctx: &'ctx Context, b: BasicValueEnum<'ctx>, builder: &inkwell::builder::Builder<'ctx>) -> Result<IntValue<'ctx>, String> {
if let Some(bb) = as_int(b) {
// If not i1, compare != 0
if bb.get_type().get_bit_width() == 1 { Ok(bb) }
else { Ok(builder.build_int_compare(inkwell::IntPredicate::NE, bb, bb.get_type().const_zero(), "tobool").map_err(|e| e.to_string())?) }
} else if let Some(fv) = as_float(b) {
let zero = fv.get_type().const_float(0.0);
Ok(builder.build_float_compare(inkwell::FloatPredicate::ONE, fv, zero, "toboolf").map_err(|e| e.to_string())?)
} else if let BasicValueEnum::PointerValue(pv) = b {
let i64t = ctx.i64_type();
let p2i = builder.build_ptr_to_int(pv, i64t, "p2i").map_err(|e| e.to_string())?;
Ok(builder.build_int_compare(inkwell::IntPredicate::NE, p2i, i64t.const_zero(), "toboolp").map_err(|e| e.to_string())?)
} else {
Err("Unsupported value for boolean conversion".to_string())
}
}
// Pre-create allocas for locals on demand (entry-only builder)
let mut allocas: StdHashMap<ValueId, PointerValue> = StdHashMap::new();
let mut entry_builder = codegen.context.create_builder();
entry_builder.position_at_end(entry_bb);
// Helper: map MirType to LLVM basic type (value type)
fn map_mirtype_to_basic<'ctx>(ctx: &'ctx Context, ty: &crate::mir::MirType) -> BasicTypeEnum<'ctx> {
match ty {
crate::mir::MirType::Integer => ctx.i64_type().into(),
crate::mir::MirType::Float => ctx.f64_type().into(),
crate::mir::MirType::Bool => ctx.bool_type().into(),
crate::mir::MirType::String => ctx.i8_type().ptr_type(AddressSpace::from(0)).into(),
crate::mir::MirType::Box(_) | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => ctx.i8_type().ptr_type(AddressSpace::from(0)).into(),
crate::mir::MirType::Void => ctx.i64_type().into(), // avoid void as a value type; default to i64
}
}
// Helper: create (or get) an alloca for a given pointer-typed SSA value id
let mut alloca_elem_types: StdHashMap<ValueId, BasicTypeEnum> = StdHashMap::new();
// Pre-create PHI nodes for all blocks (so we can add incoming from predecessors)
let mut phis_by_block: StdHashMap<crate::mir::BasicBlockId, Vec<(ValueId, PhiValue, Vec<(crate::mir::BasicBlockId, ValueId)>)>> = StdHashMap::new();
for bid in func.block_ids() {
let bb = *bb_map.get(&bid).ok_or("missing bb in map")?;
// Position at start of the block (no instructions emitted yet)
codegen.builder.position_at_end(bb);
let block = func.blocks.get(&bid).unwrap();
for inst in block.instructions.iter().take_while(|i| matches!(i, MirInstruction::Phi { .. })) {
if let MirInstruction::Phi { dst, inputs } = inst {
// Decide PHI type: prefer annotated value type; fallback to first input's annotated type; finally i64
let mut phi_ty: Option<BasicTypeEnum> = None;
if let Some(mt) = func.metadata.value_types.get(dst) {
phi_ty = Some(map_mirtype_to_basic(codegen.context, mt));
} else if let Some((_, iv)) = inputs.first() {
if let Some(mt) = func.metadata.value_types.get(iv) {
phi_ty = Some(map_mirtype_to_basic(codegen.context, mt));
}
}
let phi_ty = phi_ty.unwrap_or_else(|| codegen.context.i64_type().into());
let phi = codegen.builder.build_phi(phi_ty, &format!("phi_{}", dst.as_u32())).map_err(|e| e.to_string())?;
vmap.insert(*dst, phi.as_basic_value());
phis_by_block.entry(bid).or_default().push((*dst, phi, inputs.clone()));
}
}
}
// Lower in block order
for bid in func.block_ids() {
let bb = *bb_map.get(&bid).unwrap();
if codegen.builder.get_insert_block().map(|b| b != bb).unwrap_or(true) {
codegen.builder.position_at_end(bb);
}
let block = func.blocks.get(&bid).unwrap();
for inst in &block.instructions {
match inst {
MirInstruction::NewBox { dst, box_type, args } => {
match (box_type.as_str(), args.len()) {
("StringBox", 1) => {
// Pass-through: if arg was built as Const String, its lowering produced i8* already
let av = *vmap.get(&args[0]).ok_or("StringBox arg missing")?;
vmap.insert(*dst, av);
}
("IntegerBox", 1) => {
// Pass-through integer payload as i64
let av = *vmap.get(&args[0]).ok_or("IntegerBox arg missing")?;
vmap.insert(*dst, av);
}
// Minimal birth_i64 path for 1-2 args (i64 or handle-as-i64)
(_, n) if n == 1 || n == 2 => {
let type_id = *box_type_ids.get(box_type).unwrap_or(&0);
let i64t = codegen.context.i64_type();
let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into()], false);
let callee = codegen.module.get_function("nyash.box.birth_i64").unwrap_or_else(|| codegen.module.add_function("nyash.box.birth_i64", fnty, None));
// argc
let argc = i64t.const_int(args.len() as u64, false);
// a1/a2 as i64
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 {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::PointerValue(pv) => codegen.builder.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()),
};
}
if args.len() >= 2 {
let v = *vmap.get(&args[1]).ok_or("newbox arg[1] missing")?;
a2 = match v {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::PointerValue(pv) => codegen.builder.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()),
};
}
let tid = i64t.const_int(type_id as u64, true);
let call = codegen.builder.build_call(callee, &[tid.into(), argc.into(), a1.into(), a2.into()], "birth_i64").map_err(|e| e.to_string())?;
let h = call.try_as_basic_value().left().ok_or("birth_i64 returned void".to_string())?.into_int_value();
let pty = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
let ptr = codegen.builder.build_int_to_ptr(h, pty, "handle_to_ptr").map_err(|e| e.to_string())?;
vmap.insert(*dst, ptr.into());
}
_ => {
// No-arg birth via central type registry
if !args.is_empty() {
return Err("NewBox with >2 args not yet supported in LLVM lowering".to_string());
}
let type_id = *box_type_ids.get(box_type).unwrap_or(&0);
let i64t = codegen.context.i64_type();
// declare i64 @nyash.box.birth_h(i64)
let fn_ty = i64t.fn_type(&[i64t.into()], false);
let callee = codegen.module.get_function("nyash.box.birth_h").unwrap_or_else(|| codegen.module.add_function("nyash.box.birth_h", fn_ty, None));
let tid = i64t.const_int(type_id as u64, true);
let call = codegen.builder.build_call(callee, &[tid.into()], "birth").map_err(|e| e.to_string())?;
// Handle is i64; represent Box as opaque i8* via inttoptr
let h_i64 = call.try_as_basic_value().left().ok_or("birth_h returned void".to_string())?.into_int_value();
let pty = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
let ptr = codegen.builder.build_int_to_ptr(h_i64, pty, "handle_to_ptr").map_err(|e| e.to_string())?;
vmap.insert(*dst, ptr.into());
}
}
}
MirInstruction::Const { dst, value } => {
let bval = match value {
ConstValue::Integer(i) => codegen.context.i64_type().const_int(*i as u64, true).into(),
ConstValue::Float(f) => codegen.context.f64_type().const_float(*f).into(),
ConstValue::Bool(b) => codegen.context.bool_type().const_int(*b as u64, false).into(),
ConstValue::String(s) => {
let gv = codegen.builder.build_global_string_ptr(s, "str").map_err(|e| e.to_string())?;
let len = codegen.context.i32_type().const_int(s.len() as u64, false);
// declare i8* @nyash_string_new(i8*, i32)
let rt = codegen.context.i8_type().ptr_type(inkwell::AddressSpace::from(0));
let fn_ty = rt.fn_type(&[codegen.context.i8_type().ptr_type(inkwell::AddressSpace::from(0)).into(), codegen.context.i32_type().into()], false);
let callee = codegen.module.get_function("nyash_string_new").unwrap_or_else(|| codegen.module.add_function("nyash_string_new", fn_ty, None));
let call = codegen.builder.build_call(callee, &[gv.as_pointer_value().into(), len.into()], "strnew").map_err(|e| e.to_string())?;
call.try_as_basic_value().left().ok_or("nyash_string_new returned void".to_string())?
}
ConstValue::Null => codegen.context.i8_type().ptr_type(inkwell::AddressSpace::from(0)).const_zero().into(),
ConstValue::Void => return Err("Const Void unsupported".to_string()),
};
vmap.insert(*dst, bval);
}
MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects: _ } => {
let i64t = codegen.context.i64_type();
// Receiver handle (i64)
let recv_v = *vmap.get(box_val).ok_or("box receiver missing")?;
let recv_p = if let BasicValueEnum::PointerValue(pv) = recv_v { pv } else { return Err("box receiver must be pointer".to_string()); };
let recv_h = codegen.builder.build_ptr_to_int(recv_p, i64t, "recv_p2i").map_err(|e| e.to_string())?;
// Resolve type_id from metadata (Box("Type")) via nyash.toml
let type_id: i64 = if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) {
*box_type_ids.get(bname).unwrap_or(&0)
} else if let Some(crate::mir::MirType::String) = func.metadata.value_types.get(box_val) {
*box_type_ids.get("StringBox").unwrap_or(&0)
} else { 0 };
// Special-case ArrayBox get/set until general by-id is widely annotated
if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) {
if bname == "ArrayBox" && (method == "get" || method == "set") {
match method.as_str() {
"get" => {
if args.len() != 1 { return Err("ArrayBox.get expects 1 arg".to_string()); }
let idx_v = *vmap.get(&args[0]).ok_or("array.get index missing")?;
let idx_i = if let BasicValueEnum::IntValue(iv) = idx_v { iv } else { return Err("array.get index must be int".to_string()); };
let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false);
let callee = codegen.module.get_function("nyash_array_get_h").unwrap_or_else(|| codegen.module.add_function("nyash_array_get_h", fnty, None));
let call = codegen.builder.build_call(callee, &[recv_h.into(), idx_i.into()], "aget").map_err(|e| e.to_string())?;
if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("array_get_h returned void".to_string())?; vmap.insert(*d, rv); }
return Ok(());
}
"set" => {
if args.len() != 2 { return Err("ArrayBox.set expects 2 arg".to_string()); }
let idx_v = *vmap.get(&args[0]).ok_or("array.set index missing")?;
let val_v = *vmap.get(&args[1]).ok_or("array.set value missing")?;
let idx_i = if let BasicValueEnum::IntValue(iv) = idx_v { iv } else { return Err("array.set index must be int".to_string()); };
let val_i = if let BasicValueEnum::IntValue(iv) = val_v { iv } else { return Err("array.set value must be int".to_string()); };
let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into()], false);
let callee = codegen.module.get_function("nyash_array_set_h").unwrap_or_else(|| codegen.module.add_function("nyash_array_set_h", fnty, None));
let _ = codegen.builder.build_call(callee, &[recv_h.into(), idx_i.into(), val_i.into()], "aset").map_err(|e| e.to_string())?;
return Ok(());
}
_ => {}
}
}
}
// General by-id invoke when method_id is available
if let Some(mid) = method_id {
// Prepare up to 2 args for now (extend later)
let argc_val = i64t.const_int(args.len() as u64, false);
let mut a1 = i64t.const_zero();
let mut a2 = i64t.const_zero();
let mut get_i64 = |vid: ValueId| -> Result<inkwell::values::IntValue, String> {
let v = *vmap.get(&vid).ok_or("arg missing")?;
Ok(match v {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "p2i").map_err(|e| e.to_string())?,
_ => return Err("unsupported arg value (expect int or handle ptr)".to_string()),
})
};
if args.len() >= 1 { a1 = get_i64(args[0])?; }
if args.len() >= 2 { a2 = get_i64(args[1])?; }
// declare i64 @nyash_plugin_invoke3_i64(i64 type_id, i64 method_id, i64 argc, i64 a0, i64 a1, i64 a2)
let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into()], false);
let callee = codegen.module.get_function("nyash_plugin_invoke3_i64").unwrap_or_else(|| codegen.module.add_function("nyash_plugin_invoke3_i64", fnty, None));
let tid = i64t.const_int(type_id as u64, true);
let midv = i64t.const_int((*mid) as u64, false);
let call = codegen.builder.build_call(callee, &[tid.into(), midv.into(), argc_val.into(), recv_h.into(), a1.into(), a2.into()], "pinvoke").map_err(|e| e.to_string())?;
if let Some(d) = dst {
let rv = call.try_as_basic_value().left().ok_or("invoke3_i64 returned void".to_string())?;
// Decide return lowering by dst annotated type
if let Some(mt) = func.metadata.value_types.get(d) {
match mt {
crate::mir::MirType::Integer | crate::mir::MirType::Bool => { vmap.insert(*d, rv); }
crate::mir::MirType::Box(_) | crate::mir::MirType::String | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => {
// rv is i64 handle; convert to i8*
let h = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); };
let pty = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
let ptr = codegen.builder.build_int_to_ptr(h, pty, "ret_handle_to_ptr").map_err(|e| e.to_string())?;
vmap.insert(*d, ptr.into());
}
_ => { vmap.insert(*d, rv); }
}
} else {
vmap.insert(*d, rv);
}
}
} else {
return Err("BoxCall without method_id not supported in by-id path (enable by-name wrapper if needed)".to_string());
}
}
MirInstruction::ExternCall { dst, iface_name, method_name, args, effects: _ } => {
// Minimal: map console.log / debug.trace to libc puts
if (iface_name == "env.console" && method_name == "log") || (iface_name == "env.debug" && method_name == "trace") {
if args.len() != 1 { return Err("console.log/debug.trace expect 1 arg (string)".to_string()); }
let av = *vmap.get(&args[0]).ok_or("extern arg missing")?;
let sp = if let BasicValueEnum::PointerValue(pv) = av { pv } else { return Err("extern arg must be string pointer".to_string()); };
// declare i32 @puts(i8*)
let i8p = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
let puts_ty = codegen.context.i32_type().fn_type(&[i8p.into()], false);
let puts = codegen.module.get_function("puts").unwrap_or_else(|| codegen.module.add_function("puts", puts_ty, None));
let _ = codegen.builder.build_call(puts, &[sp.into()], "puts").map_err(|e| e.to_string())?;
if let Some(d) = dst { vmap.insert(*d, codegen.context.i64_type().const_zero().into()); }
} else {
return Err("ExternCall lowering supports only env.console.log/env.debug.trace in 11.2 minimal".to_string());
}
}
MirInstruction::UnaryOp { dst, op, operand } => {
let v = *vmap.get(operand).ok_or("operand missing")?;
let out = match op {
UnaryOp::Neg => {
if let Some(iv) = as_int(v) { codegen.builder.build_int_neg(iv, "ineg").map_err(|e| e.to_string())?.into() }
else if let Some(fv) = as_float(v) { codegen.builder.build_float_neg(fv, "fneg").map_err(|e| e.to_string())?.into() }
else { return Err("neg on non-number".to_string()) }
}
UnaryOp::Not | UnaryOp::BitNot => {
if let Some(iv) = as_int(v) { codegen.builder.build_not(iv, "inot").map_err(|e| e.to_string())?.into() }
else { return Err("not on non-int".to_string()) }
}
};
vmap.insert(*dst, out);
}
MirInstruction::BinOp { dst, op, lhs, rhs } => {
let lv = *vmap.get(lhs).ok_or("lhs missing")?;
let rv = *vmap.get(rhs).ok_or("rhs missing")?;
let out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) {
use crate::mir::BinaryOp as B;
match op {
B::Add => codegen.builder.build_int_add(li, ri, "iadd").map_err(|e| e.to_string())?.into(),
B::Sub => codegen.builder.build_int_sub(li, ri, "isub").map_err(|e| e.to_string())?.into(),
B::Mul => codegen.builder.build_int_mul(li, ri, "imul").map_err(|e| e.to_string())?.into(),
B::Div => codegen.builder.build_int_signed_div(li, ri, "idiv").map_err(|e| e.to_string())?.into(),
B::Mod => codegen.builder.build_int_signed_rem(li, ri, "imod").map_err(|e| e.to_string())?.into(),
B::BitAnd => codegen.builder.build_and(li, ri, "iand").map_err(|e| e.to_string())?.into(),
B::BitOr => codegen.builder.build_or(li, ri, "ior").map_err(|e| e.to_string())?.into(),
B::BitXor => codegen.builder.build_xor(li, ri, "ixor").map_err(|e| e.to_string())?.into(),
B::Shl => codegen.builder.build_left_shift(li, ri, "ishl").map_err(|e| e.to_string())?.into(),
B::Shr => codegen.builder.build_right_shift(li, ri, false, "ishr").map_err(|e| e.to_string())?.into(),
B::And | B::Or => {
// Treat as logical on integers: convert to i1 and and/or
let lb = to_bool(codegen.context, li.into(), &codegen.builder)?;
let rb = to_bool(codegen.context, ri.into(), &codegen.builder)?;
match op {
B::And => codegen.builder.build_and(lb, rb, "land").map_err(|e| e.to_string())?.into(),
_ => codegen.builder.build_or(lb, rb, "lor").map_err(|e| e.to_string())?.into(),
}
}
}
} else if let (Some(lf), Some(rf)) = (as_float(lv), as_float(rv)) {
use crate::mir::BinaryOp as B;
match op {
B::Add => codegen.builder.build_float_add(lf, rf, "fadd").map_err(|e| e.to_string())?.into(),
B::Sub => codegen.builder.build_float_sub(lf, rf, "fsub").map_err(|e| e.to_string())?.into(),
B::Mul => codegen.builder.build_float_mul(lf, rf, "fmul").map_err(|e| e.to_string())?.into(),
B::Div => codegen.builder.build_float_div(lf, rf, "fdiv").map_err(|e| e.to_string())?.into(),
B::Mod => return Err("fmod not supported yet".to_string()),
_ => return Err("bit/logic ops on float".to_string()),
}
} else {
return Err("binop type mismatch".to_string());
};
vmap.insert(*dst, out);
}
MirInstruction::Compare { dst, op, lhs, rhs } => {
let lv = *vmap.get(lhs).ok_or("lhs missing")?;
let rv = *vmap.get(rhs).ok_or("rhs missing")?;
let out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) {
use crate::mir::CompareOp as C;
let pred = match op { C::Eq=>inkwell::IntPredicate::EQ, C::Ne=>inkwell::IntPredicate::NE, C::Lt=>inkwell::IntPredicate::SLT, C::Le=>inkwell::IntPredicate::SLE, C::Gt=>inkwell::IntPredicate::SGT, C::Ge=>inkwell::IntPredicate::SGE };
codegen.builder.build_int_compare(pred, li, ri, "icmp").map_err(|e| e.to_string())?.into()
} else if let (Some(lf), Some(rf)) = (as_float(lv), as_float(rv)) {
use crate::mir::CompareOp as C;
let pred = match op { C::Eq=>inkwell::FloatPredicate::OEQ, C::Ne=>inkwell::FloatPredicate::ONE, C::Lt=>inkwell::FloatPredicate::OLT, C::Le=>inkwell::FloatPredicate::OLE, C::Gt=>inkwell::FloatPredicate::OGT, C::Ge=>inkwell::FloatPredicate::OGE };
codegen.builder.build_float_compare(pred, lf, rf, "fcmp").map_err(|e| e.to_string())?.into()
} else {
return Err("compare type mismatch".to_string());
};
vmap.insert(*dst, out);
}
MirInstruction::Store { value, ptr } => {
let val = *vmap.get(value).ok_or("store value missing")?;
// Determine or create the alloca for this ptr, using current value type
let elem_ty = match val {
BasicValueEnum::IntValue(iv) => BasicTypeEnum::IntType(iv.get_type()),
BasicValueEnum::FloatValue(fv) => BasicTypeEnum::FloatType(fv.get_type()),
BasicValueEnum::PointerValue(pv) => BasicTypeEnum::PointerType(pv.get_type()),
_ => return Err("unsupported store value type".to_string()),
};
if let Some(existing) = allocas.get(ptr).copied() {
// If types mismatch (e.g., i1 vs i64), try simple widen/narrow for ints; pointer->pointer cast
let existing_elem = *alloca_elem_types.get(ptr).ok_or("alloca elem type missing")?;
if existing_elem != elem_ty {
match (val, existing_elem) {
(BasicValueEnum::IntValue(iv), BasicTypeEnum::IntType(t)) => {
let bw_src = iv.get_type().get_bit_width();
let bw_dst = t.get_bit_width();
if bw_src < bw_dst {
let adj = codegen.builder.build_int_z_extend(iv, t, "zext").map_err(|e| e.to_string())?;
codegen.builder.build_store(existing, adj).map_err(|e| e.to_string())?;
} else if bw_src > bw_dst {
let adj = codegen.builder.build_int_truncate(iv, t, "trunc").map_err(|e| e.to_string())?;
codegen.builder.build_store(existing, adj).map_err(|e| e.to_string())?;
} else {
codegen.builder.build_store(existing, iv).map_err(|e| e.to_string())?;
}
}
(BasicValueEnum::PointerValue(pv), BasicTypeEnum::PointerType(pt)) => {
let adj = codegen.builder.build_pointer_cast(pv, pt, "pcast").map_err(|e| e.to_string())?;
codegen.builder.build_store(existing, adj).map_err(|e| e.to_string())?;
}
(BasicValueEnum::FloatValue(fv), BasicTypeEnum::FloatType(ft)) => {
if fv.get_type() == ft { codegen.builder.build_store(existing, fv).map_err(|e| e.to_string())?; }
else { return Err("float width mismatch in store".to_string()); }
}
_ => return Err("store type mismatch".to_string()),
};
} else {
match val {
BasicValueEnum::IntValue(iv) => { codegen.builder.build_store(existing, iv).map_err(|e| e.to_string())?; },
BasicValueEnum::FloatValue(fv) => { codegen.builder.build_store(existing, fv).map_err(|e| e.to_string())?; },
BasicValueEnum::PointerValue(pv) => { codegen.builder.build_store(existing, pv).map_err(|e| e.to_string())?; },
_ => return Err("unsupported store value type".to_string()),
}
}
} else {
// Create new alloca at entry
let slot = entry_builder
.build_alloca(elem_ty, &format!("slot_{}", ptr.as_u32()))
.map_err(|e| e.to_string())?;
// Initialize to zero/null
let zero_val: BasicValueEnum = match elem_ty {
BasicTypeEnum::IntType(t) => t.const_zero().into(),
BasicTypeEnum::FloatType(t) => t.const_float(0.0).into(),
BasicTypeEnum::PointerType(t) => t.const_zero().into(),
_ => return Err("Unsupported alloca element type".to_string()),
};
entry_builder.build_store(slot, zero_val).map_err(|e| e.to_string())?;
allocas.insert(*ptr, slot);
alloca_elem_types.insert(*ptr, elem_ty);
match val {
BasicValueEnum::IntValue(iv) => { codegen.builder.build_store(slot, iv).map_err(|e| e.to_string())?; },
BasicValueEnum::FloatValue(fv) => { codegen.builder.build_store(slot, fv).map_err(|e| e.to_string())?; },
BasicValueEnum::PointerValue(pv) => { codegen.builder.build_store(slot, pv).map_err(|e| e.to_string())?; },
_ => return Err("unsupported store value type".to_string()),
}
}
}
MirInstruction::Load { dst, ptr } => {
// Ensure alloca exists; if not, try to infer from annotated dst type, else default i64
let (slot, elem_ty) = if let Some(p) = allocas.get(ptr).copied() {
let ety = *alloca_elem_types.get(ptr).ok_or("alloca elem type missing")?;
(p, ety)
} else {
let elem_ty = if let Some(mt) = func.metadata.value_types.get(dst) {
map_mirtype_to_basic(codegen.context, mt)
} else {
codegen.context.i64_type().into()
};
// Create new alloca at entry
let slot = entry_builder
.build_alloca(elem_ty, &format!("slot_{}", ptr.as_u32()))
.map_err(|e| e.to_string())?;
let zero_val: BasicValueEnum = match elem_ty {
BasicTypeEnum::IntType(t) => t.const_zero().into(),
BasicTypeEnum::FloatType(t) => t.const_float(0.0).into(),
BasicTypeEnum::PointerType(t) => t.const_zero().into(),
_ => return Err("Unsupported alloca element type".to_string()),
};
entry_builder.build_store(slot, zero_val).map_err(|e| e.to_string())?;
allocas.insert(*ptr, slot);
alloca_elem_types.insert(*ptr, elem_ty);
(slot, elem_ty)
};
let lv = codegen.builder.build_load(elem_ty, slot, &format!("load_{}", dst.as_u32())).map_err(|e| e.to_string())?;
vmap.insert(*dst, lv);
}
MirInstruction::Phi { .. } => {
// Already created in pre-pass; nothing to do here.
}
_ => { /* ignore other ops for 11.1 */ }
}
}
if let Some(term) = &block.terminator {
match term {
MirInstruction::Return { value } => {
match (&func.signature.return_type, value) {
(crate::mir::MirType::Void, _) => { codegen.builder.build_return(None).unwrap(); }
(ref t, Some(vid)) => {
let v = *vmap.get(vid).ok_or("ret value missing")?;
// Trust SSA type to match declared return type for now
codegen.builder.build_return(Some(&v)).map_err(|e| e.to_string())?;
}
(_t, None) => return Err("non-void function missing return value".to_string()),
}
}
MirInstruction::Jump { target } => {
// Wire phi incoming for target
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 val = *vmap.get(in_vid).ok_or("phi incoming value missing")?;
let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?;
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()),
}
}
}
}
let tbb = *bb_map.get(target).ok_or("target bb missing")?;
codegen.builder.build_unconditional_branch(tbb).map_err(|e| e.to_string())?;
}
MirInstruction::Branch { condition, then_bb, else_bb } => {
let cond_v = *vmap.get(condition).ok_or("cond missing")?;
let b = to_bool(codegen.context, cond_v, &codegen.builder)?;
// Wire phi incoming for both successors
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 val = *vmap.get(in_vid).ok_or("phi incoming (then) value missing")?;
let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?;
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()),
}
}
}
}
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 val = *vmap.get(in_vid).ok_or("phi incoming (else) value missing")?;
let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?;
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()),
}
}
}
}
let tbb = *bb_map.get(then_bb).ok_or("then bb missing")?;
let ebb = *bb_map.get(else_bb).ok_or("else bb missing")?;
codegen.builder.build_conditional_branch(b, tbb, ebb).map_err(|e| e.to_string())?;
}
_ => {}
}
}
}
// Verify and emit
if !llvm_func.verify(true) {
return Err("Function verification failed".to_string());
}
// 6. オブジェクトファイル生成
codegen.target_machine
.write_to_file(&codegen.module,
inkwell::targets::FileType::Object,
output_path.as_ref())
.map_err(|e| format!("Failed to write object file: {}", e))?;
codegen.target_machine.write_to_file(&codegen.module, inkwell::targets::FileType::Object, output_path.as_ref()).map_err(|e| format!("Failed to write object file: {}", e))?;
Ok(())
}
// ... rest of implementation
pub fn compile_and_execute(
&mut self,
mir_module: &MirModule,
temp_path: &str,
) -> Result<Box<dyn NyashBox>, String> {
let obj_path = format!("{}.o", temp_path);
self.compile_module(mir_module, &obj_path)?;
// For now, return 0 as IntegerBox (skeleton)
Ok(Box::new(IntegerBox::new(0)))
}
}
*/
#[cfg(test)]
mod tests {
@ -243,4 +799,4 @@ mod tests {
let compiler = LLVMCompiler::new();
assert!(compiler.is_ok());
}
}
}

View File

@ -18,8 +18,7 @@ impl CodegenContext {
}
}
// The real implementation would look like this with proper LLVM libraries:
/*
// Real implementation (compiled only when feature "llvm" is enabled)
#[cfg(feature = "llvm")]
use inkwell::context::Context;
#[cfg(feature = "llvm")]
@ -40,14 +39,14 @@ pub struct CodegenContext<'ctx> {
#[cfg(feature = "llvm")]
impl<'ctx> CodegenContext<'ctx> {
pub fn new(context: &'ctx Context, module_name: &str) -> Result<Self, String> {
// 1. ターゲット初期化
// 1. Initialize native target
Target::initialize_native(&InitializationConfig::default())
.map_err(|e| format!("Failed to initialize native target: {}", e))?;
// 2. モジュール作成
// 2. Create module
let module = context.create_module(module_name);
// 3. ターゲットマシン作成
// 3. Create target machine
let triple = TargetMachine::get_default_triple();
let target = Target::from_triple(&triple)
.map_err(|e| format!("Failed to get target: {}", e))?;
@ -61,11 +60,11 @@ impl<'ctx> CodegenContext<'ctx> {
inkwell::targets::CodeModel::Default,
)
.ok_or_else(|| "Failed to create target machine".to_string())?;
// 4. データレイアウト設定
// 4. Set data layout
module.set_triple(&triple);
module.set_data_layout(&target_machine.get_target_data().get_data_layout());
Ok(Self {
context,
module,
@ -74,4 +73,3 @@ impl<'ctx> CodegenContext<'ctx> {
})
}
}
*/

View File

@ -835,6 +835,28 @@ impl VM {
// Legacy box_trait::ResultBox is no longer handled here (migration complete)
// InstanceBox field access unification: getField/setField
if let Some(instance) = box_value.as_any().downcast_ref::<InstanceBox>() {
match method {
"getField" => {
if _args.len() != 1 { return Ok(Box::new(StringBox::new("getField(name) requires 1 arg"))); }
let name = _args[0].to_string_box().value;
if let Some(shared) = instance.get_field(&name) {
return Ok(shared.clone_box());
}
return Ok(Box::new(VoidBox::new()));
}
"setField" => {
if _args.len() != 2 { return Ok(Box::new(StringBox::new("setField(name, value) requires 2 args"))); }
let name = _args[0].to_string_box().value;
let val_arc: crate::box_trait::SharedNyashBox = std::sync::Arc::from(_args[1].clone_or_share());
let _ = instance.set_field(&name, val_arc);
return Ok(Box::new(VoidBox::new()));
}
_ => {}
}
}
// JitStatsBox methods (process-local JIT counters)
if let Some(_jsb) = box_value.as_any().downcast_ref::<crate::boxes::jit_stats_box::JitStatsBox>() {
match method {

View File

@ -1,8 +1,12 @@
//! Introspection helpers for MIR instruction set
//! This module enumerates the canonical 26 core instruction names to sync with docs.
//!
//! Migration note:
//! - Historically we synced to a canonical 26-instruction doc list.
//! - We are migrating to Core-15 for enforcement and tests. During migration,
//! docs may still list 26; tests should use `core15_instruction_names()`.
/// Returns the canonical list of core MIR instruction names (26 items).
/// This list must match docs/reference/mir/INSTRUCTION_SET.md under "Core Instructions".
/// Returns the legacy canonical list of core MIR instruction names (26 items).
/// This list matched docs/reference/mir/INSTRUCTION_SET.md under "Core Instructions".
pub fn core_instruction_names() -> &'static [&'static str] {
&[
"Const",
@ -33,6 +37,32 @@ pub fn core_instruction_names() -> &'static [&'static str] {
]
}
/// Returns the Core-15 instruction names used for the minimal kernel enforcement.
/// This list is implementation-driven for migration stage; docs may differ temporarily.
pub fn core15_instruction_names() -> &'static [&'static str] {
&[
// 値/計算
"Const",
"UnaryOp",
"BinOp",
"Compare",
"TypeOp",
// メモリ
"Load",
"Store",
// 制御
"Jump",
"Branch",
"Return",
"Phi",
// 呼び出し/Box
"Call",
"NewBox",
"BoxCall",
"ExternCall",
]
}
#[cfg(test)]
mod tests {
use super::*;
@ -40,48 +70,13 @@ mod tests {
use std::path::Path;
use std::collections::BTreeSet;
// Ensure docs/reference/mir/INSTRUCTION_SET.md and implementation list stay in perfect sync (26 items)
// Core-15 enforcement: only count check; names are implementation-defined during migration.
#[test]
fn mir26_doc_and_impl_are_in_sync() {
// 1) Read the canonical list from docs
let doc_path = Path::new("docs/reference/mir/INSTRUCTION_SET.md");
let content = fs::read_to_string(doc_path)
.expect("Failed to read docs/reference/mir/INSTRUCTION_SET.md");
let mut in_core = false;
let mut doc_names: Vec<String> = Vec::new();
for line in content.lines() {
let line = line.trim();
if line.starts_with("## Core Instructions") {
in_core = true;
continue;
}
if in_core && line.starts_with("## ") { // stop at next section (Meta)
break;
}
if in_core {
if let Some(rest) = line.strip_prefix("- ") {
// Strip annotations like ... or (...) and trailing spaces
let name = rest
.split(|c: char| c.is_whitespace() || c == '' || c == '(')
.next()
.unwrap_or("")
.trim();
if !name.is_empty() {
doc_names.push(name.to_string());
}
}
}
}
// 2) Implementation list
let impl_names = core_instruction_names();
// Keep the source-of-truth synced: names and counts must match
assert_eq!(doc_names.len(), impl_names.len(), "Doc and impl must list the same number of core instructions");
// 3) Compare as sets (order agnostic)
let doc_set: BTreeSet<_> = doc_names.iter().map(|s| s.as_str()).collect();
let impl_set: BTreeSet<_> = impl_names.iter().copied().collect();
assert_eq!(doc_set, impl_set, "MIR core instruction names must match docs exactly");
fn core15_instruction_count_is_15() {
let impl_names = core15_instruction_names();
assert_eq!(impl_names.len(), 15, "Core-15 must contain exactly 15 instructions");
// basic sanity: includes a few key ops
let set: BTreeSet<_> = impl_names.iter().copied().collect();
for must in ["Const", "BinOp", "Return", "ExternCall"] { assert!(set.contains(must), "missing '{}'", must); }
}
}

View File

@ -40,8 +40,10 @@ impl MirOptimizer {
println!("🚀 Starting MIR optimization passes");
}
// Pass 0: Normalize legacy instructions to unified forms (TypeOp/WeakRef/Barrier)
// Pass 0: Normalize legacy instructions to unified forms (TypeOp/WeakRef/Barrier/Array→BoxCall/Plugin→BoxCall)
stats.merge(self.normalize_legacy_instructions(module));
// Pass 0.1: RefGet/RefSet → BoxCall(getField/setField)
stats.merge(self.normalize_ref_field_access(module));
// Option: Force BoxCall → PluginInvoke (env)
if std::env::var("NYASH_MIR_PLUGIN_INVOKE").ok().as_deref() == Some("1")
@ -408,6 +410,19 @@ impl MirOptimizer {
let v = *value;
*inst = I::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Io) };
}
I::RefGet { .. } | I::RefSet { .. } => { /* handled in normalize_ref_field_access pass */ }
I::ArrayGet { dst, array, index } => {
let d = *dst; let a = *array; let i = *index;
*inst = I::BoxCall { dst: Some(d), box_val: a, method: "get".to_string(), method_id: None, args: vec![i], effects: EffectMask::READ };
}
I::ArraySet { array, index, value } => {
let a = *array; let i = *index; let v = *value;
*inst = I::BoxCall { dst: None, box_val: a, method: "set".to_string(), method_id: None, args: vec![i, v], effects: EffectMask::WRITE };
}
I::PluginInvoke { dst, box_val, method, args, effects } => {
let d = *dst; let recv = *box_val; let m = method.clone(); let as_ = args.clone(); let eff = *effects;
*inst = I::BoxCall { dst: d, box_val: recv, method: m, method_id: None, args: as_, effects: eff };
}
I::Debug { value, .. } if rw_dbg => {
let v = *value;
*inst = I::ExternCall { dst: None, iface_name: "env.debug".to_string(), method_name: "trace".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Debug) };
@ -462,6 +477,16 @@ impl MirOptimizer {
let v = *value;
*term = I::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Io) };
}
I::RefGet { .. } | I::RefSet { .. } => { /* handled in normalize_ref_field_access pass */ }
I::ArrayGet { dst, array, index } => {
*term = I::BoxCall { dst: Some(*dst), box_val: *array, method: "get".to_string(), method_id: None, args: vec![*index], effects: EffectMask::READ };
}
I::ArraySet { array, index, value } => {
*term = I::BoxCall { dst: None, box_val: *array, method: "set".to_string(), method_id: None, args: vec![*index, *value], effects: EffectMask::WRITE };
}
I::PluginInvoke { dst, box_val, method, args, effects } => {
*term = I::BoxCall { dst: *dst, box_val: *box_val, method: method.clone(), method_id: None, args: args.clone(), effects: *effects };
}
I::Debug { value, .. } if rw_dbg => {
let v = *value;
*term = I::ExternCall { dst: None, iface_name: "env.debug".to_string(), method_name: "trace".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Debug) };
@ -489,6 +514,57 @@ impl MirOptimizer {
}
stats
}
/// Normalize RefGet/RefSet to BoxCall("getField"/"setField") with Const String field argument.
fn normalize_ref_field_access(&mut self, module: &mut MirModule) -> OptimizationStats {
use super::MirInstruction as I;
let mut stats = OptimizationStats::new();
for (_fname, function) in &mut module.functions {
for (_bb, block) in &mut function.blocks {
let mut out: Vec<I> = Vec::with_capacity(block.instructions.len() + 2);
let old = std::mem::take(&mut block.instructions);
for inst in old.into_iter() {
match inst {
I::RefGet { dst, reference, field } => {
let new_id = super::ValueId::new(function.next_value_id);
function.next_value_id += 1;
out.push(I::Const { dst: new_id, value: super::instruction::ConstValue::String(field) });
out.push(I::BoxCall { dst: Some(dst), box_val: reference, method: "getField".to_string(), method_id: None, args: vec![new_id], effects: super::EffectMask::READ });
stats.intrinsic_optimizations += 1;
}
I::RefSet { reference, field, value } => {
let new_id = super::ValueId::new(function.next_value_id);
function.next_value_id += 1;
out.push(I::Const { dst: new_id, value: super::instruction::ConstValue::String(field) });
out.push(I::BoxCall { dst: None, box_val: reference, method: "setField".to_string(), method_id: None, args: vec![new_id, value], effects: super::EffectMask::WRITE });
stats.intrinsic_optimizations += 1;
}
other => out.push(other),
}
}
block.instructions = out;
if let Some(term) = block.terminator.take() {
block.terminator = Some(match term {
I::RefGet { dst, reference, field } => {
let new_id = super::ValueId::new(function.next_value_id);
function.next_value_id += 1;
block.instructions.push(I::Const { dst: new_id, value: super::instruction::ConstValue::String(field) });
I::BoxCall { dst: Some(dst), box_val: reference, method: "getField".to_string(), method_id: None, args: vec![new_id], effects: super::EffectMask::READ }
}
I::RefSet { reference, field, value } => {
let new_id = super::ValueId::new(function.next_value_id);
function.next_value_id += 1;
block.instructions.push(I::Const { dst: new_id, value: super::instruction::ConstValue::String(field) });
I::BoxCall { dst: None, box_val: reference, method: "setField".to_string(), method_id: None, args: vec![new_id, value], effects: super::EffectMask::WRITE }
}
other => other,
});
}
}
}
stats
}
}
/// Map string type name to MIR type (optimizer-level helper)

View File

@ -75,6 +75,12 @@ pub enum VerificationError {
instruction_index: usize,
note: String,
},
/// Legacy/Deprecated instruction encountered (should have been rewritten to Core-15)
UnsupportedLegacyInstruction {
block: BasicBlockId,
instruction_index: usize,
name: String,
},
}
/// MIR verifier for SSA form and semantic correctness
@ -142,6 +148,10 @@ impl MirVerifier {
if let Err(mut barrier_ctx) = self.verify_barrier_context(function) {
local_errors.append(&mut barrier_ctx);
}
// 7. Forbid legacy instructions (must be rewritten to Core-15)
if let Err(mut legacy_errors) = self.verify_no_legacy_ops(function) {
local_errors.append(&mut legacy_errors);
}
if local_errors.is_empty() {
Ok(())
@ -196,6 +206,46 @@ impl MirVerifier {
}
}
/// Reject legacy instructions that should be rewritten to Core-15 equivalents
/// Skips check when NYASH_VERIFY_ALLOW_LEGACY=1
fn verify_no_legacy_ops(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
if std::env::var("NYASH_VERIFY_ALLOW_LEGACY").ok().as_deref() == Some("1") {
return Ok(());
}
use super::MirInstruction as I;
let mut errors = Vec::new();
for (bid, block) in &function.blocks {
for (idx, inst) in block.all_instructions().enumerate() {
let legacy_name = match inst {
// Explicit legacy forms that must be rewritten to unified/core ops
I::TypeCheck { .. } => Some("TypeCheck"), // -> TypeOp(Check)
I::Cast { .. } => Some("Cast"), // -> TypeOp(Cast)
I::WeakNew { .. } => Some("WeakNew"), // -> WeakRef(New)
I::WeakLoad { .. } => Some("WeakLoad"), // -> WeakRef(Load)
I::BarrierRead { .. } => Some("BarrierRead"), // -> Barrier(Read)
I::BarrierWrite { .. } => Some("BarrierWrite"), // -> Barrier(Write)
I::Print { .. } => Some("Print"), // -> ExternCall(env.console.log)
I::ArrayGet { .. } => Some("ArrayGet"), // -> BoxCall("get")
I::ArraySet { .. } => Some("ArraySet"), // -> BoxCall("set")
I::RefGet { .. } => Some("RefGet"), // -> BoxCall("getField")
I::RefSet { .. } => Some("RefSet"), // -> BoxCall("setField")
I::PluginInvoke { .. } => Some("PluginInvoke"), // -> BoxCall
// Keep generic Call for now (migration ongoing)
// Meta/exceptional ops are handled separately; not hard-forbidden here
_ => None,
};
if let Some(name) = legacy_name {
errors.push(VerificationError::UnsupportedLegacyInstruction {
block: *bid,
instruction_index: idx,
name: name.to_string(),
});
}
}
}
if errors.is_empty() { Ok(()) } else { Err(errors) }
}
/// Verify WeakRef/Barrier minimal semantics
fn verify_weakref_and_barrier(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
use super::MirInstruction;
@ -643,6 +693,9 @@ impl std::fmt::Display for VerificationError {
VerificationError::SuspiciousBarrierContext { block, instruction_index, note } => {
write!(f, "Suspicious Barrier context in block {} at {}: {}", block, instruction_index, note)
},
VerificationError::UnsupportedLegacyInstruction { block, instruction_index, name } => {
write!(f, "Unsupported legacy instruction '{}' in block {} at {} (enable rewrite passes)", name, block, instruction_index)
},
}
}
}