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 cast instruction #1649

Merged
merged 7 commits into from
Jun 14, 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]
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Tests a very simple Brillig function.
//
// The features being tested are cast operations on brillig
fn main() {
bool_casts();
field_casts();
uint_casts();
int_casts();
mixed_casts();
}

unconstrained fn bool_casts() {
assert(false == 0 as bool);
assert(true == 1 as bool);
assert(true == 3 as bool);
}

unconstrained fn field_casts() {
assert(5 as u8 as Field == 5);
assert(16 as u4 as Field == 0);
}

unconstrained fn uint_casts() {
let x: u32 = 100;
assert(x as u2 == 0);
assert(x as u4 == 4);
assert(x as u6 == 36);
assert(x as u8 == 100);
assert(x as u64 == 100);
assert(x as u126 == 100);
}

unconstrained fn int_casts() {
let x: i32 = 100;
assert(x as i2 == 0);
assert(x as i4 == 4);
assert(x as i6 == -28 as i6);
assert(x as i8 == 100);
assert(x as i8 == 100);
assert(x as i8 == 100);
}


unconstrained fn mixed_casts() {
assert(100 as u32 as i32 as u32 == 100);
assert(13 as u4 as i2 as u32 == 1);
assert(15 as u4 as i2 as u32 == 3);
assert(1 as u8 as bool == true);
assert(true as i8 == 1);
}
55 changes: 54 additions & 1 deletion crates/noirc_evaluator/src/brillig/brillig_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use crate::ssa_refactor::ir::{
types::{NumericType, Type},
value::{Value, ValueId},
};
use acvm::acir::brillig_vm::{BinaryFieldOp, BinaryIntOp, RegisterIndex, RegisterValueOrArray};
use acvm::{
acir::brillig_vm::{BinaryFieldOp, BinaryIntOp, RegisterIndex, RegisterValueOrArray},
FieldElement,
};
use iter_extended::vecmap;
use std::collections::HashMap;

Expand Down Expand Up @@ -190,10 +193,60 @@ impl BrilligGen {
let source = self.convert_ssa_value(*value, dfg);
self.context.truncate_instruction(destination, source);
}
Instruction::Cast(value, target_type) => {
let result_ids = dfg.instruction_results(instruction_id);
let destination = self.get_or_create_register(result_ids[0]);
let source = self.convert_ssa_value(*value, dfg);
self.convert_cast(destination, source, target_type, &dfg.type_of_value(*value));
}
_ => todo!("ICE: Instruction not supported {instruction:?}"),
};
}

sirasistant marked this conversation as resolved.
Show resolved Hide resolved
/// Converts an SSA cast to a sequence of Brillig opcodes.
/// Casting is only necessary when shrinking the bit size of a numeric value.
fn convert_cast(
&mut self,
destination: RegisterIndex,
source: RegisterIndex,
target_type: &Type,
source_type: &Type,
) {
fn numeric_to_bit_size(typ: &NumericType) -> u32 {
match typ {
NumericType::Signed { bit_size } | NumericType::Unsigned { bit_size } => *bit_size,
NumericType::NativeField => FieldElement::max_num_bits(),
}
}

// Casting is only valid for numeric types
// This should be checked by the frontend, so we panic if this is the case
let (source_numeric_type, target_numeric_type) = match (source_type, target_type) {
(Type::Numeric(source_numeric_type), Type::Numeric(target_numeric_type)) => {
(source_numeric_type, target_numeric_type)
}
_ => unimplemented!("The cast operation is only valid for integers."),
};

let source_bit_size = numeric_to_bit_size(source_numeric_type);
let target_bit_size = numeric_to_bit_size(target_numeric_type);

// Casting from a larger bit size to a smaller bit size (narrowing cast)
// requires a cast instruction.
// If its a widening cast, ie casting from a smaller bit size to a larger bit size
// we simply put a mov instruction as a no-op
//
// Field elements by construction always have the largest bit size
// This means that casting to a Field element, will always be a widening cast
// and therefore a no-op. Conversely, casting from a Field element
// will always be a narrowing cast and therefore a cast instruction
if source_bit_size > target_bit_size {
self.context.cast_instruction(destination, source, target_bit_size);
} else {
self.context.mov_instruction(destination, source);
}
}

/// Converts the Binary instruction into a sequence of Brillig opcodes.
fn convert_ssa_binary(
&mut self,
Expand Down
40 changes: 40 additions & 0 deletions crates/noirc_evaluator/src/brillig/brillig_ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ use acvm::{
FieldElement,
};

/// Integer arithmetic in Brillig is limited to 127 bit
/// integers.
///
/// We could lift this in the future and have Brillig
/// do big integer arithmetic when it exceeds the field size
/// or we could have users re-implement big integer arithmetic
/// in Brillig.
/// Since constrained functions do not have this property, it
/// would mean that unconstrained functions will differ from
/// constrained functions in terms of syntax compatibility.
const BRILLIG_INTEGER_ARITHMETIC_BIT_SIZE: u32 = 127;

/// Brillig context object that is used while constructing the
/// Brillig bytecode.
#[derive(Default)]
Expand Down Expand Up @@ -302,6 +314,34 @@ impl BrilligContext {
rhs: scratch_register_j,
});
}

/// Emits a modulo instruction against 2**target_bit_size
///
/// Integer arithmetic in Brillig is currently constrained to 127 bit integers.
/// We restrict the cast operation, so that integer types over 127 bits
/// cannot be created.
pub(crate) fn cast_instruction(
&mut self,
destination: RegisterIndex,
source: RegisterIndex,
target_bit_size: u32,
) {
assert!(
target_bit_size <= BRILLIG_INTEGER_ARITHMETIC_BIT_SIZE,
"tried to cast to a bit size greater than allowed {target_bit_size}"
);

// The brillig VM performs all arithmetic operations modulo 2**bit_size
// So to cast any value to a target bit size we can just issue a no-op arithmetic operation
// With bit size equal to target_bit_size
let zero = self.make_constant(Value::from(FieldElement::zero()));
self.binary_instruction(
source,
zero,
destination,
BrilligBinaryOp::Integer { op: BinaryIntOp::Add, bit_size: target_bit_size },
);
}
}

/// Type to encapsulate the binary operation types in Brillig
Expand Down
2 changes: 1 addition & 1 deletion crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl Context {
// Generate the brillig code of the function
let code = BrilligArtifact::default().link(&brillig[*id]);

let outputs: Vec<AcirType> = vecmap(result_ids, |result_id| dfg.type_of_value(*result_id).into());
let outputs: Vec<AcirType> = vecmap(result_ids, |result_id| dfg.type_of_value(*result_id).into());

let output_values = self.acir_context.brillig(code, inputs, outputs);
// Compiler sanity check
Expand Down