Files
adrien d53f92482d 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
2026-06-23 03:39:22 +02:00

183 lines
7.1 KiB
JavaScript

#!/usr/bin/env node
/**
* LLVM IR compilation test runner.
*
* 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");
const path = require("path");
const { execSync } = require("child_process");
const CodeGen = require("../../codegen/index.js");
const OUTDIR = path.join(__dirname, "out");
const CLANG = "clang -S -o /dev/null -x ir";
// ── Block data helpers ────────────────────────────────────────────────
function startBlock(id) {
return {
id: id, nextFlow: [],
generateOwnCode: function () { return ""; },
};
}
function endBlock(id, exitCode) {
return {
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 callArgs = inputs.join(", ");
var allTargets = (okTargets || []).concat(errTargets || []);
var self = {
id: id, nextFlow: allTargets.map(function (t) { return { toElement: t }; }),
errTargets: errTargets || [],
generateOwnCode: function () {
if (returnStyle === "error" && self.errTargets.length > 0) {
return " %_r_" + id + " = call i32 @" + procName + "(" + callArgs + ")\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 " br label %" + p.toElement.id + "\n"; }).join("");
}
var errT = self.errTargets[0];
var okT = null;
for (var i = 0; i < self.nextFlow.length; i++) {
if (self.errTargets.indexOf(self.nextFlow[i].toElement) === -1) {
okT = self.nextFlow[i].toElement; break;
}
}
var out = "";
if (returnStyle === "error") {
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 += " %_cmp_" + id + " = icmp ne i32 %_v_" + id + ", 0\n";
out += " br i1 %_cmp_" + id + ", label %" + errT.id + ", label %" + (okT ? okT.id : "") + "\n";
}
return out;
},
};
return self;
}
function assignBlock(id, target, value) {
return {
id: id, nextFlow: [],
generateOwnCode: function () { return " store i32 " + value + ", i32* %" + target + "\n"; },
};
}
function link(from, to) {
from.nextFlow.push({ toElement: to });
}
// ── Template builders ──────────────────────────────────────────────────
function buildConsoleTemplate() {
var start = startBlock("start");
var end = endBlock("end", 0);
var errEnd = endBlock("errEnd", 1);
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,
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", ["i32 0"], "error", null, [end], [errEnd]);
link(start, call);
return { entry: start, decls: "declare i32 @func(i32)\ndeclare void @exit(i32)", globals: "", vars: "" };
}
function buildErrorMergeTemplate() {
var start = startBlock("start");
var end = endBlock("end", 0);
var logged = assignBlock("log", "logged", "1");
var done = assignBlock("done", "done", "1");
var call = procCallBlock("init", "func", ["i32 0"], "error", null, [done], [logged]);
link(logged, done);
link(done, end);
link(start, call);
return { entry: start, decls: "declare i32 @func(i32)\ndeclare void @exit(i32)", globals: "", vars: " %logged = alloca i32\n %done = alloca i32\n" };
}
function errorSequentialTemplate() {
var start = startBlock("start");
var end = endBlock("end", 0);
var errEnd1 = endBlock("err1", 1);
var errEnd2 = endBlock("err2", 2);
var call1 = procCallBlock("a", "init_a", [], "error", null, null, [errEnd1]);
var call2 = procCallBlock("b", "init_b", [], "error", null, [end], [errEnd2]);
link(start, call1);
link(call1, call2);
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", ['i8* null', 'i32 0'], "value", "fd", [end], [errEnd]);
link(start, call);
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) {
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, "_") + ".ll");
console.log(" GEN " + path.basename(file) + " (" + source.split("\n").length + " lines)");
fs.writeFileSync(file, source, "utf8");
}
console.log("\nGenerating LLVM IR compilation tests...\n");
try { execSync("rm -f " + OUTDIR + "/*.ll", { stdio: "pipe" }); } catch (_) {}
writeSource("test_console", generateSource(buildConsoleTemplate()));
writeSource("test_error_simple", generateSource(buildErrorSimpleTemplate()));
writeSource("test_error_merge", generateSource(buildErrorMergeTemplate()));
writeSource("test_error_sequential", generateSource(errorSequentialTemplate()));
writeSource("test_error_value", generateSource(buildErrorValueTemplate()));
console.log("\nRunning clang...\n");
try {
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);
}
console.log("\u2713 All LLVM IR compilation tests passed.\n");