summary refs log tree commit diff
path: root/src
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 /src
parentc9dff9cb9c9bb9ac685d59d12f7f991c30a1cd94 (diff)
downloadroux-f6c6302192bfc6f1bfba253367317cdb52ba4370.tar.gz
Port entry point to Zig
Diffstat (limited to 'src')
-rw-r--r--src/cimport.zig280
-rw-r--r--src/main.zig253
2 files changed, 533 insertions, 0 deletions
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.?, "-");
+}