Browse Source

Start removing non-VM path (#1747)

pull/1753/head
Jason Williams 3 years ago committed by GitHub
parent
commit
dfb3df5bf2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      .github/workflows/bors.yml
  2. 22
      .github/workflows/rust.yml
  3. 20
      .github/workflows/test262.yml
  4. 2
      .vscode/tasks.json
  5. 11
      boa/Cargo.toml
  6. 14
      boa/benches/README.md
  7. 1
      boa/benches/bench_scripts/expression.js
  8. 7
      boa/benches/bench_scripts/goal_symbol_switch.js
  9. 2
      boa/benches/bench_scripts/hello_world.js
  10. 9
      boa/benches/bench_scripts/long_repetition.js
  11. 359
      boa/benches/exec.rs
  12. 290
      boa/benches/full.rs
  13. 99
      boa/benches/parser.rs
  14. 7
      boa/src/builtins/console/mod.rs
  15. 121
      boa/src/builtins/function/mod.rs
  16. 2
      boa/src/builtins/iterable/mod.rs
  17. 2
      boa/src/builtins/json/mod.rs
  18. 241
      boa/src/context.rs
  19. 1
      boa/src/environment/lexical_environment.rs
  20. 51
      boa/src/exec/mod.rs
  21. 25
      boa/src/lib.rs
  22. 278
      boa/src/object/internal_methods/function.rs
  23. 38
      boa/src/syntax/ast/node/array/mod.rs
  24. 9
      boa/src/syntax/ast/node/await_expr/mod.rs
  25. 55
      boa/src/syntax/ast/node/block/mod.rs
  26. 17
      boa/src/syntax/ast/node/break_node/mod.rs
  27. 20
      boa/src/syntax/ast/node/break_node/tests.rs
  28. 65
      boa/src/syntax/ast/node/call/mod.rs
  29. 12
      boa/src/syntax/ast/node/conditional/conditional_op/mod.rs
  30. 14
      boa/src/syntax/ast/node/conditional/if_node/mod.rs
  31. 15
      boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs
  32. 14
      boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs
  33. 13
      boa/src/syntax/ast/node/declaration/async_function_expr/mod.rs
  34. 14
      boa/src/syntax/ast/node/declaration/async_generator_decl/mod.rs
  35. 9
      boa/src/syntax/ast/node/declaration/async_generator_expr/mod.rs
  36. 26
      boa/src/syntax/ast/node/declaration/function_decl/mod.rs
  37. 17
      boa/src/syntax/ast/node/declaration/function_expr/mod.rs
  38. 11
      boa/src/syntax/ast/node/declaration/generator_decl/mod.rs
  39. 10
      boa/src/syntax/ast/node/declaration/generator_expr/mod.rs
  40. 430
      boa/src/syntax/ast/node/declaration/mod.rs
  41. 13
      boa/src/syntax/ast/node/field/get_const_field/mod.rs
  42. 14
      boa/src/syntax/ast/node/field/get_field/mod.rs
  43. 9
      boa/src/syntax/ast/node/identifier/mod.rs
  44. 12
      boa/src/syntax/ast/node/iteration/continue_node/mod.rs
  45. 30
      boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs
  46. 156
      boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs
  47. 53
      boa/src/syntax/ast/node/iteration/for_loop/mod.rs
  48. 143
      boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs
  49. 21
      boa/src/syntax/ast/node/iteration/mod.rs
  50. 27
      boa/src/syntax/ast/node/iteration/while_loop/mod.rs
  51. 91
      boa/src/syntax/ast/node/mod.rs
  52. 40
      boa/src/syntax/ast/node/new/mod.rs
  53. 143
      boa/src/syntax/ast/node/object/mod.rs
  54. 46
      boa/src/syntax/ast/node/operator/assign/mod.rs
  55. 149
      boa/src/syntax/ast/node/operator/bin_op/mod.rs
  56. 82
      boa/src/syntax/ast/node/operator/unary_op/mod.rs
  57. 16
      boa/src/syntax/ast/node/return_smt/mod.rs
  58. 9
      boa/src/syntax/ast/node/spread/mod.rs
  59. 58
      boa/src/syntax/ast/node/statement_list/mod.rs
  60. 80
      boa/src/syntax/ast/node/switch/mod.rs
  61. 78
      boa/src/syntax/ast/node/template/mod.rs
  62. 9
      boa/src/syntax/ast/node/throw/mod.rs
  63. 63
      boa/src/syntax/ast/node/try_node/mod.rs
  64. 9
      boa/src/syntax/ast/node/yield/mod.rs
  65. 23
      boa/src/tests.rs
  66. 24
      boa/src/vm/code_block.rs
  67. 21
      boa/src/vm/mod.rs
  68. 3
      boa_cli/Cargo.toml
  69. 2
      boa_cli/src/main.rs
  70. 3
      boa_tester/Cargo.toml
  71. 6
      boa_tester/src/exec/js262.rs
  72. 8
      boa_tester/src/exec/mod.rs
  73. 3
      boa_tester/src/results.rs
  74. 17
      boa_wasm/src/lib.rs

22
.github/workflows/bors.yml

@ -29,28 +29,6 @@ jobs:
with:
command: test
args: -v
test_vm_on_linux:
name: Tests on Linux with vm enabled
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
- name: Cache cargo
uses: actions/cache@v2.1.7
with:
path: |
target
~/.cargo/git
~/.cargo/registry
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/cargo@v1
with:
command: test
args: ---package Boa --lib --features=vm -- vm --nocapture
test_on_windows:
name: Tests on Windows

22
.github/workflows/rust.yml

@ -33,28 +33,6 @@ jobs:
args: --ignore-tests
- name: Upload to codecov.io
uses: codecov/codecov-action@v2.1.0
test_vm_on_linux:
name: Tests on Linux with vm enabled
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: stable
override: true
profile: minimal
- name: Cache cargo
uses: actions/cache@v2.1.7
with:
path: |
target
~/.cargo/git
~/.cargo/registry
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/cargo@v1
with:
command: test
args: ---package Boa --lib --features=vm -- vm --nocapture
test_on_windows:
name: Tests on Windows

20
.github/workflows/test262.yml

@ -61,26 +61,6 @@ jobs:
comment="${comment//$'\r'/'%0D'}"
echo "::set-output name=comment::$comment"
- name: Run the test262 test suite for VM
run: |
cd boa
mkdir -p ../results
cargo run --release --features vm --bin boa_tester -- run -v -o ../results/test262/vm
cd ..
# Run the results comparison for VM
- name: Compare results for VM
if: github.event_name == 'pull_request'
id: compare-vm
shell: bash
run: |
cd boa
comment="$(./target/release/boa_tester compare ../gh-pages/test262/vm/refs/heads/main/latest.json ../results/test262/vm/pull/latest.json -m)"
comment="${comment//'%'/'%25'}"
comment="${comment//$'\n'/'%0A'}"
comment="${comment//$'\r'/'%0D'}"
echo "::set-output name=comment::$comment"
- name: Get the PR number
if: github.event_name == 'pull_request'
id: pr-number

2
.vscode/tasks.json vendored

@ -26,7 +26,7 @@
"type": "process",
"label": "Cargo Run (VM)",
"command": "cargo",
"args": ["run", "--features", "vm", "--", "-t", "../tests/js/test.js"],
"args": ["run", "--", "-t", "../tests/js/test.js"],
"group": {
"kind": "build",
"isDefault": true

11
boa/Cargo.toml

@ -15,9 +15,6 @@ rust-version = "1.57"
profiler = ["measureme"]
deser = []
# Enable Bytecode generation & execution instead of tree walking
vm = []
# Enable Boa's WHATWG console object implementation.
console = []
@ -59,14 +56,6 @@ crate-type = ["cdylib", "lib"]
name = "boa"
bench = false
[[bench]]
name = "parser"
harness = false
[[bench]]
name = "exec"
harness = false
[[bench]]
name = "full"
harness = false

14
boa/benches/README.md

@ -1,10 +1,10 @@
# Boa Benchmarks.
# Boa Benchmarks
We divide the benchmarks in 3 sections:
For each js script in the `bench_scripts` folder, we create three benchmarks:
- Full engine benchmarks (lexing + parsing + realm creation + execution)
- Execution benchmarks
- Parsing benchmarks (lexing + parse - these are tightly coupled so must be benchmarked together)
- Parser => lexing and parsing of the source code
- Compiler => compilation of the parsed statement list into bytecode
- Execution => execution of the bytecode in the vm
The idea is to check the performance of Boa in different scenarios and dividing the Boa execution
process in its different parts.
The idea is to check the performance of Boa in different scenarios.
Different parts of Boa are benchmarked separately to make the impact of local changes visible.

1
boa/benches/bench_scripts/expression.js

@ -1 +0,0 @@
1 + 1 + 1 + 1 + 1 + 1 / 1 + 1 + 1 * 1 + 1 + 1 + 1;

7
boa/benches/bench_scripts/goal_symbol_switch.js

@ -1,7 +0,0 @@
function foo(regex, num) {}
let i = 0;
while (i < 1000000) {
foo(/ab+c/, 5.0 / 5);
i++;
}

2
boa/benches/bench_scripts/hello_world.js

@ -1,2 +0,0 @@
let foo = "hello world!";
foo;

9
boa/benches/bench_scripts/long_repetition.js

@ -1,9 +0,0 @@
for (let a = 10; a < 100; a++) {
if (a < 10) {
console.log("impossible D:");
} else if (a < 50) {
console.log("starting");
} else {
console.log("finishing");
}
}

359
boa/benches/exec.rs

@ -1,359 +0,0 @@
//! Benchmarks of the whole execution engine in Boa.
use boa::{exec::Executable, realm::Realm, syntax::Parser, Context};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
#[cfg_attr(
all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"),
global_allocator
)]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
fn create_realm(c: &mut Criterion) {
c.bench_function("Create Realm", move |b| b.iter(Realm::create));
}
static SYMBOL_CREATION: &str = include_str!("bench_scripts/symbol_creation.js");
fn symbol_creation(c: &mut Criterion) {
let mut context = Context::new();
// Parse the AST nodes.
let nodes = Parser::new(SYMBOL_CREATION.as_bytes(), false)
.parse_all()
.unwrap();
// Execute the parsed nodes, passing them through a black box, to avoid over-optimizing by the compiler
c.bench_function("Symbols (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static FOR_LOOP: &str = include_str!("bench_scripts/for_loop.js");
fn for_loop_execution(c: &mut Criterion) {
let mut context = Context::new();
// Parse the AST nodes.
let nodes = Parser::new(FOR_LOOP.as_bytes(), false).parse_all().unwrap();
// Execute the parsed nodes, passing them through a black box, to avoid over-optimizing by the compiler
c.bench_function("For loop (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static FIBONACCI: &str = include_str!("bench_scripts/fibonacci.js");
fn fibonacci(c: &mut Criterion) {
let mut context = Context::new();
// Parse the AST nodes.
let nodes = Parser::new(FIBONACCI.as_bytes(), false)
.parse_all()
.unwrap();
// Execute the parsed nodes, passing them through a black box, to avoid over-optimizing by the compiler
c.bench_function("Fibonacci (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static OBJECT_CREATION: &str = include_str!("bench_scripts/object_creation.js");
fn object_creation(c: &mut Criterion) {
let mut context = Context::new();
// Parse the AST nodes.
let nodes = Parser::new(OBJECT_CREATION.as_bytes(), false)
.parse_all()
.unwrap();
// Execute the parsed nodes, passing them through a black box, to avoid over-optimizing by the compiler
c.bench_function("Object Creation (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static OBJECT_PROP_ACCESS_CONST: &str = include_str!("bench_scripts/object_prop_access_const.js");
fn object_prop_access_const(c: &mut Criterion) {
let mut context = Context::new();
// Parse the AST nodes.
let nodes = Parser::new(OBJECT_PROP_ACCESS_CONST.as_bytes(), false)
.parse_all()
.unwrap();
// Execute the parsed nodes, passing them through a black box, to avoid over-optimizing by the compiler
c.bench_function("Static Object Property Access (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static OBJECT_PROP_ACCESS_DYN: &str = include_str!("bench_scripts/object_prop_access_dyn.js");
fn object_prop_access_dyn(c: &mut Criterion) {
let mut context = Context::new();
// Parse the AST nodes.
let nodes = Parser::new(OBJECT_PROP_ACCESS_DYN.as_bytes(), false)
.parse_all()
.unwrap();
// Execute the parsed nodes, passing them through a black box, to avoid over-optimizing by the compiler
c.bench_function("Dynamic Object Property Access (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static REGEXP_LITERAL_CREATION: &str = include_str!("bench_scripts/regexp_literal_creation.js");
fn regexp_literal_creation(c: &mut Criterion) {
let mut context = Context::new();
// Parse the AST nodes.
let nodes = Parser::new(REGEXP_LITERAL_CREATION.as_bytes(), false)
.parse_all()
.unwrap();
// Execute the parsed nodes, passing them through a black box, to avoid over-optimizing by the compiler
c.bench_function("RegExp Literal Creation (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static REGEXP_CREATION: &str = include_str!("bench_scripts/regexp_creation.js");
fn regexp_creation(c: &mut Criterion) {
let mut context = Context::new();
// Parse the AST nodes.
let nodes = Parser::new(REGEXP_CREATION.as_bytes(), false)
.parse_all()
.unwrap();
// Execute the parsed nodes, passing them through a black box, to avoid over-optimizing by the compiler
c.bench_function("RegExp (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static REGEXP_LITERAL: &str = include_str!("bench_scripts/regexp_literal.js");
fn regexp_literal(c: &mut Criterion) {
let mut context = Context::new();
// Parse the AST nodes.
let nodes = Parser::new(REGEXP_LITERAL.as_bytes(), false)
.parse_all()
.unwrap();
// Execute the parsed nodes, passing them through a black box, to avoid over-optimizing by the compiler
c.bench_function("RegExp Literal (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static REGEXP: &str = include_str!("bench_scripts/regexp.js");
fn regexp(c: &mut Criterion) {
let mut context = Context::new();
// Parse the AST nodes.
let nodes = Parser::new(REGEXP.as_bytes(), false).parse_all().unwrap();
// Execute the parsed nodes, passing them through a black box, to avoid over-optimizing by the compiler
c.bench_function("RegExp (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static ARRAY_ACCESS: &str = include_str!("bench_scripts/array_access.js");
fn array_access(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(ARRAY_ACCESS.as_bytes(), false)
.parse_all()
.unwrap();
c.bench_function("Array access (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static ARRAY_CREATE: &str = include_str!("bench_scripts/array_create.js");
fn array_creation(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(ARRAY_CREATE.as_bytes(), false)
.parse_all()
.unwrap();
c.bench_function("Array creation (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static ARRAY_POP: &str = include_str!("bench_scripts/array_pop.js");
fn array_pop(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(ARRAY_POP.as_bytes(), false)
.parse_all()
.unwrap();
c.bench_function("Array pop (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static STRING_CONCAT: &str = include_str!("bench_scripts/string_concat.js");
fn string_concat(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(STRING_CONCAT.as_bytes(), false)
.parse_all()
.unwrap();
c.bench_function("String concatenation (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static STRING_COMPARE: &str = include_str!("bench_scripts/string_compare.js");
fn string_compare(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(STRING_COMPARE.as_bytes(), false)
.parse_all()
.unwrap();
c.bench_function("String comparison (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static STRING_COPY: &str = include_str!("bench_scripts/string_copy.js");
fn string_copy(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(STRING_COPY.as_bytes(), false)
.parse_all()
.unwrap();
c.bench_function("String copy (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static NUMBER_OBJECT_ACCESS: &str = include_str!("bench_scripts/number_object_access.js");
fn number_object_access(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(NUMBER_OBJECT_ACCESS.as_bytes(), false)
.parse_all()
.unwrap();
c.bench_function("Number Object Access (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static BOOLEAN_OBJECT_ACCESS: &str = include_str!("bench_scripts/boolean_object_access.js");
fn boolean_object_access(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(BOOLEAN_OBJECT_ACCESS.as_bytes(), false)
.parse_all()
.unwrap();
c.bench_function("Boolean Object Access (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static STRING_OBJECT_ACCESS: &str = include_str!("bench_scripts/string_object_access.js");
fn string_object_access(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(STRING_OBJECT_ACCESS.as_bytes(), false)
.parse_all()
.unwrap();
c.bench_function("String Object Access (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static ARITHMETIC_OPERATIONS: &str = include_str!("bench_scripts/arithmetic_operations.js");
fn arithmetic_operations(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(ARITHMETIC_OPERATIONS.as_bytes(), false)
.parse_all()
.unwrap();
c.bench_function("Arithmetic operations (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static CLEAN_JS: &str = include_str!("bench_scripts/clean_js.js");
fn clean_js(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(CLEAN_JS.as_bytes(), false).parse_all().unwrap();
c.bench_function("Clean js (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
static MINI_JS: &str = include_str!("bench_scripts/mini_js.js");
fn mini_js(c: &mut Criterion) {
let mut context = Context::new();
let nodes = Parser::new(MINI_JS.as_bytes(), false).parse_all().unwrap();
c.bench_function("Mini js (Execution)", move |b| {
b.iter(|| black_box(&nodes).run(&mut context).unwrap())
});
}
criterion_group!(
execution,
create_realm,
symbol_creation,
for_loop_execution,
fibonacci,
array_access,
array_creation,
array_pop,
object_creation,
object_prop_access_const,
object_prop_access_dyn,
regexp_literal_creation,
regexp_creation,
regexp_literal,
regexp,
string_concat,
string_compare,
string_copy,
number_object_access,
boolean_object_access,
string_object_access,
arithmetic_operations,
clean_js,
mini_js,
);
criterion_main!(execution);

290
boa/benches/full.rs

@ -1,6 +1,6 @@
//! Benchmarks of whole program execution in Boa.
//! Benchmarks of the whole execution engine in Boa.
use boa::Context;
use boa::{parse, realm::Realm, Context};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
@ -10,215 +10,83 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion};
)]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
static SYMBOL_CREATION: &str = include_str!("bench_scripts/symbol_creation.js");
fn symbol_creation(c: &mut Criterion) {
// Execute the code by taking into account realm creation, lexing and parsing
c.bench_function("Symbols (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(SYMBOL_CREATION)))
});
}
static FOR_LOOP: &str = include_str!("bench_scripts/for_loop.js");
fn for_loop(c: &mut Criterion) {
// Execute the code by taking into account realm creation, lexing and parsing
c.bench_function("For loop (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(FOR_LOOP)))
});
}
static FIBONACCI: &str = include_str!("bench_scripts/fibonacci.js");
fn fibonacci(c: &mut Criterion) {
// Execute the code by taking into account realm creation, lexing and parsing
c.bench_function("Fibonacci (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(FIBONACCI)))
});
}
static OBJECT_CREATION: &str = include_str!("bench_scripts/object_creation.js");
fn object_creation(c: &mut Criterion) {
// Execute the code by taking into account realm creation, lexing and parsing
c.bench_function("Object Creation (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(OBJECT_CREATION)))
});
}
static OBJECT_PROP_ACCESS_CONST: &str = include_str!("bench_scripts/object_prop_access_const.js");
fn object_prop_access_const(c: &mut Criterion) {
// Execute the code by taking into account realm creation, lexing and parsing
c.bench_function("Static Object Property Access (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(OBJECT_PROP_ACCESS_CONST)))
});
}
static OBJECT_PROP_ACCESS_DYN: &str = include_str!("bench_scripts/object_prop_access_dyn.js");
fn object_prop_access_dyn(c: &mut Criterion) {
// Execute the code by taking into account realm creation, lexing and parsing
c.bench_function("Dynamic Object Property Access (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(OBJECT_PROP_ACCESS_DYN)))
});
}
static REGEXP_LITERAL_CREATION: &str = include_str!("bench_scripts/regexp_literal_creation.js");
fn regexp_literal_creation(c: &mut Criterion) {
// Execute the code by taking into account realm creation, lexing and parsing
c.bench_function("RegExp Literal Creation (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(REGEXP_LITERAL_CREATION)))
});
}
static REGEXP_CREATION: &str = include_str!("bench_scripts/regexp_creation.js");
fn regexp_creation(c: &mut Criterion) {
// Execute the code by taking into account realm creation, lexing and parsing
c.bench_function("RegExp (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(REGEXP_CREATION)))
});
}
static REGEXP_LITERAL: &str = include_str!("bench_scripts/regexp_literal.js");
fn regexp_literal(c: &mut Criterion) {
// Execute the code by taking into account realm creation, lexing and parsing
c.bench_function("RegExp Literal (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(REGEXP_LITERAL)))
});
}
static REGEXP: &str = include_str!("bench_scripts/regexp.js");
fn regexp(c: &mut Criterion) {
// Execute the code by taking into account realm creation, lexing and parsing
c.bench_function("RegExp (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(REGEXP)))
});
}
static ARRAY_ACCESS: &str = include_str!("bench_scripts/array_access.js");
fn array_access(c: &mut Criterion) {
c.bench_function("Array access (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(ARRAY_ACCESS)))
});
}
static ARRAY_CREATE: &str = include_str!("bench_scripts/array_create.js");
fn array_creation(c: &mut Criterion) {
c.bench_function("Array creation (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(ARRAY_CREATE)))
});
}
static ARRAY_POP: &str = include_str!("bench_scripts/array_pop.js");
fn array_pop(c: &mut Criterion) {
c.bench_function("Array pop (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(ARRAY_POP)))
});
}
static STRING_CONCAT: &str = include_str!("bench_scripts/string_concat.js");
fn string_concat(c: &mut Criterion) {
c.bench_function("String concatenation (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(STRING_CONCAT)))
});
}
static STRING_COMPARE: &str = include_str!("bench_scripts/string_compare.js");
fn string_compare(c: &mut Criterion) {
c.bench_function("String comparison (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(STRING_COMPARE)))
});
}
static STRING_COPY: &str = include_str!("bench_scripts/string_copy.js");
fn string_copy(c: &mut Criterion) {
c.bench_function("String copy (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(STRING_COPY)))
});
}
static NUMBER_OBJECT_ACCESS: &str = include_str!("bench_scripts/number_object_access.js");
fn number_object_access(c: &mut Criterion) {
c.bench_function("Number Object Access (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(NUMBER_OBJECT_ACCESS)))
});
}
static BOOLEAN_OBJECT_ACCESS: &str = include_str!("bench_scripts/boolean_object_access.js");
fn boolean_object_access(c: &mut Criterion) {
c.bench_function("Boolean Object Access (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(BOOLEAN_OBJECT_ACCESS)))
});
}
static STRING_OBJECT_ACCESS: &str = include_str!("bench_scripts/string_object_access.js");
fn string_object_access(c: &mut Criterion) {
c.bench_function("String Object Access (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(STRING_OBJECT_ACCESS)))
});
}
static ARITHMETIC_OPERATIONS: &str = include_str!("bench_scripts/arithmetic_operations.js");
fn arithmetic_operations(c: &mut Criterion) {
c.bench_function("Arithmetic operations (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(ARITHMETIC_OPERATIONS)))
});
}
static CLEAN_JS: &str = include_str!("bench_scripts/clean_js.js");
fn clean_js(c: &mut Criterion) {
c.bench_function("Clean js (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(CLEAN_JS)))
});
}
static MINI_JS: &str = include_str!("bench_scripts/mini_js.js");
fn mini_js(c: &mut Criterion) {
c.bench_function("Mini js (Full)", move |b| {
b.iter(|| Context::new().eval(black_box(MINI_JS)))
});
}
fn create_realm(c: &mut Criterion) {
c.bench_function("Create Realm", move |b| b.iter(Realm::create));
}
macro_rules! full_benchmarks {
($({$id:literal, $name:ident}),*) => {
fn bench_parser(c: &mut Criterion) {
$(
{
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
c.bench_function(concat!($id, " (Parser)"), move |b| {
b.iter(|| parse(black_box(CODE), false))
});
}
)*
}
fn bench_compile(c: &mut Criterion) {
$(
{
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let statement_list = parse(CODE, false).unwrap();
c.bench_function(concat!($id, " (Compiler)"), move |b| {
b.iter(|| {
Context::compile(black_box(statement_list.clone()));
})
});
}
)*
}
fn bench_execution(c: &mut Criterion) {
$(
{
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let statement_list = parse(CODE, false).unwrap();
let code_block = Context::compile(statement_list);
let mut context = Context::new();
c.bench_function(concat!($id, " (Execution)"), move |b| {
b.iter(|| {
context.execute(black_box(code_block.clone())).unwrap();
})
});
}
)*
}
};
}
full_benchmarks!(
{"Symbols", symbol_creation},
{"For loop", for_loop},
{"Fibonacci", fibonacci},
{"Object Creation", object_creation},
{"Static Object Property Access", object_prop_access_const},
{"Dynamic Object Property Access", object_prop_access_dyn},
{"RegExp Literal Creation", regexp_literal_creation},
{"RegExp Creation", regexp_creation},
{"RegExp Literal", regexp_literal},
{"RegExp", regexp},
{"Array access", array_access},
{"Array creation", array_create},
{"Array pop", array_pop},
{"String concatenation", string_concat},
{"String comparison", string_compare},
{"String copy", string_copy},
{"Number Object Access", number_object_access},
{"Boolean Object Access", boolean_object_access},
{"String Object Access", string_object_access},
{"Arithmetic operations", arithmetic_operations},
{"Clean js", clean_js},
{"Mini js", mini_js}
);
criterion_group!(
full,
symbol_creation,
for_loop,
fibonacci,
array_access,
array_creation,
array_pop,
object_creation,
object_prop_access_const,
object_prop_access_dyn,
regexp_literal_creation,
regexp_creation,
regexp_literal,
regexp,
string_concat,
string_compare,
string_copy,
number_object_access,
boolean_object_access,
string_object_access,
arithmetic_operations,
clean_js,
mini_js,
benches,
create_realm,
bench_parser,
bench_compile,
bench_execution,
);
criterion_main!(full);
criterion_main!(benches);

99
boa/benches/parser.rs

@ -1,99 +0,0 @@
//! Benchmarks of the parsing process in Boa.
use boa::syntax::parser::Parser;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
#[cfg_attr(
all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"),
global_allocator
)]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
static EXPRESSION: &str = include_str!("bench_scripts/expression.js");
fn expression_parser(c: &mut Criterion) {
c.bench_function("Expression (Parser)", move |b| {
b.iter(|| Parser::new(black_box(EXPRESSION.as_bytes()), false).parse_all())
});
}
static HELLO_WORLD: &str = include_str!("bench_scripts/hello_world.js");
fn hello_world_parser(c: &mut Criterion) {
c.bench_function("Hello World (Parser)", move |b| {
b.iter(|| Parser::new(black_box(HELLO_WORLD.as_bytes()), false).parse_all())
});
}
static FOR_LOOP: &str = include_str!("bench_scripts/for_loop.js");
fn for_loop_parser(c: &mut Criterion) {
c.bench_function("For loop (Parser)", move |b| {
b.iter(|| Parser::new(black_box(FOR_LOOP.as_bytes()), false).parse_all())
});
}
static LONG_REPETITION: &str = include_str!("bench_scripts/long_repetition.js");
fn long_file_parser(c: &mut Criterion) {
use std::{
fs::{self, File},
io::{BufWriter, Write},
};
// We include the lexing in the benchmarks, since they will get together soon, anyways.
const FILE_NAME: &str = "long_file_test.js";
{
let mut file = BufWriter::new(
File::create(FILE_NAME).unwrap_or_else(|_| panic!("could not create {}", FILE_NAME)),
);
for _ in 0..400 {
file.write_all(LONG_REPETITION.as_bytes())
.unwrap_or_else(|_| panic!("could not write {}", FILE_NAME));
}
}
let file = std::fs::File::open(FILE_NAME).expect("Could not open file");
c.bench_function("Long file (Parser)", move |b| {
b.iter(|| Parser::new(black_box(&file), false).parse_all())
});
fs::remove_file(FILE_NAME).unwrap_or_else(|_| panic!("could not remove {}", FILE_NAME));
}
static GOAL_SYMBOL_SWITCH: &str = include_str!("bench_scripts/goal_symbol_switch.js");
fn goal_symbol_switch(c: &mut Criterion) {
c.bench_function("Goal Symbols (Parser)", move |b| {
b.iter(|| Parser::new(black_box(GOAL_SYMBOL_SWITCH.as_bytes()), false).parse_all())
});
}
static CLEAN_JS: &str = include_str!("bench_scripts/clean_js.js");
fn clean_js(c: &mut Criterion) {
c.bench_function("Clean js (Parser)", move |b| {
b.iter(|| Parser::new(black_box(CLEAN_JS.as_bytes()), false).parse_all())
});
}
static MINI_JS: &str = include_str!("bench_scripts/mini_js.js");
fn mini_js(c: &mut Criterion) {
c.bench_function("Mini js (Parser)", move |b| {
b.iter(|| Parser::new(black_box(MINI_JS.as_bytes()), false).parse_all())
});
}
criterion_group!(
parser,
expression_parser,
hello_world_parser,
for_loop_parser,
long_file_parser,
goal_symbol_switch,
clean_js,
mini_js,
);
criterion_main!(parser);

7
boa/src/builtins/console/mod.rs

@ -299,7 +299,6 @@ impl Console {
Ok(JsValue::undefined())
}
#[cfg(feature = "vm")]
fn get_stack_trace(context: &mut Context) -> Vec<String> {
let mut stack_trace: Vec<String> = vec![];
let mut prev_frame = context.vm.frame.as_ref();
@ -312,12 +311,6 @@ impl Console {
stack_trace
}
#[cfg(not(feature = "vm"))]
fn get_stack_trace(_: &mut Context) -> Vec<String> {
// TODO: Implement stack trace retrieval when "vm" feature is not available
vec![]
}
/// `console.trace(...data)`
///
/// Prints a stack trace with "trace" logLevel, optionally labelled by data.

121
boa/src/builtins/function/mod.rs

@ -30,8 +30,6 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, NativeObject, ObjectData},
property::Attribute,
property::PropertyDescriptor,
syntax::ast::node::declaration::Declaration,
syntax::ast::node::{FormalParameter, RcStatementList},
BoaProfiler, Context, JsResult, JsValue,
};
use crate::{object::Object, symbol::WellKnownSymbols};
@ -186,14 +184,6 @@ pub enum Function {
constructor: bool,
captures: Captures,
},
Ordinary {
constructor: bool,
this_mode: ThisMode,
body: RcStatementList,
params: Box<[FormalParameter]>,
environment: Environment,
},
#[cfg(feature = "vm")]
VmOrdinary {
code: Gc<crate::vm::CodeBlock>,
environment: Environment,
@ -207,72 +197,11 @@ impl fmt::Debug for Function {
}
impl Function {
// Adds the final rest parameters to the Environment as an array
#[cfg(not(feature = "vm"))]
pub(crate) fn add_rest_param(
param: &FormalParameter,
index: usize,
args_list: &[JsValue],
context: &mut Context,
local_env: &Environment,
) {
use crate::builtins::Array;
// Create array of values
let array = Array::new_array(context);
Array::add_to_array_object(&array, args_list.get(index..).unwrap_or_default(), context)
.unwrap();
let binding_params = param.run(Some(array), context).unwrap_or_default();
for binding_items in binding_params.iter() {
// Create binding
local_env
.create_mutable_binding(binding_items.0.as_ref(), false, true, context)
.expect("Failed to create binding");
// Set binding to value
local_env
.initialize_binding(
binding_items.0.as_ref(),
JsValue::new(binding_items.1.clone()),
context,
)
.expect("Failed to intialize binding");
}
}
// Adds an argument to the environment
#[cfg(not(feature = "vm"))]
pub(crate) fn add_arguments_to_environment(
param: &FormalParameter,
value: JsValue,
local_env: &Environment,
context: &mut Context,
) {
let binding_params = param.run(Some(value), context).unwrap_or_default();
for binding_items in binding_params.iter() {
// Create binding
local_env
.create_mutable_binding(binding_items.0.as_ref(), false, true, context)
.expect("Failed to create binding");
// Set binding to value
local_env
.initialize_binding(
binding_items.0.as_ref(),
JsValue::new(binding_items.1.clone()),
context,
)
.expect("Failed to intialize binding");
}
}
/// Returns true if the function object is a constructor.
pub fn is_constructor(&self) -> bool {
match self {
Self::Native { constructor, .. } => *constructor,
Self::Closure { constructor, .. } => *constructor,
Self::Ordinary { constructor, .. } => *constructor,
#[cfg(feature = "vm")]
Self::VmOrdinary { code, .. } => code.constructor,
}
}
@ -535,62 +464,12 @@ impl BuiltInFunctionObject {
},
Some(name),
) => Ok(format!("function {}() {{\n [native Code]\n}}", &name).into()),
(Function::Ordinary { body, params, .. }, Some(name)) => {
let arguments: String = {
let mut argument_list: Vec<Cow<'_, str>> = Vec::new();
for params_item in params.iter() {
let argument_item = match &params_item.declaration() {
Declaration::Identifier { ident, .. } => Cow::Borrowed(ident.as_ref()),
Declaration::Pattern(pattern) => {
Cow::Owned(format!("{{{}}}", pattern.idents().join(",")))
}
};
argument_list.push(argument_item);
}
argument_list.join(",")
};
let statement_list = &*body;
// This is a kluge. The implementaion in browser seems to suggest that
// the value here is printed exactly as defined in source. I'm not sure if
// that's possible here, but for now here's a dumb heuristic that prints functions
let is_multiline = {
let value = statement_list.to_string();
value.lines().count() > 1
};
if is_multiline {
Ok(
// ?? For some reason statement_list string implementation
// sticks a \n at the end no matter what
format!(
"{}({}) {{\n{}}}",
&name,
arguments,
statement_list.to_string()
)
.into(),
)
} else {
Ok(format!(
"{}({}) {{{}}}",
&name,
arguments,
// The trim here is to remove a \n stuck at the end
// of the statement_list to_string method
statement_list.to_string().trim()
)
.into())
}
}
#[cfg(feature = "vm")]
(Function::VmOrdinary { .. }, Some(name)) if name.is_empty() => {
Ok("[Function (anonymous)]".into())
}
#[cfg(feature = "vm")]
(Function::VmOrdinary { .. }, Some(name)) => {
Ok(format!("[Function: {}]", &name).into())
}
#[cfg(feature = "vm")]
(Function::VmOrdinary { .. }, None) => Ok("[Function (anonymous)]".into()),
_ => Ok("TODO".into()),
}

2
boa/src/builtins/iterable/mod.rs

@ -195,12 +195,10 @@ impl IteratorRecord {
}
}
#[cfg(feature = "vm")]
pub(crate) fn iterator_object(&self) -> &JsValue {
&self.iterator_object
}
#[cfg(feature = "vm")]
pub(crate) fn next_function(&self) -> &JsValue {
&self.next_function
}

2
boa/src/builtins/json/mod.rs

@ -96,8 +96,6 @@ impl Json {
// 9. Let unfiltered be completion.[[Value]].
// 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral.
let unfiltered = context.eval(script_string.as_bytes())?;
#[cfg(feature = "vm")]
context.vm.pop_frame();
// 11. If IsCallable(reviver) is true, then
if let Some(obj) = args.get_or_undefined(1).as_callable() {

241
boa/src/context.rs

@ -2,34 +2,23 @@
use crate::{
builtins::{
self,
function::{Function, NativeFunctionSignature, ThisMode},
intrinsics::IntrinsicObjects,
iterable::IteratorPrototypes,
typed_array::TypedArray,
self, function::NativeFunctionSignature, intrinsics::IntrinsicObjects,
iterable::IteratorPrototypes, typed_array::TypedArray,
},
bytecompiler::ByteCompiler,
class::{Class, ClassBuilder},
exec::Interpreter,
object::PROTOTYPE,
object::{FunctionBuilder, JsObject, ObjectData},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
syntax::{
ast::{
node::{statement_list::RcStatementList, FormalParameter, StatementList},
Node,
},
Parser,
},
BoaProfiler, Executable, JsResult, JsString, JsValue,
syntax::{ast::node::StatementList, Parser},
vm::{CallFrame, CodeBlock, FinallyReturn, Vm},
BoaProfiler, JsResult, JsString, JsValue,
};
use gc::Gc;
#[cfg(feature = "console")]
use crate::builtins::console::Console;
#[cfg(feature = "vm")]
use crate::vm::{FinallyReturn, Vm};
/// Store a builtin constructor (such as `Object`) and its corresponding prototype.
#[derive(Debug, Clone)]
pub struct StandardConstructor {
@ -326,14 +315,6 @@ impl StandardObjects {
}
}
/// Internal representation of the strict mode types.
#[derive(Debug, Copy, Clone)]
pub(crate) enum StrictType {
Off,
Global,
Function,
}
/// Javascript context. It is the primary way to interact with the runtime.
///
/// `Context`s constructed in a thread share the same runtime, therefore it
@ -380,9 +361,6 @@ pub struct Context {
/// realm holds both the global object and the environment
pub(crate) realm: Realm,
/// The current executor.
executor: Interpreter,
/// console object state.
#[cfg(feature = "console")]
console: Console,
@ -399,28 +377,24 @@ pub struct Context {
/// Cached intrinsic objects
intrinsic_objects: IntrinsicObjects,
/// Whether or not strict mode is active.
strict: StrictType,
/// Whether or not global strict mode is active.
strict: bool,
#[cfg(feature = "vm")]
pub(crate) vm: Vm,
}
impl Default for Context {
fn default() -> Self {
let realm = Realm::create();
let executor = Interpreter::new();
let mut context = Self {
realm,
executor,
#[cfg(feature = "console")]
console: Console::default(),
iterator_prototypes: IteratorPrototypes::default(),
typed_array_constructor: StandardConstructor::default(),
standard_objects: Default::default(),
intrinsic_objects: IntrinsicObjects::default(),
strict: StrictType::Off,
#[cfg(feature = "vm")]
strict: false,
vm: Vm {
frame: None,
stack: Vec::with_capacity(1024),
@ -454,12 +428,6 @@ impl Context {
pub fn new() -> Self {
Default::default()
}
#[inline]
pub fn executor(&mut self) -> &mut Interpreter {
&mut self.executor
}
/// A helper function for getting an immutable reference to the `console` object.
#[cfg(feature = "console")]
pub(crate) fn console(&self) -> &Console {
@ -476,33 +444,15 @@ impl Context {
/// Returns if strict mode is currently active.
#[inline]
pub fn strict(&self) -> bool {
matches!(self.strict, StrictType::Global | StrictType::Function)
}
/// Returns the strict mode type.
#[inline]
pub(crate) fn strict_type(&self) -> StrictType {
self.strict
}
/// Set strict type.
/// Set the global strict mode of the context.
#[inline]
pub(crate) fn set_strict(&mut self, strict: StrictType) {
pub fn set_strict_mode(&mut self, strict: bool) {
self.strict = strict;
}
/// Disable the strict mode.
#[inline]
pub fn set_strict_mode_off(&mut self) {
self.strict = StrictType::Off;
}
/// Enable the global strict mode.
#[inline]
pub fn set_strict_mode_global(&mut self) {
self.strict = StrictType::Global;
}
/// Sets up the default global objects within Global
#[inline]
fn create_intrinsics(&mut self) {
@ -720,75 +670,6 @@ impl Context {
Err(self.construct_uri_error(message))
}
/// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions
pub(crate) fn create_function<N, P>(
&mut self,
name: N,
params: P,
mut body: StatementList,
constructor: bool,
this_mode: ThisMode,
) -> JsResult<JsValue>
where
N: Into<JsString>,
P: Into<Box<[FormalParameter]>>,
{
let name = name.into();
let function_prototype = self.standard_objects().function_object().prototype();
// Every new function has a prototype property pre-made
let prototype = self.construct_object();
// If a function is defined within a strict context, it is strict.
if self.strict() {
body.set_strict(true);
}
let params = params.into();
let params_len = params.len();
let func = Function::Ordinary {
constructor,
this_mode,
body: RcStatementList::from(body),
params,
environment: self.get_current_environment().clone(),
};
let function =
JsObject::from_proto_and_data(function_prototype, ObjectData::function(func));
// Set constructor field to the newly created Value (function object)
let constructor = PropertyDescriptor::builder()
.value(function.clone())
.writable(true)
.enumerable(false)
.configurable(true);
prototype.define_property_or_throw("constructor", constructor, self)?;
let prototype = PropertyDescriptor::builder()
.value(prototype)
.writable(true)
.enumerable(false)
.configurable(false);
function.define_property_or_throw(PROTOTYPE, prototype, self)?;
let length = PropertyDescriptor::builder()
.value(params_len)
.writable(false)
.enumerable(false)
.configurable(true);
function.define_property_or_throw("length", length, self)?;
let name = PropertyDescriptor::builder()
.value(name)
.writable(false)
.enumerable(false)
.configurable(true);
function.define_property_or_throw("name", name, self)?;
Ok(function.into())
}
/// Register a global native function.
///
/// This is more efficient that creating a closure function, since this does not allocate,
@ -881,29 +762,6 @@ impl Context {
}
}
#[inline]
pub(crate) fn set_value(&mut self, node: &Node, value: JsValue) -> JsResult<JsValue> {
match node {
Node::Identifier(ref name) => {
self.set_mutable_binding(name.as_ref(), value.clone(), true)?;
Ok(value)
}
Node::GetConstField(ref get_const_field_node) => Ok(get_const_field_node
.obj()
.run(self)?
.set_field(get_const_field_node.field(), value, false, self)?),
Node::GetField(ref get_field) => {
let field = get_field.field().run(self)?;
let key = field.to_property_key(self)?;
Ok(get_field
.obj()
.run(self)?
.set_field(key, value, false, self)?)
}
_ => self.throw_type_error(format!("invalid assignment to {}", node)),
}
}
/// Register a global class of type `T`, where `T` implements `Class`.
///
/// # Example
@ -983,7 +841,7 @@ impl Context {
);
}
/// Evaluates the given code.
/// Evaluates the given code by compiling down to bytecode, then interpreting the bytecode into a value
///
/// # Examples
/// ```
@ -995,9 +853,7 @@ impl Context {
/// assert!(value.is_number());
/// assert_eq!(value.as_number().unwrap(), 4.0);
/// ```
#[cfg(not(feature = "vm"))]
#[allow(clippy::unit_arg, clippy::drop_copy)]
#[inline]
pub fn eval<T: AsRef<[u8]>>(&mut self, src: T) -> JsResult<JsValue> {
let main_timer = BoaProfiler::global().start_event("Main", "Main");
let src_bytes: &[u8] = src.as_ref();
@ -1006,61 +862,34 @@ impl Context {
.parse_all()
.map_err(|e| e.to_string());
let execution_result = match parsing_result {
Ok(statement_list) => {
if statement_list.strict() {
self.set_strict_mode_global();
}
statement_list.run(self)
}
Err(e) => self.throw_syntax_error(e),
let statement_list = match parsing_result {
Ok(statement_list) => statement_list,
Err(e) => return self.throw_syntax_error(e),
};
let code_block = Context::compile(statement_list);
let result = self.execute(code_block);
// The main_timer needs to be dropped before the BoaProfiler is.
drop(main_timer);
BoaProfiler::global().drop();
execution_result
result
}
/// Evaluates the given code by compiling down to bytecode, then interpreting the bytecode into a value
///
/// # Examples
/// ```
///# use boa::Context;
/// let mut context = Context::new();
///
/// let value = context.eval("1 + 3").unwrap();
///
/// assert!(value.is_number());
/// assert_eq!(value.as_number().unwrap(), 4.0);
/// ```
#[cfg(feature = "vm")]
#[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval<T: AsRef<[u8]>>(&mut self, src: T) -> JsResult<JsValue> {
use gc::Gc;
use crate::vm::CallFrame;
let main_timer = BoaProfiler::global().start_event("Main", "Main");
let src_bytes: &[u8] = src.as_ref();
let parsing_result = Parser::new(src_bytes, false)
.parse_all()
.map_err(|e| e.to_string());
let statement_list = match parsing_result {
Ok(statement_list) => statement_list,
Err(e) => return self.throw_syntax_error(e),
};
let mut compiler = crate::bytecompiler::ByteCompiler::new(
JsString::new("<main>"),
statement_list.strict(),
);
// Compile the AST into a CodeBlock ready to execute by the VM
#[inline]
pub fn compile(statement_list: StatementList) -> CodeBlock {
let _ = BoaProfiler::global().start_event("Compilation", "Main");
let mut compiler = ByteCompiler::new(JsString::new("<main>"), statement_list.strict());
compiler.compile_statement_list(&statement_list, true);
let code_block = compiler.finish();
compiler.finish()
}
// Call the VM with the codeblock and return the result
#[inline]
pub fn execute(&mut self, code_block: CodeBlock) -> JsResult<JsValue> {
let _ = BoaProfiler::global().start_event("Execute", "Main");
let global_object = self.global_object().into();
self.vm.push_frame(CallFrame {
@ -1076,13 +905,8 @@ impl Context {
param_count: 0,
arg_count: 0,
});
let result = self.run();
// The main_timer needs to be dropped before the BoaProfiler is.
drop(main_timer);
BoaProfiler::global().drop();
result
self.run()
}
/// Return the cached iterator prototypes.
@ -1110,7 +934,6 @@ impl Context {
}
/// Set the value of trace on the context
#[cfg(feature = "vm")]
pub fn set_trace(&mut self, trace: bool) {
self.vm.trace = trace;
}

1
boa/src/environment/lexical_environment.rs

@ -93,7 +93,6 @@ impl Context {
.recursive_get_this_binding(self)
}
#[cfg(feature = "vm")]
pub(crate) fn get_global_this_binding(&mut self) -> JsResult<JsValue> {
let global = self.realm.global_env.clone();
global.get_this_binding(self)

51
boa/src/exec/mod.rs

@ -1,51 +0,0 @@
//! Execution of the AST, this is where the interpreter actually runs
#[cfg(test)]
mod tests;
use crate::{Context, JsResult, JsValue};
pub trait Executable {
/// Runs this executable in the given context.
fn run(&self, context: &mut Context) -> JsResult<JsValue>;
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum InterpreterState {
Executing,
Return,
Break(Option<Box<str>>),
Continue(Option<Box<str>>),
}
/// A Javascript intepreter
#[derive(Debug)]
pub struct Interpreter {
/// the current state of the interpreter.
state: InterpreterState,
}
impl Default for Interpreter {
fn default() -> Self {
Self::new()
}
}
impl Interpreter {
/// Creates a new interpreter.
pub fn new() -> Self {
Self {
state: InterpreterState::Executing,
}
}
#[inline]
pub(crate) fn set_current_state(&mut self, new_state: InterpreterState) {
self.state = new_state
}
#[inline]
pub(crate) fn get_current_state(&self) -> &InterpreterState {
&self.state
}
}

25
boa/src/lib.rs

@ -43,10 +43,10 @@ This is an experimental Javascript lexer, parser and compiler written in Rust. C
pub mod bigint;
pub mod builtins;
pub mod bytecompiler;
pub mod class;
pub mod context;
pub mod environment;
pub mod exec;
pub mod gc;
pub mod object;
pub mod profiler;
@ -56,12 +56,11 @@ pub mod string;
pub mod symbol;
pub mod syntax;
pub mod value;
#[cfg(feature = "vm")]
pub mod bytecompiler;
#[cfg(feature = "vm")]
pub mod vm;
#[cfg(test)]
mod tests;
/// A convenience module that re-exports the most commonly-used Boa APIs
pub mod prelude {
pub use crate::{object::JsObject, Context, JsBigInt, JsResult, JsString, JsValue};
@ -69,7 +68,7 @@ pub mod prelude {
use std::result::Result as StdResult;
pub(crate) use crate::{exec::Executable, profiler::BoaProfiler};
pub(crate) use crate::profiler::BoaProfiler;
// Export things to root level
#[doc(inline)]
@ -99,18 +98,13 @@ pub fn parse<T: AsRef<[u8]>>(src: T, strict_mode: bool) -> StdResult<StatementLi
/// Execute the code using an existing Context
/// The str is consumed and the state of the Context is changed
#[cfg(test)]
#[cfg_attr(not(feature = "vm"), allow(clippy::let_and_return))]
#[allow(clippy::let_and_return)]
pub(crate) fn forward<T: AsRef<[u8]>>(context: &mut Context, src: T) -> String {
let src_bytes: &[u8] = src.as_ref();
let result = context.eval(src_bytes).map_or_else(
context.eval(src_bytes).map_or_else(
|e| format!("Uncaught {}", e.display()),
|v| v.display().to_string(),
);
#[cfg(feature = "vm")]
context.vm.pop_frame();
result
)
}
/// Execute the code using an existing Context.
@ -125,9 +119,6 @@ pub(crate) fn forward_val<T: AsRef<[u8]>>(context: &mut Context, src: T) -> JsRe
let src_bytes: &[u8] = src.as_ref();
let result = context.eval(src_bytes);
#[cfg(feature = "vm")]
context.vm.pop_frame();
// The main_timer needs to be dropped before the BoaProfiler is.
drop(main_timer);
BoaProfiler::global().drop();

278
boa/src/object/internal_methods/function.rs

@ -6,21 +6,6 @@ use crate::{
Context, JsResult, JsValue,
};
#[cfg(not(feature = "vm"))]
use crate::{
builtins::function::{
arguments::Arguments, Captures, ClosureFunctionSignature, Function, NativeFunctionSignature,
},
context::StandardObjects,
environment::{
function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::Environment,
},
exec::{Executable, InterpreterState},
object::{internal_methods::get_prototype_from_constructor, ObjectData},
syntax::ast::node::RcStatementList,
};
/// Definitions of the internal object methods for function objects.
///
/// More information:
@ -54,10 +39,7 @@ fn function_call(
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
#[cfg(not(feature = "vm"))]
return call_construct(obj, this, args, context, false);
#[cfg(feature = "vm")]
return obj.call_internal(this, args, context);
obj.call_internal(this, args, context)
}
/// Construct an instance of this object with the specified arguments.
@ -74,261 +56,5 @@ fn function_construct(
new_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
#[cfg(not(feature = "vm"))]
return call_construct(obj, new_target, args, context, true);
#[cfg(feature = "vm")]
return obj.construct_internal(args, new_target, context);
}
/// Internal implementation of [`call`](#method.call) and [`construct`](#method.construct).
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
///
/// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
/// <https://tc39.es/ecma262/#sec-ordinarycallbindthis>
/// <https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody>
/// <https://tc39.es/ecma262/#sec-ordinarycallevaluatebody>
#[track_caller]
#[cfg(not(feature = "vm"))]
pub(super) fn call_construct(
obj: &JsObject,
this_target: &JsValue,
args: &[JsValue],
context: &mut Context,
construct: bool,
) -> JsResult<JsValue> {
/// The body of a JavaScript function.
///
/// This is needed for the call method since we cannot mutate the function itself since we
/// already borrow it so we get the function body clone it then drop the borrow and run the body
enum FunctionBody {
BuiltInFunction(NativeFunctionSignature),
BuiltInConstructor(NativeFunctionSignature),
Closure {
function: Box<dyn ClosureFunctionSignature>,
captures: Captures,
},
Ordinary(RcStatementList),
}
let this_function_object = obj.clone();
let mut has_parameter_expressions = false;
let body = if let Some(function) = obj.borrow().as_function() {
if construct && !function.is_constructor() {
let name = obj.get("name", context)?.display().to_string();
return context.throw_type_error(format!("{} is not a constructor", name));
} else {
match function {
Function::Native {
function,
constructor,
} => {
if *constructor || construct {
FunctionBody::BuiltInConstructor(*function)
} else {
FunctionBody::BuiltInFunction(*function)
}
}
Function::Closure {
function, captures, ..
} => FunctionBody::Closure {
function: function.clone(),
captures: captures.clone(),
},
Function::Ordinary {
constructor: _,
this_mode,
body,
params,
environment,
} => {
let this = if construct {
// If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let proto = get_prototype_from_constructor(
this_target,
StandardObjects::object_object,
context,
)?;
JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary()).into()
} else {
this_target.clone()
};
// Create a new Function environment whose parent is set to the scope of the function declaration (obj.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = FunctionEnvironmentRecord::new(
this_function_object.clone(),
if construct || !this_mode.is_lexical() {
Some(this.clone())
} else {
None
},
Some(environment.clone()),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if this_mode.is_lexical() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
JsValue::undefined(),
context,
)?;
let mut arguments_in_parameter_names = false;
let mut is_simple_parameter_list = true;
for param in params.iter() {
has_parameter_expressions =
has_parameter_expressions || param.init().is_some();
for param_name in param.names() {
arguments_in_parameter_names =
arguments_in_parameter_names || param_name == "arguments";
}
is_simple_parameter_list = is_simple_parameter_list
&& !param.is_rest_param()
&& param.is_identifier()
&& param.init().is_none()
}
// Turn local_env into Environment so it can be cloned
let local_env: Environment = local_env.into();
// An arguments object is added when all of the following conditions are met
// - If not in an arrow function (10.2.11.16)
// - If the parameter list does not contain `arguments` (10.2.11.17)
// - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18)
//
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
if !this_mode.is_lexical()
&& !arguments_in_parameter_names
&& (has_parameter_expressions
|| (!body.lexically_declared_names().contains("arguments")
&& !body.function_declared_names().contains("arguments")))
{
// Add arguments object
let arguments_obj =
if context.strict() || body.strict() || !is_simple_parameter_list {
Arguments::create_unmapped_arguments_object(args, context)
} else {
Arguments::create_mapped_arguments_object(
obj, params, args, &local_env, context,
)
};
local_env.create_mutable_binding("arguments", false, true, context)?;
local_env.initialize_binding("arguments", arguments_obj.into(), context)?;
}
// Push the environment first so that it will be used by default parameters
context.push_environment(local_env.clone());
// Add argument bindings to the function environment
for (i, param) in params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
Function::add_rest_param(param, i, args, context, &local_env);
break;
}
let value = match args.get(i).cloned() {
None | Some(JsValue::Undefined) => param
.init()
.map(|init| init.run(context).ok())
.flatten()
.unwrap_or_default(),
Some(value) => value,
};
Function::add_arguments_to_environment(param, value, &local_env, context);
}
if has_parameter_expressions {
// Create a second environment when default parameter expressions are used
// This prevents variables declared in the function body from being
// used in default parameter initializers.
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
let second_env = FunctionEnvironmentRecord::new(
this_function_object,
if construct || !this_mode.is_lexical() {
Some(this)
} else {
None
},
Some(local_env),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if this_mode.is_lexical() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
JsValue::undefined(),
context,
)?;
context.push_environment(second_env);
}
FunctionBody::Ordinary(body.clone())
}
#[cfg(feature = "vm")]
Function::VmOrdinary { .. } => {
todo!("vm call")
}
}
}
} else {
return context.throw_type_error("not a function");
};
match body {
FunctionBody::BuiltInConstructor(function) if construct => {
function(this_target, args, context)
}
FunctionBody::BuiltInConstructor(function) => {
function(&JsValue::undefined(), args, context)
}
FunctionBody::BuiltInFunction(function) => function(this_target, args, context),
FunctionBody::Closure { function, captures } => {
(function)(this_target, args, captures, context)
}
FunctionBody::Ordinary(body) => {
let result = body.run(context);
let this = context.get_this_binding();
if has_parameter_expressions {
context.pop_environment();
}
context.pop_environment();
if construct {
// https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
// 12. If result.[[Type]] is return, then
if context.executor().get_current_state() == &InterpreterState::Return {
// a. If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]).
if let Ok(v) = &result {
if v.is_object() {
return result;
}
}
}
// 13. Else, ReturnIfAbrupt(result).
result?;
// 14. Return ? constructorEnv.GetThisBinding().
this
} else if context.executor().get_current_state() == &InterpreterState::Return {
result
} else {
result?;
Ok(JsValue::undefined())
}
}
}
obj.construct_internal(args, new_target, context)
}

38
boa/src/syntax/ast/node/array/mod.rs

@ -1,12 +1,7 @@
//! Array declaration node.
use super::{join_nodes, Node};
use crate::{
builtins::Array,
exec::Executable,
gc::{Finalize, Trace},
BoaProfiler, Context, JsResult, JsValue,
};
use crate::gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "deser")]
@ -38,37 +33,6 @@ pub struct ArrayDecl {
arr: Box<[Node]>,
}
impl Executable for ArrayDecl {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("ArrayDecl", "exec");
let array = Array::new_array(context);
let mut elements = Vec::new();
for elem in self.as_ref() {
if let Node::Spread(ref x) = elem {
let val = x.run(context)?;
let iterator_record = val.get_iterator(context, None, None)?;
// TODO after proper internal Array representation as per https://github.com/boa-dev/boa/pull/811#discussion_r502460858
// next_index variable should be utilized here as per https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation
// let mut next_index = 0;
loop {
let next = iterator_record.next(context)?;
if next.done {
break;
}
let next_value = next.value;
//next_index += 1;
elements.push(next_value);
}
} else {
elements.push(elem.run(context)?);
}
}
Array::add_to_array_object(&array, &elements, context)?;
Ok(array)
}
}
impl AsRef<[Node]> for ArrayDecl {
fn as_ref(&self) -> &[Node] {
&self.arr

9
boa/src/syntax/ast/node/await_expr/mod.rs

@ -1,7 +1,6 @@
//! Await expression node.
use super::Node;
use crate::{exec::Executable, BoaProfiler, Context, JsResult, JsValue};
use gc::{Finalize, Trace};
use std::fmt;
@ -26,14 +25,6 @@ pub struct AwaitExpr {
expr: Box<Node>,
}
impl Executable for AwaitExpr {
fn run(&self, _: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("AwaitExpression", "exec");
// TODO: Implement AwaitExpr
Ok(JsValue::undefined())
}
}
impl<T> From<T> for AwaitExpr
where
T: Into<Box<Node>>,

55
boa/src/syntax/ast/node/block/mod.rs

@ -1,13 +1,7 @@
//! Block AST node.
use super::{Node, StatementList};
use crate::{
environment::declarative_environment_record::DeclarativeEnvironmentRecord,
exec::Executable,
exec::InterpreterState,
gc::{Finalize, Trace},
BoaProfiler, Context, JsResult, JsValue,
};
use crate::gc::{Finalize, Trace};
use std::{collections::HashSet, fmt};
#[cfg(feature = "deser")]
@ -61,53 +55,6 @@ impl Block {
}
}
impl Executable for Block {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("Block", "exec");
{
let env = context.get_current_environment();
context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
}
// https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation
// The return value is uninitialized, which means it defaults to Value::Undefined
let mut obj = JsValue::default();
for statement in self.items() {
obj = statement.run(context).map_err(|e| {
// No matter how control leaves the Block the LexicalEnvironment is always
// restored to its former state.
context.pop_environment();
e
})?;
match context.executor().get_current_state() {
InterpreterState::Return => {
// Early return.
break;
}
InterpreterState::Break(_label) => {
// TODO, break to a label.
// Early break.
break;
}
InterpreterState::Continue(_label) => {
// TODO, continue to a label
break;
}
InterpreterState::Executing => {
// Continue execution
}
}
}
// pop the block env
let _ = context.pop_environment();
Ok(obj)
}
}
impl<T> From<T> for Block
where
T: Into<StatementList>,

17
boa/src/syntax/ast/node/break_node/mod.rs

@ -1,10 +1,5 @@
use super::Node;
use crate::{
exec::Executable,
exec::InterpreterState,
gc::{Finalize, Trace},
Context, JsResult, JsValue,
};
use crate::gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "deser")]
@ -51,16 +46,6 @@ impl Break {
}
}
impl Executable for Break {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
context
.executor()
.set_current_state(InterpreterState::Break(self.label().map(Box::from)));
Ok(JsValue::undefined())
}
}
impl fmt::Display for Break {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(

20
boa/src/syntax/ast/node/break_node/tests.rs

@ -1,23 +1,3 @@
use crate::{
exec::{Executable, InterpreterState},
syntax::ast::node::Break,
Context,
};
#[test]
fn check_post_state() {
let mut context = Context::new();
let brk: Break = Break::new("label");
brk.run(&mut context).unwrap();
assert_eq!(
context.executor().get_current_state(),
&InterpreterState::Break(Some("label".into()))
);
}
#[test]
fn fmt() {
// Blocks do not store their label, so we cannot test with

65
boa/src/syntax/ast/node/call/mod.rs

@ -1,9 +1,6 @@
use crate::{
exec::Executable,
exec::InterpreterState,
gc::{Finalize, Trace},
syntax::ast::node::{join_nodes, Node},
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
@ -58,68 +55,6 @@ impl Call {
}
}
impl Executable for Call {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("Call", "exec");
let (this, func) = match self.expr() {
Node::GetConstField(ref get_const_field) => {
let mut obj = get_const_field.obj().run(context)?;
if !obj.is_object() {
obj = JsValue::from(obj.to_object(context)?);
}
(
obj.clone(),
obj.get_field(get_const_field.field(), context)?,
)
}
Node::GetField(ref get_field) => {
let mut obj = get_field.obj().run(context)?;
if !obj.is_object() {
obj = JsValue::from(obj.to_object(context)?);
}
let field = get_field.field().run(context)?;
(
obj.clone(),
obj.get_field(field.to_property_key(context)?, context)?,
)
}
_ => (
// 'this' binding should come from the function's self-contained environment
context.global_object().into(),
self.expr().run(context)?,
),
};
let mut v_args = Vec::with_capacity(self.args().len());
for arg in self.args() {
if let Node::Spread(ref x) = arg {
let val = x.run(context)?;
let iterator_record = val.get_iterator(context, None, None)?;
loop {
let next = iterator_record.next(context)?;
if next.done {
break;
}
let next_value = next.value;
v_args.push(next_value);
}
break; // after spread we don't accept any new arguments
} else {
v_args.push(arg.run(context)?);
}
}
// execute the function call itself
let fnct_result = context.call(&func, &this, &v_args);
// unset the early return flag
context
.executor()
.set_current_state(InterpreterState::Executing);
fnct_result
}
}
impl fmt::Display for Call {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(", self.expr)?;

12
boa/src/syntax/ast/node/conditional/conditional_op/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -59,16 +57,6 @@ impl ConditionalOp {
}
}
impl Executable for ConditionalOp {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
Ok(if self.cond().run(context)?.to_boolean() {
self.if_true().run(context)?
} else {
self.if_false().run(context)?
})
}
}
impl fmt::Display for ConditionalOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(

14
boa/src/syntax/ast/node/conditional/if_node/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -78,18 +76,6 @@ impl If {
}
}
impl Executable for If {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
Ok(if self.cond().run(context)?.to_boolean() {
self.body().run(context)?
} else if let Some(else_e) = self.else_node() {
else_e.run(context)?
} else {
JsValue::undefined()
})
}
}
impl fmt::Display for If {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

15
boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs

@ -1,9 +1,6 @@
use crate::{
builtins::function::ThisMode,
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
Context, JsResult, JsValue,
};
use std::fmt;
@ -71,18 +68,6 @@ impl ArrowFunctionDecl {
}
}
impl Executable for ArrowFunctionDecl {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
context.create_function(
"",
self.params().to_vec(),
self.body().clone(),
false,
ThisMode::Lexical,
)
}
}
impl fmt::Display for ArrowFunctionDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

14
boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs

@ -1,10 +1,6 @@
//! Async Function Declaration.
use crate::{
exec::Executable,
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
BoaProfiler, Context, JsResult, JsValue,
};
use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList};
use gc::{Finalize, Trace};
use std::fmt;
@ -75,14 +71,6 @@ impl AsyncFunctionDecl {
}
}
impl Executable for AsyncFunctionDecl {
fn run(&self, _: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("AsyncFunctionDecl", "exec");
// TODO: Implement AsyncFunctionDecl
Ok(JsValue::undefined())
}
}
impl From<AsyncFunctionDecl> for Node {
fn from(decl: AsyncFunctionDecl) -> Self {
Self::AsyncFunctionDecl(decl)

13
boa/src/syntax/ast/node/declaration/async_function_expr/mod.rs

@ -1,10 +1,6 @@
//! Async Function Expression.
use crate::{
exec::Executable,
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
Context, JsResult, JsValue,
};
use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList};
use gc::{Finalize, Trace};
use std::fmt;
@ -80,13 +76,6 @@ impl AsyncFunctionExpr {
}
}
impl Executable for AsyncFunctionExpr {
fn run(&self, _: &mut Context) -> JsResult<JsValue> {
// TODO: Implement AsyncFunctionExpr
Ok(JsValue::undefined())
}
}
impl fmt::Display for AsyncFunctionExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

14
boa/src/syntax/ast/node/declaration/async_generator_decl/mod.rs

@ -1,10 +1,6 @@
//! Async Generator Declaration
use crate::{
exec::Executable,
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
BoaProfiler, Context, JsResult, JsValue,
};
use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList};
use gc::{Finalize, Trace};
use std::fmt;
@ -73,14 +69,6 @@ impl AsyncGeneratorDecl {
}
}
impl Executable for AsyncGeneratorDecl {
fn run(&self, _: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("AsyncGeneratorDecl", "exec");
//TODO: Implement AsyncGeneratorDecl
Ok(JsValue::undefined())
}
}
impl From<AsyncGeneratorDecl> for Node {
fn from(decl: AsyncGeneratorDecl) -> Self {
Self::AsyncGeneratorDecl(decl)

9
boa/src/syntax/ast/node/declaration/async_generator_expr/mod.rs

@ -1,10 +1,8 @@
//! Async Generator Expression
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
Context, JsResult, JsValue,
};
use std::fmt;
@ -85,13 +83,6 @@ impl AsyncGeneratorExpr {
}
}
impl Executable for AsyncGeneratorExpr {
fn run(&self, _context: &mut Context) -> JsResult<JsValue> {
//TODO: Implement AsyncGeneratorFunction
Ok(JsValue::undefined())
}
}
impl fmt::Display for AsyncGeneratorExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

26
boa/src/syntax/ast/node/declaration/function_decl/mod.rs

@ -1,10 +1,6 @@
use crate::{
builtins::function::ThisMode,
environment::lexical_environment::VariableScope,
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
@ -85,28 +81,6 @@ impl FunctionDecl {
}
}
impl Executable for FunctionDecl {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("FunctionDecl", "exec");
let val = context.create_function(
self.name(),
self.parameters().to_vec(),
self.body().clone(),
true,
ThisMode::Global,
)?;
if context.has_binding(self.name())? {
context.set_mutable_binding(self.name(), val, context.strict())?;
} else {
context.create_mutable_binding(self.name(), false, VariableScope::Function)?;
context.initialize_binding(self.name(), val)?;
}
Ok(JsValue::undefined())
}
}
impl From<FunctionDecl> for Node {
fn from(decl: FunctionDecl) -> Self {
Self::FunctionDecl(decl)

17
boa/src/syntax/ast/node/declaration/function_expr/mod.rs

@ -1,9 +1,6 @@
use crate::{
builtins::function::ThisMode,
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
Context, JsResult, JsValue,
};
use std::fmt;
@ -97,20 +94,6 @@ impl FunctionExpr {
}
}
impl Executable for FunctionExpr {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let val = context.create_function(
self.name().unwrap_or(""),
self.parameters().to_vec(),
self.body().clone(),
true,
ThisMode::Global,
)?;
Ok(val)
}
}
impl fmt::Display for FunctionExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

11
boa/src/syntax/ast/node/declaration/generator_decl/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
@ -74,15 +72,6 @@ impl GeneratorDecl {
}
}
impl Executable for GeneratorDecl {
fn run(&self, _context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("GeneratorDecl", "exec");
// TODO: Implement GeneratorFunction
// https://tc39.es/ecma262/#sec-generatorfunction-objects
Ok(JsValue::undefined())
}
}
impl From<GeneratorDecl> for Node {
fn from(decl: GeneratorDecl) -> Self {
Self::GeneratorDecl(decl)

10
boa/src/syntax/ast/node/declaration/generator_expr/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
Context, JsResult, JsValue,
};
use std::fmt;
@ -88,14 +86,6 @@ impl GeneratorExpr {
}
}
impl Executable for GeneratorExpr {
fn run(&self, _context: &mut Context) -> JsResult<JsValue> {
// TODO: Implement GeneratorFunction
// https://tc39.es/ecma262/#sec-generatorfunction-objects
Ok(JsValue::undefined())
}
}
impl fmt::Display for GeneratorExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

430
boa/src/syntax/ast/node/declaration/mod.rs

@ -1,11 +1,7 @@
//! Declaration nodes
use crate::{
builtins::Array,
environment::lexical_environment::VariableScope,
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::{join_nodes, Identifier, Node},
Context, JsResult, JsValue,
};
use std::fmt;
@ -95,101 +91,6 @@ pub enum DeclarationList {
Var(Box<[Declaration]>),
}
impl Executable for DeclarationList {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
for decl in self.as_ref() {
use DeclarationList::*;
let val = match decl.init() {
None if self.is_const() => {
return context.throw_syntax_error("missing = in const declaration")
}
Some(init) => init.run(context)?,
None => JsValue::undefined(),
};
match &decl {
Declaration::Identifier { ident, init } => {
if self.is_var() && context.has_binding(ident.as_ref())? {
if init.is_some() {
context.set_mutable_binding(ident.as_ref(), val, context.strict())?;
}
continue;
}
match &self {
Const(_) => context.create_immutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?,
Let(_) => context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?,
Var(_) => context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Function,
)?,
}
context.initialize_binding(ident.as_ref(), val)?;
}
Declaration::Pattern(p) => {
for (ident, value) in p.run(None, context)? {
if self.is_var() && context.has_binding(ident.as_ref())? {
if !value.is_undefined() {
context.set_mutable_binding(
ident.as_ref(),
value,
context.strict(),
)?;
}
continue;
}
match &self {
Const(_) => context.create_immutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?,
Let(_) => context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?,
Var(_) => context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Function,
)?,
}
context.initialize_binding(ident.as_ref(), value)?;
}
}
}
}
Ok(JsValue::undefined())
}
}
impl DeclarationList {
#[allow(dead_code)]
pub(in crate::syntax) fn is_let(&self) -> bool {
matches!(self, Self::Let(_))
}
pub(in crate::syntax) fn is_const(&self) -> bool {
matches!(self, Self::Const(_))
}
pub(in crate::syntax) fn is_var(&self) -> bool {
matches!(self, Self::Var(_))
}
}
impl AsRef<[Declaration]> for DeclarationList {
fn as_ref(&self) -> &[Declaration] {
use DeclarationList::*;
@ -355,22 +256,6 @@ impl fmt::Display for DeclarationPattern {
}
impl DeclarationPattern {
/// Initialize the values of an object/array binding pattern.
///
/// This function only calls the specific initialization function for either the object or the array binding pattern.
/// For specific documentation and references to the ECMAScript spec, look at the called initialization functions.
#[inline]
pub(in crate::syntax) fn run(
&self,
init: Option<JsValue>,
context: &mut Context,
) -> JsResult<Vec<(Box<str>, JsValue)>> {
match &self {
DeclarationPattern::Object(pattern) => pattern.run(init, context),
DeclarationPattern::Array(pattern) => pattern.run(init, context),
}
}
/// Gets the list of identifiers declared by the binding pattern.
///
/// A single binding pattern may declare 0 to n identifiers.
@ -443,128 +328,10 @@ impl DeclarationPatternObject {
/// Gets the bindings for the object binding pattern.
#[inline]
#[cfg(feature = "vm")]
pub(crate) fn bindings(&self) -> &Vec<BindingPatternTypeObject> {
&self.bindings
}
/// Initialize the values of an object binding pattern.
///
/// More information:
/// - [ECMAScript reference: 8.5.2 Runtime Semantics: BindingInitialization][spec1]
/// - [ECMAScript reference:14.3.3.3 Runtime Semantics: KeyedBindingInitialization][spec2]
/// - [ECMAScript reference:14.3.3.2 Runtime Semantics: RestBindingInitialization][spec3]
///
/// [spec1]: https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization
/// [spec2]: https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization
/// [spec3]: https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-restbindinginitialization
pub(in crate::syntax) fn run(
&self,
init: Option<JsValue>,
context: &mut Context,
) -> JsResult<Vec<(Box<str>, JsValue)>> {
let value = if let Some(value) = init {
value
} else if let Some(node) = &self.init {
node.run(context)?
} else {
JsValue::undefined()
};
if value.is_null() {
return context.throw_type_error("Cannot destructure 'null' value");
}
if value.is_undefined() {
return context.throw_type_error("Cannot destructure 'undefined' value");
}
// 1. Perform ? RequireObjectCoercible(value).
let value = value.require_object_coercible(context)?;
let mut results = Vec::new();
// 2. Return the result of performing BindingInitialization for ObjectBindingPattern using value and environment as arguments.
for binding in &self.bindings {
use BindingPatternTypeObject::*;
match binding {
// ObjectBindingPattern : { }
Empty => {
// 1. Return NormalCompletion(empty).
}
// SingleNameBinding : BindingIdentifier Initializer[opt]
SingleName {
ident,
property_name,
default_init,
} => {
// 1. Let bindingId be StringValue of BindingIdentifier.
// 2. Let lhs be ? ResolveBinding(bindingId, environment).
// 3. Let v be ? GetV(value, propertyName).
let mut v = value.get_field(property_name.as_ref(), context)?;
// 4. If Initializer is present and v is undefined, then
if let Some(init) = default_init {
if v.is_undefined() {
// TODO: a. not implemented yet:
// a. If IsAnonymousFunctionDefinition(Initializer) is true, then
// i. Set v to the result of performing NamedEvaluation for Initializer with argument bindingId.
// b. Else,
// i. Let defaultValue be the result of evaluating Initializer.
// ii. Set v to ? GetValue(defaultValue).
v = init.run(context)?;
}
}
// 5. If environment is undefined, return ? PutValue(lhs, v).
// 6. Return InitializeReferencedBinding(lhs, v).
results.push((ident.clone(), v));
}
// BindingRestProperty : ... BindingIdentifier
RestProperty {
ident,
excluded_keys,
} => {
// 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment).
// 2. Let restObj be ! OrdinaryObjectCreate(%Object.prototype%).
let rest_obj = context.construct_object();
// 3. Perform ? CopyDataProperties(restObj, value, excludedNames).
rest_obj.copy_data_properties(value, excluded_keys.clone(), context)?;
// 4. If environment is undefined, return PutValue(lhs, restObj).
// 5. Return InitializeReferencedBinding(lhs, restObj).
results.push((ident.clone(), rest_obj.into()));
}
// BindingElement : BindingPattern Initializer[opt]
BindingPattern {
ident,
pattern,
default_init,
} => {
// 1. Let v be ? GetV(value, propertyName).
let mut v = value.get_field(ident.as_ref(), context)?;
// 2. If Initializer is present and v is undefined, then
if let Some(init) = default_init {
if v.is_undefined() {
// a. Let defaultValue be the result of evaluating Initializer.
// b. Set v to ? GetValue(defaultValue).
v = init.run(context)?;
}
}
// 3. Return the result of performing BindingInitialization for BindingPattern passing v and environment as arguments.
results.append(&mut pattern.run(Some(v), context)?);
}
}
}
Ok(results)
}
/// Gets the list of identifiers declared by the object binding pattern.
#[inline]
pub(crate) fn idents(&self) -> Vec<&str> {
@ -658,207 +425,10 @@ impl DeclarationPatternArray {
/// Gets the bindings for the array binding pattern.
#[inline]
#[cfg(feature = "vm")]
pub(crate) fn bindings(&self) -> &Vec<BindingPatternTypeArray> {
&self.bindings
}
/// Initialize the values of an array binding pattern.
///
/// More information:
/// - [ECMAScript reference: 8.5.2 Runtime Semantics: BindingInitialization][spec1]
/// - [ECMAScript reference: 8.5.3 Runtime Semantics: IteratorBindingInitialization][spec2]
///
/// [spec1]: https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization
/// [spec2]: https://tc39.es/ecma262/#sec-runtime-semantics-iteratorbindinginitialization
pub(in crate::syntax) fn run(
&self,
init: Option<JsValue>,
context: &mut Context,
) -> JsResult<Vec<(Box<str>, JsValue)>> {
let value = if let Some(value) = init {
value
} else if let Some(node) = &self.init {
node.run(context)?
} else {
JsValue::undefined()
};
if value.is_null() {
return context.throw_type_error("Cannot destructure 'null' value");
}
if value.is_undefined() {
return context.throw_type_error("Cannot destructure 'undefined' value");
}
// 1. Let iteratorRecord be ? GetIterator(value).
let iterator = value.get_iterator(context, None, None)?;
let mut result = Vec::new();
// 2. Let result be IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment.
for binding in &self.bindings {
use BindingPatternTypeArray::*;
match binding {
// ArrayBindingPattern : [ ]
Empty => {
// 1. Return NormalCompletion(empty).
}
// ArrayBindingPattern : [ Elision ]
// Note: This captures all elisions due to our representation of a the binding pattern.
Elision => {
// 1. If iteratorRecord.[[Done]] is false, then
// a. Let next be IteratorStep(iteratorRecord).
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// c. ReturnIfAbrupt(next).
// d. If next is false, set iteratorRecord.[[Done]] to true.
let _ = iterator.next(context)?;
// 2. Return NormalCompletion(empty).
}
// SingleNameBinding : BindingIdentifier Initializer[opt]
SingleName {
ident,
default_init,
} => {
// 1. Let bindingId be StringValue of BindingIdentifier.
// 2. Let lhs be ? ResolveBinding(bindingId, environment).
let next = iterator.next(context)?;
// 3. If iteratorRecord.[[Done]] is false, then
// 4. If iteratorRecord.[[Done]] is true, let v be undefined.
let mut v = if !next.done {
// a. Let next be IteratorStep(iteratorRecord).
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// c. ReturnIfAbrupt(next).
// d. If next is false, set iteratorRecord.[[Done]] to true.
// e. Else,
// i. Let v be IteratorValue(next).
// ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true.
// iii. ReturnIfAbrupt(v).
next.value
} else {
JsValue::undefined()
};
// 5. If Initializer is present and v is undefined, then
if let Some(init) = default_init {
if v.is_undefined() {
// TODO: a. not implemented yet:
// a. If IsAnonymousFunctionDefinition(Initializer) is true, then
// i. Set v to the result of performing NamedEvaluation for Initializer with argument bindingId.
// b. Else,
// i. Let defaultValue be the result of evaluating Initializer.
// ii. Set v to ? GetValue(defaultValue).
v = init.run(context)?
}
}
// 6. If environment is undefined, return ? PutValue(lhs, v).
// 7. Return InitializeReferencedBinding(lhs, v).
result.push((ident.clone(), v));
}
// BindingElement : BindingPattern Initializer[opt]
BindingPattern { pattern } => {
let next = iterator.next(context)?;
// 1. If iteratorRecord.[[Done]] is false, then
// 2. If iteratorRecord.[[Done]] is true, let v be undefined.
let v = if !next.done {
// a. Let next be IteratorStep(iteratorRecord).
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// c. ReturnIfAbrupt(next).
// d. If next is false, set iteratorRecord.[[Done]] to true.
// e. Else,
// i. Let v be IteratorValue(next).
// ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true.
// iii. ReturnIfAbrupt(v).
Some(next.value)
} else {
None
};
// 3. If Initializer is present and v is undefined, then
// a. Let defaultValue be the result of evaluating Initializer.
// b. Set v to ? GetValue(defaultValue).
// 4. Return the result of performing BindingInitialization of BindingPattern with v and environment as the arguments.
result.append(&mut pattern.run(v, context)?);
}
// BindingRestElement : ... BindingIdentifier
SingleNameRest { ident } => {
// 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment).
// 2. Let A be ! ArrayCreate(0).
// 3. Let n be 0.
let a = Array::array_create(0, None, context)
.expect("Array creation with 0 length should never fail");
// 4. Repeat,
loop {
let next = iterator.next(context)?;
// a. If iteratorRecord.[[Done]] is false, then
// i. Let next be IteratorStep(iteratorRecord).
// ii. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// iii. ReturnIfAbrupt(next).
// iv. If next is false, set iteratorRecord.[[Done]] to true.
// b. If iteratorRecord.[[Done]] is true, then
if next.done {
// i. If environment is undefined, return ? PutValue(lhs, A).
// ii. Return InitializeReferencedBinding(lhs, A).
break result.push((ident.clone(), a.clone().into()));
}
// c. Let nextValue be IteratorValue(next).
// d. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
// e. ReturnIfAbrupt(nextValue).
// f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), nextValue).
// g. Set n to n + 1.
Array::add_to_array_object(&a.clone().into(), &[next.value], context)?;
}
}
// BindingRestElement : ... BindingPattern
BindingPatternRest { pattern } => {
// 1. Let A be ! ArrayCreate(0).
// 2. Let n be 0.
let a = Array::array_create(0, None, context)
.expect("Array creation with 0 length should never fail");
// 3. Repeat,
loop {
// a. If iteratorRecord.[[Done]] is false, then
// i. Let next be IteratorStep(iteratorRecord).
// ii. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// iii. ReturnIfAbrupt(next).
// iv. If next is false, set iteratorRecord.[[Done]] to true.
let next = iterator.next(context)?;
// b. If iteratorRecord.[[Done]] is true, then
if next.done {
// i. Return the result of performing BindingInitialization of BindingPattern with A and environment as the arguments.
break result
.append(&mut pattern.run(Some(a.clone().into()), context)?);
}
// c. Let nextValue be IteratorValue(next).
// d. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
// e. ReturnIfAbrupt(nextValue).
// f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), nextValue).
// g. Set n to n + 1.
Array::add_to_array_object(&a.clone().into(), &[next.value], context)?;
}
}
}
}
// 3. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, result).
// 4. Return result.
Ok(result)
}
/// Gets the list of identifiers declared by the array binding pattern.
#[inline]
pub(crate) fn idents(&self) -> Vec<&str> {

13
boa/src/syntax/ast/node/field/get_const_field/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -61,17 +59,6 @@ impl GetConstField {
}
}
impl Executable for GetConstField {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let mut obj = self.obj().run(context)?;
if !obj.is_object() {
obj = JsValue::Object(obj.to_object(context)?);
}
obj.get_field(self.field(), context)
}
}
impl fmt::Display for GetConstField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.obj(), self.field())

14
boa/src/syntax/ast/node/field/get_field/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -60,18 +58,6 @@ impl GetField {
}
}
impl Executable for GetField {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let mut obj = self.obj().run(context)?;
if !obj.is_object() {
obj = JsValue::Object(obj.to_object(context)?);
}
let field = self.field().run(context)?;
obj.get_field(field.to_property_key(context)?, context)
}
}
impl fmt::Display for GetField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}[{}]", self.obj(), self.field())

9
boa/src/syntax/ast/node/identifier/mod.rs

@ -1,10 +1,8 @@
//! Local identifier node.
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::Node,
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
@ -33,13 +31,6 @@ pub struct Identifier {
ident: Box<str>,
}
impl Executable for Identifier {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("Identifier", "exec");
context.get_binding_value(self.as_ref())
}
}
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.ident, f)

12
boa/src/syntax/ast/node/iteration/continue_node/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::{Executable, InterpreterState},
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -45,16 +43,6 @@ impl Continue {
}
}
impl Executable for Continue {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
context
.executor()
.set_current_state(InterpreterState::Continue(self.label().map(Box::from)));
Ok(JsValue::undefined())
}
}
impl fmt::Display for Continue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "continue")?;

30
boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::{Executable, InterpreterState},
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -73,34 +71,6 @@ impl DoWhileLoop {
}
}
impl Executable for DoWhileLoop {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let mut result;
loop {
result = self.body().run(context)?;
match context.executor().get_current_state() {
InterpreterState::Break(label) => {
handle_state_with_labels!(self, label, context, break);
break;
}
InterpreterState::Continue(label) => {
handle_state_with_labels!(self, label, context, continue);
}
InterpreterState::Return => {
return Ok(result);
}
InterpreterState::Executing => {
// Continue execution.
}
}
if !self.cond().run(context)?.to_boolean() {
break;
}
}
Ok(result)
}
}
impl fmt::Display for DoWhileLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

156
boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs

@ -1,13 +1,6 @@
use crate::{
builtins::{iterable::IteratorRecord, ForInIterator},
environment::{
declarative_environment_record::DeclarativeEnvironmentRecord,
lexical_environment::VariableScope,
},
exec::{Executable, InterpreterState},
gc::{Finalize, Trace},
syntax::ast::node::{iteration::IterableLoopInitializer, Declaration, Node},
BoaProfiler, Context, JsResult, JsValue,
syntax::ast::node::{iteration::IterableLoopInitializer, Node},
};
use std::fmt;
@ -77,150 +70,3 @@ impl From<ForInLoop> for Node {
Self::ForInLoop(for_in)
}
}
impl Executable for ForInLoop {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("ForIn", "exec");
let object = self.expr().run(context)?;
let mut result = JsValue::undefined();
if object.is_null_or_undefined() {
return Ok(result);
}
let object = object.to_object(context)?;
let for_in_iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), context);
let next_function = for_in_iterator
.get_property("next")
.as_ref()
.map(|p| p.expect_value())
.cloned()
.ok_or_else(|| context.construct_type_error("Could not find property `next`"))?;
let iterator = IteratorRecord::new(for_in_iterator, next_function);
loop {
{
let env = context.get_current_environment();
context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
}
let iterator_result = iterator.next(context)?;
if iterator_result.done {
context.pop_environment();
break;
}
let next_result = iterator_result.value;
match self.init() {
IterableLoopInitializer::Identifier(ref name) => {
if context.has_binding(name.as_ref())? {
// Binding already exists
context.set_mutable_binding(
name.as_ref(),
next_result.clone(),
context.strict(),
)?;
} else {
context.create_mutable_binding(
name.as_ref(),
true,
VariableScope::Function,
)?;
context.initialize_binding(name.as_ref(), next_result)?;
}
}
IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
if context.has_binding(ident.as_ref())? {
context.set_mutable_binding(
ident.as_ref(),
next_result,
context.strict(),
)?;
} else {
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Function,
)?;
context.initialize_binding(ident.as_ref(), next_result)?;
}
}
Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? {
if context.has_binding(ident.as_ref())? {
context.set_mutable_binding(
ident.as_ref(),
value,
context.strict(),
)?;
} else {
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Function,
)?;
context.initialize_binding(ident.as_ref(), value)?;
}
}
}
},
IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), next_result)?;
}
Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? {
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), value)?;
}
}
},
IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
context.create_immutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), next_result)?;
}
Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? {
context.create_immutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), value)?;
}
}
},
}
result = self.body().run(context)?;
match context.executor().get_current_state() {
InterpreterState::Break(label) => {
handle_state_with_labels!(self, label, context, break);
break;
}
InterpreterState::Continue(label) => {
handle_state_with_labels!(self, label, context, continue);
}
InterpreterState::Return => return Ok(result),
InterpreterState::Executing => {
// Continue execution.
}
}
let _ = context.pop_environment();
}
Ok(result)
}
}

53
boa/src/syntax/ast/node/iteration/for_loop/mod.rs

@ -1,9 +1,6 @@
use crate::{
environment::declarative_environment_record::DeclarativeEnvironmentRecord,
exec::{Executable, InterpreterState},
gc::{Finalize, Trace},
syntax::ast::node::Node,
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
@ -97,56 +94,6 @@ impl ForLoop {
}
}
impl Executable for ForLoop {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
// Create the block environment.
let _timer = BoaProfiler::global().start_event("ForLoop", "exec");
{
let env = context.get_current_environment();
context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
}
if let Some(init) = self.init() {
init.run(context)?;
}
while self
.condition()
.map(|cond| cond.run(context).map(|v| v.to_boolean()))
.transpose()?
.unwrap_or(true)
{
let result = self.body().run(context)?;
match context.executor().get_current_state() {
InterpreterState::Break(label) => {
handle_state_with_labels!(self, label, context, break);
break;
}
InterpreterState::Continue(label) => {
handle_state_with_labels!(self, label, context, continue);
}
InterpreterState::Return => {
return Ok(result);
}
InterpreterState::Executing => {
// Continue execution.
}
}
if let Some(final_expr) = self.final_expr() {
final_expr.run(context)?;
}
}
// pop the block env
let _ = context.pop_environment();
Ok(JsValue::undefined())
}
}
impl fmt::Display for ForLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

143
boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs

@ -1,12 +1,6 @@
use crate::{
environment::{
declarative_environment_record::DeclarativeEnvironmentRecord,
lexical_environment::VariableScope,
},
exec::{Executable, InterpreterState},
gc::{Finalize, Trace},
syntax::ast::node::{iteration::IterableLoopInitializer, Declaration, Node},
BoaProfiler, Context, JsResult, JsValue,
syntax::ast::node::{iteration::IterableLoopInitializer, Node},
};
use std::fmt;
@ -76,138 +70,3 @@ impl From<ForOfLoop> for Node {
Self::ForOfLoop(for_of)
}
}
impl Executable for ForOfLoop {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("ForOf", "exec");
let iterable = self.iterable().run(context)?;
let iterator = iterable.get_iterator(context, None, None)?;
let mut result = JsValue::undefined();
loop {
{
let env = context.get_current_environment();
context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
}
let iterator_result = iterator.next(context)?;
if iterator_result.done {
context.pop_environment();
break;
}
let next_result = iterator_result.value;
match self.init() {
IterableLoopInitializer::Identifier(ref name) => {
if context.has_binding(name.as_ref())? {
// Binding already exists
context.set_mutable_binding(
name.as_ref(),
next_result.clone(),
context.strict(),
)?;
} else {
context.create_mutable_binding(
name.as_ref(),
true,
VariableScope::Function,
)?;
context.initialize_binding(name.as_ref(), next_result)?;
}
}
IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
if context.has_binding(ident.as_ref())? {
context.set_mutable_binding(
ident.as_ref(),
next_result,
context.strict(),
)?;
} else {
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Function,
)?;
context.initialize_binding(ident.as_ref(), next_result)?;
}
}
Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? {
if context.has_binding(ident.as_ref())? {
context.set_mutable_binding(
ident.as_ref(),
value,
context.strict(),
)?;
} else {
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Function,
)?;
context.initialize_binding(ident.as_ref(), value)?;
}
}
}
},
IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), next_result)?;
}
Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? {
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), value)?;
}
}
},
IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
context.create_immutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), next_result)?;
}
Declaration::Pattern(p) => {
for (ident, value) in p.run(Some(next_result), context)? {
context.create_immutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), value)?;
}
}
},
}
result = self.body().run(context)?;
match context.executor().get_current_state() {
InterpreterState::Break(label) => {
handle_state_with_labels!(self, label, context, break);
break;
}
InterpreterState::Continue(label) => {
handle_state_with_labels!(self, label, context, continue);
}
InterpreterState::Return => return Ok(result),
InterpreterState::Executing => {
// Continue execution.
}
}
let _ = context.pop_environment();
}
Ok(result)
}
}

21
boa/src/syntax/ast/node/iteration/mod.rs

@ -16,27 +16,6 @@ use serde::{Deserialize, Serialize};
#[cfg(test)]
mod tests;
// Checking labels for break and continue is the same operation for `ForLoop`, `While` and `DoWhile`
macro_rules! handle_state_with_labels {
($self:ident, $label:ident, $interpreter:ident, $state:tt) => {{
if let Some(brk_label) = $label {
if let Some(stmt_label) = $self.label() {
// Break from where we are, keeping "continue" set as the state
if stmt_label != brk_label.as_ref() {
break;
}
} else {
// if a label is set but the current block has no label, break
break;
}
}
$interpreter
.executor()
.set_current_state(InterpreterState::Executing);
}};
}
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub enum IterableLoopInitializer {

27
boa/src/syntax/ast/node/iteration/while_loop/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::{Executable, InterpreterState},
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -71,31 +69,6 @@ impl WhileLoop {
}
}
impl Executable for WhileLoop {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let mut result = JsValue::undefined();
while self.cond().run(context)?.to_boolean() {
result = self.body().run(context)?;
match context.executor().get_current_state() {
InterpreterState::Break(label) => {
handle_state_with_labels!(self, label, context, break);
break;
}
InterpreterState::Continue(label) => {
handle_state_with_labels!(self, label, context, continue)
}
InterpreterState::Return => {
return Ok(result);
}
InterpreterState::Executing => {
// Continue execution.
}
}
}
Ok(result)
}
}
impl fmt::Display for WhileLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

91
boa/src/syntax/ast/node/mod.rs

@ -51,11 +51,7 @@ pub use self::{
try_node::{Catch, Finally, Try},
};
use super::Const;
use crate::{
exec::Executable,
gc::{empty_trace, Finalize, Trace},
BoaProfiler, Context, JsResult, JsValue,
};
use crate::gc::{empty_trace, Finalize, Trace};
use std::{
cmp::Ordering,
fmt::{self, Display},
@ -330,72 +326,6 @@ impl Node {
}
}
impl Executable for Node {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("Executable", "exec");
match *self {
Node::AsyncFunctionDecl(ref decl) => decl.run(context),
Node::AsyncFunctionExpr(ref function_expr) => function_expr.run(context),
Node::AsyncGeneratorExpr(ref expr) => expr.run(context),
Node::AsyncGeneratorDecl(ref decl) => decl.run(context),
Node::AwaitExpr(ref expr) => expr.run(context),
Node::Call(ref call) => call.run(context),
Node::Const(Const::Null) => Ok(JsValue::null()),
Node::Const(Const::Num(num)) => Ok(JsValue::new(num)),
Node::Const(Const::Int(num)) => Ok(JsValue::new(num)),
Node::Const(Const::BigInt(ref num)) => Ok(JsValue::new(num.clone())),
Node::Const(Const::Undefined) => Ok(JsValue::undefined()),
// we can't move String from Const into value, because const is a garbage collected value
// Which means Drop() get's called on Const, but str will be gone at that point.
// Do Const values need to be garbage collected? We no longer need them once we've generated Values
Node::Const(Const::String(ref value)) => Ok(JsValue::new(value.to_string())),
Node::Const(Const::Bool(value)) => Ok(JsValue::new(value)),
Node::Block(ref block) => block.run(context),
Node::Identifier(ref identifier) => identifier.run(context),
Node::GetConstField(ref get_const_field_node) => get_const_field_node.run(context),
Node::GetField(ref get_field) => get_field.run(context),
Node::WhileLoop(ref while_loop) => while_loop.run(context),
Node::DoWhileLoop(ref do_while) => do_while.run(context),
Node::ForLoop(ref for_loop) => for_loop.run(context),
Node::ForOfLoop(ref for_of_loop) => for_of_loop.run(context),
Node::ForInLoop(ref for_in_loop) => for_in_loop.run(context),
Node::If(ref if_smt) => if_smt.run(context),
Node::ConditionalOp(ref op) => op.run(context),
Node::Switch(ref switch) => switch.run(context),
Node::Object(ref obj) => obj.run(context),
Node::ArrayDecl(ref arr) => arr.run(context),
// <https://tc39.es/ecma262/#sec-createdynamicfunction>
Node::FunctionDecl(ref decl) => decl.run(context),
// <https://tc39.es/ecma262/#sec-createdynamicfunction>
Node::FunctionExpr(ref function_expr) => function_expr.run(context),
Node::ArrowFunctionDecl(ref decl) => decl.run(context),
Node::BinOp(ref op) => op.run(context),
Node::UnaryOp(ref op) => op.run(context),
Node::New(ref call) => call.run(context),
Node::Return(ref ret) => ret.run(context),
Node::TaggedTemplate(ref template) => template.run(context),
Node::TemplateLit(ref template) => template.run(context),
Node::Throw(ref throw) => throw.run(context),
Node::Assign(ref op) => op.run(context),
Node::VarDeclList(ref decl) => decl.run(context),
Node::LetDeclList(ref decl) => decl.run(context),
Node::ConstDeclList(ref decl) => decl.run(context),
Node::Spread(ref spread) => spread.run(context),
Node::This => {
// Will either return `this` binding or undefined
context.get_this_binding()
}
Node::Try(ref try_node) => try_node.run(context),
Node::Break(ref break_node) => break_node.run(context),
Node::Continue(ref continue_node) => continue_node.run(context),
Node::Empty => Ok(JsValue::undefined()),
Node::Yield(ref y) => y.run(context),
Node::GeneratorDecl(ref decl) => decl.run(context),
Node::GeneratorExpr(ref expr) => expr.run(context),
}
}
}
/// Utility to join multiple Nodes into a single string.
fn join_nodes<N>(f: &mut fmt::Formatter<'_>, nodes: &[N]) -> fmt::Result
where
@ -473,25 +403,6 @@ impl FormalParameter {
self.is_rest_param
}
pub fn run(
&self,
init: Option<JsValue>,
context: &mut Context,
) -> JsResult<Vec<(Box<str>, JsValue)>> {
match &self.declaration {
Declaration::Identifier { ident, .. } => Ok(vec![(
ident.as_ref().to_string().into_boxed_str(),
init.unwrap(),
)]),
Declaration::Pattern(pattern) => match &pattern {
DeclarationPattern::Object(object_pattern) => object_pattern.run(init, context),
DeclarationPattern::Array(array_pattern) => array_pattern.run(init, context),
},
}
}
pub fn is_identifier(&self) -> bool {
matches!(&self.declaration, Declaration::Identifier { .. })
}

40
boa/src/syntax/ast/node/new/mod.rs

@ -1,9 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::{Call, Node},
value::JsValue,
BoaProfiler, Context, JsResult,
};
use std::fmt;
@ -46,48 +43,11 @@ impl New {
}
/// Returns the inner call
#[cfg(feature = "vm")]
pub(crate) fn call(&self) -> &Call {
&self.call
}
}
impl Executable for New {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("New", "exec");
let func_object = self.expr().run(context)?;
let mut v_args = Vec::with_capacity(self.args().len());
for arg in self.args() {
if let Node::Spread(ref x) = arg {
let val = x.run(context)?;
let iterator_record = val.get_iterator(context, None, None)?;
loop {
let next = iterator_record.next(context)?;
if next.done {
break;
}
let next_value = next.value;
v_args.push(next_value);
}
break; // after spread we don't accept any new arguments
} else {
v_args.push(arg.run(context)?);
}
}
func_object
.as_constructor()
.ok_or_else(|| {
context.construct_type_error(format!(
"{} is not a constructor",
self.expr().to_string(),
))
})
.and_then(|cons| cons.construct(&v_args, &cons.clone().into(), context))
}
}
impl From<Call> for New {
fn from(call: Call) -> Self {
Self { call }

143
boa/src/syntax/ast/node/object/mod.rs

@ -1,11 +1,8 @@
//! Object node.
use crate::{
exec::Executable,
gc::{Finalize, Trace},
property::PropertyDescriptor,
syntax::ast::node::{join_nodes, MethodDefinitionKind, Node, PropertyDefinition, PropertyName},
BoaProfiler, Context, JsResult, JsValue,
syntax::ast::node::{join_nodes, MethodDefinitionKind, Node, PropertyDefinition},
};
use std::fmt;
@ -89,144 +86,6 @@ impl Object {
}
}
impl Executable for Object {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("object", "exec");
let obj = context.construct_object();
// TODO: Implement the rest of the property types.
for property in self.properties().iter() {
match property {
PropertyDefinition::Property(name, value) => {
let name = match name {
PropertyName::Literal(name) => name.clone().into(),
PropertyName::Computed(node) => {
node.run(context)?.to_property_key(context)?
}
};
obj.__define_own_property__(
name,
PropertyDescriptor::builder()
.value(value.run(context)?)
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
context,
)?;
}
PropertyDefinition::MethodDefinition(kind, name, func) => {
let name = match name {
PropertyName::Literal(name) => name.clone().into(),
PropertyName::Computed(node) => {
node.run(context)?.to_property_key(context)?
}
};
match kind {
MethodDefinitionKind::Ordinary => {
obj.__define_own_property__(
name,
PropertyDescriptor::builder()
.value(func.run(context)?)
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
context,
)?;
}
MethodDefinitionKind::Get => {
let set = obj
.__get_own_property__(&name, context)?
.as_ref()
.and_then(|a| a.set())
.cloned();
obj.__define_own_property__(
name,
PropertyDescriptor::builder()
.maybe_get(func.run(context)?.as_object().cloned())
.maybe_set(set)
.enumerable(true)
.configurable(true)
.build(),
context,
)?;
}
MethodDefinitionKind::Set => {
let get = obj
.__get_own_property__(&name, context)?
.as_ref()
.and_then(|a| a.get())
.cloned();
obj.__define_own_property__(
name,
PropertyDescriptor::builder()
.maybe_get(get)
.maybe_set(func.run(context)?.as_object().cloned())
.enumerable(true)
.configurable(true)
.build(),
context,
)?;
}
&MethodDefinitionKind::Generator => {
// TODO: Implement generator method definition execution.
obj.__define_own_property__(
name,
PropertyDescriptor::builder()
.value(JsValue::undefined())
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
context,
)?;
}
&MethodDefinitionKind::AsyncGenerator => {
// TODO: Implement async generator method definition execution.
obj.__define_own_property__(
name,
PropertyDescriptor::builder()
.value(JsValue::undefined())
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
context,
)?;
}
&MethodDefinitionKind::Async => {
obj.__define_own_property__(
name,
PropertyDescriptor::builder()
.value(JsValue::undefined())
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
context,
)?;
}
}
}
// [spec]: https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation
PropertyDefinition::SpreadObject(node) => {
let val = node.run(context)?;
if val.is_null_or_undefined() {
continue;
}
obj.copy_data_properties::<String>(&val, vec![], context)?;
}
_ => {} // unimplemented!("{:?} type of property", i),
}
}
Ok(obj.into())
}
}
impl fmt::Display for Object {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

46
boa/src/syntax/ast/node/operator/assign/mod.rs

@ -1,9 +1,6 @@
use crate::{
environment::lexical_environment::VariableScope,
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::Node,
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
@ -52,49 +49,6 @@ impl Assign {
}
}
impl Executable for Assign {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("Assign", "exec");
let val = self.rhs().run(context)?;
match self.lhs() {
Node::Identifier(ref name) => {
if context.has_binding(name.as_ref())? {
// Binding already exists
context.set_mutable_binding(name.as_ref(), val.clone(), context.strict())?;
} else {
context.create_mutable_binding(name.as_ref(), true, VariableScope::Function)?;
context.initialize_binding(name.as_ref(), val.clone())?;
}
}
Node::GetConstField(ref get_const_field) => {
let value = get_const_field.obj().run(context)?;
let obj = value.to_object(context)?;
let succeeded =
obj.__set__(get_const_field.field().into(), val.clone(), value, context)?;
if !succeeded && context.strict() {
return context.throw_type_error(
"Assignment to read-only properties is not allowed in strict mode",
);
}
}
Node::GetField(ref get_field) => {
let value = get_field.obj().run(context)?;
let obj = value.to_object(context)?;
let field = get_field.field().run(context)?;
let key = field.to_property_key(context)?;
let succeeded = obj.__set__(key, val.clone(), value, context)?;
if !succeeded && context.strict() {
return context.throw_type_error(
"Assignment to read-only properties is not allowed in strict mode",
);
}
}
_ => (),
}
Ok(val)
}
}
impl fmt::Display for Assign {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} = {}", self.lhs, self.rhs)

149
boa/src/syntax/ast/node/operator/bin_op/mod.rs

@ -1,11 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::{
node::Node,
op::{self, AssignOp, BitOp, CompOp, LogOp, NumOp},
},
Context, JsResult, JsValue,
syntax::ast::{node::Node, op},
};
use std::fmt;
@ -55,148 +50,6 @@ impl BinOp {
pub fn rhs(&self) -> &Node {
&self.rhs
}
/// Runs the assignment operators.
fn run_assign(op: AssignOp, x: JsValue, y: &Node, context: &mut Context) -> JsResult<JsValue> {
match op {
AssignOp::Add => x.add(&y.run(context)?, context),
AssignOp::Sub => x.sub(&y.run(context)?, context),
AssignOp::Mul => x.mul(&y.run(context)?, context),
AssignOp::Exp => x.pow(&y.run(context)?, context),
AssignOp::Div => x.div(&y.run(context)?, context),
AssignOp::Mod => x.rem(&y.run(context)?, context),
AssignOp::And => x.bitand(&y.run(context)?, context),
AssignOp::Or => x.bitor(&y.run(context)?, context),
AssignOp::Xor => x.bitxor(&y.run(context)?, context),
AssignOp::Shl => x.shl(&y.run(context)?, context),
AssignOp::Shr => x.shr(&y.run(context)?, context),
AssignOp::Ushr => x.ushr(&y.run(context)?, context),
AssignOp::BoolAnd => {
if x.to_boolean() {
Ok(y.run(context)?)
} else {
Ok(x)
}
}
AssignOp::BoolOr => {
if x.to_boolean() {
Ok(x)
} else {
Ok(y.run(context)?)
}
}
AssignOp::Coalesce => {
if x.is_null_or_undefined() {
Ok(y.run(context)?)
} else {
Ok(x)
}
}
}
}
}
impl Executable for BinOp {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
match self.op() {
op::BinOp::Num(op) => {
let x = self.lhs().run(context)?;
let y = self.rhs().run(context)?;
match op {
NumOp::Add => x.add(&y, context),
NumOp::Sub => x.sub(&y, context),
NumOp::Mul => x.mul(&y, context),
NumOp::Exp => x.pow(&y, context),
NumOp::Div => x.div(&y, context),
NumOp::Mod => x.rem(&y, context),
}
}
op::BinOp::Bit(op) => {
let x = self.lhs().run(context)?;
let y = self.rhs().run(context)?;
match op {
BitOp::And => x.bitand(&y, context),
BitOp::Or => x.bitor(&y, context),
BitOp::Xor => x.bitxor(&y, context),
BitOp::Shl => x.shl(&y, context),
BitOp::Shr => x.shr(&y, context),
BitOp::UShr => x.ushr(&y, context),
}
}
op::BinOp::Comp(op) => {
let x = self.lhs().run(context)?;
let y = self.rhs().run(context)?;
Ok(JsValue::new(match op {
CompOp::Equal => x.equals(&y, context)?,
CompOp::NotEqual => !x.equals(&y, context)?,
CompOp::StrictEqual => x.strict_equals(&y),
CompOp::StrictNotEqual => !x.strict_equals(&y),
CompOp::GreaterThan => x.gt(&y, context)?,
CompOp::GreaterThanOrEqual => x.ge(&y, context)?,
CompOp::LessThan => x.lt(&y, context)?,
CompOp::LessThanOrEqual => x.le(&y, context)?,
CompOp::In => {
if !y.is_object() {
return context.throw_type_error(format!(
"right-hand side of 'in' should be an object, got {}",
y.type_of()
));
}
let key = x.to_property_key(context)?;
context.has_property(&y, &key)?
}
CompOp::InstanceOf => x.instance_of(&y, context)?,
}))
}
op::BinOp::Log(op) => Ok(match op {
LogOp::And => {
let left = self.lhs().run(context)?;
if !left.to_boolean() {
left
} else {
self.rhs().run(context)?
}
}
LogOp::Or => {
let left = self.lhs().run(context)?;
if left.to_boolean() {
left
} else {
self.rhs().run(context)?
}
}
LogOp::Coalesce => {
let left = self.lhs.run(context)?;
if left.is_null_or_undefined() {
self.rhs().run(context)?
} else {
left
}
}
}),
op::BinOp::Assign(op) => match self.lhs() {
Node::Identifier(ref name) => {
let v_a = context.get_binding_value(name.as_ref())?;
let value = Self::run_assign(op, v_a, self.rhs(), context)?;
context.set_mutable_binding(name.as_ref(), value.clone(), context.strict())?;
Ok(value)
}
Node::GetConstField(ref get_const_field) => {
let v_r_a = get_const_field.obj().run(context)?;
let v_a = v_r_a.get_field(get_const_field.field(), context)?;
let value = Self::run_assign(op, v_a, self.rhs(), context)?;
v_r_a.set_field(get_const_field.field(), value.clone(), false, context)?;
Ok(value)
}
_ => Ok(JsValue::undefined()),
},
op::BinOp::Comma => {
self.lhs().run(context)?;
Ok(self.rhs().run(context)?)
}
}
}
}
impl fmt::Display for BinOp {

82
boa/src/syntax/ast/node/operator/unary_op/mod.rs

@ -1,13 +1,9 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::{node::Node, op},
Context, JsBigInt, JsResult, JsValue,
};
use std::fmt;
use crate::builtins::Number;
use crate::value::Numeric;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
@ -49,84 +45,6 @@ impl UnaryOp {
}
}
impl Executable for UnaryOp {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
Ok(match self.op() {
op::UnaryOp::Minus => self.target().run(context)?.neg(context)?,
op::UnaryOp::Plus => JsValue::new(self.target().run(context)?.to_number(context)?),
op::UnaryOp::IncrementPost => {
let x = self.target().run(context)?;
let ret = x.clone();
let result = match x.to_numeric(context)? {
Numeric::Number(n) => (n + 1.0).into(),
Numeric::BigInt(b) => (JsBigInt::add(&b, &JsBigInt::from(1))).into(),
};
context.set_value(self.target(), result)?;
ret
}
op::UnaryOp::IncrementPre => {
let result = self.target().run(context)?.to_number(context)? + 1.0;
context.set_value(self.target(), result.into())?
}
op::UnaryOp::DecrementPost => {
let x = self.target().run(context)?;
let ret = x.clone();
let result = x.to_number(context)? - 1.0;
context.set_value(self.target(), result.into())?;
ret
}
op::UnaryOp::DecrementPre => {
let result = self.target().run(context)?.to_number(context)? - 1.0;
context.set_value(self.target(), result.into())?
}
op::UnaryOp::Not => self.target().run(context)?.not(context)?.into(),
op::UnaryOp::Tilde => {
let expr = self.target().run(context)?;
let old_v = expr.to_numeric(context)?;
match old_v {
Numeric::Number(x) => JsValue::new(Number::not(x)),
Numeric::BigInt(x) => JsValue::new(JsBigInt::not(&x)),
}
}
op::UnaryOp::Void => {
self.target().run(context)?;
JsValue::undefined()
}
op::UnaryOp::Delete => match *self.target() {
Node::GetConstField(ref get_const_field) => {
let delete_status = get_const_field
.obj()
.run(context)?
.to_object(context)?
.__delete__(&get_const_field.field().into(), context)?;
if !delete_status && context.strict() {
return context.throw_type_error("Cannot delete property");
} else {
JsValue::new(delete_status)
}
}
Node::GetField(ref get_field) => {
let obj = get_field.obj().run(context)?;
let field = &get_field.field().run(context)?;
let delete_status = obj
.to_object(context)?
.__delete__(&field.to_property_key(context)?, context)?;
if !delete_status && context.strict() {
return context.throw_type_error("Cannot delete property");
} else {
JsValue::new(delete_status)
}
}
// TODO: implement delete on references.
Node::Identifier(_) => JsValue::new(false),
_ => JsValue::new(true),
},
op::UnaryOp::TypeOf => JsValue::new(self.target().run(context)?.type_of()),
})
}
}
impl fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.op, self.target)

16
boa/src/syntax/ast/node/return_smt/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::{Executable, InterpreterState},
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -60,20 +58,6 @@ impl Return {
}
}
impl Executable for Return {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let result = match self.expr() {
Some(v) => v.run(context),
None => Ok(JsValue::undefined()),
};
// Set flag for return
context
.executor()
.set_current_state(InterpreterState::Return);
result
}
}
impl From<Return> for Node {
fn from(return_smt: Return) -> Node {
Node::Return(return_smt)

9
boa/src/syntax/ast/node/spread/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -51,13 +49,6 @@ impl Spread {
}
}
impl Executable for Spread {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
// TODO: for now we can do nothing but return the value as-is
self.val().run(context)
}
}
impl fmt::Display for Spread {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "...{}", self.val())

58
boa/src/syntax/ast/node/statement_list/mod.rs

@ -1,11 +1,8 @@
//! Statement list node.
use crate::{
context::StrictType,
exec::{Executable, InterpreterState},
gc::{empty_trace, Finalize, Trace},
syntax::ast::node::{Declaration, Node},
BoaProfiler, Context, JsResult, JsValue,
};
use std::{collections::HashSet, fmt, ops::Deref, rc::Rc};
@ -129,61 +126,6 @@ impl StatementList {
}
}
impl Executable for StatementList {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("StatementList", "exec");
// https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation
// The return value is uninitialized, which means it defaults to Value::Undefined
let mut obj = JsValue::default();
context
.executor()
.set_current_state(InterpreterState::Executing);
let strict_before = context.strict_type();
match context.strict_type() {
StrictType::Off if self.strict => context.set_strict(StrictType::Function),
StrictType::Function if !self.strict => context.set_strict_mode_off(),
_ => {}
}
for (i, item) in self.items().iter().enumerate() {
let val = match item.run(context) {
Ok(val) => val,
Err(e) => {
context.set_strict(strict_before);
return Err(e);
}
};
match context.executor().get_current_state() {
InterpreterState::Return => {
// Early return.
obj = val;
break;
}
InterpreterState::Break(_label) => {
// Early break.
break;
}
InterpreterState::Continue(_label) => {
break;
}
InterpreterState::Executing => {
// Continue execution
}
}
if i + 1 == self.items().len() {
obj = val;
}
}
context.set_strict(strict_before);
Ok(obj)
}
}
impl<T> From<T> for StatementList
where
T: Into<Box<[Node]>>,

80
boa/src/syntax/ast/node/switch/mod.rs

@ -1,10 +1,8 @@
//! Switch node.
//!
use crate::{
exec::{Executable, InterpreterState},
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -122,84 +120,6 @@ impl Switch {
}
}
impl Executable for Switch {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let val = self.val().run(context)?;
let mut result = JsValue::null();
let mut matched = false;
context
.executor()
.set_current_state(InterpreterState::Executing);
// If a case block does not end with a break statement then subsequent cases will be run without
// checking their conditions until a break is encountered.
let mut fall_through: bool = false;
for case in self.cases().iter() {
let cond = case.condition();
let block = case.body();
if fall_through || val.strict_equals(&cond.run(context)?) {
matched = true;
let result = block.run(context)?;
match context.executor().get_current_state() {
InterpreterState::Return => {
// Early return.
return Ok(result);
}
InterpreterState::Break(_label) => {
// TODO, break to a label.
// Break statement encountered so therefore end switch statement.
context
.executor()
.set_current_state(InterpreterState::Executing);
break;
}
InterpreterState::Continue(_label) => {
// TODO, continue to a label.
break;
}
InterpreterState::Executing => {
// Continuing execution / falling through to next case statement(s).
fall_through = true;
}
}
}
}
if !matched {
if let Some(default) = self.default() {
context
.executor()
.set_current_state(InterpreterState::Executing);
for (i, item) in default.iter().enumerate() {
let val = item.run(context)?;
match context.executor().get_current_state() {
InterpreterState::Return => {
// Early return.
result = val;
break;
}
InterpreterState::Break(_label) => {
// TODO, break to a label.
// Early break.
break;
}
_ => {
// Continue execution
}
}
if i == default.len() - 1 {
result = val;
}
}
}
}
Ok(result)
}
}
impl fmt::Display for Switch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

78
boa/src/syntax/ast/node/template/mod.rs

@ -1,7 +1,6 @@
//! Template literal node.
use super::Node;
use crate::{builtins::Array, exec::Executable, BoaProfiler, Context, JsResult, JsValue};
use gc::{Finalize, Trace};
#[cfg(feature = "deser")]
@ -30,33 +29,11 @@ impl TemplateLit {
TemplateLit { elements }
}
#[cfg(feature = "vm")]
pub(crate) fn elements(&self) -> &Vec<TemplateElement> {
&self.elements
}
}
impl Executable for TemplateLit {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("TemplateLiteral", "exec");
let mut result = String::new();
for element in self.elements.iter() {
match element {
TemplateElement::String(s) => {
result.push_str(s);
}
TemplateElement::Expr(node) => {
let value = node.run(context)?;
let s = value.to_string(context)?;
result.push_str(&s);
}
}
}
Ok(result.into())
}
}
impl fmt::Display for TemplateLit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "`")?;
@ -93,78 +70,23 @@ impl TaggedTemplate {
}
}
#[cfg(feature = "vm")]
pub(crate) fn tag(&self) -> &Node {
&self.tag
}
#[cfg(feature = "vm")]
pub(crate) fn raws(&self) -> &Vec<Box<str>> {
&self.raws
}
#[cfg(feature = "vm")]
pub(crate) fn cookeds(&self) -> &Vec<Option<Box<str>>> {
&self.cookeds
}
#[cfg(feature = "vm")]
pub(crate) fn exprs(&self) -> &Vec<Node> {
&self.exprs
}
}
impl Executable for TaggedTemplate {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("TaggedTemplate", "exec");
let template_object = Array::new_array(context);
let raw_array = Array::new_array(context);
for (i, raw) in self.raws.iter().enumerate() {
raw_array.set_field(i, JsValue::new(raw.as_ref()), false, context)?;
}
for (i, cooked) in self.cookeds.iter().enumerate() {
if let Some(cooked) = cooked {
template_object.set_field(i, JsValue::new(cooked.as_ref()), false, context)?;
} else {
template_object.set_field(i, JsValue::undefined(), false, context)?;
}
}
template_object.set_field("raw", raw_array, false, context)?;
let (this, func) = match *self.tag {
Node::GetConstField(ref get_const_field) => {
let mut obj = get_const_field.obj().run(context)?;
if !obj.is_object() {
obj = JsValue::Object(obj.to_object(context)?);
}
(
obj.clone(),
obj.get_field(get_const_field.field(), context)?,
)
}
Node::GetField(ref get_field) => {
let obj = get_field.obj().run(context)?;
let field = get_field.field().run(context)?;
(
obj.clone(),
obj.get_field(field.to_property_key(context)?, context)?,
)
}
_ => (context.global_object().into(), self.tag.run(context)?),
};
let mut args = vec![template_object];
for expr in self.exprs.iter() {
args.push(expr.run(context)?);
}
context.call(&func, &this, &args)
}
}
impl fmt::Display for TaggedTemplate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}`", self.tag)?;

9
boa/src/syntax/ast/node/throw/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -48,13 +46,6 @@ impl Throw {
}
}
impl Executable for Throw {
#[inline]
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
Err(self.expr().run(context)?)
}
}
impl fmt::Display for Throw {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "throw {}", self.expr)

63
boa/src/syntax/ast/node/try_node/mod.rs

@ -1,12 +1,6 @@
use crate::{
environment::{
declarative_environment_record::DeclarativeEnvironmentRecord,
lexical_environment::VariableScope,
},
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::{Block, Declaration, Node},
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
@ -94,63 +88,6 @@ impl Try {
}
}
impl Executable for Try {
fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("Try", "exec");
let res = self.block().run(context).map_or_else(
|err| {
if let Some(catch) = self.catch() {
let env = context.get_current_environment();
context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
if let Some(param) = catch.parameter() {
match param {
Declaration::Identifier { ident, init } => {
debug_assert!(init.is_none());
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), err)?;
}
Declaration::Pattern(pattern) => {
debug_assert!(pattern.init().is_none());
for (ident, value) in pattern.run(Some(err), context)? {
context.create_mutable_binding(
ident.as_ref(),
false,
VariableScope::Block,
)?;
context.initialize_binding(ident.as_ref(), value)?;
}
}
}
}
let res = catch.block().run(context);
// pop the block env
let _ = context.pop_environment();
res
} else {
Err(err)
}
},
Ok,
);
if let Some(finally) = self.finally() {
finally.run(context)?;
}
res
}
}
impl fmt::Display for Try {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)

9
boa/src/syntax/ast/node/yield/mod.rs

@ -1,8 +1,6 @@
use crate::{
exec::Executable,
gc::{Finalize, Trace},
syntax::ast::node::Node,
Context, JsResult, JsValue,
};
use std::fmt;
@ -46,13 +44,6 @@ impl Yield {
}
}
impl Executable for Yield {
fn run(&self, _context: &mut Context) -> JsResult<JsValue> {
// TODO: Implement Generator execution
Ok(JsValue::undefined())
}
}
impl From<Yield> for Node {
fn from(r#yield: Yield) -> Node {
Node::Yield(r#yield)

23
boa/src/exec/tests.rs → boa/src/tests.rs

@ -772,13 +772,10 @@ mod in_operator {
new a();
"#;
#[cfg(not(feature = "vm"))]
let error = "Uncaught \"TypeError\": \"a is not a constructor\"";
#[cfg(feature = "vm")]
let error = "Uncaught \"TypeError\": \"not a constructor\"";
check_output(&[TestAction::TestEq(scenario, error)]);
check_output(&[TestAction::TestEq(
scenario,
"Uncaught \"TypeError\": \"not a constructor\"",
)]);
}
#[test]
@ -1268,17 +1265,11 @@ fn not_a_function() {
}
"#;
#[cfg(not(feature = "vm"))]
let error = "\"TypeError: Value is not callable\"";
#[cfg(feature = "vm")]
let error = "\"TypeError: not a callable function\"";
check_output(&[
TestAction::Execute(init),
TestAction::TestEq(scenario1, error),
TestAction::TestEq(scenario2, error),
TestAction::TestEq(scenario3, error),
TestAction::TestEq(scenario1, "\"TypeError: not a callable function\""),
TestAction::TestEq(scenario2, "\"TypeError: not a callable function\""),
TestAction::TestEq(scenario3, "\"TypeError: not a callable function\""),
]);
}

24
boa/src/vm/code_block.rs

@ -23,7 +23,15 @@ use gc::Gc;
use std::{convert::TryInto, fmt::Write, mem::size_of};
/// This represents whether a value can be read from [`CodeBlock`] code.
pub unsafe trait Readable {}
///
/// # Safety
///
/// This trait is safe to implement as long as the type doesn't implement `Drop`.
/// At some point, if [negative impls][negative_impls] are stabilized, we might be able to remove
/// the unsafe bound.
///
/// [negative_impls]: https://doc.rust-lang.org/beta/unstable-book/language-features/negative-impls.html
pub(crate) unsafe trait Readable {}
unsafe impl Readable for u8 {}
unsafe impl Readable for i8 {}
@ -40,7 +48,7 @@ unsafe impl Readable for f64 {}
///
/// A CodeBlock is generated for each function compiled by the [ByteCompiler](crate::bytecompiler::ByteCompiler).
/// It stores the bytecode and the other attributes of the function.
#[derive(Debug, Trace, Finalize)]
#[derive(Clone, Debug, Trace, Finalize)]
pub struct CodeBlock {
/// Name of this function
pub(crate) name: JsString,
@ -99,7 +107,10 @@ impl CodeBlock {
/// # Safety
///
/// Does not check if read happens out-of-bounds.
pub unsafe fn read_unchecked<T: Readable>(&self, offset: usize) -> T {
pub(crate) unsafe fn read_unchecked<T>(&self, offset: usize) -> T
where
T: Readable,
{
// This has to be an unaligned read because we can't guarantee that
// the types are aligned.
self.code.as_ptr().add(offset).cast::<T>().read_unaligned()
@ -107,7 +118,10 @@ impl CodeBlock {
/// Read type T from code.
#[track_caller]
pub fn read<T: Readable>(&self, offset: usize) -> T {
pub(crate) fn read<T>(&self, offset: usize) -> T
where
T: Readable,
{
assert!(offset + size_of::<T>() - 1 < self.code.len());
// Safety: We checked that it is not an out-of-bounds read,
@ -478,7 +492,6 @@ impl JsObject {
code: code.clone(),
environment: environment.clone(),
},
Function::Ordinary { .. } => unreachable!(),
}
};
@ -644,7 +657,6 @@ impl JsObject {
code: code.clone(),
environment: environment.clone(),
},
Function::Ordinary { .. } => unreachable!(),
}
};

21
boa/src/vm/mod.rs

@ -92,8 +92,6 @@ impl Vm {
impl Context {
fn execute_instruction(&mut self) -> JsResult<bool> {
let _timer = BoaProfiler::global().start_event("execute_instruction", "vm");
macro_rules! bin_op {
($op:ident) => {{
let rhs = self.vm.pop();
@ -103,10 +101,17 @@ impl Context {
}};
}
let opcode = self.vm.frame().code.code[self.vm.frame().pc]
.try_into()
.unwrap();
self.vm.frame_mut().pc += 1;
let opcode: Opcode = {
let _timer = BoaProfiler::global().start_event("Opcode retrieval", "vm");
let opcode = self.vm.frame().code.code[self.vm.frame().pc]
.try_into()
.expect("could not convert code at PC to opcode");
self.vm.frame_mut().pc += 1;
opcode
};
let _timer =
BoaProfiler::global().start_event(&format!("INST - {}", &opcode.as_str()), "vm");
match opcode {
Opcode::Nop => {}
@ -721,7 +726,6 @@ impl Context {
self.pop_environment();
}
self.vm.frame_mut().pop_env_on_return = 0;
let _ = self.vm.pop_frame();
return Ok(true);
}
FinallyReturn::Err => {
@ -893,7 +897,6 @@ impl Context {
self.pop_environment();
}
self.vm.frame_mut().pop_env_on_return = 0;
let _ = self.vm.pop_frame();
return Ok(true);
}
}
@ -1177,6 +1180,7 @@ impl Context {
Ok(should_exit) => {
if should_exit {
let result = self.vm.pop();
self.vm.pop_frame();
return Ok(result);
}
}
@ -1230,6 +1234,7 @@ impl Context {
println!("\n");
}
self.vm.pop_frame();
if self.vm.stack.is_empty() {
return Ok(JsValue::undefined());
}

3
boa_cli/Cargo.toml

@ -22,9 +22,6 @@ colored = "2.0.0"
regex = "1.5.4"
lazy_static = "1.4.0"
[features]
vm = ["Boa/vm"]
[target.x86_64-unknown-linux-gnu.dependencies]
jemallocator = "0.3.2"

2
boa_cli/src/main.rs

@ -67,7 +67,6 @@ struct Opt {
dump_ast: Option<Option<DumpFormat>>,
/// Dump the AST to stdout with the given format.
#[cfg(feature = "vm")]
#[structopt(long = "trace", short = "t")]
trace: bool,
@ -149,7 +148,6 @@ pub fn main() -> Result<(), std::io::Error> {
let mut context = Context::new();
// Trace Output
#[cfg(feature = "vm")]
context.set_trace(args.trace);
for file in &args.files {

3
boa_tester/Cargo.toml

@ -11,9 +11,6 @@ exclude = ["../.vscode/*", "../Dockerfile", "../Makefile", "../.editorConfig"]
edition = "2021"
rust-version = "1.56"
[features]
vm = ["Boa/vm"]
[dependencies]
Boa = { path = "../boa" }
structopt = "0.3.25"

6
boa_tester/src/exec/js262.rs

@ -5,9 +5,6 @@ use boa::{
Context, JsResult, JsValue,
};
#[cfg(not(feature = "vm"))]
use boa::exec::Executable;
/// Initializes the object in the context.
pub(super) fn init(context: &mut Context) -> JsObject {
let global_obj = context.global_object();
@ -90,11 +87,8 @@ fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsRe
match boa::parse(source_text.as_str(), false) {
// TODO: check strict
Err(e) => context.throw_type_error(format!("Uncaught Syntax Error: {}", e)),
#[cfg(not(feature = "vm"))]
Ok(statement_list) => statement_list.run(context),
// Calling eval here parses the code a second time.
// TODO: We can fix this after we have have defined the public api for the vm executer.
#[cfg(feature = "vm")]
Ok(_) => context.eval(source_text.as_str()),
}
} else {

8
boa_tester/src/exec/mod.rs

@ -152,9 +152,7 @@ impl Test {
match self.set_up_env(harness, strict) {
Ok(mut context) => {
if strict {
context.set_strict_mode_global();
}
context.set_strict_mode(strict);
let res = context.eval(&self.content.as_ref());
let passed = res.is_ok();
@ -201,9 +199,7 @@ impl Test {
} else {
match self.set_up_env(harness, strict) {
Ok(mut context) => {
if strict {
context.set_strict_mode_global();
}
context.set_strict_mode(strict);
match context.eval(&self.content.as_ref()) {
Ok(res) => (false, format!("{}", res.display())),
Err(e) => {

3
boa_tester/src/results.rs

@ -218,10 +218,7 @@ pub(crate) fn compare_results(base: &Path, new: &Path, markdown: bool) {
)
}
#[cfg(feature = "vm")]
println!("#### VM implementation");
#[cfg(not(feature = "vm"))]
println!("#### Non-VM implementation");
println!("| Test result | main count | PR count | difference |");
println!("| :---------: | :----------: | :------: | :--------: |");

17
boa_wasm/src/lib.rs

@ -1,22 +1,11 @@
use boa::{exec::Executable, parse, Context};
use boa::Context;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn evaluate(src: &str) -> Result<String, JsValue> {
// Setup executor
let mut context = Context::new();
let expr = match parse(src, false) {
Ok(res) => res,
Err(e) => {
return Err(format!(
"Uncaught {}",
context.construct_syntax_error(e.to_string()).display()
)
.into());
}
};
expr.run(&mut context)
Context::new()
.eval(src)
.map_err(|e| JsValue::from(format!("Uncaught {}", e.display())))
.map(|v| v.display().to_string())
}

Loading…
Cancel
Save