Skip to content

Commit

Permalink
Implement constant folding optmization
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed Mar 16, 2023
1 parent 718fea3 commit 19664ea
Show file tree
Hide file tree
Showing 13 changed files with 382 additions and 15 deletions.
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
11 changes: 11 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,16 @@ pub enum Literal {
/// [spec]: https://tc39.es/ecma262/#sec-null-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/null
Null,

/// TODO: doc
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-undefined-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/undefined
Undefined,
}

impl From<Sym> for Literal {
Expand Down Expand Up @@ -173,6 +183,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
10 changes: 9 additions & 1 deletion boa_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ struct Opt {
#[arg(long = "vi")]
vi_mode: bool,

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

/// Generate instruction flowgraph. Default is Graphviz.
#[arg(
long,
Expand Down Expand Up @@ -207,7 +210,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 @@ -262,6 +269,7 @@ fn main() -> Result<(), io::Error> {

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

for file in &args.files {
let buffer = read(file)?;
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
27 changes: 25 additions & 2 deletions boa_engine/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ use crate::{
job::{IdleJobQueue, JobQueue, NativeJob},
native_function::NativeFunction,
object::{FunctionObjectBuilder, GlobalPropertyMap, JsObject},
optimizer::{constant_folding_optimizer::ConstantFoldingOptimizer, Optimizer},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
vm::{CallFrame, CodeBlock, Vm},
JsResult, JsValue, Source,
};
use boa_ast::{ModuleItemList, StatementList};
use boa_ast::{visitor::VisitorMut, ModuleItemList, StatementList};
use boa_gc::Gc;
use boa_interner::{Interner, Sym};
use boa_parser::{Error as ParseError, Parser};
Expand Down Expand Up @@ -101,6 +102,8 @@ pub struct Context<'host> {
host_hooks: &'host dyn HostHooks,

job_queue: &'host dyn JobQueue,

optimize: bool,
}

impl std::fmt::Debug for Context<'_> {
Expand Down Expand Up @@ -202,6 +205,15 @@ impl Context<'_> {
result
}

/// Applies optimizations to the [`StatementList`] inplace.
pub fn optimize_statement_list(&mut self, statement_list: &mut StatementList) {
let mut optimizer = Optimizer::new(&mut self.interner);
let mut cfo = ConstantFoldingOptimizer::default();
optimizer.push_pass(&mut cfo);

optimizer.visit_statement_list_mut(statement_list);
}

/// Parse the given source script.
pub fn parse_script<R: Read>(
&mut self,
Expand All @@ -212,7 +224,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.optimize {
self.optimize_statement_list(&mut result);
}
Ok(result)
}

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

/// TODO:
pub fn set_optimize(&mut self, optimize: bool) {
self.optimize = optimize;
}

/// Changes the strictness mode of the context.
pub fn strict(&mut self, strict: bool) {
self.strict = strict;
Expand Down Expand Up @@ -642,6 +663,8 @@ impl<'icu, 'hooks, 'queue> ContextBuilder<'icu, 'hooks, 'queue> {
kept_alive: Vec::new(),
host_hooks,
job_queue: self.job_queue.unwrap_or(&IdleJobQueue),
// TODO: maybe it should be off by default
optimize: true,
};

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 @@ -124,6 +124,8 @@ pub mod symbol;
pub mod value;
pub mod vm;

pub(crate) mod optimizer;

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

Expand Down
152 changes: 152 additions & 0 deletions boa_engine/src/optimizer/constant_folding_optimizer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use boa_ast::{
expression::{
literal::Literal,
operator::{
binary::{ArithmeticOp, BinaryOp, BitwiseOp, LogicalOp, RelationalOp},
unary::UnaryOp,
Binary, Unary,
},
},
Expression,
};
use boa_interner::Interner;

use super::{Pass, PassResult};

#[derive(Debug, Default)]
pub(crate) struct ConstantFoldingOptimizer {}

impl ConstantFoldingOptimizer {
pub(crate) fn constant_fold_unary_expr(
unary: &mut Unary,
interner: &mut Interner,
) -> PassResult<Expression> {
let value = if let Expression::Literal(Literal::Int(integer)) = unary.target() {
*integer
} else {
return PassResult::Leave;
};
let literal = match unary.op() {
UnaryOp::Minus => Literal::Int(-value),
UnaryOp::Plus => Literal::Int(value),
UnaryOp::Not => Literal::Bool(value == 0),
UnaryOp::Tilde => Literal::Int(!value),
UnaryOp::TypeOf => Literal::String(interner.get_or_intern("number")),
UnaryOp::Delete => Literal::Bool(true),
UnaryOp::Void => Literal::Undefined,
};

PassResult::Replace(Expression::Literal(literal))
}
pub(crate) fn constant_fold_binary_expr(
binary: &mut Binary,
_interner: &mut Interner,
) -> PassResult<Expression> {
let lhs = if let Expression::Literal(Literal::Int(integer)) = binary.lhs() {
*integer
} else {
return PassResult::Leave;
};
let rhs = if let Expression::Literal(Literal::Int(integer)) = binary.rhs() {
*integer
} else {
return PassResult::Leave;
};

let literal = match binary.op() {
BinaryOp::Arithmetic(op) => match op {
ArithmeticOp::Add => {
if let Some(result) = lhs.checked_add(rhs) {
Literal::Int(result)
} else {
Literal::Num(f64::from(lhs) + f64::from(rhs))
}
}
ArithmeticOp::Sub => {
if let Some(result) = lhs.checked_sub(rhs) {
Literal::Int(result)
} else {
Literal::Num(f64::from(lhs) - f64::from(rhs))
}
}
ArithmeticOp::Div => {
if let Some(result) = lhs.checked_div(rhs).filter(|div| rhs * div == lhs) {
Literal::Int(result)
} else {
Literal::Num(f64::from(lhs) / f64::from(rhs))
}
}
ArithmeticOp::Mul => {
if let Some(result) = lhs.checked_mul(rhs) {
Literal::Int(result)
} else {
Literal::Num(f64::from(lhs) - f64::from(rhs))
}
}
ArithmeticOp::Exp => {
if rhs.is_positive() {
if let Some(result) = lhs.checked_pow(rhs as u32) {
Literal::Int(result)
} else {
Literal::Num(f64::from(lhs).powf(f64::from(rhs)))
}
} else {
Literal::Num(f64::from(lhs).powf(f64::from(rhs)))
}
}
ArithmeticOp::Mod => {
if let Some(result) = lhs.checked_rem(rhs) {
Literal::Int(result)
} else {
Literal::Num(f64::from(lhs) % f64::from(rhs))
}
}
},
BinaryOp::Bitwise(op) => match op {
BitwiseOp::And => Literal::Int(lhs & rhs),
BitwiseOp::Or => Literal::Int(lhs | rhs),
BitwiseOp::Xor => Literal::Int(lhs ^ rhs),
BitwiseOp::Shl => Literal::Int(lhs.wrapping_shl(rhs as u32)),
BitwiseOp::Shr => Literal::Int(lhs.wrapping_shr(rhs as u32)),
BitwiseOp::UShr => {
let result = (lhs as u32).wrapping_shr(rhs as u32);
if let Ok(result) = result.try_into() {
Literal::Int(result)
} else {
Literal::Num(f64::from(result))
}
}
},
BinaryOp::Relational(op) => match op {
RelationalOp::Equal | RelationalOp::StrictEqual => Literal::Bool(lhs == rhs),
RelationalOp::NotEqual | RelationalOp::StrictNotEqual => Literal::Bool(lhs != rhs),
RelationalOp::GreaterThan => Literal::Bool(lhs > rhs),
RelationalOp::GreaterThanOrEqual => Literal::Bool(lhs >= rhs),
RelationalOp::LessThan => Literal::Bool(lhs < rhs),
RelationalOp::LessThanOrEqual => Literal::Bool(lhs <= rhs),
RelationalOp::In | RelationalOp::InstanceOf => return PassResult::Leave,
},
BinaryOp::Logical(op) => match op {
LogicalOp::And => Literal::Int(if lhs == 0 { lhs } else { rhs }),
LogicalOp::Or => Literal::Int(if lhs == 0 { rhs } else { lhs }),
LogicalOp::Coalesce => Literal::Int(rhs),
},
BinaryOp::Comma => return PassResult::Leave,
};
PassResult::Replace(Expression::Literal(literal))
}
}

impl Pass for ConstantFoldingOptimizer {
fn pass_expression(
&mut self,
expr: &mut Expression,
interner: &mut Interner,
) -> PassResult<Expression> {
match expr {
Expression::Unary(unary) => Self::constant_fold_unary_expr(unary, interner),
Expression::Binary(binary) => Self::constant_fold_binary_expr(binary, interner),
_ => PassResult::Leave,
}
}
}
Loading

0 comments on commit 19664ea

Please sign in to comment.