@specy/risc-v 2.0.1 → 3.0.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 +87 -23
- package/dist/index.d.mts +123 -24
- package/dist/index.d.ts +123 -24
- package/dist/index.js +7453 -6205
- package/dist/index.mjs +7452 -6205
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,22 +9,33 @@ It is part of a family of javascript assembly interpreters/simulators:
|
|
|
9
9
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
|
12
|
-
First, create an instance of the simulator with the
|
|
12
|
+
First, create an instance of the simulator with `makeRiscVFromFiles`. Supply a virtual source tree and the path of its entry file. Relative `.include` paths resolve from the file containing the directive, while paths beginning with `/` resolve from the virtual root.
|
|
13
13
|
|
|
14
14
|
Before running the simulator, you must assemble and initialize it. You can then step through the program, simulate with breakpoints, or simulate with a limit.
|
|
15
15
|
|
|
16
16
|
⚠️**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.
|
|
17
17
|
|
|
18
18
|
```typescript
|
|
19
|
-
import {RISCV, JsRiscV, RegisterName, BackStepAction} from '@specy/risc-v';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
import {RISCV, makeRiscVFromFiles, JsRiscV, RegisterName, BackStepAction} from '@specy/risc-v';
|
|
20
|
+
|
|
21
|
+
const files = {
|
|
22
|
+
'src/main.asm': `
|
|
23
|
+
.include "lib/math.asm"
|
|
24
|
+
.text
|
|
25
|
+
.globl main
|
|
26
|
+
main:
|
|
27
|
+
add_values(t2, 5, 7)
|
|
28
|
+
`,
|
|
29
|
+
'src/lib/math.asm': `
|
|
30
|
+
.macro add_values(%result, %left, %right)
|
|
31
|
+
li %result, %left
|
|
32
|
+
addi %result, %result, %right
|
|
33
|
+
.end_macro
|
|
34
|
+
`,
|
|
35
|
+
} as const;
|
|
25
36
|
|
|
26
37
|
// RISCV.setIs64Bit(true); // Set to 64-bit mode if needed
|
|
27
|
-
const riscvSimulator: JsRiscV =
|
|
38
|
+
const riscvSimulator: JsRiscV = makeRiscVFromFiles(files, 'src/main.asm');
|
|
28
39
|
|
|
29
40
|
riscvSimulator.assemble();
|
|
30
41
|
riscvSimulator.initialize(true); // Start at 'main'
|
|
@@ -102,16 +113,62 @@ Synchronous handlers never yield to the event loop: the simulation runs straight
|
|
|
102
113
|
returned promise settles on a microtask. Overlapping calls are queued and run one after another,
|
|
103
114
|
never concurrently.
|
|
104
115
|
|
|
116
|
+
Program time is a handler too, so a run can be given a clock of its own: `sleep` answers syscall 32
|
|
117
|
+
and `time` answers syscall 30. A live run resolves `sleep` on a timer and returns `Date.now()` from
|
|
118
|
+
`time`; a scripted run can settle `sleep` immediately, advance a virtual clock by the requested
|
|
119
|
+
milliseconds and return that clock instead, which keeps elapsed-time output reproducible.
|
|
120
|
+
|
|
121
|
+
## Memory observers
|
|
122
|
+
|
|
123
|
+
A memory-mapped device - a framebuffer, a keyboard register - is modelled by observing the memory
|
|
124
|
+
the program reads and writes:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
// Every write in a framebuffer: (address, length, value), with the width of the store in bytes.
|
|
128
|
+
const frame = riscvSimulator.addMemoryWriteObserver(0x10010000, 0x10012ffc, (address, length, value) => {
|
|
129
|
+
screen.markDirty(address)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// One memory-mapped register: reads and writes, either of which may be null.
|
|
133
|
+
const receiver = riscvSimulator.addMemoryAccessObserver(
|
|
134
|
+
0xffff0004,
|
|
135
|
+
() => keyboard.consumeCharacter(),
|
|
136
|
+
null
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
riscvSimulator.removeMemoryObserver(frame)
|
|
140
|
+
riscvSimulator.removeMemoryObservers()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
* Addresses must be word-aligned, `endAddress` is inclusive and covers its whole word, and a range
|
|
144
|
+
may not cross `0x80000000`; a registration that breaks any of these throws. Either form of a
|
|
145
|
+
high address is accepted: `0xffff0000` and `0xffff0000 | 0` name the same word.
|
|
146
|
+
* Handlers are given signed 32 bit integers, as the guest holds them: the register at
|
|
147
|
+
`0xffff000c` arrives as `-65524`, and a pixel word with its high bit set arrives negative.
|
|
148
|
+
Apply `>>> 0` wherever the unsigned form is wanted.
|
|
149
|
+
* Handlers run synchronously inside the instruction that caused the access, so they must be cheap
|
|
150
|
+
and must not write back into their own range. A returned promise is ignored, unlike an IO
|
|
151
|
+
handler's.
|
|
152
|
+
* An observer is notified *after* the access, with the value the program read or stored. A
|
|
153
|
+
register whose value is consumed by reading it must therefore be reloaded from the handler,
|
|
154
|
+
with `setPeripheralWord`, for the next read.
|
|
155
|
+
* Observers live on the simulator's memory, which assembling and initializing only clear the
|
|
156
|
+
contents of, so a registration survives `assemble()` and `initialize()` and - like a registered
|
|
157
|
+
IO handler - is shared by every `JsRiscV` instance. Notifications start once a program has been
|
|
158
|
+
assembled.
|
|
159
|
+
* `undo()` restores memory through the same stores, so an observed range reports the restored
|
|
160
|
+
values as ordinary writes and a device that follows notifications alone stays in step.
|
|
161
|
+
|
|
105
162
|
## API
|
|
106
163
|
|
|
107
164
|
### `RISCV` Static Class
|
|
108
165
|
|
|
109
166
|
Provides static methods to initialize and create simulator instances.
|
|
110
167
|
|
|
111
|
-
* `
|
|
112
|
-
Creates a
|
|
168
|
+
* `makeRiscVFromFiles(files: RISCVSourceSet, entryFile: string): JsRiscV`
|
|
169
|
+
Creates a simulator from a snapshot of a virtual source tree and its entry file. Source paths are canonical, root-relative POSIX paths. The factory is also available as `RISCV.makeRiscVFromFiles` and initializes the simulation environment when needed.
|
|
113
170
|
* `RISCV.initializeRISCV(): void`
|
|
114
|
-
Initializes the core RISC-V simulation environment. Called internally by `
|
|
171
|
+
Initializes the core RISC-V simulation environment. Called internally by `makeRiscVFromFiles`, but can be called explicitly if needed.
|
|
115
172
|
* `RISCV.getInstructionSet(): JsInstruction[]`
|
|
116
173
|
Returns an array of objects, each describing a supported RISC-V instruction (name, example, description, tokens).
|
|
117
174
|
* `RISCV.setIs64Bit(is64Bit: boolean): void`
|
|
@@ -145,15 +202,20 @@ This interface provides methods to control and interact with a RISC-V simulator
|
|
|
145
202
|
* `getConditionFlags(): number[]`: Gets the 8 condition flags (if applicable, typically related to floating-point or custom extensions).
|
|
146
203
|
* `registerHandler<T extends HandlerName>(name: T, handler: (...args: HandlerMap[T]['in']) => HandlerMap[T]['out'] | Promise<HandlerMap[T]['out']>): void`: Registers a handler function for a specific event (e.g., syscalls). See `HandlerName`, `HandlerMap` and [IO handlers](#io-handlers) for details.
|
|
147
204
|
* `getUndoStack(): JsBackStep[]`: Returns the undo stack, an array of `JsBackStep` objects representing simulation history.
|
|
148
|
-
* `readMemoryBytes(address: number, length: number): number[]`: Reads `length` bytes from memory starting at `address`. Returns an array of byte values.
|
|
149
|
-
* `setMemoryBytes(address: number, bytes: number[]): void`: Writes an array of `bytes` to memory starting at `address
|
|
150
|
-
* `
|
|
205
|
+
* `readMemoryBytes(address: number, length: number): number[]`: Reads `length` bytes from memory starting at `address`. Returns an array of byte values. Notifies no memory observer: inspecting memory from the host is not the program reading it.
|
|
206
|
+
* `setMemoryBytes(address: number, bytes: number[]): void`: Writes an array of `bytes` to memory starting at `address`, the way the program does: write observers are notified and, while undo is enabled, an undo step is recorded per byte.
|
|
207
|
+
* `setPeripheralWord(address: number, value: number): void`: Writes one word-aligned word as a peripheral would, notifying no observer and recording no undo step. See [memory observers](#memory-observers).
|
|
208
|
+
* `addMemoryWriteObserver(startAddress: number, endAddress: number, handler): number`: Observes every write in an address range. See [memory observers](#memory-observers).
|
|
209
|
+
* `addMemoryAccessObserver(address: number, onRead, onWrite): number`: Observes reads and writes of one word. See [memory observers](#memory-observers).
|
|
210
|
+
* `removeMemoryObserver(handle: number): void`: Removes one registration.
|
|
211
|
+
* `removeMemoryObservers(): void`: Removes every registration.
|
|
212
|
+
* `countMemoryObservers(): number`: The number of live registrations.
|
|
151
213
|
* `getNextStatement(): JsProgramStatement | null`: Returns the next `JsProgramStatement` to be executed, or `null` if at the end.
|
|
152
214
|
* `getStatementAtAddress(address: number): JsProgramStatement | null`: Gets the program statement at the given memory `address`.
|
|
153
|
-
* `
|
|
154
|
-
* `getCompiledStatements(): JsProgramStatement[]`: Returns
|
|
155
|
-
* `getParsedStatements():
|
|
156
|
-
* `getTokenizedLines(): RiscvTokenizedLine[]`: Returns
|
|
215
|
+
* `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.
|
|
216
|
+
* `getCompiledStatements(): JsProgramStatement[]`: Returns all machine statements after a successful assembly.
|
|
217
|
+
* `getParsedStatements(): JsProgramStatement[]`: Returns the parsed statements before pseudo-instruction expansion.
|
|
218
|
+
* `getTokenizedLines(): RiscvTokenizedLine[]`: Returns the expanded source stream with each line's path, one-based line number, original source, processed source, and tokens. It is available after tokenization succeeds, including when a later assembly stage fails.
|
|
157
219
|
* `getCallStack(): JsRiscvStackFrame[]`: Returns the current call stack as an array of stack frame objects.
|
|
158
220
|
* `getLabelAtAddress(address: number): string | null`: Returns the label name at the given memory `address`, or `null` if no label exists there.
|
|
159
221
|
|
|
@@ -165,6 +227,8 @@ This interface provides methods to control and interact with a RISC-V simulator
|
|
|
165
227
|
#### Types
|
|
166
228
|
|
|
167
229
|
* `RegisterName`: Union type for RISC-V register names (e.g., `'ra'`, `'sp'`, `'a0'`, `'t0'`, etc.).
|
|
230
|
+
* `RISCVSourceSet`: A read-only mapping from canonical virtual source paths to source strings.
|
|
231
|
+
* `RISCVSourceLocation`: A source path and one-based source line.
|
|
168
232
|
* `HandlerName`: Union type of all possible handler names (keys of `HandlerMap`).
|
|
169
233
|
* `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.
|
|
170
234
|
* `HandlerMapFns`: An object type where keys are `HandlerName`s and values are the corresponding handler functions.
|
|
@@ -172,10 +236,10 @@ This interface provides methods to control and interact with a RISC-V simulator
|
|
|
172
236
|
* `ConfirmResult`: Enum for confirm dialog results (`YES`, `NO`, `CANCEL`).
|
|
173
237
|
* `BackStepAction`: Enum representing types of actions that can be undone (e.g., `MEMORY_RESTORE_WORD`, `REGISTER_RESTORE`).
|
|
174
238
|
* `JsBackStep`: Interface representing an entry in the undo stack, detailing the action, parameters, and PC value.
|
|
175
|
-
* `JsProgramStatement`: Interface representing an assembled instruction, including source line, address, binary/machine/assembly representations.
|
|
176
|
-
* `JsInstructionToken`: Interface representing a token from the assembly source (value, type, source
|
|
239
|
+
* `JsProgramStatement`: Interface representing an assembled instruction, including its source path, source line, address, binary/machine/assembly representations, and full original source line.
|
|
240
|
+
* `JsInstructionToken`: Interface representing a token from the assembly source (value, type, and one-based source column).
|
|
177
241
|
* `JsInstruction`: Interface describing a RISC-V instruction (name, example, description, token patterns).
|
|
178
|
-
* `RiscvTokenizedLine`: An object containing
|
|
179
|
-
* `RISCVAssembleError`: Interface describing an assembly
|
|
242
|
+
* `RiscvTokenizedLine`: An object containing source identity, original and processed line strings, and parsed `JsInstructionToken`s.
|
|
243
|
+
* `RISCVAssembleError`: Interface describing an assembly diagnostic with its source path, one-based line and column, warning status, and structured macro expansion trace.
|
|
180
244
|
* `RISCVAssembleResult`: Interface for the result of `assemble()`, containing a report string, list of `RISCVAssembleError`s, and a `hasErrors` boolean.
|
|
181
|
-
* `JsRiscvStackFrame`: Interface describing a frame on the call stack (PC, target address, SP, FP, register snapshot).
|
|
245
|
+
* `JsRiscvStackFrame`: Interface describing a frame on the call stack (PC, target address, SP, FP, register snapshot).
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
type JsInstructionToken = {
|
|
2
|
-
|
|
2
|
+
/** One-based column in the processed source line. */
|
|
3
3
|
sourceColumn: number;
|
|
4
|
-
originalSourceLine: number;
|
|
5
4
|
value: string;
|
|
6
5
|
type: string;
|
|
7
6
|
};
|
|
@@ -119,10 +118,23 @@ type HandlerMap = {
|
|
|
119
118
|
in: [l: string];
|
|
120
119
|
out: void;
|
|
121
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* Syscall 32: the program asks to be suspended for this many milliseconds. Return a promise
|
|
123
|
+
* that settles when the wait is over to suspend the simulation without blocking the host.
|
|
124
|
+
*/
|
|
122
125
|
sleep: {
|
|
123
126
|
in: [milliseconds: number];
|
|
124
127
|
out: void;
|
|
125
128
|
};
|
|
129
|
+
/**
|
|
130
|
+
* Syscall 30: the program time in milliseconds, split by the syscall into a0 (low word) and
|
|
131
|
+
* a1 (high word). Answer with `Date.now()` for a live run, or with a virtual clock for a
|
|
132
|
+
* scripted one, so that elapsed-time output stays reproducible.
|
|
133
|
+
*/
|
|
134
|
+
time: {
|
|
135
|
+
in: [];
|
|
136
|
+
out: number;
|
|
137
|
+
};
|
|
126
138
|
stdIn: {
|
|
127
139
|
in: [buffer: number[], length: number];
|
|
128
140
|
out: void;
|
|
@@ -136,6 +148,27 @@ type HandlerMap = {
|
|
|
136
148
|
out: void;
|
|
137
149
|
};
|
|
138
150
|
};
|
|
151
|
+
/**
|
|
152
|
+
* Notified after a write anywhere in an observed address range.
|
|
153
|
+
*
|
|
154
|
+
* `length` is the width of the access in bytes (4, 2 or 1) and `value` is what the program stored,
|
|
155
|
+
* so a byte or halfword store reports only the bytes it touched; a peripheral that mirrors whole
|
|
156
|
+
* words should re-read the containing word with `readMemoryBytes` rather than trust `value`.
|
|
157
|
+
*
|
|
158
|
+
* Both `address` and `value` are signed 32 bit integers, as the guest holds them: the register at
|
|
159
|
+
* `0xffff000c` arrives as `-65524`, and a pixel word with its high bit set arrives negative too.
|
|
160
|
+
* Apply `>>> 0` wherever the unsigned form is wanted.
|
|
161
|
+
*/
|
|
162
|
+
type MemoryWriteObserver = (address: number, length: number, value: number) => void;
|
|
163
|
+
/**
|
|
164
|
+
* Notified after a read or a write of one observed word, with the same signed 32 bit numbers as
|
|
165
|
+
* `MemoryWriteObserver`. `value` is the value the program read or stored; on a read it is what
|
|
166
|
+
* memory held *before* the observer ran, so a register whose value is consumed by reading it must
|
|
167
|
+
* be reloaded from the handler for the next read.
|
|
168
|
+
*/
|
|
169
|
+
type MemoryAccessObserver = (address: number, value: number) => void;
|
|
170
|
+
/** Identifies one registration, for `removeMemoryObserver`. */
|
|
171
|
+
type MemoryObserverHandle = number;
|
|
139
172
|
type JsInstruction = {
|
|
140
173
|
name: string;
|
|
141
174
|
example: string;
|
|
@@ -144,16 +177,30 @@ type JsInstruction = {
|
|
|
144
177
|
getIsRv64Only: () => boolean;
|
|
145
178
|
};
|
|
146
179
|
type RiscvTokenizedLine = {
|
|
147
|
-
|
|
180
|
+
sourcePath: string;
|
|
181
|
+
/** One-based line in `sourcePath`. */
|
|
182
|
+
sourceLine: number;
|
|
183
|
+
/** Exact line supplied in the source set. */
|
|
184
|
+
source: string;
|
|
185
|
+
/** Line after assembler substitutions such as `.eqv`. */
|
|
186
|
+
processedSource: string;
|
|
148
187
|
tokens: JsInstructionToken[];
|
|
149
188
|
};
|
|
189
|
+
type RISCVSourceLocation = {
|
|
190
|
+
sourcePath: string;
|
|
191
|
+
/** One-based line in `sourcePath`. */
|
|
192
|
+
sourceLine: number;
|
|
193
|
+
};
|
|
194
|
+
type RISCVSourceSet = Readonly<Record<string, string>>;
|
|
150
195
|
type RISCVAssembleError = {
|
|
151
196
|
isWarning: boolean;
|
|
152
197
|
message: string;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
198
|
+
macroExpansionTrace: RISCVSourceLocation[];
|
|
199
|
+
sourcePath: string;
|
|
200
|
+
/** One-based line in `sourcePath`. */
|
|
201
|
+
sourceLine: number;
|
|
202
|
+
/** One-based column in `sourcePath`. Zero only for diagnostics without a source location. */
|
|
203
|
+
sourceColumn: number;
|
|
157
204
|
};
|
|
158
205
|
type RISCVAssembleResult = {
|
|
159
206
|
report: string;
|
|
@@ -164,7 +211,7 @@ type RISCVAssembleResult = {
|
|
|
164
211
|
hasWarnings: boolean;
|
|
165
212
|
};
|
|
166
213
|
declare class RISCV {
|
|
167
|
-
static
|
|
214
|
+
static makeRiscVFromFiles: typeof makeRiscVFromFiles;
|
|
168
215
|
static initializeRISCV: typeof initializeRISCV;
|
|
169
216
|
static getInstructionSet(): JsInstruction[];
|
|
170
217
|
static setIs64Bit(is64Bit: boolean): void;
|
|
@@ -196,9 +243,9 @@ type JsRiscVStackFrame = {
|
|
|
196
243
|
* Represents a statement in the assembled program.
|
|
197
244
|
*/
|
|
198
245
|
interface JsProgramStatement {
|
|
199
|
-
/**
|
|
200
|
-
|
|
201
|
-
|
|
246
|
+
/** Canonical path of the original source file. */
|
|
247
|
+
readonly sourcePath: string;
|
|
248
|
+
/** The one-based line number in the original source file. */
|
|
202
249
|
readonly sourceLine: number;
|
|
203
250
|
/**
|
|
204
251
|
* The memory address of the instruction.
|
|
@@ -348,11 +395,8 @@ interface JsRiscV {
|
|
|
348
395
|
* @param address
|
|
349
396
|
*/
|
|
350
397
|
getStatementAtAddress(address: number): JsProgramStatement;
|
|
351
|
-
/**
|
|
352
|
-
|
|
353
|
-
* @param line
|
|
354
|
-
*/
|
|
355
|
-
getStatementAtSourceLine(line: number): JsProgramStatement;
|
|
398
|
+
/** Gets every machine statement generated from an original source location. */
|
|
399
|
+
getStatementsAtSourceLocation(sourcePath: string, sourceLine: number): JsProgramStatement[];
|
|
356
400
|
getTokenizedLines(): RiscvTokenizedLine[];
|
|
357
401
|
/**
|
|
358
402
|
* Checks if the simulation can be undone.
|
|
@@ -369,7 +413,7 @@ interface JsRiscV {
|
|
|
369
413
|
* @returns An array of `JsProgramStatement` objects representing the compiled program.
|
|
370
414
|
*/
|
|
371
415
|
getCompiledStatements(): JsProgramStatement[];
|
|
372
|
-
getParsedStatements():
|
|
416
|
+
getParsedStatements(): JsProgramStatement[];
|
|
373
417
|
/**
|
|
374
418
|
* Gets the label at the given address.
|
|
375
419
|
* @param address The memory address.
|
|
@@ -459,6 +503,9 @@ interface JsRiscV {
|
|
|
459
503
|
getUndoStack(): JsBackStep[];
|
|
460
504
|
/**
|
|
461
505
|
* Reads a sequence of bytes from memory.
|
|
506
|
+
*
|
|
507
|
+
* Reading through this method notifies no memory observer: inspecting memory from the host is
|
|
508
|
+
* not the program reading it, so a memory viewer never drives a memory-mapped register.
|
|
462
509
|
* @param address The starting memory address.
|
|
463
510
|
* @param length The number of bytes to read.
|
|
464
511
|
* @returns An array of bytes read from memory.
|
|
@@ -466,15 +513,63 @@ interface JsRiscV {
|
|
|
466
513
|
readMemoryBytes(address: number, length: number): number[];
|
|
467
514
|
/**
|
|
468
515
|
* Writes a sequence of bytes to memory.
|
|
516
|
+
*
|
|
517
|
+
* Unlike `readMemoryBytes`, this writes the way the program does: it notifies write observers
|
|
518
|
+
* and, while undo is enabled, records an undo step per byte. Use `setPeripheralWord` for a
|
|
519
|
+
* device keeping its own register up to date.
|
|
469
520
|
* @param address The starting memory address.
|
|
470
521
|
* @param bytes An array of bytes to write to memory.
|
|
471
522
|
*/
|
|
472
523
|
setMemoryBytes(address: number, bytes: number[]): void;
|
|
473
524
|
/**
|
|
474
|
-
*
|
|
475
|
-
*
|
|
525
|
+
* Writes one word as a peripheral would: no observer is notified and no undo step is recorded,
|
|
526
|
+
* because the write is not the program acting. This is how a device model refreshes a
|
|
527
|
+
* memory-mapped register - a ready bit, a pending character - without feeding its own observer
|
|
528
|
+
* or consuming undo history.
|
|
529
|
+
* @param address The word address, which must be word-aligned. Either form of a high address
|
|
530
|
+
* is accepted: `0xffff0000` and `0xffff0000 | 0` name the same word.
|
|
531
|
+
* @param value The 32 bit value to store, raw, without byte-order adjustment.
|
|
532
|
+
*/
|
|
533
|
+
setPeripheralWord(address: number, value: number): void;
|
|
534
|
+
/**
|
|
535
|
+
* Observes every write in an address range, the shape a framebuffer wants.
|
|
536
|
+
*
|
|
537
|
+
* Both addresses must be word-aligned, `endAddress` is inclusive and covers its whole word, and
|
|
538
|
+
* the range may not cross 0x80000000 (split it in two registrations instead); a range that
|
|
539
|
+
* breaks any of these throws. Either form of a high address is accepted: `0xffff0000` and
|
|
540
|
+
* `0xffff0000 | 0` name the same word. The handler runs synchronously inside the storing instruction, so
|
|
541
|
+
* it must be cheap and must not write back into its own range; a returned promise is ignored.
|
|
542
|
+
*
|
|
543
|
+
* Observers live on the simulator's memory, which assembling and initializing only clear the
|
|
544
|
+
* contents of, so a registration survives `assemble()` and `initialize()`. For the same reason
|
|
545
|
+
* it is shared by every `JsRiscV` instance: register once per page, or remove the previous
|
|
546
|
+
* registration before registering again for a newly built program.
|
|
547
|
+
*
|
|
548
|
+
* Notifications only start once a program has been assembled, and undo notifies too: restoring
|
|
549
|
+
* memory during `undo()` goes through the same stores, so an observed range reports the
|
|
550
|
+
* restored values as ordinary writes.
|
|
551
|
+
* @returns A handle for `removeMemoryObserver`.
|
|
552
|
+
*/
|
|
553
|
+
addMemoryWriteObserver(startAddress: number, endAddress: number, handler: MemoryWriteObserver): MemoryObserverHandle;
|
|
554
|
+
/**
|
|
555
|
+
* Observes reads and writes of a single word, the shape a memory-mapped register wants. The
|
|
556
|
+
* address must be word-aligned. Pass `null` for a direction you do not care about. The same
|
|
557
|
+
* lifetime and synchronous-handler rules as `addMemoryWriteObserver` apply.
|
|
558
|
+
* @returns A handle for `removeMemoryObserver`.
|
|
559
|
+
*/
|
|
560
|
+
addMemoryAccessObserver(address: number, onRead: MemoryAccessObserver | null, onWrite: MemoryAccessObserver | null): MemoryObserverHandle;
|
|
561
|
+
/**
|
|
562
|
+
* Removes one registration. An unknown handle is ignored.
|
|
563
|
+
*/
|
|
564
|
+
removeMemoryObserver(handle: MemoryObserverHandle): void;
|
|
565
|
+
/**
|
|
566
|
+
* Removes every registration.
|
|
567
|
+
*/
|
|
568
|
+
removeMemoryObservers(): void;
|
|
569
|
+
/**
|
|
570
|
+
* The number of live registrations, across every `JsRiscV` instance.
|
|
476
571
|
*/
|
|
477
|
-
|
|
572
|
+
countMemoryObservers(): number;
|
|
478
573
|
/**
|
|
479
574
|
* Gets the next statement to be executed.
|
|
480
575
|
* @returns The next `JsProgramStatement`.
|
|
@@ -493,15 +588,19 @@ interface JsRiscV {
|
|
|
493
588
|
terminated: boolean;
|
|
494
589
|
}
|
|
495
590
|
/**
|
|
496
|
-
* Creates a
|
|
497
|
-
*
|
|
591
|
+
* Creates a RISC-V simulator from a virtual source tree and its entry file.
|
|
592
|
+
*
|
|
593
|
+
* Source paths are canonical, root-relative, case-sensitive POSIX paths. The source set is
|
|
594
|
+
* snapshotted by this call; only the entry file and files reached through `.include` are assembled.
|
|
595
|
+
* @param files Source text keyed by canonical source path.
|
|
596
|
+
* @param entryFile Canonical path of the file from which include expansion starts.
|
|
498
597
|
* @returns A new `JsRiscV` object.
|
|
499
598
|
*/
|
|
500
|
-
declare function
|
|
599
|
+
declare function makeRiscVFromFiles(files: RISCVSourceSet, entryFile: string): JsRiscV;
|
|
501
600
|
/**
|
|
502
601
|
* Initializes the RISCV simulator.
|
|
503
602
|
*/
|
|
504
603
|
declare function initializeRISCV(): void;
|
|
505
604
|
declare function bigintToHighLow(value: bigint): [high: number, low: number];
|
|
506
605
|
|
|
507
|
-
export { BackStepAction, ConfirmResult, DialogType, type HandlerMap, type HandlerMapFns, type JsBackStep, type JsInstruction, type JsInstructionToken, type JsProgramStatement, type JsRiscV, type JsRiscVStackFrame, RISCV, type RISCVAssembleError, type RISCVAssembleResult, RISCVRegisters, RISCV_REGISTERS, type RegisterName, type RiscvTokenizedLine, StopReason, bigintToHighLow, registerHandlers, unimplementedHandler };
|
|
606
|
+
export { BackStepAction, ConfirmResult, DialogType, type HandlerMap, type HandlerMapFns, type JsBackStep, type JsInstruction, type JsInstructionToken, type JsProgramStatement, type JsRiscV, type JsRiscVStackFrame, type MemoryAccessObserver, type MemoryObserverHandle, type MemoryWriteObserver, RISCV, type RISCVAssembleError, type RISCVAssembleResult, RISCVRegisters, type RISCVSourceLocation, type RISCVSourceSet, RISCV_REGISTERS, type RegisterName, type RiscvTokenizedLine, StopReason, bigintToHighLow, makeRiscVFromFiles, registerHandlers, unimplementedHandler };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
type JsInstructionToken = {
|
|
2
|
-
|
|
2
|
+
/** One-based column in the processed source line. */
|
|
3
3
|
sourceColumn: number;
|
|
4
|
-
originalSourceLine: number;
|
|
5
4
|
value: string;
|
|
6
5
|
type: string;
|
|
7
6
|
};
|
|
@@ -119,10 +118,23 @@ type HandlerMap = {
|
|
|
119
118
|
in: [l: string];
|
|
120
119
|
out: void;
|
|
121
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* Syscall 32: the program asks to be suspended for this many milliseconds. Return a promise
|
|
123
|
+
* that settles when the wait is over to suspend the simulation without blocking the host.
|
|
124
|
+
*/
|
|
122
125
|
sleep: {
|
|
123
126
|
in: [milliseconds: number];
|
|
124
127
|
out: void;
|
|
125
128
|
};
|
|
129
|
+
/**
|
|
130
|
+
* Syscall 30: the program time in milliseconds, split by the syscall into a0 (low word) and
|
|
131
|
+
* a1 (high word). Answer with `Date.now()` for a live run, or with a virtual clock for a
|
|
132
|
+
* scripted one, so that elapsed-time output stays reproducible.
|
|
133
|
+
*/
|
|
134
|
+
time: {
|
|
135
|
+
in: [];
|
|
136
|
+
out: number;
|
|
137
|
+
};
|
|
126
138
|
stdIn: {
|
|
127
139
|
in: [buffer: number[], length: number];
|
|
128
140
|
out: void;
|
|
@@ -136,6 +148,27 @@ type HandlerMap = {
|
|
|
136
148
|
out: void;
|
|
137
149
|
};
|
|
138
150
|
};
|
|
151
|
+
/**
|
|
152
|
+
* Notified after a write anywhere in an observed address range.
|
|
153
|
+
*
|
|
154
|
+
* `length` is the width of the access in bytes (4, 2 or 1) and `value` is what the program stored,
|
|
155
|
+
* so a byte or halfword store reports only the bytes it touched; a peripheral that mirrors whole
|
|
156
|
+
* words should re-read the containing word with `readMemoryBytes` rather than trust `value`.
|
|
157
|
+
*
|
|
158
|
+
* Both `address` and `value` are signed 32 bit integers, as the guest holds them: the register at
|
|
159
|
+
* `0xffff000c` arrives as `-65524`, and a pixel word with its high bit set arrives negative too.
|
|
160
|
+
* Apply `>>> 0` wherever the unsigned form is wanted.
|
|
161
|
+
*/
|
|
162
|
+
type MemoryWriteObserver = (address: number, length: number, value: number) => void;
|
|
163
|
+
/**
|
|
164
|
+
* Notified after a read or a write of one observed word, with the same signed 32 bit numbers as
|
|
165
|
+
* `MemoryWriteObserver`. `value` is the value the program read or stored; on a read it is what
|
|
166
|
+
* memory held *before* the observer ran, so a register whose value is consumed by reading it must
|
|
167
|
+
* be reloaded from the handler for the next read.
|
|
168
|
+
*/
|
|
169
|
+
type MemoryAccessObserver = (address: number, value: number) => void;
|
|
170
|
+
/** Identifies one registration, for `removeMemoryObserver`. */
|
|
171
|
+
type MemoryObserverHandle = number;
|
|
139
172
|
type JsInstruction = {
|
|
140
173
|
name: string;
|
|
141
174
|
example: string;
|
|
@@ -144,16 +177,30 @@ type JsInstruction = {
|
|
|
144
177
|
getIsRv64Only: () => boolean;
|
|
145
178
|
};
|
|
146
179
|
type RiscvTokenizedLine = {
|
|
147
|
-
|
|
180
|
+
sourcePath: string;
|
|
181
|
+
/** One-based line in `sourcePath`. */
|
|
182
|
+
sourceLine: number;
|
|
183
|
+
/** Exact line supplied in the source set. */
|
|
184
|
+
source: string;
|
|
185
|
+
/** Line after assembler substitutions such as `.eqv`. */
|
|
186
|
+
processedSource: string;
|
|
148
187
|
tokens: JsInstructionToken[];
|
|
149
188
|
};
|
|
189
|
+
type RISCVSourceLocation = {
|
|
190
|
+
sourcePath: string;
|
|
191
|
+
/** One-based line in `sourcePath`. */
|
|
192
|
+
sourceLine: number;
|
|
193
|
+
};
|
|
194
|
+
type RISCVSourceSet = Readonly<Record<string, string>>;
|
|
150
195
|
type RISCVAssembleError = {
|
|
151
196
|
isWarning: boolean;
|
|
152
197
|
message: string;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
198
|
+
macroExpansionTrace: RISCVSourceLocation[];
|
|
199
|
+
sourcePath: string;
|
|
200
|
+
/** One-based line in `sourcePath`. */
|
|
201
|
+
sourceLine: number;
|
|
202
|
+
/** One-based column in `sourcePath`. Zero only for diagnostics without a source location. */
|
|
203
|
+
sourceColumn: number;
|
|
157
204
|
};
|
|
158
205
|
type RISCVAssembleResult = {
|
|
159
206
|
report: string;
|
|
@@ -164,7 +211,7 @@ type RISCVAssembleResult = {
|
|
|
164
211
|
hasWarnings: boolean;
|
|
165
212
|
};
|
|
166
213
|
declare class RISCV {
|
|
167
|
-
static
|
|
214
|
+
static makeRiscVFromFiles: typeof makeRiscVFromFiles;
|
|
168
215
|
static initializeRISCV: typeof initializeRISCV;
|
|
169
216
|
static getInstructionSet(): JsInstruction[];
|
|
170
217
|
static setIs64Bit(is64Bit: boolean): void;
|
|
@@ -196,9 +243,9 @@ type JsRiscVStackFrame = {
|
|
|
196
243
|
* Represents a statement in the assembled program.
|
|
197
244
|
*/
|
|
198
245
|
interface JsProgramStatement {
|
|
199
|
-
/**
|
|
200
|
-
|
|
201
|
-
|
|
246
|
+
/** Canonical path of the original source file. */
|
|
247
|
+
readonly sourcePath: string;
|
|
248
|
+
/** The one-based line number in the original source file. */
|
|
202
249
|
readonly sourceLine: number;
|
|
203
250
|
/**
|
|
204
251
|
* The memory address of the instruction.
|
|
@@ -348,11 +395,8 @@ interface JsRiscV {
|
|
|
348
395
|
* @param address
|
|
349
396
|
*/
|
|
350
397
|
getStatementAtAddress(address: number): JsProgramStatement;
|
|
351
|
-
/**
|
|
352
|
-
|
|
353
|
-
* @param line
|
|
354
|
-
*/
|
|
355
|
-
getStatementAtSourceLine(line: number): JsProgramStatement;
|
|
398
|
+
/** Gets every machine statement generated from an original source location. */
|
|
399
|
+
getStatementsAtSourceLocation(sourcePath: string, sourceLine: number): JsProgramStatement[];
|
|
356
400
|
getTokenizedLines(): RiscvTokenizedLine[];
|
|
357
401
|
/**
|
|
358
402
|
* Checks if the simulation can be undone.
|
|
@@ -369,7 +413,7 @@ interface JsRiscV {
|
|
|
369
413
|
* @returns An array of `JsProgramStatement` objects representing the compiled program.
|
|
370
414
|
*/
|
|
371
415
|
getCompiledStatements(): JsProgramStatement[];
|
|
372
|
-
getParsedStatements():
|
|
416
|
+
getParsedStatements(): JsProgramStatement[];
|
|
373
417
|
/**
|
|
374
418
|
* Gets the label at the given address.
|
|
375
419
|
* @param address The memory address.
|
|
@@ -459,6 +503,9 @@ interface JsRiscV {
|
|
|
459
503
|
getUndoStack(): JsBackStep[];
|
|
460
504
|
/**
|
|
461
505
|
* Reads a sequence of bytes from memory.
|
|
506
|
+
*
|
|
507
|
+
* Reading through this method notifies no memory observer: inspecting memory from the host is
|
|
508
|
+
* not the program reading it, so a memory viewer never drives a memory-mapped register.
|
|
462
509
|
* @param address The starting memory address.
|
|
463
510
|
* @param length The number of bytes to read.
|
|
464
511
|
* @returns An array of bytes read from memory.
|
|
@@ -466,15 +513,63 @@ interface JsRiscV {
|
|
|
466
513
|
readMemoryBytes(address: number, length: number): number[];
|
|
467
514
|
/**
|
|
468
515
|
* Writes a sequence of bytes to memory.
|
|
516
|
+
*
|
|
517
|
+
* Unlike `readMemoryBytes`, this writes the way the program does: it notifies write observers
|
|
518
|
+
* and, while undo is enabled, records an undo step per byte. Use `setPeripheralWord` for a
|
|
519
|
+
* device keeping its own register up to date.
|
|
469
520
|
* @param address The starting memory address.
|
|
470
521
|
* @param bytes An array of bytes to write to memory.
|
|
471
522
|
*/
|
|
472
523
|
setMemoryBytes(address: number, bytes: number[]): void;
|
|
473
524
|
/**
|
|
474
|
-
*
|
|
475
|
-
*
|
|
525
|
+
* Writes one word as a peripheral would: no observer is notified and no undo step is recorded,
|
|
526
|
+
* because the write is not the program acting. This is how a device model refreshes a
|
|
527
|
+
* memory-mapped register - a ready bit, a pending character - without feeding its own observer
|
|
528
|
+
* or consuming undo history.
|
|
529
|
+
* @param address The word address, which must be word-aligned. Either form of a high address
|
|
530
|
+
* is accepted: `0xffff0000` and `0xffff0000 | 0` name the same word.
|
|
531
|
+
* @param value The 32 bit value to store, raw, without byte-order adjustment.
|
|
532
|
+
*/
|
|
533
|
+
setPeripheralWord(address: number, value: number): void;
|
|
534
|
+
/**
|
|
535
|
+
* Observes every write in an address range, the shape a framebuffer wants.
|
|
536
|
+
*
|
|
537
|
+
* Both addresses must be word-aligned, `endAddress` is inclusive and covers its whole word, and
|
|
538
|
+
* the range may not cross 0x80000000 (split it in two registrations instead); a range that
|
|
539
|
+
* breaks any of these throws. Either form of a high address is accepted: `0xffff0000` and
|
|
540
|
+
* `0xffff0000 | 0` name the same word. The handler runs synchronously inside the storing instruction, so
|
|
541
|
+
* it must be cheap and must not write back into its own range; a returned promise is ignored.
|
|
542
|
+
*
|
|
543
|
+
* Observers live on the simulator's memory, which assembling and initializing only clear the
|
|
544
|
+
* contents of, so a registration survives `assemble()` and `initialize()`. For the same reason
|
|
545
|
+
* it is shared by every `JsRiscV` instance: register once per page, or remove the previous
|
|
546
|
+
* registration before registering again for a newly built program.
|
|
547
|
+
*
|
|
548
|
+
* Notifications only start once a program has been assembled, and undo notifies too: restoring
|
|
549
|
+
* memory during `undo()` goes through the same stores, so an observed range reports the
|
|
550
|
+
* restored values as ordinary writes.
|
|
551
|
+
* @returns A handle for `removeMemoryObserver`.
|
|
552
|
+
*/
|
|
553
|
+
addMemoryWriteObserver(startAddress: number, endAddress: number, handler: MemoryWriteObserver): MemoryObserverHandle;
|
|
554
|
+
/**
|
|
555
|
+
* Observes reads and writes of a single word, the shape a memory-mapped register wants. The
|
|
556
|
+
* address must be word-aligned. Pass `null` for a direction you do not care about. The same
|
|
557
|
+
* lifetime and synchronous-handler rules as `addMemoryWriteObserver` apply.
|
|
558
|
+
* @returns A handle for `removeMemoryObserver`.
|
|
559
|
+
*/
|
|
560
|
+
addMemoryAccessObserver(address: number, onRead: MemoryAccessObserver | null, onWrite: MemoryAccessObserver | null): MemoryObserverHandle;
|
|
561
|
+
/**
|
|
562
|
+
* Removes one registration. An unknown handle is ignored.
|
|
563
|
+
*/
|
|
564
|
+
removeMemoryObserver(handle: MemoryObserverHandle): void;
|
|
565
|
+
/**
|
|
566
|
+
* Removes every registration.
|
|
567
|
+
*/
|
|
568
|
+
removeMemoryObservers(): void;
|
|
569
|
+
/**
|
|
570
|
+
* The number of live registrations, across every `JsRiscV` instance.
|
|
476
571
|
*/
|
|
477
|
-
|
|
572
|
+
countMemoryObservers(): number;
|
|
478
573
|
/**
|
|
479
574
|
* Gets the next statement to be executed.
|
|
480
575
|
* @returns The next `JsProgramStatement`.
|
|
@@ -493,15 +588,19 @@ interface JsRiscV {
|
|
|
493
588
|
terminated: boolean;
|
|
494
589
|
}
|
|
495
590
|
/**
|
|
496
|
-
* Creates a
|
|
497
|
-
*
|
|
591
|
+
* Creates a RISC-V simulator from a virtual source tree and its entry file.
|
|
592
|
+
*
|
|
593
|
+
* Source paths are canonical, root-relative, case-sensitive POSIX paths. The source set is
|
|
594
|
+
* snapshotted by this call; only the entry file and files reached through `.include` are assembled.
|
|
595
|
+
* @param files Source text keyed by canonical source path.
|
|
596
|
+
* @param entryFile Canonical path of the file from which include expansion starts.
|
|
498
597
|
* @returns A new `JsRiscV` object.
|
|
499
598
|
*/
|
|
500
|
-
declare function
|
|
599
|
+
declare function makeRiscVFromFiles(files: RISCVSourceSet, entryFile: string): JsRiscV;
|
|
501
600
|
/**
|
|
502
601
|
* Initializes the RISCV simulator.
|
|
503
602
|
*/
|
|
504
603
|
declare function initializeRISCV(): void;
|
|
505
604
|
declare function bigintToHighLow(value: bigint): [high: number, low: number];
|
|
506
605
|
|
|
507
|
-
export { BackStepAction, ConfirmResult, DialogType, type HandlerMap, type HandlerMapFns, type JsBackStep, type JsInstruction, type JsInstructionToken, type JsProgramStatement, type JsRiscV, type JsRiscVStackFrame, RISCV, type RISCVAssembleError, type RISCVAssembleResult, RISCVRegisters, RISCV_REGISTERS, type RegisterName, type RiscvTokenizedLine, StopReason, bigintToHighLow, registerHandlers, unimplementedHandler };
|
|
606
|
+
export { BackStepAction, ConfirmResult, DialogType, type HandlerMap, type HandlerMapFns, type JsBackStep, type JsInstruction, type JsInstructionToken, type JsProgramStatement, type JsRiscV, type JsRiscVStackFrame, type MemoryAccessObserver, type MemoryObserverHandle, type MemoryWriteObserver, RISCV, type RISCVAssembleError, type RISCVAssembleResult, RISCVRegisters, type RISCVSourceLocation, type RISCVSourceSet, RISCV_REGISTERS, type RegisterName, type RiscvTokenizedLine, StopReason, bigintToHighLow, makeRiscVFromFiles, registerHandlers, unimplementedHandler };
|