Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(avm): encode TS AVM instructions as bytecode, especially for testing #4115

Merged
merged 20 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions yarn-project/acir-simulator/src/avm/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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 { AvmInterpreter } from './interpreter/interpreter.js';
import { Add } from './opcodes/arithmetic.js';
import { Return } from './opcodes/control_flow.js';
import { interpretBytecode } from './opcodes/from_bytecode.js';
import { CalldataCopy } from './opcodes/memory.js';
import { Opcode } from './opcodes/opcodes.js';

function genInstructionBytecode(opcode: Opcode, args: number[], assertArgsLength: number): Buffer {
dbanks12 marked this conversation as resolved.
Show resolved Hide resolved
expect(args.length).toBe(assertArgsLength);
// 1 byte for opcode, 4 bytes for each argument
const bytecode = Buffer.alloc(1 + args.length * 4);

let byteOffset = 0;
bytecode.writeUInt8(opcode as number, byteOffset);
byteOffset++;
for (let i = 0; i < args.length; i++) {
bytecode.writeUInt32BE(args[i], byteOffset);
byteOffset += 4;
}
return bytecode;
}

describe('avm', () => {
it('Should execute bytecode', () => {
const calldata: Fr[] = [new Fr(1), new Fr(2)];
const stateManager = mock<AvmStateManager>();

// Construct bytecode
const calldataCopyArgs = [0, 2, 0];
const addArgs = [0, 1, 2];
const returnArgs = [2, 1];

const calldataCopyBytecode = genInstructionBytecode(
Opcode.CALLDATACOPY,
calldataCopyArgs,
CalldataCopy.numberOfOperands,
);
const addBytecode = genInstructionBytecode(Opcode.ADD, addArgs, Add.numberOfOperands);
const returnBytecode = genInstructionBytecode(Opcode.RETURN, returnArgs, Return.numberOfOperands);
const fullBytecode = Buffer.concat([calldataCopyBytecode, addBytecode, returnBytecode]);

// Decode bytecode into instructions
const instructions = interpretBytecode(fullBytecode);

// Execute instructions
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)]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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 { CalldataCopy } from '../opcodes/memory.js';
import { AvmInterpreter } from './interpreter.js';

describe('interpreter', () => {
Expand All @@ -17,7 +17,7 @@ describe('interpreter', () => {

const instructions: Instruction[] = [
// Copy the first two elements of the calldata to memory regions 0 and 1
new CallDataCopy(0, 2, 0),
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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Add, Sub } from './arithmetic.js';
import { OPCODE_BYTE_LENGTH, OPERAND_BTYE_LENGTH, interpretBytecode } from './from_bytecode.js';
import { OPCODE_BYTE_LENGTH, OPERAND_BYTE_LENGTH, interpretBytecode } from './from_bytecode.js';
import { Instruction } from './instruction.js';

describe('Avm Interpreter', () => {
Expand All @@ -9,7 +9,7 @@ describe('Avm Interpreter', () => {
return buf;
};
const to4Byte = (num: number): Buffer => {
const buf = Buffer.alloc(OPERAND_BTYE_LENGTH);
const buf = Buffer.alloc(OPERAND_BYTE_LENGTH);
buf.writeUInt32BE(num);
return buf;
};
Expand Down
47 changes: 15 additions & 32 deletions yarn-project/acir-simulator/src/avm/opcodes/from_bytecode.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,12 @@
import { Add, Mul, Sub } from './arithmetic.js';
import { Instruction } from './instruction.js';
import { INSTRUCTION_SET } from './instruction_set.js';
import { Opcode } from './opcodes.js';

export const OPERAND_BIT_LENGTH = 32;
export const OPERAND_BTYE_LENGTH = 4;
export const OPERAND_BYTE_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
Expand All @@ -44,18 +19,26 @@ export function interpretBytecode(bytecode: Buffer): Instruction[] {
const instructions: Instruction[] = [];

while (readPtr < bytecodeLength) {
const opcode = bytecode[readPtr];
const opcodeByte = bytecode[readPtr];
readPtr += 1;
if (!(opcodeByte in Opcode)) {
throw new Error(`Opcode ${opcodeByte} not implemented`);
}
const opcode = opcodeByte as Opcode;

const numberOfOperands = OPERANDS_LOOKUP[opcode];
const instructionType = INSTRUCTION_SET.get(opcode);
if (instructionType === undefined) {
throw new Error(`Opcode ${opcode} not implemented`);
}
const numberOfOperands = instructionType.numberOfOperands;
const operands: number[] = [];
for (let i = 0; i < numberOfOperands; i++) {
const operand = bytecode.readUInt32BE(readPtr);
readPtr += OPERAND_BTYE_LENGTH;
readPtr += OPERAND_BYTE_LENGTH;
operands.push(operand);
}

instructions.push(instructionLookup(opcode, operands));
instructions.push(new instructionType(...operands));
}

return instructions;
Expand Down
107 changes: 107 additions & 0 deletions yarn-project/acir-simulator/src/avm/opcodes/instruction_set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
Add,
/*Div,*/
Mul,
Sub,
} 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 { Instruction } from './instruction.js';
import {
CalldataCopy,
/*Cast, Mov*/
} from './memory.js';
import { Opcode } from './opcodes.js';

/** - */
type InstructionConstructor = new (...args: any[]) => Instruction;
/** - */
type InstructionConstructorAndMembers = InstructionConstructor & {
/** - */
numberOfOperands: number;
};

export const INSTRUCTION_SET: Map<Opcode, InstructionConstructorAndMembers> = new Map(
new Array<[Opcode, InstructionConstructorAndMembers]>(
// Compute
// Compute - Arithmetic
[Opcode.ADD, Add],
[Opcode.SUB, Sub],
[Opcode.MUL, Mul],
//[Opcode.DIV, Div],
//// Compute - Comparators
//[Opcode.EQ, Eq],
//[Opcode.LT, Lt],
//[Opcode.LTE, Lte],
//// Compute - Bitwise
//[Opcode.AND, And],
//[Opcode.OR, Or],
//[Opcode.XOR, Xor],
//[Opcode.NOT, Not],
//[Opcode.SHL, Shl],
//[Opcode.SHR, Shr],
//// Compute - Type Conversions
//[Opcode.CAST, Cast],

//// Execution Environment
//[Opcode.ADDRESS, Address],
//[Opcode.STORAGEADDRESS, Storageaddress],
//[Opcode.ORIGIN, Origin],
//[Opcode.SENDER, Sender],
//[Opcode.PORTAL, Portal],
//[Opcode.FEEPERL1GAS, Feeperl1gas],
//[Opcode.FEEPERL2GAS, Feeperl2gas],
//[Opcode.FEEPERDAGAS, Feeperdagas],
//[Opcode.CONTRACTCALLDEPTH, Contractcalldepth],
//// Execution Environment - Globals
//[Opcode.CHAINID, Chainid],
//[Opcode.VERSION, Version],
//[Opcode.BLOCKNUMBER, Blocknumber],
//[Opcode.TIMESTAMP, Timestamp],
//[Opcode.COINBASE, Coinbase],
//[Opcode.BLOCKL1GASLIMIT, Blockl1gaslimit],
//[Opcode.BLOCKL2GASLIMIT, Blockl2gaslimit],
//[Opcode.BLOCKDAGASLIMIT, Blockdagaslimit],
// Execution Environment - Calldata
[Opcode.CALLDATACOPY, CalldataCopy],

//// Machine State
// Machine State - Gas
//[Opcode.L1GASLEFT, L1gasleft],
//[Opcode.L2GASLEFT, L2gasleft],
//[Opcode.DAGASLEFT, Dagasleft],
//// Machine State - Internal Control Flow
//[Opcode.JUMP, Jump],
//[Opcode.JUMPI, Jumpi],
//[Opcode.INTERNALCALL, Internalcall],
//[Opcode.INTERNALRETURN, Internalreturn],
//[Opcode.INTERNALCALLDEPTH, Internalcalldepth],
//// Machine State - Memory
//[Opcode.SET, Set],
//[Opcode.MOV, Mov],
//[Opcode.CMOV, CMov],

//// World State
//[Opcode.BLOCKHEADERBYNUMBER, Blockheaderbynumber],
//[Opcode.SLOAD, Sload], // Public Storage
//[Opcode.SSTORE, Sstore], // Public Storage
//[Opcode.READL1TOL2MSG, Readl1tol2msg], // Messages
//[Opcode.SENDL2TOL1MSG, Sendl2tol1msg], // Messages
//[Opcode.EMITNOTEHASH, Emitnotehash], // Notes & Nullifiers
//[Opcode.EMITNULLIFIER, Emitnullifier], // Notes & Nullifiers

//// Accrued Substate
//[Opcode.EMITUNENCRYPTEDLOG, Emitunencryptedlog],

//// Control Flow - Contract Calls
//[Opcode.CALL, Call],
//[Opcode.STATICCALL, Staticcall],
[Opcode.RETURN, Return],
//[Opcode.REVERT, Revert],

//// Gadgets
//[Opcode.KECCAK, Keccak],
//[Opcode.POSEIDON, Poseidon],
),
);
2 changes: 1 addition & 1 deletion yarn-project/acir-simulator/src/avm/opcodes/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class Mov implements Instruction {
}

/** - */
export class CallDataCopy implements Instruction {
export class CalldataCopy implements Instruction {
static type: string = 'CALLDATACOPY';
static numberOfOperands = 3;

Expand Down
Loading
Loading