fix flame graph

This commit is contained in:
sfja 2024-12-15 00:07:36 +01:00
parent 344214f1a4
commit 5591b628db
16 changed files with 341 additions and 240 deletions

View File

@ -8,6 +8,9 @@ export type Label = { label: string };
export type Lit = number | string | boolean | Label; export type Lit = number | string | boolean | Label;
export type Locs = { [key: string]: number };
export type Refs = { [key: number]: string };
export class Assembler { export class Assembler {
private lines: Line[] = []; private lines: Line[] = [];
private addedLabels: string[] = []; private addedLabels: string[] = [];
@ -42,12 +45,12 @@ export class Assembler {
this.addedLabels.push(label); this.addedLabels.push(label);
} }
public assemble(): number[] { public assemble(): { program: number[]; locs: Locs } {
console.log("Assembling..."); console.log("Assembling...");
let ip = 0; let ip = 0;
const output: number[] = []; const program: number[] = [];
const locs: { [key: string]: number } = {}; const locs: Locs = {};
const refs: { [key: number]: string } = {}; const refs: Refs = {};
let selectedLabel = ""; let selectedLabel = "";
for (const line of this.lines) { for (const line of this.lines) {
@ -62,20 +65,20 @@ export class Assembler {
} }
for (const lit of line.ins as Lit[]) { for (const lit of line.ins as Lit[]) {
if (typeof lit === "number") { if (typeof lit === "number") {
output.push(lit); program.push(lit);
ip += 1; ip += 1;
} else if (typeof lit === "boolean") { } else if (typeof lit === "boolean") {
output.push(lit ? 1 : 0); program.push(lit ? 1 : 0);
ip += 1; ip += 1;
} else if (typeof lit === "string") { } else if (typeof lit === "string") {
output.push(lit.length); program.push(lit.length);
ip += 1; ip += 1;
for (let i = 0; i < lit.length; ++i) { for (let i = 0; i < lit.length; ++i) {
output.push(lit.charCodeAt(i)); program.push(lit.charCodeAt(i));
ip += 1; ip += 1;
} }
} else { } else {
output.push(0); program.push(0);
refs[ip] = lit.label.startsWith(".") refs[ip] = lit.label.startsWith(".")
? `${selectedLabel}${lit.label}` ? `${selectedLabel}${lit.label}`
: refs[ip] = lit.label; : refs[ip] = lit.label;
@ -83,7 +86,7 @@ export class Assembler {
} }
} }
} }
for (let i = 0; i < output.length; ++i) { for (let i = 0; i < program.length; ++i) {
if (!(i in refs)) { if (!(i in refs)) {
continue; continue;
} }
@ -93,9 +96,9 @@ export class Assembler {
); );
continue; continue;
} }
output[i] = locs[refs[i]]; program[i] = locs[refs[i]];
} }
return output; return { program, locs };
} }
public printProgram() { public printProgram() {

View File

@ -1,13 +1,6 @@
import { Pos } from "./token.ts"; import { Pos } from "./token.ts";
import { VType } from "./vtype.ts"; import { VType } from "./vtype.ts";
export type Ast = File[];
export type File = {
path: string;
stmts: Stmt[];
};
export type Stmt = { export type Stmt = {
kind: StmtKind; kind: StmtKind;
pos: Pos; pos: Pos;

View File

@ -287,6 +287,12 @@ export class Checker {
return this.checkIfExpr(expr); return this.checkIfExpr(expr);
case "loop": case "loop":
return this.checkLoopExpr(expr); return this.checkLoopExpr(expr);
case "while":
case "for_in":
case "for":
throw new Error(
"invalid ast: special loops should be desugered",
);
case "block": case "block":
return this.checkBlockExpr(expr); return this.checkBlockExpr(expr);
} }

55
compiler/compiler.ts Normal file
View File

@ -0,0 +1,55 @@
import { AstCreator } from "./ast.ts";
import { Checker } from "./checker.ts";
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
import { Reporter } from "./info.ts";
import { Lexer } from "./lexer.ts";
import { FnNamesMap, Lowerer } from "./lowerer.ts";
import { Parser } from "./parser.ts";
import { Resolver } from "./resolver.ts";
export type CompiledFile = {
filepath: string;
program: number[];
};
export type CompileResult = {
program: number[];
fnNames: FnNamesMap;
};
export class Compiler {
private astCreator = new AstCreator();
private reporter = new Reporter();
public constructor(private startFilePath: string) {}
public async compile(): Promise<CompileResult> {
const text = await Deno.readTextFile(this.startFilePath);
const lexer = new Lexer(text, this.reporter);
const parser = new Parser(lexer, this.astCreator, this.reporter);
const ast = parser.parse();
new SpecialLoopDesugarer(this.astCreator).desugar(ast);
new Resolver(this.reporter).resolve(ast);
new CompoundAssignDesugarer(this.astCreator).desugar(ast);
new Checker(this.reporter).check(ast);
if (this.reporter.errorOccured()) {
console.error("Errors occurred, stopping compilation.");
Deno.exit(1);
}
const lowerer = new Lowerer(lexer.currentPos());
lowerer.lower(ast);
lowerer.printProgram();
const { program, fnNames } = lowerer.finish();
return { program, fnNames };
}
}

6
compiler/lib.ts Normal file
View File

@ -0,0 +1,6 @@
import { Compiler, CompileResult } from "./compiler.ts";
export async function compileWithDebug(path: string): Promise<CompileResult> {
const { program, fnNames } = await new Compiler(path).compile();
return { program, fnNames };
}

View File

@ -1,15 +1,17 @@
import { Builtins } from "./arch.ts"; import { Builtins, Ops } from "./arch.ts";
import { Expr, Stmt } from "./ast.ts"; import { Expr, Stmt } from "./ast.ts";
import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts"; import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts";
import { Ops } from "./mod.ts";
import { Assembler, Label } from "./assembler.ts"; import { Assembler, Label } from "./assembler.ts";
import { vtypeToString } from "./vtype.ts"; import { vtypeToString } from "./vtype.ts";
import { Pos } from "./token.ts"; import { Pos } from "./token.ts";
export type FnNamesMap = { [pc: number]: string };
export class Lowerer { export class Lowerer {
private program = new Assembler(); private program = new Assembler();
private locals: Locals = new LocalsFnRoot(); private locals: Locals = new LocalsFnRoot();
private fnStmtIdLabelMap: { [key: number]: string } = {}; private fnStmtIdLabelMap: { [stmtId: number]: string } = {};
private fnLabelNameMap: { [name: string]: string } = {};
private breakStack: Label[] = []; private breakStack: Label[] = [];
public constructor(private lastPos: Pos) {} public constructor(private lastPos: Pos) {}
@ -29,8 +31,15 @@ export class Lowerer {
this.program.add(Ops.Pop); this.program.add(Ops.Pop);
} }
public finish(): number[] { public finish(): { program: number[]; fnNames: FnNamesMap } {
return this.program.assemble(); const { program, locs } = this.program.assemble();
const fnNames: FnNamesMap = {};
for (const label in locs) {
if (label in this.fnLabelNameMap) {
fnNames[locs[label]] = this.fnLabelNameMap[label];
}
}
return { program, fnNames };
} }
private addSourceMap({ index, line, col }: Pos) { private addSourceMap({ index, line, col }: Pos) {
@ -139,6 +148,7 @@ export class Lowerer {
? "main" ? "main"
: `${stmt.kind.ident}_${stmt.id}`; : `${stmt.kind.ident}_${stmt.id}`;
this.program.setLabel({ label }); this.program.setLabel({ label });
this.fnLabelNameMap[label] = stmt.kind.ident;
this.addSourceMap(stmt.pos); this.addSourceMap(stmt.pos);
const outerLocals = this.locals; const outerLocals = this.locals;

View File

@ -1,50 +1,5 @@
import { Ast, AstCreator, File } from "./ast.ts"; import { Compiler } from "./compiler.ts";
import { stmtToString } from "./ast_visitor.ts";
import { Checker } from "./checker.ts";
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
import { Reporter } from "./info.ts";
import { Lexer } from "./lexer.ts";
import { Lowerer } from "./lowerer.ts";
import { Parser } from "./parser.ts";
import { Resolver } from "./resolver.ts";
class Compilation { const { program } = await new Compiler(Deno.args[0]).compile();
private files: File[] = [];
public constructor(private startFile: string) {}
public compile(): Ast {
throw new Error();
}
}
const text = await Deno.readTextFile(Deno.args[0]);
const astCreator = new AstCreator();
const reporter = new Reporter();
const lexer = new Lexer(text, reporter);
const parser = new Parser(lexer, astCreator, reporter);
const ast = parser.parse();
new SpecialLoopDesugarer(astCreator).desugar(ast);
new Resolver(reporter).resolve(ast);
new CompoundAssignDesugarer(astCreator).desugar(ast);
new Checker(reporter).check(ast);
if (reporter.errorOccured()) {
console.error("Errors occurred, stopping compilation.");
Deno.exit(1);
}
const lowerer = new Lowerer(lexer.currentPos());
lowerer.lower(ast);
lowerer.printProgram();
const program = lowerer.finish();
await Deno.writeTextFile("out.slgbc", JSON.stringify(program)); await Deno.writeTextFile("out.slgbc", JSON.stringify(program));

View File

@ -1,7 +1,2 @@
export * from "./parser.ts"; export { compileWithDebug } from "./lib.ts";
export * from "./ast.ts";
export * from "./arch.ts";
export * from "./lexer.ts";
export * from "./token.ts";
export * from "./temputils.ts";

View File

@ -1,31 +0,0 @@
import { Checker } from "./checker.ts";
import { Reporter } from "./info.ts";
import { Lexer } from "./lexer.ts";
import { Lowerer } from "./lowerer.ts";
import { Parser } from "./parser.ts";
import { Resolver } from "./resolver.ts";
/// TODO: find a better place for this function
export async function compileWithDebug(path: string): Promise<number[]> {
const text = await Deno.readTextFile(path);
const reporter = new Reporter();
const lexer = new Lexer(text, reporter);
const parser = new Parser(lexer, reporter);
const ast = parser.parse();
new Resolver(reporter).resolve(ast);
new Checker(reporter).check(ast);
if (reporter.errorOccured()) {
console.error("Errors occurred, stopping compilation.");
}
const lowerer = new Lowerer(lexer.currentPos());
lowerer.lower(ast);
lowerer.printProgram();
const program = lowerer.finish();
return program;
}

20
slige-build-run-all.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
set -e
FILE_FULL_PATH=$(readlink -f $1)
cd runtime
make
cd ..
cd web/public
deno task bundle
cd ../..
./runtime/build/sliger &
cd web
deno run --allow-net --allow-read main.ts $FILE_FULL_PATH

View File

@ -13,6 +13,7 @@
"jsr:@std/http@1": "1.0.10", "jsr:@std/http@1": "1.0.10",
"jsr:@std/io@0.224": "0.224.9", "jsr:@std/io@0.224": "0.224.9",
"jsr:@std/media-types@1": "1.1.0", "jsr:@std/media-types@1": "1.1.0",
"jsr:@std/path@*": "1.0.8",
"jsr:@std/path@1": "1.0.8", "jsr:@std/path@1": "1.0.8",
"npm:@types/node@*": "22.5.4", "npm:@types/node@*": "22.5.4",
"npm:path-to-regexp@6.2.1": "6.2.1" "npm:path-to-regexp@6.2.1": "6.2.1"
@ -39,7 +40,7 @@
"jsr:@std/http", "jsr:@std/http",
"jsr:@std/io", "jsr:@std/io",
"jsr:@std/media-types", "jsr:@std/media-types",
"jsr:@std/path", "jsr:@std/path@1",
"npm:path-to-regexp" "npm:path-to-regexp"
] ]
}, },

View File

@ -13,37 +13,32 @@ if (flags._.length !== 1) {
throw new Error("please specify a filename"); throw new Error("please specify a filename");
} }
//
const filepath = flags._[0] as string; const filepath = flags._[0] as string;
const text = await Deno.readTextFile(filepath); const text = await Deno.readTextFile(filepath);
const runtime = new Runtime(13370); const runtime = new Runtime(13370);
// await runtime.start();
async function compileProgram(filepath: string) {
const result = await compiler.compileWithDebug(filepath);
return result;
}
async function runProgramWithDebug(program: number[]) { async function runProgramWithDebug(program: number[]) {
console.log("connecting");
const connection = await runtime.connect(); const connection = await runtime.connect();
console.log("conneced");
connection.send({ connection.send({
type: "run-debug", type: "run-debug",
program, program,
}); });
console.log("sent");
const res = await connection.receive<{ const res = await connection.receive<{
ok: boolean; ok: boolean;
}>(); }>();
console.log("received");
connection.close(); connection.close();
if (!res.ok) { if (!res.ok) {
throw new Error("could not run code"); throw new Error("could not run code");
} }
} }
await compileProgram(filepath).then(runProgramWithDebug); const { program, fnNames } = await compiler.compileWithDebug(filepath);
await runProgramWithDebug(program);
//
const router = new Router(); const router = new Router();
@ -91,6 +86,12 @@ router.get("/api/flame-graph", async (ctx) => {
ctx.respond = true; ctx.respond = true;
}); });
router.get("/api/flame-graph-fn-names", (ctx) => {
ctx.response.body = { ok: true, fnNames };
ctx.response.status = 200;
ctx.respond = true;
});
router.get("/api/code-coverage", async (ctx) => { router.get("/api/code-coverage", async (ctx) => {
const connection = await runtime.connect(); const connection = await runtime.connect();
connection.send({ type: "code-coverage" }); connection.send({ type: "code-coverage" });
@ -110,6 +111,8 @@ router.get("/api/code-coverage", async (ctx) => {
ctx.respond = true; ctx.respond = true;
}); });
//
const app = new Application(); const app = new Application();
app.use(router.routes()); app.use(router.routes());
app.use(router.allowedMethods()); app.use(router.allowedMethods());

View File

@ -11,6 +11,14 @@ export async function flameGraphData(): Promise<FlameGraphNode> {
.then((v) => v.flameGraph); .then((v) => v.flameGraph);
} }
export type FlameGraphFnNames = { [key: number]: string };
export async function flameGraphFnNames(): Promise<FlameGraphFnNames> {
return await fetch("/api/flame-graph-fn-names")
.then((v) => v.json())
.then((v) => v.fnNames);
}
export async function codeData(): Promise<string> { export async function codeData(): Promise<string> {
return await fetch("/api/source") return await fetch("/api/source")
.then((v) => v.json()) .then((v) => v.json())

View File

@ -1,4 +1,5 @@
import * as data from "./data.ts"; import * as data from "./data.ts";
import { FlameGraphNode } from "./data.ts";
type Color = { r: number; g: number; b: number }; type Color = { r: number; g: number; b: number };
@ -130,39 +131,50 @@ function loadCodeCoverage(
}); });
} }
type FlameGraphFnNames = { [key: number]: string };
function loadFlameGraph( function loadFlameGraph(
flameGraphData: data.FlameGraphNode, flameGraphData: data.FlameGraphNode,
fnNames: FlameGraphFnNames, fnNames: data.FlameGraphFnNames,
flameGraphDiv: HTMLDivElement, flameGraphDiv: HTMLDivElement,
) { ) {
flameGraphDiv.innerHTML = ` flameGraphDiv.innerHTML = `
<div id="fg-background">
<div id="canvas-div">
<canvas id="flame-graph-canvas"></canvas> <canvas id="flame-graph-canvas"></canvas>
<span id="flame-graph-tooltip" hidden></span> <span id="flame-graph-tooltip" hidden></span>
</div>
</div>
<div id="toolbar">
<button id="flame-graph-reset">Reset</button>
</div>
`; `;
const canvas = document.querySelector<HTMLCanvasElement>( const canvas = document.querySelector<HTMLCanvasElement>(
"#flame-graph-canvas", "#flame-graph-canvas",
)!; )!;
const resetButton = document.querySelector<HTMLButtonElement>(
"#flame-graph-reset",
)!;
canvas.width = 1000; canvas.width = 1000;
canvas.height = 500; canvas.height = 500;
const ctx = canvas.getContext("2d")!; const fnNameFont = "600 14px monospace";
ctx.font = "16px monospace";
type Node = { const ctx = canvas.getContext("2d")!;
ctx.font = fnNameFont;
type CalcNode = {
x: number; x: number;
y: number; y: number;
w: number; w: number;
h: number; h: number;
title: string; title: string;
percent: string; percent: string;
fgNode: FlameGraphNode;
}; };
const nodes: Node[] = [];
function calculateNodeRects( function calculateNodeRects(
nodes: CalcNode[],
node: data.FlameGraphNode, node: data.FlameGraphNode,
depth: number, depth: number,
totalAcc: data.FlameGraphNode["acc"], totalAcc: data.FlameGraphNode["acc"],
@ -173,9 +185,11 @@ function loadFlameGraph(
const w = ((node.acc + 1) / totalAcc) * canvas.width; const w = ((node.acc + 1) / totalAcc) * canvas.width;
const h = 30; const h = 30;
const title = fnNames[node.fn]; const title = node.fn == 0
? "<program>"
: fnNames[node.fn] ?? "<unknown>";
const percent = `${(node.acc / totalAcc * 100).toFixed(1)}%`; const percent = `${(node.acc / totalAcc * 100).toFixed(1)}%`;
nodes.push({ x, y, w, h, title, percent }); nodes.push({ x, y, w, h, title, percent, fgNode: node });
const totalChildrenAcc = node.children.reduce( const totalChildrenAcc = node.children.reduce(
(acc, child) => acc + child.acc, (acc, child) => acc + child.acc,
@ -183,32 +197,43 @@ function loadFlameGraph(
); );
let newOffsetAcc = offsetAcc + (node.acc - totalChildrenAcc) / 2; let newOffsetAcc = offsetAcc + (node.acc - totalChildrenAcc) / 2;
for (const child of node.children) { for (const child of node.children) {
calculateNodeRects(child, depth + 1, totalAcc, newOffsetAcc); calculateNodeRects(nodes, child, depth + 1, totalAcc, newOffsetAcc);
newOffsetAcc += child.acc; newOffsetAcc += child.acc;
} }
} }
calculateNodeRects(flameGraphData, 0, flameGraphData.acc, 0);
for (const node of nodes) { function drawTextCanvas(node: CalcNode): HTMLCanvasElement {
const { x, y, w, h, title } = node; const { w, h, title } = node;
ctx.fillStyle = "rgb(255, 125, 0)"; const textCanvas = document.createElement("canvas");
ctx.fillRect( textCanvas.width = Math.max(w - 8, 1);
x + 1, textCanvas.height = h;
y + 1, const textCtx = textCanvas.getContext("2d")!;
w - 2, textCtx.font = fnNameFont;
h - 2, textCtx.fillStyle = "black";
); textCtx.fillText(
ctx.fillStyle = "black";
ctx.fillText(
title, title,
(x + (w - 10) / 2 - ctx.measureText(title).width / 2) + 5, ((w - 10) / 2 - ctx.measureText(title).width / 2) + 5 - 4,
y + 20, 20,
); );
return textCanvas;
} }
function renderNodes(nodes: CalcNode[]) {
for (const node of nodes) {
const { x, y, w, h } = node;
ctx.fillStyle = "rgb(255, 125, 0)";
ctx.fillRect(
x + 2,
y + 2,
w - 4,
h - 4,
);
const textCanvas = drawTextCanvas(node);
ctx.drawImage(textCanvas, x + 4, y);
}
const tooltip = document.getElementById("flame-graph-tooltip")!; const tooltip = document.getElementById("flame-graph-tooltip")!;
canvas.addEventListener("mousemove", (e) => { const mousemoveEvent = (e: MouseEvent) => {
const x = e.offsetX; const x = e.offsetX;
const y = e.offsetY; const y = e.offsetY;
const node = nodes.find((node) => const node = nodes.find((node) =>
@ -224,10 +249,50 @@ function loadFlameGraph(
tooltip.style.left = `${e.clientX + 20}px`; tooltip.style.left = `${e.clientX + 20}px`;
tooltip.style.top = `${e.clientY + 20}px`; tooltip.style.top = `${e.clientY + 20}px`;
tooltip.hidden = false; tooltip.hidden = false;
}); };
canvas.addEventListener("mouseleave", () => { const mouseleaveEvent = () => {
tooltip.hidden = true; tooltip.hidden = true;
};
const mousedownEvent = (e: MouseEvent) => {
const x = e.offsetX;
const y = e.offsetY;
const node = nodes.find((node) =>
x >= node.x && x < node.x + node.w && y >= node.y &&
y < node.y + node.h
);
if (!node) {
return;
}
tooltip.hidden = true;
const newNodes: CalcNode[] = [];
calculateNodeRects(
newNodes,
node.fgNode,
0,
node.fgNode.acc,
0,
);
canvas.removeEventListener("mousemove", mousemoveEvent);
canvas.removeEventListener("mouseleave", mouseleaveEvent);
canvas.removeEventListener("mousedown", mousedownEvent);
ctx.clearRect(0, 0, canvas.width, canvas.height);
renderNodes(newNodes);
};
canvas.addEventListener("mousemove", mousemoveEvent);
canvas.addEventListener("mouseleave", mouseleaveEvent);
canvas.addEventListener("mousedown", mousedownEvent);
}
resetButton.addEventListener("click", () => {
const nodes: CalcNode[] = [];
calculateNodeRects(nodes, flameGraphData, 0, flameGraphData.acc, 0);
renderNodes(nodes);
}); });
const nodes: CalcNode[] = [];
calculateNodeRects(nodes, flameGraphData, 0, flameGraphData.acc, 0);
renderNodes(nodes);
} }
async function main() { async function main() {
@ -263,22 +328,33 @@ async function main() {
const codeData = await data.codeData(); const codeData = await data.codeData();
const codeCoverageData = await data.codeCoverageData(); const codeCoverageData = await data.codeCoverageData();
const flameGraphData = await data.flameGraphData(); const flameGraphData = await data.flameGraphData();
const flameGraphFnNames = await data.flameGraphFnNames();
const view = document.querySelector("#view")!; const view = document.querySelector("#view")!;
const renderFunctions: RenderFns = { const renderFunctions: RenderFns = {
"source-code": () => { "source-code": () => {
const container = document.createElement("div"); const outerContainer = document.createElement("div");
container.classList.add("code-container"); outerContainer.classList.add("code-container");
const innerContainer = document.createElement("div");
innerContainer.classList.add("code-container-inner");
const lines = createLineElement(codeData); const lines = createLineElement(codeData);
const code = document.createElement("pre"); const code = document.createElement("pre");
code.classList.add("code-source"); code.classList.add("code-source");
code.innerText = codeData; code.innerText = codeData;
container.append(lines, code); innerContainer.append(lines, code);
view.replaceChildren(container); outerContainer.append(innerContainer);
view.replaceChildren(outerContainer);
}, },
"code-coverage": () => { "code-coverage": () => {
const container = document.createElement("div"); const outerContainer = document.createElement("div");
container.classList.add("code-container"); outerContainer.classList.add("code-container");
const innerContainer = document.createElement("div");
innerContainer.classList.add("code-container-inner");
const tooltip = document.createElement("div"); const tooltip = document.createElement("div");
tooltip.id = "covers-tooltip"; tooltip.id = "covers-tooltip";
tooltip.hidden = true; tooltip.hidden = true;
@ -291,19 +367,18 @@ async function main() {
tooltip, tooltip,
); );
const lines = createLineElement(codeData); const lines = createLineElement(codeData);
container.append(lines, code); innerContainer.append(lines, code);
outerContainer.append(innerContainer);
const view = document.querySelector("#view")!; const view = document.querySelector("#view")!;
view.replaceChildren(container, tooltip); view.replaceChildren(outerContainer, tooltip);
}, },
"flame-graph": () => { "flame-graph": () => {
const container = document.createElement("div"); const container = document.createElement("div");
container.classList.add("flame-graph");
container.id = "flame-graph";
const view = document.querySelector("#view")!; const view = document.querySelector("#view")!;
view.replaceChildren(container); view.replaceChildren(container);
loadFlameGraph(flameGraphData, { loadFlameGraph(flameGraphData, flameGraphFnNames, container);
0: "<entry>",
12: "add",
18: "main",
}, container);
}, },
}; };

View File

@ -6,10 +6,10 @@
--bg-2: #313338; --bg-2: #313338;
--fg-2: #666666; --fg-2: #666666;
--black: #211F1C; --black: #211f1c;
--black-transparent: #211F1Caa; --black-transparent: #211f1caa;
--white: #ECEBE9; --white: #ecebe9;
--white-transparent: #ECEBE9aa; --white-transparent: #ecebe9aa;
--code-status: var(--white); --code-status: var(--white);
} }
@ -25,15 +25,15 @@ body {
} }
body.status-error { body.status-error {
--code-status: #FF595E; --code-status: #ff595e;
} }
body.status-waiting { body.status-waiting {
--code-status: #E3B23C; --code-status: #e3b23c;
} }
body.status-done { body.status-done {
--code-status: #63A46C; --code-status: #63a46c;
} }
main { main {
@ -72,10 +72,11 @@ main #cover {
#views-nav { #views-nav {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 1rem; padding: 1rem 0;
border-radius: 0.25rem; border-radius: 0.25rem;
border: 2px solid var(--code-status); border: 2px solid var(--code-status);
gap: 0.5rem; gap: 0.5rem;
min-width: 200px;
} }
#views-nav input { #views-nav input {
@ -87,40 +88,62 @@ main #cover {
padding: 0.4em; padding: 0.4em;
padding-bottom: 0.2em; padding-bottom: 0.2em;
cursor: pointer; cursor: pointer;
width: 100%;
text-align: center;
}
#views-nav label:hover {
background-color: rgba(255, 255, 255, 0.2);
}
#view {
overflow: hidden;
} }
#view .code-container { #view .code-container {
max-height: 100%;
overflow: scroll;
background-color: rgba(255, 255, 255, 0.1);
padding: 0.5rem;
border-radius: 0.5rem;
}
#view .code-container-inner {
display: flex; display: flex;
font-size: 1rem; font-size: 1rem;
overflow: scroll;
max-height: 100%;
} }
#view .code-lines { #view .code-lines {
background-color: rgba(255, 255, 255, 0.1);
color: var(--white-transparent); color: var(--white-transparent);
border-right: 1px solid currentcolor; border-right: 1px solid currentcolor;
padding: 0.5rem; padding-right: 0.5rem;
border-radius: 0.5rem 0 0 0.5rem; margin: 0;
} }
#view .code-source { #view .code-source {
background-color: rgba(255, 255, 255, 0.1); width: 100%;
padding: 0.5rem; padding-left: 0.5rem;
border-radius: 0 0.5rem 0.5rem 0; margin: 0;
} }
#view-nav div {
width: 100%;
}
#views-nav input:checked + label { #views-nav input:checked + label {
background-color: var(--code-status); background-color: var(--code-status);
color: var(--black); color: var(--black);
} }
#views-layout { #views-layout {
display: flex; display: flex;
margin: 0 auto; margin: 0 auto;
padding: 1rem; padding: 1rem;
gap: 1rem; gap: 1rem;
max-width: 1500px; max-width: 1500px;
height: calc(100vh - 100px);
} }
#covers-tooltip { #covers-tooltip {
@ -137,11 +160,21 @@ main #cover {
} }
#flame-graph { #flame-graph {
width: min-content;
}
#flame-graph #fg-background {
background-color: rgba(255, 255, 255, 0.1);
padding: 0.5rem;
border-radius: 0.5rem;
width: min-content;
}
#flame-graph #canvas-div {
width: 1004px; width: 1004px;
height: 504px; height: 504px;
background-color: var(--bg-2); /*border: 2px solid rgb(240, 220, 200);*/
border: 2px solid rgb(240, 220, 200); padding: 4px;
padding: 2px;
} }
#flame-graph canvas { #flame-graph canvas {
z-index: 1; z-index: 1;
@ -151,6 +184,17 @@ main #cover {
image-rendering: pixelated; image-rendering: pixelated;
transform: translate(-2px, -2px); transform: translate(-2px, -2px);
} }
#flame-graph #toolbar {
margin: 20px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
#flame-graph #toolbar button {
padding: 5px 20px;
min-width: 100px;
}
#flame-graph #flame-graph-tooltip { #flame-graph #flame-graph-tooltip {
z-index: 2; z-index: 2;
position: fixed; position: fixed;

View File

@ -1,49 +1,7 @@
export class Runtime { export class Runtime {
private runtimeProcess?: Deno.ChildProcess;
constructor(private port: number) {} constructor(private port: number) {}
async checkRuntimeRev() {
const currentRev = new TextDecoder().decode(
await new Deno.Command("git", { args: ["rev-parse", "HEAD"] })
.output()
.then((output) => output.stdout),
).trim();
const runtimeRev = (await Deno.readTextFile("../runtime/build/rev"))
.trim();
if (runtimeRev !== currentRev) {
console.error(
"runtime out-of-date; run 'make' inside runtime/ folder",
);
Deno.exit(1);
}
}
async start() {
await this.checkRuntimeRev();
this.runtimeProcess = new Deno.Command("../runtime/build/sliger", {
args: [],
stdout: "piped",
}).spawn();
}
stop() {
this.runtimeProcess?.kill();
this.runtimeProcess = undefined;
}
async connect(): Promise<RuntimeConnection> { async connect(): Promise<RuntimeConnection> {
// return await new Promise((resolve) => {
// setTimeout(async () => {
// resolve(
// new RuntimeConnection(
// await Deno.connect({
// port: this.port,
// }),
// ),
// );
// }, 100);
// });
return new RuntimeConnection( return new RuntimeConnection(
await Deno.connect({ await Deno.connect({
port: this.port, port: this.port,