Add code improvement to IR and implement simple numeric constant folding.
This commit is contained in:
parent
8942c04c6e
commit
596ee84f44
3 changed files with 285 additions and 16 deletions
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
100
huia-compiler/src/ir/constant_folding.rs
Normal file
100
huia-compiler/src/ir/constant_folding.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue