summary refs log tree commit diff
diff options
context:
space:
mode:
authorNguyễn Gia Phong <cnx@loang.net>2024-02-26 17:49:44 +0900
committerNguyễn Gia Phong <cnx@loang.net>2024-03-01 05:14:50 +0900
commitf6c6302192bfc6f1bfba253367317cdb52ba4370 (patch)
treeefd23ddd371ac9cd98082ca8f1b3ddfd1e97d245
parentc9dff9cb9c9bb9ac685d59d12f7f991c30a1cd94 (diff)
downloadroux-f6c6302192bfc6f1bfba253367317cdb52ba4370.tar.gz
Port entry point to Zig
-rw-r--r--Makefile30
-rw-r--r--all.h5
-rw-r--r--build.zig18
-rw-r--r--emit.c2
-rw-r--r--main.c197
-rw-r--r--parse.c17
-rw-r--r--src/cimport.zig280
-rw-r--r--src/main.zig253
8 files changed, 559 insertions, 243 deletions
diff --git a/Makefile b/Makefile
index 71d5065..a81e46d 100644
--- a/Makefile
+++ b/Makefile
@@ -4,27 +4,29 @@
 PREFIX = /usr/local
 BINDIR = $(PREFIX)/bin
 
-COMMSRC  = main.c util.c parse.c abi.c cfg.c mem.c ssa.c alias.c load.c \
+ZIGSRC   = src/main.zig src/cimport.zig
+COMMSRC  = util.c parse.c abi.c cfg.c mem.c ssa.c alias.c load.c \
            copy.c fold.c simpl.c live.c spill.c rega.c emit.c
 AMD64SRC = amd64/targ.c amd64/sysv.c amd64/isel.c amd64/emit.c
 ARM64SRC = arm64/targ.c arm64/abi.c arm64/isel.c arm64/emit.c
 RV64SRC  = rv64/targ.c rv64/abi.c rv64/isel.c rv64/emit.c
-SRCALL   = $(COMMSRC) $(AMD64SRC) $(ARM64SRC) $(RV64SRC)
+SRCALL   = $(ZIGSRC) $(COMMSRC) $(AMD64SRC) $(ARM64SRC) $(RV64SRC)
 
-qbe: $(SRCALL) all.h ops.h amd64/all.h arm64/all.h rv64/all.h
+zig-out/bin/roux: $(SRCALL) all.h ops.h amd64/all.h arm64/all.h rv64/all.h
 	zig build
-	ln -fs zig-out/bin/qbe qbe
 
-install: qbe
-	mkdir -p "$(DESTDIR)$(BINDIR)"
-	install -m755 qbe "$(DESTDIR)$(BINDIR)/qbe"
+install: roux
+	zig build install --prefix="$(DESTDIR)$(BINDIR)"
 
 uninstall:
-	rm -f "$(DESTDIR)$(BINDIR)/qbe"
+	zig build uninstall --prefix="$(DESTDIR)$(BINDIR)"
 
 clean:
 	rm -fr qbe zig-cache zig-out
 
+qbe: zig-out/bin/roux
+	ln -fs $< $@
+
 check: qbe
 	tools/test.sh all
 
@@ -37,14 +39,4 @@ check-rv64: qbe
 src:
 	@echo $(SRCALL)
 
-80:
-	@for F in $(SRCALL);                       \
-	do                                         \
-		awk "{                             \
-			gsub(/\\t/, \"        \"); \
-			if (length(\$$0) > $@)     \
-				printf(\"$$F:%d: %s\\n\", NR, \$$0); \
-		}" < $$F;                          \
-	done
-
-.PHONY: clean clean-gen check check-arm64 src 80 install uninstall
+.PHONY: clean check check-arm64 check-rv64 src install uninstall
diff --git a/all.h b/all.h
index e421b9c..d383c64 100644
--- a/all.h
+++ b/all.h
@@ -502,7 +502,8 @@ bshas(BSet *bs, uint elt)
 
 /* parse.c */
 extern Op optab[NOp];
-void parse(FILE *, char *, void (char *), void (Dat *), void (Fn *));
+void parse(FILE *, char *, FILE *,
+	void (Dat *, FILE *), void (Fn *, FILE *));
 void printfn(Fn *, FILE *);
 void printref(Ref, Fn *, FILE *);
 void err(char *, ...) __attribute__((noreturn));
@@ -540,8 +541,6 @@ void loadopt(Fn *);
 
 /* ssa.c */
 void filluse(Fn *);
-void fillpreds(Fn *);
-void fillrpo(Fn *);
 void ssa(Fn *);
 void ssacheck(Fn *);
 
diff --git a/build.zig b/build.zig
index 14373c9..7609463 100644
--- a/build.zig
+++ b/build.zig
@@ -1,39 +1,25 @@
 //! Build recipe
-const builtin = @import("builtin");
 const std = @import("std");
 
 pub fn build(b: *std.Build) void {
     const target = b.standardTargetOptions(.{});
-    const arch = target.cpu_arch orelse builtin.target.cpu.arch;
     const optimize = b.standardOptimizeOption(.{});
 
     const bin = b.addExecutable(.{
         .name = "roux",
+        .root_source_file = .{ .path = "src/main.zig" },
         .target = target,
         .optimize = optimize,
         .link_libc = true,
     });
     const cflags = .{
         "-std=c99", "-g", "-Wall", "-Wextra", "-Wpedantic",
-        switch (target.os_tag orelse builtin.target.os.tag) {
-            .macos => switch (arch) {
-                .aarch64 => "-DDeftgt=T_arm64_apple",
-                .x86_64 => "-DDeftgt=T_amd64_apple",
-                else => unreachable,
-            },
-            else => switch (arch) {
-                .aarch64 => "-DDeftgt=T_arm64",
-                .riscv64 => "-DDeftgt=T_rv64",
-                .x86_64 => "-DDeftgt=T_amd64_sysv",
-                else => unreachable,
-            },
-        },
         // https://lists.sr.ht/~mpu/qbe/<CYB4FWK7MACC.2IF4DEL4C9BF1@loang.net>
         "-fno-sanitize=undefined",
     };
     bin.addIncludePath(.{ .path = "." });
     bin.addCSourceFiles(&.{
-        "main.c", "util.c", "parse.c", "abi.c", "cfg.c",
+        "util.c", "parse.c", "abi.c", "cfg.c",
         "mem.c", "ssa.c", "alias.c", "load.c", "copy.c", "fold.c",
         "simpl.c", "live.c", "spill.c", "rega.c", "emit.c",
     }, &cflags);
diff --git a/emit.c b/emit.c
index 490628e..c4f6c49 100644
--- a/emit.c
+++ b/emit.c
@@ -79,6 +79,8 @@ emitdat(Dat *d, FILE *f)
 			emitlnk(d->name, d->lnk, SecBss, f);
 			fprintf(f, "\t.fill %"PRId64",1,0\n", zero);
 		}
+		fputs("/* end data */\n\n", f);
+		freeall();
 		break;
 	case DZ:
 		if (zero != -1)
diff --git a/main.c b/main.c
deleted file mode 100644
index fa60d3b..0000000
--- a/main.c
+++ /dev/null
@@ -1,197 +0,0 @@
-#include "all.h"
-#include <ctype.h>
-#include <getopt.h>
-
-Target T;
-
-char debug['Z'+1] = {
-	['P'] = 0, /* parsing */
-	['M'] = 0, /* memory optimization */
-	['N'] = 0, /* ssa construction */
-	['C'] = 0, /* copy elimination */
-	['F'] = 0, /* constant folding */
-	['A'] = 0, /* abi lowering */
-	['I'] = 0, /* instruction selection */
-	['L'] = 0, /* liveness */
-	['S'] = 0, /* spilling */
-	['R'] = 0, /* reg. allocation */
-};
-
-extern Target T_amd64_sysv;
-extern Target T_amd64_apple;
-extern Target T_arm64;
-extern Target T_arm64_apple;
-extern Target T_rv64;
-
-static Target *tlist[] = {
-	&T_amd64_sysv,
-	&T_amd64_apple,
-	&T_arm64,
-	&T_arm64_apple,
-	&T_rv64,
-	0
-};
-static FILE *outf;
-static int dbg;
-
-static void
-data(Dat *d)
-{
-	if (dbg)
-		return;
-	emitdat(d, outf);
-	if (d->type == DEnd) {
-		fputs("/* end data */\n\n", outf);
-		freeall();
-	}
-}
-
-static void
-func(Fn *fn)
-{
-	uint n;
-
-	if (dbg)
-		fprintf(stderr, "**** Function %s ****", fn->name);
-	if (debug['P']) {
-		fprintf(stderr, "\n> After parsing:\n");
-		printfn(fn, stderr);
-	}
-	T.abi0(fn);
-	fillrpo(fn);
-	fillpreds(fn);
-	filluse(fn);
-	promote(fn);
-	filluse(fn);
-	ssa(fn);
-	filluse(fn);
-	ssacheck(fn);
-	fillalias(fn);
-	loadopt(fn);
-	filluse(fn);
-	fillalias(fn);
-	coalesce(fn);
-	filluse(fn);
-	ssacheck(fn);
-	copy(fn);
-	filluse(fn);
-	fold(fn);
-	T.abi1(fn);
-	simpl(fn);
-	fillpreds(fn);
-	filluse(fn);
-	T.isel(fn);
-	fillrpo(fn);
-	filllive(fn);
-	fillloop(fn);
-	fillcost(fn);
-	spill(fn);
-	rega(fn);
-	fillrpo(fn);
-	simpljmp(fn);
-	fillpreds(fn);
-	fillrpo(fn);
-	assert(fn->rpo[0] == fn->start);
-	for (n=0;; n++)
-		if (n == fn->nblk-1) {
-			fn->rpo[n]->link = 0;
-			break;
-		} else
-			fn->rpo[n]->link = fn->rpo[n+1];
-	if (!dbg) {
-		T.emitfn(fn, outf);
-		fprintf(outf, "/* end function %s */\n\n", fn->name);
-	} else
-		fprintf(stderr, "\n");
-	freeall();
-}
-
-static void
-dbgfile(char *fn)
-{
-	emitdbgfile(fn, outf);
-}
-
-int
-main(int ac, char *av[])
-{
-	Target **t;
-	FILE *inf, *hf;
-	char *f, *sep;
-	int c;
-
-	T = Deftgt;
-	outf = stdout;
-	while ((c = getopt(ac, av, "hd:o:t:")) != -1)
-		switch (c) {
-		case 'd':
-			for (; *optarg; optarg++)
-				if (isalpha(*optarg)) {
-					debug[toupper(*optarg)] = 1;
-					dbg = 1;
-				}
-			break;
-		case 'o':
-			if (strcmp(optarg, "-") != 0) {
-				outf = fopen(optarg, "w");
-				if (!outf) {
-					fprintf(stderr, "cannot open '%s'\n", optarg);
-					exit(1);
-				}
-			}
-			break;
-		case 't':
-			if (strcmp(optarg, "?") == 0) {
-				puts(T.name);
-				exit(0);
-			}
-			for (t=tlist;; t++) {
-				if (!*t) {
-					fprintf(stderr, "unknown target '%s'\n", optarg);
-					exit(1);
-				}
-				if (strcmp(optarg, (*t)->name) == 0) {
-					T = **t;
-					break;
-				}
-			}
-			break;
-		case 'h':
-		default:
-			hf = c != 'h' ? stderr : stdout;
-			fprintf(hf, "%s [OPTIONS] {file.ssa, -}\n", av[0]);
-			fprintf(hf, "\t%-11s prints this help\n", "-h");
-			fprintf(hf, "\t%-11s output to file\n", "-o file");
-			fprintf(hf, "\t%-11s generate for a target among:\n", "-t <target>");
-			fprintf(hf, "\t%-11s ", "");
-			for (t=tlist, sep=""; *t; t++, sep=", ") {
-				fprintf(hf, "%s%s", sep, (*t)->name);
-				if (*t == &Deftgt)
-					fputs(" (default)", hf);
-			}
-			fprintf(hf, "\n");
-			fprintf(hf, "\t%-11s dump debug information\n", "-d <flags>");
-			exit(c != 'h');
-		}
-
-	do {
-		f = av[optind];
-		if (!f || strcmp(f, "-") == 0) {
-			inf = stdin;
-			f = "-";
-		} else {
-			inf = fopen(f, "r");
-			if (!inf) {
-				fprintf(stderr, "cannot open '%s'\n", f);
-				exit(1);
-			}
-		}
-		parse(inf, f, dbgfile, data, func);
-		fclose(inf);
-	} while (++optind < ac);
-
-	if (!dbg)
-		T.emitfin(outf);
-
-	exit(0);
-}
diff --git a/parse.c b/parse.c
index 738ec5b..ae7f785 100644
--- a/parse.c
+++ b/parse.c
@@ -1077,7 +1077,7 @@ parsedatstr(Dat *d)
 }
 
 static void
-parsedat(void cb(Dat *), Lnk *lnk)
+parsedat(void cb(Dat *, FILE *), Lnk *lnk, FILE *outf)
 {
 	char name[NString] = {0};
 	int t;
@@ -1097,7 +1097,7 @@ parsedat(void cb(Dat *), Lnk *lnk)
 	d.type = DStart;
 	d.name = name;
 	d.lnk = lnk;
-	cb(&d);
+	cb(&d, outf);
 
 	if (t != Tlbrace)
 		err("expected data contents in { .. }");
@@ -1130,7 +1130,7 @@ parsedat(void cb(Dat *), Lnk *lnk)
 				parsedatstr(&d);
 			else
 				err("constant literal expected");
-			cb(&d);
+			cb(&d, outf);
 			t = nextnl();
 		} while (t == Tint || t == Tflts || t == Tfltd || t == Tstr || t == Tglo);
 		if (t == Trbrace)
@@ -1140,7 +1140,7 @@ parsedat(void cb(Dat *), Lnk *lnk)
 	}
 Done:
 	d.type = DEnd;
-	cb(&d);
+	cb(&d, outf);
 }
 
 static int
@@ -1177,7 +1177,8 @@ parselnk(Lnk *lnk)
 }
 
 void
-parse(FILE *f, char *path, void dbgfile(char *), void data(Dat *), void func(Fn *))
+parse(FILE *f, char *path, FILE *outf,
+	void data(Dat *, FILE *), void func(Fn *, FILE *))
 {
 	Lnk lnk;
 	uint n;
@@ -1196,13 +1197,13 @@ parse(FILE *f, char *path, void dbgfile(char *), void data(Dat *), void func(Fn
 			err("top-level definition expected");
 		case Tdbgfile:
 			expect(Tstr);
-			dbgfile(tokval.str);
+			emitdbgfile(tokval.str, outf);
 			break;
 		case Tfunc:
-			func(parsefn(&lnk));
+			func(parsefn(&lnk), outf);
 			break;
 		case Tdata:
-			parsedat(data, &lnk);
+			parsedat(data, &lnk, outf);
 			break;
 		case Ttype:
 			parsetyp();
diff --git a/src/cimport.zig b/src/cimport.zig
new file mode 100644
index 0000000..65666b2
--- /dev/null
+++ b/src/cimport.zig
@@ -0,0 +1,280 @@
+pub const FILE = stdio.FILE;
+pub const fclose = stdio.fclose;
+pub const fopen = stdio.fopen;
+pub const fputs = stdio.fputs;
+pub const stdio = @cImport(@cInclude("stdio.h"));
+
+pub const bits = u64;
+
+pub const Target = extern struct {
+    name: [16:0]u8,
+    apple: u8,
+    /// First general-purpose register.
+    gpr0: c_int,
+    ngpr: c_int,
+    /// First floating-point register.
+    fpr0: c_int,
+    nfpr: c_int,
+    /// Globally live registers, e.g. sp, fp.
+    rglob: bits,
+    nrglob: c_int,
+    /// Caller-save.
+    rsave: [*c]c_int,
+    nrsave: [2]c_int,
+    retregs: *const fn (Ref, [*c]c_int) callconv(.C) bits,
+    argregs: *const fn (Ref, [*c]c_int) callconv(.C) bits,
+    memargs: *const fn (c_int) callconv(.C) c_int,
+    abi0: *const fn (?*Fn) callconv(.C) void,
+    abi1: *const fn (?*Fn) callconv(.C) void,
+    isel: *const fn (?*Fn) callconv(.C) void,
+    emitfn: *const fn (?*Fn, *FILE) callconv(.C) void,
+    emitfin: *const fn (*FILE) callconv(.C) void,
+    asloc: [4]u8,
+    assym: [4]u8,
+};
+
+pub const BSet = extern struct {
+    nt: c_uint,
+    t: [*c]bits,
+};
+
+pub const Ref = extern struct {
+    // type: u3,
+    // val: u29,
+    ref: u32,
+};
+
+pub const Ins = extern struct {
+    // op: u30,
+    // cls: u2,
+    op: u32,
+    to: Ref,
+    arg: [2]Ref,
+};
+
+pub const Phi = extern struct {
+    to: Ref,
+    arg: ?*Ref,
+    blk: [*c]?*Blk,
+    narg: c_uint,
+    cls: c_int,
+    link: ?*Phi,
+};
+
+pub const Blk = extern struct {
+    phi: ?*Phi,
+    ins: ?*Ins,
+    nins: c_uint,
+    jmp: extern struct {
+        type: c_short,
+        arg: Ref,
+    },
+    s1: ?*Blk,
+    s2: ?*Blk,
+    link: ?*Blk,
+    id: c_uint,
+    visit: c_uint,
+    idom: ?*Blk,
+    dom: ?*Blk,
+    dlink: ?*Blk,
+    fron: [*c]?*Blk,
+    nfron: c_uint,
+    pred: [*c]?*Blk,
+    npred: c_uint,
+    in: [1]BSet,
+    out: [1]BSet,
+    gen: [1]BSet,
+    nlive: [2]c_int,
+    loop: c_int,
+    name: [80]u8,
+};
+
+pub const Use = extern struct {
+    type: enum(c_uint) { UXXX, UPhi, UIns, UJmp },
+    bid: c_uint,
+    u: extern union {
+        ins: ?*Ins,
+        phi: ?*Phi,
+    },
+};
+
+pub const Sym = extern struct {
+    type: enum (c_uint) { SGlo, SThr },
+    id: u32,
+};
+
+pub const Alias = extern struct {
+    type: enum (c_uint) {
+        ABot = 0,
+        ALoc = 1, // stack local
+        ACon = 2,
+        AEsc = 3, // stack escaping
+        ASym = 4,
+        AUnk = 6,
+    },
+    base: c_int,
+    offset: i64,
+    u: extern union {
+        sym: Sym,
+        loc: extern struct {
+            sz: c_int,
+            m: bits,
+        },
+    },
+    slot: [*c]Alias,
+};
+
+pub const Tmp = extern struct {
+    name: [80]u8,
+    def: ?*Ins,
+    use: [*c]Use,
+    ndef: c_uint,
+    nuse: c_uint,
+    bid: c_uint,
+    cost: c_uint,
+    slot: c_int,
+    cls: c_short,
+    hint: extern struct {
+        r: c_int,
+        w: c_int,
+        m: bits,
+    },
+    phi: c_int,
+    alias: Alias,
+    width: c_uint,
+    visit: c_int,
+};
+
+pub const Con = extern struct {
+    type: enum (c_uint) { CUndef, CBits, CAddr },
+    sym: Sym,
+    bits: extern union {
+        i: i64,
+        d: f64,
+        s: f32,
+    },
+    flt: u8,
+};
+
+pub const Addr = extern struct {
+    offset: Con,
+    base: Ref,
+    index: Ref,
+    scale: c_int,
+};
+
+pub const Mem = Addr;
+
+pub const Lnk = extern struct {
+    @"export": u8,
+    thread: u8,
+    @"align": u8,
+    sec: [*c]u8,
+    secf: [*c]u8,
+};
+
+pub const Fn = extern struct {
+    start: ?*Blk,
+    tmp: [*c]Tmp,
+    con: [*c]Con,
+    mem: ?*Mem,
+    ntmp: c_int,
+    ncon: c_int,
+    nmem: c_int,
+    nblk: c_uint,
+    retty: c_int,
+    retr: Ref,
+    rpo: [*c]?*Blk,
+    reg: bits,
+    slot: c_int,
+    vararg: u8,
+    dynalloc: u8,
+    name: [80]u8,
+    lnk: Lnk,
+};
+
+pub const Dat = extern struct {
+    type: enum(c_uint) { DStart, DEnd, DB, DH, DW, DL, DZ },
+    name: [*:0]u8,
+    lnk: [*c]Lnk,
+    u: extern union {
+        num: i64,
+        fltd: f64,
+        flts: f32,
+        str: [*c]u8,
+        ref: extern struct {
+            name: [*:0]u8,
+            off: i64,
+        },
+    },
+    isref: u8,
+    isstr: u8,
+};
+
+pub extern const T_arm64_apple: Target;
+pub extern const T_amd64_apple: Target;
+pub extern const T_arm64: Target;
+pub extern const T_rv64: Target;
+pub extern const T_amd64_sysv: Target;
+pub export const targets = [_]*Target{
+    &T_amd64_sysv,
+    &T_amd64_apple,
+    &T_arm64,
+    &T_arm64_apple,
+    &T_rv64,
+};
+pub export var T: Target = undefined;
+pub export var debug = [_]bool{ false } ** ('Z' + 1);
+
+// util.c
+pub extern fn freeall() void;
+
+// parse.c
+pub extern fn parse(in: *FILE, path: [*:0]const u8, out: *FILE,
+    *const fn (*Dat, *FILE) callconv(.C) void,
+    *const fn (*Fn, *FILE) callconv(.C) void) void;
+pub extern fn printfn(fun: *Fn, out: *FILE) void;
+
+// cfg.c
+pub extern fn fillpreds(fun: *Fn) void;
+pub extern fn fillrpo(fun: *Fn) void;
+pub extern fn fillloop(fun: *Fn) void;
+pub extern fn simpljmp(fun: *Fn) void;
+
+// mem.c
+pub extern fn promote(fun: *Fn) void;
+pub extern fn coalesce(fun: *Fn) void;
+
+// alias.c
+pub extern fn fillalias(fun: *Fn) void;
+
+// load.c
+pub extern fn loadopt(fun: *Fn) void;
+
+// ssa.c
+pub extern fn filluse(fun: *Fn) void;
+pub extern fn ssa(fun: *Fn) void;
+pub extern fn ssacheck(fun: *Fn) void;
+
+// copy.c
+pub extern fn copy(fun: *Fn) void;
+
+// fold.c
+pub extern fn fold(fun: *Fn) void;
+
+// simpl.c
+pub extern fn simpl(fun: *Fn) void;
+
+// live.c
+pub extern fn filllive(fun: *Fn) void;
+
+// spill.c
+pub extern fn fillcost(fun: *Fn) void;
+pub extern fn spill(fun: *Fn) void;
+
+// rega.c
+pub extern fn rega(fun: *Fn) void;
+
+// emit.c
+pub extern fn emitdat(dat: *Dat, out: *FILE) void;
+pub extern fn emitdbgfile(fun: [*:0]const u8, out: *FILE) void;
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..fbbb8b1
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,253 @@
+//! Parse command-line arguments and call the compiler.
+const Allocator = std.mem.Allocator;
+const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator;
+const arch = builtin.target.cpu.arch;
+const argsWithAllocator = std.process.argsWithAllocator;
+const assert = std.debug.assert;
+const builtin = @import("builtin");
+const c = @import("cimport.zig");
+const cWriter = std.io.cWriter;
+const eql = std.mem.eql;
+const exit = std.os.exit;
+const getStdErr = std.io.getStdErr;
+const getStdOut = std.io.getStdOut;
+const isAlphabetic = std.ascii.isAlphabetic;
+const span = std.mem.span;
+const std = @import("std");
+const toUpper = std.ascii.toUpper;
+
+/// Native target.
+const default_target = switch (builtin.target.os.tag) {
+    .macos => switch (arch) {
+        .aarch64 => &c.T_arm64_apple,
+        .x86_64 => &c.T_amd64_apple,
+        else => unreachable,
+    },
+    else => switch (arch) {
+        .aarch64 => &c.T_arm64,
+        .riscv64 => &c.T_rv64,
+        .x86_64 => &c.T_amd64_sysv,
+        else => unreachable,
+    },
+};
+
+/// Print the help message and exit with the given status.
+/// The usage is printed to stderr on an error status, to stdout otherwise.
+fn printUsage(prog: [:0]const u8, status: u8) !noreturn {
+    const stream = (if (status == 0) getStdOut() else getStdErr()).writer();
+    try stream.print(
+        \\{s} [OPTIONS] {{file.ssa, -}}
+        \\	-h          prints this help
+        \\	-o file     output to file
+        \\	-t <target> generate for a target among:
+        \\	            
+    , .{ prog });
+    for (c.targets, 0..) |target, i| {
+        if (i > 0)
+            try stream.writeAll(", ");
+        const name: [*:0]const u8 = @ptrCast(&target.name);
+        try stream.print("{s}", .{ span(name) });
+        if (target == default_target)
+            try stream.writeAll(" (default)");
+    }
+    try stream.writeAll(
+        \\
+        \\	-d <flags>  dump debug information:
+        \\	            P (parsing),
+        \\	            M (memory optimization),
+        \\	            N (SSA construction),
+        \\	            C (copy elimination),
+        \\	            F (constant folding),
+        \\	            A (ABI lowering),
+        \\	            I (instruction selection),
+        \\	            L (liveness),
+        \\	            S (spilling),
+        \\	            R (register allocation)
+        \\
+    );
+    exit(status);
+}
+
+/// Print the given message and exit.  The message is printed
+/// to stderr on an error status, to stdout otherwise.
+fn die(comptime format: []const u8, args: anytype, status: u8) !noreturn {
+    const stream = if (status == 0) getStdOut() else getStdErr();
+    try stream.writer().print(format, args);
+    exit(status);
+}
+
+/// Function emitter.
+const Fun = struct {
+    /// Postprocess the parsed function.
+    fn process(fun: *c.Fn) void {
+        c.T.abi0(fun);
+        c.fillrpo(fun);
+        c.fillpreds(fun);
+        c.filluse(fun);
+        c.promote(fun);
+        c.filluse(fun);
+        c.ssa(fun);
+        c.filluse(fun);
+        c.ssacheck(fun);
+        c.fillalias(fun);
+        c.loadopt(fun);
+        c.filluse(fun);
+        c.fillalias(fun);
+        c.coalesce(fun);
+        c.filluse(fun);
+        c.ssacheck(fun);
+        c.copy(fun);
+        c.filluse(fun);
+        c.fold(fun);
+        c.T.abi1(fun);
+        c.simpl(fun);
+        c.fillpreds(fun);
+        c.filluse(fun);
+        c.T.isel(fun);
+        c.fillrpo(fun);
+        c.filllive(fun);
+        c.fillloop(fun);
+        c.fillcost(fun);
+        c.spill(fun);
+        c.rega(fun);
+        c.fillrpo(fun);
+        c.simpljmp(fun);
+        c.fillpreds(fun);
+        c.fillrpo(fun);
+        assert(fun.rpo[0] == fun.start);
+        var n: c_uint = 0;
+        while (n + 1 < fun.nblk) : (n += 1)
+            fun.rpo[n].?.link = fun.rpo[n + 1]
+        else
+            fun.rpo[n].?.link = null;
+    }
+
+    /// Log debugging information about the given function.
+    fn debug(fun: *c.Fn, out: *c.FILE) callconv(.C) void {
+        const writer = cWriter(@ptrCast(out));
+        const name: [*:0]const u8 = @ptrCast(&fun.name);
+        writer.print("**** Function {s} ****", .{ span(name) })
+            catch unreachable;
+        if (c.debug['P']) {
+            writer.writeAll("\n> After parsing:\n") catch unreachable;
+            c.printfn(fun, out);
+        }
+        process(fun);
+        writer.writeAll("\n") catch unreachable;
+        c.freeall();
+    }
+
+    /// Emit the given function.
+    fn emit(fun: *c.Fn, out: *c.FILE) callconv(.C) void {
+        process(fun);
+        c.T.emitfn(fun, out);
+        const writer = cWriter(@ptrCast(out));
+        const name: [*:0]const u8 = @ptrCast(&fun.name);
+        writer.print("/* end function {s} */\n\n", .{ span(name) })
+            catch unreachable;
+        c.freeall();
+    }
+};
+
+/// Parser of a compilation unit.
+const Parser = struct {
+    dbg: bool,
+    out: *c.FILE,
+
+    /// Initialize parser from command-line options.
+    pub fn init(allocator: Allocator) !Parser {
+        c.T = default_target.*;
+        var args = try argsWithAllocator(allocator);
+        var dbg = false;
+        var out: *c.FILE = c.stdio.stdout.?;
+        errdefer _ = c.fclose(out);
+
+        const prog = args.next().?;
+        while (args.next()) |arg| {
+            if (arg.len < 2 or arg[0] != '-')
+                continue;
+            const value = if (arg[1] == 'h')
+                try printUsage(prog, 0)
+            else if (arg.len == 2)
+                args.next() orelse try printUsage(prog, 1)
+            else
+                arg[2..];
+
+            switch (arg[1]) {
+                'd' => for (value) |flag| {
+                    if (isAlphabetic(flag)) {
+                        c.debug[toUpper(flag)] = true;
+                        dbg = true;
+                    }
+                },
+                'o' => {
+                    _ = c.fclose(out);
+                    out = c.fopen(value, "w") // TODO: explicit OpenError
+                        orelse try die("cannot open '{s}'\n", .{ value }, 1);
+                },
+                't' => {
+                    if (eql(u8, value, "?")) {
+                        const name: [*:0]const u8 = @ptrCast(&c.T.name);
+                        try die("{s}\n", .{ span(name) }, 0);
+                    }
+                    c.T = for (c.targets) |target| {
+                        const name: [*:0]const u8 = @ptrCast(&target.name);
+                        if (eql(u8, value, span(name)))
+                            break target.*;
+                    } else try die("unknown target '{s}'\n", .{ value }, 1);
+                },
+                else => try printUsage(prog, 1),
+            }
+        }
+
+        if (dbg)
+            _ = c.fclose(out);
+        return .{ .dbg = dbg, .out = if (dbg) c.stdio.stderr.? else out };
+    }
+
+    /// Finish writing to output file.
+    pub fn deinit(self: Parser) void {
+        if (!self.dbg)
+            c.T.emitfin(self.out);
+        _ = c.fclose(self.out);
+    }
+
+    /// Skip emitting data in debug mode.
+    fn noop_dat(_: *c.Dat, _: *c.FILE) callconv(.C) void {}
+
+    /// Parse the given file.
+    pub fn parse(self: Parser, file: *c.FILE, path: [*:0]const u8) void {
+        c.parse(file, path, self.out,
+            if (self.dbg) noop_dat else c.emitdat,
+            if (self.dbg) Fun.debug else Fun.emit);
+    }
+};
+
+pub fn main() !void {
+    var gpa = GeneralPurposeAllocator(.{}){};
+    const allocator = gpa.allocator();
+    defer _ = gpa.detectLeaks();
+
+    const parser = try Parser.init(allocator);
+    defer parser.deinit();
+    var read_stdin = true;
+    var args = try argsWithAllocator(allocator);
+    _ = args.next().?;
+    while (args.next()) |arg| {
+        if (arg[0] == '-' and arg.len > 1) {
+            // must be -d, -o or -t
+            if (arg.len == 2)
+                _ = args.next().?;
+            continue;
+        }
+        read_stdin = false;
+        const in_file = if (eql(u8, arg, "-"))
+            c.stdio.stdin.?
+        else c.fopen(arg, "r") // TODO: explicit OpenError
+            orelse try die("cannot open '{s}'\n", .{ arg }, 1);
+        defer _ = c.fclose(in_file);
+        parser.parse(in_file, arg.ptr);
+    }
+    if (read_stdin)
+        parser.parse(c.stdio.stdin.?, "-");
+}