diff --git a/tooling/dscs_compiler/src/backend/ir_lowering_codegen.rs b/tooling/dscs_compiler/src/backend/ir_lowering_codegen.rs index e6069cb..a36ad7f 100644 --- a/tooling/dscs_compiler/src/backend/ir_lowering_codegen.rs +++ b/tooling/dscs_compiler/src/backend/ir_lowering_codegen.rs @@ -1,9 +1,8 @@ +use std::any::Any; use dsa_tasks::ProcessAgent; -use crate::model::ast::{ - Block, Call, Declaration, Expression, Number, Program, Statement, TypeId, Variable, -}; -use crate::model::dsa_ir::{BasicBlock, IRProgram, Label, Literal, Terminator, VirtualReg}; +use crate::model::ast::{BinaryOperator, Block, Call, Declaration, Expression, Number, Program, Statement, TypeId, UnaryOperator, Variable}; +use crate::model::dsa_ir::{BasicBlock, IRProgram, Label, Literal, PrimitiveOperation, PrimitiveUnaryOperation, Terminator, VirtualReg}; use crate::model::{CompilerError, dsa_ir as IR}; pub struct IrLoweringCodeGenerator { @@ -383,8 +382,8 @@ impl IrLoweringCodeGenerator { } // TODO! all of the below: - Expression::Binary { .. } => Ok(()), - Expression::Unary { .. } => Ok(()), + Expression::Binary { op, left, right, .. } => self.collapse_binary_expr(left, right, op, ctx, v_reg), + Expression::Unary { op, operand, .. } => self.collapse_unary_op(operand, op, ctx, v_reg), Expression::UnaryPostfix { .. } => Ok(()), Expression::TypeCast { .. } => Ok(()), Expression::IndexAccess { .. } => Ok(()), @@ -394,6 +393,139 @@ impl IrLoweringCodeGenerator { Expression::StructLiteral { .. } => Ok(()), } } + + fn collapse_unary_op( + &self, + expr: &Expression, + op: &UnaryOperator, + ctx: &mut FunctionContext, + result_reg: VirtualReg, + ) -> Result<(), CompilerError> { + let intermediate_reg = ctx.new_vreg(); + self.collapse_expression(expr, ctx, intermediate_reg)?; + + let ir_stmt = match op { + UnaryOperator::Plus => IR::Statement::UnaryOp { + // use the identity operation as '+x' on an integer 'x' is a no-op + op: PrimitiveUnaryOperation::Identity, + operand: intermediate_reg, + result: result_reg, + }, + + UnaryOperator::Minus => IR::Statement::UnaryOp { + op: PrimitiveUnaryOperation::Negation, + operand: intermediate_reg, + result: result_reg, + }, + + // allocating a vreg for an incrementation isn't very elegant but it can be easily optimised out. + UnaryOperator::Increment => { + let right_vreg = ctx.new_vreg(); + ctx.push_stmt(IR::Statement::Const(right_vreg, Literal::Int(1))); + IR::Statement::BinaryOp { + left: intermediate_reg, + op: PrimitiveOperation::Add, + right: right_vreg, + result: result_reg, + } + }, + UnaryOperator::Decrement => { + let right_vreg = ctx.new_vreg(); + ctx.push_stmt(IR::Statement::Const(right_vreg, Literal::Int(1))); + IR::Statement::BinaryOp { + left: intermediate_reg, + op: PrimitiveOperation::Sub, + right: right_vreg, + result: result_reg, + } + }, + + // TODO: for now we assume all values are primitives. + // hence this implementation is *not correct* for structs and will produce UB. + UnaryOperator::Dereference => IR::Statement::Load { + address: intermediate_reg, + result: result_reg, + }, + + // TODO: this may be quite complex as we have to guarantee ahead of time that this value has a memory address! + UnaryOperator::AddressOf => todo!(), + + // TODO: if the expression is pure, we don't need to evaluate it to know the size. + UnaryOperator::SizeOf => { + let size = expr.type_id() + .map_err(|_| CompilerError::generic("All types are expected to be sized at this stage"))? + .size(); + IR::Statement::Const(result_reg, Literal::Int(size as isize)) + }, + + UnaryOperator::BitwiseNot => IR::Statement::UnaryOp { + op: PrimitiveUnaryOperation::Not, + operand: intermediate_reg, + result: result_reg, + }, + + // TODO: the IR can't represent boolean operators yet. do we add it to the IR or do something here instead? + UnaryOperator::LogicalNot => todo!(), + }; + + ctx.push_stmt(ir_stmt); + + Ok(()) + } + + fn collapse_binary_expr( + &self, + left: &Expression, + right: &Expression, + op: &BinaryOperator, + ctx: &mut FunctionContext, + result_vreg: VirtualReg, + ) -> Result<(), CompilerError> { + let left_vreg = ctx.new_vreg(); + self.collapse_expression(left, ctx, left_vreg)?; + + let right_vreg = ctx.new_vreg(); + self.collapse_expression(right, ctx, right_vreg)?; + + let primitive_operation = match op { + // arithmetic + BinaryOperator::Add => PrimitiveOperation::Add, + BinaryOperator::Sub => PrimitiveOperation::Sub, + + // bitwise logic + BinaryOperator::BitwiseAnd => PrimitiveOperation::And, + BinaryOperator::BitwiseOr => PrimitiveOperation::Or, + BinaryOperator::BitwiseXor => PrimitiveOperation::Xor, + BinaryOperator::LeftShift => PrimitiveOperation::LeftShift, + BinaryOperator::RightShift => PrimitiveOperation::RightShift, + + // comparison logic + BinaryOperator::Equal => PrimitiveOperation::IsEqual, + BinaryOperator::NotEqual => PrimitiveOperation::NotEqual, + BinaryOperator::LessThan => PrimitiveOperation::LessThan, + BinaryOperator::GreaterThan => PrimitiveOperation::GreaterThan, + BinaryOperator::LessOrEqual => PrimitiveOperation::LessOrEqual, + BinaryOperator::GreaterOrEqual => PrimitiveOperation::GreaterOrEqual, + + // boolean logic + BinaryOperator::LogicalAnd => todo!(), + BinaryOperator::LogicalOr => todo!(), + + // non-primitive arithmetic (TODO: requires library support) + BinaryOperator::Mul => todo!(), + BinaryOperator::Div => todo!(), + BinaryOperator::Mod => todo!(), + }; + + ctx.push_stmt(IR::Statement::BinaryOp { + left: left_vreg, + op: primitive_operation, + right: right_vreg, + result: result_vreg, + }); + + Ok(()) + } } struct BasicBlockBuilder { diff --git a/tooling/dscs_compiler/src/model/dsa_ir.rs b/tooling/dscs_compiler/src/model/dsa_ir.rs index c854ce3..0869336 100644 --- a/tooling/dscs_compiler/src/model/dsa_ir.rs +++ b/tooling/dscs_compiler/src/model/dsa_ir.rs @@ -12,6 +12,11 @@ pub enum Statement { right: VirtualReg, result: VirtualReg, }, + UnaryOp { + op: PrimitiveUnaryOperation, + operand: VirtualReg, + result: VirtualReg, + }, Copy { src: VirtualReg, dst: VirtualReg, @@ -124,6 +129,18 @@ pub enum Literal { Char(char), } +#[derive(Clone, Debug)] +pub enum PrimitiveUnaryOperation { + // used by compilers for no-ops such as the unary addition operation + // this should be optimised out in almost all cases. + Identity, + // negate a given value + Negation, + // Invert the bits of the given operation. + Not, +} + + #[derive(Clone, Debug)] pub enum PrimitiveOperation { Add, @@ -133,6 +150,16 @@ pub enum PrimitiveOperation { Xor, Nor, Nand, + + LeftShift, + RightShift, + + IsEqual, + NotEqual, + LessThan, + LessOrEqual, + GreaterThan, + GreaterOrEqual, } impl fmt::Display for VirtualReg { @@ -158,13 +185,32 @@ impl fmt::Display for Literal { impl fmt::Display for PrimitiveOperation { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let s = match self { - PrimitiveOperation::Add => "+", - PrimitiveOperation::Sub => "-", - PrimitiveOperation::And => "&", - PrimitiveOperation::Or => "|", - PrimitiveOperation::Xor => "^", - PrimitiveOperation::Nor => "nor", - PrimitiveOperation::Nand => "nand", + Self::Add => "+", + Self::Sub => "-", + Self::And => "&", + Self::Or => "|", + Self::Xor => "^", + Self::Nor => "nor", + Self::Nand => "nand", + Self::LeftShift => "<<", + Self::RightShift => ">>", + Self::IsEqual => "==", + Self::NotEqual => "!=", + Self::LessThan => "<", + Self::LessOrEqual => "<=", + Self::GreaterThan => ">", + Self::GreaterOrEqual => ">=", + }; + write!(f, "{}", s) + } +} + +impl fmt::Display for PrimitiveUnaryOperation { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let s = match self { + Self::Identity => "+", + Self::Negation => "-", + Self::Not => "~", }; write!(f, "{}", s) } @@ -174,6 +220,13 @@ impl fmt::Display for Statement { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Statement::Const(dst, lit) => write!(f, "{} = literal {}", dst, lit), + Statement::UnaryOp { + op, + operand, + result, + } => { + write!(f, "{} = {}{}", result, op, operand) + } Statement::BinaryOp { left, op,