Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
18 changes: 18 additions & 0 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,24 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
} => {
avm_instrs.push(generate_mov_instruction(Some(ALL_DIRECT), source.to_usize() as u32, destination.to_usize() as u32));
}
BrilligOpcode::ConditionalMov {
source_a,
source_b,
condition,
destination,
} => {
avm_instrs.push(AvmInstruction {
opcode: AvmOpcode::CMOV,
indirect: Some(ALL_DIRECT),
operands: vec![
AvmOperand::U32 { value: source_a.to_usize() as u32 },
AvmOperand::U32 { value: source_b.to_usize() as u32 },
AvmOperand::U32 { value: condition.to_usize() as u32 },
AvmOperand::U32 { value: destination.to_usize() as u32 },
],
..Default::default()
});
}
BrilligOpcode::Load {
destination,
source_pointer,
Expand Down
74 changes: 74 additions & 0 deletions barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,17 @@ struct BrilligOpcode {
static Mov bincodeDeserialize(std::vector<uint8_t>);
};

struct ConditionalMov {
Circuit::MemoryAddress destination;
Circuit::MemoryAddress source_a;
Circuit::MemoryAddress source_b;
Circuit::MemoryAddress condition;

friend bool operator==(const ConditionalMov&, const ConditionalMov&);
std::vector<uint8_t> bincodeSerialize() const;
static ConditionalMov bincodeDeserialize(std::vector<uint8_t>);
};

struct Load {
Circuit::MemoryAddress destination;
Circuit::MemoryAddress source_pointer;
Expand Down Expand Up @@ -644,6 +655,7 @@ struct BrilligOpcode {
Return,
ForeignCall,
Mov,
ConditionalMov,
Load,
Store,
BlackBox,
Expand Down Expand Up @@ -5824,6 +5836,68 @@ Circuit::BrilligOpcode::Mov serde::Deserializable<Circuit::BrilligOpcode::Mov>::

namespace Circuit {

inline bool operator==(const BrilligOpcode::ConditionalMov& lhs, const BrilligOpcode::ConditionalMov& rhs)
{
if (!(lhs.destination == rhs.destination)) {
return false;
}
if (!(lhs.source_a == rhs.source_a)) {
return false;
}
if (!(lhs.source_b == rhs.source_b)) {
return false;
}
if (!(lhs.condition == rhs.condition)) {
return false;
}
return true;
}

inline std::vector<uint8_t> BrilligOpcode::ConditionalMov::bincodeSerialize() const
{
auto serializer = serde::BincodeSerializer();
serde::Serializable<BrilligOpcode::ConditionalMov>::serialize(*this, serializer);
return std::move(serializer).bytes();
}

inline BrilligOpcode::ConditionalMov BrilligOpcode::ConditionalMov::bincodeDeserialize(std::vector<uint8_t> input)
{
auto deserializer = serde::BincodeDeserializer(input);
auto value = serde::Deserializable<BrilligOpcode::ConditionalMov>::deserialize(deserializer);
if (deserializer.get_buffer_offset() < input.size()) {
throw_or_abort("Some input bytes were not read");
}
return value;
}

} // end of namespace Circuit

template <>
template <typename Serializer>
void serde::Serializable<Circuit::BrilligOpcode::ConditionalMov>::serialize(
const Circuit::BrilligOpcode::ConditionalMov& obj, Serializer& serializer)
{
serde::Serializable<decltype(obj.destination)>::serialize(obj.destination, serializer);
serde::Serializable<decltype(obj.source_a)>::serialize(obj.source_a, serializer);
serde::Serializable<decltype(obj.source_b)>::serialize(obj.source_b, serializer);
serde::Serializable<decltype(obj.condition)>::serialize(obj.condition, serializer);
}

template <>
template <typename Deserializer>
Circuit::BrilligOpcode::ConditionalMov serde::Deserializable<Circuit::BrilligOpcode::ConditionalMov>::deserialize(
Deserializer& deserializer)
{
Circuit::BrilligOpcode::ConditionalMov obj;
obj.destination = serde::Deserializable<decltype(obj.destination)>::deserialize(deserializer);
obj.source_a = serde::Deserializable<decltype(obj.source_a)>::deserialize(deserializer);
obj.source_b = serde::Deserializable<decltype(obj.source_b)>::deserialize(deserializer);
obj.condition = serde::Deserializable<decltype(obj.condition)>::deserialize(deserializer);
return obj;
}

namespace Circuit {

inline bool operator==(const BrilligOpcode::Load& lhs, const BrilligOpcode::Load& rhs)
{
if (!(lhs.destination == rhs.destination)) {
Expand Down
60 changes: 59 additions & 1 deletion noir/noir-repo/acvm-repo/acir/codegen/acir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,17 @@ namespace Circuit {
static Mov bincodeDeserialize(std::vector<uint8_t>);
};

struct ConditionalMov {
Circuit::MemoryAddress destination;
Circuit::MemoryAddress source_a;
Circuit::MemoryAddress source_b;
Circuit::MemoryAddress condition;

friend bool operator==(const ConditionalMov&, const ConditionalMov&);
std::vector<uint8_t> bincodeSerialize() const;
static ConditionalMov bincodeDeserialize(std::vector<uint8_t>);
};

struct Load {
Circuit::MemoryAddress destination;
Circuit::MemoryAddress source_pointer;
Expand Down Expand Up @@ -612,7 +623,7 @@ namespace Circuit {
static Stop bincodeDeserialize(std::vector<uint8_t>);
};

std::variant<BinaryFieldOp, BinaryIntOp, Cast, JumpIfNot, JumpIf, Jump, CalldataCopy, Call, Const, Return, ForeignCall, Mov, Load, Store, BlackBox, Trap, Stop> value;
std::variant<BinaryFieldOp, BinaryIntOp, Cast, JumpIfNot, JumpIf, Jump, CalldataCopy, Call, Const, Return, ForeignCall, Mov, ConditionalMov, Load, Store, BlackBox, Trap, Stop> value;

friend bool operator==(const BrilligOpcode&, const BrilligOpcode&);
std::vector<uint8_t> bincodeSerialize() const;
Expand Down Expand Up @@ -4818,6 +4829,53 @@ Circuit::BrilligOpcode::Mov serde::Deserializable<Circuit::BrilligOpcode::Mov>::
return obj;
}

namespace Circuit {

inline bool operator==(const BrilligOpcode::ConditionalMov &lhs, const BrilligOpcode::ConditionalMov &rhs) {
if (!(lhs.destination == rhs.destination)) { return false; }
if (!(lhs.source_a == rhs.source_a)) { return false; }
if (!(lhs.source_b == rhs.source_b)) { return false; }
if (!(lhs.condition == rhs.condition)) { return false; }
return true;
}

inline std::vector<uint8_t> BrilligOpcode::ConditionalMov::bincodeSerialize() const {
auto serializer = serde::BincodeSerializer();
serde::Serializable<BrilligOpcode::ConditionalMov>::serialize(*this, serializer);
return std::move(serializer).bytes();
}

inline BrilligOpcode::ConditionalMov BrilligOpcode::ConditionalMov::bincodeDeserialize(std::vector<uint8_t> input) {
auto deserializer = serde::BincodeDeserializer(input);
auto value = serde::Deserializable<BrilligOpcode::ConditionalMov>::deserialize(deserializer);
if (deserializer.get_buffer_offset() < input.size()) {
throw serde::deserialization_error("Some input bytes were not read");
}
return value;
}

} // end of namespace Circuit

template <>
template <typename Serializer>
void serde::Serializable<Circuit::BrilligOpcode::ConditionalMov>::serialize(const Circuit::BrilligOpcode::ConditionalMov &obj, Serializer &serializer) {
serde::Serializable<decltype(obj.destination)>::serialize(obj.destination, serializer);
serde::Serializable<decltype(obj.source_a)>::serialize(obj.source_a, serializer);
serde::Serializable<decltype(obj.source_b)>::serialize(obj.source_b, serializer);
serde::Serializable<decltype(obj.condition)>::serialize(obj.condition, serializer);
}

template <>
template <typename Deserializer>
Circuit::BrilligOpcode::ConditionalMov serde::Deserializable<Circuit::BrilligOpcode::ConditionalMov>::deserialize(Deserializer &deserializer) {
Circuit::BrilligOpcode::ConditionalMov obj;
obj.destination = serde::Deserializable<decltype(obj.destination)>::deserialize(deserializer);
obj.source_a = serde::Deserializable<decltype(obj.source_a)>::deserialize(deserializer);
obj.source_b = serde::Deserializable<decltype(obj.source_b)>::deserialize(deserializer);
obj.condition = serde::Deserializable<decltype(obj.condition)>::deserialize(deserializer);
return obj;
}

namespace Circuit {

inline bool operator==(const BrilligOpcode::Load &lhs, const BrilligOpcode::Load &rhs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,10 @@ fn simple_brillig_foreign_call() {

let expected_serialization: Vec<u8> = vec![
31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 143, 177, 10, 192, 32, 16, 67, 227, 21, 74, 233,
212, 79, 177, 127, 208, 159, 233, 224, 226, 32, 226, 247, 139, 168, 16, 68, 93, 244, 45,
214, 63, 177, 127, 208, 159, 233, 224, 226, 32, 226, 247, 139, 168, 16, 68, 93, 244, 45,
119, 228, 142, 144, 92, 0, 20, 50, 7, 237, 76, 213, 190, 50, 245, 26, 175, 218, 231, 165,
57, 175, 148, 14, 137, 179, 147, 191, 114, 211, 221, 216, 240, 59, 63, 107, 221, 115, 104,
181, 103, 244, 43, 36, 10, 38, 68, 108, 25, 253, 238, 136, 1, 0, 0,
181, 103, 244, 43, 36, 10, 38, 68, 115, 180, 20, 167, 136, 1, 0, 0,
];

assert_eq!(bytes, expected_serialization)
Expand Down Expand Up @@ -306,14 +306,14 @@ fn complex_brillig_foreign_call() {

let expected_serialization: Vec<u8> = vec![
31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 125, 177, 163, 35, 179,
154, 35, 8, 51, 7, 232, 204, 9, 188, 139, 184, 83, 116, 233, 241, 173, 152, 98, 12, 213,
155, 27, 8, 51, 7, 232, 204, 9, 188, 139, 184, 83, 116, 233, 241, 173, 152, 98, 12, 213,
141, 21, 244, 65, 232, 39, 175, 233, 35, 73, 155, 3, 32, 204, 48, 206, 18, 158, 19, 175,
37, 60, 175, 228, 209, 30, 195, 143, 226, 197, 178, 103, 105, 76, 110, 160, 209, 156, 160,
209, 247, 195, 69, 235, 29, 179, 46, 81, 243, 103, 2, 239, 231, 225, 44, 117, 150, 241,
250, 201, 99, 206, 251, 96, 95, 161, 242, 14, 193, 243, 40, 162, 105, 253, 219, 12, 75, 47,
146, 186, 251, 37, 116, 86, 93, 219, 55, 245, 96, 20, 85, 75, 253, 136, 249, 87, 249, 105,
231, 220, 4, 249, 237, 132, 56, 20, 224, 109, 113, 223, 88, 82, 153, 34, 64, 34, 14, 164,
69, 172, 48, 2, 23, 243, 6, 31, 25, 5, 0, 0,
69, 172, 48, 2, 124, 203, 96, 70, 25, 5, 0, 0,
];

assert_eq!(bytes, expected_serialization)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { WitnessMap } from '@noir-lang/acvm_js';

// See `complex_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`.
export const bytecode = Uint8Array.from([
31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 125, 177, 163, 35, 179, 154, 35, 8, 51, 7, 232, 204,
31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 125, 177, 163, 35, 179, 155, 27, 8, 51, 7, 232, 204,
9, 188, 139, 184, 83, 116, 233, 241, 173, 152, 98, 12, 213, 141, 21, 244, 65, 232, 39, 175, 233, 35, 73, 155, 3, 32,
204, 48, 206, 18, 158, 19, 175, 37, 60, 175, 228, 209, 30, 195, 143, 226, 197, 178, 103, 105, 76, 110, 160, 209, 156,
160, 209, 247, 195, 69, 235, 29, 179, 46, 81, 243, 103, 2, 239, 231, 225, 44, 117, 150, 241, 250, 201, 99, 206, 251,
96, 95, 161, 242, 14, 193, 243, 40, 162, 105, 253, 219, 12, 75, 47, 146, 186, 251, 37, 116, 86, 93, 219, 55, 245, 96,
20, 85, 75, 253, 136, 249, 87, 249, 105, 231, 220, 4, 249, 237, 132, 56, 20, 224, 109, 113, 223, 88, 82, 153, 34, 64,
34, 14, 164, 69, 172, 48, 2, 23, 243, 6, 31, 25, 5, 0, 0,
34, 14, 164, 69, 172, 48, 2, 124, 203, 96, 70, 25, 5, 0, 0,
]);
export const initialWitnessMap: WitnessMap = new Map([
[1, '0x0000000000000000000000000000000000000000000000000000000000000001'],
Expand Down
4 changes: 2 additions & 2 deletions noir/noir-repo/acvm-repo/acvm_js/test/shared/foreign_call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { WitnessMap } from '@noir-lang/acvm_js';

// See `simple_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`.
export const bytecode = Uint8Array.from([
31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 143, 177, 10, 192, 32, 16, 67, 227, 21, 74, 233, 212, 79, 177, 127, 208, 159,
31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 143, 177, 10, 192, 32, 16, 67, 227, 21, 74, 233, 214, 63, 177, 127, 208, 159,
233, 224, 226, 32, 226, 247, 139, 168, 16, 68, 93, 244, 45, 119, 228, 142, 144, 92, 0, 20, 50, 7, 237, 76, 213, 190,
50, 245, 26, 175, 218, 231, 165, 57, 175, 148, 14, 137, 179, 147, 191, 114, 211, 221, 216, 240, 59, 63, 107, 221, 115,
104, 181, 103, 244, 43, 36, 10, 38, 68, 108, 25, 253, 238, 136, 1, 0, 0,
104, 181, 103, 244, 43, 36, 10, 38, 68, 115, 180, 20, 167, 136, 1, 0, 0,
]);
export const initialWitnessMap: WitnessMap = new Map([
[1, '0x0000000000000000000000000000000000000000000000000000000000000005'],
Expand Down
7 changes: 7 additions & 0 deletions noir/noir-repo/acvm-repo/brillig/src/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ pub enum BrilligOpcode {
destination: MemoryAddress,
source: MemoryAddress,
},
/// destination = condition > 0 ? source_a : source_b
ConditionalMov {
destination: MemoryAddress,
source_a: MemoryAddress,
source_b: MemoryAddress,
condition: MemoryAddress,
},
Load {
destination: MemoryAddress,
source_pointer: MemoryAddress,
Expand Down
55 changes: 55 additions & 0 deletions noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,15 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> {
self.memory.write(*destination_address, source_value);
self.increment_program_counter()
}
Opcode::ConditionalMov { destination, source_a, source_b, condition } => {
let condition_value = self.memory.read(*condition);
if condition_value.is_zero() {
self.memory.write(*destination, self.memory.read(*source_b));
} else {
self.memory.write(*destination, self.memory.read(*source_a));
}
self.increment_program_counter()
}
Opcode::Trap => self.fail("explicit trap hit in brillig".to_string()),
Opcode::Stop { return_data_offset, return_data_size } => {
self.finish(*return_data_offset, *return_data_size)
Expand Down Expand Up @@ -793,6 +802,52 @@ mod tests {
assert_eq!(source_value, Value::from(1u128));
}

#[test]
fn cmov_opcode() {
let calldata =
vec![Value::from(0u128), Value::from(1u128), Value::from(2u128), Value::from(3u128)];

let calldata_copy = Opcode::CalldataCopy {
destination_address: MemoryAddress::from(0),
size: 4,
offset: 0,
};

let opcodes = &[
calldata_copy,
Opcode::ConditionalMov {
destination: MemoryAddress(4), // Sets 3_u128 to memory address 4
source_a: MemoryAddress(2),
source_b: MemoryAddress(3),
condition: MemoryAddress(0),
},
Opcode::ConditionalMov {
destination: MemoryAddress(5), // Sets 2_u128 to memory address 5
source_a: MemoryAddress(2),
source_b: MemoryAddress(3),
condition: MemoryAddress(1),
},
];
let mut vm = VM::new(calldata, opcodes, vec![], &DummyBlackBoxSolver);

let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);

let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);

let status = vm.process_opcode();
assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });

let VM { memory, .. } = vm;

let destination_value = memory.read(MemoryAddress::from(4));
assert_eq!(destination_value, Value::from(3_u128));

let source_value = memory.read(MemoryAddress::from(5));
assert_eq!(source_value, Value::from(2_u128));
}

#[test]
fn cmp_binary_ops() {
let bit_size = 32;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1339,18 +1339,23 @@ impl<'block> BrilligBlock<'block> {
BrilligBinaryOp::LessThan,
);

self.brillig_context.codegen_branch(result_is_negative.address, |ctx, is_negative| {
if is_negative {
// Two's complement of num
let zero = ctx.make_constant_instruction(0_usize.into(), num.bit_size);
ctx.binary_instruction(zero, num, absolute_value, BrilligBinaryOp::Sub);
ctx.deallocate_single_addr(zero);
} else {
// Simply move the original num
ctx.mov_instruction(absolute_value.address, num.address);
}
});
// Two's complement of num
let zero = self.brillig_context.make_constant_instruction(0_usize.into(), num.bit_size);
let twos_complement =
SingleAddrVariable::new(self.brillig_context.allocate_register(), num.bit_size);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the old code better? Now we unconditionally execute all of absolute value (in the AVM prover it would be some extra rows). I know it reads nicer, but probably the old code is better unless we care about branch prediction (which I think we don't :P)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously it was:

JUMPI then
JUMP else
CONST  // then
SUB
JUMP end
MOV // else
//end

Now it's

CONST
SUB
CMOV

So it's half the opcodes emitted
Regarding execution trace, previously, for the truthy case it was 4 opcodes, for the falsy case it was 3 opcodes. Now it's always 3 opcodes

I think now it's better! But it was mainly so CMOV was used and tested. The old case could probably have been optimized in a different way

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right!

self.brillig_context.binary_instruction(zero, num, twos_complement, BrilligBinaryOp::Sub);

// absolute_value = result_is_negative ? twos_complement : num
self.brillig_context.conditional_mov_instruction(
absolute_value.address,
result_is_negative.address,
twos_complement.address,
num.address,
);

self.brillig_context.deallocate_single_addr(zero);
self.brillig_context.deallocate_single_addr(max_positive);
self.brillig_context.deallocate_single_addr(twos_complement);
}

fn convert_signed_division(
Expand Down
Loading