diff --git a/Cargo.toml b/Cargo.toml index aa77b1d..9ed25ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ members = [ "cubecode", "cubecrypt", "cubesys", + "cubedbt", + "cubeai", "cube-bench", ] diff --git a/cubeai/Cargo.toml b/cubeai/Cargo.toml new file mode 100644 index 0000000..cf2f13e --- /dev/null +++ b/cubeai/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cubeai" +version = "0.1.0" +edition.workspace = true +license.workspace = true +description = "CUBELinux-2 AI layer: models that operate on cube-stored traces/graphs to classify blocks, infer higher-level operations, and suggest new code sequences (PDF Package 4, §551)." + +[dependencies] +cubecoords = { path = "../cubecoords" } +cubestore = { path = "../cubestore" } +cubecode = { path = "../cubecode" } +cubedbt = { path = "../cubedbt" } diff --git a/cubeai/src/lib.rs b/cubeai/src/lib.rs new file mode 100644 index 0000000..468a57a --- /dev/null +++ b/cubeai/src/lib.rs @@ -0,0 +1,327 @@ +//! CUBELinux-2 AI layer. +//! +//! Per the PDF (Package 4, §551): *"cubeai: models that operate on +//! cube‑stored traces/graphs to classify blocks, infer higher‑level +//! operations, and suggest new code sequences or orchestrations."* +//! +//! This crate is the *structured decision* layer sitting on top of the +//! substrate (and on [`cubedbt`]). It is deterministic and dependency-free +//! today: the "model" is a transparent, inspectable classifier/suggester that +//! operates on the same [`CubeStore`] / [`CodeCell`] / [`Behavior`] types the +//! rest of the workspace uses. A learned model can later implement the same +//! traits without changing callers. +//! +//! Pipeline (the end-to-end story from §549–§551 + §554): +//! trace (cube) → classify blocks → infer higher-level op → +//! suggest `TranslationRule`s → hand to `cubedbt` to patch + run (mimic). + +use cubecode::opcode::Op; +use cubecode::{Behavior, CodeCell, Kind}; +use cubecoords::{CubeHeader, Czyx}; +use cubedbt::{store_rule, OpClass, TranslationRule}; +use cubestore::{CubeStore, HashBackend}; + +/// `C` axis band where captured traces are stored (fed by `cubetrace` in the +/// full stack; here traces are ingested directly via [`CubeAi::ingest_trace`]). +pub const C_TRACE: u8 = 230; + +/// A captured basic block: its coordinate, the op-class histogram observed +/// during tracing, and the behavior descriptors attached (from the header or +/// inferred by the trace layer). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BlockTrace { + pub label: Czyx, + /// Count of each [`OpClass`] seen in the block (`OpClass::Any` unused). + pub class_counts: [u16; 20], + /// Behavior descriptors carried by / inferred for this block. + pub behavior: Behavior, +} + +impl BlockTrace { + /// Build from a decoded code cell, deriving the op-class histogram and + /// reading any behavior descriptors from its header flags. + pub fn from_cell(cell: &CodeCell) -> BlockTrace { + let mut counts = [0u16; 20]; + for op in &cell.code { + let c = OpClass::of(op) as usize; + if c < 20 { + counts[c] += 1; + } + } + BlockTrace { + label: cell.label, + class_counts: counts, + behavior: Behavior::from_flags(cell.header.flags.bits()), + } + } + + /// Total op count. + pub fn total(&self) -> u32 { + self.class_counts.iter().map(|&c| c as u32).sum() + } +} + +/// A higher-level classification of a block, inferred by [`BlockClassifier`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum BlockKind { + /// Mostly arithmetic/logic — a computation block. + Computation, + /// Dominated by control flow (jumps / comparisons) — a branch block. + Branch, + /// Contains call links to other records — a call/composition block. + CallTrampoline, + /// Heavy I/O or network descriptors — an I/O section. + IoSection, + /// Otherwise: a plain linear sequence. + Sequence, +} + +/// Deterministic classifier: maps a [`BlockTrace`] to a [`BlockKind`] from its +/// op-class histogram and behavior descriptors. (The transparent "model".) +pub struct BlockClassifier; + +impl BlockClassifier { + pub fn classify(block: &BlockTrace) -> BlockKind { + let c = &block.class_counts; + let arith = c[OpClass::Add as usize] + + c[OpClass::Sub as usize] + + c[OpClass::Mul as usize] + + c[OpClass::Div as usize] + + c[OpClass::Mod as usize] + + c[OpClass::And as usize] + + c[OpClass::Or as usize] + + c[OpClass::Xor as usize] + + c[OpClass::Shl as usize] + + c[OpClass::Shr as usize]; + let ctrl = c[OpClass::Eq as usize] + + c[OpClass::Ne as usize] + + c[OpClass::Lt as usize] + + c[OpClass::Gt as usize] + + c[OpClass::Le as usize] + + c[OpClass::Ge as usize]; + let jumps = c[OpClass::Const as usize]; + let calls = c[OpClass::CallLink as usize]; + + if block.behavior.0 & (Behavior::IO_HEAVY | Behavior::NETWORK) != 0 { + return BlockKind::IoSection; + } + if calls > 0 { + return BlockKind::CallTrampoline; + } + if ctrl > 0 && ctrl >= arith { + return BlockKind::Branch; + } + if arith > 0 { + return BlockKind::Computation; + } + // Fallback: anything with comparisons/jumps is a branch, else sequence. + if jumps > 0 || ctrl > 0 { + BlockKind::Branch + } else { + BlockKind::Sequence + } + } +} + +/// Suggest transformations for a block, returning candidate [`TranslationRule`]s +/// the DBT layer can apply (PDF: "suggest new code sequences or +/// orchestrations"). Deterministic and local: each suggestion records *why*. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Suggestion { + pub rule: TranslationRule, + pub rationale: String, +} + +/// The AI runtime over a cube store: ingests traces, classifies, and suggests +/// DBT rules. Writes suggested rules into the `c220` rule band via +/// [`store_rule`] so `cubedbt::DbRuntime` can discover and apply them. +pub struct CubeAi { + store: CubeStore, + next_trace_x: u8, + next_rule_x: u8, +} + +impl CubeAi { + /// Build an empty AI runtime over a fresh in-memory store. + pub fn new() -> Self { + CubeAi { + store: CubeStore::new(HashBackend::new()), + next_trace_x: 1, + next_rule_x: 1, + } + } + + /// Ingest a captured trace (e.g. from `cubetrace`): store the block under + /// the `c230` trace band and return its coordinate. + pub fn ingest_trace(&mut self, block: &BlockTrace) -> Czyx { + let label = Czyx::new(C_TRACE, 1, 1, self.next_trace_x); + self.next_trace_x = self.next_trace_x.wrapping_add(1).max(1); + let mut h = CubeHeader::new(); + h.title = Some(format!("trace:{:?}", block.label)); + h.doc_type = Some(Kind::Other.as_str().into()); + h.size_bytes = Some(block.class_counts.len() as u64 * 2); + h.flags.0 |= block.behavior.to_flags(); + h.refresh_flags(); + // Body: the raw histogram (20 x u16 le). + let mut body = Vec::with_capacity(40); + for c in &block.class_counts { + body.extend_from_slice(&c.to_le_bytes()); + } + self.store.put_record(label, &h, &body); + label + } + + /// Classify a single block. + pub fn classify(&self, block: &BlockTrace) -> BlockKind { + BlockClassifier::classify(block) + } + + /// Produce suggestions for a block (does not yet persist them). + pub fn suggest(&self, block: &BlockTrace) -> Vec { + let kind = BlockClassifier::classify(block); + let mut out = Vec::new(); + match kind { + BlockKind::Computation => { + // Suggest vectorizing a repeated multiply-by-constant: + // Mul(const) -> Const(shift?), but keep it conservative: + // replace Mul with a left-shift when operand is a power of two. + out.push(Suggestion { + rule: TranslationRule { + name: format!("compute-opt:{:?}", block.label), + target: OpClass::Mul, + fragment: vec![Op::Shl], + }, + rationale: "computation block: Mul may be replaced by Shl (power-of-two)".into(), + }); + } + BlockKind::Branch => { + out.push(Suggestion { + rule: TranslationRule { + name: format!("branch-opt:{:?}", block.label), + target: OpClass::Any, + fragment: vec![Op::Nop], + }, + rationale: "branch block: redundant ops may be collapsed to Nop".into(), + }); + } + BlockKind::IoSection => { + out.push(Suggestion { + rule: TranslationRule { + name: format!("io-batch:{:?}", block.label), + target: OpClass::CallLink, + fragment: vec![Op::CallLink(0)], + }, + rationale: "io section: calls may be coalesced via a batched variant".into(), + }); + } + _ => {} + } + out + } + + /// Ingest a block and persist its suggestions as DBT rules in the `c220` + /// band. Returns the stored rule coordinates (empty if no suggestions). + pub fn ingest_and_suggest(&mut self, block: &BlockTrace) -> Vec { + self.ingest_trace(block); + let sugs = self.suggest(block); + let mut coords = Vec::new(); + for s in &sugs { + let c = store_rule(&mut self.store, &s.rule, self.next_rule_x); + self.next_rule_x = self.next_rule_x.wrapping_add(1).max(1); + coords.push(c); + } + coords + } + + /// Borrow the backing store (e.g. to hand to `cubedbt::DbRuntime`). + pub fn store(&self) -> &CubeStore { + &self.store + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cubecode::opcode::Op; + use cubecoords::Czyx; + + fn cell(coord: Czyx, code: Vec, beh: Behavior) -> CodeCell { + let mut h = CubeHeader::new(); + h.title = Some("blk".into()); + h.doc_type = Some(Kind::Fn.as_str().into()); + h.flags.0 |= beh.to_flags(); + h.refresh_flags(); + CodeCell::from_record(coord, &h, &cubecode::opcode::encode(&code)) + .expect("cell is valid bytecode") + } + + #[test] + fn trace_classifies_computation() { + let c = cell( + Czyx::new(1, 1, 1, 1), + vec![Op::Const(3), Op::Const(4), Op::Mul, Op::Halt], + Behavior(Behavior::PURE), + ); + let t = BlockTrace::from_cell(&c); + assert_eq!(BlockClassifier::classify(&t), BlockKind::Computation); + // total() sums op-class histogram; Halt maps to OpClass::Any (index 20, + // outside the [0..20) histogram), so 3 counted ops (Const, Const, Mul). + assert_eq!(t.total(), 3); + } + + #[test] + fn trace_classifies_io_section() { + let c = cell( + Czyx::new(1, 1, 1, 2), + vec![Op::Const(1), Op::Halt], + Behavior(Behavior::IO_HEAVY | Behavior::NETWORK), + ); + let t = BlockTrace::from_cell(&c); + assert_eq!(BlockClassifier::classify(&t), BlockKind::IoSection); + } + + #[test] + fn trace_classifies_call_trampoline() { + let c = cell( + Czyx::new(1, 1, 1, 3), + vec![Op::CallLink(0), Op::CallLink(1), Op::Halt], + Behavior::default(), + ); + let t = BlockTrace::from_cell(&c); + assert_eq!(BlockClassifier::classify(&t), BlockKind::CallTrampoline); + } + + #[test] + fn suggest_emits_rule_and_persists() { + let c = cell( + Czyx::new(1, 1, 1, 4), + vec![Op::Const(3), Op::Const(4), Op::Mul, Op::Halt], + Behavior(Behavior::PURE), + ); + let t = BlockTrace::from_cell(&c); + let mut ai = CubeAi::new(); + let coords = ai.ingest_and_suggest(&t); + assert_eq!(coords.len(), 1, "computation block suggests one rule"); + assert_eq!(coords[0].c, rule_band()); + // The stored rule is discoverable by cubedbt. + let rt = cubedbt::DbRuntime::new(ai.store().clone()); + assert_eq!(rt.discover_rules().len(), 1); + } + + #[test] + fn ingest_trace_stores_under_c230() { + let c = cell(Czyx::new(1, 1, 1, 5), vec![Op::Halt], Behavior::default()); + let t = BlockTrace::from_cell(&c); + let mut ai = CubeAi::new(); + let coord = ai.ingest_trace(&t); + assert_eq!(coord.c, C_TRACE); + } + + // Helper: the rule band constant lives in cubedbt; assert equality without + // importing the const name directly (kept local to the test). + fn rule_band() -> u8 { + cubedbt::C_DBT_RULE + } +} + + diff --git a/cubedbt/Cargo.toml b/cubedbt/Cargo.toml new file mode 100644 index 0000000..114d549 --- /dev/null +++ b/cubedbt/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cubedbt" +version = "0.1.0" +edition.workspace = true +license.workspace = true +description = "CUBELinux-2 DBT runtime: reads CZYX-stored translation rules and code fragments and patches them into a code cache to execute mimicked behavior (PDF Package 4, §549)." + +[dependencies] +cubecoords = { path = "../cubecoords" } +cubestore = { path = "../cubestore" } +cubecode = { path = "../cubecode" } diff --git a/cubedbt/src/lib.rs b/cubedbt/src/lib.rs new file mode 100644 index 0000000..cd9c1d3 --- /dev/null +++ b/cubedbt/src/lib.rs @@ -0,0 +1,429 @@ +//! CUBELinux-2 DBT (dynamic binary translation) runtime. +//! +//! Per the PDF (Package 4, §549): *"cubedbt: a runtime that reads CZYX‑stored +//! translation rules and code fragments and patches them into a code cache to +//! execute mimicked behavior."* +//! +//! This crate is the substrate for the PDF's "watch a binary, archive its +//! behavior structurally, replay or transform it as if it were its own program" +//! story (§554). It is deliberately dependency-free and operates on the real +//! `CubeStore` / `CodeCell` / `Vm` types from the rest of the workspace, so it +//! is exercisable today on `HashBackend` and slots into `ConcurrentStore` +//! later without an API change. +//! +//! Model +//! ----- +//! * A **translation rule** is a CZYX record (`Kind::Variant`) whose body is a +//! serialized [`TranslationRule`]. It names an *op kind* it can replace and +//! carries a *replacement fragment* (a `Vec`). The rule record links to +//! the original code cell it may substitute (the association edge). +//! * A **code cache** is the patched working set: `patch()` rewrites an +//! original `CodeCell`'s bytecode under the eligible rules and writes the +//! result back as a new record (preserving the original header's kind/title +//! and behavior descriptors, so it runs through the same `Vm`). +//! * A **DBT runtime** discovers rules in a store and can `mimic` a target +//! coordinate: fetch original → apply rules → run the patched version in the +//! `Vm`, returning the `RunResult`. + +use cubecode::{opcode::Op, CodeCell, Kind, Vm}; +use cubecoords::{Czyx, CubeHeader}; +use cubestore::{CubeBackend, CubeStore, HashBackend}; + +/// `C` axis band where DBT translation rules are stored, kept distinct from the +/// `c210` OS-kernel band (`cubecode::C_OS_KERNEL`) and the `c200` snapshot data. +pub const C_DBT_RULE: u8 = 220; + +/// `C` axis band where patched/mimicked code-cache entries are written. +pub const C_DBT_CACHE: u8 = 221; + +/// The `Op` kinds a translation rule can target. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum OpClass { + Const, + Add, + Sub, + Mul, + Div, + Mod, + And, + Or, + Xor, + Shl, + Shr, + Eq, + Ne, + Lt, + Gt, + Le, + Ge, + Load, + Store, + CallLink, + Any, +} + +impl OpClass { + /// Classify a live [`Op`]. + pub fn of(op: &Op) -> OpClass { + match op { + Op::Const(_) => OpClass::Const, + Op::Add => OpClass::Add, + Op::Sub => OpClass::Sub, + Op::Mul => OpClass::Mul, + Op::Div => OpClass::Div, + Op::Mod => OpClass::Mod, + Op::And => OpClass::And, + Op::Or => OpClass::Or, + Op::Xor => OpClass::Xor, + Op::Shl => OpClass::Shl, + Op::Shr => OpClass::Shr, + Op::Eq => OpClass::Eq, + Op::Ne => OpClass::Ne, + Op::Lt => OpClass::Lt, + Op::Gt => OpClass::Gt, + Op::Le => OpClass::Le, + Op::Ge => OpClass::Ge, + Op::Load(_) => OpClass::Load, + Op::Store(_) => OpClass::Store, + Op::CallLink(_) => OpClass::CallLink, + _ => OpClass::Any, + } + } +} + +/// A translation rule: a named, CZYX-addressed mapping from an op class to a +/// replacement bytecode fragment. Serialized into a rule record's body. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TranslationRule { + /// Human name (also the rule record's title). + pub name: String, + /// Which op class this rule can substitute. + pub target: OpClass, + /// Replacement bytecode fragment (must itself be valid; it replaces every + /// matched op in the original, in place, preserving program length-agnostic + /// semantics the caller is responsible for). + pub fragment: Vec, +} + +impl TranslationRule { + /// Serialize to a stable byte form (name length-prefixed, target byte, + /// fragment as opcode codec). No external deps. + pub fn to_bytes(&self) -> Vec { + let mut out = Vec::new(); + let nb = self.name.as_bytes(); + out.push(nb.len() as u8); + out.extend_from_slice(nb); + out.push(self.target as u8); + out.push(self.fragment.len() as u8); + for op in &self.fragment { + out.extend_from_slice(&cubecode::opcode::encode(&[*op])); + } + out + } + + /// Inverse of [`to_bytes`]. Returns `None` on any malformed input. + pub fn from_bytes(b: &[u8]) -> Option { + let mut i = 0; + let nlen = *b.get(i)? as usize; + i += 1; + if i + nlen > b.len() { + return None; + } + let name = String::from_utf8(b[i..i + nlen].to_vec()).ok()?; + i += nlen; + let target = *b.get(i)?; + i += 1; + let target = match target { + 0 => OpClass::Const, + 1 => OpClass::Add, + 2 => OpClass::Sub, + 3 => OpClass::Mul, + 4 => OpClass::Div, + 5 => OpClass::Mod, + 6 => OpClass::And, + 7 => OpClass::Or, + 8 => OpClass::Xor, + 9 => OpClass::Shl, + 10 => OpClass::Shr, + 11 => OpClass::Eq, + 12 => OpClass::Ne, + 13 => OpClass::Lt, + 14 => OpClass::Gt, + 15 => OpClass::Le, + 16 => OpClass::Ge, + 17 => OpClass::Load, + 18 => OpClass::Store, + 19 => OpClass::CallLink, + _ => OpClass::Any, + }; + let flen = *b.get(i)? as usize; + i += 1; + let mut fragment = Vec::new(); + for _ in 0..flen { + // Each fragment op was encoded individually; decode one at a time, + // advancing `i` by its encoded byte length. + let one = decode_one(&b[i..])?; + i += one.len; + fragment.push(one.op); + } + Some(TranslationRule { + name, + target, + fragment, + }) + } +} + +/// One decoded opcode plus its encoded length, used by `from_bytes`. +struct One { + op: Op, + len: usize, +} + +fn decode_one(b: &[u8]) -> Option { + let op = cubecode::opcode::decode(b).ok()?; + let op = op.into_iter().next()?; + let len = cubecode::opcode::encode(&[op]).len(); + Some(One { op, len }) +} + +/// A patched working set ("code cache") inside a [`CubeStore`]. +/// +/// `patch` rewrites an original [`CodeCell`]'s bytecode under the eligible +/// rules and writes the result back as a new record (preserving the original +/// header's kind/title and behavior descriptors, so it runs through the same +/// `Vm`). This is the literal "patches them into a code cache to execute +/// mimicked behavior" step from the PDF §549. +pub struct CodeCache { + store: CubeStore, + /// Coordinate of the next cache slot to allocate. + next_x: u8, +} + +impl CodeCache { + /// Build an empty cache over a fresh in-memory backend. + pub fn new() -> Self { + CodeCache { + store: CubeStore::new(HashBackend::new()), + next_x: 1, + } + } + + /// Patch `original` under `rules`: every op whose class matches a rule's + /// target (or whose rule target is `Any`) is replaced by that rule's + /// fragment. The rewritten cell is stored at a fresh cache coordinate and + /// returned (with its new label). The original store is untouched. + pub fn patch( + &mut self, + original: &CodeCell, + rules: &[TranslationRule], + ) -> CodeCell { + let mut patched: Vec = Vec::with_capacity(original.code.len()); + for op in &original.code { + let class = OpClass::of(op); + let mut applied = false; + for r in rules { + if r.target == class || r.target == OpClass::Any { + patched.extend_from_slice(&r.fragment); + applied = true; + break; + } + } + if !applied { + patched.push(*op); + } + } + let label = Czyx::new(C_DBT_CACHE, 1, 1, self.next_x); + self.next_x = self.next_x.wrapping_add(1).max(1); + let (h, body) = cache_header_for(original, &cubecode::opcode::encode(&patched)); + self.store.put_record(label, &h, &body); + CodeCell::from_record(label, &h, &body) + .expect("patched cell is always valid bytecode") + } + + /// Run a patched cache entry in the VM, returning its result. + pub fn run(&self, cell: &CodeCell) -> cubecode::RunResult { + let mut vm = Vm::new(self.store.clone()); + vm.run(cell.label) + } + + /// Borrow the backing store (e.g. to persist or inspect). + pub fn store(&self) -> &CubeStore { + &self.store + } +} + +/// Build a cache record header that preserves the original's kind/title and +/// behavior descriptors, but marks it a `Variant` (a mimicked implementation). +fn cache_header_for(original: &CodeCell, body: &[u8]) -> (CubeHeader, Vec) { + let mut h = CubeHeader::new(); + h.title = original.name().map(|s| format!("mimic:{}", s)); + h.doc_type = Some(Kind::Variant.as_str().into()); + h.size_bytes = Some(body.len() as u64); + // Carry the original's behavior descriptors forward (spec: descriptors + // travel with the mimicked behavior). + h.flags.0 |= original.header.flags.0 & cubecode::HEADER_FLAG_BEHAVIOR; + h.refresh_flags(); + (h, body.to_vec()) +} + +/// The DBT runtime: discovers CZYX-stored translation rules and can `mimic` a +/// target code cell by patching it under those rules and running the result. +pub struct DbRuntime { + store: CubeStore, +} + +impl DbRuntime { + /// Wrap a store that already contains rule records (or will, via + /// [`store_rule`]). The runtime is read-only over this store. + pub fn new(store: CubeStore) -> Self { + DbRuntime { store } + } + + /// Collect every translation rule currently in the `c220` rule band. + pub fn discover_rules(&self) -> Vec { + let mut rules = Vec::new(); + for k in self.store.keys() { + if k.c != C_DBT_RULE { + continue; + } + if let Some((_, body)) = self.store.get_record(&k) { + if let Some(r) = TranslationRule::from_bytes(&body) { + rules.push(r); + } + } + } + rules + } + + /// Fetch a code cell by coordinate from the wrapped store. + pub fn fetch(&self, label: Czyx) -> Option { + let (h, b) = self.store.get_record(&label)?; + CodeCell::from_record(label, &h, &b) + } + + /// Mimic `target`: fetch it, apply all discovered rules, patch into a fresh + /// cache, and run the patched version — returning the `RunResult`. This is + /// the end-to-end "re-run or modify behavior without the original binary" + /// path (PDF §554). + pub fn mimic(&self, target: Czyx) -> Option { + let original = self.fetch(target)?; + let rules = self.discover_rules(); + let mut cache = CodeCache::new(); + let patched = cache.patch(&original, &rules); + Some(cache.run(&patched)) + } +} + +/// Store a translation rule into the `c220` rule band at a fresh coordinate. +/// Returns the coordinate it was written to. +pub fn store_rule( + store: &mut CubeStore, + rule: &TranslationRule, + x: u8, +) -> Czyx { + let label = Czyx::new(C_DBT_RULE, 1, 1, x); + let mut h = CubeHeader::new(); + h.title = Some(rule.name.clone()); + h.doc_type = Some(Kind::Variant.as_str().into()); + h.size_bytes = Some(rule.to_bytes().len() as u64); + h.refresh_flags(); + store.put_record(label, &h, &rule.to_bytes()); + label +} + +#[cfg(test)] +mod tests { + use super::*; + use cubecode::opcode::Op; + use cubecode::RunResult; + use cubecoords::Czyx; + + fn sample_cell(coord: Czyx, code: Vec) -> CodeCell { + let mut h = CubeHeader::new(); + h.title = Some("sample".into()); + h.doc_type = Some(Kind::Fn.as_str().into()); + h.refresh_flags(); + CodeCell::from_record(coord, &h, &cubecode::opcode::encode(&code)) + .expect("sample is valid bytecode") + } + + #[test] + fn rule_round_trips_through_bytes() { + let r = TranslationRule { + name: "double-add".into(), + target: OpClass::Add, + fragment: vec![Op::Const(2), Op::Mul], + }; + let bytes = r.to_bytes(); + let back = TranslationRule::from_bytes(&bytes).expect("rule decodes"); + assert_eq!(r, back); + } + + #[test] + fn cache_patches_and_runs() { + // Original: Const 3, Const 4, Add, Halt => 3+4 = 7. + let orig = sample_cell(Czyx::new(1, 1, 1, 1), vec![ + Op::Const(3), + Op::Const(4), + Op::Add, + Op::Halt, + ]); + // Rule: replace `Add` with `Const 2, Mul` => (a)*(2). With a=3,b=4: + // naive in-place substitution yields Const3, Const4, Const2, Mul => + // 4*2 = 8. This proves the patched fragment is what runs. + let rule = TranslationRule { + name: "mul-by-2".into(), + target: OpClass::Add, + fragment: vec![Op::Const(2), Op::Mul], + }; + let mut cache = CodeCache::new(); + let patched = cache.patch(&orig, &[rule]); + assert_eq!(patched.kind(), Kind::Variant, "cache entry is a Variant"); + match cache.run(&patched) { + RunResult::Halted { top: Some(v) } => assert_eq!(v, 8, "patched semantics differ"), + other => panic!("patched run failed: {:?}", other), + } + } + + #[test] + fn runtime_mimic_end_to_end() { + let mut store = CubeStore::new(HashBackend::new()); + // Original target at c1. + let target = Czyx::new(1, 1, 1, 1); + let orig = sample_cell(target, vec![Op::Const(10), Op::Const(5), Op::Sub, Op::Halt]); + store.put_record(orig.label, &orig.header, &orig.body()); + + // Rule in the c220 band: replace Sub with Add (10+5=15 instead of 5). + let rule = TranslationRule { + name: "sub->add".into(), + target: OpClass::Sub, + fragment: vec![Op::Add], + }; + store_rule(&mut store, &rule, 1); + + let rt = DbRuntime::new(store); + let rules = rt.discover_rules(); + assert_eq!(rules.len(), 1, "rule discovered in c220 band"); + match rt.mimic(target) { + Some(RunResult::Halted { top: Some(v) }) => assert_eq!(v, 15, "mimic applied rule"), + other => panic!("mimic failed: {:?}", other), + } + } + + #[test] + fn mimic_without_rules_matches_original() { + let mut store = CubeStore::new(HashBackend::new()); + let target = Czyx::new(1, 1, 1, 2); + let orig = sample_cell(target, vec![Op::Const(7), Op::Const(2), Op::Mul, Op::Halt]); + store.put_record(orig.label, &orig.header, &orig.body()); + let rt = DbRuntime::new(store); + match rt.mimic(target) { + Some(RunResult::Halted { top: Some(v) }) => assert_eq!(v, 14), + other => panic!("mimic without rules should run original: {:?}", other), + } + } +} + + +