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
|
||||
// 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::{
|
||||
MutableAccumulatorAnalyzer, RhsExprKind,
|
||||
AccumulatorKind, MutableAccumulatorAnalyzer, RhsExprKind,
|
||||
};
|
||||
|
||||
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
|
||||
// According to spec: x ∈ {Const, BodyLocal, Captured, Pinned, Carrier}
|
||||
// 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 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 string_accumulator_emitter; // Phase 100 P3-2: String accumulator (out = out + ch)
|
||||
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
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