feat(joinir): Phase 189 Box 1・2 モジュール化実装

Box 1: joinir_id_remapper.rs (~380行)
- ValueId/BlockId ID空間変換の独立化
- 決定性を重視した実装
- 全MIR命令型に対応(Await含む)

Box 2: joinir_inline_boundary_injector.rs (~180行)
- JoinInlineBoundary Copy命令注入
- Entry blockへのCopy instruction挿入
- SSA値空間の接続

統合:
- src/mir/builder.rs に mod 宣言追加
- ビルド成功確認
- テスト実行確認(loop_min_while.hako)

Phase 189 モジュール化の第一歩完了
This commit is contained in:
nyash-codex
2025-12-05 14:11:49 +09:00
parent d303d24b43
commit 827990e742
3 changed files with 596 additions and 0 deletions

View File

@ -32,6 +32,8 @@ mod fields; // field access/assignment lowering split
mod if_form;
mod lifecycle;
mod loop_frontend_binding; // Phase 50: Loop Frontend Binding (JoinIR variable mapping)
mod joinir_id_remapper; // Phase 189: JoinIR ID remapping (ValueId/BlockId translation)
mod joinir_inline_boundary_injector; // Phase 189: JoinInlineBoundary Copy instruction injector
pub(crate) mod loops;
mod ops;
mod phi;

View File

@ -0,0 +1,392 @@
//! Phase 189: JoinIR ID Remapper - ValueId/BlockId ID空間変換の独立化
//!
//! 責務:
//! - ValueId/BlockId の ID割り当て
//! - JoinIR fragment → host MIR への ID変換
//! - 決定性を重視した実装
use std::collections::HashMap;
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId};
/// JoinIR ID space を host MIR ID space に変換する
pub struct JoinIrIdRemapper {
/// (func_name, old_block_id) → new_block_id のマッピング
block_map: HashMap<(String, BasicBlockId), BasicBlockId>,
/// old_value_id → new_value_id のマッピング
value_map: HashMap<ValueId, ValueId>,
}
impl JoinIrIdRemapper {
pub fn new() -> Self {
Self {
block_map: HashMap::new(),
value_map: HashMap::new(),
}
}
/// Block ID mapping を取得
pub fn get_block(&self, func_name: &str, old_id: BasicBlockId) -> Option<BasicBlockId> {
self.block_map.get(&(func_name.to_string(), old_id)).copied()
}
/// Value ID mapping を取得
pub fn get_value(&self, old_id: ValueId) -> Option<ValueId> {
self.value_map.get(&old_id).copied()
}
/// Block mapping を設定
pub fn set_block(&mut self, func_name: String, old_id: BasicBlockId, new_id: BasicBlockId) {
self.block_map.insert((func_name, old_id), new_id);
}
/// Value mapping を設定
pub fn set_value(&mut self, old_id: ValueId, new_id: ValueId) {
self.value_map.insert(old_id, new_id);
}
/// Block 内の ValueId を収集
pub fn collect_values_in_block(&self, block: &BasicBlock) -> Vec<ValueId> {
let mut values = Vec::new();
for inst in &block.instructions {
values.extend(self.collect_values_in_instruction(inst));
}
if let Some(ref term) = block.terminator {
values.extend(self.collect_values_in_instruction(term));
}
values
}
/// 命令内の ValueId を収集
pub fn collect_values_in_instruction(&self, inst: &MirInstruction) -> Vec<ValueId> {
use crate::mir::MirInstruction::*;
match inst {
Const { dst, .. } => vec![*dst],
UnaryOp { dst, operand, .. } => vec![*dst, *operand],
BinOp { dst, lhs, rhs, .. } => vec![*dst, *lhs, *rhs],
Compare { dst, lhs, rhs, .. } => vec![*dst, *lhs, *rhs],
Load { dst, ptr } => vec![*dst, *ptr],
Store { value, ptr } => vec![*value, *ptr],
Call { dst, func, args, .. } => {
let mut vals = vec![*func];
if let Some(d) = dst {
vals.push(*d);
}
vals.extend(args.iter().copied());
vals
}
BoxCall { dst, box_val, args, .. } => {
let mut vals = vec![*box_val];
if let Some(d) = dst {
vals.push(*d);
}
vals.extend(args.iter().copied());
vals
}
PluginInvoke { dst, box_val, args, .. } => {
let mut vals = vec![*box_val];
if let Some(d) = dst {
vals.push(*d);
}
vals.extend(args.iter().copied());
vals
}
Branch { condition, .. } => vec![*condition],
Return { value } => value.iter().copied().collect(),
Phi { dst, inputs, .. } => {
let mut vals = vec![*dst];
vals.extend(inputs.iter().map(|(_, v)| *v));
vals
}
Copy { dst, src } => vec![*dst, *src],
NewBox { dst, args, .. } => {
let mut vals = vec![*dst];
vals.extend(args.iter().copied());
vals
}
NewClosure { dst, captures, me, .. } => {
let mut vals = vec![*dst];
vals.extend(captures.iter().map(|(_, v)| *v));
if let Some(m) = me {
vals.push(*m);
}
vals
}
Print { value, .. } => vec![*value],
Debug { value, .. } => vec![*value],
DebugLog { values, .. } => values.clone(),
Throw { exception, .. } => vec![*exception],
Catch { exception_value, .. } => vec![*exception_value],
RefNew { dst, box_val } => vec![*dst, *box_val],
RefGet { dst, reference, .. } => vec![*dst, *reference],
RefSet { reference, value, .. } => vec![*reference, *value],
WeakNew { dst, box_val } => vec![*dst, *box_val],
WeakLoad { dst, weak_ref } => vec![*dst, *weak_ref],
WeakRef { dst, value, .. } => vec![*dst, *value],
BarrierRead { ptr } => vec![*ptr],
BarrierWrite { ptr } => vec![*ptr],
Barrier { ptr, .. } => vec![*ptr],
FutureNew { dst, value } => vec![*dst, *value],
FutureSet { future, value } => vec![*future, *value],
Await { dst, future } => vec![*dst, *future],
TypeCheck { dst, value, .. } => vec![*dst, *value],
Cast { dst, value, .. } => vec![*dst, *value],
TypeOp { dst, value, .. } => vec![*dst, *value],
ArrayGet { dst, array, index } => vec![*dst, *array, *index],
ArraySet { array, index, value } => vec![*array, *index, *value],
Jump { .. } | Nop | Safepoint => vec![],
ExternCall { dst, args, .. } => {
let mut vals = Vec::new();
if let Some(d) = dst {
vals.push(*d);
}
vals.extend(args.iter().copied());
vals
}
}
}
/// 命令を新しい ID空間にリマップ
pub fn remap_instruction(&self, inst: &MirInstruction) -> MirInstruction {
use crate::mir::MirInstruction::*;
let remap = |v: ValueId| self.value_map.get(&v).copied().unwrap_or(v);
match inst {
Const { dst, value } => Const {
dst: remap(*dst),
value: value.clone(),
},
UnaryOp { dst, op, operand } => UnaryOp {
dst: remap(*dst),
op: *op,
operand: remap(*operand),
},
BinOp { dst, op, lhs, rhs } => BinOp {
dst: remap(*dst),
op: *op,
lhs: remap(*lhs),
rhs: remap(*rhs),
},
Compare { dst, op, lhs, rhs } => Compare {
dst: remap(*dst),
op: *op,
lhs: remap(*lhs),
rhs: remap(*rhs),
},
Load { dst, ptr } => Load {
dst: remap(*dst),
ptr: remap(*ptr),
},
Store { value, ptr } => Store {
value: remap(*value),
ptr: remap(*ptr),
},
Call { dst, func, callee, args, effects } => Call {
dst: dst.map(remap),
func: remap(*func),
callee: callee.clone(),
args: args.iter().map(|&a| remap(a)).collect(),
effects: *effects,
},
BoxCall { dst, box_val, method, method_id, args, effects } => BoxCall {
dst: dst.map(remap),
box_val: remap(*box_val),
method: method.clone(),
method_id: *method_id,
args: args.iter().map(|&a| remap(a)).collect(),
effects: *effects,
},
PluginInvoke { dst, box_val, method, args, effects } => PluginInvoke {
dst: dst.map(remap),
box_val: remap(*box_val),
method: method.clone(),
args: args.iter().map(|&a| remap(a)).collect(),
effects: *effects,
},
Copy { dst, src } => Copy {
dst: remap(*dst),
src: remap(*src),
},
NewBox { dst, box_type, args } => NewBox {
dst: remap(*dst),
box_type: box_type.clone(),
args: args.iter().map(|&a| remap(a)).collect(),
},
NewClosure { dst, params, body, captures, me } => NewClosure {
dst: remap(*dst),
params: params.clone(),
body: body.clone(),
captures: captures.iter().map(|(n, v)| (n.clone(), remap(*v))).collect(),
me: me.map(remap),
},
Print { value, effects } => Print {
value: remap(*value),
effects: *effects,
},
Debug { value, message } => Debug {
value: remap(*value),
message: message.clone(),
},
DebugLog { message, values } => DebugLog {
message: message.clone(),
values: values.iter().map(|&v| remap(v)).collect(),
},
Throw { exception, effects } => Throw {
exception: remap(*exception),
effects: *effects,
},
Catch { exception_type, exception_value, handler_bb } => Catch {
exception_type: exception_type.clone(),
exception_value: remap(*exception_value),
handler_bb: *handler_bb,
},
RefNew { dst, box_val } => RefNew {
dst: remap(*dst),
box_val: remap(*box_val),
},
RefGet { dst, reference, field } => RefGet {
dst: remap(*dst),
reference: remap(*reference),
field: field.clone(),
},
RefSet { reference, field, value } => RefSet {
reference: remap(*reference),
field: field.clone(),
value: remap(*value),
},
WeakNew { dst, box_val } => WeakNew {
dst: remap(*dst),
box_val: remap(*box_val),
},
WeakLoad { dst, weak_ref } => WeakLoad {
dst: remap(*dst),
weak_ref: remap(*weak_ref),
},
WeakRef { dst, op, value } => WeakRef {
dst: remap(*dst),
op: *op,
value: remap(*value),
},
BarrierRead { ptr } => BarrierRead {
ptr: remap(*ptr),
},
BarrierWrite { ptr } => BarrierWrite {
ptr: remap(*ptr),
},
Barrier { op, ptr } => Barrier {
op: *op,
ptr: remap(*ptr),
},
FutureNew { dst, value } => FutureNew {
dst: remap(*dst),
value: remap(*value),
},
FutureSet { future, value } => FutureSet {
future: remap(*future),
value: remap(*value),
},
Await { dst, future } => Await {
dst: remap(*dst),
future: remap(*future),
},
TypeCheck { dst, value, expected_type } => TypeCheck {
dst: remap(*dst),
value: remap(*value),
expected_type: expected_type.clone(),
},
Cast { dst, value, target_type } => Cast {
dst: remap(*dst),
value: remap(*value),
target_type: target_type.clone(),
},
TypeOp { dst, op, value, ty } => TypeOp {
dst: remap(*dst),
op: *op,
value: remap(*value),
ty: ty.clone(),
},
ArrayGet { dst, array, index } => ArrayGet {
dst: remap(*dst),
array: remap(*array),
index: remap(*index),
},
ArraySet { array, index, value } => ArraySet {
array: remap(*array),
index: remap(*index),
value: remap(*value),
},
ExternCall { dst, iface_name, method_name, args, effects } => ExternCall {
dst: dst.map(remap),
iface_name: iface_name.clone(),
method_name: method_name.clone(),
args: args.iter().map(|&a| remap(a)).collect(),
effects: *effects,
},
// Pass through unchanged
Branch { .. } | Jump { .. } | Return { .. } | Phi { .. } | Nop | Safepoint => inst.clone(),
}
}
/// Value ID をリマップ
pub fn remap_value(&self, v: ValueId) -> ValueId {
self.value_map.get(&v).copied().unwrap_or(v)
}
/// Block ID をリマップ
pub fn remap_block(&self, func_name: &str, b: BasicBlockId) -> BasicBlockId {
self.block_map
.get(&(func_name.to_string(), b))
.copied()
.unwrap_or(b)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remapper_new() {
let remapper = JoinIrIdRemapper::new();
assert_eq!(remapper.get_value(ValueId(0)), None);
}
#[test]
fn test_remapper_set_and_get_value() {
let mut remapper = JoinIrIdRemapper::new();
remapper.set_value(ValueId(0), ValueId(10));
assert_eq!(remapper.get_value(ValueId(0)), Some(ValueId(10)));
}
#[test]
fn test_remapper_set_and_get_block() {
let mut remapper = JoinIrIdRemapper::new();
remapper.set_block("main".to_string(), BasicBlockId(0), BasicBlockId(100));
assert_eq!(
remapper.get_block("main", BasicBlockId(0)),
Some(BasicBlockId(100))
);
}
#[test]
fn test_remap_value() {
let mut remapper = JoinIrIdRemapper::new();
remapper.set_value(ValueId(5), ValueId(50));
assert_eq!(remapper.remap_value(ValueId(5)), ValueId(50));
assert_eq!(remapper.remap_value(ValueId(99)), ValueId(99)); // Unmapped returns original
}
#[test]
fn test_collect_values_simple() {
let remapper = JoinIrIdRemapper::new();
let inst = MirInstruction::BinOp {
dst: ValueId(1),
op: crate::mir::types::BinaryOp::Add,
lhs: ValueId(2),
rhs: ValueId(3),
};
let values = remapper.collect_values_in_instruction(&inst);
assert_eq!(values, vec![ValueId(1), ValueId(2), ValueId(3)]);
}
}

View File

@ -0,0 +1,202 @@
//! Phase 188-Impl-3: JoinInlineBoundary Copy Instruction Injector
//!
//! 責務:
//! - JoinInlineBoundary で指定された入出力の Copy instruction 生成
//! - Entry block への Copy instruction 挿入
//! - SSA 値空間の接続
use std::collections::HashMap;
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
pub struct BoundaryInjector;
impl BoundaryInjector {
/// JoinInlineBoundary で指定された入力を entry block に Copy instruction として挿入
///
/// # Arguments
///
/// * `func` - 対象の MirFunction
/// * `entry_block_id` - entry block の ID
/// * `boundary` - JoinInlineBoundary入力マッピング情報
/// * `value_map` - ValueId リマッピング情報
/// * `debug` - デバッグログ出力
///
/// # Returns
///
/// * `Ok(())` - 成功
/// * `Err(String)` - エラー(ブロックが見つからない等)
pub fn inject_boundary_copies(
func: &mut MirFunction,
entry_block_id: BasicBlockId,
boundary: &JoinInlineBoundary,
value_map: &HashMap<ValueId, ValueId>,
debug: bool,
) -> Result<(), String> {
// Boundary が空の場合はスキップ
if boundary.join_inputs.is_empty() {
return Ok(());
}
if debug {
eprintln!(
"[BoundaryInjector] Injecting {} Copy instructions at entry block {:?}",
boundary.join_inputs.len(),
entry_block_id
);
}
// Entry block を取得
let entry_block = func
.get_block_mut(entry_block_id)
.ok_or(format!("Entry block {:?} not found", entry_block_id))?;
// Copy instructions を生成して挿入
let mut copy_instructions = Vec::new();
for (join_input, host_input) in boundary
.join_inputs
.iter()
.zip(boundary.host_inputs.iter())
{
// リマップ後の ValueId を取得
let remapped_join = value_map.get(join_input).copied().unwrap_or(*join_input);
let remapped_host = *host_input; // host_input is already in host space
// Copy instruction: remapped_join = Copy remapped_host
let copy_inst = MirInstruction::Copy {
dst: remapped_join,
src: remapped_host,
};
copy_instructions.push(copy_inst);
if debug {
eprintln!(
"[BoundaryInjector] Copy {:?} = Copy {:?}",
remapped_join, remapped_host
);
}
}
// Entry block の先頭に Copy instructions を挿入
// Reverse order to preserve original order when inserting at position 0
for inst in copy_instructions.into_iter().rev() {
entry_block.instructions.insert(0, inst);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::{BasicBlock, MirModule};
#[test]
fn test_injector_empty_boundary() {
// 空の boundary で何もしない
let boundary = JoinInlineBoundary::new_inputs_only(vec![], vec![]);
let mut module = MirModule::new();
let mut func = module.define_function("test".to_string(), vec![]);
let entry_block = func.create_block();
let value_map = HashMap::new();
let result = BoundaryInjector::inject_boundary_copies(
&mut func,
entry_block,
&boundary,
&value_map,
false,
);
assert!(result.is_ok());
}
#[test]
fn test_injector_single_copy() {
// 単一の Copy instruction を挿入
let boundary = JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)],
vec![ValueId(10)],
);
let mut module = MirModule::new();
let mut func = module.define_function("test".to_string(), vec![]);
let entry_block = func.create_block();
let mut value_map = HashMap::new();
value_map.insert(ValueId(0), ValueId(100)); // JoinIR ValueId(0) remapped to ValueId(100)
let result = BoundaryInjector::inject_boundary_copies(
&mut func,
entry_block,
&boundary,
&value_map,
false,
);
assert!(result.is_ok());
// Copy instruction が挿入されたことを確認
let block = func.get_block(entry_block).unwrap();
assert!(!block.instructions.is_empty());
// First instruction should be Copy
match &block.instructions[0] {
MirInstruction::Copy { dst, src } => {
assert_eq!(*dst, ValueId(100)); // Remapped join input
assert_eq!(*src, ValueId(10)); // Host input
}
_ => panic!("Expected Copy instruction"),
}
}
#[test]
fn test_injector_multiple_copies() {
// 複数の Copy instruction を挿入
let boundary = JoinInlineBoundary::new_inputs_only(
vec![ValueId(0), ValueId(1)],
vec![ValueId(10), ValueId(20)],
);
let mut module = MirModule::new();
let mut func = module.define_function("test".to_string(), vec![]);
let entry_block = func.create_block();
let mut value_map = HashMap::new();
value_map.insert(ValueId(0), ValueId(100));
value_map.insert(ValueId(1), ValueId(101));
let result = BoundaryInjector::inject_boundary_copies(
&mut func,
entry_block,
&boundary,
&value_map,
false,
);
assert!(result.is_ok());
let block = func.get_block(entry_block).unwrap();
assert_eq!(block.instructions.len(), 2);
// Check both copy instructions
match &block.instructions[0] {
MirInstruction::Copy { dst, src } => {
assert_eq!(*dst, ValueId(100));
assert_eq!(*src, ValueId(10));
}
_ => panic!("Expected Copy instruction"),
}
match &block.instructions[1] {
MirInstruction::Copy { dst, src } => {
assert_eq!(*dst, ValueId(101));
assert_eq!(*src, ValueId(20));
}
_ => panic!("Expected Copy instruction"),
}
}
}