@fluffylabs/anan-as 1.2.0 → 1.3.0-5cbd3aa
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 +50 -112
- package/dist/build/debug-raw.js +78 -139
- package/dist/build/debug-raw.wasm +0 -0
- package/dist/build/debug.d.ts +50 -112
- package/dist/build/debug.js +81 -147
- package/dist/build/debug.wasm +0 -0
- package/dist/build/js/assembly/api-debugger.d.ts +55 -0
- package/dist/build/js/assembly/api-debugger.js +245 -0
- package/dist/build/js/assembly/api-internal.d.ts +13 -0
- package/dist/build/js/assembly/api-internal.js +191 -0
- package/dist/build/js/assembly/api-types.d.ts +45 -0
- package/dist/build/js/assembly/api-types.js +52 -0
- package/dist/build/js/assembly/api-utils.d.ts +79 -0
- package/dist/build/js/assembly/api-utils.js +221 -0
- package/dist/build/js/assembly/arguments.d.ts +44 -0
- package/dist/build/js/assembly/arguments.js +164 -0
- package/dist/build/js/assembly/codec.d.ts +24 -0
- package/dist/build/js/assembly/codec.js +139 -0
- package/dist/build/js/assembly/gas.d.ts +11 -0
- package/dist/build/js/assembly/gas.js +33 -0
- package/dist/build/js/assembly/index-shared.d.ts +4 -0
- package/dist/build/js/assembly/index-shared.js +4 -0
- package/dist/build/js/assembly/instructions/bit.d.ts +11 -0
- package/dist/build/js/assembly/instructions/bit.js +53 -0
- package/dist/build/js/assembly/instructions/branch.d.ts +17 -0
- package/dist/build/js/assembly/instructions/branch.js +120 -0
- package/dist/build/js/assembly/instructions/jump.d.ts +5 -0
- package/dist/build/js/assembly/instructions/jump.js +21 -0
- package/dist/build/js/assembly/instructions/load.d.ts +17 -0
- package/dist/build/js/assembly/instructions/load.js +134 -0
- package/dist/build/js/assembly/instructions/logic.d.ts +10 -0
- package/dist/build/js/assembly/instructions/logic.js +47 -0
- package/dist/build/js/assembly/instructions/math.d.ts +28 -0
- package/dist/build/js/assembly/instructions/math.js +225 -0
- package/dist/build/js/assembly/instructions/misc.d.ts +6 -0
- package/dist/build/js/assembly/instructions/misc.js +22 -0
- package/dist/build/js/assembly/instructions/mov.d.ts +6 -0
- package/dist/build/js/assembly/instructions/mov.js +35 -0
- package/dist/build/js/assembly/instructions/outcome.d.ts +30 -0
- package/dist/build/js/assembly/instructions/outcome.js +88 -0
- package/dist/build/js/assembly/instructions/rot.d.ts +15 -0
- package/dist/build/js/assembly/instructions/rot.js +66 -0
- package/dist/build/js/assembly/instructions/set.d.ts +7 -0
- package/dist/build/js/assembly/instructions/set.js +36 -0
- package/dist/build/js/assembly/instructions/shift.d.ts +19 -0
- package/dist/build/js/assembly/instructions/shift.js +121 -0
- package/dist/build/js/assembly/instructions/store.d.ts +17 -0
- package/dist/build/js/assembly/instructions/store.js +101 -0
- package/dist/build/js/assembly/instructions/utils.d.ts +25 -0
- package/dist/build/js/assembly/instructions/utils.js +91 -0
- package/dist/build/js/assembly/instructions-exe.d.ts +2 -0
- package/dist/build/js/assembly/instructions-exe.js +245 -0
- package/dist/build/js/assembly/instructions.d.ts +10 -0
- package/dist/build/js/assembly/instructions.js +252 -0
- package/dist/build/js/assembly/interpreter.d.ts +28 -0
- package/dist/build/js/assembly/interpreter.js +221 -0
- package/dist/build/js/assembly/math.d.ts +6 -0
- package/dist/build/js/assembly/math.js +22 -0
- package/dist/build/js/assembly/memory-page.d.ts +36 -0
- package/dist/build/js/assembly/memory-page.js +74 -0
- package/dist/build/js/assembly/memory.d.ts +83 -0
- package/dist/build/js/assembly/memory.js +482 -0
- package/dist/build/js/assembly/portable.d.ts +24 -0
- package/dist/build/js/assembly/portable.js +363 -0
- package/dist/build/js/assembly/program-build.d.ts +2 -0
- package/dist/build/js/assembly/program-build.js +104 -0
- package/dist/build/js/assembly/program.d.ts +85 -0
- package/dist/build/js/assembly/program.js +340 -0
- package/dist/build/js/assembly/registers.d.ts +6 -0
- package/dist/build/js/assembly/registers.js +9 -0
- package/dist/build/js/assembly/spi.d.ts +92 -0
- package/dist/build/js/assembly/spi.js +152 -0
- package/dist/build/js/portable/bootstrap.d.ts +1 -0
- package/dist/build/js/portable/bootstrap.js +6 -0
- package/dist/build/js/portable/index.d.ts +4 -0
- package/dist/build/js/portable/index.js +6 -0
- package/dist/build/js/portable-bundle.js +4497 -0
- package/dist/build/release-inline.js +1 -1
- package/dist/build/release-mini-inline.js +1 -1
- package/dist/build/release-mini.d.ts +50 -112
- package/dist/build/release-mini.js +81 -147
- package/dist/build/release-mini.wasm +0 -0
- package/dist/build/release-stub-inline.js +1 -1
- package/dist/build/release-stub.d.ts +50 -112
- package/dist/build/release-stub.js +81 -147
- package/dist/build/release-stub.wasm +0 -0
- package/dist/build/release.d.ts +50 -112
- package/dist/build/release.js +81 -147
- 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 +125 -0
- package/dist/test/test-w3f-portable.js +5 -0
- package/dist/test/test-w3f.js +3 -120
- package/package.json +22 -11
|
@@ -0,0 +1,482 @@
|
|
|
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";
|
|
9
|
+
import { Access, Arena, PAGE_SIZE, PAGE_SIZE_SHIFT, Page, RawPage, RESERVED_MEMORY, RESERVED_PAGES, } from "./memory-page";
|
|
10
|
+
import { portable } from "./portable";
|
|
11
|
+
// @unmanaged
|
|
12
|
+
export class MaybePageFault {
|
|
13
|
+
constructor() {
|
|
14
|
+
/** Accessing memory triggered a page fault. */
|
|
15
|
+
this.isFault = false;
|
|
16
|
+
/** The page fault was caused by invalid memory access (i.e. writing to read-only memory). */
|
|
17
|
+
this.isAccess = false;
|
|
18
|
+
/** Start memory index of a page that triggered the fault. */
|
|
19
|
+
this.fault = 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const EMPTY_UINT8ARRAY = new Uint8Array(0);
|
|
23
|
+
const EMPTY_PAGE = new Page(Access.None, new RawPage(-1, EMPTY_UINT8ARRAY));
|
|
24
|
+
class Chunks {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.firstPageData = EMPTY_UINT8ARRAY;
|
|
27
|
+
this.firstPageOffset = 0;
|
|
28
|
+
this.secondPageData = EMPTY_UINT8ARRAY;
|
|
29
|
+
this.secondPageEnd = 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class PageResult {
|
|
33
|
+
constructor() {
|
|
34
|
+
this.page = EMPTY_PAGE;
|
|
35
|
+
this.relativeAddress = 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const MEMORY_SIZE = 4294967296;
|
|
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);
|
|
84
|
+
export class MemoryBuilder {
|
|
85
|
+
constructor(preAllocatePages = 0) {
|
|
86
|
+
this.pages = new Map();
|
|
87
|
+
this.arena = new Arena(preAllocatePages);
|
|
88
|
+
}
|
|
89
|
+
/** Allocates memory pages with given `access`, for given `address` and initialize with `zeroes` */
|
|
90
|
+
setEmpty(access, address, len) {
|
|
91
|
+
const endAddress = address + len;
|
|
92
|
+
for (let currentAddress = address; currentAddress < endAddress; currentAddress += PAGE_SIZE) {
|
|
93
|
+
this.getOrCreatePageForAddress(access, currentAddress);
|
|
94
|
+
}
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
/** Allocates memory pages with given `access`, for given `address` and writes there `data` */
|
|
98
|
+
setData(access, address, data) {
|
|
99
|
+
let currentAddress = address;
|
|
100
|
+
let currentData = data;
|
|
101
|
+
while (currentData.length > 0) {
|
|
102
|
+
const page = this.getOrCreatePageForAddress(access, currentAddress);
|
|
103
|
+
const relAddress = currentAddress % PAGE_SIZE;
|
|
104
|
+
const spaceInPage = PAGE_SIZE - relAddress;
|
|
105
|
+
const end = u32(currentData.length) < spaceInPage ? currentData.length : spaceInPage;
|
|
106
|
+
page.raw.data.set(currentData.subarray(0, end), relAddress);
|
|
107
|
+
// move to the next address to write
|
|
108
|
+
currentAddress = currentAddress + end;
|
|
109
|
+
currentData = currentData.subarray(end);
|
|
110
|
+
}
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
/** Returns memory page for given address (creates if not exists) */
|
|
114
|
+
getOrCreatePageForAddress(access, address) {
|
|
115
|
+
const pageIdx = u32(address >> PAGE_SIZE_SHIFT);
|
|
116
|
+
if (pageIdx < RESERVED_PAGES) {
|
|
117
|
+
throw new Error(`Attempting to allocate reserved page: ${pageIdx}`);
|
|
118
|
+
}
|
|
119
|
+
if (!this.pages.has(pageIdx)) {
|
|
120
|
+
const page = this.arena.acquire();
|
|
121
|
+
this.pages.set(pageIdx, new Page(access, page));
|
|
122
|
+
}
|
|
123
|
+
return this.pages.get(pageIdx);
|
|
124
|
+
}
|
|
125
|
+
build(sbrkAddress = RESERVED_MEMORY, maxHeapPointer = MAX_MEMORY_ADDRESS) {
|
|
126
|
+
return new Memory(this.arena, this.pages, sbrkAddress, maxHeapPointer);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export class Memory {
|
|
130
|
+
constructor(arena, pages = new Map(), sbrkAddress = 0, maxHeapPointer = MAX_MEMORY_ADDRESS) {
|
|
131
|
+
this.arena = arena;
|
|
132
|
+
this.pages = pages;
|
|
133
|
+
this.sbrkAddress = sbrkAddress;
|
|
134
|
+
this.pageResult = new PageResult();
|
|
135
|
+
this.chunksResult = new Chunks();
|
|
136
|
+
this.cache = new PageCache();
|
|
137
|
+
const sbrkPage = u32(sbrkAddress >> PAGE_SIZE_SHIFT);
|
|
138
|
+
if (sbrkPage < RESERVED_PAGES) {
|
|
139
|
+
throw new Error("sbrk within reserved memory is not allowed!");
|
|
140
|
+
}
|
|
141
|
+
this.lastAllocatedPage = pages.has(sbrkPage) ? sbrkPage : sbrkPage - 1;
|
|
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
|
+
}
|
|
152
|
+
}
|
|
153
|
+
pageDump(index) {
|
|
154
|
+
const cached = this.cache.lookup(index);
|
|
155
|
+
if (cached !== null) {
|
|
156
|
+
return cached.raw.data;
|
|
157
|
+
}
|
|
158
|
+
if (!this.pages.has(index)) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const page = this.pages.get(index);
|
|
162
|
+
this.cache.insert(index, page);
|
|
163
|
+
return page.raw.data;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Returns the WASM linear memory pointer (byte offset) for the backing buffer of the page at `pageIndex`.
|
|
167
|
+
*
|
|
168
|
+
* Returns `0` if the page does not exist or is not readable (page/access fault).
|
|
169
|
+
*
|
|
170
|
+
* This enables efficient memory reading on the JS side without extra WASM allocations:
|
|
171
|
+
* ```ts
|
|
172
|
+
* let pagesRead = 0;
|
|
173
|
+
* for (let address = start; address < end; address += PAGE_SIZE) {
|
|
174
|
+
* const page = address >> PAGE_SIZE_SHIFT;
|
|
175
|
+
* const ptr = getPagePointer(page);
|
|
176
|
+
* if (ptr === 0) {
|
|
177
|
+
* throw new Error(`Page fault at ${page << PAGE_SIZE_SHIFT}`);
|
|
178
|
+
* }
|
|
179
|
+
* destination.set(
|
|
180
|
+
* new Uint8Array(wasm.instance.exports.memory.buffer, ptr, Math.min(end - address, PAGE_SIZE)),
|
|
181
|
+
* pagesRead << PAGE_SIZE_SHIFT,
|
|
182
|
+
* );
|
|
183
|
+
* pagesRead += 1;
|
|
184
|
+
* }
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
getPagePointer(pageIndex) {
|
|
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);
|
|
195
|
+
}
|
|
196
|
+
if (!page.can(Access.Read)) {
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
// Trigger lazy allocation if the backing buffer has not been created yet.
|
|
200
|
+
// @ts-ignore: dataStart is an AS-specific property on Uint8Array
|
|
201
|
+
return page.raw.data.dataStart;
|
|
202
|
+
}
|
|
203
|
+
free() {
|
|
204
|
+
// @ts-ignore: AS returns T[], JS returns iterator - asArray handles both
|
|
205
|
+
const pages = portable.asArray(this.pages.values());
|
|
206
|
+
for (let i = 0; i < pages.length; i++) {
|
|
207
|
+
this.arena.release(pages[i].raw);
|
|
208
|
+
}
|
|
209
|
+
this.pages.clear();
|
|
210
|
+
this.cache.clear();
|
|
211
|
+
}
|
|
212
|
+
sbrk(faultRes, amount) {
|
|
213
|
+
const freeMemoryStart = u64(this.sbrkAddress);
|
|
214
|
+
if (amount === 0) {
|
|
215
|
+
faultRes.isFault = false;
|
|
216
|
+
return freeMemoryStart;
|
|
217
|
+
}
|
|
218
|
+
const newSbrk = portable.u64_add(freeMemoryStart, u64(amount));
|
|
219
|
+
if (newSbrk > this.maxHeapPointer) {
|
|
220
|
+
faultRes.isFault = true;
|
|
221
|
+
return freeMemoryStart;
|
|
222
|
+
}
|
|
223
|
+
this.sbrkAddress = u32(newSbrk);
|
|
224
|
+
const pageIdx = i32(portable.u64_sub(newSbrk, u64(1)) >> u64(PAGE_SIZE_SHIFT));
|
|
225
|
+
if (pageIdx === this.lastAllocatedPage) {
|
|
226
|
+
faultRes.isFault = false;
|
|
227
|
+
return freeMemoryStart;
|
|
228
|
+
}
|
|
229
|
+
for (let i = this.lastAllocatedPage + 1; i <= pageIdx; i++) {
|
|
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);
|
|
234
|
+
}
|
|
235
|
+
this.lastAllocatedPage = pageIdx;
|
|
236
|
+
faultRes.isFault = false;
|
|
237
|
+
return freeMemoryStart;
|
|
238
|
+
}
|
|
239
|
+
getU8(faultRes, address) {
|
|
240
|
+
return u64(u8(this.getBytesReversed(faultRes, Access.Read, address, 1)));
|
|
241
|
+
}
|
|
242
|
+
getU16(faultRes, address) {
|
|
243
|
+
return u64(portable.bswap_u16(u16(this.getBytesReversed(faultRes, Access.Read, address, 2))));
|
|
244
|
+
}
|
|
245
|
+
getU32(faultRes, address) {
|
|
246
|
+
return u64(portable.bswap_u32(u32(this.getBytesReversed(faultRes, Access.Read, address, 4))));
|
|
247
|
+
}
|
|
248
|
+
getU64(faultRes, address) {
|
|
249
|
+
return portable.bswap_u64(this.getBytesReversed(faultRes, Access.Read, address, 8));
|
|
250
|
+
}
|
|
251
|
+
getI8(faultRes, address) {
|
|
252
|
+
return Inst.u8SignExtend(u8(this.getU8(faultRes, address)));
|
|
253
|
+
}
|
|
254
|
+
getI16(faultRes, address) {
|
|
255
|
+
return Inst.u16SignExtend(u16(this.getU16(faultRes, address)));
|
|
256
|
+
}
|
|
257
|
+
getI32(faultRes, address) {
|
|
258
|
+
return Inst.u32SignExtend(u32(this.getU32(faultRes, address)));
|
|
259
|
+
}
|
|
260
|
+
setU8(faultRes, address, value) {
|
|
261
|
+
this.setBytes(faultRes, address, value, 1);
|
|
262
|
+
}
|
|
263
|
+
setU16(faultRes, address, value) {
|
|
264
|
+
this.setBytes(faultRes, address, value, 2);
|
|
265
|
+
}
|
|
266
|
+
setU32(faultRes, address, value) {
|
|
267
|
+
this.setBytes(faultRes, address, value, 4);
|
|
268
|
+
}
|
|
269
|
+
setU64(faultRes, address, value) {
|
|
270
|
+
this.setBytes(faultRes, address, value, 8);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* DO NOT USE.
|
|
274
|
+
*
|
|
275
|
+
* @deprecated exposed temporarily for debugger/typeberry API.
|
|
276
|
+
*/
|
|
277
|
+
getMemory(fault, address, length) {
|
|
278
|
+
// first traverse memory and see if we don't page fault
|
|
279
|
+
if (length > 0) {
|
|
280
|
+
let nextAddress = address;
|
|
281
|
+
const pagesToCheck = i32(portable.u64_add(u64(length), u64(PAGE_SIZE - 1)) >> u64(PAGE_SIZE_SHIFT));
|
|
282
|
+
for (let page = 0; page < pagesToCheck; page++) {
|
|
283
|
+
const pageData = this.pageResult;
|
|
284
|
+
this.getPage(fault, pageData, Access.Read, nextAddress);
|
|
285
|
+
if (fault.isFault) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
nextAddress += PAGE_SIZE;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// only after, actually allocate and read the bytes.
|
|
292
|
+
const destination = new Uint8Array(length);
|
|
293
|
+
this.bytesRead(fault, address, destination, 0);
|
|
294
|
+
if (fault.isFault) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
return destination;
|
|
298
|
+
}
|
|
299
|
+
bytesRead(faultRes, address, destination, destinationOffset) {
|
|
300
|
+
let nextAddress = address;
|
|
301
|
+
let destinationIndex = i32(destinationOffset);
|
|
302
|
+
while (destinationIndex < destination.length) {
|
|
303
|
+
const bytesLeft = destination.length - destinationIndex;
|
|
304
|
+
const pageData = this.pageResult;
|
|
305
|
+
this.getPage(faultRes, pageData, Access.Read, nextAddress);
|
|
306
|
+
if (faultRes.isFault) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const relAddress = pageData.relativeAddress;
|
|
310
|
+
const bytesToRead = relAddress + bytesLeft < PAGE_SIZE ? bytesLeft : PAGE_SIZE - pageData.relativeAddress;
|
|
311
|
+
// actually copy the bytes
|
|
312
|
+
const pageEnd = relAddress + bytesToRead;
|
|
313
|
+
const data = pageData.page.raw.data;
|
|
314
|
+
for (let i = relAddress; i < pageEnd; i++) {
|
|
315
|
+
destination[destinationIndex] = data[i];
|
|
316
|
+
destinationIndex++;
|
|
317
|
+
}
|
|
318
|
+
// move the pointers
|
|
319
|
+
nextAddress += bytesToRead;
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
/** Write bytes from given `source` (with `sourceOffset`) at given `address`. */
|
|
324
|
+
bytesWrite(faultRes, address, source, sourceOffset) {
|
|
325
|
+
let nextAddress = address;
|
|
326
|
+
let sourceIndex = i32(sourceOffset);
|
|
327
|
+
while (sourceIndex < source.length) {
|
|
328
|
+
const bytesLeft = source.length - sourceIndex;
|
|
329
|
+
const pageData = this.pageResult;
|
|
330
|
+
this.getPage(faultRes, pageData, Access.Write, nextAddress);
|
|
331
|
+
if (faultRes.isFault) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const relAddress = pageData.relativeAddress;
|
|
335
|
+
const bytesToWrite = relAddress + bytesLeft < PAGE_SIZE ? bytesLeft : PAGE_SIZE - pageData.relativeAddress;
|
|
336
|
+
// actually copy the bytes
|
|
337
|
+
const pageEnd = relAddress + bytesToWrite;
|
|
338
|
+
const data = pageData.page.raw.data;
|
|
339
|
+
for (let i = relAddress; i < pageEnd; i++) {
|
|
340
|
+
data[i] = source[sourceIndex];
|
|
341
|
+
sourceIndex++;
|
|
342
|
+
}
|
|
343
|
+
// move the pointers
|
|
344
|
+
nextAddress += bytesToWrite;
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
getPage(faultRes, pageData, access, address) {
|
|
349
|
+
const pageIdx = u32(address >> 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
|
|
367
|
+
if (!this.pages.has(pageIdx)) {
|
|
368
|
+
fault(faultRes, pageIdx << PAGE_SIZE_SHIFT);
|
|
369
|
+
pageData.page = EMPTY_PAGE;
|
|
370
|
+
pageData.relativeAddress = relAddress;
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const page = this.pages.get(pageIdx);
|
|
374
|
+
// Insert into cache for next time
|
|
375
|
+
this.cache.insert(pageIdx, page);
|
|
376
|
+
if (!page.can(access)) {
|
|
377
|
+
fault(faultRes, pageIdx << PAGE_SIZE_SHIFT);
|
|
378
|
+
faultRes.isAccess = true;
|
|
379
|
+
pageData.page = EMPTY_PAGE;
|
|
380
|
+
pageData.relativeAddress = relAddress;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
faultRes.isFault = false;
|
|
384
|
+
pageData.page = page;
|
|
385
|
+
pageData.relativeAddress = relAddress;
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
getChunks(faultRes, chunks, access, address, bytes) {
|
|
389
|
+
/**
|
|
390
|
+
* Accessing empty set of bytes is always valid.
|
|
391
|
+
* https://graypaper.fluffylabs.dev/#/68eaa1f/24a80024a800?v=0.6.4
|
|
392
|
+
*/
|
|
393
|
+
if (bytes === 0) {
|
|
394
|
+
faultRes.isFault = false;
|
|
395
|
+
chunks.firstPageData = EMPTY_UINT8ARRAY;
|
|
396
|
+
chunks.firstPageOffset = 0;
|
|
397
|
+
chunks.secondPageData = EMPTY_UINT8ARRAY;
|
|
398
|
+
chunks.secondPageEnd = 0;
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const pageData = this.pageResult;
|
|
402
|
+
this.getPage(faultRes, pageData, access, address);
|
|
403
|
+
if (faultRes.isFault) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const page = pageData.page;
|
|
407
|
+
const relativeAddress = pageData.relativeAddress;
|
|
408
|
+
const endAddress = relativeAddress + u32(bytes);
|
|
409
|
+
const needSecondPage = endAddress > PAGE_SIZE;
|
|
410
|
+
// everything is on one page - easy case
|
|
411
|
+
if (!needSecondPage) {
|
|
412
|
+
chunks.firstPageData = page.raw.data;
|
|
413
|
+
chunks.firstPageOffset = relativeAddress;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const secondPageIdx = u32((address + u32(bytes)) % MEMORY_SIZE) >> PAGE_SIZE_SHIFT;
|
|
417
|
+
const secondPageStart = secondPageIdx << PAGE_SIZE_SHIFT;
|
|
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);
|
|
427
|
+
}
|
|
428
|
+
if (!secondPage.can(access)) {
|
|
429
|
+
fault(faultRes, secondPageStart);
|
|
430
|
+
faultRes.isAccess = true;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
chunks.firstPageData = page.raw.data;
|
|
434
|
+
chunks.firstPageOffset = relativeAddress;
|
|
435
|
+
chunks.secondPageData = secondPage.raw.data;
|
|
436
|
+
chunks.secondPageEnd = relativeAddress + u32(bytes) - PAGE_SIZE;
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
/** Write some bytes to at most 2 pages. */
|
|
440
|
+
setBytes(faultRes, address, value, bytes) {
|
|
441
|
+
const r = this.chunksResult;
|
|
442
|
+
this.getChunks(faultRes, r, Access.Write, address, bytes);
|
|
443
|
+
if (faultRes.isFault) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
let bytesLeft = u64(value);
|
|
447
|
+
// write to first page
|
|
448
|
+
const firstPageEnd = IntMath.minU32(PAGE_SIZE, r.firstPageOffset + bytes);
|
|
449
|
+
for (let i = r.firstPageOffset; i < firstPageEnd; i++) {
|
|
450
|
+
r.firstPageData[i] = u8(bytesLeft);
|
|
451
|
+
bytesLeft >>= u64(8);
|
|
452
|
+
}
|
|
453
|
+
// write rest to the second page
|
|
454
|
+
for (let i = 0; i < r.secondPageEnd; i++) {
|
|
455
|
+
r.secondPageData[i] = u8(bytesLeft);
|
|
456
|
+
bytesLeft >>= u64(8);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
getBytesReversed(faultRes, access, address, bytes) {
|
|
460
|
+
this.getChunks(faultRes, this.chunksResult, access, address, bytes);
|
|
461
|
+
if (faultRes.isFault) {
|
|
462
|
+
return u64(0);
|
|
463
|
+
}
|
|
464
|
+
// result (bytes in reverse order)
|
|
465
|
+
let r = u64(0);
|
|
466
|
+
const firstPageEnd = IntMath.minU32(PAGE_SIZE, this.chunksResult.firstPageOffset + bytes);
|
|
467
|
+
// read from first page
|
|
468
|
+
for (let i = this.chunksResult.firstPageOffset; i < firstPageEnd; i++) {
|
|
469
|
+
r = (r << u64(8)) | u64(this.chunksResult.firstPageData[i]);
|
|
470
|
+
}
|
|
471
|
+
// read from the second page
|
|
472
|
+
for (let i = 0; i < this.chunksResult.secondPageEnd; i++) {
|
|
473
|
+
r = (r << u64(8)) | u64(this.chunksResult.secondPageData[i]);
|
|
474
|
+
}
|
|
475
|
+
return r;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function fault(r, address) {
|
|
479
|
+
r.isFault = true;
|
|
480
|
+
r.isAccess = false;
|
|
481
|
+
r.fault = address;
|
|
482
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare class portable {
|
|
2
|
+
static installPolyfills(): void;
|
|
3
|
+
static asArray<T>(v: T[]): T[];
|
|
4
|
+
static arrayAt<T>(v: T[], i: u32): T;
|
|
5
|
+
static staticArrayAt<T>(v: StaticArray<T>, i: u32): T;
|
|
6
|
+
static asU32(v: u32): u32;
|
|
7
|
+
static uint8ArrayView(data: ArrayBuffer, offset: i32, length: i32): Uint8Array;
|
|
8
|
+
static bswap_u16(v: u16): u16;
|
|
9
|
+
static bswap_u32(v: u32): u32;
|
|
10
|
+
static bswap_u64(v: u64): u64;
|
|
11
|
+
static popcnt_u32(v: u32): u32;
|
|
12
|
+
static popcnt_u64(v: u64): u64;
|
|
13
|
+
static clz_u32(v: u32): u32;
|
|
14
|
+
static clz_u64(v: u64): u64;
|
|
15
|
+
static ctz_u32(v: u32): u32;
|
|
16
|
+
static ctz_u64(v: u64): u64;
|
|
17
|
+
static rotr_u32(v: u32, shift: u32): u32;
|
|
18
|
+
static rotr_u64(v: u64, shift: u64): u64;
|
|
19
|
+
static rotl_u32(v: u32, shift: u32): u32;
|
|
20
|
+
static rotl_u64(v: u64, shift: u64): u64;
|
|
21
|
+
static u64_add(a: u64, b: u64): u64;
|
|
22
|
+
static u64_sub(a: u64, b: u64): u64;
|
|
23
|
+
static u64_mul(a: u64, b: u64): u64;
|
|
24
|
+
}
|