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,
JumpIfTrue: 0x0f,
Builtin: 0x10,
Duplicate: 0x11,
Swap: 0x12,
Add: 0x20,
Subtract: 0x21,
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 };
@ -14,7 +14,28 @@ export type Refs = { [key: number]: string };
export class Assembler {
private lines: Line[] = [];
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 {
if (this.addedLabels.length > 0) {
@ -26,17 +47,6 @@ export class Assembler {
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 {
return { label: `.L${(this.labelCounter++).toString()}` };
}

View File

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

View File

@ -7,10 +7,8 @@ fn string_to_int_impl(text: string) -> int {
let len = string_length(text);
if len == 0 {
return 1;
return -1;
}
println("hello world");
if text[0] == "0"[0] {
if len == 1 {
0
@ -41,10 +39,10 @@ fn parse_digits(text: string, base: int, digit_set: string) -> 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]
} else if ch >= "a"[0] and "f"[0] >= ch {
ch - "a"[0]
} else if ch >= "a"[0] and ch <= "f"[0] {
ch - "a"[0] + 10
} else {
-1
}
@ -52,16 +50,17 @@ fn char_val(ch: int) -> int {
fn test_string_to_int_impl() -> bool {
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))
or 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))
or test("should convert hexadecimal", 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 convert decimal", assert_int_equal(string_to_int_impl("10"), 10))
and test("should convert binary", assert_int_equal(string_to_int_impl("0b110"), 6))
and test("should convert octal", assert_int_equal(string_to_int_impl("071"), 57))
and test("should convert hex", assert_int_equal(string_to_int_impl("0xaa"), 170))
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 {
if value != target {
println("assertion failed: " + int_to_string(value) + " != " + int_to_string(target));
println("assertion failed: " + itos(value) + " != " + itos(target));
return false;
}
true
@ -109,6 +108,8 @@ fn file_read_to_string(file: int) -> string #[builtin(FileReadToString)] {}
fn file_flush(file: int) #[builtin(FileFlush)] {}
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 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 {
let result = "";
let len = string_length(str);
let actual_to =
let abs_to =
if to >= len { len }
else if to < 0 { len - to }
else if to < 0 { len + to + 1 }
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

View File

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

View File

@ -33,6 +33,8 @@ size_t VM::instruction_size(size_t i) const
return 1;
case Op::Builtin:
return 2;
case Op::Duplicate:
case Op::Swap:
case Op::Add:
case Op::Subtract:
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::JumpIfTrue: return "JumpIfTrue";
case Op::Builtin: return "Builtin";
case Op::Duplicate: return "Duplicate";
case Op::Swap: return "Swap";
case Op::Add: return "Add";
case Op::Subtract: return "Subtract";
case Op::Multiply: return "Multiply";
@ -38,9 +40,8 @@ auto sliger::maybe_op_to_string(uint32_t value) -> std::string
case Op::Not: return "Not";
case Op::SourceMap: return "SourceMap";
/* 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

View File

@ -177,6 +177,21 @@ void VM::run_instruction()
run_builtin(static_cast<Builtin>(builtin_id));
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: {
assert_stack_has(2);
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_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 stdout() -> int { 1 }

View File

@ -2,6 +2,11 @@
<html>
<head>
<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">
<script src="dist/bundle.js" type="module" defer></script>
</head>

View File

@ -27,7 +27,7 @@ export function loadFlameGraph(
canvas.width = 1000;
canvas.height = 500;
const fnNameFont = "600 14px monospace";
const fnNameFont = "600 14px 'Roboto Mono'";
const ctx = canvas.getContext("2d")!;
ctx.font = fnNameFont;
@ -39,7 +39,7 @@ export function loadFlameGraph(
h: number;
title: string;
percent: string;
fgNode: FlameGraphNode;
fgNode: data.FlameGraphNode;
};
function calculateNodeRects(
@ -91,14 +91,40 @@ export function loadFlameGraph(
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,
);
ctx.fillRect(x + 2, y + 2, w - 4, h - 4);
const textCanvas = drawTextCanvas(node);
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")!;

View File

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