From 596ee84f448bab19ad5acce675cd6c41da898fc7 Mon Sep 17 00:00:00 2001 From: James Harton Date: Mon, 18 Mar 2019 17:38:36 +1300 Subject: [PATCH] Add code improvement to IR and implement simple numeric constant folding. --- huia-compiler/src/ir/builder.rs | 4 +- huia-compiler/src/ir/constant_folding.rs | 100 ++++++++++++ huia-compiler/src/ir/mod.rs | 197 +++++++++++++++++++++-- 3 files changed, 285 insertions(+), 16 deletions(-) create mode 100644 huia-compiler/src/ir/constant_folding.rs diff --git a/huia-compiler/src/ir/builder.rs b/huia-compiler/src/ir/builder.rs index cb44e56..5f2bd41 100644 --- a/huia-compiler/src/ir/builder.rs +++ b/huia-compiler/src/ir/builder.rs @@ -9,7 +9,7 @@ use huia_parser::ast::{Location, NodeType, Term, Value}; /// The Builder is a simple stack machine for converting AST into IR. #[derive(Debug)] -struct Builder { +pub struct Builder { blocks: Vec, types: Vec, } @@ -553,7 +553,7 @@ impl Builder { self.peek_block_mut().unwrap().push(ir); } - fn pop_ir(&mut self) -> Option { + pub fn pop_ir(&mut self) -> Option { self.peek_block_mut().unwrap().pop() } diff --git a/huia-compiler/src/ir/constant_folding.rs b/huia-compiler/src/ir/constant_folding.rs new file mode 100644 index 0000000..4df469d --- /dev/null +++ b/huia-compiler/src/ir/constant_folding.rs @@ -0,0 +1,100 @@ +use crate::context::Context; +use crate::ir::{IRKind, Val, IR}; +use huia_parser::ast::binary::Operator as BinOp; +use std::f64; + +/// Perform constant folding on the provided node, or not. +pub fn pass(ir: &mut IR, context: &mut Context) { + if ir.is_infix() { + let (op, lhs, rhs) = ir.get_infix().unwrap(); + if !(lhs.is_constant() && rhs.is_constant()) { + return; + } + + let lhs = lhs.get_value().unwrap(); + let rhs = rhs.get_value().unwrap(); + + if lhs.is_integer() && rhs.is_integer() { + let lhs = lhs.integer().unwrap(); + let rhs = rhs.integer().unwrap(); + let value = perform_i64(&op, lhs, rhs); + if let Some(value) = value { + ir.set_kind(IRKind::Constant(Val::Integer(value))); + let idx = context.constant_string("Huia.Native.Integer"); + ir.set_result_type(context.reference_type(&idx, ir.location.clone())); + } + } else if lhs.is_float() && rhs.is_float() { + let lhs = lhs.float().unwrap(); + let rhs = rhs.float().unwrap(); + let value = perform_f64(&op, lhs, rhs); + if let Some(value) = value { + ir.set_kind(IRKind::Constant(Val::Float(value))); + let idx = context.constant_string("Huia.Native.Float"); + ir.set_result_type(context.reference_type(&idx, ir.location.clone())); + } + } + } +} + +fn perform_i64(op: &BinOp, lhs: i64, rhs: i64) -> Option { + match op { + BinOp::BitwiseAnd => Some(lhs & rhs), + BinOp::BitwiseOr => Some(lhs | rhs), + BinOp::BitwiseXor => Some(lhs ^ rhs), + BinOp::Divide => Some(lhs / rhs), + BinOp::Exponent => Some(lhs.pow(rhs as u32)), // FIXME: Potentially lossy conversion. + BinOp::Minus => Some(lhs - rhs), + BinOp::Modulus => Some(lhs % rhs), + BinOp::Multiply => Some(lhs * rhs), + BinOp::Plus => Some(lhs + rhs), + BinOp::ShiftLeft => Some(lhs << rhs), + BinOp::ShiftRight => Some(lhs >> rhs), + _ => None, + } +} + +fn perform_f64(op: &BinOp, lhs: f64, rhs: f64) -> Option { + match op { + BinOp::Divide => Some(lhs / rhs), + BinOp::Minus => Some(lhs - rhs), + BinOp::Modulus => Some(lhs % rhs), + BinOp::Multiply => Some(lhs * rhs), + BinOp::Plus => Some(lhs + rhs), + _ => None, + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ir::builder::Builder; + use huia_parser::ast::Term; + + #[test] + fn integer_folding() { + let term = Term::input("3 * 2 + 13 / 2").unwrap()[0].clone(); + let mut context = Context::test(); + let mut builder = Builder::default(); + + builder.build(term, &mut context); + let mut ir = builder.pop_ir().unwrap(); + ir.improve(&mut context, pass); + + assert!(ir.is_constant()); + assert_eq!(ir.get_value().unwrap().integer().unwrap(), 12); + } + + #[test] + fn float_folding() { + let term = Term::input("1.5 * 2.0 / 3.0").unwrap()[0].clone(); + let mut context = Context::test(); + let mut builder = Builder::default(); + + builder.build(term, &mut context); + let mut ir = builder.pop_ir().unwrap(); + ir.improve(&mut context, pass); + + assert!(ir.is_constant()); + assert!(ir.get_value().unwrap().float().unwrap() - 1.0 < f64::EPSILON); + } +} diff --git a/huia-compiler/src/ir/mod.rs b/huia-compiler/src/ir/mod.rs index 05f49f8..a54f197 100644 --- a/huia-compiler/src/ir/mod.rs +++ b/huia-compiler/src/ir/mod.rs @@ -1,6 +1,8 @@ mod builder; +mod constant_folding; use crate::block::BlockIdx; +use crate::context::Context; use crate::function::FunctionIdx; use crate::location::Location; use crate::stable::StringIdx; @@ -11,7 +13,7 @@ use huia_parser::ast::unary::Operator as UnOp; /// Describes the intermediate representation as converted from the Parser's AST. #[derive(Debug, PartialEq)] pub struct IR { - location: Option, + location: Location, result_type: TyIdx, kind: IRKind, } @@ -19,7 +21,7 @@ pub struct IR { impl IR { pub fn new_call(result_type: TyIdx, location: Location, arguments: Vec) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::Call(arguments), } @@ -27,7 +29,7 @@ impl IR { pub fn new_constant(result_type: TyIdx, location: Location, value: Val) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::Constant(value), } @@ -39,7 +41,7 @@ impl IR { properties: Vec<(StringIdx, IR)>, ) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::Constructor(properties), } @@ -47,7 +49,7 @@ impl IR { pub fn new_function_ref(result_type: TyIdx, location: Location, function: FunctionIdx) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::FunctionRef(function), } @@ -55,7 +57,7 @@ impl IR { pub fn new_get_local(result_type: TyIdx, location: Location, name: StringIdx) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::GetLocal(name), } @@ -63,7 +65,7 @@ impl IR { pub fn new_get_property(result_type: TyIdx, location: Location, name: StringIdx) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::GetProperty(name), } @@ -77,7 +79,7 @@ impl IR { negative: BlockIdx, ) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::If(Box::new(test), positive, negative), } @@ -87,7 +89,7 @@ impl IR { let lhs = Box::new(lhs); let rhs = Box::new(rhs); IR { - location: Some(location), + location, result_type, kind: IRKind::Infix(op, lhs, rhs), } @@ -95,7 +97,7 @@ impl IR { pub fn new_set_local(result_type: TyIdx, location: Location, name: StringIdx, value: IR) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::SetLocal(name, Box::new(value)), } @@ -107,7 +109,7 @@ impl IR { properties: Vec<(StringIdx, IR)>, ) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::SetProperties(properties), } @@ -115,7 +117,7 @@ impl IR { pub fn new_type_reference(result_type: TyIdx, location: Location, ty: TyIdx) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::TypeReference(ty), } @@ -123,7 +125,7 @@ impl IR { pub fn new_unary(result_type: TyIdx, location: Location, op: UnOp, rhs: IR) -> IR { IR { - location: Some(location), + location, result_type, kind: IRKind::Unary(op, Box::new(rhs)), } @@ -136,6 +138,74 @@ impl IR { } } + pub fn get_infix(&self) -> Option<(&BinOp, &IR, &IR)> { + match self.kind { + IRKind::Infix(ref op, ref lhs, ref rhs) => Some((op, lhs, rhs)), + _ => None, + } + } + + /// Provided an "improver" function, possibly improve this node. Implies + /// improvement of child nodes as required. + /// + /// Child nodes are visited *before* the receiving node to ensure that + /// improvements can bubble up. + pub fn improve( + &mut self, + mut context: &mut Context, + improver: F, + ) { + match self.kind { + IRKind::Call(ref mut args) => { + for mut arg in args.iter_mut() { + improver(&mut arg, &mut context); + } + } + IRKind::Constant(ref mut value) => match value { + Val::Array(ref mut elements) => { + for mut element in elements.iter_mut() { + improver(&mut element, &mut context); + } + } + Val::Map(ref mut elements) => { + for (ref mut key, ref mut value) in elements.iter_mut() { + improver(key, &mut context); + improver(value, &mut context); + } + } + _ => (), + }, + IRKind::Constructor(ref mut properties) => { + for (_key, ref mut value) in properties.iter_mut() { + improver(value, &mut context) + } + } + IRKind::FunctionRef(..) => (), + IRKind::GetLocal(..) => (), + IRKind::GetProperty(..) => (), + IRKind::If(ref mut test, ..) => { + improver(test, &mut context); + } + IRKind::Infix(_, ref mut lhs, ref mut rhs) => { + improver(lhs, &mut context); + improver(rhs, &mut context); + } + IRKind::SetLocal(_, ref mut rhs) => { + improver(rhs, &mut context); + } + IRKind::SetProperties(ref mut properties) => { + for (_key, ref mut value) in properties.iter_mut() { + improver(value, &mut context) + } + } + IRKind::TypeReference(..) => (), + IRKind::Unary(_, ref mut rhs) => { + improver(rhs, &mut context); + } + } + improver(self, &mut context); + } + pub fn is_call(&self) -> bool { match self.kind { IRKind::Call(..) => true, @@ -150,6 +220,13 @@ impl IR { } } + pub fn is_constant_float(&self) -> bool { + match self.kind { + IRKind::Constant(ref value) => value.is_float(), + _ => false, + } + } + pub fn is_constuctor(&self) -> bool { match self.kind { IRKind::Constructor(..) => true, @@ -209,10 +286,25 @@ impl IR { pub fn result_type(&self) -> TyIdx { self.result_type.clone() } + + pub fn get_value(&self) -> Option<&Val> { + match self.kind { + IRKind::Constant(ref value) => Some(value), + _ => None, + } + } + + pub fn set_kind(&mut self, kind: IRKind) { + self.kind = kind; + } + + pub fn set_result_type(&mut self, ty: TyIdx) { + self.result_type = ty; + } } #[derive(Debug, PartialEq)] -enum IRKind { +pub enum IRKind { /// Call top of the stack with arguments. Call(Vec), /// A constant value of the specified type. @@ -250,3 +342,80 @@ pub enum Val { Array(Vec), Map(Vec<(IR, IR)>), } + +impl Val { + pub fn is_atom(&self) -> bool { + match self { + Val::Atom(..) => true, + _ => false, + } + } + + pub fn is_boolean(&self) -> bool { + match self { + Val::Boolean(..) => true, + _ => false, + } + } + + pub fn is_float(&self) -> bool { + match self { + Val::Float(..) => true, + _ => false, + } + } + + pub fn is_integer(&self) -> bool { + match self { + Val::Integer(..) => true, + _ => false, + } + } + + pub fn is_string(&self) -> bool { + match self { + Val::String(..) => true, + _ => false, + } + } + pub fn is_array(&self) -> bool { + match self { + Val::Array(..) => true, + _ => false, + } + } + pub fn is_map(&self) -> bool { + match self { + Val::Map(..) => true, + _ => false, + } + } + + pub fn array(&self) -> Option> { + match self { + Val::Array(ref elements) => Some(elements.iter().collect()), + _ => None, + } + } + + pub fn map(&self) -> Option> { + match self { + Val::Map(ref elements) => Some(elements.iter().collect()), + _ => None, + } + } + + pub fn float(&self) -> Option { + match self { + Val::Float(ref value) => Some(*value), + _ => None, + } + } + + pub fn integer(&self) -> Option { + match self { + Val::Integer(ref value) => Some(*value), + _ => None, + } + } +}