qualia_core_db/governance/
webizen_bytecode.rs1use 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 OutputBufferFull,
42 InvalidProgram,
44 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#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
56pub struct ExecutionStats {
57 pub match_count: usize,
59 pub vm_cycles: u64,
61 pub direct_jump_ops: u64,
63 pub lexicon_lookup_ops: u64,
65}
66
67#[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
82pub 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 if operand & MSB != 0 {
142 stats.direct_jump_ops += 1;
146 condition = field == operand;
147 } else {
148 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 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#[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 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 let quin_ptr = &quin as *const NQuin as *const v128;
241 let _reg_a = unsafe { v128_load(quin_ptr.add(0)) }; let _reg_b = unsafe { v128_load(quin_ptr.add(1)) }; let _reg_c = unsafe { v128_load(quin_ptr.add(2)) }; 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 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#[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]; 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 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}