Skip to content

Commit a7796fa

Browse files
feat(core): tape stability fixes, heal→MAPE-K, JIT LLVM-IR optimization
- autograd tape: ε-clamp tape_log, overflow-clamp tape_exp, fix tape_pow_int backward 0·∞→NaN poison; stability sweep (div/softmax/mean/sigmoid verified safe) - self-healer → MAPE-K: persistent cross-pass Knowledge (HealClassCounts.add / last_fixpoint_knowledge) + set-signature convergence; surfaced as heal-summary in --check and OMC_HEAL - HEAL as generator-repair operator: tests/heal_as_generator.rs (corrupt→heal recovery) - safe self-mod: fn_swap_verified determinism gate + invariant SET (array 3rd arg) - addressing: fns_on_subface (3-level HAddr descent); memo cache FIFO-bounded (memo_put) - JIT: omnimcode-codegen optimize() — curated scalar pass pipeline (mem2reg/instcombine/ gvn/simplifycfg), LSR excluded (known segfault), env-gated in dispatch (OMC_HBIT_JIT_OPT) - tests: substrate_v18 (18), heal_fixpoint (4), heal_as_generator, jit_optimize — all green - gitignore: knowledge-web data (knowledge.db/derived.db/caches/corpora) never committed Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6f7f9c9 commit a7796fa

8 files changed

Lines changed: 807 additions & 43 deletions

File tree

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,25 @@ logs/security/
6767
# Python virtualenvs
6868
**/.venv/
6969
**/venv/
70+
71+
# regenerable built-web caches (dictweb/mind)
72+
experiments/transformerless_lm/.dictcache/
73+
experiments/transformerless_lm/.mindcache/
74+
experiments/transformerless_lm/library/
75+
76+
# Knowledge-web DATA + caches — regenerable and FAR over GitHub's 100MB limit. NEVER commit.
77+
# knowledge.db (~5-9GB), derived.db, *.textbak / *.prefold_bak backups, WAL/SHM, embedding + fluency
78+
# caches, run logs/ledgers. The CODE (*.py) that builds them IS tracked; the data is shared elsewhere.
79+
experiments/transformerless_lm/*.db
80+
experiments/transformerless_lm/*.db-wal
81+
experiments/transformerless_lm/*.db-shm
82+
experiments/transformerless_lm/*.db.textbak
83+
experiments/transformerless_lm/*.prefold_bak
84+
experiments/transformerless_lm/knowledge_compressed.db*
85+
experiments/transformerless_lm/.kwebcache
86+
experiments/transformerless_lm/.fluencycache_*
87+
experiments/transformerless_lm/selfimprove_ledger.jsonl
88+
experiments/transformerless_lm/*.log
89+
# seed corpora (input data, downloadable/regenerable — e.g. dict_webster.txt is 27MB) + regenerable node lists
90+
experiments/transformerless_lm/corpora/
91+
experiments/transformerless_lm/nodes_new.json

omnimcode-cli/src/main.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// omnimcode-core/src/main.rs - OMNIcode Standalone Executable Entry Point
22

33
use omnimcode_core::parser::Parser;
4-
use omnimcode_core::interpreter::Interpreter;
4+
use omnimcode_core::interpreter::{Interpreter, last_fixpoint_knowledge};
55
use omnimcode_core::ast::{Expression, Statement};
66

77
use std::env;
@@ -1602,6 +1602,12 @@ fn check_program(path: &str) -> i32 {
16021602
path, total_fail, fail_diags.len(), lint.len(), total_warn,
16031603
iters, outcome);
16041604
}
1605+
// MAPE-K: the cross-pass Knowledge breakdown (what the healer did over ALL
1606+
// passes — the per-pass counter only sees the final clean pass).
1607+
let heal_summary = last_fixpoint_knowledge().summary();
1608+
if !heal_summary.is_empty() {
1609+
println!(" heal-summary (all passes): {}", heal_summary);
1610+
}
16051611
for d in &fail_diags {
16061612
println!(" heal: {}", d);
16071613
}
@@ -1659,6 +1665,10 @@ fn execute_program(source: &str) -> Result<(), String> {
16591665
"--- OMC_HEAL: {} diagnostic(s) across {} iteration(s) ({}) ---",
16601666
diagnostics.len(), iters, outcome
16611667
);
1668+
let heal_summary = last_fixpoint_knowledge().summary();
1669+
if !heal_summary.is_empty() {
1670+
eprintln!(" heal-summary (all passes): {}", heal_summary);
1671+
}
16621672
for d in &diagnostics {
16631673
eprintln!(" {}", d);
16641674
}

omnimcode-codegen/src/lib.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,49 @@ impl<'ctx> JitContext<'ctx> {
730730
/// `FunctionValue` so callers can verify it.
731731
///
732732
/// Session B constraints:
733+
/// Run a curated LLVM IR optimization pipeline over the module (mem2reg + scalar
734+
/// peephole/redundancy passes) using LLVM's new pass manager. Run ONCE after all
735+
/// functions are lowered, before extraction/execution.
736+
///
737+
/// DELIBERATELY excludes Loop Strength Reduction and all loop passes: LSR crashed
738+
/// non-deterministically at `OptimizationLevel::Default` on the LCSSA-form loops the
739+
/// dual-band lowerer emits (see `new`), which is why the JIT engine itself runs at
740+
/// `None`. These scalar passes recover most of that traded-away peephole optimization
741+
/// WITHOUT touching the loop machinery that crashes.
742+
///
743+
/// Conservative + non-fatal: only optimizes a module that VERIFIES (never hands
744+
/// malformed IR to the optimizer — that's exactly the case that segfaults), and any
745+
/// pass-pipeline error leaves the module untouched (the JIT still works, unoptimized).
746+
pub fn optimize(&self) -> Result<(), CodegenError> {
747+
use inkwell::passes::PassBuilderOptions;
748+
use inkwell::targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetMachine};
749+
if let Err(msg) = self.module.verify() {
750+
return Err(format!("module did not verify; optimization skipped: {}", msg.to_string()));
751+
}
752+
Target::initialize_native(&InitializationConfig::default())
753+
.map_err(|e| format!("initialize_native: {}", e))?;
754+
let triple = TargetMachine::get_default_triple();
755+
let target = Target::from_triple(&triple).map_err(|e| e.to_string())?;
756+
let tm = target
757+
.create_target_machine(
758+
&triple,
759+
"", // generic CPU — IR-level passes don't need host tuning
760+
"",
761+
OptimizationLevel::None,
762+
RelocMode::Default,
763+
CodeModel::Default,
764+
)
765+
.ok_or_else(|| "create_target_machine returned None".to_string())?;
766+
// Scalar pipeline ONLY — no loop passes, no LSR (the known crasher).
767+
self.module
768+
.run_passes(
769+
"mem2reg,instcombine,reassociate,gvn,simplifycfg",
770+
&tm,
771+
PassBuilderOptions::create(),
772+
)
773+
.map_err(|e| e.to_string())
774+
}
775+
733776
/// - All params and the return type are `i64`.
734777
/// - Only the int-flavored op subset listed in the crate docs.
735778
/// - `Op::Call(name, _)` must target the function being lowered
@@ -984,6 +1027,17 @@ impl<'ctx> JitContext<'ctx> {
9841027
eprintln!("[OMC_HBIT_JIT_DUMP_IR]");
9851028
eprintln!("{}", self.module.print_to_string().to_string());
9861029
}
1030+
// L1.5+ optional IR optimization (opt-in via OMC_HBIT_JIT_OPT=1): recover the
1031+
// peephole optimization traded away when the engine was lowered to
1032+
// OptimizationLevel::None (see `new`), via a scalar pipeline that EXCLUDES the LSR
1033+
// pass that crashed. Default OFF out of respect for that segfault history; verify-gated
1034+
// and non-fatal (a pass error leaves the module unoptimized-but-working). Runs ONCE here,
1035+
// after all functions are lowered + cleaned, before extraction triggers MCJIT codegen.
1036+
if std::env::var("OMC_HBIT_JIT_OPT").as_deref() == Ok("1") {
1037+
if let Err(e) = self.optimize() {
1038+
eprintln!("[OMC_HBIT_JIT_OPT] optimization skipped: {}", e);
1039+
}
1040+
}
9871041
// Phase 3: extract fn pointers for everything that survived
9881042
// both lowering and dependency cleanup.
9891043
let mut out: HashMap<String, JittedFn> = HashMap::new();
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//! JIT IR-optimization tests (JitContext::optimize).
2+
//!
3+
//! optimize() runs a curated scalar pass pipeline (mem2reg + instcombine/reassociate/
4+
//! gvn/simplifycfg) via LLVM's new pass manager. The pipeline DELIBERATELY excludes Loop
5+
//! Strength Reduction — LSR crashed at OptimizationLevel::Default on the dual-band lowerer's
6+
//! LCSSA-form loops (why the engine runs at None). These tests prove the pipeline (a) runs
7+
//! without crashing on real lowered IR including a LOOP-with-locals (the danger zone), and
8+
//! (b) PRESERVES semantics — the optimized JIT'd function still returns the correct value.
9+
10+
#![cfg(feature = "llvm-jit")]
11+
12+
use inkwell::context::Context;
13+
use omnimcode_codegen::JitContext;
14+
use omnimcode_core::ast::Pos;
15+
use omnimcode_core::bytecode::{CompiledFunction, Const, Op};
16+
use omnimcode_core::parser::Parser;
17+
18+
fn skeleton(name: &str, params: Vec<&str>, ops: Vec<Op>, constants: Vec<Const>) -> CompiledFunction {
19+
let n = ops.len();
20+
let param_types = vec![None; params.len()];
21+
CompiledFunction {
22+
name: name.to_string(),
23+
params: params.into_iter().map(String::from).collect(),
24+
param_types,
25+
return_type: None,
26+
op_positions: vec![Pos::unknown(); n],
27+
pragmas: Vec::new(),
28+
call_cache: (0..n).map(|_| std::cell::Cell::new(0)).collect(),
29+
ops,
30+
constants,
31+
}
32+
}
33+
34+
#[test]
35+
fn optimize_preserves_simple_arithmetic() {
36+
let f = skeleton(
37+
"double",
38+
vec!["x"],
39+
vec![Op::LoadParam(0), Op::LoadParam(0), Op::Add, Op::Return],
40+
vec![],
41+
);
42+
let ctx = Context::create();
43+
let jit = JitContext::new(&ctx).expect("jit ctx");
44+
jit.lower_function(&f).expect("lower");
45+
jit.optimize().expect("optimize ok");
46+
unsafe {
47+
let native = jit.get_i64_i64("double").expect("jit fn");
48+
assert_eq!(native.call(21), 42, "optimized fn must still double");
49+
assert_eq!(native.call(-5), -10);
50+
}
51+
}
52+
53+
#[test]
54+
fn optimize_preserves_loop_with_locals() {
55+
// sum_to_n: allocas (StoreVar/LoadVar/AssignVar → mem2reg promotes) + a while LOOP
56+
// (the LSR danger zone). The scalar pipeline must survive it AND keep the result exact.
57+
let f = skeleton(
58+
"sum_to_n",
59+
vec!["n"],
60+
vec![
61+
Op::LoadConst(0),
62+
Op::StoreVar("s".into()),
63+
Op::LoadConst(1),
64+
Op::StoreVar("k".into()),
65+
Op::LoadVar("k".into()),
66+
Op::LoadParam(0),
67+
Op::Le,
68+
Op::JumpIfFalse(10),
69+
Op::Pop,
70+
Op::LoadVar("s".into()),
71+
Op::LoadVar("k".into()),
72+
Op::Add,
73+
Op::AssignVar("s".into()),
74+
Op::LoadVar("k".into()),
75+
Op::LoadConst(1),
76+
Op::Add,
77+
Op::AssignVar("k".into()),
78+
Op::Jump(-14),
79+
Op::Pop,
80+
Op::LoadVar("s".into()),
81+
Op::Return,
82+
],
83+
vec![Const::Int(0), Const::Int(1)],
84+
);
85+
let ctx = Context::create();
86+
let jit = JitContext::new(&ctx).expect("jit ctx");
87+
jit.lower_function(&f).expect("lower");
88+
jit.optimize().expect("optimize must not error on a loop+locals fn");
89+
unsafe {
90+
let native = jit.get_i64_i64("sum_to_n").expect("jit fn");
91+
assert_eq!(native.call(10), 55, "optimized loop must still sum 1..10");
92+
assert_eq!(native.call(100), 5050);
93+
assert_eq!(native.call(0), 0);
94+
assert_eq!(native.call(1), 1);
95+
}
96+
}
97+
98+
#[test]
99+
fn jit_module_dispatch_with_opt_env_preserves_results() {
100+
// The DISPATCH path (jit_module) wires optimize() behind OMC_HBIT_JIT_OPT=1. With it on,
101+
// a cross-fn-call program must still build a correct dispatch table. Env leakage to other
102+
// parallel tests is benign — the passes preserve semantics, so any test still passes.
103+
std::env::set_var("OMC_HBIT_JIT_OPT", "1");
104+
let source = r#"
105+
fn helper(x) { return x * 2; }
106+
fn caller(x) { return helper(x) + 1; }
107+
"#;
108+
let mut parser = Parser::new(source);
109+
let statements = parser.parse().expect("parse");
110+
let module = omnimcode_core::compiler::compile_program(&statements).expect("compile");
111+
let ctx = Context::create();
112+
let jit = JitContext::new(&ctx).expect("jit");
113+
let jitted = jit.jit_module(&module).expect("jit_module (opt path)");
114+
let caller = jitted.get("caller").expect("caller fn");
115+
assert_eq!(caller.call(&[10]).expect("call"), 21, "opt dispatch must preserve caller=helper(x)+1");
116+
assert_eq!(caller.call(&[100]).expect("call"), 201);
117+
assert_eq!(caller.call(&[0]).expect("call"), 1);
118+
std::env::remove_var("OMC_HBIT_JIT_OPT");
119+
}

0 commit comments

Comments
 (0)