@fluffylabs/anan-as 1.2.0-ef04361 → 1.3.0-1ebcf8f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/bin/index.js +138 -5
  2. package/dist/bin/src/fuzz.js +2 -2
  3. package/dist/bin/src/test-json.js +2 -6
  4. package/dist/bin/src/trace-parse.js +1 -0
  5. package/dist/bin/src/trace-replay.js +14 -9
  6. package/dist/bin/src/tracer.js +16 -13
  7. package/dist/bin/src/utils.js +2 -2
  8. package/dist/build/compiler-inline.js +1 -1
  9. package/dist/build/compiler.d.ts +17 -13
  10. package/dist/build/compiler.js +13 -21
  11. package/dist/build/compiler.wasm +0 -0
  12. package/dist/build/debug-inline.js +1 -1
  13. package/dist/build/debug-raw-inline.js +1 -1
  14. package/dist/build/debug-raw.d.ts +34 -32
  15. package/dist/build/debug-raw.js +62 -69
  16. package/dist/build/debug-raw.wasm +0 -0
  17. package/dist/build/debug.d.ts +34 -32
  18. package/dist/build/debug.js +63 -70
  19. package/dist/build/debug.wasm +0 -0
  20. package/dist/build/js/assembly/api-debugger.d.ts +4 -4
  21. package/dist/build/js/assembly/api-debugger.js +6 -6
  22. package/dist/build/js/assembly/api-internal.d.ts +1 -1
  23. package/dist/build/js/assembly/api-internal.js +2 -3
  24. package/dist/build/js/assembly/api-types.d.ts +4 -4
  25. package/dist/build/js/assembly/api-types.js +3 -4
  26. package/dist/build/js/assembly/api-utils.d.ts +14 -6
  27. package/dist/build/js/assembly/api-utils.js +28 -13
  28. package/dist/build/js/assembly/arguments.d.ts +4 -4
  29. package/dist/build/js/assembly/arguments.js +10 -10
  30. package/dist/build/js/assembly/gas.d.ts +8 -13
  31. package/dist/build/js/assembly/gas.js +17 -7
  32. package/dist/build/js/assembly/instructions/bit.js +22 -22
  33. package/dist/build/js/assembly/instructions/branch.js +56 -56
  34. package/dist/build/js/assembly/instructions/jump.js +10 -10
  35. package/dist/build/js/assembly/instructions/load.js +41 -41
  36. package/dist/build/js/assembly/instructions/logic.js +20 -20
  37. package/dist/build/js/assembly/instructions/math.js +105 -105
  38. package/dist/build/js/assembly/instructions/misc.js +10 -10
  39. package/dist/build/js/assembly/instructions/mov.js +16 -16
  40. package/dist/build/js/assembly/instructions/outcome.d.ts +7 -7
  41. package/dist/build/js/assembly/instructions/outcome.js +63 -38
  42. package/dist/build/js/assembly/instructions/rot.js +18 -18
  43. package/dist/build/js/assembly/instructions/set.js +18 -18
  44. package/dist/build/js/assembly/instructions/shift.js +59 -59
  45. package/dist/build/js/assembly/instructions/store.js +29 -29
  46. package/dist/build/js/assembly/instructions/utils.d.ts +6 -4
  47. package/dist/build/js/assembly/instructions/utils.js +32 -16
  48. package/dist/build/js/assembly/instructions.d.ts +2 -3
  49. package/dist/build/js/assembly/instructions.js +4 -4
  50. package/dist/build/js/assembly/interpreter.d.ts +0 -1
  51. package/dist/build/js/assembly/interpreter.js +30 -38
  52. package/dist/build/js/assembly/math.d.ts +6 -8
  53. package/dist/build/js/assembly/math.js +21 -13
  54. package/dist/build/js/assembly/memory-page.d.ts +2 -4
  55. package/dist/build/js/assembly/memory-page.js +13 -7
  56. package/dist/build/js/assembly/memory.d.ts +1 -0
  57. package/dist/build/js/assembly/memory.js +119 -23
  58. package/dist/build/js/assembly/portable.js +1 -0
  59. package/dist/build/js/assembly/program-build.js +4 -4
  60. package/dist/build/js/assembly/program.d.ts +15 -8
  61. package/dist/build/js/assembly/program.js +95 -39
  62. package/dist/build/js/assembly/spi.d.ts +1 -1
  63. package/dist/build/js/assembly/spi.js +2 -2
  64. package/dist/build/js/portable/bootstrap.js +1 -0
  65. package/dist/build/js/portable-bundle.js +829 -641
  66. package/dist/build/release-inline.js +1 -1
  67. package/dist/build/release-mini-inline.js +1 -1
  68. package/dist/build/release-mini.d.ts +34 -32
  69. package/dist/build/release-mini.js +63 -70
  70. package/dist/build/release-mini.wasm +0 -0
  71. package/dist/build/release-stub-inline.js +1 -1
  72. package/dist/build/release-stub.d.ts +34 -32
  73. package/dist/build/release-stub.js +63 -70
  74. package/dist/build/release-stub.wasm +0 -0
  75. package/dist/build/release.d.ts +34 -32
  76. package/dist/build/release.js +63 -70
  77. package/dist/build/release.wasm +0 -0
  78. package/dist/build/test-inline.js +1 -1
  79. package/dist/build/test.wasm +0 -0
  80. package/dist/test/test-gas-cost.js +2 -3
  81. package/dist/test/test-trace-format.js +166 -0
  82. package/dist/test/test-w3f-common.js +1 -2
  83. package/package.json +14 -10
  84. package/dist/build/js/assembly/gas-costs.d.ts +0 -6
  85. package/dist/build/js/assembly/gas-costs.js +0 -39
@@ -3,7 +3,7 @@ export class Instruction {
3
3
  constructor() {
4
4
  this.name = "";
5
5
  this.kind = Arguments.Zero;
6
- this.gas = i64(0);
6
+ this.gas = u32(0);
7
7
  this.isTerminating = false;
8
8
  }
9
9
  }
@@ -11,13 +11,13 @@ function instruction(name, kind, gas, isTerminating = false) {
11
11
  const i = new Instruction();
12
12
  i.name = name;
13
13
  i.kind = kind;
14
- i.gas = i64(gas);
14
+ i.gas = u32(gas);
15
15
  i.isTerminating = isTerminating;
16
16
  return i;
17
17
  }
18
18
  export const MISSING_INSTRUCTION = instruction("INVALID", Arguments.Zero, 1, false);
19
19
  export const SBRK = instruction("SBRK", Arguments.TwoReg, 1);
20
- export const INSTRUCTIONS = [
20
+ export const INSTRUCTIONS = StaticArray.fromArray([
21
21
  /* 000 */ instruction("TRAP", Arguments.Zero, 1, true),
22
22
  /* 001 */ instruction("FALLTHROUGH", Arguments.Zero, 1, true),
23
23
  MISSING_INSTRUCTION,
@@ -249,4 +249,4 @@ export const INSTRUCTIONS = [
249
249
  /* 228 */ instruction("MAX_U", Arguments.ThreeReg, 1),
250
250
  /* 229 */ instruction("MIN", Arguments.ThreeReg, 1),
251
251
  /* 230 */ instruction("MIN_U", Arguments.ThreeReg, 1),
252
- ];
252
+ ]);
@@ -19,7 +19,6 @@ export declare class Interpreter {
19
19
  status: Status;
20
20
  exitCode: u32;
21
21
  nextPc: u32;
22
- useSbrkGas: boolean;
23
22
  private djumpRes;
24
23
  private argsRes;
25
24
  private outcomeRes;
@@ -1,11 +1,10 @@
1
1
  import { Args } from "./arguments";
2
2
  import { gasCounter } from "./gas";
3
- import { INSTRUCTIONS, MISSING_INSTRUCTION, SBRK } from "./instructions";
3
+ import { INSTRUCTIONS, MISSING_INSTRUCTION } from "./instructions";
4
4
  import { Outcome, OutcomeData, Result } from "./instructions/outcome";
5
- import { reg } from "./instructions/utils";
6
5
  import { RUN } from "./instructions-exe";
7
6
  import { MemoryBuilder } from "./memory";
8
- import { PAGE_SIZE, PAGE_SIZE_SHIFT, RESERVED_MEMORY } from "./memory-page";
7
+ import { RESERVED_MEMORY } from "./memory-page";
9
8
  import { portable } from "./portable";
10
9
  import { decodeArguments } from "./program";
11
10
  export var Status;
@@ -51,7 +50,6 @@ export class Interpreter {
51
50
  this.status = Status.OK;
52
51
  this.exitCode = 0;
53
52
  this.nextPc = 0;
54
- this.useSbrkGas = false;
55
53
  }
56
54
  nextSteps(nSteps = 1) {
57
55
  // resuming after host call
@@ -72,9 +70,11 @@ export class Interpreter {
72
70
  this.nextPc = -1;
73
71
  return true;
74
72
  }
75
- const program = this.program;
76
- const code = program.code;
77
- const mask = program.mask;
73
+ const code = this.program.code;
74
+ const mask = this.program.mask;
75
+ const gasCosts = this.program.gasCosts.codeAndGas;
76
+ const basicBlocks = this.program.basicBlocks;
77
+ const jumpTable = this.program.jumpTable;
78
78
  const argsRes = this.argsRes;
79
79
  const outcomeRes = this.outcomeRes;
80
80
  for (let i = 0; i < nSteps; i++) {
@@ -94,10 +94,12 @@ export class Interpreter {
94
94
  }
95
95
  return false;
96
96
  }
97
- const instruction = code[pc];
98
- const iData = instruction < INSTRUCTIONS.length ? INSTRUCTIONS[instruction] : MISSING_INSTRUCTION;
99
- // check gas (might be done for each block instead).
100
- if (this.gas.sub(iData.gas)) {
97
+ // check gas via pre-computed cost table (per-instruction or per-block)
98
+ const codeAndGas = portable.staticArrayAt(gasCosts, pc);
99
+ const instruction = codeAndGas & 0xff;
100
+ const gasCost = codeAndGas >> 8;
101
+ const iData = instruction < INSTRUCTIONS.length ? unchecked(INSTRUCTIONS[instruction]) : MISSING_INSTRUCTION;
102
+ if (gasCost > 0 && this.gas.sub(gasCost)) {
101
103
  this.status = Status.OOG;
102
104
  return false;
103
105
  }
@@ -108,21 +110,25 @@ export class Interpreter {
108
110
  // get args and invoke instruction
109
111
  const skipBytes = mask.skipBytesToNextInstruction(pc);
110
112
  const args = decodeArguments(argsRes, iData.kind, code, pc + 1, skipBytes);
111
- // additional gas cost of sbrk
112
- if (iData === SBRK && this.useSbrkGas) {
113
- const alloc = u64(u32(this.registers[reg(u64(args.a))]));
114
- const gas = portable.u64_mul(portable.u64_sub(portable.u64_add(alloc, u64(PAGE_SIZE)), u64(1)) >> u64(PAGE_SIZE_SHIFT), u64(16));
115
- if (this.gas.sub(gas)) {
116
- this.status = Status.OOG;
117
- return false;
118
- }
119
- }
120
- const exe = RUN[instruction];
113
+ const exe = unchecked(RUN[instruction]);
121
114
  const outcome = exe(outcomeRes, args, this.registers, this.memory);
122
- // TODO [ToDr] Spaghetti
115
+ // Fast path: Ok is the most common outcome (~70%+ of instructions)
116
+ if (outcome.outcome === Outcome.Ok) {
117
+ this.pc += 1 + skipBytes;
118
+ continue;
119
+ }
123
120
  switch (outcome.outcome) {
121
+ case Outcome.StaticJump: {
122
+ const branchResult = branch(this.branchRes, basicBlocks, pc, outcome.staticJump);
123
+ if (!branchResult.isOkay) {
124
+ this.status = Status.PANIC;
125
+ return false;
126
+ }
127
+ this.pc = branchResult.newPc;
128
+ continue;
129
+ }
124
130
  case Outcome.DynamicJump: {
125
- const res = dJump(this.djumpRes, program.jumpTable, outcome.dJump);
131
+ const res = dJump(this.djumpRes, jumpTable, outcome.dJump);
126
132
  if (res.status === DjumpStatus.HALT) {
127
133
  this.status = Status.HALT;
128
134
  return false;
@@ -131,16 +137,7 @@ export class Interpreter {
131
137
  this.status = Status.PANIC;
132
138
  return false;
133
139
  }
134
- const branchResult = branch(this.branchRes, program.basicBlocks, res.newPc, 0);
135
- if (!branchResult.isOkay) {
136
- this.status = Status.PANIC;
137
- return false;
138
- }
139
- this.pc = branchResult.newPc;
140
- continue;
141
- }
142
- case Outcome.StaticJump: {
143
- const branchResult = branch(this.branchRes, program.basicBlocks, pc, outcome.staticJump);
140
+ const branchResult = branch(this.branchRes, basicBlocks, res.newPc, 0);
144
141
  if (!branchResult.isOkay) {
145
142
  this.status = Status.PANIC;
146
143
  return false;
@@ -179,11 +176,6 @@ export class Interpreter {
179
176
  }
180
177
  throw new Error("Unknown result");
181
178
  }
182
- case Outcome.Ok: {
183
- // by default move to next instruction.
184
- this.pc += 1 + skipBytes;
185
- continue;
186
- }
187
179
  }
188
180
  }
189
181
  return true;
@@ -1,8 +1,6 @@
1
- /**
2
- * Integer minimum of two i32 values.
3
- */
4
- export declare function minI32(a: i32, b: i32): i32;
5
- /**
6
- * Unsigned integer minimum of two u32 values.
7
- */
8
- export declare function minU32(a: u32, b: u32): u32;
1
+ export declare class IntMath {
2
+ /** Integer minimum of two i32 values. */
3
+ static minI32(a: i32, b: i32): i32;
4
+ /** Unsigned integer minimum of two u32 values. */
5
+ static minU32(a: u32, b: u32): u32;
6
+ }
@@ -1,14 +1,22 @@
1
- /**
2
- * Integer minimum of two i32 values.
3
- */
4
- // @inline
5
- export function minI32(a, b) {
6
- return a < b ? a : b;
7
- }
8
- /**
9
- * Unsigned integer minimum of two u32 values.
10
- */
11
- // @inline
12
- export function minU32(a, b) {
13
- return a < b ? a : b;
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ export class IntMath {
8
+ /** Integer minimum of two i32 values. */
9
+ static minI32(a, b) {
10
+ return a < b ? a : b;
11
+ }
12
+ /** Unsigned integer minimum of two u32 values. */
13
+ static minU32(a, b) {
14
+ return a < b ? a : b;
15
+ }
14
16
  }
17
+ __decorate([
18
+ inline
19
+ ], IntMath, "minI32", null);
20
+ __decorate([
21
+ inline
22
+ ], IntMath, "minU32", null);
@@ -9,8 +9,6 @@ export declare const SEGMENT_SIZE_SHIFT: u32;
9
9
  /** https://graypaper.fluffylabs.dev/#/ab2cdbd/254401254a01?v=0.7.2 */
10
10
  export declare const RESERVED_MEMORY: u32;
11
11
  export declare const RESERVED_PAGES: u32;
12
- /** Amount of memory to allocate eagerly */
13
- export declare const ALLOCATE_EAGERLY: u32;
14
12
  export declare enum Access {
15
13
  None = 0,
16
14
  Read = 1,
@@ -24,8 +22,8 @@ export declare class Page {
24
22
  }
25
23
  export declare class RawPage {
26
24
  readonly id: ArenaId;
27
- page: Uint8Array | null;
28
- constructor(id: ArenaId, page: Uint8Array | null);
25
+ page: Uint8Array;
26
+ constructor(id: ArenaId, page: Uint8Array);
29
27
  get data(): Uint8Array;
30
28
  }
31
29
  export declare class Arena {
@@ -1,3 +1,9 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
1
7
  import { portable } from "./portable";
2
8
  /** `Z_P`: https://graypaper.fluffylabs.dev/#/ab2cdbd/44d20044d200?v=0.7.2 */
3
9
  export const PAGE_SIZE = 2 ** 12; // 4_096
@@ -8,8 +14,6 @@ export const SEGMENT_SIZE_SHIFT = 16;
8
14
  /** https://graypaper.fluffylabs.dev/#/ab2cdbd/254401254a01?v=0.7.2 */
9
15
  export const RESERVED_MEMORY = 2 ** 16;
10
16
  export const RESERVED_PAGES = RESERVED_MEMORY / PAGE_SIZE; // 16
11
- /** Amount of memory to allocate eagerly */
12
- export const ALLOCATE_EAGERLY = 2 ** 21; // 2MB
13
17
  export var Access;
14
18
  (function (Access) {
15
19
  Access[Access["None"] = 0] = "None";
@@ -25,18 +29,21 @@ export class Page {
25
29
  return this.access === Access.Write || this.access === access;
26
30
  }
27
31
  }
32
+ __decorate([
33
+ inline
34
+ ], Page.prototype, "can", null);
28
35
  export class RawPage {
29
36
  constructor(id, page) {
30
37
  this.id = id;
31
38
  this.page = page;
32
39
  }
33
40
  get data() {
34
- if (this.page === null) {
35
- this.page = new Uint8Array(PAGE_SIZE).fill(0);
36
- }
37
41
  return this.page;
38
42
  }
39
43
  }
44
+ __decorate([
45
+ inline
46
+ ], RawPage.prototype, "data", null);
40
47
  export class Arena {
41
48
  constructor(pageCount) {
42
49
  this.arenaBytes = PAGE_SIZE * pageCount;
@@ -57,8 +64,7 @@ export class Arena {
57
64
  if (allocatedMemory === this.arenaBytes) {
58
65
  console.log("Warning: Run out of pages! Allocating.");
59
66
  }
60
- // actually allocate for some time, but later do it lazily
61
- const data = allocatedMemory < ALLOCATE_EAGERLY ? new Uint8Array(PAGE_SIZE) : null;
67
+ const data = new Uint8Array(PAGE_SIZE);
62
68
  this.extraPageIndex += 1;
63
69
  return new RawPage(this.extraPageIndex, data);
64
70
  }
@@ -27,6 +27,7 @@ export declare class Memory {
27
27
  private pageResult;
28
28
  private chunksResult;
29
29
  private maxHeapPointer;
30
+ private cache;
30
31
  constructor(arena: Arena, pages?: Map<PageIndex, Page>, sbrkAddress?: u32, maxHeapPointer?: u32);
31
32
  pageDump(index: PageIndex): Uint8Array | null;
32
33
  /**
@@ -1,5 +1,11 @@
1
- import { u8SignExtend, u16SignExtend, u32SignExtend } from "./instructions/utils";
2
- import { minU32 } from "./math";
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Inst } from "./instructions/utils";
8
+ import { IntMath } from "./math";
3
9
  import { Access, Arena, PAGE_SIZE, PAGE_SIZE_SHIFT, Page, RawPage, RESERVED_MEMORY, RESERVED_PAGES, } from "./memory-page";
4
10
  import { portable } from "./portable";
5
11
  // @unmanaged
@@ -14,7 +20,7 @@ export class MaybePageFault {
14
20
  }
15
21
  }
16
22
  const EMPTY_UINT8ARRAY = new Uint8Array(0);
17
- const EMPTY_PAGE = new Page(Access.None, new RawPage(-1, null));
23
+ const EMPTY_PAGE = new Page(Access.None, new RawPage(-1, EMPTY_UINT8ARRAY));
18
24
  class Chunks {
19
25
  constructor() {
20
26
  this.firstPageData = EMPTY_UINT8ARRAY;
@@ -31,6 +37,50 @@ class PageResult {
31
37
  }
32
38
  const MEMORY_SIZE = 4294967296;
33
39
  const MAX_MEMORY_ADDRESS = 4294967295;
40
+ // Direct-mapped page cache for fast lookups.
41
+ // Cache size must be a power of 2. 256 entries covers most working sets.
42
+ const PAGE_CACHE_SHIFT = 8;
43
+ const PAGE_CACHE_SIZE = 1 << PAGE_CACHE_SHIFT; // 256
44
+ const PAGE_CACHE_MASK = PAGE_CACHE_SIZE - 1;
45
+ class PageCache {
46
+ constructor() {
47
+ // Parallel arrays for cache: tags store the page index, entries store the page.
48
+ // A tag of 0xFFFFFFFF means empty (no valid page).
49
+ this.tags = new StaticArray(PAGE_CACHE_SIZE);
50
+ this.entries = new StaticArray(PAGE_CACHE_SIZE);
51
+ const empty = EMPTY_PAGE;
52
+ for (let i = 0; i < PAGE_CACHE_SIZE; i++) {
53
+ this.tags[i] = 0xffffffff;
54
+ this.entries[i] = empty;
55
+ }
56
+ }
57
+ lookup(pageIdx) {
58
+ const slot = pageIdx & PAGE_CACHE_MASK;
59
+ if (unchecked(this.tags[slot]) === pageIdx) {
60
+ return unchecked(this.entries[slot]);
61
+ }
62
+ return null;
63
+ }
64
+ insert(pageIdx, page) {
65
+ const slot = pageIdx & PAGE_CACHE_MASK;
66
+ // biome-ignore lint/suspicious/noAssignInExpressions: intentional AS pattern
67
+ unchecked((this.tags[slot] = pageIdx));
68
+ // biome-ignore lint/suspicious/noAssignInExpressions: intentional AS pattern
69
+ unchecked((this.entries[slot] = page));
70
+ }
71
+ clear() {
72
+ for (let i = 0; i < PAGE_CACHE_SIZE; i++) {
73
+ this.tags[i] = 0xffffffff;
74
+ this.entries[i] = EMPTY_PAGE;
75
+ }
76
+ }
77
+ }
78
+ __decorate([
79
+ inline
80
+ ], PageCache.prototype, "lookup", null);
81
+ __decorate([
82
+ inline
83
+ ], PageCache.prototype, "insert", null);
34
84
  export class MemoryBuilder {
35
85
  constructor(preAllocatePages = 0) {
36
86
  this.pages = new Map();
@@ -83,18 +133,34 @@ export class Memory {
83
133
  this.sbrkAddress = sbrkAddress;
84
134
  this.pageResult = new PageResult();
85
135
  this.chunksResult = new Chunks();
136
+ this.cache = new PageCache();
86
137
  const sbrkPage = u32(sbrkAddress >> PAGE_SIZE_SHIFT);
87
138
  if (sbrkPage < RESERVED_PAGES) {
88
139
  throw new Error("sbrk within reserved memory is not allowed!");
89
140
  }
90
141
  this.lastAllocatedPage = pages.has(sbrkPage) ? sbrkPage : sbrkPage - 1;
91
142
  this.maxHeapPointer = u64(maxHeapPointer);
143
+ // Pre-populate cache with all existing pages
144
+ // @ts-ignore: AS Map iterator has array-like behavior
145
+ const keys = pages.keys();
146
+ // @ts-ignore: AS Map iterator has array-like behavior
147
+ for (let i = 0; i < keys.length; i++) {
148
+ // @ts-ignore: AS Map iterator has array-like behavior
149
+ const key = keys[i];
150
+ this.cache.insert(key, pages.get(key));
151
+ }
92
152
  }
93
153
  pageDump(index) {
154
+ const cached = this.cache.lookup(index);
155
+ if (cached !== null) {
156
+ return cached.raw.data;
157
+ }
94
158
  if (!this.pages.has(index)) {
95
159
  return null;
96
160
  }
97
- return this.pages.get(index).raw.data;
161
+ const page = this.pages.get(index);
162
+ this.cache.insert(index, page);
163
+ return page.raw.data;
98
164
  }
99
165
  /**
100
166
  * Returns the WASM linear memory pointer (byte offset) for the backing buffer of the page at `pageIndex`.
@@ -119,10 +185,14 @@ export class Memory {
119
185
  * ```
120
186
  */
121
187
  getPagePointer(pageIndex) {
122
- if (!this.pages.has(pageIndex)) {
123
- return 0;
188
+ let page = this.cache.lookup(pageIndex);
189
+ if (page === null) {
190
+ if (!this.pages.has(pageIndex)) {
191
+ return 0;
192
+ }
193
+ page = this.pages.get(pageIndex);
194
+ this.cache.insert(pageIndex, page);
124
195
  }
125
- const page = this.pages.get(pageIndex);
126
196
  if (!page.can(Access.Read)) {
127
197
  return 0;
128
198
  }
@@ -137,6 +207,7 @@ export class Memory {
137
207
  this.arena.release(pages[i].raw);
138
208
  }
139
209
  this.pages.clear();
210
+ this.cache.clear();
140
211
  }
141
212
  sbrk(faultRes, amount) {
142
213
  const freeMemoryStart = u64(this.sbrkAddress);
@@ -152,13 +223,17 @@ export class Memory {
152
223
  this.sbrkAddress = u32(newSbrk);
153
224
  const pageIdx = i32(portable.u64_sub(newSbrk, u64(1)) >> u64(PAGE_SIZE_SHIFT));
154
225
  if (pageIdx === this.lastAllocatedPage) {
226
+ faultRes.isFault = false;
155
227
  return freeMemoryStart;
156
228
  }
157
229
  for (let i = this.lastAllocatedPage + 1; i <= pageIdx; i++) {
158
- const page = this.arena.acquire();
159
- this.pages.set(i, new Page(Access.Write, page));
230
+ const rawPage = this.arena.acquire();
231
+ const page = new Page(Access.Write, rawPage);
232
+ this.pages.set(i, page);
233
+ this.cache.insert(i, page);
160
234
  }
161
235
  this.lastAllocatedPage = pageIdx;
236
+ faultRes.isFault = false;
162
237
  return freeMemoryStart;
163
238
  }
164
239
  getU8(faultRes, address) {
@@ -174,13 +249,13 @@ export class Memory {
174
249
  return portable.bswap_u64(this.getBytesReversed(faultRes, Access.Read, address, 8));
175
250
  }
176
251
  getI8(faultRes, address) {
177
- return u8SignExtend(u8(this.getU8(faultRes, address)));
252
+ return Inst.u8SignExtend(u8(this.getU8(faultRes, address)));
178
253
  }
179
254
  getI16(faultRes, address) {
180
- return u16SignExtend(u16(this.getU16(faultRes, address)));
255
+ return Inst.u16SignExtend(u16(this.getU16(faultRes, address)));
181
256
  }
182
257
  getI32(faultRes, address) {
183
- return u32SignExtend(u32(this.getU32(faultRes, address)));
258
+ return Inst.u32SignExtend(u32(this.getU32(faultRes, address)));
184
259
  }
185
260
  setU8(faultRes, address, value) {
186
261
  this.setBytes(faultRes, address, value, 1);
@@ -272,17 +347,34 @@ export class Memory {
272
347
  }
273
348
  getPage(faultRes, pageData, access, address) {
274
349
  const pageIdx = u32(address >> PAGE_SIZE_SHIFT);
275
- const relAddress = address % PAGE_SIZE;
276
- const pageStart = pageIdx << PAGE_SIZE_SHIFT;
350
+ const relAddress = address & (PAGE_SIZE - 1);
351
+ // Fast path: check cache first
352
+ const cached = this.cache.lookup(pageIdx);
353
+ if (cached !== null) {
354
+ if (!cached.can(access)) {
355
+ fault(faultRes, pageIdx << PAGE_SIZE_SHIFT);
356
+ faultRes.isAccess = true;
357
+ pageData.page = EMPTY_PAGE;
358
+ pageData.relativeAddress = relAddress;
359
+ return;
360
+ }
361
+ faultRes.isFault = false;
362
+ pageData.page = cached;
363
+ pageData.relativeAddress = relAddress;
364
+ return;
365
+ }
366
+ // Slow path: check Map
277
367
  if (!this.pages.has(pageIdx)) {
278
- fault(faultRes, pageStart);
368
+ fault(faultRes, pageIdx << PAGE_SIZE_SHIFT);
279
369
  pageData.page = EMPTY_PAGE;
280
370
  pageData.relativeAddress = relAddress;
281
371
  return;
282
372
  }
283
373
  const page = this.pages.get(pageIdx);
374
+ // Insert into cache for next time
375
+ this.cache.insert(pageIdx, page);
284
376
  if (!page.can(access)) {
285
- fault(faultRes, pageStart);
377
+ fault(faultRes, pageIdx << PAGE_SIZE_SHIFT);
286
378
  faultRes.isAccess = true;
287
379
  pageData.page = EMPTY_PAGE;
288
380
  pageData.relativeAddress = relAddress;
@@ -323,12 +415,16 @@ export class Memory {
323
415
  }
324
416
  const secondPageIdx = u32((address + u32(bytes)) % MEMORY_SIZE) >> PAGE_SIZE_SHIFT;
325
417
  const secondPageStart = secondPageIdx << PAGE_SIZE_SHIFT;
326
- if (!this.pages.has(secondPageIdx)) {
327
- fault(faultRes, secondPageStart);
328
- return;
418
+ // Try cache first for second page
419
+ let secondPage = this.cache.lookup(secondPageIdx);
420
+ if (secondPage === null) {
421
+ if (!this.pages.has(secondPageIdx)) {
422
+ fault(faultRes, secondPageStart);
423
+ return;
424
+ }
425
+ secondPage = this.pages.get(secondPageIdx);
426
+ this.cache.insert(secondPageIdx, secondPage);
329
427
  }
330
- // fetch the second page and check access
331
- const secondPage = this.pages.get(secondPageIdx);
332
428
  if (!secondPage.can(access)) {
333
429
  fault(faultRes, secondPageStart);
334
430
  faultRes.isAccess = true;
@@ -349,7 +445,7 @@ export class Memory {
349
445
  }
350
446
  let bytesLeft = u64(value);
351
447
  // write to first page
352
- const firstPageEnd = minU32(PAGE_SIZE, r.firstPageOffset + bytes);
448
+ const firstPageEnd = IntMath.minU32(PAGE_SIZE, r.firstPageOffset + bytes);
353
449
  for (let i = r.firstPageOffset; i < firstPageEnd; i++) {
354
450
  r.firstPageData[i] = u8(bytesLeft);
355
451
  bytesLeft >>= u64(8);
@@ -367,7 +463,7 @@ export class Memory {
367
463
  }
368
464
  // result (bytes in reverse order)
369
465
  let r = u64(0);
370
- const firstPageEnd = minU32(PAGE_SIZE, this.chunksResult.firstPageOffset + bytes);
466
+ const firstPageEnd = IntMath.minU32(PAGE_SIZE, this.chunksResult.firstPageOffset + bytes);
371
467
  // read from first page
372
468
  for (let i = this.chunksResult.firstPageOffset; i < firstPageEnd; i++) {
373
469
  r = (r << u64(8)) | u64(this.chunksResult.firstPageData[i]);
@@ -24,6 +24,7 @@ export class portable {
24
24
  arr.fill(0);
25
25
  return arr;
26
26
  };
27
+ g.StaticArray.fromArray = (x) => x;
27
28
  g.ASC_TARGET = 0;
28
29
  g.i8 = (value) => {
29
30
  const n = typeof value === "bigint" ? Number(value) : value;
@@ -1,7 +1,7 @@
1
1
  import { Arguments, higNibble, lowNibble, REQUIRED_BYTES } from "./arguments";
2
2
  import { encodeVarU32 } from "./codec";
3
3
  import { INSTRUCTIONS, MISSING_INSTRUCTION } from "./instructions";
4
- import { minI32 } from "./math";
4
+ import { IntMath } from "./math";
5
5
  /** Turn given bytecode into a valid program. Add JumpTable and Mask. */
6
6
  export function wrapAsProgram(bytecode) {
7
7
  const jumpTableLength = 0;
@@ -57,9 +57,9 @@ function skipBytes(kind, data) {
57
57
  case Arguments.TwoReg:
58
58
  return 1;
59
59
  case Arguments.TwoRegOneImm:
60
- return 1 + minI32(4, data.length);
60
+ return 1 + IntMath.minI32(4, data.length);
61
61
  case Arguments.TwoRegOneOff:
62
- return 1 + minI32(4, data.length);
62
+ return 1 + IntMath.minI32(4, data.length);
63
63
  case Arguments.TwoRegTwoImm: {
64
64
  const low = lowNibble(data[1]);
65
65
  const split = low + 1;
@@ -100,5 +100,5 @@ function immBytes(dataLength, required) {
100
100
  if (dataLength < required) {
101
101
  return 0;
102
102
  }
103
- return minI32(4, dataLength - required);
103
+ return IntMath.minI32(4, dataLength - required);
104
104
  }
@@ -1,6 +1,7 @@
1
1
  import { Args, Arguments } from "./arguments";
2
2
  import { Registers } from "./registers";
3
3
  export type ProgramCounter = u32;
4
+ export type Code = StaticArray<u8>;
4
5
  export declare class CodeAndMetadata {
5
6
  readonly code: Uint8Array;
6
7
  readonly metadata: Uint8Array;
@@ -10,10 +11,10 @@ export declare class CodeAndMetadata {
10
11
  export declare function extractCodeAndMetadata(data: Uint8Array): CodeAndMetadata;
11
12
  /** Convert `u8` to `Uint8Array` */
12
13
  export declare function liftBytes(data: u8[]): Uint8Array;
13
- /** Convert `Uint8Array` to `u8` */
14
- export declare function lowerBytes(data: Uint8Array): u8[];
14
+ /** Convert `Uint8Array` to `Code` (StaticArray<u8>) */
15
+ export declare function lowerBytes(data: Uint8Array): Code;
15
16
  /** https://graypaper.fluffylabs.dev/#/cc517d7/234f01234f01?v=0.6.5 */
16
- export declare function deblob(program: Uint8Array): Program;
17
+ export declare function deblob(program: Uint8Array, useBlockGas: boolean): Program;
17
18
  /**
18
19
  * https://graypaper.fluffylabs.dev/#/cc517d7/236e01236e01?v=0.6.5
19
20
  */
@@ -39,6 +40,11 @@ export declare class Mask {
39
40
  skipBytesToNextInstruction(i: u32): u32;
40
41
  toString(): string;
41
42
  }
43
+ export declare class GasCosts {
44
+ readonly codeAndGas: StaticArray<u32>;
45
+ constructor(code: Code, mask: Mask, blocks: BasicBlocks, useBlockGasCost: boolean);
46
+ toString(): string;
47
+ }
42
48
  export declare enum BasicBlock {
43
49
  NONE = 0,
44
50
  START = 2,
@@ -49,7 +55,7 @@ export declare enum BasicBlock {
49
55
  */
50
56
  export declare class BasicBlocks {
51
57
  readonly isStartOrEnd: StaticArray<BasicBlock>;
52
- constructor(code: u8[], mask: Mask);
58
+ constructor(code: Code, mask: Mask);
53
59
  isStart(newPc: u32): boolean;
54
60
  toString(): string;
55
61
  }
@@ -59,14 +65,15 @@ export declare class JumpTable {
59
65
  toString(): string;
60
66
  }
61
67
  export declare class Program {
62
- readonly code: u8[];
68
+ readonly code: Code;
63
69
  readonly mask: Mask;
64
70
  readonly jumpTable: JumpTable;
65
71
  readonly basicBlocks: BasicBlocks;
66
- constructor(code: u8[], mask: Mask, jumpTable: JumpTable, basicBlocks: BasicBlocks);
72
+ readonly gasCosts: GasCosts;
73
+ constructor(code: Code, mask: Mask, jumpTable: JumpTable, basicBlocks: BasicBlocks, gasCosts: GasCosts);
67
74
  toString(): string;
68
75
  }
69
- export declare function decodeArguments(args: Args, kind: Arguments, code: u8[], offset: i32, lim: u32): Args;
76
+ export declare function decodeArguments(args: Args, kind: Arguments, code: Code, offset: i32, lim: u32): Args;
70
77
  declare class ResolvedArguments {
71
78
  a: i64;
72
79
  b: i64;
@@ -74,5 +81,5 @@ declare class ResolvedArguments {
74
81
  d: i64;
75
82
  decoded: Args;
76
83
  }
77
- export declare function resolveArguments(argsRes: Args, kind: Arguments, code: u8[], offset: u32, lim: u32, registers: Registers): ResolvedArguments | null;
84
+ export declare function resolveArguments(argsRes: Args, kind: Arguments, code: Code, offset: u32, lim: u32, registers: Registers): ResolvedArguments | null;
78
85
  export {};