refactor: 集中 AST 缓存逻辑实现并清理无关依赖

This commit is contained in:
2026-06-05 23:53:30 +08:00
parent 50fb4bab23
commit fe3331d995
9 changed files with 109 additions and 234 deletions
Generated
+1 -2
View File
@@ -771,6 +771,7 @@ dependencies = [
name = "oxide_compiler" name = "oxide_compiler"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"oxide_kernel",
"oxide_parser", "oxide_parser",
"rustc-hash", "rustc-hash",
"serde", "serde",
@@ -781,7 +782,6 @@ dependencies = [
name = "oxide_kernel" name = "oxide_kernel"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo",
"dashmap", "dashmap",
"hashbrown 0.14.5", "hashbrown 0.14.5",
"oxide_compiler", "oxide_compiler",
@@ -822,7 +822,6 @@ dependencies = [
"oxide_parser", "oxide_parser",
"oxide_types", "oxide_types",
"proptest", "proptest",
"rustc-hash",
"ryu", "ryu",
] ]
+3
View File
@@ -11,3 +11,6 @@ oxide_parser = { path = "../oxide_parser" }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
rustc-hash.workspace = true rustc-hash.workspace = true
[dev-dependencies]
oxide_kernel = { path = "../oxide_kernel" }
-133
View File
@@ -1,133 +0,0 @@
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<u64, Arc<CompiledModule>>,
}
impl CodeCache {
pub fn new() -> Self {
Self {
map: HashMap::new(),
}
}
pub fn get_or_compile(
&mut self,
program: &oxide_parser::Program,
compiler: &Compiler,
) -> Result<Arc<CompiledModule>, 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()
}
}
+89
View File
@@ -501,3 +501,92 @@ impl Compiler {
} }
} }
} }
pub fn structural_hash(program: &oxide_parser::Program) -> u64 {
use std::hash::Hasher;
let mut h = rustc_hash::FxHasher::default();
for stmt in &program.body {
hash_statement(stmt, &mut h);
}
h.finish()
}
fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher) {
use std::hash::Hash;
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 rustc_hash::FxHasher) {
use std::hash::Hash;
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);
}
}
}
-1
View File
@@ -1,6 +1,5 @@
#![doc = "OxideJS - AST to bytecode compiler"] #![doc = "OxideJS - AST to bytecode compiler"]
pub mod cache;
pub mod compiler; pub mod compiler;
pub mod module; pub mod module;
pub mod opcode; pub mod opcode;
+15 -12
View File
@@ -1,6 +1,6 @@
use oxide_compiler::cache::CodeCache;
use oxide_compiler::compiler::Compiler; use oxide_compiler::compiler::Compiler;
use oxide_compiler::opcode::{self, OpCode}; use oxide_compiler::opcode::{self, OpCode};
use oxide_kernel::code_forge::CodeForge;
use oxide_parser::Allocator; use oxide_parser::Allocator;
fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule { fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
@@ -13,7 +13,7 @@ fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
fn parse_to_hash(source: &str) -> u64 { fn parse_to_hash(source: &str) -> u64 {
let allocator = Allocator::default(); let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse failed"); let program = oxide_parser::parse(&allocator, source).expect("parse failed");
oxide_compiler::cache::structural_hash(&program) oxide_compiler::compiler::structural_hash(&program)
} }
#[test] #[test]
@@ -81,31 +81,34 @@ fn compile_binary_ops() {
} }
#[test] #[test]
fn structural_cache_hit() { fn structural_hash_hit() {
let forge = CodeForge::new();
let compiler = Compiler::new();
let allocator = Allocator::default(); let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, "1 + 2").expect("parse failed"); let program = oxide_parser::parse(&allocator, "1 + 2").expect("parse failed");
let compiler = Compiler::new();
let mut cache = CodeCache::new();
let m1 = cache let m1 = forge
.get_or_compile(&program, &compiler) .get_or_compile(&program, &compiler)
.expect("compile failed"); .expect("first compile");
let m2 = cache let m2 = forge
.get_or_compile(&program, &compiler) .get_or_compile(&program, &compiler)
.expect("compile failed"); .expect("second compile");
assert_eq!(m1.bytecode, m2.bytecode); assert_eq!(
(m1.bytecode.len(), m1.n_registers),
(m2.bytecode.len(), m2.n_registers)
);
} }
#[test] #[test]
fn structural_cache_same_shape() { fn structural_hash_same_shape() {
let hash_a = parse_to_hash("var x = 1 + 2; var y = x;"); let hash_a = parse_to_hash("var x = 1 + 2; var y = x;");
let hash_b = parse_to_hash("var a = 3 + 4; var b = a;"); let hash_b = parse_to_hash("var a = 3 + 4; var b = a;");
assert_eq!(hash_a, hash_b); assert_eq!(hash_a, hash_b);
} }
#[test] #[test]
fn structural_cache_different_shape() { fn structural_hash_different_shape() {
let hash_a = parse_to_hash("1 + 2"); let hash_a = parse_to_hash("1 + 2");
let hash_b = parse_to_hash("1 + 2 * 3"); let hash_b = parse_to_hash("1 + 2 * 3");
assert_ne!(hash_a, hash_b); assert_ne!(hash_a, hash_b);
-1
View File
@@ -10,7 +10,6 @@ description = "OxideJS - Shared kernel (CodeForge, ShapeForge, PropForge, String
oxide_compiler = { path = "../oxide_compiler" } oxide_compiler = { path = "../oxide_compiler" }
oxide_parser = { path = "../oxide_parser" } oxide_parser = { path = "../oxide_parser" }
oxide_types = { path = "../oxide_types" } oxide_types = { path = "../oxide_types" }
bumpalo.workspace = true
dashmap.workspace = true dashmap.workspace = true
hashbrown.workspace = true hashbrown.workspace = true
rustc-hash.workspace = true rustc-hash.workspace = true
+1 -84
View File
@@ -1,94 +1,11 @@
use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
use dashmap::DashMap; use dashmap::DashMap;
use oxide_compiler::compiler::Compiler; use oxide_compiler::compiler::Compiler;
use oxide_compiler::module::CompiledModule; use oxide_compiler::module::CompiledModule;
use oxide_parser::{Expression, Statement};
pub fn structural_hash(program: &oxide_parser::Program) -> u64 { pub use oxide_compiler::compiler::structural_hash;
let mut h = rustc_hash::FxHasher::default();
for stmt in &program.body {
hash_statement(stmt, &mut h);
}
h.finish()
}
fn hash_statement(stmt: &Statement, h: &mut rustc_hash::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 rustc_hash::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);
}
}
}
pub struct CodeForge { pub struct CodeForge {
map: DashMap<u64, Arc<CompiledModule>>, map: DashMap<u64, Arc<CompiledModule>>,
-1
View File
@@ -10,7 +10,6 @@ description = "OxideJS - Register-based VM with epoch arena memory"
oxide_compiler = { path = "../oxide_compiler" } oxide_compiler = { path = "../oxide_compiler" }
oxide_kernel = { path = "../oxide_kernel" } oxide_kernel = { path = "../oxide_kernel" }
oxide_types = { path = "../oxide_types" } oxide_types = { path = "../oxide_types" }
rustc-hash.workspace = true
fast-float.workspace = true fast-float.workspace = true
ryu.workspace = true ryu.workspace = true