From 77c01e12a5fcfec391f841fb9d887b090e983799 Mon Sep 17 00:00:00 2001 From: sfja Date: Tue, 17 Dec 2024 02:10:11 +0100 Subject: [PATCH] fix labels, return, code coverage --- compiler/arch.ts | 2 + compiler/assembler.ts | 36 +++++++----- compiler/lowerer.ts | 62 +++++++++++++-------- examples/survey_code_coverage_program_2.slg | 33 +++++------ runtime/arch.hpp | 2 + runtime/instruction_size.cpp | 2 + runtime/to_string.cpp | 7 ++- runtime/vm.cpp | 15 +++++ stdlib.slg | 2 + web/public/index.html | 5 ++ web/public/src/flamegraph.ts | 42 +++++++++++--- web/public/style.css | 5 ++ 12 files changed, 150 insertions(+), 63 deletions(-) diff --git a/compiler/arch.ts b/compiler/arch.ts index 4942416..eca36bd 100644 --- a/compiler/arch.ts +++ b/compiler/arch.ts @@ -22,6 +22,8 @@ export const Ops = { Jump: 0x0e, JumpIfTrue: 0x0f, Builtin: 0x10, + Duplicate: 0x11, + Swap: 0x12, Add: 0x20, Subtract: 0x21, Multiply: 0x22, diff --git a/compiler/assembler.ts b/compiler/assembler.ts index 5775715..ab7cc99 100644 --- a/compiler/assembler.ts +++ b/compiler/assembler.ts @@ -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()}` }; } diff --git a/compiler/lowerer.ts b/compiler/lowerer.ts index 56b8ca3..95c8fc3 100644 --- a/compiler/lowerer.ts +++ b/compiler/lowerer.ts @@ -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. ` + diff --git a/examples/survey_code_coverage_program_2.slg b/examples/survey_code_coverage_program_2.slg index 3f9e32f..2198bd7 100644 --- a/examples/survey_code_coverage_program_2.slg +++ b/examples/survey_code_coverage_program_2.slg @@ -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,27 +39,28 @@ 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 } } 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)) + test("should convert zero", assert_int_equal(string_to_int_impl("0"), 0)) + 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 diff --git a/runtime/arch.hpp b/runtime/arch.hpp index 472b2fb..d7ae557 100644 --- a/runtime/arch.hpp +++ b/runtime/arch.hpp @@ -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, diff --git a/runtime/instruction_size.cpp b/runtime/instruction_size.cpp index 6eba25a..b9e7d54 100644 --- a/runtime/instruction_size.cpp +++ b/runtime/instruction_size.cpp @@ -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: diff --git a/runtime/to_string.cpp b/runtime/to_string.cpp index a93ae7d..e3df691 100644 --- a/runtime/to_string.cpp +++ b/runtime/to_string.cpp @@ -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"; @@ -37,10 +39,9 @@ auto sliger::maybe_op_to_string(uint32_t value) -> std::string case Op::Xor: return "Xor"; case Op::Not: return "Not"; case Op::SourceMap: return "SourceMap"; - /* clang-format on */ - default: - return std::to_string(value); + /* clang-format on */ } + return std::to_string(value); } auto sliger::maybe_builtin_to_string(uint32_t value) -> std::string diff --git a/runtime/vm.cpp b/runtime/vm.cpp index adcffb1..cd0087f 100644 --- a/runtime/vm.cpp +++ b/runtime/vm.cpp @@ -177,6 +177,21 @@ void VM::run_instruction() run_builtin(static_cast(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; diff --git a/stdlib.slg b/stdlib.slg index e1b2eea..566f169 100644 --- a/stdlib.slg +++ b/stdlib.slg @@ -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 } diff --git a/web/public/index.html b/web/public/index.html index 89abb0c..c6e3eb4 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -2,6 +2,11 @@ + + + + + diff --git a/web/public/src/flamegraph.ts b/web/public/src/flamegraph.ts index 4655e90..bc6a5af 100644 --- a/web/public/src/flamegraph.ts +++ b/web/public/src/flamegraph.ts @@ -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")!; diff --git a/web/public/style.css b/web/public/style.css index d797a17..166f7ba 100644 --- a/web/public/style.css +++ b/web/public/style.css @@ -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); }