fix flame graph
This commit is contained in:
parent
344214f1a4
commit
5591b628db
@ -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() {
|
||||||
|
@ -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;
|
||||||
|
@ -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
55
compiler/compiler.ts
Normal 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
6
compiler/lib.ts
Normal 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 };
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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));
|
||||||
|
@ -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";
|
|
||||||
|
@ -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
20
slige-build-run-all.sh
Executable 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
|
||||||
|
|
@ -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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
25
web/main.ts
25
web/main.ts
@ -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());
|
||||||
|
@ -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())
|
||||||
|
@ -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);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user