@fluffylabs/anan-as 1.2.0-9723cd9 → 1.2.0-9f08a39
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 +136 -4
- package/dist/bin/src/trace-parse.js +1 -0
- package/dist/bin/src/trace-replay.js +6 -5
- 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 +15 -0
- package/dist/build/compiler.js +17 -1
- 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.wasm +0 -0
- package/dist/build/debug.wasm +0 -0
- package/dist/build/js/portable/bootstrap.js +1 -0
- package/dist/build/js/portable-bundle.js +5 -4
- package/dist/build/release-inline.js +1 -1
- 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-trace-format.js +166 -0
- package/package.json +5 -5
package/dist/bin/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
|
-
import { disassemble, HasMetadata, InputKind, prepareProgram, pvmDestroy, pvmResume, pvmSetRegisters, pvmStart, } from "../build/release.js";
|
|
4
|
+
import { disassemble, HasMetadata, InputKind, prepareProgram, pvmDestroy, pvmReadMemory, pvmResume, pvmSetRegisters, pvmStart, } from "../build/release.js";
|
|
5
5
|
import { LOG_GAS_COST, LOG_HOST_CALL_INDEX, printLogHostCall, WHAT } from "./src/log-host-call.js";
|
|
6
6
|
import { STATUS } from "./src/trace-parse.js";
|
|
7
7
|
import { replayTraceFile } from "./src/trace-replay.js";
|
|
8
8
|
import { hexDecode, hexEncode } from "./src/utils.js";
|
|
9
|
+
// Page access modes (matches assembly/memory-page.ts Access enum)
|
|
10
|
+
const ACCESS_READ = 1;
|
|
11
|
+
const ACCESS_WRITE = 2;
|
|
9
12
|
const HELP_TEXT = `Usage:
|
|
10
13
|
anan-as disassemble [--spi] [--no-metadata] <file.(jam|pvm|spi|bin)>
|
|
11
|
-
anan-as run [--spi] [--no-logs] [--no-metadata] [--no-log-host-call] [--pc <number>] [--gas <number>] <file.jam> [spi-args.bin or hex]
|
|
14
|
+
anan-as run [--spi] [--no-logs] [--no-metadata] [--no-log-host-call] [--pc <number>] [--gas <number>] [--regs <r0,r1,...,r12>] <file.jam> [spi-args.bin or hex]
|
|
12
15
|
anan-as replay-trace [--no-metadata] [--no-verify] [--no-logs] [--no-log-host-call] <trace.log>
|
|
13
16
|
|
|
14
17
|
Commands:
|
|
@@ -24,6 +27,10 @@ Flags:
|
|
|
24
27
|
--no-verify Skip verification against trace data (replay-trace only)
|
|
25
28
|
--pc <number> Set initial program counter (default: 0)
|
|
26
29
|
--gas <number> Set initial gas amount (default: 10_000)
|
|
30
|
+
--regs <values> Set initial registers (comma-separated, 13 values: r0,r1,...,r12; supports decimal and 0x hex)
|
|
31
|
+
--pages <specs> Add memory pages (semicolon-separated: "addr:size;addr:size:ro"; append ":r" or ":ro" for read-only)
|
|
32
|
+
--mem <specs> Initialize memory (semicolon-separated: "addr:hex_bytes;addr:hex_bytes")
|
|
33
|
+
--dump <specs> Dump memory after execution (semicolon-separated: "addr:len;addr:len")
|
|
27
34
|
--help, -h Show this help message`;
|
|
28
35
|
main();
|
|
29
36
|
function main() {
|
|
@@ -84,7 +91,7 @@ function handleDisassemble(args) {
|
|
|
84
91
|
console.error("Supported extensions: .jam, .pvm, .spi, .bin");
|
|
85
92
|
process.exit(1);
|
|
86
93
|
}
|
|
87
|
-
const ext = file.
|
|
94
|
+
const ext = file.slice(dotIndex);
|
|
88
95
|
if (!validExtensions.includes(ext)) {
|
|
89
96
|
console.error(`Error: Invalid file extension '${ext}' for disassemble command.`);
|
|
90
97
|
console.error("Supported extensions: .jam, .pvm, .spi, .bin");
|
|
@@ -109,6 +116,10 @@ function handleRun(args) {
|
|
|
109
116
|
help: { type: "boolean", short: "h", default: false },
|
|
110
117
|
pc: { type: "string" },
|
|
111
118
|
gas: { type: "string" },
|
|
119
|
+
regs: { type: "string" },
|
|
120
|
+
pages: { type: "string" },
|
|
121
|
+
mem: { type: "string" },
|
|
122
|
+
dump: { type: "string" },
|
|
112
123
|
},
|
|
113
124
|
});
|
|
114
125
|
if (values.help) {
|
|
@@ -150,13 +161,17 @@ function handleRun(args) {
|
|
|
150
161
|
// Parse and validate PC and gas options
|
|
151
162
|
const initialPc = parsePc(values.pc);
|
|
152
163
|
const initialGas = parseGas(values.gas);
|
|
164
|
+
const initialRegisters = parseRegs(values.regs);
|
|
165
|
+
const initialPages = parsePages(values.pages);
|
|
166
|
+
const initialMemory = parseMem(values.mem);
|
|
167
|
+
const dumpRegions = parseDump(values.dump);
|
|
153
168
|
const programCode = Array.from(readFileSync(programFile));
|
|
154
169
|
const name = kind === InputKind.Generic ? "generic PVM" : "JAM SPI";
|
|
155
170
|
console.log(`🚀 Running ${programFile} (as ${name})`);
|
|
156
171
|
try {
|
|
157
172
|
const preallocateMemoryPages = 128;
|
|
158
173
|
const useBlockGas = true;
|
|
159
|
-
const program = prepareProgram(kind, hasMetadata, programCode,
|
|
174
|
+
const program = prepareProgram(kind, hasMetadata, programCode, initialRegisters, initialPages, initialMemory, spiArgs, preallocateMemoryPages, useBlockGas);
|
|
160
175
|
const id = pvmStart(program);
|
|
161
176
|
let gas = initialGas;
|
|
162
177
|
let pc = initialPc;
|
|
@@ -180,6 +195,24 @@ function handleRun(args) {
|
|
|
180
195
|
break;
|
|
181
196
|
}
|
|
182
197
|
}
|
|
198
|
+
// Dump memory regions before destroying the VM
|
|
199
|
+
for (const region of dumpRegions) {
|
|
200
|
+
const data = pvmReadMemory(id, region.address, region.length);
|
|
201
|
+
const addrHex = `0x${region.address.toString(16)}`;
|
|
202
|
+
if (data) {
|
|
203
|
+
console.log(`\nMemory @ ${addrHex} (${region.length} bytes):`);
|
|
204
|
+
for (let off = 0; off < data.length; off += 16) {
|
|
205
|
+
const addr = region.address + off;
|
|
206
|
+
const slice = Array.from(data.slice(off, Math.min(off + 16, data.length)));
|
|
207
|
+
const hex = slice.map((b) => b.toString(16).padStart(2, "0")).join(" ");
|
|
208
|
+
const ascii = slice.map((b) => (b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : ".")).join("");
|
|
209
|
+
console.log(` ${addr.toString(16).padStart(8, "0")}: ${hex.padEnd(47)} ${ascii}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
console.log(`\nMemory @ ${addrHex}: <page fault>`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
183
216
|
const result = pvmDestroy(id);
|
|
184
217
|
console.log(`Status: ${result?.status}`);
|
|
185
218
|
console.log(`Exit code: ${result?.exitCode}`);
|
|
@@ -293,3 +326,102 @@ function parsePc(pcStr) {
|
|
|
293
326
|
}
|
|
294
327
|
return pcValue;
|
|
295
328
|
}
|
|
329
|
+
function parseRegs(regsStr) {
|
|
330
|
+
if (regsStr === undefined) {
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
const parts = regsStr.split(",");
|
|
334
|
+
if (parts.length !== 13) {
|
|
335
|
+
throw new Error(`--regs must have exactly 13 comma-separated values (got ${parts.length}).\nFormat: --regs r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12`);
|
|
336
|
+
}
|
|
337
|
+
return parts.map((s, i) => {
|
|
338
|
+
try {
|
|
339
|
+
return BigInt.asUintN(64, BigInt(s.trim()));
|
|
340
|
+
}
|
|
341
|
+
catch (_e) {
|
|
342
|
+
throw new Error(`--regs value at index ${i} ("${s.trim()}") is not a valid integer.`);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
function parseNum(s) {
|
|
347
|
+
return Number(s.trim());
|
|
348
|
+
}
|
|
349
|
+
function parsePages(pagesStr) {
|
|
350
|
+
if (pagesStr === undefined) {
|
|
351
|
+
return [];
|
|
352
|
+
}
|
|
353
|
+
// Format: "addr:size;addr:size" — all pages are writable
|
|
354
|
+
// Or "addr:size:ro" (or "addr:size:r") for read-only
|
|
355
|
+
const specs = pagesStr.split(";").filter((s) => s.trim().length > 0);
|
|
356
|
+
return specs.map((spec, i) => {
|
|
357
|
+
const parts = spec.split(":");
|
|
358
|
+
if (parts.length < 2 || parts.length > 3) {
|
|
359
|
+
throw new Error(`--pages entry ${i} ("${spec}") must be "addr:size" or "addr:size:ro" (or "addr:size:r").`);
|
|
360
|
+
}
|
|
361
|
+
const address = parseNum(parts[0]);
|
|
362
|
+
const length = parseNum(parts[1]);
|
|
363
|
+
const flag = parts[2]?.trim();
|
|
364
|
+
const access = flag === "ro" || flag === "r" ? ACCESS_READ : ACCESS_WRITE;
|
|
365
|
+
if (Number.isNaN(address) || Number.isNaN(length) || length <= 0) {
|
|
366
|
+
throw new Error(`--pages entry ${i} ("${spec}") has invalid address or size.`);
|
|
367
|
+
}
|
|
368
|
+
return { address, length, access };
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
function parseMem(memStr) {
|
|
372
|
+
if (memStr === undefined) {
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
// Format: "addr:hexbytes;addr:hexbytes"
|
|
376
|
+
// Example: "0x20000:0500000000000000;0x20008:0300000000000000"
|
|
377
|
+
const specs = memStr.split(";").filter((s) => s.trim().length > 0);
|
|
378
|
+
return specs.map((spec, i) => {
|
|
379
|
+
const colonIdx = spec.indexOf(":");
|
|
380
|
+
if (colonIdx === -1) {
|
|
381
|
+
throw new Error(`--mem entry ${i} ("${spec}") must be "addr:hexbytes".`);
|
|
382
|
+
}
|
|
383
|
+
const addrStr = spec.slice(0, colonIdx).trim();
|
|
384
|
+
let hexStr = spec.slice(colonIdx + 1).trim();
|
|
385
|
+
const address = parseNum(addrStr);
|
|
386
|
+
if (Number.isNaN(address)) {
|
|
387
|
+
throw new Error(`--mem entry ${i} has invalid address "${addrStr}".`);
|
|
388
|
+
}
|
|
389
|
+
// Strip 0x prefix from hex data
|
|
390
|
+
if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) {
|
|
391
|
+
hexStr = hexStr.slice(2);
|
|
392
|
+
}
|
|
393
|
+
if (hexStr.length % 2 !== 0) {
|
|
394
|
+
throw new Error(`--mem entry ${i} hex data has odd length.`);
|
|
395
|
+
}
|
|
396
|
+
const data = [];
|
|
397
|
+
for (let j = 0; j < hexStr.length; j += 2) {
|
|
398
|
+
const pair = hexStr.slice(j, j + 2);
|
|
399
|
+
if (!/^[0-9a-fA-F]{2}$/.test(pair)) {
|
|
400
|
+
throw new Error(`--mem entry ${i} has invalid hex byte at position ${j}: "${pair}".`);
|
|
401
|
+
}
|
|
402
|
+
const byte = parseInt(pair, 16);
|
|
403
|
+
data.push(byte);
|
|
404
|
+
}
|
|
405
|
+
return { address, data };
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
function parseDump(dumpStr) {
|
|
409
|
+
if (dumpStr === undefined) {
|
|
410
|
+
return [];
|
|
411
|
+
}
|
|
412
|
+
// Format: "addr:len;addr:len"
|
|
413
|
+
// Example: "0x20000:64;0x20100:32"
|
|
414
|
+
const specs = dumpStr.split(";").filter((s) => s.trim().length > 0);
|
|
415
|
+
return specs.map((spec, i) => {
|
|
416
|
+
const parts = spec.split(":");
|
|
417
|
+
if (parts.length !== 2) {
|
|
418
|
+
throw new Error(`--dump entry ${i} ("${spec}") must be "addr:len".`);
|
|
419
|
+
}
|
|
420
|
+
const address = parseNum(parts[0]);
|
|
421
|
+
const length = parseNum(parts[1]);
|
|
422
|
+
if (Number.isNaN(address) || Number.isNaN(length) || length <= 0) {
|
|
423
|
+
throw new Error(`--dump entry ${i} ("${spec}") has invalid address or length.`);
|
|
424
|
+
}
|
|
425
|
+
return { address, length };
|
|
426
|
+
});
|
|
427
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import * as defaultPvm from "../../build/release.js";
|
|
3
3
|
import { LOG_HOST_CALL_INDEX, printLogHostCall } from "./log-host-call.js";
|
|
4
|
-
import {
|
|
4
|
+
import { buildInitialChunks, buildInitialPages, encodeRegistersFromDump, extractSpiArgs, isSpiTrace, parseTrace, STATUS, statusToTermination, } from "./trace-parse.js";
|
|
5
5
|
import { ConsoleTracer } from "./tracer.js";
|
|
6
6
|
import { hexEncode } from "./utils.js";
|
|
7
7
|
export function replayTraceFile(filePath, options) {
|
|
@@ -25,11 +25,12 @@ export function replayTraceFile(filePath, options) {
|
|
|
25
25
|
try {
|
|
26
26
|
let gas = start.gas;
|
|
27
27
|
let pc = start.pc;
|
|
28
|
-
// Print start
|
|
29
|
-
tracer.
|
|
30
|
-
|
|
31
|
-
tracer.
|
|
28
|
+
// Print prelude: program, initial memwrites, start
|
|
29
|
+
tracer.program(program);
|
|
30
|
+
for (const write of initialMemWrites) {
|
|
31
|
+
tracer.memwrite(write.address, write.data);
|
|
32
32
|
}
|
|
33
|
+
tracer.start(pc, gas, start.registers);
|
|
33
34
|
for (;;) {
|
|
34
35
|
const pause = pvmResume(id, gas, pc, options.logs);
|
|
35
36
|
if (!pause) {
|
package/dist/bin/src/tracer.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
// Trace output format: https://github.com/FluffyLabs/jam-ecalli-trace/blob/main/ecalli-trace-jip.md
|
|
2
2
|
export class ConsoleTracer {
|
|
3
|
+
program(data) {
|
|
4
|
+
console.log(`program ${formatHex(data)}`);
|
|
5
|
+
}
|
|
3
6
|
start(pc, gas, registers) {
|
|
4
7
|
console.log(`start pc=${pc} gas=${gas} ${formatRegisters(registers)}`);
|
|
5
8
|
}
|
|
6
|
-
spiArgs(address, data) {
|
|
7
|
-
console.log(` memwrite ${address} len=${data.length} <- ${hexEncode(data)}`);
|
|
8
|
-
}
|
|
9
9
|
ecalli(index, pc, gas, registers) {
|
|
10
|
-
console.log(
|
|
10
|
+
console.log(`ecalli=${index} pc=${pc} gas=${gas} ${formatRegisters(registers)}`);
|
|
11
11
|
}
|
|
12
12
|
memread(address, data) {
|
|
13
|
-
console.log(`
|
|
13
|
+
console.log(` memread ${formatAddress(address)} len=${data.length} -> ${formatHex(data)}`);
|
|
14
14
|
}
|
|
15
15
|
memwrite(address, data) {
|
|
16
|
-
console.log(`
|
|
16
|
+
console.log(` memwrite ${formatAddress(address)} len=${data.length} <- ${formatHex(data)}`);
|
|
17
17
|
}
|
|
18
18
|
setreg(index, value) {
|
|
19
|
-
console.log(`
|
|
19
|
+
console.log(` setreg r${index.toString().padStart(2, "0")} <- 0x${value.toString(16)}`);
|
|
20
20
|
}
|
|
21
21
|
setgas(gas) {
|
|
22
|
-
console.log(`
|
|
22
|
+
console.log(` setgas <- ${gas}`);
|
|
23
23
|
}
|
|
24
24
|
termination(type, exitCode, pc, gas, registers) {
|
|
25
|
-
let termLine =
|
|
26
|
-
if (type === "PANIC"
|
|
25
|
+
let termLine = `${type}`;
|
|
26
|
+
if (type === "PANIC") {
|
|
27
27
|
termLine += `=${exitCode}`;
|
|
28
28
|
}
|
|
29
29
|
termLine += ` pc=${pc} gas=${gas} ${formatRegisters(registers)}`;
|
|
@@ -31,8 +31,8 @@ export class ConsoleTracer {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
export class NoOpTracer {
|
|
34
|
+
program() { }
|
|
34
35
|
start() { }
|
|
35
|
-
spiArgs() { }
|
|
36
36
|
ecalli() { }
|
|
37
37
|
memread() { }
|
|
38
38
|
memwrite() { }
|
|
@@ -56,9 +56,12 @@ function formatRegisters(registers) {
|
|
|
56
56
|
}
|
|
57
57
|
return entries
|
|
58
58
|
.sort((a, b) => a.idx - b.idx)
|
|
59
|
-
.map((e) => `r${e.idx}=0x${e.val.toString(16)}`)
|
|
59
|
+
.map((e) => `r${e.idx.toString().padStart(2, "0")}=0x${e.val.toString(16)}`)
|
|
60
60
|
.join(" ");
|
|
61
61
|
}
|
|
62
|
+
function formatAddress(address) {
|
|
63
|
+
return `0x${Number(address).toString(16).padStart(8, "0")}`;
|
|
64
|
+
}
|
|
62
65
|
function formatHex(data) {
|
|
63
66
|
return `0x${Buffer.from(data).toString("hex")}`;
|
|
64
67
|
}
|
package/dist/bin/src/utils.js
CHANGED
|
@@ -6,14 +6,14 @@ export function hexDecode(data) {
|
|
|
6
6
|
if (!data.startsWith("0x")) {
|
|
7
7
|
throw new Error("hex input must start with 0x");
|
|
8
8
|
}
|
|
9
|
-
const hex = data.
|
|
9
|
+
const hex = data.slice(2);
|
|
10
10
|
const len = hex.length;
|
|
11
11
|
if (len % 2 === 1) {
|
|
12
12
|
throw new Error("Odd number of nibbles");
|
|
13
13
|
}
|
|
14
14
|
const bytes = new Uint8Array(len / 2);
|
|
15
15
|
for (let i = 0; i < len; i += 2) {
|
|
16
|
-
const c = hex.
|
|
16
|
+
const c = hex.slice(i, i + 2);
|
|
17
17
|
const byteIndex = i / 2;
|
|
18
18
|
if (!/^[0-9a-fA-F]{2}$/.test(c)) {
|
|
19
19
|
throw new Error(`hexDecode: invalid hex pair "${c}" in data "${data}" for bytes[${byteIndex}]`);
|