diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cimport.zig | 280 | ||||
-rw-r--r-- | src/main.zig | 253 |
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.?, "-"); +} |