Skip to content

Commit 88f97ec

Browse files
committed
Add thread-local SymbolInterner
now: O(1), before O(n) string comparison
1 parent 305de33 commit 88f97ec

File tree

1 file changed

+48
-4
lines changed

1 file changed

+48
-4
lines changed

src/value.rs

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,47 @@
1+
use std::cell::RefCell;
2+
use std::collections::HashMap;
13
use std::fmt;
24
use std::rc::Rc;
35

46
use crate::bytecode::Chunk;
57

8+
//=============================================================================
9+
// Symbol Interner
10+
//=============================================================================
11+
//
12+
// All symbols are interned for O(1) comparison. The interner maintains a
13+
// mapping from string content to Rc<str>, ensuring identical symbols share
14+
// the same Rc. Symbol comparison then uses Rc::ptr_eq() instead of string
15+
// comparison.
16+
17+
thread_local! {
18+
static SYMBOL_INTERNER: RefCell<SymbolInterner> = RefCell::new(SymbolInterner::new());
19+
}
20+
21+
struct SymbolInterner {
22+
symbols: HashMap<Box<str>, Rc<str>>,
23+
}
24+
25+
impl SymbolInterner {
26+
fn new() -> Self {
27+
SymbolInterner {
28+
symbols: HashMap::new(),
29+
}
30+
}
31+
32+
/// Intern a symbol string, returning a shared Rc<str>.
33+
/// If the symbol already exists, returns the existing Rc.
34+
/// Otherwise, creates a new Rc and stores it.
35+
fn intern(&mut self, s: &str) -> Rc<str> {
36+
if let Some(rc) = self.symbols.get(s) {
37+
return rc.clone();
38+
}
39+
let rc: Rc<str> = Rc::from(s);
40+
self.symbols.insert(s.into(), rc.clone());
41+
rc
42+
}
43+
}
44+
645
//=============================================================================
746
// NaN-Boxing Implementation
847
//=============================================================================
@@ -133,9 +172,10 @@ impl Value {
133172
Value::from_heap(heap)
134173
}
135174

136-
/// Create a symbol value
175+
/// Create a symbol value (interned for O(1) comparison)
137176
pub fn symbol(s: &str) -> Value {
138-
let heap = Rc::new(HeapObject::Symbol(Rc::from(s)));
177+
let interned = SYMBOL_INTERNER.with(|interner| interner.borrow_mut().intern(s));
178+
let heap = Rc::new(HeapObject::Symbol(interned));
139179
Value::from_heap(heap)
140180
}
141181

@@ -369,8 +409,11 @@ impl Value {
369409
}
370410

371411
/// Create Symbol (backwards compat) - from Rc<str>
412+
/// Note: For proper interning, prefer Value::symbol(&str) instead
372413
pub fn Symbol(s: Rc<str>) -> Value {
373-
let heap = Rc::new(HeapObject::Symbol(s));
414+
// Re-intern to ensure pointer equality works correctly
415+
let interned = SYMBOL_INTERNER.with(|interner| interner.borrow_mut().intern(&s));
416+
let heap = Rc::new(HeapObject::Symbol(interned));
374417
Value::from_heap(heap)
375418
}
376419

@@ -549,7 +592,8 @@ impl PartialEq for Value {
549592
// Heap object comparison
550593
match (self.as_heap(), other.as_heap()) {
551594
(Some(HeapObject::String(a)), Some(HeapObject::String(b))) => a == b,
552-
(Some(HeapObject::Symbol(a)), Some(HeapObject::Symbol(b))) => a == b,
595+
// Symbol comparison uses Rc::ptr_eq for O(1) - symbols are interned!
596+
(Some(HeapObject::Symbol(a)), Some(HeapObject::Symbol(b))) => Rc::ptr_eq(a, b),
553597
(Some(HeapObject::List(a)), Some(HeapObject::List(b))) => a == b,
554598
(Some(HeapObject::Cons(a)), Some(HeapObject::Cons(b))) => {
555599
a.car == b.car && a.cdr == b.cdr

0 commit comments

Comments
 (0)