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): Add unique labeling and remove relative jumps #1652

Merged
merged 9 commits into from
Jun 15, 2023
436 changes: 21 additions & 415 deletions crates/noirc_evaluator/src/brillig/brillig_gen.rs

Large diffs are not rendered by default.

409 changes: 409 additions & 0 deletions crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions crates/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::collections::HashMap;

use acvm::acir::brillig_vm::RegisterIndex;

use crate::{
brillig::brillig_ir::BrilligContext,
ssa_refactor::ir::{function::FunctionId, value::ValueId},
};

pub(crate) struct FunctionContext {
pub(crate) function_id: FunctionId,
/// Map from SSA values to Register Indices.
pub(crate) ssa_value_to_register: HashMap<ValueId, RegisterIndex>,
}

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

let register = brillig_context.create_register();

// Cache the `ValueId` so that if we call it again, it will
// return the register that has just been created.
//
// WARNING: This assumes that a register has not been
// modified. If a MOV instruction has overwritten the value
// at a register, then this cache will be invalid.
self.ssa_value_to_register.insert(value, register);

register
}
}
46 changes: 38 additions & 8 deletions crates/noirc_evaluator/src/brillig/brillig_ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ pub(crate) struct BrilligContext {
latest_register: usize,
/// Tracks memory allocations
memory: BrilligMemory,
/// Context label, must be unique with respect to the function
/// being linked.
context_label: String,
/// Section label, used to separate sections of code
section_label: usize,
}

impl BrilligContext {
Expand All @@ -64,16 +69,41 @@ impl BrilligContext {
}

/// Adds a label to the next opcode
pub(crate) fn add_label_to_next_opcode<T: ToString>(&mut self, label: T) {
pub(crate) fn enter_context<T: ToString>(&mut self, label: T) {
self.context_label = label.to_string();
self.section_label = 0;
// Add a context label to the next opcode
self.obj.add_label_at_position(label.to_string(), self.obj.index_of_next_opcode());
// Add a section label to the next opcode
self.obj
.add_label_at_position(self.current_section_label(), self.obj.index_of_next_opcode());
}

/// Increments the section label and adds a section label to the next opcode
fn enter_next_section(&mut self) {
self.section_label += 1;
self.obj
.add_label_at_position(self.current_section_label(), self.obj.index_of_next_opcode());
}

/// Internal function used to compute the section labels
fn compute_section_label(&self, section: usize) -> String {
format!("{}-{}", self.context_label, section)
}

/// Returns the next section label
fn next_section_label(&self) -> String {
self.compute_section_label(self.section_label + 1)
}

/// Returns the current section label
fn current_section_label(&self) -> String {
self.compute_section_label(self.section_label)
}

/// Adds a unresolved `Jump` instruction to the bytecode.
pub(crate) fn jump_instruction<T: ToString>(&mut self, target_label: T) {
self.add_unresolved_jump(
BrilligOpcode::Jump { location: 0 },
UnresolvedJumpLocation::Label(target_label.to_string()),
);
self.add_unresolved_jump(BrilligOpcode::Jump { location: 0 }, target_label.to_string());
}

/// Adds a unresolved `JumpIf` instruction to the bytecode.
Expand All @@ -84,7 +114,7 @@ impl BrilligContext {
) {
self.add_unresolved_jump(
BrilligOpcode::JumpIf { condition, location: 0 },
UnresolvedJumpLocation::Label(target_label.to_string()),
target_label.to_string(),
);
}

Expand All @@ -109,12 +139,12 @@ impl BrilligContext {
/// Emits brillig bytecode to jump to a trap condition if `condition`
/// is false.
pub(crate) fn constrain_instruction(&mut self, condition: RegisterIndex) {
// Jump to the relative location after the trap
self.add_unresolved_jump(
BrilligOpcode::JumpIf { condition, location: 0 },
UnresolvedJumpLocation::Relative(2),
self.next_section_label(),
);
self.push_opcode(BrilligOpcode::Trap);
self.enter_next_section();
}

/// Processes a return instruction.
Expand Down
23 changes: 4 additions & 19 deletions crates/noirc_evaluator/src/brillig/brillig_ir/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,14 @@ pub(crate) type JumpInstructionPosition = OpcodeLocation;

/// When constructing the bytecode, there may be instructions
/// which require one to jump to a specific region of code (function)
/// or a position relative to the current instruction.
///
/// The position of a function cannot always be known
/// at this point in time, so Jumps are unresolved
/// until all functions/all of the bytecode has been processed.
/// `Label` is used as the jump location and once all of the bytecode
/// has been processed, the jumps are resolved using a map from Labels
/// to their position in the bytecode.
///
/// Sometimes the jump destination may be relative to the jump instruction.
/// Since the absolute position in the bytecode cannot be known until
/// all internal and external functions have been linked, jumps of this
/// nature cannot be fully resolved while building the bytecode either.
/// We add relative jumps into the `Relative` variant of this enum.
#[derive(Debug, Clone)]
pub(crate) enum UnresolvedJumpLocation {
Label(String),
Relative(i32),
}
pub(crate) type UnresolvedJumpLocation = Label;

impl BrilligArtifact {
/// Link two Brillig artifacts together and resolve all unresolved jump instructions.
Expand All @@ -66,7 +55,8 @@ impl BrilligArtifact {
}

for (label_id, position_in_bytecode) in &obj.labels {
self.labels.insert(label_id.clone(), position_in_bytecode + offset);
let old_value = self.labels.insert(label_id.clone(), position_in_bytecode + offset);
assert!(old_value.is_none(), "overwriting label {label_id} {old_value:?}");
}

self.byte_code.extend_from_slice(&obj.byte_code);
Expand Down Expand Up @@ -126,12 +116,7 @@ impl BrilligArtifact {
/// linkage with other bytecode has happened.
fn resolve_jumps(&mut self) {
for (location_of_jump, unresolved_location) in &self.unresolved_jumps {
let resolved_location = match unresolved_location {
UnresolvedJumpLocation::Label(label) => self.labels[label],
UnresolvedJumpLocation::Relative(offset) => {
(offset + *location_of_jump as i32) as usize
}
};
let resolved_location = self.labels[unresolved_location];

let jump_instruction = self.byte_code[*location_of_jump].clone();
match jump_instruction {
Expand Down
4 changes: 2 additions & 2 deletions crates/noirc_evaluator/src/brillig/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub(crate) mod brillig_gen;
pub(crate) mod brillig_ir;

use self::{brillig_gen::BrilligGen, brillig_ir::artifact::BrilligArtifact};
use self::{brillig_gen::convert_ssa_function, brillig_ir::artifact::BrilligArtifact};
use crate::ssa_refactor::{
ir::function::{Function, FunctionId, RuntimeType},
ssa_gen::Ssa,
Expand All @@ -19,7 +19,7 @@ pub struct Brillig {
impl Brillig {
/// Compiles a function into brillig and store the compilation artifacts
pub(crate) fn compile(&mut self, func: &Function) {
let obj = BrilligGen::compile(func);
let obj = convert_ssa_function(func);
self.ssa_function_to_brillig.insert(func.id(), obj);
}
}
Expand Down
6 changes: 6 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ impl std::fmt::Display for Id<super::function::Function> {
}
}

impl std::fmt::Display for Id<super::instruction::Instruction> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "f{}", self.index)
}
}

/// A DenseMap is a Vec wrapper where each element corresponds
/// to a unique ID that can be used to access the element. No direct
/// access to indices is provided. Since IDs must be stable and correspond
Expand Down