merge: resolve conflicts (prefer cranelift-dev for Core-13 defaults; drop modules/using, add env.local/env.box shims)
This commit is contained in:
@ -95,9 +95,39 @@ pub fn compile_and_execute(mir_module: &MirModule, _temp_name: &str) -> Result<B
|
||||
MirInstruction::Copy { dst, src } => {
|
||||
if let Some(v) = regs.get(src).cloned() { regs.insert(*dst, v); }
|
||||
}
|
||||
MirInstruction::Debug { .. } | MirInstruction::Print { .. } | MirInstruction::Barrier { .. } | MirInstruction::BarrierRead { .. } | MirInstruction::BarrierWrite { .. } | MirInstruction::Safepoint | MirInstruction::Load { .. } | MirInstruction::Store { .. } | MirInstruction::TypeOp { .. } | MirInstruction::Compare { .. } | MirInstruction::NewBox { .. } | MirInstruction::PluginInvoke { .. } | MirInstruction::BoxCall { .. } | MirInstruction::ExternCall { .. } | MirInstruction::RefGet { .. } | MirInstruction::RefSet { .. } | MirInstruction::WeakRef { .. } | MirInstruction::FutureNew { .. } | MirInstruction::FutureSet { .. } | MirInstruction::Await { .. } | MirInstruction::Throw { .. } | MirInstruction::Catch { .. } => {
|
||||
MirInstruction::Debug { .. } | MirInstruction::Print { .. } | MirInstruction::Barrier { .. } | MirInstruction::BarrierRead { .. } | MirInstruction::BarrierWrite { .. } | MirInstruction::Safepoint | MirInstruction::Load { .. } | MirInstruction::Store { .. } | MirInstruction::TypeOp { .. } | MirInstruction::Compare { .. } | MirInstruction::NewBox { .. } | MirInstruction::PluginInvoke { .. } | MirInstruction::BoxCall { .. } | MirInstruction::RefGet { .. } | MirInstruction::RefSet { .. } | MirInstruction::WeakRef { .. } | MirInstruction::FutureNew { .. } | MirInstruction::FutureSet { .. } | MirInstruction::Await { .. } | MirInstruction::Throw { .. } | MirInstruction::Catch { .. } => {
|
||||
// ignore for minimal path
|
||||
}
|
||||
MirInstruction::ExternCall { dst, iface_name, method_name, args, .. } => {
|
||||
use crate::backend::vm::VMValue as V;
|
||||
match (iface_name.as_str(), method_name.as_str()) {
|
||||
("env.local", "get") => {
|
||||
if let Some(d) = dst { if let Some(a0) = args.get(0) { if let Some(v) = regs.get(a0).cloned() { regs.insert(*d, v); } } }
|
||||
}
|
||||
("env.local", "set") => {
|
||||
if args.len() >= 2 { if let Some(v) = regs.get(&args[1]).cloned() { regs.insert(args[0], v); } }
|
||||
// dst ignored
|
||||
}
|
||||
("env.box", "new") => {
|
||||
if let Some(d) = dst {
|
||||
if let Some(a0) = args.get(0) {
|
||||
if let Some(V::String(ty)) = regs.get(a0).cloned() {
|
||||
let reg = crate::runtime::box_registry::get_global_registry();
|
||||
// Collect args as NyashBox
|
||||
let mut ny_args: Vec<Box<dyn crate::box_trait::NyashBox>> = Vec::new();
|
||||
for vid in args.iter().skip(1) {
|
||||
if let Some(v) = regs.get(vid).cloned() { ny_args.push(v.to_nyash_box()); }
|
||||
}
|
||||
if let Ok(b) = reg.create_box(&ty, &ny_args) {
|
||||
regs.insert(*d, V::from_nyash_box(b));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => { /* ignore other externs in skeleton */ }
|
||||
}
|
||||
}
|
||||
MirInstruction::Phi { .. } => { /* handled above */ }
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -6,12 +6,70 @@ use crate::backend::{VM, VMError, VMValue};
|
||||
impl VM {
|
||||
/// Execute ExternCall instruction
|
||||
pub(crate) fn execute_extern_call(&mut self, dst: Option<ValueId>, iface_name: &str, method_name: &str, args: &[ValueId]) -> Result<ControlFlow, VMError> {
|
||||
// Core-13 pure shims: env.local.{get,set}, env.box.new
|
||||
match (iface_name, method_name) {
|
||||
("env.local", "get") => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("env.local.get arity".into())); }
|
||||
let ptr = args[0];
|
||||
let v = self.get_value(ptr).unwrap_or(crate::backend::vm::VMValue::Void);
|
||||
if let Some(d) = dst { self.set_value(d, v); }
|
||||
return Ok(ControlFlow::Continue);
|
||||
}
|
||||
("env.local", "set") => {
|
||||
if args.len() != 2 { return Err(VMError::InvalidInstruction("env.local.set arity".into())); }
|
||||
let ptr = args[0];
|
||||
let val = self.get_value(args[1])?;
|
||||
self.set_value(ptr, val);
|
||||
if let Some(d) = dst { self.set_value(d, crate::backend::vm::VMValue::Void); }
|
||||
return Ok(ControlFlow::Continue);
|
||||
}
|
||||
("env.box", "new") => {
|
||||
if args.is_empty() { return Err(VMError::InvalidInstruction("env.box.new requires type name".into())); }
|
||||
// first arg must be Const String type name
|
||||
let ty = self.get_value(args[0])?;
|
||||
let ty_name = match ty { crate::backend::vm::VMValue::String(s) => s, _ => return Err(VMError::InvalidInstruction("env.box.new first arg must be string".into())) };
|
||||
// remaining args as NyashBox
|
||||
let mut ny_args: Vec<Box<dyn NyashBox>> = Vec::new();
|
||||
for id in args.iter().skip(1) {
|
||||
let v = self.get_value(*id)?;
|
||||
ny_args.push(v.to_nyash_box());
|
||||
}
|
||||
let reg = crate::runtime::box_registry::get_global_registry();
|
||||
match reg.create_box(&ty_name, &ny_args) {
|
||||
Ok(b) => { if let Some(d) = dst { self.set_value(d, crate::backend::vm::VMValue::from_nyash_box(b)); } }
|
||||
Err(e) => { return Err(VMError::InvalidInstruction(format!("env.box.new failed for {}: {}", ty_name, e))); }
|
||||
}
|
||||
return Ok(ControlFlow::Continue);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Optional routing to name→slot handlers for stability and diagnostics
|
||||
if crate::config::env::extern_route_slots() {
|
||||
if let Some(slot) = crate::runtime::extern_registry::resolve_slot(iface_name, method_name) {
|
||||
// Decode args to VMValue as needed by handlers below
|
||||
let vm_args: Vec<VMValue> = args.iter().filter_map(|a| self.get_value(*a).ok()).collect();
|
||||
match (iface_name, method_name, slot) {
|
||||
("env.local", "get", 40) => {
|
||||
if let Some(d) = dst { if let Some(a0) = args.get(0) { let v = self.get_value(*a0).unwrap_or(VMValue::Void); self.set_value(d, v); } }
|
||||
return Ok(ControlFlow::Continue);
|
||||
}
|
||||
("env.local", "set", 41) => {
|
||||
if args.len() >= 2 { let ptr = args[0]; let val = vm_args.get(1).cloned().unwrap_or(VMValue::Void); self.set_value(ptr, val); }
|
||||
if let Some(d) = dst { self.set_value(d, VMValue::Void); }
|
||||
return Ok(ControlFlow::Continue);
|
||||
}
|
||||
("env.box", "new", 50) => {
|
||||
if vm_args.is_empty() { return Err(VMError::InvalidInstruction("env.box.new requires type".into())); }
|
||||
let ty = &vm_args[0]; let ty_name = match ty { VMValue::String(s) => s.clone(), _ => return Err(VMError::InvalidInstruction("env.box.new first arg must be string".into())) };
|
||||
let mut ny_args: Vec<Box<dyn NyashBox>> = Vec::new();
|
||||
for v in vm_args.iter().skip(1) { ny_args.push(v.to_nyash_box()); }
|
||||
let reg = crate::runtime::box_registry::get_global_registry();
|
||||
match reg.create_box(&ty_name, &ny_args) {
|
||||
Ok(b) => { if let Some(d) = dst { self.set_value(d, VMValue::from_nyash_box(b)); } }
|
||||
Err(e) => { return Err(VMError::InvalidInstruction(format!("env.box.new failed for {}: {}", ty_name, e))); }
|
||||
}
|
||||
return Ok(ControlFlow::Continue);
|
||||
}
|
||||
("env.console", m @ ("log" | "warn" | "error" | "println"), 10) => {
|
||||
if let Some(a0) = vm_args.get(0) {
|
||||
match m { "warn" => eprintln!("[warn] {}", a0.to_string()), "error" => eprintln!("[error] {}", a0.to_string()), _ => println!("{}", a0.to_string()), }
|
||||
|
||||
86
src/cli.rs
86
src/cli.rs
@ -57,10 +57,6 @@ pub struct CliConfig {
|
||||
// Phase-15: JSON IR v0 bridge
|
||||
pub ny_parser_pipe: bool,
|
||||
pub json_file: Option<String>,
|
||||
// Using/module resolution helpers (MVP)
|
||||
pub using: Option<String>,
|
||||
pub using_path: Option<String>,
|
||||
pub modules: Option<String>,
|
||||
// Build system (MVP)
|
||||
pub build_path: Option<String>,
|
||||
pub build_app: Option<String>,
|
||||
@ -107,24 +103,7 @@ impl CliConfig {
|
||||
.value_name("FILE")
|
||||
.help("Read Ny JSON IR v0 from a file and execute via MIR Interpreter")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("using")
|
||||
.long("using")
|
||||
.value_name("LIST")
|
||||
.help("Declare namespaces or aliases (comma-separated). Ex: 'acme.util, acme.math as M' or '\"apps/x.nyash\" as X'")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("using-path")
|
||||
.long("using-path")
|
||||
.value_name("PATHS")
|
||||
.help("Search paths for using (':' separated). Ex: 'apps:lib:.'")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("module")
|
||||
.long("module")
|
||||
.value_name("MAP")
|
||||
.help("Namespace to path mapping (comma-separated). Ex: 'acme.util=apps/acme/util.nyash'")
|
||||
)
|
||||
|
||||
.arg(
|
||||
Arg::new("debug-fuel")
|
||||
.long("debug-fuel")
|
||||
@ -427,9 +406,6 @@ impl CliConfig {
|
||||
parser_ny: matches.get_one::<String>("parser").map(|s| s == "ny").unwrap_or(false),
|
||||
ny_parser_pipe: matches.get_flag("ny-parser-pipe"),
|
||||
json_file: matches.get_one::<String>("json-file").cloned(),
|
||||
using: matches.get_one::<String>("using").cloned(),
|
||||
using_path: matches.get_one::<String>("using-path").cloned(),
|
||||
modules: matches.get_one::<String>("module").cloned(),
|
||||
// Build system (MVP)
|
||||
build_path: matches.get_one::<String>("build").cloned(),
|
||||
build_app: matches.get_one::<String>("build-app").cloned(),
|
||||
@ -441,31 +417,9 @@ impl CliConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse debug fuel value ("unlimited" or numeric)
|
||||
fn parse_debug_fuel(value: &str) -> Option<usize> {
|
||||
if value == "unlimited" {
|
||||
None // No limit
|
||||
} else {
|
||||
value.parse::<usize>().ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_debug_fuel() {
|
||||
assert_eq!(parse_debug_fuel("unlimited"), None);
|
||||
assert_eq!(parse_debug_fuel("1000"), Some(1000));
|
||||
assert_eq!(parse_debug_fuel("invalid"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_config() {
|
||||
// This test would require mocking clap's behavior
|
||||
// For now, we just ensure the structure is valid
|
||||
let config = CliConfig {
|
||||
impl Default for CliConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
file: None,
|
||||
debug_fuel: Some(100000),
|
||||
dump_ast: false,
|
||||
@ -505,17 +459,39 @@ mod tests {
|
||||
parser_ny: false,
|
||||
ny_parser_pipe: false,
|
||||
json_file: None,
|
||||
using: None,
|
||||
using_path: None,
|
||||
modules: None,
|
||||
build_path: None,
|
||||
build_app: None,
|
||||
build_out: None,
|
||||
build_aot: None,
|
||||
build_profile: None,
|
||||
build_target: None,
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse debug fuel value ("unlimited" or numeric)
|
||||
fn parse_debug_fuel(value: &str) -> Option<usize> {
|
||||
if value == "unlimited" {
|
||||
None // No limit
|
||||
} else {
|
||||
value.parse::<usize>().ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_debug_fuel() {
|
||||
assert_eq!(parse_debug_fuel("unlimited"), None);
|
||||
assert_eq!(parse_debug_fuel("1000"), Some(1000));
|
||||
assert_eq!(parse_debug_fuel("invalid"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_config() {
|
||||
let config = CliConfig::default();
|
||||
assert_eq!(config.backend, "interpreter");
|
||||
assert_eq!(config.iterations, 10);
|
||||
}
|
||||
|
||||
@ -86,12 +86,27 @@ pub fn await_max_ms() -> u64 {
|
||||
}
|
||||
|
||||
// ---- Phase 11.8 MIR cleanup toggles ----
|
||||
pub fn mir_core13() -> bool { std::env::var("NYASH_MIR_CORE13").ok().as_deref() == Some("1") }
|
||||
/// Core-13 minimal MIR mode toggle
|
||||
/// Default: ON (unless explicitly disabled with NYASH_MIR_CORE13=0)
|
||||
pub fn mir_core13() -> bool {
|
||||
match std::env::var("NYASH_MIR_CORE13").ok() {
|
||||
Some(v) => {
|
||||
let lv = v.to_ascii_lowercase();
|
||||
!(lv == "0" || lv == "false" || lv == "off")
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
pub fn mir_ref_boxcall() -> bool { std::env::var("NYASH_MIR_REF_BOXCALL").ok().as_deref() == Some("1") || mir_core13() }
|
||||
pub fn mir_array_boxcall() -> bool { std::env::var("NYASH_MIR_ARRAY_BOXCALL").ok().as_deref() == Some("1") || mir_core13() }
|
||||
pub fn mir_plugin_invoke() -> bool { std::env::var("NYASH_MIR_PLUGIN_INVOKE").ok().as_deref() == Some("1") }
|
||||
pub fn plugin_only() -> bool { std::env::var("NYASH_PLUGIN_ONLY").ok().as_deref() == Some("1") }
|
||||
|
||||
/// Core-13 "pure" mode: after normalization, only the 13 canonical ops are allowed.
|
||||
/// If enabled, the optimizer will try lightweight rewrites for Load/Store/NewBox/Unary,
|
||||
/// and the final verifier will reject any remaining non-Core-13 ops.
|
||||
pub fn mir_core13_pure() -> bool { std::env::var("NYASH_MIR_CORE13_PURE").ok().as_deref() == Some("1") }
|
||||
|
||||
// ---- Optimizer diagnostics ----
|
||||
pub fn opt_debug() -> bool { std::env::var("NYASH_OPT_DEBUG").is_ok() }
|
||||
pub fn opt_diag() -> bool { std::env::var("NYASH_OPT_DIAG").is_ok() }
|
||||
|
||||
@ -157,6 +157,10 @@ impl MirBuilder {
|
||||
/// Emit a weak reference creation (Unified: WeakRef(New))
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn emit_weak_new(&mut self, box_val: ValueId) -> Result<ValueId, String> {
|
||||
if crate::config::env::mir_core13_pure() {
|
||||
// Pure mode: avoid WeakRef emission; pass-through
|
||||
return Ok(box_val);
|
||||
}
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::WeakRef { dst, op: super::WeakRefOp::New, value: box_val })?;
|
||||
Ok(dst)
|
||||
@ -165,6 +169,10 @@ impl MirBuilder {
|
||||
/// Emit a weak reference load (Unified: WeakRef(Load))
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn emit_weak_load(&mut self, weak_ref: ValueId) -> Result<ValueId, String> {
|
||||
if crate::config::env::mir_core13_pure() {
|
||||
// Pure mode: avoid WeakRef emission; pass-through
|
||||
return Ok(weak_ref);
|
||||
}
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::WeakRef { dst, op: super::WeakRefOp::Load, value: weak_ref })?;
|
||||
Ok(dst)
|
||||
@ -771,6 +779,27 @@ impl MirBuilder {
|
||||
/// Build new expression: new ClassName(arguments)
|
||||
pub(super) fn build_new_expression(&mut self, class: String, arguments: Vec<ASTNode>) -> Result<ValueId, String> {
|
||||
// Phase 9.78a: Unified Box creation using NewBox instruction
|
||||
// Core-13 pure mode: emit ExternCall(env.box.new) with type name const only
|
||||
if crate::config::env::mir_core13_pure() {
|
||||
// Emit Const String for type name
|
||||
let ty_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: ty_id, value: ConstValue::String(class.clone()) })?;
|
||||
// Evaluate arguments (pass through to env.box.new shim)
|
||||
let mut arg_vals: Vec<ValueId> = Vec::with_capacity(arguments.len());
|
||||
for a in arguments { arg_vals.push(self.build_expression(a)?); }
|
||||
// Build arg list: [type, a1, a2, ...]
|
||||
let mut args: Vec<ValueId> = Vec::with_capacity(1 + arg_vals.len());
|
||||
args.push(ty_id);
|
||||
args.extend(arg_vals);
|
||||
// Call env.box.new
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::ExternCall {
|
||||
dst: Some(dst), iface_name: "env.box".to_string(), method_name: "new".to_string(), args, effects: EffectMask::PURE,
|
||||
})?;
|
||||
// 型注釈(最小)
|
||||
self.value_types.insert(dst, super::MirType::Box(class.clone()));
|
||||
return Ok(dst);
|
||||
}
|
||||
|
||||
// Optimization: Primitive wrappers → emit Const directly when possible
|
||||
if class == "IntegerBox" && arguments.len() == 1 {
|
||||
|
||||
@ -73,10 +73,35 @@ impl super::MirBuilder {
|
||||
// Build a unary operation
|
||||
pub(super) fn build_unary_op(&mut self, operator: String, operand: ASTNode) -> Result<ValueId, String> {
|
||||
let operand_val = self.build_expression(operand)?;
|
||||
// Core-13 純化: UnaryOp を直接 展開(Neg/Not/BitNot)
|
||||
if crate::config::env::mir_core13_pure() {
|
||||
match operator.as_str() {
|
||||
"-" => {
|
||||
let zero = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: zero, value: crate::mir::ConstValue::Integer(0) })?;
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::BinOp { dst, op: crate::mir::BinaryOp::Sub, lhs: zero, rhs: operand_val })?;
|
||||
return Ok(dst);
|
||||
}
|
||||
"!" | "not" => {
|
||||
let f = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: f, value: crate::mir::ConstValue::Bool(false) })?;
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Compare { dst, op: crate::mir::CompareOp::Eq, lhs: operand_val, rhs: f })?;
|
||||
return Ok(dst);
|
||||
}
|
||||
"~" => {
|
||||
let all1 = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: all1, value: crate::mir::ConstValue::Integer(-1) })?;
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::BinOp { dst, op: crate::mir::BinaryOp::BitXor, lhs: operand_val, rhs: all1 })?;
|
||||
return Ok(dst);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let dst = self.value_gen.next();
|
||||
|
||||
let mir_op = self.convert_unary_operator(operator)?;
|
||||
|
||||
self.emit_instruction(MirInstruction::UnaryOp { dst, op: mir_op, operand: operand_val })?;
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
@ -126,6 +126,37 @@ impl MirCompiler {
|
||||
return Err(format!("Core-13 strict: final MIR contains {} legacy ops", legacy_count));
|
||||
}
|
||||
}
|
||||
|
||||
// Core-13 pure: allow only the 13 canonical ops; reject all others
|
||||
if crate::config::env::mir_core13_pure() {
|
||||
let mut bad = 0usize;
|
||||
let is_allowed = |i: &MirInstruction| -> bool {
|
||||
matches!(i,
|
||||
MirInstruction::Const { .. }
|
||||
| MirInstruction::BinOp { .. }
|
||||
| MirInstruction::Compare { .. }
|
||||
| MirInstruction::Jump { .. }
|
||||
| MirInstruction::Branch { .. }
|
||||
| MirInstruction::Return { .. }
|
||||
| MirInstruction::Phi { .. }
|
||||
| MirInstruction::Call { .. }
|
||||
| MirInstruction::BoxCall { .. }
|
||||
| MirInstruction::ExternCall { .. }
|
||||
| MirInstruction::TypeOp { .. }
|
||||
| MirInstruction::Safepoint
|
||||
| MirInstruction::Barrier { .. }
|
||||
)
|
||||
};
|
||||
for (_fname, function) in &module.functions {
|
||||
for (_bb, block) in &function.blocks {
|
||||
for inst in &block.instructions { if !is_allowed(inst) { bad += 1; } }
|
||||
if let Some(term) = &block.terminator { if !is_allowed(term) { bad += 1; } }
|
||||
}
|
||||
}
|
||||
if bad > 0 {
|
||||
return Err(format!("Core-13 pure strict: final MIR contains {} non-Core-13 ops", bad));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the generated MIR
|
||||
let verification_result = self.verifier.verify_module(&module);
|
||||
@ -292,6 +323,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_lowering_await_expression() {
|
||||
if crate::config::env::mir_core13_pure() { eprintln!("[TEST] skip await under Core-13 pure mode"); return; }
|
||||
// Build AST: await 1 (semantic is nonsensical but should emit Await)
|
||||
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: crate::ast::Span::unknown() }), span: crate::ast::Span::unknown() };
|
||||
let mut compiler = MirCompiler::new();
|
||||
@ -302,6 +334,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_await_has_checkpoints() {
|
||||
if crate::config::env::mir_core13_pure() { eprintln!("[TEST] skip await under Core-13 pure mode"); return; }
|
||||
use crate::ast::{LiteralValue, Span};
|
||||
// Build: await 1
|
||||
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() };
|
||||
@ -317,6 +350,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_rewritten_await_still_checkpoints() {
|
||||
if crate::config::env::mir_core13_pure() { eprintln!("[TEST] skip await under Core-13 pure mode"); return; }
|
||||
use crate::ast::{LiteralValue, Span};
|
||||
// Enable rewrite so Await → ExternCall(env.future.await)
|
||||
std::env::set_var("NYASH_REWRITE_FUTURE", "1");
|
||||
@ -388,6 +422,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_try_catch_compilation() {
|
||||
// Core-13 pure モードでは Try/Catch 命令は許容集合外のためスキップ
|
||||
if crate::config::env::mir_core13_pure() {
|
||||
eprintln!("[TEST] skip try/catch under Core-13 pure mode");
|
||||
return;
|
||||
}
|
||||
let mut compiler = MirCompiler::new();
|
||||
|
||||
let try_catch_ast = ASTNode::TryCatch {
|
||||
|
||||
@ -85,7 +85,12 @@ impl MirOptimizer {
|
||||
// Lowererがより正確にBox種別を選べるようにする。
|
||||
let updates = crate::mir::passes::type_hints::propagate_param_type_hints(module);
|
||||
if updates > 0 { stats.intrinsic_optimizations += updates as usize; }
|
||||
|
||||
|
||||
// Pass 7 (optional): Core-13 pure normalization
|
||||
if crate::config::env::mir_core13_pure() {
|
||||
stats.merge(self.normalize_pure_core13(module));
|
||||
}
|
||||
|
||||
if self.debug {
|
||||
println!("✅ Optimization complete: {}", stats);
|
||||
}
|
||||
@ -115,6 +120,120 @@ impl MirOptimizer {
|
||||
|
||||
stats
|
||||
}
|
||||
|
||||
/// Core-13 "pure" normalization: rewrite a few non-13 ops to allowed forms.
|
||||
/// - Load(dst, ptr) => ExternCall(Some dst, env.local.get, [ptr])
|
||||
/// - Store(val, ptr) => ExternCall(None, env.local.set, [ptr, val])
|
||||
/// - NewBox(dst, T, args...) => ExternCall(Some dst, env.box.new, [Const String(T), args...])
|
||||
/// - UnaryOp:
|
||||
/// Neg x => BinOp(Sub, Const 0, x)
|
||||
/// Not x => Compare(Eq, x, Const false)
|
||||
/// BitNot x => BinOp(BitXor, x, Const(-1))
|
||||
fn normalize_pure_core13(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||
use super::instruction::ConstValue;
|
||||
use super::{MirInstruction as I, BinaryOp, CompareOp};
|
||||
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() + 8);
|
||||
let old = std::mem::take(&mut block.instructions);
|
||||
for inst in old.into_iter() {
|
||||
match inst {
|
||||
I::Load { dst, ptr } => {
|
||||
out.push(I::ExternCall {
|
||||
dst: Some(dst), iface_name: "env.local".to_string(), method_name: "get".to_string(),
|
||||
args: vec![ptr], effects: super::EffectMask::READ,
|
||||
});
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
I::Store { value, ptr } => {
|
||||
out.push(I::ExternCall {
|
||||
dst: None, iface_name: "env.local".to_string(), method_name: "set".to_string(),
|
||||
args: vec![ptr, value], effects: super::EffectMask::WRITE,
|
||||
});
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
I::NewBox { dst, box_type, mut args } => {
|
||||
// prepend type name as Const String
|
||||
let ty_id = super::ValueId::new(function.next_value_id);
|
||||
function.next_value_id += 1;
|
||||
out.push(I::Const { dst: ty_id, value: ConstValue::String(box_type) });
|
||||
let mut call_args = Vec::with_capacity(1 + args.len());
|
||||
call_args.push(ty_id);
|
||||
call_args.append(&mut args);
|
||||
out.push(I::ExternCall {
|
||||
dst: Some(dst), iface_name: "env.box".to_string(), method_name: "new".to_string(),
|
||||
args: call_args, effects: super::EffectMask::PURE, // constructor is logically alloc; conservatively PURE here
|
||||
});
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
I::UnaryOp { dst, op, operand } => {
|
||||
match op {
|
||||
super::UnaryOp::Neg => {
|
||||
let zero = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
|
||||
out.push(I::Const { dst: zero, value: ConstValue::Integer(0) });
|
||||
out.push(I::BinOp { dst, op: BinaryOp::Sub, lhs: zero, rhs: operand });
|
||||
}
|
||||
super::UnaryOp::Not => {
|
||||
let f = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
|
||||
out.push(I::Const { dst: f, value: ConstValue::Bool(false) });
|
||||
out.push(I::Compare { dst, op: CompareOp::Eq, lhs: operand, rhs: f });
|
||||
}
|
||||
super::UnaryOp::BitNot => {
|
||||
let all1 = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
|
||||
out.push(I::Const { dst: all1, value: ConstValue::Integer(-1) });
|
||||
out.push(I::BinOp { dst, op: BinaryOp::BitXor, lhs: operand, rhs: all1 });
|
||||
}
|
||||
}
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
other => out.push(other),
|
||||
}
|
||||
}
|
||||
block.instructions = out;
|
||||
if let Some(term) = block.terminator.take() {
|
||||
block.terminator = Some(match term {
|
||||
I::Load { dst, ptr } => {
|
||||
I::ExternCall { dst: Some(dst), iface_name: "env.local".to_string(), method_name: "get".to_string(), args: vec![ptr], effects: super::EffectMask::READ }
|
||||
}
|
||||
I::Store { value, ptr } => {
|
||||
I::ExternCall { dst: None, iface_name: "env.local".to_string(), method_name: "set".to_string(), args: vec![ptr, value], effects: super::EffectMask::WRITE }
|
||||
}
|
||||
I::NewBox { dst, box_type, mut args } => {
|
||||
let ty_id = super::ValueId::new(function.next_value_id);
|
||||
function.next_value_id += 1;
|
||||
block.instructions.push(I::Const { dst: ty_id, value: ConstValue::String(box_type) });
|
||||
let mut call_args = Vec::with_capacity(1 + args.len());
|
||||
call_args.push(ty_id);
|
||||
call_args.append(&mut args);
|
||||
I::ExternCall { dst: Some(dst), iface_name: "env.box".to_string(), method_name: "new".to_string(), args: call_args, effects: super::EffectMask::PURE }
|
||||
}
|
||||
I::UnaryOp { dst, op, operand } => {
|
||||
match op {
|
||||
super::UnaryOp::Neg => {
|
||||
let zero = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
|
||||
block.instructions.push(I::Const { dst: zero, value: ConstValue::Integer(0) });
|
||||
I::BinOp { dst, op: BinaryOp::Sub, lhs: zero, rhs: operand }
|
||||
}
|
||||
super::UnaryOp::Not => {
|
||||
let f = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
|
||||
block.instructions.push(I::Const { dst: f, value: ConstValue::Bool(false) });
|
||||
I::Compare { dst, op: CompareOp::Eq, lhs: operand, rhs: f }
|
||||
}
|
||||
super::UnaryOp::BitNot => {
|
||||
let all1 = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
|
||||
block.instructions.push(I::Const { dst: all1, value: ConstValue::Integer(-1) });
|
||||
I::BinOp { dst, op: BinaryOp::BitXor, lhs: operand, rhs: all1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
other => other,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
stats
|
||||
}
|
||||
|
||||
/// Eliminate dead code in a single function
|
||||
fn eliminate_dead_code_in_function(&mut self, function: &mut MirFunction) -> usize {
|
||||
|
||||
@ -34,9 +34,17 @@ static EXTERNS: Lazy<Vec<ExternSpec>> = Lazy::new(|| vec![
|
||||
ExternSpec { iface: "env.future", method: "birth", min_arity: 1, max_arity: 1, slot: Some(20) },
|
||||
ExternSpec { iface: "env.future", method: "set", min_arity: 2, max_arity: 2, slot: Some(21) },
|
||||
ExternSpec { iface: "env.future", method: "await", min_arity: 1, max_arity: 1, slot: Some(22) },
|
||||
// modules (minimal registry)
|
||||
ExternSpec { iface: "env.modules", method: "set", min_arity: 2, max_arity: 2, slot: None },
|
||||
ExternSpec { iface: "env.modules", method: "get", min_arity: 1, max_arity: 1, slot: None },
|
||||
<<<<<<< HEAD
|
||||
// core-13 pure support shims
|
||||
ExternSpec { iface: "env.local", method: "get", min_arity: 1, max_arity: 1, slot: Some(40) },
|
||||
ExternSpec { iface: "env.local", method: "set", min_arity: 2, max_arity: 2, slot: Some(41) },
|
||||
ExternSpec { iface: "env.box", method: "new", min_arity: 1, max_arity: 255, slot: Some(50) },
|
||||
=======
|
||||
// core-13 pure support shims
|
||||
ExternSpec { iface: "env.local", method: "get", min_arity: 1, max_arity: 1, slot: Some(40) },
|
||||
ExternSpec { iface: "env.local", method: "set", min_arity: 2, max_arity: 2, slot: Some(41) },
|
||||
ExternSpec { iface: "env.box", method: "new", min_arity: 1, max_arity: 255, slot: Some(50) },
|
||||
>>>>>>> cranelift-dev
|
||||
]);
|
||||
|
||||
pub fn resolve(iface: &str, method: &str) -> Option<ExternSpec> {
|
||||
|
||||
@ -68,6 +68,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_none_on_missing_or_invalid() {
|
||||
// Ensure env override is not present for this test
|
||||
env::remove_var("NYASH_SYNTAX_SUGAR_LEVEL");
|
||||
let dir = tempdir().unwrap();
|
||||
let file = dir.path().join("nyash.toml");
|
||||
fs::write(&file, "[syntax]\nsugar_level='unknown'\n").unwrap();
|
||||
@ -77,4 +79,3 @@ mod tests {
|
||||
assert_eq!(cfg2.level, SugarLevel::None);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
83
src/tests/mir_core13_normalize.rs
Normal file
83
src/tests/mir_core13_normalize.rs
Normal file
@ -0,0 +1,83 @@
|
||||
mod tests {
|
||||
use crate::mir::{
|
||||
MirModule, MirFunction, FunctionSignature, MirType, BasicBlock, BasicBlockId, ValueId,
|
||||
MirInstruction as I,
|
||||
};
|
||||
use crate::mir::optimizer::MirOptimizer;
|
||||
|
||||
fn mk_func(name: &str) -> (MirFunction, BasicBlockId) {
|
||||
let sig = FunctionSignature { name: name.to_string(), params: vec![], return_type: MirType::Void, effects: crate::mir::effect::EffectMask::PURE };
|
||||
let entry = BasicBlockId::new(0);
|
||||
(MirFunction::new(sig, entry), entry)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core13_normalizes_array_get_set_to_boxcall() {
|
||||
// Build function with ArrayGet/ArraySet, then optimize under Core-13
|
||||
let (mut f, bb0) = mk_func("core13_array_norm");
|
||||
let mut b0 = BasicBlock::new(bb0);
|
||||
let arr = ValueId::new(0);
|
||||
let idx = ValueId::new(1);
|
||||
let val = ValueId::new(2);
|
||||
let dst = ValueId::new(3);
|
||||
b0.add_instruction(I::ArrayGet { dst, array: arr, index: idx });
|
||||
b0.add_instruction(I::ArraySet { array: arr, index: idx, value: val });
|
||||
b0.add_instruction(I::Return { value: None });
|
||||
f.add_block(b0);
|
||||
let mut m = MirModule::new("test_core13_array".into());
|
||||
m.add_function(f);
|
||||
|
||||
let mut opt = MirOptimizer::new();
|
||||
let _ = opt.optimize_module(&mut m);
|
||||
|
||||
let func = m.get_function("core13_array_norm").unwrap();
|
||||
let block = func.get_block(bb0).unwrap();
|
||||
// Expect only BoxCall in place of ArrayGet/ArraySet
|
||||
let mut saw_get = false;
|
||||
let mut saw_set = false;
|
||||
for inst in block.all_instructions() {
|
||||
match inst {
|
||||
I::BoxCall { method, .. } if method == "get" => { saw_get = true; },
|
||||
I::BoxCall { method, .. } if method == "set" => { saw_set = true; },
|
||||
I::ArrayGet { .. } | I::ArraySet { .. } => panic!("legacy Array* must be normalized under Core-13"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
assert!(saw_get && saw_set, "expected BoxCall(get) and BoxCall(set) present");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core13_normalizes_ref_get_set_to_boxcall() {
|
||||
// Build function with RefGet/RefSet, then optimize under Core-13
|
||||
let (mut f, bb0) = mk_func("core13_ref_norm");
|
||||
let mut b0 = BasicBlock::new(bb0);
|
||||
let r = ValueId::new(10);
|
||||
let v = ValueId::new(11);
|
||||
let dst = ValueId::new(12);
|
||||
b0.add_instruction(I::RefGet { dst, reference: r, field: "foo".to_string() });
|
||||
b0.add_instruction(I::RefSet { reference: r, field: "bar".to_string(), value: v });
|
||||
b0.add_instruction(I::Return { value: None });
|
||||
f.add_block(b0);
|
||||
let mut m = MirModule::new("test_core13_ref".into());
|
||||
m.add_function(f);
|
||||
|
||||
let mut opt = MirOptimizer::new();
|
||||
let _ = opt.optimize_module(&mut m);
|
||||
|
||||
let func = m.get_function("core13_ref_norm").unwrap();
|
||||
let block = func.get_block(bb0).unwrap();
|
||||
// Expect BoxCall(getField/setField), a Barrier(Write) before setField, and no RefGet/RefSet remain
|
||||
let mut saw_get_field = false;
|
||||
let mut saw_set_field = false;
|
||||
for inst in block.all_instructions() {
|
||||
match inst {
|
||||
I::BoxCall { method, .. } if method == "getField" => { saw_get_field = true; },
|
||||
I::BoxCall { method, .. } if method == "setField" => { saw_set_field = true; },
|
||||
I::RefGet { .. } | I::RefSet { .. } => panic!("legacy Ref* must be normalized under Core-13"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
assert!(saw_get_field && saw_set_field, "expected BoxCall(getField) and BoxCall(setField) present");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user