CREATOR API (CAPI)¶
CAPI allows instruction definitions to interact with custom CREATOR functions.
Tip
CAPI is a global variable, so you can access it from your browser's developer console if you're using the web application.
Memory¶
Interaction with CREATOR's memory. At src/core/capi/memory.mts
CAPI.MEM.write¶
CAPI.MEM.write(
address: bigint,
bytes: number,
value: bigint,
reg_name?: string,
hint?: string,
noSegFault: boolean = true,
): void { }
Writes a specific value to the specified address and number of bytes. You can provide extra information about which register used to hold the value (reg_name), or any hint about what that value might be (hint). To enable checking if the address is in a writable segment, set noSegFault to false.
E.g.:
CAPI.MEM.read¶
CAPI.MEM.read(
address: bigint,
bytes: number,
reg_name: string,
noSegFault: boolean = true,
): bigint { }
Reads a specific number of bytes in the specified address and returns them. You can provide extra information about which register will hold the value (reg_name). To enable checking if the address is in a readable segment, set noSegFault to false.
E.g.:
CAPI.MEM.addHint¶
Adds a hint (description of what the address holds) for the specified memory address. If a hint already exists at the specified address, it replaces it. You can optionally specify the size of the stored type in bits (sizeInBits). Returns true if the hint was successfully added, else false.
E.g.:
CAPI.MEM.alloc¶
Allocates bytes bytes of memory and return the starting address.
E.g.:
CAPI.MEM.writeValueToMemory¶
Writes a value to memory respecting endianness configuration.
E.g.:
CAPI.MEM.readValueToMemory¶
Reads a value to memory respecting endianness configuration.
E.g.:
System calls¶
CREATOR's system calls.
CAPI.SYSCALL.exit¶
Terminates the execution of the program.
E.g.:
CAPI.SYSCALL.print¶
CAPI.SYSCALL.print(
value: number | bigint,
type: "int32" | "float" | "double" | "char" | "string",
): void { }
Prints the specified value of the specified type to the console.
Supported types are:
"int32": signed 32-bit integer"float"/"double": JS's Number"char": UTF-16 character value"string": address of a null-terminated array of 1-byte chars
E.g.:
CAPI.SYSCALL.read¶
CAPI.SYSCALL.read(
dest_reg_info: string,
type: "int32" | "float" | "double" | "char" | "string",
aux_info?: string,
): void { }
Reads the specified value type from the console and stores it in the specified register by name (dest_reg_info).
Supported types are:
"int32": signed 32-bit integer"float"/"double": JS's Number"char": UTF-16 character value"string":aux_infois the name of the register that holds the address of a null-terminated array of 1-byte chars
E.g.:
CAPI.SYSCALL.get_clk_cycles¶
Returns the number of clock cycles that have passed since the program started.
E.g.:
Validation¶
Checks and validations support. At src/core/capi/validation.mts
CAPI.SYSCALL.raise¶
Raises an error with a specific msg.
E.g.:
CAPI.VALIDATION.isOverflow¶
Checks if the result res_u of operating two operands op1 and op2 caused an overflow.
E.g.:
CAPI.VALIDATION.isMisaligned¶
Checks if the address addr is misaligned (it is not a multiple of word-size bytes or size bytes).
E.g.:
Stack¶
These functions are used for the stack tracker and sentinel modules, and are a way to tell CREATOR when a new function frame begins and ends. They should be included in the instructions that jump to, o return from, a routine, as is the case of RISC-V's jal and jr instructions.
At src/core/capi/stack.mts
CAPI.STACK.beginFrame¶
Marks the beginning of a function frame at address. If not specified, it takes the current (real) value of the program counter.
E.g.:
CAPI.STACK.endFrame¶
Ends the current stack frame.
E.g.:
Floating point¶
CAPI.FP.split_double¶
Given a double precision IEEE 754 value reg, gets the 32-bits most significant (index=1) bits or the least significant bits (index=0). It returns it as a string of bits.
E.g.:
CAPI.FP.uint2float32¶
Transforms the unsigned integer value to a single precision IEEE754 JS Number.
E.g.:
CAPI.FP.float322uint¶
Transforms the single precision floating point (JS Number) value into an unsigned integer.
E.g.:
CAPI.FP.int2uint¶
Transforms a signed integer (JS Number) value into an unsigned integer of bits number of bits.
E.g.:
CAPI.FP.uint2int¶
Transforms an unsigned integer value into a signed integer (JS Number) of bits number of bits.
E.g.:
CAPI.FP.uint2float64¶
Transforms an unsigned integer value0 into a signed integer to a 64-bit float (JS Number). Supports two calling conventions:
- Single
value0argument: converts a 64-bit integer directly - Two 32-bit arguments: converts low (
value0) and high (value1) 32-bit parts.
E.g.:
CAPI.FP.float642uint¶
Transforms the double precision floating point (JS Number) value into an unsigned integer.
E.g.:
CAPI.FP.check_ieee¶
Check the type of number is in IEEE 754 format. Returns a 10-bit mask where the position of the set bit indicates the type of the IEEE 754 number:
- 0 -> -inf
- 1 -> -normalized number
- 2 -> -non-normalized number
- 3 -> -0
- 4 -> +0
- 5 -> +non-normalized number
- 6 -> +normalized number
- 7 -> +inf
- 8 -> signaling NaN
- 9 -> quiet NaN
E.g.:
CAPI.FP.float2bin¶
Transforms the single precision floating point JS Number f into a binary string.
E.g.:
Registers¶
At src/core/capi/registers.mts
CAPI.REG.read¶
Returns the value stored in register name.
E.g.:
CAPI.REG.write¶
Stores value in register name.
E.g.:
Architecture (CAPI.ARCH)¶
CAPI.ARCH exposes the interface of the currently loaded architecture plugin.
The supported plugins are riscv, mips and z80.
RISC-V¶
The RISC-V architecture plugin provides utilities for handling floating point operations and immediate value generation.
generateLoadImmediate¶
Generates a sequence of RISC-V instructions to load an immediate value into a register. Returns a semicolon-separated string of instructions that can load values of any size into the destination register.
E.g.:
const instructions = CAPI.ARCH.generateLoadImmediate(0x12345678n, "t0");
// Returns: "lui t0, 0x12345;addiw t0, t0, 1656"
toJSNumberD¶
Converts a 64-bit register value to a JavaScript number when the D (double-precision floating point) extension is enabled. Returns a tuple containing the numeric value and a type string indicating the representation.
The function handles:
- Canonical 64-bit NaN (
0x7ff8000000000000n) →[NaN, "NaN64"] - NaN-boxed 32-bit floats (upper 32 bits all 1's) →
[value, "NaNBfloat32_64"] - Valid 64-bit doubles →
[value, "float64"] - Zero →
[0, "float32"]
E.g.:
toJSNumberS¶
Converts a 32-bit register value to a JavaScript number when only the S (single-precision floating point) extension is enabled. Returns a tuple containing the numeric value and a type string.
The function handles:
- Canonical 32-bit NaN (
0x7fc00000n) →[NaN, "NaN32"] - Valid 32-bit floats →
[value, "float32"] - Zero →
[0, "float32"]
E.g.:
toBigInt¶
Converts a JavaScript number to a bigint representation suitable for storing in a RISC-V register. The type parameter specifies the format.
Supported types:
"float32": Single-precision float"float64": Double-precision float"NaNBfloat32_64": NaN-boxed single-precision (upper 32 bits all 1's)"NaN64": Canonical 64-bit NaN"NaN32": Canonical 32-bit NaN
E.g.:
NaNBox¶
NaN-boxes a 32-bit floating point value for use in 64-bit floating point registers. Sets the upper 32 bits to all 1's as per the RISC-V specification (section 21.2).
E.g.:
MIPS¶
The MIPS architecture plugin provides utilities for handling double-precision floating point operations across register pairs.
validateEvenRegister¶
Validates that a register number is even. Required for double-precision operations in MIPS, which use register pairs. Throws an error if the register is not even.
E.g.:
readDouble¶
Reads a double-precision floating point value from a MIPS register pair. The regName must be even (e.g., "f0", "f2"). The function reads from the specified register and the next sequential register to reconstruct a 64-bit double.
E.g.:
writeDouble¶
Writes a double-precision floating point value to a MIPS register pair. The regName must be even. The function splits the value across the specified register and the next sequential register.
E.g.:
readDoublePair¶
Reads two double-precision values from register pairs and returns them as an array. Both register names must be even.
E.g.:
writeDoublePair¶
CAPI.ARCH.writeDoublePair(value1: number, value2: number, reg1Name: string, reg2Name: string): void { }
Writes two double-precision values to register pairs. Both register names must be even.
E.g.:
binaryDoubleOperation¶
CAPI.ARCH.binaryDoubleOperation(
destReg: string,
src1Reg: string,
src2Reg: string,
operation: (val1: number, val2: number) => number
): void { }
Performs a binary operation on two double-precision values. Reads from source register pairs, applies the operation function, and writes the result to the destination register pair. All register names must be even.
E.g.:
unaryDoubleOperation¶
CAPI.ARCH.unaryDoubleOperation(
destReg: string,
srcReg: string,
operation: (val: number) => number
): void { }
Performs a unary operation on a double-precision value. Reads from the source register pair, applies the operation function, and writes the result to the destination register pair. Both register names must be even.
E.g.:
Z80¶
The Z80 architecture plugin provides utilities for flag calculations, keyboard handling, and I/O operations.
Flag Constants¶
The following flag bit masks are available:
CAPI.ARCH.S_FLAG // 0x80 - Sign flag
CAPI.ARCH.Z_FLAG // 0x40 - Zero flag
CAPI.ARCH.H_FLAG // 0x10 - Half-carry flag
CAPI.ARCH.PV_FLAG // 0x04 - Parity/Overflow flag
CAPI.ARCH.N_FLAG // 0x02 - Add/Subtract flag
CAPI.ARCH.C_FLAG // 0x01 - Carry flag
State Variables¶
The following variables track the Z80 system state:
CAPI.ARCH.borderColor // Border color (0-7) for ZX Spectrum
CAPI.ARCH.interruptMode // Current interrupt mode (0, 1, or 2)
CAPI.ARCH.interruptPin // Interrupt pin state (0 = low, 1 = high)
CAPI.ARCH.timerCounter // Timer counter value (bigint)
CAPI.ARCH.keyState // Object mapping event.code strings to pressed state (boolean)
CAPI.ARCH.keyMap // Keyboard matrix mapping port high bytes to key codes
E.g.:
// Set interrupt mode 2
CAPI.ARCH.interruptMode = 2;
// Check if a key is pressed
if (CAPI.ARCH.keyState["KeyA"]) {
// Key A is pressed
}
// Set border color to red
CAPI.ARCH.borderColor = 2n;
calculateFlags_INC¶
Calculates flags for an 8-bit INC (increment) operation. Returns the new F register value with appropriate flags set. The C flag is preserved from initialF.
E.g.:
calculateFlags_DEC¶
Calculates flags for an 8-bit DEC (decrement) operation. Returns the new F register value. The C flag is preserved, and N flag is always set.
E.g.:
calculateFlags_ADD¶
Calculates flags for 8-bit addition (ADD). Returns the new F register value with all flags computed based on the operation.
E.g.:
calculateFlags_ADC¶
Calculates flags for 8-bit addition with carry (ADC). The carry bit is extracted from initialF.
E.g.:
calculateFlags_SUB¶
Calculates flags for 8-bit subtraction (SUB). The N flag is always set for subtraction operations.
E.g.:
calculateFlags_SBC¶
Calculates flags for 8-bit subtraction with carry (SBC). The carry bit is extracted from initialF.
E.g.:
calculateFlags_LOGICAL¶
Calculates flags for logical operations (AND, OR, XOR). Set setH to 1 for AND operations (sets H flag), or 0 for OR/XOR operations (resets H flag). N and C flags are always reset.
E.g.:
calculateFlags_CP¶
Calculates flags for the compare (CP) operation. Performs a subtraction for flag calculation purposes without storing the result.
E.g.:
calculateFlags_ADD16¶
Calculates flags for 16-bit addition (ADD HL, rr). The S, Z, and P/V flags are preserved from initialF. N flag is reset.
E.g.:
calculateFlags_SBC16¶
Calculates flags for 16-bit subtraction with carry (SBC HL, rr). The carry bit is extracted from initialF.
E.g.:
calculateFlags_BIT¶
Calculates flags for the BIT instruction, which tests a specific bit (0-7) in a value. The C flag is preserved from initialF, and H flag is always set. N flag is always reset.
E.g.:
calculateFlags_ROTATE¶
Calculates flags for rotate and shift instructions (RLC, RRC, SLA, etc.). The carry parameter should be 0n or 1n representing the bit that was shifted out. H and N flags are always reset.
E.g.:
const carry = registers.A >> 7n;
const result = ((registers.A << 1n) | carry) & 0xffn;
registers.F = CAPI.ARCH.calculateFlags_ROTATE(result, carry);
calculateFlags_SRL¶
Calculates flags for the SRL (Shift Right Logical) instruction. The carry parameter is the original bit 0 that was shifted out. S, H, and N flags are always reset.
E.g.:
const carry = registers.A & 1n;
const result = registers.A >> 1n;
registers.F = CAPI.ARCH.calculateFlags_SRL(result, carry);
pressKey¶
Registers a keyboard key as being pressed. The code parameter should be a JavaScript event.code string (e.g., "KeyA", "Digit1").
E.g.:
releaseKey¶
Registers a keyboard key as being released.
E.g.:
readULAKeyboard¶
Emulates a read from the ZX Spectrum ULA keyboard port. The high byte of the port address determines which half-row of keys to poll. Returns an 8-bit value where bits 0-4 represent key states (0 = pressed, 1 = not pressed), and bits 5-7 are typically high.
E.g.:
read¶
Reads a byte from an I/O port. Handles:
- Port
0x01: Keyboard buffer (non-blocking) - ULA ports (keyboard matrix): ZX Spectrum keyboard
- Unhandled ports: Returns
0xFF
E.g.:
write¶
Writes a byte to an I/O port. Handles:
- Port
0x02: Screen output - Port
0xFE: ZX Spectrum ULA (sets border color from bits 0-2) - Port
0x17: CREATOR-specific port for ecall (exits on value10n)
E.g.:
CAPI.ARCH.write(0x02n, 65n); // Write 'A' to screen
CAPI.ARCH.write(0xfen, 0x02n); // Set border color to red
Interrupts¶
Functions to manage interrupts and privilege modes.
For more information, see Interrupt Support.
CAPI.INTERRUPTS.setUserMode¶
Sets the privilege level to User.
E.g.:
CAPI.INTERRUPTS.setKernelMode¶
Sets the privilege level to Kernel.
E.g.:
CAPI.INTERRUPTS.create¶
Creates an interrupt of the specified type.
E.g.:
CAPI.INTERRUPTS.enable¶
Enables an interrupt type.
E.g.:
CAPI.INTERRUPTS.globalEnable¶
Globally enables interrupts.
E.g.:
CAPI.INTERRUPTS.disable¶
Disables an interrupt type.
E.g.:
CAPI.INTERRUPTS.globalDisable¶
Globally disables interrupts.
E.g.:
CAPI.INTERRUPTS.isEnabled¶
Checks if an interrupt type is enabled.
E.g.:
CAPI.INTERRUPTS.isGlobalEnabled¶
Checks if interrupts are globally enabled.
E.g.:
CAPI.INTERRUPTS.clear¶
Clears interrupts of the specified type.
E.g.:
CAPI.INTERRUPTS.globalClear¶
Clears all interrupts.
E.g.:
CAPI.INTERRUPTS.setCustomHandler¶
Sets the interrupt handler to the custom handler.
E.g.:
CAPI.INTERRUPTS.setCREATORHandler¶
Sets the interrupt handler to the default CREATOR handler.
E.g.:
CAPI.INTERRUPTS.setHighlight¶
Highlights the current instruction as "interrupted" in the UI.
E.g.:
CAPI.INTERRUPTS.clearHighlight¶
Removes the "interrupted" highlight in the UI. Typically used in instructions that return from the interrupt handler, such as RISC-V's mret.
E.g.: