@specy/risc-v 3.7.0 → 4.1.0
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/README.md +208 -15
- package/dist/index.d.mts +233 -79
- package/dist/index.d.ts +233 -79
- package/dist/index.js +12659 -13574
- package/dist/index.mjs +12658 -13574
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,6 +23,10 @@ Omission selects `rars`. `RISCV.assemblerProfiles` lists supported profiles; eve
|
|
|
23
23
|
|
|
24
24
|
`getAddressOfLabel(name)` returns a defined label/alias address after successful assembly, or `-1` when absent. Profile state is per program; width, memory and execution state retain the existing single-instance constraint.
|
|
25
25
|
|
|
26
|
+
Floating point literals are read as Java 21 reads them, correctly rounded: `.double` stores
|
|
27
|
+
`Double.parseDouble` of its token and `.float` that double rounded to a float, as RARS does, while
|
|
28
|
+
the `gnu-compiler-v1` profile reads `.float` straight to single precision, as GNU as does.
|
|
29
|
+
|
|
26
30
|
⚠️**WARNING**⚠️ You must have only one instance of the simulator at a time. Memory, registers, and other state may be shared or behave unpredictably with multiple instances.
|
|
27
31
|
|
|
28
32
|
```typescript
|
|
@@ -65,15 +69,15 @@ console.log(`t2: ${t2}`);
|
|
|
65
69
|
const data = riscvSimulator.readMemoryBytes(0xffff0000, 4); // Read 4 bytes from address 0xffff0000
|
|
66
70
|
riscvSimulator.setMemoryBytes(0xffff0000, [0x01, 0x02, 0x03, 0x04]); // Write 4 bytes to address 0xffff0000
|
|
67
71
|
|
|
68
|
-
// Registering Handlers (for syscalls and other events):
|
|
69
|
-
riscvSimulator.registerHandler("
|
|
70
|
-
|
|
72
|
+
// Registering Handlers (for syscalls and other events): every print syscall writes its text here.
|
|
73
|
+
riscvSimulator.registerHandler("printString", (text: string) => {
|
|
74
|
+
process.stdout.write(text);
|
|
71
75
|
});
|
|
72
76
|
|
|
73
77
|
// Handlers may also be async: returning a promise suspends the simulation until it settles,
|
|
74
78
|
// so IO can be backed by an async API without blocking the event loop.
|
|
75
79
|
riscvSimulator.registerHandler("readInt", async () => {
|
|
76
|
-
return await
|
|
80
|
+
return await promptUserForALine(); // the syscall parses the line itself
|
|
77
81
|
});
|
|
78
82
|
|
|
79
83
|
// Accessing the undo stack:
|
|
@@ -106,7 +110,7 @@ Every syscall that needs to talk to the outside world goes through a handler you
|
|
|
106
110
|
handler can return its result directly, or return a promise of it:
|
|
107
111
|
|
|
108
112
|
```typescript
|
|
109
|
-
riscvSimulator.registerHandler("readInt", () => 42);
|
|
113
|
+
riscvSimulator.registerHandler("readInt", () => "42"); // synchronous
|
|
110
114
|
riscvSimulator.registerHandler("readString", () => fetchLine()); // asynchronous
|
|
111
115
|
```
|
|
112
116
|
|
|
@@ -116,17 +120,95 @@ promise settles, so nothing has to be shoehorned into a synchronous API. Because
|
|
|
116
120
|
`simulateWithBreakpointsAndLimit` all return a promise. Everything else on `JsRiscV` (registers,
|
|
117
121
|
memory, statements, the undo stack) stays synchronous.
|
|
118
122
|
|
|
119
|
-
If a handler
|
|
120
|
-
|
|
123
|
+
If a handler throws, its promise rejects or it answers what its type does not allow, the pending
|
|
124
|
+
`step`/`simulate*` call rejects with a `RuntimeError` of kind `handler`, whose `cause` is what the
|
|
125
|
+
handler threw or rejected with (see [Running and stopping](#running-and-stopping)).
|
|
121
126
|
|
|
122
127
|
Synchronous handlers never yield to the event loop: the simulation runs straight through, and the
|
|
123
128
|
returned promise settles on a microtask. Overlapping calls are queued and run one after another,
|
|
124
129
|
never concurrently.
|
|
125
130
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
The syscalls behave as RARS 1.6 does on Java 21, so handlers only move text and bytes:
|
|
132
|
+
|
|
133
|
+
* Every print syscall formats its value and writes the text through `printString`. Print float
|
|
134
|
+
and print double write what `Float.toString` and `Double.toString` write: `1.0`, `0.001`,
|
|
135
|
+
`1.0E-4`, `1.0E7`, `-0.0`, `NaN`, `Infinity`.
|
|
136
|
+
* `readInt`, `readFloat` and `readDouble` answer with the line typed. The syscall trims it and
|
|
137
|
+
parses it as `Integer.parseInt`, `Float.parseFloat` and `Double.parseDouble` do, hexadecimal
|
|
138
|
+
floats and an `f` or `d` suffix included, and stops the program on anything else:
|
|
139
|
+
`Runtime exception at 0x00400004: invalid integer input (syscall 5)`.
|
|
140
|
+
* `readChar` answers with the character typed, Enter as `"\n"`. The syscall takes the first
|
|
141
|
+
UTF-16 unit, and an empty answer stops the program with `invalid char input (syscall 12)`.
|
|
142
|
+
* `inputDialog` answers `null` when the user cancels, which dialogs 51 to 54 report to the
|
|
143
|
+
program as status -2; `confirm` answers 2 for Cancel. The program waits for `outputDialog` to
|
|
144
|
+
settle.
|
|
145
|
+
* Bytes cross as plain arrays of numbers from 0 to 255. `writeFile` answers with the number of
|
|
146
|
+
bytes written, or -1, and the program receives that count; `readFile` and `stdIn` answer
|
|
147
|
+
`[count, bytes]`, with a count of 0 at the end of the input and -1 for a failed read.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
riscvSimulator.registerHandler("inputDialog", (message) => window.prompt(message)); // null on Cancel
|
|
151
|
+
riscvSimulator.registerHandler("writeFile", (descriptor, bytes) => files.write(descriptor, Uint8Array.from(bytes)));
|
|
152
|
+
riscvSimulator.registerHandler("stdIn", async (length) => {
|
|
153
|
+
const bytes = await terminal.readBytes(length);
|
|
154
|
+
return [bytes.length, Array.from(bytes)];
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
| Handler | Syscalls | Receives | Answers |
|
|
159
|
+
| --- | --- | --- | --- |
|
|
160
|
+
| `printString` | 1 to 4, 11, 34 to 36 | the text to print | nothing |
|
|
161
|
+
| `readInt`, `readFloat`, `readDouble`, `readString` | 5 to 8 | nothing | the line typed |
|
|
162
|
+
| `readChar` | 12 | nothing | the character typed |
|
|
163
|
+
| `openFile` | 1024 | path, flags, whether to append | the file descriptor, or -1 |
|
|
164
|
+
| `readFile` | 63 | file descriptor, most bytes to read | `[count, bytes]` |
|
|
165
|
+
| `writeFile` | 64 | file descriptor, bytes | bytes written, or -1 |
|
|
166
|
+
| `closeFile` | 57 | file descriptor | nothing |
|
|
167
|
+
| `seekFile` | 62 | file descriptor, offset, whence | the new position, or -1 |
|
|
168
|
+
| `stdIn` | 63 on descriptor 0 | most bytes to read | `[count, bytes]` |
|
|
169
|
+
| `stdOut`, `stdErr` | 64 on descriptors 1 and 2 | bytes | nothing |
|
|
170
|
+
| `confirm` | 50 | message | `ConfirmResult` |
|
|
171
|
+
| `inputDialog` | 51 to 54 | message | the text entered, or `null` |
|
|
172
|
+
| `outputDialog` | 55, 56, 58 to 60 | message, `DialogType` | nothing |
|
|
173
|
+
| `sleep` | 32 | milliseconds | nothing |
|
|
174
|
+
| `time` | 30, and an instruction reading the `time` CSR | nothing | milliseconds |
|
|
175
|
+
| `randomSeed` (optional) | 41 to 44, a generator's first use | the generator's number | a seed from 0 to 2^48 - 1 |
|
|
176
|
+
|
|
177
|
+
Program time is a handler too, so a run can be given a clock of its own: `sleep` answers syscall 32,
|
|
178
|
+
and `time` answers syscall 30 and every read of the `time` CSR. A live run resolves `sleep` on a
|
|
179
|
+
timer and returns `Date.now()` from `time`; a scripted run can settle `sleep` immediately, advance a
|
|
180
|
+
virtual clock by the requested milliseconds and return that clock instead, which keeps elapsed-time
|
|
181
|
+
output reproducible.
|
|
182
|
+
|
|
183
|
+
## Text
|
|
184
|
+
|
|
185
|
+
Text is UTF-8 throughout, as in RARS, so a string's text is the same in a literal, through print
|
|
186
|
+
string (4) and through write (64), and the same as the UTF-8 a C compiler emits:
|
|
187
|
+
|
|
188
|
+
* `.ascii`, `.asciz` and `.string` store a literal by code point, `\u` escapes included: four
|
|
189
|
+
hexadecimal digits naming one UTF-16 unit, and two units that make a surrogate pair name one
|
|
190
|
+
character. RARS encodes a literal one UTF-16 unit at a time, which stores a character outside the
|
|
191
|
+
Basic Multilingual Plane as `??`; here `"😀"` and `"\ud83d\ude00"` both store its four bytes.
|
|
192
|
+
* Print string (4), the path of open (1024) and the messages of the dialogs (50 to 60) are read
|
|
193
|
+
as UTF-8, the way Java 21 decodes it: a malformed sequence prints as U+FFFD. Message dialog double
|
|
194
|
+
(58), which RARS reads one byte per character, reads UTF-8 like the others.
|
|
195
|
+
* Read string (8) and input dialog string (54) store the text as UTF-8, measuring the buffer in
|
|
196
|
+
bytes: a buffer of n bytes holds at most n - 1 bytes of text, so a character can be
|
|
197
|
+
cut where the buffer ends. An unpaired surrogate is stored as `?`, as Java encodes it.
|
|
198
|
+
* Print char (11) prints the character numbered by the low byte of `a0`, and read char (12)
|
|
199
|
+
answers the first UTF-16 unit typed: 233 for `é`, 8364 for `€` and 0xd83d, a high surrogate,
|
|
200
|
+
for `😀`.
|
|
201
|
+
|
|
202
|
+
## Corrections to RARS
|
|
203
|
+
|
|
204
|
+
* GetCWD (17) writes `/`, the root that relative and absolute file paths both resolve against.
|
|
205
|
+
RARS writes the JVM's working directory, which a browser does not have.
|
|
206
|
+
* Input dialog double (53) reads its message from `a0`, as every dialog does; RARS reads `x4`.
|
|
207
|
+
* The `time` CSR, and `timeh`, its upper half on RV32, read the program time in milliseconds
|
|
208
|
+
from the `time` handler when an instruction reads them, as the time service (30) does, so a
|
|
209
|
+
scripted run's virtual clock reaches them. RARS writes the host clock into the counter after
|
|
210
|
+
every instruction. The counter reads 0 until the program reads it, and Undo puts back the reading
|
|
211
|
+
before. `cycle` and `instret` count instructions as in RARS.
|
|
130
212
|
|
|
131
213
|
## Memory observers
|
|
132
214
|
|
|
@@ -169,6 +251,89 @@ riscvSimulator.removeMemoryObservers()
|
|
|
169
251
|
* `undo()` restores memory through the same stores, so an observed range reports the restored
|
|
170
252
|
values as ordinary writes and a device that follows notifications alone stays in step.
|
|
171
253
|
|
|
254
|
+
## Random numbers
|
|
255
|
+
|
|
256
|
+
The random services draw from `java.util.Random`, implemented exactly, so a program that seeds a
|
|
257
|
+
generator with ecall 40 prints the numbers RARS prints: `nextInt` (41), `nextInt(bound)` with
|
|
258
|
+
Java's rejection of over-represented values (42), `nextFloat` (43) and `nextDouble` (44). Each
|
|
259
|
+
number in `a0` names a generator of its own.
|
|
260
|
+
|
|
261
|
+
A generator the program has not seeded starts, on its first use, from the seed the optional
|
|
262
|
+
`randomSeed` handler answers: a whole number from 0 to 2^48 - 1, as `new Random(seed)` takes it.
|
|
263
|
+
Without the handler it starts from host randomness, as in RARS; a scripted run answers with a fixed
|
|
264
|
+
seed instead and gets the same numbers every time. Handlers are shared by every `JsRiscV` instance,
|
|
265
|
+
and registering `undefined` removes one.
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
riscvSimulator.registerHandler("randomSeed", () => 42); // the same numbers on every run
|
|
269
|
+
riscvSimulator.registerHandler("randomSeed", undefined); // host randomness again
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The generators belong to the run: `initialize` forgets them, so each starts from a new seed. Undo
|
|
273
|
+
puts a generator back as it was before the service ran, so Undo then Step draws the same number.
|
|
274
|
+
|
|
275
|
+
## Running and stopping
|
|
276
|
+
|
|
277
|
+
Every run call (`step`, `simulate`, `simulateWithLimit`, `simulateWithBreakpoints`,
|
|
278
|
+
`simulateWithBreakpointsAndLimit`) resolves to a `StopReason`, the same enum `@specy/mips` exports:
|
|
279
|
+
|
|
280
|
+
| `StopReason` | When |
|
|
281
|
+
| --- | --- |
|
|
282
|
+
| `MAX_STEPS` | the instruction limit was reached; a `step` ends on it |
|
|
283
|
+
| `BREAKPOINT` | the program counter reached a breakpoint address, whose instruction has not run, or an `ebreak` ran |
|
|
284
|
+
| `NORMAL_TERMINATION` | exit (10) or exit2 (93) ran |
|
|
285
|
+
| `CLIFF_TERMINATION` | the program ran off the end of its code; the call that runs the last instruction says so |
|
|
286
|
+
|
|
287
|
+
`terminated` is read from the program's state, so it is right after `undo()` too: it is true once
|
|
288
|
+
an exit has run or there is no statement at the program counter, and `getNextStatement()` then
|
|
289
|
+
returns `null`. A program that has exited runs nothing more: every run call resolves to
|
|
290
|
+
`NORMAL_TERMINATION` again until `undo()` or `initialize()`. `exitCode` is exit2's `a0` once it has
|
|
291
|
+
run and 0 otherwise, after exit and after running off the end too; `initialize` resets it and
|
|
292
|
+
undoing the exit puts back the code before it. `getStopReason()` reports the last run call's
|
|
293
|
+
reason, `NONE` after `initialize`.
|
|
294
|
+
|
|
295
|
+
A runtime failure rejects the run call with a `RuntimeError`, an `Error` with typed fields:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
try {
|
|
299
|
+
await riscvSimulator.simulate();
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (isRuntimeError(error)) {
|
|
302
|
+
// error.kind: 'exception' | 'syscall' | 'handler' | 'internal'
|
|
303
|
+
// error.address, error.sourcePath, error.line (one-based, null without a statement)
|
|
304
|
+
console.log(`${error.sourcePath}:${error.line}: ${error.message}`);
|
|
305
|
+
// main.asm:7: Runtime exception at 0x00400018: invalid or unimplemented syscall service: 99
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`exception` is a RISC-V exception the program had no trap handler for (a misaligned or faulting
|
|
311
|
+
access, an illegal instruction, an instruction fetch outside the program), `syscall` a service that
|
|
312
|
+
refused its number, arguments or input, `handler` a host handler or memory observer that threw,
|
|
313
|
+
rejected or broke its contract (its `cause` is what it threw), and `internal` the simulator
|
|
314
|
+
failing. The message is RARS's own. A failure leaves `terminated` false and `getStopReason()` at
|
|
315
|
+
`EXCEPTION`; any other rejection, such as a run call on a program that did not assemble, is the
|
|
316
|
+
plain error it is.
|
|
317
|
+
|
|
318
|
+
## Memory layout
|
|
319
|
+
|
|
320
|
+
Programs run in RARS's memory layout: text from 0x00400000, static data from 0x10010000, the heap
|
|
321
|
+
from 0x10040000, the stack down from 0x7fffeffc and memory-mapped IO from 0xffff0000. The data
|
|
322
|
+
segment, which static data and the heap share, ends at 0x10400000.
|
|
323
|
+
|
|
324
|
+
* A RARS-dialect program keeps this layout whatever its size, as in RARS: data past
|
|
325
|
+
0x10040000 shares its addresses with the heap.
|
|
326
|
+
* A `gnu-compiler-v1` program's heap follows its static data (`.data`, `.rodata`, `.bss` and
|
|
327
|
+
common symbols): once they reach past 0x10040000, the heap starts at the first 4 KiB page after
|
|
328
|
+
them, where ld and a kernel put the break, and sbrk (9) hands out that address first. Static data
|
|
329
|
+
must fit in the data segment, 4,128,768 bytes from 0x10010000; a program with more fails to
|
|
330
|
+
assemble with `Static data ends at 0x10410000, past the end of the data segment at 0x10400000: ...`.
|
|
331
|
+
* Library members linked after a RARS-dialect program must end below 0x10040000, or the program
|
|
332
|
+
fails to assemble with `Static data reaches the heap at 0x10040000`.
|
|
333
|
+
|
|
334
|
+
`getHeapStart()` reports where the heap starts, and `initialize` empties it again. An allocator
|
|
335
|
+
built on sbrk follows the heap wherever it starts.
|
|
336
|
+
|
|
172
337
|
## API
|
|
173
338
|
|
|
174
339
|
### `RISCV` Static Class
|
|
@@ -193,13 +358,18 @@ This interface provides methods to control and interact with a RISC-V simulator
|
|
|
193
358
|
* `canUndo: boolean` (Read-only): True if an undo operation can be performed.
|
|
194
359
|
* `programCounter: number` (Read-only): The current value of the program counter (PC).
|
|
195
360
|
* `stackPointer: number` (Read-only): The current value of the stack pointer (`$sp`).
|
|
196
|
-
* `terminated: boolean` (Read-only):
|
|
361
|
+
* `terminated: boolean` (Read-only): Whether the program has ended, by an exit or by running off the end, read from its state so that it is right after `undo()`.
|
|
362
|
+
* `exitCode: number` (Read-only): exit2's code once it has run, 0 otherwise.
|
|
197
363
|
|
|
198
364
|
#### Methods
|
|
199
365
|
|
|
200
366
|
* `assemble(): RISCVAssembleResult`: Assembles the program. Returns an object containing a report, error list, and an `hasErrors` flag.
|
|
201
367
|
* `initialize(startAtMain: boolean): void`: Initializes the simulator state for execution. If `startAtMain` is true, execution begins at the `main` label; otherwise, it starts at the first instruction.
|
|
202
|
-
* `step(): Promise<StopReason>`: Executes a single instruction.
|
|
368
|
+
* `step(): Promise<StopReason>`: Executes a single instruction. See [Running and stopping](#running-and-stopping) for every run call's `StopReason` and `RuntimeError`.
|
|
369
|
+
* `getStopReason(): StopReason`: Why the last run call stopped, `NONE` after `initialize`.
|
|
370
|
+
* `getHeapStart(): number`: Where the heap, and so sbrk's first block, starts: 0x10040000, or the
|
|
371
|
+
first page after static data in a `gnu-compiler-v1` program whose static data reaches past it.
|
|
372
|
+
See [Memory layout](#memory-layout).
|
|
203
373
|
* `undo(): void`: Undoes the last instruction executed, if `canUndo` is true and undo is enabled.
|
|
204
374
|
* `setUndoEnabled(enabled: boolean): void`: Enables or disables the undo feature.
|
|
205
375
|
* `setUndoSize(size: number): void`: Sets the maximum number of steps kept in the undo history. Must be called before assembling.
|
|
@@ -220,7 +390,7 @@ This interface provides methods to control and interact with a RISC-V simulator
|
|
|
220
390
|
* `removeMemoryObserver(handle: number): void`: Removes one registration.
|
|
221
391
|
* `removeMemoryObservers(): void`: Removes every registration.
|
|
222
392
|
* `countMemoryObservers(): number`: The number of live registrations.
|
|
223
|
-
* `getNextStatement(): JsProgramStatement | null`: Returns the next `JsProgramStatement` to be executed, or `null`
|
|
393
|
+
* `getNextStatement(): JsProgramStatement | null`: Returns the next `JsProgramStatement` to be executed, or `null` once the program has terminated.
|
|
224
394
|
* `getStatementAtAddress(address: number): JsProgramStatement | null`: Gets the program statement at the given memory `address`.
|
|
225
395
|
* `getStatementsAtSourceLocation(sourcePath: string, sourceLine: number): JsProgramStatement[]`: Returns every machine statement generated from an original one-based source location, in address order. Pseudo-instructions and macro calls can produce multiple results.
|
|
226
396
|
* `getCompiledStatements(): JsProgramStatement[]`: Returns all machine statements after a successful assembly.
|
|
@@ -240,9 +410,11 @@ This interface provides methods to control and interact with a RISC-V simulator
|
|
|
240
410
|
* `RISCVSourceSet`: A read-only mapping from canonical virtual source paths to source strings.
|
|
241
411
|
* `RISCVSourceLocation`: A source path and one-based source line.
|
|
242
412
|
* `HandlerName`: Union type of all possible handler names (keys of `HandlerMap`).
|
|
413
|
+
* `StopReason`: Enum of the reasons a run call stops.
|
|
414
|
+
* `RuntimeError`: The typed error a run call rejects with when the program fails; `isRuntimeError(error)` tells it apart.
|
|
243
415
|
* `HandlerMap`: An object type mapping `HandlerName`s to their expected input argument types (`in`) and return type (`out`). This defines the signature for syscall/event handlers.
|
|
244
416
|
* `HandlerMapFns`: An object type where keys are `HandlerName`s and values are the corresponding handler functions.
|
|
245
|
-
* `DialogType`: Enum for message dialog types (`ERROR_MESSAGE`, `INFORMATION_MESSAGE`, `WARNING_MESSAGE`, `QUESTION_MESSAGE`).
|
|
417
|
+
* `DialogType`: Enum for message dialog types (`PLAIN_MESSAGE`, `ERROR_MESSAGE`, `INFORMATION_MESSAGE`, `WARNING_MESSAGE`, `QUESTION_MESSAGE`).
|
|
246
418
|
* `ConfirmResult`: Enum for confirm dialog results (`YES`, `NO`, `CANCEL`).
|
|
247
419
|
* `BackStepAction`: Enum representing types of actions that can be undone (e.g., `MEMORY_RESTORE_WORD`, `REGISTER_RESTORE`).
|
|
248
420
|
* `JsBackStep`: Interface representing an entry in the undo stack, detailing the action, parameters, and PC value.
|
|
@@ -253,3 +425,24 @@ This interface provides methods to control and interact with a RISC-V simulator
|
|
|
253
425
|
* `RISCVAssembleError`: Interface describing an assembly diagnostic with its source path, one-based line and column, warning status, and structured macro expansion trace.
|
|
254
426
|
* `RISCVAssembleResult`: Interface for the result of `assemble()`, containing a report string, list of `RISCVAssembleError`s, and a `hasErrors` boolean.
|
|
255
427
|
* `JsRiscvStackFrame`: Interface describing a frame on the call stack (PC, target address, SP, FP, register snapshot).
|
|
428
|
+
|
|
429
|
+
### Dynamic instruction identities
|
|
430
|
+
|
|
431
|
+
`getCurrentInstructionSerial(): string | null` names the instruction currently executing. Read it
|
|
432
|
+
inside a host handler (including an awaited handler) to journal host effects. The eventual
|
|
433
|
+
`JsInstructionUndoGroup.serial` and each `JsBackStep.serial` carry exactly that identity. Core-only
|
|
434
|
+
services, exits, write-free instructions and failed instructions also receive their own identity.
|
|
435
|
+
Pokes have a distinct `JsPokeUndoGroup.serial`; the current-instruction getter remains null in a Poke.
|
|
436
|
+
Serials are opaque positive decimal strings, allocated monotonically for the lifetime of this module
|
|
437
|
+
across instances, Undo, initialize and reassembly. Do not convert them to JavaScript numbers. At
|
|
438
|
+
serial-space exhaustion execution fails rather than reusing an identity.
|
|
439
|
+
|
|
440
|
+
History keeps only the bounded restore stack. The Undo size still counts raw restore slots, with a
|
|
441
|
+
Poke taking one slot; oldest instructions are evicted whole. An instruction larger than the capacity
|
|
442
|
+
is discarded whole, so a retained group always represents a complete rollback. `initialize` clears
|
|
443
|
+
retained history and Poke journals. Executing an instruction or opening a Poke with recording
|
|
444
|
+
disabled clears older retained history, preventing Undo across an unrecorded gap. No-history
|
|
445
|
+
instructions still expose a serial inside handlers. Match and retain host frames by serial against
|
|
446
|
+
`getUndoGroups`/`getUndoGroupsRange`, never by PC; drop frames whose groups are no longer retained.
|
|
447
|
+
|
|
448
|
+
`initialize` is rejected while an instruction or Poke is active.
|