From f6c6302192bfc6f1bfba253367317cdb52ba4370 Mon Sep 17 00:00:00 2001 From: Nguyễn Gia Phong Date: Mon, 26 Feb 2024 17:49:44 +0900 Subject: Port entry point to Zig --- Makefile | 30 +++--- all.h | 5 +- build.zig | 18 +--- emit.c | 2 + main.c | 197 --------------------------------------- parse.c | 17 ++-- src/cimport.zig | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 559 insertions(+), 243 deletions(-) delete mode 100644 main.c create mode 100644 src/cimport.zig create mode 100644 src/main.zig 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/ "-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 -#include - -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 "); - 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 "); - 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 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 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.?, "-"); +} -- cgit 1.4.1