use std::cell::LazyCell;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::sync::Arc;
use crate::value::{DictMap, VmAsyncBuiltinFn, VmBuiltinFn, VmValue};
use crate::BuiltinId;
use super::{Vm, VmBuiltinDispatch, VmBuiltinEntry, VmBuiltinMetadata};
struct StdlibRegistration {
builtins: Arc<BTreeMap<String, VmBuiltinFn>>,
async_builtins: Arc<BTreeMap<String, VmAsyncBuiltinFn>>,
capability_methods:
Arc<BTreeMap<harn_builtin_meta::CapabilityId, BTreeMap<String, VmBuiltinDispatch>>>,
builtin_metadata: Arc<BTreeMap<String, VmBuiltinMetadata>>,
builtins_by_id: Arc<HashMap<BuiltinId, VmBuiltinEntry>>,
builtin_id_collisions: Arc<HashSet<BuiltinId>>,
globals: Arc<DictMap>,
}
thread_local! {
static REGISTRATION: LazyCell<StdlibRegistration> = LazyCell::new(|| {
let mut vm = Vm::new();
crate::stdlib::register_stdlib_bindings(&mut vm);
for (name, value) in vm.globals.iter() {
assert!(immutable_namespace_value(value),
"stdlib registration global `{name}` contains invocation state");
}
assert!(vm.harness().is_none(), "stdlib bindings installed a Harness");
StdlibRegistration {
builtins: Arc::clone(&vm.builtins),
async_builtins: Arc::clone(&vm.async_builtins),
capability_methods: Arc::clone(&vm.capability_methods),
builtin_metadata: Arc::clone(&vm.builtin_metadata),
builtins_by_id: Arc::clone(&vm.builtins_by_id),
builtin_id_collisions: Arc::clone(&vm.builtin_id_collisions),
globals: Arc::clone(&vm.globals),
}
});
}
fn immutable_namespace_value(value: &VmValue) -> bool {
match value {
VmValue::Nil
| VmValue::Bool(_)
| VmValue::Int(_)
| VmValue::Float(_)
| VmValue::String(_)
| VmValue::BuiltinRef(_) => true,
VmValue::Dict(entries) => entries.values().all(immutable_namespace_value),
_ => false,
}
}
impl Vm {
pub(crate) fn install_shared_stdlib_registration(&mut self) -> bool {
if !self.builtins.is_empty()
|| !self.async_builtins.is_empty()
|| !self.capability_methods.is_empty()
{
return false;
}
REGISTRATION.with(|registration| {
self.builtins = Arc::clone(®istration.builtins);
self.async_builtins = Arc::clone(®istration.async_builtins);
self.capability_methods = Arc::clone(®istration.capability_methods);
self.builtin_metadata = Arc::clone(®istration.builtin_metadata);
self.builtins_by_id = Arc::clone(®istration.builtins_by_id);
self.builtin_id_collisions = Arc::clone(®istration.builtin_id_collisions);
if self.globals.is_empty() {
self.globals = Arc::clone(®istration.globals);
} else {
Arc::make_mut(&mut self.globals).extend(
registration
.globals
.iter()
.map(|(name, value)| (name.clone(), value.clone())),
);
}
});
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test(flavor = "current_thread")]
async fn standard_library_shares_bindings_but_not_invocation_state() {
let mut first = Vm::new();
let mut sibling = Vm::new();
crate::stdlib::register_vm_stdlib(&mut first);
crate::stdlib::register_vm_stdlib(&mut sibling);
assert!(first.builtins.len() > 100, "empty registry is not sharing");
assert!(Arc::ptr_eq(&first.builtins, &sibling.builtins));
assert!(Arc::ptr_eq(
&first.builtin_metadata,
&sibling.builtin_metadata
));
assert!(!Arc::ptr_eq(&first.sync_runtime, &sibling.sync_runtime));
assert!(!Arc::ptr_eq(
&first.shared_state_runtime,
&sibling.shared_state_runtime
));
assert!(!Arc::ptr_eq(
first.harness().unwrap().inner(),
sibling.harness().unwrap().inner()
));
let args = vec![VmValue::Int(-7)];
assert!(matches!(
first.call_named_builtin("abs", args.clone()).await.unwrap(),
VmValue::Int(7)
));
first.register_builtin("abs", |_, _| Ok(VmValue::Int(99)));
assert!(matches!(
first.call_named_builtin("abs", args.clone()).await.unwrap(),
VmValue::Int(99)
));
assert!(matches!(
sibling
.call_named_builtin("abs", args.clone())
.await
.unwrap(),
VmValue::Int(7)
));
let mut later = Vm::new();
crate::stdlib::register_vm_stdlib(&mut later);
assert!(matches!(
later.call_named_builtin("abs", args).await.unwrap(),
VmValue::Int(7)
));
first.set_global("pi", VmValue::Int(0));
assert!(matches!(sibling.global("pi"), Some(VmValue::Float(_))));
assert!(matches!(later.global("pi"), Some(VmValue::Float(_))));
}
#[tokio::test(flavor = "current_thread")]
async fn preinstalled_host_bindings_keep_ordered_registration() {
let mut vm = Vm::new();
vm.register_builtin("host_custom", |_, _| Ok(VmValue::Int(42)));
vm.set_global("host_fact", VmValue::Int(13));
assert!(!vm.install_shared_stdlib_registration());
crate::stdlib::register_vm_stdlib(&mut vm);
assert!(matches!(
vm.call_named_builtin("host_custom", vec![]).await.unwrap(),
VmValue::Int(42)
));
assert!(matches!(
vm.call_named_builtin("abs", vec![VmValue::Int(-7)])
.await
.unwrap(),
VmValue::Int(7)
));
assert!(matches!(vm.global("host_fact"), Some(VmValue::Int(13))));
}
#[test]
fn namespaces_cannot_capture_execution_objects() {
assert!(immutable_namespace_value(&VmValue::Float(1.0)));
let mut vm = Vm::new();
crate::stdlib::register_vm_stdlib(&mut vm);
assert!(!immutable_namespace_value(
&vm.root_harness_value().unwrap()
));
}
}