fix labels, return, code coverage

This commit is contained in:
sfja 2024-12-17 02:10:11 +01:00
parent b2bdf471f0
commit 77c01e12a5
12 changed files with 150 additions and 63 deletions

View File

@ -22,6 +22,8 @@ export const Ops = {
Jump: 0x0e, Jump: 0x0e,
JumpIfTrue: 0x0f, JumpIfTrue: 0x0f,
Builtin: 0x10, Builtin: 0x10,
Duplicate: 0x11,
Swap: 0x12,
Add: 0x20, Add: 0x20,
Subtract: 0x21, Subtract: 0x21,
Multiply: 0x22, Multiply: 0x22,

View File

@ -1,4 +1,4 @@
import { Ops, opToString } from "./arch.ts"; import { opToString } from "./arch.ts";
export type Line = { labels?: string[]; ins: Ins }; export type Line = { labels?: string[]; ins: Ins };
@ -14,7 +14,28 @@ export type Refs = { [key: number]: string };
export class Assembler { export class Assembler {
private lines: Line[] = []; private lines: Line[] = [];
private addedLabels: string[] = []; private addedLabels: string[] = [];
private labelCounter = 0;
private constructor(private labelCounter: number) {}
public static newRoot(): Assembler {
return new Assembler(0);
}
public fork(): Assembler {
return new Assembler(this.labelCounter);
}
public join(assembler: Assembler) {
this.labelCounter = assembler.labelCounter;
if (assembler.lines.length < 0) {
return;
}
if (assembler.lines[0].labels !== undefined) {
this.addedLabels.push(...assembler.lines[0].labels);
}
this.add(...assembler.lines[0].ins);
this.lines.push(...assembler.lines.slice(1));
}
public add(...ins: Ins): Assembler { public add(...ins: Ins): Assembler {
if (this.addedLabels.length > 0) { if (this.addedLabels.length > 0) {
@ -26,17 +47,6 @@ export class Assembler {
return this; return this;
} }
public concat(assembler: Assembler) {
if (assembler.lines.length < 0) {
return;
}
if (assembler.lines[0].labels !== undefined) {
this.addedLabels.push(...assembler.lines[0].labels);
}
this.add(...assembler.lines[0].ins);
this.lines.push(...assembler.lines.slice(1));
}
public makeLabel(): Label { public makeLabel(): Label {
return { label: `.L${(this.labelCounter++).toString()}` }; return { label: `.L${(this.labelCounter++).toString()}` };
} }

View File

@ -8,7 +8,7 @@ import { Pos } from "./token.ts";
export type FnNamesMap = { [pc: number]: string }; export type FnNamesMap = { [pc: number]: string };
export class Lowerer { export class Lowerer {
private program = new Assembler(); private program = Assembler.newRoot();
private locals: Locals = new LocalsFnRoot(); private locals: Locals = new LocalsFnRoot();
private fnStmtIdLabelMap: { [stmtId: number]: string } = {}; private fnStmtIdLabelMap: { [stmtId: number]: string } = {};
private fnLabelNameMap: { [name: string]: string } = {}; private fnLabelNameMap: { [name: string]: string } = {};
@ -19,16 +19,16 @@ export class Lowerer {
public lower(stmts: Stmt[]) { public lower(stmts: Stmt[]) {
this.addClearingSourceMap(); this.addClearingSourceMap();
this.program.add(Ops.PushPtr, { label: "_start" }); this.program.add(Ops.PushPtr, { label: "main" });
this.program.add(Ops.Call, 0);
this.program.add(Ops.PushPtr, { label: "_exit" });
this.program.add(Ops.Jump); this.program.add(Ops.Jump);
this.scoutFnHeaders(stmts); this.scoutFnHeaders(stmts);
for (const stmt of stmts) { for (const stmt of stmts) {
this.lowerStaticStmt(stmt); this.lowerStaticStmt(stmt);
} }
this.program.setLabel({ label: "_start" }); this.program.setLabel({ label: "_exit" });
this.addSourceMap(this.lastPos); this.addSourceMap(this.lastPos);
this.program.add(Ops.PushPtr, { label: "main" });
this.program.add(Ops.Call, 0);
this.program.add(Ops.Pop); this.program.add(Ops.Pop);
} }
@ -171,7 +171,7 @@ export class Lowerer {
const returnLabel = this.program.makeLabel(); const returnLabel = this.program.makeLabel();
this.returnStack.push(returnLabel); this.returnStack.push(returnLabel);
this.program = new Assembler(); this.program = outerProgram.fork();
this.locals = fnRoot; this.locals = fnRoot;
for (const { ident } of stmt.kind.params) { for (const { ident } of stmt.kind.params) {
this.locals.allocSym(ident); this.locals.allocSym(ident);
@ -185,18 +185,17 @@ export class Lowerer {
} }
this.locals = outerLocals; this.locals = outerLocals;
this.returnStack.pop();
this.program.setLabel(returnLabel);
const localAmount = fnRoot.stackReserved() - const localAmount = fnRoot.stackReserved() -
stmt.kind.params.length; stmt.kind.params.length;
for (let i = 0; i < localAmount; ++i) { for (let i = 0; i < localAmount; ++i) {
outerProgram.add(Ops.PushNull); outerProgram.add(Ops.PushNull);
} }
this.returnStack.pop();
this.program.setLabel(returnLabel);
this.program.add(Ops.Return); this.program.add(Ops.Return);
outerProgram.concat(this.program); outerProgram.join(this.program);
this.program = outerProgram; this.program = outerProgram;
} }
@ -353,9 +352,8 @@ export class Lowerer {
switch (expr.kind.unaryType) { switch (expr.kind.unaryType) {
case "-": { case "-": {
this.program.add(Ops.PushInt, 0); this.program.add(Ops.PushInt, 0);
this.program.add(Ops.PushInt, 1); this.program.add(Ops.Swap);
this.program.add(Ops.Subtract); this.program.add(Ops.Subtract);
this.program.add(Ops.Multiply);
return; return;
} }
default: default:
@ -373,9 +371,25 @@ export class Lowerer {
if (expr.kind.type !== "binary") { if (expr.kind.type !== "binary") {
throw new Error(); throw new Error();
} }
const vtype = expr.kind.left.vtype!;
if (vtype.type === "bool") {
if (["or", "and"].includes(expr.kind.binaryType)) {
const shortCircuitLabel = this.program.makeLabel();
this.lowerExpr(expr.kind.left);
this.program.add(Ops.Duplicate);
if (expr.kind.binaryType === "and") {
this.program.add(Ops.Not);
}
this.program.add(Ops.PushPtr, shortCircuitLabel);
this.program.add(Ops.JumpIfTrue);
this.program.add(Ops.Pop);
this.lowerExpr(expr.kind.right);
this.program.setLabel(shortCircuitLabel);
return;
}
}
this.lowerExpr(expr.kind.left); this.lowerExpr(expr.kind.left);
this.lowerExpr(expr.kind.right); this.lowerExpr(expr.kind.right);
const vtype = expr.kind.left.vtype!;
if (vtype.type === "int") { if (vtype.type === "int") {
switch (expr.kind.binaryType) { switch (expr.kind.binaryType) {
case "+": case "+":
@ -387,6 +401,9 @@ export class Lowerer {
case "*": case "*":
this.program.add(Ops.Multiply); this.program.add(Ops.Multiply);
return; return;
case "/":
this.program.add(Ops.Multiply);
return;
case "==": case "==":
this.program.add(Ops.Equal); this.program.add(Ops.Equal);
return; return;
@ -397,6 +414,15 @@ export class Lowerer {
case "<": case "<":
this.program.add(Ops.LessThan); this.program.add(Ops.LessThan);
return; return;
case ">":
this.program.add(Ops.Swap);
this.program.add(Ops.LessThan);
return;
case "<=":
this.program.add(Ops.Swap);
this.program.add(Ops.LessThan);
this.program.add(Ops.Not);
return;
case ">=": case ">=":
this.program.add(Ops.LessThan); this.program.add(Ops.LessThan);
this.program.add(Ops.Not); this.program.add(Ops.Not);
@ -419,16 +445,6 @@ export class Lowerer {
return; return;
} }
} }
if (vtype.type === "bool") {
if (expr.kind.binaryType === "or") {
this.program.add(Ops.Or);
return;
}
if (expr.kind.binaryType === "and") {
this.program.add(Ops.And);
return;
}
}
throw new Error( throw new Error(
`unhandled binaryType` + `unhandled binaryType` +
` '${vtypeToString(expr.vtype!)}' aka. ` + ` '${vtypeToString(expr.vtype!)}' aka. ` +

View File

@ -7,10 +7,8 @@ fn string_to_int_impl(text: string) -> int {
let len = string_length(text); let len = string_length(text);
if len == 0 { if len == 0 {
return 1; return -1;
} }
println("hello world");
if text[0] == "0"[0] { if text[0] == "0"[0] {
if len == 1 { if len == 1 {
0 0
@ -41,27 +39,28 @@ fn parse_digits(text: string, base: int, digit_set: string) -> int {
} }
fn char_val(ch: int) -> int { fn char_val(ch: int) -> int {
if ch >= "0"[0] and "9"[0] >= ch { if ch >= "0"[0] and ch <= "9"[0] {
ch - "0"[0] ch - "0"[0]
} else if ch >= "a"[0] and "f"[0] >= ch { } else if ch >= "a"[0] and ch <= "f"[0] {
ch - "a"[0] ch - "a"[0] + 10
} else { } else {
-1 -1
} }
} }
fn test_string_to_int_impl() -> bool { fn test_string_to_int_impl() -> bool {
test("should convert zero", assert_int_equal(string_to_int_impl("0"), 0)) test("should convert zero", assert_int_equal(string_to_int_impl("0"), 0))
or test("should convert decimal", assert_int_equal(string_to_int_impl("10"), 10)) and test("should convert decimal", assert_int_equal(string_to_int_impl("10"), 10))
or test("should convert binary", assert_int_equal(string_to_int_impl("0b110"), 6)) and test("should convert binary", assert_int_equal(string_to_int_impl("0b110"), 6))
or test("should convert octal", assert_int_equal(string_to_int_impl("071"), 51)) and test("should convert octal", assert_int_equal(string_to_int_impl("071"), 57))
or test("should convert hexadecimal", assert_int_equal(string_to_int_impl("0xaa"), 170)) and test("should convert hex", assert_int_equal(string_to_int_impl("0xaa"), 170))
or test("should fail", assert_int_equal(string_to_int_impl("john"), -1)) and test("should fail", assert_int_equal(string_to_int_impl("john"), -1))
and test("should fail", assert_int_equal(string_to_int_impl(""), -1))
} }
fn assert_int_equal(value: int, target: int) -> bool { fn assert_int_equal(value: int, target: int) -> bool {
if value != target { if value != target {
println("assertion failed: " + int_to_string(value) + " != " + int_to_string(target)); println("assertion failed: " + itos(value) + " != " + itos(target));
return false; return false;
} }
true true
@ -109,6 +108,8 @@ fn file_read_to_string(file: int) -> string #[builtin(FileReadToString)] {}
fn file_flush(file: int) #[builtin(FileFlush)] {} fn file_flush(file: int) #[builtin(FileFlush)] {}
fn file_eof(file: int) -> bool #[builtin(FileEof)] {} fn file_eof(file: int) -> bool #[builtin(FileEof)] {}
fn itos(number: int) -> string #[builtin(IntToString)] {}
fn stoi(str: string) -> int #[builtin(StringToInt)] {}
fn stdin() -> int { 0 } fn stdin() -> int { 0 }
fn stdout() -> int { 1 } fn stdout() -> int { 1 }
@ -175,11 +176,11 @@ fn string_split(str: string, seperator: int) -> [string] {
fn string_slice(str: string, from: int, to: int) -> string { fn string_slice(str: string, from: int, to: int) -> string {
let result = ""; let result = "";
let len = string_length(str); let len = string_length(str);
let actual_to = let abs_to =
if to >= len { len } if to >= len { len }
else if to < 0 { len - to } else if to < 0 { len + to + 1 }
else { to }; else { to };
for (let i = from; i < actual_to; i += i) { for (let i = from; i < abs_to; i += 1) {
result = string_push_char(result, str[i]); result = string_push_char(result, str[i]);
} }
result result

View File

@ -24,6 +24,8 @@ enum class Op : uint32_t {
Jump = 0x0e, Jump = 0x0e,
JumpIfTrue = 0x0f, JumpIfTrue = 0x0f,
Builtin = 0x10, Builtin = 0x10,
Duplicate = 0x11,
Swap = 0x12,
Add = 0x20, Add = 0x20,
Subtract = 0x21, Subtract = 0x21,
Multiply = 0x22, Multiply = 0x22,

View File

@ -33,6 +33,8 @@ size_t VM::instruction_size(size_t i) const
return 1; return 1;
case Op::Builtin: case Op::Builtin:
return 2; return 2;
case Op::Duplicate:
case Op::Swap:
case Op::Add: case Op::Add:
case Op::Subtract: case Op::Subtract:
case Op::Multiply: case Op::Multiply:

View File

@ -25,6 +25,8 @@ auto sliger::maybe_op_to_string(uint32_t value) -> std::string
case Op::Jump: return "Jump"; case Op::Jump: return "Jump";
case Op::JumpIfTrue: return "JumpIfTrue"; case Op::JumpIfTrue: return "JumpIfTrue";
case Op::Builtin: return "Builtin"; case Op::Builtin: return "Builtin";
case Op::Duplicate: return "Duplicate";
case Op::Swap: return "Swap";
case Op::Add: return "Add"; case Op::Add: return "Add";
case Op::Subtract: return "Subtract"; case Op::Subtract: return "Subtract";
case Op::Multiply: return "Multiply"; case Op::Multiply: return "Multiply";
@ -37,10 +39,9 @@ auto sliger::maybe_op_to_string(uint32_t value) -> std::string
case Op::Xor: return "Xor"; case Op::Xor: return "Xor";
case Op::Not: return "Not"; case Op::Not: return "Not";
case Op::SourceMap: return "SourceMap"; case Op::SourceMap: return "SourceMap";
/* clang-format on */ /* clang-format on */
default:
return std::to_string(value);
} }
return std::to_string(value);
} }
auto sliger::maybe_builtin_to_string(uint32_t value) -> std::string auto sliger::maybe_builtin_to_string(uint32_t value) -> std::string

View File

@ -177,6 +177,21 @@ void VM::run_instruction()
run_builtin(static_cast<Builtin>(builtin_id)); run_builtin(static_cast<Builtin>(builtin_id));
break; break;
} }
case Op::Duplicate: {
assert_stack_has(1);
auto value = stack_pop();
stack_push(value);
stack_push(value);
break;
}
case Op::Swap: {
assert_stack_has(2);
auto right = stack_pop();
auto left = stack_pop();
stack_push(right);
stack_push(left);
break;
}
case Op::Add: { case Op::Add: {
assert_stack_has(2); assert_stack_has(2);
auto right = stack_pop().as_int().value; auto right = stack_pop().as_int().value;

View File

@ -27,6 +27,8 @@ fn file_read_to_string(file: int) -> string #[builtin(FileReadToString)] {}
fn file_flush(file: int) #[builtin(FileFlush)] {} fn file_flush(file: int) #[builtin(FileFlush)] {}
fn file_eof(file: int) -> bool #[builtin(FileEof)] {} fn file_eof(file: int) -> bool #[builtin(FileEof)] {}
fn itos(number: int) -> string #[builtin(IntToString)] {}
fn stoi(str: string) -> int #[builtin(StringToInt)] {}
fn stdin() -> int { 0 } fn stdin() -> int { 0 }
fn stdout() -> int { 1 } fn stdout() -> int { 1 }

View File

@ -2,6 +2,11 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="dist/bundle.js" type="module" defer></script> <script src="dist/bundle.js" type="module" defer></script>
</head> </head>

View File

@ -27,7 +27,7 @@ export function loadFlameGraph(
canvas.width = 1000; canvas.width = 1000;
canvas.height = 500; canvas.height = 500;
const fnNameFont = "600 14px monospace"; const fnNameFont = "600 14px 'Roboto Mono'";
const ctx = canvas.getContext("2d")!; const ctx = canvas.getContext("2d")!;
ctx.font = fnNameFont; ctx.font = fnNameFont;
@ -39,7 +39,7 @@ export function loadFlameGraph(
h: number; h: number;
title: string; title: string;
percent: string; percent: string;
fgNode: FlameGraphNode; fgNode: data.FlameGraphNode;
}; };
function calculateNodeRects( function calculateNodeRects(
@ -91,14 +91,40 @@ export function loadFlameGraph(
for (const node of nodes) { for (const node of nodes) {
const { x, y, w, h } = node; const { x, y, w, h } = node;
ctx.fillStyle = "rgb(255, 125, 0)"; ctx.fillStyle = "rgb(255, 125, 0)";
ctx.fillRect( ctx.fillRect(x + 2, y + 2, w - 4, h - 4);
x + 2,
y + 2,
w - 4,
h - 4,
);
const textCanvas = drawTextCanvas(node); const textCanvas = drawTextCanvas(node);
ctx.drawImage(textCanvas, x + 4, y); ctx.drawImage(textCanvas, x + 4, y);
const edgePadding = 4;
const edgeWidth = 8;
const leftGradient = ctx.createLinearGradient(
x + 2 + edgePadding,
0,
x + 2 + edgeWidth,
0,
);
leftGradient.addColorStop(1, "rgba(255, 125, 0, 0.0)");
leftGradient.addColorStop(0, "rgba(255, 125, 0, 1.0)");
ctx.fillStyle = leftGradient;
ctx.fillRect(x + 2, y + 2, Math.min(edgeWidth, (w - 4) / 2), h - 4);
const rightGradient = ctx.createLinearGradient(
x + w - 2 - edgeWidth,
0,
x + w - 2 - edgePadding,
0,
);
rightGradient.addColorStop(0, "rgba(255, 125, 0, 0.0)");
rightGradient.addColorStop(1, "rgba(255, 125, 0, 1.0)");
ctx.fillStyle = rightGradient;
ctx.fillRect(
x + w - 2 - Math.min(edgeWidth, (w - 4) / 2),
y + 2,
Math.min(edgeWidth, (w - 4) / 2),
h - 4,
);
} }
const tooltip = document.getElementById("flame-graph-tooltip")!; const tooltip = document.getElementById("flame-graph-tooltip")!;

View File

@ -104,6 +104,11 @@ main #cover {
border-radius: 0.5rem; border-radius: 0.5rem;
} }
#view .code-container pre {
font-family: "Roboto Mono", monospace;
font-weight: 600;
}
#view .code-container.code-coverage { #view .code-container.code-coverage {
max-height: calc(100% - 103px); max-height: calc(100% - 103px);
} }