feat(joinir): string accumulator emitter (JoinIR)
- Add StringAccumulatorEmitter in join_ir/lowering/common/ - Emit string concat as BinOp(Add) for polymorphic VM/LLVM handling - Ensure VM/LLVM same semantics - Fail-Fast: RHS must be Variable (not Literal/MethodCall) - Pattern2 wiring: string carrier昇格 + type refinement + validation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -186,8 +186,9 @@ fn prepare_pattern2_inputs(
|
|||||||
|
|
||||||
// Phase 100 P2-2: Mutable Accumulator Analysis
|
// Phase 100 P2-2: Mutable Accumulator Analysis
|
||||||
// Detect accumulator pattern (target = target + x) and promote to carrier
|
// Detect accumulator pattern (target = target + x) and promote to carrier
|
||||||
|
// Phase 100 P3-3: Added AccumulatorKind support for string accumulators
|
||||||
use crate::mir::loop_pattern_detection::mutable_accumulator_analyzer::{
|
use crate::mir::loop_pattern_detection::mutable_accumulator_analyzer::{
|
||||||
MutableAccumulatorAnalyzer, RhsExprKind,
|
AccumulatorKind, MutableAccumulatorAnalyzer, RhsExprKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mutable_spec = MutableAccumulatorAnalyzer::analyze(body)?;
|
let mutable_spec = MutableAccumulatorAnalyzer::analyze(body)?;
|
||||||
@ -221,6 +222,81 @@ fn prepare_pattern2_inputs(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Phase 100 P3-3: Refine AccumulatorKind based on actual target type
|
||||||
|
// AST-only detection (P3-1) defaults to Int for Variables
|
||||||
|
// Here we check actual type from builder's type context
|
||||||
|
let mut refined_kind = spec.kind; // Start with AST-detected kind
|
||||||
|
if spec.rhs_expr_kind == RhsExprKind::Var && refined_kind == AccumulatorKind::Int {
|
||||||
|
// Check if target is string-typed (refine Int → String if needed)
|
||||||
|
use crate::mir::MirType;
|
||||||
|
if let Some(target_type) = builder.type_ctx.value_types.get(target_id) {
|
||||||
|
match target_type {
|
||||||
|
MirType::Box(box_name) if box_name == "StringBox" => {
|
||||||
|
refined_kind = AccumulatorKind::String;
|
||||||
|
if verbose {
|
||||||
|
log.log(
|
||||||
|
"phase100_p3",
|
||||||
|
format!(
|
||||||
|
"Refined accumulator kind: Int → String (target '{}' is StringBox)",
|
||||||
|
spec.target_name
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MirType::Integer => {
|
||||||
|
// Confirmed Int
|
||||||
|
if verbose {
|
||||||
|
log.log(
|
||||||
|
"phase100_p3",
|
||||||
|
format!(
|
||||||
|
"Confirmed accumulator kind: Int (target '{}' is Integer)",
|
||||||
|
spec.target_name
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Unknown type - keep default Int for backward compat
|
||||||
|
if verbose {
|
||||||
|
log.log(
|
||||||
|
"phase100_p3",
|
||||||
|
format!(
|
||||||
|
"Accumulator kind: Int (default, target '{}' type unknown: {:?})",
|
||||||
|
spec.target_name, target_type
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 100 P3-3: Validate String accumulator constraints
|
||||||
|
if refined_kind == AccumulatorKind::String {
|
||||||
|
// String accumulator RHS must be Variable (not Literal, not MethodCall)
|
||||||
|
match spec.rhs_expr_kind {
|
||||||
|
RhsExprKind::Var => {
|
||||||
|
// OK: Variable RHS is allowed for String accumulators
|
||||||
|
if verbose {
|
||||||
|
log.log(
|
||||||
|
"phase100_p3",
|
||||||
|
format!(
|
||||||
|
"String accumulator '{}' = '{}' + '{}' (Variable RHS: OK)",
|
||||||
|
spec.target_name, spec.target_name, spec.rhs_var_or_lit
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RhsExprKind::Literal => {
|
||||||
|
// Fail-Fast: Literal RHS not supported in P3 (will be P3.1)
|
||||||
|
return Err(format!(
|
||||||
|
"[joinir/mutable-acc] String accumulator '{}' with Literal RHS not supported in Phase 100 P3 (will be P3.1)",
|
||||||
|
spec.target_name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check RHS read-only via captured_env lookup
|
// Check RHS read-only via captured_env lookup
|
||||||
// According to spec: x ∈ {Const, BodyLocal, Captured, Pinned, Carrier}
|
// According to spec: x ∈ {Const, BodyLocal, Captured, Pinned, Carrier}
|
||||||
// At this point in prepare_pattern2_inputs:
|
// At this point in prepare_pattern2_inputs:
|
||||||
|
|||||||
@ -8,6 +8,7 @@ pub mod body_local_slot; // Phase 92 P3: Read-only body-local slot for condition
|
|||||||
pub mod dual_value_rewriter; // Phase 246-EX/247-EX: name-based dual-value rewrites
|
pub mod dual_value_rewriter; // Phase 246-EX/247-EX: name-based dual-value rewrites
|
||||||
pub mod condition_only_emitter; // Phase 93 P0: ConditionOnly (Derived Slot) recalculation
|
pub mod condition_only_emitter; // Phase 93 P0: ConditionOnly (Derived Slot) recalculation
|
||||||
pub mod body_local_derived_emitter; // Phase 94: Derived body-local (P5b escape "ch" reassignment)
|
pub mod body_local_derived_emitter; // Phase 94: Derived body-local (P5b escape "ch" reassignment)
|
||||||
|
pub mod string_accumulator_emitter; // Phase 100 P3-2: String accumulator (out = out + ch)
|
||||||
|
|
||||||
use crate::mir::loop_form::LoopForm;
|
use crate::mir::loop_form::LoopForm;
|
||||||
use crate::mir::query::{MirQuery, MirQueryBox};
|
use crate::mir::query::{MirQuery, MirQueryBox};
|
||||||
|
|||||||
116
src/mir/join_ir/lowering/common/string_accumulator_emitter.rs
Normal file
116
src/mir/join_ir/lowering/common/string_accumulator_emitter.rs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
//! Phase 100 P3-2: String Accumulator Emitter
|
||||||
|
//!
|
||||||
|
//! Dedicated emitter for string concatenation in JoinIR (VM/LLVM same semantics).
|
||||||
|
//!
|
||||||
|
//! # Responsibility
|
||||||
|
//!
|
||||||
|
//! Emit JoinIR instructions for string concatenation: `out = out + ch`
|
||||||
|
//! where ch is a Variable (not Literal, not MethodCall).
|
||||||
|
//!
|
||||||
|
//! # Design
|
||||||
|
//!
|
||||||
|
//! String concatenation uses BinOp(Add) just like integer addition.
|
||||||
|
//! Type semantics are enforced by:
|
||||||
|
//! 1. P3-1: AccumulatorKind detection (Int vs String)
|
||||||
|
//! 2. P3-3: Pattern2 wiring validation (RHS must be Variable, string-typed)
|
||||||
|
//! 3. This emitter: Emit BinOp(Add) for string operands
|
||||||
|
//!
|
||||||
|
//! The VM and LLVM backends handle BinOp(Add) polymorphically:
|
||||||
|
//! - Integer + Integer → integer addition
|
||||||
|
//! - String + String → string concatenation (via AddOperator.apply/2)
|
||||||
|
//!
|
||||||
|
//! # Phase 100 P3 Contract
|
||||||
|
//!
|
||||||
|
//! **Allowed**: `out = out + ch` where ch ∈ {Variable (string-typed)}
|
||||||
|
//! **Fail-Fast**: Literal RHS, MethodCall RHS, non-string RHS
|
||||||
|
|
||||||
|
use crate::mir::join_ir::{BinOpKind, JoinInst, MirLikeInst};
|
||||||
|
use crate::mir::ValueId;
|
||||||
|
|
||||||
|
/// Emit string concatenation in JoinIR
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `target_id` - ValueId of the accumulator variable (e.g., "out")
|
||||||
|
/// * `rhs_id` - ValueId of the RHS variable (e.g., "ch")
|
||||||
|
/// * `alloc_value` - ValueId allocator closure
|
||||||
|
/// * `instructions` - Output vector to append instructions to
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// ValueId of the concatenation result
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// // For "out = out + ch":
|
||||||
|
/// let out_next = emit_string_concat(
|
||||||
|
/// out_param, // ValueId of "out" parameter
|
||||||
|
/// ch_value, // ValueId of "ch" body-local variable
|
||||||
|
/// &mut alloc_value,
|
||||||
|
/// &mut instructions,
|
||||||
|
/// )?;
|
||||||
|
/// // Generates:
|
||||||
|
/// // out_next = BinOp(Add, out_param, ch_value)
|
||||||
|
/// ```
|
||||||
|
pub fn emit_string_concat(
|
||||||
|
target_id: ValueId,
|
||||||
|
rhs_id: ValueId,
|
||||||
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
|
instructions: &mut Vec<JoinInst>,
|
||||||
|
) -> Result<ValueId, String> {
|
||||||
|
// Phase 100 P3-2: Emit BinOp(Add) for string concatenation
|
||||||
|
// Type semantics enforced by Pattern2 wiring (P3-3)
|
||||||
|
let result = alloc_value();
|
||||||
|
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||||
|
dst: result,
|
||||||
|
op: BinOpKind::Add,
|
||||||
|
lhs: target_id,
|
||||||
|
rhs: rhs_id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_emit_string_concat() {
|
||||||
|
// Phase 100 P3-2: Unit test for string concat emission
|
||||||
|
let mut value_counter = 10u32;
|
||||||
|
let mut alloc_value = || {
|
||||||
|
let id = ValueId(value_counter);
|
||||||
|
value_counter += 1;
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
let target_id = ValueId(1); // "out" parameter
|
||||||
|
let rhs_id = ValueId(2); // "ch" body-local
|
||||||
|
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
|
||||||
|
let result = emit_string_concat(
|
||||||
|
target_id,
|
||||||
|
rhs_id,
|
||||||
|
&mut alloc_value,
|
||||||
|
&mut instructions,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// Verify result
|
||||||
|
assert_eq!(result, ValueId(10));
|
||||||
|
|
||||||
|
// Verify instruction
|
||||||
|
assert_eq!(instructions.len(), 1);
|
||||||
|
match &instructions[0] {
|
||||||
|
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||||
|
assert_eq!(*dst, ValueId(10));
|
||||||
|
assert_eq!(*op, BinOpKind::Add);
|
||||||
|
assert_eq!(*lhs, target_id);
|
||||||
|
assert_eq!(*rhs, rhs_id);
|
||||||
|
}
|
||||||
|
_ => panic!("Expected BinOp instruction"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user