diff --git a/yarn-project/simulator/src/avm/opcodes/addressing_mode.test.ts b/yarn-project/simulator/src/avm/opcodes/addressing_mode.test.ts new file mode 100644 index 000000000000..767e2c00e78e --- /dev/null +++ b/yarn-project/simulator/src/avm/opcodes/addressing_mode.test.ts @@ -0,0 +1,33 @@ +import { TaggedMemory, Uint32 } from '../avm_memory_types.js'; +import { Addressing, AddressingMode } from './addressing_mode.js'; + +describe('Addressing', () => { + it('should reserialize correctly', () => { + const addressingMode = Addressing.fromWire(0b10101010); + const wireModes = addressingMode.toWire(); + expect(wireModes).toBe(0b10101010); + }); + + it('should convert to wire format correctly', () => { + const addressingMode = new Addressing([ + AddressingMode.INDIRECT, + AddressingMode.DIRECT, + AddressingMode.INDIRECT, + AddressingMode.DIRECT, + ]); + const wireModes = addressingMode.toWire(); + expect(wireModes).toBe(0b10100000); + }); + + it('should resolve offsets correctly', () => { + const addressingMode = Addressing.fromWire(0b10011111); + const offsets = [10, 20, 30]; + const mem = new TaggedMemory(); + mem.set(10, new Uint32(100)); + mem.set(20, new Uint32(200)); + mem.set(30, new Uint32(300)); + + const resolved = addressingMode.resolve(offsets, mem); + expect(resolved).toEqual([100, 20, 30]); + }); +}); diff --git a/yarn-project/simulator/src/avm/opcodes/addressing_mode.ts b/yarn-project/simulator/src/avm/opcodes/addressing_mode.ts new file mode 100644 index 000000000000..30b1c8e9dafd --- /dev/null +++ b/yarn-project/simulator/src/avm/opcodes/addressing_mode.ts @@ -0,0 +1,66 @@ +import { strict as assert } from 'assert'; + +import { TaggedMemory, TypeTag } from '../avm_memory_types.js'; + +export enum AddressingMode { + DIRECT, + INDIRECT, + INDIRECT_PLUS_CONSTANT, // Not implemented yet. +} + +/** A class to represent the addressing mode of an instruction. */ +export class Addressing { + public constructor( + /** The addressing mode for each operand. The length of this array is the number of operands of the instruction. */ + private readonly modePerOperand: AddressingMode[], + ) { + assert(modePerOperand.length <= 8, 'At most 8 operands are supported'); + } + + public static fromWire(wireModes: number): Addressing { + // The modes are stored in the wire format as a byte, with each bit representing the mode for an operand. + // The most significant bit represents the first operand, and the least significant bit represents the last operand. + const modes = new Array(8); + for (let i = 0; i < 8; i++) { + modes[i] = (wireModes & (1 << (7 - i))) === 0 ? AddressingMode.DIRECT : AddressingMode.INDIRECT; + } + return new Addressing(modes); + } + + public toWire(): number { + // The modes are stored in the wire format as a byte, with each bit representing the mode for an operand. + // The most significant bit represents the first operand, and the least significant bit represents the last operand. + let wire: number = 0; + for (let i = 0; i < 8; i++) { + if (this.modePerOperand[i] === AddressingMode.INDIRECT) { + wire |= 1 << (7 - i); + } + } + return wire; + } + + /** + * Resolves the offsets using the addressing mode. + * @param offsets The offsets to resolve. + * @param mem The memory to use for resolution. + * @returns The resolved offsets. The length of the returned array is the same as the length of the input array. + */ + public resolve(offsets: number[], mem: TaggedMemory): number[] { + assert(offsets.length <= this.modePerOperand.length); + const resolved = new Array(offsets.length); + for (const [i, offset] of offsets.entries()) { + switch (this.modePerOperand[i]) { + case AddressingMode.INDIRECT: + mem.checkTag(TypeTag.UINT32, offset); + resolved[i] = Number(mem.get(offset).toBigInt()); + break; + case AddressingMode.DIRECT: + resolved[i] = offset; + break; + default: + throw new Error(`Unimplemented addressing mode: ${AddressingMode[this.modePerOperand[i]]}`); + } + } + return resolved; + } +} diff --git a/yarn-project/simulator/src/avm/opcodes/memory.test.ts b/yarn-project/simulator/src/avm/opcodes/memory.test.ts index f34d7797b3a3..d7b691435a1e 100644 --- a/yarn-project/simulator/src/avm/opcodes/memory.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/memory.test.ts @@ -4,6 +4,7 @@ import { AvmContext } from '../avm_context.js'; import { Field, TypeTag, Uint8, Uint16, Uint32, Uint64, Uint128 } from '../avm_memory_types.js'; import { InstructionExecutionError } from '../errors.js'; import { initContext, initExecutionEnvironment } from '../fixtures/index.js'; +import { Addressing, AddressingMode } from './addressing_mode.js'; import { CMov, CalldataCopy, Cast, Mov, Set } from './memory.js'; describe('Memory instructions', () => { @@ -304,6 +305,16 @@ describe('Memory instructions', () => { expect(tag).toEqual(TypeTag.UINT16); }); + it('Should support INDIRECT addressing', async () => { + context.machineState.memory.set(0, new Uint16(55)); + context.machineState.memory.set(10, new Uint32(20)); + const addressing = new Addressing([/*srcOffset*/ AddressingMode.DIRECT, /*dstOffset*/ AddressingMode.INDIRECT]); + await new Mov(/*indirect=*/ addressing.toWire(), /*srcOffset=*/ 0, /*dstOffset=*/ 10).execute(context); + + expect(context.machineState.memory.get(1)).toBeUndefined(); + expect(context.machineState.memory.get(20)).toEqual(new Uint16(55n)); + }); + it('Should move field elements on different memory cells', async () => { context.machineState.memory.set(0, new Field(27)); await new Mov(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1).execute(context); diff --git a/yarn-project/simulator/src/avm/opcodes/memory.ts b/yarn-project/simulator/src/avm/opcodes/memory.ts index 6f181e52f96f..d8c9ad7c0aed 100644 --- a/yarn-project/simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/simulator/src/avm/opcodes/memory.ts @@ -3,6 +3,7 @@ import { Field, TaggedMemory, TypeTag } from '../avm_memory_types.js'; import { InstructionExecutionError } from '../errors.js'; import { BufferCursor } from '../serialization/buffer_cursor.js'; import { Opcode, OperandType, deserialize, serialize } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; import { TwoOperandInstruction } from './instruction_impl.js'; @@ -152,9 +153,14 @@ export class Mov extends Instruction { } async execute(context: AvmContext): Promise { - const a = context.machineState.memory.get(this.srcOffset); + const [srcOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve( + [this.srcOffset, this.dstOffset], + context.machineState.memory, + ); - context.machineState.memory.set(this.dstOffset, a); + const a = context.machineState.memory.get(srcOffset); + + context.machineState.memory.set(dstOffset, a); context.machineState.incrementPc(); }