@fluffylabs/anan-as 1.1.3-e3864a9 → 1.1.3-eee81bd

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 (41) hide show
  1. package/README.md +47 -10
  2. package/dist/bin/index.js +174 -102
  3. package/dist/bin/{fuzz.js → src/fuzz.js} +3 -20
  4. package/dist/bin/src/log-host-call.js +41 -0
  5. package/dist/bin/src/trace-parse.js +315 -0
  6. package/dist/bin/src/trace-replay.js +130 -0
  7. package/dist/bin/src/tracer.js +64 -0
  8. package/dist/bin/src/utils.js +25 -0
  9. package/dist/bin/test.js +1 -3
  10. package/dist/build/compiler-inline.js +1 -1
  11. package/dist/build/compiler.d.ts +2 -2
  12. package/dist/build/compiler.js +1 -1
  13. package/dist/build/compiler.wasm +0 -0
  14. package/dist/build/debug-inline.js +1 -1
  15. package/dist/build/debug-raw-inline.js +1 -1
  16. package/dist/build/debug-raw.d.ts +113 -57
  17. package/dist/build/debug-raw.js +97 -61
  18. package/dist/build/debug-raw.wasm +0 -0
  19. package/dist/build/debug.d.ts +113 -57
  20. package/dist/build/debug.js +107 -64
  21. package/dist/build/debug.wasm +0 -0
  22. package/dist/build/release-inline.js +1 -1
  23. package/dist/build/release-mini-inline.js +1 -1
  24. package/dist/build/release-mini.d.ts +113 -57
  25. package/dist/build/release-mini.js +107 -64
  26. package/dist/build/release-mini.wasm +0 -0
  27. package/dist/build/release-stub-inline.js +1 -1
  28. package/dist/build/release-stub.d.ts +113 -57
  29. package/dist/build/release-stub.js +107 -64
  30. package/dist/build/release-stub.wasm +0 -0
  31. package/dist/build/release.d.ts +113 -57
  32. package/dist/build/release.js +107 -64
  33. package/dist/build/release.wasm +0 -0
  34. package/dist/build/test-inline.js +1 -1
  35. package/dist/build/test.wasm +0 -0
  36. package/dist/test/test-as.js +3 -0
  37. package/dist/{bin → test}/test-gas-cost.js +1 -1
  38. package/dist/test/test-trace-replay.js +19 -0
  39. package/dist/{bin → test}/test-w3f.js +3 -1
  40. package/package.json +5 -5
  41. /package/dist/bin/{test-json.js → src/test-json.js} +0 -0
package/README.md CHANGED
@@ -48,9 +48,26 @@ import ananAs from '@fluffylabs/anan-as/release-mini';
48
48
  // make sure to call GC after multiple independent runs
49
49
  ananAs.__collect();
50
50
 
51
+ // Release build with stub host functions (for standalone testing)
52
+ import ananAs from '@fluffylabs/anan-as/release-stub';
53
+
54
+ // Compiler module (for PVM bytecode compilation)
55
+ import ananAs from '@fluffylabs/anan-as/compiler';
56
+ ```
57
+
58
+ ### Inline Builds
59
+
60
+ Inline builds bundle the WASM binary directly into the JavaScript module (base64 encoded),
61
+ eliminating the need to fetch a separate `.wasm` file:
62
+
63
+ ```javascript
64
+ import ananAs from '@fluffylabs/anan-as/debug-inline';
65
+ import ananAs from '@fluffylabs/anan-as/release-inline';
66
+ import ananAs from '@fluffylabs/anan-as/release-mini-inline';
67
+ import ananAs from '@fluffylabs/anan-as/release-stub-inline';
51
68
  ```
52
69
 
53
- ## Raw Bindings
70
+ ### Raw Bindings
54
71
 
55
72
  Raw bindings give you direct access to WebAssembly exports
56
73
  without the JavaScript wrapper layer.
@@ -69,7 +86,6 @@ const module = await WebAssembly.instantiateStreaming(
69
86
  imports
70
87
  );
71
88
  const ananAs = await instantiate(module);
72
-
73
89
  ```
74
90
 
75
91
  ## Version Tags
@@ -105,24 +121,33 @@ To run the example in the browser at [http://localhost:3000](http://localhost:30
105
121
  npm run web
106
122
  ```
107
123
 
108
- To run JSON test vectors.
124
+ To run tests:
109
125
 
110
126
  ```cmd
111
- npm start ./path/to/tests/*.json
127
+ # Run AssemblyScript unit tests and trace replay tests
128
+ npm test
129
+
130
+ # Run W3F test vectors
131
+ npm run test:w3f
132
+
133
+ # Run gas cost tests
134
+ npm run test:gas-cost
112
135
  ```
113
136
 
114
137
  ## CLI Usage
115
138
 
116
- The package includes a CLI tool for disassembling and running PVM bytecode:
139
+ The package includes a CLI tool for disassembling, running, and replaying PVM bytecode:
117
140
 
118
141
  ```bash
119
142
  # Disassemble bytecode to assembly
120
143
  npx @fluffylabs/anan-as disassemble [--spi] [--no-metadata] <file.(jam|pvm|spi|bin)>
121
144
 
122
145
  # Run JAM programs
123
- npx @fluffylabs/anan-as run [--spi] [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <file.jam> [spi-args.bin]
146
+ npx @fluffylabs/anan-as run [--spi] [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <file.jam> [spi-args.bin or hex]
124
147
 
125
- The `run` command executes PVM bytecode until it encounters a `halt` instruction or the first host call. For full execution including host call handling, use the disassemble command or other tooling.
148
+ # Replay an ecalli trace
149
+ # Learn more: https://github.com/tomusdrw/JIPs/blob/td-jip6-ecalliloggin/JIP-6.md
150
+ npx @fluffylabs/anan-as replay-trace [--no-metadata] [--no-verify] [--no-logs] <trace.log>
126
151
 
127
152
  # Show help
128
153
  npx @fluffylabs/anan-as --help
@@ -130,18 +155,23 @@ npx @fluffylabs/anan-as disassemble --help
130
155
  npx @fluffylabs/anan-as run --help
131
156
  ```
132
157
 
158
+ The `run` command executes PVM bytecode until it encounters a `halt` instruction or a host call.
159
+ The `replay-trace` command re-executes an ecalli trace, replaying recorded host call responses.
160
+
133
161
  ### Commands
134
162
 
135
163
  - `disassemble`: Convert PVM bytecode to human-readable assembly
136
164
  - `run`: Execute PVM bytecode and show results
165
+ - `replay-trace`: Re-execute an ecalli trace with recorded host call responses
137
166
 
138
167
  ### Flags
139
168
 
140
169
  - `--spi`: Treat input as JAM SPI format instead of generic PVM
141
170
  - `--no-metadata`: Input does not start with metadata
142
- - `--no-logs`: Disable execution logs (run command only)
171
+ - `--no-logs`: Disable execution logs (run and replay-trace commands)
172
+ - `--no-verify`: Skip verification against trace data (replay-trace only)
143
173
  - `--pc <number>`: Set initial program counter (default: 0)
144
- - `--gas <number>`: Set initial gas amount (default: 0)
174
+ - `--gas <number>`: Set initial gas amount (default: 10,000)
145
175
  - `--help`, `-h`: Show help information
146
176
 
147
177
  ### Examples
@@ -168,6 +198,13 @@ npx @fluffylabs/anan-as run --no-logs program.jam
168
198
  # Run a JAM program with custom initial PC and gas
169
199
  npx @fluffylabs/anan-as run --pc 100 --gas 10000 program.jam
170
200
 
171
- # Run SPI program with arguments
201
+ # Run SPI program with arguments (file or hex)
172
202
  npx @fluffylabs/anan-as run --spi program.spi args.bin
203
+ npx @fluffylabs/anan-as run --spi program.spi 0xdeadbeef
204
+
205
+ # Replay an ecalli trace
206
+ npx @fluffylabs/anan-as replay-trace trace.log
207
+
208
+ # Replay without verification
209
+ npx @fluffylabs/anan-as replay-trace --no-verify trace.log
173
210
  ```
package/dist/bin/index.js CHANGED
@@ -1,22 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
3
  import minimist from "minimist";
4
- import { disassemble, HasMetadata, InputKind, prepareProgram, runProgram } from "../build/release.js";
4
+ import { disassemble, HasMetadata, InputKind, prepareProgram, pvmDestroy, pvmResume, pvmSetRegisters, pvmStart, } from "../build/release.js";
5
+ import { LOG_GAS_COST, LOG_HOST_CALL_INDEX, printLogHostCall, WHAT } from "./src/log-host-call.js";
6
+ import { STATUS } from "./src/trace-parse.js";
7
+ import { replayTraceFile } from "./src/trace-replay.js";
8
+ import { hexDecode, hexEncode } from "./src/utils.js";
5
9
  const HELP_TEXT = `Usage:
6
10
  anan-as disassemble [--spi] [--no-metadata] <file.(jam|pvm|spi|bin)>
7
- anan-as run [--spi] [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <file.jam> [spi-args.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]
12
+ anan-as replay-trace [--no-metadata] [--no-verify] [--no-logs] [--no-log-host-call] <trace.log>
8
13
 
9
14
  Commands:
10
15
  disassemble Disassemble PVM bytecode to assembly
11
16
  run Execute PVM bytecode
17
+ replay-trace Re-execute a ecalli IO trace
12
18
 
13
19
  Flags:
14
- --spi Treat input as JAM SPI format
15
- --no-metadata Input does not contain metadata
16
- --no-logs Disable execution logs (run command only)
17
- --pc <number> Set initial program counter (default: 0)
18
- --gas <number> Set initial gas amount (default: 0)
19
- --help, -h Show this help message`;
20
+ --spi Treat input as JAM SPI format
21
+ --no-metadata Input does not contain metadata
22
+ --no-logs Disable execution logs
23
+ --no-log-host-call Disable built-in handling of JIP-1 log host call (ecalli 100)
24
+ --no-verify Skip verification against trace data (replay-trace only)
25
+ --pc <number> Set initial program counter (default: 0)
26
+ --gas <number> Set initial gas amount (default: 10_000)
27
+ --help, -h Show this help message`;
20
28
  main();
21
29
  function main() {
22
30
  const args = process.argv.slice(2);
@@ -33,6 +41,9 @@ function main() {
33
41
  case "run":
34
42
  handleRun(args.slice(1));
35
43
  break;
44
+ case "replay-trace":
45
+ handleReplayTrace(args.slice(1));
46
+ break;
36
47
  default:
37
48
  console.error(`Error: Unknown sub-command '${subCommand}'`);
38
49
  console.error("");
@@ -42,8 +53,9 @@ function main() {
42
53
  }
43
54
  function handleDisassemble(args) {
44
55
  const parsed = minimist(args, {
45
- boolean: ["spi", "no-metadata", "help"],
56
+ boolean: ["spi", "metadata", "help"],
46
57
  alias: { h: "help" },
58
+ default: { metadata: true },
47
59
  });
48
60
  if (parsed.help) {
49
61
  console.log(HELP_TEXT);
@@ -76,7 +88,7 @@ function handleDisassemble(args) {
76
88
  process.exit(1);
77
89
  }
78
90
  const kind = parsed.spi ? InputKind.SPI : InputKind.Generic;
79
- const hasMetadata = parsed["no-metadata"] ? HasMetadata.No : HasMetadata.Yes;
91
+ const hasMetadata = parsed.metadata ? HasMetadata.Yes : HasMetadata.No;
80
92
  const f = readFileSync(file);
81
93
  const name = kind === InputKind.Generic ? "generic PVM" : "JAM SPI";
82
94
  console.log(`🤖 Assembly of ${file} (as ${name})`);
@@ -84,9 +96,11 @@ function handleDisassemble(args) {
84
96
  }
85
97
  function handleRun(args) {
86
98
  const parsed = minimist(args, {
87
- boolean: ["spi", "no-logs", "no-metadata", "help"],
88
- string: ["pc", "gas"],
99
+ boolean: ["spi", "logs", "metadata", "help", "log-host-call"],
100
+ /** Prevents parsing hex values as numbers. */
101
+ string: ["pc", "gas", "_"],
89
102
  alias: { h: "help" },
103
+ default: { metadata: true, logs: true, "log-host-call": true },
90
104
  });
91
105
  if (parsed.help) {
92
106
  console.log(HELP_TEXT);
@@ -100,16 +114,16 @@ function handleRun(args) {
100
114
  }
101
115
  const kind = parsed.spi ? InputKind.SPI : InputKind.Generic;
102
116
  let programFile;
103
- let spiArgsFile;
117
+ let spiArgsStr;
104
118
  if (kind === InputKind.SPI) {
105
- // For SPI programs, expect: <program.spi> [spi-args.bin]
119
+ // For SPI programs, expect: <program.spi> [spi-args.bin or hex]
106
120
  if (files.length > 2) {
107
121
  console.error("Error: Too many arguments for SPI run command.");
108
- console.error("Usage: anan-as run --spi [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <program.spi> [spi-args.bin]");
122
+ console.error("Usage: anan-as run --spi [--no-logs] [--no-metadata] [--pc <number>] [--gas <number>] <program.spi> [spi-args.bin or hex]");
109
123
  process.exit(1);
110
124
  }
111
125
  programFile = files[0];
112
- spiArgsFile = files[1]; // optional
126
+ spiArgsStr = files[1]; // optional
113
127
  }
114
128
  else {
115
129
  // For generic programs, expect exactly one file
@@ -120,101 +134,159 @@ function handleRun(args) {
120
134
  }
121
135
  programFile = files[0];
122
136
  }
123
- // Validate program file extension
124
- const expectedExt = kind === InputKind.SPI ? ".spi" : ".jam";
125
- const dotIndex = programFile.lastIndexOf(".");
126
- if (dotIndex === -1) {
127
- console.error(`Error: File '${programFile}' has no extension.`);
128
- console.error(`Expected: ${expectedExt}`);
129
- process.exit(1);
137
+ // Validate SPI args file if provided
138
+ const spiArgs = parseSpiArgs(spiArgsStr);
139
+ const logs = parsed.logs;
140
+ const logHostCall = parsed["log-host-call"];
141
+ const hasMetadata = parsed.metadata ? HasMetadata.Yes : HasMetadata.No;
142
+ // Parse and validate PC and gas options
143
+ const initialPc = parsePc(parsed);
144
+ const initialGas = parseGas(parsed);
145
+ const programCode = Array.from(readFileSync(programFile));
146
+ const name = kind === InputKind.Generic ? "generic PVM" : "JAM SPI";
147
+ console.log(`🚀 Running ${programFile} (as ${name})`);
148
+ try {
149
+ const program = prepareProgram(kind, hasMetadata, programCode, [], [], [], spiArgs);
150
+ const id = pvmStart(program, false);
151
+ let gas = initialGas;
152
+ let pc = initialPc;
153
+ for (;;) {
154
+ const pause = pvmResume(id, gas, pc, logs);
155
+ if (!pause) {
156
+ throw new Error("pvmResume returned null");
157
+ }
158
+ if (pause.status === STATUS.HOST && pause.exitCode === LOG_HOST_CALL_INDEX && logHostCall) {
159
+ printLogHostCall(id, pause.registers);
160
+ // Set r7 = WHAT
161
+ const regs = pause.registers;
162
+ regs[7] = WHAT;
163
+ pvmSetRegisters(id, regs);
164
+ // Deduct gas and advance PC
165
+ gas = pause.gas >= LOG_GAS_COST ? pause.gas - LOG_GAS_COST : 0n;
166
+ pc = pause.nextPc;
167
+ }
168
+ else {
169
+ console.warn(`Unhandled host call: ecalli ${pause.exitCode}. Finishing.`);
170
+ break;
171
+ }
172
+ }
173
+ const result = pvmDestroy(id);
174
+ console.log(`Status: ${result?.status}`);
175
+ console.log(`Exit code: ${result?.exitCode}`);
176
+ console.log(`Program counter: ${result?.pc}`);
177
+ console.log(`Gas remaining: ${result?.gas}`);
178
+ console.log(`Registers: [${result?.registers.join(", ")}]`);
179
+ console.log(`Result: [${hexEncode(result?.result ?? [])}]`);
130
180
  }
131
- const ext = programFile.substring(dotIndex);
132
- if (ext !== expectedExt) {
133
- console.error(`Error: Invalid file extension '${ext}' for run command.`);
134
- console.error(`Expected: ${expectedExt}`);
181
+ catch (error) {
182
+ console.error(`Error running ${programFile}:`, error);
135
183
  process.exit(1);
136
184
  }
137
- // Validate SPI args file if provided
138
- let spiArgs;
139
- if (spiArgsFile) {
140
- const argsDotIndex = spiArgsFile.lastIndexOf(".");
141
- if (argsDotIndex === -1) {
142
- console.error(`Error: SPI args file '${spiArgsFile}' has no extension.`);
143
- console.error(`Expected: .bin`);
144
- process.exit(1);
145
- }
146
- const argsExt = spiArgsFile.substring(argsDotIndex);
147
- if (argsExt !== ".bin") {
148
- console.error(`Error: SPI args file must have .bin extension, got '${argsExt}'.`);
149
- process.exit(1);
150
- }
151
- spiArgs = new Uint8Array(readFileSync(spiArgsFile));
185
+ }
186
+ function handleReplayTrace(args) {
187
+ const parsed = minimist(args, {
188
+ boolean: ["metadata", "verify", "logs", "help", "log-host-call"],
189
+ alias: { h: "help" },
190
+ default: { metadata: true, logs: true, verify: true, "log-host-call": true },
191
+ });
192
+ if (parsed.help) {
193
+ console.log(HELP_TEXT);
194
+ return;
152
195
  }
153
- const logs = !parsed["no-logs"];
154
- const hasMetadata = parsed["no-metadata"] ? HasMetadata.No : HasMetadata.Yes;
155
- // Parse and validate PC and gas options
156
- let initialPc = 0;
157
- if (parsed.pc !== undefined) {
158
- // Ensure it's a string/number, not boolean
159
- if (typeof parsed.pc === "boolean") {
160
- console.error("Error: --pc requires a value.");
161
- process.exit(1);
162
- }
163
- const pcStr = String(parsed.pc);
164
- // Reject floats and non-integer strings
165
- if (pcStr.includes(".") || !/^-?\d+$/.test(pcStr)) {
166
- console.error("Error: --pc must be a valid integer.");
167
- process.exit(1);
168
- }
169
- const pcValue = parseInt(pcStr, 10);
170
- if (!Number.isInteger(pcValue) || pcValue < 0 || pcValue > 0xffffffff) {
171
- console.error("Error: --pc must be a non-negative integer <= 2^32-1.");
172
- process.exit(1);
173
- }
174
- initialPc = pcValue;
196
+ const files = parsed._;
197
+ if (files.length === 0) {
198
+ console.error("Error: No trace file provided for replay-trace command.");
199
+ console.error("Usage: anan-as replay-trace [--no-metadata] [--no-verify] [--no-logs] <trace.log>");
200
+ process.exit(1);
175
201
  }
176
- let initialGas = BigInt(0);
177
- if (parsed.gas !== undefined) {
178
- // Ensure it's a string/number, not boolean
179
- if (typeof parsed.gas === "boolean") {
180
- console.error("Error: --gas requires a value.");
181
- process.exit(1);
182
- }
183
- const gasStr = String(parsed.gas);
184
- // Reject floats and non-integer strings
185
- if (gasStr.includes(".") || !/^-?\d+$/.test(gasStr)) {
186
- console.error("Error: --gas must be a valid integer.");
187
- process.exit(1);
188
- }
189
- let gasValue;
190
- try {
191
- gasValue = BigInt(gasStr);
192
- }
193
- catch (_e) {
194
- console.error("Error: --gas must be a valid integer.");
195
- process.exit(1);
196
- }
197
- const MAX_I64 = (1n << 63n) - 1n;
198
- if (gasValue < 0n || gasValue > MAX_I64) {
199
- console.error("Error: --gas must be a non-negative integer <= 2^63-1.");
200
- process.exit(1);
201
- }
202
- initialGas = gasValue;
202
+ if (files.length > 1) {
203
+ console.error("Error: Only one trace file can be replayed at a time.");
204
+ console.error("Usage: anan-as replay-trace [--no-metadata] [--no-verify] [--no-logs] <trace.log>");
205
+ process.exit(1);
203
206
  }
204
- const f = readFileSync(programFile);
205
- const name = kind === InputKind.Generic ? "generic PVM" : "JAM SPI";
206
- console.log(`🚀 Running ${programFile} (as ${name})`);
207
+ const file = files[0];
208
+ const hasMetadata = parsed.metadata ? HasMetadata.Yes : HasMetadata.No;
209
+ const verify = parsed.verify;
210
+ const logs = parsed.logs;
211
+ const logHostCall = parsed["log-host-call"];
207
212
  try {
208
- const program = prepareProgram(kind, hasMetadata, Array.from(f), [], [], [], spiArgs ? Array.from(spiArgs) : []);
209
- const result = runProgram(program, initialGas, initialPc, logs, false);
210
- console.log(`Status: ${result.status}`);
211
- console.log(`Exit code: ${result.exitCode}`);
212
- console.log(`Program counter: ${result.pc}`);
213
- console.log(`Gas remaining: ${result.gas}`);
214
- console.log(`Registers: [${result.registers.join(", ")}]`);
213
+ const summary = replayTraceFile(file, {
214
+ logs,
215
+ hasMetadata,
216
+ verify,
217
+ logHostCall,
218
+ });
219
+ console.log(`✅ Replay complete: ${summary.ecalliCount} ecalli entries`);
220
+ console.log(`Status: ${summary.termination.type}`);
221
+ console.log(`Program counter: ${summary.termination.pc}`);
222
+ console.log(`Gas remaining: ${summary.termination.gas}`);
215
223
  }
216
224
  catch (error) {
217
- console.error(`Error running ${programFile}:`, error);
225
+ console.error(`Error replaying trace ${file}:`, error);
226
+ process.exit(1);
227
+ }
228
+ }
229
+ function parseGas(parsed) {
230
+ if (parsed.gas === undefined) {
231
+ return BigInt(10_000);
232
+ }
233
+ // Ensure it's a string/number, not boolean
234
+ if (typeof parsed.gas === "boolean") {
235
+ console.error("Error: --gas requires a value.");
236
+ process.exit(1);
237
+ }
238
+ const gasStr = String(parsed.gas);
239
+ // Reject floats and non-integer strings
240
+ if (gasStr.includes(".") || !/^-?\d+$/.test(gasStr)) {
241
+ console.error("Error: --gas must be a valid integer.");
242
+ process.exit(1);
243
+ }
244
+ let gasValue;
245
+ try {
246
+ gasValue = BigInt(gasStr);
247
+ }
248
+ catch (_e) {
249
+ console.error("Error: --gas must be a valid integer.");
250
+ process.exit(1);
251
+ }
252
+ const MAX_I64 = (1n << 63n) - 1n;
253
+ if (gasValue < 0n || gasValue > MAX_I64) {
254
+ console.error("Error: --gas must be a non-negative integer <= 2^63-1.");
255
+ process.exit(1);
256
+ }
257
+ return gasValue;
258
+ }
259
+ function parseSpiArgs(spiArgsStr) {
260
+ if (!spiArgsStr) {
261
+ return [];
262
+ }
263
+ try {
264
+ return Array.from(hexDecode(spiArgsStr));
265
+ }
266
+ catch (e) {
267
+ console.log(`Attempting to read ${spiArgsStr} as a file, since it's not a hex value: ${e}`);
268
+ return Array.from(readFileSync(spiArgsStr));
269
+ }
270
+ }
271
+ function parsePc(parsed) {
272
+ if (parsed.pc === undefined) {
273
+ return 0;
274
+ }
275
+ // Ensure it's a string/number, not boolean
276
+ if (typeof parsed.pc === "boolean") {
277
+ console.error("Error: --pc requires a value.");
278
+ process.exit(1);
279
+ }
280
+ const pcStr = String(parsed.pc);
281
+ // Reject floats and non-integer strings
282
+ if (pcStr.includes(".") || !/^-?\d+$/.test(pcStr)) {
283
+ console.error("Error: --pc must be a valid integer.");
284
+ process.exit(1);
285
+ }
286
+ const pcValue = parseInt(pcStr, 10);
287
+ if (!Number.isInteger(pcValue) || pcValue < 0 || pcValue > 0xffffffff) {
288
+ console.error("Error: --pc must be a non-negative integer <= 2^32-1.");
218
289
  process.exit(1);
219
290
  }
291
+ return pcValue;
220
292
  }
@@ -3,7 +3,7 @@ import "json-bigint-patch";
3
3
  import fs from "node:fs";
4
4
  import { tryAsGas } from "@typeberry/lib/pvm-interface";
5
5
  import { Interpreter } from "@typeberry/lib/pvm-interpreter";
6
- import { disassemble, HasMetadata, InputKind, prepareProgram, runProgram, wrapAsProgram } from "../build/release.js";
6
+ import { disassemble, HasMetadata, InputKind, prepareProgram, runProgram, wrapAsProgram } from "../../build/release.js";
7
7
  const runNumber = 0;
8
8
  export function fuzz(data) {
9
9
  const gas = 200n;
@@ -56,30 +56,13 @@ export function fuzz(data) {
56
56
  throw e;
57
57
  }
58
58
  }
59
+ import { hexEncode } from "./utils.js";
59
60
  function programHex(program) {
60
- return Array.from(program)
61
- .map((x) => x.toString(16).padStart(2, "0"))
62
- .join("");
61
+ return hexEncode(program, false);
63
62
  }
64
63
  function linkTo(programHex) {
65
64
  return `https://pvm.fluffylabs.dev/?program=0x${programHex}#/`;
66
65
  }
67
- const REGISTER_BYTE_WIDTH = 8;
68
- function _decodeRegisters(value) {
69
- if (value.length === 0) {
70
- return [];
71
- }
72
- if (value.length % REGISTER_BYTE_WIDTH !== 0) {
73
- throw new Error(`Invalid register buffer size: ${value.length}`);
74
- }
75
- const view = new DataView(value.buffer, value.byteOffset, value.byteLength);
76
- const registerCount = value.length / REGISTER_BYTE_WIDTH;
77
- const registers = new Array(registerCount);
78
- for (let i = 0; i < registerCount; i++) {
79
- registers[i] = view.getBigUint64(i * REGISTER_BYTE_WIDTH, true);
80
- }
81
- return registers;
82
- }
83
66
  function decodeRegistersFromTypeberry(vm) {
84
67
  const registers = [];
85
68
  // Try to get up to 13 registers (common register count)
@@ -0,0 +1,41 @@
1
+ import { pvmReadMemory } from "../../build/release.js";
2
+ export const LOG_HOST_CALL_INDEX = 100;
3
+ export const LOG_GAS_COST = 10n;
4
+ /** The WHAT return value - indicates the host call is not implemented / acknowledged. */
5
+ export const WHAT = 0xfffffffffffffffen;
6
+ const LOG_LEVELS = ["FATAL", "ERROR", "WARN", "INFO", "DEBUG"];
7
+ const MAX_LOG_LEN = 8192;
8
+ /**
9
+ * Print the log message from a JIP-1 log host call (ecalli 100).
10
+ *
11
+ * Reads the level, target, and message from the PVM registers and memory,
12
+ * then prints via console.info.
13
+ */
14
+ export function printLogHostCall(pvmId, registers) {
15
+ const level = Number(registers[7]);
16
+ const targetPtr = Number(registers[8] & 0xffffffffn);
17
+ const targetLen = Math.min(Math.max(0, Number(registers[9] & 0xffffffffn)), MAX_LOG_LEN);
18
+ const messagePtr = Number(registers[10] & 0xffffffffn);
19
+ const messageLen = Math.min(Math.max(0, Number(registers[11] & 0xffffffffn)), MAX_LOG_LEN);
20
+ const levelStr = LOG_LEVELS[level] ?? `LEVEL(${level})`;
21
+ let target = "";
22
+ if (targetPtr !== 0 && targetLen > 0) {
23
+ const targetBytes = pvmReadMemory(pvmId, targetPtr, targetLen);
24
+ if (targetBytes) {
25
+ target = new TextDecoder().decode(targetBytes);
26
+ }
27
+ }
28
+ let message = "";
29
+ if (messagePtr !== 0 && messageLen > 0) {
30
+ const messageBytes = pvmReadMemory(pvmId, messagePtr, messageLen);
31
+ if (messageBytes) {
32
+ message = new TextDecoder().decode(messageBytes);
33
+ }
34
+ }
35
+ if (target) {
36
+ console.info(`[${levelStr}] ${target}: ${message}`);
37
+ }
38
+ else {
39
+ console.info(`[${levelStr}] ${message}`);
40
+ }
41
+ }