@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.
- package/dist/bin/index.js +138 -5
- package/dist/bin/src/fuzz.js +2 -2
- package/dist/bin/src/test-json.js +2 -6
- package/dist/bin/src/trace-parse.js +1 -0
- package/dist/bin/src/trace-replay.js +14 -9
- package/dist/bin/src/tracer.js +16 -13
- package/dist/bin/src/utils.js +2 -2
- package/dist/build/compiler-inline.js +1 -1
- package/dist/build/compiler.d.ts +17 -13
- package/dist/build/compiler.js +13 -21
- package/dist/build/compiler.wasm +0 -0
- package/dist/build/debug-inline.js +1 -1
- package/dist/build/debug-raw-inline.js +1 -1
- package/dist/build/debug-raw.d.ts +34 -32
- package/dist/build/debug-raw.js +62 -69
- package/dist/build/debug-raw.wasm +0 -0
- package/dist/build/debug.d.ts +34 -32
- package/dist/build/debug.js +63 -70
- package/dist/build/debug.wasm +0 -0
- package/dist/build/js/assembly/api-debugger.d.ts +4 -4
- package/dist/build/js/assembly/api-debugger.js +6 -6
- package/dist/build/js/assembly/api-internal.d.ts +1 -1
- package/dist/build/js/assembly/api-internal.js +2 -3
- package/dist/build/js/assembly/api-types.d.ts +4 -4
- package/dist/build/js/assembly/api-types.js +3 -4
- package/dist/build/js/assembly/api-utils.d.ts +14 -6
- package/dist/build/js/assembly/api-utils.js +28 -13
- package/dist/build/js/assembly/arguments.d.ts +4 -4
- package/dist/build/js/assembly/arguments.js +10 -10
- package/dist/build/js/assembly/gas.d.ts +8 -13
- package/dist/build/js/assembly/gas.js +17 -7
- package/dist/build/js/assembly/instructions/bit.js +22 -22
- package/dist/build/js/assembly/instructions/branch.js +56 -56
- package/dist/build/js/assembly/instructions/jump.js +10 -10
- package/dist/build/js/assembly/instructions/load.js +41 -41
- package/dist/build/js/assembly/instructions/logic.js +20 -20
- package/dist/build/js/assembly/instructions/math.js +105 -105
- package/dist/build/js/assembly/instructions/misc.js +10 -10
- package/dist/build/js/assembly/instructions/mov.js +16 -16
- package/dist/build/js/assembly/instructions/outcome.d.ts +7 -7
- package/dist/build/js/assembly/instructions/outcome.js +63 -38
- package/dist/build/js/assembly/instructions/rot.js +18 -18
- package/dist/build/js/assembly/instructions/set.js +18 -18
- package/dist/build/js/assembly/instructions/shift.js +59 -59
- package/dist/build/js/assembly/instructions/store.js +29 -29
- package/dist/build/js/assembly/instructions/utils.d.ts +6 -4
- package/dist/build/js/assembly/instructions/utils.js +32 -16
- package/dist/build/js/assembly/instructions.d.ts +2 -3
- package/dist/build/js/assembly/instructions.js +4 -4
- package/dist/build/js/assembly/interpreter.d.ts +0 -1
- package/dist/build/js/assembly/interpreter.js +30 -38
- package/dist/build/js/assembly/math.d.ts +6 -8
- package/dist/build/js/assembly/math.js +21 -13
- package/dist/build/js/assembly/memory-page.d.ts +2 -4
- package/dist/build/js/assembly/memory-page.js +13 -7
- package/dist/build/js/assembly/memory.d.ts +1 -0
- package/dist/build/js/assembly/memory.js +119 -23
- package/dist/build/js/assembly/portable.js +1 -0
- package/dist/build/js/assembly/program-build.js +4 -4
- package/dist/build/js/assembly/program.d.ts +15 -8
- package/dist/build/js/assembly/program.js +95 -39
- package/dist/build/js/assembly/spi.d.ts +1 -1
- package/dist/build/js/assembly/spi.js +2 -2
- package/dist/build/js/portable/bootstrap.js +1 -0
- package/dist/build/js/portable-bundle.js +829 -641
- package/dist/build/release-inline.js +1 -1
- package/dist/build/release-mini-inline.js +1 -1
- package/dist/build/release-mini.d.ts +34 -32
- package/dist/build/release-mini.js +63 -70
- package/dist/build/release-mini.wasm +0 -0
- package/dist/build/release-stub-inline.js +1 -1
- package/dist/build/release-stub.d.ts +34 -32
- package/dist/build/release-stub.js +63 -70
- package/dist/build/release-stub.wasm +0 -0
- package/dist/build/release.d.ts +34 -32
- package/dist/build/release.js +63 -70
- package/dist/build/release.wasm +0 -0
- package/dist/build/test-inline.js +1 -1
- package/dist/build/test.wasm +0 -0
- package/dist/test/test-gas-cost.js +2 -3
- package/dist/test/test-trace-format.js +166 -0
- package/dist/test/test-w3f-common.js +1 -2
- package/package.json +14 -10
- package/dist/build/js/assembly/gas-costs.d.ts +0 -6
- 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 =
|
|
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 =
|
|
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
|
+
]);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Args } from "./arguments";
|
|
2
2
|
import { gasCounter } from "./gas";
|
|
3
|
-
import { INSTRUCTIONS, MISSING_INSTRUCTION
|
|
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 {
|
|
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
|
|
76
|
-
const
|
|
77
|
-
const
|
|
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
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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,
|
|
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,
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
28
|
-
constructor(id: ArenaId, page: Uint8Array
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
|
159
|
-
|
|
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
|
|
276
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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]);
|
|
@@ -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 {
|
|
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 `
|
|
14
|
-
export declare function lowerBytes(data: Uint8Array):
|
|
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:
|
|
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:
|
|
68
|
+
readonly code: Code;
|
|
63
69
|
readonly mask: Mask;
|
|
64
70
|
readonly jumpTable: JumpTable;
|
|
65
71
|
readonly basicBlocks: BasicBlocks;
|
|
66
|
-
|
|
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:
|
|
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:
|
|
84
|
+
export declare function resolveArguments(argsRes: Args, kind: Arguments, code: Code, offset: u32, lim: u32, registers: Registers): ResolvedArguments | null;
|
|
78
85
|
export {};
|