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

feat(brillig): added arithmetic operations on brillig #1565

Merged
merged 4 commits into from
Jun 6, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.1"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
x = "3"
y = "4"
result = "7"
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Tests a very simple program.
//
// The features being tested is basic arithmetics on brillig
fn main(x: Field, y: Field, result: Field) {
assert(result == add(x, y));
}

unconstrained fn add(x : Field, y : Field) -> Field {
x + y
}
217 changes: 212 additions & 5 deletions crates/noirc_evaluator/src/brillig/brillig_gen.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,231 @@
use crate::ssa_refactor::ir::function::Function;
use std::collections::HashMap;

use crate::ssa_refactor::ir::{
basic_block::BasicBlock,
dfg::DataFlowGraph,
function::Function,
instruction::{Binary, BinaryOp, Instruction, InstructionId, TerminatorInstruction},
types::{NumericType, Type},
value::{Value, ValueId},
};

use super::artifact::BrilligArtifact;

use acvm::acir::brillig_vm::Opcode as BrilligOpcode;
use acvm::acir::brillig_vm::{
BinaryFieldOp, BinaryIntOp, Opcode as BrilligOpcode, RegisterIndex, Value as BrilligValue,
};
#[derive(Default)]
/// Generate the compilation artifacts for compiling a function into brillig bytecode.
pub(crate) struct BrilligGen {
obj: BrilligArtifact,
/// A usize indicating the latest un-used register.
latest_register: usize,
/// Map from SSA values to Register Indices.
ssa_value_to_register: HashMap<ValueId, RegisterIndex>,
}

impl BrilligGen {
/// Adds a brillig instruction to the brillig code base
/// Adds a brillig instruction to the brillig byte code
fn push_code(&mut self, code: BrilligOpcode) {
self.obj.byte_code.push(code);
}

/// Gets a `RegisterIndex` for a `ValueId`, if one already exists
/// or creates a new `RegisterIndex` using the latest available
/// free register.
fn get_or_create_register(&mut self, value: ValueId) -> RegisterIndex {
if let Some(register_index) = self.ssa_value_to_register.get(&value) {
return *register_index;
}

let register = RegisterIndex::from(self.latest_register);
self.ssa_value_to_register.insert(value, register);

self.latest_register += 1;

register
}

/// Converts an SSA Basic block into a sequence of Brillig opcodes
fn convert_block(&mut self, block: &BasicBlock, dfg: &DataFlowGraph) {
self.convert_block_params(block, dfg);

for instruction_id in block.instructions() {
self.convert_ssa_instruction(*instruction_id, dfg);
}

self.convert_ssa_return(block, dfg);
}

/// Converts the SSA return instruction into the necessary BRillig return
/// opcode.
///
/// For Brillig, the return is implicit; The caller will take `N` values from
/// the Register starting at register index 0. `N` indicates the number of
/// return values expected.
fn convert_ssa_return(&mut self, block: &BasicBlock, dfg: &DataFlowGraph) {
let return_values = match block.terminator().unwrap() {
TerminatorInstruction::Return { return_values } => return_values,
_ => todo!("ICE: Unsupported return"),
};

// Check if the program returns the `Unit/None` type.
// This type signifies that the program returns nothing.
let is_return_unit_type =
return_values.len() == 1 && dfg.type_of_value(return_values[0]) == Type::Unit;
if is_return_unit_type {
return;
}

for (destination_index, value_id) in return_values.iter().enumerate() {
let return_register = self.convert_ssa_value(*value_id, dfg);
if destination_index > self.latest_register {
self.latest_register = destination_index;
}
self.push_code(BrilligOpcode::Mov {
destination: destination_index.into(),
source: return_register,
});
}
}

/// Converts SSA Block parameters into Brillig Registers.
fn convert_block_params(&mut self, block: &BasicBlock, dfg: &DataFlowGraph) {
for param_id in block.parameters() {
let value = &dfg[*param_id];
let param_type = match value {
Value::Param { typ, .. } => typ,
_ => unreachable!("ICE: Only Param type values should appear in block parameters"),
};
match param_type {
Type::Numeric(_) => {
self.get_or_create_register(*param_id);
}
_ => {
todo!("ICE: Param type not supported")
}
}
}
}

/// Converts an SSA instruction into a sequence of Brillig opcodes.
fn convert_ssa_instruction(&mut self, instruction_id: InstructionId, dfg: &DataFlowGraph) {
let instruction = &dfg[instruction_id];

match instruction {
Instruction::Binary(binary) => {
let result_ids = dfg.instruction_results(instruction_id);
let result_register = self.get_or_create_register(result_ids[0]);
self.convert_ssa_binary(binary, dfg, result_register);
}
_ => todo!("ICE: Instruction not supported"),
};
}

/// Converts the Binary instruction into a sequence of Brillig opcodes.
fn convert_ssa_binary(
&mut self,
binary: &Binary,
dfg: &DataFlowGraph,
result_register: RegisterIndex,
) {
let left_type = dfg[binary.lhs].get_type();
let right_type = dfg[binary.rhs].get_type();
if left_type != right_type {
todo!("ICE: Binary operands must have the same type")
}

let left = self.convert_ssa_value(binary.lhs, dfg);
let right = self.convert_ssa_value(binary.rhs, dfg);

let opcode = match left_type {
Type::Numeric(numeric_type) => match numeric_type {
NumericType::NativeField => {
let op = match binary.operator {
BinaryOp::Add => BinaryFieldOp::Add,
BinaryOp::Sub => BinaryFieldOp::Sub,
BinaryOp::Mul => BinaryFieldOp::Mul,
BinaryOp::Div => BinaryFieldOp::Div,
BinaryOp::Eq => BinaryFieldOp::Equals,
_ => todo!("ICE: Binary operator not supported for field type"),
};
BrilligOpcode::BinaryFieldOp {
op,
destination: result_register,
lhs: left,
rhs: right,
}
}
NumericType::Signed { bit_size } | NumericType::Unsigned { bit_size } => {
let op = match binary.operator {
BinaryOp::Add => BinaryIntOp::Add,
BinaryOp::Sub => BinaryIntOp::Sub,
BinaryOp::Mul => BinaryIntOp::Mul,
BinaryOp::Div => match numeric_type {
NumericType::Signed { .. } => BinaryIntOp::SignedDiv,
NumericType::Unsigned { .. } => BinaryIntOp::UnsignedDiv,
_ => unreachable!("ICE: Binary type not supported"),
},
BinaryOp::Eq => BinaryIntOp::Equals,
BinaryOp::Lt => BinaryIntOp::LessThan,
BinaryOp::Shl => BinaryIntOp::Shl,
BinaryOp::Shr => BinaryIntOp::Shr,
BinaryOp::Xor => BinaryIntOp::Xor,
BinaryOp::Or => BinaryIntOp::Or,
BinaryOp::And => BinaryIntOp::And,
BinaryOp::Mod => todo!("modulo operation does not have a 1-1 binary operation and so we delay this implementation"),
};
BrilligOpcode::BinaryIntOp {
op,
destination: result_register,
bit_size,
lhs: left,
rhs: right,
}
}
},
_ => {
todo!("ICE: Binary type not supported")
}
};

self.push_code(opcode);
}

/// Converts an SSA `ValueId` into a `RegisterIndex`.
fn convert_ssa_value(&mut self, value_id: ValueId, dfg: &DataFlowGraph) -> RegisterIndex {
let value = &dfg[value_id];

let register = match value {
Value::Param { .. } | Value::Instruction { .. } => {
// All block parameters and instruction results should have already been
// converted to registers so we fetch from the cache.
self.ssa_value_to_register[&value_id]
}
Value::NumericConstant { constant, .. } => {
let register_index = self.get_or_create_register(value_id);
self.push_code(BrilligOpcode::Const {
destination: register_index,
value: BrilligValue::from(*constant),
});
register_index
}
_ => {
todo!("ICE: Should have been in cache {value:?}")
}
};
register
}

/// Compiles an SSA function into a Brillig artifact which
/// contains a sequence of SSA opcodes.
pub(crate) fn compile(func: &Function) -> BrilligArtifact {
let mut brillig = BrilligGen::default();
// we only support empty functions for now
assert_eq!(func.dfg.num_instructions(), 0);

let dfg = &func.dfg;

brillig.convert_block(&dfg[func.entry_block()], dfg);

brillig.push_code(BrilligOpcode::Stop);

brillig.obj
Expand Down