mod intrinsics;
pub use intrinsics::*;
use super::{
Agent, ExecutionContext, JsResult, environments::GlobalEnvironment, new_global_environment,
};
use crate::{
ecmascript::{
AbstractModule, BUILTIN_STRING_MEMORY, HostDefined, LoadedModules, ModuleRequest, Number,
Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value, define_property_or_throw,
},
engine::{Bindable, GcScope, NoGcScope, Scopable, bindable_handle},
heap::{
ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
HeapIndexHandle, HeapMarkAndSweep, WorkQueues, arena_vec_access, index_handle,
},
};
use core::marker::PhantomData;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Realm<'a>(BaseIndex<'a, RealmRecord<'static>>);
index_handle!(Realm);
arena_vec_access!(Realm, 'a, RealmRecord, realms);
impl core::fmt::Debug for Realm<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Realm({:?})", self.get_index_u32())
}
}
impl<'r> Realm<'r> {
pub fn host_defined(self, agent: &Agent) -> Option<HostDefined> {
self.get(agent).host_defined.clone()
}
pub fn initialize_host_defined(self, agent: &mut Agent, host_defined: HostDefined) {
assert!(
self.get(agent).host_defined.is_none(),
"Attempted to replace Realm's [[HostDefined]] slot data."
);
self.get_mut(agent).host_defined.replace(host_defined);
}
pub fn global_object(self, agent: &mut Agent) -> Object<'r> {
self.get(agent).global_object
}
pub(crate) fn global_env<'gc>(
self,
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
) -> Option<GlobalEnvironment<'gc>> {
self.get(agent).global_env.bind(gc)
}
pub(crate) fn insert_loaded_module<'gc>(
self,
agent: &mut Agent,
request: ModuleRequest<'gc>,
module: AbstractModule<'gc>,
) {
let requests = &agent.heap.module_request_records;
self.get_mut(&mut agent.heap.realms)
.loaded_modules
.insert_loaded_module(requests, request, module);
}
}
impl HeapMarkAndSweep for Realm<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
queues.realms.push(*self);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
compactions.realms.shift_index(&mut self.0);
}
}
impl<'a> CreateHeapData<RealmRecord<'a>, Realm<'a>> for Heap {
fn create(&mut self, data: RealmRecord<'a>) -> Realm<'a> {
self.realms.push(data.unbind());
self.alloc_counter += core::mem::size_of::<RealmRecord<'static>>();
Realm(BaseIndex::last(&self.realms))
}
}
#[derive(Debug)]
pub(crate) struct RealmRecord<'a> {
agent_signifier: PhantomData<Agent>,
intrinsics: Intrinsics,
pub(crate) global_object: Object<'a>,
pub(crate) global_env: Option<GlobalEnvironment<'a>>,
#[expect(dead_code)]
template_map: (),
loaded_modules: LoadedModules<'a>,
pub(crate) host_defined: Option<HostDefined>,
}
unsafe impl Send for RealmRecord<'_> {}
impl RealmRecord<'_> {
pub(crate) fn intrinsics(&self) -> &Intrinsics {
&self.intrinsics
}
}
bindable_handle!(RealmRecord);
impl HeapMarkAndSweep for RealmRecord<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
agent_signifier: _,
intrinsics,
global_object,
global_env,
template_map: _,
loaded_modules,
host_defined: _,
} = self;
intrinsics.mark_values(queues);
global_env.mark_values(queues);
global_object.mark_values(queues);
loaded_modules.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
agent_signifier: _,
intrinsics,
global_object,
global_env,
template_map: _,
loaded_modules,
host_defined: _,
} = self;
intrinsics.sweep_values(compactions);
global_env.sweep_values(compactions);
global_object.sweep_values(compactions);
loaded_modules.sweep_values(compactions);
}
}
pub(crate) fn create_realm<'gc>(agent: &mut Agent, gc: NoGcScope<'gc, '_>) -> Realm<'gc> {
let realm_rec = RealmRecord {
intrinsics: create_intrinsics(agent),
agent_signifier: PhantomData,
global_object: Object::Object(OrdinaryObject::_DEF),
global_env: None,
template_map: (),
host_defined: None,
loaded_modules: Default::default(),
};
let realm = agent.heap.create(realm_rec).bind(gc);
Intrinsics::create_intrinsics(agent, realm.unbind(), gc);
realm
}
pub(crate) fn create_intrinsics(agent: &mut Agent) -> Intrinsics {
Intrinsics::new(agent)
}
pub(crate) fn set_realm_global_object(
agent: &mut Agent,
realm_id: Realm,
global_object: Option<Object>,
this_value: Option<Object>,
gc: NoGcScope,
) {
let global_object = global_object.unwrap_or_else(|| {
let intrinsics = &agent.get_realm_record_by_id(realm_id).intrinsics;
Object::Object(
OrdinaryObject::create_intrinsic_object(
agent,
Some(intrinsics.object_prototype().into()),
&[],
)
.expect("Should perform GC here"),
)
});
let this_value = this_value.unwrap_or(global_object);
realm_id.get_mut(agent).global_object = global_object.unbind();
let new_global_env = new_global_environment(agent, global_object, this_value, gc);
realm_id.get_mut(agent).global_env = Some(new_global_env.unbind());
}
pub(crate) fn set_default_global_bindings<'a>(
agent: &mut Agent,
realm_id: Realm,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Object<'a>> {
let global = realm_id.get(agent).global_object.scope(agent, gc.nogc());
macro_rules! define_property {
(intrinsic $name:ident, $value:ident) => {
let value = agent
.get_realm_record_by_id(realm_id)
.intrinsics()
.$value()
.into();
define_property!($name, value, Some(true), Some(false), Some(true));
};
($name:ident, $value:ident, $writable:expr, $enumerable:expr, $configurable:expr) => {
let name = PropertyKey::from(BUILTIN_STRING_MEMORY.$name);
let value = $value;
let desc = PropertyDescriptor {
value: Some(value),
writable: $writable,
enumerable: $enumerable,
configurable: $configurable,
..Default::default()
};
define_property_or_throw(agent, global.get(agent), name, desc, gc.reborrow())
.unbind()?
.bind(gc.nogc());
};
}
{
let global_env = realm_id.get(agent).global_env.bind(gc.nogc());
let value = global_env.unwrap().get_this_binding(agent).unbind().into();
define_property!(globalThis, value, Some(true), Some(false), Some(true));
let value = Number::from_f64(agent, f64::INFINITY, gc.nogc())
.unbind()
.into();
define_property!(Infinity, value, Some(false), Some(false), Some(false));
let value = Number::from_f64(agent, f64::NAN, gc.nogc()).unbind().into();
define_property!(NaN, value, Some(false), Some(false), Some(false));
let value = Value::Undefined;
define_property!(undefined, value, Some(false), Some(false), Some(false));
}
{
define_property!(intrinsic eval, eval);
define_property!(intrinsic isFinite, is_finite);
define_property!(intrinsic isNaN, is_nan);
define_property!(intrinsic parseFloat, parse_float);
define_property!(intrinsic parseInt, parse_int);
define_property!(intrinsic decodeURI, decode_uri);
define_property!(intrinsic decodeURIComponent, decode_uri_component);
define_property!(intrinsic encodeURI, encode_uri);
define_property!(intrinsic encodeURIComponent, encode_uri_component);
#[cfg(feature = "annex-b-global")]
{
define_property!(intrinsic escape, escape);
define_property!(intrinsic unescape, unescape);
}
}
{
define_property!(intrinsic AggregateError, aggregate_error);
define_property!(intrinsic Array, array);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic ArrayBuffer, array_buffer);
define_property!(intrinsic BigInt, big_int);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic BigInt64Array, big_int64_array);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic BigUint64Array, big_uint64_array);
define_property!(intrinsic Boolean, boolean);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic DataView, data_view);
#[cfg(feature = "date")]
define_property!(intrinsic Date, date);
define_property!(intrinsic Error, error);
define_property!(intrinsic EvalError, eval_error);
define_property!(intrinsic FinalizationRegistry, finalization_registry);
#[cfg(feature = "proposal-float16array")]
define_property!(intrinsic Float16Array, float16_array);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic Float32Array, float32_array);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic Float64Array, float64_array);
define_property!(intrinsic Function, function);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic Int8Array, int8_array);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic Int16Array, int16_array);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic Int32Array, int32_array);
define_property!(intrinsic Iterator, iterator);
define_property!(intrinsic Map, map);
define_property!(intrinsic Number, number);
define_property!(intrinsic Object, object);
define_property!(intrinsic Promise, promise);
define_property!(intrinsic Proxy, proxy);
define_property!(intrinsic RangeError, range_error);
define_property!(intrinsic ReferenceError, reference_error);
#[cfg(feature = "regexp")]
define_property!(intrinsic RegExp, reg_exp);
#[cfg(feature = "set")]
define_property!(intrinsic Set, set);
#[cfg(feature = "shared-array-buffer")]
define_property!(intrinsic SharedArrayBuffer, shared_array_buffer);
define_property!(intrinsic String, string);
define_property!(intrinsic Symbol, symbol);
define_property!(intrinsic SyntaxError, syntax_error);
define_property!(intrinsic TypeError, type_error);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic Uint8Array, uint8_array);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic Uint8ClampedArray, uint8_clamped_array);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic Uint16Array, uint16_array);
#[cfg(feature = "array-buffer")]
define_property!(intrinsic Uint32Array, uint32_array);
define_property!(intrinsic URIError, uri_error);
#[cfg(feature = "weak-refs")]
define_property!(intrinsic WeakMap, weak_map);
#[cfg(feature = "weak-refs")]
define_property!(intrinsic WeakRef, weak_ref);
#[cfg(feature = "weak-refs")]
define_property!(intrinsic WeakSet, weak_set);
}
{
#[cfg(feature = "atomics")]
define_property!(intrinsic Atomics, atomics);
#[cfg(feature = "json")]
define_property!(intrinsic JSON, json);
#[cfg(feature = "math")]
define_property!(intrinsic Math, math);
define_property!(intrinsic Reflect, reflect);
#[cfg(feature = "temporal")]
define_property!(intrinsic Temporal, temporal);
}
Ok(global.get(agent).bind(gc.into_nogc()))
}
pub(crate) fn initialize_host_defined_realm(
agent: &mut Agent,
create_global_object: Option<impl for<'a> FnOnce(&mut Agent, GcScope<'a, '_>) -> Object<'a>>,
create_global_this_value: Option<
impl for<'a> FnOnce(&mut Agent, GcScope<'a, '_>) -> Object<'a>,
>,
initialize_global_object: Option<impl FnOnce(&mut Agent, Object, GcScope)>,
mut gc: GcScope,
) {
let realm = create_realm(agent, gc.nogc());
let new_context = ExecutionContext {
ecmascript_code: None,
function: None,
realm: realm.unbind(),
script_or_module: None,
};
agent.push_execution_context(new_context);
let global = create_global_this_value.map(|create_global_this_value| {
create_global_this_value(agent, gc.reborrow())
.unbind()
.scope(agent, gc.nogc())
});
let this_value =
create_global_object.map(|create_global_object| create_global_object(agent, gc.reborrow()));
set_realm_global_object(
agent,
agent.current_realm_id_internal(),
global.map(|g| g.get(agent)),
this_value.unbind(),
gc.nogc(),
);
let global_object =
set_default_global_bindings(agent, agent.current_realm_id_internal(), gc.reborrow())
.unbind()
.map_err(|err| {
err.value()
.string_repr(agent, gc.reborrow())
.to_string_lossy_(agent)
.to_string()
})
.unwrap()
.unbind()
.bind(gc.nogc());
if let Some(initialize_global_object) = initialize_global_object {
initialize_global_object(agent, global_object.unbind(), gc.reborrow());
};
}
pub(crate) fn initialize_default_realm(agent: &mut Agent, gc: GcScope) {
let create_global_object: Option<for<'a> fn(&mut Agent, GcScope<'a, '_>) -> Object<'a>> = None;
let create_global_this_value: Option<for<'a> fn(&mut Agent, GcScope<'a, '_>) -> Object<'a>> =
None;
let initialize_global_object: Option<fn(&mut Agent, Object, GcScope)> = None;
initialize_host_defined_realm(
agent,
create_global_object,
create_global_this_value,
initialize_global_object,
gc,
);
}
#[cfg(test)]
mod test {
use crate::heap::HeapIndexHandle;
#[allow(unused_imports)]
use crate::{
ecmascript::types::BuiltinFunctionHeapData,
engine::{Bindable, GcScope},
heap::{
IntrinsicConstructorIndexes, IntrinsicFunctionIndexes, IntrinsicObjectIndexes,
LAST_INTRINSIC_CONSTRUCTOR_INDEX, LAST_INTRINSIC_FUNCTION_INDEX,
LAST_INTRINSIC_OBJECT_INDEX, LAST_WELL_KNOWN_SYMBOL_INDEX,
},
};
fn panic_builtin_function_missing(index: usize) {
let index = index as u32;
let mut changed_index = index;
if changed_index <= LAST_INTRINSIC_CONSTRUCTOR_INDEX as u32 {
panic!(
"Found a missing BuiltinFunction at constructor index {:?}",
unsafe { core::mem::transmute::<u32, IntrinsicConstructorIndexes>(changed_index) }
);
}
changed_index -= LAST_INTRINSIC_CONSTRUCTOR_INDEX as u32 + 1;
if changed_index <= LAST_INTRINSIC_FUNCTION_INDEX as u32 {
panic!(
"Found a missing BuiltinFunction at function index {:?}",
unsafe { core::mem::transmute::<u32, IntrinsicFunctionIndexes>(changed_index) }
);
}
panic!("Found a missing BuiltinFunction at index {index:?}");
}
fn panic_blank_object(index: usize) {
let index = index as u32;
let mut changed_index = index;
if changed_index <= LAST_INTRINSIC_OBJECT_INDEX as u32 {
panic!("Found a blank Object at object index {:?}", unsafe {
core::mem::transmute::<u32, IntrinsicObjectIndexes>(changed_index)
});
}
changed_index -= LAST_INTRINSIC_OBJECT_INDEX as u32 + 1;
if changed_index <= LAST_INTRINSIC_CONSTRUCTOR_INDEX as u32 {
panic!(
"Found a blank BuiltinFunction at constructor index {:?}",
unsafe { core::mem::transmute::<u32, IntrinsicConstructorIndexes>(changed_index) }
);
}
panic!("Found a blank object at index {index:?}");
}
#[test]
fn test_default_realm_sanity() {
use super::initialize_default_realm;
use crate::ecmascript::{Agent, AgentOptions, DefaultHostHooks, ObjectRecord};
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let gc = GcScope::new(&mut gc, &mut scope);
initialize_default_realm(&mut agent, gc);
assert_eq!(
agent
.current_realm_record()
.intrinsics()
.object_prototype()
.get_index(),
0
);
let object_constructor = agent.current_realm_record().intrinsics().object();
assert_eq!(object_constructor.get_index(), 0);
#[cfg(feature = "array-buffer")]
assert!(agent.heap.array_buffers.is_empty());
assert_eq!(agent.heap.arrays.len(), 1);
assert!(agent.heap.bigints.is_empty());
assert!(agent.heap.bound_functions.is_empty());
let missing_builtin = agent
.heap
.builtin_functions
.iter()
.enumerate()
.find(|(_, item)| *item == &BuiltinFunctionHeapData::BLANK);
if let Some((missing_builtin_index, _)) = missing_builtin {
panic_builtin_function_missing(missing_builtin_index);
}
#[cfg(feature = "date")]
assert!(agent.heap.dates.is_empty());
assert!(agent.heap.ecmascript_functions.is_empty());
assert_eq!(agent.heap.environments.declarative.len(), 1);
assert!(agent.heap.environments.function.is_empty());
assert_eq!(agent.heap.environments.global.len(), 1);
assert_eq!(agent.heap.environments.object.len(), 1);
assert!(agent.heap.errors.is_empty());
assert!(agent.heap.globals.borrow().is_empty());
assert!(agent.heap.modules.is_empty());
let blank_object = agent
.heap
.objects
.iter()
.enumerate()
.find(|(_, item)| *item == &ObjectRecord::BLANK);
if let Some((blank_object_index, _)) = blank_object {
panic_blank_object(blank_object_index);
}
assert_eq!(agent.heap.realms.len(), 1);
assert!(agent.heap.scripts.is_empty());
assert_eq!(
agent.heap.symbols.len() - 1,
LAST_WELL_KNOWN_SYMBOL_INDEX as usize
);
#[cfg(feature = "regexp")]
assert!(agent.heap.regexps.is_empty());
}
}