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:
@@ -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