Skip to content

Commit

Permalink
Implement constant folding for other literals
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed Mar 17, 2023
1 parent 2d66a06 commit 29abb90
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 103 deletions.
3 changes: 1 addition & 2 deletions boa_engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,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
2 changes: 1 addition & 1 deletion boa_engine/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl Context<'_> {

/// 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 optimizer = Optimizer::new(self);
let mut cfo = ConstantFoldingOptimizer::default();
optimizer.push_pass(&mut cfo);

Expand Down
195 changes: 104 additions & 91 deletions boa_engine/src/optimizer/constant_folding_optimizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,143 +9,156 @@ use boa_ast::{
},
Expression,
};
use boa_interner::Interner;

use crate::{builtins::Number, value::Numeric, Context, JsBigInt, JsString, JsValue};

use super::{Pass, PassResult};

fn literal_to_js_value(literal: Literal, context: &mut Context<'_>) -> JsValue {
match literal {
Literal::String(v) => {
JsValue::new(JsString::from(context.interner().resolve_expect(v).utf16()))
}
Literal::Num(v) => JsValue::new(v),
Literal::Int(v) => JsValue::new(v),
Literal::BigInt(v) => JsValue::new(JsBigInt::new(v)),
Literal::Bool(v) => JsValue::new(v),
Literal::Null => JsValue::null(),
Literal::Undefined => JsValue::undefined(),
}
}

fn js_value_to_literal(value: JsValue, context: &mut Context<'_>) -> Literal {
match value {
JsValue::Null => Literal::Null,
JsValue::Undefined => Literal::Undefined,
JsValue::Boolean(v) => Literal::Bool(v),
JsValue::String(v) => Literal::String(context.interner_mut().get_or_intern(v.as_ref())),
JsValue::Rational(v) => Literal::Num(v),
JsValue::Integer(v) => Literal::Int(v),
JsValue::BigInt(v) => Literal::BigInt(Box::new(v.as_inner().clone())),
JsValue::Object(_) | JsValue::Symbol(_) => {
unreachable!("value must not be a object or symbol")
}
}
}

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

impl ConstantFoldingOptimizer {
pub(crate) fn constant_fold_unary_expr(
unary: &mut Unary,
interner: &mut Interner,
context: &mut Context<'_>,
) -> PassResult<Expression> {
let value = if let Expression::Literal(Literal::Int(integer)) = unary.target() {
*integer
let literal = if let Expression::Literal(literal) = unary.target_mut() {
std::mem::replace(literal, Literal::Undefined)
} 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,
let value = match (literal, unary.op()) {
(literal, UnaryOp::Minus) => literal_to_js_value(literal, context).neg(context),
(literal, UnaryOp::Plus) => literal_to_js_value(literal, context)
.to_number(context)
.map(JsValue::new),
(literal, UnaryOp::Not) => literal_to_js_value(literal, context)
.not()
.map(JsValue::new),
(literal, UnaryOp::Tilde) => Ok(
match literal_to_js_value(literal, context)
.to_numeric(context)
.expect("should not fail")
{
Numeric::Number(number) => Number::not(number).into(),
Numeric::BigInt(bigint) => JsBigInt::not(&bigint).into(),
},
),
(literal, UnaryOp::TypeOf) => Ok(JsValue::new(
literal_to_js_value(literal, context).type_of(),
)),
(_, UnaryOp::Delete) => {
return PassResult::Replace(Expression::Literal(Literal::Bool(true)))
}
(_, UnaryOp::Void) => {
return PassResult::Replace(Expression::Literal(Literal::Undefined))
}
};

PassResult::Replace(Expression::Literal(literal))
let value = value.expect("should not fail");

PassResult::Replace(Expression::Literal(js_value_to_literal(value, context)))
}
pub(crate) fn constant_fold_binary_expr(
binary: &mut Binary,
_interner: &mut Interner,
context: &mut Context<'_>,
) -> PassResult<Expression> {
let lhs = if let Expression::Literal(Literal::Int(integer)) = binary.lhs() {
*integer
let lhs = if let Expression::Literal(literal) = binary.lhs_mut() {
std::mem::replace(literal, Literal::Undefined)
} else {
return PassResult::Leave;
};
let rhs = if let Expression::Literal(Literal::Int(integer)) = binary.rhs() {
*integer
let rhs = if let Expression::Literal(literal) = binary.rhs_mut() {
std::mem::replace(literal, Literal::Undefined)
} else {
return PassResult::Leave;
};

let literal = match binary.op() {
let lhs = literal_to_js_value(lhs, context);
let rhs = literal_to_js_value(rhs, context);

let value = 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))
}
}
ArithmeticOp::Add => lhs.add(&rhs, context),
ArithmeticOp::Sub => lhs.sub(&rhs, context),
ArithmeticOp::Div => lhs.div(&rhs, context),
ArithmeticOp::Mul => lhs.mul(&rhs, context),
ArithmeticOp::Exp => lhs.pow(&rhs, context),
ArithmeticOp::Mod => lhs.rem(&rhs, context),
},
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))
}
}
BitwiseOp::And => lhs.bitand(&rhs, context),
BitwiseOp::Or => lhs.bitor(&rhs, context),
BitwiseOp::Xor => lhs.bitxor(&rhs, context),
BitwiseOp::Shl => lhs.shr(&rhs, context),
BitwiseOp::Shr => lhs.shl(&rhs, context),
BitwiseOp::UShr => lhs.ushr(&rhs, context),
},
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,
RelationalOp::Equal => lhs.equals(&rhs, context).map(JsValue::new),
RelationalOp::NotEqual => lhs.equals(&rhs, context).map(|x| !x).map(JsValue::new),
RelationalOp::StrictEqual => Ok(JsValue::new(lhs.strict_equals(&rhs))),
RelationalOp::StrictNotEqual => Ok(JsValue::new(!lhs.strict_equals(&rhs))),
RelationalOp::GreaterThan => lhs.gt(&rhs, context).map(JsValue::new),
RelationalOp::GreaterThanOrEqual => lhs.ge(&rhs, context).map(JsValue::new),
RelationalOp::LessThan => lhs.lt(&rhs, context).map(JsValue::new),
RelationalOp::LessThanOrEqual => lhs.le(&rhs, context).map(JsValue::new),
},
// TODO: Implement branch removal on non-literal expression, by determening truthiness of expressions
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),
LogicalOp::And => Ok(if lhs.to_boolean() { rhs } else { lhs }),
LogicalOp::Or => Ok(if lhs.to_boolean() { lhs } else { rhs }),
LogicalOp::Coalesce => Ok(if lhs.is_null_or_undefined() { rhs } else { lhs }),
},
// TODO: deternine if expressions are pure, or not
BinaryOp::Comma => return PassResult::Leave,
};
PassResult::Replace(Expression::Literal(literal))

let value = value.expect("should not fail");

PassResult::Replace(Expression::Literal(js_value_to_literal(value, context)))
}
}

impl Pass for ConstantFoldingOptimizer {
fn pass_expression(
&mut self,
expr: &mut Expression,
interner: &mut Interner,
context: &mut Context<'_>,
) -> PassResult<Expression> {
match expr {
Expression::Unary(unary) => Self::constant_fold_unary_expr(unary, interner),
Expression::Binary(binary) => Self::constant_fold_binary_expr(binary, interner),
Expression::Unary(unary) => Self::constant_fold_unary_expr(unary, context),
Expression::Binary(binary) => Self::constant_fold_binary_expr(binary, context),
_ => PassResult::Leave,
}
}
Expand Down
19 changes: 10 additions & 9 deletions boa_engine/src/optimizer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ pub(crate) mod constant_folding_optimizer;
use std::{fmt::Debug, ops::ControlFlow};

use boa_ast::{visitor::VisitorMut, Expression};
use boa_interner::Interner;

use crate::Context;

#[derive(Debug)]

Expand All @@ -19,7 +20,7 @@ pub(crate) trait Pass: Debug {
fn pass_expression(
&mut self,
_expr: &mut Expression,
_interner: &mut Interner,
_context: &mut Context<'_>,
) -> PassResult<Expression> {
PassResult::<Expression>::Leave
}
Expand Down Expand Up @@ -79,16 +80,16 @@ impl Walker {
}

#[derive(Debug)]
pub(crate) struct Optimizer<'pass> {
pub(crate) struct Optimizer<'pass, 'host> {
passes: Vec<&'pass mut dyn Pass>,
interner: &'pass mut Interner,
context: &'pass mut Context<'host>,
}

impl<'pass> Optimizer<'pass> {
pub(crate) fn new(interner: &'pass mut Interner) -> Self {
impl<'pass, 'host> Optimizer<'pass, 'host> {
pub(crate) fn new(context: &'pass mut Context<'host>) -> Self {
Self {
passes: Vec::new(),
interner,
context,
}
}
pub(crate) fn push_pass(&mut self, pass: &'pass mut dyn Pass) {
Expand All @@ -100,7 +101,7 @@ impl<'pass> Optimizer<'pass> {
let mut walker = Walker::new();
for pass in &mut self.passes {
walker.walk_expression_postorder(expr, |expr| -> PassResult<Expression> {
pass.pass_expression(expr, self.interner)
pass.pass_expression(expr, self.context)
});
}
walker.changed
Expand All @@ -114,7 +115,7 @@ impl<'pass> Optimizer<'pass> {
}
}

impl<'ast> VisitorMut<'ast> for Optimizer<'_> {
impl<'ast> VisitorMut<'ast> for Optimizer<'_, '_> {
type BreakTy = ();

fn visit_expression_mut(&mut self, node: &'ast mut Expression) -> ControlFlow<Self::BreakTy> {
Expand Down

0 comments on commit 29abb90

Please sign in to comment.