phase_9_79b_1: Add Unified Registry IDs + Builder Slotting\n- MIR BoxCall carries optional method_id (slot)\n- Slot registry with universal slots [0..3]\n- Builder resolves method_id when receiver type known\n- Printer shows method_id; backends updated\n- Update CURRENT_TASK + MIR spec note
This commit is contained in:
@ -7,9 +7,9 @@
|
||||
|
||||
### 直近タスク(小さく早く)
|
||||
1) 9.79b.1: Unified Registry IDs + Builder Slotting
|
||||
- 型ID/メソッドスロットの導入(レジストリ)
|
||||
- ユニバーサルメソッド低スロット予約(0..3)
|
||||
- Builderが解決可能なBoxCallに`method_id`を付与(未解決は遅延)
|
||||
- 型ID/メソッドスロットの導入(レジストリ)✅ 実装
|
||||
- ユニバーサルメソッド低スロット予約(0..3)✅ テストで不変確認
|
||||
- Builderが解決可能なBoxCallに`method_id`を付与(未解決は遅延)✅ 実装/Printer表示
|
||||
2) 9.79b.2: VM VTable Thunks + Mono-PIC
|
||||
- `execute_boxcall`をvtable+thunkの単一路線へ
|
||||
- call-site単位のモノモーフィックPICを追加
|
||||
@ -30,8 +30,8 @@ cargo build --release -j32
|
||||
- Docs: P2Pリファレンス/サンプル
|
||||
|
||||
### ⏭️ 次(9.79b)
|
||||
- 9.79b.1: `phase_9_79b_1_unified_registry_ids_and_builder_slotting.md`
|
||||
- 9.79b.2: `phase_9_79b_2_vm_vtable_thunks_and_pic.md`
|
||||
- 9.79b.1: `phase_9_79b_1_unified_registry_ids_and_builder_slotting.md` ✅ 最小スコープ達成(method_id導入)
|
||||
- 9.79b.2: `phase_9_79b_2_vm_vtable_thunks_and_pic.md` → 着手予定
|
||||
|
||||
## 統一Box設計メモ(唯一参照)
|
||||
- `docs/ideas/other/2025-08-25-unified-box-design-deep-analysis.md`
|
||||
|
||||
@ -22,6 +22,7 @@ Last Updated: 2025-08-25
|
||||
- Call
|
||||
- ExternCall
|
||||
- BoxCall
|
||||
- Note: BoxCall carries optional `method_id` (numeric slot) when the builder can resolve the receiver type; otherwise falls back to name-only late bind. Universal methods use reserved slots: 0=toString, 1=type, 2=equals, 3=clone.
|
||||
- NewBox
|
||||
- ArrayGet
|
||||
- ArraySet
|
||||
@ -42,4 +43,3 @@ Last Updated: 2025-08-25
|
||||
## 同期ルール
|
||||
- 命令の追加/削除/統合は、まずこの文書を更新し、次に実装(列挙/Printer/Verifier/Optimizer/VM)を同期。最後に「総数=26」テストを更新する。
|
||||
- 実装が26を外れた場合はCIを赤にする(設計の合意なく増減させないため)。
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ pub fn execute_instruction(vm: &mut VM, instruction: &MirInstruction, debug_glob
|
||||
|
||||
// Complex operations
|
||||
MirInstruction::Call { dst, func, args, effects: _ } => vm.execute_call(*dst, *func, args),
|
||||
MirInstruction::BoxCall { dst, box_val, method, args, effects: _ } => vm.execute_boxcall(*dst, *box_val, method, args),
|
||||
MirInstruction::BoxCall { dst, box_val, method, args, effects: _ , .. } => vm.execute_boxcall(*dst, *box_val, method, args),
|
||||
MirInstruction::NewBox { dst, box_type, args } => vm.execute_newbox(*dst, box_type, args),
|
||||
|
||||
// Array operations
|
||||
|
||||
@ -404,7 +404,7 @@ impl WasmCodegen {
|
||||
},
|
||||
|
||||
// Phase 9.77: BoxCall Implementation - Critical Box method calls
|
||||
MirInstruction::BoxCall { dst, box_val, method, args, effects: _ } => {
|
||||
MirInstruction::BoxCall { dst, box_val, method, args, effects: _ , .. } => {
|
||||
self.generate_box_call(*dst, *box_val, method, args)
|
||||
},
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ use super::{
|
||||
FunctionSignature, ValueId, ConstValue, BinaryOp, UnaryOp, CompareOp,
|
||||
MirType, EffectMask, Effect, BasicBlockIdGenerator, ValueIdGenerator
|
||||
};
|
||||
use super::slot_registry::resolve_slot_by_type_name;
|
||||
use crate::ast::{ASTNode, LiteralValue, BinaryOperator};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
@ -563,11 +564,17 @@ impl MirBuilder {
|
||||
}
|
||||
|
||||
let box_val = arg_values.remove(0);
|
||||
// Try to resolve method slot if the object originates from a known NewBox
|
||||
let method_id = self
|
||||
.value_origin_newbox
|
||||
.get(&box_val)
|
||||
.and_then(|class_name| resolve_slot_by_type_name(class_name, &name));
|
||||
|
||||
self.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(dst),
|
||||
box_val,
|
||||
method: name,
|
||||
method_id,
|
||||
args: arg_values,
|
||||
effects: EffectMask::PURE.add(Effect::ReadHeap), // Conservative default
|
||||
})?;
|
||||
@ -742,7 +749,13 @@ impl MirBuilder {
|
||||
eprintln!("[BUILDER] emit @bb{} -> {}", block_id, match &instruction {
|
||||
MirInstruction::TypeOp { dst, op, value, ty } => format!("typeop {:?} {} {:?} -> {}", op, value, ty, dst),
|
||||
MirInstruction::Print { value, .. } => format!("print {}", value),
|
||||
MirInstruction::BoxCall { box_val, method, args, dst, .. } => format!("boxcall {}.{}({:?}) -> {:?}", box_val, method, args, dst),
|
||||
MirInstruction::BoxCall { box_val, method, method_id, args, dst, .. } => {
|
||||
if let Some(mid) = method_id {
|
||||
format!("boxcall {}.{}[#{}]({:?}) -> {:?}", box_val, method, mid, args, dst)
|
||||
} else {
|
||||
format!("boxcall {}.{}({:?}) -> {:?}", box_val, method, args, dst)
|
||||
}
|
||||
},
|
||||
MirInstruction::Call { func, args, dst, .. } => format!("call {}({:?}) -> {:?}", func, args, dst),
|
||||
MirInstruction::NewBox { dst, box_type, args } => format!("new {}({:?}) -> {}", box_type, args, dst),
|
||||
MirInstruction::Const { dst, value } => format!("const {:?} -> {}", value, dst),
|
||||
@ -1030,14 +1043,16 @@ impl MirBuilder {
|
||||
})?;
|
||||
|
||||
// Record origin for optimization: dst was created by NewBox of class
|
||||
self.value_origin_newbox.insert(dst, class);
|
||||
self.value_origin_newbox.insert(dst, class.clone());
|
||||
|
||||
// Immediately call birth(...) on the created instance to run constructor semantics.
|
||||
// birth typically returns void; we don't capture the result here (dst: None)
|
||||
let birt_mid = resolve_slot_by_type_name(&class, "birth");
|
||||
self.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: None,
|
||||
box_val: dst,
|
||||
method: "birth".to_string(),
|
||||
method_id: birt_mid,
|
||||
args: arg_values,
|
||||
effects: EffectMask::READ.add(Effect::ReadHeap),
|
||||
})?;
|
||||
@ -1307,10 +1322,16 @@ impl MirBuilder {
|
||||
}
|
||||
|
||||
// Fallback: Emit a BoxCall instruction for regular or plugin/builtin method calls
|
||||
// Try to resolve method slot when receiver type can be inferred
|
||||
let maybe_class = self.value_origin_newbox.get(&object_value).cloned();
|
||||
let mid = maybe_class
|
||||
.as_ref()
|
||||
.and_then(|cls| resolve_slot_by_type_name(cls, &method));
|
||||
self.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(result_id),
|
||||
box_val: object_value,
|
||||
method,
|
||||
method_id: mid,
|
||||
args: arg_values,
|
||||
effects: EffectMask::READ.add(Effect::ReadHeap), // Method calls may have side effects
|
||||
})?;
|
||||
@ -1368,6 +1389,7 @@ impl MirBuilder {
|
||||
dst: Some(result_id),
|
||||
box_val: parent_value,
|
||||
method,
|
||||
method_id: None,
|
||||
args: arg_values,
|
||||
effects: EffectMask::READ.add(Effect::ReadHeap),
|
||||
})?;
|
||||
|
||||
@ -74,10 +74,13 @@ pub enum MirInstruction {
|
||||
|
||||
/// Box method invocation
|
||||
/// `%dst = invoke %box.method(%args...)`
|
||||
/// method_id: Optional numeric slot id when resolved at build time
|
||||
BoxCall {
|
||||
dst: Option<ValueId>,
|
||||
box_val: ValueId,
|
||||
method: String,
|
||||
/// Optional numeric method slot id (Unified Registry). None = late bind.
|
||||
method_id: Option<u16>,
|
||||
args: Vec<ValueId>,
|
||||
effects: EffectMask,
|
||||
},
|
||||
|
||||
@ -21,6 +21,7 @@ pub mod printer;
|
||||
pub mod value_id;
|
||||
pub mod effect;
|
||||
pub mod optimizer;
|
||||
pub mod slot_registry; // Phase 9.79b.1: method slot resolution (IDs)
|
||||
|
||||
// Re-export main types for easy access
|
||||
pub use instruction::{MirInstruction, BinaryOp, CompareOp, UnaryOp, ConstValue, MirType, TypeOpKind, WeakRefOp, BarrierOp};
|
||||
@ -37,6 +38,7 @@ pub use printer::MirPrinter;
|
||||
pub use value_id::{ValueId, LocalId, ValueIdGenerator};
|
||||
pub use effect::{EffectMask, Effect};
|
||||
pub use optimizer::MirOptimizer;
|
||||
pub use slot_registry::{BoxTypeId, MethodSlot};
|
||||
|
||||
/// MIR compilation result
|
||||
#[derive(Debug, Clone)]
|
||||
@ -229,6 +231,23 @@ mod tests {
|
||||
assert!(dump.contains(".push("), "Expected BoxCall to .push(...). Got:\n{}", dump);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boxcall_method_id_on_universal_slot() {
|
||||
// Build AST: (new ArrayBox()).toString()
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::New { class: "ArrayBox".to_string(), arguments: vec![], type_arguments: vec![], span: crate::ast::Span::unknown() }),
|
||||
method: "toString".to_string(),
|
||||
arguments: vec![],
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
|
||||
let mut compiler = MirCompiler::new();
|
||||
let result = compiler.compile(ast).expect("compile should succeed");
|
||||
let dump = MirPrinter::new().print_module(&result.module);
|
||||
// Expect a BoxCall with numeric method id [#0] for toString universal slot
|
||||
assert!(dump.contains("toString[#0]"), "Expected method_id #0 for toString. Dump:\n{}", dump);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lowering_await_expression() {
|
||||
// Build AST: await 1 (semantic is nonsensical but should emit Await)
|
||||
|
||||
@ -312,16 +312,16 @@ impl MirPrinter {
|
||||
}
|
||||
},
|
||||
|
||||
MirInstruction::BoxCall { dst, box_val, method, args, effects: _ } => {
|
||||
MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects: _ } => {
|
||||
let args_str = args.iter()
|
||||
.map(|v| format!("{}", v))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
let id_suffix = method_id.map(|id| format!("[#{}]", id)).unwrap_or_default();
|
||||
if let Some(dst) = dst {
|
||||
format!("{} = call {}.{}({})", dst, box_val, method, args_str)
|
||||
format!("{} = call {}.{}{}({})", dst, box_val, method, id_suffix, args_str)
|
||||
} else {
|
||||
format!("call {}.{}({})", box_val, method, args_str)
|
||||
format!("call {}.{}{}({})", box_val, method, id_suffix, args_str)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
102
src/mir/slot_registry.rs
Normal file
102
src/mir/slot_registry.rs
Normal file
@ -0,0 +1,102 @@
|
||||
/*!
|
||||
* MIR Slot Registry (Phase 9.79b.1)
|
||||
*
|
||||
* Provides numeric BoxTypeId assignment and per-type method slot resolution.
|
||||
* - Low slots [0..3] are universally reserved: 0=toString, 1=type, 2=equals, 3=clone
|
||||
* - Exposes minimal APIs for the MIR builder to resolve method slots when
|
||||
* the receiver type is known at build time.
|
||||
*/
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub type BoxTypeId = u32;
|
||||
pub type MethodSlot = u16;
|
||||
|
||||
// Global maps (scoped to compiler process)
|
||||
static TYPE_IDS: Lazy<Mutex<HashMap<String, BoxTypeId>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
static NEXT_TYPE_ID: Lazy<Mutex<BoxTypeId>> = Lazy::new(|| Mutex::new(100)); // start after small reserved area
|
||||
|
||||
// Per-type explicit slot reservations: (type_id, method) -> slot
|
||||
static EXPLICIT_SLOTS: Lazy<Mutex<HashMap<(BoxTypeId, String), MethodSlot>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
// Universal slots mapping for quick checks
|
||||
fn universal_slot(method: &str) -> Option<MethodSlot> {
|
||||
match method {
|
||||
"toString" => Some(0),
|
||||
"type" => Some(1),
|
||||
"equals" => Some(2),
|
||||
"clone" => Some(3),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get or assign a numeric BoxTypeId for a given type name.
|
||||
pub fn get_or_assign_type_id(type_name: &str) -> BoxTypeId {
|
||||
let mut map = TYPE_IDS.lock().unwrap();
|
||||
if let Some(&id) = map.get(type_name) {
|
||||
return id;
|
||||
}
|
||||
let mut next = NEXT_TYPE_ID.lock().unwrap();
|
||||
let id = *next;
|
||||
*next += 1;
|
||||
map.insert(type_name.to_string(), id);
|
||||
id
|
||||
}
|
||||
|
||||
/// Reserve a method slot for a given (type_id, method) pair.
|
||||
/// If the method is one of the universal methods, the reservation is ignored
|
||||
/// as universal slots are implicitly enforced for all types.
|
||||
pub fn reserve_method_slot(type_id: BoxTypeId, method: &str, slot: MethodSlot) {
|
||||
if universal_slot(method).is_some() {
|
||||
return; // universal slots are global invariants
|
||||
}
|
||||
let mut table = EXPLICIT_SLOTS.lock().unwrap();
|
||||
table.insert((type_id, method.to_string()), slot);
|
||||
}
|
||||
|
||||
/// Resolve a method slot given numeric type id and method name.
|
||||
pub fn resolve_slot(type_id: BoxTypeId, method: &str) -> Option<MethodSlot> {
|
||||
// Universal first
|
||||
if let Some(s) = universal_slot(method) {
|
||||
return Some(s);
|
||||
}
|
||||
let table = EXPLICIT_SLOTS.lock().unwrap();
|
||||
table.get(&(type_id, method.to_string())).copied()
|
||||
}
|
||||
|
||||
/// Resolve a method slot given a type name and method name.
|
||||
pub fn resolve_slot_by_type_name(type_name: &str, method: &str) -> Option<MethodSlot> {
|
||||
let ty = get_or_assign_type_id(type_name);
|
||||
resolve_slot(ty, method)
|
||||
}
|
||||
|
||||
/// Minimal MIR Debug Info scaffold to map IDs back to names (off by default).
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct MIRDebugInfo {
|
||||
// Optionally carry reverse maps when enabled in the future.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_universal_slots_reserved() {
|
||||
let tid = get_or_assign_type_id("StringBox");
|
||||
assert_eq!(resolve_slot(tid, "toString"), Some(0));
|
||||
assert_eq!(resolve_slot(tid, "type"), Some(1));
|
||||
assert_eq!(resolve_slot(tid, "equals"), Some(2));
|
||||
assert_eq!(resolve_slot(tid, "clone"), Some(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_explicit_slot_reservation() {
|
||||
let tid = get_or_assign_type_id("ArrayBox");
|
||||
reserve_method_slot(tid, "push", 8);
|
||||
assert_eq!(resolve_slot(tid, "push"), Some(8));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user