mod abstract_module_records;
mod cyclic_module_records;
mod source_text_module_records;
pub use abstract_module_records::*;
pub use cyclic_module_records::*;
pub use source_text_module_records::*;
use super::continue_dynamic_import;
use ahash::AHasher;
use hashbrown::{HashTable, hash_table::Entry};
use oxc_ast::ast;
use std::hash::{Hash, Hasher};
use crate::{
ecmascript::{
Agent, HostDefined, JsResult, Module, Realm, Script, ScriptOrModule,
module_namespace_create, types::String,
},
engine::{Bindable, HeapRootData, HeapRootRef, NoGcScope, Rootable, bindable_handle},
heap::{
ArenaAccess, BaseIndex, CompactionLists, HeapIndexHandle, HeapMarkAndSweep, WorkQueues,
arena_vec_access,
},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ModuleRequestRecord<'a> {
specifier: String<'a>,
attributes: Option<Box<[ImportAttributeRecord<'a>]>>,
hash: u64,
}
impl<'a> ModuleRequestRecord<'a> {
fn attributes(&self) -> &[ImportAttributeRecord<'a>] {
self.attributes.as_ref().map_or(&[], |attrs| attrs.as_ref())
}
}
impl AsRef<[ModuleRequestRecord<'static>]> for Agent {
fn as_ref(&self) -> &[ModuleRequestRecord<'static>] {
&self.heap.module_request_records
}
}
impl AsMut<[ModuleRequestRecord<'static>]> for Agent {
fn as_mut(&mut self) -> &mut [ModuleRequestRecord<'static>] {
&mut self.heap.module_request_records
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct ModuleRequest<'a>(BaseIndex<'a, ModuleRequestRecord<'static>>);
bindable_handle!(ModuleRequest);
arena_vec_access!(ModuleRequest, 'a, ModuleRequestRecord, module_request_records);
impl<'r> ModuleRequest<'r> {
pub(super) fn new(
agent: &mut Agent,
specifier: &str,
with_clause: Option<&ast::WithClause>,
gc: NoGcScope<'r, '_>,
) -> Self {
let mut state = AHasher::default();
specifier.hash(&mut state);
let specifier = String::from_str(agent, specifier, gc).unbind();
let attributes = with_clause.map(|with_clause| {
let mut key_value_pairs = with_clause
.with_entries
.iter()
.map(|attr| {
let key = attr.key.as_atom().as_str();
let value = attr.value.value.as_str();
(key, value)
})
.collect::<Box<[(&str, &str)]>>();
key_value_pairs.sort_by_key(|attr| attr.0);
key_value_pairs
.into_iter()
.map(|(key, value)| {
key.hash(&mut state);
value.hash(&mut state);
ImportAttributeRecord {
key: String::from_str(agent, key, gc).unbind(),
value: String::from_str(agent, value, gc).unbind(),
}
})
.collect()
});
let hash = state.finish();
let index = agent.heap.module_request_records.len() as u32;
agent.heap.module_request_records.push(ModuleRequestRecord {
specifier,
attributes,
hash,
});
Self::from_index_u32(index)
}
pub(super) fn new_dynamic(
agent: &mut Agent,
specifier: String,
attributes: Box<[ImportAttributeRecord]>,
gc: NoGcScope<'r, '_>,
) -> Self {
let mut state = AHasher::default();
specifier.to_string_lossy_(agent).hash(&mut state);
for attribute in attributes.iter() {
attribute.key.to_string_lossy_(agent).hash(&mut state);
attribute.value.to_string_lossy_(agent).hash(&mut state);
}
let hash = state.finish();
let index = agent.heap.module_request_records.len() as u32;
agent.heap.module_request_records.push(
ModuleRequestRecord {
specifier,
attributes: Some(attributes),
hash,
}
.unbind(),
);
Self::from_index_u32(index).bind(gc)
}
pub fn specifier(self, agent: &Agent) -> String<'r> {
self.get(agent).specifier
}
pub fn attributes(self, agent: &Agent) -> &[ImportAttributeRecord<'r>] {
self.get(agent).attributes()
}
}
impl HeapIndexHandle for ModuleRequest<'_> {
const _DEF: Self = Self(BaseIndex::MAX);
#[inline]
fn from_index_u32(index: u32) -> Self {
Self(BaseIndex::from_index_u32(index))
}
#[inline]
fn get_index_u32(self) -> u32 {
self.0.get_index_u32()
}
}
bindable_handle!(ModuleRequestRecord);
#[derive(Debug)]
pub(crate) struct LoadedModuleRequestRecord<'a> {
module_request: ModuleRequest<'a>,
module: AbstractModule<'a>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImportAttributeRecord<'a> {
pub(crate) key: String<'a>,
pub(crate) value: String<'a>,
}
bindable_handle!(ImportAttributeRecord);
#[derive(Debug, Default)]
pub(crate) struct LoadedModules<'a> {
table: HashTable<LoadedModuleRequestRecord<'a>>,
}
impl<'a> LoadedModules<'a> {
pub(crate) fn get_loaded_module(
&self,
requests: &Vec<ModuleRequestRecord<'static>>,
module_request: ModuleRequest<'a>,
) -> Option<AbstractModule<'a>> {
let hash = module_request.get(requests).hash;
self.table
.find(hash, |record| {
module_requests_equal(
record.module_request.get(requests),
module_request.get(requests),
)
})
.map(|record| record.module)
}
pub(crate) fn insert_loaded_module<'gc>(
&mut self,
requests: &Vec<ModuleRequestRecord<'static>>,
module_request: ModuleRequest<'gc>,
module: AbstractModule<'gc>,
) {
let hash = module_request.get(requests).hash;
match self.table.entry(
hash,
|record| {
module_requests_equal(
record.module_request.get(requests),
module_request.get(requests),
)
},
|record| record.module_request.get(requests).hash,
) {
Entry::Occupied(e) => {
assert!(e.get().module == module);
}
Entry::Vacant(e) => {
e.insert(LoadedModuleRequestRecord {
module_request: module_request.unbind(),
module: module.unbind(),
});
}
}
}
}
fn module_requests_equal(left: &ModuleRequestRecord, right: &ModuleRequestRecord) -> bool {
if left.specifier != right.specifier {
return false;
}
let left_attrs = left.attributes();
let right_attrs = right.attributes();
let left_attrs_count = left_attrs.len();
let right_attrs_count = right_attrs.len();
if left_attrs_count != right_attrs_count {
return false;
}
for (l, r) in left_attrs.iter().zip(right_attrs.iter()) {
if l.key != r.key || l.value != r.value {
return false;
}
}
true
}
pub(crate) fn get_imported_module<'a>(
agent: &Agent,
referrer: SourceTextModule<'a>,
request: ModuleRequest,
gc: NoGcScope<'a, '_>,
) -> AbstractModule<'a> {
referrer
.get_loaded_module(agent, request)
.expect("Could not find loaded module for request")
.bind(gc)
}
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct Referrer<'a>(InnerReferrer<'a>);
impl Referrer<'_> {
pub fn realm<'a>(self, agent: &Agent, gc: NoGcScope<'a, '_>) -> Realm<'a> {
match self.0 {
InnerReferrer::Script(s) => s.realm(agent, gc),
InnerReferrer::SourceTextModule(m) => m.realm(agent, gc),
InnerReferrer::Realm(r) => r.bind(gc),
}
}
pub fn host_defined(self, agent: &Agent) -> Option<HostDefined> {
match self.0 {
InnerReferrer::Script(s) => s.host_defined(agent),
InnerReferrer::SourceTextModule(m) => m.host_defined(agent),
InnerReferrer::Realm(r) => r.host_defined(agent),
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum InnerReferrer<'a> {
Script(Script<'a>),
SourceTextModule(SourceTextModule<'a>),
Realm(Realm<'a>),
}
impl<'a> From<Script<'a>> for Referrer<'a> {
fn from(value: Script<'a>) -> Self {
Self(InnerReferrer::Script(value))
}
}
impl<'a> From<SourceTextModule<'a>> for Referrer<'a> {
fn from(value: SourceTextModule<'a>) -> Self {
Self(InnerReferrer::SourceTextModule(value))
}
}
impl<'a> From<Realm<'a>> for Referrer<'a> {
fn from(value: Realm<'a>) -> Self {
Self(InnerReferrer::Realm(value))
}
}
impl<'a> From<ScriptOrModule<'a>> for Referrer<'a> {
fn from(value: ScriptOrModule<'a>) -> Self {
match value {
ScriptOrModule::Script(s) => Self(InnerReferrer::Script(s)),
ScriptOrModule::SourceTextModule(m) => Self(InnerReferrer::SourceTextModule(m)),
}
}
}
bindable_handle!(Referrer);
impl Rootable for Referrer<'_> {
type RootRepr = HeapRootRef;
fn to_root_repr(value: Self) -> Result<Self::RootRepr, HeapRootData> {
InnerReferrer::to_root_repr(value.0)
}
fn from_root_repr(value: &Self::RootRepr) -> Result<Self, HeapRootRef> {
InnerReferrer::from_root_repr(value).map(Self)
}
fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr {
heap_ref
}
fn from_heap_data(heap_data: HeapRootData) -> Option<Self> {
InnerReferrer::from_heap_data(heap_data).map(Self)
}
}
bindable_handle!(InnerReferrer);
impl Rootable for InnerReferrer<'_> {
type RootRepr = HeapRootRef;
fn to_root_repr(value: Self) -> Result<Self::RootRepr, HeapRootData> {
match value {
Self::Script(s) => Err(HeapRootData::from(s)),
Self::SourceTextModule(m) => Err(HeapRootData::from(m)),
Self::Realm(r) => Err(HeapRootData::from(r)),
}
}
fn from_root_repr(value: &Self::RootRepr) -> Result<Self, HeapRootRef> {
Err(*value)
}
fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr {
heap_ref
}
fn from_heap_data(heap_data: HeapRootData) -> Option<Self> {
match heap_data {
HeapRootData::Script(s) => Some(Self::Script(s)),
HeapRootData::SourceTextModule(m) => Some(Self::SourceTextModule(m)),
HeapRootData::Realm(r) => Some(Self::Realm(r)),
_ => None,
}
}
}
impl Referrer<'_> {
fn insert_loaded_module(
self,
agent: &mut Agent,
request: ModuleRequest,
module: AbstractModule,
) {
match self.0 {
InnerReferrer::Script(s) => s.insert_loaded_module(agent, request, module),
InnerReferrer::SourceTextModule(m) => m.insert_loaded_module(agent, request, module),
InnerReferrer::Realm(r) => r.insert_loaded_module(agent, request, module),
}
}
}
pub fn finish_loading_imported_module<'a>(
agent: &mut Agent,
referrer: Referrer<'a>,
module_request: ModuleRequest<'a>,
payload: &mut GraphLoadingStateRecord<'a>,
result: JsResult<'a, AbstractModule<'a>>,
gc: NoGcScope<'a, '_>,
) {
if let Ok(result) = result {
referrer.insert_loaded_module(agent, module_request, result);
}
if payload.pending_modules_count > 0 {
continue_module_loading(agent, payload, result, gc);
} else {
continue_dynamic_import(agent, payload.promise_capability.clone(), result, gc);
}
}
pub(crate) fn all_import_attributes_supported(
agent: &Agent,
attributes: &[ImportAttributeRecord],
) -> bool {
let supported = agent.host_hooks.get_supported_import_attributes();
for attribute in attributes {
let key = attribute.key.to_string_lossy_(agent);
if !supported.contains(&key.as_ref()) {
return false;
}
}
true
}
pub(crate) fn get_module_namespace<'a>(
agent: &mut Agent,
module: AbstractModule,
gc: NoGcScope<'a, '_>,
) -> Module<'a> {
let module = module.bind(gc);
if let Some(module) = module.as_source_text_module() {
debug_assert!(!matches!(
module.status(agent),
CyclicModuleRecordStatus::New | CyclicModuleRecordStatus::Unlinked
));
}
let namespace = module.namespace(agent, gc);
let Some(namespace) = namespace else {
let exported_names = module.get_exported_names(agent, &mut vec![], gc);
let unambiguous_names = exported_names
.into_iter()
.filter(|name| {
let resolution = module.resolve_export(agent, *name, &mut vec![], gc);
matches!(resolution, Some(ResolvedBinding::Resolved { .. }))
})
.collect::<Box<[String]>>();
return module_namespace_create(agent, module, unambiguous_names, gc);
};
namespace
}
impl HeapMarkAndSweep for ModuleRequest<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
queues.module_request_records.push(*self);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
compactions.module_request_records.shift_index(&mut self.0);
}
}
impl HeapMarkAndSweep for LoadedModules<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
for loaded_module_request in self.table.iter() {
loaded_module_request.mark_values(queues);
}
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
for loaded_module_request in self.table.iter_mut() {
loaded_module_request.sweep_values(compactions);
}
}
}
impl HeapMarkAndSweep for LoadedModuleRequestRecord<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
module_request,
module,
} = self;
module_request.mark_values(queues);
module.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
module_request,
module,
} = self;
module_request.sweep_values(compactions);
module.sweep_values(compactions);
}
}