- Add CoreServicesConfig struct (from_env, minimal, all_enabled) - Implement with_core_from_registry_optional() for selective initialization - Update CoreBoxesImpl fields to Option<Arc<dyn XyzService>> - Maintain backward compatibility (with_core_from_registry calls all_enabled) - Add NYASH_CORE_DISABLE_* environment variable support - ConsoleBox remains mandatory (Graceful Degradation principle) - Add unit tests for optional initialization - Update console_println! macro to handle Option type - Fix direct console.println() calls in vm.rs and selfhost.rs - Create core_optional_design.md documentation Note: Phase 104 will extend ConsoleService to be optional as well with graceful fallback in console_println! macro. Files modified: - src/runtime/plugin_host.rs (CoreServicesConfig, with_core_from_registry_optional, tests) - src/runtime/core_services.rs (CoreBoxesImpl fields → Option type) - src/runtime/mod.rs (console_println! macro updated) - src/runner/modes/vm.rs (handle Option console) - src/runner/selfhost.rs (handle Option console) - docs/development/current/main/core_optional_design.md (new) - docs/development/current/main/ring0-inventory.md (Phase 103 entry) Test results: - Build: ✅ Success (0 errors, 7 warnings) - Unit tests: ✅ 3/3 passed (optional_core_tests) - Runtime tests: ✅ 63/63 passed - Smoke tests: ✅ 30/31 passed (1 pre-existing timeout)
472 lines
20 KiB
Rust
472 lines
20 KiB
Rust
/*!
|
||
* MIR Verification - SSA form and semantic verification
|
||
*
|
||
* Implements dominance checking, SSA verification, and semantic analysis
|
||
*/
|
||
|
||
use super::{MirFunction, MirModule};
|
||
use crate::debug::log as dlog;
|
||
use crate::mir::verification_types::VerificationError;
|
||
mod awaits;
|
||
mod barrier;
|
||
mod cfg;
|
||
mod dom;
|
||
mod legacy;
|
||
mod ssa;
|
||
mod utils;
|
||
|
||
/// MIR verifier for SSA form and semantic correctness
|
||
pub struct MirVerifier {
|
||
/// Current verification errors
|
||
errors: Vec<VerificationError>,
|
||
}
|
||
|
||
impl MirVerifier {
|
||
/// Create a new MIR verifier
|
||
pub fn new() -> Self {
|
||
Self { errors: Vec::new() }
|
||
}
|
||
|
||
/// Verify an entire MIR module
|
||
pub fn verify_module(&mut self, module: &MirModule) -> Result<(), Vec<VerificationError>> {
|
||
self.errors.clear();
|
||
|
||
// Stage‑B/selfhost 専用: dev verify を一時緩和するためのトグル
|
||
if !crate::config::env::stageb_dev_verify_enabled() {
|
||
return Ok(());
|
||
}
|
||
|
||
for (_name, function) in &module.functions {
|
||
if let Err(mut func_errors) = self.verify_function(function) {
|
||
// Dev-only trace: BreakFinderBox / LoopSSA 周辺のSSAバグを詳細に観測する。
|
||
//
|
||
// NYASH_BREAKFINDER_SSA_TRACE=1 のときだけ有効になり、
|
||
// compiler_stageb.hako など大規模モジュール内での
|
||
// BreakFinderBox.* に対する UndefinedValue を詳細に出力する。
|
||
//
|
||
// 併せて、同じトグルで「任意の関数」に対する UndefinedValue も
|
||
// 簡易ログとして出力し、どの関数で支配関係が崩れているかを
|
||
// 追いやすくしている(箱理論の観測レイヤー強化)。
|
||
if std::env::var("NYASH_BREAKFINDER_SSA_TRACE").ok().as_deref() == Some("1") {
|
||
let log = crate::runtime::get_global_ring0().log.clone();
|
||
// 1) BreakFinderBox / LoopSSA 向けの詳細ログ
|
||
if function.signature.name.starts_with("BreakFinderBox.")
|
||
|| function.signature.name.starts_with("LoopSSA.")
|
||
{
|
||
for e in &func_errors {
|
||
if let VerificationError::UndefinedValue {
|
||
value,
|
||
block,
|
||
instruction_index,
|
||
} = e
|
||
{
|
||
if let Some(bb) = function.blocks.get(block) {
|
||
let inst = bb.instructions.get(*instruction_index);
|
||
log.debug(&format!(
|
||
"[breakfinder/ssa] UndefinedValue {:?} in fn {} at bb={:?}, inst={} => {:?}",
|
||
value,
|
||
function.signature.name,
|
||
block,
|
||
instruction_index,
|
||
inst,
|
||
));
|
||
} else {
|
||
log.debug(&format!(
|
||
"[breakfinder/ssa] UndefinedValue {:?} in fn {} at bb={:?}, inst={}",
|
||
value,
|
||
function.signature.name,
|
||
block,
|
||
instruction_index
|
||
));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2) 任意の関数向けの簡易 UndefinedValue ログ
|
||
for e in &func_errors {
|
||
match e {
|
||
VerificationError::UndefinedValue {
|
||
value,
|
||
block,
|
||
instruction_index,
|
||
} => {
|
||
log.debug(&format!(
|
||
"[mir-ssa-debug] UndefinedValue {:?} in fn {} at bb={:?}, inst={}",
|
||
value,
|
||
function.signature.name,
|
||
block,
|
||
instruction_index
|
||
));
|
||
if let Some(bb) = function.blocks.get(block) {
|
||
let inst_opt = bb
|
||
.all_spanned_instructions_enumerated()
|
||
.nth(*instruction_index);
|
||
if let Some((_idx, sp)) = inst_opt {
|
||
log.debug(&format!(
|
||
"[mir-ssa-debug-inst] inst={:?}",
|
||
sp.inst
|
||
));
|
||
}
|
||
}
|
||
}
|
||
VerificationError::DominatorViolation {
|
||
value,
|
||
use_block,
|
||
def_block,
|
||
} => {
|
||
log.debug(&format!(
|
||
"[mir-ssa-debug] DominatorViolation {:?} in fn {}: use_block={:?}, def_block={:?}",
|
||
value,
|
||
function.signature.name,
|
||
use_block,
|
||
def_block
|
||
));
|
||
}
|
||
_ => {}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Add function context to errors
|
||
for _error in &mut func_errors {
|
||
// Could add function name to error context here
|
||
}
|
||
self.errors.extend(func_errors);
|
||
}
|
||
}
|
||
|
||
if self.errors.is_empty() {
|
||
Ok(())
|
||
} else {
|
||
Err(self.errors.clone())
|
||
}
|
||
}
|
||
|
||
/// Verify a single MIR function
|
||
pub fn verify_function(
|
||
&mut self,
|
||
function: &MirFunction,
|
||
) -> Result<(), Vec<VerificationError>> {
|
||
let mut local_errors = Vec::new();
|
||
|
||
// 1. Check SSA form
|
||
if let Err(mut ssa_errors) = self.verify_ssa_form(function) {
|
||
local_errors.append(&mut ssa_errors);
|
||
}
|
||
|
||
// 2. Check dominance relations
|
||
if let Err(mut dom_errors) = self.verify_dominance(function) {
|
||
local_errors.append(&mut dom_errors);
|
||
}
|
||
|
||
// 3. Check control flow integrity
|
||
if let Err(mut cfg_errors) = self.verify_control_flow(function) {
|
||
local_errors.append(&mut cfg_errors);
|
||
}
|
||
// 4. Check merge-block value usage (ensure Phi is used)
|
||
if let Err(mut merge_errors) = self.verify_merge_uses(function) {
|
||
local_errors.append(&mut merge_errors);
|
||
}
|
||
// 5. Minimal checks for WeakRef/Barrier
|
||
if let Err(mut weak_barrier_errors) = self.verify_weakref_and_barrier(function) {
|
||
local_errors.append(&mut weak_barrier_errors);
|
||
}
|
||
// 6. Light barrier-context diagnostic (strict mode only)
|
||
if let Err(mut barrier_ctx) = self.verify_barrier_context(function) {
|
||
local_errors.append(&mut barrier_ctx);
|
||
}
|
||
// 7. Forbid legacy instructions (must be rewritten to Core-15)
|
||
if let Err(mut legacy_errors) = self.verify_no_legacy_ops(function) {
|
||
local_errors.append(&mut legacy_errors);
|
||
}
|
||
// 8. Async semantics: ensure checkpoints around await
|
||
if let Err(mut await_cp) = self.verify_await_checkpoints(function) {
|
||
local_errors.append(&mut await_cp);
|
||
}
|
||
|
||
// 9. PHI-off strict edge-copy policy (optional)
|
||
if crate::config::env::mir_no_phi() && crate::config::env::verify_edge_copy_strict() {
|
||
if let Err(mut ecs) = self.verify_edge_copy_strict(function) {
|
||
local_errors.append(&mut ecs);
|
||
}
|
||
}
|
||
|
||
// 10. Ret-block purity (optional, dev-only)
|
||
if crate::config::env::verify_ret_purity() {
|
||
if let Err(mut rpe) = self.verify_ret_block_purity(function) {
|
||
local_errors.append(&mut rpe);
|
||
}
|
||
}
|
||
|
||
if local_errors.is_empty() {
|
||
Ok(())
|
||
} else {
|
||
if dlog::on("NYASH_DEBUG_VERIFIER") {
|
||
let log = crate::runtime::get_global_ring0().log.clone();
|
||
log.debug(&format!(
|
||
"[VERIFY] {} errors in function {}",
|
||
local_errors.len(),
|
||
function.signature.name
|
||
));
|
||
for e in &local_errors {
|
||
match e {
|
||
VerificationError::MergeUsesPredecessorValue {
|
||
value,
|
||
merge_block,
|
||
pred_block,
|
||
} => {
|
||
log.debug(&format!(
|
||
" • MergeUsesPredecessorValue: value=%{:?} merge_bb={:?} pred_bb={:?} -- hint: insert/use Phi in merge block for values from predecessors",
|
||
value, merge_block, pred_block
|
||
));
|
||
}
|
||
VerificationError::DominatorViolation {
|
||
value,
|
||
use_block,
|
||
def_block,
|
||
} => {
|
||
log.debug(&format!(
|
||
" • DominatorViolation: value=%{:?} use_bb={:?} def_bb={:?} -- hint: ensure definition dominates use, or route via Phi",
|
||
value, use_block, def_block
|
||
));
|
||
}
|
||
VerificationError::InvalidPhi {
|
||
phi_value,
|
||
block,
|
||
reason,
|
||
} => {
|
||
log.debug(&format!(
|
||
" • InvalidPhi: phi_dst=%{:?} in bb={:?} reason={} -- hint: check inputs cover all predecessors and placed at block start",
|
||
phi_value, block, reason
|
||
));
|
||
}
|
||
VerificationError::InvalidWeakRefSource {
|
||
weak_ref,
|
||
block,
|
||
instruction_index,
|
||
reason,
|
||
} => {
|
||
log.debug(&format!(
|
||
" • InvalidWeakRefSource: weak=%{:?} at {}:{} reason='{}' -- hint: source must be WeakRef(new)/WeakNew; ensure creation precedes load and value flows correctly",
|
||
weak_ref, block, instruction_index, reason
|
||
));
|
||
}
|
||
VerificationError::InvalidBarrierPointer {
|
||
ptr,
|
||
block,
|
||
instruction_index,
|
||
reason,
|
||
} => {
|
||
log.debug(&format!(
|
||
" • InvalidBarrierPointer: ptr=%{:?} at {}:{} reason='{}' -- hint: barrier pointer must be a valid ref (not void/null); ensure it is defined and non-void",
|
||
ptr, block, instruction_index, reason
|
||
));
|
||
}
|
||
VerificationError::SuspiciousBarrierContext {
|
||
block,
|
||
instruction_index,
|
||
note,
|
||
} => {
|
||
log.debug(&format!(
|
||
" • SuspiciousBarrierContext: at {}:{} note='{}' -- hint: place barrier within ±2 of load/store/ref ops in same block or disable strict check",
|
||
block, instruction_index, note
|
||
));
|
||
}
|
||
other => {
|
||
log.debug(&format!(" • {:?}", other));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Err(local_errors)
|
||
}
|
||
}
|
||
|
||
/// When PHI-off strict mode is enabled, enforce that merge blocks do not
|
||
/// introduce self-copies and that each predecessor provides a Copy into the
|
||
/// merged destination for values used in the merge block that do not dominate it.
|
||
fn verify_edge_copy_strict(
|
||
&self,
|
||
function: &MirFunction,
|
||
) -> Result<(), Vec<VerificationError>> {
|
||
let mut errors = Vec::new();
|
||
let preds = utils::compute_predecessors(function);
|
||
let def_block = utils::compute_def_blocks(function);
|
||
let dominators = utils::compute_dominators(function);
|
||
|
||
for (merge_bid, merge_bb) in &function.blocks {
|
||
let p = preds.get(merge_bid).cloned().unwrap_or_default();
|
||
if p.len() < 2 {
|
||
continue; // Only enforce on real merges (>=2 predecessors)
|
||
}
|
||
|
||
// Collect values used in merge block
|
||
let mut used_values = std::collections::HashSet::new();
|
||
for sp in merge_bb.all_spanned_instructions() {
|
||
for v in sp.inst.used_values() {
|
||
used_values.insert(v);
|
||
}
|
||
}
|
||
|
||
// For each used value that doesn't dominate the merge block, enforce edge-copy policy
|
||
for &u in &used_values {
|
||
if let Some(&def_bb) = def_block.get(&u) {
|
||
// If the def dominates the merge block, edge copies are not required
|
||
let dominated = dominators
|
||
.get(merge_bid)
|
||
.map(|set| set.contains(&def_bb))
|
||
.unwrap_or(false);
|
||
if dominated && def_bb != *merge_bid {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// Merge block itself must not contain a Copy to the merged value
|
||
let has_self_copy_in_merge = merge_bb.instructions.iter().any(
|
||
|inst| matches!(inst, super::MirInstruction::Copy { dst, .. } if *dst == u),
|
||
);
|
||
if has_self_copy_in_merge {
|
||
errors.push(VerificationError::EdgeCopyStrictViolation {
|
||
block: *merge_bid,
|
||
value: u,
|
||
pred_block: None,
|
||
reason:
|
||
"merge block contains Copy to merged value; use predecessor copies only"
|
||
.to_string(),
|
||
});
|
||
}
|
||
|
||
// Each predecessor must provide a Copy into the merged destination
|
||
for pred in &p {
|
||
let Some(pbb) = function.blocks.get(pred) else {
|
||
continue;
|
||
};
|
||
let has_copy = pbb.instructions.iter().any(|inst| {
|
||
matches!(
|
||
inst,
|
||
super::MirInstruction::Copy { dst, .. } if *dst == u
|
||
)
|
||
});
|
||
if !has_copy {
|
||
errors.push(VerificationError::MergeUsesPredecessorValue {
|
||
value: u,
|
||
merge_block: *merge_bid,
|
||
pred_block: *pred,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if errors.is_empty() {
|
||
Ok(())
|
||
} else {
|
||
Err(errors)
|
||
}
|
||
}
|
||
|
||
/// Verify that any block ending with Return contains no side-effecting instructions before it.
|
||
/// Allowed before Return: Const, Copy, Phi, Nop only. Others are considered side-effecting for this policy.
|
||
fn verify_ret_block_purity(
|
||
&self,
|
||
function: &MirFunction,
|
||
) -> Result<(), Vec<VerificationError>> {
|
||
use super::MirInstruction as I;
|
||
let mut errors = Vec::new();
|
||
for (bid, bb) in &function.blocks {
|
||
if let Some(I::Return { .. }) = bb.terminator {
|
||
for (idx, inst) in bb.instructions.iter().enumerate() {
|
||
let allowed = matches!(
|
||
inst,
|
||
I::Const { .. } | I::Copy { .. } | I::Phi { .. } | I::Nop
|
||
);
|
||
if !allowed {
|
||
let name = format!("{:?}", inst);
|
||
errors.push(VerificationError::RetBlockSideEffect {
|
||
block: *bid,
|
||
instruction_index: idx,
|
||
name,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if errors.is_empty() {
|
||
Ok(())
|
||
} else {
|
||
Err(errors)
|
||
}
|
||
}
|
||
|
||
/// Reject legacy instructions that should be rewritten to Core-15 equivalents
|
||
/// Skips check when NYASH_VERIFY_ALLOW_LEGACY=1
|
||
fn verify_no_legacy_ops(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||
legacy::check_no_legacy_ops(function)
|
||
}
|
||
|
||
/// Ensure that each Await instruction (or ExternCall(env.future.await)) is immediately
|
||
/// preceded and followed by a checkpoint.
|
||
/// A checkpoint is either MirInstruction::Safepoint or ExternCall("env.runtime", "checkpoint").
|
||
fn verify_await_checkpoints(
|
||
&self,
|
||
function: &MirFunction,
|
||
) -> Result<(), Vec<VerificationError>> {
|
||
awaits::check_await_checkpoints(function)
|
||
}
|
||
|
||
/// Verify WeakRef/Barrier minimal semantics
|
||
fn verify_weakref_and_barrier(
|
||
&self,
|
||
function: &MirFunction,
|
||
) -> Result<(), Vec<VerificationError>> {
|
||
barrier::check_weakref_and_barrier(function)
|
||
}
|
||
|
||
/// Light diagnostic: Barrier should be near memory ops in the same block (best-effort)
|
||
/// Enabled only when NYASH_VERIFY_BARRIER_STRICT=1
|
||
fn verify_barrier_context(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||
barrier::check_barrier_context(function)
|
||
}
|
||
|
||
/// Verify SSA form properties
|
||
fn verify_ssa_form(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||
ssa::check_ssa_form(function)
|
||
}
|
||
|
||
/// Verify dominance relations (def must dominate use across blocks)
|
||
fn verify_dominance(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||
dom::check_dominance(function)
|
||
}
|
||
|
||
/// Verify control flow graph integrity
|
||
fn verify_control_flow(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||
cfg::check_control_flow(function)
|
||
}
|
||
|
||
/// Verify that blocks with multiple predecessors do not use predecessor-defined values directly.
|
||
/// In merge blocks, values coming from predecessors must be routed through Phi.
|
||
fn verify_merge_uses(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||
cfg::check_merge_uses(function)
|
||
}
|
||
|
||
/// Get all verification errors from the last run
|
||
pub fn get_errors(&self) -> &[VerificationError] {
|
||
&self.errors
|
||
}
|
||
|
||
/// Clear verification errors
|
||
pub fn clear_errors(&mut self) {
|
||
self.errors.clear();
|
||
}
|
||
}
|
||
|
||
impl Default for MirVerifier {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {}
|