Skip to content

Commit 67b0167

Browse files
committed
Smart cache invalidation
Constant deduplication HashMap String allocation in global lookup
1 parent 88f97ec commit 67b0167

File tree

2 files changed

+84
-35
lines changed

2 files changed

+84
-35
lines changed

src/bytecode.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::value::Value;
2+
use std::collections::HashMap;
23

34
pub type Reg = u8;
45
pub type ConstIdx = u16;
@@ -71,6 +72,11 @@ pub struct Chunk {
7172
pub num_params: u8,
7273
pub num_registers: u8,
7374
pub protos: Vec<Chunk>, // nested function prototypes
75+
// Constant deduplication indexes for O(1) lookup of common types
76+
#[allow(dead_code)]
77+
int_const_idx: HashMap<i64, ConstIdx>,
78+
#[allow(dead_code)]
79+
symbol_const_idx: HashMap<String, ConstIdx>,
7480
}
7581

7682
impl Chunk {
@@ -81,19 +87,44 @@ impl Chunk {
8187
num_params: 0,
8288
num_registers: 0,
8389
protos: Vec::new(),
90+
int_const_idx: HashMap::new(),
91+
symbol_const_idx: HashMap::new(),
8492
}
8593
}
8694

8795
pub fn add_constant(&mut self, value: Value) -> ConstIdx {
88-
// Check if constant already exists
96+
// Fast path: check specialized indexes for common types (O(1))
97+
if let Some(n) = value.as_int() {
98+
if let Some(&idx) = self.int_const_idx.get(&n) {
99+
return idx;
100+
}
101+
}
102+
if let Some(s) = value.as_symbol() {
103+
if let Some(&idx) = self.symbol_const_idx.get(s) {
104+
return idx;
105+
}
106+
}
107+
108+
// Slow path: linear scan for other types (strings, floats, etc.)
89109
for (i, c) in self.constants.iter().enumerate() {
90110
if *c == value {
91111
return i as ConstIdx;
92112
}
93113
}
94-
let idx = self.constants.len();
114+
115+
// Not found - add new constant and update indexes
116+
let idx = self.constants.len() as ConstIdx;
117+
118+
// Update specialized indexes
119+
if let Some(n) = value.as_int() {
120+
self.int_const_idx.insert(n, idx);
121+
}
122+
if let Some(s) = value.as_symbol() {
123+
self.symbol_const_idx.insert(s.to_string(), idx);
124+
}
125+
95126
self.constants.push(value);
96-
idx as ConstIdx
127+
idx
97128
}
98129

99130
pub fn emit(&mut self, op: Op) -> usize {

src/vm.rs

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ pub struct VM {
1919
registers: Vec<Value>,
2020
frames: Vec<CallFrame>,
2121
globals: Rc<RefCell<HashMap<String, Value>>>,
22-
// Cache for global lookups: (chunk_ptr, const_idx) -> Value
23-
// This avoids repeated HashMap lookups for recursive function calls
24-
global_cache: HashMap<(usize, u16), Value>,
22+
// Cache for global lookups: name -> Value
23+
// Keyed by name to allow targeted invalidation when a specific global changes
24+
global_cache: HashMap<String, Value>,
2525
}
2626

2727
impl VM {
@@ -44,9 +44,21 @@ impl VM {
4444
}
4545

4646
pub fn define_global(&mut self, name: &str, value: Value) {
47-
self.globals.borrow_mut().insert(name.to_string(), value);
48-
// Invalidate cache when globals change
49-
self.global_cache.clear();
47+
// Avoid String allocation if key already exists
48+
{
49+
let mut globals = self.globals.borrow_mut();
50+
if let Some(existing) = globals.get_mut(name) {
51+
*existing = value.clone();
52+
} else {
53+
globals.insert(name.to_string(), value.clone());
54+
}
55+
}
56+
// Update cache (same optimization)
57+
if let Some(existing) = self.global_cache.get_mut(name) {
58+
*existing = value;
59+
} else {
60+
self.global_cache.insert(name.to_string(), value);
61+
}
5062
}
5163

5264
pub fn run(&mut self, chunk: Chunk) -> Result<Value, String> {
@@ -103,22 +115,20 @@ impl VM {
103115
}
104116

105117
Op::GetGlobal(dest, name_idx) => {
106-
// Use chunk pointer as part of cache key for uniqueness
118+
// Get name from constant pool
107119
let chunk = &self.frames.last().unwrap().chunk;
108-
let chunk_ptr = Rc::as_ptr(chunk) as usize;
109-
let cache_key = (chunk_ptr, name_idx);
120+
let name = chunk.constants[name_idx as usize].as_symbol()
121+
.ok_or("GetGlobal: expected symbol")?;
110122

111-
let value = if let Some(cached) = self.global_cache.get(&cache_key) {
112-
// Cache hit - avoid HashMap lookup entirely
123+
let value = if let Some(cached) = self.global_cache.get(name) {
124+
// Cache hit - avoid globals HashMap lookup
113125
cached.clone()
114126
} else {
115-
// Cache miss - do the lookup (using &str to avoid String allocation)
116-
let name = chunk.constants[name_idx as usize].as_symbol()
117-
.ok_or("GetGlobal: expected symbol")?;
127+
// Cache miss - look up in globals
118128
let v = self.globals.borrow().get(name).cloned()
119129
.ok_or_else(|| format!("Undefined variable: {}", name))?;
120130
// Cache for future lookups
121-
self.global_cache.insert(cache_key, v.clone());
131+
self.global_cache.insert(name.to_string(), v.clone());
122132
v
123133
};
124134
self.registers[base + dest as usize] = value;
@@ -129,9 +139,21 @@ impl VM {
129139
let name = chunk.constants[name_idx as usize].as_symbol()
130140
.ok_or("SetGlobal: expected symbol")?;
131141
let value = self.registers[base + src as usize].clone();
132-
self.globals.borrow_mut().insert(name.to_string(), value);
133-
// Invalidate cache - global was modified
134-
self.global_cache.clear();
142+
// Avoid String allocation if key already exists
143+
{
144+
let mut globals = self.globals.borrow_mut();
145+
if let Some(existing) = globals.get_mut(name) {
146+
*existing = value.clone();
147+
} else {
148+
globals.insert(name.to_string(), value.clone());
149+
}
150+
}
151+
// Update cache (same optimization)
152+
if let Some(existing) = self.global_cache.get_mut(name) {
153+
*existing = value;
154+
} else {
155+
self.global_cache.insert(name.to_string(), value);
156+
}
135157
}
136158

137159
Op::Closure(dest, proto_idx) => {
@@ -258,19 +280,17 @@ impl VM {
258280
Op::CallGlobal(dest, name_idx, nargs) => {
259281
// Optimized: look up global and call directly without intermediate register
260282
let chunk = &self.frames.last().unwrap().chunk;
261-
let chunk_ptr = Rc::as_ptr(chunk) as usize;
262-
let cache_key = (chunk_ptr, name_idx);
283+
let name = chunk.constants[name_idx as usize].as_symbol()
284+
.ok_or("CallGlobal: expected symbol")?;
263285

264286
// Get function from cache or globals
265-
let func_value = if let Some(cached) = self.global_cache.get(&cache_key) {
287+
let func_value = if let Some(cached) = self.global_cache.get(name) {
266288
cached
267289
} else {
268-
let name = chunk.constants[name_idx as usize].as_symbol()
269-
.ok_or("CallGlobal: expected symbol")?;
270290
let v = self.globals.borrow().get(name).cloned()
271291
.ok_or_else(|| format!("Undefined function: {}", name))?;
272-
self.global_cache.insert(cache_key, v);
273-
self.global_cache.get(&cache_key).unwrap()
292+
self.global_cache.insert(name.to_string(), v);
293+
self.global_cache.get(name).unwrap()
274294
};
275295

276296
if let Some(cf) = func_value.as_compiled_function() {
@@ -316,19 +336,17 @@ impl VM {
316336
Op::TailCallGlobal(name_idx, arg_start, nargs) => {
317337
// Optimized: look up global and tail-call directly
318338
let chunk = &self.frames.last().unwrap().chunk;
319-
let chunk_ptr = Rc::as_ptr(chunk) as usize;
320-
let cache_key = (chunk_ptr, name_idx);
339+
let name = chunk.constants[name_idx as usize].as_symbol()
340+
.ok_or("TailCallGlobal: expected symbol")?;
321341

322342
// Get function from cache or globals
323-
let func_value = if let Some(cached) = self.global_cache.get(&cache_key) {
343+
let func_value = if let Some(cached) = self.global_cache.get(name) {
324344
cached
325345
} else {
326-
let name = chunk.constants[name_idx as usize].as_symbol()
327-
.ok_or("TailCallGlobal: expected symbol")?;
328346
let v = self.globals.borrow().get(name).cloned()
329347
.ok_or_else(|| format!("Undefined function: {}", name))?;
330-
self.global_cache.insert(cache_key, v);
331-
self.global_cache.get(&cache_key).unwrap()
348+
self.global_cache.insert(name.to_string(), v);
349+
self.global_cache.get(name).unwrap()
332350
};
333351

334352
if let Some(cf) = func_value.as_compiled_function() {

0 commit comments

Comments
 (0)