Add code improvement to IR and implement simple numeric constant folding.

This commit is contained in:
James Harton 2019-03-18 17:38:36 +13:00
parent 8942c04c6e
commit 596ee84f44
3 changed files with 285 additions and 16 deletions

View file

@ -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<Block>,
types: Vec<TyIdx>,
}
@ -553,7 +553,7 @@ impl Builder {
self.peek_block_mut().unwrap().push(ir);
}
fn pop_ir(&mut self) -> Option<IR> {
pub fn pop_ir(&mut self) -> Option<IR> {
self.peek_block_mut().unwrap().pop()
}

View file

@ -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<i64> {
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<f64> {
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);
}
}

View file

@ -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: 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 {
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<F: Fn(&mut IR, &mut Context)>(
&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<IR>),
/// A constant value of the specified type.
@ -250,3 +342,80 @@ pub enum Val {
Array(Vec<IR>),
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<Vec<&IR>> {
match self {
Val::Array(ref elements) => Some(elements.iter().collect()),
_ => None,
}
}
pub fn map(&self) -> Option<Vec<&(IR, IR)>> {
match self {
Val::Map(ref elements) => Some(elements.iter().collect()),
_ => None,
}
}
pub fn float(&self) -> Option<f64> {
match self {
Val::Float(ref value) => Some(*value),
_ => None,
}
}
pub fn integer(&self) -> Option<i64> {
match self {
Val::Integer(ref value) => Some(*value),
_ => None,
}
}
}