Skip to content

Commit

Permalink
feat(avm): encode TS AVM instructions as bytecode, especially for tes…
Browse files Browse the repository at this point in the history
  • Loading branch information
dbanks12 authored Jan 18, 2024
1 parent 2bbf7a9 commit de6e2ed
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 23 deletions.
4 changes: 2 additions & 2 deletions yarn-project/acir-simulator/src/avm/avm_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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 { decodeBytecode } from './opcodes/decode_bytecode.js';
import { Instruction } from './opcodes/index.js';

/**
Expand Down Expand Up @@ -34,7 +34,7 @@ export class AvmContext {
// const bytecode = stateManager.journal.hostStorage.contractsDb.getBytecode(contractAddress);
const bytecode = Buffer.from('0x01000100020003');

const instructions: Instruction[] = interpretBytecode(bytecode);
const instructions: Instruction[] = decodeBytecode(bytecode);

const context = new AvmMachineState(calldata);
const interpreter = new AvmInterpreter(context, this.stateManager, instructions);
Expand Down
41 changes: 41 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,41 @@
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 { decodeBytecode } from './opcodes/decode_bytecode.js';
import { encodeToBytecode } from './opcodes/encode_to_bytecode.js';
import { Opcode } from './opcodes/opcodes.js';

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 = encodeToBytecode(Opcode.CALLDATACOPY, calldataCopyArgs);
const addBytecode = encodeToBytecode(Opcode.ADD, addArgs);
const returnBytecode = encodeToBytecode(Opcode.RETURN, returnArgs);
const fullBytecode = Buffer.concat([calldataCopyBytecode, addBytecode, returnBytecode]);

// Decode bytecode into instructions
const instructions = decodeBytecode(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
@@ -1,20 +1,20 @@
import { Add, Sub } from './arithmetic.js';
import { OPCODE_BYTE_LENGTH, OPERAND_BYTE_LENGTH, interpretBytecode } from './from_bytecode.js';
import { Instruction } from './instruction.js';
import { decodeBytecode } from './decode_bytecode.js';
import { AVM_OPCODE_BYTE_LENGTH, AVM_OPERAND_BYTE_LENGTH, Instruction } from './instruction.js';

describe('Avm Interpreter', () => {
describe('Avm Decoder', () => {
const toByte = (num: number): Buffer => {
const buf = Buffer.alloc(OPCODE_BYTE_LENGTH);
const buf = Buffer.alloc(AVM_OPCODE_BYTE_LENGTH);
buf.writeUInt8(num);
return buf;
};
const to4Byte = (num: number): Buffer => {
const buf = Buffer.alloc(OPERAND_BYTE_LENGTH);
const buf = Buffer.alloc(AVM_OPERAND_BYTE_LENGTH);
buf.writeUInt32BE(num);
return buf;
};

it('Should read bytecode string into a list of opcodes', () => {
it('Should read bytecode buffer into a list of opcodes', () => {
const opcode = 1;
const opcode2 = 2;
const a = 1;
Expand All @@ -30,7 +30,7 @@ describe('Avm Interpreter', () => {

const expectedInstructions: Instruction[] = [new Add(a, b, c), new Sub(a, b, c)];

const instructions = interpretBytecode(bytecode);
const instructions = decodeBytecode(bytecode);
expect(instructions).toEqual(expectedInstructions);
});
});
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
import { Instruction } from './instruction.js';
import { AVM_OPCODE_BYTE_LENGTH, AVM_OPERAND_BYTE_LENGTH, 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_BYTE_LENGTH = 4;
export const OPCODE_BIT_LENGTH = 8;
export const OPCODE_BYTE_LENGTH = 1;

/**
* Convert a buffer of bytecode into an array of instructions
* @param bytecode - Buffer of bytecode
* @returns Bytecode interpreted into an ordered array of Instructions
* @returns Bytecode decoded into an ordered array of Instructions
*/
export function interpretBytecode(bytecode: Buffer): Instruction[] {
let readPtr = 0;
export function decodeBytecode(bytecode: Buffer): Instruction[] {
let bytePtr = 0;
const bytecodeLength = bytecode.length;

const instructions: Instruction[] = [];

while (readPtr < bytecodeLength) {
const opcodeByte = bytecode[readPtr];
readPtr += 1;
while (bytePtr < bytecodeLength) {
const opcodeByte = bytecode[bytePtr];
bytePtr += AVM_OPCODE_BYTE_LENGTH;
if (!(opcodeByte in Opcode)) {
throw new Error(`Opcode ${opcodeByte} not implemented`);
}
Expand All @@ -33,8 +28,8 @@ export function interpretBytecode(bytecode: Buffer): Instruction[] {
const numberOfOperands = instructionType.numberOfOperands;
const operands: number[] = [];
for (let i = 0; i < numberOfOperands; i++) {
const operand = bytecode.readUInt32BE(readPtr);
readPtr += OPERAND_BYTE_LENGTH;
const operand = bytecode.readUInt32BE(bytePtr);
bytePtr += AVM_OPERAND_BYTE_LENGTH;
operands.push(operand);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { encodeToBytecode } from './encode_to_bytecode.js';
import { AVM_OPCODE_BYTE_LENGTH, AVM_OPERAND_BYTE_LENGTH } from './instruction.js';
import { Opcode } from './opcodes.js';

describe('Avm Encoder', () => {
const toByte = (num: number): Buffer => {
const buf = Buffer.alloc(AVM_OPCODE_BYTE_LENGTH);
buf.writeUInt8(num);
return buf;
};
const to4Byte = (num: number): Buffer => {
const buf = Buffer.alloc(AVM_OPERAND_BYTE_LENGTH);
buf.writeUInt32BE(num);
return buf;
};

it('Should properly encode instructions into bytecode buffers', () => {
const addArgs = [0, 1, 2];
const subArgs = [3, 4, 5];

const addBytecode = encodeToBytecode(Opcode.ADD, addArgs);
const subBytecode = encodeToBytecode(Opcode.SUB, subArgs);

const expectedAddBytecode = Buffer.concat([toByte(Opcode.ADD), to4Byte(0), to4Byte(1), to4Byte(2)]);
const expectedSubBytecode = Buffer.concat([toByte(Opcode.SUB), to4Byte(3), to4Byte(4), to4Byte(5)]);

expect(addBytecode).toEqual(expectedAddBytecode);
expect(subBytecode).toEqual(expectedSubBytecode);
});
});
32 changes: 32 additions & 0 deletions yarn-project/acir-simulator/src/avm/opcodes/encode_to_bytecode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AVM_OPCODE_BYTE_LENGTH, AVM_OPERAND_BYTE_LENGTH } from './instruction.js';
import { INSTRUCTION_SET } from './instruction_set.js';
import { Opcode } from './opcodes.js';

/**
* Encode an instruction (opcode & arguments) to bytecode.
* @param opcode - the opcode to encode
* @param args - the arguments to encode
* @returns the bytecode for this one instruction
*/
export function encodeToBytecode(opcode: Opcode, args: number[]): Buffer {
const instructionType = INSTRUCTION_SET.get(opcode);
if (instructionType === undefined) {
throw new Error(`Opcode ${opcode} not implemented`);
}

const numberOfOperands = instructionType.numberOfOperands;
if (args.length !== numberOfOperands) {
throw new Error(`Opcode ${opcode} expects ${numberOfOperands} arguments, but ${args.length} were provided`);
}

const bytecode = Buffer.alloc(AVM_OPCODE_BYTE_LENGTH + numberOfOperands * AVM_OPERAND_BYTE_LENGTH);

let bytePtr = 0;
bytecode.writeUInt8(opcode as number, bytePtr);
bytePtr += AVM_OPCODE_BYTE_LENGTH;
for (let i = 0; i < args.length; i++) {
bytecode.writeUInt32BE(args[i], bytePtr);
bytePtr += AVM_OPERAND_BYTE_LENGTH;
}
return bytecode;
}
3 changes: 3 additions & 0 deletions yarn-project/acir-simulator/src/avm/opcodes/instruction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { AvmMachineState } from '../avm_machine_state.js';
import { AvmStateManager } from '../avm_state_manager.js';

export const AVM_OPERAND_BYTE_LENGTH = 4;
export const AVM_OPCODE_BYTE_LENGTH = 1;

/**
* Opcode base class
*/
Expand Down

0 comments on commit de6e2ed

Please sign in to comment.