feat(dbt+ai): build out cubedbt and cubeai packages (Package 4, PDF §549/§551)
cubedbt: DBT runtime that reads CZYX-stored translation rules and patches them into a code cache to execute mimicked behavior. TranslationRule (CZYX Variant record in c220 band) maps an op-class to a replacement bytecode fragment; CodeCache patches an original CodeCell under eligible rules and writes the result as a Variant (preserving behavior descriptors), and DbRuntime.mimic() fetches -> patches -> runs in the real Vm, proving 're-run or modify behavior without the original binary'. cubeai: models over cube-stored traces/graphs to classify blocks (Computation/Branch/CallTrampoline/IoSection/Sequence from the op-class histogram + behavior descriptors) and suggest new code sequences as cubedbt TranslationRules (persisted into the c220 rule band, discoverable by DbRuntime). End-to-end: trace -> classify -> suggest -> mimic. Both crates are dependency-free and operate on the real CubeStore/CodeCell/ Vm/Behavior types, so they are exercisable today on HashBackend and slot into ConcurrentStore later without an API change. ./check quick green: cubedbt 4 tests, cubeai 5 tests, full workspace green.
This commit is contained in:
@@ -7,6 +7,8 @@ members = [
|
||||
"cubecode",
|
||||
"cubecrypt",
|
||||
"cubesys",
|
||||
"cubedbt",
|
||||
"cubeai",
|
||||
"cube-bench",
|
||||
]
|
||||
|
||||
|
||||
@@ -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" }
|
||||
@@ -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<HashBackend>,
|
||||
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<Suggestion> {
|
||||
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<Czyx> {
|
||||
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<HashBackend> {
|
||||
&self.store
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cubecode::opcode::Op;
|
||||
use cubecoords::Czyx;
|
||||
|
||||
fn cell(coord: Czyx, code: Vec<Op>, 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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" }
|
||||
@@ -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<Op>`). 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<Op>,
|
||||
}
|
||||
|
||||
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<u8> {
|
||||
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<TranslationRule> {
|
||||
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<One> {
|
||||
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<HashBackend>,
|
||||
/// 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<Op> = 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<HashBackend> {
|
||||
&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<u8>) {
|
||||
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<B: CubeBackend> {
|
||||
store: CubeStore<B>,
|
||||
}
|
||||
|
||||
impl<B: CubeBackend> DbRuntime<B> {
|
||||
/// 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<B>) -> Self {
|
||||
DbRuntime { store }
|
||||
}
|
||||
|
||||
/// Collect every translation rule currently in the `c220` rule band.
|
||||
pub fn discover_rules(&self) -> Vec<TranslationRule> {
|
||||
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<CodeCell> {
|
||||
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<cubecode::RunResult> {
|
||||
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<B: CubeBackend>(
|
||||
store: &mut CubeStore<B>,
|
||||
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<Op>) -> 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user