Skip to content

Commit

Permalink
feat(avm): Migrate simulator memory to a map (#10715)
Browse files Browse the repository at this point in the history
Resolves #10370
  • Loading branch information
jeanmon authored Dec 16, 2024
1 parent d0a4b2f commit 64d5f2b
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 29 deletions.
24 changes: 22 additions & 2 deletions yarn-project/simulator/src/avm/avm_memory_types.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AssertionError } from 'assert';

import {
Field,
MeteredTaggedMemory,
Expand Down Expand Up @@ -28,13 +30,13 @@ describe('TaggedMemory', () => {
expect(mem.get(10)).toStrictEqual(new Field(5));
});

it(`Should fail getSlice on unset elements`, () => {
it(`Slice should get Field(0) on unset elements`, () => {
const mem = new TaggedMemory();

mem.set(10, new Field(10));
mem.set(12, new Field(12));

expect(() => mem.getSlice(10, /*size=*/ 4)).toThrow(/size/);
expect(mem.getSlice(10, /*size=*/ 4)).toStrictEqual([new Field(10), new Field(0), new Field(12), new Field(0)]);
});

it(`Should set and get slices`, () => {
Expand All @@ -45,6 +47,24 @@ describe('TaggedMemory', () => {

expect(mem.getSlice(10, /*size=*/ 2)).toStrictEqual(val);
});

it(`Should access and write in last index of memory`, () => {
const last = TaggedMemory.MAX_MEMORY_SIZE - 1;
const mem = new TaggedMemory();
const val = new Uint64(42);
mem.set(last, val);
expect(mem.get(last)).toStrictEqual(val);
});

it(`Should not access beyond memory last index`, () => {
const mem = new TaggedMemory();

expect(() => mem.set(TaggedMemory.MAX_MEMORY_SIZE, new Field(1))).toThrow(AssertionError);
expect(() => mem.get(TaggedMemory.MAX_MEMORY_SIZE)).toThrow(AssertionError);

expect(() => mem.set(TaggedMemory.MAX_MEMORY_SIZE + 15, new Field(1))).toThrow(AssertionError);
expect(() => mem.get(TaggedMemory.MAX_MEMORY_SIZE + 7)).toThrow(AssertionError);
});
});

describe('MeteredTaggedMemory', () => {
Expand Down
56 changes: 29 additions & 27 deletions yarn-project/simulator/src/avm/avm_memory_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,14 @@ export class TaggedMemory implements TaggedMemoryInterface {
// Whether to track and validate memory accesses for each instruction.
static readonly TRACK_MEMORY_ACCESSES = process.env.NODE_ENV === 'test';

// FIXME: memory should be 2^32, but TS max array size is: 2^32 - 1
static readonly MAX_MEMORY_SIZE = Number((1n << 32n) - 1n);
private _mem: MemoryValue[];
// Memory is modelled by a map with key type being number.
// We however restrict the keys to be non-negative integers smaller than
// MAX_MEMORY_SIZE.
static readonly MAX_MEMORY_SIZE = Number(1n << 32n);
private _mem: Map<number, MemoryValue>;

constructor() {
// We do not initialize memory size here because otherwise tests blow up when diffing.
this._mem = [];
this._mem = new Map<number, MemoryValue>();
}

public getMaxMemorySize(): number {
Expand All @@ -257,8 +258,9 @@ export class TaggedMemory implements TaggedMemoryInterface {
}

public getAs<T>(offset: number): T {
assert(Number.isInteger(offset));
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
const word = this._mem[offset];
const word = this._mem.get(offset);
TaggedMemory.log.trace(`get(${offset}) = ${word}`);
if (word === undefined) {
TaggedMemory.log.debug(`WARNING: Memory at offset ${offset} is undefined!`);
Expand All @@ -268,46 +270,46 @@ export class TaggedMemory implements TaggedMemoryInterface {
}

public getSlice(offset: number, size: number): MemoryValue[] {
assert(Number.isInteger(offset) && Number.isInteger(size));
assert(offset + size <= TaggedMemory.MAX_MEMORY_SIZE);
const value = this._mem.slice(offset, offset + size);
TaggedMemory.log.trace(`getSlice(${offset}, ${size}) = ${value}`);
for (let i = 0; i < value.length; i++) {
if (value[i] === undefined) {
value[i] = new Field(0);
}
const slice = new Array<MemoryValue>(size);

for (let i = 0; i < size; i++) {
slice[i] = this._mem.get(offset + i) ?? new Field(0);
}
assert(value.length === size, `Expected slice of size ${size}, got ${value.length}.`);
return value;

TaggedMemory.log.trace(`getSlice(${offset}, ${size}) = ${slice}`);
return slice;
}

public getSliceAs<T>(offset: number, size: number): T[] {
assert(offset + size <= TaggedMemory.MAX_MEMORY_SIZE);
return this.getSlice(offset, size) as T[];
}

public getSliceTags(offset: number, size: number): TypeTag[] {
assert(offset + size <= TaggedMemory.MAX_MEMORY_SIZE);
return this._mem.slice(offset, offset + size).map(TaggedMemory.getTag);
return this.getSlice(offset, size).map(TaggedMemory.getTag);
}

public set(offset: number, v: MemoryValue) {
assert(Number.isInteger(offset));
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
this._mem[offset] = v;
this._mem.set(offset, v);
TaggedMemory.log.trace(`set(${offset}, ${v})`);
}

public setSlice(offset: number, vs: MemoryValue[]) {
assert(offset + vs.length <= TaggedMemory.MAX_MEMORY_SIZE);
// We may need to extend the memory size, otherwise splice doesn't insert.
if (offset + vs.length > this._mem.length) {
this._mem.length = offset + vs.length;
}
this._mem.splice(offset, vs.length, ...vs);
TaggedMemory.log.trace(`setSlice(${offset}, ${vs})`);
public setSlice(offset: number, slice: MemoryValue[]) {
assert(Number.isInteger(offset));
assert(offset + slice.length <= TaggedMemory.MAX_MEMORY_SIZE);
slice.forEach((element, idx) => {
this._mem.set(offset + idx, element);
});
TaggedMemory.log.trace(`setSlice(${offset}, ${slice})`);
}

public getTag(offset: number): TypeTag {
return TaggedMemory.getTag(this._mem[offset]);
assert(Number.isInteger(offset));
assert(offset < TaggedMemory.MAX_MEMORY_SIZE);
return TaggedMemory.getTag(this._mem.get(offset));
}

/**
Expand Down

0 comments on commit 64d5f2b

Please sign in to comment.