Skip to content

Commit

Permalink
chore(avm-simulator): be explicit about wrapping arithmetic (#5287)
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro authored Mar 18, 2024
1 parent 21ccb4b commit 1b2cf58
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 110 deletions.
246 changes: 139 additions & 107 deletions yarn-project/simulator/src/avm/opcodes/arithmetic.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AvmContext } from '../avm_context.js';
import { Field, TypeTag } from '../avm_memory_types.js';
import { Field, TypeTag, Uint8, Uint16, Uint32, Uint64, Uint128 } from '../avm_memory_types.js';
import { initContext } from '../fixtures/index.js';
import { Add, Div, FieldDiv, Mul, Sub } from './arithmetic.js';

Expand Down Expand Up @@ -32,44 +32,45 @@ describe('Arithmetic Instructions', () => {
expect(inst.serialize()).toEqual(buf);
});

it('Should add correctly over field elements', async () => {
const a = new Field(1n);
const b = new Field(2n);

context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Add(
/*indirect=*/ 0,
/*inTag=*/ TypeTag.FIELD,
/*aOffset=*/ 0,
/*bOffset=*/ 1,
/*dstOffset=*/ 2,
).execute(context);

const expected = new Field(3n);
const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
describe.each([
[new Field(1n), new Field(2n), new Field(3n), TypeTag.FIELD],
[new Uint8(1n), new Uint8(2n), new Uint8(3n), TypeTag.UINT8],
[new Uint16(1n), new Uint16(2n), new Uint16(3n), TypeTag.UINT16],
[new Uint32(1n), new Uint32(2n), new Uint32(3n), TypeTag.UINT32],
[new Uint64(1n), new Uint64(2n), new Uint64(3n), TypeTag.UINT64],
[new Uint128(1n), new Uint128(2n), new Uint128(3n), TypeTag.UINT128],
])('Should add correctly', (a, b, expected, tag) => {
it(`${TypeTag[tag]}`, async () => {
context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Add(/*indirect=*/ 0, /*inTag=*/ tag, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).execute(
context,
);

const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
});
});

it('Should wrap around on addition', async () => {
const a = new Field(1n);
const b = new Field(Field.MODULUS - 1n);

context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Add(
/*indirect=*/ 0,
/*inTag=*/ TypeTag.FIELD,
/*aOffset=*/ 0,
/*bOffset=*/ 1,
/*dstOffset=*/ 2,
).execute(context);

const expected = new Field(0n);
const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
describe.each([
[new Field((Field.MODULUS + 1n) / 2n), new Field(1n), TypeTag.FIELD],
[new Uint8((1n << 7n) + 1n), new Uint8(2n), TypeTag.UINT8],
[new Uint16((1n << 15n) + 1n), new Uint16(2n), TypeTag.UINT16],
[new Uint32((1n << 31n) + 1n), new Uint32(2n), TypeTag.UINT32],
[new Uint64((1n << 63n) + 1n), new Uint64(2n), TypeTag.UINT64],
[new Uint128((1n << 127n) + 1n), new Uint128(2n), TypeTag.UINT128],
])('Should wrap around', (a, expected, tag) => {
it(`${TypeTag[tag]}`, async () => {
context.machineState.memory.set(0, a);

await new Add(/*indirect=*/ 0, /*inTag=*/ tag, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 2).execute(
context,
);

const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
});
});
});

Expand All @@ -95,24 +96,51 @@ describe('Arithmetic Instructions', () => {
expect(inst.serialize()).toEqual(buf);
});

it('Should subtract correctly over field elements', async () => {
const a = new Field(1n);
const b = new Field(2n);

context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Sub(
/*indirect=*/ 0,
/*inTag=*/ TypeTag.FIELD,
/*aOffset=*/ 0,
/*bOffset=*/ 1,
/*dstOffset=*/ 2,
).execute(context);
describe.each([
[new Field(200n), new Field(100n), new Field(100n), TypeTag.FIELD],
[new Uint8(200n), new Uint8(100n), new Uint8(100n), TypeTag.UINT8],
[new Uint16(200n), new Uint16(100n), new Uint16(100n), TypeTag.UINT16],
[new Uint32(200n), new Uint32(100n), new Uint32(100n), TypeTag.UINT32],
[new Uint64(200n), new Uint64(100n), new Uint64(100n), TypeTag.UINT64],
[new Uint128(200n), new Uint128(100n), new Uint128(100n), TypeTag.UINT128],
])('Should subtract correctly', (a, b, expected, tag) => {
it(`${TypeTag[tag]}`, async () => {
context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Sub(/*indirect=*/ 0, /*inTag=*/ tag, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).execute(
context,
);

const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
});
});

const expected = new Field(Field.MODULUS - 1n);
const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
describe.each([
[
new Field((Field.MODULUS + 1n) / 2n),
new Field((Field.MODULUS + 1n) / 2n + 2n),
new Field(Field.MODULUS - 2n),
TypeTag.FIELD,
],
[new Uint8(1n << 7n), new Uint8((1n << 7n) + 2n), new Uint8((1n << 8n) - 2n), TypeTag.UINT8],
[new Uint16(1n << 15n), new Uint16((1n << 15n) + 2n), new Uint16((1n << 16n) - 2n), TypeTag.UINT16],
[new Uint32(1n << 31n), new Uint32((1n << 31n) + 2n), new Uint32((1n << 32n) - 2n), TypeTag.UINT32],
[new Uint64(1n << 63n), new Uint64((1n << 63n) + 2n), new Uint64((1n << 64n) - 2n), TypeTag.UINT64],
[new Uint128(1n << 127n), new Uint128((1n << 127n) + 2n), new Uint128((1n << 128n) - 2n), TypeTag.UINT128],
])('Should wrap around', (a, b, expected, tag) => {
it(`${TypeTag[tag]}`, async () => {
context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Sub(/*indirect=*/ 0, /*inTag=*/ tag, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).execute(
context,
);

const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
});
});
});

Expand All @@ -138,44 +166,46 @@ describe('Arithmetic Instructions', () => {
expect(inst.serialize()).toEqual(buf);
});

it('Should multiply correctly over field elements', async () => {
const a = new Field(2n);
const b = new Field(3n);

context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Mul(
/*indirect=*/ 0,
/*inTag=*/ TypeTag.FIELD,
/*aOffset=*/ 0,
/*bOffset=*/ 1,
/*dstOffset=*/ 2,
).execute(context);

const expected = new Field(6n);
const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
describe.each([
[new Field(200n), new Field(100n), new Field(20000n), TypeTag.FIELD],
[new Uint8(2n), new Uint8(100n), new Uint8(200n), TypeTag.UINT8],
[new Uint16(200n), new Uint16(100n), new Uint16(20000n), TypeTag.UINT16],
[new Uint32(200n), new Uint32(100n), new Uint32(20000n), TypeTag.UINT32],
[new Uint64(200n), new Uint64(100n), new Uint64(20000n), TypeTag.UINT64],
[new Uint128(200n), new Uint128(100n), new Uint128(20000n), TypeTag.UINT128],
])('Should multiply correctly', (a, b, expected, tag) => {
it(`${TypeTag[tag]}`, async () => {
context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Mul(/*indirect=*/ 0, /*inTag=*/ tag, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).execute(
context,
);

const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
});
});

it('Should wrap around on multiplication', async () => {
const a = new Field(2n);
const b = new Field(Field.MODULUS / 2n - 1n);

context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Mul(
/*indirect=*/ 0,
/*inTag=*/ TypeTag.FIELD,
/*aOffset=*/ 0,
/*bOffset=*/ 1,
/*dstOffset=*/ 2,
).execute(context);

const expected = new Field(Field.MODULUS - 3n);
const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
describe.each([
[new Field((Field.MODULUS + 1n) / 2n + 2n), new Field(2n), new Field(5n), TypeTag.FIELD],
[new Uint8(1n << 7n), new Uint8(2n), new Uint8(0n), TypeTag.UINT8],
[new Uint16(1n << 15n), new Uint16(2n), new Uint16(0n), TypeTag.UINT16],
[new Uint32(1n << 31n), new Uint32(2n), new Uint32(0n), TypeTag.UINT32],
[new Uint64(1n << 63n), new Uint64(2n), new Uint64(0n), TypeTag.UINT64],
[new Uint128(1n << 127n), new Uint128(2n), new Uint128(0n), TypeTag.UINT128],
])('Should wrap around', (a, b, expected, tag) => {
it(`${TypeTag[tag]}`, async () => {
context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Mul(/*indirect=*/ 0, /*inTag=*/ tag, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).execute(
context,
);

const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
});
});
});

Expand All @@ -201,23 +231,25 @@ describe('Arithmetic Instructions', () => {
expect(inst.serialize()).toEqual(buf);
});

it('Should perform integer division', async () => {
const a = new Field(100n);
const b = new Field(5n);

context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Div(
/*indirect=*/ 0,
/*inTag=*/ TypeTag.FIELD,
/*aOffset=*/ 0,
/*bOffset=*/ 1,
/*dstOffset=*/ 2,
).execute(context);

const actual = context.machineState.memory.get(2);
expect(actual).toEqual(new Field(20));
describe.each([
[new Field(200n), new Field(99n), new Field(2n), TypeTag.FIELD],
[new Uint8(200n), new Uint8(99n), new Uint8(2n), TypeTag.UINT8],
[new Uint16(200n), new Uint16(99n), new Uint16(2n), TypeTag.UINT16],
[new Uint32(200n), new Uint32(99n), new Uint32(2n), TypeTag.UINT32],
[new Uint64(200n), new Uint64(99n), new Uint64(2n), TypeTag.UINT64],
[new Uint128(200n), new Uint128(99n), new Uint128(2n), TypeTag.UINT128],
])('Should divide correctly', (a, b, expected, tag) => {
it(`${TypeTag[tag]}`, async () => {
context.machineState.memory.set(0, a);
context.machineState.memory.set(1, b);

await new Div(/*indirect=*/ 0, /*inTag=*/ tag, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).execute(
context,
);

const actual = context.machineState.memory.get(2);
expect(actual).toEqual(expected);
});
});
});

Expand Down
6 changes: 3 additions & 3 deletions yellow-paper/src/preprocess/InstructionSet/InstructionSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const INSTRUCTION_SET_RAW = [
],
"Expression": "`M[dstOffset] = M[aOffset] + M[bOffset] mod 2^k`",
"Summary": "Addition (a + b)",
"Details": "",
"Details": "Wraps on overflow",
"Tag checks": "`T[aOffset] == T[bOffset] == inTag`",
"Tag updates": "`T[dstOffset] = inTag`",
},
Expand All @@ -63,7 +63,7 @@ const INSTRUCTION_SET_RAW = [
],
"Expression": "`M[dstOffset] = M[aOffset] - M[bOffset] mod 2^k`",
"Summary": "Subtraction (a - b)",
"Details": "",
"Details": "Wraps on undeflow",
"Tag checks": "`T[aOffset] == T[bOffset] == inTag`",
"Tag updates": "`T[dstOffset] = inTag`",
},
Expand All @@ -82,7 +82,7 @@ const INSTRUCTION_SET_RAW = [
],
"Expression": "`M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k`",
"Summary": "Multiplication (a * b)",
"Details": "",
"Details": "Wraps on overflow",
"Tag checks": "`T[aOffset] == T[bOffset] == inTag`",
"Tag updates": "`T[dstOffset] = inTag`",
},
Expand Down

0 comments on commit 1b2cf58

Please sign in to comment.