From 9f7b6413e8a4a69d1dbe7b729b223cb8ed0c07c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Fri, 5 Jun 2026 02:50:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E5=93=88=E5=B8=8C=E4=B8=8E=E7=BC=96=E8=AF=91=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + Cargo.toml | 1 + crates/oxide_compiler/Cargo.toml | 1 + crates/oxide_compiler/src/cache.rs | 133 +++++++++++++++++++++++++++++ crates/oxide_compiler/src/lib.rs | 1 + 5 files changed, 137 insertions(+) create mode 100644 crates/oxide_compiler/src/cache.rs diff --git a/Cargo.lock b/Cargo.lock index a48769a..36aabba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -764,6 +764,7 @@ name = "oxide_compiler" version = "0.1.0" dependencies = [ "oxide_parser", + "rustc-hash", "serde", "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index 2511cb3..78eeeb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ rayon = "1" bumpalo = "3" hashbrown = "0.14" proptest = "1" +rustc-hash = "2" [workspace.lints.clippy] all = { level = "deny", priority = -1 } diff --git a/crates/oxide_compiler/Cargo.toml b/crates/oxide_compiler/Cargo.toml index f5e571a..ee8c801 100644 --- a/crates/oxide_compiler/Cargo.toml +++ b/crates/oxide_compiler/Cargo.toml @@ -10,3 +10,4 @@ description = "OxideJS - AST to bytecode compiler" oxide_parser = { path = "../oxide_parser" } serde.workspace = true serde_json.workspace = true +rustc-hash.workspace = true diff --git a/crates/oxide_compiler/src/cache.rs b/crates/oxide_compiler/src/cache.rs new file mode 100644 index 0000000..177af76 --- /dev/null +++ b/crates/oxide_compiler/src/cache.rs @@ -0,0 +1,133 @@ +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; +use std::sync::Arc; + +use oxide_parser::{Expression, Statement}; +use rustc_hash::FxHasher; + +use crate::compiler::Compiler; +use crate::module::CompiledModule; + +/// Hash AST structure — strips identifiers and literal values. +/// Two scripts with different variable names but identical tree shape +/// produce the same hash, enabling structural cache reuse. +pub fn structural_hash(program: &oxide_parser::Program) -> u64 { + let mut h = FxHasher::default(); + + for stmt in &program.body { + hash_statement(stmt, &mut h); + } + + h.finish() +} + +fn hash_statement(stmt: &Statement, h: &mut FxHasher) { + match stmt { + Statement::ExpressionStatement(es) => { + 0u8.hash(h); + hash_expression(&es.expression, h); + } + Statement::VariableDeclaration(decl) => { + 1u8.hash(h); + (decl.declarations.len() as u32).hash(h); + } + Statement::ReturnStatement(ret) => { + 2u8.hash(h); + if let Some(arg) = &ret.argument { + hash_expression(arg, h); + } + } + Statement::IfStatement(ifs) => { + 3u8.hash(h); + hash_expression(&ifs.test, h); + hash_statement(&ifs.consequent, h); + if let Some(alt) = &ifs.alternate { + hash_statement(alt, h); + } + } + Statement::WhileStatement(wh) => { + 4u8.hash(h); + hash_expression(&wh.test, h); + hash_statement(&wh.body, h); + } + Statement::ForStatement(fr) => { + 5u8.hash(h); + hash_statement(&fr.body, h); + } + Statement::BlockStatement(block) => { + 6u8.hash(h); + for s in &block.body { + hash_statement(s, h); + } + } + _ => { + std::mem::discriminant(stmt).hash(h); + } + } +} + +fn hash_expression(expr: &Expression, h: &mut FxHasher) { + match expr { + Expression::BinaryExpression(bin) => { + 0u8.hash(h); + std::mem::discriminant(&bin.operator).hash(h); + hash_expression(&bin.left, h); + hash_expression(&bin.right, h); + } + Expression::UnaryExpression(un) => { + 1u8.hash(h); + std::mem::discriminant(&un.operator).hash(h); + hash_expression(&un.argument, h); + } + Expression::CallExpression(_) => { + 2u8.hash(h); + } + Expression::LogicalExpression(log) => { + 3u8.hash(h); + std::mem::discriminant(&log.operator).hash(h); + hash_expression(&log.left, h); + hash_expression(&log.right, h); + } + _ => { + std::mem::discriminant(expr).hash(h); + // strip literal values — only node type matters for structural identity + } + } +} + +/// Local compilation cache — Phase 4 in-memory HashMap. +/// Migrated to OxideKernel CodeForge (DashMap) in Phase 7. +pub struct CodeCache { + map: HashMap>, +} + +impl CodeCache { + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } + + pub fn get_or_compile( + &mut self, + program: &oxide_parser::Program, + compiler: &Compiler, + ) -> Result, String> { + let hash = structural_hash(program); + + if let Some(module) = self.map.get(&hash) { + return Ok(Arc::clone(module)); + } + + let module = compiler.compile(program)?; + let module = Arc::new(module); + self.map.insert(hash, Arc::clone(&module)); + Ok(module) + } +} + +impl Default for CodeCache { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/oxide_compiler/src/lib.rs b/crates/oxide_compiler/src/lib.rs index 00ab9d8..fc8009c 100644 --- a/crates/oxide_compiler/src/lib.rs +++ b/crates/oxide_compiler/src/lib.rs @@ -1,5 +1,6 @@ #![doc = "OxideJS - AST to bytecode compiler"] +pub mod cache; pub mod compiler; pub mod module; pub mod opcode;