Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(ssa refactor): Implement ssa-gen for binary, block, tuple, extract-tuple-field, and semi expressions #1217

Merged
merged 2 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,32 +170,43 @@ pub(crate) struct Binary {
}

/// Binary Operations allowed in the IR.
/// Aside from the comparison operators (Eq and Lt), all operators
/// will return the same type as their operands.
/// The operand types must match for all binary operators.
/// All binary operators are also only for numeric types. To implement
/// e.g. equality for a compound type like a struct, one must add a
/// separate Eq operation for each field and combine them later with And.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(crate) enum BinaryOp {
/// Addition of two types.
/// The result will have the same type as
/// the operands.
/// Addition of lhs + rhs.
Add,
/// Subtraction of two types.
/// The result will have the same type as
/// the operands.
/// Subtraction of lhs - rhs.
Sub,
/// Multiplication of two types.
/// The result will have the same type as
/// the operands.
/// Multiplication of lhs * rhs.
Mul,
/// Division of two types.
/// The result will have the same type as
/// the operands.
/// Division of lhs / rhs.
Div,
/// Modulus of lhs % rhs.
Mod,
/// Checks whether two types are equal.
/// Returns true if the types were equal and
/// false otherwise.
Eq,
/// Checks whether two types are equal.
/// Returns true if the types were not equal and
/// false otherwise.
Neq,
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
/// Checks whether the lhs is less than the rhs.
/// All other comparison operators should be translated
/// to less than. For example (a > b) = (b < a) = !(a >= b) = !(b <= a).
/// The result will always be a u1.
Lt,
/// Bitwise and (&)
And,
/// Bitwise or (|)
Or,
/// Bitwise xor (^)
Xor,
/// Shift lhs left by rhs bits (<<)
Shl,
/// Shift lhs right by rhs bits (>>)
Shr,
}

impl std::fmt::Display for BinaryOp {
Expand All @@ -206,7 +217,13 @@ impl std::fmt::Display for BinaryOp {
BinaryOp::Mul => write!(f, "mul"),
BinaryOp::Div => write!(f, "div"),
BinaryOp::Eq => write!(f, "eq"),
BinaryOp::Neq => write!(f, "neq"),
BinaryOp::Mod => write!(f, "mod"),
BinaryOp::Lt => write!(f, "lt"),
BinaryOp::And => write!(f, "and"),
BinaryOp::Or => write!(f, "or"),
BinaryOp::Xor => write!(f, "xor"),
BinaryOp::Shl => write!(f, "shl"),
BinaryOp::Shr => write!(f, "shr"),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,23 @@ impl<'ssa> FunctionBuilder<'ssa> {
self.insert_instruction(Instruction::Store { address, value });
}

/// Insert a Store instruction at the end of the current block, storing the given element
/// at the given address. Expects that the address points to a previous Allocate instruction.
/// Returns the result of the add instruction.
pub(crate) fn insert_add(&mut self, lhs: ValueId, rhs: ValueId, typ: Type) -> ValueId {
let operator = BinaryOp::Add;
/// Insert a binary instruction at the end of the current block.
/// Returns the result of the binary instruction.
pub(crate) fn insert_binary(
&mut self,
lhs: ValueId,
operator: BinaryOp,
rhs: ValueId,
typ: Type,
) -> ValueId {
let id = self.insert_instruction(Instruction::Binary(Binary { lhs, rhs, operator }));
self.current_function.dfg.make_instruction_results(id, typ)[0]
}

/// Insert a not instruction at the end of the current block.
/// Returns the result of the instruction.
pub(crate) fn insert_not(&mut self, rhs: ValueId, typ: Type) -> ValueId {
let id = self.insert_instruction(Instruction::Not(rhs));
self.current_function.dfg.make_instruction_results(id, typ)[0]
}
}
76 changes: 76 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use noirc_frontend::monomorphization::ast::{self, LocalId, Parameters};
use noirc_frontend::monomorphization::ast::{FuncId, Program};
use noirc_frontend::Signedness;

use crate::ssa_refactor::ir::instruction::BinaryOp;
use crate::ssa_refactor::ir::types::Type;
use crate::ssa_refactor::ir::value::ValueId;
use crate::ssa_refactor::ssa_builder::SharedBuilderContext;
use crate::ssa_refactor::{
ir::function::FunctionId as IrFunctionId, ssa_builder::function_builder::FunctionBuilder,
Expand Down Expand Up @@ -123,6 +125,80 @@ impl<'a> FunctionContext<'a> {
ast::Type::Vec(_) => Type::Reference,
}
}

/// Insert a unit constant into the current function if not already
/// present, and return its value
pub(super) fn unit_value(&mut self) -> Values {
self.builder.numeric_constant(0u128.into(), Type::Unit).into()
}

/// Insert a binary instruction at the end of the current block.
/// Converts the form of the binary instruction as necessary
/// (e.g. swapping arguments, inserting a not) to represent it in the IR.
/// For example, (a <= b) is represented as !(b < a)
pub(super) fn insert_binary(
&mut self,
mut lhs: ValueId,
operator: noirc_frontend::BinaryOpKind,
mut rhs: ValueId,
) -> Values {
let op = convert_operator(operator);

if operator_requires_swapped_operands(operator) {
std::mem::swap(&mut lhs, &mut rhs);
}

// TODO: Rework how types are stored.
// They should be on values rather than on instruction results
let typ = Type::field();
jfecher marked this conversation as resolved.
Show resolved Hide resolved
let mut result = self.builder.insert_binary(lhs, op, rhs, typ);

if operator_requires_not(operator) {
result = self.builder.insert_not(result, typ);
}
result.into()
}
}

/// True if the given operator cannot be encoded directly and needs
/// to be represented as !(some other operator)
fn operator_requires_not(op: noirc_frontend::BinaryOpKind) -> bool {
use noirc_frontend::BinaryOpKind::*;
matches!(op, NotEqual | LessEqual | GreaterEqual)
}

/// True if the given operator cannot be encoded directly and needs
/// to have its lhs and rhs swapped to be represented with another operator.
/// Example: (a > b) needs to be represented as (b < a)
fn operator_requires_swapped_operands(op: noirc_frontend::BinaryOpKind) -> bool {
use noirc_frontend::BinaryOpKind::*;
matches!(op, Greater | LessEqual)
}

/// Converts the given operator to the appropriate BinaryOp.
/// Take care when using this to insert a binary instruction: this requires
/// checking operator_requires_not and operator_requires_swapped_operands
/// to represent the full operation correctly.
fn convert_operator(op: noirc_frontend::BinaryOpKind) -> BinaryOp {
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
use noirc_frontend::BinaryOpKind;
match op {
BinaryOpKind::Add => BinaryOp::Add,
BinaryOpKind::Subtract => BinaryOp::Sub,
BinaryOpKind::Multiply => BinaryOp::Mul,
BinaryOpKind::Divide => BinaryOp::Div,
BinaryOpKind::Modulo => BinaryOp::Mod,
BinaryOpKind::Equal => BinaryOp::Eq,
BinaryOpKind::NotEqual => BinaryOp::Eq, // Requires not
BinaryOpKind::Less => BinaryOp::Lt,
BinaryOpKind::Greater => BinaryOp::Lt, // Requires operand swap
BinaryOpKind::LessEqual => BinaryOp::Lt, // Requires operand swap and not
BinaryOpKind::GreaterEqual => BinaryOp::Lt, // Requires not
BinaryOpKind::And => BinaryOp::And,
BinaryOpKind::Or => BinaryOp::Or,
BinaryOpKind::Xor => BinaryOp::Xor,
BinaryOpKind::ShiftRight => BinaryOp::Shr,
BinaryOpKind::ShiftLeft => BinaryOp::Shl,
}
}

impl SharedContext {
Expand Down
50 changes: 38 additions & 12 deletions crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use self::{
value::{Tree, Values},
};

use super::{ir::types::Type, ssa_builder::SharedBuilderContext};
use super::{
ir::{instruction::BinaryOp, types::Type, value::ValueId},
ssa_builder::SharedBuilderContext,
};

pub(crate) fn generate_ssa(program: Program) {
let context = SharedContext::new(program);
Expand Down Expand Up @@ -58,6 +61,17 @@ impl<'a> FunctionContext<'a> {
}
}

/// Codegen any non-tuple expression so that we can unwrap the Values
/// tree to return a single value for use with most SSA instructions.
fn codegen_non_tuple_expression(&mut self, expr: &Expression) -> ValueId {
match self.codegen_expression(expr) {
Tree::Branch(branches) => {
panic!("codegen_non_tuple_expression called on tuple {branches:?}")
}
Tree::Leaf(value) => value.eval(),
}
}

fn codegen_ident(&mut self, _ident: &ast::Ident) -> Values {
todo!()
}
Expand Down Expand Up @@ -103,7 +117,7 @@ impl<'a> FunctionContext<'a> {
array
} else {
let offset = self.builder.numeric_constant((i as u128).into(), Type::field());
self.builder.insert_add(array, offset, Type::field())
self.builder.insert_binary(array, BinaryOp::Add, offset, Type::field())
};
self.builder.insert_store(address, value.eval());
i += 1;
Expand All @@ -113,16 +127,22 @@ impl<'a> FunctionContext<'a> {
array.into()
}

fn codegen_block(&mut self, _block: &[Expression]) -> Values {
todo!()
fn codegen_block(&mut self, block: &[Expression]) -> Values {
let mut result = self.unit_value();
for expr in block {
result = self.codegen_expression(expr);
}
result
}

fn codegen_unary(&mut self, _unary: &ast::Unary) -> Values {
todo!()
}

fn codegen_binary(&mut self, _binary: &ast::Binary) -> Values {
todo!()
fn codegen_binary(&mut self, binary: &ast::Binary) -> Values {
let lhs = self.codegen_non_tuple_expression(&binary.lhs);
let rhs = self.codegen_non_tuple_expression(&binary.rhs);
self.insert_binary(lhs, binary.operator, rhs)
}

fn codegen_index(&mut self, _index: &ast::Index) -> Values {
Expand All @@ -141,12 +161,17 @@ impl<'a> FunctionContext<'a> {
todo!()
}

fn codegen_tuple(&mut self, _tuple: &[Expression]) -> Values {
todo!()
fn codegen_tuple(&mut self, tuple: &[Expression]) -> Values {
Tree::Branch(vecmap(tuple, |expr| self.codegen_expression(expr)))
}

fn codegen_extract_tuple_field(&mut self, _tuple: &Expression, _index: usize) -> Values {
todo!()
fn codegen_extract_tuple_field(&mut self, tuple: &Expression, index: usize) -> Values {
match self.codegen_expression(tuple) {
Tree::Branch(mut trees) => trees.remove(index),
Tree::Leaf(value) => {
unreachable!("Tried to extract tuple index {index} from non-tuple {value:?}")
}
}
}

fn codegen_call(&mut self, _call: &ast::Call) -> Values {
Expand All @@ -165,7 +190,8 @@ impl<'a> FunctionContext<'a> {
todo!()
}

fn codegen_semi(&mut self, _semi: &Expression) -> Values {
todo!()
fn codegen_semi(&mut self, expr: &Expression) -> Values {
self.codegen_expression(expr);
self.unit_value()
}
}
1 change: 1 addition & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::ssa_refactor::ir::function::FunctionId as IrFunctionId;
use crate::ssa_refactor::ir::types::Type;
use crate::ssa_refactor::ir::value::ValueId as IrValueId;

#[derive(Debug)]
pub(super) enum Tree<T> {
Branch(Vec<Tree<T>>),
Leaf(T),
Expand Down