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
// ═══════════════════════════════════════════════════════════════════════════════