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:
@ -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
|
||||
|
||||
Reference in New Issue
Block a user