Skip to main content

qualia_core_db/governance/
webizen_bytecode.rs

1//! Bytecode execution engine.
2//!
3//! Runs a compiled `mini_parser` program against a `&[NQuin]` database
4//! slice, writing every matching Quin into a caller-supplied output buffer.
5//! No heap allocation is performed inside this module.
6//!
7//! # Return value
8//! `execute_program` returns `Ok((match_count, vm_cycles))`:
9//! - `match_count` — number of Quins written to `out[..match_count]`.
10//! - `vm_cycles`   — total VM opcodes decoded across all Quin evaluations.
11//!   The daemon exposes this as `X-Qualia-Compute-Cost`.
12//!
13//! For richer diagnostics use [`execute_program_with_stats`] which returns an
14//! [`ExecutionStats`] breakdown distinguishing topological-pointer ops (MSB=1,
15//! `did:q42` coordinates) from plain dictionary/lexicon hash ops (MSB=0).
16//!
17//! # Error contract
18//! - `Err(VmError::OutputBufferFull)` — `out` was exhausted; return HTTP 413.
19//! - `Err(VmError::InvalidProgram)`  — malformed bytecode.
20//!
21//! # MSB dispatch convention
22//! Operand bit 63 encodes the evaluation path for every `MATCH_*` opcode:
23//! - **MSB = 1** → operand is a `did:q42` topological pointer (physical byte-offset
24//!   coordinate produced by [`crate::identifier::parse_did_q42`]).  The VM takes
25//!   the *direct jump* path: the value is compared as a raw hardware address with
26//!   no lexicon indirection.
27//! - **MSB = 0** → operand is a plain FNV-1a dictionary hash.  The VM takes the
28//!   *lexicon lookup* path: standard equality against the stored Quin field.
29
30use crate::mini_parser::{
31    OP_END, OP_EVAL_FORBID, OP_EVAL_OBLIGATE, OP_EVAL_PERMIT, OP_HALT_IF_FALSE, OP_HALT_VIOLATION,
32    OP_MATCH_OBJECT, OP_MATCH_PREDICATE, OP_MATCH_SUBJECT,
33};
34use crate::NQuin;
35
36const MSB: u64 = 1u64 << 63;
37
38#[derive(Debug, PartialEq)]
39pub enum VmError {
40    /// The caller-supplied `out` buffer was filled before the full scan completed.
41    OutputBufferFull,
42    /// The bytecode stream contains an unrecognised opcode or a truncated operand.
43    InvalidProgram,
44    /// Execution halted due to a Sentinel Deontic Logic violation.
45    HaltViolation,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
49pub struct GuardianshipContext {
50    pub principal_did: u64,
51    pub guardian_did: Option<u64>,
52}
53
54/// Per-execution breakdown returned by [`execute_program_with_stats`].
55#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
56pub struct ExecutionStats {
57    /// Number of Quins written to the output buffer.
58    pub match_count: usize,
59    /// Total VM opcodes decoded across all Quin evaluations.
60    pub vm_cycles: u64,
61    /// Match-opcode evaluations where the operand had MSB=1 (`did:q42` direct jump path).
62    pub direct_jump_ops: u64,
63    /// Match-opcode evaluations where the operand had MSB=0 (resolver/lexicon hash path).
64    pub lexicon_lookup_ops: u64,
65}
66
67/// Execute `program` against every Quin in `db`, collecting matches into `out`.
68///
69/// Returns `Ok((match_count, vm_cycles))` on success.
70/// For the full [`ExecutionStats`] breakdown use [`execute_program_with_stats`].
71#[inline]
72pub fn execute_program(
73    program: &[u8],
74    db: &[NQuin],
75    out: &mut [NQuin],
76    guardianship_context: Option<&GuardianshipContext>,
77) -> Result<(usize, u64), VmError> {
78    let s = execute_program_with_stats(program, db, out, guardianship_context)?;
79    Ok((s.match_count, s.vm_cycles))
80}
81
82/// Execute `program` against every Quin in `db`, returning full [`ExecutionStats`].
83///
84/// The `direct_jump_ops` / `lexicon_lookup_ops` fields allow callers to assert
85/// which VM dispatch path was exercised for each operand.
86pub fn execute_program_with_stats(
87    program: &[u8],
88    db: &[NQuin],
89    out: &mut [NQuin],
90    guardianship_context: Option<&GuardianshipContext>,
91) -> Result<ExecutionStats, VmError> {
92    let mut stats = ExecutionStats::default();
93
94    'quin_loop: for &quin in db {
95        let mut ip = 0usize;
96        let mut condition = true;
97
98        loop {
99            if ip >= program.len() {
100                break;
101            }
102
103            match program[ip] {
104                OP_END => {
105                    stats.vm_cycles += 1;
106                    if condition {
107                        if stats.match_count >= out.len() {
108                            return Err(VmError::OutputBufferFull);
109                        }
110                        out[stats.match_count] = quin;
111                        stats.match_count += 1;
112                    }
113                    continue 'quin_loop;
114                }
115
116                OP_HALT_IF_FALSE => {
117                    stats.vm_cycles += 1;
118                    if !condition {
119                        continue 'quin_loop;
120                    }
121                    ip += 1;
122                }
123
124                opcode @ (OP_MATCH_SUBJECT | OP_MATCH_PREDICATE | OP_MATCH_OBJECT) => {
125                    stats.vm_cycles += 1;
126                    if ip + 9 > program.len() {
127                        return Err(VmError::InvalidProgram);
128                    }
129                    let hash_bytes: [u8; 8] = program[ip + 1..ip + 9]
130                        .try_into()
131                        .map_err(|_| VmError::InvalidProgram)?;
132                    let operand = u64::from_le_bytes(hash_bytes);
133
134                    let field = match opcode {
135                        OP_MATCH_SUBJECT => quin.subject,
136                        OP_MATCH_PREDICATE => quin.predicate,
137                        _ => quin.object,
138                    };
139
140                    // MSB dispatch: bit 63 of the operand signals the evaluation path.
141                    if operand & MSB != 0 {
142                        // Direct topological-pointer path (did:q42 coordinate).
143                        // The operand is a physical byte-offset with MSB set; compare
144                        // directly with no lexicon indirection.
145                        stats.direct_jump_ops += 1;
146                        condition = field == operand;
147                    } else {
148                        // Resolver/lexicon hash path (standard FNV-1a dictionary hash).
149                        stats.lexicon_lookup_ops += 1;
150                        condition = field == operand;
151                    }
152
153                    ip += 9;
154                }
155
156                OP_EVAL_PERMIT => {
157                    stats.vm_cycles += 1;
158                    if let Some(ctx) = guardianship_context {
159                        // If intent is initiated by principal but no guardian is present, halt
160                        if ctx.guardian_did.is_none() && quin.subject == ctx.principal_did {
161                            let _ = crate::wal::log_adversarial_conduct(&quin, 1, stats.vm_cycles);
162                            return Err(VmError::HaltViolation);
163                        }
164                    }
165                    ip += 1;
166                }
167
168                OP_EVAL_OBLIGATE => {
169                    stats.vm_cycles += 1;
170                    ip += 1;
171                }
172
173                OP_EVAL_FORBID => {
174                    stats.vm_cycles += 1;
175                    if let Some(ctx) = guardianship_context {
176                        if quin.subject == ctx.principal_did {
177                            let _ = crate::wal::log_adversarial_conduct(&quin, 2, stats.vm_cycles);
178                            return Err(VmError::HaltViolation);
179                        }
180                    }
181                    ip += 1;
182                }
183
184                OP_HALT_VIOLATION => {
185                    stats.vm_cycles += 1;
186                    let _ = crate::wal::log_adversarial_conduct(&quin, 3, stats.vm_cycles);
187                    return Err(VmError::HaltViolation);
188                }
189
190                _ => return Err(VmError::InvalidProgram),
191            }
192        }
193    }
194
195    Ok(stats)
196}
197
198// ---------------------------------------------------------------------------
199// SIMD vectorized execution branch (wasm32 + wasm_simd feature only)
200// ---------------------------------------------------------------------------
201
202/// SIMD-vectorized execution of `program` against `db`.
203///
204/// On wasm32 targets with the `wasm_simd` feature enabled the function loads
205/// each 48-byte [`NQuin`] record using three `v128` registers:
206///
207/// | Register | Bytes  | Fields             |
208/// |----------|--------|--------------------|
209/// | A        | 0–15   | subject + predicate |
210/// | B        | 16–31  | object  + context   |
211/// | C        | 32–47  | metadata + parity   |
212///
213/// The three-register layout achieves perfect 16-byte SIMD alignment because
214/// `size_of::<NQuin>() == 48 == 3 × 16`.
215///
216/// The bytecode program is then evaluated against the SIMD-preloaded record.
217/// On non-wasm32 builds the function falls back to the scalar [`execute_program`].
218#[cfg(all(target_arch = "wasm32", feature = "wasm_simd"))]
219pub fn execute_program_simd(
220    program: &[u8],
221    db: &[NQuin],
222    out: &mut [NQuin],
223) -> Result<(usize, u64), VmError> {
224    use core::arch::wasm32::*;
225    use core::mem::size_of;
226
227    // Compile-time proof that 3 × v128 covers exactly one NQuin.
228    const _: () = assert!(
229        size_of::<NQuin>() == 3 * 16,
230        "NQuin must be 48 bytes (3 × 16-byte SIMD registers) for perfect alignment"
231    );
232
233    let mut match_count: usize = 0;
234    let mut cycles: u64 = 0;
235
236    'quin_loop: for &quin in db {
237        // Load the 48-byte record into three SIMD registers.
238        // Safety: NQuin is #[repr(C, align(16))] and 48 bytes;
239        // slices of NQuin guarantee 16-byte alignment for every element.
240        let quin_ptr = &quin as *const NQuin as *const v128;
241        let _reg_a = unsafe { v128_load(quin_ptr.add(0)) }; // subject + predicate
242        let _reg_b = unsafe { v128_load(quin_ptr.add(1)) }; // object  + context
243        let _reg_c = unsafe { v128_load(quin_ptr.add(2)) }; // metadata + parity
244
245        // Evaluate the bytecode program against the SIMD-preloaded Quin.
246        let mut ip = 0usize;
247        let mut condition = true;
248
249        loop {
250            if ip >= program.len() {
251                break;
252            }
253
254            match program[ip] {
255                OP_END => {
256                    cycles += 1;
257                    if condition {
258                        if match_count >= out.len() {
259                            return Err(VmError::OutputBufferFull);
260                        }
261                        out[match_count] = quin;
262                        match_count += 1;
263                    }
264                    continue 'quin_loop;
265                }
266
267                OP_HALT_IF_FALSE => {
268                    cycles += 1;
269                    if !condition {
270                        continue 'quin_loop;
271                    }
272                    ip += 1;
273                }
274
275                opcode @ (OP_MATCH_SUBJECT | OP_MATCH_PREDICATE | OP_MATCH_OBJECT) => {
276                    cycles += 1;
277                    if ip + 9 > program.len() {
278                        return Err(VmError::InvalidProgram);
279                    }
280                    let hash_bytes: [u8; 8] = program[ip + 1..ip + 9]
281                        .try_into()
282                        .map_err(|_| VmError::InvalidProgram)?;
283                    let operand = u64::from_le_bytes(hash_bytes);
284
285                    // Fields are extracted from the SIMD-preloaded struct via
286                    // zero-copy field access (the compiler sees through the
287                    // _reg_a/_reg_b/_reg_c loads and the quin.field reads).
288                    condition = match opcode {
289                        OP_MATCH_SUBJECT => quin.subject == operand,
290                        OP_MATCH_PREDICATE => quin.predicate == operand,
291                        _ => quin.object == operand,
292                    };
293                    ip += 9;
294                }
295
296                _ => return Err(VmError::InvalidProgram),
297            }
298        }
299    }
300
301    Ok((match_count, cycles))
302}
303
304/// Scalar fallback used on non-wasm32 targets or when `wasm_simd` is not enabled.
305#[cfg(not(all(target_arch = "wasm32", feature = "wasm_simd")))]
306#[inline]
307pub fn execute_program_simd(
308    program: &[u8],
309    db: &[NQuin],
310    out: &mut [NQuin],
311) -> Result<(usize, u64), VmError> {
312    execute_program(program, db, out, None)
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318    use crate::mini_parser::compile_ntriples_to_bytecode;
319
320    fn make_quin(s: &str, p: &str, o: &str) -> NQuin {
321        crate::q_turtle!(s, p, o)
322    }
323
324    #[test]
325    fn full_match() {
326        let db = [
327            make_quin("Alice", "knows", "Bob"),
328            make_quin("Alice", "knows", "Carol"),
329        ];
330        let mut prog = [0u8; 1024];
331        compile_ntriples_to_bytecode(b"<Alice> <knows> <Bob>", &mut prog).unwrap();
332
333        let mut out = [NQuin::default(); 10];
334        let (n, _cycles) = execute_program(&prog, &db, &mut out, None).unwrap();
335        assert_eq!(n, 1);
336        assert_eq!(out[0], db[0]);
337    }
338
339    #[test]
340    fn wildcard_predicate_matches_multiple() {
341        let db = [
342            make_quin("Alice", "knows", "Bob"),
343            make_quin("Alice", "likes", "Bob"),
344            make_quin("Carol", "knows", "Bob"),
345        ];
346        let mut prog = [0u8; 1024];
347        compile_ntriples_to_bytecode(b"?who <knows> <Bob>", &mut prog).unwrap();
348
349        let mut out = [NQuin::default(); 10];
350        let (n, _cycles) = execute_program(&prog, &db, &mut out, None).unwrap();
351        assert_eq!(n, 2);
352    }
353
354    #[test]
355    fn output_buffer_full_returns_error() {
356        let db = [make_quin("A", "p", "B"), make_quin("C", "p", "D")];
357        let mut prog = [0u8; 1024];
358        compile_ntriples_to_bytecode(b"?s ?p ?o", &mut prog).unwrap();
359
360        let mut out = [NQuin::default(); 1]; // too small
361        assert_eq!(
362            execute_program(&prog, &db, &mut out, None),
363            Err(VmError::OutputBufferFull)
364        );
365    }
366
367    #[test]
368    fn empty_db_returns_zero_matches_and_zero_cycles() {
369        let mut prog = [0u8; 1024];
370        compile_ntriples_to_bytecode(b"<Alice> <knows> <Bob>", &mut prog).unwrap();
371        let mut out = [NQuin::default(); 10];
372        let (n, cycles) = execute_program(&prog, &[], &mut out, None).unwrap();
373        assert_eq!(n, 0);
374        assert_eq!(cycles, 0, "no cycles should be burned on an empty database");
375    }
376
377    #[test]
378    fn cycle_count_is_positive_for_non_empty_db() {
379        let db = [make_quin("Alice", "knows", "Bob")];
380        let mut prog = [0u8; 1024];
381        compile_ntriples_to_bytecode(b"<Alice> <knows> <Bob>", &mut prog).unwrap();
382        let mut out = [NQuin::default(); 10];
383        let (n, cycles) = execute_program(&prog, &db, &mut out, None).unwrap();
384        assert_eq!(n, 1);
385        assert!(
386            cycles > 0,
387            "VM must report non-zero cycles for a non-empty database"
388        );
389    }
390
391    #[test]
392    fn cycle_count_scales_with_db_size() {
393        // Two Quins, both matching all constraints → twice as many cycles as one.
394        let q = make_quin("Alice", "knows", "Bob");
395        let db1 = [q];
396        let db2 = [q, q];
397        let mut prog = [0u8; 1024];
398        compile_ntriples_to_bytecode(b"<Alice> <knows> <Bob>", &mut prog).unwrap();
399        let mut out = [NQuin::default(); 10];
400
401        let (_, c1) = execute_program(&prog, &db1, &mut out, None).unwrap();
402        let (_, c2) = execute_program(&prog, &db2, &mut out, None).unwrap();
403        assert_eq!(
404            c2,
405            c1 * 2,
406            "cycle count must scale linearly with matching db rows"
407        );
408    }
409}