Skip to content

Commit 81d3387

Browse files
authored
feat(engine): Track stack depth in compiler and reset in try-catch blocks (#923)
1 parent 5c97345 commit 81d3387

File tree

14 files changed

+866
-658
lines changed

14 files changed

+866
-658
lines changed

nova_vm/src/ecmascript/execution/environments/declarative_environment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ impl<'e> DeclarativeEnvironment<'e> {
337337
// b. If S is true, throw a TypeError exception.
338338
if is_strict {
339339
let error_message = format!(
340-
"Cannot assign to immutable identifier '{}' in strict mode.",
340+
"invalid assignment to const '{}'",
341341
name.to_string_lossy(agent)
342342
);
343343
return Err(agent.throw_exception(ExceptionType::TypeError, error_message, gc));

nova_vm/src/ecmascript/execution/environments/function_environment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ impl<'e> FunctionEnvironment<'e> {
352352
// b. If S is true, throw a TypeError exception.
353353
if is_strict {
354354
let error_message = format!(
355-
"Cannot assign to immutable identifier '{}' in strict mode.",
355+
"invalid assignment to const '{}'",
356356
name.to_string_lossy(agent)
357357
);
358358
return Err(agent.throw_exception(ExceptionType::TypeError, error_message, gc));

nova_vm/src/engine/bytecode/bytecode_compiler.rs

Lines changed: 537 additions & 474 deletions
Large diffs are not rendered by default.

nova_vm/src/engine/bytecode/bytecode_compiler/assignment.rs

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,21 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Assign
228228
Ok(ValueOutput::Value)
229229
} else {
230230
// 2. let lval be ? GetValue(lref).
231-
lref.get_value_keep_reference(ctx)?;
232-
ctx.add_instruction(Instruction::Load);
231+
let _lval = lref.get_value_keep_reference(ctx)?;
232+
let lval_copy = ctx.load_to_stack();
233233
let do_push_reference = lref.has_reference() && !self.right.is_literal();
234234
if do_push_reference {
235235
ctx.add_instruction(Instruction::PushReference);
236236
}
237237
// 3. Let rref be ? Evaluation of AssignmentExpression.
238238
// 4. Let rval be ? GetValue(rref).
239-
let _rval = self.right.compile(ctx)?.get_value(ctx)?;
239+
let _rval = match self.right.compile(ctx).and_then(|r| r.get_value(ctx)) {
240+
Ok(r) => r,
241+
Err(err) => {
242+
lval_copy.forget(ctx);
243+
return Err(err);
244+
}
245+
};
240246

241247
// 5. Let assignmentOpText be the source text matched by AssignmentOperator.
242248
// 6. Let opText be the sequence of Unicode code points associated with assignmentOpText in the following table:
@@ -261,6 +267,8 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Assign
261267
ast::BinaryOperator::BitwiseAnd => Instruction::ApplyBitwiseAndBinaryOperator,
262268
_ => unreachable!(),
263269
};
270+
// Consumed by instruction.
271+
lval_copy.forget(ctx);
264272
ctx.add_instruction(op_text);
265273
let r_copy = ctx.load_copy_to_stack();
266274
let r = ValueOutput::Value;
@@ -300,18 +308,26 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Assign
300308
// stack: []
301309
if let Some(target) = self.as_simple_assignment_target() {
302310
let needs_load_store = target.is_member_expression();
303-
if needs_load_store {
304-
ctx.add_instruction(Instruction::Load);
311+
let place = if needs_load_store {
312+
let value_on_stack = ctx.load_to_stack();
305313
// result: None
306314
// stack: [value]
307-
}
308-
let place = target.compile(ctx)?;
309-
if needs_load_store {
310-
// result: None
311-
// stack: [value]
312-
// reference: &target
313-
ctx.add_instruction(Instruction::Store);
314-
}
315+
match target.compile(ctx) {
316+
Ok(p) => {
317+
// result: None
318+
// stack: [value]
319+
// reference: &target
320+
value_on_stack.store(ctx);
321+
p
322+
}
323+
Err(err) => {
324+
value_on_stack.forget(ctx);
325+
return Err(err);
326+
}
327+
}
328+
} else {
329+
target.compile(ctx)?
330+
};
315331
// result: value
316332
// stack: []
317333
// reference: &target
@@ -758,32 +774,33 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope>
758774
// this! When we enter here, self.binding property access result should
759775
// be in the result register.
760776
if let Some(init) = &self.init {
761-
ctx.add_instruction(Instruction::LoadCopy);
777+
let binding_copy = ctx.load_copy_to_stack();
762778
// result: binding
763779
// stack: [binding]
764780
ctx.add_instruction(Instruction::IsUndefined);
765781
// result: binding === undefined
766782
// stack: [binding]
767783
let jump_slot = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot);
768-
ctx.add_instruction(Instruction::Store);
784+
binding_copy.store(ctx);
769785
// result: binding
770786
// stack: []
771787
if is_anonymous_function_definition(init) {
772788
let identifier_string = ctx.create_string(self.binding.name.as_str());
773789
ctx.add_instruction_with_constant(Instruction::StoreConstant, identifier_string);
774790
ctx.name_identifier = Some(NamedEvaluationParameter::Result);
775791
}
776-
init.compile(ctx)?.get_value(ctx)?;
792+
// Ignore errors: this is not an unconditional path.
793+
let _ = init.compile(ctx).and_then(|r| r.get_value(ctx));
777794
ctx.name_identifier = None;
778795
// result: init
779796
// stack: []
780-
ctx.add_instruction(Instruction::Load);
797+
let init_on_stack = ctx.load_to_stack();
781798
// result: None
782799
// stack: [init]
783800
ctx.set_jump_target_here(jump_slot);
784801
// result: None
785802
// stack: [binding / init]
786-
ctx.add_instruction(Instruction::Store);
803+
init_on_stack.store(ctx);
787804
// result: binding / init
788805
// stack: []
789806
}
@@ -831,19 +848,26 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Proper
831848
// Note: Private names are not allowed in this position.
832849
ast::PropertyKey::PrivateIdentifier(_) => unreachable!(),
833850
_ => {
834-
ctx.add_instruction(Instruction::Load);
851+
let source_on_stack = ctx.load_to_stack();
835852
// result: None
836853
// stack: [source]
837854
let expr = self.to_expression();
838-
let source = expr.compile(ctx)?.get_value(ctx)?;
855+
let expr_result = expr.compile(ctx).and_then(|r| r.get_value(ctx));
856+
857+
// Source on stack is either forget on the stack and cleaned up
858+
// by try-catch if expr is Err, or is consumed by below
859+
// instruction.
860+
source_on_stack.forget(ctx);
861+
862+
let expr_result = expr_result?;
839863

840864
// result: expr
841865
// stack: [source]
842866
ctx.add_instruction(Instruction::EvaluatePropertyAccessWithExpressionKey);
843867
// result: None
844868
// stack: []
845869
// reference: &source[expr]
846-
Ok(source.to_expression_key())
870+
Ok(expr_result.to_expression_key())
847871
}
848872
}
849873
}
@@ -855,14 +879,14 @@ fn compile_initializer<'s>(
855879
) {
856880
// result: value
857881
// stack: []
858-
ctx.add_instruction(Instruction::LoadCopy);
882+
let value_copy = ctx.load_copy_to_stack();
859883
ctx.add_instruction(Instruction::IsUndefined);
860884
// result: value === undefined
861885
// stack: [value]
862886
let jump_slot = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot);
863887
// result: None
864888
// stack: [value]
865-
ctx.add_instruction(Instruction::Store);
889+
value_copy.store(ctx);
866890
// result: value
867891
// stack: []
868892
if is_anonymous_function_definition(&target.init)
@@ -880,13 +904,13 @@ fn compile_initializer<'s>(
880904
ctx.name_identifier = None;
881905
// result: init
882906
// stack: []
883-
ctx.add_instruction(Instruction::Load);
907+
let init_on_stack = ctx.load_to_stack();
884908
// result: None
885909
// stack: [init]
886910
ctx.set_jump_target_here(jump_slot);
887911
// result: None
888912
// stack: [value / init]
889-
ctx.add_instruction(Instruction::Store);
913+
init_on_stack.store(ctx);
890914
// result: value / init
891915
// stack: []
892916
}

nova_vm/src/engine/bytecode/bytecode_compiler/block_declaration_instantiation.rs

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
use oxc_ecmascript::BoundNames;
66

77
use crate::engine::bytecode::bytecode_compiler::{
8-
StatementResult,
9-
compile_context::{LexicalScope, StackVariable},
10-
variable_escapes_scope,
8+
StatementResult, compile_context::BlockEnvPrep, variable_escapes_scope,
119
};
1210

1311
use super::{
@@ -17,12 +15,14 @@ use super::{
1715

1816
/// ### [14.2.3 BlockDeclarationInstantiation ( code, env )](https://tc39.es/ecma262/#sec-blockdeclarationinstantiation)
1917
///
18+
/// This can clobber the result register and can push additional items to the top of the stack.
19+
///
2020
/// The abstract operation BlockDeclarationInstantiation takes arguments code
2121
/// (a Parse Node) and env (a Declarative Environment Record) and returns
2222
/// unused. code is the Parse Node corresponding to the body of the block. env
2323
/// is the Environment Record in which bindings are to be created.
2424
///
25-
/// > Note
25+
/// > Note:
2626
/// >
2727
/// > When a Block or CaseBlock is evaluated a new Declarative Environment
2828
/// > Record is created and bindings for each block scoped variable, constant,
@@ -33,39 +33,34 @@ pub(super) fn instantiation<'s, 'gc>(
3333
code: &'s impl LexicallyScopedDeclarations<'s>,
3434
cb: impl FnOnce(&mut CompileContext<'_, 's, 'gc, '_>) -> StatementResult<'gc>,
3535
) -> StatementResult<'gc> {
36-
let mut decl_env = None;
37-
let mut local_lexical_names = Vec::new();
36+
let mut block_prep = Vec::new();
3837
// 1. Let declarations be the LexicallyScopedDeclarations of code.
3938
// 2. Let privateEnv be the running execution context's PrivateEnvironment.
4039
// 3. For each element d of declarations, do
4140
code.lexically_scoped_declarations(&mut |d| {
42-
handle_block_lexically_scoped_declaration(ctx, &mut decl_env, &mut local_lexical_names, d);
41+
handle_block_lexically_scoped_declaration(ctx, &mut block_prep, d);
4342
});
4443

4544
// 4. Return unused.
4645
let result = cb(ctx);
4746

48-
for lex_name in local_lexical_names {
49-
lex_name.exit(ctx);
50-
}
51-
if let Some(decl_env) = decl_env {
52-
decl_env.exit(ctx);
47+
for prop in block_prep.into_iter().rev() {
48+
prop.exit(ctx);
5349
}
5450
result
5551
}
5652

5753
fn handle_block_lexically_scoped_declaration<'s>(
5854
ctx: &mut CompileContext<'_, 's, '_, '_>,
59-
decl_env: &mut Option<LexicalScope>,
60-
local_lexical_names: &mut Vec<StackVariable>,
55+
block_prep: &mut Vec<BlockEnvPrep>,
6156
d: LexicallyScopedDeclaration<'s>,
6257
) {
6358
match d {
6459
// a. For each element dn of the BoundNames of d, do
6560
LexicallyScopedDeclaration::Variable(decl) if decl.kind.is_const() => {
6661
// i. If IsConstantDeclaration of d is true, then
6762
decl.id.bound_names(&mut |identifier| {
68-
if handle_lexical_variable(ctx, identifier, decl_env, local_lexical_names, None) {
63+
if handle_lexical_variable(ctx, identifier, block_prep, None) {
6964
let dn = ctx.create_string(&identifier.name);
7065
// 1. Perform ! env.CreateImmutableBinding(dn, true).
7166
ctx.add_instruction_with_identifier(
@@ -77,7 +72,7 @@ fn handle_block_lexically_scoped_declaration<'s>(
7772
}
7873
// ii. Else,
7974
LexicallyScopedDeclaration::Variable(decl) => decl.id.bound_names(&mut |identifier| {
80-
if handle_lexical_variable(ctx, identifier, decl_env, local_lexical_names, None) {
75+
if handle_lexical_variable(ctx, identifier, block_prep, None) {
8176
// 1. Perform ! env.CreateMutableBinding(dn, false).
8277
// NOTE: This step is replaced in section B.3.2.6.
8378
let dn = ctx.create_string(&identifier.name);
@@ -95,7 +90,7 @@ fn handle_block_lexically_scoped_declaration<'s>(
9590
let Some(identifier) = &decl.id else {
9691
unreachable!()
9792
};
98-
if handle_lexical_variable(ctx, identifier, decl_env, local_lexical_names, Some(decl)) {
93+
if handle_lexical_variable(ctx, identifier, block_prep, Some(decl)) {
9994
let dn = ctx.create_string(&identifier.name);
10095
// 1. Perform ! env.CreateMutableBinding(dn, false).
10196
// NOTE: This step is replaced in section B.3.2.6.
@@ -116,7 +111,7 @@ fn handle_block_lexically_scoped_declaration<'s>(
116111
}
117112
LexicallyScopedDeclaration::Class(decl) => {
118113
decl.bound_names(&mut |identifier| {
119-
if handle_lexical_variable(ctx, identifier, decl_env, local_lexical_names, None) {
114+
if handle_lexical_variable(ctx, identifier, block_prep, None) {
120115
// 1. Perform ! env.CreateMutableBinding(dn, false).
121116
// NOTE: This step is replaced in section B.3.2.6.
122117
let dn = ctx.create_string(&identifier.name);
@@ -130,7 +125,7 @@ fn handle_block_lexically_scoped_declaration<'s>(
130125
LexicallyScopedDeclaration::DefaultExport => unreachable!(),
131126
#[cfg(feature = "typescript")]
132127
LexicallyScopedDeclaration::TSEnum(decl) => {
133-
if handle_lexical_variable(ctx, &decl.id, decl_env, local_lexical_names, None) {
128+
if handle_lexical_variable(ctx, &decl.id, block_prep, None) {
134129
let dn = ctx.create_string(&decl.id.name);
135130
// Create mutable binding for the enum
136131
ctx.add_instruction_with_identifier(
@@ -145,13 +140,12 @@ fn handle_block_lexically_scoped_declaration<'s>(
145140
fn handle_lexical_variable<'s>(
146141
ctx: &mut CompileContext<'_, 's, '_, '_>,
147142
identifier: &oxc_ast::ast::BindingIdentifier,
148-
decl_env: &mut Option<LexicalScope>,
149-
local_lexical_names: &mut Vec<StackVariable>,
143+
block_prep: &mut Vec<BlockEnvPrep>,
150144
f: Option<&'s oxc_ast::ast::Function<'s>>,
151145
) -> bool {
152146
if variable_escapes_scope(ctx, identifier) {
153-
if decl_env.is_none() {
154-
*decl_env = Some(ctx.enter_lexical_scope());
147+
if !block_prep.iter().any(|p| p.is_env()) {
148+
block_prep.push(BlockEnvPrep::Env(ctx.enter_lexical_scope()));
155149
}
156150
true
157151
} else {
@@ -161,7 +155,7 @@ fn handle_lexical_variable<'s>(
161155
} else {
162156
ctx.push_stack_variable(identifier.symbol_id(), false)
163157
};
164-
local_lexical_names.push(var);
158+
block_prep.push(BlockEnvPrep::Var(var));
165159
false
166160
}
167161
}

nova_vm/src/engine/bytecode/bytecode_compiler/class_definition_evaluation.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ use crate::{
2020
engine::{
2121
CompileContext, CompileEvaluation, FunctionExpression, Instruction,
2222
NamedEvaluationParameter, SendableRef,
23-
bytecode::bytecode_compiler::{ExpressionError, ValueOutput, variable_escapes_scope},
23+
bytecode::bytecode_compiler::{
24+
ExpressionError, ValueOutput, compile_context::BlockEnvPrep, variable_escapes_scope,
25+
},
2426
},
2527
};
2628
use ahash::{AHashMap, AHashSet};
@@ -1037,8 +1039,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Static
10371039
// a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars.
10381040
// b. Let instantiatedVarNames be a copy of the List parameterBindings.
10391041
let mut instantiated_var_names = AHashSet::new();
1040-
let static_env = ctx.enter_lexical_scope();
1041-
let mut stack_variables = vec![];
1042+
let mut block_prep: Vec<BlockEnvPrep> = vec![];
10421043

10431044
// c. For each element n of varNames, do
10441045
self.var_declared_names(&mut |identifier| {
@@ -1050,6 +1051,9 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Static
10501051
}
10511052
let n_string = ctx.create_string(&n);
10521053
if variable_escapes_scope(ctx, identifier) {
1054+
if !block_prep.iter().any(|p| p.is_env()) {
1055+
block_prep.push(BlockEnvPrep::Env(ctx.enter_lexical_scope()));
1056+
}
10531057
// 2. Perform ! env.CreateMutableBinding(n, false).
10541058
ctx.add_instruction_with_identifier(
10551059
Instruction::CreateMutableBinding,
@@ -1063,7 +1067,9 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Static
10631067
ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined);
10641068
ctx.add_instruction(Instruction::InitializeReferencedBinding);
10651069
} else {
1066-
stack_variables.push(ctx.push_stack_variable(identifier.symbol_id(), false));
1070+
block_prep.push(BlockEnvPrep::Var(
1071+
ctx.push_stack_variable(identifier.symbol_id(), false),
1072+
));
10671073
}
10681074
});
10691075

@@ -1092,7 +1098,9 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Static
10921098
dn.to_property_key(),
10931099
);
10941100
} else {
1095-
stack_variables.push(ctx.push_stack_variable(identifier.symbol_id(), false));
1101+
block_prep.push(BlockEnvPrep::Var(
1102+
ctx.push_stack_variable(identifier.symbol_id(), false),
1103+
));
10961104
}
10971105
};
10981106
let mut create_default_export = false;
@@ -1148,10 +1156,9 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Static
11481156
break;
11491157
}
11501158
}
1151-
for stack_variable in stack_variables {
1152-
stack_variable.exit(ctx);
1159+
for block_prep in block_prep.into_iter().rev() {
1160+
block_prep.exit(ctx);
11531161
}
1154-
static_env.exit(ctx);
11551162
}
11561163
}
11571164

0 commit comments

Comments
 (0)