Files
hakorune/src/mir/hints.rs
nyash-codex 6ecd8f7f52 feat(runtime): Phase 103 CoreServices Optional化 - Memory Constraints対応
- 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)
2025-12-03 13:59:06 +09:00

334 lines
11 KiB
Rust

#![allow(dead_code)]
//! MIR Hints — zero-cost structural guidance (scaffold)
//!
//! Hints guide lowering/verification without affecting semantics.
//! They must be stripped before final IR emission.
use crate::runtime::get_global_ring0;
/// Lightweight set of hint kinds (scaffold).
#[derive(Debug, Clone)]
pub enum HintKind {
ScopeEnter(u32),
ScopeLeave(u32),
Defer(Vec<String>),
JoinResult(String),
LoopCarrier(Vec<String>),
LoopHeader,
LoopLatch,
NoEmptyPhi,
}
/// Hint sink (no-op). Backends/resolvers may hook into this later.
#[derive(Default)]
pub struct HintSink {
enabled: bool,
}
impl HintSink {
pub fn new() -> Self {
Self { enabled: false }
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
fn cfg() -> HintCfg {
// New unified env: NYASH_MIR_HINTS="<target>|<filters>"
// Examples:
// NYASH_MIR_HINTS=trace|all -> stderr + all kinds
// NYASH_MIR_HINTS=tmp/hints.jsonl|loop -> jsonl file + loop-only
// NYASH_MIR_HINTS=jsonl=tmp/h.jsonl|scope|join
// Back-compat: NYASH_MIR_TRACE_HINTS=1 -> stderr + all kinds
if let Ok(spec) = std::env::var("NYASH_MIR_HINTS") {
return HintCfg::parse(&spec);
}
if std::env::var("NYASH_MIR_TRACE_HINTS").ok().as_deref() == Some("1") {
return HintCfg {
sink: HintSinkTarget::Stderr,
kinds: HintKinds::All,
};
}
HintCfg {
sink: HintSinkTarget::None,
kinds: HintKinds::None,
}
}
#[inline]
pub fn record(&mut self, hint: HintKind) {
// Resolve config (env-based). Lightweight and robust; acceptable to parse per call.
let cfg = Self::cfg();
if matches!(cfg.sink, HintSinkTarget::None) {
return;
}
// Filter kinds
let k = hint_tag(&hint);
if !cfg.kinds.contains(k) {
return;
}
match cfg.sink {
HintSinkTarget::None => {}
HintSinkTarget::Stderr => match hint {
HintKind::ScopeEnter(id) => {
get_global_ring0()
.log
.debug(&format!("[mir][hint] ScopeEnter({})", id))
}
HintKind::ScopeLeave(id) => {
get_global_ring0()
.log
.debug(&format!("[mir][hint] ScopeLeave({})", id))
}
HintKind::Defer(calls) => get_global_ring0()
.log
.debug(&format!("[mir][hint] Defer({})", calls.join(";"))),
HintKind::JoinResult(var) => get_global_ring0()
.log
.debug(&format!("[mir][hint] JoinResult({})", var)),
HintKind::LoopCarrier(vars) => {
get_global_ring0()
.log
.debug(&format!("[mir][hint] LoopCarrier({})", vars.join(",")))
}
HintKind::LoopHeader => {
get_global_ring0().log.debug("[mir][hint] LoopHeader")
}
HintKind::LoopLatch => {
get_global_ring0().log.debug("[mir][hint] LoopLatch")
}
HintKind::NoEmptyPhi => get_global_ring0().log.debug("[mir][hint] NoEmptyPhi"),
},
HintSinkTarget::Jsonl(ref path) => {
// Append one JSON object per line. Best-effort; ignore errors.
let _ = append_jsonl(path, &hint);
}
}
}
#[inline]
pub fn scope_enter(&mut self, id: u32) {
self.record(HintKind::ScopeEnter(id));
}
#[inline]
pub fn scope_leave(&mut self, id: u32) {
self.record(HintKind::ScopeLeave(id));
}
#[inline]
pub fn defer_calls<S: Into<String>>(&mut self, calls: impl IntoIterator<Item = S>) {
self.record(HintKind::Defer(
calls.into_iter().map(|s| s.into()).collect(),
))
}
#[inline]
pub fn join_result<S: Into<String>>(&mut self, var: S) {
self.record(HintKind::JoinResult(var.into()));
}
#[inline]
pub fn loop_carrier<S: Into<String>>(&mut self, vars: impl IntoIterator<Item = S>) {
self.record(HintKind::LoopCarrier(
vars.into_iter().map(|s| s.into()).collect(),
))
}
#[inline]
pub fn loop_header(&mut self) {
self.record(HintKind::LoopHeader);
}
#[inline]
pub fn loop_latch(&mut self) {
self.record(HintKind::LoopLatch);
}
#[inline]
pub fn no_empty_phi(&mut self) {
self.record(HintKind::NoEmptyPhi);
}
}
// ---- Unified hint config parser ----
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum HintTag {
Scope,
Join,
Loop,
Phi,
}
fn hint_tag(h: &HintKind) -> HintTag {
match h {
HintKind::ScopeEnter(_) | HintKind::ScopeLeave(_) | HintKind::Defer(_) => HintTag::Scope,
HintKind::JoinResult(_) => HintTag::Join,
HintKind::LoopCarrier(_) | HintKind::LoopHeader | HintKind::LoopLatch => HintTag::Loop,
HintKind::NoEmptyPhi => HintTag::Phi,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HintKinds {
None,
Some {
scope: bool,
join: bool,
loopk: bool,
phi: bool,
},
All,
}
impl HintKinds {
fn contains(&self, tag: HintTag) -> bool {
match self {
HintKinds::All => true,
HintKinds::None => false,
HintKinds::Some {
scope,
join,
loopk,
phi,
} => match tag {
HintTag::Scope => *scope,
HintTag::Join => *join,
HintTag::Loop => *loopk,
HintTag::Phi => *phi,
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum HintSinkTarget {
None,
Stderr,
Jsonl(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct HintCfg {
sink: HintSinkTarget,
kinds: HintKinds,
}
impl HintCfg {
fn parse(spec: &str) -> Self {
let mut sink = HintSinkTarget::None;
let mut kinds = HintKinds::None;
let mut saw_filter = false;
for tok in spec.split('|').map(|s| s.trim()).filter(|s| !s.is_empty()) {
let tl = tok.to_ascii_lowercase();
if tl == "off" {
sink = HintSinkTarget::None;
kinds = HintKinds::None;
continue;
}
if tl == "trace" || tl == "stderr" {
sink = HintSinkTarget::Stderr;
continue;
}
if tl.starts_with("jsonl=") {
sink = HintSinkTarget::Jsonl(tok[6..].trim().to_string());
continue;
}
// Heuristic: token looks like a path → jsonl
if tok.contains('/') || tok.contains('.') {
sink = HintSinkTarget::Jsonl(tok.to_string());
continue;
}
// Filters
match tl.as_str() {
"all" => {
kinds = HintKinds::All;
saw_filter = true;
}
"scope" => {
kinds = merge_kind(kinds, |k| HintKinds::Some {
scope: true,
join: matches!(k, HintKinds::Some { join: true, .. } | HintKinds::All),
loopk: matches!(k, HintKinds::Some { loopk: true, .. } | HintKinds::All),
phi: matches!(k, HintKinds::Some { phi: true, .. } | HintKinds::All),
})
}
"join" => {
kinds = merge_kind(kinds, |k| HintKinds::Some {
scope: matches!(k, HintKinds::Some { scope: true, .. } | HintKinds::All),
join: true,
loopk: matches!(k, HintKinds::Some { loopk: true, .. } | HintKinds::All),
phi: matches!(k, HintKinds::Some { phi: true, .. } | HintKinds::All),
})
}
"loop" => {
kinds = merge_kind(kinds, |k| HintKinds::Some {
scope: matches!(k, HintKinds::Some { scope: true, .. } | HintKinds::All),
join: matches!(k, HintKinds::Some { join: true, .. } | HintKinds::All),
loopk: true,
phi: matches!(k, HintKinds::Some { phi: true, .. } | HintKinds::All),
})
}
"phi" => {
kinds = merge_kind(kinds, |k| HintKinds::Some {
scope: matches!(k, HintKinds::Some { scope: true, .. } | HintKinds::All),
join: matches!(k, HintKinds::Some { join: true, .. } | HintKinds::All),
loopk: matches!(k, HintKinds::Some { loopk: true, .. } | HintKinds::All),
phi: true,
})
}
_ => {}
}
}
if !saw_filter && !matches!(kinds, HintKinds::All) {
// default to all if no filter specified
kinds = HintKinds::All;
}
// default sink if only filters appear
if matches!(sink, HintSinkTarget::None) {
sink = HintSinkTarget::Stderr;
}
HintCfg { sink, kinds }
}
}
fn merge_kind<F: FnOnce(HintKinds) -> HintKinds>(k: HintKinds, f: F) -> HintKinds {
match k {
HintKinds::All => HintKinds::All,
x => f(x),
}
}
fn append_jsonl(path: &str, hint: &HintKind) -> std::io::Result<()> {
use std::io::Write;
let mut obj = serde_json::json!({ "kind": kind_name(hint) });
match hint {
HintKind::ScopeEnter(id) => obj["value"] = serde_json::json!({"enter": id}),
HintKind::ScopeLeave(id) => obj["value"] = serde_json::json!({"leave": id}),
HintKind::Defer(calls) => obj["value"] = serde_json::json!({"defer": calls}),
HintKind::JoinResult(v) => obj["value"] = serde_json::json!({"join": v}),
HintKind::LoopCarrier(vs) => obj["value"] = serde_json::json!({"carrier": vs}),
HintKind::LoopHeader => obj["value"] = serde_json::json!({"loop": "header"}),
HintKind::LoopLatch => obj["value"] = serde_json::json!({"loop": "latch"}),
HintKind::NoEmptyPhi => obj["value"] = serde_json::json!({"phi": "no_empty"}),
}
let line = obj.to_string();
if let Some(dir) = std::path::Path::new(path).parent() {
let _ = std::fs::create_dir_all(dir);
}
let mut f = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)?;
writeln!(f, "{}", line)?;
Ok(())
}
fn kind_name(h: &HintKind) -> &'static str {
match h {
HintKind::ScopeEnter(_) => "ScopeEnter",
HintKind::ScopeLeave(_) => "ScopeLeave",
HintKind::Defer(_) => "Defer",
HintKind::JoinResult(_) => "JoinResult",
HintKind::LoopCarrier(_) => "LoopCarrier",
HintKind::LoopHeader => "LoopHeader",
HintKind::LoopLatch => "LoopLatch",
HintKind::NoEmptyPhi => "NoEmptyPhi",
}
}