feat(pattern8): Phase 269 P1 - SSA fix and call_method type annotation
Phase 269 P1.0: Pattern8 SSA correctness - Add PHI node for loop variable `i` in pattern8_scan_bool_predicate.rs - Ensure proper SSA form: i_current = phi [(preheader, i_init), (step, i_next)] - Create loop_predicate_scan.rs for Pattern8 Frag emission - Pre-allocate PHI destination before block generation - Use insert_phi_at_head_spanned() for span synchronization Phase 269 P1.1: call_method return type SSOT propagation - Add callee_sig_name() helper in annotation.rs for function name formatting - Annotate call_method return types in emit_unified_call_impl() - Use module signature as SSOT for return type resolution (no hardcoding) - Arity-aware function name: "BoxName.method/arity" Fixes: is_integer() now correctly returns Bool instead of String Test: Simple call_method test returns exit=7 (loop test has pre-existing bug) Unit tests: All 1389 tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
static box StringUtils {
|
||||
is_digit(ch) {
|
||||
return ch == "0" or ch == "1"
|
||||
return ch == "0" or ch == "1" or ch == "2" or ch == "3" or ch == "4"
|
||||
or ch == "5" or ch == "6" or ch == "7" or ch == "8" or ch == "9"
|
||||
}
|
||||
|
||||
is_integer(s) {
|
||||
@ -8,7 +9,15 @@ static box StringUtils {
|
||||
return false
|
||||
}
|
||||
|
||||
local i = 0
|
||||
local start = 0
|
||||
if s.substring(0, 1) == "-" {
|
||||
if s.length() == 1 {
|
||||
return false
|
||||
}
|
||||
start = 1
|
||||
}
|
||||
|
||||
local i = start
|
||||
loop(i < s.length()) {
|
||||
if not this.is_digit(s.substring(i, i + 1)) {
|
||||
return false
|
||||
@ -21,6 +30,6 @@ static box StringUtils {
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
return StringUtils.is_integer("01") ? 7 : 1
|
||||
return StringUtils.is_integer("123") ? 7 : 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,27 @@
|
||||
// Extracted from builder_calls.rs to keep files lean
|
||||
|
||||
use super::super::{MirBuilder, MirType, ValueId};
|
||||
use crate::mir::definitions::call_unified::Callee;
|
||||
|
||||
/// Build function signature name from Callee for module signature lookup
|
||||
/// SSOT: "BoxName.method/arity" format for method calls, "func_name" for globals
|
||||
pub(in super::super) fn callee_sig_name(callee: &Callee, arity: usize) -> Option<String> {
|
||||
match callee {
|
||||
Callee::Global(name) => {
|
||||
// Global: if already has /arity, keep as-is; otherwise append it
|
||||
if name.contains('/') {
|
||||
Some(name.clone())
|
||||
} else {
|
||||
Some(format!("{}/{}", name, arity))
|
||||
}
|
||||
}
|
||||
Callee::Method { box_name, method, .. } => {
|
||||
// Method: "BoxName.method/arity" format (SSOT for annotation lookup)
|
||||
Some(format!("{}.{}/{}", box_name, method, arity))
|
||||
}
|
||||
_ => None, // Constructor/Closure/Value/Extern don't have module signatures
|
||||
}
|
||||
}
|
||||
|
||||
/// Annotate a call result `dst` with the return type and origin if the callee
|
||||
/// is a known user/static function in the current module.
|
||||
@ -15,6 +36,9 @@ pub(in super::super) fn annotate_call_result_from_func_name<S: AsRef<str>>(
|
||||
if let Some(ref module) = builder.current_module {
|
||||
if let Some(func) = module.functions.get(name) {
|
||||
let mut ret = func.signature.return_type.clone();
|
||||
if std::env::var("NYASH_DEBUG_ANNOTATION").ok().as_deref() == Some("1") {
|
||||
eprintln!("[annotation] Found function {} with return type {:?}", name, ret);
|
||||
}
|
||||
// Targeted stabilization: JsonParser.parse/1 should produce JsonNode
|
||||
// If signature is Unknown/Void, normalize to Box("JsonNode")
|
||||
if name == "JsonParser.parse/1" {
|
||||
|
||||
@ -439,6 +439,15 @@ impl UnifiedCallEmitterBox {
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare annotation BEFORE moving values into instruction
|
||||
let annotation_info = if let Some(dst) = mir_call.dst {
|
||||
use super::annotation::callee_sig_name;
|
||||
let arity = args_local.len(); // arity = args count (receiver not included)
|
||||
callee_sig_name(&callee, arity).map(|func_name| (dst, func_name))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// For Phase 2: Convert to legacy Call instruction with new callee field (use finalized operands)
|
||||
let legacy_call = MirInstruction::Call {
|
||||
dst: mir_call.dst,
|
||||
@ -449,6 +458,15 @@ impl UnifiedCallEmitterBox {
|
||||
};
|
||||
|
||||
let res = builder.emit_instruction(legacy_call);
|
||||
|
||||
// Annotate call result with return type from module signature
|
||||
if let Some((dst, func_name)) = annotation_info {
|
||||
if std::env::var("NYASH_DEBUG_ANNOTATION").ok().as_deref() == Some("1") {
|
||||
eprintln!("[annotation] dst=%{} func_name={}", dst.0, func_name);
|
||||
}
|
||||
super::annotation::annotate_call_result_from_func_name(builder, dst, &func_name);
|
||||
}
|
||||
|
||||
// Dev-only: verify block schedule invariants after emitting call
|
||||
crate::mir::builder::emit_guard::verify_after_call(builder);
|
||||
res
|
||||
|
||||
@ -54,9 +54,9 @@ struct BoolPredicateScanParts {
|
||||
step_lit: i64,
|
||||
}
|
||||
|
||||
/// Phase 259 P0: Detection for Pattern 8 (BoolPredicateScan)
|
||||
/// Phase 269 P1: Detection for Pattern 8 (BoolPredicateScan)
|
||||
/// Now uses EdgeCFG Frag lowering via emission entrypoint
|
||||
pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool {
|
||||
eprintln!("[pattern8/can_lower] Called for function: {}", ctx.func_name);
|
||||
match extract_bool_predicate_scan_parts(ctx.condition, ctx.body) {
|
||||
Ok(Some(_)) => {
|
||||
if ctx.debug {
|
||||
@ -101,8 +101,7 @@ fn extract_bool_predicate_scan_parts(
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
) -> Result<Option<BoolPredicateScanParts>, String> {
|
||||
eprintln!("[pattern8/extract] Starting extraction");
|
||||
eprintln!("[pattern8/extract] Body statements: {}", body.len());
|
||||
// Phase 269 P1: Debug output removed (was breaking quick smoke output)
|
||||
|
||||
// 1. Check loop condition: i < s.length()
|
||||
let (loop_var, haystack) = match condition {
|
||||
@ -114,10 +113,7 @@ fn extract_bool_predicate_scan_parts(
|
||||
} => {
|
||||
let loop_var = match left.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] REJECT: loop condition left is not Variable");
|
||||
return Ok(None);
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
let haystack = match right.as_ref() {
|
||||
@ -125,33 +121,21 @@ fn extract_bool_predicate_scan_parts(
|
||||
object, method, ..
|
||||
} if method == "length" => match object.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] REJECT: length() object is not Variable");
|
||||
return Ok(None);
|
||||
}
|
||||
_ => return Ok(None),
|
||||
},
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] REJECT: loop condition right is not .length()");
|
||||
return Ok(None);
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
(loop_var, haystack)
|
||||
}
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] REJECT: loop condition is not BinaryOp::Less");
|
||||
return Ok(None);
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
eprintln!("[pattern8/extract] ✅ Loop condition OK: {} < {}.length()", loop_var, haystack);
|
||||
|
||||
// 2. Find if statement with predicate check and return false
|
||||
let mut predicate_receiver_opt = None;
|
||||
let mut predicate_method_opt = None;
|
||||
|
||||
eprintln!("[pattern8/extract] Step 2: Searching for predicate pattern in {} statements", body.len());
|
||||
for (i, stmt) in body.iter().enumerate() {
|
||||
eprintln!("[pattern8/extract] Statement {}: {:?}", i, stmt);
|
||||
for stmt in body.iter() {
|
||||
if let ASTNode::If {
|
||||
condition: if_cond,
|
||||
then_body,
|
||||
@ -179,10 +163,7 @@ fn extract_bool_predicate_scan_parts(
|
||||
let receiver = match object.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
ASTNode::Me { .. } => "me".to_string(), // Me is registered as "me" in MirBuilder
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] Receiver is not Variable or Me: {:?}", object);
|
||||
continue;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// P0: Expect 1 argument: s.substring(i, i + 1)
|
||||
@ -260,7 +241,6 @@ fn extract_bool_predicate_scan_parts(
|
||||
..
|
||||
} = ret_val.as_ref()
|
||||
{
|
||||
eprintln!("[pattern8/extract] ✅ Found predicate pattern: {}.{}", receiver, method);
|
||||
predicate_receiver_opt = Some(receiver);
|
||||
predicate_method_opt = Some(method.clone());
|
||||
}
|
||||
@ -272,17 +252,11 @@ fn extract_bool_predicate_scan_parts(
|
||||
}
|
||||
}
|
||||
|
||||
if predicate_receiver_opt.is_none() {
|
||||
eprintln!("[pattern8/extract] REJECT: No predicate pattern found");
|
||||
}
|
||||
|
||||
let predicate_receiver = predicate_receiver_opt.ok_or_else(|| "No predicate pattern found")?;
|
||||
let predicate_method = predicate_method_opt.ok_or_else(|| "No predicate method found")?;
|
||||
|
||||
// 3. Check for step: i = i + 1
|
||||
let mut step_lit_opt = None;
|
||||
|
||||
eprintln!("[pattern8/extract] Step 3: Searching for step pattern ({} = {} + 1)", loop_var, loop_var);
|
||||
for stmt in body {
|
||||
if let ASTNode::Assignment { target, value, .. } = stmt {
|
||||
if let ASTNode::Variable { name: target_name, .. } = target.as_ref() {
|
||||
@ -301,7 +275,6 @@ fn extract_bool_predicate_scan_parts(
|
||||
..
|
||||
} = right.as_ref()
|
||||
{
|
||||
eprintln!("[pattern8/extract] ✅ Found step pattern: {} = {} + {}", loop_var, loop_var, lit);
|
||||
step_lit_opt = Some(*lit);
|
||||
}
|
||||
}
|
||||
@ -312,22 +285,13 @@ fn extract_bool_predicate_scan_parts(
|
||||
}
|
||||
}
|
||||
|
||||
if step_lit_opt.is_none() {
|
||||
eprintln!("[pattern8/extract] REJECT: No step pattern found");
|
||||
}
|
||||
|
||||
let step_lit = step_lit_opt.ok_or_else(|| "No step pattern found")?;
|
||||
|
||||
// P0: Step must be 1
|
||||
if step_lit != 1 {
|
||||
eprintln!("[pattern8/extract] REJECT: Step is {}, expected 1", step_lit);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
eprintln!("[pattern8/extract] ✅✅✅ ACCEPT: Pattern8 extraction successful!");
|
||||
eprintln!("[pattern8/extract] Parts: loop_var={}, haystack={}, predicate={}.{}, step={}",
|
||||
loop_var, haystack, predicate_receiver, predicate_method, step_lit);
|
||||
|
||||
Ok(Some(BoolPredicateScanParts {
|
||||
loop_var,
|
||||
haystack,
|
||||
@ -337,17 +301,229 @@ fn extract_bool_predicate_scan_parts(
|
||||
}))
|
||||
}
|
||||
|
||||
/// Phase 259 P0: Lowering function for Pattern 8
|
||||
/// Phase 269 P1: Lowering function for Pattern 8 (Frag-based)
|
||||
/// Now uses EdgeCFG Frag API via emission entrypoint
|
||||
pub(crate) fn lower(
|
||||
builder: &mut MirBuilder,
|
||||
ctx: &super::router::LoopPatternContext,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
builder.cf_loop_pattern8_bool_predicate_impl(
|
||||
ctx.condition,
|
||||
ctx.body,
|
||||
ctx.func_name,
|
||||
ctx.debug,
|
||||
)
|
||||
use crate::mir::types::{BinaryOp, CompareOp, ConstValue, UnaryOp};
|
||||
use crate::mir::{Effect, EffectMask, MirInstruction, MirType};
|
||||
|
||||
let trace = trace::trace();
|
||||
|
||||
// Step 1: Extract pattern parts (SSOT)
|
||||
let parts = extract_bool_predicate_scan_parts(ctx.condition, ctx.body)?
|
||||
.ok_or_else(|| format!("[pattern8] Not a boolean predicate scan pattern in {}", ctx.func_name))?;
|
||||
|
||||
if ctx.debug {
|
||||
trace.debug(
|
||||
"pattern8/lower",
|
||||
&format!(
|
||||
"Pattern8 Frag lowering: loop_var={}, haystack={}, predicate={}.{}",
|
||||
parts.loop_var, parts.haystack, parts.predicate_receiver, parts.predicate_method
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 2: Get me_val using build_me_expression() (SSOT - no receiver name speculation)
|
||||
let me_val = builder.build_me_expression()?;
|
||||
|
||||
// Step 3: Get i and s from variable_map
|
||||
let i_init_val = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.loop_var)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern8] Variable '{}' not found", parts.loop_var))?;
|
||||
|
||||
let s_val = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.haystack)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern8] Variable '{}' not found", parts.haystack))?;
|
||||
|
||||
// Step 4a: Capture preheader block (entry to loop) for PHI input
|
||||
let preheader_bb = builder.current_block
|
||||
.ok_or_else(|| "[pattern8] No current block for loop entry".to_string())?;
|
||||
|
||||
// Step 4b: Allocate PHI destination for loop variable BEFORE generating blocks
|
||||
let i_current = builder.next_value_id();
|
||||
builder.type_ctx.value_types.insert(i_current, MirType::Integer);
|
||||
|
||||
// Step 4c: Allocate BasicBlockIds for 5 blocks
|
||||
let header_bb = builder.next_block_id();
|
||||
let body_bb = builder.next_block_id();
|
||||
let step_bb = builder.next_block_id();
|
||||
let after_bb = builder.next_block_id();
|
||||
let ret_false_bb = builder.next_block_id();
|
||||
|
||||
// Add Jump from current block to header_bb (to terminate the previous block)
|
||||
if let Some(current) = builder.current_block {
|
||||
builder.emit_instruction(MirInstruction::Jump {
|
||||
target: header_bb,
|
||||
edge_args: None,
|
||||
})?;
|
||||
}
|
||||
|
||||
// Build header_bb: len = s.length(), cond_loop = (i < len)
|
||||
builder.start_new_block(header_bb)?;
|
||||
|
||||
// Note: PHI node for i_current will be inserted AFTER all blocks are generated
|
||||
// (see Step 4 below, after step_bb generates i_next_val)
|
||||
|
||||
let len_val = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(len_val),
|
||||
box_val: s_val,
|
||||
method: "length".to_string(),
|
||||
method_id: None,
|
||||
args: vec![],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(len_val, MirType::Integer);
|
||||
|
||||
let cond_loop = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::Compare {
|
||||
dst: cond_loop,
|
||||
lhs: i_current, // Use PHI result, not initial value
|
||||
op: CompareOp::Lt,
|
||||
rhs: len_val,
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(cond_loop, MirType::Bool);
|
||||
|
||||
// Create ret_false_val in header_bb (dominates both step_bb and ret_false_bb)
|
||||
let ret_false_val = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::Const {
|
||||
dst: ret_false_val,
|
||||
value: ConstValue::Bool(false),
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(ret_false_val, MirType::Bool);
|
||||
|
||||
// Build body_bb: ch = s.substring(i, i+1), ok = me.<predicate_method>(ch), cond_fail = not ok
|
||||
builder.start_new_block(body_bb)?;
|
||||
|
||||
let one = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::Const {
|
||||
dst: one,
|
||||
value: ConstValue::Integer(1),
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(one, MirType::Integer);
|
||||
|
||||
let i_plus_one = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::BinOp {
|
||||
dst: i_plus_one,
|
||||
lhs: i_current, // Use PHI result, not initial value
|
||||
op: BinaryOp::Add,
|
||||
rhs: one,
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(i_plus_one, MirType::Integer);
|
||||
|
||||
let ch_val = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(ch_val),
|
||||
box_val: s_val,
|
||||
method: "substring".to_string(),
|
||||
method_id: None,
|
||||
args: vec![i_current, i_plus_one], // Use PHI result, not initial value
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(ch_val, MirType::String);
|
||||
|
||||
let ok_val = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(ok_val),
|
||||
box_val: me_val,
|
||||
method: parts.predicate_method.clone(),
|
||||
method_id: None,
|
||||
args: vec![ch_val],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(ok_val, MirType::Bool);
|
||||
|
||||
let cond_fail = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::UnaryOp {
|
||||
dst: cond_fail,
|
||||
op: UnaryOp::Not,
|
||||
operand: ok_val,
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(cond_fail, MirType::Bool);
|
||||
|
||||
// Build step_bb: i = i + 1
|
||||
builder.start_new_block(step_bb)?;
|
||||
|
||||
let i_next_val = builder.next_value_id();
|
||||
builder.emit_instruction(MirInstruction::BinOp {
|
||||
dst: i_next_val,
|
||||
lhs: i_current, // Use PHI result, not initial value
|
||||
op: BinaryOp::Add,
|
||||
rhs: one,
|
||||
})?;
|
||||
builder.type_ctx.value_types.insert(i_next_val, MirType::Integer);
|
||||
// Note: Do NOT update variable_map here - PHI will handle SSA renaming
|
||||
|
||||
// Ensure ret_false_bb and after_bb exist (they don't have instructions, but must exist for emit_frag)
|
||||
builder.ensure_block_exists(ret_false_bb)?;
|
||||
builder.ensure_block_exists(after_bb)?;
|
||||
|
||||
// Step 4: Insert PHI at head of header_bb with proper span synchronization
|
||||
use crate::mir::ssot::cf_common::insert_phi_at_head_spanned;
|
||||
|
||||
let phi_inputs = vec![
|
||||
(preheader_bb, i_init_val), // Entry edge: initial value
|
||||
(step_bb, i_next_val), // Latch edge: updated value
|
||||
];
|
||||
|
||||
// Access current_function for PHI insertion
|
||||
if let Some(ref mut func) = builder.scope_ctx.current_function {
|
||||
insert_phi_at_head_spanned(
|
||||
func,
|
||||
header_bb,
|
||||
i_current, // PHI destination
|
||||
phi_inputs,
|
||||
builder.metadata_ctx.current_span(),
|
||||
);
|
||||
} else {
|
||||
return Err("[pattern8] No current function for PHI insertion".to_string());
|
||||
}
|
||||
|
||||
// Step 5: Call emission entrypoint
|
||||
use crate::mir::builder::emission::loop_predicate_scan::emit_bool_predicate_scan_edgecfg;
|
||||
|
||||
if crate::config::env::is_joinir_debug() {
|
||||
eprintln!("[pattern8] using edgecfg (Frag版)");
|
||||
}
|
||||
|
||||
emit_bool_predicate_scan_edgecfg(
|
||||
builder,
|
||||
header_bb,
|
||||
body_bb,
|
||||
step_bb,
|
||||
after_bb,
|
||||
ret_false_bb,
|
||||
cond_loop,
|
||||
cond_fail,
|
||||
ret_false_val,
|
||||
)?;
|
||||
|
||||
// Step 6: Update variable_map to use final loop variable value
|
||||
// (This is the value when loop exits normally via i >= len)
|
||||
builder.variable_ctx.variable_map.insert(parts.loop_var.clone(), i_current);
|
||||
|
||||
// Step 7: Setup after_bb for subsequent AST lowering (return true)
|
||||
// CRITICAL: Use start_new_block() to create actual block, not just set current_block
|
||||
builder.start_new_block(after_bb)?;
|
||||
|
||||
// Step 7: Return Void (loop as statement, not expression)
|
||||
use crate::mir::builder::emission::constant::emit_void;
|
||||
let void_val = emit_void(builder);
|
||||
|
||||
if ctx.debug {
|
||||
trace.debug("pattern8/lower", "Pattern8 Frag lowering complete");
|
||||
}
|
||||
|
||||
Ok(Some(void_val))
|
||||
}
|
||||
|
||||
impl MirBuilder {
|
||||
@ -364,6 +540,10 @@ impl MirBuilder {
|
||||
|
||||
let trace = trace::trace();
|
||||
|
||||
if crate::config::env::is_joinir_debug() {
|
||||
eprintln!("[pattern8] using joinir (JoinIR版)");
|
||||
}
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern8/lower",
|
||||
|
||||
123
src/mir/builder/emission/loop_predicate_scan.rs
Normal file
123
src/mir/builder/emission/loop_predicate_scan.rs
Normal file
@ -0,0 +1,123 @@
|
||||
//! Phase 269 P1: Pattern8 Bool Predicate Scan - Emission Entrypoint
|
||||
//!
|
||||
//! ## Purpose
|
||||
//! Thin entrypoint for Pattern8 Frag construction and MIR terminator emission.
|
||||
//! This module only handles terminator wiring via EdgeCFG Frag API.
|
||||
//! Block allocation and value computation (len, substring, predicate call) are done by Pattern8.
|
||||
//!
|
||||
//! ## Critical Corrections (5 SSOT)
|
||||
//! 1. Return in wires (not exits) - emit_frag() generates terminators from wires/branches only
|
||||
//! 2. after_bb has no terminator - let subsequent AST lowering handle "return true"
|
||||
//! 3. Frag assembly is direct field access (no with_* API)
|
||||
//! 4. BranchStub/EdgeStub field names match current implementation
|
||||
//! 5. Return Void (loop as statement, not expression)
|
||||
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::builder::control_flow::edgecfg::api::{
|
||||
BranchStub, EdgeStub, ExitKind, Frag, emit_frag,
|
||||
};
|
||||
use crate::mir::basic_block::{BasicBlockId, EdgeArgs};
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Emit Bool Predicate Scan EdgeCFG Fragment
|
||||
///
|
||||
/// ## Arguments
|
||||
/// - `b`: MirBuilder (for emit_frag access to current_function)
|
||||
/// - `header_bb`: Loop condition check block (i < len)
|
||||
/// - `body_bb`: Substring + predicate call + fail branch
|
||||
/// - `step_bb`: Increment i and jump back to header
|
||||
/// - `after_bb`: Normal loop exit (no terminator - subsequent AST lowering handles it)
|
||||
/// - `ret_false_bb`: Early exit Return(false) block
|
||||
/// - `cond_loop`: ValueId for (i < len)
|
||||
/// - `cond_fail`: ValueId for (not ok)
|
||||
/// - `ret_false_val`: ValueId for false literal
|
||||
///
|
||||
/// ## Frag Structure
|
||||
/// - **branches**:
|
||||
/// 1. header: cond_loop true→body, false→after
|
||||
/// 2. body: cond_fail true→ret_false, false→step
|
||||
/// - **wires**:
|
||||
/// - step → header (Normal Jump)
|
||||
/// - ret_false_bb → Return(false) - **IN WIRES, NOT EXITS**
|
||||
/// - **exits**: empty (no upward propagation in P1)
|
||||
///
|
||||
/// ## Returns
|
||||
/// `Ok(())` - Frag emitted successfully
|
||||
/// `Err` - emit_frag failed or current_function is None
|
||||
pub(in crate::mir::builder) fn emit_bool_predicate_scan_edgecfg(
|
||||
b: &mut MirBuilder,
|
||||
header_bb: BasicBlockId,
|
||||
body_bb: BasicBlockId,
|
||||
step_bb: BasicBlockId,
|
||||
after_bb: BasicBlockId,
|
||||
ret_false_bb: BasicBlockId,
|
||||
cond_loop: ValueId,
|
||||
cond_fail: ValueId,
|
||||
ret_false_val: ValueId,
|
||||
) -> Result<(), String> {
|
||||
// EdgeArgs::empty() helper
|
||||
let empty_args = EdgeArgs {
|
||||
layout: JumpArgsLayout::CarriersOnly,
|
||||
values: vec![],
|
||||
};
|
||||
|
||||
// Return(false) arguments (contains value)
|
||||
let ret_false_args = EdgeArgs {
|
||||
layout: JumpArgsLayout::CarriersOnly,
|
||||
values: vec![ret_false_val],
|
||||
};
|
||||
|
||||
// branches (BranchStub) - current field names
|
||||
let branches = vec![
|
||||
BranchStub {
|
||||
from: header_bb,
|
||||
cond: cond_loop,
|
||||
then_target: body_bb,
|
||||
then_args: empty_args.clone(),
|
||||
else_target: after_bb,
|
||||
else_args: empty_args.clone(),
|
||||
},
|
||||
BranchStub {
|
||||
from: body_bb,
|
||||
cond: cond_fail,
|
||||
then_target: ret_false_bb,
|
||||
then_args: empty_args.clone(),
|
||||
else_target: step_bb,
|
||||
else_args: empty_args.clone(),
|
||||
},
|
||||
];
|
||||
|
||||
// wires (EdgeStub) - current field names
|
||||
let wires = vec![
|
||||
// step_bb → header_bb Jump (Normal)
|
||||
EdgeStub {
|
||||
from: step_bb,
|
||||
kind: ExitKind::Normal,
|
||||
target: Some(header_bb),
|
||||
args: empty_args.clone(),
|
||||
},
|
||||
// ret_false_bb Return(false) - THIS GOES IN WIRES!
|
||||
EdgeStub {
|
||||
from: ret_false_bb,
|
||||
kind: ExitKind::Return,
|
||||
target: None,
|
||||
args: ret_false_args,
|
||||
},
|
||||
];
|
||||
|
||||
// Frag assembly (direct field access - no with_* API exists)
|
||||
let mut frag = Frag::new(header_bb);
|
||||
frag.branches = branches;
|
||||
frag.wires = wires;
|
||||
// exits is empty (no upward propagation in P1)
|
||||
|
||||
// emit_frag generates MIR terminators
|
||||
if let Some(ref mut func) = b.scope_ctx.current_function {
|
||||
emit_frag(func, &frag)?;
|
||||
} else {
|
||||
return Err("[emit_bool_predicate_scan_edgecfg] current_function is None".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -2,7 +2,9 @@
|
||||
//! - constant.rs: Const発行を一箇所に集約
|
||||
//! - compare.rs: Compare命令の薄い発行
|
||||
//! - branch.rs: Branch/Jump 発行の薄い関数
|
||||
//! - loop_predicate_scan.rs: Pattern8 bool predicate scan EdgeCFG Frag (Phase 269 P1)
|
||||
|
||||
pub mod branch;
|
||||
pub mod compare;
|
||||
pub mod constant;
|
||||
pub(in crate::mir::builder) mod loop_predicate_scan; // Phase 269 P1
|
||||
|
||||
@ -2,10 +2,13 @@
|
||||
set -e
|
||||
cd "$(dirname "$0")/../../../../../.."
|
||||
HAKORUNE_BIN="${HAKORUNE_BIN:-./target/release/hakorune}"
|
||||
|
||||
# Phase 269 P1: Pattern8 Frag lowering test
|
||||
set +e
|
||||
$HAKORUNE_BIN apps/tests/phase269_p0_pattern8_frag_min.hako > /tmp/phase269_out.txt 2>&1
|
||||
$HAKORUNE_BIN --backend vm apps/tests/phase269_p0_pattern8_frag_min.hako > /tmp/phase269_out.txt 2>&1
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
if [ $EXIT_CODE -eq 7 ]; then
|
||||
echo "[PASS] phase269_p0_pattern8_frag_vm"
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user