Skip to main content

oxide_core/vm/
module.rs

1//! ECMAScript modules.
2//!
3//! A module record is an internal record (tag `Module`) whose slots hold
4//! the module's environment (the scope its root function captures: one cell
5//! per binding), its root function, its namespace, its evaluation error and
6//! its top-level promise capability; the rest is [`ModuleData`]. Every
7//! record stays in the registry for the runtime's life (QuickJS keeps its
8//! modules too), so the records name each other by `ObjectId`.
9//!
10//! An import binding is the exporter's cell, installed in the importer's
11//! environment while linking: a live binding is a shared cell, read by the
12//! loop's `LoadCaptured` with no new opcode. Linking and evaluation are the
13//! specification's (`Link`, `InnerModuleLinking`, `Evaluate`,
14//! `InnerModuleEvaluation` and the asynchronous-module machinery for
15//! top-level `await`). Modules load synchronously through the host's
16//! [`ModuleHost`] when they are first requested.
17
18use std::cell::Cell;
19
20use super::compile::{CompileError, Compiler};
21use super::native::CallSite;
22use super::{Cx, Throw, error};
23use crate::PropertyKey;
24use crate::builtins::{Native, native_closure, native_data};
25use crate::heap::object::{InternalData, InternalTag, ObjectKind, flags};
26use crate::runtime::Intrinsic;
27use crate::value::{ObjectId, ScopeId, Value};
28use crate::{ErrorKind, KeyKind};
29
30// ---- the host's description of a module ----
31
32/// What one binding of a module's root function is.
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum ModuleBinding {
35    /// A declaration of the module: `lexical` ones start uninitialized.
36    Declaration { lexical: bool },
37    /// An import (or a declaration sharing an import's slot): the
38    /// exporter's cell, installed while linking.
39    Import,
40    /// The module's `import.meta` object.
41    ImportMeta,
42}
43
44/// One requested module.
45#[derive(Clone, Debug)]
46pub struct ModuleRequest {
47    pub specifier: String,
48    pub attributes: Vec<(String, String)>,
49}
50
51/// One import: from request `request`, the export `name` (`None` for the
52/// namespace), into binding `binding`.
53#[derive(Clone, Debug)]
54pub struct ModuleImport {
55    pub request: u32,
56    pub name: Option<String>,
57    pub binding: u16,
58}
59
60/// Where an exported name comes from.
61#[derive(Clone, Debug)]
62pub enum ExportTarget {
63    /// A binding of the module.
64    Local(u16),
65    /// An export of request `request` (`None` for its namespace).
66    Indirect { request: u32, name: Option<String> },
67}
68
69#[derive(Clone, Debug)]
70pub struct ModuleExport {
71    pub name: String,
72    pub target: ExportTarget,
73}
74
75/// A hoisted declaration initialized while linking: `undefined`, or a
76/// function from child `child` of the root unit (named `default` for an
77/// anonymous default export).
78#[derive(Clone, Copy, Debug)]
79pub struct ModuleInitializer {
80    pub binding: u16,
81    pub function: Option<(u32, bool)>,
82}
83
84/// The tables of a compiled module.
85#[derive(Clone, Debug, Default)]
86pub struct ModuleTables {
87    pub bindings: Vec<ModuleBinding>,
88    pub requests: Vec<ModuleRequest>,
89    pub imports: Vec<ModuleImport>,
90    pub exports: Vec<ModuleExport>,
91    pub star_exports: Vec<u32>,
92    pub initializers: Vec<ModuleInitializer>,
93    pub has_top_level_await: bool,
94}
95
96/// A loaded module: its root function (instantiated in the [`Compiler`])
97/// and its tables, or the text of a JSON module.
98pub enum ModuleSource {
99    Compiled {
100        function: ObjectId,
101        tables: ModuleTables,
102        /// Properties of the module's `import.meta`, in order (qjs's `url`
103        /// and `main`); the values must be rooted by the loader.
104        import_meta: Vec<(String, Value)>,
105    },
106    Json(JsonText),
107    /// A JSON5 module (QuickJS's extended JSON, `type: "json5"`).
108    Json5(JsonText),
109    /// A module whose exports the embedder supplies, each a binding of
110    /// its own (`default` is the default export); the values must be
111    /// rooted by the loader.
112    Synthetic {
113        exports: Vec<(String, Value)>,
114    },
115}
116
117/// A JSON module's text: UTF-16 units, and the units that stand for bytes
118/// of invalid UTF-8 in the file, which QuickJS's byte-level parser rejects
119/// with `Bad UTF-8 sequence` where a token reads them (not in a comment).
120#[derive(Clone, Debug, Default, PartialEq, Eq)]
121pub struct JsonText {
122    pub units: Vec<u16>,
123    /// Sorted indices into `units`.
124    pub invalid: Vec<u32>,
125    /// Sorted indices of the low halves of four-byte sequences and of stray
126    /// continuation bytes, which QuickJS's byte columns do not count (a
127    /// CESU-8 pair counts twice).
128    pub no_column: Vec<u32>,
129}
130
131impl From<Vec<u16>> for JsonText {
132    fn from(units: Vec<u16>) -> Self {
133        JsonText {
134            units,
135            invalid: Vec::new(),
136            no_column: Vec::new(),
137        }
138    }
139}
140
141impl JsonText {
142    /// The text of `bytes` decoded as QuickJS decodes UTF-8 (lone and
143    /// paired surrogates as is); each byte of an invalid sequence becomes
144    /// one inert unit marked invalid. `decode` is QuickJS's code-point
145    /// decoder: the code point and the bytes it used, or `None`.
146    pub fn from_bytes(bytes: &[u8], decode: impl Fn(&[u8]) -> Option<(u32, usize)>) -> Self {
147        let mut text = JsonText {
148            units: Vec::with_capacity(bytes.len()),
149            invalid: Vec::new(),
150            no_column: Vec::new(),
151        };
152        let mut offset = 0;
153        while offset < bytes.len() {
154            let byte = bytes[offset];
155            if byte < 0x80 {
156                text.units.push(u16::from(byte));
157                offset += 1;
158                continue;
159            }
160            match decode(&bytes[offset..]) {
161                Some((code, used)) if code <= 0x10_ffff => {
162                    if code <= 0xffff {
163                        text.units.push(code as u16);
164                    } else {
165                        let scalar = code - 0x1_0000;
166                        text.units.push(0xd800 | (scalar >> 10) as u16);
167                        text.no_column.push(text.units.len() as u32);
168                        text.units.push(0xdc00 | (scalar & 0x3ff) as u16);
169                    }
170                    offset += used;
171                }
172                _ => {
173                    // A stray continuation byte adds no column either.
174                    if (0x80..0xc0).contains(&byte) {
175                        text.no_column.push(text.units.len() as u32);
176                    }
177                    text.invalid.push(text.units.len() as u32);
178                    text.units.push(0x7f);
179                    offset += 1;
180                }
181            }
182        }
183        text
184    }
185}
186
187/// Resolves `specifier` against the module or script named `referrer`.
188pub type NormalizeFn =
189    fn(&mut Compiler<'_>, &str, &str, &[(String, String)]) -> Result<String, CompileError>;
190
191/// Loads and compiles the module named `name`.
192pub type LoadModuleFn =
193    fn(&mut Compiler<'_>, &str, &[(String, String)]) -> Result<ModuleSource, CompileError>;
194
195/// The step of running a module that threw.
196#[derive(Clone, Copy, Debug, PartialEq, Eq)]
197pub enum ModulePhase {
198    Load,
199    Link,
200    Evaluate,
201}
202
203/// The embedder's module loader.
204#[derive(Clone, Copy)]
205pub struct ModuleHost {
206    pub normalize: NormalizeFn,
207    pub load: LoadModuleFn,
208}
209
210// ---- records ----
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
213enum Status {
214    Unlinked,
215    Linking,
216    Linked,
217    Evaluating,
218    EvaluatingAsync,
219    Evaluated,
220}
221
222/// A module record's non-value state.
223#[derive(Debug)]
224pub struct ModuleData {
225    name: String,
226    tables: ModuleTables,
227    status: Status,
228    /// The records of `tables.requests`, once loaded.
229    requested: Vec<ObjectId>,
230    dfs_index: u32,
231    dfs_ancestor_index: u32,
232    /// `[[AsyncEvaluation]]`'s order, when true.
233    async_evaluation: Option<u64>,
234    pending_async_dependencies: u32,
235    async_parents: Vec<ObjectId>,
236    cycle_root: Option<ObjectId>,
237}
238
239// Slots of a module record.
240const ENVIRONMENT: usize = 0;
241const FUNCTION: usize = 1;
242const NAMESPACE: usize = 2;
243const ERROR: usize = 3;
244const CAPABILITY: usize = 4;
245const SLOTS: usize = 7;
246
247thread_local! {
248    static ASYNC_ORDER: Cell<u64> = const { Cell::new(0) };
249}
250
251fn data<'a>(cx: &'a Cx<'_>, module: ObjectId) -> &'a ModuleData {
252    match &cx.heap.internal(module).data {
253        InternalData::Module(data) => data,
254        _ => unreachable!("a module record"),
255    }
256}
257
258fn data_mut<'a>(cx: &'a mut Cx<'_>, module: ObjectId) -> &'a mut ModuleData {
259    match &mut cx.heap.internal_mut(module).data {
260        InternalData::Module(data) => data,
261        _ => unreachable!("a module record"),
262    }
263}
264
265fn slot(cx: &Cx<'_>, module: ObjectId, index: usize) -> Value {
266    cx.heap.internal_slot(module, index)
267}
268
269fn set_slot(cx: &mut Cx<'_>, module: ObjectId, index: usize, value: Value) {
270    cx.heap.set_internal_slot(module, index, value);
271}
272
273fn environment(cx: &Cx<'_>, module: ObjectId) -> ScopeId {
274    ScopeId(
275        slot(cx, module, ENVIRONMENT)
276            .as_object()
277            .expect("an environment")
278            .index(),
279    )
280}
281
282/// The cell of binding `binding` of `module`.
283fn cell(cx: &Cx<'_>, module: ObjectId, binding: u16) -> Value {
284    cx.heap
285        .scope_get(environment(cx, module), 0, u32::from(binding))
286}
287
288fn compile_error(cx: &mut Cx<'_>, failure: CompileError) -> Throw {
289    super::compile::throw_failure(cx, failure)
290}
291
292/// The record of the module named `name`, loading and compiling it (and,
293/// recursively, what it requests) on first use.
294fn load(cx: &mut Cx<'_>, name: &str, attributes: &[(String, String)]) -> Result<ObjectId, Throw> {
295    if let Some(&module) = cx.host.modules.get(name) {
296        return Ok(module);
297    }
298    let Some(host) = cx.host.module_host else {
299        return Err(error::throw(
300            cx,
301            ErrorKind::Internal,
302            "unsupported: modules",
303        ));
304    };
305    let source = (host.load)(
306        &mut Compiler::new(cx.heap, cx.static_code, cx.realm),
307        name,
308        attributes,
309    );
310    let source = source.map_err(|failure| compile_error(cx, failure))?;
311    let mark = cx.heap.roots().mark();
312    let result = match source {
313        ModuleSource::Compiled {
314            function,
315            tables,
316            import_meta,
317        } => {
318            cx.heap.roots().push(Value::object(function));
319            create(cx, name, Some(function), tables, &import_meta)
320        }
321        ModuleSource::Json(text) => json_module(cx, name, &text, false),
322        ModuleSource::Json5(text) => json_module(cx, name, &text, true),
323        ModuleSource::Synthetic { exports } => synthetic_module(cx, name, &exports),
324    };
325    cx.heap.roots().truncate(mark);
326    let module = result?;
327    // The requested modules, depth first.
328    let requests = data(cx, module).tables.requests.clone();
329    let mut requested = Vec::with_capacity(requests.len());
330    for request in &requests {
331        let normalized = (host.normalize)(
332            &mut Compiler::new(cx.heap, cx.static_code, cx.realm),
333            name,
334            &request.specifier,
335            &request.attributes,
336        );
337        let normalized = normalized.map_err(|failure| compile_error(cx, failure))?;
338        requested.push(load(cx, &normalized, &request.attributes)?);
339    }
340    data_mut(cx, module).requested = requested;
341    Ok(module)
342}
343
344/// A record for a freshly compiled module, with its environment: a cell per
345/// binding (an uninitialized lexical one empty, `import.meta` its object),
346/// imports filled while linking.
347/// A JSON module: its one binding, the default export, is the parsed text.
348fn json_module(
349    cx: &mut Cx<'_>,
350    name: &str,
351    text: &JsonText,
352    extended: bool,
353) -> Result<ObjectId, Throw> {
354    let value = crate::builtins::json_module_text(cx, text, name, extended)?;
355    cx.heap.roots().push(value);
356    synthetic_module(cx, name, &[("default".into(), value)])
357}
358
359/// A module of the given exports: one non-lexical binding per name, set to
360/// its value, and nothing to evaluate.
361fn synthetic_module(
362    cx: &mut Cx<'_>,
363    name: &str,
364    exports: &[(String, Value)],
365) -> Result<ObjectId, Throw> {
366    if exports.len() > usize::from(u16::MAX) {
367        return Err(error::throw(
368            cx,
369            ErrorKind::Range,
370            "too many module exports",
371        ));
372    }
373    for (_, value) in exports {
374        cx.heap.roots().push(*value);
375    }
376    let tables = ModuleTables {
377        bindings: vec![ModuleBinding::Declaration { lexical: false }; exports.len()],
378        exports: exports
379            .iter()
380            .enumerate()
381            .map(|(index, (name, _))| ModuleExport {
382                name: name.as_str().into(),
383                target: ExportTarget::Local(index as u16),
384            })
385            .collect(),
386        ..ModuleTables::default()
387    };
388    let module = create(cx, name, None, tables, &[])?;
389    for (index, (_, value)) in exports.iter().enumerate() {
390        let holder = cell(cx, module, index as u16);
391        cx.heap
392            .scope_set(ScopeId(holder.as_object().unwrap().index()), 0, 0, *value);
393    }
394    Ok(module)
395}
396
397fn create(
398    cx: &mut Cx<'_>,
399    name: &str,
400    function: Option<ObjectId>,
401    tables: ModuleTables,
402    import_meta: &[(String, Value)],
403) -> Result<ObjectId, Throw> {
404    let source = ModuleParts { function, tables };
405    let heap_error = |cx: &mut Cx<'_>, error| error::heap_error(cx, error);
406    let count = source.tables.bindings.len() as u32;
407    let scope = cx
408        .heap
409        .alloc_scope(None, count)
410        .map_err(|e| heap_error(cx, e))?;
411    cx.heap.roots().push(Value::object(scope.object()));
412    for (index, binding) in source.tables.bindings.iter().enumerate() {
413        let initial = match binding {
414            ModuleBinding::Declaration { lexical: true } => Value::EMPTY,
415            ModuleBinding::Declaration { lexical: false } | ModuleBinding::Import => {
416                Value::UNDEFINED
417            }
418            ModuleBinding::ImportMeta => {
419                let meta = cx
420                    .heap
421                    .alloc_object(ObjectKind::Ordinary, None, 2)
422                    .map_err(|e| heap_error(cx, e))?;
423                cx.heap.roots().push(Value::object(meta));
424                for (key, value) in import_meta {
425                    let key = cx.heap.property_key(key).map_err(|e| heap_error(cx, e))?;
426                    cx.heap
427                        .add_property(meta, key, crate::PropertyFlags::DEFAULT, *value)
428                        .map_err(|e| heap_error(cx, e))?;
429                }
430                Value::object(meta)
431            }
432        };
433        if *binding == ModuleBinding::Import {
434            continue;
435        }
436        let cell = cx
437            .heap
438            .alloc_scope(None, 1)
439            .map_err(|e| heap_error(cx, e))?;
440        cx.heap.scope_set(cell, 0, 0, initial);
441        cx.heap
442            .scope_set(scope, 0, index as u32, Value::object(cell.object()));
443    }
444    if let Some(function) = source.function {
445        cx.heap.set_function_scope(function, scope);
446    }
447    let mut slots = vec![Value::UNDEFINED; SLOTS];
448    slots[ENVIRONMENT] = Value::object(scope.object());
449    slots[FUNCTION] = source.function.map_or(Value::UNDEFINED, Value::object);
450    slots[ERROR] = Value::EMPTY;
451    let record = ModuleData {
452        name: name.to_owned(),
453        tables: source.tables,
454        status: Status::Unlinked,
455        requested: Vec::new(),
456        dfs_index: 0,
457        dfs_ancestor_index: 0,
458        async_evaluation: None,
459        pending_async_dependencies: 0,
460        async_parents: Vec::new(),
461        cycle_root: None,
462    };
463    let module = cx
464        .heap
465        .alloc_internal(
466            ObjectKind::Builtin,
467            InternalTag::Module,
468            None,
469            0,
470            &slots,
471            InternalData::Module(Box::new(record)),
472            0,
473        )
474        .map_err(|e| heap_error(cx, e))?;
475    cx.heap.modules.push(Value::object(module));
476    cx.host.modules.insert(name.to_owned(), module);
477    Ok(module)
478}
479
480struct ModuleParts {
481    function: Option<ObjectId>,
482    tables: ModuleTables,
483}
484
485// ---- exports ----
486
487/// What an export resolves to: a module's binding, or its namespace.
488#[derive(Clone, Copy, Debug, PartialEq, Eq)]
489enum Resolution {
490    Binding(ObjectId, u16),
491    Namespace(ObjectId),
492}
493
494enum Resolved {
495    Found(Resolution),
496    NotFound,
497    /// The walk met a `(module, name)` pair it is resolving already.
498    Circular,
499    Ambiguous,
500}
501
502/// `ResolveExport(exportName, resolveSet)`.
503fn resolve_export(
504    cx: &Cx<'_>,
505    module: ObjectId,
506    name: &str,
507    set: &mut Vec<(ObjectId, String)>,
508) -> Resolved {
509    if set.iter().any(|(m, n)| *m == module && n == name) {
510        return Resolved::Circular;
511    }
512    set.push((module, name.to_owned()));
513    let record = data(cx, module);
514    for export in &record.tables.exports {
515        if export.name != name {
516            continue;
517        }
518        return match export.target.clone() {
519            ExportTarget::Local(binding) => {
520                // A re-exported import resolves where the import does.
521                match record
522                    .tables
523                    .imports
524                    .iter()
525                    .find(|import| import.binding == binding)
526                {
527                    Some(import) => {
528                        let imported = record.requested[import.request as usize];
529                        let import_name = import.name.clone();
530                        match import_name {
531                            None => Resolved::Found(Resolution::Namespace(imported)),
532                            Some(import_name) => resolve_export(cx, imported, &import_name, set),
533                        }
534                    }
535                    None => Resolved::Found(Resolution::Binding(module, binding)),
536                }
537            }
538            ExportTarget::Indirect {
539                request,
540                name: import_name,
541            } => {
542                let imported = record.requested[request as usize];
543                match import_name {
544                    None => Resolved::Found(Resolution::Namespace(imported)),
545                    Some(import_name) => resolve_export(cx, imported, &import_name, set),
546                }
547            }
548        };
549    }
550    if name == "default" {
551        return Resolved::NotFound;
552    }
553    let mut star: Option<Resolution> = None;
554    for &request in &record.tables.star_exports {
555        let imported = record.requested[request as usize];
556        match resolve_export(cx, imported, name, set) {
557            Resolved::Ambiguous => return Resolved::Ambiguous,
558            Resolved::NotFound | Resolved::Circular => {}
559            Resolved::Found(resolution) => match star {
560                None => star = Some(resolution),
561                Some(existing) if existing != resolution => return Resolved::Ambiguous,
562                Some(_) => {}
563            },
564        }
565    }
566    match star {
567        Some(resolution) => Resolved::Found(resolution),
568        None => Resolved::NotFound,
569    }
570}
571
572/// `GetExportedNames(exportStarSet)`.
573fn exported_names(cx: &Cx<'_>, module: ObjectId, set: &mut Vec<ObjectId>) -> Vec<String> {
574    if set.contains(&module) {
575        return Vec::new();
576    }
577    set.push(module);
578    let record = data(cx, module);
579    let mut names: Vec<String> = record
580        .tables
581        .exports
582        .iter()
583        .map(|export| export.name.clone())
584        .collect();
585    for &request in &record.tables.star_exports {
586        let imported = record.requested[request as usize];
587        for name in exported_names(cx, imported, set) {
588            if name != "default" && !names.contains(&name) {
589                names.push(name);
590            }
591        }
592    }
593    names
594}
595
596fn syntax(cx: &mut Cx<'_>, message: String) -> Throw {
597    error::throw(cx, ErrorKind::Syntax, &message)
598}
599
600/// QuickJS's `js_resolve_export_throw_error` for `name` in `module`.
601#[cold]
602#[inline(never)]
603fn resolve_error(cx: &mut Cx<'_>, resolved: &Resolved, module: ObjectId, name: &str) -> Throw {
604    let name = super::property::str_atom_text(name);
605    let module = super::property::str_atom_text(&data(cx, module).name);
606    let message = match resolved {
607        Resolved::Circular => {
608            format!("circular reference when looking for export '{name}' in module '{module}'")
609        }
610        Resolved::Ambiguous => format!("export '{name}' in module '{module}' is ambiguous"),
611        _ => format!("Could not find export '{name}' in module '{module}'"),
612    };
613    syntax(cx, message)
614}
615
616/// `GetModuleNamespace(module)`.
617pub(crate) fn namespace(cx: &mut Cx<'_>, module: ObjectId) -> Result<ObjectId, Throw> {
618    if let Some(namespace) = slot(cx, module, NAMESPACE).as_object() {
619        return Ok(namespace);
620    }
621    let names = exported_names(cx, module, &mut Vec::new());
622    let mut exports: Vec<(Vec<u16>, Resolution)> = Vec::new();
623    for name in names {
624        if let Resolved::Found(resolution) = resolve_export(cx, module, &name, &mut Vec::new()) {
625            exports.push((name.encode_utf16().collect(), resolution));
626        }
627    }
628    exports.sort_by(|a, b| a.0.cmp(&b.0));
629    let mut keys = Vec::with_capacity(exports.len());
630    let mut slots = vec![Value::object(module)];
631    // The keys are rooted until the namespace holds them.
632    let mark = cx.heap.roots().mark();
633    let namespace = (|| {
634        for (name, resolution) in &exports {
635            let key = cx
636                .heap
637                .property_key_units(name)
638                .map_err(|e| error::heap_error(cx, e))?;
639            if let Some(value) = key.heap_value() {
640                cx.heap.roots().push(value);
641            }
642            keys.push(key);
643            slots.push(match *resolution {
644                Resolution::Binding(owner, binding) => cell(cx, owner, binding),
645                Resolution::Namespace(owner) => Value::object(owner),
646            });
647        }
648        cx.heap
649            .alloc_internal(
650                ObjectKind::ModuleNamespace,
651                InternalTag::None,
652                None,
653                1,
654                &slots,
655                InternalData::Exports(std::mem::take(&mut keys).into_boxed_slice()),
656                0,
657            )
658            .map_err(|e| error::heap_error(cx, e))
659    })();
660    cx.heap.roots().truncate(mark);
661    let namespace = namespace?;
662    cx.heap.clear_object_flag(namespace, flags::EXTENSIBLE);
663    // Interning may collect: the namespace is not reachable yet.
664    let mark = cx.heap.roots().mark();
665    cx.heap.roots().push(Value::object(namespace));
666    let tag = cx.heap.intern("Module");
667    cx.heap.roots().truncate(mark);
668    let tag = tag.map_err(|e| error::heap_error(cx, e))?;
669    let key = crate::runtime::WellKnown::key(cx.realm.symbols.to_string_tag);
670    cx.heap
671        .add_property(
672            namespace,
673            key,
674            crate::PropertyFlags::data(false, false, false),
675            Value::string(tag),
676        )
677        .map_err(|e| error::heap_error(cx, e))?;
678    set_slot(cx, module, NAMESPACE, Value::object(namespace));
679    Ok(namespace)
680}
681
682/// The value of a namespace's export at `position`, or a `ReferenceError`
683/// while it is uninitialized.
684pub(crate) fn namespace_value(
685    cx: &mut Cx<'_>,
686    object: ObjectId,
687    position: usize,
688) -> Result<Value, Throw> {
689    let holder = cx.heap.internal_slot(object, position + 1);
690    let holder = holder.as_object().expect("a binding holder");
691    if cx.heap.internal_tag(holder) == InternalTag::Module {
692        return namespace(cx, holder).map(Value::object);
693    }
694    let value = cx.heap.scope_get(ScopeId(holder.index()), 0, 0);
695    if value.is_empty() {
696        let key = namespace_keys(cx, object)[position];
697        let message = format!(
698            "{} is not initialized",
699            super::property::atom_text(cx.heap, key)
700        );
701        return Err(error::throw(cx, ErrorKind::Reference, &message));
702    }
703    Ok(value)
704}
705
706/// A namespace's export keys, sorted.
707pub(crate) fn namespace_keys<'a>(cx: &'a Cx<'_>, namespace: ObjectId) -> &'a [PropertyKey] {
708    match &cx.heap.internal(namespace).data {
709        InternalData::Exports(keys) => keys,
710        _ => unreachable!("a namespace"),
711    }
712}
713
714/// The position of export `key` of a namespace.
715pub(crate) fn namespace_position(
716    cx: &Cx<'_>,
717    namespace: ObjectId,
718    key: PropertyKey,
719) -> Option<usize> {
720    if matches!(key.kind(), KeyKind::Symbol(_)) {
721        return None;
722    }
723    namespace_keys(cx, namespace)
724        .iter()
725        .position(|&export| export == key)
726}
727
728// ---- linking ----
729
730/// `Link()`.
731fn link(cx: &mut Cx<'_>, module: ObjectId) -> Result<(), Throw> {
732    let mut stack = Vec::new();
733    if let Err(throw) = inner_link(cx, module, &mut stack, 0) {
734        for module in stack {
735            data_mut(cx, module).status = Status::Unlinked;
736        }
737        return Err(throw);
738    }
739    Ok(())
740}
741
742/// `InnerModuleLinking(module, stack, index)`.
743fn inner_link(
744    cx: &mut Cx<'_>,
745    module: ObjectId,
746    stack: &mut Vec<ObjectId>,
747    mut index: u32,
748) -> Result<u32, Throw> {
749    if data(cx, module).status != Status::Unlinked {
750        return Ok(index);
751    }
752    let record = data_mut(cx, module);
753    record.status = Status::Linking;
754    record.dfs_index = index;
755    record.dfs_ancestor_index = index;
756    index += 1;
757    stack.push(module);
758    let requested = data(cx, module).requested.clone();
759    for required in requested {
760        index = inner_link(cx, required, stack, index)?;
761        let required = data(cx, required);
762        if required.status == Status::Linking {
763            let ancestor = required.dfs_ancestor_index;
764            let record = data_mut(cx, module);
765            record.dfs_ancestor_index = record.dfs_ancestor_index.min(ancestor);
766        }
767    }
768    initialize_environment(cx, module)?;
769    let record = data(cx, module);
770    if record.dfs_ancestor_index == record.dfs_index {
771        while let Some(member) = stack.pop() {
772            data_mut(cx, member).status = Status::Linked;
773            if member == module {
774                break;
775            }
776        }
777    }
778    Ok(index)
779}
780
781/// `InitializeEnvironment()`: checks the indirect exports, installs the
782/// imports' cells and creates the hoisted functions.
783fn initialize_environment(cx: &mut Cx<'_>, module: ObjectId) -> Result<(), Throw> {
784    let tables = data(cx, module).tables.clone();
785    let requested = data(cx, module).requested.clone();
786    for export in &tables.exports {
787        if let ExportTarget::Indirect { name: Some(_), .. } = export.target {
788            match resolve_export(cx, module, &export.name, &mut Vec::new()) {
789                Resolved::Found(_) => {}
790                failure => return Err(resolve_error(cx, &failure, module, &export.name)),
791            }
792        }
793    }
794    let scope = environment(cx, module);
795    for import in &tables.imports {
796        let imported = requested[import.request as usize];
797        let holder = match &import.name {
798            None => {
799                let namespace = namespace(cx, imported)?;
800                let cell = cx
801                    .heap
802                    .alloc_scope(None, 1)
803                    .map_err(|e| error::heap_error(cx, e))?;
804                cx.heap.scope_set(cell, 0, 0, Value::object(namespace));
805                Value::object(cell.object())
806            }
807            Some(name) => match resolve_export(cx, imported, name, &mut Vec::new()) {
808                Resolved::Found(Resolution::Binding(owner, binding)) => cell(cx, owner, binding),
809                Resolved::Found(Resolution::Namespace(owner)) => {
810                    let namespace = namespace(cx, owner)?;
811                    let cell = cx
812                        .heap
813                        .alloc_scope(None, 1)
814                        .map_err(|e| error::heap_error(cx, e))?;
815                    cx.heap.scope_set(cell, 0, 0, Value::object(namespace));
816                    Value::object(cell.object())
817                }
818                failure => return Err(resolve_error(cx, &failure, imported, name)),
819            },
820        };
821        cx.heap
822            .scope_set(scope, 0, u32::from(import.binding), holder);
823    }
824    let Some(function) = slot(cx, module, FUNCTION).as_object() else {
825        return Ok(());
826    };
827    for initializer in &tables.initializers {
828        let value = match initializer.function {
829            None => Value::UNDEFINED,
830            Some((child, default_name)) => {
831                let closure = super::interp::closure_over_captures(cx, function, child)?;
832                if default_name {
833                    let name = cx
834                        .heap
835                        .property_key("default")
836                        .map_err(|e| error::heap_error(cx, e))?;
837                    super::interp::set_function_name(cx, closure, name, "", false)?;
838                }
839                Value::object(closure)
840            }
841        };
842        let holder = cell(cx, module, initializer.binding);
843        cx.heap
844            .scope_set(ScopeId(holder.as_object().unwrap().index()), 0, 0, value);
845    }
846    Ok(())
847}
848
849// ---- evaluation ----
850
851/// `Evaluate()`: the promise of the module's evaluation.
852fn evaluate(cx: &mut Cx<'_>, module: ObjectId) -> Result<ObjectId, Throw> {
853    let mut module = module;
854    let status = data(cx, module).status;
855    if matches!(status, Status::EvaluatingAsync | Status::Evaluated)
856        && let Some(root) = data(cx, module).cycle_root
857    {
858        module = root;
859    }
860    if let Some(promise) = slot(cx, module, CAPABILITY).as_object() {
861        return Ok(promise);
862    }
863    let prototype = cx.realm.intrinsic(Intrinsic::PromisePrototype);
864    let promise = super::jobs::new_promise(cx, prototype)?;
865    set_slot(cx, module, CAPABILITY, Value::object(promise));
866    let mut stack = Vec::new();
867    match inner_evaluate(cx, module, &mut stack, 0) {
868        Err(throw) if !super::error::catchable(cx) => return Err(throw),
869        Err(Throw(thrown)) => {
870            for member in stack {
871                data_mut(cx, member).status = Status::Evaluated;
872                set_slot(cx, member, ERROR, thrown);
873            }
874            super::jobs::reject_promise(cx, promise, thrown);
875        }
876        Ok(_) => {
877            if data(cx, module).async_evaluation.is_none() {
878                super::jobs::fulfill_promise(cx, promise, Value::UNDEFINED);
879            }
880        }
881    }
882    Ok(promise)
883}
884
885/// The evaluation error of an evaluated module, if any.
886fn evaluation_error(cx: &Cx<'_>, module: ObjectId) -> Option<Value> {
887    let error = slot(cx, module, ERROR);
888    (!error.is_empty()).then_some(error)
889}
890
891/// `InnerModuleEvaluation(module, stack, index)`.
892fn inner_evaluate(
893    cx: &mut Cx<'_>,
894    module: ObjectId,
895    stack: &mut Vec<ObjectId>,
896    mut index: u32,
897) -> Result<u32, Throw> {
898    match data(cx, module).status {
899        Status::EvaluatingAsync | Status::Evaluated => {
900            return match evaluation_error(cx, module) {
901                Some(error) => Err(Throw(error)),
902                None => Ok(index),
903            };
904        }
905        Status::Evaluating => return Ok(index),
906        _ => {}
907    }
908    let record = data_mut(cx, module);
909    record.status = Status::Evaluating;
910    record.dfs_index = index;
911    record.dfs_ancestor_index = index;
912    record.pending_async_dependencies = 0;
913    index += 1;
914    stack.push(module);
915    let requested = data(cx, module).requested.clone();
916    for required in requested {
917        index = inner_evaluate(cx, required, stack, index)?;
918        let mut required = required;
919        if data(cx, required).status == Status::Evaluating {
920            let ancestor = data(cx, required).dfs_ancestor_index;
921            let record = data_mut(cx, module);
922            record.dfs_ancestor_index = record.dfs_ancestor_index.min(ancestor);
923        } else {
924            required = data(cx, required).cycle_root.unwrap_or(required);
925            if let Some(error) = evaluation_error(cx, required) {
926                return Err(Throw(error));
927            }
928        }
929        if data(cx, required).async_evaluation.is_some() {
930            data_mut(cx, module).pending_async_dependencies += 1;
931            data_mut(cx, required).async_parents.push(module);
932        }
933    }
934    let record = data(cx, module);
935    if record.pending_async_dependencies > 0 || record.tables.has_top_level_await {
936        let order = ASYNC_ORDER.with(|order| {
937            order.set(order.get() + 1);
938            order.get()
939        });
940        data_mut(cx, module).async_evaluation = Some(order);
941        if data(cx, module).pending_async_dependencies == 0 {
942            execute_async(cx, module)?;
943        }
944    } else {
945        execute(cx, module)?;
946    }
947    let record = data(cx, module);
948    if record.dfs_ancestor_index == record.dfs_index {
949        while let Some(member) = stack.pop() {
950            let record = data_mut(cx, member);
951            record.status = if record.async_evaluation.is_none() {
952                Status::Evaluated
953            } else {
954                Status::EvaluatingAsync
955            };
956            record.cycle_root = Some(module);
957            if member == module {
958                break;
959            }
960        }
961    }
962    Ok(index)
963}
964
965/// Runs a module's body: synchronous, or for one with top-level `await`
966/// the promise its asynchronous root returns.
967fn execute(cx: &mut Cx<'_>, module: ObjectId) -> Result<Value, Throw> {
968    let function = slot(cx, module, FUNCTION);
969    if function.is_undefined() {
970        return Ok(Value::UNDEFINED);
971    }
972    let result = super::call(cx, function, Value::UNDEFINED, &[])?;
973    // A module's root is an asynchronous function; without top-level
974    // `await` it has settled when the call returns.
975    if !data(cx, module).tables.has_top_level_await
976        && let Some(promise) = result.as_object()
977        && super::jobs::is_promise(cx, result)
978        && super::jobs::state(cx, promise) == super::jobs::REJECTED
979    {
980        cx.heap
981            .set_internal_slot(promise, super::jobs::HANDLED, Value::TRUE);
982        return Err(Throw(super::jobs::result(cx, promise)));
983    }
984    Ok(result)
985}
986
987/// `ExecuteAsyncModule(module)`.
988fn execute_async(cx: &mut Cx<'_>, module: ObjectId) -> Result<(), Throw> {
989    let mark = cx.heap.roots().mark();
990    let result = (|| {
991        let data_object = native_data(cx, &[Value::object(module)])?;
992        cx.heap.roots().push(Value::object(data_object));
993        let fulfilled = native_closure(cx, Native::ModuleAsyncFulfilled, data_object)?;
994        cx.heap.roots().push(Value::object(fulfilled));
995        let rejected = native_closure(cx, Native::ModuleAsyncRejected, data_object)?;
996        cx.heap.roots().push(Value::object(rejected));
997        let promise = execute(cx, module)?;
998        cx.heap.roots().push(promise);
999        let promise = match promise.as_object() {
1000            Some(promise) if super::jobs::is_promise(cx, Value::object(promise)) => promise,
1001            _ => {
1002                // A module the host compiled synchronously: settled now.
1003                let prototype = cx.realm.intrinsic(Intrinsic::PromisePrototype);
1004                let settled = super::jobs::new_promise(cx, prototype)?;
1005                super::jobs::fulfill_promise(cx, settled, Value::UNDEFINED);
1006                settled
1007            }
1008        };
1009        super::jobs::perform_then(
1010            cx,
1011            promise,
1012            Value::object(fulfilled),
1013            Value::object(rejected),
1014            None,
1015            Value::UNDEFINED,
1016        )
1017    })();
1018    cx.heap.roots().truncate(mark);
1019    result
1020}
1021
1022/// `AsyncModuleExecutionFulfilled(module)`.
1023fn async_fulfilled(cx: &mut Cx<'_>, module: ObjectId) -> Result<(), Throw> {
1024    if data(cx, module).status == Status::Evaluated {
1025        return Ok(());
1026    }
1027    let record = data_mut(cx, module);
1028    record.async_evaluation = None;
1029    record.status = Status::Evaluated;
1030    if let Some(promise) = slot(cx, module, CAPABILITY).as_object() {
1031        super::jobs::fulfill_promise(cx, promise, Value::UNDEFINED);
1032    }
1033    let mut list = Vec::new();
1034    gather_available_ancestors(cx, module, &mut list);
1035    list.sort_by_key(|&member| data(cx, member).async_evaluation.unwrap_or(u64::MAX));
1036    for member in list {
1037        if data(cx, member).status == Status::Evaluated {
1038            continue;
1039        }
1040        if data(cx, member).tables.has_top_level_await {
1041            execute_async(cx, member)?;
1042        } else {
1043            match execute(cx, member) {
1044                Err(throw) if !super::error::catchable(cx) => return Err(throw),
1045                Err(Throw(thrown)) => async_rejected(cx, member, thrown),
1046                Ok(_) => {
1047                    let record = data_mut(cx, member);
1048                    record.async_evaluation = None;
1049                    record.status = Status::Evaluated;
1050                    if let Some(promise) = slot(cx, member, CAPABILITY).as_object() {
1051                        super::jobs::fulfill_promise(cx, promise, Value::UNDEFINED);
1052                    }
1053                }
1054            }
1055        }
1056    }
1057    Ok(())
1058}
1059
1060/// `GatherAvailableAncestors(module, execList)`.
1061fn gather_available_ancestors(cx: &mut Cx<'_>, module: ObjectId, list: &mut Vec<ObjectId>) {
1062    let parents = data(cx, module).async_parents.clone();
1063    for parent in parents {
1064        let root = data(cx, parent).cycle_root.unwrap_or(parent);
1065        if list.contains(&parent) || evaluation_error(cx, root).is_some() {
1066            continue;
1067        }
1068        let record = data_mut(cx, parent);
1069        record.pending_async_dependencies = record.pending_async_dependencies.saturating_sub(1);
1070        if record.pending_async_dependencies == 0 {
1071            list.push(parent);
1072            if !data(cx, parent).tables.has_top_level_await {
1073                gather_available_ancestors(cx, parent, list);
1074            }
1075        }
1076    }
1077}
1078
1079/// `AsyncModuleExecutionRejected(module, error)`.
1080fn async_rejected(cx: &mut Cx<'_>, module: ObjectId, error: Value) {
1081    if data(cx, module).status == Status::Evaluated {
1082        return;
1083    }
1084    set_slot(cx, module, ERROR, error);
1085    let record = data_mut(cx, module);
1086    record.status = Status::Evaluated;
1087    record.async_evaluation = None;
1088    if let Some(promise) = slot(cx, module, CAPABILITY).as_object() {
1089        super::jobs::reject_promise(cx, promise, error);
1090    }
1091    let parents = data(cx, module).async_parents.clone();
1092    for parent in parents {
1093        async_rejected(cx, parent, error);
1094    }
1095}
1096
1097pub(crate) fn module_async_fulfilled(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
1098    let module = site.data(cx, 0).as_object().unwrap();
1099    async_fulfilled(cx, module)?;
1100    Ok(Value::UNDEFINED)
1101}
1102
1103pub(crate) fn module_async_rejected(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
1104    let module = site.data(cx, 0).as_object().unwrap();
1105    let error = site.arg(cx, 0);
1106    async_rejected(cx, module, error);
1107    Ok(Value::UNDEFINED)
1108}
1109
1110// ---- entry points ----
1111
1112/// Loads, links and evaluates the module named `name`: the promise of its
1113/// evaluation.
1114pub(crate) fn run(cx: &mut Cx<'_>, name: &str) -> Result<ObjectId, Throw> {
1115    let module = load(cx, name, &[])?;
1116    link(cx, module)?;
1117    evaluate(cx, module)
1118}
1119
1120/// The name of the module or script running `function`'s code, for
1121/// `import()`.
1122///
1123/// As QuickJS's `JS_GetScriptOrModuleName`, the root of `eval` code takes the
1124/// name of the first frame below it that is not `eval` code.
1125fn referrer(cx: &Cx<'_>, function: ObjectId) -> String {
1126    let unit_of = |function: ObjectId| {
1127        cx.heap
1128            .function_bytecode(function)
1129            .map(|bytecode| cx.unit(cx.heap.bytecode_unit(bytecode)))
1130    };
1131    let name = |unit: &crate::bytecode::CodeUnit| {
1132        unit.debug
1133            .filename
1134            .as_deref()
1135            .unwrap_or_default()
1136            .to_owned()
1137    };
1138    let Some(unit) = unit_of(function) else {
1139        return String::new();
1140    };
1141    if !unit.has(crate::bytecode::flags::EVAL) {
1142        return name(&unit);
1143    }
1144    let frames = &cx.heap.vm.frames;
1145    let below = frames
1146        .iter()
1147        .rposition(|frame| frame.function == function)
1148        .map_or(&frames[..0], |at| &frames[..at]);
1149    below
1150        .iter()
1151        .rev()
1152        .map(|frame| cx.unit(frame.unit))
1153        .find(|unit| !unit.has(crate::bytecode::flags::EVAL))
1154        .map(|unit| name(&unit))
1155        .unwrap_or_default()
1156}
1157
1158/// `import(specifier, options)` from code of `function`: a promise of the
1159/// module's namespace. The specifier and options convert now; the loading,
1160/// linking and evaluation run in a job, as QuickJS's
1161/// `js_dynamic_import_job` does, so they never preempt an evaluation in
1162/// progress.
1163pub(crate) fn import(
1164    cx: &mut Cx<'_>,
1165    function: ObjectId,
1166    specifier: Value,
1167    options: Value,
1168) -> Result<Value, Throw> {
1169    let prototype = cx.realm.intrinsic(Intrinsic::PromisePrototype);
1170    let promise = super::jobs::new_promise(cx, prototype)?;
1171    let mark = cx.heap.roots().mark();
1172    cx.heap.roots().push(Value::object(promise));
1173    let outcome = (|| -> Result<(), Throw> {
1174        let specifier = super::ops::to_string(cx, specifier)?;
1175        cx.heap.roots().push(specifier);
1176        let attributes = import_attributes(cx, options)?;
1177        let kind = match attributes.iter().find(|(key, _)| key == "type") {
1178            Some((_, value)) => crate::builtins::string_text(cx, value)?,
1179            None => Value::UNDEFINED,
1180        };
1181        cx.heap.roots().push(kind);
1182        let referrer = referrer(cx, function);
1183        let referrer = crate::builtins::string_text(cx, &referrer)?;
1184        cx.heap.roots().push(referrer);
1185        let data_object = native_data(cx, &[Value::object(promise), specifier, referrer, kind])?;
1186        cx.heap.roots().push(Value::object(data_object));
1187        let job = native_closure(cx, Native::ModuleImportJob, data_object)?;
1188        cx.heap.jobs.push_back(super::jobs::Job::Call {
1189            function: Value::object(job),
1190            this: Value::UNDEFINED,
1191            args: Vec::new(),
1192        });
1193        Ok(())
1194    })();
1195    if let Err(Throw(thrown)) = outcome {
1196        if !super::error::catchable(cx) {
1197            cx.heap.roots().truncate(mark);
1198            return Err(Throw(thrown));
1199        }
1200        super::jobs::reject_promise(cx, promise, thrown);
1201    }
1202    cx.heap.roots().truncate(mark);
1203    Ok(Value::object(promise))
1204}
1205
1206/// The job of an `import()`: load, link and evaluate, then settle its
1207/// promise with the namespace once the evaluation has.
1208pub(crate) fn module_import_job(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
1209    let promise = site.data(cx, 0).as_object().unwrap();
1210    let specifier = site.data(cx, 1);
1211    let referrer = site.data(cx, 2);
1212    let kind = site.data(cx, 3);
1213    let outcome = (|| -> Result<(), Throw> {
1214        let specifier = cx.heap.string_text(specifier.as_string().unwrap());
1215        let referrer = cx.heap.string_text(referrer.as_string().unwrap());
1216        let attributes = match kind.as_string() {
1217            Some(kind) => vec![("type".to_owned(), cx.heap.string_text(kind))],
1218            None => Vec::new(),
1219        };
1220        let Some(host) = cx.host.module_host else {
1221            return Err(error::throw(
1222                cx,
1223                ErrorKind::Internal,
1224                "unsupported: modules",
1225            ));
1226        };
1227        let name = (host.normalize)(
1228            &mut Compiler::new(cx.heap, cx.static_code, cx.realm),
1229            &referrer,
1230            &specifier,
1231            &attributes,
1232        );
1233        let name = name.map_err(|failure| compile_error(cx, failure))?;
1234        let module = load(cx, &name, &attributes)?;
1235        link(cx, module)?;
1236        let evaluation = evaluate(cx, module)?;
1237        cx.heap.roots().push(Value::object(evaluation));
1238        let data_object = native_data(cx, &[Value::object(module), Value::object(promise)])?;
1239        cx.heap.roots().push(Value::object(data_object));
1240        let fulfilled = native_closure(cx, Native::ModuleImportFulfilled, data_object)?;
1241        cx.heap.roots().push(Value::object(fulfilled));
1242        let rejected = native_closure(cx, Native::ModuleImportRejected, data_object)?;
1243        cx.heap.roots().push(Value::object(rejected));
1244        super::jobs::perform_then(
1245            cx,
1246            evaluation,
1247            Value::object(fulfilled),
1248            Value::object(rejected),
1249            None,
1250            Value::UNDEFINED,
1251        )
1252    })();
1253    if let Err(Throw(thrown)) = outcome {
1254        if !super::error::catchable(cx) {
1255            return Err(Throw(thrown));
1256        }
1257        super::jobs::reject_promise(cx, promise, thrown);
1258    }
1259    Ok(Value::UNDEFINED)
1260}
1261
1262/// The `with` attributes of `import()`'s options.
1263fn import_attributes(cx: &mut Cx<'_>, options: Value) -> Result<Vec<(String, String)>, Throw> {
1264    if options.is_undefined() {
1265        return Ok(Vec::new());
1266    }
1267    let Some(options) = options.as_object() else {
1268        return Err(error::type_error(cx, "options must be an object"));
1269    };
1270    let key = cx
1271        .heap
1272        .property_key("with")
1273        .map_err(|e| error::heap_error(cx, e))?;
1274    let with = super::object::get_property(cx, options, key)?;
1275    if with.is_undefined() {
1276        return Ok(Vec::new());
1277    }
1278    let Some(with) = with.as_object() else {
1279        return Err(error::type_error(cx, "options.with must be an object"));
1280    };
1281    let mut attributes = Vec::new();
1282    for key in super::object::enumerable_own_keys(cx, with)? {
1283        let value = super::object::get_property(cx, with, key)?;
1284        let Some(value) = value.as_string() else {
1285            return Err(error::type_error(
1286                cx,
1287                "module attribute values must be strings",
1288            ));
1289        };
1290        let value = cx.heap.string_text(value);
1291        attributes.push((super::property::key_text(cx.heap, key), value));
1292    }
1293    Ok(attributes)
1294}
1295
1296pub(crate) fn module_import_fulfilled(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
1297    let module = site.data(cx, 0).as_object().unwrap();
1298    let promise = site.data(cx, 1).as_object().unwrap();
1299    match namespace(cx, module) {
1300        Ok(namespace) => super::jobs::resolve_promise(cx, promise, Value::object(namespace))?,
1301        Err(Throw(thrown)) => super::jobs::reject_promise(cx, promise, thrown),
1302    }
1303    Ok(Value::UNDEFINED)
1304}
1305
1306pub(crate) fn module_import_rejected(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
1307    let promise = site.data(cx, 1).as_object().unwrap();
1308    let error = site.arg(cx, 0);
1309    super::jobs::reject_promise(cx, promise, error);
1310    Ok(Value::UNDEFINED)
1311}
1312
1313impl crate::Runtime {
1314    /// Installs the embedder's module loader.
1315    pub fn set_module_host(&mut self, host: ModuleHost) {
1316        self.host.module_host = Some(host);
1317    }
1318
1319    /// Drops the module named `name` from the name map: a later import of
1320    /// the name loads it again. Modules that imported it keep the record.
1321    pub fn forget_module(&mut self, name: &str) {
1322        self.native_cx().forget_module(name);
1323    }
1324
1325    /// Loads, links and evaluates the module named `name` (already
1326    /// normalized); returns the promise of its evaluation, unrooted.
1327    pub fn run_module(&mut self, name: &str) -> Result<Value, Throw> {
1328        self.native_cx().run_module(name)
1329    }
1330
1331    /// [`Runtime::run_module`](crate::Runtime::run_module) with the step
1332    /// that threw: loading (compiling the graph), linking or evaluating.
1333    pub fn run_module_phased(&mut self, name: &str) -> Result<Value, (ModulePhase, Throw)> {
1334        let mut cx = self.cx();
1335        let module = load(&mut cx, name, &[]).map_err(|throw| (ModulePhase::Load, throw))?;
1336        link(&mut cx, module).map_err(|throw| (ModulePhase::Link, throw))?;
1337        evaluate(&mut cx, module)
1338            .map(Value::object)
1339            .map_err(|throw| (ModulePhase::Evaluate, throw))
1340    }
1341
1342    /// The state of a promise (0 pending, 1 fulfilled, 2 rejected) and its
1343    /// result.
1344    pub fn promise_state(&mut self, promise: Value) -> Option<(i32, Value)> {
1345        self.native_cx().promise_state(promise)
1346    }
1347}
1348
1349impl crate::NativeCx<'_> {
1350    /// [`Runtime::forget_module`](crate::Runtime::forget_module).
1351    pub fn forget_module(&mut self, name: &str) {
1352        self.host.modules.remove(name);
1353    }
1354
1355    /// [`Runtime::run_module`](crate::Runtime::run_module).
1356    pub fn run_module(&mut self, name: &str) -> Result<Value, Throw> {
1357        run(&mut self.cx(), name).map(Value::object)
1358    }
1359
1360    /// [`Runtime::promise_state`](crate::Runtime::promise_state).
1361    pub fn promise_state(&mut self, promise: Value) -> Option<(i32, Value)> {
1362        let cx = self.cx();
1363        let object = promise.as_object()?;
1364        if !super::jobs::is_promise(&cx, promise) {
1365            return None;
1366        }
1367        Some((
1368            super::jobs::state(&cx, object),
1369            super::jobs::result(&cx, object),
1370        ))
1371    }
1372}