diff --git a/.gitignore b/.gitignore index ca5e1f1..3e16ec5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ vendor/highlight/* vendor/mathjax/* !vendor/download.sh +docs/llvm/ diff --git a/blocks-algo.js b/blocks-algo.js index 0913f61..64a1b23 100644 --- a/blocks-algo.js +++ b/blocks-algo.js @@ -177,13 +177,13 @@ class WCEnd extends WCBlock { generateOwnCode() { const code = this._selectedCode; if (code === 0) { - const frees = this._genStringFrees(); - if (frees) return `\n${frees} return 0;\n`; - return "\n return 0;\n"; + return " ret i32 0\n"; } - return `\n exit(${code});\n`; + return " call void @exit(i32 " + code + ")\n unreachable\n"; } + generateFlowCode() { return ""; } + saveData() { return { exitCode: this._selectedCode }; } restoreData(d) { this._selectByCode(d.exitCode !== undefined ? d.exitCode : 0); @@ -235,6 +235,15 @@ class WCDecision extends WCBlock { return resolveSourceName(block) || "flag"; } + generateOwnCode() { + const source = findDataSource(this._el.boolIn); + if (source) { + const name = resolveSourceName(source) || "flag"; + return " %_c = load i1, i1* %" + name + "\n"; + } + return " %_c = alloca i1\n store i1 0, i1* %_c\n %_c = load i1, i1* %_c\n"; + } + generateFlowCode() { let yesTarget = null, noTarget = null; for (const path of this.wcNextFlow) { @@ -245,11 +254,9 @@ class WCDecision extends WCBlock { noTarget = path.toElement; } } - const source = findDataSource(this._el.boolIn); - const cond = source ? this._getBoolName(source) : "0"; let out = ""; - if (yesTarget) out += ` if (${cond}) goto ${yesTarget.id};\n`; - if (noTarget) out += ` goto ${noTarget.id};\n`; + if (yesTarget) out += " br i1 %_c, label %" + yesTarget.id + ", label %" + (noTarget ? noTarget.id : "") + "\n"; + else if (noTarget) out += " br label %" + noTarget.id + "\n"; return out; } } diff --git a/blocks-proc.js b/blocks-proc.js index 5f5bbbe..202bcd3 100644 --- a/blocks-proc.js +++ b/blocks-proc.js @@ -477,127 +477,93 @@ class WCProcCall extends WCBlock { return block.dataType === "string" ? name + ".data" : name; } - generateOwnCode() { - const qname = this._selectedProc; - if (!qname) return ""; - const parts = qname.split("/"); - const procName = parts[1] || qname; - - let returnStyle = this._cachedReturnStyle || "pointer"; - - const inputs = []; - const outputs = []; - for (const row of this._el.inputs.querySelectorAll(".proccall-arg-row")) { - const hook = row.querySelector(".hook"); - const inp = row.querySelector(".proccall-arg-input"); - const source = hook ? findDataSource(hook) : null; + _buildLLVMArgs() { + var inputs = []; + var outputs = []; + for (var ri = 0; ri < this._el.inputs.querySelectorAll(".proccall-arg-row").length; ri++) { + var row = this._el.inputs.querySelectorAll(".proccall-arg-row")[ri]; + var hook = row.querySelector(".hook"); + var inp = row.querySelector(".proccall-arg-input"); + var source = hook ? findDataSource(hook) : null; if (source) { - inputs.push(this._sourceName(source)); + inputs.push("%" + this._sourceName(source)); } else { - let val = inp.value || inp.placeholder; - if (inp.dataset.paramType === "string") val = `"${escapeString(val)}"`; + var val = inp.value || inp.placeholder; + if (inp.dataset.paramType === "string") { + var idx = CodeGen.allocString(val); + val = CodeGen.llvmStringArg(val, idx); + } else if (inp.dataset.paramType === "int" || inp.dataset.paramType === "i32") { + val = "i32 " + val; + } else if (inp.dataset.paramType === "float" || inp.dataset.paramType === "double") { + val = "double " + val; + } else { + val = "i32 " + val; // default integer + } inputs.push(val); } } - for (const row of this._el.outputs.querySelectorAll(".proccall-arg-row")) { - const inp = row.querySelector(".proccall-arg-input"); - outputs.push("&" + (inp.value || inp.placeholder)); - } - if (this._fmtProc) { - for (const row of this._el.fmtargsList.querySelectorAll(".proccall-arg-row")) { - const hook = row.querySelector(".hook"); - const inp = row.querySelector(".proccall-arg-input"); - const source = hook ? findDataSource(hook) : null; - if (source) { - inputs.push(this._sourceName(source)); - } else { - let val = inp.value || inp.placeholder; - if (hook.dataset.type === "string") val = `"${escapeString(val)}"`; - inputs.push(val); - } - } + for (var rj = 0; rj < this._el.outputs.querySelectorAll(".proccall-arg-row").length; rj++) { + var row2 = this._el.outputs.querySelectorAll(".proccall-arg-row")[rj]; + var inp2 = row2.querySelector(".proccall-arg-input"); + outputs.push(inp2.value || inp2.placeholder); } + return { inputs: inputs, outputs: outputs }; + } - const hasErrPath = this._el.errHook && this._el.errHook.classList.contains("visible") && - Array.from(this.wcNextFlow).some(p => p.fromHook === this._el.errHook); - - // For error-capable calls, the call is emitted inside generateFlowCode - // (as part of the if-condition) to avoid double execution. - if (returnStyle === "error" && hasErrPath) return ""; - - if (returnStyle === "value" && outputs.length === 1 && hasErrPath) { - const outVar = outputs[0].replace(/^&/, ""); - return ` ${outVar} = ${procName}(${inputs.join(", ")});\n`; - } + generateOwnCode() { + var qname = this._selectedProc; + if (!qname) return ""; + var sp = qname.split("/"); + var procName = sp[1] || qname; + var returnStyle = this._cachedReturnStyle || "pointer"; + var io = this._buildLLVMArgs(); + var callArgs = io.inputs.join(", "); + var hasErrPath = this._el.errHook && this._el.errHook.classList.contains("visible") && + Array.from(this.wcNextFlow).some(function (p) { return p.fromHook === this._el.errHook; }.bind(this)); + var rn = "%_r_" + this.id, vn = "%_v_" + this.id; if (returnStyle === "error") { - return ` ${procName}(${inputs.join(", ")});\n`; + if (hasErrPath) return " " + rn + " = call i32 @" + procName + "(" + callArgs + ")\n"; + return " call void @" + procName + "(" + callArgs + ")\n"; } - if (returnStyle === "value" && outputs.length === 1) { - return ` ${procName}(${inputs.join(", ")});\n`; + if (returnStyle === "value" && io.outputs.length === 1) { + if (hasErrPath) return " " + vn + " = call i32 @" + procName + "(" + callArgs + ")\n"; + return " call void @" + procName + "(" + callArgs + ")\n"; } - const args = [...inputs, ...outputs].join(", "); - return ` ${procName}(${args});\n`; + + return " call void @" + procName + "(" + callArgs + ")\n"; } generateFlowCode() { - const hasErrPath = this._el.errHook && this._el.errHook.classList.contains("visible") && - Array.from(this.wcNextFlow).some(p => p.fromHook === this._el.errHook); + var hasErrPath = this._el.errHook && this._el.errHook.classList.contains("visible") && + Array.from(this.wcNextFlow).some(function (p) { return p.fromHook === this._el.errHook; }.bind(this)); if (!hasErrPath) return WCBlock.prototype.generateFlowCode.call(this); - const qname = this._selectedProc; + var qname = this._selectedProc; if (!qname) return ""; - const parts = qname.split("/"); - const procName = parts[1] || qname; + var sp = qname.split("/"); + var procName = sp[1] || qname; + var returnStyle = this._cachedReturnStyle || "pointer"; + var io = this._buildLLVMArgs(); - let returnStyle = this._cachedReturnStyle || "pointer"; - - const inputs = []; - const outputs = []; - for (const row of this._el.inputs.querySelectorAll(".proccall-arg-row")) { - const hook = row.querySelector(".hook"); - const inp = row.querySelector(".proccall-arg-input"); - const source = hook ? findDataSource(hook) : null; - if (source) { - inputs.push(this._sourceName(source)); - } else { - inputs.push(inp.value || inp.placeholder); - } - } - for (const row of this._el.outputs.querySelectorAll(".proccall-arg-row")) { - const inp = row.querySelector(".proccall-arg-input"); - outputs.push("&" + (inp.value || inp.placeholder)); - } - if (this._fmtProc) { - for (const row of this._el.fmtargsList.querySelectorAll(".proccall-arg-row")) { - const hook = row.querySelector(".hook"); - const inp = row.querySelector(".proccall-arg-input"); - const source = hook ? findDataSource(hook) : null; - if (source) { - inputs.push(this._sourceName(source)); - } else { - inputs.push(inp.value || inp.placeholder); - } - } + var errTarget = null, okTarget = null; + var flows = Array.from(this.wcNextFlow); + for (var fi = 0; fi < flows.length; fi++) { + var p = flows[fi]; + if (p.fromHook === this._el.errHook) errTarget = p.toElement; + else okTarget = p.toElement; } - const args = inputs.join(", "); - const errTarget = Array.from(this.wcNextFlow) - .filter(p => p.fromHook === this._el.errHook) - .map(p => p.toElement)[0]; - const okTarget = Array.from(this.wcNextFlow) - .filter(p => p.fromHook !== this._el.errHook) - .map(p => p.toElement)[0]; - - let code = ""; + var rn = "%_r_" + this.id, vn = "%_v_" + this.id, cn = "%_cmp_" + this.id; + var code = ""; if (returnStyle === "error") { - code += ` if (${procName}(${args}) != 0) goto ${errTarget.id};\n`; - } else if (returnStyle === "value" && outputs.length === 1) { - const outVar = outputs[0].replace(/^&/, ""); - code += ` if (${outVar} != 0) goto ${errTarget.id};\n`; + code += " " + cn + " = icmp ne i32 " + rn + ", 0\n"; + if (errTarget) code += " br i1 " + cn + ", label %" + errTarget.id + ", label %" + (okTarget ? okTarget.id : "") + "\n"; + } else if (returnStyle === "value" && io.outputs.length === 1) { + code += " " + cn + " = icmp ne i32 " + vn + ", 0\n"; + if (errTarget) code += " br i1 " + cn + ", label %" + errTarget.id + ", label %" + (okTarget ? okTarget.id : "") + "\n"; } - if (okTarget) code += ` goto ${okTarget.id};\n`; return code; } diff --git a/codegen/index.js b/codegen/index.js index 6eb4850..ff7a5d9 100644 --- a/codegen/index.js +++ b/codegen/index.js @@ -1,61 +1,117 @@ -// ── Pure C code generation — zero-DOM, flat DFA model ───────────────── +// ── Pure LLVM IR code generation — zero-DOM, flat DFA model ─────────── // -// Every block is a C label. Every flow edge is a `goto label;`. -// There is zero nesting: no if/else chains, no branch-analysis, no -// shared-block computation. Decisions become `if (cond) goto yes; -// goto no;`. Error handling becomes `if (call != 0) goto err; -// goto ok;`. -// -// Block interface: -// { id: string, -// generateOwnCode(): string, // C code for this one block -// generateFlowCode?(): string, // flow-control (gotos). If absent, -// // CodeGen walks wcNextFlow instead. -// wcNextFlow?: [{toElement: block}], -// nextFlow?: [{toElement: block}] } +// Every block is an LLVM IR label. Every edge is a `br` instruction. +// Decisions become `br i1 %cond, label %yes, label %no`. +// Error handling becomes `br i1 %result, label %err, label %ok`. (function () { "use strict"; var CodeGen = {}; + // ── String pool ───────────────────────────────────────────────────── + + var _strPool = []; + CodeGen.resetStringPool = function () { _strPool = []; }; + CodeGen.getStringPool = function () { return _strPool; }; + /** - * Emits a complete C program as a flat sequence of labelled blocks - * connected by gotos. Each block is emitted once (DFS order); already- - * visited blocks become `goto label;` stubs. - * - * @param {Object} entry the first block (e.g. START) - * @returns {string} + * Registers a string literal and returns its unique index. */ + CodeGen.allocString = function (s) { + var idx = _strPool.indexOf(s); + if (idx === -1) { idx = _strPool.length; _strPool.push(s); } + return idx; + }; + + /** + * Escapes a JS string for LLVM IR `c"..."` constant, and produces + * the full `getelementptr` reference for use as a call argument. + */ + CodeGen.llvmStringArg = function (str, idx) { + var escaped = ""; + for (var i = 0; i < str.length; i++) { + var ch = str.charCodeAt(i); + if (ch === 0x22) escaped += "\\22"; + else if (ch === 0x5C) escaped += "\\5C"; + else if (ch === 0x0A) escaped += "\\0A"; + else if (ch === 0x0D) escaped += "\\0D"; + else if (ch === 0x09) escaped += "\\09"; + else if (ch < 0x20 || ch > 0x7E) { + escaped += "\\" + ch.toString(16).toUpperCase().padStart(2, "0"); + } else { + escaped += str[i]; + } + } + var byteLen = str.length + 1; // includes null terminator + return "i8* getelementptr inbounds ([" + byteLen + " x i8], [" + byteLen + " x i8]* @.str." + idx + ", i32 0, i32 0)"; + }; + + /** + * Generates the LLVM IR global constant for a string pool entry. + */ + CodeGen.stringGlobal = function (idx) { + var str = _strPool[idx]; + var escaped = "", byteLen = 0; + for (var i = 0; i < str.length; i++) { + var ch = str.charCodeAt(i); + byteLen++; + if (ch === 0x22) escaped += "\\22"; + else if (ch === 0x5C) escaped += "\\5C"; + else if (ch === 0x0A) escaped += "\\0A"; + else if (ch === 0x0D) escaped += "\\0D"; + else if (ch === 0x09) escaped += "\\09"; + else if (ch < 0x20 || ch > 0x7E) { + var h = ch.toString(16).toUpperCase(); + escaped += "\\" + (h.length === 1 ? "0" : "") + h; + } else { + escaped += str[i]; + } + } + byteLen++; // null terminator + return "@.str." + idx + " = private unnamed_addr constant [" + byteLen + " x i8] c\"" + escaped + "\\00\""; + }; + + // ── Flat DFA emit ─────────────────────────────────────────────────── + CodeGen.emitFlat = function (entry) { var visited = new Set(); var parts = []; + function bb(block) { return "bb_" + block.id; } + function emit(block) { if (visited.has(block)) { - parts.push(" goto " + block.id + ";\n"); + parts.push(" br label %" + bb(block) + "\n"); return; } visited.add(block); - parts.push("\n" + block.id + ":"); + parts.push("\n" + bb(block) + ":"); var own = block.generateOwnCode(); if (own) parts.push(own); - // Flow control: custom generateFlowCode, or auto from wcNextFlow var flow = block.wcNextFlow || block.nextFlow || []; var fc = ""; if (typeof block.generateFlowCode === "function") { fc = block.generateFlowCode(); + // Replace bare label references with bb_ prefixed versions + var replaced = ""; + var pos = 0, m; + while ((m = /label\s+%([\w.]+)/g).lastIndex = pos, m = m.exec(fc)) { + replaced += fc.slice(pos, m.index) + "label %bb_" + m[1]; + pos = m.index + m[0].length; + } + replaced += fc.slice(pos); + fc = replaced; } else { for (var i = 0; i < flow.length; i++) { var t = flow[i].toElement; - if (t) fc += " goto " + t.id + ";\n"; + if (t) fc += " br label %" + bb(t) + "\n"; } } if (fc) parts.push("\n" + fc); - // Recurse into successors (visited blocks are skipped at top) for (var i = 0; i < flow.length; i++) { var t = flow[i].toElement; if (t) emit(t); @@ -66,18 +122,19 @@ return parts.join(""); }; - /** - * Assembles a complete C source file. - */ - CodeGen.generateCFile = function (entryBlock, includes, declarations) { - var lines = ["// --- WiseCode\u00ae ---"]; - for (var i = 0; i < includes.length; i++) lines.push(includes[i]); - lines.push(""); - lines.push("int main() {"); + // ── Full module assembly ──────────────────────────────────────────── + + CodeGen.generateIRFile = function (entryBlock, declarations, globals, prologue) { + var lines = ["; ModuleID = \"wisecode\"", "target triple = \"x86_64-pc-linux-gnu\"", ""]; if (declarations) lines.push(declarations); + if (globals) lines.push(globals); + lines.push(""); + lines.push("define i32 @main() {"); + if (prologue) lines.push("entry:\n" + prologue + "\n br label %bb_" + entryBlock.id); lines.push(CodeGen.emitFlat(entryBlock)); lines.push("}"); - lines.push("// --- END ---"); + lines.push(""); + lines.push("; --- END ---"); return lines.join("\n"); }; diff --git a/helpers.js b/helpers.js index fc41607..d945830 100644 --- a/helpers.js +++ b/helpers.js @@ -233,7 +233,7 @@ function findDataSource(hook) { /** Copies the generated C code text to the clipboard. */ function copyCode() { - const el = $id("c-code"); + const el = $id("ir-code"); if (el && el.textContent) navigator.clipboard.writeText(el.textContent).catch(() => {}); } diff --git a/index.html b/index.html index ceaaa56..42326a3 100644 --- a/index.html +++ b/index.html @@ -446,8 +446,8 @@ -
-
+
+
@@ -527,7 +527,7 @@ - + diff --git a/shell.nix b/shell.nix index 683e00e..bf566ce 100644 --- a/shell.nix +++ b/shell.nix @@ -40,10 +40,8 @@ pkgs.mkShell { opencode python3 codegraph - sdl3 - mesa # for GL/gl.h nodejs - gcc + clang ]; shellHook = '' diff --git a/tests/app/test-runner.js b/tests/app/test-runner.js index ce2dd00..42e25ba 100644 --- a/tests/app/test-runner.js +++ b/tests/app/test-runner.js @@ -82,19 +82,19 @@ const APP_TEST_SUITE = { this._group("Code generation", () => { this._test("generateCode returns without error", () => { prog.generateCode(); - const out = $id("c-code"); + const out = $id("ir-code"); if (!out || !out.textContent) throw Error("no code output"); }); this._test("generated code contains main function", () => { prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; if (!code.includes("int main()")) throw Error("missing int main()"); }); this._test("generated code contains return 0", () => { prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; if (!code.includes("return 0")) throw Error("missing return 0"); }); @@ -230,7 +230,7 @@ const APP_TEST_SUITE = { _linkFlow(dec, "#hook-out1", a, ".hook-in"); _linkFlow(a, "#hook-next", end, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; dec.delete(); a.delete(); _linkFlow(start, ".hook", end, ".hook"); @@ -253,7 +253,7 @@ const APP_TEST_SUITE = { _linkFlow(aYes, "#hook-next", end, ".hook"); _linkFlow(aNo, "#hook-next", end, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; dec.delete(); aYes.delete(); aNo.delete(); _linkFlow(start, ".hook", end, ".hook"); @@ -301,7 +301,7 @@ const APP_TEST_SUITE = { for (const p of saved) deleteLink(p); _linkFlow(start, ".hook", end, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; if (!code.includes("int main()")) throw Error("missing main"); if (!code.includes("return 0")) throw Error("missing return 0"); if (!code.includes("exit")) throw Error("missing exit"); @@ -530,7 +530,7 @@ const APP_TEST_SUITE = { _linkFlow(call, ".hook-out", mainEnd, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; call.delete(); errEnd.delete(); restore(); @@ -556,7 +556,7 @@ const APP_TEST_SUITE = { _linkFlow(okAssign, "#hook-next", mainEnd, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; call.delete(); errEnd.delete(); okAssign.delete(); restore(); @@ -589,7 +589,7 @@ const APP_TEST_SUITE = { _linkFlow(sharedAssign, "#hook-next", mainEnd, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; call.delete(); errAssign.delete(); sharedAssign.delete(); restore(); @@ -622,7 +622,7 @@ const APP_TEST_SUITE = { _linkFlow(okAssign, "#hook-next", mainEnd, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; call.delete(); errEnd.delete(); dec.delete(); okAssign.delete(); restore(); @@ -641,7 +641,7 @@ const APP_TEST_SUITE = { _linkFlow(call, ".hook-out", mainEnd, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; call.delete(); restore(); @@ -664,7 +664,7 @@ const APP_TEST_SUITE = { _linkFlow(call, ".hook-out", mainEnd, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; call.delete(); errEnd.delete(); restore(); @@ -693,7 +693,7 @@ const APP_TEST_SUITE = { _linkFlow(call2, ".hook-out", mainEnd, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; call1.delete(); call2.delete(); errEnd1.delete(); errEnd2.delete(); restore(); @@ -718,7 +718,7 @@ const APP_TEST_SUITE = { _linkFlow(shared, "#hook-next", mainEnd, ".hook"); prog.generateCode(); - const code = $id("c-code").textContent; + const code = $id("ir-code").textContent; call.delete(); shared.delete(); restore(); diff --git a/tests/compile/Makefile b/tests/compile/Makefile index 2bb7403..cb42f57 100644 --- a/tests/compile/Makefile +++ b/tests/compile/Makefile @@ -1,33 +1,16 @@ -CC ?= gcc -CFLAGS ?= -Wall -Wextra -fsyntax-only +CLANG := clang +CFLAGS := -S -o /dev/null -x ir -Qunused-arguments OUTDIR ?= out -SRC := $(wildcard $(OUTDIR)/*.c) +SRC := $(wildcard $(OUTDIR)/*.ll) -SDL3_CFLAGS := $(shell pkg-config --cflags sdl3 2>/dev/null || echo) -GL_CFLAGS := $(shell pkg-config --cflags gl 2>/dev/null || echo) +.PHONY: all clean -SDL3_SRC := $(filter %_sdl3.c, $(SRC)) -PLAIN_SRC := $(filter-out %_sdl3.c, $(SRC)) - -.PHONY: all sdl3 clean - -all: $(PLAIN_SRC) - @for f in $(PLAIN_SRC); do \ +all: $(SRC) + @for f in $(SRC); do \ printf " CC %s\n" "$$(basename $$f)"; \ - $(CC) $(CFLAGS) "$$f" 2>&1 || exit 1; \ - done - -sdl3: $(SDL3_SRC) - @if [ -z "$(SDL3_SRC)" ]; then \ - echo " (no SDL3 tests)"; \ - exit 0; \ - fi - @echo " (with SDL3 flags)" ; \ - for f in $(SDL3_SRC); do \ - printf " CC %s\n" "$$(basename $$f)"; \ - $(CC) $(CFLAGS) $(SDL3_CFLAGS) $(GL_CFLAGS) "$$f" 2>&1 || exit 1; \ + $(CLANG) $(CFLAGS) "$$f" 2>&1 || exit 1; \ done clean: - rm -f $(OUTDIR)/*.c + rm -f $(OUTDIR)/*.ll diff --git a/tests/compile/out/.gitignore b/tests/compile/out/.gitignore index 064a8d8..fd64565 100644 --- a/tests/compile/out/.gitignore +++ b/tests/compile/out/.gitignore @@ -1 +1 @@ -*.c +*.ll diff --git a/tests/compile/run.js b/tests/compile/run.js index ff66b56..8ca879c 100644 --- a/tests/compile/run.js +++ b/tests/compile/run.js @@ -1,9 +1,9 @@ #!/usr/bin/env node /** - * C compilation test runner — flat DFA code generation. + * LLVM IR compilation test runner. * - * Block data objects are minimal: generateOwnCode() + optional generateFlowCode(). - * The shared CodeGen.emitFlat walks the graph, emitting labels + gotos. + * Generates .ll files from block data structures using the shared CodeGen + * module, then compiles them with `clang -S -o /dev/null` to verify valid IR. */ const fs = require("fs"); @@ -12,60 +12,62 @@ const { execSync } = require("child_process"); const CodeGen = require("../../codegen/index.js"); const OUTDIR = path.join(__dirname, "out"); -const MKFILE = path.join(__dirname, "Makefile"); +const CLANG = "clang -S -o /dev/null -x ir"; // ── Block data helpers ──────────────────────────────────────────────── function startBlock(id) { return { - id: id, - isDecision: false, - nextFlow: [], + id: id, nextFlow: [], generateOwnCode: function () { return ""; }, }; } function endBlock(id, exitCode) { - var code = exitCode === 0 ? " return 0;\n" : " exit(" + exitCode + ");\n"; return { - id: id, - isDecision: false, - nextFlow: [], - generateOwnCode: function () { return code; }, + id: id, nextFlow: [], + generateOwnCode: function () { + if (exitCode === 0) return " ret i32 0\n"; + return " call void @exit(i32 " + exitCode + ")\n unreachable\n"; + }, + generateFlowCode: function () { return ""; }, }; } function procCallBlock(id, procName, inputs, returnStyle, returnValueName, okTargets, errTargets) { - var call = procName + "(" + inputs.join(", ") + ")"; + var callArgs = inputs.join(", "); + var allTargets = (okTargets || []).concat(errTargets || []); var self = { - id: id, - isDecision: false, - nextFlow: (okTargets || []).concat(errTargets || []).map(function (t) { return { toElement: t }; }), + id: id, nextFlow: allTargets.map(function (t) { return { toElement: t }; }), errTargets: errTargets || [], generateOwnCode: function () { - if (returnStyle === "error" && self.errTargets.length > 0) return ""; - if (returnStyle === "value" && returnValueName && self.errTargets.length > 0) { - return " " + returnValueName + " = " + call + ";\n"; + if (returnStyle === "error" && self.errTargets.length > 0) { + return " %_r_" + id + " = call i32 @" + procName + "(" + callArgs + ")\n"; } - return " " + call + ";\n"; + if (returnStyle === "value" && returnValueName && self.errTargets.length > 0) { + return " %_v_" + id + " = call i32 @" + procName + "(" + callArgs + ")\n"; + } + return " call void @" + procName + "(" + callArgs + ")\n"; }, generateFlowCode: function () { if (self.errTargets.length === 0) { - return self.nextFlow.map(function (p) { return " goto " + p.toElement.id + ";\n"; }).join(""); + return self.nextFlow.map(function (p) { return " br label %" + p.toElement.id + "\n"; }).join(""); } var errT = self.errTargets[0]; var okT = null; for (var i = 0; i < self.nextFlow.length; i++) { - var t = self.nextFlow[i].toElement; - if (self.errTargets.indexOf(t) === -1) { okT = t; break; } + if (self.errTargets.indexOf(self.nextFlow[i].toElement) === -1) { + okT = self.nextFlow[i].toElement; break; + } } var out = ""; if (returnStyle === "error") { - out += " if (" + call + " != 0) goto " + errT.id + ";\n"; + out += " %_cmp_" + id + " = icmp ne i32 %_r_" + id + ", 0\n"; + out += " br i1 %_cmp_" + id + ", label %" + errT.id + ", label %" + (okT ? okT.id : "") + "\n"; } else if (returnStyle === "value" && returnValueName) { - out += " if (" + returnValueName + " != 0) goto " + errT.id + ";\n"; + out += " %_cmp_" + id + " = icmp ne i32 %_v_" + id + ", 0\n"; + out += " br i1 %_cmp_" + id + ", label %" + errT.id + ", label %" + (okT ? okT.id : "") + "\n"; } - if (okT) out += " goto " + okT.id + ";\n"; return out; }, }; @@ -74,10 +76,8 @@ function procCallBlock(id, procName, inputs, returnStyle, returnValueName, okTar function assignBlock(id, target, value) { return { - id: id, - isDecision: false, - nextFlow: [], - generateOwnCode: function () { return " " + target + " = " + value + ";\n"; }, + id: id, nextFlow: [], + generateOwnCode: function () { return " store i32 " + value + ", i32* %" + target + "\n"; }, }; } @@ -91,18 +91,25 @@ function buildConsoleTemplate() { var start = startBlock("start"); var end = endBlock("end", 0); var errEnd = endBlock("errEnd", 1); - var printf = procCallBlock("printf", "printf", ['"Hello, World!\\n"'], "value", "result", [end], [errEnd]); + CodeGen.allocString("Hello, World!\n"); + var strArg = CodeGen.llvmStringArg("Hello, World!\n", 0); + var printf = procCallBlock("printf", "printf", [strArg], "value", "result", [end], [errEnd]); link(start, printf); - return { entry: start, includes: ["#include ", "#include "], declarations: " int result;" }; + return { + entry: start, + decls: "declare i32 @printf(i8*, ...)\ndeclare void @exit(i32)", + globals: CodeGen.stringGlobal(0), + vars: " %r_ = alloca i32\n %v_ = alloca i32\n", + }; } function buildErrorSimpleTemplate() { var start = startBlock("start"); var end = endBlock("end", 0); var errEnd = endBlock("errEnd", 1); - var call = procCallBlock("init", "func", [], "error", null, [end], [errEnd]); + var call = procCallBlock("init", "func", ["i32 0"], "error", null, [end], [errEnd]); link(start, call); - return { entry: start, includes: ["#include "], declarations: "", proto: ["int func(void);"] }; + return { entry: start, decls: "declare i32 @func(i32)\ndeclare void @exit(i32)", globals: "", vars: "" }; } function buildErrorMergeTemplate() { @@ -110,11 +117,11 @@ function buildErrorMergeTemplate() { var end = endBlock("end", 0); var logged = assignBlock("log", "logged", "1"); var done = assignBlock("done", "done", "1"); - var call = procCallBlock("init", "func", [], "error", null, [done], [logged]); + var call = procCallBlock("init", "func", ["i32 0"], "error", null, [done], [logged]); link(logged, done); link(done, end); link(start, call); - return { entry: start, includes: ["#include "], declarations: " int logged;\n int done;", proto: ["int func(void);"] }; + return { entry: start, decls: "declare i32 @func(i32)\ndeclare void @exit(i32)", globals: "", vars: " %logged = alloca i32\n %done = alloca i32\n" }; } function errorSequentialTemplate() { @@ -126,47 +133,36 @@ function errorSequentialTemplate() { var call2 = procCallBlock("b", "init_b", [], "error", null, [end], [errEnd2]); link(start, call1); link(call1, call2); - return { entry: start, includes: ["#include "], declarations: "", proto: ["int init_a(void);", "int init_b(void);"] }; + return { entry: start, decls: "declare i32 @init_a()\ndeclare i32 @init_b()\ndeclare void @exit(i32)", globals: "", vars: "" }; } function buildErrorValueTemplate() { var start = startBlock("start"); var end = endBlock("end", 0); var errEnd = endBlock("errEnd", 1); - var call = procCallBlock("open", "open_file", ['"data.txt"', "0"], "value", "fd", [end], [errEnd]); + var call = procCallBlock("open", "open_file", ['i8* null', 'i32 0'], "value", "fd", [end], [errEnd]); link(start, call); - return { entry: start, includes: ["#include "], declarations: " int fd;", proto: ["int open_file(const char *path, int flags);"] }; + return { entry: start, decls: "declare i32 @open_file(i8*, i32)\ndeclare void @exit(i32)", globals: "", vars: " %fd = alloca i32\n" }; } // ── Source generation ────────────────────────────────────────────────── function generateSource(opts) { - var parts = ["// --- WiseCode\u00ae ---"]; - for (var i = 0; i < opts.includes.length; i++) parts.push(opts.includes[i]); - if (opts.proto) { - for (var i = 0; i < opts.proto.length; i++) parts.push(opts.proto[i]); - } - parts.push(""); - parts.push("int main() {"); - if (opts.declarations) parts.push(opts.declarations); - parts.push(CodeGen.emitFlat(opts.entry)); - parts.push("}"); - parts.push("// --- END ---"); - return parts.join("\n"); + return CodeGen.generateIRFile(opts.entry, opts.decls, opts.globals, opts.vars); } // ── Main ──────────────────────────────────────────────────────────────── function writeSource(label, source) { fs.mkdirSync(OUTDIR, { recursive: true }); - var file = path.join(OUTDIR, label.replace(/[^a-z0-9]/gi, "_") + ".c"); + var file = path.join(OUTDIR, label.replace(/[^a-z0-9]/gi, "_") + ".ll"); console.log(" GEN " + path.basename(file) + " (" + source.split("\n").length + " lines)"); fs.writeFileSync(file, source, "utf8"); } -console.log("\nGenerating C compilation tests...\n"); +console.log("\nGenerating LLVM IR compilation tests...\n"); -try { execSync("rm -f " + OUTDIR + "/*.c", { stdio: "pipe" }); } catch (_) {} +try { execSync("rm -f " + OUTDIR + "/*.ll", { stdio: "pipe" }); } catch (_) {} writeSource("test_console", generateSource(buildConsoleTemplate())); writeSource("test_error_simple", generateSource(buildErrorSimpleTemplate())); @@ -174,21 +170,13 @@ writeSource("test_error_merge", generateSource(buildErrorMergeTemplate())); writeSource("test_error_sequential", generateSource(errorSequentialTemplate())); writeSource("test_error_value", generateSource(buildErrorValueTemplate())); -console.log("\nRunning make...\n"); +console.log("\nRunning clang...\n"); try { - execSync('make -C "' + OUTDIR + '" -f "' + MKFILE + '" OUTDIR="' + OUTDIR + '" all 2>&1', { stdio: "inherit" }); + execSync('make -C "' + OUTDIR + '" -f "' + path.join(__dirname, "Makefile") + '" OUTDIR="' + OUTDIR + '" all 2>&1', { stdio: "inherit" }); } catch (e) { console.error("\n\u2717 Compilation failed.\n"); process.exit(1); } -try { - execSync("pkg-config --exists sdl3", { stdio: "pipe" }); - console.log("\nSDL3 headers found, generating SDL3 test...\n"); - console.log(" (SDL3 generation not yet implemented; skipped)\n"); -} catch (_) { - console.log("\nSDL3 headers not found (skipping SDL3 compilation test).\n"); -} - -console.log("\u2713 All compilation tests passed.\n"); +console.log("\u2713 All LLVM IR compilation tests passed.\n"); diff --git a/wcprogram.js b/wcprogram.js index c9eed70..300fa28 100644 --- a/wcprogram.js +++ b/wcprogram.js @@ -190,29 +190,27 @@ class WCProgram extends HTMLElement { return { boolDefs, scalarDefs, vectorDefs, stringDefs, arrayDefs }; } - _generateIncludes(proccalls, hasStrings) { - const libNames = new Set(); - for (const p of proccalls) { - const qname = p._selectedProc; - if (qname) { - const lib = qname.split("/")[0]; - if (lib) libNames.add(lib); + _generateLLVMDecls(proccalls) { + var seen = new Set(); + var lines = []; + for (var pi = 0; pi < proccalls.length; pi++) { + var qname = proccalls[pi]._selectedProc; + if (!qname || seen.has(qname)) continue; + seen.add(qname); + var decl = LLVM_DECL_MAP[qname]; + if (decl) { + lines.push(decl); + } else { + var fn = qname.split("/")[1] || qname; + lines.push("declare i32 @" + fn + "(...)"); } } - let includes = Array.from(libNames).map(l => { - const path = INCLUDE_MAP[l] || (l + ".h"); - return `#include <${path}>`; - }).join("\n"); - if (hasStrings) { - const hdr = "#include \n#include "; - includes = includes ? includes + "\n" + hdr : hdr; - } - return includes; + return lines.join("\n"); } generateCode() { - const outputEl = $id("c-code"); - if (!outputEl) { showError("Missing #c-code output element"); return; } + const outputEl = $id("ir-code"); + if (!outputEl) { showError("Missing #ir-code output element"); return; } const { usedQuantities, usedUnits } = this._gatherUsedTypes(); @@ -220,51 +218,46 @@ class WCProgram extends HTMLElement { const unitDefs = this._generateUnitDefs(usedUnits); const { boolDefs, scalarDefs, vectorDefs, stringDefs, arrayDefs } = this._generateVariableDeclarations(); - const hasStrings = $id("variable-area").querySelectorAll("wc-string").length > 0; - const stringStruct = hasStrings - ? "typedef struct { char *data; long int length; } string;\n\n" - : ""; + CodeGen.resetStringPool(); const firstChild = this.firstElementChild; const body = firstChild && typeof CodeGen.emitFlat === "function" ? CodeGen.emitFlat(firstChild) - : "\n// (no flowchart body)\n"; - const proccalls = this.querySelectorAll("wc-proccall"); - const includes = this._generateIncludes(proccalls, hasStrings); + : "; no flowchart body\n"; + // Collect external function declarations from used libraries + const proccalls = this.querySelectorAll("wc-proccall"); + const decls = this._generateLLVMDecls(proccalls); + + // Collect string globals + const strPool = CodeGen.getStringPool(); + const globals = strPool.map(function (_, i) { return CodeGen.stringGlobal(i); }).join("\n"); + + // Variable declarations -> alloca instructions in the prologue const dataSection = (boolDefs || scalarDefs || vectorDefs || stringDefs || arrayDefs) ? [boolDefs, scalarDefs, vectorDefs, stringDefs, arrayDefs] - .filter(Boolean).map(s => s + "\n").join("") + .filter(Boolean).map(function (s) { return s + "\n"; }).join("") : ""; - const hasReturnOrExit = - body.includes("\n return ") || body.includes("\n exit("); - const raw = - "// --- WiseCode\u00ae ---\n" + - (includes ? includes + "\n\n" : "") + - "int main() {\n" + - (typeDefs ? typeDefs + "\n\n" : "") + - (unitDefs ? unitDefs + "\n\n" : "") + - stringStruct + dataSection + body + - (hasReturnOrExit ? "" : "return 0;\n") + - "}\n// --- END ---"; + const raw = CodeGen.generateIRFile(firstChild, decls, globals, dataSection); outputEl.textContent = raw; if (typeof hljs !== "undefined") hljs.highlightElement(outputEl); - outputEl.parentElement.dataset.language = "c"; + outputEl.parentElement.dataset.language = "llvm"; } } /** - * Maps library names to their C #include paths. - * Libraries not listed here use the default `<{name}.h>` convention. + * Maps library/function names to their LLVM IR `declare` signature. + * Key is the proc qname, value is the declare line. + * Functions not listed get a generic `declare i32 @name(...)`. */ -const INCLUDE_MAP = { - "sdl3": "SDL3/SDL.h", - "glfw": "GLFW/glfw3.h", - "gl": "GL/gl.h", +const LLVM_DECL_MAP = { + "stdio/printf": "declare i32 @printf(i8*, ...)", + "stdlib/exit": "declare void @exit(i32)", }; + // ═══════════════════════════════════════════════════════════════════════════════ // Block registry // ═══════════════════════════════════════════════════════════════════════════════