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: process blocks and jumps when compiling brillig #1591

Merged
merged 6 commits into from
Jun 7, 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 @@
x = "1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Tests a very simple program.
//
// The features being tested is basic conditonal on brillig
fn main(x: Field) {
assert(4 == conditional(x as bool));
}

unconstrained fn conditional(x : bool) -> Field {
if x {
4
}else {
5
}
}
85 changes: 79 additions & 6 deletions crates/noirc_evaluator/src/brillig/artifact.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,98 @@
use crate::ssa_refactor::ir::basic_block::BasicBlockId;
use acvm::acir::brillig_vm::Opcode as BrilligOpcode;
use std::collections::HashMap;

/// Pointer to a unresolved Jump instruction in
/// the bytecode.
pub(crate) type JumpLabel = usize;

/// Pointer to a position in the bytecode where a
/// particular basic block starts.
pub(crate) type BlockLabel = usize;

#[derive(Default, Debug, Clone)]
/// Artifacts resulting from the compilation of a function into brillig byte code
/// Currently it is just the brillig bytecode of the function
/// Artifacts resulting from the compilation of a function into brillig byte code.
/// Currently it is just the brillig bytecode of the function.
pub(crate) struct BrilligArtifact {
pub(crate) byte_code: Vec<BrilligOpcode>,
/// The set of jumps that need to have their locations
/// resolved.
unresolved_jumps: Vec<(JumpLabel, BasicBlockId)>,
/// A map of the basic blocks to their positions
/// in the bytecode.
blocks: HashMap<BasicBlockId, BlockLabel>,
}

impl BrilligArtifact {
// Link some compiled brillig bytecode with its referenced artifacts
/// Link some compiled brillig bytecode with its referenced artifacts.
pub(crate) fn link(&mut self, obj: &BrilligArtifact) -> Vec<BrilligOpcode> {
self.link_with(obj);
self.resolve_jumps();
self.byte_code.clone()
}

// Link with a brillig artifact
/// Link with a brillig artifact
fn link_with(&mut self, obj: &BrilligArtifact) {
if obj.byte_code.is_empty() {
panic!("ICE: unresolved symbol");
let offset = self.code_len();
for (jump_label, block_id) in &obj.unresolved_jumps {
self.unresolved_jumps.push((jump_label + offset, *block_id));
}

for (block_id, block_label) in &obj.blocks {
self.blocks.insert(*block_id, block_label + offset);
}

self.byte_code.extend_from_slice(&obj.byte_code);
}

/// Adds a unresolved jump to be fixed at the end of bytecode processing.
pub(crate) fn add_unresolved_jump(&mut self, destination: BasicBlockId) {
self.unresolved_jumps.push((self.code_len(), destination));
}

/// Adds a label in the bytecode to specify where this block's
/// opcodes will start.
pub(crate) fn add_block_label(&mut self, block: BasicBlockId) {
self.blocks.insert(block, self.code_len());
}

/// Number of the opcodes currently in the bytecode
pub(crate) fn code_len(&self) -> usize {
self.byte_code.len()
}

/// Resolves all of the unresolved jumps in the program.
///
/// Note: This should only be called once all blocks are processed.
fn resolve_jumps(&mut self) {
for (jump_label, block) in &self.unresolved_jumps {
let jump_instruction = self.byte_code[*jump_label].clone();

let actual_block_location = self.blocks[block];

match jump_instruction {
BrilligOpcode::Jump { location } => {
assert_eq!(location, 0, "location is not zero, which means that the jump label does not need resolving");

self.byte_code[*jump_label] =
BrilligOpcode::Jump { location: actual_block_location };
}
BrilligOpcode::JumpIfNot { condition, location } => {
assert_eq!(location, 0, "location is not zero, which means that the jump label does not need resolving");

self.byte_code[*jump_label] =
BrilligOpcode::JumpIfNot { condition, location: actual_block_location };
}
BrilligOpcode::JumpIf { condition, location } => {
assert_eq!(location, 0,"location is not zero, which means that the jump label does not need resolving");

self.byte_code[*jump_label] =
BrilligOpcode::JumpIf { condition, location: actual_block_location };
}
_ => unreachable!(
"all jump labels should point to a jump instruction in the bytecode"
),
}
}
}
}
77 changes: 59 additions & 18 deletions crates/noirc_evaluator/src/brillig/brillig_gen.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
use std::collections::HashMap;

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

use super::artifact::BrilligArtifact;

use acvm::acir::brillig_vm::{
BinaryFieldOp, BinaryIntOp, Opcode as BrilligOpcode, RegisterIndex, Value as BrilligValue,
};
use std::collections::HashMap;

#[derive(Default)]
/// Generate the compilation artifacts for compiling a function into brillig bytecode.
pub(crate) struct BrilligGen {
Expand Down Expand Up @@ -47,14 +46,48 @@ impl BrilligGen {
}

/// Converts an SSA Basic block into a sequence of Brillig opcodes
fn convert_block(&mut self, block: &BasicBlock, dfg: &DataFlowGraph) {
fn convert_block(&mut self, block_id: BasicBlockId, dfg: &DataFlowGraph) {
self.obj.add_block_label(block_id);
let block = &dfg[dbg!(block_id)];
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);
// Jump to the next block
let jump = block.terminator().expect("block is expected to be constructed");
match jump {
TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => {
let condition = self.convert_ssa_value(*condition, dfg);
self.jump_if(condition, *then_destination);
self.jump(*else_destination);
}
TerminatorInstruction::Jmp { destination, arguments } => {
let target = &dfg[*destination];
for (src, dest) in arguments.iter().zip(target.parameters()) {
let destination = self.convert_ssa_value(*dest, dfg);
let source = self.convert_ssa_value(*src, dfg);
self.push_code(BrilligOpcode::Mov { destination, source });
}
self.jump(*destination);
}
TerminatorInstruction::Return { return_values } => {
self.convert_ssa_return(return_values, dfg);
}
}
}

/// Adds a unresolved `Jump` instruction to the bytecode.
fn jump(&mut self, target: BasicBlockId) {
self.obj.add_unresolved_jump(target);
self.push_code(BrilligOpcode::Jump { location: 0 });
}

/// Adds a unresolved `JumpIf` instruction to the bytecode.
fn jump_if(&mut self, condition: RegisterIndex, target: BasicBlockId) {
self.obj.add_unresolved_jump(target);
self.push_code(BrilligOpcode::JumpIf { condition, location: 0 });
}

/// Converts the SSA return instruction into the necessary BRillig return
Expand All @@ -63,12 +96,7 @@ impl BrilligGen {
/// 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"),
};

fn convert_ssa_return(&mut self, return_values: &[ValueId], dfg: &DataFlowGraph) {
// Check if the program returns the `Unit/None` type.
// This type signifies that the program returns nothing.
let is_return_unit_type =
Expand Down Expand Up @@ -171,7 +199,7 @@ impl BrilligGen {
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]
self.get_or_create_register(value_id)
}
Value::NumericConstant { constant, .. } => {
let register_index = self.get_or_create_register(value_id);
Expand All @@ -193,14 +221,27 @@ impl BrilligGen {
pub(crate) fn compile(func: &Function) -> BrilligArtifact {
let mut brillig = BrilligGen::default();

let dfg = &func.dfg;

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

brillig.push_code(BrilligOpcode::Stop);

brillig.obj
}

/// Converting an SSA function into Brillig bytecode.
///
/// TODO: Change this to use `dfg.basic_blocks_iter` which will return an
/// TODO iterator of all of the basic blocks.
/// TODO(Jake): what order is this ^
fn convert_ssa_function(&mut self, func: &Function) {
let mut reverse_post_order = Vec::new();
reverse_post_order.extend_from_slice(PostOrder::with_function(func).as_slice());
reverse_post_order.reverse();

for block in reverse_post_order {
self.convert_block(block, &func.dfg);
}
}
}

/// Type to encapsulate the binary operation types in Brillig
Expand Down