refactor: 大規模なモジュールを分割し、コードの構造を改善
runner, mir/builder, backend/llvm の各モジュールが肥大化していたため、責務ごとにファイルを分割し、見通しを改善するリファクタリングを実施。
### `runner`
- `mod.rs` に集中していたロジックを、以下のモジュールに分割:
- `tasks.rs`: `nyash.toml` のタスク実行処理
- `build.rs`: AOTビルドパイプラインの実装
- `pipeline.rs`: `using` の解決など、パイプライン中のユーティリティ
- `demos.rs`: デモの実行処理
### `mir/builder`
- `if/else` 文のPHIノード生成ロジックを `stmts.rs` から `phi.rs` へ切り出し。
- `utils.rs` にあったPHI関連のヘルパーも `phi.rs` に集約。
- ASTから自由変数を収集するロジックを `vars.rs` へ切り出し。
### `backend/llvm/compiler/codegen`
- 巨大だった `lower_one_function` 関数を、`function.rs` モジュールとして分離。
- `sanitize_symbol` などのヘルパー関数を `utils.rs` へ移動。
This commit is contained in:
424
src/backend/llvm/compiler/codegen/function.rs
Normal file
424
src/backend/llvm/compiler/codegen/function.rs
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn lower_one_function<'ctx>(
|
||||||
|
codegen: &CodegenContext<'ctx>,
|
||||||
|
llvm_func: FunctionValue<'ctx>,
|
||||||
|
func: &crate::mir::function::MirFunction,
|
||||||
|
name: &str,
|
||||||
|
box_type_ids: &HashMap<String, i64>,
|
||||||
|
llvm_funcs: &HashMap<String, FunctionValue<'ctx>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
// Create basic blocks (prefix names with function label to avoid any ambiguity)
|
||||||
|
let fn_label = sanitize_symbol(name);
|
||||||
|
let (mut bb_map, entry_bb) = instructions::create_basic_blocks(codegen, llvm_func, func, &fn_label);
|
||||||
|
let mut cursor = instructions::builder_cursor::BuilderCursor::new(&codegen.builder);
|
||||||
|
cursor.at_end(func.entry_block, entry_bb);
|
||||||
|
let mut vmap: HashMap<ValueId, BasicValueEnum> = HashMap::new();
|
||||||
|
let mut allocas: HashMap<ValueId, PointerValue> = HashMap::new();
|
||||||
|
let entry_builder = codegen.context.create_builder();
|
||||||
|
entry_builder.position_at_end(entry_bb);
|
||||||
|
let mut alloca_elem_types: HashMap<ValueId, BasicTypeEnum> = HashMap::new();
|
||||||
|
let mut phis_by_block: HashMap<
|
||||||
|
crate::mir::BasicBlockId,
|
||||||
|
Vec<(ValueId, PhiValue, Vec<(crate::mir::BasicBlockId, ValueId)>)>,
|
||||||
|
> = HashMap::new();
|
||||||
|
// Snapshot of values at the end of each basic block (for sealed-SSA PHI wiring)
|
||||||
|
let mut block_end_values: HashMap<crate::mir::BasicBlockId, HashMap<ValueId, BasicValueEnum>> = HashMap::new();
|
||||||
|
// Build successors and predecessors map (for optional sealed-SSA PHI wiring)
|
||||||
|
let mut succs: HashMap<crate::mir::BasicBlockId, Vec<crate::mir::BasicBlockId>> = HashMap::new();
|
||||||
|
for (bid, block) in &func.blocks {
|
||||||
|
let v: Vec<crate::mir::BasicBlockId> = block.successors.iter().copied().collect();
|
||||||
|
succs.insert(*bid, v);
|
||||||
|
}
|
||||||
|
let mut preds: HashMap<crate::mir::BasicBlockId, Vec<crate::mir::BasicBlockId>> = HashMap::new();
|
||||||
|
for (b, ss) in &succs {
|
||||||
|
for s in ss { preds.entry(*s).or_default().push(*b); }
|
||||||
|
}
|
||||||
|
// Track sealed blocks to know when all preds of a successor are sealed
|
||||||
|
let mut sealed_blocks: std::collections::HashSet<crate::mir::BasicBlockId> = std::collections::HashSet::new();
|
||||||
|
// Bind parameters
|
||||||
|
for (i, pid) in func.params.iter().enumerate() {
|
||||||
|
if let Some(av) = llvm_func.get_nth_param(i as u32) {
|
||||||
|
vmap.insert(*pid, av);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Gather block order once for fallthrough handling
|
||||||
|
let block_ids: Vec<crate::mir::BasicBlockId> = func.block_ids().into_iter().collect();
|
||||||
|
|
||||||
|
// Precreate phis
|
||||||
|
for bid in &block_ids {
|
||||||
|
let bb = *bb_map.get(bid).ok_or("missing bb in map")?;
|
||||||
|
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 {
|
||||||
|
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()));
|
||||||
|
if std::env::var("NYASH_LLVM_TRACE_PHI").ok().as_deref() == Some("1") {
|
||||||
|
let ty_str = phi
|
||||||
|
.as_basic_value()
|
||||||
|
.get_type()
|
||||||
|
.print_to_string()
|
||||||
|
.to_string();
|
||||||
|
let mut pairs: Vec<String> = Vec::new();
|
||||||
|
for (pb, vid) in inputs {
|
||||||
|
pairs.push(format!("({}->{})", pb.as_u32(), vid.as_u32()));
|
||||||
|
}
|
||||||
|
eprintln!(
|
||||||
|
"[PHI:new] fn={} bb={} dst={} ty={} inputs={}",
|
||||||
|
fn_label,
|
||||||
|
bid.as_u32(),
|
||||||
|
dst.as_u32(),
|
||||||
|
ty_str,
|
||||||
|
pairs.join(",")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map of const strings for Call resolution
|
||||||
|
let const_strs = utils::build_const_str_map(func);
|
||||||
|
|
||||||
|
// Lower body
|
||||||
|
let mut loopform_loop_id: u32 = 0;
|
||||||
|
// Default sealed-SSA ON unless explicitly disabled with NYASH_LLVM_PHI_SEALED=0
|
||||||
|
let sealed_mode = std::env::var("NYASH_LLVM_PHI_SEALED").ok().as_deref() != Some("0");
|
||||||
|
// LoopForm registry (per-function lowering; gated)
|
||||||
|
let mut loopform_registry: HashMap<crate::mir::BasicBlockId, (inkwell::basic_block::BasicBlock, PhiValue, PhiValue, inkwell::basic_block::BasicBlock)> = HashMap::new();
|
||||||
|
let mut loopform_body_to_header: HashMap<crate::mir::BasicBlockId, crate::mir::BasicBlockId> = HashMap::new();
|
||||||
|
// Per-function Resolver for dominance-safe value access (i64 minimal)
|
||||||
|
let mut resolver = instructions::Resolver::new();
|
||||||
|
for (bi, bid) in block_ids.iter().enumerate() {
|
||||||
|
let bb = *bb_map.get(bid).unwrap();
|
||||||
|
// Use cursor to position at BB start for lowering
|
||||||
|
cursor.at_end(*bid, bb);
|
||||||
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[LLVM] lowering bb={}", bid.as_u32());
|
||||||
|
}
|
||||||
|
let block = func.blocks.get(bid).unwrap();
|
||||||
|
let mut defined_in_block: std::collections::HashSet<ValueId> = std::collections::HashSet::new();
|
||||||
|
for inst in &block.instructions {
|
||||||
|
match inst {
|
||||||
|
MirInstruction::NewBox { dst, box_type, args } => {
|
||||||
|
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 } => {
|
||||||
|
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) => {
|
||||||
|
// Hoist string creation to entry block to dominate all uses.
|
||||||
|
let entry_term = unsafe { entry_bb.get_terminator() };
|
||||||
|
if let Some(t) = entry_term { entry_builder.position_before(&t); }
|
||||||
|
else { entry_builder.position_at_end(entry_bb); }
|
||||||
|
let gv = entry_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);
|
||||||
|
let rt = codegen.context.ptr_type(inkwell::AddressSpace::from(0));
|
||||||
|
let fn_ty = rt.fn_type(&[
|
||||||
|
codegen.context.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 = entry_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.ptr_type(inkwell::AddressSpace::from(0)).const_zero().into(),
|
||||||
|
ConstValue::Void => codegen.context.i64_type().const_zero().into(),
|
||||||
|
};
|
||||||
|
vmap.insert(*dst, bval);
|
||||||
|
defined_in_block.insert(*dst);
|
||||||
|
},
|
||||||
|
MirInstruction::Call { dst, func: callee, args, .. } => {
|
||||||
|
instructions::lower_call(
|
||||||
|
codegen,
|
||||||
|
&mut cursor,
|
||||||
|
&mut resolver,
|
||||||
|
*bid,
|
||||||
|
func,
|
||||||
|
&mut vmap,
|
||||||
|
dst,
|
||||||
|
callee,
|
||||||
|
args,
|
||||||
|
&const_strs,
|
||||||
|
llvm_funcs,
|
||||||
|
&bb_map,
|
||||||
|
&preds,
|
||||||
|
&block_end_values,
|
||||||
|
)?;
|
||||||
|
if let Some(d) = dst { defined_in_block.insert(*d); }
|
||||||
|
},
|
||||||
|
MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects: _ } => {
|
||||||
|
instructions::lower_boxcall(
|
||||||
|
codegen,
|
||||||
|
&mut cursor,
|
||||||
|
&mut resolver,
|
||||||
|
*bid,
|
||||||
|
func,
|
||||||
|
&mut vmap,
|
||||||
|
dst,
|
||||||
|
box_val,
|
||||||
|
method,
|
||||||
|
method_id,
|
||||||
|
args,
|
||||||
|
box_type_ids,
|
||||||
|
&entry_builder,
|
||||||
|
&bb_map,
|
||||||
|
&preds,
|
||||||
|
&block_end_values,
|
||||||
|
)?;
|
||||||
|
if let Some(d) = dst { defined_in_block.insert(*d); }
|
||||||
|
},
|
||||||
|
MirInstruction::ExternCall { dst, iface_name, method_name, args, effects: _ } => {
|
||||||
|
instructions::lower_externcall(
|
||||||
|
codegen,
|
||||||
|
&mut cursor,
|
||||||
|
&mut resolver,
|
||||||
|
*bid,
|
||||||
|
func,
|
||||||
|
&mut vmap,
|
||||||
|
dst,
|
||||||
|
iface_name,
|
||||||
|
method_name,
|
||||||
|
args,
|
||||||
|
&bb_map,
|
||||||
|
&preds,
|
||||||
|
&block_end_values,
|
||||||
|
)?;
|
||||||
|
if let Some(d) = dst { defined_in_block.insert(*d); }
|
||||||
|
},
|
||||||
|
MirInstruction::UnaryOp { dst, op, operand } => {
|
||||||
|
instructions::lower_unary(
|
||||||
|
codegen,
|
||||||
|
&mut cursor,
|
||||||
|
&mut resolver,
|
||||||
|
*bid,
|
||||||
|
func,
|
||||||
|
&mut vmap,
|
||||||
|
*dst,
|
||||||
|
op,
|
||||||
|
operand,
|
||||||
|
&bb_map,
|
||||||
|
&preds,
|
||||||
|
&block_end_values,
|
||||||
|
)?;
|
||||||
|
defined_in_block.insert(*dst);
|
||||||
|
},
|
||||||
|
MirInstruction::BinOp { dst, op, lhs, rhs } => {
|
||||||
|
instructions::lower_binop(codegen, &mut cursor, &mut resolver, *bid, func, &mut vmap, *dst, op, lhs, rhs, &bb_map, &preds, &block_end_values)?;
|
||||||
|
defined_in_block.insert(*dst);
|
||||||
|
},
|
||||||
|
MirInstruction::Compare { dst, op, lhs, rhs } => {
|
||||||
|
let out = instructions::lower_compare(codegen, &mut cursor, &mut resolver, *bid, func, &vmap, op, lhs, rhs, &bb_map, &preds, &block_end_values)?;
|
||||||
|
vmap.insert(*dst, out);
|
||||||
|
defined_in_block.insert(*dst);
|
||||||
|
},
|
||||||
|
MirInstruction::Store { 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)?;
|
||||||
|
defined_in_block.insert(*dst);
|
||||||
|
},
|
||||||
|
MirInstruction::Phi { .. } => { /* precreated */ }
|
||||||
|
_ => { /* ignore others */ }
|
||||||
|
}
|
||||||
|
// Snapshot end-of-block values
|
||||||
|
let mut snap: HashMap<ValueId, BasicValueEnum> = HashMap::new();
|
||||||
|
for vid in &defined_in_block { if let Some(v) = vmap.get(vid).copied() { snap.insert(*vid, v); } }
|
||||||
|
block_end_values.insert(*bid, snap);
|
||||||
|
}
|
||||||
|
// Terminator handling
|
||||||
|
if let Some(term) = &block.terminator {
|
||||||
|
cursor.at_end(*bid, bb);
|
||||||
|
match term {
|
||||||
|
MirInstruction::Return { value } => {
|
||||||
|
instructions::emit_return(codegen, &mut cursor, &mut resolver, *bid, func, &vmap, value, &bb_map, &preds, &block_end_values)?;
|
||||||
|
}
|
||||||
|
MirInstruction::Jump { target } => {
|
||||||
|
let mut handled = false;
|
||||||
|
if std::env::var("NYASH_ENABLE_LOOPFORM").ok().as_deref() == Some("1") &&
|
||||||
|
std::env::var("NYASH_LOOPFORM_BODY2DISPATCH").ok().as_deref() == Some("1") {
|
||||||
|
if let Some(hdr) = loopform_body_to_header.get(bid) {
|
||||||
|
if hdr == target {
|
||||||
|
if let Some((dispatch_bb, tag_phi, payload_phi, _latch_bb)) = loopform_registry.get(hdr) {
|
||||||
|
let i8t = codegen.context.i8_type();
|
||||||
|
let i64t = codegen.context.i64_type();
|
||||||
|
let pred_llbb = *bb_map.get(bid).ok_or("loopform: body llbb missing")?;
|
||||||
|
let z = i8t.const_zero();
|
||||||
|
let pz = i64t.const_zero();
|
||||||
|
tag_phi.add_incoming(&[(&z, pred_llbb)]);
|
||||||
|
payload_phi.add_incoming(&[(&pz, pred_llbb)]);
|
||||||
|
cursor.emit_term(*bid, |b| { b.build_unconditional_branch(*dispatch_bb).unwrap(); });
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !handled {
|
||||||
|
instructions::emit_jump(codegen, &mut cursor, *bid, target, &bb_map, &phis_by_block)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MirInstruction::Branch { condition, then_bb, else_bb } => {
|
||||||
|
let mut handled_by_loopform = false;
|
||||||
|
if std::env::var("NYASH_ENABLE_LOOPFORM").ok().as_deref() == Some("1") {
|
||||||
|
let mut is_back = |start: crate::mir::BasicBlockId| -> u8 {
|
||||||
|
if let Some(b) = func.blocks.get(&start) {
|
||||||
|
if let Some(crate::mir::instruction::MirInstruction::Jump { target }) = &b.terminator {
|
||||||
|
if target == bid { return 1; }
|
||||||
|
if let Some(b2) = func.blocks.get(target) {
|
||||||
|
if let Some(crate::mir::instruction::MirInstruction::Jump { target: t2 }) = &b2.terminator {
|
||||||
|
if t2 == bid { return 2; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let d_then = is_back(*then_bb);
|
||||||
|
let d_else = is_back(*else_bb);
|
||||||
|
let choose_body = if d_then > 0 && d_else == 0 { Some((*then_bb, *else_bb)) }
|
||||||
|
else if d_else > 0 && d_then == 0 { Some((*else_bb, *then_bb)) }
|
||||||
|
else if d_then > 0 && d_else > 0 { if d_then <= d_else { Some((*then_bb, *else_bb)) } else { Some((*else_bb, *then_bb)) } }
|
||||||
|
else { None };
|
||||||
|
if let Some((body_sel, after_sel)) = choose_body {
|
||||||
|
let body_block = func.blocks.get(&body_sel).unwrap();
|
||||||
|
handled_by_loopform = instructions::lower_while_loopform(
|
||||||
|
codegen, &mut cursor, &mut resolver, func, llvm_func, condition, &body_block.instructions,
|
||||||
|
loopform_loop_id, &fn_label, *bid, body_sel, after_sel, &bb_map, &vmap, &preds, &block_end_values,
|
||||||
|
&mut loopform_registry, &mut loopform_body_to_header,
|
||||||
|
)?;
|
||||||
|
loopform_loop_id = loopform_loop_id.wrapping_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !handled_by_loopform {
|
||||||
|
instructions::emit_branch(codegen, &mut cursor, &mut resolver, *bid, condition, then_bb, else_bb, &bb_map, &phis_by_block, &vmap, &preds, &block_end_values)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cursor.at_end(*bid, bb);
|
||||||
|
if let Some(next_bid) = block_ids.get(bi + 1) {
|
||||||
|
instructions::emit_jump(codegen, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block)?;
|
||||||
|
} else {
|
||||||
|
let entry_first = func.entry_block;
|
||||||
|
instructions::emit_jump(codegen, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[LLVM] no terminator in MIR for bb={} (fallback)", bid.as_u32());
|
||||||
|
}
|
||||||
|
cursor.at_end(*bid, bb);
|
||||||
|
if let Some(next_bid) = block_ids.get(bi + 1) {
|
||||||
|
instructions::emit_jump(codegen, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block)?;
|
||||||
|
} else {
|
||||||
|
let entry_first = func.entry_block;
|
||||||
|
instructions::emit_jump(codegen, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unsafe { bb.get_terminator() }.is_none() {
|
||||||
|
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[LLVM] extra guard inserting fallback for bb={}", bid.as_u32());
|
||||||
|
}
|
||||||
|
cursor.at_end(*bid, bb);
|
||||||
|
if let Some(next_bid) = block_ids.get(bi + 1) {
|
||||||
|
instructions::emit_jump(codegen, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block)?;
|
||||||
|
} else {
|
||||||
|
let entry_first = func.entry_block;
|
||||||
|
instructions::emit_jump(codegen, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sealed_mode {
|
||||||
|
instructions::flow::seal_block(codegen, &mut cursor, func, *bid, &succs, &bb_map, &phis_by_block, &block_end_values)?;
|
||||||
|
sealed_blocks.insert(*bid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if std::env::var("NYASH_ENABLE_LOOPFORM").ok().as_deref() == Some("1") &&
|
||||||
|
std::env::var("NYASH_LOOPFORM_LATCH2HEADER").ok().as_deref() == Some("1") {
|
||||||
|
for (hdr_bid, (_dispatch_bb, _tag_phi, _payload_phi, latch_bb)) in &loopform_registry {
|
||||||
|
if let Some(phis) = phis_by_block.get(hdr_bid) {
|
||||||
|
instructions::normalize_header_phis_for_latch(
|
||||||
|
codegen,
|
||||||
|
*hdr_bid,
|
||||||
|
*latch_bb,
|
||||||
|
phis,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instructions::dev_check_dispatch_only_phi(&phis_by_block, &loopform_registry);
|
||||||
|
}
|
||||||
|
for bb in llvm_func.get_basic_blocks() {
|
||||||
|
if unsafe { bb.get_terminator() }.is_none() {
|
||||||
|
codegen.builder.position_at_end(bb);
|
||||||
|
let _ = codegen.builder.build_unreachable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !llvm_func.verify(true) {
|
||||||
|
if std::env::var("NYASH_LLVM_DUMP_ON_FAIL").ok().as_deref() == Some("1") {
|
||||||
|
let ir = codegen.module.print_to_string().to_string();
|
||||||
|
let dump_dir = std::path::Path::new("tmp");
|
||||||
|
let _ = std::fs::create_dir_all(dump_dir);
|
||||||
|
let dump_path = dump_dir.join(format!("llvm_fail_{}.ll", sanitize_symbol(name)));
|
||||||
|
let _ = std::fs::write(&dump_path, ir);
|
||||||
|
eprintln!("[LLVM] wrote IR dump: {}", dump_path.display());
|
||||||
|
}
|
||||||
|
return Err(format!("Function verification failed: {}", name));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@ -18,32 +18,19 @@ use self::types::{
|
|||||||
classify_tag, cmp_eq_ne_any, i64_to_ptr, map_mirtype_to_basic, to_bool, to_i64_any,
|
classify_tag, cmp_eq_ne_any, i64_to_ptr, map_mirtype_to_basic, to_bool, to_i64_any,
|
||||||
};
|
};
|
||||||
mod instructions;
|
mod instructions;
|
||||||
|
mod utils;
|
||||||
|
mod function;
|
||||||
|
|
||||||
// --- Local helpers (refactor to keep compile_module slim) ---
|
// --- Local helpers (refactor to keep compile_module slim) ---
|
||||||
|
|
||||||
fn sanitize_symbol(name: &str) -> String {
|
// Moved to utils.rs
|
||||||
name.chars()
|
fn sanitize_symbol(name: &str) -> String { utils::sanitize_symbol(name) }
|
||||||
.map(|c| match c { '.' | '/' | '-' => '_', other => other })
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_const_str_map(f: &crate::mir::function::MirFunction) -> HashMap<ValueId, String> {
|
// Moved to utils.rs
|
||||||
let mut m = HashMap::new();
|
fn build_const_str_map(f: &crate::mir::function::MirFunction) -> HashMap<ValueId, String> { utils::build_const_str_map(f) }
|
||||||
for bid in f.block_ids() {
|
|
||||||
if let Some(b) = f.blocks.get(&bid) {
|
|
||||||
for inst in &b.instructions {
|
|
||||||
if let MirInstruction::Const { dst, value: ConstValue::String(s) } = inst {
|
|
||||||
m.insert(*dst, s.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(MirInstruction::Const { dst, value: ConstValue::String(s) }) = &b.terminator {
|
|
||||||
m.insert(*dst, s.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// moved: lower_one_function is implemented in function.rs
|
||||||
|
#[cfg(any())]
|
||||||
fn lower_one_function<'ctx>(
|
fn lower_one_function<'ctx>(
|
||||||
codegen: &CodegenContext<'ctx>,
|
codegen: &CodegenContext<'ctx>,
|
||||||
llvm_func: FunctionValue<'ctx>,
|
llvm_func: FunctionValue<'ctx>,
|
||||||
@ -142,7 +129,7 @@ fn lower_one_function<'ctx>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Map of const strings for Call resolution
|
// Map of const strings for Call resolution
|
||||||
let const_strs = build_const_str_map(func);
|
let const_strs = utils::build_const_str_map(func);
|
||||||
|
|
||||||
// Lower body
|
// Lower body
|
||||||
let mut loopform_loop_id: u32 = 0;
|
let mut loopform_loop_id: u32 = 0;
|
||||||
@ -572,7 +559,7 @@ impl LLVMCompiler {
|
|||||||
BasicTypeEnum::PointerType(t) => t.fn_type(¶ms_bt.iter().map(|t| (*t).into()).collect::<Vec<_>>(), false),
|
BasicTypeEnum::PointerType(t) => t.fn_type(¶ms_bt.iter().map(|t| (*t).into()).collect::<Vec<_>>(), false),
|
||||||
_ => return Err("Unsupported return basic type".to_string()),
|
_ => return Err("Unsupported return basic type".to_string()),
|
||||||
};
|
};
|
||||||
let sym = format!("ny_f_{}", sanitize_symbol(name));
|
let sym = format!("ny_f_{}", utils::sanitize_symbol(name));
|
||||||
let lf = codegen.module.add_function(&sym, ll_fn_ty, None);
|
let lf = codegen.module.add_function(&sym, ll_fn_ty, None);
|
||||||
llvm_funcs.insert(name.clone(), lf);
|
llvm_funcs.insert(name.clone(), lf);
|
||||||
}
|
}
|
||||||
@ -580,7 +567,7 @@ impl LLVMCompiler {
|
|||||||
// Lower all functions
|
// Lower all functions
|
||||||
for (name, func) in &mir_module.functions {
|
for (name, func) in &mir_module.functions {
|
||||||
let llvm_func = *llvm_funcs.get(name).ok_or("predecl not found")?;
|
let llvm_func = *llvm_funcs.get(name).ok_or("predecl not found")?;
|
||||||
lower_one_function(&codegen, llvm_func, func, name, &box_type_ids, &llvm_funcs)?;
|
function::lower_one_function(&codegen, llvm_func, func, name, &box_type_ids, &llvm_funcs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build entry wrapper and emit object
|
// Build entry wrapper and emit object
|
||||||
|
|||||||
27
src/backend/llvm/compiler/codegen/utils.rs
Normal file
27
src/backend/llvm/compiler/codegen/utils.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::mir::ValueId;
|
||||||
|
use crate::mir::instruction::{MirInstruction, ConstValue};
|
||||||
|
|
||||||
|
pub(super) fn sanitize_symbol(name: &str) -> String {
|
||||||
|
name.chars()
|
||||||
|
.map(|c| match c { '.' | '/' | '-' => '_', other => other })
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn build_const_str_map(f: &crate::mir::function::MirFunction) -> HashMap<ValueId, String> {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
for bid in f.block_ids() {
|
||||||
|
if let Some(b) = f.blocks.get(&bid) {
|
||||||
|
for inst in &b.instructions {
|
||||||
|
if let MirInstruction::Const { dst, value: ConstValue::String(s) } = inst {
|
||||||
|
m.insert(*dst, s.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(MirInstruction::Const { dst, value: ConstValue::String(s) }) = &b.terminator {
|
||||||
|
m.insert(*dst, s.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ use std::collections::HashMap;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
mod builder_calls;
|
mod builder_calls;
|
||||||
|
mod phi;
|
||||||
mod stmts;
|
mod stmts;
|
||||||
mod ops;
|
mod ops;
|
||||||
mod utils;
|
mod utils;
|
||||||
@ -28,6 +29,7 @@ mod exprs_peek; // peek expression
|
|||||||
mod exprs_lambda; // lambda lowering
|
mod exprs_lambda; // lambda lowering
|
||||||
mod exprs_include; // include lowering
|
mod exprs_include; // include lowering
|
||||||
mod plugin_sigs; // plugin signature loader
|
mod plugin_sigs; // plugin signature loader
|
||||||
|
mod vars; // variables/scope helpers
|
||||||
|
|
||||||
// moved helpers to builder/utils.rs
|
// moved helpers to builder/utils.rs
|
||||||
|
|
||||||
@ -284,12 +286,12 @@ impl MirBuilder {
|
|||||||
if let super::MirInstruction::Return { value: Some(v) } = inst {
|
if let super::MirInstruction::Return { value: Some(v) } = inst {
|
||||||
if let Some(mt) = self.value_types.get(v).cloned() { inferred = Some(mt); break 'outer; }
|
if let Some(mt) = self.value_types.get(v).cloned() { inferred = Some(mt); break 'outer; }
|
||||||
// 追加: v が PHI の場合は入力側の型から推定
|
// 追加: v が PHI の場合は入力側の型から推定
|
||||||
if let Some(mt) = utils::infer_type_from_phi(&function, *v, &self.value_types) { inferred = Some(mt); break 'outer; }
|
if let Some(mt) = phi::infer_type_from_phi(&function, *v, &self.value_types) { inferred = Some(mt); break 'outer; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(super::MirInstruction::Return { value: Some(v) }) = &bb.terminator {
|
if let Some(super::MirInstruction::Return { value: Some(v) }) = &bb.terminator {
|
||||||
if let Some(mt) = self.value_types.get(v).cloned() { inferred = Some(mt); break; }
|
if let Some(mt) = self.value_types.get(v).cloned() { inferred = Some(mt); break; }
|
||||||
if let Some(mt) = utils::infer_type_from_phi(&function, *v, &self.value_types) { inferred = Some(mt); break; }
|
if let Some(mt) = phi::infer_type_from_phi(&function, *v, &self.value_types) { inferred = Some(mt); break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(mt) = inferred { function.signature.return_type = mt; }
|
if let Some(mt) = inferred { function.signature.return_type = mt; }
|
||||||
@ -524,52 +526,7 @@ impl MirBuilder {
|
|||||||
let mut used: HashSet<String> = HashSet::new();
|
let mut used: HashSet<String> = HashSet::new();
|
||||||
let mut locals: HashSet<String> = HashSet::new();
|
let mut locals: HashSet<String> = HashSet::new();
|
||||||
for p in params.iter() { locals.insert(p.clone()); }
|
for p in params.iter() { locals.insert(p.clone()); }
|
||||||
fn collect_vars(node: &crate::ast::ASTNode, used: &mut HashSet<String>, locals: &mut HashSet<String>) {
|
for st in body.iter() { vars::collect_free_vars(st, &mut used, &mut locals); }
|
||||||
match node {
|
|
||||||
crate::ast::ASTNode::Variable { name, .. } => {
|
|
||||||
if name != "me" && name != "this" && !locals.contains(name) {
|
|
||||||
used.insert(name.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
crate::ast::ASTNode::Local { variables, .. } => { for v in variables { locals.insert(v.clone()); } }
|
|
||||||
crate::ast::ASTNode::Assignment { target, value, .. } => { collect_vars(target, used, locals); collect_vars(value, used, locals); }
|
|
||||||
crate::ast::ASTNode::BinaryOp { left, right, .. } => { collect_vars(left, used, locals); collect_vars(right, used, locals); }
|
|
||||||
crate::ast::ASTNode::UnaryOp { operand, .. } => { collect_vars(operand, used, locals); }
|
|
||||||
crate::ast::ASTNode::MethodCall { object, arguments, .. } => { collect_vars(object, used, locals); for a in arguments { collect_vars(a, used, locals); } }
|
|
||||||
crate::ast::ASTNode::FunctionCall { arguments, .. } => { for a in arguments { collect_vars(a, used, locals); } }
|
|
||||||
crate::ast::ASTNode::Call { callee, arguments, .. } => { collect_vars(callee, used, locals); for a in arguments { collect_vars(a, used, locals); } }
|
|
||||||
crate::ast::ASTNode::FieldAccess { object, .. } => { collect_vars(object, used, locals); }
|
|
||||||
crate::ast::ASTNode::New { arguments, .. } => { for a in arguments { collect_vars(a, used, locals); } }
|
|
||||||
crate::ast::ASTNode::If { condition, then_body, else_body, .. } => {
|
|
||||||
collect_vars(condition, used, locals);
|
|
||||||
for st in then_body { collect_vars(st, used, locals); }
|
|
||||||
if let Some(eb) = else_body { for st in eb { collect_vars(st, used, locals); } }
|
|
||||||
}
|
|
||||||
crate::ast::ASTNode::Loop { condition, body, .. } => { collect_vars(condition, used, locals); for st in body { collect_vars(st, used, locals); } }
|
|
||||||
crate::ast::ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
|
||||||
for st in try_body { collect_vars(st, used, locals); }
|
|
||||||
for c in catch_clauses { for st in &c.body { collect_vars(st, used, locals); } }
|
|
||||||
if let Some(fb) = finally_body { for st in fb { collect_vars(st, used, locals); } }
|
|
||||||
}
|
|
||||||
crate::ast::ASTNode::Throw { expression, .. } => { collect_vars(expression, used, locals); }
|
|
||||||
crate::ast::ASTNode::Print { expression, .. } => { collect_vars(expression, used, locals); }
|
|
||||||
crate::ast::ASTNode::Return { value, .. } => { if let Some(v) = value { collect_vars(v, used, locals); } }
|
|
||||||
crate::ast::ASTNode::AwaitExpression { expression, .. } => { collect_vars(expression, used, locals); }
|
|
||||||
crate::ast::ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => {
|
|
||||||
collect_vars(scrutinee, used, locals);
|
|
||||||
for (_, e) in arms { collect_vars(e, used, locals); }
|
|
||||||
collect_vars(else_expr, used, locals);
|
|
||||||
}
|
|
||||||
crate::ast::ASTNode::Program { statements, .. } => { for st in statements { collect_vars(st, used, locals); } }
|
|
||||||
crate::ast::ASTNode::FunctionDeclaration { params, body, .. } => {
|
|
||||||
let mut inner = locals.clone();
|
|
||||||
for p in params { inner.insert(p.clone()); }
|
|
||||||
for st in body { collect_vars(st, used, &mut inner); }
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for st in body.iter() { collect_vars(st, &mut used, &mut locals); }
|
|
||||||
// Materialize captures from current variable_map if known
|
// Materialize captures from current variable_map if known
|
||||||
let mut captures: Vec<(String, ValueId)> = Vec::new();
|
let mut captures: Vec<(String, ValueId)> = Vec::new();
|
||||||
for name in used.into_iter() {
|
for name in used.into_iter() {
|
||||||
|
|||||||
99
src/mir/builder/phi.rs
Normal file
99
src/mir/builder/phi.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
|
||||||
|
use crate::mir::{MirFunction, ValueId, MirType, MirInstruction, BasicBlockId};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::ast::ASTNode;
|
||||||
|
use super::MirBuilder;
|
||||||
|
|
||||||
|
// PHI-based return type inference helper
|
||||||
|
pub(super) fn infer_type_from_phi(
|
||||||
|
function: &MirFunction,
|
||||||
|
ret_val: ValueId,
|
||||||
|
types: &HashMap<ValueId, MirType>,
|
||||||
|
) -> Option<MirType> {
|
||||||
|
for (_bid, bb) in function.blocks.iter() {
|
||||||
|
for inst in bb.instructions.iter() {
|
||||||
|
if let MirInstruction::Phi { dst, inputs } = inst {
|
||||||
|
if *dst == ret_val {
|
||||||
|
let mut it = inputs.iter().filter_map(|(_, v)| types.get(v));
|
||||||
|
if let Some(first) = it.next() {
|
||||||
|
if it.all(|mt| mt == first) {
|
||||||
|
return Some(first.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local helper for if-statement analysis (moved from stmts.rs)
|
||||||
|
pub(super) fn extract_assigned_var(ast: &ASTNode) -> Option<String> {
|
||||||
|
match ast {
|
||||||
|
ASTNode::Assignment { target, .. } => {
|
||||||
|
if let ASTNode::Variable { name, .. } = target.as_ref() { Some(name.clone()) } else { None }
|
||||||
|
}
|
||||||
|
ASTNode::Program { statements, .. } => statements.last().and_then(|st| extract_assigned_var(st)),
|
||||||
|
ASTNode::If { then_body, else_body, .. } => {
|
||||||
|
// Look into nested if: if both sides assign the same variable, propagate that name upward.
|
||||||
|
let then_prog = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
|
||||||
|
let tvar = extract_assigned_var(&then_prog);
|
||||||
|
let evar = else_body.as_ref().and_then(|eb| {
|
||||||
|
let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() };
|
||||||
|
extract_assigned_var(&ep)
|
||||||
|
});
|
||||||
|
match (tvar, evar) {
|
||||||
|
(Some(tv), Some(ev)) if tv == ev => Some(tv),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MirBuilder {
|
||||||
|
/// Normalize Phi creation for if/else constructs.
|
||||||
|
/// This handles variable reassignment patterns and ensures a single exit value.
|
||||||
|
pub(super) fn normalize_if_else_phi(
|
||||||
|
&mut self,
|
||||||
|
then_block: BasicBlockId,
|
||||||
|
else_block: BasicBlockId,
|
||||||
|
then_value_raw: ValueId,
|
||||||
|
else_value_raw: ValueId,
|
||||||
|
pre_if_var_map: &HashMap<String, ValueId>,
|
||||||
|
then_ast_for_analysis: &ASTNode,
|
||||||
|
else_ast_for_analysis: &Option<ASTNode>,
|
||||||
|
then_var_map_end: &HashMap<String, ValueId>,
|
||||||
|
else_var_map_end_opt: &Option<HashMap<String, ValueId>>,
|
||||||
|
pre_then_var_value: Option<ValueId>,
|
||||||
|
) -> Result<ValueId, String> {
|
||||||
|
// If only the then-branch assigns a variable (e.g., `if c { x = ... }`) and the else
|
||||||
|
// does not assign the same variable, bind that variable to a Phi of (then_value, pre_if_value).
|
||||||
|
let assigned_var_then = extract_assigned_var(then_ast_for_analysis);
|
||||||
|
let assigned_var_else = else_ast_for_analysis.as_ref().and_then(|a| extract_assigned_var(a));
|
||||||
|
let result_val = self.value_gen.next();
|
||||||
|
|
||||||
|
if let Some(var_name) = assigned_var_then.clone() {
|
||||||
|
let else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false);
|
||||||
|
// Resolve branch-end values for the assigned variable
|
||||||
|
let then_value_for_var = then_var_map_end.get(&var_name).copied().unwrap_or(then_value_raw);
|
||||||
|
let else_value_for_var = if else_assigns_same {
|
||||||
|
else_var_map_end_opt.as_ref().and_then(|m| m.get(&var_name).copied()).unwrap_or(else_value_raw)
|
||||||
|
} else {
|
||||||
|
// Else doesn't assign: use pre-if value if available
|
||||||
|
pre_then_var_value.unwrap_or(else_value_raw)
|
||||||
|
};
|
||||||
|
// Emit Phi for the assigned variable and bind it
|
||||||
|
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value_for_var), (else_block, else_value_for_var)] })?;
|
||||||
|
self.variable_map = pre_if_var_map.clone();
|
||||||
|
self.variable_map.insert(var_name, result_val);
|
||||||
|
} else {
|
||||||
|
// No variable assignment pattern detected – just emit Phi for expression result
|
||||||
|
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value_raw), (else_block, else_value_raw)] })?;
|
||||||
|
// Merge variable map conservatively to pre-if snapshot (no new bindings)
|
||||||
|
self.variable_map = pre_if_var_map.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result_val)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ use super::{MirInstruction, EffectMask, Effect, ConstValue, ValueId};
|
|||||||
use crate::mir::TypeOpKind;
|
use crate::mir::TypeOpKind;
|
||||||
use crate::mir::loop_builder::LoopBuilder;
|
use crate::mir::loop_builder::LoopBuilder;
|
||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
|
use super::phi::extract_assigned_var;
|
||||||
|
|
||||||
impl super::MirBuilder {
|
impl super::MirBuilder {
|
||||||
// Print statement: env.console.log(value) with early TypeOp handling
|
// Print statement: env.console.log(value) with early TypeOp handling
|
||||||
@ -140,31 +141,18 @@ impl super::MirBuilder {
|
|||||||
// merge + phi
|
// merge + phi
|
||||||
self.current_block = Some(merge_block);
|
self.current_block = Some(merge_block);
|
||||||
self.ensure_block_exists(merge_block)?;
|
self.ensure_block_exists(merge_block)?;
|
||||||
// If only the then-branch assigns a variable (e.g., `if c { x = ... }`) and the else
|
let result_val = self.normalize_if_else_phi(
|
||||||
// does not assign the same variable, bind that variable to a Phi of (then_value, pre_if_value).
|
then_block,
|
||||||
let assigned_var_then = extract_assigned_var(&then_ast_for_analysis);
|
else_block,
|
||||||
let assigned_var_else = else_ast_for_analysis.as_ref().and_then(|a| extract_assigned_var(a));
|
then_value_raw,
|
||||||
let result_val = self.value_gen.next();
|
else_value_raw,
|
||||||
if let Some(var_name) = assigned_var_then.clone() {
|
&pre_if_var_map,
|
||||||
let else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false);
|
&then_ast_for_analysis,
|
||||||
// Resolve branch-end values for the assigned variable
|
&else_ast_for_analysis,
|
||||||
let then_value_for_var = then_var_map_end.get(&var_name).copied().unwrap_or(then_value_raw);
|
&then_var_map_end,
|
||||||
let else_value_for_var = if else_assigns_same {
|
&else_var_map_end_opt,
|
||||||
else_var_map_end_opt.as_ref().and_then(|m| m.get(&var_name).copied()).unwrap_or(else_value_raw)
|
pre_then_var_value,
|
||||||
} else {
|
)?;
|
||||||
// Else doesn't assign: use pre-if value if available
|
|
||||||
pre_then_var_value.unwrap_or(else_value_raw)
|
|
||||||
};
|
|
||||||
// Emit Phi for the assigned variable and bind it
|
|
||||||
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value_for_var), (else_block, else_value_for_var)] })?;
|
|
||||||
self.variable_map = pre_if_var_map.clone();
|
|
||||||
self.variable_map.insert(var_name, result_val);
|
|
||||||
} else {
|
|
||||||
// No variable assignment pattern detected – just emit Phi for expression result
|
|
||||||
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value_raw), (else_block, else_value_raw)] })?;
|
|
||||||
// Merge variable map conservatively to pre-if snapshot (no new bindings)
|
|
||||||
self.variable_map = pre_if_var_map.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result_val)
|
Ok(result_val)
|
||||||
}
|
}
|
||||||
@ -328,26 +316,4 @@ impl super::MirBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local helper for if-statement analysis
|
|
||||||
fn extract_assigned_var(ast: &ASTNode) -> Option<String> {
|
|
||||||
match ast {
|
|
||||||
ASTNode::Assignment { target, .. } => {
|
|
||||||
if let ASTNode::Variable { name, .. } = target.as_ref() { Some(name.clone()) } else { None }
|
|
||||||
}
|
|
||||||
ASTNode::Program { statements, .. } => statements.last().and_then(|st| extract_assigned_var(st)),
|
|
||||||
ASTNode::If { then_body, else_body, .. } => {
|
|
||||||
// Look into nested if: if both sides assign the same variable, propagate that name upward.
|
|
||||||
let then_prog = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
|
|
||||||
let tvar = extract_assigned_var(&then_prog);
|
|
||||||
let evar = else_body.as_ref().and_then(|eb| {
|
|
||||||
let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() };
|
|
||||||
extract_assigned_var(&ep)
|
|
||||||
});
|
|
||||||
match (tvar, evar) {
|
|
||||||
(Some(tv), Some(ev)) if tv == ev => Some(tv),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -41,28 +41,7 @@ pub(super) fn builder_debug_log(msg: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PHI-based return type inference helper
|
|
||||||
pub(super) fn infer_type_from_phi(
|
|
||||||
function: &super::MirFunction,
|
|
||||||
ret_val: super::ValueId,
|
|
||||||
types: &std::collections::HashMap<super::ValueId, super::MirType>,
|
|
||||||
) -> Option<super::MirType> {
|
|
||||||
for (_bid, bb) in function.blocks.iter() {
|
|
||||||
for inst in bb.instructions.iter() {
|
|
||||||
if let super::MirInstruction::Phi { dst, inputs } = inst {
|
|
||||||
if *dst == ret_val {
|
|
||||||
let mut it = inputs.iter().filter_map(|(_, v)| types.get(v));
|
|
||||||
if let Some(first) = it.next() {
|
|
||||||
if it.all(|mt| mt == first) {
|
|
||||||
return Some(first.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lightweight helpers moved from builder.rs to reduce file size
|
// Lightweight helpers moved from builder.rs to reduce file size
|
||||||
impl super::MirBuilder {
|
impl super::MirBuilder {
|
||||||
|
|||||||
74
src/mir/builder/vars.rs
Normal file
74
src/mir/builder/vars.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use crate::ast::ASTNode;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
/// Collect free variables used in `node` into `used`, excluding names present in `locals`.
|
||||||
|
/// `locals` is updated as new local declarations are encountered.
|
||||||
|
pub(super) fn collect_free_vars(node: &ASTNode, used: &mut HashSet<String>, locals: &mut HashSet<String>) {
|
||||||
|
match node {
|
||||||
|
ASTNode::Variable { name, .. } => {
|
||||||
|
if name != "me" && name != "this" && !locals.contains(name) {
|
||||||
|
used.insert(name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASTNode::Local { variables, .. } => {
|
||||||
|
for v in variables { locals.insert(v.clone()); }
|
||||||
|
}
|
||||||
|
ASTNode::Assignment { target, value, .. } => {
|
||||||
|
collect_free_vars(target, used, locals);
|
||||||
|
collect_free_vars(value, used, locals);
|
||||||
|
}
|
||||||
|
ASTNode::BinaryOp { left, right, .. } => {
|
||||||
|
collect_free_vars(left, used, locals);
|
||||||
|
collect_free_vars(right, used, locals);
|
||||||
|
}
|
||||||
|
ASTNode::UnaryOp { operand, .. } => { collect_free_vars(operand, used, locals); }
|
||||||
|
ASTNode::MethodCall { object, arguments, .. } => {
|
||||||
|
collect_free_vars(object, used, locals);
|
||||||
|
for a in arguments { collect_free_vars(a, used, locals); }
|
||||||
|
}
|
||||||
|
ASTNode::FunctionCall { arguments, .. } => {
|
||||||
|
for a in arguments { collect_free_vars(a, used, locals); }
|
||||||
|
}
|
||||||
|
ASTNode::Call { callee, arguments, .. } => {
|
||||||
|
collect_free_vars(callee, used, locals);
|
||||||
|
for a in arguments { collect_free_vars(a, used, locals); }
|
||||||
|
}
|
||||||
|
ASTNode::FieldAccess { object, .. } => { collect_free_vars(object, used, locals); }
|
||||||
|
ASTNode::New { arguments, .. } => {
|
||||||
|
for a in arguments { collect_free_vars(a, used, locals); }
|
||||||
|
}
|
||||||
|
ASTNode::If { condition, then_body, else_body, .. } => {
|
||||||
|
collect_free_vars(condition, used, locals);
|
||||||
|
for st in then_body { collect_free_vars(st, used, locals); }
|
||||||
|
if let Some(eb) = else_body { for st in eb { collect_free_vars(st, used, locals); } }
|
||||||
|
}
|
||||||
|
ASTNode::Loop { condition, body, .. } => {
|
||||||
|
collect_free_vars(condition, used, locals);
|
||||||
|
for st in body { collect_free_vars(st, used, locals); }
|
||||||
|
}
|
||||||
|
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||||
|
for st in try_body { collect_free_vars(st, used, locals); }
|
||||||
|
for c in catch_clauses { for st in &c.body { collect_free_vars(st, used, locals); } }
|
||||||
|
if let Some(fb) = finally_body { for st in fb { collect_free_vars(st, used, locals); } }
|
||||||
|
}
|
||||||
|
ASTNode::Throw { expression, .. } => { collect_free_vars(expression, used, locals); }
|
||||||
|
ASTNode::Print { expression, .. } => { collect_free_vars(expression, used, locals); }
|
||||||
|
ASTNode::Return { value, .. } => { if let Some(v) = value { collect_free_vars(v, used, locals); } }
|
||||||
|
ASTNode::AwaitExpression { expression, .. } => { collect_free_vars(expression, used, locals); }
|
||||||
|
ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => {
|
||||||
|
collect_free_vars(scrutinee, used, locals);
|
||||||
|
for (_, e) in arms { collect_free_vars(e, used, locals); }
|
||||||
|
collect_free_vars(else_expr, used, locals);
|
||||||
|
}
|
||||||
|
ASTNode::Program { statements, .. } => {
|
||||||
|
for st in statements { collect_free_vars(st, used, locals); }
|
||||||
|
}
|
||||||
|
ASTNode::FunctionDeclaration { params, body, .. } => {
|
||||||
|
let mut inner = locals.clone();
|
||||||
|
for p in params { inner.insert(p.clone()); }
|
||||||
|
for st in body { collect_free_vars(st, used, &mut inner); }
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
140
src/runner/build.rs
Normal file
140
src/runner/build.rs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
use super::NyashRunner;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
pub(super) fn run_build_mvp_impl(runner: &NyashRunner, cfg_path: &str) -> Result<(), String> {
|
||||||
|
let cwd = std::env::current_dir().unwrap_or(PathBuf::from("."));
|
||||||
|
let cfg_abspath = if Path::new(cfg_path).is_absolute() { PathBuf::from(cfg_path) } else { cwd.join(cfg_path) };
|
||||||
|
// 1) Load nyash.toml
|
||||||
|
let text = std::fs::read_to_string(&cfg_abspath).map_err(|e| format!("read {}: {}", cfg_abspath.display(), e))?;
|
||||||
|
let doc = toml::from_str::<toml::Value>(&text).map_err(|e| format!("parse {}: {}", cfg_abspath.display(), e))?;
|
||||||
|
// 2) Apply [env]
|
||||||
|
if let Some(env_tbl) = doc.get("env").and_then(|v| v.as_table()) {
|
||||||
|
for (k, v) in env_tbl.iter() { if let Some(s) = v.as_str() { std::env::set_var(k, s); } }
|
||||||
|
}
|
||||||
|
// Derive options
|
||||||
|
let profile = runner.config.build_profile.clone().unwrap_or_else(|| "release".into());
|
||||||
|
let aot = runner.config.build_aot.clone().unwrap_or_else(|| "cranelift".into());
|
||||||
|
let out = runner.config.build_out.clone();
|
||||||
|
let target = runner.config.build_target.clone();
|
||||||
|
// 3) Build plugins: read [plugins] values as paths and build each
|
||||||
|
if let Some(pl_tbl) = doc.get("plugins").and_then(|v| v.as_table()) {
|
||||||
|
for (name, v) in pl_tbl.iter() {
|
||||||
|
if let Some(path) = v.as_str() {
|
||||||
|
let p = if Path::new(path).is_absolute() { PathBuf::from(path) } else { cwd.join(path) };
|
||||||
|
let mut cmd = std::process::Command::new("cargo");
|
||||||
|
cmd.arg("build");
|
||||||
|
if profile == "release" { cmd.arg("--release"); }
|
||||||
|
if let Some(t) = &target { cmd.args(["--target", t]); }
|
||||||
|
cmd.current_dir(&p);
|
||||||
|
println!("[build] plugin {} at {}", name, p.display());
|
||||||
|
let status = cmd.status().map_err(|e| format!("spawn cargo (plugin {}): {}", name, e))?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(format!("plugin build failed: {} (dir={})", name, p.display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 4) Build nyash core (features)
|
||||||
|
{
|
||||||
|
let mut cmd = std::process::Command::new("cargo");
|
||||||
|
cmd.arg("build");
|
||||||
|
if profile == "release" { cmd.arg("--release"); }
|
||||||
|
match aot.as_str() { "llvm" => { cmd.args(["--features","llvm"]); }, _ => { cmd.args(["--features","cranelift-jit"]); } }
|
||||||
|
if let Some(t) = &target { cmd.args(["--target", t]); }
|
||||||
|
println!("[build] nyash core ({}, features={})", profile, if aot=="llvm" {"llvm"} else {"cranelift-jit"});
|
||||||
|
let status = cmd.status().map_err(|e| format!("spawn cargo (core): {}", e))?;
|
||||||
|
if !status.success() { return Err("nyash core build failed".into()); }
|
||||||
|
}
|
||||||
|
// 5) Determine app entry
|
||||||
|
let app = if let Some(a) = runner.config.build_app.clone() { a } else {
|
||||||
|
// try [build].app, else suggest
|
||||||
|
if let Some(tbl) = doc.get("build").and_then(|v| v.as_table()) {
|
||||||
|
if let Some(s) = tbl.get("app").and_then(|v| v.as_str()) { s.to_string() } else { String::new() }
|
||||||
|
} else { String::new() }
|
||||||
|
};
|
||||||
|
let app = if !app.is_empty() { app } else {
|
||||||
|
// collect candidates under apps/**/main.nyash
|
||||||
|
let mut cand: Vec<String> = Vec::new();
|
||||||
|
fn walk(dir: &Path, acc: &mut Vec<String>) {
|
||||||
|
if let Ok(rd) = std::fs::read_dir(dir) {
|
||||||
|
for e in rd.flatten() {
|
||||||
|
let p = e.path();
|
||||||
|
if p.is_dir() { walk(&p, acc); }
|
||||||
|
else if p.file_name().map(|n| n=="main.nyash").unwrap_or(false) {
|
||||||
|
acc.push(p.display().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
walk(&cwd.join("apps"), &mut cand);
|
||||||
|
let msg = if cand.is_empty() {
|
||||||
|
"no app specified (--app) and no apps/**/main.nyash found".to_string()
|
||||||
|
} else {
|
||||||
|
format!("no app specified (--app). Candidates:\n - {}", cand.join("\n - "))
|
||||||
|
};
|
||||||
|
return Err(msg);
|
||||||
|
};
|
||||||
|
// 6) Emit object
|
||||||
|
let obj_dir = cwd.join("target").join("aot_objects");
|
||||||
|
let _ = std::fs::create_dir_all(&obj_dir);
|
||||||
|
let obj_path = obj_dir.join("main.o");
|
||||||
|
if aot == "llvm" {
|
||||||
|
if std::env::var("LLVM_SYS_180_PREFIX").ok().is_none() && std::env::var("LLVM_SYS_181_PREFIX").ok().is_none() {
|
||||||
|
return Err("LLVM 18 not configured. Set LLVM_SYS_180_PREFIX or install LLVM 18 (llvm-config)".into());
|
||||||
|
}
|
||||||
|
std::env::set_var("NYASH_LLVM_OBJ_OUT", &obj_path);
|
||||||
|
println!("[emit] LLVM object → {}", obj_path.display());
|
||||||
|
let status = std::process::Command::new(cwd.join("target").join(profile.clone()).join(if cfg!(windows) {"nyash.exe"} else {"nyash"}))
|
||||||
|
.args(["--backend","llvm", &app])
|
||||||
|
.status().map_err(|e| format!("spawn nyash llvm: {}", e))?;
|
||||||
|
if !status.success() { return Err("LLVM emit failed".into()); }
|
||||||
|
} else {
|
||||||
|
std::env::set_var("NYASH_AOT_OBJECT_OUT", &obj_dir);
|
||||||
|
println!("[emit] Cranelift object → {} (directory)", obj_dir.display());
|
||||||
|
let status = std::process::Command::new(cwd.join("target").join(profile.clone()).join(if cfg!(windows) {"nyash.exe"} else {"nyash"}))
|
||||||
|
.args(["--backend","vm", &app])
|
||||||
|
.status().map_err(|e| format!("spawn nyash jit-aot: {}", e))?;
|
||||||
|
if !status.success() { return Err("Cranelift emit failed".into()); }
|
||||||
|
}
|
||||||
|
if !obj_path.exists() {
|
||||||
|
// In Cranelift path we produce target/aot_objects/<name>.o; fall back to main.o default
|
||||||
|
if !obj_dir.join("main.o").exists() { return Err(format!("object not generated under {}", obj_dir.display())); }
|
||||||
|
}
|
||||||
|
let out_path = if let Some(o) = out { PathBuf::from(o) } else { if cfg!(windows) { cwd.join("app.exe") } else { cwd.join("app") } };
|
||||||
|
// 7) Link
|
||||||
|
println!("[link] → {}", out_path.display());
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
// Prefer MSVC link.exe, then clang fallback
|
||||||
|
if let Ok(link) = which::which("link") {
|
||||||
|
let status = std::process::Command::new(&link).args(["/NOLOGO", &format!("/OUT:{}", out_path.display().to_string())])
|
||||||
|
.arg(&obj_path)
|
||||||
|
.arg(cwd.join("target").join("release").join("nyrt.lib"))
|
||||||
|
.status().map_err(|e| format!("spawn link.exe: {}", e))?;
|
||||||
|
if status.success() { println!("OK"); return Ok(()); }
|
||||||
|
}
|
||||||
|
if let Ok(clang) = which::which("clang") {
|
||||||
|
let status = std::process::Command::new(&clang)
|
||||||
|
.args(["-o", &out_path.display().to_string(), &obj_path.display().to_string()])
|
||||||
|
.arg(cwd.join("target").join("release").join("nyrt.lib").display().to_string())
|
||||||
|
.arg("-lntdll")
|
||||||
|
.status().map_err(|e| format!("spawn clang: {}", e))?;
|
||||||
|
if status.success() { println!("OK"); return Ok(()); }
|
||||||
|
return Err("link failed on Windows (tried link.exe and clang)".into());
|
||||||
|
}
|
||||||
|
return Err("no linker found (need Visual Studio link.exe or LLVM clang)".into());
|
||||||
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
let status = std::process::Command::new("cc")
|
||||||
|
.arg(&obj_path)
|
||||||
|
.args(["-L", &cwd.join("target").join("release").display().to_string()])
|
||||||
|
.args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive", "-lpthread", "-ldl", "-lm"])
|
||||||
|
.args(["-o", &out_path.display().to_string()])
|
||||||
|
.status().map_err(|e| format!("spawn cc: {}", e))?;
|
||||||
|
if !status.success() { return Err("link failed (cc)".into()); }
|
||||||
|
}
|
||||||
|
println!("✅ Success: {}", out_path.display());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
@ -134,3 +134,18 @@ pub(super) fn demo_interpreter_system() {
|
|||||||
Err(e) => println!(" ❌ Parse error: {}", e),
|
Err(e) => println!(" ❌ Parse error: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run all demo sections (moved from runner/mod.rs)
|
||||||
|
pub(super) fn run_all_demos() {
|
||||||
|
println!("🦀 Nyash Rust Implementation - Everything is Box! 🦀");
|
||||||
|
println!("====================================================");
|
||||||
|
demo_basic_boxes();
|
||||||
|
demo_box_operations();
|
||||||
|
demo_box_collections();
|
||||||
|
demo_environment_system();
|
||||||
|
demo_tokenizer_system();
|
||||||
|
demo_parser_system();
|
||||||
|
demo_interpreter_system();
|
||||||
|
println!("\n🎉 All Box operations completed successfully!");
|
||||||
|
println!("Memory safety guaranteed by Rust's borrow checker! 🛡️");
|
||||||
|
}
|
||||||
|
|||||||
@ -33,6 +33,8 @@ mod json_v0_bridge;
|
|||||||
mod mir_json_emit;
|
mod mir_json_emit;
|
||||||
mod pipe_io;
|
mod pipe_io;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
|
mod tasks;
|
||||||
|
mod build;
|
||||||
mod dispatch;
|
mod dispatch;
|
||||||
mod selfhost;
|
mod selfhost;
|
||||||
|
|
||||||
@ -43,35 +45,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
/// Resolve a using target according to priority: modules > relative > using-paths
|
/// Resolve a using target according to priority: modules > relative > using-paths
|
||||||
/// Returns Ok(resolved_path_or_token). On strict mode, ambiguous matches cause error.
|
/// Returns Ok(resolved_path_or_token). On strict mode, ambiguous matches cause error.
|
||||||
fn resolve_using_target(
|
use pipeline::resolve_using_target;
|
||||||
tgt: &str,
|
|
||||||
is_path: bool,
|
|
||||||
modules: &[(String, String)],
|
|
||||||
using_paths: &[String],
|
|
||||||
context_dir: Option<&std::path::Path>,
|
|
||||||
strict: bool,
|
|
||||||
verbose: bool,
|
|
||||||
) -> Result<String, String> {
|
|
||||||
if is_path { return Ok(tgt.to_string()); }
|
|
||||||
// 1) modules mapping
|
|
||||||
if let Some((_, p)) = modules.iter().find(|(n, _)| n == tgt) { return Ok(p.clone()); }
|
|
||||||
// 2) build candidate list: relative then using-paths
|
|
||||||
let rel = tgt.replace('.', "/") + ".nyash";
|
|
||||||
let mut cand: Vec<String> = Vec::new();
|
|
||||||
if let Some(dir) = context_dir { let c = dir.join(&rel); if c.exists() { cand.push(c.to_string_lossy().to_string()); } }
|
|
||||||
for base in using_paths {
|
|
||||||
let c = std::path::Path::new(base).join(&rel);
|
|
||||||
if c.exists() { cand.push(c.to_string_lossy().to_string()); }
|
|
||||||
}
|
|
||||||
if cand.is_empty() {
|
|
||||||
if verbose { eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt); }
|
|
||||||
return Ok(tgt.to_string());
|
|
||||||
}
|
|
||||||
if cand.len() > 1 && strict {
|
|
||||||
return Err(format!("ambiguous using '{}': {}", tgt, cand.join(", ")));
|
|
||||||
}
|
|
||||||
Ok(cand.remove(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Main execution coordinator
|
/// Main execution coordinator
|
||||||
pub struct NyashRunner {
|
pub struct NyashRunner {
|
||||||
@ -79,30 +53,7 @@ pub struct NyashRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Minimal task runner: read nyash.toml [env] and [tasks], run the named task via shell
|
/// Minimal task runner: read nyash.toml [env] and [tasks], run the named task via shell
|
||||||
fn run_named_task(name: &str) -> Result<(), String> {
|
use tasks::run_named_task;
|
||||||
let cfg_path = "nyash.toml";
|
|
||||||
let text = fs::read_to_string(cfg_path).map_err(|e| format!("read {}: {}", cfg_path, e))?;
|
|
||||||
let doc = toml::from_str::<toml::Value>(&text).map_err(|e| format!("parse {}: {}", cfg_path, e))?;
|
|
||||||
// Apply [env]
|
|
||||||
if let Some(env_tbl) = doc.get("env").and_then(|v| v.as_table()) {
|
|
||||||
for (k, v) in env_tbl.iter() {
|
|
||||||
if let Some(s) = v.as_str() { std::env::set_var(k, s); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Lookup [tasks]
|
|
||||||
let tasks = doc.get("tasks").and_then(|v| v.as_table()).ok_or("[tasks] not found in nyash.toml")?;
|
|
||||||
let cmd = tasks.get(name).and_then(|v| v.as_str()).ok_or_else(|| format!("task '{}' not found", name))?;
|
|
||||||
// Basic variable substitution
|
|
||||||
let root = std::env::current_dir().unwrap_or(PathBuf::from(".")).display().to_string();
|
|
||||||
let cmd = cmd.replace("{root}", &root);
|
|
||||||
// Run via shell
|
|
||||||
#[cfg(windows)]
|
|
||||||
let status = std::process::Command::new("cmd").args(["/C", &cmd]).status().map_err(|e| e.to_string())?;
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
let status = std::process::Command::new("sh").arg("-lc").arg(&cmd).status().map_err(|e| e.to_string())?;
|
|
||||||
if !status.success() { return Err(format!("task '{}' failed with status {:?}", name, status.code())); }
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NyashRunner {
|
impl NyashRunner {
|
||||||
/// Create a new runner with the given configuration
|
/// Create a new runner with the given configuration
|
||||||
@ -392,7 +343,7 @@ impl NyashRunner {
|
|||||||
// Delegate file-mode execution to modes::common dispatcher
|
// Delegate file-mode execution to modes::common dispatcher
|
||||||
self.run_file(filename);
|
self.run_file(filename);
|
||||||
} else {
|
} else {
|
||||||
self.execute_demo_mode();
|
demos::run_all_demos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,20 +351,7 @@ impl NyashRunner {
|
|||||||
|
|
||||||
/// Execute file-based mode with backend selection
|
/// Execute file-based mode with backend selection
|
||||||
|
|
||||||
/// Execute demo mode with all demonstrations
|
// demo runner moved to runner/demos.rs
|
||||||
fn execute_demo_mode(&self) {
|
|
||||||
println!("🦀 Nyash Rust Implementation - Everything is Box! 🦀");
|
|
||||||
println!("====================================================");
|
|
||||||
demos::demo_basic_boxes();
|
|
||||||
demos::demo_box_operations();
|
|
||||||
demos::demo_box_collections();
|
|
||||||
demos::demo_environment_system();
|
|
||||||
demos::demo_tokenizer_system();
|
|
||||||
demos::demo_parser_system();
|
|
||||||
demos::demo_interpreter_system();
|
|
||||||
println!("\n🎉 All Box operations completed successfully!");
|
|
||||||
println!("Memory safety guaranteed by Rust's borrow checker! 🛡️");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute Nyash file with interpreter (moved to modes/common.rs)
|
/// Execute Nyash file with interpreter (moved to modes/common.rs)
|
||||||
#[cfg(any())]
|
#[cfg(any())]
|
||||||
@ -838,143 +776,9 @@ impl NyashRunner {
|
|||||||
|
|
||||||
// execute_mir_module moved to runner/dispatch.rs
|
// execute_mir_module moved to runner/dispatch.rs
|
||||||
|
|
||||||
/// Minimal AOT build pipeline driven by nyash.toml (MVP, single-platform, best-effort)
|
/// Minimal AOT build pipeline driven by nyash.toml (mvp)
|
||||||
fn run_build_mvp(&self, cfg_path: &str) -> Result<(), String> {
|
fn run_build_mvp(&self, cfg_path: &str) -> Result<(), String> {
|
||||||
use std::path::{Path, PathBuf};
|
build::run_build_mvp_impl(self, cfg_path)
|
||||||
let cwd = std::env::current_dir().unwrap_or(PathBuf::from("."));
|
|
||||||
let cfg_abspath = if Path::new(cfg_path).is_absolute() { PathBuf::from(cfg_path) } else { cwd.join(cfg_path) };
|
|
||||||
// 1) Load nyash.toml
|
|
||||||
let text = std::fs::read_to_string(&cfg_abspath).map_err(|e| format!("read {}: {}", cfg_abspath.display(), e))?;
|
|
||||||
let doc = toml::from_str::<toml::Value>(&text).map_err(|e| format!("parse {}: {}", cfg_abspath.display(), e))?;
|
|
||||||
// 2) Apply [env]
|
|
||||||
if let Some(env_tbl) = doc.get("env").and_then(|v| v.as_table()) {
|
|
||||||
for (k, v) in env_tbl.iter() { if let Some(s) = v.as_str() { std::env::set_var(k, s); } }
|
|
||||||
}
|
|
||||||
// Derive options
|
|
||||||
let profile = self.config.build_profile.clone().unwrap_or_else(|| "release".into());
|
|
||||||
let aot = self.config.build_aot.clone().unwrap_or_else(|| "cranelift".into());
|
|
||||||
let out = self.config.build_out.clone();
|
|
||||||
let target = self.config.build_target.clone();
|
|
||||||
// 3) Build plugins: read [plugins] values as paths and build each
|
|
||||||
if let Some(pl_tbl) = doc.get("plugins").and_then(|v| v.as_table()) {
|
|
||||||
for (name, v) in pl_tbl.iter() {
|
|
||||||
if let Some(path) = v.as_str() {
|
|
||||||
let p = if Path::new(path).is_absolute() { PathBuf::from(path) } else { cwd.join(path) };
|
|
||||||
let mut cmd = std::process::Command::new("cargo");
|
|
||||||
cmd.arg("build");
|
|
||||||
if profile == "release" { cmd.arg("--release"); }
|
|
||||||
if let Some(t) = &target { cmd.args(["--target", t]); }
|
|
||||||
cmd.current_dir(&p);
|
|
||||||
println!("[build] plugin {} at {}", name, p.display());
|
|
||||||
let status = cmd.status().map_err(|e| format!("spawn cargo (plugin {}): {}", name, e))?;
|
|
||||||
if !status.success() {
|
|
||||||
return Err(format!("plugin build failed: {} (dir={})", name, p.display()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 4) Build nyash core (features)
|
|
||||||
{
|
|
||||||
let mut cmd = std::process::Command::new("cargo");
|
|
||||||
cmd.arg("build");
|
|
||||||
if profile == "release" { cmd.arg("--release"); }
|
|
||||||
match aot.as_str() { "llvm" => { cmd.args(["--features","llvm"]); }, _ => { cmd.args(["--features","cranelift-jit"]); } }
|
|
||||||
if let Some(t) = &target { cmd.args(["--target", t]); }
|
|
||||||
println!("[build] nyash core ({}, features={})", profile, if aot=="llvm" {"llvm"} else {"cranelift-jit"});
|
|
||||||
let status = cmd.status().map_err(|e| format!("spawn cargo (core): {}", e))?;
|
|
||||||
if !status.success() { return Err("nyash core build failed".into()); }
|
|
||||||
}
|
|
||||||
// 5) Determine app entry
|
|
||||||
let app = if let Some(a) = self.config.build_app.clone() { a } else {
|
|
||||||
// try [build].app, else suggest
|
|
||||||
if let Some(tbl) = doc.get("build").and_then(|v| v.as_table()) {
|
|
||||||
if let Some(s) = tbl.get("app").and_then(|v| v.as_str()) { s.to_string() } else { String::new() }
|
|
||||||
} else { String::new() }
|
|
||||||
};
|
|
||||||
let app = if !app.is_empty() { app } else {
|
|
||||||
// collect candidates under apps/**/main.nyash
|
|
||||||
let mut cand: Vec<String> = Vec::new();
|
|
||||||
fn walk(dir: &Path, acc: &mut Vec<String>) {
|
|
||||||
if let Ok(rd) = std::fs::read_dir(dir) {
|
|
||||||
for e in rd.flatten() {
|
|
||||||
let p = e.path();
|
|
||||||
if p.is_dir() { walk(&p, acc); }
|
|
||||||
else if p.file_name().map(|n| n=="main.nyash").unwrap_or(false) {
|
|
||||||
acc.push(p.display().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
walk(&cwd.join("apps"), &mut cand);
|
|
||||||
let msg = if cand.is_empty() {
|
|
||||||
"no app specified (--app) and no apps/**/main.nyash found".to_string()
|
|
||||||
} else {
|
|
||||||
format!("no app specified (--app). Candidates:\n - {}", cand.join("\n - "))
|
|
||||||
};
|
|
||||||
return Err(msg);
|
|
||||||
};
|
|
||||||
// 6) Emit object
|
|
||||||
let obj_dir = cwd.join("target").join("aot_objects");
|
|
||||||
let _ = std::fs::create_dir_all(&obj_dir);
|
|
||||||
let obj_path = obj_dir.join("main.o");
|
|
||||||
if aot == "llvm" {
|
|
||||||
if std::env::var("LLVM_SYS_180_PREFIX").ok().is_none() && std::env::var("LLVM_SYS_181_PREFIX").ok().is_none() {
|
|
||||||
return Err("LLVM 18 not configured. Set LLVM_SYS_180_PREFIX or install LLVM 18 (llvm-config)".into());
|
|
||||||
}
|
|
||||||
std::env::set_var("NYASH_LLVM_OBJ_OUT", &obj_path);
|
|
||||||
println!("[emit] LLVM object → {}", obj_path.display());
|
|
||||||
let status = std::process::Command::new(cwd.join("target").join(profile.clone()).join(if cfg!(windows) {"nyash.exe"} else {"nyash"}))
|
|
||||||
.args(["--backend","llvm", &app])
|
|
||||||
.status().map_err(|e| format!("spawn nyash llvm: {}", e))?;
|
|
||||||
if !status.success() { return Err("LLVM emit failed".into()); }
|
|
||||||
} else {
|
|
||||||
std::env::set_var("NYASH_AOT_OBJECT_OUT", &obj_dir);
|
|
||||||
println!("[emit] Cranelift object → {} (directory)", obj_dir.display());
|
|
||||||
let status = std::process::Command::new(cwd.join("target").join(profile.clone()).join(if cfg!(windows) {"nyash.exe"} else {"nyash"}))
|
|
||||||
.args(["--backend","vm", &app])
|
|
||||||
.status().map_err(|e| format!("spawn nyash jit-aot: {}", e))?;
|
|
||||||
if !status.success() { return Err("Cranelift emit failed".into()); }
|
|
||||||
}
|
|
||||||
if !obj_path.exists() {
|
|
||||||
// In Cranelift path we produce target/aot_objects/<name>.o; fall back to main.o default
|
|
||||||
if !obj_dir.join("main.o").exists() { return Err(format!("object not generated under {}", obj_dir.display())); }
|
|
||||||
}
|
|
||||||
let out_path = if let Some(o) = out { PathBuf::from(o) } else { if cfg!(windows) { cwd.join("app.exe") } else { cwd.join("app") } };
|
|
||||||
// 7) Link
|
|
||||||
println!("[link] → {}", out_path.display());
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
// Prefer MSVC link.exe, then clang fallback
|
|
||||||
if let Ok(link) = which::which("link") {
|
|
||||||
let status = std::process::Command::new(&link).args(["/NOLOGO", &format!("/OUT:{}", out_path.display().to_string())])
|
|
||||||
.arg(&obj_path)
|
|
||||||
.arg(cwd.join("target").join("release").join("nyrt.lib"))
|
|
||||||
.status().map_err(|e| format!("spawn link.exe: {}", e))?;
|
|
||||||
if status.success() { println!("OK"); return Ok(()); }
|
|
||||||
}
|
|
||||||
if let Ok(clang) = which::which("clang") {
|
|
||||||
let status = std::process::Command::new(&clang)
|
|
||||||
.args(["-o", &out_path.display().to_string(), &obj_path.display().to_string()])
|
|
||||||
.arg(cwd.join("target").join("release").join("nyrt.lib").display().to_string())
|
|
||||||
.arg("-lntdll")
|
|
||||||
.status().map_err(|e| format!("spawn clang: {}", e))?;
|
|
||||||
if status.success() { println!("OK"); return Ok(()); }
|
|
||||||
return Err("link failed on Windows (tried link.exe and clang)".into());
|
|
||||||
}
|
|
||||||
return Err("no linker found (need Visual Studio link.exe or LLVM clang)".into());
|
|
||||||
}
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
{
|
|
||||||
let status = std::process::Command::new("cc")
|
|
||||||
.arg(&obj_path)
|
|
||||||
.args(["-L", &cwd.join("target").join("release").display().to_string()])
|
|
||||||
.args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive", "-lpthread", "-ldl", "-lm"])
|
|
||||||
.args(["-o", &out_path.display().to_string()])
|
|
||||||
.status().map_err(|e| format!("spawn cc: {}", e))?;
|
|
||||||
if !status.success() { return Err("link failed (cc)".into()); }
|
|
||||||
}
|
|
||||||
println!("✅ Success: {}", out_path.display());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -99,3 +99,35 @@ pub(super) fn suggest_in_base(base: &str, leaf: &str, out: &mut Vec<String>) {
|
|||||||
let p = std::path::Path::new(base);
|
let p = std::path::Path::new(base);
|
||||||
walk(p, leaf, out, 4);
|
walk(p, leaf, out, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a using target according to priority: modules > relative > using-paths
|
||||||
|
/// Returns Ok(resolved_path_or_token). On strict mode, ambiguous matches cause error.
|
||||||
|
pub(super) fn resolve_using_target(
|
||||||
|
tgt: &str,
|
||||||
|
is_path: bool,
|
||||||
|
modules: &[(String, String)],
|
||||||
|
using_paths: &[String],
|
||||||
|
context_dir: Option<&std::path::Path>,
|
||||||
|
strict: bool,
|
||||||
|
verbose: bool,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
if is_path { return Ok(tgt.to_string()); }
|
||||||
|
// 1) modules mapping
|
||||||
|
if let Some((_, p)) = modules.iter().find(|(n, _)| n == tgt) { return Ok(p.clone()); }
|
||||||
|
// 2) build candidate list: relative then using-paths
|
||||||
|
let rel = tgt.replace('.', "/") + ".nyash";
|
||||||
|
let mut cand: Vec<String> = Vec::new();
|
||||||
|
if let Some(dir) = context_dir { let c = dir.join(&rel); if c.exists() { cand.push(c.to_string_lossy().to_string()); } }
|
||||||
|
for base in using_paths {
|
||||||
|
let c = std::path::Path::new(base).join(&rel);
|
||||||
|
if c.exists() { cand.push(c.to_string_lossy().to_string()); }
|
||||||
|
}
|
||||||
|
if cand.is_empty() {
|
||||||
|
if verbose { eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt); }
|
||||||
|
return Ok(tgt.to_string());
|
||||||
|
}
|
||||||
|
if cand.len() > 1 && strict {
|
||||||
|
return Err(format!("ambiguous using '{}': {}", tgt, cand.join(", ")));
|
||||||
|
}
|
||||||
|
Ok(cand.remove(0))
|
||||||
|
}
|
||||||
|
|||||||
28
src/runner/tasks.rs
Normal file
28
src/runner/tasks.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Minimal task runner: read nyash.toml [env] and [tasks], run the named task via shell
|
||||||
|
pub(super) fn run_named_task(name: &str) -> Result<(), String> {
|
||||||
|
let cfg_path = "nyash.toml";
|
||||||
|
let text = std::fs::read_to_string(cfg_path).map_err(|e| format!("read {}: {}", cfg_path, e))?;
|
||||||
|
let doc = toml::from_str::<toml::Value>(&text).map_err(|e| format!("parse {}: {}", cfg_path, e))?;
|
||||||
|
// Apply [env]
|
||||||
|
if let Some(env_tbl) = doc.get("env").and_then(|v| v.as_table()) {
|
||||||
|
for (k, v) in env_tbl.iter() {
|
||||||
|
if let Some(s) = v.as_str() { std::env::set_var(k, s); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Lookup [tasks]
|
||||||
|
let tasks = doc.get("tasks").and_then(|v| v.as_table()).ok_or("[tasks] not found in nyash.toml")?;
|
||||||
|
let cmd = tasks.get(name).and_then(|v| v.as_str()).ok_or_else(|| format!("task '{}' not found", name))?;
|
||||||
|
// Basic variable substitution
|
||||||
|
let root = std::env::current_dir().unwrap_or(PathBuf::from(".")).display().to_string();
|
||||||
|
let cmd = cmd.replace("{root}", &root);
|
||||||
|
// Run via shell
|
||||||
|
#[cfg(windows)]
|
||||||
|
let status = std::process::Command::new("cmd").args(["/C", &cmd]).status().map_err(|e| e.to_string())?;
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let status = std::process::Command::new("sh").arg("-lc").arg(&cmd).status().map_err(|e| e.to_string())?;
|
||||||
|
if !status.success() { return Err(format!("task '{}' failed with status {:?}", name, status.code())); }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user