Merge selfhosting-dev into main (Core-13 pure CI/tests + LLVM bridge) (#126)

* WIP: sync before merging origin/main

* fix: unify using/module + build CLI; add missing helper in runner; build passes; core smokes green; jit any.len string now returns 3

* Apply local changes after merging main; keep docs/phase-15 removed per main; add phase-15.1 docs and tests

* Remove legacy docs/phase-15/README.md to align with main

* integration: add Core-13 pure CI, tests, and minimal LLVM execute bridge (no docs) (#125)

Co-authored-by: Tomoaki <tomoaki@example.com>

---------

Co-authored-by: Selfhosting Dev <selfhost@example.invalid>
Co-authored-by: Tomoaki <tomoaki@example.com>
This commit is contained in:
moe-charm
2025-09-07 07:36:15 +09:00
committed by GitHub
parent 07350c5dd9
commit b8bdb867d8
70 changed files with 2010 additions and 57 deletions

View File

@ -15,13 +15,19 @@ struct ProgramV0 {
#[serde(tag = "type")]
enum StmtV0 {
Return { expr: ExprV0 },
Extern { iface: String, method: String, args: Vec<ExprV0> },
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(tag = "type")]
enum ExprV0 {
Int { value: serde_json::Value },
Str { value: String },
Bool { value: bool },
Binary { op: String, lhs: Box<ExprV0>, rhs: Box<ExprV0> },
Extern { iface: String, method: String, args: Vec<ExprV0> },
Compare { op: String, lhs: Box<ExprV0>, rhs: Box<ExprV0> },
Logical { op: String, lhs: Box<ExprV0>, rhs: Box<ExprV0> }, // short-circuit: &&, || (or: "and"/"or")
}
pub fn parse_json_v0_to_module(json: &str) -> Result<MirModule, String> {
@ -29,28 +35,60 @@ pub fn parse_json_v0_to_module(json: &str) -> Result<MirModule, String> {
if prog.version != 0 || prog.kind != "Program" {
return Err("unsupported IR: expected {version:0, kind:\"Program\"}".into());
}
let stmt = prog.body.get(0).ok_or("empty body")?;
// Create module and main function
let mut module = MirModule::new("ny_json_v0".into());
let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE };
let entry = BasicBlockId::new(0);
let mut f = MirFunction::new(sig, entry);
// Build expression
let ret_val = match stmt {
StmtV0::Return { expr } => lower_expr(&mut f, expr)?,
};
// Return
if let Some(bb) = f.get_block_mut(entry) {
bb.set_terminator(MirInstruction::Return { value: Some(ret_val) });
if prog.body.is_empty() { return Err("empty body".into()); }
// Lower all statements; capture last expression for return when the last is Return
let mut last_ret: Option<(crate::mir::ValueId, BasicBlockId)> = None;
for (i, stmt) in prog.body.iter().enumerate() {
match stmt {
StmtV0::Extern { iface, method, args } => {
// void extern call
let entry_bb = f.entry_block;
let (arg_ids, _cur) = lower_args(&mut f, entry_bb, args)?;
if let Some(bb) = f.get_block_mut(entry) {
bb.add_instruction(MirInstruction::ExternCall { dst: None, iface_name: iface.clone(), method_name: method.clone(), args: arg_ids, effects: EffectMask::IO });
}
if i == prog.body.len()-1 { last_ret = None; }
}
StmtV0::Return { expr } => {
let entry_bb = f.entry_block;
last_ret = Some(lower_expr(&mut f, entry_bb, expr)?);
}
}
}
// Infer return type (integer only for v0)
f.signature.return_type = MirType::Integer;
// Return last value (or 0)
if let Some((rv, cur)) = last_ret {
if let Some(bb) = f.get_block_mut(cur) {
bb.set_terminator(MirInstruction::Return { value: Some(rv) });
} else {
return Err("invalid block when setting return".into());
}
} else {
let dst_id = f.next_value_id();
if let Some(bb) = f.get_block_mut(entry) {
bb.add_instruction(MirInstruction::Const { dst: dst_id, value: ConstValue::Integer(0) });
bb.set_terminator(MirInstruction::Return { value: Some(dst_id) });
}
}
// Keep return type unknown to allow dynamic display (VM/Interpreter)
f.signature.return_type = MirType::Unknown;
module.add_function(f);
Ok(module)
}
fn lower_expr(f: &mut MirFunction, e: &ExprV0) -> Result<crate::mir::ValueId, String> {
fn next_block_id(f: &MirFunction) -> BasicBlockId {
let mut mx = 0u32;
for k in f.blocks.keys() { if k.0 >= mx { mx = k.0 + 1; } }
BasicBlockId::new(mx)
}
fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<(crate::mir::ValueId, BasicBlockId), String> {
match e {
ExprV0::Int { value } => {
// Accept number or stringified digits
@ -60,24 +98,123 @@ fn lower_expr(f: &mut MirFunction, e: &ExprV0) -> Result<crate::mir::ValueId, St
return Err("invalid int literal".into());
};
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(f.entry_block) {
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(ival) });
}
Ok(dst)
Ok((dst, cur_bb))
}
ExprV0::Str { value } => {
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::String(value.clone()) });
}
Ok((dst, cur_bb))
}
ExprV0::Bool { value } => {
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Bool(*value) });
}
Ok((dst, cur_bb))
}
ExprV0::Binary { op, lhs, rhs } => {
let l = lower_expr(f, lhs)?;
let r = lower_expr(f, rhs)?;
let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?;
let (r, cur_after_r) = lower_expr(f, cur_after_l, rhs)?;
let bop = match op.as_str() { "+" => BinaryOp::Add, "-" => BinaryOp::Sub, "*" => BinaryOp::Mul, "/" => BinaryOp::Div, _ => return Err("unsupported op".into()) };
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(f.entry_block) {
if let Some(bb) = f.get_block_mut(cur_after_r) {
bb.add_instruction(MirInstruction::BinOp { dst, op: bop, lhs: l, rhs: r });
}
Ok(dst)
Ok((dst, cur_after_r))
}
ExprV0::Extern { iface, method, args } => {
let (arg_ids, cur2) = lower_args(f, cur_bb, args)?;
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur2) {
bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: iface.clone(), method_name: method.clone(), args: arg_ids, effects: EffectMask::IO });
}
Ok((dst, cur2))
}
ExprV0::Compare { op, lhs, rhs } => {
let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?;
let (r, cur_after_r) = lower_expr(f, cur_after_l, rhs)?;
let cop = match op.as_str() {
"==" => crate::mir::CompareOp::Eq,
"!=" => crate::mir::CompareOp::Ne,
"<" => crate::mir::CompareOp::Lt,
"<=" => crate::mir::CompareOp::Le,
">" => crate::mir::CompareOp::Gt,
">=" => crate::mir::CompareOp::Ge,
_ => return Err("unsupported compare op".into()),
};
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_after_r) {
bb.add_instruction(MirInstruction::Compare { dst, op: cop, lhs: l, rhs: r });
}
Ok((dst, cur_after_r))
}
ExprV0::Logical { op, lhs, rhs } => {
// Short-circuit boolean logic with branches + phi
let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?;
let rhs_bb = next_block_id(f);
let fall_bb = BasicBlockId::new(rhs_bb.0 + 1);
let merge_bb = BasicBlockId::new(rhs_bb.0 + 2);
f.add_block(crate::mir::BasicBlock::new(rhs_bb));
f.add_block(crate::mir::BasicBlock::new(fall_bb));
f.add_block(crate::mir::BasicBlock::new(merge_bb));
// Branch depending on op
let is_and = matches!(op.as_str(), "&&" | "and");
if let Some(bb) = f.get_block_mut(cur_after_l) {
if is_and {
bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: rhs_bb, else_bb: fall_bb });
} else {
// OR: if lhs true, go to fall_bb (true path), else evaluate rhs
bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: fall_bb, else_bb: rhs_bb });
}
}
// Telemetry: note short-circuit lowering
crate::jit::events::emit_lower(
serde_json::json!({
"id": "shortcircuit",
"op": if is_and { "and" } else { "or" },
"rhs_bb": rhs_bb.0,
"fall_bb": fall_bb.0,
"merge_bb": merge_bb.0
}),
"shortcircuit",
"<json_v0>"
);
// false/true constant in fall_bb depending on op
let cdst = f.next_value_id();
if let Some(bb) = f.get_block_mut(fall_bb) {
let cval = if is_and { ConstValue::Bool(false) } else { ConstValue::Bool(true) };
bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval });
bb.set_terminator(MirInstruction::Jump { target: merge_bb });
}
// evaluate rhs in rhs_bb
let (rval, _rhs_end) = lower_expr(f, rhs_bb, rhs)?;
if let Some(bb) = f.get_block_mut(rhs_bb) {
if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); }
}
// merge with phi
let out = f.next_value_id();
if let Some(bb) = f.get_block_mut(merge_bb) {
bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_bb, rval), (fall_bb, cdst)] });
}
Ok((out, merge_bb))
}
}
}
fn lower_args(f: &mut MirFunction, cur_bb: BasicBlockId, args: &[ExprV0]) -> Result<(Vec<crate::mir::ValueId>, BasicBlockId), String> {
let mut out = Vec::with_capacity(args.len());
let mut cur = cur_bb;
for a in args {
let (v, c) = lower_expr(f, cur, a)?; out.push(v); cur = c;
}
Ok((out, cur))
}
pub fn maybe_dump_mir(module: &MirModule) {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
let mut p = MirPrinter::new();