Browse Source

Implement the global `eval()` function (#2041)

This Pull Request fixes/closes #948.

It changes the following:

- Implement the global `eval()` function.

Runtime code evaluation brings some challenges for environments. Currently the setting and getting of variable bindings is done via indices that are calculated at compile time. This prevents costly hashmap lookups at runtime.
Evaluiation at runtime needs access to existing compile time environments. This is a relatively easy change. We wrap compile time environments in `Gc` and make them accessible at runtime.

Because `eval()` can add var bindings to existing function environments, we have to adjust the environments for this. Because we cannot recompile all previously stored binding indices, we have to fallback to hashmap lookups at runtime. To prevent this from tanking our performance we add a flag to each environment that marks if any `eval()` has been executed in that environment (or outer environments). This makes it possible to retain the performance of precompiled environment lookups while having a fallback for `eval()`.

TLDR: `eval()` is not only horribly unsafe but also a burden for performance. [Never use eval()!](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!)
pull/2070/head
raskad 3 years ago
parent
commit
8721a3167b
  1. 152
      boa_engine/src/builtins/eval/mod.rs
  2. 3
      boa_engine/src/builtins/mod.rs
  3. 266
      boa_engine/src/bytecompiler.rs
  4. 17
      boa_engine/src/context/mod.rs
  5. 333
      boa_engine/src/environments/compile.rs
  6. 2
      boa_engine/src/environments/mod.rs
  7. 278
      boa_engine/src/environments/runtime.rs
  8. 13
      boa_engine/src/realm.rs
  9. 67
      boa_engine/src/vm/code_block.rs
  10. 150
      boa_engine/src/vm/mod.rs
  11. 22
      boa_engine/src/vm/opcode.rs

152
boa_engine/src/builtins/eval/mod.rs

@ -0,0 +1,152 @@
//! This module implements the global `eval` function.
//!
//! The `eval()` function evaluates JavaScript code represented as a string.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-eval-x
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
use crate::{
builtins::{function::Function, BuiltIn, JsArgs},
object::{JsObject, ObjectData},
property::Attribute,
Context, JsValue,
};
use boa_profiler::Profiler;
use rustc_hash::FxHashSet;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Eval;
impl BuiltIn for Eval {
const NAME: &'static str = "eval";
const ATTRIBUTE: Attribute = Attribute::READONLY
.union(Attribute::NON_ENUMERABLE)
.union(Attribute::PERMANENT);
fn init(context: &mut Context) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let object = JsObject::from_proto_and_data(
context.intrinsics().constructors().function().prototype(),
ObjectData::function(Function::Native {
function: Self::eval,
constructor: false,
}),
);
Some(object.into())
}
}
impl Eval {
/// `19.2.1 eval ( x )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-eval-x
fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result<JsValue, JsValue> {
// 1. Return ? PerformEval(x, false, false).
Self::perform_eval(args.get_or_undefined(0), false, false, context)
}
/// `19.2.1.1 PerformEval ( x, strictCaller, direct )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-performeval
pub(crate) fn perform_eval(
x: &JsValue,
direct: bool,
strict: bool,
context: &mut Context,
) -> Result<JsValue, JsValue> {
// 1. Assert: If direct is false, then strictCaller is also false.
if !direct {
debug_assert!(!strict);
}
// 2. If Type(x) is not String, return x.
let x = if let Some(x) = x.as_string() {
x.clone()
} else {
return Ok(x.clone());
};
// Because of implementation details the following code differs from the spec.
// Parse the script body (11.a - 11.d)
// TODO: Implement errors for 11.e - 11.h
let body = match context.parse(x.as_bytes()).map_err(|e| e.to_string()) {
Ok(body) => body,
Err(e) => return context.throw_syntax_error(e),
};
// 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`.
// Because our environment model does not map directly to the spec this section looks very different.
// 14 - 33 are in the following section, together with EvalDeclarationInstantiation.
if direct {
// If the call to eval is direct, the code is executed in the current environment.
// Poison the current environment, because it may contain new declarations after/during eval.
context.realm.environments.poison_current();
// Set the compile time environment to the current running environment and save the number of current environments.
context.realm.compile_env = context.realm.environments.current_compile_environment();
let environments_len = context.realm.environments.len();
// Error if any var declaration in the eval code already exists as a let/const declaration in the current running environment.
let mut vars = FxHashSet::default();
body.var_declared_names_new(&mut vars);
if let Some(name) = context
.realm
.environments
.has_lex_binding_until_function_environment(&vars)
{
let name = context.interner().resolve_expect(name);
let msg = format!("variable declaration {name} in eval function already exists as lexically declaration");
return context.throw_syntax_error(msg);
}
// Compile and execute the eval statement list.
let code_block = context.compile_with_new_declarative(&body, strict)?;
context
.realm
.environments
.extend_outer_function_environment();
let result = context.execute(code_block);
// Pop any added runtime environments that where not removed during the eval execution.
context.realm.environments.truncate(environments_len);
result
} else {
// If the call to eval is indirect, the code is executed in the global environment.
// Poison all environments, because the global environment may contain new declarations after/during eval.
context.realm.environments.poison_all();
// Pop all environments before the eval execution.
let environments = context.realm.environments.pop_to_global();
let environments_len = context.realm.environments.len();
context.realm.compile_env = context.realm.environments.current_compile_environment();
// Compile and execute the eval statement list.
let code_block = context.compile_with_new_declarative(&body, false)?;
let result = context.execute(code_block);
// Restore all environments to the state from before the eval execution.
context.realm.environments.truncate(environments_len);
context.realm.environments.extend(environments);
result
}
}
}

3
boa_engine/src/builtins/mod.rs

@ -9,6 +9,7 @@ pub mod console;
pub mod dataview; pub mod dataview;
pub mod date; pub mod date;
pub mod error; pub mod error;
pub mod eval;
pub mod function; pub mod function;
pub mod generator; pub mod generator;
pub mod generator_function; pub mod generator_function;
@ -41,6 +42,7 @@ pub(crate) use self::{
AggregateError, Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, AggregateError, Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError,
UriError, UriError,
}, },
eval::Eval,
function::BuiltInFunctionObject, function::BuiltInFunctionObject,
global_this::GlobalThis, global_this::GlobalThis,
infinity::Infinity, infinity::Infinity,
@ -152,6 +154,7 @@ pub fn init(context: &mut Context) {
DataView, DataView,
Map, Map,
Number, Number,
Eval,
Set, Set,
String, String,
RegExp, RegExp,

266
boa_engine/src/bytecompiler.rs

@ -1,6 +1,6 @@
use crate::{ use crate::{
builtins::function::ThisMode, builtins::function::ThisMode,
environments::BindingLocator, environments::{BindingLocator, CompileTimeEnvironment},
syntax::ast::{ syntax::ast::{
node::{ node::{
declaration::{ declaration::{
@ -19,7 +19,7 @@ use crate::{
vm::{BindingOpcode, CodeBlock, Opcode}, vm::{BindingOpcode, CodeBlock, Opcode},
Context, JsBigInt, JsResult, JsString, JsValue, Context, JsBigInt, JsResult, JsString, JsValue,
}; };
use boa_gc::Gc; use boa_gc::{Cell, Gc};
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::mem::size_of; use std::mem::size_of;
@ -95,6 +95,14 @@ impl<'b> ByteCompiler<'b> {
self.context.interner() self.context.interner()
} }
/// Push a compile time environment to the current `CodeBlock` and return it's index.
#[inline]
fn push_compile_environment(&mut self, environment: Gc<Cell<CompileTimeEnvironment>>) -> usize {
let index = self.code_block.compile_environments.len();
self.code_block.compile_environments.push(environment);
index
}
#[inline] #[inline]
fn get_or_insert_literal(&mut self, literal: Literal) -> u32 { fn get_or_insert_literal(&mut self, literal: Literal) -> u32 {
if let Some(index) = self.literals_map.get(&literal) { if let Some(index) = self.literals_map.get(&literal) {
@ -281,13 +289,24 @@ impl<'b> ByteCompiler<'b> {
Label { index } Label { index }
} }
/// Emit an opcode with a dummy operand.
/// Return the `Label` of the operand.
#[inline] #[inline]
fn jump_with_custom_opcode(&mut self, opcode: Opcode) -> Label { fn emit_opcode_with_operand(&mut self, opcode: Opcode) -> Label {
let index = self.next_opcode_location(); let index = self.next_opcode_location();
self.emit(opcode, &[Self::DUMMY_ADDRESS]); self.emit(opcode, &[Self::DUMMY_ADDRESS]);
Label { index } Label { index }
} }
/// Emit an opcode with two dummy operands.
/// Return the `Label`s of the two operands.
#[inline]
fn emit_opcode_with_two_operands(&mut self, opcode: Opcode) -> (Label, Label) {
let index = self.next_opcode_location();
self.emit(opcode, &[Self::DUMMY_ADDRESS, Self::DUMMY_ADDRESS]);
(Label { index }, Label { index: index + 4 })
}
#[inline] #[inline]
fn patch_jump_with_target(&mut self, label: Label, target: u32) { fn patch_jump_with_target(&mut self, label: Label, target: u32) {
let Label { index } = label; let Label { index } = label;
@ -551,6 +570,35 @@ impl<'b> ByteCompiler<'b> {
Ok(()) Ok(())
} }
/// Compile a statement list in a new declarative environment.
#[inline]
pub(crate) fn compile_statement_list_with_new_declarative(
&mut self,
list: &[Node],
use_expr: bool,
strict: bool,
) -> JsResult<()> {
self.context.push_compile_time_environment(strict);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_declarations(list)?;
if let Some((last, items)) = list.split_last() {
for node in items {
self.compile_stmt(node, false)?;
}
self.compile_stmt(last, use_expr)?;
}
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
Ok(())
}
#[inline] #[inline]
pub fn compile_expr(&mut self, expr: &Node, use_expr: bool) -> JsResult<()> { pub fn compile_expr(&mut self, expr: &Node, use_expr: bool) -> JsResult<()> {
match expr { match expr {
@ -726,17 +774,17 @@ impl<'b> ByteCompiler<'b> {
BinOp::Log(op) => { BinOp::Log(op) => {
match op { match op {
LogOp::And => { LogOp::And => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd); let exit = self.emit_opcode_with_operand(Opcode::LogicalAnd);
self.compile_expr(binary.rhs(), true)?; self.compile_expr(binary.rhs(), true)?;
self.patch_jump(exit); self.patch_jump(exit);
} }
LogOp::Or => { LogOp::Or => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalOr); let exit = self.emit_opcode_with_operand(Opcode::LogicalOr);
self.compile_expr(binary.rhs(), true)?; self.compile_expr(binary.rhs(), true)?;
self.patch_jump(exit); self.patch_jump(exit);
} }
LogOp::Coalesce => { LogOp::Coalesce => {
let exit = self.jump_with_custom_opcode(Opcode::Coalesce); let exit = self.emit_opcode_with_operand(Opcode::Coalesce);
self.compile_expr(binary.rhs(), true)?; self.compile_expr(binary.rhs(), true)?;
self.patch_jump(exit); self.patch_jump(exit);
} }
@ -761,7 +809,7 @@ impl<'b> ByteCompiler<'b> {
AssignOp::Shr => Some(Opcode::ShiftRight), AssignOp::Shr => Some(Opcode::ShiftRight),
AssignOp::Ushr => Some(Opcode::UnsignedShiftRight), AssignOp::Ushr => Some(Opcode::UnsignedShiftRight),
AssignOp::BoolAnd => { AssignOp::BoolAnd => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd); let exit = self.emit_opcode_with_operand(Opcode::LogicalAnd);
self.compile_expr(binary.rhs(), true)?; self.compile_expr(binary.rhs(), true)?;
let access = let access =
Self::compile_access(binary.lhs()).ok_or_else(|| { Self::compile_access(binary.lhs()).ok_or_else(|| {
@ -774,7 +822,7 @@ impl<'b> ByteCompiler<'b> {
None None
} }
AssignOp::BoolOr => { AssignOp::BoolOr => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalOr); let exit = self.emit_opcode_with_operand(Opcode::LogicalOr);
self.compile_expr(binary.rhs(), true)?; self.compile_expr(binary.rhs(), true)?;
let access = let access =
Self::compile_access(binary.lhs()).ok_or_else(|| { Self::compile_access(binary.lhs()).ok_or_else(|| {
@ -787,7 +835,7 @@ impl<'b> ByteCompiler<'b> {
None None
} }
AssignOp::Coalesce => { AssignOp::Coalesce => {
let exit = self.jump_with_custom_opcode(Opcode::Coalesce); let exit = self.emit_opcode_with_operand(Opcode::Coalesce);
self.compile_expr(binary.rhs(), true)?; self.compile_expr(binary.rhs(), true)?;
let access = let access =
Self::compile_access(binary.lhs()).ok_or_else(|| { Self::compile_access(binary.lhs()).ok_or_else(|| {
@ -1067,7 +1115,7 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::InitIterator);
self.emit_opcode(Opcode::PushUndefined); self.emit_opcode(Opcode::PushUndefined);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
let start = self.jump_with_custom_opcode(Opcode::GeneratorNextDelegate); let start = self.emit_opcode_with_operand(Opcode::GeneratorNextDelegate);
self.emit(Opcode::Jump, &[start_address]); self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(start); self.patch_jump(start);
} else { } else {
@ -1260,7 +1308,8 @@ impl<'b> ByteCompiler<'b> {
} }
Node::ForLoop(for_loop) => { Node::ForLoop(for_loop) => {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); let push_env =
self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
if let Some(init) = for_loop.init() { if let Some(init) = for_loop.init() {
self.create_decls_from_stmt(init)?; self.create_decls_from_stmt(init)?;
@ -1298,13 +1347,16 @@ impl<'b> ByteCompiler<'b> {
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
let num_bindings = self.context.pop_compile_time_environment().num_bindings(); let (num_bindings, compile_environment) =
self.patch_jump_with_target(push_env, num_bindings as u32); self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }
Node::ForInLoop(for_in_loop) => { Node::ForInLoop(for_in_loop) => {
self.compile_expr(for_in_loop.expr(), true)?; self.compile_expr(for_in_loop.expr(), true)?;
let early_exit = self.jump_with_custom_opcode(Opcode::ForInLoopInitIterator); let early_exit = self.emit_opcode_with_operand(Opcode::ForInLoopInitIterator);
self.emit_opcode(Opcode::LoopStart); self.emit_opcode(Opcode::LoopStart);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
@ -1312,8 +1364,9 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::LoopContinue); self.emit_opcode(Opcode::LoopContinue);
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); let push_env =
let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext); self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
let exit = self.emit_opcode_with_operand(Opcode::ForInLoopNext);
match for_in_loop.init() { match for_in_loop.init() {
IterableLoopInitializer::Identifier(ref ident) => { IterableLoopInitializer::Identifier(ref ident) => {
@ -1368,8 +1421,11 @@ impl<'b> ByteCompiler<'b> {
self.compile_stmt(for_in_loop.body(), false)?; self.compile_stmt(for_in_loop.body(), false)?;
let num_bindings = self.context.pop_compile_time_environment().num_bindings(); let (num_bindings, compile_environment) =
self.patch_jump_with_target(push_env, num_bindings as u32); self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
self.emit(Opcode::Jump, &[start_address]); self.emit(Opcode::Jump, &[start_address]);
@ -1392,8 +1448,9 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::LoopContinue); self.emit_opcode(Opcode::LoopContinue);
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); let push_env =
let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext); self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
let exit = self.emit_opcode_with_operand(Opcode::ForInLoopNext);
match for_of_loop.init() { match for_of_loop.init() {
IterableLoopInitializer::Identifier(ref ident) => { IterableLoopInitializer::Identifier(ref ident) => {
@ -1448,8 +1505,11 @@ impl<'b> ByteCompiler<'b> {
self.compile_stmt(for_of_loop.body(), false)?; self.compile_stmt(for_of_loop.body(), false)?;
let num_bindings = self.context.pop_compile_time_environment().num_bindings(); let (num_bindings, compile_environment) =
self.patch_jump_with_target(push_env, num_bindings as u32); self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
self.emit(Opcode::Jump, &[start_address]); self.emit(Opcode::Jump, &[start_address]);
@ -1623,11 +1683,15 @@ impl<'b> ByteCompiler<'b> {
} }
Node::Block(block) => { Node::Block(block) => {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); let push_env =
self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_declarations(block.items())?; self.create_declarations(block.items())?;
self.compile_statement_list(block.items(), use_expr)?; self.compile_statement_list(block.items(), use_expr)?;
let num_bindings = self.context.pop_compile_time_environment().num_bindings(); let (num_bindings, compile_environment) =
self.patch_jump_with_target(push_env, num_bindings as u32); self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }
Node::Throw(throw) => { Node::Throw(throw) => {
@ -1636,7 +1700,8 @@ impl<'b> ByteCompiler<'b> {
} }
Node::Switch(switch) => { Node::Switch(switch) => {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); let push_env =
self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
for case in switch.cases() { for case in switch.cases() {
self.create_declarations(case.body().items())?; self.create_declarations(case.body().items())?;
} }
@ -1649,10 +1714,10 @@ impl<'b> ByteCompiler<'b> {
let mut labels = Vec::with_capacity(switch.cases().len()); let mut labels = Vec::with_capacity(switch.cases().len());
for case in switch.cases() { for case in switch.cases() {
self.compile_expr(case.condition(), true)?; self.compile_expr(case.condition(), true)?;
labels.push(self.jump_with_custom_opcode(Opcode::Case)); labels.push(self.emit_opcode_with_operand(Opcode::Case));
} }
let exit = self.jump_with_custom_opcode(Opcode::Default); let exit = self.emit_opcode_with_operand(Opcode::Default);
for (label, case) in labels.into_iter().zip(switch.cases()) { for (label, case) in labels.into_iter().zip(switch.cases()) {
self.patch_jump(label); self.patch_jump(label);
@ -1668,8 +1733,12 @@ impl<'b> ByteCompiler<'b> {
self.pop_switch_control_info(); self.pop_switch_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
let num_bindings = self.context.pop_compile_time_environment().num_bindings();
self.patch_jump_with_target(push_env, num_bindings as u32); let (num_bindings, compile_environment) =
self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }
Node::FunctionDecl(_function) => self.function(node, false)?, Node::FunctionDecl(_function) => self.function(node, false)?,
@ -1686,13 +1755,17 @@ impl<'b> ByteCompiler<'b> {
let try_start = self.next_opcode_location(); let try_start = self.next_opcode_location();
self.emit(Opcode::TryStart, &[Self::DUMMY_ADDRESS, 0]); self.emit(Opcode::TryStart, &[Self::DUMMY_ADDRESS, 0]);
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); let push_env =
self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_declarations(t.block().items())?; self.create_declarations(t.block().items())?;
self.compile_statement_list(t.block().items(), use_expr)?; self.compile_statement_list(t.block().items(), use_expr)?;
let num_bindings = self.context.pop_compile_time_environment().num_bindings(); let (num_bindings, compile_environment) =
self.patch_jump_with_target(push_env, num_bindings as u32); self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::TryEnd); self.emit_opcode(Opcode::TryEnd);
@ -1702,12 +1775,13 @@ impl<'b> ByteCompiler<'b> {
if let Some(catch) = t.catch() { if let Some(catch) = t.catch() {
self.push_try_control_info_catch_start(); self.push_try_control_info_catch_start();
let catch_start = if t.finally().is_some() { let catch_start = if t.finally().is_some() {
Some(self.jump_with_custom_opcode(Opcode::CatchStart)) Some(self.emit_opcode_with_operand(Opcode::CatchStart))
} else { } else {
None None
}; };
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); let push_env =
self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
if let Some(decl) = catch.parameter() { if let Some(decl) = catch.parameter() {
match decl { match decl {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
@ -1728,8 +1802,12 @@ impl<'b> ByteCompiler<'b> {
self.create_declarations(catch.block().items())?; self.create_declarations(catch.block().items())?;
self.compile_statement_list(catch.block().items(), use_expr)?; self.compile_statement_list(catch.block().items(), use_expr)?;
let num_bindings = self.context.pop_compile_time_environment().num_bindings(); let (num_bindings, compile_environment) =
self.patch_jump_with_target(push_env, num_bindings as u32); self.context.pop_compile_time_environment();
let index_compile_environment =
self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
if let Some(catch_start) = catch_start { if let Some(catch_start) = catch_start {
self.emit_opcode(Opcode::CatchEnd); self.emit_opcode(Opcode::CatchEnd);
@ -1755,13 +1833,18 @@ impl<'b> ByteCompiler<'b> {
); );
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); let push_env =
self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_declarations(finally.items())?; self.create_declarations(finally.items())?;
self.compile_statement_list(finally.items(), false)?; self.compile_statement_list(finally.items(), false)?;
let num_bindings = self.context.pop_compile_time_environment().num_bindings(); let (num_bindings, compile_environment) =
self.patch_jump_with_target(push_env, num_bindings as u32); self.context.pop_compile_time_environment();
let index_compile_environment =
self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::FinallyEnd); self.emit_opcode(Opcode::FinallyEnd);
@ -1879,7 +1962,7 @@ impl<'b> ByteCompiler<'b> {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
compiler.context.create_mutable_binding(ident.sym(), false); compiler.context.create_mutable_binding(ident.sym(), false);
if let Some(init) = parameter.declaration().init() { if let Some(init) = parameter.declaration().init() {
let skip = compiler.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); let skip = compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
compiler.compile_expr(init, true)?; compiler.compile_expr(init, true)?;
compiler.patch_jump(skip); compiler.patch_jump(skip);
} }
@ -1901,7 +1984,7 @@ impl<'b> ByteCompiler<'b> {
let env_label = if parameters.has_expressions() { let env_label = if parameters.has_expressions() {
compiler.code_block.num_bindings = compiler.context.get_binding_number(); compiler.code_block.num_bindings = compiler.context.get_binding_number();
compiler.context.push_compile_time_environment(true); compiler.context.push_compile_time_environment(true);
Some(compiler.jump_with_custom_opcode(Opcode::PushFunctionEnvironment)) Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment))
} else { } else {
None None
}; };
@ -1916,17 +1999,22 @@ impl<'b> ByteCompiler<'b> {
compiler.compile_statement_list(body.items(), false)?; compiler.compile_statement_list(body.items(), false)?;
if let Some(env_label) = env_label { if let Some(env_label) = env_label {
let num_bindings = compiler let (num_bindings, compile_environment) =
.context
.pop_compile_time_environment()
.num_bindings();
compiler.patch_jump_with_target(env_label, num_bindings as u32);
compiler.context.pop_compile_time_environment(); compiler.context.pop_compile_time_environment();
let index_compile_environment = compiler.push_compile_environment(compile_environment);
compiler.patch_jump_with_target(env_label.0, num_bindings as u32);
compiler.patch_jump_with_target(env_label.1, index_compile_environment as u32);
let (_, compile_environment) = compiler.context.pop_compile_time_environment();
compiler.push_compile_environment(compile_environment);
} else { } else {
compiler.code_block.num_bindings = compiler let (num_bindings, compile_environment) =
.context compiler.context.pop_compile_time_environment();
.pop_compile_time_environment() compiler
.num_bindings(); .code_block
.compile_environments
.push(compile_environment);
compiler.code_block.num_bindings = num_bindings;
} }
compiler.code_block.params = parameters.clone(); compiler.code_block.params = parameters.clone();
@ -1966,12 +2054,16 @@ impl<'b> ByteCompiler<'b> {
pub(crate) fn call(&mut self, node: &Node, use_expr: bool) -> JsResult<()> { pub(crate) fn call(&mut self, node: &Node, use_expr: bool) -> JsResult<()> {
#[derive(PartialEq)] #[derive(PartialEq)]
enum CallKind { enum CallKind {
CallEval,
Call, Call,
New, New,
} }
let (call, kind) = match node { let (call, kind) = match node {
Node::Call(call) => (call, CallKind::Call), Node::Call(call) => match call.expr() {
Node::Identifier(ident) if ident.sym() == Sym::EVAL => (call, CallKind::CallEval),
_ => (call, CallKind::Call),
},
Node::New(new) => (new.call(), CallKind::New), Node::New(new) => (new.call(), CallKind::New),
_ => unreachable!(), _ => unreachable!(),
}; };
@ -1996,7 +2088,7 @@ impl<'b> ByteCompiler<'b> {
} }
expr => { expr => {
self.compile_expr(expr, true)?; self.compile_expr(expr, true)?;
if kind == CallKind::Call { if kind == CallKind::Call || kind == CallKind::CallEval {
self.emit_opcode(Opcode::This); self.emit_opcode(Opcode::This);
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
} }
@ -2010,6 +2102,10 @@ impl<'b> ByteCompiler<'b> {
let last_is_rest_parameter = matches!(call.args().last(), Some(Node::Spread(_))); let last_is_rest_parameter = matches!(call.args().last(), Some(Node::Spread(_)));
match kind { match kind {
CallKind::CallEval if last_is_rest_parameter => {
self.emit(Opcode::CallEvalWithRest, &[call.args().len() as u32]);
}
CallKind::CallEval => self.emit(Opcode::CallEval, &[call.args().len() as u32]),
CallKind::Call if last_is_rest_parameter => { CallKind::Call if last_is_rest_parameter => {
self.emit(Opcode::CallWithRest, &[call.args().len() as u32]); self.emit(Opcode::CallWithRest, &[call.args().len() as u32]);
} }
@ -2039,7 +2135,7 @@ impl<'b> ByteCompiler<'b> {
) -> JsResult<()> { ) -> JsResult<()> {
match pattern { match pattern {
DeclarationPattern::Object(pattern) => { DeclarationPattern::Object(pattern) => {
let skip_init = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); let skip_init = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
if let Some(init) = pattern.init() { if let Some(init) = pattern.init() {
self.compile_expr(init, true)?; self.compile_expr(init, true)?;
} else { } else {
@ -2078,7 +2174,8 @@ impl<'b> ByteCompiler<'b> {
} }
if let Some(init) = default_init { if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true)?; self.compile_expr(init, true)?;
self.patch_jump(skip); self.patch_jump(skip);
} }
@ -2140,7 +2237,8 @@ impl<'b> ByteCompiler<'b> {
} }
if let Some(init) = default_init { if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true)?; self.compile_expr(init, true)?;
self.patch_jump(skip); self.patch_jump(skip);
} }
@ -2153,7 +2251,7 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Pop);
} }
DeclarationPattern::Array(pattern) => { DeclarationPattern::Array(pattern) => {
let skip_init = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); let skip_init = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
if let Some(init) = pattern.init() { if let Some(init) = pattern.init() {
self.compile_expr(init, true)?; self.compile_expr(init, true)?;
} else { } else {
@ -2192,7 +2290,8 @@ impl<'b> ByteCompiler<'b> {
} => { } => {
self.emit_opcode(next); self.emit_opcode(next);
if let Some(init) = default_init { if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true)?; self.compile_expr(init, true)?;
self.patch_jump(skip); self.patch_jump(skip);
} }
@ -2462,7 +2561,8 @@ impl<'b> ByteCompiler<'b> {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
compiler.context.create_mutable_binding(ident.sym(), false); compiler.context.create_mutable_binding(ident.sym(), false);
if let Some(init) = parameter.declaration().init() { if let Some(init) = parameter.declaration().init() {
let skip = compiler.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); let skip =
compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
compiler.compile_expr(init, true)?; compiler.compile_expr(init, true)?;
compiler.patch_jump(skip); compiler.patch_jump(skip);
} }
@ -2482,30 +2582,38 @@ impl<'b> ByteCompiler<'b> {
let env_label = if expr.parameters().has_expressions() { let env_label = if expr.parameters().has_expressions() {
compiler.code_block.num_bindings = compiler.context.get_binding_number(); compiler.code_block.num_bindings = compiler.context.get_binding_number();
compiler.context.push_compile_time_environment(true); compiler.context.push_compile_time_environment(true);
Some(compiler.jump_with_custom_opcode(Opcode::PushFunctionEnvironment)) Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment))
} else { } else {
None None
}; };
compiler.create_declarations(expr.body().items())?; compiler.create_declarations(expr.body().items())?;
compiler.compile_statement_list(expr.body().items(), false)?; compiler.compile_statement_list(expr.body().items(), false)?;
if let Some(env_label) = env_label { if let Some(env_label) = env_label {
let num_bindings = compiler let (num_bindings, compile_environment) =
.context
.pop_compile_time_environment()
.num_bindings();
compiler.patch_jump_with_target(env_label, num_bindings as u32);
compiler.context.pop_compile_time_environment(); compiler.context.pop_compile_time_environment();
let index_compile_environment =
compiler.push_compile_environment(compile_environment);
compiler.patch_jump_with_target(env_label.0, num_bindings as u32);
compiler.patch_jump_with_target(env_label.1, index_compile_environment as u32);
let (_, compile_environment) = compiler.context.pop_compile_time_environment();
compiler.push_compile_environment(compile_environment);
} else { } else {
compiler.code_block.num_bindings = compiler let (num_bindings, compile_environment) =
.context compiler.context.pop_compile_time_environment();
.pop_compile_time_environment() compiler
.num_bindings(); .code_block
.compile_environments
.push(compile_environment);
compiler.code_block.num_bindings = num_bindings;
} }
} else { } else {
compiler.code_block.num_bindings = compiler let (num_bindings, compile_environment) =
.context compiler.context.pop_compile_time_environment();
.pop_compile_time_environment() compiler
.num_bindings(); .code_block
.compile_environments
.push(compile_environment);
compiler.code_block.num_bindings = num_bindings;
} }
compiler.emit_opcode(Opcode::PushUndefined); compiler.emit_opcode(Opcode::PushUndefined);
@ -2655,16 +2763,20 @@ impl<'b> ByteCompiler<'b> {
compiler.context.push_compile_time_environment(true); compiler.context.push_compile_time_environment(true);
compiler.create_declarations(statement_list.items())?; compiler.create_declarations(statement_list.items())?;
compiler.compile_statement_list(statement_list.items(), false)?; compiler.compile_statement_list(statement_list.items(), false)?;
compiler.code_block.num_bindings = compiler let (num_bindings, compile_environment) =
.context compiler.context.pop_compile_time_environment();
.pop_compile_time_environment() compiler
.num_bindings(); .code_block
.compile_environments
.push(compile_environment);
compiler.code_block.num_bindings = num_bindings;
let code = Gc::new(compiler.finish()); let code = Gc::new(compiler.finish());
let index = self.code_block.functions.len() as u32; let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code); self.code_block.functions.push(code);
self.emit(Opcode::GetFunction, &[index]); self.emit(Opcode::GetFunction, &[index]);
self.emit(Opcode::Call, &[0]); self.emit(Opcode::Call, &[0]);
self.emit_opcode(Opcode::Pop);
} }
ClassElement::MethodDefinition(..) ClassElement::MethodDefinition(..)
| ClassElement::PrivateMethodDefinition(..) | ClassElement::PrivateMethodDefinition(..)

17
boa_engine/src/context/mod.rs

@ -670,6 +670,23 @@ impl Context {
Ok(Gc::new(compiler.finish())) Ok(Gc::new(compiler.finish()))
} }
/// Compile the AST into a `CodeBlock` with an additional declarative environment.
#[inline]
pub(crate) fn compile_with_new_declarative(
&mut self,
statement_list: &StatementList,
strict: bool,
) -> JsResult<Gc<CodeBlock>> {
let _timer = Profiler::global().start_event("Compilation", "Main");
let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), self);
compiler.compile_statement_list_with_new_declarative(
statement_list.items(),
true,
strict || statement_list.strict(),
)?;
Ok(Gc::new(compiler.finish()))
}
/// Call the VM with a `CodeBlock` and return the result. /// Call the VM with a `CodeBlock` and return the result.
/// ///
/// Since this function receives a `Gc<CodeBlock>`, cloning the code is very cheap, since it's /// Since this function receives a `Gc<CodeBlock>`, cloning the code is very cheap, since it's

333
boa_engine/src/environments/compile.rs

@ -1,6 +1,7 @@
use crate::{ use crate::{
environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsString, JsValue, environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsString, JsValue,
}; };
use boa_gc::{Cell, Finalize, Gc, Trace};
use boa_interner::Sym; use boa_interner::Sym;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -11,60 +12,195 @@ use rustc_hash::FxHashMap;
struct CompileTimeBinding { struct CompileTimeBinding {
index: usize, index: usize,
mutable: bool, mutable: bool,
lex: bool,
} }
/// A compile time environment maps bound identifiers to their binding positions. /// A compile time environment maps bound identifiers to their binding positions.
/// ///
/// A compile time environment also indicates, if it is a function environment. /// A compile time environment also indicates, if it is a function environment.
#[derive(Debug)] #[derive(Debug, Finalize, Trace)]
pub(crate) struct CompileTimeEnvironment { pub(crate) struct CompileTimeEnvironment {
outer: Option<Gc<Cell<Self>>>,
environment_index: usize,
#[unsafe_ignore_trace]
bindings: FxHashMap<Sym, CompileTimeBinding>, bindings: FxHashMap<Sym, CompileTimeBinding>,
function_scope: bool, function_scope: bool,
} }
impl CompileTimeEnvironment { impl CompileTimeEnvironment {
/// Crate a new global compile time environment.
#[inline]
pub(crate) fn new_global() -> Self {
Self {
outer: None,
environment_index: 0,
bindings: FxHashMap::default(),
function_scope: true,
}
}
/// Check if environment has a lexical binding with the given name.
#[inline]
pub(crate) fn has_lex_binding(&self, name: Sym) -> bool {
self.bindings
.get(&name)
.map_or(false, |binding| binding.lex)
}
/// Returns the number of bindings in this environment. /// Returns the number of bindings in this environment.
#[inline] #[inline]
pub(crate) fn num_bindings(&self) -> usize { pub(crate) fn num_bindings(&self) -> usize {
self.bindings.len() self.bindings.len()
} }
}
/// The compile time environment stack contains a stack of all environments at bytecode compile time. /// Check if the environment is a function environment.
/// #[inline]
/// The first environment on the stack represents the global environment. pub(crate) fn is_function(&self) -> bool {
/// This is never being deleted and is tied to the existence of the realm. self.function_scope
/// All other environments are being dropped once they are not needed anymore. }
#[derive(Debug)]
pub(crate) struct CompileTimeEnvironmentStack {
stack: Vec<CompileTimeEnvironment>,
}
impl CompileTimeEnvironmentStack { /// Get the locator for a binding name.
/// Creates a new compile time environment stack. #[inline]
pub(crate) fn get_binding(&self, name: Sym) -> Option<BindingLocator> {
self.bindings
.get(&name)
.map(|binding| BindingLocator::declarative(name, self.environment_index, binding.index))
}
/// Get the locator for a binding name in this and all outer environments.
#[inline]
pub(crate) fn get_binding_recursive(&self, name: Sym) -> BindingLocator {
if let Some(binding) = self.bindings.get(&name) {
BindingLocator::declarative(name, self.environment_index, binding.index)
} else if let Some(outer) = &self.outer {
outer.borrow().get_binding_recursive(name)
} else {
BindingLocator::global(name)
}
}
/// Check if a binding name exists in this and all outer environments.
#[inline]
pub(crate) fn has_binding_recursive(&self, name: Sym) -> bool {
if self.bindings.contains_key(&name) {
true
} else if let Some(outer) = &self.outer {
outer.borrow().has_binding_recursive(name)
} else {
false
}
}
/// Create a mutable binding.
/// ///
/// This function should only be used once, on realm creation. /// If the binding is a function scope binding and this is a declarative environment, try the outer environment.
#[inline] #[inline]
pub(crate) fn new() -> Self { pub(crate) fn create_mutable_binding(&mut self, name: Sym, function_scope: bool) -> bool {
Self { if let Some(outer) = &self.outer {
stack: vec![CompileTimeEnvironment { if !function_scope || self.function_scope {
bindings: FxHashMap::default(), if !self.bindings.contains_key(&name) {
function_scope: true, let binding_index = self.bindings.len();
}], self.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: true,
lex: !function_scope,
},
);
}
true
} else {
return outer
.borrow_mut()
.create_mutable_binding(name, function_scope);
}
} else if function_scope {
false
} else {
if !self.bindings.contains_key(&name) {
let binding_index = self.bindings.len();
self.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: true,
lex: !function_scope,
},
);
}
true
}
}
/// Crate an immutable binding.
#[inline]
pub(crate) fn create_immutable_binding(&mut self, name: Sym) {
let binding_index = self.bindings.len();
self.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: false,
lex: true,
},
);
}
/// Return the binding locator for a mutable binding with the given binding name and scope.
#[inline]
pub(crate) fn initialize_mutable_binding(
&self,
name: Sym,
function_scope: bool,
) -> BindingLocator {
if let Some(outer) = &self.outer {
if function_scope && !self.function_scope {
return outer
.borrow()
.initialize_mutable_binding(name, function_scope);
}
if let Some(binding) = self.bindings.get(&name) {
BindingLocator::declarative(name, self.environment_index, binding.index)
} else {
outer
.borrow()
.initialize_mutable_binding(name, function_scope)
}
} else if let Some(binding) = self.bindings.get(&name) {
BindingLocator::declarative(name, self.environment_index, binding.index)
} else {
BindingLocator::global(name)
} }
} }
/// Get the number of bindings for the current last environment. /// Return the binding locator for an immutable binding.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if there are no environments on the stack. /// Panics if the binding is not in the current environment.
#[inline] #[inline]
pub(crate) fn get_binding_number(&self) -> usize { pub(crate) fn initialize_immutable_binding(&self, name: Sym) -> BindingLocator {
self.stack let binding = self.bindings.get(&name).expect("binding must exist");
.last() BindingLocator::declarative(name, self.environment_index, binding.index)
.expect("global environment must always exist") }
.num_bindings()
/// Return the binding locator for a mutable binding.
#[inline]
pub(crate) fn set_mutable_binding_recursive(&self, name: Sym) -> BindingLocator {
match self.bindings.get(&name) {
Some(binding) if binding.mutable => {
BindingLocator::declarative(name, self.environment_index, binding.index)
}
Some(_) => BindingLocator::mutate_immutable(name),
None => {
if let Some(outer) = &self.outer {
outer.borrow().set_mutable_binding_recursive(name)
} else {
BindingLocator::global(name)
}
}
}
} }
} }
@ -74,10 +210,15 @@ impl Context {
/// Note: This function only works at bytecode compile time! /// Note: This function only works at bytecode compile time!
#[inline] #[inline]
pub(crate) fn push_compile_time_environment(&mut self, function_scope: bool) { pub(crate) fn push_compile_time_environment(&mut self, function_scope: bool) {
self.realm.compile_env.stack.push(CompileTimeEnvironment { let environment_index = self.realm.compile_env.borrow().environment_index + 1;
let outer = self.realm.compile_env.clone();
self.realm.compile_env = Gc::new(Cell::new(CompileTimeEnvironment {
outer: Some(outer),
environment_index,
bindings: FxHashMap::default(), bindings: FxHashMap::default(),
function_scope, function_scope,
}); }));
} }
/// Pop the last compile time environment from the stack. /// Pop the last compile time environment from the stack.
@ -88,16 +229,20 @@ impl Context {
/// ///
/// Panics if there are no more environments that can be pop'ed. /// Panics if there are no more environments that can be pop'ed.
#[inline] #[inline]
pub(crate) fn pop_compile_time_environment(&mut self) -> CompileTimeEnvironment { pub(crate) fn pop_compile_time_environment(
assert!( &mut self,
self.realm.compile_env.stack.len() > 1, ) -> (usize, Gc<Cell<CompileTimeEnvironment>>) {
"cannot pop global environment" let current_env_borrow = self.realm.compile_env.borrow();
); if let Some(outer) = &current_env_borrow.outer {
self.realm let outer_clone = outer.clone();
.compile_env let num_bindings = current_env_borrow.num_bindings();
.stack drop(current_env_borrow);
.pop() let current = self.realm.compile_env.clone();
.expect("len > 1 already checked") self.realm.compile_env = outer_clone;
(num_bindings, current)
} else {
panic!("cannot pop global environment")
}
} }
/// Get the number of bindings for the current compile time environment. /// Get the number of bindings for the current compile time environment.
@ -109,12 +254,7 @@ impl Context {
/// Panics if there are no environments on the compile time environment stack. /// Panics if there are no environments on the compile time environment stack.
#[inline] #[inline]
pub(crate) fn get_binding_number(&self) -> usize { pub(crate) fn get_binding_number(&self) -> usize {
self.realm self.realm.compile_env.borrow().num_bindings()
.compile_env
.stack
.last()
.expect("global environment must always exist")
.num_bindings()
} }
/// Get the binding locator of the binding at bytecode compile time. /// Get the binding locator of the binding at bytecode compile time.
@ -122,12 +262,7 @@ impl Context {
/// Note: This function only works at bytecode compile time! /// Note: This function only works at bytecode compile time!
#[inline] #[inline]
pub(crate) fn get_binding_value(&self, name: Sym) -> BindingLocator { pub(crate) fn get_binding_value(&self, name: Sym) -> BindingLocator {
for (i, env) in self.realm.compile_env.stack.iter().enumerate().rev() { self.realm.compile_env.borrow().get_binding_recursive(name)
if let Some(binding) = env.bindings.get(&name) {
return BindingLocator::declarative(name, i, binding.index);
}
}
BindingLocator::global(name)
} }
/// Return if a declarative binding exists at bytecode compile time. /// Return if a declarative binding exists at bytecode compile time.
@ -136,12 +271,7 @@ impl Context {
/// Note: This function only works at bytecode compile time! /// Note: This function only works at bytecode compile time!
#[inline] #[inline]
pub(crate) fn has_binding(&self, name: Sym) -> bool { pub(crate) fn has_binding(&self, name: Sym) -> bool {
for env in self.realm.compile_env.stack.iter().rev() { self.realm.compile_env.borrow().has_binding_recursive(name)
if env.bindings.contains_key(&name) {
return true;
}
}
false
} }
/// Create a mutable binding at bytecode compile time. /// Create a mutable binding at bytecode compile time.
@ -154,21 +284,19 @@ impl Context {
/// Panics if the global environment is not function scoped. /// Panics if the global environment is not function scoped.
#[inline] #[inline]
pub(crate) fn create_mutable_binding(&mut self, name: Sym, function_scope: bool) { pub(crate) fn create_mutable_binding(&mut self, name: Sym, function_scope: bool) {
if !self
.realm
.compile_env
.borrow_mut()
.create_mutable_binding(name, function_scope)
{
let name_str = JsString::from(self.interner().resolve_expect(name)); let name_str = JsString::from(self.interner().resolve_expect(name));
for (i, env) in self.realm.compile_env.stack.iter_mut().enumerate().rev() {
if !function_scope || env.function_scope {
if env.bindings.contains_key(&name) {
return;
}
if i == 0 {
let desc = self let desc = self
.realm .realm
.global_property_map .global_property_map
.string_property_map() .string_property_map()
.get(&name_str); .get(&name_str);
if function_scope && desc.is_none() { if desc.is_none() {
self.global_bindings_mut().insert( self.global_bindings_mut().insert(
name_str, name_str,
PropertyDescriptor::builder() PropertyDescriptor::builder()
@ -178,25 +306,8 @@ impl Context {
.configurable(true) .configurable(true)
.build(), .build(),
); );
return;
} else if function_scope {
return;
} }
} }
let binding_index = env.bindings.len();
env.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: true,
},
);
return;
}
continue;
}
panic!("global environment must be function scoped")
} }
/// Initialize a mutable binding at bytecode compile time and return it's binding locator. /// Initialize a mutable binding at bytecode compile time and return it's binding locator.
@ -208,16 +319,10 @@ impl Context {
name: Sym, name: Sym,
function_scope: bool, function_scope: bool,
) -> BindingLocator { ) -> BindingLocator {
for (i, env) in self.realm.compile_env.stack.iter().enumerate().rev() { self.realm
if function_scope && !env.function_scope { .compile_env
continue; .borrow()
} .initialize_mutable_binding(name, function_scope)
if let Some(binding) = env.bindings.get(&name) {
return BindingLocator::declarative(name, i, binding.index);
}
return BindingLocator::global(name);
}
BindingLocator::global(name)
} }
/// Create an immutable binding at bytecode compile time. /// Create an immutable binding at bytecode compile time.
@ -230,21 +335,10 @@ impl Context {
/// Panics if the global environment does not exist. /// Panics if the global environment does not exist.
#[inline] #[inline]
pub(crate) fn create_immutable_binding(&mut self, name: Sym) { pub(crate) fn create_immutable_binding(&mut self, name: Sym) {
let env = self self.realm
.realm
.compile_env .compile_env
.stack .borrow_mut()
.last_mut() .create_immutable_binding(name);
.expect("global environment must always exist");
let binding_index = env.bindings.len();
env.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: false,
},
);
} }
/// Initialize an immutable binding at bytecode compile time and return it's binding locator. /// Initialize an immutable binding at bytecode compile time and return it's binding locator.
@ -256,16 +350,10 @@ impl Context {
/// Panics if the global environment does not exist or a the binding was not created on the current environment. /// Panics if the global environment does not exist or a the binding was not created on the current environment.
#[inline] #[inline]
pub(crate) fn initialize_immutable_binding(&self, name: Sym) -> BindingLocator { pub(crate) fn initialize_immutable_binding(&self, name: Sym) -> BindingLocator {
let environment_index = self.realm.compile_env.stack.len() - 1; self.realm
let env = self
.realm
.compile_env .compile_env
.stack .borrow()
.last() .initialize_immutable_binding(name)
.expect("global environment must always exist");
let binding = env.bindings.get(&name).expect("binding must exist");
BindingLocator::declarative(name, environment_index, binding.index)
} }
/// Return the binding locator for a set operation on an existing binding. /// Return the binding locator for a set operation on an existing binding.
@ -273,14 +361,9 @@ impl Context {
/// Note: This function only works at bytecode compile time! /// Note: This function only works at bytecode compile time!
#[inline] #[inline]
pub(crate) fn set_mutable_binding(&self, name: Sym) -> BindingLocator { pub(crate) fn set_mutable_binding(&self, name: Sym) -> BindingLocator {
for (i, env) in self.realm.compile_env.stack.iter().enumerate().rev() { self.realm
if let Some(binding) = env.bindings.get(&name) { .compile_env
if binding.mutable { .borrow()
return BindingLocator::declarative(name, i, binding.index); .set_mutable_binding_recursive(name)
}
return BindingLocator::mutate_immutable(name);
}
}
BindingLocator::global(name)
} }
} }

2
boa_engine/src/environments/mod.rs

@ -28,7 +28,7 @@ mod compile;
mod runtime; mod runtime;
pub(crate) use { pub(crate) use {
compile::CompileTimeEnvironmentStack, compile::CompileTimeEnvironment,
runtime::{BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack}, runtime::{BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack},
}; };

278
boa_engine/src/environments/runtime.rs

@ -1,18 +1,34 @@
use crate::{Context, JsResult, JsValue}; use crate::{environments::CompileTimeEnvironment, Context, JsResult, JsValue};
use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_gc::{Cell, Finalize, Gc, Trace};
use boa_interner::Sym; use boa_interner::Sym;
use rustc_hash::FxHashSet;
/// A declarative environment holds the bindings values at runtime. /// A declarative environment holds binding values at runtime.
/// ///
/// Bindings are stored in a fixed size list of optional values. /// Bindings are stored in a fixed size list of optional values.
/// If a binding is not initialized, the value is `None`. /// If a binding is not initialized, the value is `None`.
/// ///
/// Optionally, an environment can hold a `this` value. /// Optionally, an environment can hold a `this` value.
/// The `this` value is present only if the environment is a function environment. /// The `this` value is present only if the environment is a function environment.
///
/// Code evaluation at runtime (e.g. the `eval` built-in function) can add
/// bindings to existing, compiled function environments.
/// This makes it impossible to determine the location of all bindings at compile time.
/// To dynamically check for added bindings at runtime, a reference to the
/// corresponding compile time environment is needed.
///
/// Checking all environments for potential added bindings at runtime on every get/set
/// would offset the performance improvement of determining binding locations at compile time.
/// To minimize this, each environment holds a `poisoned` flag.
/// If bindings where added at runtime, the current environment and all inner environments
/// are marked as poisoned.
/// All poisoned environments have to be checked for added bindings.
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
pub(crate) struct DeclarativeEnvironment { pub(crate) struct DeclarativeEnvironment {
bindings: Cell<Vec<Option<JsValue>>>, bindings: Cell<Vec<Option<JsValue>>>,
this: Option<JsValue>, this: Option<JsValue>,
compile: Gc<Cell<CompileTimeEnvironment>>,
poisoned: Cell<bool>,
} }
impl DeclarativeEnvironment { impl DeclarativeEnvironment {
@ -59,15 +75,77 @@ pub struct DeclarativeEnvironmentStack {
impl DeclarativeEnvironmentStack { impl DeclarativeEnvironmentStack {
/// Create a new environment stack with the most outer declarative environment. /// Create a new environment stack with the most outer declarative environment.
#[inline] #[inline]
pub(crate) fn new() -> Self { pub(crate) fn new(global_compile_environment: Gc<Cell<CompileTimeEnvironment>>) -> Self {
Self { Self {
stack: vec![Gc::new(DeclarativeEnvironment { stack: vec![Gc::new(DeclarativeEnvironment {
bindings: Cell::new(Vec::new()), bindings: Cell::new(Vec::new()),
this: None, this: None,
compile: global_compile_environment,
poisoned: Cell::new(false),
})], })],
} }
} }
/// Extends the length of the next outer function environment to the number of compiled bindings.
///
/// This is only useful when compiled bindings are added after the initial compilation (eval).
pub(crate) fn extend_outer_function_environment(&mut self) {
for env in self.stack.iter().rev() {
if env.this.is_some() {
let compile_bindings_number = env.compile.borrow().num_bindings();
let mut bindings_mut = env.bindings.borrow_mut();
if compile_bindings_number > bindings_mut.len() {
let diff = compile_bindings_number - bindings_mut.len();
bindings_mut.extend(vec![None; diff]);
}
break;
}
}
}
/// Check if any of the provided binding names are defined as lexical bindings.
///
/// Start at the current environment.
/// Stop at the next outer function environment.
pub(crate) fn has_lex_binding_until_function_environment(
&self,
names: &FxHashSet<Sym>,
) -> Option<Sym> {
for env in self.stack.iter().rev() {
let compile = env.compile.borrow();
for name in names {
if compile.has_lex_binding(*name) {
return Some(*name);
}
}
if compile.is_function() {
break;
}
}
None
}
/// Pop all current environments except the global environment.
pub(crate) fn pop_to_global(&mut self) -> Vec<Gc<DeclarativeEnvironment>> {
self.stack.split_off(1)
}
/// Get the number of current environments.
pub(crate) fn len(&self) -> usize {
self.stack.len()
}
/// Truncate current environments to the given number.
pub(crate) fn truncate(&mut self, len: usize) {
self.stack.truncate(len);
}
/// Extend the current environment stack with the given environments.
pub(crate) fn extend(&mut self, other: Vec<Gc<DeclarativeEnvironment>>) {
self.stack.extend(other);
}
/// Set the number of bindings on the global environment. /// Set the number of bindings on the global environment.
/// ///
/// # Panics /// # Panics
@ -97,20 +175,57 @@ impl DeclarativeEnvironmentStack {
} }
/// Push a declarative environment on the environments stack. /// Push a declarative environment on the environments stack.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
#[inline] #[inline]
pub(crate) fn push_declarative(&mut self, num_bindings: usize) { pub(crate) fn push_declarative(
&mut self,
num_bindings: usize,
compile_environment: Gc<Cell<CompileTimeEnvironment>>,
) {
let poisoned = self
.stack
.last()
.expect("global environment must always exist")
.poisoned
.borrow()
.to_owned();
self.stack.push(Gc::new(DeclarativeEnvironment { self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]), bindings: Cell::new(vec![None; num_bindings]),
this: None, this: None,
compile: compile_environment,
poisoned: Cell::new(poisoned),
})); }));
} }
/// Push a function environment on the environments stack. /// Push a function environment on the environments stack.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
#[inline] #[inline]
pub(crate) fn push_function(&mut self, num_bindings: usize, this: JsValue) { pub(crate) fn push_function(
&mut self,
num_bindings: usize,
compile_environment: Gc<Cell<CompileTimeEnvironment>>,
this: JsValue,
) {
let poisoned = self
.stack
.last()
.expect("global environment must always exist")
.poisoned
.borrow()
.to_owned();
self.stack.push(Gc::new(DeclarativeEnvironment { self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]), bindings: Cell::new(vec![None; num_bindings]),
this: Some(this), this: Some(this),
compile: compile_environment,
poisoned: Cell::new(poisoned),
})); }));
} }
@ -134,6 +249,47 @@ impl DeclarativeEnvironmentStack {
.clone() .clone()
} }
/// Get the compile environment for the current runtime environment.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
pub(crate) fn current_compile_environment(&self) -> Gc<Cell<CompileTimeEnvironment>> {
self.stack
.last()
.expect("global environment must always exist")
.compile
.clone()
}
/// Mark that there may be added bindings in the current environment.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
#[inline]
pub(crate) fn poison_current(&mut self) {
let mut poisoned = self
.stack
.last()
.expect("global environment must always exist")
.poisoned
.borrow_mut();
*poisoned = true;
}
/// Mark that there may be added binding in all environments.
#[inline]
pub(crate) fn poison_all(&mut self) {
for env in &mut self.stack {
let mut poisoned = env.poisoned.borrow_mut();
if *poisoned {
return;
}
*poisoned = true;
}
}
/// Get the value of a binding. /// Get the value of a binding.
/// ///
/// # Panics /// # Panics
@ -142,9 +298,30 @@ impl DeclarativeEnvironmentStack {
#[inline] #[inline]
pub(crate) fn get_value_optional( pub(crate) fn get_value_optional(
&self, &self,
environment_index: usize, mut environment_index: usize,
binding_index: usize, mut binding_index: usize,
name: Sym,
) -> Option<JsValue> { ) -> Option<JsValue> {
if environment_index != self.stack.len() - 1 {
for env_index in (environment_index + 1..self.stack.len()).rev() {
let env = self
.stack
.get(env_index)
.expect("environment index must be in range");
if !*env.poisoned.borrow() {
break;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
environment_index = b.environment_index;
binding_index = b.binding_index;
break;
}
}
}
}
self.stack self.stack
.get(environment_index) .get(environment_index)
.expect("environment index must be in range") .expect("environment index must be in range")
@ -155,6 +332,34 @@ impl DeclarativeEnvironmentStack {
.clone() .clone()
} }
/// Get the value of a binding by it's name.
///
/// This only considers function environments that are poisoned.
/// All other bindings are accessed via indices.
#[inline]
pub(crate) fn get_value_global_poisoned(&self, name: Sym) -> Option<JsValue> {
for env in self.stack.iter().rev() {
if !*env.poisoned.borrow() {
return None;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
return self
.stack
.get(b.environment_index)
.expect("environment index must be in range")
.bindings
.borrow()
.get(b.binding_index)
.expect("binding index must be in range")
.clone();
}
}
}
None
}
/// Set the value of a binding. /// Set the value of a binding.
/// ///
/// # Panics /// # Panics
@ -188,10 +393,31 @@ impl DeclarativeEnvironmentStack {
#[inline] #[inline]
pub(crate) fn put_value_if_initialized( pub(crate) fn put_value_if_initialized(
&mut self, &mut self,
environment_index: usize, mut environment_index: usize,
binding_index: usize, mut binding_index: usize,
name: Sym,
value: JsValue, value: JsValue,
) -> bool { ) -> bool {
if environment_index != self.stack.len() - 1 {
for env_index in (environment_index + 1..self.stack.len()).rev() {
let env = self
.stack
.get(env_index)
.expect("environment index must be in range");
if !*env.poisoned.borrow() {
break;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
environment_index = b.environment_index;
binding_index = b.binding_index;
break;
}
}
}
}
let mut bindings = self let mut bindings = self
.stack .stack
.get(environment_index) .get(environment_index)
@ -234,6 +460,40 @@ impl DeclarativeEnvironmentStack {
*binding = Some(value); *binding = Some(value);
} }
} }
/// Set the value of a binding by it's name.
///
/// This only considers function environments that are poisoned.
/// All other bindings are set via indices.
///
/// # Panics
///
/// Panics if the environment or binding index are out of range.
#[inline]
pub(crate) fn put_value_global_poisoned(&mut self, name: Sym, value: &JsValue) -> bool {
for env in self.stack.iter().rev() {
if !*env.poisoned.borrow() {
return false;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
let mut bindings = self
.stack
.get(b.environment_index)
.expect("environment index must be in range")
.bindings
.borrow_mut();
let binding = bindings
.get_mut(b.binding_index)
.expect("binding index must be in range");
*binding = Some(value.clone());
return true;
}
}
}
false
}
} }
/// A binding locator contains all information about a binding that is needed to resolve it at runtime. /// A binding locator contains all information about a binding that is needed to resolve it at runtime.

13
boa_engine/src/realm.rs

@ -5,9 +5,10 @@
//! A realm is represented in this implementation as a Realm struct with the fields specified from the spec. //! A realm is represented in this implementation as a Realm struct with the fields specified from the spec.
use crate::{ use crate::{
environments::{CompileTimeEnvironmentStack, DeclarativeEnvironmentStack}, environments::{CompileTimeEnvironment, DeclarativeEnvironmentStack},
object::{GlobalPropertyMap, JsObject, ObjectData, PropertyMap}, object::{GlobalPropertyMap, JsObject, ObjectData, PropertyMap},
}; };
use boa_gc::{Cell, Gc};
use boa_profiler::Profiler; use boa_profiler::Profiler;
/// Representation of a Realm. /// Representation of a Realm.
@ -19,7 +20,7 @@ pub struct Realm {
pub(crate) global_extensible: bool, pub(crate) global_extensible: bool,
pub(crate) global_property_map: PropertyMap, pub(crate) global_property_map: PropertyMap,
pub(crate) environments: DeclarativeEnvironmentStack, pub(crate) environments: DeclarativeEnvironmentStack,
pub(crate) compile_env: CompileTimeEnvironmentStack, pub(crate) compile_env: Gc<Cell<CompileTimeEnvironment>>,
} }
impl Realm { impl Realm {
@ -31,12 +32,14 @@ impl Realm {
// Allow identification of the global object easily // Allow identification of the global object easily
let global_object = JsObject::from_proto_and_data(None, ObjectData::global()); let global_object = JsObject::from_proto_and_data(None, ObjectData::global());
let global_compile_environment = Gc::new(Cell::new(CompileTimeEnvironment::new_global()));
Self { Self {
global_object, global_object,
global_extensible: true, global_extensible: true,
global_property_map: PropertyMap::default(), global_property_map: PropertyMap::default(),
environments: DeclarativeEnvironmentStack::new(), environments: DeclarativeEnvironmentStack::new(global_compile_environment.clone()),
compile_env: CompileTimeEnvironmentStack::new(), compile_env: global_compile_environment,
} }
} }
@ -53,7 +56,7 @@ impl Realm {
/// Set the number of bindings on the global environment. /// Set the number of bindings on the global environment.
#[inline] #[inline]
pub(crate) fn set_global_binding_number(&mut self) { pub(crate) fn set_global_binding_number(&mut self) {
let binding_number = self.compile_env.get_binding_number(); let binding_number = self.compile_env.borrow().num_bindings();
self.environments.set_global_binding_number(binding_number); self.environments.set_global_binding_number(binding_number);
} }
} }

67
boa_engine/src/vm/code_block.rs

@ -11,7 +11,7 @@ use crate::{
generator::{Generator, GeneratorContext, GeneratorState}, generator::{Generator, GeneratorContext, GeneratorState},
}, },
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
environments::{BindingLocator, DeclarativeEnvironmentStack}, environments::{BindingLocator, CompileTimeEnvironment, DeclarativeEnvironmentStack},
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::{PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
syntax::ast::node::FormalParameterList, syntax::ast::node::FormalParameterList,
@ -100,6 +100,9 @@ pub struct CodeBlock {
/// Similar to the `[[ClassFieldInitializerName]]` slot in the spec. /// Similar to the `[[ClassFieldInitializerName]]` slot in the spec.
/// Holds class field names that are computed at class declaration time. /// Holds class field names that are computed at class declaration time.
pub(crate) computed_field_names: Option<Cell<Vec<PropertyKey>>>, pub(crate) computed_field_names: Option<Cell<Vec<PropertyKey>>>,
/// Compile time environments in this function.
pub(crate) compile_environments: Vec<Gc<Cell<CompileTimeEnvironment>>>,
} }
impl CodeBlock { impl CodeBlock {
@ -121,6 +124,7 @@ impl CodeBlock {
lexical_name_argument: false, lexical_name_argument: false,
arguments_binding: None, arguments_binding: None,
computed_field_names: None, computed_field_names: None,
compile_environments: Vec::new(),
} }
} }
@ -190,6 +194,8 @@ impl CodeBlock {
| Opcode::LogicalAnd | Opcode::LogicalAnd
| Opcode::LogicalOr | Opcode::LogicalOr
| Opcode::Coalesce | Opcode::Coalesce
| Opcode::CallEval
| Opcode::CallEvalWithRest
| Opcode::Call | Opcode::Call
| Opcode::CallWithRest | Opcode::CallWithRest
| Opcode::New | Opcode::New
@ -198,13 +204,14 @@ impl CodeBlock {
| Opcode::ForInLoopNext | Opcode::ForInLoopNext
| Opcode::ConcatToString | Opcode::ConcatToString
| Opcode::CopyDataProperties | Opcode::CopyDataProperties
| Opcode::GeneratorNextDelegate | Opcode::GeneratorNextDelegate => {
| Opcode::PushDeclarativeEnvironment => {
let result = self.read::<u32>(*pc).to_string(); let result = self.read::<u32>(*pc).to_string();
*pc += size_of::<u32>(); *pc += size_of::<u32>();
result result
} }
Opcode::TryStart => { Opcode::TryStart
| Opcode::PushDeclarativeEnvironment
| Opcode::PushFunctionEnvironment => {
let operand1 = self.read::<u32>(*pc); let operand1 = self.read::<u32>(*pc);
*pc += size_of::<u32>(); *pc += size_of::<u32>();
let operand2 = self.read::<u32>(*pc); let operand2 = self.read::<u32>(*pc);
@ -322,7 +329,6 @@ impl CodeBlock {
| Opcode::FinallyEnd | Opcode::FinallyEnd
| Opcode::This | Opcode::This
| Opcode::Return | Opcode::Return
| Opcode::PushFunctionEnvironment
| Opcode::PopEnvironment | Opcode::PopEnvironment
| Opcode::LoopStart | Opcode::LoopStart
| Opcode::LoopContinue | Opcode::LoopContinue
@ -634,10 +640,19 @@ impl JsObject {
this.clone() this.clone()
}; };
context if code.params.has_expressions() {
.realm context.realm.environments.push_function(
.environments code.num_bindings,
.push_function(code.num_bindings, this.clone()); code.compile_environments[1].clone(),
this.clone(),
);
} else {
context.realm.environments.push_function(
code.num_bindings,
code.compile_environments[0].clone(),
this.clone(),
);
}
if let Some(binding) = code.arguments_binding { if let Some(binding) = code.arguments_binding {
let arguments_obj = let arguments_obj =
@ -733,10 +748,19 @@ impl JsObject {
this.clone() this.clone()
}; };
context if code.params.has_expressions() {
.realm context.realm.environments.push_function(
.environments code.num_bindings,
.push_function(code.num_bindings, this.clone()); code.compile_environments[1].clone(),
this.clone(),
);
} else {
context.realm.environments.push_function(
code.num_bindings,
code.compile_environments[0].clone(),
this.clone(),
);
}
if let Some(binding) = code.arguments_binding { if let Some(binding) = code.arguments_binding {
let arguments_obj = let arguments_obj =
@ -904,10 +928,19 @@ impl JsObject {
this this
}; };
context if code.params.has_expressions() {
.realm context.realm.environments.push_function(
.environments code.num_bindings,
.push_function(code.num_bindings, this.clone().into()); code.compile_environments[1].clone(),
this.clone().into(),
);
} else {
context.realm.environments.push_function(
code.num_bindings,
code.compile_environments[0].clone(),
this.clone().into(),
);
}
let mut arguments_in_parameter_names = false; let mut arguments_in_parameter_names = false;
let mut is_simple_parameter_list = true; let mut is_simple_parameter_list = true;

150
boa_engine/src/vm/mod.rs

@ -445,6 +445,13 @@ impl Context {
binding_locator.throw_mutate_immutable(self)?; binding_locator.throw_mutate_immutable(self)?;
let value = if binding_locator.is_global() { let value = if binding_locator.is_global() {
if let Some(value) = self
.realm
.environments
.get_value_global_poisoned(binding_locator.name())
{
value
} else {
let key: JsString = self let key: JsString = self
.interner() .interner()
.resolve_expect(binding_locator.name()) .resolve_expect(binding_locator.name())
@ -461,14 +468,19 @@ impl Context {
self.call(&get, &self.global_object().clone().into(), &[])? self.call(&get, &self.global_object().clone().into(), &[])?
} }
_ => { _ => {
return self.throw_reference_error(format!("{key} is not defined")) return self
.throw_reference_error(format!("{key} is not defined"))
} }
}, },
_ => return self.throw_reference_error(format!("{key} is not defined")), _ => {
return self.throw_reference_error(format!("{key} is not defined"))
}
}
} }
} else if let Some(value) = self.realm.environments.get_value_optional( } else if let Some(value) = self.realm.environments.get_value_optional(
binding_locator.environment_index(), binding_locator.environment_index(),
binding_locator.binding_index(), binding_locator.binding_index(),
binding_locator.name(),
) { ) {
value value
} else { } else {
@ -484,6 +496,13 @@ impl Context {
let binding_locator = self.vm.frame().code.bindings[index as usize]; let binding_locator = self.vm.frame().code.bindings[index as usize];
binding_locator.throw_mutate_immutable(self)?; binding_locator.throw_mutate_immutable(self)?;
let value = if binding_locator.is_global() { let value = if binding_locator.is_global() {
if let Some(value) = self
.realm
.environments
.get_value_global_poisoned(binding_locator.name())
{
value
} else {
let key: JsString = self let key: JsString = self
.interner() .interner()
.resolve_expect(binding_locator.name()) .resolve_expect(binding_locator.name())
@ -503,9 +522,11 @@ impl Context {
}, },
_ => JsValue::undefined(), _ => JsValue::undefined(),
} }
}
} else if let Some(value) = self.realm.environments.get_value_optional( } else if let Some(value) = self.realm.environments.get_value_optional(
binding_locator.environment_index(), binding_locator.environment_index(),
binding_locator.binding_index(), binding_locator.binding_index(),
binding_locator.name(),
) { ) {
value value
} else { } else {
@ -521,6 +542,11 @@ impl Context {
binding_locator.throw_mutate_immutable(self)?; binding_locator.throw_mutate_immutable(self)?;
if binding_locator.is_global() { if binding_locator.is_global() {
if !self
.realm
.environments
.put_value_global_poisoned(binding_locator.name(), &value)
{
let key: JsString = self let key: JsString = self
.interner() .interner()
.resolve_expect(binding_locator.name()) .resolve_expect(binding_locator.name())
@ -533,19 +559,23 @@ impl Context {
)); ));
} }
let success = crate::object::internal_methods::global::global_set_no_receiver( let success =
crate::object::internal_methods::global::global_set_no_receiver(
&key.clone().into(), &key.clone().into(),
value, value,
self, self,
)?; )?;
if !success && (self.strict() || self.vm.frame().code.strict) { if !success && (self.strict() || self.vm.frame().code.strict) {
return self return self.throw_type_error(format!(
.throw_type_error(format!("cannot set non-writable property: {key}",)); "cannot set non-writable property: {key}",
));
}
} }
} else if !self.realm.environments.put_value_if_initialized( } else if !self.realm.environments.put_value_if_initialized(
binding_locator.environment_index(), binding_locator.environment_index(),
binding_locator.binding_index(), binding_locator.binding_index(),
binding_locator.name(),
value, value,
) { ) {
self.throw_reference_error(format!( self.throw_reference_error(format!(
@ -1200,6 +1230,95 @@ impl Context {
let function = create_generator_function_object(code, self); let function = create_generator_function_object(code, self);
self.vm.push(function); self.vm.push(function);
} }
Opcode::CallEval => {
if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded");
}
let argument_count = self.vm.read::<u32>();
let mut arguments = Vec::with_capacity(argument_count as usize);
for _ in 0..argument_count {
arguments.push(self.vm.pop());
}
arguments.reverse();
let func = self.vm.pop();
let mut this = self.vm.pop();
let object = match func {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => return self.throw_type_error("not a callable function"),
};
if this.is_null_or_undefined() {
this = self.global_object().clone().into();
}
// A native function with the name "eval" implies, that is this the built-in eval function.
let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. }));
let strict = self.strict() || self.vm.frame().code.strict;
if eval {
if let Some(x) = arguments.get(0) {
let result =
crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?;
self.vm.push(result);
} else {
self.vm.push(JsValue::Undefined);
}
} else {
let result = object.__call__(&this, &arguments, self)?;
self.vm.push(result);
}
}
Opcode::CallEvalWithRest => {
if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded");
}
let argument_count = self.vm.read::<u32>();
let rest_argument = self.vm.pop();
let mut arguments = Vec::with_capacity(argument_count as usize);
for _ in 0..(argument_count - 1) {
arguments.push(self.vm.pop());
}
arguments.reverse();
let func = self.vm.pop();
let mut this = self.vm.pop();
let iterator_record = rest_argument.get_iterator(self, None, None)?;
let mut rest_arguments = Vec::new();
while let Some(next) = iterator_record.step(self)? {
rest_arguments.push(next.value(self)?);
}
arguments.append(&mut rest_arguments);
let object = match func {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => return self.throw_type_error("not a callable function"),
};
if this.is_null_or_undefined() {
this = self.global_object().clone().into();
}
// A native function with the name "eval" implies, that is this the built-in eval function.
let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. }));
let strict = self.strict() || self.vm.frame().code.strict;
if eval {
if let Some(x) = arguments.get(0) {
let result =
crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?;
self.vm.push(result);
} else {
self.vm.push(JsValue::Undefined);
}
} else {
let result = object.__call__(&this, &arguments, self)?;
self.vm.push(result);
}
}
Opcode::Call => { Opcode::Call => {
if self.vm.stack_size_limit <= self.vm.stack.len() { if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded"); return self.throw_range_error("Maximum call stack size exceeded");
@ -1340,14 +1459,23 @@ impl Context {
} }
Opcode::PushDeclarativeEnvironment => { Opcode::PushDeclarativeEnvironment => {
let num_bindings = self.vm.read::<u32>(); let num_bindings = self.vm.read::<u32>();
let compile_environments_index = self.vm.read::<u32>();
let compile_environment = self.vm.frame().code.compile_environments
[compile_environments_index as usize]
.clone();
self.realm self.realm
.environments .environments
.push_declarative(num_bindings as usize); .push_declarative(num_bindings as usize, compile_environment);
self.vm.frame_mut().loop_env_stack_inc(); self.vm.frame_mut().loop_env_stack_inc();
self.vm.frame_mut().try_env_stack_inc(); self.vm.frame_mut().try_env_stack_inc();
} }
Opcode::PushFunctionEnvironment => { Opcode::PushFunctionEnvironment => {
let num_bindings = self.vm.read::<u32>(); let num_bindings = self.vm.read::<u32>();
let compile_environments_index = self.vm.read::<u32>();
let compile_environment = self.vm.frame().code.compile_environments
[compile_environments_index as usize]
.clone();
let is_constructor = self.vm.frame().code.constructor; let is_constructor = self.vm.frame().code.constructor;
let is_lexical = self.vm.frame().code.this_mode.is_lexical(); let is_lexical = self.vm.frame().code.this_mode.is_lexical();
let this = if is_constructor || !is_lexical { let this = if is_constructor || !is_lexical {
@ -1356,9 +1484,11 @@ impl Context {
JsValue::undefined() JsValue::undefined()
}; };
self.realm self.realm.environments.push_function(
.environments num_bindings as usize,
.push_function(num_bindings as usize, this); compile_environment,
this,
);
} }
Opcode::PopEnvironment => { Opcode::PopEnvironment => {
self.realm.environments.pop(); self.realm.environments.pop();
@ -1839,7 +1969,7 @@ impl Context {
println!("\n"); println!("\n");
} }
if self.vm.stack.is_empty() { if self.vm.stack.len() <= start_stack_size {
return Ok((JsValue::undefined(), ReturnType::Normal)); return Ok((JsValue::undefined(), ReturnType::Normal));
} }

22
boa_engine/src/vm/opcode.rs

@ -825,6 +825,20 @@ pub enum Opcode {
/// Stack: **=>** func /// Stack: **=>** func
GetGenerator, GetGenerator,
/// Call a function named "eval".
///
/// Operands: argument_count: `u32`
///
/// Stack: func, this, argument_1, ... argument_n **=>** result
CallEval,
/// Call a function named "eval" where the last argument is a rest parameter.
///
/// Operands: argument_count: `u32`
///
/// Stack: func, this, argument_1, ... argument_n **=>** result
CallEvalWithRest,
/// Call a function. /// Call a function.
/// ///
/// Operands: argument_count: `u32` /// Operands: argument_count: `u32`
@ -862,14 +876,14 @@ pub enum Opcode {
/// Push a declarative environment. /// Push a declarative environment.
/// ///
/// Operands: num_bindings: `u32` /// Operands: num_bindings: `u32`, compile_environments_index: `u32`
/// ///
/// Stack: **=>** /// Stack: **=>**
PushDeclarativeEnvironment, PushDeclarativeEnvironment,
/// Push a function environment. /// Push a function environment.
/// ///
/// Operands: /// Operands: num_bindings: `u32`, compile_environments_index: `u32`
/// ///
/// Stack: **=>** /// Stack: **=>**
PushFunctionEnvironment, PushFunctionEnvironment,
@ -1155,6 +1169,8 @@ impl Opcode {
Opcode::Default => "Default", Opcode::Default => "Default",
Opcode::GetFunction => "GetFunction", Opcode::GetFunction => "GetFunction",
Opcode::GetGenerator => "GetGenerator", Opcode::GetGenerator => "GetGenerator",
Opcode::CallEval => "CallEval",
Opcode::CallEvalWithRest => "CallEvalWithRest",
Opcode::Call => "Call", Opcode::Call => "Call",
Opcode::CallWithRest => "CallWithRest", Opcode::CallWithRest => "CallWithRest",
Opcode::New => "New", Opcode::New => "New",
@ -1287,6 +1303,8 @@ impl Opcode {
Opcode::Default => "INST - Default", Opcode::Default => "INST - Default",
Opcode::GetFunction => "INST - GetFunction", Opcode::GetFunction => "INST - GetFunction",
Opcode::GetGenerator => "INST - GetGenerator", Opcode::GetGenerator => "INST - GetGenerator",
Opcode::CallEval => "INST - CallEval",
Opcode::CallEvalWithRest => "INST - CallEvalWithRest",
Opcode::Call => "INST - Call", Opcode::Call => "INST - Call",
Opcode::CallWithRest => "INST - CallWithRest", Opcode::CallWithRest => "INST - CallWithRest",
Opcode::New => "INST - New", Opcode::New => "INST - New",

Loading…
Cancel
Save