1use crate::model::Variable;
7use crate::query::plan::ExecutionPlan;
8use crate::OxirsError;
9use std::collections::HashMap;
10use std::sync::{Arc, RwLock};
11
12pub struct WasmQueryCompiler {
14 cache: Arc<RwLock<CompilationCache>>,
16 target: WasmTarget,
18 optimization: OptimizationLevel,
20}
21
22#[derive(Debug, Clone)]
24pub enum WasmTarget {
25 Wasm1_0,
27 WasmSimd,
29 WasmThreads,
31 WasmSimdThreads,
33 WasmGC,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
39pub enum OptimizationLevel {
40 None,
42 Basic,
44 Standard,
46 Aggressive,
48 Size,
50}
51
52struct CompilationCache {
54 modules: HashMap<QueryHash, CachedModule>,
56 total_size: usize,
58 max_size: usize,
60}
61
62type QueryHash = u64;
64
65struct CachedModule {
67 wasm_bytes: Vec<u8>,
69 #[allow(dead_code)]
71 metadata: ModuleMetadata,
72 #[allow(dead_code)]
74 compile_time: std::time::Duration,
75 usage_count: usize,
77}
78
79#[derive(Debug, Clone)]
81pub struct ModuleMetadata {
82 pub size: usize,
84 pub memory_pages: u32,
86 pub exports: Vec<ExportedFunction>,
88 pub imports: Vec<RequiredImport>,
90 pub optimization_stats: OptimizationStats,
92}
93
94#[derive(Debug, Clone)]
96pub struct ExportedFunction {
97 pub name: String,
99 pub params: Vec<WasmType>,
101 pub returns: Vec<WasmType>,
103 pub kind: FunctionKind,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq)]
109pub enum WasmType {
110 I32,
111 I64,
112 F32,
113 F64,
114 V128, FuncRef, ExternRef, }
118
119#[derive(Debug, Clone, PartialEq)]
121pub enum FunctionKind {
122 QueryExec,
124 TripleMatch,
126 Join,
128 Filter,
130 Aggregate,
132 Helper(String),
134}
135
136#[derive(Debug, Clone)]
138pub struct RequiredImport {
139 pub module: String,
141 pub name: String,
143 pub import_type: ImportType,
145}
146
147#[derive(Debug, Clone)]
149pub enum ImportType {
150 Function {
152 params: Vec<WasmType>,
153 returns: Vec<WasmType>,
154 },
155 Memory {
157 min_pages: u32,
158 max_pages: Option<u32>,
159 },
160 Table {
162 element_type: WasmType,
163 min_size: u32,
164 max_size: Option<u32>,
165 },
166 Global { value_type: WasmType, mutable: bool },
168}
169
170#[derive(Debug, Clone, Default)]
172pub struct OptimizationStats {
173 pub instructions_eliminated: usize,
175 pub functions_inlined: usize,
177 pub dead_code_removed: usize,
179 pub loops_optimized: usize,
181 pub memory_optimizations: usize,
183}
184
185pub trait WasmRuntime: Send + Sync {
187 fn instantiate(&self, wasm_bytes: &[u8]) -> Result<Box<dyn WasmInstance>, OxirsError>;
189
190 fn validate(&self, wasm_bytes: &[u8]) -> Result<(), OxirsError>;
192
193 fn capabilities(&self) -> RuntimeCapabilities;
195}
196
197pub trait WasmInstance: Send + Sync {
199 fn execute_query(&mut self, input: &QueryInput) -> Result<QueryOutput, OxirsError>;
201
202 fn memory_usage(&self) -> usize;
204
205 fn reset(&mut self);
207}
208
209#[derive(Debug, Clone)]
211pub struct RuntimeCapabilities {
212 pub simd: bool,
214 pub threads: bool,
216 pub bulk_memory: bool,
218 pub reference_types: bool,
220 pub max_memory_pages: u32,
222}
223
224pub struct QueryInput {
226 pub data: Vec<u8>,
228 pub bindings: HashMap<String, Vec<u8>>,
230 pub limits: ExecutionLimits,
232}
233
234pub struct QueryOutput {
236 pub results: Vec<u8>,
238 pub stats: ExecutionStats,
240}
241
242#[derive(Debug, Clone)]
244pub struct ExecutionLimits {
245 pub timeout_ms: u32,
247 pub max_memory: usize,
249 pub max_results: usize,
251}
252
253#[derive(Debug, Clone)]
255pub struct ExecutionStats {
256 pub exec_time_us: u64,
258 pub memory_used: usize,
260 pub triples_scanned: usize,
262 pub results_count: usize,
264}
265
266impl WasmQueryCompiler {
267 pub fn new(target: WasmTarget, optimization: OptimizationLevel) -> Self {
269 Self {
270 cache: Arc::new(RwLock::new(CompilationCache::new())),
271 target,
272 optimization,
273 }
274 }
275
276 pub fn compile(&self, plan: &ExecutionPlan) -> Result<Vec<u8>, OxirsError> {
278 let hash = self.hash_plan(plan);
279
280 if let Some(module) = self.get_cached(hash) {
282 return Ok(module);
283 }
284
285 let wasm_bytes = self.generate_wasm(plan)?;
287
288 let optimized = self.optimize_wasm(wasm_bytes)?;
290
291 self.cache_module(hash, optimized.clone())?;
293
294 Ok(optimized)
295 }
296
297 fn generate_wasm(&self, plan: &ExecutionPlan) -> Result<Vec<u8>, OxirsError> {
299 let mut builder = WasmModuleBuilder::new(self.target.clone());
300
301 builder.add_memory(1, Some(1024))?; self.add_imports(&mut builder)?;
306
307 self.add_data_structures(&mut builder)?;
309
310 self.generate_query_function(&mut builder, plan)?;
312
313 self.generate_helpers(&mut builder)?;
315
316 builder.build()
318 }
319
320 fn add_imports(&self, builder: &mut WasmModuleBuilder) -> Result<(), OxirsError> {
322 builder.add_import(
324 "host",
325 "alloc",
326 ImportType::Function {
327 params: vec![WasmType::I32],
328 returns: vec![WasmType::I32],
329 },
330 )?;
331
332 builder.add_import(
333 "host",
334 "free",
335 ImportType::Function {
336 params: vec![WasmType::I32],
337 returns: vec![],
338 },
339 )?;
340
341 builder.add_import(
342 "host",
343 "log",
344 ImportType::Function {
345 params: vec![WasmType::I32, WasmType::I32],
346 returns: vec![],
347 },
348 )?;
349
350 Ok(())
351 }
352
353 fn add_data_structures(&self, builder: &mut WasmModuleBuilder) -> Result<(), OxirsError> {
355 builder.add_struct(
357 "Term",
358 vec![
359 ("kind", WasmType::I32),
360 ("value_ptr", WasmType::I32),
361 ("value_len", WasmType::I32),
362 ("datatype_ptr", WasmType::I32),
363 ("datatype_len", WasmType::I32),
364 ("lang_ptr", WasmType::I32),
365 ("lang_len", WasmType::I32),
366 ],
367 )?;
368
369 builder.add_struct(
371 "Triple",
372 vec![
373 ("subject", WasmType::I32),
374 ("predicate", WasmType::I32),
375 ("object", WasmType::I32),
376 ],
377 )?;
378
379 builder.add_struct(
381 "Binding",
382 vec![
383 ("var_ptr", WasmType::I32),
384 ("var_len", WasmType::I32),
385 ("term", WasmType::I32),
386 ],
387 )?;
388
389 Ok(())
390 }
391
392 fn generate_query_function(
394 &self,
395 builder: &mut WasmModuleBuilder,
396 plan: &ExecutionPlan,
397 ) -> Result<(), OxirsError> {
398 match plan {
399 ExecutionPlan::TripleScan { pattern } => {
400 self.generate_triple_scan(builder, pattern)?;
401 }
402 ExecutionPlan::HashJoin {
403 left,
404 right,
405 join_vars,
406 } => {
407 self.generate_hash_join(builder, left, right, join_vars)?;
408 }
409 _ => {
410 return Err(OxirsError::Query(
411 "Unsupported plan type for WASM".to_string(),
412 ))
413 }
414 }
415
416 Ok(())
417 }
418
419 fn generate_triple_scan(
421 &self,
422 builder: &mut WasmModuleBuilder,
423 _pattern: &crate::model::pattern::TriplePattern,
424 ) -> Result<(), OxirsError> {
425 builder.add_function(
426 "query_exec",
427 vec![
428 WasmType::I32, WasmType::I32, WasmType::I32, WasmType::I32, ],
433 vec![WasmType::I32],
434 |fb| {
435 fb.local_get(0);
438 fb.local_get(1);
439 fb.call("scan_triples");
440 fb.i32_const(0); },
442 )?;
443
444 Ok(())
445 }
446
447 fn generate_hash_join(
449 &self,
450 builder: &mut WasmModuleBuilder,
451 left: &ExecutionPlan,
452 right: &ExecutionPlan,
453 _join_vars: &[Variable],
454 ) -> Result<(), OxirsError> {
455 self.generate_query_function(builder, left)?;
457 self.generate_query_function(builder, right)?;
458
459 builder.add_function(
461 "hash_join",
462 vec![
463 WasmType::I32, WasmType::I32, WasmType::I32, WasmType::I32, ],
468 vec![WasmType::I32],
469 |fb| {
470 fb.i32_const(0);
472 },
473 )?;
474
475 Ok(())
476 }
477
478 fn generate_helpers(&self, builder: &mut WasmModuleBuilder) -> Result<(), OxirsError> {
480 builder.add_function(
482 "str_eq",
483 vec![
484 WasmType::I32,
485 WasmType::I32, WasmType::I32,
487 WasmType::I32, ],
489 vec![WasmType::I32],
490 |fb| {
491 fb.local_get(1);
493 fb.local_get(3);
494 fb.i32_ne();
495 fb.if_else(
496 WasmType::I32,
497 |fb| {
498 fb.i32_const(0); },
500 |fb| {
501 fb.i32_const(1); },
504 );
505 },
506 )?;
507
508 builder.add_function(
510 "match_term",
511 vec![
512 WasmType::I32, WasmType::I32, ],
515 vec![WasmType::I32],
516 |fb| {
517 fb.i32_const(1); },
519 )?;
520
521 Ok(())
522 }
523
524 fn optimize_wasm(&self, wasm_bytes: Vec<u8>) -> Result<Vec<u8>, OxirsError> {
526 match self.optimization {
527 OptimizationLevel::None => Ok(wasm_bytes),
528 _ => {
529 Ok(wasm_bytes)
531 }
532 }
533 }
534
535 fn hash_plan(&self, plan: &ExecutionPlan) -> QueryHash {
537 use std::collections::hash_map::DefaultHasher;
538 use std::hash::{Hash, Hasher};
539
540 let mut hasher = DefaultHasher::new();
541 format!("{plan:?}").hash(&mut hasher);
542 hasher.finish()
543 }
544
545 fn get_cached(&self, hash: QueryHash) -> Option<Vec<u8>> {
547 let cache = self.cache.read().ok()?;
548 cache.modules.get(&hash).map(|m| m.wasm_bytes.clone())
549 }
550
551 fn cache_module(&self, hash: QueryHash, wasm_bytes: Vec<u8>) -> Result<(), OxirsError> {
553 let mut cache = self
554 .cache
555 .write()
556 .map_err(|_| OxirsError::Query("Failed to acquire cache lock".to_string()))?;
557
558 let metadata = ModuleMetadata {
559 size: wasm_bytes.len(),
560 memory_pages: 1,
561 exports: vec![],
562 imports: vec![],
563 optimization_stats: OptimizationStats::default(),
564 };
565
566 cache.add(
567 hash,
568 CachedModule {
569 wasm_bytes,
570 metadata,
571 compile_time: std::time::Duration::from_millis(10),
572 usage_count: 0,
573 },
574 );
575
576 Ok(())
577 }
578}
579
580struct WasmModuleBuilder {
582 #[allow(dead_code)]
583 target: WasmTarget,
584 imports: Vec<RequiredImport>,
585 functions: Vec<FunctionDef>,
586 memory: Option<MemoryDef>,
587 structs: Vec<StructDef>,
588}
589
590struct FunctionDef {
592 #[allow(dead_code)]
593 name: String,
594 #[allow(dead_code)]
595 params: Vec<WasmType>,
596 #[allow(dead_code)]
597 returns: Vec<WasmType>,
598 #[allow(dead_code)]
599 body: Vec<WasmInstruction>,
600}
601
602struct MemoryDef {
604 #[allow(dead_code)]
605 min_pages: u32,
606 #[allow(dead_code)]
607 max_pages: Option<u32>,
608}
609
610struct StructDef {
612 #[allow(dead_code)]
613 name: String,
614 #[allow(dead_code)]
615 fields: Vec<(String, WasmType)>,
616}
617
618#[allow(dead_code)]
620enum WasmInstruction {
621 LocalGet(u32),
622 LocalSet(u32),
623 I32Const(i32),
624 I32Add,
625 I32Sub,
626 I32Eq,
627 I32Ne,
628 Call(String),
629 If(Vec<WasmInstruction>),
630 IfElse(WasmType, Vec<WasmInstruction>, Vec<WasmInstruction>),
631 Return,
632}
633
634struct FunctionBuilder {
636 instructions: Vec<WasmInstruction>,
637}
638
639impl FunctionBuilder {
640 fn new() -> Self {
641 Self {
642 instructions: Vec::new(),
643 }
644 }
645
646 fn local_get(&mut self, idx: u32) {
647 self.instructions.push(WasmInstruction::LocalGet(idx));
648 }
649
650 #[allow(dead_code)]
651 fn local_set(&mut self, idx: u32) {
652 self.instructions.push(WasmInstruction::LocalSet(idx));
653 }
654
655 fn i32_const(&mut self, val: i32) {
656 self.instructions.push(WasmInstruction::I32Const(val));
657 }
658
659 #[allow(dead_code)]
660 fn i32_add(&mut self) {
661 self.instructions.push(WasmInstruction::I32Add);
662 }
663
664 fn i32_ne(&mut self) {
665 self.instructions.push(WasmInstruction::I32Ne);
666 }
667
668 fn call(&mut self, func: &str) {
669 self.instructions
670 .push(WasmInstruction::Call(func.to_string()));
671 }
672
673 fn if_else<F1, F2>(&mut self, result_type: WasmType, then_fn: F1, else_fn: F2)
674 where
675 F1: FnOnce(&mut Self),
676 F2: FnOnce(&mut Self),
677 {
678 let mut then_builder = FunctionBuilder::new();
679 then_fn(&mut then_builder);
680
681 let mut else_builder = FunctionBuilder::new();
682 else_fn(&mut else_builder);
683
684 self.instructions.push(WasmInstruction::IfElse(
685 result_type,
686 then_builder.instructions,
687 else_builder.instructions,
688 ));
689 }
690}
691
692impl WasmModuleBuilder {
693 fn new(target: WasmTarget) -> Self {
694 Self {
695 target,
696 imports: Vec::new(),
697 functions: Vec::new(),
698 memory: None,
699 structs: Vec::new(),
700 }
701 }
702
703 fn add_import(
704 &mut self,
705 module: &str,
706 name: &str,
707 import_type: ImportType,
708 ) -> Result<(), OxirsError> {
709 self.imports.push(RequiredImport {
710 module: module.to_string(),
711 name: name.to_string(),
712 import_type,
713 });
714 Ok(())
715 }
716
717 fn add_memory(&mut self, min: u32, max: Option<u32>) -> Result<(), OxirsError> {
718 self.memory = Some(MemoryDef {
719 min_pages: min,
720 max_pages: max,
721 });
722 Ok(())
723 }
724
725 fn add_struct(&mut self, name: &str, fields: Vec<(&str, WasmType)>) -> Result<(), OxirsError> {
726 self.structs.push(StructDef {
727 name: name.to_string(),
728 fields: fields
729 .into_iter()
730 .map(|(n, t)| (n.to_string(), t))
731 .collect(),
732 });
733 Ok(())
734 }
735
736 fn add_function<F>(
737 &mut self,
738 name: &str,
739 params: Vec<WasmType>,
740 returns: Vec<WasmType>,
741 body_fn: F,
742 ) -> Result<(), OxirsError>
743 where
744 F: FnOnce(&mut FunctionBuilder),
745 {
746 let mut builder = FunctionBuilder::new();
747 body_fn(&mut builder);
748
749 self.functions.push(FunctionDef {
750 name: name.to_string(),
751 params,
752 returns,
753 body: builder.instructions,
754 });
755
756 Ok(())
757 }
758
759 fn build(self) -> Result<Vec<u8>, OxirsError> {
760 Ok(vec![0x00, 0x61, 0x73, 0x6d]) }
764}
765
766impl CompilationCache {
767 fn new() -> Self {
768 Self {
769 modules: HashMap::new(),
770 total_size: 0,
771 max_size: 100 * 1024 * 1024, }
773 }
774
775 fn add(&mut self, hash: QueryHash, module: CachedModule) {
776 self.total_size += module.wasm_bytes.len();
777 self.modules.insert(hash, module);
778
779 while self.total_size > self.max_size && !self.modules.is_empty() {
781 if let Some((&hash, _)) = self.modules.iter().min_by_key(|(_, m)| m.usage_count) {
783 if let Some(removed) = self.modules.remove(&hash) {
784 self.total_size -= removed.wasm_bytes.len();
785 }
786 }
787 }
788 }
789}
790
791pub struct StreamingWasmCompiler {
793 base_compiler: WasmQueryCompiler,
794 chunk_size: usize,
795}
796
797impl StreamingWasmCompiler {
798 pub fn new(target: WasmTarget, optimization: OptimizationLevel) -> Self {
800 Self {
801 base_compiler: WasmQueryCompiler::new(target, optimization),
802 chunk_size: 1024 * 1024, }
804 }
805
806 pub async fn compile_streaming(
808 &self,
809 plan: &ExecutionPlan,
810 ) -> Result<impl futures::Stream<Item = Result<Vec<u8>, OxirsError>> + use<>, OxirsError> {
811 use futures::stream;
812
813 let wasm_bytes = self.base_compiler.compile(plan)?;
815
816 let chunks: Vec<Vec<u8>> = wasm_bytes
818 .chunks(self.chunk_size)
819 .map(|c| c.to_vec())
820 .collect();
821
822 Ok(stream::iter(chunks.into_iter().map(Ok)))
823 }
824}
825
826#[cfg(test)]
827mod tests {
828 use super::*;
829
830 #[test]
831 fn test_wasm_compiler_creation() {
832 let compiler = WasmQueryCompiler::new(WasmTarget::Wasm1_0, OptimizationLevel::Standard);
833
834 let cache = compiler.cache.read().unwrap();
835 assert_eq!(cache.modules.len(), 0);
836 }
837
838 #[test]
839 fn test_module_builder() {
840 let mut builder = WasmModuleBuilder::new(WasmTarget::Wasm1_0);
841
842 builder.add_memory(1, Some(16)).unwrap();
843 builder
844 .add_import(
845 "host",
846 "log",
847 ImportType::Function {
848 params: vec![WasmType::I32],
849 returns: vec![],
850 },
851 )
852 .unwrap();
853
854 builder
855 .add_function("test", vec![], vec![WasmType::I32], |fb| {
856 fb.i32_const(42);
857 })
858 .unwrap();
859
860 let wasm = builder.build().unwrap();
861 assert!(!wasm.is_empty());
862 }
863
864 #[test]
865 fn test_optimization_levels() {
866 assert_eq!(OptimizationLevel::None as u8, 0);
867 assert!(OptimizationLevel::Aggressive > OptimizationLevel::Standard);
868 }
869}