Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Implement constant folding optimization #2679

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions boa_ast/src/expression/literal/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ impl AsRef<[Option<Expression>]> for ArrayLiteral {
}
}

impl AsMut<[Option<Expression>]> for ArrayLiteral {
#[inline]
fn as_mut(&mut self) -> &mut [Option<Expression>] {
&mut self.arr
}
}

impl<T> From<T> for ArrayLiteral
where
T: Into<Box<[Option<Expression>]>>,
Expand Down
7 changes: 7 additions & 0 deletions boa_ast/src/expression/literal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ pub enum Literal {
/// [spec]: https://tc39.es/ecma262/#sec-null-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/null
Null,

/// This represents the JavaScript `undefined` value, it does not reference the `undefined` global variable,
/// it will directly evaluate to `undefined`.
///
/// NOTE: This is used for optimizations.
Undefined,
}

impl From<Sym> for Literal {
Expand Down Expand Up @@ -173,6 +179,7 @@ impl ToInternedString for Literal {
Self::BigInt(ref num) => num.to_string(),
Self::Bool(v) => v.to_string(),
Self::Null => "null".to_owned(),
Self::Undefined => "undefined".to_owned(),
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions boa_ast/src/expression/operator/binary/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ impl Binary {
pub const fn rhs(&self) -> &Expression {
&self.rhs
}

/// Gets the left hand side of the binary operation.
#[inline]
#[must_use]
pub fn lhs_mut(&mut self) -> &mut Expression {
&mut self.lhs
}

/// Gets the right hand side of the binary operation.
#[inline]
#[must_use]
pub fn rhs_mut(&mut self) -> &mut Expression {
&mut self.rhs
}
}

impl ToInternedString for Binary {
Expand Down
7 changes: 7 additions & 0 deletions boa_ast/src/expression/operator/unary/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ impl Unary {
pub fn target(&self) -> &Expression {
self.target.as_ref()
}

/// Gets the target of this unary operator.
#[inline]
#[must_use]
pub fn target_mut(&mut self) -> &mut Expression {
self.target.as_mut()
}
}

impl ToInternedString for Unary {
Expand Down
60 changes: 42 additions & 18 deletions boa_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ use boa_ast::StatementList;
use boa_engine::{
context::ContextBuilder,
job::{FutureJob, JobQueue, NativeJob},
optimizer::OptimizerOptions,
vm::flowgraph::{Direction, Graph},
Context, JsResult, Source,
};
Expand All @@ -89,6 +90,7 @@ const READLINE_COLOR: Color = Color::Cyan;
// https://docs.rs/structopt/0.3.11/structopt/#type-magic
#[derive(Debug, Parser)]
#[command(author, version, about, name = "boa")]
#[allow(clippy::struct_excessive_bools)] // NOTE: Allow having more than 3 bools in struct
struct Opt {
/// The JavaScript file(s) to be evaluated.
#[arg(name = "FILE", value_hint = ValueHint::FilePath)]
Expand Down Expand Up @@ -118,6 +120,12 @@ struct Opt {
#[arg(long = "vi")]
vi_mode: bool,

#[arg(long, short = 'O', group = "optimizer")]
optimize: bool,

#[arg(long, requires = "optimizer")]
optimizer_statistics: bool,

/// Generate instruction flowgraph. Default is Graphviz.
#[arg(
long,
Expand Down Expand Up @@ -207,7 +215,11 @@ where
S: AsRef<[u8]> + ?Sized,
{
if let Some(ref arg) = args.dump_ast {
let ast = parse_tokens(src, context)?;
let mut ast = parse_tokens(src, context)?;

if args.optimize {
context.optimize_statement_list(&mut ast);
}

match arg {
Some(DumpFormat::Json) => println!(
Expand Down Expand Up @@ -251,31 +263,17 @@ fn generate_flowgraph(
Ok(result)
}

fn main() -> Result<(), io::Error> {
let args = Opt::parse();

let queue = Jobs::default();
let mut context = ContextBuilder::new()
.job_queue(&queue)
.build()
.expect("cannot fail with default global object");

// Strict mode
context.strict(args.strict);

// Trace Output
context.set_trace(args.trace);

fn evaluate_files(args: &Opt, context: &mut Context<'_>) -> Result<(), io::Error> {
for file in &args.files {
let buffer = read(file)?;

if args.has_dump_flag() {
if let Err(e) = dump(&buffer, &args, &mut context) {
if let Err(e) = dump(&buffer, args, context) {
eprintln!("{e}");
}
} else if let Some(flowgraph) = args.flowgraph {
match generate_flowgraph(
&mut context,
context,
&buffer,
flowgraph.unwrap_or(FlowgraphFormat::Graphviz),
args.flowgraph_direction,
Expand All @@ -292,6 +290,30 @@ fn main() -> Result<(), io::Error> {
}
}

Ok(())
}

fn main() -> Result<(), io::Error> {
let args = Opt::parse();

let queue = Jobs::default();
let mut context = ContextBuilder::new()
.job_queue(&queue)
.build()
.expect("cannot fail with default global object");

// Strict mode
context.strict(args.strict);

// Trace Output
context.set_trace(args.trace);

// Configure optimizer options
let mut optimizer_options = OptimizerOptions::empty();
optimizer_options.set(OptimizerOptions::STATISTICS, args.optimizer_statistics);
optimizer_options.set(OptimizerOptions::OPTIMIZE_ALL, args.optimize);
context.set_optimizer_options(optimizer_options);

if args.files.is_empty() {
let config = Config::builder()
.keyseq_timeout(1)
Expand Down Expand Up @@ -365,6 +387,8 @@ fn main() -> Result<(), io::Error> {
editor
.save_history(CLI_HISTORY)
.expect("could not save CLI history");
} else {
evaluate_files(&args, &mut context)?;
}

Ok(())
Expand Down
16 changes: 15 additions & 1 deletion boa_engine/benches/full.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Benchmarks of the whole execution engine in Boa.

use boa_engine::{context::DefaultHooks, realm::Realm, Context, Source};
use boa_engine::{
context::DefaultHooks, optimizer::OptimizerOptions, realm::Realm, Context, Source,
};
use criterion::{criterion_group, criterion_main, Criterion};
use std::hint::black_box;

Expand All @@ -24,6 +26,10 @@ macro_rules! full_benchmarks {
{
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default();

// Disable optimizations
context.set_optimizer_options(OptimizerOptions::empty());

c.bench_function(concat!($id, " (Parser)"), move |b| {
b.iter(|| context.parse_script(black_box(Source::from_bytes(CODE))))
});
Expand All @@ -35,6 +41,10 @@ macro_rules! full_benchmarks {
{
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default();

// Disable optimizations
context.set_optimizer_options(OptimizerOptions::empty());

let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed");
c.bench_function(concat!($id, " (Compiler)"), move |b| {
b.iter(|| {
Expand All @@ -49,6 +59,10 @@ macro_rules! full_benchmarks {
{
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default();

// Disable optimizations
context.set_optimizer_options(OptimizerOptions::empty());

let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed");
let code_block = context.compile_script(&statement_list).unwrap();
c.bench_function(concat!($id, " (Execution)"), move |b| {
Expand Down
1 change: 1 addition & 0 deletions boa_engine/src/bytecompiler/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ impl ByteCompiler<'_, '_> {
AstLiteral::Bool(true) => self.emit(Opcode::PushTrue, &[]),
AstLiteral::Bool(false) => self.emit(Opcode::PushFalse, &[]),
AstLiteral::Null => self.emit(Opcode::PushNull, &[]),
AstLiteral::Undefined => self.emit(Opcode::PushUndefined, &[]),
}

if !use_expr {
Expand Down
3 changes: 1 addition & 2 deletions boa_engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,7 @@ impl<'b, 'host> ByteCompiler<'b, 'host> {
}

// Check if the f64 value can fit in an i32.
#[allow(clippy::float_cmp)]
if f64::from(value as i32) == value {
if f64::from(value as i32).to_bits() == value.to_bits() {
self.emit_push_integer(value as i32);
} else {
self.emit_opcode(Opcode::PushRational);
Expand Down
31 changes: 29 additions & 2 deletions boa_engine/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::{
job::{IdleJobQueue, JobQueue, NativeJob},
native_function::NativeFunction,
object::{FunctionObjectBuilder, GlobalPropertyMap, JsObject},
optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
vm::{CallFrame, CodeBlock, Vm},
Expand Down Expand Up @@ -101,6 +102,8 @@ pub struct Context<'host> {
host_hooks: &'host dyn HostHooks,

job_queue: &'host dyn JobQueue,

optimizer_options: OptimizerOptions,
}

impl std::fmt::Debug for Context<'_> {
Expand All @@ -113,7 +116,8 @@ impl std::fmt::Debug for Context<'_> {
.field("vm", &self.vm)
.field("strict", &self.strict)
.field("promise_job_queue", &"JobQueue")
.field("hooks", &"HostHooks");
.field("hooks", &"HostHooks")
.field("optimizer_options", &self.optimizer_options);

#[cfg(feature = "intl")]
debug.field("icu", &self.icu);
Expand Down Expand Up @@ -202,6 +206,15 @@ impl Context<'_> {
result
}

/// Applies optimizations to the [`StatementList`] inplace.
pub fn optimize_statement_list(
&mut self,
statement_list: &mut StatementList,
) -> OptimizerStatistics {
let mut optimizer = Optimizer::new(self);
optimizer.apply(statement_list)
}

/// Parse the given source script.
pub fn parse_script<R: Read>(
&mut self,
Expand All @@ -212,7 +225,11 @@ impl Context<'_> {
if self.strict {
parser.set_strict();
}
parser.parse_script(&mut self.interner)
let mut result = parser.parse_script(&mut self.interner)?;
if !self.optimizer_options().is_empty() {
self.optimize_statement_list(&mut result);
}
Ok(result)
}

/// Parse the given source script.
Expand Down Expand Up @@ -427,6 +444,15 @@ impl Context<'_> {
self.vm.trace = trace;
}

/// Get optimizer options.
pub const fn optimizer_options(&self) -> OptimizerOptions {
self.optimizer_options
}
/// Enable or disable optimizations
pub fn set_optimizer_options(&mut self, optimizer_options: OptimizerOptions) {
self.optimizer_options = optimizer_options;
}

/// Changes the strictness mode of the context.
pub fn strict(&mut self, strict: bool) {
self.strict = strict;
Expand Down Expand Up @@ -643,6 +669,7 @@ impl<'icu, 'hooks, 'queue> ContextBuilder<'icu, 'hooks, 'queue> {
kept_alive: Vec::new(),
host_hooks,
job_queue: self.job_queue.unwrap_or(&IdleJobQueue),
optimizer_options: OptimizerOptions::OPTIMIZE_ALL,
};

builtins::set_default_global_bindings(&mut context)?;
Expand Down
2 changes: 2 additions & 0 deletions boa_engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ pub mod symbol;
pub mod value;
pub mod vm;

pub mod optimizer;

#[cfg(feature = "console")]
pub mod console;

Expand Down
Loading