Skip to content

Commit a09bd99

Browse files
authored
Merge 62ee1c3 into 38b485e
2 parents 38b485e + 62ee1c3 commit a09bd99

19 files changed

+228
-211
lines changed

yarn-project/simulator/src/avm/avm_memory_types.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ export class TaggedMemory implements TaggedMemoryInterface {
413413
}
414414

415415
/** No-op. Implemented here for compatibility with the MeteredTaggedMemory. */
416-
public assert(_operations: Partial<MemoryOperations & { indirect: number }>) {}
416+
public assert(_operations: Partial<MemoryOperations & { addressing: Addressing }>) {}
417417
}
418418

419419
/** Tagged memory wrapper with metering for each memory read and write operation. */
@@ -435,10 +435,15 @@ export class MeteredTaggedMemory implements TaggedMemoryInterface {
435435
* Asserts that the exact number of memory operations have been performed.
436436
* Indirect represents the flags for indirect accesses: each bit set to one counts as an extra read.
437437
*/
438-
public assert(operations: Partial<MemoryOperations & { indirect: number }>) {
439-
const { reads: expectedReads, writes: expectedWrites, indirect } = { reads: 0, writes: 0, ...operations };
440-
441-
const totalExpectedReads = expectedReads + Addressing.fromWire(indirect ?? 0).count(AddressingMode.INDIRECT);
438+
public assert(operations: Partial<MemoryOperations & { addressing: Addressing }>) {
439+
const {
440+
reads: expectedReads,
441+
writes: expectedWrites,
442+
addressing,
443+
} = { reads: 0, writes: 0, addressing: new Addressing([]), ...operations };
444+
445+
const totalExpectedReads =
446+
expectedReads + addressing.count(AddressingMode.INDIRECT) + addressing.count(AddressingMode.RELATIVE);
442447
const { reads: actualReads, writes: actualWrites } = this.reset();
443448
if (actualReads !== totalExpectedReads) {
444449
throw new InstructionExecutionError(

yarn-project/simulator/src/avm/opcodes/accrued_substate.ts

+28-36
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,11 @@ export class NoteHashExists extends Instruction {
2828
}
2929

3030
public async execute(context: AvmContext): Promise<void> {
31-
const memoryOperations = { reads: 2, writes: 1, indirect: this.indirect };
3231
const memory = context.machineState.memory.track(this.type);
3332
context.machineState.consumeGas(this.gasCost());
34-
const [noteHashOffset, leafIndexOffset, existsOffset] = Addressing.fromWire(this.indirect).resolve(
35-
[this.noteHashOffset, this.leafIndexOffset, this.existsOffset],
36-
memory,
37-
);
33+
const operands = [this.noteHashOffset, this.leafIndexOffset, this.existsOffset];
34+
const addressing = Addressing.fromWire(this.indirect, operands.length);
35+
const [noteHashOffset, leafIndexOffset, existsOffset] = addressing.resolve(operands, memory);
3836
memory.checkTags(TypeTag.FIELD, noteHashOffset, leafIndexOffset);
3937

4038
// Note that this instruction accepts any type in memory, and converts to Field.
@@ -48,7 +46,7 @@ export class NoteHashExists extends Instruction {
4846
);
4947
memory.set(existsOffset, exists ? new Uint8(1) : new Uint8(0));
5048

51-
memory.assert(memoryOperations);
49+
memory.assert({ reads: 2, writes: 1, addressing });
5250
context.machineState.incrementPc();
5351
}
5452
}
@@ -64,11 +62,12 @@ export class EmitNoteHash extends Instruction {
6462
}
6563

6664
public async execute(context: AvmContext): Promise<void> {
67-
const memoryOperations = { reads: 1, indirect: this.indirect };
6865
const memory = context.machineState.memory.track(this.type);
6966
context.machineState.consumeGas(this.gasCost());
7067

71-
const [noteHashOffset] = Addressing.fromWire(this.indirect).resolve([this.noteHashOffset], memory);
68+
const operands = [this.noteHashOffset];
69+
const addressing = Addressing.fromWire(this.indirect, operands.length);
70+
const [noteHashOffset] = addressing.resolve(operands, memory);
7271
memory.checkTag(TypeTag.FIELD, noteHashOffset);
7372

7473
if (context.environment.isStaticCall) {
@@ -78,7 +77,7 @@ export class EmitNoteHash extends Instruction {
7877
const noteHash = memory.get(noteHashOffset).toFr();
7978
context.persistableState.writeNoteHash(context.environment.storageAddress, noteHash);
8079

81-
memory.assert(memoryOperations);
80+
memory.assert({ reads: 1, addressing });
8281
context.machineState.incrementPc();
8382
}
8483
}
@@ -105,14 +104,12 @@ export class NullifierExists extends Instruction {
105104
}
106105

107106
public async execute(context: AvmContext): Promise<void> {
108-
const memoryOperations = { reads: 2, writes: 1, indirect: this.indirect };
109107
const memory = context.machineState.memory.track(this.type);
110108
context.machineState.consumeGas(this.gasCost());
111109

112-
const [nullifierOffset, addressOffset, existsOffset] = Addressing.fromWire(this.indirect).resolve(
113-
[this.nullifierOffset, this.addressOffset, this.existsOffset],
114-
memory,
115-
);
110+
const operands = [this.nullifierOffset, this.addressOffset, this.existsOffset];
111+
const addressing = Addressing.fromWire(this.indirect, operands.length);
112+
const [nullifierOffset, addressOffset, existsOffset] = addressing.resolve(operands, memory);
116113
memory.checkTags(TypeTag.FIELD, nullifierOffset, addressOffset);
117114

118115
const nullifier = memory.get(nullifierOffset).toFr();
@@ -121,7 +118,7 @@ export class NullifierExists extends Instruction {
121118

122119
memory.set(existsOffset, exists ? new Uint8(1) : new Uint8(0));
123120

124-
memory.assert(memoryOperations);
121+
memory.assert({ reads: 2, writes: 1, addressing });
125122
context.machineState.incrementPc();
126123
}
127124
}
@@ -141,11 +138,12 @@ export class EmitNullifier extends Instruction {
141138
throw new StaticCallAlterationError();
142139
}
143140

144-
const memoryOperations = { reads: 1, indirect: this.indirect };
145141
const memory = context.machineState.memory.track(this.type);
146142
context.machineState.consumeGas(this.gasCost());
147143

148-
const [nullifierOffset] = Addressing.fromWire(this.indirect).resolve([this.nullifierOffset], memory);
144+
const operands = [this.nullifierOffset];
145+
const addressing = Addressing.fromWire(this.indirect, operands.length);
146+
const [nullifierOffset] = addressing.resolve(operands, memory);
149147
memory.checkTag(TypeTag.FIELD, nullifierOffset);
150148

151149
const nullifier = memory.get(nullifierOffset).toFr();
@@ -162,7 +160,7 @@ export class EmitNullifier extends Instruction {
162160
}
163161
}
164162

165-
memory.assert(memoryOperations);
163+
memory.assert({ reads: 1, addressing });
166164
context.machineState.incrementPc();
167165
}
168166
}
@@ -189,14 +187,12 @@ export class L1ToL2MessageExists extends Instruction {
189187
}
190188

191189
public async execute(context: AvmContext): Promise<void> {
192-
const memoryOperations = { reads: 2, writes: 1, indirect: this.indirect };
193190
const memory = context.machineState.memory.track(this.type);
194191
context.machineState.consumeGas(this.gasCost());
195192

196-
const [msgHashOffset, msgLeafIndexOffset, existsOffset] = Addressing.fromWire(this.indirect).resolve(
197-
[this.msgHashOffset, this.msgLeafIndexOffset, this.existsOffset],
198-
memory,
199-
);
193+
const operands = [this.msgHashOffset, this.msgLeafIndexOffset, this.existsOffset];
194+
const addressing = Addressing.fromWire(this.indirect, operands.length);
195+
const [msgHashOffset, msgLeafIndexOffset, existsOffset] = addressing.resolve(operands, memory);
200196
memory.checkTags(TypeTag.FIELD, msgHashOffset, msgLeafIndexOffset);
201197

202198
const msgHash = memory.get(msgHashOffset).toFr();
@@ -208,7 +204,7 @@ export class L1ToL2MessageExists extends Instruction {
208204
);
209205
memory.set(existsOffset, exists ? new Uint8(1) : new Uint8(0));
210206

211-
memory.assert(memoryOperations);
207+
memory.assert({ reads: 2, writes: 1, addressing });
212208
context.machineState.incrementPc();
213209
}
214210
}
@@ -230,22 +226,20 @@ export class EmitUnencryptedLog extends Instruction {
230226

231227
const memory = context.machineState.memory.track(this.type);
232228

233-
const [logOffset, logSizeOffset] = Addressing.fromWire(this.indirect).resolve(
234-
[this.logOffset, this.logSizeOffset],
235-
memory,
236-
);
229+
const operands = [this.logOffset, this.logSizeOffset];
230+
const addressing = Addressing.fromWire(this.indirect, operands.length);
231+
const [logOffset, logSizeOffset] = addressing.resolve(operands, memory);
237232
memory.checkTag(TypeTag.UINT32, logSizeOffset);
238233
const logSize = memory.get(logSizeOffset).toNumber();
239234
memory.checkTagsRange(TypeTag.FIELD, logOffset, logSize);
240235

241236
const contractAddress = context.environment.address;
242237

243-
const memoryOperations = { reads: 1 + logSize, indirect: this.indirect };
244238
context.machineState.consumeGas(this.gasCost(logSize));
245239
const log = memory.getSlice(logOffset, logSize).map(f => f.toFr());
246240
context.persistableState.writeUnencryptedLog(contractAddress, log);
247241

248-
memory.assert(memoryOperations);
242+
memory.assert({ reads: 1 + logSize, addressing });
249243
context.machineState.incrementPc();
250244
}
251245
}
@@ -265,20 +259,18 @@ export class SendL2ToL1Message extends Instruction {
265259
throw new StaticCallAlterationError();
266260
}
267261

268-
const memoryOperations = { reads: 2, indirect: this.indirect };
269262
const memory = context.machineState.memory.track(this.type);
270263
context.machineState.consumeGas(this.gasCost());
271264

272-
const [recipientOffset, contentOffset] = Addressing.fromWire(this.indirect).resolve(
273-
[this.recipientOffset, this.contentOffset],
274-
memory,
275-
);
265+
const operands = [this.recipientOffset, this.contentOffset];
266+
const addressing = Addressing.fromWire(this.indirect, operands.length);
267+
const [recipientOffset, contentOffset] = addressing.resolve(operands, memory);
276268

277269
const recipient = memory.get(recipientOffset).toFr();
278270
const content = memory.get(contentOffset).toFr();
279271
context.persistableState.writeL2ToL1Message(recipient, content);
280272

281-
memory.assert(memoryOperations);
273+
memory.assert({ reads: 2, addressing });
282274
context.machineState.incrementPc();
283275
}
284276
}

yarn-project/simulator/src/avm/opcodes/addressing_mode.ts

+27-24
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,28 @@ import { strict as assert } from 'assert';
33
import { type TaggedMemoryInterface } from '../avm_memory_types.js';
44

55
export enum AddressingMode {
6-
DIRECT,
7-
INDIRECT,
8-
INDIRECT_PLUS_CONSTANT, // Not implemented yet.
6+
DIRECT = 0,
7+
INDIRECT = 1,
8+
RELATIVE = 2,
9+
INDIRECT_RELATIVE = 3,
910
}
1011

1112
/** A class to represent the addressing mode of an instruction. */
1213
export class Addressing {
1314
public constructor(
1415
/** The addressing mode for each operand. The length of this array is the number of operands of the instruction. */
1516
private readonly modePerOperand: AddressingMode[],
16-
) {
17-
assert(modePerOperand.length <= 8, 'At most 8 operands are supported');
18-
}
17+
) {}
1918

20-
public static fromWire(wireModes: number): Addressing {
19+
// TODO(facundo): 8 for backwards compatibility.
20+
public static fromWire(wireModes: number, numOperands: number = 8): Addressing {
2121
// The modes are stored in the wire format as a byte, with each bit representing the mode for an operand.
2222
// The least significant bit represents the zeroth operand, and the most significant bit represents the last operand.
23-
const modes = new Array<AddressingMode>(8);
24-
for (let i = 0; i < 8; i++) {
25-
modes[i] = (wireModes & (1 << i)) === 0 ? AddressingMode.DIRECT : AddressingMode.INDIRECT;
23+
const modes = new Array<AddressingMode>(numOperands);
24+
for (let i = 0; i < numOperands; i++) {
25+
modes[i] =
26+
(((wireModes >> i) & 1) * AddressingMode.INDIRECT) |
27+
(((wireModes >> (i + numOperands)) & 1) * AddressingMode.RELATIVE);
2628
}
2729
return new Addressing(modes);
2830
}
@@ -31,17 +33,20 @@ export class Addressing {
3133
// The modes are stored in the wire format as a byte, with each bit representing the mode for an operand.
3234
// The least significant bit represents the zeroth operand, and the least significant bit represents the last operand.
3335
let wire: number = 0;
34-
for (let i = 0; i < 8; i++) {
35-
if (this.modePerOperand[i] === AddressingMode.INDIRECT) {
36+
for (let i = 0; i < this.modePerOperand.length; i++) {
37+
if (this.modePerOperand[i] & AddressingMode.INDIRECT) {
3638
wire |= 1 << i;
3739
}
40+
if (this.modePerOperand[i] & AddressingMode.RELATIVE) {
41+
wire |= 1 << (this.modePerOperand.length + i);
42+
}
3843
}
3944
return wire;
4045
}
4146

4247
/** Returns how many operands use the given addressing mode. */
4348
public count(mode: AddressingMode): number {
44-
return this.modePerOperand.filter(m => m === mode).length;
49+
return this.modePerOperand.filter(m => (m & mode) !== 0).length;
4550
}
4651

4752
/**
@@ -54,17 +59,15 @@ export class Addressing {
5459
assert(offsets.length <= this.modePerOperand.length);
5560
const resolved = new Array(offsets.length);
5661
for (const [i, offset] of offsets.entries()) {
57-
switch (this.modePerOperand[i]) {
58-
case AddressingMode.INDIRECT:
59-
// NOTE(reviewer): less than equal is a deviation from the spec - i dont see why this shouldnt be possible!
60-
mem.checkIsValidMemoryOffsetTag(offset);
61-
resolved[i] = Number(mem.get(offset).toBigInt());
62-
break;
63-
case AddressingMode.DIRECT:
64-
resolved[i] = offset;
65-
break;
66-
default:
67-
throw new Error(`Unimplemented addressing mode: ${AddressingMode[this.modePerOperand[i]]}`);
62+
const mode = this.modePerOperand[i];
63+
resolved[i] = offset;
64+
if (mode & AddressingMode.RELATIVE) {
65+
mem.checkIsValidMemoryOffsetTag(0);
66+
resolved[i] += Number(mem.get(0).toBigInt());
67+
}
68+
if (mode & AddressingMode.INDIRECT) {
69+
mem.checkIsValidMemoryOffsetTag(resolved[i]);
70+
resolved[i] = Number(mem.get(resolved[i]).toBigInt());
6871
}
6972
}
7073
return resolved;

yarn-project/simulator/src/avm/opcodes/arithmetic.test.ts

+27
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { type AvmContext } from '../avm_context.js';
22
import { Field, TypeTag, Uint8, Uint16, Uint32, Uint64, Uint128 } from '../avm_memory_types.js';
33
import { initContext } from '../fixtures/index.js';
44
import { Opcode } from '../serialization/instruction_serialization.js';
5+
import { Addressing, AddressingMode } from './addressing_mode.js';
56
import { Add, Div, FieldDiv, Mul, Sub } from './arithmetic.js';
67

78
describe('Arithmetic Instructions', () => {
@@ -54,6 +55,32 @@ describe('Arithmetic Instructions', () => {
5455
});
5556
});
5657

58+
it('Should add in relative indirect mode', async () => {
59+
const a = new Field(1n);
60+
const b = new Field(2n);
61+
62+
context.machineState.memory.set(10, a);
63+
context.machineState.memory.set(11, b);
64+
65+
context.machineState.memory.set(0, new Uint32(30)); // stack pointer
66+
context.machineState.memory.set(32, new Uint32(5)); // indirect
67+
68+
await new Add(
69+
/*indirect=*/ new Addressing([
70+
/*aOffset*/ AddressingMode.DIRECT,
71+
/*bOffset*/ AddressingMode.DIRECT,
72+
/*dstOffset*/ AddressingMode.INDIRECT | AddressingMode.RELATIVE,
73+
]).toWire(),
74+
/*inTag=*/ TypeTag.FIELD,
75+
/*aOffset=*/ 10,
76+
/*bOffset=*/ 11,
77+
/*dstOffset=*/ 2, // We expect the result to be stored at MEM[30 + 2] = 5
78+
).execute(context);
79+
80+
const actual = context.machineState.memory.get(5);
81+
expect(actual).toEqual(new Field(3n));
82+
});
83+
5784
describe.each([
5885
[new Field((Field.MODULUS + 1n) / 2n), new Field(1n), TypeTag.FIELD],
5986
[new Uint8((1n << 7n) + 1n), new Uint8(2n), TypeTag.UINT8],

yarn-project/simulator/src/avm/opcodes/arithmetic.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ import { ThreeOperandInstruction } from './instruction_impl.js';
66

77
export abstract class ThreeOperandArithmeticInstruction extends ThreeOperandInstruction {
88
public async execute(context: AvmContext): Promise<void> {
9-
const memoryOperations = { reads: 2, writes: 1, indirect: this.indirect };
109
const memory = context.machineState.memory.track(this.type);
1110
context.machineState.consumeGas(this.gasCost());
1211

13-
const [aOffset, bOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve(
14-
[this.aOffset, this.bOffset, this.dstOffset],
15-
memory,
16-
);
12+
const operands = [this.aOffset, this.bOffset, this.dstOffset];
13+
const addressing = Addressing.fromWire(this.indirect, operands.length);
14+
const [aOffset, bOffset, dstOffset] = addressing.resolve(operands, memory);
1715
memory.checkTags(this.inTag, aOffset, bOffset);
1816

1917
const a = memory.get(aOffset);
@@ -22,7 +20,7 @@ export abstract class ThreeOperandArithmeticInstruction extends ThreeOperandInst
2220
const dest = this.compute(a, b);
2321
memory.set(dstOffset, dest);
2422

25-
memory.assert(memoryOperations);
23+
memory.assert({ reads: 2, writes: 1, addressing });
2624
context.machineState.incrementPc();
2725
}
2826

0 commit comments

Comments
 (0)