feat: switch from C to LLVM IR code generation

- Replace all C code generation with LLVM IR (flat DFA model with
  bb_ prefix labels to avoid SSA value name collisions)
- Remove c.min.js, add llvm.min.js highlight.js language definition
- Update shell.nix: gcc -> clang, remove sdl3/mesa deps
- Download LLVM Language Reference to docs/llvm/LangRef.html
- Rename #c-code to #ir-code in HTML, JS, and CSS
- Replace _generateIncludes with _generateLLVMDecls for extern
  function declarations using LLVM_DECL_MAP
- Add string pool (CodeGen.allocString, stringGlobal, llvmStringArg)
  for LLVM IR global string constants with getelementptr references
This commit is contained in:
2026-06-23 03:39:22 +02:00
parent 35959784de
commit d53f92482d
12 changed files with 289 additions and 296 deletions
+91 -34
View File
@@ -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");
};