Skip to content

Commit 04ee9fb

Browse files
committed
Fix thread isolation - spawned threads can access parent functions
1 parent e97b4b7 commit 04ee9fb

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed

src/vm.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::cell::RefCell;
55
use std::collections::HashMap;
66
use std::hash::{Hash, Hasher};
77
use std::rc::Rc;
8+
use std::sync::Arc;
89

910
// Register file size - each function call uses ~(locals + 16) registers
1011
// For 10000 deep recursion with ~20 registers per call = 200000 registers
@@ -414,6 +415,14 @@ impl VM {
414415
let symbol_rc = unsafe { frame.chunk.constants.get_unchecked(name_idx as usize) }
415416
.as_symbol_rc()
416417
.ok_or("CallGlobal: expected symbol")?;
418+
419+
// Special handling for spawn - needs access to globals
420+
if &*symbol_rc == "spawn" {
421+
let result = self.handle_spawn(base, dest, nargs)?;
422+
unsafe { *self.registers.get_unchecked_mut(base + dest as usize) = result };
423+
continue;
424+
}
425+
417426
let key = SymbolKey(symbol_rc);
418427

419428
// INLINE CACHE: Check typed caches first (no type check needed)
@@ -1374,6 +1383,96 @@ impl VM {
13741383
}
13751384
}
13761385

1386+
/// Handle spawn with access to globals - allows spawned threads to call parent-defined functions
1387+
fn handle_spawn(&self, base: usize, dest: u8, nargs: u8) -> Result<Value, String> {
1388+
use std::sync::Mutex;
1389+
use std::thread;
1390+
use crate::value::HeapObject;
1391+
1392+
if nargs != 1 {
1393+
return Err("spawn expects 1 argument (function)".to_string());
1394+
}
1395+
1396+
// Get the function argument
1397+
let arg_start = base + dest as usize + 1;
1398+
let func = &self.registers[arg_start];
1399+
1400+
// Only compiled functions can be spawned (not closures with captured environments)
1401+
let chunk = if let Some(chunk) = func.as_compiled_function() {
1402+
chunk.clone()
1403+
} else {
1404+
return Err("spawn expects a compiled function (not a closure)".to_string());
1405+
};
1406+
1407+
// Convert Rc<Chunk> to Arc<Chunk> for thread safety
1408+
let arc_chunk: Arc<Chunk> = Arc::new((*chunk).clone());
1409+
1410+
// Collect shareable globals (compiled functions and native functions)
1411+
// We need to convert them to thread-safe format
1412+
let mut shared_globals: Vec<(String, SharedGlobal)> = Vec::new();
1413+
{
1414+
let globals = self.globals.borrow();
1415+
for (name, value) in globals.iter() {
1416+
if let Some(cf) = value.as_compiled_function() {
1417+
// Convert compiled function to Arc-based
1418+
// cf is &Rc<Chunk>, so **cf gives us Chunk
1419+
shared_globals.push((
1420+
name.clone(),
1421+
SharedGlobal::CompiledFunction(Arc::new((**cf).clone()))
1422+
));
1423+
} else if let Some(nf) = value.as_native_function() {
1424+
// Native functions are just function pointers, safe to share
1425+
shared_globals.push((
1426+
name.clone(),
1427+
SharedGlobal::NativeFunction(nf.name.clone(), nf.func)
1428+
));
1429+
}
1430+
// Skip other types (data values) - they can't be safely shared
1431+
}
1432+
}
1433+
1434+
// Spawn the thread with access to shared globals
1435+
let handle = thread::spawn(move || {
1436+
// Create a new VM for this thread
1437+
let mut thread_vm = standard_vm();
1438+
1439+
// Add the shared globals to the thread's VM
1440+
for (name, shared) in shared_globals {
1441+
match shared {
1442+
SharedGlobal::CompiledFunction(arc_chunk) => {
1443+
// Convert Arc<Chunk> back to Rc<Chunk>
1444+
let rc_chunk = Rc::new((*arc_chunk).clone());
1445+
thread_vm.define_global(&name, Value::CompiledFunction(rc_chunk));
1446+
}
1447+
SharedGlobal::NativeFunction(fn_name, func_ptr) => {
1448+
thread_vm.define_global(&name, Value::native_function(&fn_name, func_ptr));
1449+
}
1450+
}
1451+
}
1452+
1453+
// Execute the function (it should be zero-argument)
1454+
match thread_vm.run((*arc_chunk).clone()) {
1455+
Ok(result) => {
1456+
// Convert result to SharedValue
1457+
result.make_shared()
1458+
}
1459+
Err(e) => Err(format!("Thread execution error: {}", e)),
1460+
}
1461+
});
1462+
1463+
// Wrap the JoinHandle in Arc<Mutex<Option<>>> so it can be joined once
1464+
let thread_handle = Arc::new(Mutex::new(Some(handle)));
1465+
1466+
// Create a HeapObject::ThreadHandle and wrap it in a Value
1467+
let heap = Rc::new(HeapObject::ThreadHandle(thread_handle));
1468+
Ok(Value::from_heap(heap))
1469+
}
1470+
}
1471+
1472+
/// Helper enum for sharing globals between threads
1473+
enum SharedGlobal {
1474+
CompiledFunction(Arc<Chunk>),
1475+
NativeFunction(String, fn(&[Value]) -> Result<Value, String>),
13771476
}
13781477

13791478
impl Default for VM {

0 commit comments

Comments
 (0)