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
468
469
470
471
472
473
474
475
476
477
// 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 std::cell::Cell;

use ahash::{AHashMap, AHashSet};
use oxc_ast::ast::BindingIdentifier;
use oxc_ecmascript::BoundNames;
use oxc_span::Ident;

use crate::{
    ecmascript::{
        BUILTIN_STRING_MEMORY, Contains, ContainsExpression, ContainsSymbol, FunctionAstRef,
        LexicallyDeclaredNames, LexicallyScopedDeclaration, LexicallyScopedDeclarations, Value,
        VarDeclaredNames, VarScopedDeclaration, VarScopedDeclarations,
    },
    engine::{
        Instruction,
        bytecode::bytecode_compiler::{
            CompileContext, ValueOutput, compile_context::StackVariable, variable_escapes_scope,
        },
    },
};

use super::{CompileEvaluation, simple_array_pattern};

/// ### [10.2.11 FunctionDeclarationInstantiation ( func, argumentsList )](https://tc39.es/ecma262/#sec-functiondeclarationinstantiation)
///
/// The abstract operation FunctionDeclarationInstantiation takes arguments
/// func (an ECMAScript function object) and argumentsList (a List of
/// ECMAScript language values) and returns either a normal completion
/// containing unused or a throw completion. func is the function object for
/// which the execution context is being established.
///
/// > NOTE 1: When an execution context is established for evaluating an
/// > ECMAScript function a new Function Environment Record is created and
/// > bindings for each formal parameter are instantiated in that Environment
/// > Record. Each declaration in the function body is also instantiated. If
/// > the function's formal parameters do not include any default value
/// > initializers then the body declarations are instantiated in the same
/// > Environment Record as the parameters. If default value parameter
/// > initializers exist, a second Environment Record is created for the body
/// > declarations. Formal parameters and functions are initialized as part of
/// > FunctionDeclarationInstantiation. All other bindings are initialized
/// > during evaluation of the function body.
///
/// > NOTE 2: B.3.2 provides an extension to the above algorithm that is
/// > necessary for backwards compatibility with web browser implementations of
/// > ECMAScript that predate ECMAScript 2015.
pub(crate) fn instantiation<'s>(
    ctx: &mut CompileContext<'_, 's, '_, '_>,
    stack_variables: &mut Vec<StackVariable>,
    func: FunctionAstRef<'s>,
    strict: bool,
    is_lexical: bool,
) {
    // 1. Let calleeContext be the running execution context.
    // 2. Let code be func.[[ECMAScriptCode]].
    let body = func.ecmascript_code();
    // 3. Let strict be func.[[Strict]].
    // 4. Let formals be func.[[FormalParameters]].
    let formals = func.formal_parameters();

    // 8. Let hasParameterExpressions be ContainsExpression of formals.
    let has_parameter_expressions = formals.contains_expression();

    // 12. Let functionNames be a new empty List.
    // 13. Let functionsToInitialize be a new empty List.
    // NOTE: the keys of `functions` will be `functionNames`, its values will be
    // `functionsToInitialize`.
    let mut functions = AHashMap::new();
    body.var_scoped_declarations(&mut |d| {
        // a. If d is neither a VariableDeclaration nor a ForBinding nor a
        //    BindingIdentifier, then
        // i. Assert: d is either a FunctionDeclaration, a
        //    GeneratorDeclaration, an AsyncFunctionDeclaration, or an
        //    AsyncGeneratorDeclaration.
        let VarScopedDeclaration::Function(d) = d else {
            return;
        };
        // Skip function declarations with declare modifier - they are
        // TypeScript ambient declarations
        #[cfg(feature = "typescript")]
        if d.declare {
            return;
        }

        // ii. Let fn be the sole element of the BoundNames of d.
        let f_name = d.id.as_ref().unwrap().name;
        // iii. If functionNames does not contain fn, then
        //   1. Insert fn as the first element of functionNames.
        //   2. NOTE: If there are multiple function declarations for the
        //      same name, the last declaration is used.
        //   3. Insert d as the first element of functionsToInitialize.
        functions.insert(f_name, d);
    });

    // 15. Let argumentsObjectNeeded be true.
    let mut arguments_object_needed = true;
    // 16. If func.[[ThisMode]] is lexical, then
    if arguments_object_needed && is_lexical {
        // a. NOTE: Arrow functions never have an arguments object.
        // b. Set argumentsObjectNeeded to false.
        arguments_object_needed = false;
    } else if arguments_object_needed {
        // 17. Else if parameterNames contains "arguments", then
        formals.bound_names(&mut |name| {
            // a. Set argumentsObjectNeeded to false.
            if arguments_object_needed && name.name == Ident::new_const("arguments") {
                arguments_object_needed = false;
            }
        });
    }
    // 18. Else if hasParameterExpressions is false, then
    if arguments_object_needed && !has_parameter_expressions {
        // a. If functionNames contains "arguments" or
        if functions.contains_key(&Ident::new_const("arguments")) {
            // i. Set argumentsObjectNeeded to false.
            arguments_object_needed = false;
        } else {
            // lexicalNames contains "arguments", then
            body.lexically_declared_names(&mut |binding| {
                if binding.name == Ident::new_const("arguments") {
                    // i. Set argumentsObjectNeeded to false.
                    arguments_object_needed = false;
                }
            })
        }
    }

    // Note: after the easy checks we check a for optimisation. If the scope is not
    // poisoned and the function contains no arguments references, then it's
    // impossible for users to detect the arguments object being missing.
    if arguments_object_needed {
        arguments_object_needed = ctx.scope_contains_direct_eval(func.scope_id())
            || Contains::contains(&func, ContainsSymbol::Arguments);
    }

    // 5. Let parameterNames be the BoundNames of formals.
    // 6. If parameterNames has any duplicate entries, let hasDuplicates be
    //    true. Otherwise, let hasDuplicates be false.
    let mut parameter_names = AHashSet::with_capacity(formals.parameters_count());
    let mut env_parameters = Vec::with_capacity(formals.parameters_count());
    let mut stack_parameters = Vec::with_capacity(if arguments_object_needed {
        0
    } else {
        formals.parameters_count()
    });
    let mut has_duplicates = false;
    formals.bound_names(&mut |identifier| {
        let added = parameter_names.insert(identifier.name);
        if added {
            if arguments_object_needed || variable_escapes_scope(ctx, identifier) {
                env_parameters.push(identifier.name);
            } else {
                stack_parameters.push(identifier.symbol_id());
            }
        } else {
            has_duplicates = true;
        }
    });

    // 19. If strict is true or hasParameterExpressions is false, then
    //   a. NOTE: Only a single Environment Record is needed for the parameters,
    //      since calls to eval in strict mode code cannot create new bindings
    //      which are visible outside of the eval.
    //   b. Let env be the LexicalEnvironment of calleeContext.
    // 20. Else,
    //   a. NOTE: A separate Environment Record is needed to ensure that
    //      bindings created by direct eval calls in the formal parameter list
    //      are outside the environment where parameters are declared.
    //   b. Let calleeEnv be the LexicalEnvironment of calleeContext.
    //   c. Let env be NewDeclarativeEnvironment(calleeEnv).
    //   d. Assert: The VariableEnvironment of calleeContext and calleeEnv are
    //      the same Environment Record.
    //   e. Set the LexicalEnvironment of calleeContext to env.
    if !strict && has_parameter_expressions {
        // Note: these are not lexical scopes per-se, just something we "start
        // with". Thus, we do not use ctx.enter_lexical_scope().
        ctx.add_instruction(Instruction::EnterDeclarativeEnvironment);
    }

    // 21. For each String paramName of parameterNames, do
    for param_name in &stack_parameters {
        stack_variables.push(ctx.push_stack_variable(*param_name, false));
    }
    for param_name in &env_parameters {
        // a. Let alreadyDeclared be ! env.HasBinding(paramName).
        // b. NOTE: Early errors ensure that duplicate parameter names can only
        //    occur in non-strict functions that do not have parameter default
        //    values or rest parameters.
        // c. If alreadyDeclared is false, then
        // NOTE: Addition into `stack_parameters` or `env_parameters` is
        // guarded by AHashSet::insert returning `true`, so `alreadyDeclared` is
        // always false.

        // i. Perform ! env.CreateMutableBinding(paramName, false).
        let param_name = ctx.create_string(param_name);
        ctx.add_instruction_with_identifier(
            Instruction::CreateMutableBinding,
            param_name.to_property_key(),
        );
        // ii. If hasDuplicates is true, then
        if has_duplicates {
            // 1. Perform ! env.InitializeBinding(paramName, undefined).
            ctx.add_instruction_with_identifier(
                Instruction::ResolveBinding,
                param_name.to_property_key(),
            );
            ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined);
            ctx.add_instruction(Instruction::InitializeReferencedBinding);
        }
    }

    // 22. If argumentsObjectNeeded is true, then
    if arguments_object_needed {
        // a. If strict is true or simpleParameterList is false, then
        //     i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
        // b. Else,
        //     i. NOTE: A mapped argument object is only provided for
        //        non-strict functions that don't have a rest parameter, any
        //        parameter default value initializers, or any destructured
        //        parameters.
        //     ii. Let ao be CreateMappedArgumentsObject(func, formals,
        //         argumentsList, env).
        // TODO: Currently, we always create an unmapped arguments object, even
        // in non-strict mode.
        ctx.add_instruction(Instruction::CreateUnmappedArgumentsObject);

        // c. If strict is true, then
        if strict {
            // i. Perform ! env.CreateImmutableBinding("arguments", false).
            // ii. NOTE: In strict mode code early errors prevent attempting to
            //     assign to this binding, so its mutability is not observable.
            ctx.add_instruction_with_identifier(
                Instruction::CreateImmutableBinding,
                BUILTIN_STRING_MEMORY.arguments.to_property_key(),
            );
        } else {
            // d. Else,
            //   i. Perform ! env.CreateMutableBinding("arguments", false).
            ctx.add_instruction_with_identifier(
                Instruction::CreateMutableBinding,
                BUILTIN_STRING_MEMORY.arguments.to_property_key(),
            );
        }

        // e. Perform ! env.InitializeBinding("arguments", ao).
        ctx.add_instruction_with_identifier(
            Instruction::ResolveBinding,
            BUILTIN_STRING_MEMORY.arguments.to_property_key(),
        );
        ctx.add_instruction(Instruction::InitializeReferencedBinding);

        // f. Let parameterBindings be the list-concatenation of parameterNames
        //    and « "arguments" ».
        // NOTE: reusing `parameter_names` for `parameterBindings`.
        parameter_names.insert("arguments".into());
    }

    // 24. Let iteratorRecord be CreateListIteratorRecord(argumentsList).
    // 25. If hasDuplicates is true, then
    //   a. Perform ? IteratorBindingInitialization of formals with arguments
    //      iteratorRecord and undefined.
    // 26. Else,
    //   a. Perform ? IteratorBindingInitialization of formals with arguments
    //      iteratorRecord and env.
    if formals.has_parameter() {
        if has_parameter_expressions {
            // Note: ignore errors here for safety.
            let lexical_binding_state = ctx.lexical_binding_state;
            ctx.lexical_binding_state = !has_duplicates;
            let _ = formals.compile(ctx);
            ctx.lexical_binding_state = lexical_binding_state;
        } else {
            simple_array_pattern(
                ctx,
                formals.items.iter().map(|param| Some(&param.pattern)),
                formals.rest.as_ref().map(|r| &r.rest),
                formals.items.len(),
                !has_duplicates,
            );
        }
    }
    // Remove the arguments iterator from the iterator stack.
    ctx.add_instruction(Instruction::IteratorPop);

    // 27. If hasParameterExpressions is false, then
    if !has_parameter_expressions {
        // a. NOTE: Only a single Environment Record is needed for the
        //    parameters and top-level vars.
        // b. Let instantiatedVarNames be a copy of the List parameterBindings.
        let mut instantiated_var_names = parameter_names.clone();
        // c. For each element n of varNames, do
        body.var_declared_names(&mut |d| {
            let n = d.name;
            // i. If instantiatedVarNames does not contain n, then
            if !instantiated_var_names.insert(n) {
                return;
            }
            // 1. Append n to instantiatedVarNames.
            let n_string = ctx.create_string(&n);
            if variable_escapes_scope(ctx, d) {
                // 2. Perform ! env.CreateMutableBinding(n, false).
                ctx.add_instruction_with_identifier(
                    Instruction::CreateMutableBinding,
                    n_string.to_property_key(),
                );
                // 3. Perform ! env.InitializeBinding(n, undefined).
                ctx.add_instruction_with_identifier(
                    Instruction::ResolveBinding,
                    n_string.to_property_key(),
                );
                ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined);
                ctx.add_instruction(Instruction::InitializeReferencedBinding);
            } else {
                stack_variables.push(ctx.push_stack_variable(d.symbol_id(), false));
            }
        });

        // d. Let varEnv be env.
        // 30. If strict is false, then
        //   a. Let lexEnv be NewDeclarativeEnvironment(varEnv).
        // 31. Else,
        //   a. Let lexEnv be varEnv.
        // 32. Set the LexicalEnvironment of calleeContext to lexEnv.
        if !strict {
            // Note: these are not lexical scopes per-se, just something we
            // "start with". Thus, we do not use ctx.enter_lexical_scope().
            ctx.add_instruction(Instruction::EnterDeclarativeEnvironment);
        }
    } else {
        // 28. Else,
        // a. NOTE: A separate Environment Record is needed to ensure that
        //    closures created by expressions in the formal parameter list do
        //    not have visibility of declarations in the function body.
        // b. Let varEnv be NewDeclarativeEnvironment(env).
        // c. Set the VariableEnvironment of calleeContext to varEnv.
        // NOTE: Since this step operates on a variable environment, rather than
        // the usual lexical environments, we implement this by pushing all
        // variable names and values into the stack, and then having a single
        // instruction that initializes all of them and sets the right
        // environment in one go.

        // d. Let instantiatedVarNames be a new empty List.
        let mut instantiated_var_names = AHashSet::new();
        // e. For each element n of varNames, do
        body.var_declared_names(&mut |d| {
            let n = d.name;
            // i. If instantiatedVarNames does not contain n, then
            if !instantiated_var_names.insert(n) {
                return;
            }
            // 1. Append n to instantiatedVarNames.
            // 3. If parameterBindings does not contain n, or if functionNames
            //    contains n, then
            let n_string = ctx.create_string(&n);
            if !parameter_names.contains(&n) || functions.contains_key(&n) {
                // a. Let initialValue be undefined.
                ctx.add_instruction_with_constant(Instruction::LoadConstant, Value::Undefined);
            } else {
                // 4. Else,
                // a. Let initialValue be ! env.GetBindingValue(n, false).
                ctx.add_instruction_with_identifier(
                    Instruction::ResolveBinding,
                    n_string.to_property_key(),
                );
                ctx.add_instruction(Instruction::GetValue);
                ctx.add_instruction(Instruction::Load);
            }

            // 2. Perform ! varEnv.CreateMutableBinding(n, false).
            // 5. Perform ! varEnv.InitializeBinding(n, initialValue).
            // 6. NOTE: A var with the same name as a formal parameter
            //    initially has the same value as the corresponding initialized
            //    parameter.
            ctx.add_instruction_with_constant(Instruction::LoadConstant, n_string);
        });

        // 30. If strict is false, then
        //   a. Let lexEnv be NewDeclarativeEnvironment(varEnv).
        //   b. NOTE: Non-strict functions use a separate Environment Record
        //      for top-level lexical declarations so that a direct eval can
        //      determine whether any var scoped declarations introduced by the
        //      eval code conflict with pre-existing top-level lexically scoped
        //      declarations. This is not needed for strict functions because a
        //      strict direct eval always places all declarations into a new
        //      Environment Record.
        // 31. Else,
        //   a. Let lexEnv be varEnv.
        // 32. Set the LexicalEnvironment of calleeContext to lexEnv.
        ctx.add_instruction_with_immediate_and_immediate(
            Instruction::InitializeVariableEnvironment,
            instantiated_var_names.len(),
            strict.into(),
        );
    }

    // 33. Let lexDeclarations be the LexicallyScopedDeclarations of code.
    // 34. For each element d of lexDeclarations, do
    {
        let is_constant_declaration = Cell::new(false);
        let cb = &mut |identifier: &BindingIdentifier<'s>| {
            if variable_escapes_scope(ctx, identifier) {
                let dn = ctx.create_string(&identifier.name);
                ctx.add_instruction_with_identifier(
                    // i. If IsConstantDeclaration of d is true, then
                    if is_constant_declaration.get() {
                        // 1. Perform ! lexEnv.CreateImmutableBinding(dn, true).
                        Instruction::CreateImmutableBinding
                    } else {
                        // ii. Else,
                        // 1. Perform ! lexEnv.CreateMutableBinding(dn, false).
                        Instruction::CreateMutableBinding
                    },
                    dn.to_property_key(),
                );
            } else {
                stack_variables.push(ctx.push_stack_variable(identifier.symbol_id(), false));
            }
        };
        let mut create_default_export = false;
        body.lexically_scoped_declarations(&mut |d| {
            // a. NOTE: A lexically declared name cannot be the same as a
            //    function/generator declaration, formal parameter, or a var name.
            //    Lexically declared names are only instantiated here but not
            //    initialized.
            // b. For each element dn of the BoundNames of d, do
            match d {
                LexicallyScopedDeclaration::Variable(decl) => {
                    is_constant_declaration.set(decl.kind.is_const());
                    decl.id.bound_names(cb);
                    is_constant_declaration.set(false);
                }
                LexicallyScopedDeclaration::Function(decl) => {
                    // Skip function declarations with declare modifier - they are TypeScript ambient declarations
                    #[cfg(feature = "typescript")]
                    if decl.declare {
                        return;
                    }

                    decl.bound_names(cb);
                }
                LexicallyScopedDeclaration::Class(decl) => {
                    decl.bound_names(cb);
                }
                LexicallyScopedDeclaration::DefaultExport => {
                    create_default_export = true;
                }
                #[cfg(feature = "typescript")]
                LexicallyScopedDeclaration::TSEnum(decl) => {
                    decl.id.bound_names(cb);
                }
            }
        });
        if create_default_export {
            let dn = BUILTIN_STRING_MEMORY._default_;
            ctx.add_instruction_with_identifier(
                Instruction::CreateMutableBinding,
                dn.to_property_key(),
            );
        }
    }

    // 36. For each Parse Node f of functionsToInitialize, do
    for f in functions.values() {
        // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv
        //    and privateEnv.
        f.compile(ctx);
        // a. Let fn be the sole element of the BoundNames of f.
        // c. Perform ! varEnv.SetMutableBinding(fn, fo, false).
        // TODO: This compilation is incorrect if !strict, when varEnv != lexEnv.
        let f = f.id.as_ref().unwrap().compile(ctx);
        f.put_value(ctx, ValueOutput::Value).unwrap();
    }
}