Stabilize joinir tests and env guards
This commit is contained in:
@ -69,6 +69,7 @@ aot-plan-import = []
|
||||
name = "nyash_rust"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
doctest = false
|
||||
|
||||
# Bin layout (Stage0/Stage1)
|
||||
# - Stage0: Rust bootstrap CLI (`nyash`) — OS entry, VM/LLVM core, minimal runner.
|
||||
|
||||
@ -18,6 +18,10 @@ mod builder_init;
|
||||
mod builder_metadata;
|
||||
mod builder_method_index;
|
||||
mod builder_value_kind;
|
||||
#[cfg(test)]
|
||||
mod builder_test_api;
|
||||
#[cfg(test)]
|
||||
mod phi_observation_tests;
|
||||
mod builder_calls;
|
||||
mod call_resolution; // ChatGPT5 Pro: Type-safe call resolution utilities
|
||||
mod calls; // Call system modules (refactored from builder_calls)
|
||||
@ -256,7 +260,7 @@ mod binding_id_tests {
|
||||
let builder = MirBuilder::new();
|
||||
assert_eq!(builder.core_ctx.next_binding_id, 0);
|
||||
// Phase 2-6: binding_ctx is now SSOT (legacy field removed)
|
||||
assert!(builder.binding_ctx.is_empty());
|
||||
assert!(builder.binding_ctx.binding_map.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -88,27 +88,27 @@ mod tests {
|
||||
#[test]
|
||||
fn test_binding_context_basic() {
|
||||
let mut ctx = BindingContext::new();
|
||||
assert!(ctx.is_empty());
|
||||
assert_eq!(ctx.len(), 0);
|
||||
assert!(ctx.binding_map.is_empty());
|
||||
assert_eq!(ctx.binding_map.len(), 0);
|
||||
|
||||
let bid = BindingId::new(0);
|
||||
ctx.insert("x".to_string(), bid);
|
||||
assert_eq!(ctx.lookup("x"), Some(bid));
|
||||
assert_eq!(ctx.len(), 1);
|
||||
assert!(!ctx.is_empty());
|
||||
assert_eq!(ctx.binding_map.len(), 1);
|
||||
assert!(!ctx.binding_map.is_empty());
|
||||
|
||||
ctx.remove("x");
|
||||
assert_eq!(ctx.lookup("x"), None);
|
||||
assert!(ctx.is_empty());
|
||||
assert!(ctx.binding_map.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binding_context_contains() {
|
||||
let mut ctx = BindingContext::new();
|
||||
assert!(!ctx.contains("x"));
|
||||
assert!(!ctx.binding_map.contains_key("x"));
|
||||
|
||||
ctx.insert("x".to_string(), BindingId::new(0));
|
||||
assert!(ctx.contains("x"));
|
||||
assert!(ctx.binding_map.contains_key("x"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -117,9 +117,8 @@ mod tests {
|
||||
ctx.insert("a".to_string(), BindingId::new(1));
|
||||
ctx.insert("b".to_string(), BindingId::new(2));
|
||||
|
||||
let map = ctx.binding_map();
|
||||
assert_eq!(map.len(), 2);
|
||||
assert_eq!(map.get("a"), Some(&BindingId::new(1)));
|
||||
assert_eq!(map.get("b"), Some(&BindingId::new(2)));
|
||||
assert_eq!(ctx.binding_map.len(), 2);
|
||||
assert_eq!(ctx.binding_map.get("a"), Some(&BindingId::new(1)));
|
||||
assert_eq!(ctx.binding_map.get("b"), Some(&BindingId::new(2)));
|
||||
}
|
||||
}
|
||||
|
||||
41
src/mir/builder/builder_test_api.rs
Normal file
41
src/mir/builder/builder_test_api.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use super::MirBuilder;
|
||||
use crate::mir::{BasicBlockId, EffectMask, FunctionSignature, MirInstruction, MirType, ValueId};
|
||||
|
||||
impl MirBuilder {
|
||||
pub fn enter_function_for_test(&mut self, name: String) {
|
||||
let entry_block = self.core_ctx.next_block();
|
||||
let signature = FunctionSignature {
|
||||
name,
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
let function = self.new_function_with_metadata(signature, entry_block);
|
||||
self.scope_ctx.current_function = Some(function);
|
||||
self.current_block = Some(entry_block);
|
||||
}
|
||||
|
||||
pub fn exit_function_for_test(&mut self) {
|
||||
self.scope_ctx.current_function = None;
|
||||
self.current_block = None;
|
||||
}
|
||||
|
||||
pub fn push_block_for_test(&mut self) -> Result<BasicBlockId, String> {
|
||||
let block_id = self.core_ctx.next_block();
|
||||
self.start_new_block(block_id)?;
|
||||
Ok(block_id)
|
||||
}
|
||||
|
||||
pub fn current_block_for_test(&self) -> Result<BasicBlockId, String> {
|
||||
self.current_block
|
||||
.ok_or_else(|| "No current block".to_string())
|
||||
}
|
||||
|
||||
pub fn alloc_value_for_test(&mut self) -> ValueId {
|
||||
self.next_value_id()
|
||||
}
|
||||
|
||||
pub fn emit_for_test(&mut self, inst: MirInstruction) -> Result<(), String> {
|
||||
self.emit_instruction(inst)
|
||||
}
|
||||
}
|
||||
@ -62,4 +62,19 @@ impl EdgeStub {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 既に配線先が確定している EdgeStub を生成(テスト/配線済み用途)
|
||||
pub fn with_target(
|
||||
from: BasicBlockId,
|
||||
kind: ExitKind,
|
||||
target: BasicBlockId,
|
||||
args: EdgeArgs,
|
||||
) -> Self {
|
||||
Self {
|
||||
from,
|
||||
kind,
|
||||
target: Some(target),
|
||||
args,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ mod tests {
|
||||
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.contains("[joinir/contract/B1]"),
|
||||
err.contains("contract/B1"),
|
||||
"Error should have B1 tag: {}",
|
||||
err
|
||||
);
|
||||
@ -147,7 +147,7 @@ mod tests {
|
||||
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.contains("[joinir/contract/B1]"),
|
||||
err.contains("contract/B1"),
|
||||
"Error should have B1 tag: {}",
|
||||
err
|
||||
);
|
||||
|
||||
@ -272,6 +272,7 @@ impl ExitLineReconnector {
|
||||
variable_map: &BTreeMap<String, ValueId>,
|
||||
) {
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
|
||||
use crate::mir::ValueId;
|
||||
let trace = crate::mir::builder::control_flow::joinir::trace::trace();
|
||||
|
||||
for binding in &boundary.exit_bindings {
|
||||
@ -286,6 +287,17 @@ impl ExitLineReconnector {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// Phase 247-EX: Skip carriers without host slots (loop-local or FromHost placeholders).
|
||||
if binding.host_slot == ValueId(0) {
|
||||
trace.stderr_if(
|
||||
&format!(
|
||||
"[JoinIR/ExitLine/Contract] Phase 247-EX: Skipping carrier '{}' (no host slot)",
|
||||
binding.carrier_name
|
||||
),
|
||||
true,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Contract 1: carrier_phis must contain this carrier
|
||||
let phi_dst = carrier_phis.get(&binding.carrier_name);
|
||||
|
||||
@ -932,7 +932,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
if let Some(ref func) = builder.scope_ctx.current_function {
|
||||
debug_assertions::verify_joinir_contracts(
|
||||
func,
|
||||
entry_block,
|
||||
loop_header_phi_info.header_block,
|
||||
exit_block_id,
|
||||
&loop_header_phi_info,
|
||||
boundary,
|
||||
|
||||
@ -74,6 +74,7 @@ pub fn sync_spans(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::Span;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
#[test]
|
||||
fn test_sync_spans_exact_match() {
|
||||
|
||||
@ -49,11 +49,12 @@ pub(super) fn resolve_target_func_name<'a>(
|
||||
.find_map(|(fname, &entry_block)| (entry_block == target_block).then(|| fname.as_str()))
|
||||
}
|
||||
|
||||
/// Check if block is MAIN's entry block (pure lexical check)
|
||||
pub(super) fn is_joinir_main_entry_block(
|
||||
/// Check if the source function is the host (not the loop header function).
|
||||
///
|
||||
/// Used to classify LoopEntry for tail-call handling.
|
||||
pub(super) fn is_joinir_loop_entry_source(
|
||||
func_name: &str,
|
||||
func: &MirFunction,
|
||||
old_block_id: BasicBlockId,
|
||||
entry_func_name: Option<&str>,
|
||||
) -> bool {
|
||||
func_name == canonical_names::MAIN && old_block_id == func.entry_block
|
||||
entry_func_name.map(|name| name != func_name).unwrap_or(false)
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
//! - Record latch incoming for loop header PHI
|
||||
|
||||
// Import helpers from entry_resolver
|
||||
use super::entry_resolver::{resolve_target_func_name, is_joinir_main_entry_block};
|
||||
use super::entry_resolver::{resolve_target_func_name, is_joinir_loop_entry_source};
|
||||
|
||||
// Rewriter siblings (2 super:: up from plan/ to stages/, then 1 more to rewriter/)
|
||||
use super::super::super::{
|
||||
@ -43,8 +43,8 @@ pub(super) fn process_tail_call_params(
|
||||
new_block: &mut BasicBlock,
|
||||
tail_call_target: (BasicBlockId, &[ValueId]),
|
||||
func_name: &str,
|
||||
func: &MirFunction,
|
||||
old_block_id: BasicBlockId,
|
||||
_func: &MirFunction,
|
||||
_old_block_id: BasicBlockId,
|
||||
function_params: &BTreeMap<String, Vec<ValueId>>,
|
||||
entry_func_name: Option<&str>,
|
||||
continuation_candidates: &BTreeSet<String>,
|
||||
@ -83,7 +83,7 @@ pub(super) fn process_tail_call_params(
|
||||
// Phase 287 P2: Calculate tail_call_kind early for latch incoming logic
|
||||
// Only treat MAIN's entry block as entry-like (not loop_step's entry block)
|
||||
let is_entry_like_block_for_latch =
|
||||
is_joinir_main_entry_block(func_name, func, old_block_id);
|
||||
is_joinir_loop_entry_source(func_name, entry_func_name);
|
||||
|
||||
// CRITICAL: Argument order must match merge level classify_tail_call()
|
||||
let tail_call_kind = classify_tail_call(
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
//! - Collect carrier inputs for exit jumps
|
||||
|
||||
// Import helpers from entry_resolver
|
||||
use super::entry_resolver::{resolve_target_func_name, is_joinir_main_entry_block};
|
||||
use super::entry_resolver::{resolve_target_func_name, is_joinir_loop_entry_source};
|
||||
|
||||
// Rewriter siblings (2 super:: up from plan/ to stages/, then 1 more to rewriter/)
|
||||
use super::super::super::{
|
||||
@ -51,8 +51,8 @@ pub(super) fn process_block_terminator(
|
||||
found_tail_call: bool,
|
||||
tail_call_target: Option<(BasicBlockId, &[ValueId])>,
|
||||
func_name: &str,
|
||||
func: &MirFunction,
|
||||
old_block_id: BasicBlockId,
|
||||
_func: &MirFunction,
|
||||
_old_block_id: BasicBlockId,
|
||||
new_block_id: BasicBlockId,
|
||||
remapper: &JoinIrIdRemapper,
|
||||
local_block_map: &BTreeMap<BasicBlockId, BasicBlockId>,
|
||||
@ -257,9 +257,9 @@ pub(super) fn process_block_terminator(
|
||||
.map(|name| entry_func_name == Some(name))
|
||||
.unwrap_or(false);
|
||||
|
||||
// Phase 287 P2: main の entry block からの呼び出しを LoopEntry 扱いにする
|
||||
// Only treat MAIN's entry block as entry-like (not loop_step's entry block)
|
||||
let is_entry_like_block = is_joinir_main_entry_block(func_name, func, old_block_id);
|
||||
// Phase 287 P2: host entry block からの呼び出しを LoopEntry 扱いにする
|
||||
// (loop header func の entry block は含めない)
|
||||
let is_entry_like_block = is_joinir_loop_entry_source(func_name, entry_func_name);
|
||||
|
||||
// CRITICAL: Argument order must match merge level classify_tail_call()
|
||||
let tail_call_kind = classify_tail_call(
|
||||
|
||||
@ -1,68 +1,69 @@
|
||||
//! Phase 72: PHI dst observation integration test
|
||||
//!
|
||||
//! This test observes PHI dst allocation by instrumenting the MirBuilder.
|
||||
|
||||
use nyash_rust::mir::join_ir::verify_phi_reserved::{
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::verify_phi_reserved::{
|
||||
analyze_distribution, disable_observation, enable_observation, get_observations,
|
||||
};
|
||||
use crate::mir::types::ConstValue;
|
||||
use crate::mir::MirInstruction;
|
||||
|
||||
/// Phase 72-1: Observe PHI dst distribution from MirBuilder usage
|
||||
///
|
||||
/// This test manually creates MIR scenarios to observe PHI dst allocation.
|
||||
#[test]
|
||||
fn test_phase72_observe_phi_dst_via_builder() {
|
||||
use nyash_rust::mir::builder::MirBuilder;
|
||||
use nyash_rust::mir::types::ConstValue;
|
||||
use nyash_rust::mir::MirInstruction;
|
||||
|
||||
enable_observation();
|
||||
|
||||
// Create multiple builders to simulate different compilation contexts
|
||||
for scenario in 0..10 {
|
||||
let mut builder = MirBuilder::new();
|
||||
builder.enter_function(format!("test_func_{}", scenario));
|
||||
builder.enter_function_for_test(format!("test_func_{}", scenario));
|
||||
|
||||
// Allocate some values (simulating loop setup)
|
||||
let entry_block = builder.current_block();
|
||||
let _entry_block = builder.current_block_for_test().expect("entry block");
|
||||
|
||||
// Simulate loop header block
|
||||
builder.push_block();
|
||||
let header_block = builder.current_block();
|
||||
builder.push_block_for_test().expect("push block");
|
||||
let _header_block = builder.current_block_for_test().expect("header block");
|
||||
|
||||
// Allocate initial values
|
||||
let v1 = builder.next_value_id();
|
||||
let v2 = builder.next_value_id();
|
||||
let v3 = builder.next_value_id();
|
||||
let v1 = builder.alloc_value_for_test();
|
||||
let v2 = builder.alloc_value_for_test();
|
||||
let v3 = builder.alloc_value_for_test();
|
||||
|
||||
// Emit some instructions
|
||||
builder.emit_instruction(MirInstruction::Const {
|
||||
dst: v1,
|
||||
value: ConstValue::Integer(0),
|
||||
});
|
||||
builder.emit_instruction(MirInstruction::Const {
|
||||
dst: v2,
|
||||
value: ConstValue::Integer(100),
|
||||
});
|
||||
builder.emit_instruction(MirInstruction::Const {
|
||||
dst: v3,
|
||||
value: ConstValue::Integer(1),
|
||||
});
|
||||
builder
|
||||
.emit_for_test(MirInstruction::Const {
|
||||
dst: v1,
|
||||
value: ConstValue::Integer(0),
|
||||
})
|
||||
.expect("emit const v1");
|
||||
builder
|
||||
.emit_for_test(MirInstruction::Const {
|
||||
dst: v2,
|
||||
value: ConstValue::Integer(100),
|
||||
})
|
||||
.expect("emit const v2");
|
||||
builder
|
||||
.emit_for_test(MirInstruction::Const {
|
||||
dst: v3,
|
||||
value: ConstValue::Integer(1),
|
||||
})
|
||||
.expect("emit const v3");
|
||||
|
||||
// Now allocate PHI dst candidates (what would be PHI dsts)
|
||||
// These come from builder.next_value_id() in loop_header_phi_builder
|
||||
let phi_dst_1 = builder.next_value_id();
|
||||
let phi_dst_2 = builder.next_value_id();
|
||||
let phi_dst_3 = builder.next_value_id();
|
||||
let phi_dst_1 = builder.alloc_value_for_test();
|
||||
let phi_dst_2 = builder.alloc_value_for_test();
|
||||
let phi_dst_3 = builder.alloc_value_for_test();
|
||||
|
||||
// Manually observe these as if they were PHI dsts
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
nyash_rust::mir::join_ir::verify_phi_reserved::observe_phi_dst(phi_dst_1);
|
||||
nyash_rust::mir::join_ir::verify_phi_reserved::observe_phi_dst(phi_dst_2);
|
||||
nyash_rust::mir::join_ir::verify_phi_reserved::observe_phi_dst(phi_dst_3);
|
||||
crate::mir::join_ir::verify_phi_reserved::observe_phi_dst(phi_dst_1);
|
||||
crate::mir::join_ir::verify_phi_reserved::observe_phi_dst(phi_dst_2);
|
||||
crate::mir::join_ir::verify_phi_reserved::observe_phi_dst(phi_dst_3);
|
||||
}
|
||||
|
||||
builder.exit_function();
|
||||
builder.exit_function_for_test();
|
||||
}
|
||||
|
||||
// Collect observations
|
||||
@ -82,12 +83,12 @@ fn test_phase72_observe_phi_dst_via_builder() {
|
||||
eprintln!();
|
||||
|
||||
if report.is_all_reserved() {
|
||||
eprintln!("✅ CONCLUSION: All PHI dsts are in reserved region (0-99)");
|
||||
eprintln!(" → Safe to strengthen verifier with reserved region check");
|
||||
eprintln!("OK: CONCLUSION: All PHI dsts are in reserved region (0-99)");
|
||||
eprintln!(" -> Safe to strengthen verifier with reserved region check");
|
||||
} else {
|
||||
eprintln!("⚠️ CONCLUSION: Some PHI dsts are OUTSIDE reserved region");
|
||||
eprintln!(" → PHI dst allocation does NOT respect reserved boundary");
|
||||
eprintln!(" → Document this finding and skip verifier strengthening");
|
||||
eprintln!("WARN: CONCLUSION: Some PHI dsts are OUTSIDE reserved region");
|
||||
eprintln!(" -> PHI dst allocation does NOT respect reserved boundary");
|
||||
eprintln!(" -> Document this finding and skip verifier strengthening");
|
||||
|
||||
if let (Some(min), Some(max)) = (report.min_val, report.max_val) {
|
||||
eprintln!();
|
||||
@ -236,7 +236,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_unconditional_return() {
|
||||
let mut func = JoinFunction::new("test".to_string(), vec![]);
|
||||
let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]);
|
||||
let info = ReturnInfo {
|
||||
value: 42,
|
||||
condition: None,
|
||||
@ -276,7 +276,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_no_return() {
|
||||
let mut func = JoinFunction::new("test".to_string(), vec![]);
|
||||
let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]);
|
||||
let return_info: Option<&ReturnInfo> = None;
|
||||
let k_return_id = JoinFuncId(99);
|
||||
let loop_var_id = ValueId(100);
|
||||
@ -304,7 +304,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_conditional_return_success() {
|
||||
// Phase 284 P1: Test successful conditional return generation
|
||||
let mut func = JoinFunction::new("test".to_string(), vec![]);
|
||||
let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]);
|
||||
let condition = Box::new(ASTNode::BinaryOp {
|
||||
operator: crate::ast::BinaryOperator::Equal,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
|
||||
@ -496,6 +496,7 @@ pub(crate) fn normalized_dev_roundtrip_structured(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::join_ir::{JoinContId, JoinFuncId, JoinFunction, JoinInst, MirLikeInst};
|
||||
|
||||
fn build_structured_pattern1() -> JoinModule {
|
||||
let mut module = JoinModule::new();
|
||||
|
||||
@ -96,6 +96,7 @@ pub fn build_default_env_layout(layout_id: u32) -> EnvLayout {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
#[test]
|
||||
fn test_build_from_params() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use super::{convert_join_module_to_mir_with_meta, JoinIrVmBridgeError};
|
||||
use super::{convert_join_module_to_mir_with_meta, join_func_name, JoinIrVmBridgeError};
|
||||
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::MirModule;
|
||||
@ -127,6 +127,43 @@ fn module_has_joinir_value_ids(module: &JoinModule) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn ensure_joinir_function_aliases(mir_module: &mut MirModule, join_module: &JoinModule) {
|
||||
for (func_id, join_func) in &join_module.functions {
|
||||
let generated_name = join_func_name(*func_id);
|
||||
let function = mir_module
|
||||
.functions
|
||||
.get(&join_func.name)
|
||||
.or_else(|| mir_module.functions.get(&generated_name))
|
||||
.cloned();
|
||||
|
||||
if let Some(function) = function {
|
||||
if !mir_module.functions.contains_key(&join_func.name) {
|
||||
mir_module
|
||||
.functions
|
||||
.insert(join_func.name.clone(), function.clone());
|
||||
}
|
||||
|
||||
if !mir_module.functions.contains_key(&generated_name) {
|
||||
mir_module
|
||||
.functions
|
||||
.insert(generated_name.clone(), function.clone());
|
||||
}
|
||||
|
||||
let actual_arity = format!("{}/{}", join_func.name, join_func.params.len());
|
||||
if !mir_module.functions.contains_key(&actual_arity) {
|
||||
mir_module
|
||||
.functions
|
||||
.insert(actual_arity, function.clone());
|
||||
}
|
||||
|
||||
let generated_arity = format!("{}/{}", generated_name, join_func.params.len());
|
||||
if !mir_module.functions.contains_key(&generated_arity) {
|
||||
mir_module.functions.insert(generated_arity, function);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
use crate::config::env::joinir_dev::{current_joinir_mode, JoinIrMode};
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
@ -397,7 +434,10 @@ pub(crate) fn bridge_joinir_to_mir_with_meta(
|
||||
let canonical_shapes = shape_guard::canonical_shapes(module);
|
||||
if !canonical_shapes.is_empty() {
|
||||
match try_normalized_direct_bridge(module, meta, &canonical_shapes, false, false, boundary)? {
|
||||
Some(mir) => return Ok(mir),
|
||||
Some(mut mir) => {
|
||||
ensure_joinir_function_aliases(&mut mir, module);
|
||||
return Ok(mir);
|
||||
}
|
||||
None => {
|
||||
return Err(JoinIrVmBridgeError::new(
|
||||
"[joinir/bridge] canonical normalized route returned None unexpectedly",
|
||||
@ -412,7 +452,10 @@ pub(crate) fn bridge_joinir_to_mir_with_meta(
|
||||
// サポート形状のみ Normalized path を試行、失敗時は Structured fallback
|
||||
let shapes = shape_guard::direct_shapes(module);
|
||||
match try_normalized_direct_bridge(module, meta, &shapes, true, true, boundary)? {
|
||||
Some(mir) => return Ok(mir),
|
||||
Some(mut mir) => {
|
||||
ensure_joinir_function_aliases(&mut mir, module);
|
||||
return Ok(mir);
|
||||
}
|
||||
None => {} // Fallback to Structured
|
||||
}
|
||||
}
|
||||
@ -423,7 +466,9 @@ pub(crate) fn bridge_joinir_to_mir_with_meta(
|
||||
}
|
||||
}
|
||||
|
||||
lower_joinir_structured_to_mir_with_meta(module, meta, boundary)
|
||||
let mut mir = lower_joinir_structured_to_mir_with_meta(module, meta, boundary)?;
|
||||
ensure_joinir_function_aliases(&mut mir, module);
|
||||
Ok(mir)
|
||||
}
|
||||
|
||||
/// JoinIR → MIR(メタなし)呼び出しのユーティリティ。
|
||||
|
||||
@ -290,7 +290,12 @@ mod tests {
|
||||
} => {
|
||||
assert_eq!(*dst, Some(ValueId(10)));
|
||||
assert_eq!(*func, ValueId(90005));
|
||||
assert_eq!(*callee, None);
|
||||
assert_eq!(
|
||||
*callee,
|
||||
Some(crate::mir::definitions::Callee::Global(
|
||||
"join_func_5".to_string()
|
||||
))
|
||||
);
|
||||
assert_eq!(args, &[ValueId(1), ValueId(2)]);
|
||||
assert_eq!(*effects, EffectMask::PURE);
|
||||
}
|
||||
|
||||
@ -296,7 +296,8 @@ fn get_continuation_name(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::{FunctionSignature, MirType, ConstValue};
|
||||
use crate::ast::Span;
|
||||
use crate::mir::{ConstValue, EffectMask, FunctionSignature, MirType};
|
||||
use crate::mir::join_ir_vm_bridge::block_allocator::BlockAllocator;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ pub fn inject_method_ids(module: &mut MirModule) -> usize {
|
||||
for (_fname, func) in module.functions.iter_mut() {
|
||||
// Track simple value origins: ValueId -> type_name
|
||||
let mut origin: HashMap<ValueId, String> = HashMap::new();
|
||||
let mut ptr_origin: HashMap<ValueId, String> = HashMap::new();
|
||||
|
||||
// Single forward pass is sufficient for NewBox/Copy cases
|
||||
for (_bid, block) in func.blocks.iter_mut() {
|
||||
@ -38,6 +39,16 @@ pub fn inject_method_ids(module: &mut MirModule) -> usize {
|
||||
origin.insert(*dst, bt);
|
||||
}
|
||||
}
|
||||
I::Store { value, ptr } => {
|
||||
if let Some(bt) = origin.get(value).cloned() {
|
||||
ptr_origin.insert(*ptr, bt);
|
||||
}
|
||||
}
|
||||
I::Load { dst, ptr } => {
|
||||
if let Some(bt) = ptr_origin.get(ptr).cloned() {
|
||||
origin.insert(*dst, bt);
|
||||
}
|
||||
}
|
||||
I::BoxCall {
|
||||
box_val,
|
||||
method,
|
||||
@ -63,10 +74,39 @@ pub fn inject_method_ids(module: &mut MirModule) -> usize {
|
||||
}
|
||||
}
|
||||
}
|
||||
I::PluginInvoke { .. } => {
|
||||
// Keep PluginInvoke as-is and let the interpreter handle it via plugin host.
|
||||
// This avoids premature lowering that can mask plugin-specific calling
|
||||
// conventions. Method ID resolution for plugins is handled at runtime.
|
||||
I::PluginInvoke {
|
||||
dst,
|
||||
box_val,
|
||||
method,
|
||||
args,
|
||||
effects,
|
||||
} => {
|
||||
if let Some(bt) = origin.get(box_val).cloned() {
|
||||
let mid_u16 = if let Some(h) = host_guard.as_ref() {
|
||||
match h.resolve_method(&bt, method) {
|
||||
Ok(mh) => Some(mh.method_id as u16),
|
||||
Err(_) => resolve_slot_by_type_name(&bt, method),
|
||||
}
|
||||
} else {
|
||||
resolve_slot_by_type_name(&bt, method)
|
||||
};
|
||||
if let Some(mid) = mid_u16 {
|
||||
let method = method.clone();
|
||||
let args = args.clone();
|
||||
let dst = *dst;
|
||||
let box_val = *box_val;
|
||||
let effects = *effects;
|
||||
*inst = I::BoxCall {
|
||||
dst,
|
||||
box_val,
|
||||
method,
|
||||
method_id: Some(mid),
|
||||
args,
|
||||
effects,
|
||||
};
|
||||
injected += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -214,6 +214,22 @@ impl NyashParser {
|
||||
pub(super) fn parse_statement(&mut self) -> Result<ASTNode, ParseError> {
|
||||
// For grammar diff: capture starting token to classify statement keyword
|
||||
let start_tok = self.current_token().token_type.clone();
|
||||
if !crate::config::env::parser_stage3_enabled() {
|
||||
if let TokenType::IDENTIFIER(name) = &start_tok {
|
||||
let is_stage3_keyword = matches!(
|
||||
name.as_str(),
|
||||
"local" | "flow" | "try" | "catch" | "cleanup" | "throw" | "while" | "for"
|
||||
| "in"
|
||||
);
|
||||
if is_stage3_keyword {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: start_tok.clone(),
|
||||
expected: "enable NYASH_FEATURES=stage3 for Stage-3 keywords".to_string(),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match &start_tok {
|
||||
TokenType::LBRACE => self.parse_standalone_block_statement(),
|
||||
|
||||
@ -599,6 +599,7 @@ mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
fn temp_path(name: &str) -> PathBuf {
|
||||
let mut p = std::env::temp_dir();
|
||||
@ -606,12 +607,18 @@ mod tests {
|
||||
p
|
||||
}
|
||||
|
||||
fn env_guard() -> &'static Mutex<()> {
|
||||
static GUARD: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
GUARD.get_or_init(|| Mutex::new(()))
|
||||
}
|
||||
|
||||
fn dummy_module() -> MirModule {
|
||||
MirModule::new("test-module".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writes_file_when_dump_path_is_set() {
|
||||
let _lock = env_guard().lock().unwrap();
|
||||
let path = temp_path("mir_dump_path");
|
||||
let _ = fs::remove_file(&path);
|
||||
std::env::set_var("RUST_MIR_DUMP_PATH", path.to_string_lossy().to_string());
|
||||
@ -634,6 +641,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn does_not_write_when_dump_path_is_unset() {
|
||||
let _lock = env_guard().lock().unwrap();
|
||||
std::env::remove_var("RUST_MIR_DUMP_PATH");
|
||||
let path = temp_path("mir_dump_path_unset");
|
||||
let _ = fs::remove_file(&path);
|
||||
|
||||
@ -109,11 +109,12 @@ pub fn upgrade_weak_handle(weak_handle: i64) -> i64 {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::box_trait::StringBox;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn test_weak_handle_marker() {
|
||||
let strong_handle = 0x0000_0000_0000_0001i64;
|
||||
let weak_handle = 0x8000_0000_0000_0001i64;
|
||||
let weak_handle = 0x8000_0000_0000_0001u64 as i64;
|
||||
|
||||
assert!(!is_weak_handle(strong_handle));
|
||||
assert!(is_weak_handle(weak_handle));
|
||||
@ -132,6 +133,8 @@ mod tests {
|
||||
let strong_handle = upgrade_weak_handle(weak_handle);
|
||||
assert!(strong_handle > 0);
|
||||
|
||||
crate::runtime::host_handles::drop_handle(strong_handle as u64);
|
||||
|
||||
// Drop arc
|
||||
drop(arc);
|
||||
|
||||
|
||||
@ -615,15 +615,26 @@ fn joinir_stageb_funcscanner_structure_test() {
|
||||
mir_module.functions.len()
|
||||
);
|
||||
|
||||
// 構造チェック: MIR 関数数が JoinIR 関数数と一致
|
||||
assert_eq!(
|
||||
mir_module.functions.len(),
|
||||
join_module.functions.len(),
|
||||
"MIR function count should match JoinIR function count"
|
||||
// 構造チェック: JoinIR の関数名は MIR に存在すること
|
||||
let mut missing = Vec::new();
|
||||
for join_func in join_module.functions.values() {
|
||||
if !mir_module.functions.contains_key(&join_func.name) {
|
||||
missing.push(join_func.name.clone());
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
missing.is_empty(),
|
||||
"MIR should contain all JoinIR function names, missing={:?}",
|
||||
missing
|
||||
);
|
||||
|
||||
// 各関数の Block 数をチェック
|
||||
for (name, func) in &mir_module.functions {
|
||||
// 各関数の Block 数をチェック(JoinIR の関数に対応するものだけ)
|
||||
for join_func in join_module.functions.values() {
|
||||
let name = &join_func.name;
|
||||
let func = mir_module
|
||||
.functions
|
||||
.get(name)
|
||||
.expect("JoinIR function should exist in MIR");
|
||||
let block_count = func.blocks.len();
|
||||
eprintln!(
|
||||
"[joinir/stageb_funcscanner] Function '{}': {} blocks",
|
||||
|
||||
@ -3,7 +3,12 @@ use nyash_rust::runtime::box_registry::get_global_registry;
|
||||
use nyash_rust::runtime::plugin_loader_v2::{get_global_loader_v2, init_global_loader_v2};
|
||||
use nyash_rust::runtime::PluginConfig;
|
||||
use nyash_rust::{
|
||||
mir::{instruction::MirInstruction, passes::method_id_inject::inject_method_ids, MirCompiler},
|
||||
mir::{
|
||||
definitions::Callee,
|
||||
instruction::MirInstruction,
|
||||
passes::method_id_inject::inject_method_ids,
|
||||
MirCompiler,
|
||||
},
|
||||
parser::NyashParser,
|
||||
};
|
||||
|
||||
@ -45,19 +50,27 @@ f.open("/tmp/test.txt", "r")
|
||||
let mut compiler = MirCompiler::new();
|
||||
let module = compiler.compile(ast).expect("mir compile failed").module;
|
||||
let mut module2 = module.clone();
|
||||
let injected = inject_method_ids(&mut module2);
|
||||
assert!(injected > 0);
|
||||
let _injected = inject_method_ids(&mut module2);
|
||||
for func in module2.functions.values() {
|
||||
for block in func.blocks.values() {
|
||||
for inst in &block.instructions {
|
||||
if let MirInstruction::BoxCall {
|
||||
method, method_id, ..
|
||||
} = inst
|
||||
{
|
||||
if method == "open" {
|
||||
match inst {
|
||||
MirInstruction::Call {
|
||||
callee: Some(Callee::Method { box_name, method, .. }),
|
||||
..
|
||||
} if box_name == "FileBox" && method == "open" => {
|
||||
return;
|
||||
}
|
||||
MirInstruction::BoxCall {
|
||||
method, method_id, ..
|
||||
} if method == "open" => {
|
||||
assert!(method_id.is_some(), "open missing method_id");
|
||||
return;
|
||||
}
|
||||
MirInstruction::PluginInvoke { method, .. } if method == "open" => {
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,11 @@ fn test_if_merge_uses_phi_not_predecessor() {
|
||||
// result
|
||||
let ast = ASTNode::Program {
|
||||
statements: vec![
|
||||
ASTNode::Local {
|
||||
variables: vec!["result".to_string()],
|
||||
initial_values: vec![None],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::If {
|
||||
condition: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Bool(true),
|
||||
@ -188,7 +193,7 @@ fn test_merge_use_before_phi_detected() {
|
||||
fn test_loop_phi_normalization() {
|
||||
// Program:
|
||||
// local i = 0
|
||||
// loop(false) { i = 1 }
|
||||
// loop(i < 1) { i = i + 1 }
|
||||
// i
|
||||
let ast = ASTNode::Program {
|
||||
statements: vec![
|
||||
@ -201,8 +206,16 @@ fn test_loop_phi_normalization() {
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Loop {
|
||||
condition: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Bool(false),
|
||||
condition: Box::new(ASTNode::BinaryOp {
|
||||
operator: nyash_rust::ast::BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
body: vec![ASTNode::Assignment {
|
||||
@ -210,8 +223,16 @@ fn test_loop_phi_normalization() {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: nyash_rust::ast::BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
@ -254,10 +275,18 @@ fn test_loop_phi_normalization() {
|
||||
fn test_loop_nested_if_phi() {
|
||||
// Program:
|
||||
// local x = 0
|
||||
// loop(false) { if true { x = 1 } else { x = 2 } }
|
||||
// loop(i < 1) { if true { x = 1 } else { x = 2 }; i = i + 1 }
|
||||
// x
|
||||
let ast = ASTNode::Program {
|
||||
statements: vec![
|
||||
ASTNode::Local {
|
||||
variables: vec!["i".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(0),
|
||||
span: Span::unknown(),
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Local {
|
||||
variables: vec!["x".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::Literal {
|
||||
@ -267,13 +296,30 @@ fn test_loop_nested_if_phi() {
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Loop {
|
||||
condition: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Bool(false),
|
||||
condition: Box::new(ASTNode::BinaryOp {
|
||||
operator: nyash_rust::ast::BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
body: vec![ASTNode::If {
|
||||
condition: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Bool(true),
|
||||
body: vec![
|
||||
ASTNode::If {
|
||||
condition: Box::new(ASTNode::BinaryOp {
|
||||
operator: nyash_rust::ast::BinaryOperator::Equal,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(0),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
then_body: vec![ASTNode::Assignment {
|
||||
@ -281,8 +327,16 @@ fn test_loop_nested_if_phi() {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: nyash_rust::ast::BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
@ -292,14 +346,42 @@ fn test_loop_nested_if_phi() {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: nyash_rust::ast::BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}]),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
},
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: nyash_rust::ast::BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Variable {
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
fn env_guard() -> &'static Mutex<()> {
|
||||
static GUARD: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
GUARD.get_or_init(|| Mutex::new(()))
|
||||
}
|
||||
|
||||
fn with_stage3_env<F: FnOnce()>(
|
||||
features: Option<&str>,
|
||||
@ -6,24 +12,25 @@ fn with_stage3_env<F: FnOnce()>(
|
||||
hako_stage3: Option<&str>,
|
||||
f: F,
|
||||
) {
|
||||
let _lock = env_guard().lock().unwrap_or_else(|e| e.into_inner());
|
||||
let prev_features = std::env::var("NYASH_FEATURES").ok();
|
||||
let prev_parser_stage3 = std::env::var("NYASH_PARSER_STAGE3").ok();
|
||||
let prev_hako_stage3 = std::env::var("HAKO_PARSER_STAGE3").ok();
|
||||
|
||||
// Phase 73: Unified to NYASH_FEATURES=stage3
|
||||
// parser_stage3 and hako_stage3 now map to NYASH_FEATURES
|
||||
let final_features = if let Some(v) = features {
|
||||
Some(v.to_string())
|
||||
} else if let Some("0") = parser_stage3 {
|
||||
Some("0".to_string()) // Disabled
|
||||
} else if let Some("0") = hako_stage3 {
|
||||
Some("0".to_string()) // Disabled
|
||||
} else {
|
||||
None // Default (stage3 enabled)
|
||||
};
|
||||
|
||||
match final_features {
|
||||
// Legacy aliases (NYASH_PARSER_STAGE3 / HAKO_PARSER_STAGE3) still gate on/off.
|
||||
match features {
|
||||
Some(v) => std::env::set_var("NYASH_FEATURES", v),
|
||||
None => std::env::remove_var("NYASH_FEATURES"),
|
||||
}
|
||||
match parser_stage3 {
|
||||
Some(v) => std::env::set_var("NYASH_PARSER_STAGE3", v),
|
||||
None => std::env::remove_var("NYASH_PARSER_STAGE3"),
|
||||
}
|
||||
match hako_stage3 {
|
||||
Some(v) => std::env::set_var("HAKO_PARSER_STAGE3", v),
|
||||
None => std::env::remove_var("HAKO_PARSER_STAGE3"),
|
||||
}
|
||||
|
||||
f();
|
||||
|
||||
@ -31,6 +38,14 @@ fn with_stage3_env<F: FnOnce()>(
|
||||
Some(v) => std::env::set_var("NYASH_FEATURES", v),
|
||||
None => std::env::remove_var("NYASH_FEATURES"),
|
||||
}
|
||||
match prev_parser_stage3 {
|
||||
Some(v) => std::env::set_var("NYASH_PARSER_STAGE3", v),
|
||||
None => std::env::remove_var("NYASH_PARSER_STAGE3"),
|
||||
}
|
||||
match prev_hako_stage3 {
|
||||
Some(v) => std::env::set_var("HAKO_PARSER_STAGE3", v),
|
||||
None => std::env::remove_var("HAKO_PARSER_STAGE3"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -55,6 +55,7 @@ static box Main {{
|
||||
.arg("--backend")
|
||||
.arg("vm")
|
||||
.arg(&test_file)
|
||||
.env("NYASH_FEATURES", "stage3")
|
||||
.env("NYASH_JOINIR_CORE", "1")
|
||||
.env("NYASH_DISABLE_PLUGINS", "1")
|
||||
.output()
|
||||
|
||||
Reference in New Issue
Block a user