Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Super duper dumb recompiler. #44

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
assembly/generated-pvm.ts
tests/*
22 changes: 17 additions & 5 deletions assembly/api-generic.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { RELEVANT_ARGS } from "./arguments";
import * as compiler from "./compile/compiler";
import { INSTRUCTIONS, MISSING_INSTRUCTION } from "./instructions";
import { Interpreter, Status } from "./interpreter";
import { Memory, MemoryBuilder } from "./memory";
import { Access, PAGE_SIZE } from "./memory-page";
import { Program, decodeArguments, decodeProgram, liftBytes, resolveArguments } from "./program";
import { NO_OF_REGISTERS, Registers } from "./registers";
import { NO_OF_REGISTERS, newRegisters } from "./registers";

export class InitialPage {
address: u32 = 0;
Expand Down Expand Up @@ -66,17 +67,28 @@ export function getAssembly(p: Program): string {
return v;
}

export function compile(input: VmInput, _useSbrkGas: boolean = false): string {
const program = decodeProgram(liftBytes(input.program));
const registers = newRegisters();
for (let r = 0; r < registers.length; r++) {
registers[r] = input.registers[r];
}

const result = compiler.compile(program, input.pc, input.gas, registers);
return result;
}

export function runVm(input: VmInput, logs: boolean = false, useSbrkGas: boolean = false): VmOutput {
const p = decodeProgram(liftBytes(input.program));
const program = decodeProgram(liftBytes(input.program));

const registers: Registers = new StaticArray(NO_OF_REGISTERS);
const registers = newRegisters();
for (let r = 0; r < registers.length; r++) {
registers[r] = input.registers[r];
}
const builder = new MemoryBuilder();
const memory = buildMemory(builder, input.pageMap, input.memory);

const int = new Interpreter(p, registers, memory);
const int = new Interpreter(program, registers, memory);
int.useSbrkGas = useSbrkGas;
int.nextPc = input.pc;
int.gas.set(input.gas);
Expand All @@ -97,7 +109,7 @@ export function runVm(input: VmInput, logs: boolean = false, useSbrkGas: boolean
if (logs) {
const instruction = int.pc < u32(int.program.code.length) ? int.program.code[int.pc] : 0;
const iData = instruction >= <u8>INSTRUCTIONS.length ? MISSING_INSTRUCTION : INSTRUCTIONS[instruction];
const skipBytes = p.mask.bytesToNextInstruction(int.pc);
const skipBytes = program.mask.bytesToNextInstruction(int.pc);
const name = changetype<string>(iData.namePtr);
console.log(`INSTRUCTION = ${name} (${instruction})`);
const args = resolveArguments(iData.kind, int.program.code.subarray(int.pc + 1), skipBytes, int.registers);
Expand Down
70 changes: 70 additions & 0 deletions assembly/compile-gen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env node

// generate files that produce a compiled version
import { readFileSync, readdirSync, writeFileSync } from "node:fs";

main();

function main() {
// instructions-compile
const exe = read("./instructions-exe.ts").replace("RUN", "COMPILE");
write("./compile/instructions.ts", exe);
// instruction files
const files = readdirSync("./instructions").filter((x) => !x.endsWith(".test.ts"));

for (const f of files) {
let c = read(`./instructions/${f}`);
if (f === "outcome.ts" || f === "utils.ts") {
c = c.replace(/\(args: Args.*/, "(context: CompileContext, args: Args) => void");
c = c.replace(/"\.\.\//g, '"../../');
c = `import {CompileContext} from "../context";\n\n ${c}`;
} else {
// make sure we get the context parameter
c = c.replace(/\(args[,)].*/g, "(ctx, args) => {");
c = c.replace(/\(\) =>/g, "(ctx) =>");
// now replace all content
const regex = /({\n)(.*?)(\n};)/gs;
c = c.replace(regex, (match, start, content, end) => {
return replaceWithCompiled(match, start, content, end);
});
}
write(`./compile/instructions/${f}`, c);
}
}

function replaceWithCompiled(_match, start, content, end) {
const contentArray = content.split("\n");
const newContent = contentArray
.map((v) => {
let x = v;
x = x.replace(/registers/g, "regs");
x = x.replace(/regs\[/g, "regs[${");
x = x.replace(/\]/g, "}]");
x = x.replace(/u32SignExtend\((args.[^>]?)\)/g, "${u32SignExtend($1)}");
if (x.indexOf("return") !== -1) {
if (x.indexOf("ok()") !== -1) {
return "";
}

// returns processed in context
x = `ctx.${x.replace(/^\s*return /g, "")}`;
// args to okOrFault should be stringified
x = x.replace(/okOrFault\((.*?)\)/, 'okOrFault("$1")');
x = x.replace(/dJump\((.*?)\)/, 'dJump("$1")');
return x;
}
return ` ctx.addBlockLine(\`${x}\`, args);`;
})
.filter((x) => x.length > 0)
.join("\n");

return `${start}${newContent}\n${end}`;
}

function read(path) {
return readFileSync(path, "utf8");
}
function write(path, content) {
const v = "// This file is auto-generated, take a look at compile-gen.js\n\n";
return writeFileSync(path, v + content);
}
1 change: 1 addition & 0 deletions assembly/compile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
instructions.ts
71 changes: 71 additions & 0 deletions assembly/compile/compiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { INSTRUCTIONS, MISSING_INSTRUCTION } from "../instructions";
import { Program, decodeArguments } from "../program";
import { Registers } from "../registers";
import { CompileContext } from "./context";
import { COMPILE } from "./instructions";

export function compile(program: Program, initialPc: u32, gas: i64, registers: Registers): string {
const ctx = new CompileContext();
// externalities
ctx.push("declare function trap(): void;");
ctx.push("declare function outOfGas(): void;");
ctx.push("declare function fault(address: u32): void;");
ctx.push("declare function dumpReg(idx: u32, data: i64): void;");

for (let i = 0; i < registers.length; i++) {
ctx.push(`let regs${i}: i64 = 0x${registers[i].toString(16)};`);
}
ctx.push(`\nlet gas = 0x${gas.toString(16)};\n`);

// initial entry point
ctx.push(`\nblock${initialPc}();\n`);

// program
ctx.pc = 0;
let blockGas: i64 = 0;
while (ctx.pc < <u32>program.code.length) {
if (!program.mask.isInstruction(ctx.pc)) {
throw new Error("not an instruction?");
}

if (program.basicBlocks.isStart(ctx.pc)) {
if (ctx.pc > 0) {
ctx.endBlock(blockGas);
}
ctx.startBlock(ctx.pc);
blockGas = 0;
}

const instruction = program.code[ctx.pc];
const iData = <i32>instruction < INSTRUCTIONS.length ? INSTRUCTIONS[instruction] : MISSING_INSTRUCTION;

blockGas += iData.gas;
const skipBytes = program.mask.bytesToNextInstruction(ctx.pc);
const args = decodeArguments(iData.kind, program.code.subarray(ctx.pc + 1), skipBytes);
// TODO gas stuff?
const exe = COMPILE[instruction];
ctx.addBlockLine(`{ // ${changetype<string>(iData.namePtr)}`);
// handle jumps and other results?
exe(ctx, args);
ctx.addBlockLine("};");
// move to next instruction
ctx.pc += 1 + skipBytes;
}

ctx.endBlock(blockGas);

// utility functions
ctx.push("// utils");
ctx.push("@inline function u32SignExtend(v: u32): i64 { return i64(i32(v)); }");
ctx.push("@inline function u16SignExtend(v: u16): i64 { return i64(i32(i16(v))); }");
ctx.push("@inline function u8SignExtend(v: u8): i64 { return i64(i32(i16(i8(v)))); }");

// print out registers at the end
ctx.push("\n// registers");
for (let i = 0; i < registers.length; i++) {
ctx.push(`dumpReg(${i}, regs${i});`);
}

// TODO [ToDr] print out what we have here?
return ctx.data.join("\n");
}
68 changes: 68 additions & 0 deletions assembly/compile/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Args } from "../arguments";
import { NO_OF_REGISTERS } from "../registers";

export class CompileContext {
public readonly data: string[] = [];
public readonly bufferedBlock: string[] = [];
public pc: u32 = 0;

flushBlockBuffer(): void {
for (let i = 0; i < this.bufferedBlock.length; i++) {
this.data.push(this.bufferedBlock[i]);
}
this.bufferedBlock.length = 0;
}

push(v: string): void {
this.data.push(v);
}

addBlockLine(x: string, args: Args = new Args()): void {
let v = x;
// some replacing happens here
v = v
.replace("args.a", `${args.a}`)
.replace("args.b", `${args.b}`)
.replace("args.c", `${args.c}`)
.replace("args.d", `${args.d}`);
for (let i = 0; i < NO_OF_REGISTERS; i++) {
for (let j = 0; j < 3; j++) {
v = v.replace(`regs[${i}]`, `regs${i}`);
}
}

this.bufferedBlock.push(` ${v}`);
}

staticJump(arg: u32): void {
this.addBlockLine(` return block${this.pc + arg}(); // ${this.pc} + ${arg}`);
}

panic(): void {
this.addBlockLine(" trap(); abort();");
}

hostCall(num: u32): void {
this.addBlockLine(` host(${num})`);
}

okOrFault(v: string): void {
this.addBlockLine(` if (${v}.isFault) { fault(0); abort(); }`);
}

dJump(v: string): void {
// TODO ToDr this must be using the jump table
this.addBlockLine(` // dynamic jump to ${v}`);
}

startBlock(pc: u32): void {
this.push(`function block${pc}(): void {`);
}

endBlock(blockGas: i64): void {
this.push(` gas -= ${blockGas};`);
this.push(" if (gas < 0) { outOfGas(); }");
this.flushBlockBuffer();
this.push("}");
}
}
1 change: 1 addition & 0 deletions assembly/compile/instructions/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*
2 changes: 1 addition & 1 deletion assembly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { VmInput, VmOutput, getAssembly, runVm } from "./api-generic";
import { decodeProgram, decodeSpi, liftBytes } from "./program";

export * from "./api";
export { runVm, getAssembly } from "./api-generic";
export { runVm, compile, getAssembly } from "./api-generic";
export { wrapAsProgram } from "./program-build";

export enum InputKind {
Expand Down
4 changes: 3 additions & 1 deletion assembly/instructions/jump.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { InstructionRun, dJump, staticJump } from "./outcome";
import { reg, u32SignExtend } from "./utils";

// JUMP
export const jump: InstructionRun = (args) => staticJump(args.a);
export const jump: InstructionRun = (args) => {
return staticJump(args.a);
};

// JUMP_IND
export const jump_ind: InstructionRun = (args, registers) => {
Expand Down
16 changes: 12 additions & 4 deletions assembly/instructions/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@ import { InstructionRun, hostCall, ok, okOrFault, panic } from "./outcome";
import { reg } from "./utils";

// INVALID
export const INVALID: InstructionRun = () => panic();
export const INVALID: InstructionRun = () => {
return panic();
};

// TRAP
export const trap: InstructionRun = () => panic();
export const trap: InstructionRun = () => {
return panic();
};

// FALLTHROUGH
export const fallthrough: InstructionRun = () => ok();
export const fallthrough: InstructionRun = () => {
return ok();
};

// ECALLI
export const ecalli: InstructionRun = (args) => hostCall(args.a);
export const ecalli: InstructionRun = (args) => {
return hostCall(args.a);
};

// SBRK
export const sbrk: InstructionRun = (args, registers, memory) => {
Expand Down
Empty file removed assembly/math.ts
Empty file.
11 changes: 5 additions & 6 deletions bin/disassemble.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node

import fs from 'node:fs';
import fs from "node:fs";
import { InputKind, disassemble } from "../build/release.js";

main();
Expand All @@ -9,7 +9,7 @@ function main() {
const args = process.argv.slice(2);

let kind = InputKind.Generic;
if (args.length > 0 && args[0] === '--spi') {
if (args.length > 0 && args[0] === "--spi") {
args.shift();
kind = InputKind.SPI;
}
Expand All @@ -20,11 +20,10 @@ function main() {
process.exit(1);
}


args.forEach(arg => {
for (const arg of args) {
const f = fs.readFileSync(arg);
const name = kind === InputKind.Generic ? 'generic PVM' : 'JAM SPI';
const name = kind === InputKind.Generic ? "generic PVM" : "JAM SPI";
console.log(`🤖 Assembly of ${arg} (as ${name})`);
console.log(disassemble(Array.from(f), kind));
});
}
}
Loading