nova_vm 1.0.0

Nova Virtual Machine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use ahash::AHashMap;

use crate::{
    ecmascript::{
        AbstractModule, AbstractModuleSlots, Agent, ExceptionType, JsError, JsResult, String,
        TryResult, Value,
    },
    engine::{Bindable, NoGcScope},
    heap::{CompactionLists, HeapMarkAndSweep, WorkQueues},
};

use super::{
    DeclarativeEnvironment, DeclarativeEnvironmentRecord, Environments, ModuleEnvironment, OuterEnv,
};

/// ### [9.1.1.5 Module Environment Records](https://tc39.es/ecma262/#sec-module-environment-records)
/// A Module Environment Record is a Declarative Environment Record that is
/// used to represent the outer scope of an ECMAScript Module. In additional to
/// normal mutable and immutable bindings, Module Environment Records also
/// provide immutable import bindings which are bindings that provide indirect
/// access to a target binding that exists in another Environment Record.
///
/// Module Environment Records support all of the Declarative Environment
/// Record methods listed in [Table 16](https://tc39.es/ecma262/#table-abstract-methods-of-environment-records)
/// and share the same specifications for all of those methods except for
/// GetBindingValue, DeleteBinding, HasThisBinding and GetThisBinding. In
/// addition, Module Environment Records support the methods listed in
/// # [Table 22](https://tc39.es/ecma262/#table-additional-methods-of-module-environment-records).
///
/// NOTE: There is no data-wise difference between a DeclarativeEnvironment and
/// a ModuleEnvironment, so we treat them exactly the same way.
#[derive(Debug)]
pub(crate) struct ModuleEnvironmentRecord {
    /// Module Environment Records support all of the Declarative Environment
    /// Record methods listed in [Table 16](https://tc39.es/ecma262/#table-abstract-methods-of-environment-records)
    /// and share the same specifications for all of those methods except for
    /// GetBindingValue, DeleteBinding, HasThisBinding and GetThisBinding.
    declarative_environment: DeclarativeEnvironment<'static>,
    indirect_bindings: AHashMap<String<'static>, IndirectBinding<'static>>,
}

#[derive(Debug)]
struct IndirectBinding<'a> {
    /// ### \[\[M]]
    ///
    /// Module record which holds the direct binding for \[\[N2]].
    m: AbstractModule<'a>,
    /// ### \[\[N2]]
    ///
    /// Name of the direct binding in \[\[M]].
    n2: String<'a>,
}

impl ModuleEnvironmentRecord {
    fn new(dcl_env: DeclarativeEnvironment) -> Self {
        Self {
            declarative_environment: dcl_env.unbind(),
            indirect_bindings: Default::default(),
        }
    }

    fn has_indirect_binding(&self, name: String) -> bool {
        self.indirect_bindings.contains_key(&name.unbind())
    }

    fn get_indirect_binding(&self, name: String) -> Option<&IndirectBinding<'static>> {
        self.indirect_bindings.get(&name.unbind())
    }
}

/// 9.1.2.6 NewModuleEnvironment ( E )
///
/// The abstract operation NewModuleEnvironment takes argument E (an
/// Environment Record) and returns a Module Environment Record.
pub(crate) fn new_module_environment<'a>(
    agent: &mut Agent,
    outer_env: OuterEnv,
    gc: NoGcScope<'a, '_>,
) -> ModuleEnvironment<'a> {
    // 1. Let env be a new Module Environment Record containing no bindings.
    agent.heap.alloc_counter += core::mem::size_of::<Option<DeclarativeEnvironmentRecord>>()
        + core::mem::size_of::<Option<ModuleEnvironmentRecord>>();
    // 2. Set env.[[OuterEnv]] to E.
    let declarative_environment = agent
        .heap
        .environments
        .push_declarative_environment(DeclarativeEnvironmentRecord::new(outer_env), gc);
    // 3. Return env.
    agent
        .heap
        .environments
        .push_module_environment(ModuleEnvironmentRecord::new(declarative_environment), gc)
}

impl<'e> ModuleEnvironment<'e> {
    fn get_declarative_env(self, agent: &impl AsRef<Environments>) -> DeclarativeEnvironment<'e> {
        agent
            .as_ref()
            .get_module_environment(self)
            .declarative_environment
    }

    pub(crate) fn get_outer_env(self, agent: &Agent) -> OuterEnv<'e> {
        self.get_declarative_env(agent).get_outer_env(agent)
    }

    /// # [HasBinding(N)](https://tc39.es/ecma262/#table-abstract-methods-of-environment-records)
    ///
    /// Determine if an Environment Record has a binding for the String value
    /// N. Return true if it does and false if it does not.
    pub(crate) fn has_binding(self, agent: &impl AsRef<Environments>, name: String) -> bool {
        let env = agent.as_ref().get_module_environment(self);

        env.has_indirect_binding(name) || env.declarative_environment.has_binding(agent, name)
    }

    /// # [CreateMutableBinding(N, D)](https://tc39.es/ecma262/#table-abstract-methods-of-environment-records)
    ///
    /// Create a new but uninitialized mutable binding in an Environment
    /// Record. The String value N is the text of the bound name. If the
    /// Boolean argument D is true the binding may be subsequently deleted.
    pub(crate) fn create_mutable_binding(
        self,
        agent: &mut Agent,
        name: String,
        is_deletable: bool,
    ) {
        self.get_declarative_env(agent)
            .create_mutable_binding(agent, name, is_deletable);
    }

    /// # [CreateImmutableBinding(N, S)](https://tc39.es/ecma262/#table-abstract-methods-of-environment-records)
    ///
    /// Create a new but uninitialized immutable binding in an Environment
    /// Record. The String value N is the text of the bound name. If S is true
    /// then attempts to set it after it has been initialized will always throw
    /// an exception, regardless of the strict mode setting of operations that
    /// reference that binding.
    pub(crate) fn create_immutable_binding(
        self,
        envs: &mut impl AsMut<Environments>,
        name: String,
    ) {
        let envs = envs.as_mut();
        self.inner_create_immutable_binding(envs, name);
    }

    fn inner_create_immutable_binding(self, envs: &mut Environments, name: String) {
        envs.get_declarative_environment_mut(self.get_declarative_env(envs))
            .create_immutable_binding(name, true);
    }

    /// # [InitializeBinding(N, V)](https://tc39.es/ecma262/#table-abstract-methods-of-environment-records)
    ///
    /// Set the value of an already existing but uninitialized binding in an
    /// Environment Record. The String value N is the text of the bound name.
    /// V is the value for the binding and is a value of any ECMAScript
    /// language type.
    pub(crate) fn initialize_binding(
        self,
        envs: &mut impl AsMut<Environments>,
        name: String,
        value: Value,
    ) {
        let envs = envs.as_mut();
        self.inner_initialize_binding(envs, name, value);
    }

    fn inner_initialize_binding(self, envs: &mut Environments, name: String, value: Value) {
        envs.get_declarative_environment_mut(self.get_declarative_env(envs))
            .initialize_binding(name, value);
    }

    /// # [SetMutableBinding(N, V, S)](https://tc39.es/ecma262/#table-abstract-methods-of-environment-records)
    ///
    /// Set the value of an already existing mutable binding in an Environment
    /// Record. The String value N is the text of the bound name. V is the
    /// value for the binding and may be a value of any ECMAScript language
    /// type. S is a Boolean flag. If S is true and the binding cannot be set
    /// throw a TypeError exception.
    pub(crate) fn set_mutable_binding<'a>(
        self,
        agent: &mut Agent,
        name: String,
        value: Value,
        gc: NoGcScope<'a, '_>,
    ) -> JsResult<'a, ()> {
        let env_rec = agent.heap.environments.get_module_environment(self);
        if env_rec.has_indirect_binding(name) {
            let error_message = format!(
                "Cannot assign to immutable binding '{}'.",
                name.to_string_lossy_(agent)
            );
            return Err(agent.throw_exception(ExceptionType::TypeError, error_message, gc));
        }
        env_rec
            .declarative_environment
            .set_mutable_binding(agent, name, value, true, gc)
    }

    pub(crate) fn try_get_binding_value(
        self,
        agent: &mut Agent,
        name: String,
        is_strict: bool,
        gc: NoGcScope<'e, '_>,
    ) -> TryResult<'e, Value<'e>> {
        let Some(value) = self.get_binding_value(agent, name, is_strict, gc) else {
            return throw_uninitialized_binding(agent, name, gc).into();
        };
        TryResult::Continue(value)
    }

    pub(crate) fn env_get_binding_value(
        self,
        agent: &mut Agent,
        name: String,
        is_strict: bool,
        gc: NoGcScope<'e, '_>,
    ) -> JsResult<'e, Value<'e>> {
        let Some(value) = self.get_binding_value(agent, name, is_strict, gc) else {
            return Err(throw_uninitialized_binding(agent, name, gc));
        };
        Ok(value)
    }

    /// ### [9.1.1.5.1 GetBindingValue ( N, S )](https://tc39.es/ecma262/#sec-module-environment-records)
    ///
    /// The GetBindingValue concrete method of a Module Environment Record
    /// envRec takes arguments N (a String) and S (a Boolean) and returns
    /// either a normal completion containing an ECMAScript language value or a
    /// throw completion. It returns the value of its bound identifier whose
    /// name is N. However, if the binding is an indirect binding the value of
    /// the target binding is returned. If the binding exists but is
    /// uninitialized a ReferenceError is thrown.
    ///
    /// > NOTE: S will always be true because a Module is always strict mode
    /// > code.
    pub(crate) fn get_binding_value(
        self,
        agent: &Agent,
        name: String,
        is_strict: bool,
        gc: NoGcScope<'e, '_>,
    ) -> Option<Value<'e>> {
        // 1. Assert: S is true.
        debug_assert!(is_strict);
        // 2. Assert: envRec has a binding for N.
        debug_assert!(self.has_binding(agent, name));
        // 3. If the binding for N is an indirect binding, then
        let env_rec = agent.heap.environments.get_module_environment(self);
        if let Some(IndirectBinding { m, n2 }) = env_rec.get_indirect_binding(name) {
            // a. Let M and N2 be the indirection values provided when this
            //    binding for N was created.
            // b. Let targetEnv be M.[[Environment]].
            // c. If targetEnv is empty, throw a ReferenceError exception.
            let target_env = m.environment(agent, gc)?;
            // d. Return ? targetEnv.GetBindingValue(N2, true).
            return target_env.get_binding_value(agent, *n2, true, gc);
        }
        let decl_env = env_rec.declarative_environment;
        let binding = agent
            .heap
            .environments
            .get_declarative_environment(decl_env)
            .get_binding(name)
            .unwrap();
        // 4. If the binding for N in envRec is an uninitialized binding, throw
        //    a ReferenceError exception.
        let value = binding.value?;
        // 5. Return the value currently bound to N in envRec.
        Some(value.bind(gc))
    }
}

pub(crate) fn throw_uninitialized_binding<'a>(
    agent: &mut Agent,
    name: String,
    gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
    let name = name.to_string_lossy_(agent);
    agent.throw_exception(
        ExceptionType::ReferenceError,
        format!("attempted to access uninitialized binding {name}"),
        gc,
    )
}
/// ### [9.1.1.5.5 CreateImportBinding ( envRec, N, M, N2 )](https://tc39.es/ecma262/#sec-createimportbinding)
///
/// The abstract operation CreateImportBinding takes arguments envRec (a
/// Module Environment Record), N (a String), M (a Module Record), and N2
/// (a String) and returns unused. It creates a new initialized immutable
/// indirect binding for the name N. A binding must not already exist in
/// envRec for N. N2 is the name of a binding that exists in M's Module
/// Environment Record. Accesses to the value of the new binding will
/// indirectly access the bound value of the target binding.
pub(crate) fn create_import_binding(
    agent: &mut Agent,
    env_rec: ModuleEnvironment,
    n: String,
    m: AbstractModule,
    n2: String,
    gc: NoGcScope,
) {
    // 1. Assert: envRec does not already have a binding for N.
    debug_assert!(!env_rec.has_binding(agent, n));
    let m_environment = m.environment(agent, gc);
    let envs = &mut agent.heap.environments;
    let can_be_direct_binding = if let Some(m_environment) = m_environment {
        // 2. Assert: When M.[[Environment]] is instantiated, it will have a
        //    direct binding for N2.
        let m_decl_env = m_environment.get_declarative_env(envs);
        let m_decl_env = envs.get_declarative_environment_mut(m_decl_env);
        let m_direct_binding = m_decl_env.get_binding(n2);
        let Some(m_direct_binding) = m_direct_binding else {
            unreachable!();
        };
        !m_direct_binding.mutable
    } else {
        // We enter this path when the target module is our current module's
        // parent, ie. this is a circular import. All circular bindings are
        // must be indirect as we cannot know if we're pointing to immutable or
        // mutable data.
        false
    };
    if can_be_direct_binding {
        // Optimisation: references to immutable bindings can be initialised as
        // direct bindings, as the data behind them can never change. The data
        // for the direct binding will be initialised when the module is about
        // to be evaluated.
        env_rec.create_immutable_binding(envs, n);
    } else {
        // 3. Create an immutable indirect binding in envRec for N that
        //    references M and N2 as its target binding and record that the
        //    binding is initialized.
        let created_new = envs
            .get_module_environment_mut(env_rec)
            .indirect_bindings
            .insert(
                n.unbind(),
                IndirectBinding {
                    m: m.unbind(),
                    n2: n2.unbind(),
                },
            )
            .is_none();
        debug_assert!(created_new);
    }
    // 4. Return unused.
}

/// ### [9.1.1.5.5 CreateImportBinding ( envRec, N, M, N2 )](https://tc39.es/ecma262/#sec-createimportbinding)
///
/// Note: this version does not assert that the target module will have a
/// direct binding for the target name. It always creates an indirect binding.
pub(crate) fn create_indirect_import_binding(
    agent: &mut Agent,
    env_rec: ModuleEnvironment,
    n: String,
    m: AbstractModule,
    n2: String,
) {
    // 1. Assert: envRec does not already have a binding for N.
    debug_assert!(!env_rec.has_binding(agent, n));
    let envs = &mut agent.heap.environments;
    // 2. Assert: When M.[[Environment]] is instantiated, it will have a direct
    //    binding for N2.
    // 3. Create an immutable indirect binding in envRec for N that
    //    references M and N2 as its target binding and record that the
    //    binding is initialized.
    let created_new = envs
        .get_module_environment_mut(env_rec)
        .indirect_bindings
        .insert(
            n.unbind(),
            IndirectBinding {
                m: m.unbind(),
                n2: n2.unbind(),
            },
        )
        .is_none();
    debug_assert!(created_new);
    // 4. Return unused.
}

/// ### [9.1.1.5.5 CreateImportBinding ( envRec, N, M, N2 )](https://tc39.es/ecma262/#sec-createimportbinding)
///
/// > NOTE: Performs the initializing of a previously created import binding.
pub(crate) fn initialize_import_binding(
    agent: &mut Agent,
    env_rec: ModuleEnvironment,
    n: String,
    m: AbstractModule,
    n2: String,
    gc: NoGcScope,
) {
    let direct_binding = env_rec.get_declarative_env(agent).get_binding_mut(agent, n);
    let Some(direct_binding) = direct_binding else {
        // Note: if we have indirect binding to name, then it has already been
        // initialized as part of CreateImportBinding.
        return;
    };
    debug_assert!(!direct_binding.mutable);
    debug_assert!(direct_binding.strict);
    let direct_binding_value = &mut direct_binding.value as *mut Option<Value<'static>>;
    let value = m
        .environment(agent, gc)
        .expect("Attempted to access unlinked module's environment")
        .get_declarative_env(agent)
        .get_binding(agent, n2)
        .expect("Direct binding target did not exist")
        .value
        .expect("Attempted to access uninitialized binding");
    // SAFETY: m.environment, get_declarative_env and get_binding both perform
    // no mutation on module environments; the direct_binding_value pointer
    // still points to valid memory and has not been trampled with.
    unsafe { *direct_binding_value = Some(value) };
    // 4. Return unused.
}

impl HeapMarkAndSweep for ModuleEnvironment<'static> {
    fn mark_values(&self, queues: &mut WorkQueues) {
        queues.module_environments.push(*self);
    }

    fn sweep_values(&mut self, compactions: &CompactionLists) {
        compactions.module_environments.shift_index(&mut self.0);
    }
}

impl HeapMarkAndSweep for ModuleEnvironmentRecord {
    fn mark_values(&self, queues: &mut WorkQueues) {
        let Self {
            declarative_environment,
            indirect_bindings,
        } = self;
        declarative_environment.mark_values(queues);
        indirect_bindings.mark_values(queues);
    }

    fn sweep_values(&mut self, compactions: &CompactionLists) {
        let Self {
            declarative_environment,
            indirect_bindings,
        } = self;
        declarative_environment.sweep_values(compactions);
        indirect_bindings.sweep_values(compactions);
    }
}

impl HeapMarkAndSweep for IndirectBinding<'static> {
    fn mark_values(&self, queues: &mut WorkQueues) {
        let Self { m, n2 } = self;
        m.mark_values(queues);
        n2.mark_values(queues);
    }

    fn sweep_values(&mut self, compactions: &CompactionLists) {
        let Self { m, n2 } = self;
        m.sweep_values(compactions);
        n2.sweep_values(compactions);
    }
}