Skip to content

Commit

Permalink
feat(avm): implement comparator opcodes
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro committed Jan 26, 2024
1 parent 1c72c0d commit e775c17
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 22 deletions.
46 changes: 34 additions & 12 deletions yarn-project/acir-simulator/src/avm/avm_memory_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export interface MemoryValue {
mul(rhs: MemoryValue): MemoryValue;
div(rhs: MemoryValue): MemoryValue;

equals(rhs: MemoryValue): boolean;
lt(rhs: MemoryValue): boolean;

// We need this to be able to build an instance of the subclasses.
build(n: bigint): MemoryValue;

// Use sparingly.
toBigInt(): bigint;
}
Expand All @@ -34,9 +40,7 @@ abstract class UnsignedInteger implements IntegralValue {
assert(n < this.mod);
}

// We need this to be able to build an instance of the subclass
// and not of type UnsignedInteger.
protected abstract build(n: bigint): UnsignedInteger;
public abstract build(n: bigint): UnsignedInteger;

public add(rhs: UnsignedInteger): UnsignedInteger {
assert(this.bits == rhs.bits);
Expand Down Expand Up @@ -90,12 +94,18 @@ abstract class UnsignedInteger implements IntegralValue {
return this.build(~this.n & this.bitmask);
}

public toBigInt(): bigint {
return this.n;
public equals(rhs: UnsignedInteger): boolean {
assert(this.bits == rhs.bits);
return this.n === rhs.n;
}

public lt(rhs: UnsignedInteger): boolean {
assert(this.bits == rhs.bits);
return this.n < rhs.n;
}

public equals(rhs: UnsignedInteger) {
return this.bits == rhs.bits && this.toBigInt() == rhs.toBigInt();
public toBigInt(): bigint {
return this.n;
}
}

Expand All @@ -104,7 +114,7 @@ export class Uint8 extends UnsignedInteger {
super(BigInt(n), 8n);
}

protected build(n: bigint): Uint8 {
public build(n: bigint): Uint8 {
return new Uint8(n);
}
}
Expand All @@ -114,7 +124,7 @@ export class Uint16 extends UnsignedInteger {
super(BigInt(n), 16n);
}

protected build(n: bigint): Uint16 {
public build(n: bigint): Uint16 {
return new Uint16(n);
}
}
Expand All @@ -124,7 +134,7 @@ export class Uint32 extends UnsignedInteger {
super(BigInt(n), 32n);
}

protected build(n: bigint): Uint32 {
public build(n: bigint): Uint32 {
return new Uint32(n);
}
}
Expand All @@ -134,7 +144,7 @@ export class Uint64 extends UnsignedInteger {
super(BigInt(n), 64n);
}

protected build(n: bigint): Uint64 {
public build(n: bigint): Uint64 {
return new Uint64(n);
}
}
Expand All @@ -144,7 +154,7 @@ export class Uint128 extends UnsignedInteger {
super(BigInt(n), 128n);
}

protected build(n: bigint): Uint128 {
public build(n: bigint): Uint128 {
return new Uint128(n);
}
}
Expand All @@ -157,6 +167,10 @@ export class Field implements MemoryValue {
this.rep = new Fr(v);
}

public build(n: bigint): Field {
return new Field(n);
}

public add(rhs: Field): Field {
return new Field(this.rep.add(rhs.rep));
}
Expand All @@ -173,6 +187,14 @@ export class Field implements MemoryValue {
return new Field(this.rep.div(rhs.rep));
}

public equals(rhs: Field): boolean {
return this.rep.equals(rhs.rep);
}

public lt(rhs: Field): boolean {
return this.rep.lt(rhs.rep);
}

public toBigInt(): bigint {
return this.rep.toBigInt();
}
Expand Down
147 changes: 147 additions & 0 deletions yarn-project/acir-simulator/src/avm/opcodes/comparators.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { MockProxy, mock } from 'jest-mock-extended';

import { AvmMachineState } from '../avm_machine_state.js';
import { Field, TypeTag, Uint16, Uint32 } from '../avm_memory_types.js';
import { initExecutionEnvironment } from '../fixtures/index.js';
import { AvmJournal } from '../journal/journal.js';
import { Eq, Lt, Lte } from './comparators.js';
import { InstructionExecutionError } from './instruction.js';

describe('Comparators', () => {
let machineState: AvmMachineState;
let journal: MockProxy<AvmJournal>;

beforeEach(async () => {
machineState = new AvmMachineState(initExecutionEnvironment());
journal = mock<AvmJournal>();
});

describe('Eq', () => {
it('Works on integral types', async () => {
machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(3), new Uint32(1)]);

[
new Eq(/*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10, TypeTag.UINT32),
new Eq(/*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 11, TypeTag.UINT32),
new Eq(/*aOffset=*/ 0, /*bOffset=*/ 3, /*dstOffset=*/ 12, TypeTag.UINT32),
].forEach(i => i.execute(machineState, journal));

const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4);
expect(actual).toEqual([new Uint32(0), new Uint32(0), new Uint32(1)]);
});

it('Works on field elements', async () => {
machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(3), new Field(1)]);

[
new Eq(/*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10, TypeTag.FIELD),
new Eq(/*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 11, TypeTag.FIELD),
new Eq(/*aOffset=*/ 0, /*bOffset=*/ 3, /*dstOffset=*/ 12, TypeTag.FIELD),
].forEach(i => i.execute(machineState, journal));

const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4);
expect(actual).toEqual([new Field(0), new Field(0), new Field(1)]);
});

it('InTag is checked', async () => {
machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]);

const ops = [
new Eq(/*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10, TypeTag.FIELD),
new Eq(/*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 10, TypeTag.UINT32),
new Eq(/*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 10, TypeTag.UINT16),
new Eq(/*aOffset=*/ 1, /*bOffset=*/ 1, /*dstOffset=*/ 10, TypeTag.UINT16),
];

for (const o of ops) {
await expect(() => o.execute(machineState, journal)).rejects.toThrow(InstructionExecutionError);
}
});
});

describe('Lt', () => {
it('Works on integral types', async () => {
machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(0)]);

[
new Lt(/*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10, TypeTag.UINT32),
new Lt(/*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11, TypeTag.UINT32),
new Lt(/*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12, TypeTag.UINT32),
].forEach(i => i.execute(machineState, journal));

const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4);
expect(actual).toEqual([new Uint32(0), new Uint32(1), new Uint32(0)]);
});

it('Works on field elements', async () => {
machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(0)]);

[
new Lt(/*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10, TypeTag.FIELD),
new Lt(/*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11, TypeTag.FIELD),
new Lt(/*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12, TypeTag.FIELD),
].forEach(i => i.execute(machineState, journal));

const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4);
expect(actual).toEqual([new Field(0), new Field(1), new Field(0)]);
});

it('InTag is checked', async () => {
machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]);

const ops = [
new Lt(/*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10, TypeTag.FIELD),
new Lt(/*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 10, TypeTag.UINT32),
new Lt(/*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 10, TypeTag.UINT16),
new Lt(/*aOffset=*/ 1, /*bOffset=*/ 1, /*dstOffset=*/ 10, TypeTag.UINT16),
];

for (const o of ops) {
await expect(() => o.execute(machineState, journal)).rejects.toThrow(InstructionExecutionError);
}
});
});

describe('Lte', () => {
it('Works on integral types', async () => {
machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(0)]);

[
new Lte(/*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10, TypeTag.UINT32),
new Lte(/*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11, TypeTag.UINT32),
new Lte(/*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12, TypeTag.UINT32),
].forEach(i => i.execute(machineState, journal));

const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4);
expect(actual).toEqual([new Uint32(1), new Uint32(1), new Uint32(0)]);
});

it('Works on field elements', async () => {
machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(0)]);

[
new Lte(/*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10, TypeTag.FIELD),
new Lte(/*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11, TypeTag.FIELD),
new Lte(/*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12, TypeTag.FIELD),
].forEach(i => i.execute(machineState, journal));

const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4);
expect(actual).toEqual([new Field(1), new Field(1), new Field(0)]);
});

it('InTag is checked', async () => {
machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]);

const ops = [
new Lte(/*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10, TypeTag.FIELD),
new Lte(/*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 10, TypeTag.UINT32),
new Lte(/*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 10, TypeTag.UINT16),
new Lte(/*aOffset=*/ 1, /*bOffset=*/ 1, /*dstOffset=*/ 10, TypeTag.UINT16),
];

for (const o of ops) {
await expect(() => o.execute(machineState, journal)).rejects.toThrow(InstructionExecutionError);
}
});
});
});
29 changes: 19 additions & 10 deletions yarn-project/acir-simulator/src/avm/opcodes/comparators.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { AvmMachineState } from '../avm_machine_state.js';
import { Field } from '../avm_memory_types.js';
import { TypeTag } from '../avm_memory_types.js';
import { AvmJournal } from '../journal/index.js';
import { Instruction } from './instruction.js';

export class Eq extends Instruction {
static type: string = 'EQ';
static numberOfOperands = 3;

constructor(private aOffset: number, private bOffset: number, private destOffset: number) {
constructor(private aOffset: number, private bOffset: number, private dstOffset: number, private inTag: TypeTag) {
super();
}

async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise<void> {
Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset);

const a = machineState.memory.get(this.aOffset);
const b = machineState.memory.get(this.bOffset);

const dest = new Field(a.toBigInt() == b.toBigInt() ? 1 : 0);
machineState.memory.set(this.destOffset, dest);
// Result will be of the same type as 'a'.
const dest = a.build(a.equals(b) ? 1n : 0n);
machineState.memory.set(this.dstOffset, dest);

this.incrementPc(machineState);
}
Expand All @@ -26,16 +29,19 @@ export class Lt extends Instruction {
static type: string = 'Lt';
static numberOfOperands = 3;

constructor(private aOffset: number, private bOffset: number, private destOffset: number) {
constructor(private aOffset: number, private bOffset: number, private dstOffset: number, private inTag: TypeTag) {
super();
}

async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise<void> {
Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset);

const a = machineState.memory.get(this.aOffset);
const b = machineState.memory.get(this.bOffset);

const dest = new Field(a.toBigInt() < b.toBigInt() ? 1 : 0);
machineState.memory.set(this.destOffset, dest);
// Result will be of the same type as 'a'.
const dest = a.build(a.lt(b) ? 1n : 0n);
machineState.memory.set(this.dstOffset, dest);

this.incrementPc(machineState);
}
Expand All @@ -45,16 +51,19 @@ export class Lte extends Instruction {
static type: string = 'LTE';
static numberOfOperands = 3;

constructor(private aOffset: number, private bOffset: number, private destOffset: number) {
constructor(private aOffset: number, private bOffset: number, private dstOffset: number, private inTag: TypeTag) {
super();
}

async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise<void> {
Instruction.checkTags(machineState, this.inTag, this.aOffset, this.bOffset);

const a = machineState.memory.get(this.aOffset);
const b = machineState.memory.get(this.bOffset);

const dest = new Field(a.toBigInt() < b.toBigInt() ? 1 : 0);
machineState.memory.set(this.destOffset, dest);
// Result will be of the same type as 'a'.
const dest = a.build(a.equals(b) || a.lt(b) ? 1n : 0n);
machineState.memory.set(this.dstOffset, dest);

this.incrementPc(machineState);
}
Expand Down

0 comments on commit e775c17

Please sign in to comment.