From 432f6d1ffa56754e0bbcfc99368e417196963efe Mon Sep 17 00:00:00 2001 From: Maddiaa <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:26:13 +0000 Subject: [PATCH] feat: avm executor - layout and groundwork (#3928) --- .gitignore | 2 + .../src/avm/avm_context.test.ts | 3 + .../acir-simulator/src/avm/avm_context.ts | 44 ++++ .../src/avm/avm_machine_state.ts | 79 +++++++ .../src/avm/avm_message_call_result.ts | 34 +++ .../src/avm/avm_state_manager.ts | 44 ++++ .../acir-simulator/src/avm/fixtures/index.ts | 1 + yarn-project/acir-simulator/src/avm/index.ts | 3 + .../src/avm/interpreter/index.ts | 1 + .../src/avm/interpreter/interpreter.test.ts | 37 ++++ .../src/avm/interpreter/interpreter.ts | 54 +++++ .../src/avm/journal/host_storage.ts | 18 ++ .../acir-simulator/src/avm/journal/index.ts | 2 + .../src/avm/journal/journal.test.ts | 7 + .../acir-simulator/src/avm/journal/journal.ts | 101 +++++++++ .../src/avm/opcodes/arithmetic.ts | 70 ++++++ .../acir-simulator/src/avm/opcodes/bitwise.ts | 100 +++++++++ .../acir-simulator/src/avm/opcodes/call.ts | 0 .../src/avm/opcodes/comparators.ts | 52 +++++ .../src/avm/opcodes/control_flow.ts | 16 ++ .../src/avm/opcodes/from_bytecode.test.ts | 36 ++++ .../src/avm/opcodes/from_bytecode.ts | 62 ++++++ .../acir-simulator/src/avm/opcodes/index.ts | 6 + .../src/avm/opcodes/instruction.ts | 9 + .../acir-simulator/src/avm/opcodes/memory.ts | 60 ++++++ .../acir-simulator/src/avm/opcodes/opcodes.ts | 77 +++++++ .../docs/public-vm/gen/_InstructionSet.mdx | 204 +++++++++--------- .../InstructionSet/InstructionSet.js | 40 ++-- 28 files changed, 1042 insertions(+), 120 deletions(-) create mode 100644 yarn-project/acir-simulator/src/avm/avm_context.test.ts create mode 100644 yarn-project/acir-simulator/src/avm/avm_context.ts create mode 100644 yarn-project/acir-simulator/src/avm/avm_machine_state.ts create mode 100644 yarn-project/acir-simulator/src/avm/avm_message_call_result.ts create mode 100644 yarn-project/acir-simulator/src/avm/avm_state_manager.ts create mode 100644 yarn-project/acir-simulator/src/avm/fixtures/index.ts create mode 100644 yarn-project/acir-simulator/src/avm/index.ts create mode 100644 yarn-project/acir-simulator/src/avm/interpreter/index.ts create mode 100644 yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts create mode 100644 yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts create mode 100644 yarn-project/acir-simulator/src/avm/journal/host_storage.ts create mode 100644 yarn-project/acir-simulator/src/avm/journal/index.ts create mode 100644 yarn-project/acir-simulator/src/avm/journal/journal.test.ts create mode 100644 yarn-project/acir-simulator/src/avm/journal/journal.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/call.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/comparators.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/index.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/instruction.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/memory.ts create mode 100644 yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts diff --git a/.gitignore b/.gitignore index 3ab25e14bb4..a7ebba4e7a3 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ cmake-build-debug # Local Netlify folder .netlify + +.graphite* \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/avm/avm_context.test.ts b/yarn-project/acir-simulator/src/avm/avm_context.test.ts new file mode 100644 index 00000000000..05e9cf10f3e --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_context.test.ts @@ -0,0 +1,3 @@ +describe('Avm', () => { + it('Executes a simple call', () => {}); +}); diff --git a/yarn-project/acir-simulator/src/avm/avm_context.ts b/yarn-project/acir-simulator/src/avm/avm_context.ts new file mode 100644 index 00000000000..9d52f869acf --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_context.ts @@ -0,0 +1,44 @@ +import { Fr } from '@aztec/foundation/fields'; + +import { AvmMachineState } from './avm_machine_state.js'; +import { AvmMessageCallResult } from './avm_message_call_result.js'; +import { AvmStateManager } from './avm_state_manager.js'; +import { AvmInterpreter } from './interpreter/index.js'; +import { interpretBytecode } from './opcodes/from_bytecode.js'; +import { Instruction } from './opcodes/index.js'; + +/** + * Avm Executor manages the execution of the AVM + * + * It stores a state manager + */ +export class AvmContext { + private stateManager: AvmStateManager; + + constructor(stateManager: AvmStateManager) { + this.stateManager = stateManager; + } + + /** + * Call a contract with the given calldata + * + * - We get the contract from storage + * - We interpret the bytecode + * - We run the interpreter + * + * @param contractAddress - + * @param calldata - + */ + public call(contractAddress: Fr, calldata: Fr[]): AvmMessageCallResult { + // NOTE: the following is mocked as getPublicBytecode does not exist yet + // const bytecode = stateManager.journal.hostStorage.contractsDb.getBytecode(contractAddress); + const bytecode = Buffer.from('0x01000100020003'); + + const instructions: Instruction[] = interpretBytecode(bytecode); + + const context = new AvmMachineState(calldata); + const interpreter = new AvmInterpreter(context, this.stateManager, instructions); + + return interpreter.run(); + } +} diff --git a/yarn-project/acir-simulator/src/avm/avm_machine_state.ts b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts new file mode 100644 index 00000000000..aa0fc689b58 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_machine_state.ts @@ -0,0 +1,79 @@ +import { Fr } from '@aztec/foundation/fields'; + +/** + * Store's data for an Avm execution frame + */ +export class AvmMachineState { + /** - */ + public readonly calldata: Fr[]; + private returnData: Fr[]; + + // TODO: implement tagged memory + /** - */ + public memory: Fr[]; + + /** - */ + public pc: number; + /** - */ + public callStack: number[]; + + /** + * Create a new avm context + * @param calldata - + */ + constructor(calldata: Fr[]) { + this.calldata = calldata; + this.returnData = []; + this.memory = []; + + this.pc = 0; + this.callStack = []; + } + + /** + * Return data must NOT be modified once it is set + * @param returnData - + */ + public setReturnData(returnData: Fr[]) { + this.returnData = returnData; + Object.freeze(returnData); + } + + /** - */ + public getReturnData(): Fr[] { + return this.returnData; + } + + /** - + * @param offset - + */ + public readMemory(offset: number): Fr { + // TODO: check offset is within bounds + return this.memory[offset] ?? Fr.ZERO; + } + + /** - + * @param offset - + * @param size - + */ + public readMemoryChunk(offset: number, size: number): Fr[] { + // TODO: bounds -> initialise to 0 + return this.memory.slice(offset, offset + size); + } + + /** - + * @param offset - + * @param value - + */ + public writeMemory(offset: number, value: Fr): void { + this.memory[offset] = value; + } + + /** - + * @param offset - + * @param values - + */ + public writeMemoryChunk(offset: number, values: Fr[]): void { + this.memory.splice(offset, values.length, ...values); + } +} diff --git a/yarn-project/acir-simulator/src/avm/avm_message_call_result.ts b/yarn-project/acir-simulator/src/avm/avm_message_call_result.ts new file mode 100644 index 00000000000..ec258dff112 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_message_call_result.ts @@ -0,0 +1,34 @@ +import { Fr } from '@aztec/foundation/fields'; + +/** + * AVM message call result. + */ +export class AvmMessageCallResult { + /** - */ + public readonly reverted: boolean; + /** .- */ + public readonly output: Fr[]; + + constructor(reverted: boolean, output: Fr[]) { + this.reverted = reverted; + this.output = output; + } + + /** + * Terminate a call as a success + * @param output - Return data + * @returns instance of AvmMessageCallResult + */ + public static success(output: Fr[]): AvmMessageCallResult { + return new AvmMessageCallResult(false, output); + } + + /** + * Terminate a call as a revert + * @param output - Return data ( revert message ) + * @returns instance of AvmMessageCallResult + */ + public static revert(output: Fr[]): AvmMessageCallResult { + return new AvmMessageCallResult(true, output); + } +} diff --git a/yarn-project/acir-simulator/src/avm/avm_state_manager.ts b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts new file mode 100644 index 00000000000..250a7d8f122 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/avm_state_manager.ts @@ -0,0 +1,44 @@ +import { BlockHeader } from '@aztec/circuits.js'; + +import { AvmJournal, HostStorage } from './journal/index.js'; + +/** + * The Avm State Manager is the interpreter's interface to the node's state + * It creates revertible views into the node state and manages the current call's journal + */ +export class AvmStateManager { + /** - */ + public readonly blockHeader: BlockHeader; + + /** + * Journal keeps track of pending state changes + */ + public readonly journal: AvmJournal; + + constructor(blockHeader: BlockHeader, journal: AvmJournal) { + this.blockHeader = blockHeader; + this.journal = journal; + } + + /** + * Create a base state root manager + * - this should be created by the highest level item where the state + * can be reverted + * @param blockHeader - + * @param hostStorage - An immutable view into the node db + * @returns Avm State Manager + */ + public static rootStateManager(blockHeader: BlockHeader, hostStorage: HostStorage): AvmStateManager { + const journal = new AvmJournal(hostStorage); + return new AvmStateManager(blockHeader, journal); + } + + /** + * Avm State + * @param parent - Avm state manager with a forked journal + * @returns + */ + public static forkStateManager(parent: AvmStateManager): AvmStateManager { + return new AvmStateManager(parent.blockHeader, parent.journal); + } +} diff --git a/yarn-project/acir-simulator/src/avm/fixtures/index.ts b/yarn-project/acir-simulator/src/avm/fixtures/index.ts new file mode 100644 index 00000000000..cfab7bc47b8 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/fixtures/index.ts @@ -0,0 +1 @@ +// Place large AVM text fixtures in here diff --git a/yarn-project/acir-simulator/src/avm/index.ts b/yarn-project/acir-simulator/src/avm/index.ts new file mode 100644 index 00000000000..6a102a6cd57 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/index.ts @@ -0,0 +1,3 @@ +export * from './avm_machine_state.js'; +export * from './avm_context.js'; +export * from './avm_state_manager.js'; diff --git a/yarn-project/acir-simulator/src/avm/interpreter/index.ts b/yarn-project/acir-simulator/src/avm/interpreter/index.ts new file mode 100644 index 00000000000..d5189852b6e --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/interpreter/index.ts @@ -0,0 +1 @@ +export * from './interpreter.js'; diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts new file mode 100644 index 00000000000..3b86414ab63 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -0,0 +1,37 @@ +import { Fr } from '@aztec/foundation/fields'; + +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 { Instruction } from '../opcodes/instruction.js'; +import { CallDataCopy } from '../opcodes/memory.js'; +import { AvmInterpreter } from './interpreter.js'; + +describe('interpreter', () => { + it('Should execute a series of instructions', () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const stateManager = mock(); + + const instructions: Instruction[] = [ + // Copy the first two elements of the calldata to memory regions 0 and 1 + new CallDataCopy(0, 2, 0), + // Add the two together and store the result in memory region 2 + new Add(0, 1, 2), // 1 + 2 + // Return the result + new Return(2, 1), // [3] + ]; + + const context = new AvmMachineState(calldata); + const interpreter = new AvmInterpreter(context, stateManager, instructions); + const avmReturnData = interpreter.run(); + + expect(avmReturnData.reverted).toBe(false); + + const returnData = avmReturnData.output; + expect(returnData.length).toBe(1); + expect(returnData).toEqual([new Fr(3)]); + }); +}); diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts new file mode 100644 index 00000000000..47fb9c3771d --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -0,0 +1,54 @@ +// import { AvmContext } from "../avm_machineState.js"; +import { Fr } from '@aztec/foundation/fields'; + +import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmMessageCallResult } from '../avm_message_call_result.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Instruction } from '../opcodes/index.js'; + +/** + * Avm Interpreter + * + * Executes an Avm context + */ +export class AvmInterpreter { + private instructions: Instruction[] = []; + private machineState: AvmMachineState; + private stateManager: AvmStateManager; + + constructor(machineState: AvmMachineState, stateManager: AvmStateManager, bytecode: Instruction[]) { + this.machineState = machineState; + this.stateManager = stateManager; + this.instructions = bytecode; + } + + /** + * Run the avm + * @returns bool - successful execution will return true + * - reverted execution will return false + * - any other panic will throw + */ + run(): AvmMessageCallResult { + try { + for (const instruction of this.instructions) { + instruction.execute(this.machineState, this.stateManager); + } + + const returnData = this.machineState.getReturnData(); + return AvmMessageCallResult.success(returnData); + } catch (e) { + // TODO: This should only accept AVM defined errors, anything else SHOULD be thrown upstream + const revertData = this.machineState.getReturnData(); + return AvmMessageCallResult.revert(revertData); + } + } + + /** + * Get the return data from avm execution + * TODO: this should fail if the code has not been executed + * - maybe move the return in run into a variable and track it + */ + returnData(): Fr[] { + return this.machineState.getReturnData(); + } +} diff --git a/yarn-project/acir-simulator/src/avm/journal/host_storage.ts b/yarn-project/acir-simulator/src/avm/journal/host_storage.ts new file mode 100644 index 00000000000..ccd31891205 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/journal/host_storage.ts @@ -0,0 +1,18 @@ +import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js'; + +/** - */ +export class HostStorage { + /** - */ + public readonly stateDb: PublicStateDB; + /** - */ + public readonly contractsDb: PublicContractsDB; + + /** - */ + public readonly commitmentsDb: CommitmentsDB; + + constructor(stateDb: PublicStateDB, contractsDb: PublicContractsDB, commitmentsDb: CommitmentsDB) { + this.stateDb = stateDb; + this.contractsDb = contractsDb; + this.commitmentsDb = commitmentsDb; + } +} diff --git a/yarn-project/acir-simulator/src/avm/journal/index.ts b/yarn-project/acir-simulator/src/avm/journal/index.ts new file mode 100644 index 00000000000..86fe25115d8 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/journal/index.ts @@ -0,0 +1,2 @@ +export * from './host_storage.js'; +export * from './journal.js'; diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.test.ts b/yarn-project/acir-simulator/src/avm/journal/journal.test.ts new file mode 100644 index 00000000000..c356db72386 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/journal/journal.test.ts @@ -0,0 +1,7 @@ +describe('journal', () => { + it('Should write to storage', () => {}); + + it('Should read from storage', () => {}); + + it('Should merge two journals together', () => {}); +}); diff --git a/yarn-project/acir-simulator/src/avm/journal/journal.ts b/yarn-project/acir-simulator/src/avm/journal/journal.ts new file mode 100644 index 00000000000..67563fe6370 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/journal/journal.ts @@ -0,0 +1,101 @@ +import { Fr } from '@aztec/foundation/fields'; + +import { HostStorage } from './host_storage.js'; + +// TODO: all of the data that comes out of the avm ready for write should be in this format +/** - */ +export type JournalData = { + /** - */ + newCommitments: Fr[]; + /** - */ + newL1Message: Fr[]; + /** - */ + storageWrites: { [key: string]: { [key: string]: Fr } }; +}; + +// This persists for an entire block +// Each transaction should have its own journal that gets appended to this one upon success +/** - */ +export class AvmJournal { + // TODO: should we make private? + /** - */ + public readonly hostStorage: HostStorage; + + // We need to keep track of the following + // - State reads + // - State updates + // - New Commitments + // - Commitment reads + + private newCommitments: Fr[] = []; + private newL1Message: Fr[] = []; + + // TODO: type this structure -> contract address -> key -> value + private storageWrites: { [key: string]: { [key: string]: Fr } } = {}; + + constructor(hostStorage: HostStorage) { + this.hostStorage = hostStorage; + } + + // TODO: work on the typing + /** + * - + * @param contractAddress - + * @param key - + * @param value - + */ + public writeStorage(contractAddress: Fr, key: Fr, value: Fr) { + // TODO: do we want this map to be ordered -> is there performance upside to this? + this.storageWrites[contractAddress.toString()][key.toString()] = value; + } + + /** + * - + * @param contractAddress - + * @param key - + */ + public readStorage(contractAddress: Fr, key: Fr) { + const cachedValue = this.storageWrites[contractAddress.toString()][key.toString()]; + if (cachedValue) { + return cachedValue; + } + return this.hostStorage.stateDb.storageRead(contractAddress, key); + } + + /** + * - + * @param commitment - + */ + public writeCommitment(commitment: Fr) { + this.newCommitments.push(commitment); + } + + /** + * - + * @param message - + */ + public writeL1Message(message: Fr) { + this.newL1Message.push(message); + } + + // TODO: This function will merge two journals together -> the new head of the chain + /** + * - + * @param journal - + */ + public mergeJournal(journal: AvmJournal) { + // TODO: This function will + void journal; + } + + /** + * - + */ + public flush(): JournalData { + return { + newCommitments: this.newCommitments, + newL1Message: this.newL1Message, + storageWrites: this.storageWrites, + }; + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts new file mode 100644 index 00000000000..ab6465debf4 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -0,0 +1,70 @@ +import { Fr } from '@aztec/foundation/fields'; + +import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Instruction } from './instruction.js'; + +/** -*/ +export class Add implements Instruction { + static type: string = 'ADD'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + 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)); + machineState.writeMemory(this.destOffset, dest); + } +} + +/** -*/ +export class Sub implements Instruction { + static type: string = 'SUB'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + 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)); + machineState.writeMemory(this.destOffset, dest); + } +} + +/** -*/ +export class Mul implements Instruction { + static type: string = 'MUL'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); + + const dest = new Fr((a.toBigInt() * b.toBigInt()) % Fr.MODULUS); + machineState.writeMemory(this.destOffset, dest); + } +} + +/** -*/ +export class Div implements Instruction { + static type: string = 'DIV'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); + + // 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); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts new file mode 100644 index 00000000000..62376269d8e --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -0,0 +1,100 @@ +import { Fr } from '@aztec/foundation/fields'; + +import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Instruction } from './instruction.js'; + +/** - */ +export class And implements Instruction { + static type: string = 'AND'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() & b.toBigInt()); + machineState.writeMemory(this.destOffset, dest); + } +} + +/** - */ +export class Or implements Instruction { + static type: string = 'OR'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() | b.toBigInt()); + machineState.writeMemory(this.destOffset, dest); + } +} + +/** - */ +export class Xor implements Instruction { + static type: string = 'XOR'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() ^ b.toBigInt()); + machineState.writeMemory(this.destOffset, dest); + } +} + +/** - */ +export class Not implements Instruction { + static type: string = 'NOT'; + static numberOfOperands = 2; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + + const dest = new Fr(~a.toBigInt()); + machineState.writeMemory(this.destOffset, dest); + } +} + +/** -*/ +export class Shl implements Instruction { + static type: string = 'SHL'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() << b.toBigInt()); + machineState.writeMemory(this.destOffset, dest); + } +} + +/** -*/ +export class Shr implements Instruction { + static type: string = 'SHR'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() >> b.toBigInt()); + machineState.writeMemory(this.destOffset, dest); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/call.ts b/yarn-project/acir-simulator/src/avm/opcodes/call.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts new file mode 100644 index 00000000000..ddd3c9d1de9 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts @@ -0,0 +1,52 @@ +import { Fr } from '@aztec/foundation/fields'; + +import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Instruction } from './instruction.js'; + +/** -*/ +export class Eq implements Instruction { + static type: string = 'EQ'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() == b.toBigInt()); + machineState.writeMemory(this.destOffset, dest); + } +} +/** -*/ +export class Lt implements Instruction { + static type: string = 'Lt'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() < b.toBigInt()); + machineState.writeMemory(this.destOffset, dest); + } +} + +/** -*/ +export class Lte implements Instruction { + static type: string = 'LTE'; + static numberOfOperands = 3; + + constructor(private aOffset: number, private bOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a: Fr = machineState.readMemory(this.aOffset); + const b: Fr = machineState.readMemory(this.bOffset); + + const dest = new Fr(a.toBigInt() < b.toBigInt()); + machineState.writeMemory(this.destOffset, dest); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts new file mode 100644 index 00000000000..31336e397be --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -0,0 +1,16 @@ +import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Instruction } from './instruction.js'; + +/** - */ +export class Return implements Instruction { + static type: string = 'RETURN'; + static numberOfOperands = 2; + + constructor(private returnOffset: number, private copySize: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const returnData = machineState.readMemoryChunk(this.returnOffset, this.returnOffset + this.copySize); + machineState.setReturnData(returnData); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts new file mode 100644 index 00000000000..e7f95b89b4f --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.test.ts @@ -0,0 +1,36 @@ +import { Add, Sub } from './arithmetic.js'; +import { OPCODE_BYTE_LENGTH, OPERAND_BTYE_LENGTH, interpretBytecode } from './from_bytecode.js'; +import { Instruction } from './instruction.js'; + +describe('Avm Interpreter', () => { + const toByte = (num: number): Buffer => { + const buf = Buffer.alloc(OPCODE_BYTE_LENGTH); + buf.writeUInt8(num); + return buf; + }; + const to4Byte = (num: number): Buffer => { + const buf = Buffer.alloc(OPERAND_BTYE_LENGTH); + buf.writeUInt32BE(num); + return buf; + }; + + it('Should read bytecode string into a list of opcodes', () => { + const opcode = 1; + const opcode2 = 2; + const a = 1; + const b = 2; + const c = 3; + + const ops = toByte(opcode); + const ops2 = toByte(opcode2); + const as = to4Byte(a); + const bs = to4Byte(b); + const cs = to4Byte(c); + const bytecode = Buffer.concat([ops, as, bs, cs, ops2, as, bs, cs]); + + const expectedInstructions: Instruction[] = [new Add(a, b, c), new Sub(a, b, c)]; + + const instructions = interpretBytecode(bytecode); + expect(instructions).toEqual(expectedInstructions); + }); +}); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts new file mode 100644 index 00000000000..697ff6be13b --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts @@ -0,0 +1,62 @@ +import { Add, Mul, Sub } from './arithmetic.js'; +import { Instruction } from './instruction.js'; + +export const OPERAND_BIT_LENGTH = 32; +export const OPERAND_BTYE_LENGTH = 4; +export const OPCODE_BIT_LENGTH = 8; +export const OPCODE_BYTE_LENGTH = 1; + +const OPERANDS_LOOKUP: { [key: number]: number } = { + 0x1: Add.numberOfOperands, + 0x2: Sub.numberOfOperands, + 0x3: Mul.numberOfOperands, +}; + +/** + * Given the opcode and operands that have been parsed by the interpreter + * We return a construction of the opcode + * + * @param opcode - Opcode value + * @param operands - Array of operands + */ +function instructionLookup(opcode: number, operands: number[]): Instruction { + switch (opcode) { + case 0x1: + return new Add(operands[0], operands[1], operands[2]); + case 0x2: + return new Sub(operands[0], operands[1], operands[2]); + case 0x3: + return new Mul(operands[0], operands[1], operands[2]); + default: + throw new Error(`Opcode ${opcode} not found`); + } +} + +/** + * Convert a buffer of bytecode into an array of instructions + * @param bytecode - Buffer of bytecode + * @returns Bytecode interpreted into an ordered array of Instructions + */ +export function interpretBytecode(bytecode: Buffer): Instruction[] { + let readPtr = 0; + const bytecodeLength = bytecode.length; + + const instructions: Instruction[] = []; + + while (readPtr < bytecodeLength) { + const opcode = bytecode[readPtr]; + readPtr += 1; + + const numberOfOperands = OPERANDS_LOOKUP[opcode]; + const operands: number[] = []; + for (let i = 0; i < numberOfOperands; i++) { + const operand = bytecode.readUInt32BE(readPtr); + readPtr += OPERAND_BTYE_LENGTH; + operands.push(operand); + } + + instructions.push(instructionLookup(opcode, operands)); + } + + return instructions; +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/index.ts b/yarn-project/acir-simulator/src/avm/opcodes/index.ts new file mode 100644 index 00000000000..f1f479bab46 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/index.ts @@ -0,0 +1,6 @@ +export * from './arithmetic.js'; +export * from './control_flow.js'; +export * from './call.js'; +export * from './instruction.js'; +export * from './comparators.js'; +export * from './memory.js'; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts new file mode 100644 index 00000000000..0411251b705 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts @@ -0,0 +1,9 @@ +import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmStateManager } from '../avm_state_manager.js'; + +/** + * Opcode base class + */ +export abstract class Instruction { + abstract execute(machineState: AvmMachineState, stateManager: AvmStateManager): void; +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts new file mode 100644 index 00000000000..38fa560a3e6 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -0,0 +1,60 @@ +import { Fr } from '@aztec/foundation/fields'; + +import { AvmMachineState } from '../avm_machine_state.js'; +import { AvmStateManager } from '../avm_state_manager.js'; +import { Instruction } from './instruction.js'; + +/** - */ +export class Set implements Instruction { + static type: string = 'SET'; + static numberOfOperands = 2; + + constructor(private constt: bigint, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const dest = new Fr(this.constt); + machineState.writeMemory(this.destOffset, dest); + } +} + +// 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 { + static type: string = 'CAST'; + static numberOfOperands = 2; + + constructor(private aOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a = machineState.readMemory(this.aOffset); + + machineState.writeMemory(this.destOffset, a); + } +} + +/** - */ +export class Mov implements Instruction { + static type: string = 'MOV'; + static numberOfOperands = 2; + + constructor(private aOffset: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const a = machineState.readMemory(this.aOffset); + + machineState.writeMemory(this.destOffset, a); + } +} + +/** - */ +export class CallDataCopy implements Instruction { + static type: string = 'CALLDATACOPY'; + static numberOfOperands = 3; + + constructor(private cdOffset: number, private copySize: number, private destOffset: number) {} + + execute(machineState: AvmMachineState, _stateManager: AvmStateManager): void { + const calldata = machineState.calldata.slice(this.cdOffset, this.cdOffset + this.copySize); + machineState.writeMemoryChunk(this.destOffset, calldata); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts new file mode 100644 index 00000000000..c277c055628 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts @@ -0,0 +1,77 @@ +/** + * All avm opcodes + */ +export enum Opcodes { + // Arithmetic + ADD, + SUB, + MUL, + DIV, + EQ, + LT, + LTE, + AND, + OR, + XOR, + NOT, + SHL, + SHR, + CAST, + // Memory + SET, + MOV, + CMOV, + CALLDATACOPY, + + // Logs + EMITNOTEHASH, + EMITNULLIFIER, + SENDL2TOL1MSG, + ULOG, + // Control flow + JUMP, + JUMPI, + INTERNALCALL, + INTERNALRETURN, + + // Storage + SLOAD, + SSTORE, + // Contract call control flow + RETURN, + REVERT, + CALL, + STATICCALL, + + CHAINID, + VERSION, + BLOCKNUMBER, + TIMESTAMP, + COINBASE, + BLOCKL1GASLIMIT, + BLOCKL2GASLIMIT, + NULLIFIERSOOT, + CONTRACTSROOT, + MSGSROOT, + NOTESROOT, + PUBLICDATAROOT, + GLOBALSHASH, + BLOCKSROOT, + GRANDROOT, + + // Call context + ORIGIN, + REFUNDEE, + FEEPERL1GAS, + FEEPERL2GAS, + CALLER, + ADDRESS, + PORTAL, + CALLDEPTH, + l1GAS, + L2GAS, + + // Black box + KECCAK, + POSEIDON, +} diff --git a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx b/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx index 60047658fa7..c362c88b840 100644 --- a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx +++ b/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx @@ -30,7 +30,15 @@ Click on an instruction name to jump to its section. } - 0x02 [`DIV`](#isa-section-div) + 0x02 [`MUL`](#isa-section-mul) + Subtraction (a - b) + 128 + { + `M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k` + } + + + 0x03 [`DIV`](#isa-section-div) Unsigned division (a / b) 128 { @@ -38,7 +46,7 @@ Click on an instruction name to jump to its section. } - 0x03 [`EQ`](#isa-section-eq) + 0x04 [`EQ`](#isa-section-eq) Equality check (a == b) 128 { @@ -46,7 +54,7 @@ Click on an instruction name to jump to its section. } - 0x04 [`LT`](#isa-section-lt) + 0x05 [`LT`](#isa-section-lt) Less-than check (a < b) 128 { @@ -54,7 +62,7 @@ Click on an instruction name to jump to its section. } - 0x05 [`LTE`](#isa-section-lte) + 0x06 [`LTE`](#isa-section-lte) Less-than-or-equals check (a <= b) 128 { @@ -62,7 +70,7 @@ Click on an instruction name to jump to its section. } - 0x06 [`AND`](#isa-section-and) + 0x07 [`AND`](#isa-section-and) Bitwise AND (a & b) 128 { @@ -70,7 +78,7 @@ Click on an instruction name to jump to its section. } - 0x07 [`OR`](#isa-section-or) + 0x08 [`OR`](#isa-section-or) Bitwise OR (a | b) 128 { @@ -78,7 +86,7 @@ Click on an instruction name to jump to its section. } - 0x08 [`XOR`](#isa-section-xor) + 0x09 [`XOR`](#isa-section-xor) Bitwise XOR (a ^ b) 128 { @@ -86,7 +94,7 @@ Click on an instruction name to jump to its section. } - 0x09 [`NOT`](#isa-section-not) + 0x0a [`NOT`](#isa-section-not) Bitwise NOT (inversion) 96 { @@ -94,7 +102,7 @@ Click on an instruction name to jump to its section. } - 0x0a [`SHL`](#isa-section-shl) + 0x0b [`SHL`](#isa-section-shl) Bitwise leftward shift (a << b) 128 { @@ -102,7 +110,7 @@ Click on an instruction name to jump to its section. } - 0x0b [`SHR`](#isa-section-shr) + 0x0c [`SHR`](#isa-section-shr) Bitwise rightward shift (a >> b) 128 { @@ -110,7 +118,7 @@ Click on an instruction name to jump to its section. } - 0x0c [`CAST`](#isa-section-cast) + 0x0d [`CAST`](#isa-section-cast) Type cast 96 { @@ -118,7 +126,7 @@ Click on an instruction name to jump to its section. } - 0x0d [`SET`](#isa-section-set) + 0x0e [`SET`](#isa-section-set) Set a memory word from a constant in the bytecode. 64+N { @@ -126,7 +134,7 @@ Click on an instruction name to jump to its section. } - 0x0e [`MOV`](#isa-section-mov) + 0x0f [`MOV`](#isa-section-mov) Move a word from source memory location to destination`. 88 { @@ -134,7 +142,7 @@ Click on an instruction name to jump to its section. } - 0x0f [`CMOV`](#isa-section-cmov) + 0x10 [`CMOV`](#isa-section-cmov) Move a word (conditionally chosen) from one memory location to another (`d = cond > 0 ? a : b`). 152 { @@ -142,7 +150,7 @@ Click on an instruction name to jump to its section. } - 0x10 [`CALLDATACOPY`](#isa-section-calldatacopy) + 0x11 [`CALLDATACOPY`](#isa-section-calldatacopy) Copy calldata into memory. 120 { @@ -150,7 +158,7 @@ Click on an instruction name to jump to its section. } - 0x11 [`SLOAD`](#isa-section-sload) + 0x12 [`SLOAD`](#isa-section-sload) Load a word from storage. 88 { @@ -158,7 +166,7 @@ Click on an instruction name to jump to its section. } - 0x12 [`SSTORE`](#isa-section-sstore) + 0x13 [`SSTORE`](#isa-section-sstore) Write a word to storage. 88 { @@ -166,25 +174,25 @@ Click on an instruction name to jump to its section. } - 0x13 [`EMITNOTEHASH`](#isa-section-emitnotehash) + 0x14 [`EMITNOTEHASH`](#isa-section-emitnotehash) Emit a new note hash to be inserted into the notes tree 56 emitNoteHash(M[contentOffset]) - 0x14 [`EMITNULLIFIER`](#isa-section-emitnullifier) + 0x15 [`EMITNULLIFIER`](#isa-section-emitnullifier) Emit a new nullifier to be inserted into the nullifier tree 56 emitNullifier(M[nullifierOffset]) - 0x15 [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg) + 0x16 [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg) Send an L2-to-L1 message 56 sendL2ToL1Message(M[contentOffset]) - 0x16 [`JUMP`](#isa-section-jump) + 0x17 [`JUMP`](#isa-section-jump) Jump to a location in the bytecode. 48 { @@ -192,7 +200,7 @@ Click on an instruction name to jump to its section. } - 0x17 [`JUMPI`](#isa-section-jumpi) + 0x18 [`JUMPI`](#isa-section-jumpi) Conditionally jump to a location in the bytecode. 88 { @@ -200,7 +208,7 @@ Click on an instruction name to jump to its section. } - 0x18 [`RETURN`](#isa-section-return) + 0x19 [`RETURN`](#isa-section-return) Halt execution with `success`, optionally returning some data. 88 { @@ -208,7 +216,7 @@ Click on an instruction name to jump to its section. } - 0x19 [`REVERT`](#isa-section-revert) + 0x1a [`REVERT`](#isa-section-revert) Halt execution with `failure`, reverting state changes and optionally returning some data. 88 { @@ -216,7 +224,7 @@ Click on an instruction name to jump to its section. } - 0x1a [`CALL`](#isa-section-call) + 0x1b [`CALL`](#isa-section-call) Call into another contract. 248 @@ -227,7 +235,7 @@ Click on an instruction name to jump to its section. - 0x1b [`STATICCALL`](#isa-section-staticcall) + 0x1c [`STATICCALL`](#isa-section-staticcall) Call into another contract, disallowing persistent state modifications. 248 @@ -238,7 +246,7 @@ Click on an instruction name to jump to its section. - 0x1c [`ULOG`](#isa-section-ulog) + 0x1d [`ULOG`](#isa-section-ulog) Emit an unencrypted log with data from the `field` memory page 88 { @@ -246,7 +254,7 @@ Click on an instruction name to jump to its section. } - 0x1d [`CHAINID`](#isa-section-chainid) + 0x1e [`CHAINID`](#isa-section-chainid) Get this rollup's L1 chain ID 56 { @@ -254,7 +262,7 @@ Click on an instruction name to jump to its section. } - 0x1e [`VERSION`](#isa-section-version) + 0x1f [`VERSION`](#isa-section-version) Get this rollup's L2 version ID 56 { @@ -262,7 +270,7 @@ Click on an instruction name to jump to its section. } - 0x1f [`BLOCKNUMBER`](#isa-section-blocknumber) + 0x20 [`BLOCKNUMBER`](#isa-section-blocknumber) Get this block's number 56 { @@ -270,7 +278,7 @@ Click on an instruction name to jump to its section. } - 0x20 [`TIMESTAMP`](#isa-section-timestamp) + 0x21 [`TIMESTAMP`](#isa-section-timestamp) Get this L2 block's timestamp 56 { @@ -278,7 +286,7 @@ Click on an instruction name to jump to its section. } - 0x21 [`COINBASE`](#isa-section-coinbase) + 0x22 [`COINBASE`](#isa-section-coinbase) Get the block's beneficiary address 56 { @@ -286,7 +294,7 @@ Click on an instruction name to jump to its section. } - 0x22 [`BLOCKL1GASLIMIT`](#isa-section-blockl1gaslimit) + 0x23 [`BLOCKL1GASLIMIT`](#isa-section-blockl1gaslimit) Total amount of "L1 gas" that a block can consume 56 { @@ -294,7 +302,7 @@ Click on an instruction name to jump to its section. } - 0x23 [`BLOCKL2GASLIMIT`](#isa-section-blockl2gaslimit) + 0x24 [`BLOCKL2GASLIMIT`](#isa-section-blockl2gaslimit) Total amount of "L2 gas" that a block can consume 56 { @@ -302,7 +310,7 @@ Click on an instruction name to jump to its section. } - 0x24 [`NOTESROOT`](#isa-section-notesroot) + 0x25 [`NOTESROOT`](#isa-section-notesroot) Get the historical note-hash tree root as of the specified block number. 88 { @@ -310,7 +318,7 @@ Click on an instruction name to jump to its section. } - 0x25 [`NULLIFIERSROOT`](#isa-section-nullroot) + 0x26 [`NULLIFIERSROOT`](#isa-section-nullroot) Get the historical nullifier tree root as of the specified block number. 88 { @@ -318,7 +326,7 @@ Click on an instruction name to jump to its section. } - 0x26 [`CONTRACTSROOT`](#isa-section-contractsroot) + 0x27 [`CONTRACTSROOT`](#isa-section-contractsroot) Get the historical contracts tree root as of the specified block number. 88 { @@ -326,21 +334,13 @@ Click on an instruction name to jump to its section. } - 0x27 [`MSGSROOT`](#isa-section-msgsroot) + 0x28 [`MSGSROOT`](#isa-section-msgsroot) Get the historical l1-to-l2 message tree root as of the specified block number. 88 { `M[dstOffset] = HistoricalBlockData[M[blockNumOffset]].l1_to_l2_message_tree_root` } - - 0x28 [`NOTESROOT`](#isa-section-notesroot) - Get the historical note-hash tree root as of the specified block number. - 88 - { - `M[dstOffset] = HistoricalBlockData[M[blockNumOffset]].note_hash_tree_root` - } - 0x29 [`PUBLICDATAROOT`](#isa-section-publicdataroot) Get the historical public data tree root as of the specified block number. @@ -498,7 +498,26 @@ Subtraction (a - b) [![](./images/bit-formats/SUB.png)](./images/bit-formats/SUB.png) -### `DIV` (0x02) +### `MUL` (0x02) +Subtraction (a - b) + +[See in table.](#isa-table-mul) + +- **Category**: arithmetic +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. 0th bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. + - **in-tag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. +- **Args**: + - **aOffset**: memory offset of the operation's left input + - **bOffset**: memory offset of the operation's right input + - **dstOffset**: memory offset specifying where to store operation's result +- **Expression**: `M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k` +- **Tag checks**: `T[aOffset] == T[bOffset] == in-tag` +- **Tag updates**: `T[dstOffset] = in-tag` +- **Bit-size**: 128 + + +### `DIV` (0x03) Unsigned division (a / b) [See in table.](#isa-table-div) @@ -518,7 +537,7 @@ Unsigned division (a / b) [![](./images/bit-formats/DIV.png)](./images/bit-formats/DIV.png) -### `EQ` (0x03) +### `EQ` (0x04) Equality check (a == b) [See in table.](#isa-table-eq) @@ -538,7 +557,7 @@ Equality check (a == b) [![](./images/bit-formats/EQ.png)](./images/bit-formats/EQ.png) -### `LT` (0x04) +### `LT` (0x05) Less-than check (a < b) [See in table.](#isa-table-lt) @@ -558,7 +577,7 @@ Less-than check (a < b) [![](./images/bit-formats/LT.png)](./images/bit-formats/LT.png) -### `LTE` (0x05) +### `LTE` (0x06) Less-than-or-equals check (a <= b) [See in table.](#isa-table-lte) @@ -578,7 +597,7 @@ Less-than-or-equals check (a <= b) [![](./images/bit-formats/LTE.png)](./images/bit-formats/LTE.png) -### `AND` (0x06) +### `AND` (0x07) Bitwise AND (a & b) [See in table.](#isa-table-and) @@ -598,7 +617,7 @@ Bitwise AND (a & b) [![](./images/bit-formats/AND.png)](./images/bit-formats/AND.png) -### `OR` (0x07) +### `OR` (0x08) Bitwise OR (a | b) [See in table.](#isa-table-or) @@ -618,7 +637,7 @@ Bitwise OR (a | b) [![](./images/bit-formats/OR.png)](./images/bit-formats/OR.png) -### `XOR` (0x08) +### `XOR` (0x09) Bitwise XOR (a ^ b) [See in table.](#isa-table-xor) @@ -638,7 +657,7 @@ Bitwise XOR (a ^ b) [![](./images/bit-formats/XOR.png)](./images/bit-formats/XOR.png) -### `NOT` (0x09) +### `NOT` (0x0a) Bitwise NOT (inversion) [See in table.](#isa-table-not) @@ -657,7 +676,7 @@ Bitwise NOT (inversion) [![](./images/bit-formats/NOT.png)](./images/bit-formats/NOT.png) -### `SHL` (0x0a) +### `SHL` (0x0b) Bitwise leftward shift (a << b) [See in table.](#isa-table-shl) @@ -677,7 +696,7 @@ Bitwise leftward shift (a << b) [![](./images/bit-formats/SHL.png)](./images/bit-formats/SHL.png) -### `SHR` (0x0b) +### `SHR` (0x0c) Bitwise rightward shift (a >> b) [See in table.](#isa-table-shr) @@ -697,7 +716,7 @@ Bitwise rightward shift (a >> b) [![](./images/bit-formats/SHR.png)](./images/bit-formats/SHR.png) -### `CAST` (0x0c) +### `CAST` (0x0d) Type cast [See in table.](#isa-table-cast) @@ -716,7 +735,7 @@ Type cast [![](./images/bit-formats/CAST.png)](./images/bit-formats/CAST.png) -### `SET` (0x0d) +### `SET` (0x0e) Set a memory word from a constant in the bytecode. [See in table.](#isa-table-set) @@ -735,7 +754,7 @@ Set a memory word from a constant in the bytecode. [![](./images/bit-formats/SET.png)](./images/bit-formats/SET.png) -### `MOV` (0x0e) +### `MOV` (0x0f) Move a word from source memory location to destination`. [See in table.](#isa-table-mov) @@ -752,7 +771,7 @@ Move a word from source memory location to destination`. [![](./images/bit-formats/MOV.png)](./images/bit-formats/MOV.png) -### `CMOV` (0x0f) +### `CMOV` (0x10) Move a word (conditionally chosen) from one memory location to another (`d = cond > 0 ? a : b`). [See in table.](#isa-table-cmov) @@ -772,7 +791,7 @@ Move a word (conditionally chosen) from one memory location to another (`d = con [![](./images/bit-formats/CMOV.png)](./images/bit-formats/CMOV.png) -### `CALLDATACOPY` (0x10) +### `CALLDATACOPY` (0x11) Copy calldata into memory. [See in table.](#isa-table-calldatacopy) @@ -791,7 +810,7 @@ Copy calldata into memory. [![](./images/bit-formats/CALLDATACOPY.png)](./images/bit-formats/CALLDATACOPY.png) -### `SLOAD` (0x11) +### `SLOAD` (0x12) Load a word from storage. [See in table.](#isa-table-sload) @@ -809,7 +828,7 @@ Load a word from storage. [![](./images/bit-formats/SLOAD.png)](./images/bit-formats/SLOAD.png) -### `SSTORE` (0x12) +### `SSTORE` (0x13) Write a word to storage. [See in table.](#isa-table-sstore) @@ -826,7 +845,7 @@ Write a word to storage. [![](./images/bit-formats/SSTORE.png)](./images/bit-formats/SSTORE.png) -### `EMITNOTEHASH` (0x13) +### `EMITNOTEHASH` (0x14) Emit a new note hash to be inserted into the notes tree [See in table.](#isa-table-emitnotehash) @@ -841,7 +860,7 @@ Emit a new note hash to be inserted into the notes tree [![](./images/bit-formats/EMITNOTEHASH.png)](./images/bit-formats/EMITNOTEHASH.png) -### `EMITNULLIFIER` (0x14) +### `EMITNULLIFIER` (0x15) Emit a new nullifier to be inserted into the nullifier tree [See in table.](#isa-table-emitnullifier) @@ -856,7 +875,7 @@ Emit a new nullifier to be inserted into the nullifier tree [![](./images/bit-formats/EMITNULLIFIER.png)](./images/bit-formats/EMITNULLIFIER.png) -### `SENDL2TOL1MSG` (0x15) +### `SENDL2TOL1MSG` (0x16) Send an L2-to-L1 message [See in table.](#isa-table-sendl2tol1msg) @@ -871,7 +890,7 @@ Send an L2-to-L1 message [![](./images/bit-formats/SENDL2TOL1MSG.png)](./images/bit-formats/SENDL2TOL1MSG.png) -### `JUMP` (0x16) +### `JUMP` (0x17) Jump to a location in the bytecode. [See in table.](#isa-table-jump) @@ -885,7 +904,7 @@ Jump to a location in the bytecode. [![](./images/bit-formats/JUMP.png)](./images/bit-formats/JUMP.png) -### `JUMPI` (0x17) +### `JUMPI` (0x18) Conditionally jump to a location in the bytecode. [See in table.](#isa-table-jumpi) @@ -902,7 +921,7 @@ Conditionally jump to a location in the bytecode. [![](./images/bit-formats/JUMPI.png)](./images/bit-formats/JUMPI.png) -### `RETURN` (0x18) +### `RETURN` (0x19) Halt execution with `success`, optionally returning some data. [See in table.](#isa-table-return) @@ -919,7 +938,7 @@ Halt execution with `success`, optionally returning some data. [![](./images/bit-formats/RETURN.png)](./images/bit-formats/RETURN.png) -### `REVERT` (0x19) +### `REVERT` (0x1a) Halt execution with `failure`, reverting state changes and optionally returning some data. [See in table.](#isa-table-revert) @@ -936,7 +955,7 @@ Halt execution with `failure`, reverting state changes and optionally returning [![](./images/bit-formats/REVERT.png)](./images/bit-formats/REVERT.png) -### `CALL` (0x1a) +### `CALL` (0x1b) Call into another contract. [See in table.](#isa-table-call) @@ -972,7 +991,7 @@ T[retOffset:retOffset+retSize] = field`} [![](./images/bit-formats/CALL.png)](./images/bit-formats/CALL.png) -### `STATICCALL` (0x1b) +### `STATICCALL` (0x1c) Call into another contract, disallowing persistent state modifications. [See in table.](#isa-table-staticcall) @@ -1006,7 +1025,7 @@ T[retOffset:retOffset+retSize] = field`} [![](./images/bit-formats/STATICCALL.png)](./images/bit-formats/STATICCALL.png) -### `ULOG` (0x1c) +### `ULOG` (0x1d) Emit an unencrypted log with data from the `field` memory page [See in table.](#isa-table-ulog) @@ -1022,7 +1041,7 @@ Emit an unencrypted log with data from the `field` memory page [![](./images/bit-formats/ULOG.png)](./images/bit-formats/ULOG.png) -### `CHAINID` (0x1d) +### `CHAINID` (0x1e) Get this rollup's L1 chain ID [See in table.](#isa-table-chainid) @@ -1038,7 +1057,7 @@ Get this rollup's L1 chain ID [![](./images/bit-formats/CHAINID.png)](./images/bit-formats/CHAINID.png) -### `VERSION` (0x1e) +### `VERSION` (0x1f) Get this rollup's L2 version ID [See in table.](#isa-table-version) @@ -1054,7 +1073,7 @@ Get this rollup's L2 version ID [![](./images/bit-formats/VERSION.png)](./images/bit-formats/VERSION.png) -### `BLOCKNUMBER` (0x1f) +### `BLOCKNUMBER` (0x20) Get this block's number [See in table.](#isa-table-blocknumber) @@ -1070,7 +1089,7 @@ Get this block's number [![](./images/bit-formats/BLOCKNUMBER.png)](./images/bit-formats/BLOCKNUMBER.png) -### `TIMESTAMP` (0x20) +### `TIMESTAMP` (0x21) Get this L2 block's timestamp [See in table.](#isa-table-timestamp) @@ -1086,7 +1105,7 @@ Get this L2 block's timestamp [![](./images/bit-formats/TIMESTAMP.png)](./images/bit-formats/TIMESTAMP.png) -### `COINBASE` (0x21) +### `COINBASE` (0x22) Get the block's beneficiary address [See in table.](#isa-table-coinbase) @@ -1102,7 +1121,7 @@ Get the block's beneficiary address [![](./images/bit-formats/COINBASE.png)](./images/bit-formats/COINBASE.png) -### `BLOCKL1GASLIMIT` (0x22) +### `BLOCKL1GASLIMIT` (0x23) Total amount of "L1 gas" that a block can consume [See in table.](#isa-table-blockl1gaslimit) @@ -1118,7 +1137,7 @@ Total amount of "L1 gas" that a block can consume [![](./images/bit-formats/BLOCKL1GASLIMIT.png)](./images/bit-formats/BLOCKL1GASLIMIT.png) -### `BLOCKL2GASLIMIT` (0x23) +### `BLOCKL2GASLIMIT` (0x24) Total amount of "L2 gas" that a block can consume [See in table.](#isa-table-blockl2gaslimit) @@ -1134,7 +1153,7 @@ Total amount of "L2 gas" that a block can consume [![](./images/bit-formats/BLOCKL2GASLIMIT.png)](./images/bit-formats/BLOCKL2GASLIMIT.png) -### `NOTESROOT` (0x24) +### `NOTESROOT` (0x25) Get the historical note-hash tree root as of the specified block number. [See in table.](#isa-table-notesroot) @@ -1151,7 +1170,7 @@ Get the historical note-hash tree root as of the specified block number. [![](./images/bit-formats/NOTESROOT.png)](./images/bit-formats/NOTESROOT.png) -### `NULLIFIERSROOT` (0x25) +### `NULLIFIERSROOT` (0x26) Get the historical nullifier tree root as of the specified block number. [See in table.](#isa-table-nullroot) @@ -1168,7 +1187,7 @@ Get the historical nullifier tree root as of the specified block number. [![](./images/bit-formats/NULLIFIERSROOT.png)](./images/bit-formats/NULLIFIERSROOT.png) -### `CONTRACTSROOT` (0x26) +### `CONTRACTSROOT` (0x27) Get the historical contracts tree root as of the specified block number. [See in table.](#isa-table-contractsroot) @@ -1185,7 +1204,7 @@ Get the historical contracts tree root as of the specified block number. [![](./images/bit-formats/CONTRACTSROOT.png)](./images/bit-formats/CONTRACTSROOT.png) -### `MSGSROOT` (0x27) +### `MSGSROOT` (0x28) Get the historical l1-to-l2 message tree root as of the specified block number. [See in table.](#isa-table-msgsroot) @@ -1202,23 +1221,6 @@ Get the historical l1-to-l2 message tree root as of the specified block number. [![](./images/bit-formats/MSGSROOT.png)](./images/bit-formats/MSGSROOT.png) -### `NOTESROOT` (0x28) -Get the historical note-hash tree root as of the specified block number. - -[See in table.](#isa-table-notesroot) - -- **Category**: historical access -- **Flags**: - - **indirect**: Toggles whether each memory-offset argument is an indirect offset. 0th bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. -- **Args**: - - **blockNumOffset**: memory offset of the block number input - - **dstOffset**: memory offset specifying where to store operation's result -- **Expression**: `M[dstOffset] = HistoricalBlockData[M[blockNumOffset]].note_hash_tree_root` -- **Tag updates**: `T[dstOffset] = field` -- **Bit-size**: 88 - -[![](./images/bit-formats/NOTESROOT.png)](./images/bit-formats/NOTESROOT.png) - ### `PUBLICDATAROOT` (0x29) Get the historical public data tree root as of the specified block number. diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index 4dfd2ed34a6..ff3fd87fb47 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -54,6 +54,27 @@ const INSTRUCTION_SET_RAW = [ "Tag checks": "`T[aOffset] == T[bOffset] == in-tag`", "Tag updates": "`T[dstOffset] = in-tag`", }, + { + "id": "mul", + "Name": "`MUL`", + "Category": "arithmetic", + "Flags": [ + {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, + {"name": "in-tag", "description": IN_TAG_DESCRIPTION}, + ], + "#memreads": "2", + "#memwrites": "1", + "Args": [ + {"name": "aOffset", "description": "memory offset of the operation's left input"}, + {"name": "bOffset", "description": "memory offset of the operation's right input"}, + {"name": "dstOffset", "description": "memory offset specifying where to store operation's result"}, + ], + "Expression": "`M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k`", + "Summary": "Multiplication (a * b)", + "Details": "", + "Tag checks": "`T[aOffset] == T[bOffset] == in-tag`", + "Tag updates": "`T[dstOffset] = in-tag`", + }, { "id": "div", "Name": "`DIV`", @@ -815,25 +836,6 @@ T[retOffset:retOffset+retSize] = field "Tag checks": "", "Tag updates": "`T[dstOffset] = field`", }, - { - "id": "notesroot", - "Name": "`NOTESROOT`", - "Category": "historical access", - "Flags": [ - {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, - ], - "#memreads": "1", - "#memwrites": "1", - "Args": [ - {"name": "blockNumOffset", "description": "memory offset of the block number input"}, - {"name": "dstOffset", "description": "memory offset specifying where to store operation's result"}, - ], - "Expression": "`M[dstOffset] = HistoricalBlockData[M[blockNumOffset]].note_hash_tree_root`", - "Summary": "Get the historical note-hash tree root as of the specified block number.", - "Details": "", - "Tag checks": "", - "Tag updates": "`T[dstOffset] = field`", - }, { "id": "publicdataroot", "Name": "`PUBLICDATAROOT`",