typed registers in VM
This commit is contained in:
parent
8c1a107e94
commit
62ff97083f
|
@ -33,3 +33,9 @@ run-debug: ## run the app and wait for an inspector to connect
|
|||
upload-climate:
|
||||
cc-test-reporter format-coverage -t lcov -o $(TARGET_PATH)/coverage/codeclimate.json -p $(ROOT_PATH) $(TARGET_PATH)/coverage/lcov.info
|
||||
cc-test-reporter upload-coverage --debug -i $(TARGET_PATH)/coverage/codeclimate.json -r "$(shell echo "${CODECLIMATE_SECRET}" | base64 -d)"
|
||||
|
||||
run-assembly:
|
||||
node out/src/index.js assembly ./src/examples/loop.assembly.ssrp
|
||||
|
||||
run-abstract:
|
||||
node out/src/index.js abstract ./src/examples/loop.abstract.ssrp
|
|
@ -1,6 +1,6 @@
|
|||
import pegjs from 'pegjs';
|
||||
|
||||
import { FunctionNode, ProgramRegisters } from './ast.js';
|
||||
import { FunctionNode, RawRegisters } from './ast.js';
|
||||
import { loadFile } from './files.js';
|
||||
import { Codec, ProgramModule } from './module.js';
|
||||
|
||||
|
@ -13,7 +13,7 @@ export function loadGrammar() {
|
|||
|
||||
export interface ParserOutput {
|
||||
functions: Array<FunctionNode>;
|
||||
registers: Partial<ProgramRegisters>;
|
||||
registers: Partial<RawRegisters>;
|
||||
}
|
||||
|
||||
export function abstractToAST(source: string): ProgramModule {
|
||||
|
@ -42,4 +42,3 @@ export const codec: Codec = {
|
|||
load: abstractToAST,
|
||||
save: abstractFromAST,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import pegjs from 'pegjs';
|
||||
|
||||
import { CommandNode, ProgramRegisters } from './ast.js';
|
||||
import { CommandNode, RawRegisters } from './ast.js';
|
||||
import { loadFile } from './files.js';
|
||||
import { Codec, ProgramModule } from './module.js';
|
||||
|
||||
|
@ -13,7 +13,7 @@ export function loadGrammar() {
|
|||
|
||||
export interface ParserOutput {
|
||||
instructions: Array<CommandNode>;
|
||||
registers: Partial<ProgramRegisters>;
|
||||
registers: Partial<RawRegisters>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
16
src/ast.ts
16
src/ast.ts
|
@ -1,6 +1,4 @@
|
|||
export type Address = number;
|
||||
export type Counter = number;
|
||||
export type Register = number;
|
||||
|
||||
export enum OpCode {
|
||||
// flow
|
||||
|
@ -42,6 +40,13 @@ export enum CompareOperator {
|
|||
LESS_EQUAL = '<=',
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
type: 'address';
|
||||
module: number;
|
||||
function: number;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export interface AddressRegisters {
|
||||
PC: Address;
|
||||
RA: Address;
|
||||
|
@ -49,6 +54,11 @@ export interface AddressRegisters {
|
|||
IH: Address;
|
||||
}
|
||||
|
||||
export interface Register {
|
||||
type: 'data';
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface DataRegisters {
|
||||
R1: Register;
|
||||
R2: Register;
|
||||
|
@ -58,6 +68,8 @@ export interface DataRegisters {
|
|||
|
||||
export type ProgramRegisters = AddressRegisters & DataRegisters;
|
||||
|
||||
export type RawRegisters = {[K in keyof ProgramRegisters]: number};
|
||||
|
||||
export type RegisterName = keyof ProgramRegisters;
|
||||
|
||||
// nodes
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Address, FunctionNode, ProgramRegisters } from './ast';
|
||||
import { FunctionNode, ProgramRegisters, RawRegisters } from './ast';
|
||||
|
||||
export interface Table<TEntry> {
|
||||
names: Array<[string, Address]>;
|
||||
names: Array<[string, number]>;
|
||||
entries: Array<TEntry>;
|
||||
}
|
||||
|
||||
|
@ -12,13 +12,10 @@ export interface GlobalEntry {
|
|||
export interface ProgramModule {
|
||||
functions: Table<FunctionNode>;
|
||||
globals: Table<GlobalEntry>;
|
||||
registers: Partial<ProgramRegisters>;
|
||||
registers: Partial<RawRegisters>;
|
||||
}
|
||||
|
||||
export interface ProgramStack {
|
||||
counter: number;
|
||||
values: Array<number>;
|
||||
}
|
||||
export type ProgramStack = Array<number>;
|
||||
|
||||
export interface Program {
|
||||
registers: ProgramRegisters;
|
||||
|
|
111
src/runtime.ts
111
src/runtime.ts
|
@ -1,29 +1,56 @@
|
|||
import { BranchNode, CommandNode, MATH_OP_CODE, OpCode, ProgramRegisters, Register, RegisterName, ValueNode } from './ast.js';
|
||||
import { Program, ProgramModule, ProgramStack } from './module.js';
|
||||
import {
|
||||
Address,
|
||||
BranchNode,
|
||||
CommandNode,
|
||||
MATH_OP_CODE,
|
||||
OpCode,
|
||||
ProgramRegisters,
|
||||
RawRegisters,
|
||||
Register,
|
||||
RegisterName,
|
||||
ValueNode,
|
||||
} from './ast.js';
|
||||
import { Program, ProgramModule } from './module.js';
|
||||
|
||||
export function address(offset = 0): Address {
|
||||
return {
|
||||
type: 'address',
|
||||
module: 0,
|
||||
function: 0,
|
||||
offset,
|
||||
};
|
||||
}
|
||||
|
||||
export function register(value = 0): Register {
|
||||
return {
|
||||
type: 'data',
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function completeRegisters(partial: Partial<RawRegisters>): ProgramRegisters {
|
||||
return {
|
||||
PC: address(partial.PC),
|
||||
RA: address(partial.RA),
|
||||
EH: address(partial.EH),
|
||||
IH: address(partial.IH),
|
||||
R1: register(partial.R1),
|
||||
R2: register(partial.R2),
|
||||
R3: register(partial.R3),
|
||||
R4: register(partial.R4),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the memory for a program module to run.
|
||||
*/
|
||||
export function prep(runtime: ProgramModule, program: ProgramModule): Program {
|
||||
return {
|
||||
registers: {
|
||||
PC: 0,
|
||||
RA: 0,
|
||||
EH: 0,
|
||||
IH: 0,
|
||||
R1: 0,
|
||||
R2: 0,
|
||||
R3: 0,
|
||||
R4: 0,
|
||||
...program.registers,
|
||||
},
|
||||
registers: completeRegisters(program.registers),
|
||||
runtime,
|
||||
program,
|
||||
modules: [],
|
||||
stack: {
|
||||
counter: 0,
|
||||
values: [],
|
||||
},
|
||||
stack: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -39,23 +66,37 @@ export function getRegister(value?: ValueNode): RegisterName {
|
|||
}
|
||||
}
|
||||
|
||||
export function getValue(registers: ProgramRegisters, value?: ValueNode): Register {
|
||||
export function getValue(registers: ProgramRegisters, value?: ValueNode): number {
|
||||
if (value === null || value === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (value.type === 'register') {
|
||||
return registers[value.register];
|
||||
const register = registers[value.register];
|
||||
if (register.type === 'address') {
|
||||
return register.offset;
|
||||
} else {
|
||||
return register.value;
|
||||
}
|
||||
} else {
|
||||
return value.constant;
|
||||
}
|
||||
}
|
||||
|
||||
export function setValue(registers: ProgramRegisters, name: RegisterName, value: number): void {
|
||||
const register = registers[name];
|
||||
if (register.type === 'address') {
|
||||
register.offset = value;
|
||||
} else {
|
||||
register.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the program and execute one command.
|
||||
*/
|
||||
export function step(state: Program, fn: BranchNode, pc_offset = 0): boolean {
|
||||
const instruction = fn.body[state.registers.PC - pc_offset];
|
||||
const instruction = fn.body[state.registers.PC.offset - pc_offset];
|
||||
console.log('step', instruction, state.registers);
|
||||
|
||||
switch (instruction.type) {
|
||||
|
@ -67,14 +108,14 @@ export function step(state: Program, fn: BranchNode, pc_offset = 0): boolean {
|
|||
code: MATH_OP_CODE[instruction.op],
|
||||
};
|
||||
const result = stepCommand(command, state.registers, state.stack);
|
||||
state.registers.PC += result.step;
|
||||
state.registers.PC.offset += result.step;
|
||||
console.log('post', result, state.registers);
|
||||
return result.stop;
|
||||
}
|
||||
case 'command':
|
||||
{
|
||||
const result = stepCommand(instruction, state.registers, state.stack);
|
||||
state.registers.PC += result.step;
|
||||
state.registers.PC.offset += result.step;
|
||||
console.log('post', result, state.registers);
|
||||
return result.stop;
|
||||
}
|
||||
|
@ -86,20 +127,20 @@ export function step(state: Program, fn: BranchNode, pc_offset = 0): boolean {
|
|||
code: OpCode.COMPARE,
|
||||
};
|
||||
const result = stepCommand(command, state.registers, state.stack);
|
||||
state.registers.PC += 1; // abstract conditionals are always a single node
|
||||
state.registers.PC.offset += 1; // abstract conditionals are always a single node
|
||||
if (result.step === 1) {
|
||||
// step through body
|
||||
console.log('step body', instruction.body);
|
||||
state.registers.RA = state.registers.PC;
|
||||
for (let i = 0; i < instruction.body.length; ++i) {
|
||||
if (step(state, instruction, state.registers.RA) === true) {
|
||||
if (step(state, instruction, state.registers.RA.offset) === true) {
|
||||
return true;
|
||||
}
|
||||
if (state.registers.PC < state.registers.RA) {
|
||||
if (state.registers.PC.offset < state.registers.RA.offset) {
|
||||
// CALL to earlier code
|
||||
break;
|
||||
}
|
||||
if (state.registers.PC > state.registers.RA + instruction.body.length) {
|
||||
if (state.registers.PC.offset > state.registers.RA.offset + instruction.body.length) {
|
||||
// CALL to later code
|
||||
break;
|
||||
}
|
||||
|
@ -130,7 +171,7 @@ export interface StepResult {
|
|||
step: number;
|
||||
}
|
||||
|
||||
export function stepCommand(instruction: CommandNode, registers: ProgramRegisters, stack: ProgramStack): StepResult {
|
||||
export function stepCommand(instruction: CommandNode, registers: ProgramRegisters, stack: Array<number>): StepResult {
|
||||
switch (instruction.code) {
|
||||
case OpCode.STOP:
|
||||
return {
|
||||
|
@ -154,7 +195,7 @@ export function stepCommand(instruction: CommandNode, registers: ProgramRegister
|
|||
{
|
||||
const dest = getValue(registers, instruction.first);
|
||||
console.log('JUMP', dest);
|
||||
registers.PC = dest;
|
||||
setValue(registers, 'PC', dest);
|
||||
return {
|
||||
stop: false,
|
||||
step: 0,
|
||||
|
@ -164,7 +205,7 @@ export function stepCommand(instruction: CommandNode, registers: ProgramRegister
|
|||
{
|
||||
const dest = getValue(registers, instruction.first);
|
||||
console.log('CALL', dest);
|
||||
registers.PC = dest;
|
||||
registers.PC.offset = dest;
|
||||
return {
|
||||
stop: false,
|
||||
step: 0,
|
||||
|
@ -174,21 +215,21 @@ export function stepCommand(instruction: CommandNode, registers: ProgramRegister
|
|||
{
|
||||
const reg = getRegister(instruction.first);
|
||||
console.log('PEEK', reg);
|
||||
registers[reg] = stack.values[stack.values.length - 1] || 0;
|
||||
setValue(registers, reg, stack[stack.length - 1] || 0);
|
||||
break;
|
||||
}
|
||||
case OpCode.POP:
|
||||
{
|
||||
const reg = getRegister(instruction.first);
|
||||
console.log('POP', reg);
|
||||
registers[reg] = stack.values.pop() || 0;
|
||||
setValue(registers, reg, stack.pop() || 0);
|
||||
break;
|
||||
}
|
||||
case OpCode.PUSH:
|
||||
{
|
||||
const val = getValue(registers, instruction.first);
|
||||
console.log('PUSH', val);
|
||||
stack.values.push(val);
|
||||
stack.push(val);
|
||||
break;
|
||||
}
|
||||
case OpCode.ADD:
|
||||
|
@ -196,7 +237,7 @@ export function stepCommand(instruction: CommandNode, registers: ProgramRegister
|
|||
const reg = getRegister(instruction.first);
|
||||
const val = (getValue(registers, instruction.second) + getValue(registers, instruction.third)) % Number.MAX_SAFE_INTEGER;
|
||||
console.log('ADD', reg, val);
|
||||
registers[reg] = val;
|
||||
setValue(registers, reg, val);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -212,11 +253,11 @@ export function stepCommand(instruction: CommandNode, registers: ProgramRegister
|
|||
/**
|
||||
* Step the program until the function has been exhausted.
|
||||
*/
|
||||
export function runProgram(main: ProgramModule): ProgramStack {
|
||||
export function runProgram(main: ProgramModule): Array<number> {
|
||||
const state = prep(RUNTIME_MODULE, main);
|
||||
console.log('start');
|
||||
const fn = state.program.functions.entries[0];
|
||||
while (state.registers.PC < fn.body.length) {
|
||||
while (state.registers.PC.offset < fn.body.length) {
|
||||
if (step(state, fn) === true) {
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue