From 804f6997d1967f1fcb90b682b0d23da1c8e2f4b2 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:14:34 +0000 Subject: [PATCH 01/12] feat(avm): add internal jump and return, adjust control flow --- .../src/avm/avm_machine_state.ts | 7 ++ .../src/avm/interpreter/interpreter.ts | 3 +- .../src/avm/opcodes/arithmetic.ts | 36 +++++++--- .../acir-simulator/src/avm/opcodes/bitwise.ts | 48 +++++++++---- .../src/avm/opcodes/comparators.ts | 22 ++++-- .../src/avm/opcodes/control_flow.ts | 67 ++++++++++++++++++- .../src/avm/opcodes/instruction.ts | 4 ++ .../acir-simulator/src/avm/opcodes/memory.ts | 32 ++++++--- 8 files changed, 180 insertions(+), 39 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/avm_machine_state.ts b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts index aa0fc689b58d..c840e9fb1a92 100644 --- a/yarn-project/acir-simulator/src/avm/avm_machine_state.ts +++ b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts @@ -12,6 +12,12 @@ export class AvmMachineState { /** - */ public memory: Fr[]; + /** + * When an internal_call is invoked, the internal call stack is added to with the current pc + 1 + * When internal_return is invoked, the latest value is popped from the internal call stack and set to the pc. + */ + public internalCallStack: number[] = []; + /** - */ public pc: number; /** - */ @@ -76,4 +82,5 @@ export class AvmMachineState { public writeMemoryChunk(offset: number, values: Fr[]): void { this.memory.splice(offset, values.length, ...values); } + } diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 47fb9c3771d9..51742fa84079 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -30,7 +30,8 @@ export class AvmInterpreter { */ run(): AvmMessageCallResult { try { - for (const instruction of this.instructions) { + while (this.machineState.pc < this.instructions.length) { + const instruction = this.instructions[this.machineState.pc]; instruction.execute(this.machineState, this.stateManager); } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index ab6465debf4c..7f2fb3db052c 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -5,43 +5,53 @@ import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; /** -*/ -export class Add implements Instruction { +export class Add extends Instruction { static type: string = 'ADD'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a = machineState.readMemory(this.aOffset); const b = machineState.readMemory(this.bOffset); - const dest = new Fr(a.toBigInt() + (b.toBigInt() % Fr.MODULUS)); + const dest = new Fr((a.toBigInt() + b.toBigInt()) % Fr.MODULUS); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } /** -*/ -export class Sub implements Instruction { +export class Sub extends Instruction { static type: string = 'SUB'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a = machineState.readMemory(this.aOffset); const b = machineState.readMemory(this.bOffset); - const dest = new Fr(a.toBigInt() - (b.toBigInt() % Fr.MODULUS)); + const dest = new Fr((a.toBigInt() - b.toBigInt()) % Fr.MODULUS); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } /** -*/ -export class Mul implements Instruction { +export class Mul extends Instruction { static type: string = 'MUL'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -49,15 +59,19 @@ export class Mul implements Instruction { const dest = new Fr((a.toBigInt() * b.toBigInt()) % Fr.MODULUS); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } /** -*/ -export class Div implements Instruction { +export class Div extends Instruction { static type: string = 'DIV'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -66,5 +80,7 @@ export class Div implements Instruction { // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3993): proper field division const dest = new Fr(a.toBigInt() / b.toBigInt()); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index 62376269d8eb..8edc38b3eb53 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -5,11 +5,13 @@ import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; /** - */ -export class And implements Instruction { +export class And extends Instruction { static type: string = 'AND'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { +super(); +} execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -17,15 +19,19 @@ export class And implements Instruction { const dest = new Fr(a.toBigInt() & b.toBigInt()); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } /** - */ -export class Or implements Instruction { +export class Or extends Instruction { static type: string = 'OR'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { +super(); +} execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -33,15 +39,19 @@ export class Or implements Instruction { const dest = new Fr(a.toBigInt() | b.toBigInt()); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } /** - */ -export class Xor implements Instruction { +export class Xor extends Instruction { static type: string = 'XOR'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { +super(); +} execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -49,30 +59,38 @@ export class Xor implements Instruction { const dest = new Fr(a.toBigInt() ^ b.toBigInt()); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } /** - */ -export class Not implements Instruction { +export class Not extends Instruction { static type: string = 'NOT'; static numberOfOperands = 2; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { +super(); +} execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); const dest = new Fr(~a.toBigInt()); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } /** -*/ -export class Shl implements Instruction { +export class Shl extends Instruction { static type: string = 'SHL'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { +super(); +} execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -80,15 +98,19 @@ export class Shl implements Instruction { const dest = new Fr(a.toBigInt() << b.toBigInt()); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } /** -*/ -export class Shr implements Instruction { +export class Shr extends Instruction { static type: string = 'SHR'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { +super(); +} execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -96,5 +118,7 @@ export class Shr implements Instruction { const dest = new Fr(a.toBigInt() >> b.toBigInt()); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts index ddd3c9d1de99..40692147ac4a 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts @@ -5,11 +5,13 @@ import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; /** -*/ -export class Eq implements Instruction { +export class Eq extends Instruction { static type: string = 'EQ'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { +super(); +} execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -20,11 +22,13 @@ export class Eq implements Instruction { } } /** -*/ -export class Lt implements Instruction { +export class Lt extends Instruction { static type: string = 'Lt'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { +super(); +} execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -32,15 +36,19 @@ export class Lt implements Instruction { const dest = new Fr(a.toBigInt() < b.toBigInt()); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } /** -*/ -export class Lte implements Instruction { +export class Lte extends Instruction { static type: string = 'LTE'; static numberOfOperands = 3; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private bOffset: number, private destOffset: number) { +super(); +} execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -48,5 +56,7 @@ export class Lte implements Instruction { const dest = new Fr(a.toBigInt() < b.toBigInt()); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts index 31336e397be2..5d6e8fc9fd15 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -1,16 +1,79 @@ +import { machine } from 'os'; import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; /** - */ -export class Return implements Instruction { +export class Return extends Instruction { static type: string = 'RETURN'; static numberOfOperands = 2; - constructor(private returnOffset: number, private copySize: number) {} + constructor(private returnOffset: number, private copySize: number) { +super(); +} execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const returnData = machineState.readMemoryChunk(this.returnOffset, this.returnOffset + this.copySize); machineState.setReturnData(returnData); } } + +/** -*/ +export class Jump extends Instruction { + static type: string = 'JUMP'; + static numberOfOperands = 1; + + constructor(private jumpOffset: number) { + super(); + } + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + // TODO: this jump offset must be range constrained. + machineState.pc = this.jumpOffset; + } +} + +/** -*/ +export class InternalCall extends Instruction { + static type: string = 'INTERNAL_CALL'; + static numberOfOperands = 1; + + constructor(private jumpOffset: number) { + super(); + } + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + // TODO: this jump offset must be range constrained. + machineState.internalCallStack.push(machineState.pc + 1); + machineState.pc = this.jumpOffset; + } +} + +/** -*/ +export class InternalReturn extends Instruction { + static type: string = 'INTERNAL_RETURN'; + static numberOfOperands = 0; + + constructor() { + super(); + } + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const jumpOffset = machineState.internalCallStack.pop(); + if (jumpOffset === undefined) { + // TODO: Add in an error if this pop is undefined + throw new InternalCallStackEmptyError(); + } + machineState.pc = jumpOffset; + } +} + + +/** + * Thrown if the internal call stack is popped when it is empty + */ +export class InternalCallStackEmptyError extends Error { + constructor() { + super('Internal call stack is empty'); + } +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts index 9a97ab21a002..a334b10700c4 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts @@ -9,4 +9,8 @@ export const AVM_OPCODE_BYTE_LENGTH = 1; */ export abstract class Instruction { abstract execute(machineState: AvmMachineState, stateManager: AvmStateManager): void; + + incrementPc(machineState: AvmMachineState): void { + machineState.pc++; + } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts index bf14c1c235bb..c856645eef9b 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -5,56 +5,72 @@ import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; /** - */ -export class Set implements Instruction { +export class Set extends Instruction { static type: string = 'SET'; static numberOfOperands = 2; - constructor(private constt: bigint, private destOffset: number) {} + constructor(private constt: bigint, private destOffset: number) { + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const dest = new Fr(this.constt); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3987): tags are not implemented yet - this will behave as a mov /** - */ -export class Cast implements Instruction { +export class Cast extends Instruction { static type: string = 'CAST'; static numberOfOperands = 2; - constructor(private aOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private destOffset: number) { + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a = machineState.readMemory(this.aOffset); machineState.writeMemory(this.destOffset, a); + + this.incrementPc(machineState); } } /** - */ -export class Mov implements Instruction { +export class Mov extends Instruction { static type: string = 'MOV'; static numberOfOperands = 2; - constructor(private aOffset: number, private destOffset: number) {} + constructor(private aOffset: number, private destOffset: number) { + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a = machineState.readMemory(this.aOffset); machineState.writeMemory(this.destOffset, a); + + this.incrementPc(machineState); } } /** - */ -export class CalldataCopy implements Instruction { +export class CalldataCopy extends Instruction { static type: string = 'CALLDATACOPY'; static numberOfOperands = 3; - constructor(private cdOffset: number, private copySize: number, private destOffset: number) {} + constructor(private cdOffset: number, private copySize: number, private destOffset: number) { + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const calldata = machineState.calldata.slice(this.cdOffset, this.cdOffset + this.copySize); machineState.writeMemoryChunk(this.destOffset, calldata); + + this.incrementPc(machineState); } } From 5a6bdfcaaf1a561cbc22a1f8b4b6fd935e4b1e06 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:38:09 +0000 Subject: [PATCH 02/12] tests: test program counter implementation --- .../src/avm/avm_machine_state.ts | 4 +- .../acir-simulator/src/avm/opcodes/bitwise.ts | 30 ++++---- .../src/avm/opcodes/comparators.ts | 14 ++-- .../src/avm/opcodes/control_flow.test.ts | 73 +++++++++++++++++++ .../src/avm/opcodes/control_flow.ts | 8 +- 5 files changed, 102 insertions(+), 27 deletions(-) create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts diff --git a/yarn-project/acir-simulator/src/avm/avm_machine_state.ts b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts index c840e9fb1a92..74973e5a10d9 100644 --- a/yarn-project/acir-simulator/src/avm/avm_machine_state.ts +++ b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts @@ -16,7 +16,7 @@ export class AvmMachineState { * When an internal_call is invoked, the internal call stack is added to with the current pc + 1 * When internal_return is invoked, the latest value is popped from the internal call stack and set to the pc. */ - public internalCallStack: number[] = []; + public internalCallStack: number[]; /** - */ public pc: number; @@ -31,6 +31,7 @@ export class AvmMachineState { this.calldata = calldata; this.returnData = []; this.memory = []; + this.internalCallStack = []; this.pc = 0; this.callStack = []; @@ -82,5 +83,4 @@ export class AvmMachineState { public writeMemoryChunk(offset: number, values: Fr[]): void { this.memory.splice(offset, values.length, ...values); } - } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index 8edc38b3eb53..bc5c3a211cb6 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -10,8 +10,8 @@ export class And extends Instruction { static numberOfOperands = 3; constructor(private aOffset: number, private bOffset: number, private destOffset: number) { -super(); -} + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -30,8 +30,8 @@ export class Or extends Instruction { static numberOfOperands = 3; constructor(private aOffset: number, private bOffset: number, private destOffset: number) { -super(); -} + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -50,8 +50,8 @@ export class Xor extends Instruction { static numberOfOperands = 3; constructor(private aOffset: number, private bOffset: number, private destOffset: number) { -super(); -} + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -69,14 +69,16 @@ export class Not extends Instruction { static type: string = 'NOT'; static numberOfOperands = 2; - constructor(private aOffset: number, private bOffset: number, private destOffset: number) { -super(); -} + constructor(private aOffset: number, private destOffset: number) { + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); - const dest = new Fr(~a.toBigInt()); + // TODO: hack -> until proper field arithmetic is implemented + const result = ~a.toBigInt(); + const dest = new Fr(result < 0 ? Fr.MODULUS + /* using a + as result is -ve*/ result : result); machineState.writeMemory(this.destOffset, dest); this.incrementPc(machineState); @@ -89,8 +91,8 @@ export class Shl extends Instruction { static numberOfOperands = 3; constructor(private aOffset: number, private bOffset: number, private destOffset: number) { -super(); -} + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -109,8 +111,8 @@ export class Shr extends Instruction { static numberOfOperands = 3; constructor(private aOffset: number, private bOffset: number, private destOffset: number) { -super(); -} + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts index 40692147ac4a..685a5438c421 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts @@ -10,8 +10,8 @@ export class Eq extends Instruction { static numberOfOperands = 3; constructor(private aOffset: number, private bOffset: number, private destOffset: number) { -super(); -} + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -19,6 +19,8 @@ super(); const dest = new Fr(a.toBigInt() == b.toBigInt()); machineState.writeMemory(this.destOffset, dest); + + this.incrementPc(machineState); } } /** -*/ @@ -27,8 +29,8 @@ export class Lt extends Instruction { static numberOfOperands = 3; constructor(private aOffset: number, private bOffset: number, private destOffset: number) { -super(); -} + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); @@ -47,8 +49,8 @@ export class Lte extends Instruction { static numberOfOperands = 3; constructor(private aOffset: number, private bOffset: number, private destOffset: number) { -super(); -} + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const a: Fr = machineState.readMemory(this.aOffset); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts new file mode 100644 index 000000000000..fd7bb2f6598d --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts @@ -0,0 +1,73 @@ +import { mock } from 'jest-mock-extended'; + +import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Add, Mul, Sub } from './arithmetic.js'; +import { And, Not, Or, Shl, Shr, Xor } from './bitwise.js'; +import { Eq, Lt, Lte } from './comparators.js'; +import { InternalCall, InternalReturn, Jump } from './control_flow.js'; +import { CalldataCopy, Cast, Mov, Set } from './memory.js'; + +describe('Control Flow Opcodes', () => { + let stateManager = mock(); + let machineState: AvmMachineState; + + beforeEach(() => { + stateManager = mock(); + machineState = new AvmMachineState([]); + }); + + it('Should implement JUMP', () => { + const jumpLocation = 22; + + expect(machineState.pc).toBe(0); + + const instruction = new Jump(jumpLocation); + instruction.execute(machineState, stateManager); + expect(machineState.pc).toBe(jumpLocation); + }); + + it('Should implement Internal Call and Return', () => { + const jumpLocation = 22; + + expect(machineState.pc).toBe(0); + + const instruction = new InternalCall(jumpLocation); + const returnInstruction = new InternalReturn(); + + instruction.execute(machineState, stateManager); + expect(machineState.pc).toBe(jumpLocation); + + returnInstruction.execute(machineState, stateManager); + expect(machineState.pc).toBe(1); + }); + + it('Should increment PC on All other Instructions', () => { + const instructions = [ + new Add(0, 1, 2), + new Sub(0, 1, 2), + new Mul(0, 1, 2), + new Lt(0, 1, 2), + new Lte(0, 1, 2), + new Eq(0, 1, 2), + new Xor(0, 1, 2), + new And(0, 1, 2), + new Or(0, 1, 2), + new Shl(0, 1, 2), + new Shr(0, 1, 2), + new Not(0, 2), + new CalldataCopy(0, 1, 2), + new Set(0n, 1), + new Mov(0, 1), + new Cast(0, 1), + ]; + + for (const instruction of instructions) { + // Use a fresh machine state each run + const innerMachineState = new AvmMachineState([]); + expect(machineState.pc).toBe(0); + instruction.execute(innerMachineState, stateManager); + expect(innerMachineState.pc).toBe(1); + } + }); +}); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts index 5d6e8fc9fd15..c45b657f1485 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -1,4 +1,3 @@ -import { machine } from 'os'; import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Instruction } from './instruction.js'; @@ -9,8 +8,8 @@ export class Return extends Instruction { static numberOfOperands = 2; constructor(private returnOffset: number, private copySize: number) { -super(); -} + super(); + } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const returnData = machineState.readMemoryChunk(this.returnOffset, this.returnOffset + this.copySize); @@ -68,7 +67,6 @@ export class InternalReturn extends Instruction { } } - /** * Thrown if the internal call stack is popped when it is empty */ @@ -76,4 +74,4 @@ export class InternalCallStackEmptyError extends Error { constructor() { super('Internal call stack is empty'); } -} \ No newline at end of file +} From 644b3171a2ee4ca937134fa95257664d021250c6 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:40:38 +0000 Subject: [PATCH 03/12] test: add test for error case --- .../acir-simulator/src/avm/opcodes/control_flow.test.ts | 7 ++++++- .../acir-simulator/src/avm/opcodes/control_flow.ts | 3 --- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts index fd7bb2f6598d..19d3e0ab0c1d 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts @@ -5,7 +5,7 @@ import { AvmStateManager } from '../avm_state_manager.js'; import { Add, Mul, Sub } from './arithmetic.js'; import { And, Not, Or, Shl, Shr, Xor } from './bitwise.js'; import { Eq, Lt, Lte } from './comparators.js'; -import { InternalCall, InternalReturn, Jump } from './control_flow.js'; +import { InternalCall, InternalCallStackEmptyError, InternalReturn, Jump } from './control_flow.js'; import { CalldataCopy, Cast, Mov, Set } from './memory.js'; describe('Control Flow Opcodes', () => { @@ -42,6 +42,11 @@ describe('Control Flow Opcodes', () => { expect(machineState.pc).toBe(1); }); + it("Should error if Internal Return is called without a corresponding Internal Call", () => { + const returnInstruction = new InternalReturn(); + expect(() => returnInstruction.execute(machineState, stateManager)).toThrow(InternalCallStackEmptyError); + }); + it('Should increment PC on All other Instructions', () => { const instructions = [ new Add(0, 1, 2), diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts index c45b657f1485..4c62ef9d7d59 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -27,7 +27,6 @@ export class Jump extends Instruction { } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { - // TODO: this jump offset must be range constrained. machineState.pc = this.jumpOffset; } } @@ -42,7 +41,6 @@ export class InternalCall extends Instruction { } execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { - // TODO: this jump offset must be range constrained. machineState.internalCallStack.push(machineState.pc + 1); machineState.pc = this.jumpOffset; } @@ -60,7 +58,6 @@ export class InternalReturn extends Instruction { execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const jumpOffset = machineState.internalCallStack.pop(); if (jumpOffset === undefined) { - // TODO: Add in an error if this pop is undefined throw new InternalCallStackEmptyError(); } machineState.pc = jumpOffset; From 86dc711af68896da810996928b692be2f7c972af Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:55:54 +0000 Subject: [PATCH 04/12] feat: add error for invalid program counter --- .../src/avm/interpreter/interpreter.ts | 20 +++++++++++ .../src/avm/opcodes/control_flow.test.ts | 34 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 51742fa84079..3862eb72733e 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -32,6 +32,11 @@ export class AvmInterpreter { try { while (this.machineState.pc < this.instructions.length) { const instruction = this.instructions[this.machineState.pc]; + + if (!instruction) { + this.invalidProgramCounter(); + } + instruction.execute(this.machineState, this.stateManager); } @@ -52,4 +57,19 @@ export class AvmInterpreter { returnData(): Fr[] { return this.machineState.getReturnData(); } + + private invalidProgramCounter(): void { + throw new InvalidProgramCounterError(this.machineState.pc, this.instructions.length); + } } + + +/** + * Error is thrown when the program counter goes to an invalid location. + * There is no instruction at the provided pc + */ +export class InvalidProgramCounterError extends Error { + constructor(pc: number, max: number) { + super(`Invalid program counter ${pc}, max is ${max}`); + } +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts index 19d3e0ab0c1d..214b52bc7144 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts @@ -42,6 +42,40 @@ describe('Control Flow Opcodes', () => { expect(machineState.pc).toBe(1); }); + it("Should chain series of control flow instructions", () => { + const jumpLocation0 = 22; + const jumpLocation1 = 69; + const jumpLocation2 = 1337; + + const aloneJumpLocation = 420; + + const instructions = [ // pc | internal call stack + new InternalCall(jumpLocation0), // 22 | [1] + new InternalCall(jumpLocation1), // 69 | [1, 23] + new InternalReturn(), // 23 | [1] + new Jump(aloneJumpLocation), // 420 | [1] + new InternalCall(jumpLocation2), // 1337| [1, 421] + new InternalReturn(), // 421 | [1] + new InternalReturn(), // 1 | [] + ]; + + // The expected program counter after each instruction is invoked + const expectedPcs = [ + jumpLocation0, + jumpLocation1, + jumpLocation0 + 1, + aloneJumpLocation, + jumpLocation2, + aloneJumpLocation + 1, + 1, + ]; + + for (let i = 0; i < instructions.length; i++) { + instructions[i].execute(machineState, stateManager); + expect(machineState.pc).toBe(expectedPcs[i]); + } + }); + it("Should error if Internal Return is called without a corresponding Internal Call", () => { const returnInstruction = new InternalReturn(); expect(() => returnInstruction.execute(machineState, stateManager)).toThrow(InternalCallStackEmptyError); From 8dbdf94c37cd41a0c6420d37f0ebafa9a31578bb Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:19:34 +0000 Subject: [PATCH 05/12] test: revert with invalid jump dest --- .../src/avm/avm_machine_state.ts | 7 ++++ .../src/avm/interpreter/interpreter.test.ts | 20 +++++++++-- .../src/avm/interpreter/interpreter.ts | 30 ++++++++++++---- .../src/avm/opcodes/control_flow.test.ts | 35 ++++++++++--------- .../src/avm/opcodes/control_flow.ts | 2 ++ .../src/avm/opcodes/instruction.ts | 4 +++ 6 files changed, 73 insertions(+), 25 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/avm_machine_state.ts b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts index 74973e5a10d9..53e8599b7c37 100644 --- a/yarn-project/acir-simulator/src/avm/avm_machine_state.ts +++ b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts @@ -23,6 +23,11 @@ export class AvmMachineState { /** - */ public callStack: number[]; + /** + * If an instruction triggers a halt, then it ends execution of the VM + */ + public halted: boolean; + /** * Create a new avm context * @param calldata - @@ -35,6 +40,8 @@ export class AvmMachineState { this.pc = 0; this.callStack = []; + + this.halted = false; } /** diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts index 19fb0e99b546..8d2035f7151a 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -5,10 +5,10 @@ import { mock } from 'jest-mock-extended'; import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; import { Add } from '../opcodes/arithmetic.js'; -import { Return } from '../opcodes/control_flow.js'; +import { Jump, Return } from '../opcodes/control_flow.js'; import { Instruction } from '../opcodes/instruction.js'; import { CalldataCopy } from '../opcodes/memory.js'; -import { AvmInterpreter } from './interpreter.js'; +import { AvmInterpreter, InvalidProgramCounterError } from './interpreter.js'; describe('interpreter', () => { it('Should execute a series of instructions', () => { @@ -34,4 +34,20 @@ describe('interpreter', () => { expect(returnData.length).toBe(1); expect(returnData).toEqual([new Fr(3)]); }); + + it('Should revert with an invalid jump', () => { + const calldata: Fr[] = []; + const stateManager = mock(); + + const invalidJumpDestination = 22; + + const instructions: Instruction[] = [new Jump(invalidJumpDestination)]; + + const context = new AvmMachineState(calldata); + const interpreter = new AvmInterpreter(context, stateManager, instructions); + + const avmReturnData = interpreter.run(); + + expect(avmReturnData.reverted).toBe(true); + }); }); diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 3862eb72733e..8765a8f3fa5b 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -30,14 +30,20 @@ export class AvmInterpreter { */ run(): AvmMessageCallResult { try { - while (this.machineState.pc < this.instructions.length) { + while (!this.machineState.halted && this.machineState.pc < this.instructions.length) { const instruction = this.instructions[this.machineState.pc]; if (!instruction) { - this.invalidProgramCounter(); + this.failedAssertInstruction(); } instruction.execute(this.machineState, this.stateManager); + + console.log(this.machineState.pc); + if (this.machineState.pc >= this.instructions.length) { + console.log('hit'); + this.failedAssertProgramCounter(); + } } const returnData = this.machineState.getReturnData(); @@ -58,13 +64,16 @@ export class AvmInterpreter { return this.machineState.getReturnData(); } - private invalidProgramCounter(): void { + private failedAssertProgramCounter(): void { throw new InvalidProgramCounterError(this.machineState.pc, this.instructions.length); } -} + private failedAssertInstruction(): void { + throw new InvalidInstructionError(this.machineState.pc); + } +} -/** +/** * Error is thrown when the program counter goes to an invalid location. * There is no instruction at the provided pc */ @@ -72,4 +81,13 @@ export class InvalidProgramCounterError extends Error { constructor(pc: number, max: number) { super(`Invalid program counter ${pc}, max is ${max}`); } -} \ No newline at end of file +} + +/** + * This assertion should never be hit - there should always be a valid instruction + */ +export class InvalidInstructionError extends Error { + constructor(pc: number) { + super(`Invalid instruction at ${pc}`); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts index 214b52bc7144..721ed684f08f 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts @@ -42,32 +42,33 @@ describe('Control Flow Opcodes', () => { expect(machineState.pc).toBe(1); }); - it("Should chain series of control flow instructions", () => { + it('Should chain series of control flow instructions', () => { const jumpLocation0 = 22; const jumpLocation1 = 69; const jumpLocation2 = 1337; const aloneJumpLocation = 420; - const instructions = [ // pc | internal call stack - new InternalCall(jumpLocation0), // 22 | [1] - new InternalCall(jumpLocation1), // 69 | [1, 23] - new InternalReturn(), // 23 | [1] - new Jump(aloneJumpLocation), // 420 | [1] - new InternalCall(jumpLocation2), // 1337| [1, 421] - new InternalReturn(), // 421 | [1] - new InternalReturn(), // 1 | [] + const instructions = [ + // pc | internal call stack + new InternalCall(jumpLocation0), // 22 | [1] + new InternalCall(jumpLocation1), // 69 | [1, 23] + new InternalReturn(), // 23 | [1] + new Jump(aloneJumpLocation), // 420 | [1] + new InternalCall(jumpLocation2), // 1337| [1, 421] + new InternalReturn(), // 421 | [1] + new InternalReturn(), // 1 | [] ]; // The expected program counter after each instruction is invoked const expectedPcs = [ - jumpLocation0, - jumpLocation1, - jumpLocation0 + 1, - aloneJumpLocation, - jumpLocation2, - aloneJumpLocation + 1, - 1, + jumpLocation0, + jumpLocation1, + jumpLocation0 + 1, + aloneJumpLocation, + jumpLocation2, + aloneJumpLocation + 1, + 1, ]; for (let i = 0; i < instructions.length; i++) { @@ -76,7 +77,7 @@ describe('Control Flow Opcodes', () => { } }); - it("Should error if Internal Return is called without a corresponding Internal Call", () => { + it('Should error if Internal Return is called without a corresponding Internal Call', () => { const returnInstruction = new InternalReturn(); expect(() => returnInstruction.execute(machineState, stateManager)).toThrow(InternalCallStackEmptyError); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts index 4c62ef9d7d59..6f4de7fc5d5b 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -14,6 +14,8 @@ export class Return extends Instruction { execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { const returnData = machineState.readMemoryChunk(this.returnOffset, this.returnOffset + this.copySize); machineState.setReturnData(returnData); + + this.halt(machineState); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts index a334b10700c4..c77a1a9ddd38 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts @@ -13,4 +13,8 @@ export abstract class Instruction { incrementPc(machineState: AvmMachineState): void { machineState.pc++; } + + halt(machineState: AvmMachineState): void { + machineState.halted = true; + } } From 76d8746263616573bb1cc37276b3fec0d5eaa29f Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:21:32 +0000 Subject: [PATCH 06/12] fix: lint --- yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 8765a8f3fa5b..fd309050e87b 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -39,9 +39,7 @@ export class AvmInterpreter { instruction.execute(this.machineState, this.stateManager); - console.log(this.machineState.pc); if (this.machineState.pc >= this.instructions.length) { - console.log('hit'); this.failedAssertProgramCounter(); } } From 40562992d12e990b8d1739cb5ee2521feca5ba24 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:34:10 +0000 Subject: [PATCH 07/12] chore: remove wrapped errors --- .../src/avm/interpreter/interpreter.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index fd309050e87b..48cd11378e94 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -34,13 +34,14 @@ export class AvmInterpreter { const instruction = this.instructions[this.machineState.pc]; if (!instruction) { - this.failedAssertInstruction(); + throw new InvalidInstructionError(this.machineState.pc); } + // TODO: do we want richer error messages here instruction.execute(this.machineState, this.stateManager); if (this.machineState.pc >= this.instructions.length) { - this.failedAssertProgramCounter(); + throw new InvalidProgramCounterError(this.machineState.pc, this.instructions.length); } } @@ -61,14 +62,6 @@ export class AvmInterpreter { returnData(): Fr[] { return this.machineState.getReturnData(); } - - private failedAssertProgramCounter(): void { - throw new InvalidProgramCounterError(this.machineState.pc, this.instructions.length); - } - - private failedAssertInstruction(): void { - throw new InvalidInstructionError(this.machineState.pc); - } } /** From ca7a23a6d22d8da9265b5d15f67b4678014ccd51 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:34:31 +0000 Subject: [PATCH 08/12] :broom --- yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 48cd11378e94..c75635996955 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -37,7 +37,6 @@ export class AvmInterpreter { throw new InvalidInstructionError(this.machineState.pc); } - // TODO: do we want richer error messages here instruction.execute(this.machineState, this.stateManager); if (this.machineState.pc >= this.instructions.length) { From 240b44b365a8d6767109036d91443ee823079113 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:38:14 +0000 Subject: [PATCH 09/12] lint --- .../acir-simulator/src/avm/interpreter/interpreter.test.ts | 2 +- .../acir-simulator/src/avm/interpreter/interpreter.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts index 8d2035f7151a..a52a2c13222e 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -8,7 +8,7 @@ import { Add } from '../opcodes/arithmetic.js'; import { Jump, Return } from '../opcodes/control_flow.js'; import { Instruction } from '../opcodes/instruction.js'; import { CalldataCopy } from '../opcodes/memory.js'; -import { AvmInterpreter, InvalidProgramCounterError } from './interpreter.js'; +import { AvmInterpreter } from './interpreter.js'; describe('interpreter', () => { it('Should execute a series of instructions', () => { diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index c75635996955..9bbd511c2d37 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -67,7 +67,7 @@ export class AvmInterpreter { * Error is thrown when the program counter goes to an invalid location. * There is no instruction at the provided pc */ -export class InvalidProgramCounterError extends Error { +class InvalidProgramCounterError extends Error { constructor(pc: number, max: number) { super(`Invalid program counter ${pc}, max is ${max}`); } @@ -76,7 +76,7 @@ export class InvalidProgramCounterError extends Error { /** * This assertion should never be hit - there should always be a valid instruction */ -export class InvalidInstructionError extends Error { +class InvalidInstructionError extends Error { constructor(pc: number) { super(`Invalid instruction at ${pc}`); } From fb1a5c050215c203248e4a76bc2b2b7efd106b4a Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:39:00 +0000 Subject: [PATCH 10/12] chore: remove underscore --- yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts index 6f4de7fc5d5b..2d8d61c6100e 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -35,7 +35,7 @@ export class Jump extends Instruction { /** -*/ export class InternalCall extends Instruction { - static type: string = 'INTERNAL_CALL'; + static type: string = 'INTERNALCALL'; static numberOfOperands = 1; constructor(private jumpOffset: number) { @@ -50,7 +50,7 @@ export class InternalCall extends Instruction { /** -*/ export class InternalReturn extends Instruction { - static type: string = 'INTERNAL_RETURN'; + static type: string = 'INTERNALRETURN'; static numberOfOperands = 0; constructor() { From a9e67f345491b1e6230f204ec6a774113da797b9 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:51:18 +0000 Subject: [PATCH 11/12] feat: conditional jump --- .../src/avm/opcodes/control_flow.test.ts | 35 ++++++++++++++++++- .../src/avm/opcodes/control_flow.ts | 20 +++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts index 721ed684f08f..864ee0d0b425 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts @@ -1,3 +1,5 @@ +import { Fr } from '@aztec/foundation/fields'; + import { mock } from 'jest-mock-extended'; import { AvmMachineState } from '../avm_machine_state.js'; @@ -5,7 +7,7 @@ import { AvmStateManager } from '../avm_state_manager.js'; import { Add, Mul, Sub } from './arithmetic.js'; import { And, Not, Or, Shl, Shr, Xor } from './bitwise.js'; import { Eq, Lt, Lte } from './comparators.js'; -import { InternalCall, InternalCallStackEmptyError, InternalReturn, Jump } from './control_flow.js'; +import { InternalCall, InternalCallStackEmptyError, InternalReturn, Jump, JumpI } from './control_flow.js'; import { CalldataCopy, Cast, Mov, Set } from './memory.js'; describe('Control Flow Opcodes', () => { @@ -27,6 +29,37 @@ describe('Control Flow Opcodes', () => { expect(machineState.pc).toBe(jumpLocation); }); + it('Should implement JUMPI - truthy', () => { + const jumpLocation = 22; + const jumpLocation1 = 69; + + expect(machineState.pc).toBe(0); + + machineState.writeMemory(0, new Fr(1n)); + machineState.writeMemory(1, new Fr(2n)); + + const instruction = new JumpI(jumpLocation, 0); + instruction.execute(machineState, stateManager); + expect(machineState.pc).toBe(jumpLocation); + + // Truthy can be greater than 1 + const instruction1 = new JumpI(jumpLocation1, 1); + instruction1.execute(machineState, stateManager); + expect(machineState.pc).toBe(jumpLocation1); + }); + + it('Should implement JUMPI - falsy', () => { + const jumpLocation = 22; + + expect(machineState.pc).toBe(0); + + machineState.writeMemory(0, new Fr(0n)); + + const instruction = new JumpI(jumpLocation, 0); + instruction.execute(machineState, stateManager); + expect(machineState.pc).toBe(1); + }); + it('Should implement Internal Call and Return', () => { const jumpLocation = 22; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts index 2d8d61c6100e..b988979076e1 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -33,6 +33,26 @@ export class Jump extends Instruction { } } +/** -*/ +export class JumpI extends Instruction { + static type: string = 'JUMPI'; + static numberOfOperands = 1; + + constructor(private jumpOffset: number, private condOffset: number) { + super(); + } + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const condition = machineState.readMemory(this.condOffset); + + if (condition.toBigInt() == 0n) { + this.incrementPc(machineState); + } else { + machineState.pc = this.jumpOffset; + } + } +} + /** -*/ export class InternalCall extends Instruction { static type: string = 'INTERNALCALL'; From c1c5a0624ee51c8249d1d1230d397018c50b2e89 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:53:24 +0000 Subject: [PATCH 12/12] chore: add opcodes to instruction set --- .../acir-simulator/src/avm/opcodes/instruction_set.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn-project/acir-simulator/src/avm/opcodes/instruction_set.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction_set.ts index 5d8c8921daf5..1211205b979d 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/instruction_set.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/instruction_set.ts @@ -6,7 +6,7 @@ import { } from './arithmetic.js'; //import { And, Not, Or, Shl, Shr, Xor } from './bitwise.js'; //import { Eq, Lt, Lte } from './comparators.js'; -import { Return } from './control_flow.js'; +import { InternalCall, InternalReturn, Jump, JumpI, Return } from './control_flow.js'; import { Instruction } from './instruction.js'; import { CalldataCopy, @@ -72,10 +72,10 @@ export const INSTRUCTION_SET: Map = ne //[Opcode.L2GASLEFT, L2gasleft], //[Opcode.DAGASLEFT, Dagasleft], //// Machine State - Internal Control Flow - //[Opcode.JUMP, Jump], - //[Opcode.JUMPI, Jumpi], - //[Opcode.INTERNALCALL, Internalcall], - //[Opcode.INTERNALRETURN, Internalreturn], + [Opcode.JUMP, Jump], + [Opcode.JUMPI, JumpI], + [Opcode.INTERNALCALL, InternalCall], + [Opcode.INTERNALRETURN, InternalReturn], //// Machine State - Memory //[Opcode.SET, Set], //[Opcode.MOV, Mov],