feat(mir/llvm): Phase 273 P0-P1 DomainPlan→CorePlan + LLVM arg fix
Phase 273 P0-P1: Two-layer plan architecture - DomainPlan: Pattern-specific knowledge (ScanWithInit) - CorePlan: Fixed vocabulary (Seq, Loop, If, Effect, Exit) - ValueId references only (String expressions forbidden) - Pipeline: Extractor→Normalizer→Verifier→Lowerer New plan/ module: - mod.rs: Type definitions, SSOT spec - normalizer.rs: DomainPlan→CorePlan + ID allocation - verifier.rs: V1-V6 invariant checks (fail-fast) - lowerer.rs: CorePlan→MIR (pattern-agnostic) LLVM fix (ChatGPT): - function_lower.py: Fix argument reference bug - Phase 258 index_of_string now PASS on LLVM backend 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -104,38 +104,76 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
||||
if func is None:
|
||||
func = ir.Function(builder.module, func_ty, name=name)
|
||||
|
||||
# Map parameters to vmap. Prefer mapping by referenced value-ids that have no
|
||||
# local definition (common in v0 JSON where params appear as lhs/rhs ids).
|
||||
# Map parameters to vmap.
|
||||
#
|
||||
# SSOT: If `func_data["params"]` is present, it defines the ValueId ↔ arg position contract.
|
||||
# Use it first to avoid heuristic mis-mapping (which can silently ignore some parameters).
|
||||
#
|
||||
# Fallback: If params are missing (older JSON / legacy emit), use a heuristic:
|
||||
# - map "used but not defined" ValueIds to args in ascending ValueId order.
|
||||
try:
|
||||
arity = len(func.args)
|
||||
# Collect defined and used ids
|
||||
defs = set()
|
||||
uses = set()
|
||||
for bb in (blocks or []):
|
||||
for ins in (bb.get('instructions') or []):
|
||||
try:
|
||||
dstv = ins.get('dst')
|
||||
if isinstance(dstv, int):
|
||||
defs.add(int(dstv))
|
||||
except Exception:
|
||||
pass
|
||||
for k in ('lhs','rhs','value','cond','box_val'):
|
||||
params_list = func_data.get("params", []) or []
|
||||
|
||||
if (
|
||||
isinstance(params_list, list)
|
||||
and len(params_list) == arity
|
||||
and all(isinstance(v, int) for v in params_list)
|
||||
):
|
||||
for i in range(arity):
|
||||
builder.vmap[int(params_list[i])] = func.args[i]
|
||||
else:
|
||||
# Collect defined and used ids
|
||||
defs = set()
|
||||
uses = set()
|
||||
for bb in (blocks or []):
|
||||
for ins in (bb.get('instructions') or []):
|
||||
try:
|
||||
v = ins.get(k)
|
||||
if isinstance(v, int):
|
||||
uses.add(int(v))
|
||||
dstv = ins.get('dst')
|
||||
if isinstance(dstv, int):
|
||||
defs.add(int(dstv))
|
||||
except Exception:
|
||||
pass
|
||||
cand = [vid for vid in uses if vid not in defs]
|
||||
cand.sort()
|
||||
mapped = 0
|
||||
for i in range(min(arity, len(cand))):
|
||||
builder.vmap[int(cand[i])] = func.args[i]
|
||||
mapped += 1
|
||||
# Fallback: also map positional 0..arity-1 to args if not already mapped
|
||||
for i in range(arity):
|
||||
if i not in builder.vmap:
|
||||
builder.vmap[i] = func.args[i]
|
||||
|
||||
for k in ('lhs', 'rhs', 'value', 'cond', 'box_val', 'box', 'src'):
|
||||
try:
|
||||
v = ins.get(k)
|
||||
if isinstance(v, int):
|
||||
uses.add(int(v))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# List operands
|
||||
try:
|
||||
a = ins.get('args')
|
||||
if isinstance(a, list):
|
||||
for v in a:
|
||||
if isinstance(v, int):
|
||||
uses.add(int(v))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Unified calls: mir_call.args
|
||||
try:
|
||||
mc = ins.get('mir_call')
|
||||
if isinstance(mc, dict):
|
||||
a = mc.get('args')
|
||||
if isinstance(a, list):
|
||||
for v in a:
|
||||
if isinstance(v, int):
|
||||
uses.add(int(v))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
cand = [vid for vid in uses if vid not in defs]
|
||||
cand.sort()
|
||||
for i in range(min(arity, len(cand))):
|
||||
builder.vmap[int(cand[i])] = func.args[i]
|
||||
|
||||
# Legacy fallback: map positional 0..arity-1 only when params are missing.
|
||||
for i in range(arity):
|
||||
if i not in builder.vmap:
|
||||
builder.vmap[i] = func.args[i]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@ -39,7 +39,13 @@ use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Phase 257 P0: Scan direction for forward/reverse scan
|
||||
// Phase 273 P1: Import DomainPlan types (Plan renamed to DomainPlan)
|
||||
use crate::mir::builder::control_flow::plan::{DomainPlan, ScanDirection as PlanScanDirection, ScanWithInitPlan};
|
||||
|
||||
// Phase 273 P0.1: Local ScanDirection and ScanParts removed (migrated to plan/mod.rs)
|
||||
// Only keeping internal helper enum for legacy extract_scan_with_init_parts()
|
||||
|
||||
/// Phase 273 P0.1: Internal scan direction for legacy helper (temporary)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ScanDirection {
|
||||
/// Forward scan: i < s.length(), i = i + 1
|
||||
@ -48,76 +54,57 @@ enum ScanDirection {
|
||||
Reverse,
|
||||
}
|
||||
|
||||
/// Phase 254 P1: Extracted structure for scan-with-init pattern
|
||||
///
|
||||
/// This structure contains all the information needed to lower an index_of-style loop.
|
||||
/// Phase 273 P0.1: Internal structure for legacy helper (temporary)
|
||||
#[derive(Debug, Clone)]
|
||||
struct ScanParts {
|
||||
/// Loop variable name (e.g., "i")
|
||||
loop_var: String,
|
||||
/// Haystack variable name (e.g., "s")
|
||||
haystack: String,
|
||||
/// Needle variable name (e.g., "ch")
|
||||
needle: String,
|
||||
/// Step literal (Phase 257: can be 1 forward or -1 reverse)
|
||||
step_lit: i64,
|
||||
/// Early return expression (P0: must be Variable(loop_var))
|
||||
early_return_expr: ASTNode,
|
||||
/// Not-found return literal (P0: must be -1)
|
||||
not_found_return_lit: i64,
|
||||
/// Scan direction (Phase 257 P0)
|
||||
scan_direction: ScanDirection,
|
||||
/// Phase 258 P0: True if dynamic needle (substr.length()), false if fixed (ch)
|
||||
dynamic_needle: bool,
|
||||
}
|
||||
|
||||
/// Phase 254 P0: Detection for Pattern 6 (ScanWithInit)
|
||||
/// Phase 273 P1: Pure extractor that returns DomainPlan (SSOT)
|
||||
///
|
||||
/// Detects index_of/find/contains pattern:
|
||||
/// - Loop with `i < x.length()` or `i < len` condition
|
||||
/// - If with MethodCall in condition and early return
|
||||
/// - ConstStep `i = i + 1`
|
||||
/// - Post-loop return
|
||||
/// This is the new entry point for Pattern6 extraction.
|
||||
/// Router calls this directly, then passes to Normalizer + Verifier + Lowerer.
|
||||
///
|
||||
/// Detection is structure-based only (no function name checks).
|
||||
pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool {
|
||||
// Phase 257 P1.1: SSOT between detect and extract
|
||||
// Call extract to check if pattern is actually extractable
|
||||
// This prevents false positives where detection is too broad
|
||||
match extract_scan_with_init_parts(ctx.condition, ctx.body, None) {
|
||||
Ok(Some(_)) => {
|
||||
// Pattern is extractable
|
||||
if ctx.debug {
|
||||
trace::trace().debug(
|
||||
"pattern6/can_lower",
|
||||
"accept: pattern extractable (SSOT verified)",
|
||||
);
|
||||
}
|
||||
true
|
||||
}
|
||||
Ok(None) => {
|
||||
// Not this pattern (fall through to other patterns)
|
||||
if ctx.debug {
|
||||
trace::trace().debug(
|
||||
"pattern6/can_lower",
|
||||
"reject: pattern not extractable (Ok(None))",
|
||||
);
|
||||
}
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
// Extraction error (log in debug mode, fall through)
|
||||
if ctx.debug {
|
||||
trace::trace().debug(
|
||||
"pattern6/can_lower",
|
||||
&format!("reject: extraction error: {}", e),
|
||||
);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(Some(DomainPlan::ScanWithInit(...)))` - Successfully extracted the pattern
|
||||
/// * `Ok(None)` - Not a scan-with-init pattern (try next pattern)
|
||||
/// * `Err(String)` - Internal consistency error
|
||||
pub(crate) fn extract_scan_with_init_plan(
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
fn_body: Option<&[ASTNode]>,
|
||||
) -> Result<Option<DomainPlan>, String> {
|
||||
// Call internal extraction helper
|
||||
let parts = extract_scan_with_init_parts(condition, body, fn_body)?;
|
||||
|
||||
// Wrap in DomainPlan if extracted successfully
|
||||
Ok(parts.map(|p| {
|
||||
DomainPlan::ScanWithInit(ScanWithInitPlan {
|
||||
loop_var: p.loop_var,
|
||||
haystack: p.haystack,
|
||||
needle: p.needle,
|
||||
step_lit: p.step_lit,
|
||||
early_return_expr: p.early_return_expr,
|
||||
not_found_return_lit: p.not_found_return_lit,
|
||||
scan_direction: match p.scan_direction {
|
||||
ScanDirection::Forward => PlanScanDirection::Forward,
|
||||
ScanDirection::Reverse => PlanScanDirection::Reverse,
|
||||
},
|
||||
dynamic_needle: p.dynamic_needle,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
// Phase 273 P0.1: can_lower() removed (router now calls extract_scan_with_init_plan() directly)
|
||||
|
||||
/// Check if AST node contains MethodCall
|
||||
fn contains_methodcall(node: &ASTNode) -> bool {
|
||||
match node {
|
||||
@ -511,292 +498,10 @@ fn extract_scan_with_init_parts(
|
||||
}))
|
||||
}
|
||||
|
||||
/// Phase 254 P0: Lowering function for Pattern 6
|
||||
///
|
||||
/// Delegates to MirBuilder implementation.
|
||||
pub(crate) fn lower(
|
||||
builder: &mut MirBuilder,
|
||||
ctx: &super::router::LoopPatternContext,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
builder.cf_loop_pattern6_scan_with_init_impl(
|
||||
ctx.condition,
|
||||
ctx.body,
|
||||
ctx.func_name,
|
||||
ctx.debug,
|
||||
ctx.fn_body,
|
||||
)
|
||||
}
|
||||
// Phase 273 P0.1: lower() removed (router now uses PlanLowerer::lower_scan_with_init())
|
||||
|
||||
impl MirBuilder {
|
||||
/// Phase 272 P0.1: Pattern 6 (ScanWithInit) Frag-based implementation
|
||||
///
|
||||
/// Lowers index_of-style loops using EdgeCFG Frag construction (replacing JoinIRConversionPipeline).
|
||||
///
|
||||
/// # P0 Scope
|
||||
/// - Forward scan only (step = 1)
|
||||
/// - Reverse scan / dynamic needle → fallback to Ok(None)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `condition` - Loop condition AST
|
||||
/// * `body` - Loop body statements
|
||||
/// * `func_name` - Function name for debugging
|
||||
/// * `debug` - Enable debug output
|
||||
/// * `fn_body` - Optional function body for capture analysis
|
||||
pub(crate) fn cf_loop_pattern6_scan_with_init_impl(
|
||||
&mut self,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
func_name: &str,
|
||||
debug: bool,
|
||||
fn_body: Option<&[ASTNode]>,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::mir::{BinaryOp, CompareOp, ConstValue, EffectMask, Effect, MirInstruction, MirType};
|
||||
use crate::mir::ssot::cf_common::insert_phi_at_head_spanned;
|
||||
|
||||
let trace = trace::trace();
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern6/lower",
|
||||
&format!("Phase 272 P0.1: ScanWithInit Frag lowering for {}", func_name),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 1: Extract pattern parts
|
||||
let parts = extract_scan_with_init_parts(condition, body, fn_body)?
|
||||
.ok_or_else(|| format!("[pattern6] Not a scan-with-init pattern in {}", func_name))?;
|
||||
|
||||
// P0 Scope: Forward scan (step=1) only
|
||||
if parts.step_lit != 1 {
|
||||
// Reverse scan / dynamic needle: Pattern6 not applicable
|
||||
// Return Ok(None) to let other patterns/generic lowering handle this
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern6/lower",
|
||||
&format!("P0 fallback: step_lit={} (not forward scan)", parts.step_lit),
|
||||
);
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if parts.dynamic_needle {
|
||||
// Dynamic needle not supported in P0
|
||||
if debug {
|
||||
trace.debug("pattern6/lower", "P0 fallback: dynamic_needle=true");
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern6/lower",
|
||||
&format!(
|
||||
"Extracted: loop_var={}, haystack={}, needle={}, step={}, not_found={}",
|
||||
parts.loop_var, parts.haystack, parts.needle, parts.step_lit, parts.not_found_return_lit
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 2: Get host ValueIds for variables
|
||||
let s_host = self
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.haystack)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern6] Variable {} not found", parts.haystack))?;
|
||||
|
||||
let needle_host = self
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.needle)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern6] Variable {} not found", parts.needle))?;
|
||||
|
||||
// Step 3: Get initial loop variable value from variable_map (Pattern8 方式)
|
||||
let i_init_val = self
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.loop_var)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[pattern6] Loop variable {} not found", parts.loop_var))?;
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern6/lower",
|
||||
&format!(
|
||||
"Host ValueIds: s={:?}, needle={:?}, i_init={:?}",
|
||||
s_host, needle_host, i_init_val
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 4a: Capture preheader block (entry to loop) for PHI input
|
||||
let preheader_bb = self.current_block
|
||||
.ok_or_else(|| "[pattern6] No current block for loop entry".to_string())?;
|
||||
|
||||
// Step 4b: Allocate PHI destination for loop variable BEFORE generating blocks
|
||||
let i_current = self.next_value_id();
|
||||
self.type_ctx.value_types.insert(i_current, MirType::Integer);
|
||||
|
||||
// Step 4c: Allocate BasicBlockIds for 5 blocks
|
||||
let header_bb = self.next_block_id();
|
||||
let body_bb = self.next_block_id();
|
||||
let step_bb = self.next_block_id();
|
||||
let after_bb = self.next_block_id();
|
||||
let ret_found_bb = self.next_block_id();
|
||||
|
||||
// Add Jump from current block to header_bb (to terminate the previous block)
|
||||
if let Some(_current) = self.current_block {
|
||||
self.emit_instruction(MirInstruction::Jump {
|
||||
target: header_bb,
|
||||
edge_args: None,
|
||||
})?;
|
||||
}
|
||||
|
||||
// Build header_bb: len = s.length(), cond_loop = (i < len)
|
||||
self.start_new_block(header_bb)?;
|
||||
|
||||
// Note: PHI node for i_current will be inserted AFTER all blocks are generated
|
||||
// (see Step 7 below, after step_bb generates i_next_val)
|
||||
|
||||
let len_val = self.next_value_id();
|
||||
self.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(len_val),
|
||||
box_val: s_host,
|
||||
method: "length".to_string(),
|
||||
method_id: None,
|
||||
args: vec![],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
self.type_ctx.value_types.insert(len_val, MirType::Integer);
|
||||
|
||||
let cond_loop = self.next_value_id();
|
||||
self.emit_instruction(MirInstruction::Compare {
|
||||
dst: cond_loop,
|
||||
lhs: i_current, // Use PHI result, not initial value
|
||||
op: CompareOp::Lt,
|
||||
rhs: len_val,
|
||||
})?;
|
||||
self.type_ctx.value_types.insert(cond_loop, MirType::Bool);
|
||||
|
||||
// Build body_bb: ch = s.substring(i, i+1), cond_match = (ch == needle)
|
||||
self.start_new_block(body_bb)?;
|
||||
|
||||
let one_val = self.next_value_id();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: one_val,
|
||||
value: ConstValue::Integer(1),
|
||||
})?;
|
||||
self.type_ctx.value_types.insert(one_val, MirType::Integer);
|
||||
|
||||
let i_plus_one = self.next_value_id();
|
||||
self.emit_instruction(MirInstruction::BinOp {
|
||||
dst: i_plus_one,
|
||||
lhs: i_current, // Use PHI result, not initial value
|
||||
op: BinaryOp::Add,
|
||||
rhs: one_val,
|
||||
})?;
|
||||
self.type_ctx.value_types.insert(i_plus_one, MirType::Integer);
|
||||
|
||||
let ch_val = self.next_value_id();
|
||||
self.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(ch_val),
|
||||
box_val: s_host,
|
||||
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),
|
||||
})?;
|
||||
self.type_ctx.value_types.insert(ch_val, MirType::String);
|
||||
|
||||
let cond_match = self.next_value_id();
|
||||
self.emit_instruction(MirInstruction::Compare {
|
||||
dst: cond_match,
|
||||
lhs: ch_val,
|
||||
op: CompareOp::Eq,
|
||||
rhs: needle_host,
|
||||
})?;
|
||||
self.type_ctx.value_types.insert(cond_match, MirType::Bool);
|
||||
|
||||
// Build step_bb: i_next = i + 1
|
||||
self.start_new_block(step_bb)?;
|
||||
|
||||
let i_next_val = self.next_value_id();
|
||||
self.emit_instruction(MirInstruction::BinOp {
|
||||
dst: i_next_val,
|
||||
lhs: i_current, // Use PHI result, not initial value
|
||||
op: BinaryOp::Add,
|
||||
rhs: one_val, // Reuse one_val from body_bb
|
||||
})?;
|
||||
self.type_ctx.value_types.insert(i_next_val, MirType::Integer);
|
||||
// Note: Do NOT update variable_map here - PHI will handle SSA renaming
|
||||
|
||||
// Ensure ret_found_bb and after_bb exist (they don't have instructions, but must exist for emit_frag)
|
||||
self.ensure_block_exists(ret_found_bb)?;
|
||||
self.ensure_block_exists(after_bb)?;
|
||||
|
||||
// Step 7: Insert PHI at head of header_bb - Phase 272 P0.2 Refactoring: use emission/phi.rs
|
||||
use crate::mir::builder::emission::phi::insert_loop_phi;
|
||||
|
||||
insert_loop_phi(
|
||||
self,
|
||||
header_bb,
|
||||
i_current,
|
||||
vec![
|
||||
(preheader_bb, i_init_val), // Entry edge: initial value
|
||||
(step_bb, i_next_val), // Latch edge: updated value
|
||||
],
|
||||
"pattern6/header_phi",
|
||||
)?;
|
||||
|
||||
if debug {
|
||||
trace.debug("pattern6/lower", "PHI inserted at header_bb");
|
||||
}
|
||||
|
||||
// Step 8: Call emission entrypoint
|
||||
use crate::mir::builder::emission::loop_scan_with_init::emit_scan_with_init_edgecfg;
|
||||
|
||||
emit_scan_with_init_edgecfg(
|
||||
self,
|
||||
header_bb,
|
||||
body_bb,
|
||||
step_bb,
|
||||
after_bb,
|
||||
ret_found_bb,
|
||||
cond_loop,
|
||||
cond_match,
|
||||
i_current, // Return value for found case
|
||||
)?;
|
||||
|
||||
if debug {
|
||||
trace.debug("pattern6/lower", "Frag emitted successfully");
|
||||
}
|
||||
|
||||
// Step 9: Update variable_map to use final loop variable value
|
||||
// (This is the value when loop exits normally via i >= len)
|
||||
self.variable_ctx.variable_map.insert(parts.loop_var.clone(), i_current);
|
||||
|
||||
// Step 10: Setup after_bb for subsequent AST lowering (return -1)
|
||||
// CRITICAL: Use start_new_block() to create actual block, not just set current_block
|
||||
self.start_new_block(after_bb)?;
|
||||
|
||||
// Step 11: Return Void (pattern applied successfully)
|
||||
use crate::mir::builder::emission::constant::emit_void;
|
||||
let void_val = emit_void(self);
|
||||
|
||||
if debug {
|
||||
trace.debug(
|
||||
"pattern6/lower",
|
||||
&format!("Pattern 6 Frag complete, returning Void {:?}", void_val),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Some(void_val))
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 255 P2: var() function removed - now using super::common::var
|
||||
// Phase 273 P0.1: cf_loop_pattern6_scan_with_init_impl() removed (migrated to plan/lowerer.rs)
|
||||
// The implementation is now in PlanLowerer::lower_scan_with_init()
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@ -25,6 +25,11 @@ use crate::mir::ValueId;
|
||||
|
||||
use crate::mir::loop_pattern_detection::{LoopFeatures, LoopPatternKind};
|
||||
|
||||
// Phase 273 P1: Import Plan components (DomainPlan → Normalizer → Verifier → Lowerer)
|
||||
use crate::mir::builder::control_flow::plan::lowerer::PlanLowerer;
|
||||
use crate::mir::builder::control_flow::plan::normalizer::PlanNormalizer;
|
||||
use crate::mir::builder::control_flow::plan::verifier::PlanVerifier;
|
||||
|
||||
/// Phase 193: Import AST Feature Extractor Box
|
||||
/// (declared in mod.rs as pub module, import from parent)
|
||||
use super::ast_feature_extractor as ast_features;
|
||||
@ -229,11 +234,8 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
|
||||
detect: super::pattern4_with_continue::can_lower,
|
||||
lower: super::pattern4_with_continue::lower,
|
||||
},
|
||||
LoopPatternEntry {
|
||||
name: "Pattern6_ScanWithInit", // Phase 254 P0: index_of/find/contains pattern (before P3)
|
||||
detect: super::pattern6_scan_with_init::can_lower,
|
||||
lower: super::pattern6_scan_with_init::lower,
|
||||
},
|
||||
// Phase 273 P0.1: Pattern6 entry removed (migrated to Plan-based routing)
|
||||
// Pattern6_ScanWithInit now handled via extract_scan_with_init_plan() + PlanLowerer
|
||||
LoopPatternEntry {
|
||||
name: "Pattern7_SplitScan", // Phase 256 P0: split/tokenization with variable step (before P3)
|
||||
detect: super::pattern7_split_scan::can_lower,
|
||||
@ -278,17 +280,57 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
|
||||
/// - Pattern detection: `ctx.pattern_kind` (from `loop_pattern_detection::classify`)
|
||||
/// - No redundant pattern detection in detect functions
|
||||
/// - All patterns use structure-based classification
|
||||
///
|
||||
/// # Phase 273 P1: DomainPlan → Normalizer → Verifier → Lowerer
|
||||
///
|
||||
/// Pattern6 uses the new two-layer Plan architecture:
|
||||
/// - extract_scan_with_init_plan() → DomainPlan (pure extraction)
|
||||
/// - PlanNormalizer::normalize() → CorePlan (pattern knowledge expansion)
|
||||
/// - PlanVerifier::verify() → fail-fast validation
|
||||
/// - PlanLowerer::lower() → MIR emission (pattern-agnostic)
|
||||
pub(crate) fn route_loop_pattern(
|
||||
builder: &mut MirBuilder,
|
||||
ctx: &LoopPatternContext,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use super::super::trace;
|
||||
|
||||
// Phase 273 P1: Try Plan-based Pattern6 first (before table iteration)
|
||||
// Flow: Extract → Normalize → Verify → Lower
|
||||
match super::pattern6_scan_with_init::extract_scan_with_init_plan(
|
||||
ctx.condition,
|
||||
ctx.body,
|
||||
ctx.fn_body,
|
||||
)? {
|
||||
Some(domain_plan) => {
|
||||
// DomainPlan extracted successfully
|
||||
trace::trace().pattern("route", "Pattern6_ScanWithInit (DomainPlan)", true);
|
||||
|
||||
// Step 1: Normalize DomainPlan → CorePlan
|
||||
let core_plan = PlanNormalizer::normalize(builder, domain_plan, ctx)?;
|
||||
|
||||
// Step 2: Verify CorePlan invariants (fail-fast)
|
||||
PlanVerifier::verify(&core_plan)?;
|
||||
|
||||
// Step 3: Lower CorePlan → MIR
|
||||
return PlanLowerer::lower(builder, core_plan, ctx);
|
||||
}
|
||||
None => {
|
||||
// Not Pattern6 - continue to other patterns
|
||||
if ctx.debug {
|
||||
trace::trace().debug(
|
||||
"route",
|
||||
"Pattern6 Plan extraction returned None, trying other patterns",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 183: Route based on pre-classified pattern kind
|
||||
// Pattern kind was already determined by ctx.pattern_kind in LoopPatternContext::new()
|
||||
// This eliminates duplicate detection logic across routers.
|
||||
|
||||
// Find matching pattern entry based on pattern_kind
|
||||
// Phase 273 P0.1: Pattern6 skip logic removed (entry no longer in LOOP_PATTERNS)
|
||||
for entry in LOOP_PATTERNS {
|
||||
if (entry.detect)(builder, ctx) {
|
||||
// Phase 195: Use unified trace for pattern matching
|
||||
|
||||
@ -60,6 +60,9 @@ pub(in crate::mir::builder) mod normalization;
|
||||
// Phase 264: EdgeCFG Fragment API (入口SSOT)
|
||||
pub(in crate::mir::builder) mod edgecfg;
|
||||
|
||||
// Phase 273 P0: Plan Extractor (Pure) + PlanLowerer SSOT
|
||||
pub(in crate::mir::builder) mod plan;
|
||||
|
||||
// Phase 140-P4-A: Re-export for loop_canonicalizer SSOT (crate-wide visibility)
|
||||
pub(crate) use joinir::detect_skip_whitespace_pattern;
|
||||
|
||||
|
||||
250
src/mir/builder/control_flow/plan/lowerer.rs
Normal file
250
src/mir/builder/control_flow/plan/lowerer.rs
Normal file
@ -0,0 +1,250 @@
|
||||
//! Phase 273 P1: PlanLowerer - CorePlan → MIR 生成 (SSOT)
|
||||
//!
|
||||
//! # Responsibilities
|
||||
//!
|
||||
//! - Receive CorePlan from PlanNormalizer
|
||||
//! - Emit MIR instructions using pre-allocated ValueIds
|
||||
//! - No pattern-specific knowledge (pattern-agnostic)
|
||||
//!
|
||||
//! # Key Design Decision
|
||||
//!
|
||||
//! Lowerer processes CorePlan ONLY. It does not know about scan, split, or
|
||||
//! any other pattern-specific semantics. All pattern knowledge is in Normalizer.
|
||||
|
||||
use super::{CoreEffectPlan, CoreLoopPlan, CorePlan};
|
||||
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::{Effect, EffectMask, MirInstruction, ValueId};
|
||||
|
||||
/// Phase 273 P1: PlanLowerer - CorePlan → MIR 生成 (SSOT)
|
||||
pub(in crate::mir::builder) struct PlanLowerer;
|
||||
|
||||
impl PlanLowerer {
|
||||
/// CorePlan を受け取り、MIR を生成
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder` - MIR builder (mutable access for instruction emission)
|
||||
/// * `plan` - CorePlan from Normalizer (pre-allocated ValueIds)
|
||||
/// * `ctx` - Loop pattern context for debug/func_name
|
||||
pub(in crate::mir::builder) fn lower(
|
||||
builder: &mut MirBuilder,
|
||||
plan: CorePlan,
|
||||
ctx: &LoopPatternContext,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
match plan {
|
||||
CorePlan::Seq(plans) => Self::lower_seq(builder, plans, ctx),
|
||||
CorePlan::Loop(loop_plan) => Self::lower_loop(builder, loop_plan, ctx),
|
||||
CorePlan::If(_if_plan) => {
|
||||
// P1: If is handled inline in Loop body
|
||||
Err("[lowerer] Standalone CorePlan::If not yet supported".to_string())
|
||||
}
|
||||
CorePlan::Effect(effect) => {
|
||||
Self::emit_effect(builder, &effect)?;
|
||||
Ok(None)
|
||||
}
|
||||
CorePlan::Exit(_exit) => {
|
||||
// P1: Exit is handled by edge CFG in Loop
|
||||
Err("[lowerer] Standalone CorePlan::Exit not yet supported".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Seq: process plans in order
|
||||
fn lower_seq(
|
||||
builder: &mut MirBuilder,
|
||||
plans: Vec<CorePlan>,
|
||||
ctx: &LoopPatternContext,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
let mut result = None;
|
||||
for plan in plans {
|
||||
result = Self::lower(builder, plan, ctx)?;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Loop: emit blocks, effects, PHI, and edge CFG
|
||||
///
|
||||
/// This is pattern-agnostic. All pattern knowledge is in Normalizer.
|
||||
fn lower_loop(
|
||||
builder: &mut MirBuilder,
|
||||
loop_plan: CoreLoopPlan,
|
||||
ctx: &LoopPatternContext,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::mir::builder::control_flow::joinir::trace;
|
||||
|
||||
let trace_logger = trace::trace();
|
||||
let debug = ctx.debug;
|
||||
|
||||
if debug {
|
||||
trace_logger.debug(
|
||||
"lowerer/loop",
|
||||
&format!(
|
||||
"Phase 273 P1: Lowering CoreLoopPlan for {}",
|
||||
ctx.func_name
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 1: Emit Jump from preheader (current block) to header
|
||||
if builder.current_block.is_some() {
|
||||
builder.emit_instruction(MirInstruction::Jump {
|
||||
target: loop_plan.header_bb,
|
||||
edge_args: None,
|
||||
})?;
|
||||
}
|
||||
|
||||
// Step 2: Start header block and emit header effects
|
||||
builder.start_new_block(loop_plan.header_bb)?;
|
||||
|
||||
for effect in &loop_plan.header_effects {
|
||||
Self::emit_effect(builder, effect)?;
|
||||
}
|
||||
|
||||
if debug {
|
||||
trace_logger.debug(
|
||||
"lowerer/loop",
|
||||
&format!("Header effects emitted: {} effects", loop_plan.header_effects.len()),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 3: Start body block and emit body plans
|
||||
builder.start_new_block(loop_plan.body_bb)?;
|
||||
|
||||
for plan in loop_plan.body {
|
||||
match plan {
|
||||
CorePlan::Effect(effect) => {
|
||||
Self::emit_effect(builder, &effect)?;
|
||||
}
|
||||
_ => {
|
||||
// P1: Only Effect plans in body for now
|
||||
return Err("[lowerer] Non-Effect plan in Loop body not yet supported".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if debug {
|
||||
trace_logger.debug("lowerer/loop", "Body plans emitted");
|
||||
}
|
||||
|
||||
// Step 4: Start step block and emit step effects
|
||||
builder.start_new_block(loop_plan.step_bb)?;
|
||||
|
||||
for effect in &loop_plan.step_effects {
|
||||
Self::emit_effect(builder, effect)?;
|
||||
}
|
||||
|
||||
if debug {
|
||||
trace_logger.debug(
|
||||
"lowerer/loop",
|
||||
&format!("Step effects emitted: {} effects", loop_plan.step_effects.len()),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 5: Ensure found_bb and after_bb exist
|
||||
builder.ensure_block_exists(loop_plan.found_bb)?;
|
||||
builder.ensure_block_exists(loop_plan.after_bb)?;
|
||||
|
||||
// Step 6: Insert PHI at header for each carrier
|
||||
use crate::mir::builder::emission::phi::insert_loop_phi;
|
||||
|
||||
for carrier in &loop_plan.carriers {
|
||||
insert_loop_phi(
|
||||
builder,
|
||||
loop_plan.header_bb,
|
||||
carrier.phi_dst,
|
||||
vec![
|
||||
(loop_plan.preheader_bb, carrier.init_value),
|
||||
(loop_plan.step_bb, carrier.step_value),
|
||||
],
|
||||
"lowerer/loop_phi",
|
||||
)?;
|
||||
}
|
||||
|
||||
if debug {
|
||||
trace_logger.debug("lowerer/loop", "PHI inserted at header_bb");
|
||||
}
|
||||
|
||||
// Step 7: Emit edge CFG (terminators)
|
||||
use crate::mir::builder::emission::loop_scan_with_init::emit_scan_with_init_edgecfg;
|
||||
|
||||
emit_scan_with_init_edgecfg(
|
||||
builder,
|
||||
loop_plan.header_bb,
|
||||
loop_plan.body_bb,
|
||||
loop_plan.step_bb,
|
||||
loop_plan.after_bb,
|
||||
loop_plan.found_bb,
|
||||
loop_plan.cond_loop,
|
||||
loop_plan.cond_match,
|
||||
loop_plan.carriers.first().map(|c| c.phi_dst).unwrap_or(ValueId(0)),
|
||||
)?;
|
||||
|
||||
if debug {
|
||||
trace_logger.debug("lowerer/loop", "Edge CFG emitted");
|
||||
}
|
||||
|
||||
// Step 8: Update variable_map for carriers
|
||||
for carrier in &loop_plan.carriers {
|
||||
builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.insert(carrier.name.clone(), carrier.phi_dst);
|
||||
}
|
||||
|
||||
// Step 9: Setup after_bb for subsequent AST lowering
|
||||
builder.start_new_block(loop_plan.after_bb)?;
|
||||
|
||||
// Step 10: Return Void (pattern applied successfully)
|
||||
use crate::mir::builder::emission::constant::emit_void;
|
||||
let void_val = emit_void(builder);
|
||||
|
||||
if debug {
|
||||
trace_logger.debug(
|
||||
"lowerer/loop",
|
||||
&format!("Loop complete, returning Void {:?}", void_val),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Some(void_val))
|
||||
}
|
||||
|
||||
/// Emit a single CoreEffectPlan as MirInstruction
|
||||
fn emit_effect(builder: &mut MirBuilder, effect: &CoreEffectPlan) -> Result<(), String> {
|
||||
match effect {
|
||||
CoreEffectPlan::Const { dst, value } => {
|
||||
builder.emit_instruction(MirInstruction::Const {
|
||||
dst: *dst,
|
||||
value: value.clone(),
|
||||
})?;
|
||||
}
|
||||
CoreEffectPlan::MethodCall { dst, object, method, args } => {
|
||||
builder.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(*dst),
|
||||
box_val: *object,
|
||||
method: method.clone(),
|
||||
method_id: None,
|
||||
args: args.clone(),
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
}
|
||||
CoreEffectPlan::BinOp { dst, lhs, op, rhs } => {
|
||||
builder.emit_instruction(MirInstruction::BinOp {
|
||||
dst: *dst,
|
||||
lhs: *lhs,
|
||||
op: *op,
|
||||
rhs: *rhs,
|
||||
})?;
|
||||
}
|
||||
CoreEffectPlan::Compare { dst, lhs, op, rhs } => {
|
||||
builder.emit_instruction(MirInstruction::Compare {
|
||||
dst: *dst,
|
||||
lhs: *lhs,
|
||||
op: *op,
|
||||
rhs: *rhs,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
230
src/mir/builder/control_flow/plan/mod.rs
Normal file
230
src/mir/builder/control_flow/plan/mod.rs
Normal file
@ -0,0 +1,230 @@
|
||||
//! Phase 273 P1: DomainPlan/CorePlan 二層構造 + PlanNormalizer + PlanVerifier
|
||||
//!
|
||||
//! This module provides a two-layer Plan architecture for loop pattern lowering:
|
||||
//!
|
||||
//! # Architecture
|
||||
//!
|
||||
//! ```text
|
||||
//! DomainPlan (Pattern固有)
|
||||
//! ↓ PlanNormalizer (SSOT)
|
||||
//! CorePlan (固定語彙 - 構造ノードのみ)
|
||||
//! ↓ PlanLowerer
|
||||
//! MIR (block/value/phi)
|
||||
//! ```
|
||||
//!
|
||||
//! - **DomainPlan**: Pattern-specific plans (ScanWithInit etc.)
|
||||
//! - **PlanNormalizer**: DomainPlan → CorePlan conversion (SSOT, scan knowledge here)
|
||||
//! - **CorePlan**: Fixed vocabulary, expressions as ValueId references (no String parsing)
|
||||
//! - **PlanVerifier**: Fail-fast validation for CorePlan invariants
|
||||
//! - **PlanLowerer**: Processes CorePlan only (no string interpretation)
|
||||
//!
|
||||
//! # Key Design Decision (String式禁止)
|
||||
//!
|
||||
//! CorePlan expressions use **ValueId references only** (String expressions forbidden).
|
||||
//! This prevents "second language processor" from growing inside Lowerer.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::{BasicBlockId, BinaryOp, CompareOp, ConstValue, ValueId};
|
||||
|
||||
pub(in crate::mir::builder) mod lowerer;
|
||||
pub(in crate::mir::builder) mod normalizer;
|
||||
pub(in crate::mir::builder) mod verifier;
|
||||
|
||||
// ============================================================================
|
||||
// DomainPlan (Pattern固有)
|
||||
// ============================================================================
|
||||
|
||||
/// Phase 273 P1: DomainPlan - Pattern-specific plan vocabulary
|
||||
///
|
||||
/// DomainPlan contains pattern-specific knowledge (e.g., scan semantics).
|
||||
/// Normalizer converts DomainPlan → CorePlan with ValueId generation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) enum DomainPlan {
|
||||
/// Pattern6: index_of / find scan
|
||||
ScanWithInit(ScanWithInitPlan),
|
||||
// P2+: Split(SplitPlan), BoolPredicate(BoolPredicatePlan), etc.
|
||||
}
|
||||
|
||||
/// Phase 273 P0: Scan direction for forward/reverse scan
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(in crate::mir::builder) enum ScanDirection {
|
||||
/// Forward scan: i < s.length(), i = i + 1
|
||||
Forward,
|
||||
/// Reverse scan: i >= 0, i = i - 1
|
||||
Reverse,
|
||||
}
|
||||
|
||||
/// Phase 273 P0: Extracted structure for scan-with-init pattern
|
||||
///
|
||||
/// This structure contains all the information needed to lower an index_of-style loop.
|
||||
/// Moved from pattern6_scan_with_init.rs for centralization.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) struct ScanWithInitPlan {
|
||||
/// Loop variable name (e.g., "i")
|
||||
pub loop_var: String,
|
||||
/// Haystack variable name (e.g., "s")
|
||||
pub haystack: String,
|
||||
/// Needle variable name (e.g., "ch")
|
||||
pub needle: String,
|
||||
/// Step literal (Phase 257: can be 1 forward or -1 reverse)
|
||||
pub step_lit: i64,
|
||||
/// Early return expression (P0: must be Variable(loop_var))
|
||||
pub early_return_expr: ASTNode,
|
||||
/// Not-found return literal (P0: must be -1)
|
||||
pub not_found_return_lit: i64,
|
||||
/// Scan direction (Phase 257 P0)
|
||||
pub scan_direction: ScanDirection,
|
||||
/// Phase 258 P0: True if dynamic needle (substr.length()), false if fixed (ch)
|
||||
pub dynamic_needle: bool,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CorePlan (固定語彙 - 構造ノードのみ)
|
||||
// ============================================================================
|
||||
|
||||
/// Phase 273 P1: CorePlan - Fixed vocabulary plan (structure nodes only)
|
||||
///
|
||||
/// CorePlan expressions use **ValueId references only** (no String parsing).
|
||||
/// This prevents "second language processor" from growing inside Lowerer.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) enum CorePlan {
|
||||
/// Sequence: execute plans in order
|
||||
Seq(Vec<CorePlan>),
|
||||
|
||||
/// Loop with carriers (PHI variables)
|
||||
Loop(CoreLoopPlan),
|
||||
|
||||
/// Conditional branching
|
||||
If(CoreIfPlan),
|
||||
|
||||
/// Side effect (already lowered to ValueId)
|
||||
Effect(CoreEffectPlan),
|
||||
|
||||
/// Control flow exit (Return/Break/Continue)
|
||||
Exit(CoreExitPlan),
|
||||
}
|
||||
|
||||
/// Phase 273 P1: Loop plan with carriers
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) struct CoreLoopPlan {
|
||||
// === Block IDs (pre-allocated by Normalizer) ===
|
||||
|
||||
/// Preheader block (entry to loop)
|
||||
pub preheader_bb: BasicBlockId,
|
||||
|
||||
/// Header block (loop condition check)
|
||||
pub header_bb: BasicBlockId,
|
||||
|
||||
/// Body block (loop body start)
|
||||
pub body_bb: BasicBlockId,
|
||||
|
||||
/// Step block (increment and back-edge)
|
||||
pub step_bb: BasicBlockId,
|
||||
|
||||
/// After block (loop exit)
|
||||
pub after_bb: BasicBlockId,
|
||||
|
||||
/// Found block (early exit on match)
|
||||
pub found_bb: BasicBlockId,
|
||||
|
||||
// === Effects per block (emitted by Lowerer) ===
|
||||
|
||||
/// Header effects (emitted in header_bb before branch)
|
||||
pub header_effects: Vec<CoreEffectPlan>,
|
||||
|
||||
/// Body plans (emitted in body_bb, can contain If/Exit)
|
||||
pub body: Vec<CorePlan>,
|
||||
|
||||
/// Step effects (emitted in step_bb before back-edge)
|
||||
pub step_effects: Vec<CoreEffectPlan>,
|
||||
|
||||
// === Loop control ===
|
||||
|
||||
/// Loop carriers (PHI variables)
|
||||
pub carriers: Vec<CoreCarrierInfo>,
|
||||
|
||||
/// Loop condition (for header→body/after branch)
|
||||
pub cond_loop: ValueId,
|
||||
|
||||
/// Match condition (for body→found/step branch)
|
||||
pub cond_match: ValueId,
|
||||
|
||||
/// Loop variable name (for variable_map update after loop)
|
||||
pub loop_var_name: String,
|
||||
}
|
||||
|
||||
/// Phase 273 P1: Loop carrier (PHI variable)
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) struct CoreCarrierInfo {
|
||||
/// Variable name (for variable_map update)
|
||||
pub name: String,
|
||||
|
||||
/// Initial value (from preheader)
|
||||
pub init_value: ValueId,
|
||||
|
||||
/// Step value (from step block, back-edge)
|
||||
pub step_value: ValueId,
|
||||
|
||||
/// PHI destination (loop variable inside loop)
|
||||
pub phi_dst: ValueId,
|
||||
}
|
||||
|
||||
/// Phase 273 P1: Conditional plan
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) struct CoreIfPlan {
|
||||
/// Condition (ValueId reference, not String!)
|
||||
pub condition: ValueId,
|
||||
|
||||
/// Then branch plans
|
||||
pub then_plans: Vec<CorePlan>,
|
||||
|
||||
/// Else branch plans (optional)
|
||||
pub else_plans: Option<Vec<CorePlan>>,
|
||||
}
|
||||
|
||||
/// Phase 273 P1: Effect plan (side effects already lowered to ValueId)
|
||||
///
|
||||
/// Effect vocabulary is minimal (scan-specific variants forbidden):
|
||||
/// - MethodCall, BinOp, Compare, Const only
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) enum CoreEffectPlan {
|
||||
/// Method call (args are ValueIds, not Strings!)
|
||||
MethodCall {
|
||||
dst: ValueId,
|
||||
object: ValueId,
|
||||
method: String, // Method name only (OK as String)
|
||||
args: Vec<ValueId>,
|
||||
},
|
||||
|
||||
/// Binary operation
|
||||
BinOp {
|
||||
dst: ValueId,
|
||||
lhs: ValueId,
|
||||
op: BinaryOp,
|
||||
rhs: ValueId,
|
||||
},
|
||||
|
||||
/// Comparison
|
||||
Compare {
|
||||
dst: ValueId,
|
||||
lhs: ValueId,
|
||||
op: CompareOp,
|
||||
rhs: ValueId,
|
||||
},
|
||||
|
||||
/// Constant
|
||||
Const { dst: ValueId, value: ConstValue },
|
||||
}
|
||||
|
||||
/// Phase 273 P1: Exit plan (control flow exit)
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) enum CoreExitPlan {
|
||||
/// Return with optional value
|
||||
Return(Option<ValueId>),
|
||||
|
||||
/// Break from loop
|
||||
Break,
|
||||
|
||||
/// Continue to next iteration
|
||||
Continue,
|
||||
}
|
||||
290
src/mir/builder/control_flow/plan/normalizer.rs
Normal file
290
src/mir/builder/control_flow/plan/normalizer.rs
Normal file
@ -0,0 +1,290 @@
|
||||
//! Phase 273 P1: PlanNormalizer - DomainPlan → CorePlan 変換 (SSOT)
|
||||
//!
|
||||
//! # Responsibilities
|
||||
//!
|
||||
//! - Convert DomainPlan to CorePlan (SSOT for pattern-specific knowledge)
|
||||
//! - Generate ValueIds for CorePlan expressions
|
||||
//! - Expand pattern-specific operations into generic CoreEffectPlan
|
||||
//!
|
||||
//! # Key Design Decision
|
||||
//!
|
||||
//! Normalizer is the ONLY place that knows pattern-specific semantics.
|
||||
//! Lowerer processes CorePlan without any pattern knowledge.
|
||||
|
||||
use super::{
|
||||
CoreCarrierInfo, CoreEffectPlan, CoreLoopPlan, CorePlan, DomainPlan, ScanWithInitPlan,
|
||||
};
|
||||
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::{BinaryOp, CompareOp, ConstValue, MirType};
|
||||
|
||||
/// Phase 273 P1: PlanNormalizer - DomainPlan → CorePlan 変換 (SSOT)
|
||||
pub(in crate::mir::builder) struct PlanNormalizer;
|
||||
|
||||
impl PlanNormalizer {
|
||||
/// Normalize DomainPlan to CorePlan
|
||||
///
|
||||
/// This is the SSOT for pattern-specific knowledge expansion.
|
||||
/// All pattern semantics (scan, split, etc.) are expanded here.
|
||||
pub(in crate::mir::builder) fn normalize(
|
||||
builder: &mut MirBuilder,
|
||||
domain: DomainPlan,
|
||||
ctx: &LoopPatternContext,
|
||||
) -> Result<CorePlan, String> {
|
||||
match domain {
|
||||
DomainPlan::ScanWithInit(parts) => Self::normalize_scan_with_init(builder, parts, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
/// ScanWithInit → CorePlan 変換
|
||||
///
|
||||
/// Expands scan-specific semantics into generic CorePlan:
|
||||
/// - header_effects: one=1, needle_len, len, bound, cond_loop
|
||||
/// - body: i+needle_len, substring, cond_match
|
||||
/// - step_effects: i_next = i + 1
|
||||
fn normalize_scan_with_init(
|
||||
builder: &mut MirBuilder,
|
||||
parts: ScanWithInitPlan,
|
||||
ctx: &LoopPatternContext,
|
||||
) -> Result<CorePlan, String> {
|
||||
use crate::mir::builder::control_flow::joinir::trace;
|
||||
|
||||
let trace_logger = trace::trace();
|
||||
let debug = ctx.debug;
|
||||
|
||||
if debug {
|
||||
trace_logger.debug(
|
||||
"normalizer/scan_with_init",
|
||||
&format!(
|
||||
"Phase 273 P1: Normalizing ScanWithInit for {}",
|
||||
ctx.func_name
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// P1 Scope: Forward scan (step=1) only
|
||||
if parts.step_lit != 1 {
|
||||
return Err(format!(
|
||||
"[normalizer] P1 scope: only forward scan supported (step={})",
|
||||
parts.step_lit
|
||||
));
|
||||
}
|
||||
|
||||
// Step 1: Get host ValueIds for variables
|
||||
let s_host = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.haystack)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.haystack))?;
|
||||
|
||||
let needle_host = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.needle)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.needle))?;
|
||||
|
||||
let i_init_val = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.loop_var)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?;
|
||||
|
||||
// Step 2: Capture preheader block (entry to loop) for PHI input
|
||||
let preheader_bb = builder
|
||||
.current_block
|
||||
.ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?;
|
||||
|
||||
// Step 3: 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 found_bb = builder.next_block_id();
|
||||
|
||||
// Step 4: Allocate ValueIds for CorePlan
|
||||
let i_current = builder.next_value_id(); // PHI destination
|
||||
builder
|
||||
.type_ctx
|
||||
.value_types
|
||||
.insert(i_current, MirType::Integer);
|
||||
|
||||
let one_val = builder.next_value_id();
|
||||
builder
|
||||
.type_ctx
|
||||
.value_types
|
||||
.insert(one_val, MirType::Integer);
|
||||
|
||||
let needle_len_val = if parts.dynamic_needle {
|
||||
let v = builder.next_value_id();
|
||||
builder.type_ctx.value_types.insert(v, MirType::Integer);
|
||||
v
|
||||
} else {
|
||||
one_val // reuse one_val for fixed needle
|
||||
};
|
||||
|
||||
let len_val = builder.next_value_id();
|
||||
builder
|
||||
.type_ctx
|
||||
.value_types
|
||||
.insert(len_val, MirType::Integer);
|
||||
|
||||
let bound_val = builder.next_value_id();
|
||||
builder
|
||||
.type_ctx
|
||||
.value_types
|
||||
.insert(bound_val, MirType::Integer);
|
||||
|
||||
let cond_loop = builder.next_value_id();
|
||||
builder
|
||||
.type_ctx
|
||||
.value_types
|
||||
.insert(cond_loop, MirType::Bool);
|
||||
|
||||
let i_plus_needle_len = builder.next_value_id();
|
||||
builder
|
||||
.type_ctx
|
||||
.value_types
|
||||
.insert(i_plus_needle_len, MirType::Integer);
|
||||
|
||||
let window_val = builder.next_value_id();
|
||||
builder
|
||||
.type_ctx
|
||||
.value_types
|
||||
.insert(window_val, MirType::String);
|
||||
|
||||
let cond_match = builder.next_value_id();
|
||||
builder
|
||||
.type_ctx
|
||||
.value_types
|
||||
.insert(cond_match, MirType::Bool);
|
||||
|
||||
let i_next_val = builder.next_value_id();
|
||||
builder
|
||||
.type_ctx
|
||||
.value_types
|
||||
.insert(i_next_val, MirType::Integer);
|
||||
|
||||
if debug {
|
||||
trace_logger.debug(
|
||||
"normalizer/scan_with_init",
|
||||
&format!(
|
||||
"Allocated: preheader={:?}, header={:?}, body={:?}, step={:?}, after={:?}, found={:?}",
|
||||
preheader_bb, header_bb, body_bb, step_bb, after_bb, found_bb
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 5: Build header_effects (emitted in header_bb)
|
||||
let mut header_effects = vec![
|
||||
// one = 1
|
||||
CoreEffectPlan::Const {
|
||||
dst: one_val,
|
||||
value: ConstValue::Integer(1),
|
||||
},
|
||||
];
|
||||
|
||||
// needle_len = needle.length() if dynamic, else reuse one_val
|
||||
if parts.dynamic_needle {
|
||||
header_effects.push(CoreEffectPlan::MethodCall {
|
||||
dst: needle_len_val,
|
||||
object: needle_host,
|
||||
method: "length".to_string(),
|
||||
args: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
// len = s.length()
|
||||
header_effects.push(CoreEffectPlan::MethodCall {
|
||||
dst: len_val,
|
||||
object: s_host,
|
||||
method: "length".to_string(),
|
||||
args: vec![],
|
||||
});
|
||||
|
||||
// bound = len - needle_len
|
||||
header_effects.push(CoreEffectPlan::BinOp {
|
||||
dst: bound_val,
|
||||
lhs: len_val,
|
||||
op: BinaryOp::Sub,
|
||||
rhs: needle_len_val,
|
||||
});
|
||||
|
||||
// cond_loop = i <= bound
|
||||
header_effects.push(CoreEffectPlan::Compare {
|
||||
dst: cond_loop,
|
||||
lhs: i_current,
|
||||
op: CompareOp::Le,
|
||||
rhs: bound_val,
|
||||
});
|
||||
|
||||
// Step 6: Build body (emitted in body_bb)
|
||||
let body = vec![
|
||||
// i_plus_needle_len = i + needle_len
|
||||
CorePlan::Effect(CoreEffectPlan::BinOp {
|
||||
dst: i_plus_needle_len,
|
||||
lhs: i_current,
|
||||
op: BinaryOp::Add,
|
||||
rhs: needle_len_val,
|
||||
}),
|
||||
// window = s.substring(i, i + needle_len)
|
||||
CorePlan::Effect(CoreEffectPlan::MethodCall {
|
||||
dst: window_val,
|
||||
object: s_host,
|
||||
method: "substring".to_string(),
|
||||
args: vec![i_current, i_plus_needle_len],
|
||||
}),
|
||||
// cond_match = window == needle
|
||||
CorePlan::Effect(CoreEffectPlan::Compare {
|
||||
dst: cond_match,
|
||||
lhs: window_val,
|
||||
op: CompareOp::Eq,
|
||||
rhs: needle_host,
|
||||
}),
|
||||
];
|
||||
|
||||
// Step 7: Build step_effects (emitted in step_bb)
|
||||
let step_effects = vec![
|
||||
// i_next = i + 1
|
||||
CoreEffectPlan::BinOp {
|
||||
dst: i_next_val,
|
||||
lhs: i_current,
|
||||
op: BinaryOp::Add,
|
||||
rhs: one_val,
|
||||
},
|
||||
];
|
||||
|
||||
// Step 8: Build CoreLoopPlan
|
||||
let loop_plan = CoreLoopPlan {
|
||||
preheader_bb,
|
||||
header_bb,
|
||||
body_bb,
|
||||
step_bb,
|
||||
after_bb,
|
||||
found_bb,
|
||||
header_effects,
|
||||
body,
|
||||
step_effects,
|
||||
carriers: vec![CoreCarrierInfo {
|
||||
name: parts.loop_var.clone(),
|
||||
init_value: i_init_val,
|
||||
step_value: i_next_val,
|
||||
phi_dst: i_current,
|
||||
}],
|
||||
cond_loop,
|
||||
cond_match,
|
||||
loop_var_name: parts.loop_var,
|
||||
};
|
||||
|
||||
if debug {
|
||||
trace_logger.debug(
|
||||
"normalizer/scan_with_init",
|
||||
"CorePlan construction complete",
|
||||
);
|
||||
}
|
||||
|
||||
Ok(CorePlan::Loop(loop_plan))
|
||||
}
|
||||
}
|
||||
245
src/mir/builder/control_flow/plan/verifier.rs
Normal file
245
src/mir/builder/control_flow/plan/verifier.rs
Normal file
@ -0,0 +1,245 @@
|
||||
//! Phase 273 P1: PlanVerifier - CorePlan 不変条件検証 (fail-fast)
|
||||
//!
|
||||
//! # Responsibilities
|
||||
//!
|
||||
//! - Validate CorePlan invariants before lowering
|
||||
//! - Fail fast on close-but-unsupported patterns
|
||||
//! - Prevent silent miscompilation
|
||||
//!
|
||||
//! # Invariants (V1-V6)
|
||||
//!
|
||||
//! - V1: Carrier completeness (name/init_value/step_value present)
|
||||
//! - V2: Condition validity (valid ValueId)
|
||||
//! - V3: Exit validity (Return in function, Break/Continue in loop)
|
||||
//! - V4: Seq non-empty
|
||||
//! - V5: If completeness (then_plans non-empty)
|
||||
//! - V6: ValueId validity (all ValueIds pre-generated)
|
||||
|
||||
use super::{CoreCarrierInfo, CoreEffectPlan, CoreExitPlan, CoreIfPlan, CoreLoopPlan, CorePlan};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Phase 273 P1: PlanVerifier - CorePlan 不変条件検証 (fail-fast)
|
||||
pub(in crate::mir::builder) struct PlanVerifier;
|
||||
|
||||
impl PlanVerifier {
|
||||
/// Verify CorePlan invariants
|
||||
///
|
||||
/// Returns Ok(()) if all invariants hold, Err with details otherwise.
|
||||
pub(in crate::mir::builder) fn verify(plan: &CorePlan) -> Result<(), String> {
|
||||
Self::verify_plan(plan, 0, false)
|
||||
}
|
||||
|
||||
fn verify_plan(plan: &CorePlan, depth: usize, in_loop: bool) -> Result<(), String> {
|
||||
match plan {
|
||||
CorePlan::Seq(plans) => Self::verify_seq(plans, depth, in_loop),
|
||||
CorePlan::Loop(loop_plan) => Self::verify_loop(loop_plan, depth),
|
||||
CorePlan::If(if_plan) => Self::verify_if(if_plan, depth, in_loop),
|
||||
CorePlan::Effect(effect) => Self::verify_effect(effect, depth),
|
||||
CorePlan::Exit(exit) => Self::verify_exit(exit, depth, in_loop),
|
||||
}
|
||||
}
|
||||
|
||||
/// V4: Seq non-empty
|
||||
fn verify_seq(plans: &[CorePlan], depth: usize, in_loop: bool) -> Result<(), String> {
|
||||
if plans.is_empty() {
|
||||
return Err(format!(
|
||||
"[V4] Empty Seq at depth {} (Seq must have at least one plan)",
|
||||
depth
|
||||
));
|
||||
}
|
||||
|
||||
for (i, plan) in plans.iter().enumerate() {
|
||||
Self::verify_plan(plan, depth + 1, in_loop).map_err(|e| {
|
||||
format!("[Seq[{}]] {}", i, e)
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// V1: Carrier completeness, V2: Condition validity
|
||||
fn verify_loop(loop_plan: &CoreLoopPlan, depth: usize) -> Result<(), String> {
|
||||
// V1: Carrier completeness
|
||||
for (i, carrier) in loop_plan.carriers.iter().enumerate() {
|
||||
Self::verify_carrier(carrier, depth, i)?;
|
||||
}
|
||||
|
||||
// V2: Condition validity (basic check - ValueId should be non-zero for safety)
|
||||
// Note: Full ValueId validity check would require builder context
|
||||
Self::verify_value_id_basic(loop_plan.cond_loop, depth, "cond_loop")?;
|
||||
Self::verify_value_id_basic(loop_plan.cond_match, depth, "cond_match")?;
|
||||
|
||||
// Verify header_effects
|
||||
for (i, effect) in loop_plan.header_effects.iter().enumerate() {
|
||||
Self::verify_effect(effect, depth).map_err(|e| {
|
||||
format!("[Loop.header_effects[{}]] {}", i, e)
|
||||
})?;
|
||||
}
|
||||
|
||||
// Verify body plans (now in_loop = true)
|
||||
for (i, plan) in loop_plan.body.iter().enumerate() {
|
||||
Self::verify_plan(plan, depth + 1, true).map_err(|e| {
|
||||
format!("[Loop.body[{}]] {}", i, e)
|
||||
})?;
|
||||
}
|
||||
|
||||
// Verify step_effects
|
||||
for (i, effect) in loop_plan.step_effects.iter().enumerate() {
|
||||
Self::verify_effect(effect, depth).map_err(|e| {
|
||||
format!("[Loop.step_effects[{}]] {}", i, e)
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// V1: Carrier completeness
|
||||
fn verify_carrier(carrier: &CoreCarrierInfo, depth: usize, index: usize) -> Result<(), String> {
|
||||
if carrier.name.is_empty() {
|
||||
return Err(format!(
|
||||
"[V1] Carrier[{}] at depth {} has empty name",
|
||||
index, depth
|
||||
));
|
||||
}
|
||||
|
||||
Self::verify_value_id_basic(carrier.init_value, depth, &format!("carrier[{}].init_value", index))?;
|
||||
Self::verify_value_id_basic(carrier.step_value, depth, &format!("carrier[{}].step_value", index))?;
|
||||
Self::verify_value_id_basic(carrier.phi_dst, depth, &format!("carrier[{}].phi_dst", index))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// V5: If completeness
|
||||
fn verify_if(if_plan: &CoreIfPlan, depth: usize, in_loop: bool) -> Result<(), String> {
|
||||
// V2: Condition validity
|
||||
Self::verify_value_id_basic(if_plan.condition, depth, "if.condition")?;
|
||||
|
||||
// V5: then_plans non-empty
|
||||
if if_plan.then_plans.is_empty() {
|
||||
return Err(format!(
|
||||
"[V5] If at depth {} has empty then_plans",
|
||||
depth
|
||||
));
|
||||
}
|
||||
|
||||
for (i, plan) in if_plan.then_plans.iter().enumerate() {
|
||||
Self::verify_plan(plan, depth + 1, in_loop).map_err(|e| {
|
||||
format!("[If.then[{}]] {}", i, e)
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Some(else_plans) = &if_plan.else_plans {
|
||||
for (i, plan) in else_plans.iter().enumerate() {
|
||||
Self::verify_plan(plan, depth + 1, in_loop).map_err(|e| {
|
||||
format!("[If.else[{}]] {}", i, e)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// V6: Effect ValueId validity
|
||||
fn verify_effect(effect: &CoreEffectPlan, depth: usize) -> Result<(), String> {
|
||||
match effect {
|
||||
CoreEffectPlan::MethodCall { dst, object, method, args } => {
|
||||
Self::verify_value_id_basic(*dst, depth, "MethodCall.dst")?;
|
||||
Self::verify_value_id_basic(*object, depth, "MethodCall.object")?;
|
||||
if method.is_empty() {
|
||||
return Err(format!(
|
||||
"[V6] MethodCall at depth {} has empty method name",
|
||||
depth
|
||||
));
|
||||
}
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
Self::verify_value_id_basic(*arg, depth, &format!("MethodCall.args[{}]", i))?;
|
||||
}
|
||||
}
|
||||
CoreEffectPlan::BinOp { dst, lhs, op: _, rhs } => {
|
||||
Self::verify_value_id_basic(*dst, depth, "BinOp.dst")?;
|
||||
Self::verify_value_id_basic(*lhs, depth, "BinOp.lhs")?;
|
||||
Self::verify_value_id_basic(*rhs, depth, "BinOp.rhs")?;
|
||||
}
|
||||
CoreEffectPlan::Compare { dst, lhs, op: _, rhs } => {
|
||||
Self::verify_value_id_basic(*dst, depth, "Compare.dst")?;
|
||||
Self::verify_value_id_basic(*lhs, depth, "Compare.lhs")?;
|
||||
Self::verify_value_id_basic(*rhs, depth, "Compare.rhs")?;
|
||||
}
|
||||
CoreEffectPlan::Const { dst, value: _ } => {
|
||||
Self::verify_value_id_basic(*dst, depth, "Const.dst")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// V3: Exit validity
|
||||
fn verify_exit(exit: &CoreExitPlan, depth: usize, in_loop: bool) -> Result<(), String> {
|
||||
match exit {
|
||||
CoreExitPlan::Return(opt_val) => {
|
||||
if let Some(val) = opt_val {
|
||||
Self::verify_value_id_basic(*val, depth, "Return.value")?;
|
||||
}
|
||||
// Return is always valid (in function context)
|
||||
}
|
||||
CoreExitPlan::Break => {
|
||||
if !in_loop {
|
||||
return Err(format!(
|
||||
"[V3] Break at depth {} outside of loop",
|
||||
depth
|
||||
));
|
||||
}
|
||||
}
|
||||
CoreExitPlan::Continue => {
|
||||
if !in_loop {
|
||||
return Err(format!(
|
||||
"[V3] Continue at depth {} outside of loop",
|
||||
depth
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// V6: Basic ValueId validity check
|
||||
///
|
||||
/// Note: This is a basic check. Full validity would require builder context.
|
||||
fn verify_value_id_basic(value_id: ValueId, depth: usize, context: &str) -> Result<(), String> {
|
||||
// ValueId(0) might be valid in some contexts, so we don't check for zero
|
||||
// This is a placeholder for more sophisticated checks if needed
|
||||
let _ = (value_id, depth, context);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::{BasicBlockId, BinaryOp, CompareOp, ConstValue, ValueId};
|
||||
|
||||
#[test]
|
||||
fn test_verify_empty_seq_fails() {
|
||||
let plan = CorePlan::Seq(vec![]);
|
||||
let result = PlanVerifier::verify(&plan);
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("[V4]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_break_outside_loop_fails() {
|
||||
let plan = CorePlan::Exit(CoreExitPlan::Break);
|
||||
let result = PlanVerifier::verify(&plan);
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("[V3]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_const_effect_succeeds() {
|
||||
let plan = CorePlan::Effect(CoreEffectPlan::Const {
|
||||
dst: ValueId(1),
|
||||
value: ConstValue::Integer(42),
|
||||
});
|
||||
let result = PlanVerifier::verify(&plan);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
@ -398,6 +398,17 @@ impl NyashRunner {
|
||||
}
|
||||
#[cfg(all(not(feature = "llvm-inkwell-legacy")))]
|
||||
{
|
||||
// Fail-fast: if the user explicitly requested the llvmlite harness but this binary
|
||||
// was built without the `llvm-harness` feature, do not silently fall back to mock.
|
||||
if crate::config::env::env_bool("NYASH_LLVM_USE_HARNESS") {
|
||||
crate::console_println!(
|
||||
"❌ LLVM harness requested (NYASH_LLVM_USE_HARNESS=1), but this binary was built without `--features llvm` (llvm-harness)."
|
||||
);
|
||||
crate::console_println!(
|
||||
" Fix: cargo build --release --features llvm"
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
crate::console_println!("🔧 Mock LLVM Backend Execution:");
|
||||
crate::console_println!(" Build with --features llvm-inkwell-legacy for Rust/inkwell backend, or set NYASH_LLVM_OBJ_OUT and NYASH_LLVM_USE_HARNESS=1 for harness.");
|
||||
// NamingBox SSOT: Select entry (arity-aware, Main.main → main fallback)
|
||||
|
||||
Reference in New Issue
Block a user