feat(joinir): Phase 259 P0 - Pattern8 BoolPredicateScan + Copy binding fix
Pattern8 (Boolean Predicate Scan) implementation for is_integer/1:
- New pattern detection for `loop + if not predicate() { return false }`
- JoinIR lowerer with main/loop_step/k_exit structure
- Me receiver passed as param (by-name 禁止)
Key fixes:
1. expr_result = Some(join_exit_value) (Pattern7 style)
2. Tail-call: dst: None (no extra Ret instruction)
3. instruction_rewriter: Add `&& is_loop_header_with_phi` check
- Pattern8 has no carriers → no PHIs → MUST generate Copy bindings
- Without this, ValueId(103/104/105) were undefined
Status: Copy instructions now generated correctly, but exit block
creation issue remains (next step: Step A-C in指示書).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
35
apps/tests/phase259_p0_is_integer_min.hako
Normal file
35
apps/tests/phase259_p0_is_integer_min.hako
Normal file
@ -0,0 +1,35 @@
|
||||
static box StringUtils {
|
||||
is_digit(ch) {
|
||||
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) {
|
||||
if s.length() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
return StringUtils.is_integer("123") ? 7 : 1
|
||||
}
|
||||
}
|
||||
@ -22,8 +22,42 @@ Related:
|
||||
|
||||
## Proposed Approach (P0)
|
||||
|
||||
方針: prelude(nested-if)は既存 lowering のまま、loop 部分だけを scan パターンへ寄せる(構造で解決)。
|
||||
**P0 Design Decision: Pattern8(新規)採用**
|
||||
|
||||
P0 の狙い:
|
||||
- loop の core は “scan until mismatch” で、Pattern6(scan)と近い
|
||||
- ただし return 値が `i/-1` ではなく `true/false` なので、scan パターンの “return payload” を一般化する必要がある可能性がある
|
||||
### Why Pattern8?
|
||||
|
||||
Pattern6(index_of系)は "見つける" scan(返り値: 整数 i or -1)で、is_integer は "全部検証する" predicate scan(返り値: 真偽値 true/false)。役割が異なるため、Pattern8 として分離した。
|
||||
|
||||
### Pattern8 vs Pattern6
|
||||
|
||||
| | Pattern6 (index_of系) | Pattern8 (is_integer系) |
|
||||
|---|---|---|
|
||||
| 役割 | "見つける" scan | "全部検証する" predicate scan |
|
||||
| Match形 | `substring(...) == needle` | `not predicate(ch)` → early exit |
|
||||
| 返り値 | Integer (i or -1) | Boolean (true/false) |
|
||||
| Exit PHI | `i`(ループ状態変数) | `ret_bool`(検証結果) |
|
||||
| Carriers | [i] (LoopState) | [] (empty, expr_result のみ) |
|
||||
|
||||
### JoinIR Contract
|
||||
|
||||
- **jump_args_layout**: ExprResultPlusCarriers(carriers=0)
|
||||
- **expr_result**: Some(join_exit_value) - ret_bool from k_exit (pipeline handling)
|
||||
- **exit_bindings**: Empty(carriers なし)
|
||||
- **SSOT**: `join_inputs = entry_func.params.clone()`
|
||||
- **Me receiver**: Passed as param [i, me, s] (by-name 禁止)
|
||||
|
||||
### 受理形(P0固定)
|
||||
|
||||
```nyash
|
||||
loop(i < s.length()) {
|
||||
if not this.is_digit(s.substring(i, i + 1)) {
|
||||
return false
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return true
|
||||
```
|
||||
|
||||
- prelude の start 計算は許可(ただし i_init = start で渡す)
|
||||
- predicate は Me method call(this.is_digit)のみ
|
||||
- step は 1 固定
|
||||
|
||||
@ -645,7 +645,7 @@ pub(super) fn merge_and_rewrite(
|
||||
"[cf_loop/joinir] Phase 33-21: Skip param bindings in header block (PHIs define carriers)"
|
||||
);
|
||||
}
|
||||
} else if is_recursive_call || is_target_loop_entry {
|
||||
} else if (is_recursive_call || is_target_loop_entry) && is_loop_header_with_phi {
|
||||
// Phase 256 P1.10: Skip param bindings for:
|
||||
// - Recursive call (loop_step → loop_step): latch edge
|
||||
// - Entry call (main → loop_step): entry edge
|
||||
@ -653,6 +653,9 @@ pub(super) fn merge_and_rewrite(
|
||||
// Header PHIs receive values from these edges via separate mechanism.
|
||||
// Generating Copies here would cause multiple definitions.
|
||||
//
|
||||
// Phase 259 P0 FIX: Only skip if loop header HAS PHIs!
|
||||
// Pattern8 has no carriers → no PHIs → MUST generate Copy bindings.
|
||||
//
|
||||
// Update remapper mappings for any further instructions.
|
||||
for (i, arg_val_remapped) in args.iter().enumerate() {
|
||||
if i < target_params.len() {
|
||||
|
||||
@ -80,6 +80,7 @@ pub(in crate::mir::builder) mod pattern4_with_continue;
|
||||
pub(in crate::mir::builder) mod pattern5_infinite_early_exit; // Phase 131-11
|
||||
pub(in crate::mir::builder) mod pattern6_scan_with_init; // Phase 254 P0: index_of/find/contains pattern
|
||||
pub(in crate::mir::builder) mod pattern7_split_scan; // Phase 256 P0: split/tokenization with variable step
|
||||
pub(in crate::mir::builder) mod pattern8_scan_bool_predicate; // Phase 259 P0: boolean predicate scan (is_integer/is_valid)
|
||||
pub(in crate::mir::builder) mod pattern_pipeline;
|
||||
pub(in crate::mir::builder) mod router;
|
||||
pub(in crate::mir::builder) mod trim_loop_lowering; // Phase 180: Dedicated Trim/P5 lowering module
|
||||
|
||||
@ -0,0 +1,549 @@
|
||||
//! Pattern 8: Boolean Predicate Scan (is_integer/is_valid form)
|
||||
//!
|
||||
//! Phase 259 P0: Dedicated pattern for boolean predicate validation loops
|
||||
//!
|
||||
//! ## Pattern Structure
|
||||
//!
|
||||
//! ```nyash
|
||||
//! is_integer(s) {
|
||||
//! local i = start // Computed in prelude
|
||||
//! loop(i < s.length()) {
|
||||
//! if not this.is_digit(s.substring(i, i + 1)) {
|
||||
//! return false
|
||||
//! }
|
||||
//! i = i + 1
|
||||
//! }
|
||||
//! return true
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Detection Criteria (P0: Fixed Form Only)
|
||||
//!
|
||||
//! 1. Loop condition: `i < s.length()`
|
||||
//! 2. Loop body has if statement with:
|
||||
//! - Condition: `not this.method(...)` (UnaryOp::Not + MethodCall)
|
||||
//! - Then branch: `return false` (early exit)
|
||||
//! 3. Loop body has step: `i = i + 1`
|
||||
//! 4. Post-loop: `return true`
|
||||
//!
|
||||
//! ## vs Pattern 6
|
||||
//!
|
||||
//! - Pattern 6: Match scan (substring == needle → return i)
|
||||
//! - Pattern 8: Predicate scan (not is_digit → return false, else true)
|
||||
//! - Pattern 6: Returns integer (index or -1)
|
||||
//! - Pattern 8: Returns boolean (true/false)
|
||||
|
||||
use super::super::trace;
|
||||
use super::common::var;
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Phase 259 P0: Extracted structure for boolean predicate scan
|
||||
#[derive(Debug, Clone)]
|
||||
struct BoolPredicateScanParts {
|
||||
/// Loop variable name (e.g., "i")
|
||||
loop_var: String,
|
||||
/// Haystack variable name (e.g., "s")
|
||||
haystack: String,
|
||||
/// Predicate method receiver (e.g., "this")
|
||||
predicate_receiver: String,
|
||||
/// Predicate method name (e.g., "is_digit")
|
||||
predicate_method: String,
|
||||
/// Step literal (P0: must be 1)
|
||||
step_lit: i64,
|
||||
}
|
||||
|
||||
/// Phase 259 P0: Detection for Pattern 8 (BoolPredicateScan)
|
||||
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 {
|
||||
trace::trace().debug(
|
||||
"pattern8/can_lower",
|
||||
"accept: boolean predicate scan pattern extractable",
|
||||
);
|
||||
}
|
||||
true
|
||||
}
|
||||
Ok(None) => {
|
||||
if ctx.debug {
|
||||
trace::trace().debug(
|
||||
"pattern8/can_lower",
|
||||
"reject: not a boolean predicate scan pattern",
|
||||
);
|
||||
}
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
if ctx.debug {
|
||||
trace::trace().debug(
|
||||
"pattern8/can_lower",
|
||||
&format!("reject: extraction error: {}", e),
|
||||
);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 259 P0: Extract boolean predicate scan pattern parts
|
||||
///
|
||||
/// # P0 Restrictions (Fail-Fast)
|
||||
///
|
||||
/// - Loop condition: `i < s.length()` (forward only)
|
||||
/// - If condition: `not this.method(s.substring(i, i + 1))` (UnaryOp::Not)
|
||||
/// - Then branch: `return false` (Literal::Bool(false))
|
||||
/// - Step: `i = i + 1` (step_lit == 1)
|
||||
/// - Post-loop: `return true` (enforced by caller)
|
||||
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());
|
||||
|
||||
// 1. Check loop condition: i < s.length()
|
||||
let (loop_var, haystack) = match condition {
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
let haystack = match right.as_ref() {
|
||||
ASTNode::MethodCall {
|
||||
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);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] REJECT: loop condition right is not .length()");
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
(loop_var, haystack)
|
||||
}
|
||||
_ => {
|
||||
eprintln!("[pattern8/extract] REJECT: loop condition is not BinaryOp::Less");
|
||||
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);
|
||||
if let ASTNode::If {
|
||||
condition: if_cond,
|
||||
then_body,
|
||||
..
|
||||
} = stmt
|
||||
{
|
||||
// Check if condition is: not this.method(...)
|
||||
if let ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand,
|
||||
..
|
||||
} = if_cond.as_ref()
|
||||
{
|
||||
// Operand must be MethodCall
|
||||
if let ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} = operand.as_ref()
|
||||
{
|
||||
// Extract receiver (e.g., "me")
|
||||
// Phase 259 P0: Support both Variable and Me node
|
||||
// IMPORTANT: Me is registered as "me" in variable_map (not "this")
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// P0: Expect 1 argument: s.substring(i, i + 1)
|
||||
if arguments.len() != 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate argument is substring call
|
||||
if let ASTNode::MethodCall {
|
||||
object: substr_obj,
|
||||
method: substr_method,
|
||||
arguments: substr_args,
|
||||
..
|
||||
} = &arguments[0]
|
||||
{
|
||||
if substr_method != "substring" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Object must be haystack
|
||||
if let ASTNode::Variable { name, .. } = substr_obj.as_ref() {
|
||||
if name != &haystack {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Args: (i, i + 1)
|
||||
if substr_args.len() != 2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Arg 0: loop_var
|
||||
match &substr_args[0] {
|
||||
ASTNode::Variable { name, .. } if name == &loop_var => {}
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
// Arg 1: loop_var + 1
|
||||
match &substr_args[1] {
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
// Left: loop_var
|
||||
match left.as_ref() {
|
||||
ASTNode::Variable { name, .. } if name == &loop_var => {}
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
// Right: Literal(1)
|
||||
match right.as_ref() {
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
..
|
||||
} => {}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check then_body contains: return false
|
||||
if then_body.len() == 1 {
|
||||
if let ASTNode::Return { value, .. } = &then_body[0] {
|
||||
if let Some(ret_val) = value {
|
||||
if let ASTNode::Literal {
|
||||
value: LiteralValue::Bool(false),
|
||||
..
|
||||
} = ret_val.as_ref()
|
||||
{
|
||||
eprintln!("[pattern8/extract] ✅ Found predicate pattern: {}.{}", receiver, method);
|
||||
predicate_receiver_opt = Some(receiver);
|
||||
predicate_method_opt = Some(method.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
if target_name == &loop_var {
|
||||
if let ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} = value.as_ref()
|
||||
{
|
||||
if let ASTNode::Variable { name: left_name, .. } = left.as_ref() {
|
||||
if left_name == &loop_var {
|
||||
if let ASTNode::Literal {
|
||||
value: LiteralValue::Integer(lit),
|
||||
..
|
||||
} = right.as_ref()
|
||||
{
|
||||
eprintln!("[pattern8/extract] ✅ Found step pattern: {} = {} + {}", loop_var, loop_var, lit);
|
||||
step_lit_opt = Some(*lit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
predicate_receiver,
|
||||
predicate_method,
|
||||
step_lit,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Phase 259 P0: Lowering function for Pattern 8
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
impl MirBuilder {
|
||||
/// Phase 259 P0: Pattern 8 (BoolPredicateScan) implementation
|
||||
pub(crate) fn cf_loop_pattern8_bool_predicate_impl(
|
||||
&mut self,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
func_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
|
||||
|
||||
let trace = trace::trace();
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern8/lower",
|
||||
&format!("Phase 259 P0: BoolPredicateScan lowering for {}", func_name),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 1: Extract pattern parts
|
||||
let parts = extract_bool_predicate_scan_parts(condition, body)?
|
||||
.ok_or_else(|| format!("[pattern8] Not a boolean predicate scan pattern in {}", func_name))?;
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern8/lower",
|
||||
&format!(
|
||||
"Extracted: loop_var={}, haystack={}, predicate={}.{}, step={}",
|
||||
parts.loop_var, parts.haystack, parts.predicate_receiver, parts.predicate_method, parts.step_lit
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 2: Get host ValueIds for variables
|
||||
eprintln!("[pattern8/lower] variable_map contents:");
|
||||
for (name, vid) in &self.variable_ctx.variable_map {
|
||||
eprintln!("[pattern8/lower] {} -> {:?}", name, vid);
|
||||
}
|
||||
eprintln!("[pattern8/lower] Looking for receiver: {}", parts.predicate_receiver);
|
||||
|
||||
let s_host = self
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.haystack)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern8] Variable {} not found", parts.haystack))?;
|
||||
|
||||
let i_host = self
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.loop_var)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern8] Variable {} not found", parts.loop_var))?;
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern8/lower",
|
||||
&format!("Host ValueIds: s={:?}, i={:?}", s_host, i_host),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 3: Create JoinModule
|
||||
let mut join_value_space = JoinValueSpace::new();
|
||||
use crate::mir::join_ir::lowering::scan_bool_predicate_minimal::lower_scan_bool_predicate_minimal;
|
||||
|
||||
let join_module = lower_scan_bool_predicate_minimal(
|
||||
&mut join_value_space,
|
||||
&parts.predicate_receiver,
|
||||
&parts.predicate_method,
|
||||
);
|
||||
|
||||
// Step 4: Build boundary (SSOT - use entry.params)
|
||||
use super::common::get_entry_function;
|
||||
let main_func = get_entry_function(&join_module, "pattern8")?;
|
||||
|
||||
// SSOT: Use actual params from JoinModule entry
|
||||
let join_inputs = main_func.params.clone();
|
||||
|
||||
// host_inputs in same order: [i, me, s] (alphabetical)
|
||||
// Phase 259 P0: me_host = receiver ValueId
|
||||
// IMPORTANT: Me receiver might not be in variable_map yet, so we use build_me_expression()
|
||||
let me_host = if parts.predicate_receiver == "me" {
|
||||
self.build_me_expression()?
|
||||
} else {
|
||||
self
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.predicate_receiver)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern8] Receiver {} not found", parts.predicate_receiver))?
|
||||
};
|
||||
|
||||
let host_inputs = vec![i_host, me_host, s_host];
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern8/lower",
|
||||
&format!(
|
||||
"Boundary inputs: join_inputs={:?}, host_inputs={:?}",
|
||||
join_inputs, host_inputs
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Verify count consistency
|
||||
if join_inputs.len() != host_inputs.len() {
|
||||
return Err(format!(
|
||||
"[pattern8] Params count mismatch: join_inputs={}, host_inputs={}",
|
||||
join_inputs.len(), host_inputs.len()
|
||||
));
|
||||
}
|
||||
|
||||
// Step 5: Build exit_bindings (no carriers, only expr_result)
|
||||
// Pattern8 returns ret_bool directly (not loop variable)
|
||||
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
|
||||
|
||||
let k_exit_func = join_module.require_function(
|
||||
crate::mir::join_ir::lowering::canonical_names::K_EXIT,
|
||||
"Pattern 8",
|
||||
);
|
||||
let join_exit_value = k_exit_func
|
||||
.params
|
||||
.first()
|
||||
.copied()
|
||||
.expect("k_exit must have parameter for exit value");
|
||||
|
||||
// Phase 259 P0: Allocate host ValueId for return value
|
||||
let result_host = self.next_value_id();
|
||||
self.type_ctx.value_types.insert(result_host, crate::mir::MirType::Bool);
|
||||
|
||||
// Phase 259 P0: exit_bindings contains ret_bool binding (Pattern7 style)
|
||||
// This allows remapper to map join_exit_value → result_host
|
||||
let result_exit_binding = LoopExitBinding {
|
||||
carrier_name: "ret_bool".to_string(), // Logical name for return value
|
||||
join_exit_value,
|
||||
host_slot: result_host,
|
||||
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
|
||||
};
|
||||
|
||||
let exit_bindings = vec![result_exit_binding];
|
||||
|
||||
// Step 6: Build boundary with expr_result
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
|
||||
let carrier_info = CarrierInfo {
|
||||
loop_var_name: parts.loop_var.clone(),
|
||||
loop_var_id: i_host,
|
||||
carriers: vec![], // No carriers - Pattern8 uses expr_result only
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: Vec::new(),
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
promoted_bindings: std::collections::BTreeMap::new(),
|
||||
};
|
||||
|
||||
// Phase 259 P0: expr_result = join_exit_value (Pattern7 style)
|
||||
// Pattern8 returns boolean from k_exit, not loop variable
|
||||
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
.with_inputs(join_inputs, host_inputs)
|
||||
.with_exit_bindings(exit_bindings)
|
||||
.with_carrier_info(carrier_info)
|
||||
.with_expr_result(Some(join_exit_value)) // ✅ CRITICAL: Set expr_result to k_exit param
|
||||
.build();
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern8/lower",
|
||||
"Built boundary with expr_result (ret_bool from k_exit)",
|
||||
);
|
||||
}
|
||||
|
||||
// Step 7: Execute JoinIRConversionPipeline
|
||||
use super::conversion_pipeline::JoinIRConversionPipeline;
|
||||
let result = JoinIRConversionPipeline::execute(
|
||||
self,
|
||||
join_module,
|
||||
Some(&boundary),
|
||||
"pattern8",
|
||||
debug,
|
||||
)?;
|
||||
|
||||
eprintln!("[pattern8/lower] Pipeline execution complete, result: {:?}", result);
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern8/lower",
|
||||
&format!("Pattern 8 complete, result: {:?}", result),
|
||||
);
|
||||
}
|
||||
|
||||
// Pattern8 returns ret_bool (expr_result), not Void
|
||||
eprintln!("[pattern8/lower] Returning result: {:?}", result);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
@ -203,6 +203,11 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
|
||||
detect: super::pattern7_split_scan::can_lower,
|
||||
lower: super::pattern7_split_scan::lower,
|
||||
},
|
||||
LoopPatternEntry {
|
||||
name: "Pattern8_BoolPredicateScan", // Phase 259 P0: boolean predicate scan (is_integer/is_valid)
|
||||
detect: super::pattern8_scan_bool_predicate::can_lower,
|
||||
lower: super::pattern8_scan_bool_predicate::lower,
|
||||
},
|
||||
LoopPatternEntry {
|
||||
name: "Pattern3_WithIfPhi",
|
||||
detect: super::pattern3_with_if_phi::can_lower,
|
||||
|
||||
@ -79,6 +79,7 @@ pub mod simple_while_minimal; // Phase 188-Impl-1: Pattern 1 minimal lowerer
|
||||
pub mod scan_with_init_minimal; // Phase 254 P1: Pattern 6 minimal lowerer (index_of/find/contains)
|
||||
pub mod scan_with_init_reverse; // Phase 257 P0: Pattern 6 reverse scan lowerer (last_index_of)
|
||||
pub mod split_scan_minimal; // Phase 256 P0: Pattern 7 minimal lowerer (split/tokenization with variable step)
|
||||
pub mod scan_bool_predicate_minimal; // Phase 259 P0: Pattern 8 minimal lowerer (is_integer/is_valid boolean predicate scan)
|
||||
pub mod skip_ws;
|
||||
pub mod stage1_using_resolver;
|
||||
pub mod stageb_body;
|
||||
|
||||
252
src/mir/join_ir/lowering/scan_bool_predicate_minimal.rs
Normal file
252
src/mir/join_ir/lowering/scan_bool_predicate_minimal.rs
Normal file
@ -0,0 +1,252 @@
|
||||
//! Phase 259 P0: Pattern 8 (BoolPredicateScan) JoinIR Lowerer
|
||||
//!
|
||||
//! Target: apps/tests/phase259_p0_is_integer_min.hako
|
||||
//!
|
||||
//! Expected JoinIR:
|
||||
//! ```text
|
||||
//! fn main(i, me, s):
|
||||
//! result = loop_step(i, me, s)
|
||||
//! ret result
|
||||
//!
|
||||
//! fn loop_step(i, me, s):
|
||||
//! // 1. Check exit condition: i >= s.length()
|
||||
//! len = StringBox.length(s)
|
||||
//! exit_cond = (i >= len)
|
||||
//! const_true = true
|
||||
//! Jump(k_exit, [const_true], cond=exit_cond) // All passed
|
||||
//!
|
||||
//! // 2. Extract character
|
||||
//! const_1 = 1
|
||||
//! i_plus_1 = i + 1
|
||||
//! ch = StringBox.substring(s, i, i_plus_1)
|
||||
//!
|
||||
//! // 3. Call predicate (Me method → MethodCall with me receiver)
|
||||
//! ok = MethodCall(me, predicate_method, [ch])
|
||||
//!
|
||||
//! // 4. Check predicate result (inverted - if !ok, return false)
|
||||
//! const_false = false
|
||||
//! not_ok = !ok
|
||||
//! Jump(k_exit, [const_false], cond=not_ok) // Predicate failed
|
||||
//!
|
||||
//! // 5. Tail recursion
|
||||
//! tail_result = Call(loop_step, [i_plus_1, me, s])
|
||||
//! Ret(tail_result)
|
||||
//!
|
||||
//! fn k_exit(ret_bool):
|
||||
//! ret ret_bool
|
||||
//! ```
|
||||
|
||||
use crate::mir::join_ir::lowering::canonical_names as cn;
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
use crate::mir::join_ir::{
|
||||
CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, MirType,
|
||||
UnaryOp,
|
||||
};
|
||||
|
||||
/// Lower Pattern 8 (BoolPredicateScan) to JoinIR
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `join_value_space` - Unified ValueId allocator
|
||||
/// * `predicate_receiver` - Receiver name (e.g., "this")
|
||||
/// * `predicate_method` - Method name (e.g., "is_digit")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `JoinModule` - Successfully lowered to JoinIR
|
||||
pub(crate) fn lower_scan_bool_predicate_minimal(
|
||||
join_value_space: &mut JoinValueSpace,
|
||||
predicate_receiver: &str,
|
||||
predicate_method: &str,
|
||||
) -> JoinModule {
|
||||
let mut join_module = JoinModule::new();
|
||||
|
||||
// Function IDs
|
||||
let main_id = JoinFuncId::new(0);
|
||||
let loop_step_id = JoinFuncId::new(1);
|
||||
let k_exit_id = JoinFuncId::new(2);
|
||||
|
||||
// main() params/locals
|
||||
let i_main_param = join_value_space.alloc_param();
|
||||
let me_main_param = join_value_space.alloc_param(); // Phase 259 P0: Me receiver param
|
||||
let s_main_param = join_value_space.alloc_param();
|
||||
let loop_result = join_value_space.alloc_local();
|
||||
|
||||
// loop_step params/locals
|
||||
let i_step_param = join_value_space.alloc_param();
|
||||
let me_step_param = join_value_space.alloc_param(); // Phase 259 P0: Me receiver param
|
||||
let s_step_param = join_value_space.alloc_param();
|
||||
let len = join_value_space.alloc_local();
|
||||
let exit_cond = join_value_space.alloc_local();
|
||||
let const_true = join_value_space.alloc_local();
|
||||
let const_1 = join_value_space.alloc_local();
|
||||
let i_plus_1 = join_value_space.alloc_local();
|
||||
let ch = join_value_space.alloc_local();
|
||||
let ok = join_value_space.alloc_local();
|
||||
let const_false = join_value_space.alloc_local();
|
||||
let not_ok = join_value_space.alloc_local();
|
||||
|
||||
// k_exit params
|
||||
let ret_bool_param = join_value_space.alloc_param();
|
||||
|
||||
// main() function
|
||||
let mut main_func = JoinFunction::new(
|
||||
main_id,
|
||||
"main".to_string(),
|
||||
vec![i_main_param, me_main_param, s_main_param], // Phase 259 P0: +me_param
|
||||
);
|
||||
|
||||
main_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_main_param, me_main_param, s_main_param], // Phase 259 P0: +me_param
|
||||
k_next: None,
|
||||
dst: Some(loop_result),
|
||||
});
|
||||
|
||||
main_func.body.push(JoinInst::Ret {
|
||||
value: Some(loop_result),
|
||||
});
|
||||
|
||||
join_module.add_function(main_func);
|
||||
|
||||
// loop_step(i, me, s) function
|
||||
let mut loop_step_func = JoinFunction::new(
|
||||
loop_step_id,
|
||||
cn::LOOP_STEP.to_string(),
|
||||
vec![i_step_param, me_step_param, s_step_param], // Phase 259 P0: +me_param
|
||||
);
|
||||
|
||||
// 1. len = s.length()
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(len),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "length".to_string(),
|
||||
args: vec![s_step_param],
|
||||
}));
|
||||
|
||||
// 2. exit_cond = (i >= len)
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: exit_cond,
|
||||
op: CompareOp::Ge,
|
||||
lhs: i_step_param,
|
||||
rhs: len,
|
||||
}));
|
||||
|
||||
// 3. const_true = true
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_true,
|
||||
value: ConstValue::Bool(true),
|
||||
}));
|
||||
|
||||
// 4. Jump(k_exit, [true], cond=exit_cond) - all passed
|
||||
loop_step_func.body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![const_true],
|
||||
cond: Some(exit_cond),
|
||||
});
|
||||
|
||||
// 5. const_1 = 1
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_1,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
|
||||
// 6. i_plus_1 = i + 1
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: i_plus_1,
|
||||
op: crate::mir::join_ir::BinOpKind::Add,
|
||||
lhs: i_step_param,
|
||||
rhs: const_1,
|
||||
}));
|
||||
|
||||
// 7. ch = s.substring(i, i_plus_1)
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(ch),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "substring".to_string(),
|
||||
args: vec![s_step_param, i_step_param, i_plus_1],
|
||||
}));
|
||||
|
||||
// 8. ok = me.predicate_method(ch)
|
||||
// P0: predicate_receiver is "this" → use MethodCall with me_step_param
|
||||
// Phase 259 P0: Me receiver passed as param (by-name 禁止)
|
||||
loop_step_func.body.push(JoinInst::MethodCall {
|
||||
dst: ok,
|
||||
receiver: me_step_param, // Me receiver as param
|
||||
method: predicate_method.to_string(),
|
||||
args: vec![ch],
|
||||
type_hint: Some(MirType::Bool),
|
||||
});
|
||||
|
||||
// 9. const_false = false
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_false,
|
||||
value: ConstValue::Bool(false),
|
||||
}));
|
||||
|
||||
// 10. not_ok = !ok (invert predicate for jump condition)
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst: not_ok,
|
||||
op: UnaryOp::Not,
|
||||
operand: ok,
|
||||
}));
|
||||
|
||||
// 11. Jump(k_exit, [false], cond=not_ok) - predicate failed
|
||||
loop_step_func.body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![const_false],
|
||||
cond: Some(not_ok),
|
||||
});
|
||||
|
||||
// 12. Call(loop_step, [i_plus_1, me, s]) - tail recursion
|
||||
// Phase 259 P0: dst: None for tail-call (bridge handles optimization)
|
||||
loop_step_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_plus_1, me_step_param, s_step_param], // Phase 259 P0: +me_param
|
||||
k_next: None,
|
||||
dst: None, // Tail-call: bridge が適切に処理
|
||||
});
|
||||
|
||||
join_module.add_function(loop_step_func);
|
||||
|
||||
// k_exit(ret_bool) function
|
||||
let mut k_exit_func = JoinFunction::new(
|
||||
k_exit_id,
|
||||
cn::K_EXIT.to_string(),
|
||||
vec![ret_bool_param],
|
||||
);
|
||||
|
||||
k_exit_func.body.push(JoinInst::Ret {
|
||||
value: Some(ret_bool_param),
|
||||
});
|
||||
|
||||
join_module.add_function(k_exit_func);
|
||||
|
||||
// Set entry point
|
||||
join_module.entry = Some(main_id);
|
||||
|
||||
eprintln!("[joinir/pattern8] Generated JoinIR for BoolPredicateScan Pattern");
|
||||
eprintln!("[joinir/pattern8] Functions: main, loop_step, k_exit");
|
||||
eprintln!(
|
||||
"[joinir/pattern8] Predicate: {}.{}",
|
||||
predicate_receiver, predicate_method
|
||||
);
|
||||
|
||||
join_module
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd "$(dirname "$0")/../../.."
|
||||
HAKORUNE_BIN="${HAKORUNE_BIN:-./target/release/hakorune}"
|
||||
NYASH_LLVM_USE_HARNESS=1 $HAKORUNE_BIN --backend llvm apps/tests/phase259_p0_is_integer_min.hako > /tmp/phase259_llvm.txt 2>&1
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 7 ]; then
|
||||
echo "[PASS] phase259_p0_is_integer_llvm_exe"
|
||||
exit 0
|
||||
else
|
||||
echo "[FAIL] phase259_p0_is_integer_llvm_exe: expected exit 7, got $EXIT_CODE"
|
||||
cat /tmp/phase259_llvm.txt
|
||||
exit 1
|
||||
fi
|
||||
@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd "$(dirname "$0")/../../.."
|
||||
HAKORUNE_BIN="${HAKORUNE_BIN:-./target/release/hakorune}"
|
||||
$HAKORUNE_BIN apps/tests/phase259_p0_is_integer_min.hako > /tmp/phase259_out.txt 2>&1
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 7 ]; then
|
||||
echo "[PASS] phase259_p0_is_integer_vm"
|
||||
exit 0
|
||||
else
|
||||
echo "[FAIL] phase259_p0_is_integer_vm: expected exit 7, got $EXIT_CODE"
|
||||
cat /tmp/phase259_out.txt
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user