From cc6add979fc1f2816d1294754a9b966ab30905dc Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 25 Apr 2023 11:33:24 -0500 Subject: [PATCH 1/2] Implement binary instructions --- .../src/ssa_refactor/ir/instruction.rs | 74 ++++++++++++++----- .../ssa_builder/function_builder.rs | 21 ++++-- .../src/ssa_refactor/ssa_gen/context.rs | 74 +++++++++++++++++++ .../src/ssa_refactor/ssa_gen/mod.rs | 50 ++++++++++--- .../src/ssa_refactor/ssa_gen/value.rs | 1 + 5 files changed, 186 insertions(+), 34 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 442f1dbd47e..4d0305a03d5 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -170,32 +170,66 @@ 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, + /// 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, + /// Bitiwse or (|) + Or, + /// Bitwise xor (^) + Xor, + /// Shift lhs left by rhs bits (<<) + Shl, + /// Shift lhs right by rhs bits (>>) + Shr, +} + +impl From for BinaryOp { + fn from(value: noirc_frontend::BinaryOpKind) -> Self { + match value { + noirc_frontend::BinaryOpKind::Add => todo!(), + noirc_frontend::BinaryOpKind::Subtract => todo!(), + noirc_frontend::BinaryOpKind::Multiply => todo!(), + noirc_frontend::BinaryOpKind::Divide => todo!(), + noirc_frontend::BinaryOpKind::Equal => todo!(), + noirc_frontend::BinaryOpKind::NotEqual => todo!(), + noirc_frontend::BinaryOpKind::Less => todo!(), + noirc_frontend::BinaryOpKind::LessEqual => todo!(), + noirc_frontend::BinaryOpKind::Greater => todo!(), + noirc_frontend::BinaryOpKind::GreaterEqual => todo!(), + noirc_frontend::BinaryOpKind::And => todo!(), + noirc_frontend::BinaryOpKind::Or => todo!(), + noirc_frontend::BinaryOpKind::Xor => todo!(), + noirc_frontend::BinaryOpKind::ShiftRight => todo!(), + noirc_frontend::BinaryOpKind::ShiftLeft => todo!(), + noirc_frontend::BinaryOpKind::Modulo => todo!(), + } + } } impl std::fmt::Display for BinaryOp { @@ -206,7 +240,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"), } } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs index c76d2943abe..7911aa2988a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs @@ -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] + } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs index 32133feea13..bdb732551c7 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -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, @@ -123,6 +125,78 @@ impl<'a> FunctionContext<'a> { ast::Type::Vec(_) => Type::Reference, } } + + 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(); + 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) +} + +/// Lossily (!) 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 { + 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 { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index 2f9c6646282..3b469ad9664 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -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); @@ -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!() } @@ -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; @@ -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 { @@ -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 { @@ -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() } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs index c3911d367c1..83a5d15c904 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs @@ -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 { Branch(Vec>), Leaf(T), From 378b35a10e1fe3c6052b9dcfac53b3b0b049f966 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 25 Apr 2023 11:43:57 -0500 Subject: [PATCH 2/2] Cleanup PR --- .../src/ssa_refactor/ir/instruction.rs | 25 +------------------ .../src/ssa_refactor/ssa_gen/context.rs | 4 ++- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 4d0305a03d5..9b5aeb9388c 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -199,7 +199,7 @@ pub(crate) enum BinaryOp { Lt, /// Bitwise and (&) And, - /// Bitiwse or (|) + /// Bitwise or (|) Or, /// Bitwise xor (^) Xor, @@ -209,29 +209,6 @@ pub(crate) enum BinaryOp { Shr, } -impl From for BinaryOp { - fn from(value: noirc_frontend::BinaryOpKind) -> Self { - match value { - noirc_frontend::BinaryOpKind::Add => todo!(), - noirc_frontend::BinaryOpKind::Subtract => todo!(), - noirc_frontend::BinaryOpKind::Multiply => todo!(), - noirc_frontend::BinaryOpKind::Divide => todo!(), - noirc_frontend::BinaryOpKind::Equal => todo!(), - noirc_frontend::BinaryOpKind::NotEqual => todo!(), - noirc_frontend::BinaryOpKind::Less => todo!(), - noirc_frontend::BinaryOpKind::LessEqual => todo!(), - noirc_frontend::BinaryOpKind::Greater => todo!(), - noirc_frontend::BinaryOpKind::GreaterEqual => todo!(), - noirc_frontend::BinaryOpKind::And => todo!(), - noirc_frontend::BinaryOpKind::Or => todo!(), - noirc_frontend::BinaryOpKind::Xor => todo!(), - noirc_frontend::BinaryOpKind::ShiftRight => todo!(), - noirc_frontend::BinaryOpKind::ShiftLeft => todo!(), - noirc_frontend::BinaryOpKind::Modulo => todo!(), - } - } -} - impl std::fmt::Display for BinaryOp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs index bdb732551c7..8f7b4e3de9a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -126,6 +126,8 @@ impl<'a> FunctionContext<'a> { } } + /// 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() } @@ -173,7 +175,7 @@ fn operator_requires_swapped_operands(op: noirc_frontend::BinaryOpKind) -> bool matches!(op, Greater | LessEqual) } -/// Lossily (!) converts the given operator to the appropriate BinaryOp. +/// 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.