- 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
183 lines
7.1 KiB
JavaScript
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");
|