Skip to content

Commit

Permalink
feat(avm-simulator): euclidean and field div (#5181)
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro committed Mar 13, 2024
1 parent cfd673d commit 037a38f
Show file tree
Hide file tree
Showing 14 changed files with 288 additions and 131 deletions.
2 changes: 2 additions & 0 deletions avm-transpiler/src/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub enum AvmOpcode {
SUB,
MUL,
DIV,
FDIV,
EQ,
LT,
LTE,
Expand Down Expand Up @@ -82,6 +83,7 @@ impl AvmOpcode {
AvmOpcode::SUB => "SUB",
AvmOpcode::MUL => "MUL",
AvmOpcode::DIV => "DIV",
AvmOpcode::FDIV => "FDIV",
// Compute - Comparators
AvmOpcode::EQ => "EQ",
AvmOpcode::LT => "LT",
Expand Down
51 changes: 34 additions & 17 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
BinaryFieldOp::Add => AvmOpcode::ADD,
BinaryFieldOp::Sub => AvmOpcode::SUB,
BinaryFieldOp::Mul => AvmOpcode::MUL,
BinaryFieldOp::Div => AvmOpcode::DIV,
BinaryFieldOp::Div => AvmOpcode::FDIV,
BinaryFieldOp::Equals => AvmOpcode::EQ,
};
avm_instrs.push(AvmInstruction {
Expand All @@ -62,29 +62,42 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
lhs,
rhs,
} => {
assert!(is_integral_bit_size(*bit_size), "BinaryIntOp::{:?} bit_size must be integral, got {:?}", op, bit_size);
let is_integral = is_integral_bit_size(*bit_size);
let avm_opcode = match op {
BinaryIntOp::Add => AvmOpcode::ADD,
BinaryIntOp::Sub => AvmOpcode::SUB,
BinaryIntOp::Mul => AvmOpcode::MUL,
BinaryIntOp::UnsignedDiv => AvmOpcode::DIV,
BinaryIntOp::Equals => AvmOpcode::EQ,
BinaryIntOp::LessThan => AvmOpcode::LT,
BinaryIntOp::LessThanEquals => AvmOpcode::LTE,
BinaryIntOp::And => AvmOpcode::AND,
BinaryIntOp::Or => AvmOpcode::OR,
BinaryIntOp::Xor => AvmOpcode::XOR,
BinaryIntOp::Shl => AvmOpcode::SHL,
BinaryIntOp::Shr => AvmOpcode::SHR,
BinaryIntOp::Add if is_integral => AvmOpcode::ADD,
BinaryIntOp::Sub if is_integral => AvmOpcode::SUB,
BinaryIntOp::Mul if is_integral => AvmOpcode::MUL,
BinaryIntOp::UnsignedDiv if is_integral => AvmOpcode::DIV,
BinaryIntOp::UnsignedDiv if is_field_bit_size(*bit_size) => AvmOpcode::FDIV,
BinaryIntOp::Equals if is_integral => AvmOpcode::EQ,
BinaryIntOp::LessThan if is_integral => AvmOpcode::LT,
BinaryIntOp::LessThanEquals if is_integral => AvmOpcode::LTE,
BinaryIntOp::And if is_integral => AvmOpcode::AND,
BinaryIntOp::Or if is_integral => AvmOpcode::OR,
BinaryIntOp::Xor if is_integral => AvmOpcode::XOR,
BinaryIntOp::Shl if is_integral => AvmOpcode::SHL,
BinaryIntOp::Shr if is_integral => AvmOpcode::SHR,
// https://github.com/noir-lang/noir/issues/4543
// Using Field for now, until the bug is fixed.
BinaryIntOp::Mul if is_field_bit_size(*bit_size) => AvmOpcode::MUL,
BinaryIntOp::Sub if is_field_bit_size(*bit_size) => AvmOpcode::SUB,
// https://github.com/noir-lang/noir/issues/4544
// These are implemented on our side, but Brillig does not have LT(E) in BinaryFieldOp
// So they use BinaryIntOp.
BinaryIntOp::LessThan if is_field_bit_size(*bit_size) => AvmOpcode::LT,
BinaryIntOp::LessThanEquals if is_field_bit_size(*bit_size) => AvmOpcode::LTE,
_ => panic!(
"Transpiler doesn't know how to process BinaryIntOp {:?}",
brillig_instr
"Transpiler doesn't know how to process {:?}", brillig_instr
),
};
avm_instrs.push(AvmInstruction {
opcode: avm_opcode,
indirect: Some(ALL_DIRECT),
tag: Some(tag_from_bit_size(*bit_size)),
tag: if is_integral {
Some(tag_from_bit_size(*bit_size))
} else {
None
},
operands: vec![
AvmOperand::U32 {
value: lhs.to_usize() as u32,
Expand Down Expand Up @@ -997,6 +1010,10 @@ fn map_brillig_pcs_to_avm_pcs(initial_offset: usize, brillig: &Brillig) -> Vec<u
pc_map
}

fn is_field_bit_size(bit_size: u32) -> bool {
bit_size == 254
}

fn is_integral_bit_size(bit_size: u32) -> bool {
match bit_size {
1 | 8 | 16 | 32 | 64 | 128 => true,
Expand Down
2 changes: 2 additions & 0 deletions barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const std::unordered_map<OpCode, size_t> Bytecode::OPERANDS_NUM = {
{ OpCode::SUB, 3 },
{ OpCode::MUL, 3 },
{ OpCode::DIV, 3 },
{ OpCode::FDIV, 3 },
//// Compute - Comparators
//{OpCode::EQ, },
//{OpCode::LT, },
Expand Down Expand Up @@ -154,6 +155,7 @@ bool Bytecode::has_in_tag(OpCode const op_code)
case OpCode::STATICCALL:
case OpCode::RETURN:
case OpCode::REVERT:
case OpCode::FDIV:
return false;
default:
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum class OpCode : uint8_t {
SUB,
MUL,
DIV,
FDIV,
// Compute - Comparators
EQ,
LT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ TEST_F(AvmExecutionTests, simpleInternalCall)
"03" // U32
"0D3D2518" // val 222111000 = 0xD3D2518
"00000004" // dst_offset 4
"25" // INTERNALCALL 37
+ to_hex(OpCode::INTERNALCALL) + // opcode INTERNALCALL
"00000004" // jmp_dest
+ to_hex(OpCode::ADD) + // opcode ADD
"00" // Indirect flag
Expand Down
9 changes: 9 additions & 0 deletions yarn-project/foundation/src/fields/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,15 @@ export class Fr extends BaseField {
const bInv = modInverse(rhs.toBigInt());
return this.mul(bInv);
}

// Integer division.
ediv(rhs: Fr) {
if (rhs.isZero()) {
throw new Error('Division by zero');
}

return new Fr(this.toBigInt() / rhs.toBigInt());
}
}

/**
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/simulator/src/avm/avm_memory_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,13 @@ export class Field extends MemoryValue {
return new Field(this.rep.mul(rhs.rep));
}

// Euclidean division.
public div(rhs: Field): Field {
return new Field(this.rep.ediv(rhs.rep));
}

// Field division.
public fdiv(rhs: Field): Field {
return new Field(this.rep.div(rhs.rep));
}

Expand Down
42 changes: 41 additions & 1 deletion yarn-project/simulator/src/avm/opcodes/arithmetic.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AvmContext } from '../avm_context.js';
import { Field, TypeTag } from '../avm_memory_types.js';
import { initContext } from '../fixtures/index.js';
import { Add, Div, Mul, Sub } from './arithmetic.js';
import { Add, Div, FieldDiv, Mul, Sub } from './arithmetic.js';

describe('Arithmetic Instructions', () => {
let context: AvmContext;
Expand Down Expand Up @@ -201,6 +201,46 @@ describe('Arithmetic Instructions', () => {
expect(inst.serialize()).toEqual(buf);
});

it('Should perform integer division', async () => {
const a = new Field(100n);
const b = new Field(5n);

context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Div(
/*indirect=*/ 0,
/*inTag=*/ TypeTag.FIELD,
/*aOffset=*/ 0,
/*bOffset=*/ 1,
/*dstOffset=*/ 2,
).execute(context);

const actual = context.machineState.memory.get(2);
expect(actual).toEqual(new Field(20));
});
});

describe('FDiv', () => {
it('Should (de)serialize correctly', () => {
const buf = Buffer.from([
FieldDiv.opcode, // opcode
0x01, // indirect
...Buffer.from('12345678', 'hex'), // aOffset
...Buffer.from('23456789', 'hex'), // bOffset
...Buffer.from('3456789a', 'hex'), // dstOffset
]);
const inst = new FieldDiv(
/*indirect=*/ 0x01,
/*aOffset=*/ 0x12345678,
/*bOffset=*/ 0x23456789,
/*dstOffset=*/ 0x3456789a,
);

expect(FieldDiv.deserialize(buf)).toEqual(inst);
expect(inst.serialize()).toEqual(buf);
});

it('Should perform field division', async () => {
const a = new Field(10n);
const b = new Field(5n);
Expand Down
34 changes: 33 additions & 1 deletion yarn-project/simulator/src/avm/opcodes/arithmetic.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { AvmContext } from '../avm_context.js';
import { Opcode } from '../serialization/instruction_serialization.js';
import { Field, TypeTag } from '../avm_memory_types.js';
import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
import { Instruction } from './instruction.js';
import { ThreeOperandInstruction } from './instruction_impl.js';

export class Add extends ThreeOperandInstruction {
Expand Down Expand Up @@ -79,3 +81,33 @@ export class Div extends ThreeOperandInstruction {
context.machineState.incrementPc();
}
}

export class FieldDiv extends Instruction {
static type: string = 'FDIV';
static readonly opcode = Opcode.FDIV;

// Informs (de)serialization. See Instruction.deserialize.
static readonly wireFormat: OperandType[] = [
OperandType.UINT8,
OperandType.UINT8,
OperandType.UINT32,
OperandType.UINT32,
OperandType.UINT32,
];

constructor(private indirect: number, private aOffset: number, private bOffset: number, private dstOffset: number) {
super();
}

async execute(context: AvmContext): Promise<void> {
context.machineState.memory.checkTags(TypeTag.FIELD, this.aOffset, this.bOffset);

const a = context.machineState.memory.getAs<Field>(this.aOffset);
const b = context.machineState.memory.getAs<Field>(this.bOffset);

const dest = a.fdiv(b);
context.machineState.memory.set(this.dstOffset, dest);

context.machineState.incrementPc();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
FeePerDAGas,
FeePerL1Gas,
FeePerL2Gas,
FieldDiv,
InternalCall,
InternalReturn,
Jump,
Expand Down Expand Up @@ -66,6 +67,7 @@ const INSTRUCTION_SET = () =>
[Sub.opcode, Sub],
[Mul.opcode, Mul],
[Div.opcode, Div],
[FieldDiv.opcode, FieldDiv],
[Eq.opcode, Eq],
[Lt.opcode, Lt],
[Lte.opcode, Lte],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum Opcode {
SUB,
MUL,
DIV,
FDIV,
EQ,
LT,
LTE,
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/simulator/src/public/avm_executor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('AVM WitGen and Proof Generation', () => {
// new Add(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2),
// new Return(/*indirect=*/ 0, /*returnOffset=*/ 2, /*copySize=*/ 1),
// ]);
const bytecode: Buffer = Buffer.from('HwAAAAAAAAAAAgAAAAAAAAYAAAAAAAAAAQAAAAI3AAAAAAIAAAAB', 'base64');
const bytecode: Buffer = Buffer.from('IAAAAAAAAAAAAgAAAAAAAAYAAAAAAAAAAQAAAAI4AAAAAAIAAAAB', 'base64');
publicContracts.getBytecode.mockResolvedValue(bytecode);
const executor = new PublicExecutor(publicState, publicContracts, commitmentsDb, header);
const functionData = FunctionData.empty();
Expand Down
Loading

0 comments on commit 037a38f

Please sign in to comment.