diff --git a/.gitattributes b/.gitattributes
index ef01f614..9e23cff3 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,3 @@
*.zig text eol=lf
+
+deps/** linguist-vendored
diff --git a/README.md b/README.md
index 97a4a849..010f06f1 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,23 @@
# Aro
A C compiler with the goal of providing fast compilation and low memory usage with good diagnostics.
-Currently it can preprocess, parse and semantically analyze ~75% of standard C17 with
+Currently it can preprocess, parse and semantically analyze ~85% of standard C17 with
work still being needed to support all of the usual extensions.
+Basic code generation is supported for x86-64 linux and can produce a valid hello world:
+```shell
+$ cat hello.c
+extern int printf(const char *restrict fmt, ...);
+int main(void) {
+ pritnf("Hello, world!\n");
+ return 0;
+}
+$ zig build run -- hello.c -c
+$ zig run hello.o -lc
+Hello, world!
+$
+```
+
Future plans for the project include making it the C backend of Zig's `translate-c` feature and
making it an optional C frontend for the self-hosted Zig compiler.
```c
@@ -50,4 +64,4 @@ fn_def: 'fn (argc: int, argv: **const char) int'
value: 4
```
-types are printed in Zig style as C types are more confusing than necessary
+types are printed in Zig style as C types are more confusing than necessary, actual error messages contain proper C like types
diff --git a/build.zig b/build.zig
index 472389d4..9e9dd59c 100644
--- a/build.zig
+++ b/build.zig
@@ -12,9 +12,15 @@ pub fn build(b: *Builder) void {
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
+ const zig_pkg = std.build.Pkg{
+ .name = "zig",
+ .path = .{ .path = "deps/zig/lib.zig" },
+ };
+
const exe = b.addExecutable("arocc", "src/main.zig");
exe.setTarget(target);
exe.setBuildMode(mode);
+ exe.addPackage(zig_pkg);
exe.install();
const run_cmd = exe.run();
@@ -30,12 +36,18 @@ pub fn build(b: *Builder) void {
tests_step.dependOn(&exe.step);
var unit_tests = b.addTest("src/main.zig");
+ unit_tests.addPackage(zig_pkg);
tests_step.dependOn(&unit_tests.step);
const integration_tests = b.addExecutable("arocc", "test/runner.zig");
- integration_tests.addPackagePath("aro", "src/lib.zig");
+ integration_tests.addPackage(.{
+ .name = "aro",
+ .path = .{ .path = "src/lib.zig" },
+ .dependencies = &.{zig_pkg},
+ });
const integration_test_runner = integration_tests.run();
integration_test_runner.addArg(b.pathFromRoot("test/cases"));
+ integration_test_runner.addArg(b.zig_exe);
tests_step.dependOn(&integration_test_runner.step);
}
diff --git a/deps/zig/codegen/x86_64.zig b/deps/zig/codegen/x86_64.zig
new file mode 100644
index 00000000..72a74680
--- /dev/null
+++ b/deps/zig/codegen/x86_64.zig
@@ -0,0 +1,716 @@
+const std = @import("std");
+const testing = std.testing;
+const mem = std.mem;
+const assert = std.debug.assert;
+const ArrayList = std.ArrayList;
+const Allocator = std.mem.Allocator;
+const DW = std.dwarf;
+
+// zig fmt: off
+
+/// Definitions of all of the x64 registers. The order is semantically meaningful.
+/// The registers are defined such that IDs go in descending order of 64-bit,
+/// 32-bit, 16-bit, and then 8-bit, and each set contains exactly sixteen
+/// registers. This results in some useful properties:
+///
+/// Any 64-bit register can be turned into its 32-bit form by adding 16, and
+/// vice versa. This also works between 32-bit and 16-bit forms. With 8-bit, it
+/// works for all except for sp, bp, si, and di, which do *not* have an 8-bit
+/// form.
+///
+/// If (register & 8) is set, the register is extended.
+///
+/// The ID can be easily determined by figuring out what range the register is
+/// in, and then subtracting the base.
+pub const Register = enum(u8) {
+ // 0 through 15, 64-bit registers. 8-15 are extended.
+ // id is just the int value.
+ rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi,
+ r8, r9, r10, r11, r12, r13, r14, r15,
+
+ // 16 through 31, 32-bit registers. 24-31 are extended.
+ // id is int value - 16.
+ eax, ecx, edx, ebx, esp, ebp, esi, edi,
+ r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d,
+
+ // 32-47, 16-bit registers. 40-47 are extended.
+ // id is int value - 32.
+ ax, cx, dx, bx, sp, bp, si, di,
+ r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w,
+
+ // 48-63, 8-bit registers. 56-63 are extended.
+ // id is int value - 48.
+ al, cl, dl, bl, ah, ch, dh, bh,
+ r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b,
+
+ /// Returns the bit-width of the register.
+ pub fn size(self: Register) u7 {
+ return switch (@enumToInt(self)) {
+ 0...15 => 64,
+ 16...31 => 32,
+ 32...47 => 16,
+ 48...64 => 8,
+ else => unreachable,
+ };
+ }
+
+ /// Returns whether the register is *extended*. Extended registers are the
+ /// new registers added with amd64, r8 through r15. This also includes any
+ /// other variant of access to those registers, such as r8b, r15d, and so
+ /// on. This is needed because access to these registers requires special
+ /// handling via the REX prefix, via the B or R bits, depending on context.
+ pub fn isExtended(self: Register) bool {
+ return @enumToInt(self) & 0x08 != 0;
+ }
+
+ /// This returns the 4-bit register ID, which is used in practically every
+ /// opcode. Note that bit 3 (the highest bit) is *never* used directly in
+ /// an instruction (@see isExtended), and requires special handling. The
+ /// lower three bits are often embedded directly in instructions (such as
+ /// the B8 variant of moves), or used in R/M bytes.
+ pub fn id(self: Register) u4 {
+ return @truncate(u4, @enumToInt(self));
+ }
+
+ /// Like id, but only returns the lower 3 bits.
+ pub fn low_id(self: Register) u3 {
+ return @truncate(u3, @enumToInt(self));
+ }
+
+ /// Returns the index into `callee_preserved_regs`.
+ pub fn allocIndex(self: Register) ?u4 {
+ return switch (self) {
+ .rax, .eax, .ax, .al => 0,
+ .rcx, .ecx, .cx, .cl => 1,
+ .rdx, .edx, .dx, .dl => 2,
+ .rsi, .esi, .si => 3,
+ .rdi, .edi, .di => 4,
+ .r8, .r8d, .r8w, .r8b => 5,
+ .r9, .r9d, .r9w, .r9b => 6,
+ .r10, .r10d, .r10w, .r10b => 7,
+ .r11, .r11d, .r11w, .r11b => 8,
+ else => null,
+ };
+ }
+
+ /// Convert from any register to its 64 bit alias.
+ pub fn to64(self: Register) Register {
+ return @intToEnum(Register, self.id());
+ }
+
+ /// Convert from any register to its 32 bit alias.
+ pub fn to32(self: Register) Register {
+ return @intToEnum(Register, @as(u8, self.id()) + 16);
+ }
+
+ /// Convert from any register to its 16 bit alias.
+ pub fn to16(self: Register) Register {
+ return @intToEnum(Register, @as(u8, self.id()) + 32);
+ }
+
+ /// Convert from any register to its 8 bit alias.
+ pub fn to8(self: Register) Register {
+ return @intToEnum(Register, @as(u8, self.id()) + 48);
+ }
+
+ pub fn dwarfLocOp(self: Register) u8 {
+ return switch (self.to64()) {
+ .rax => DW.OP.reg0,
+ .rdx => DW.OP.reg1,
+ .rcx => DW.OP.reg2,
+ .rbx => DW.OP.reg3,
+ .rsi => DW.OP.reg4,
+ .rdi => DW.OP.reg5,
+ .rbp => DW.OP.reg6,
+ .rsp => DW.OP.reg7,
+
+ .r8 => DW.OP.reg8,
+ .r9 => DW.OP.reg9,
+ .r10 => DW.OP.reg10,
+ .r11 => DW.OP.reg11,
+ .r12 => DW.OP.reg12,
+ .r13 => DW.OP.reg13,
+ .r14 => DW.OP.reg14,
+ .r15 => DW.OP.reg15,
+
+ else => unreachable,
+ };
+ }
+};
+
+// zig fmt: on
+
+/// These registers belong to the called function.
+pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 };
+pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
+pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };
+
+/// Encoding helper functions for x86_64 instructions
+///
+/// Many of these helpers do very little, but they can help make things
+/// slightly more readable with more descriptive field names / function names.
+///
+/// Some of them also have asserts to ensure that we aren't doing dumb things.
+/// For example, trying to use register 4 (esp) in an indirect modr/m byte is illegal,
+/// you need to encode it with an SIB byte.
+///
+/// Note that ALL of these helper functions will assume capacity,
+/// so ensure that the `code` has sufficient capacity before using them.
+/// The `init` method is the recommended way to ensure capacity.
+pub const Encoder = struct {
+ /// Non-owning reference to the code array
+ code: *ArrayList(u8),
+
+ const Self = @This();
+
+ /// Wrap `code` in Encoder to make it easier to call these helper functions
+ ///
+ /// maximum_inst_size should contain the maximum number of bytes
+ /// that the encoded instruction will take.
+ /// This is because the helper functions will assume capacity
+ /// in order to avoid bounds checking.
+ pub fn init(code: *ArrayList(u8), maximum_inst_size: u8) !Self {
+ try code.ensureUnusedCapacity(maximum_inst_size);
+ return Self{ .code = code };
+ }
+
+ /// Directly write a number to the code array with big endianness
+ pub fn writeIntBig(self: Self, comptime T: type, value: T) void {
+ mem.writeIntBig(
+ T,
+ self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)),
+ value,
+ );
+ }
+
+ /// Directly write a number to the code array with little endianness
+ pub fn writeIntLittle(self: Self, comptime T: type, value: T) void {
+ mem.writeIntLittle(
+ T,
+ self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)),
+ value,
+ );
+ }
+
+ // --------
+ // Prefixes
+ // --------
+
+ pub const LegacyPrefixes = packed struct {
+ /// LOCK
+ prefix_f0: bool = false,
+ /// REPNZ, REPNE, REP, Scalar Double-precision
+ prefix_f2: bool = false,
+ /// REPZ, REPE, REP, Scalar Single-precision
+ prefix_f3: bool = false,
+
+ /// CS segment override or Branch not taken
+ prefix_2e: bool = false,
+ /// DS segment override
+ prefix_36: bool = false,
+ /// ES segment override
+ prefix_26: bool = false,
+ /// FS segment override
+ prefix_64: bool = false,
+ /// GS segment override
+ prefix_65: bool = false,
+
+ /// Branch taken
+ prefix_3e: bool = false,
+
+ /// Operand size override (enables 16 bit operation)
+ prefix_66: bool = false,
+
+ /// Address size override (enables 16 bit address size)
+ prefix_67: bool = false,
+
+ padding: u5 = 0,
+ };
+
+ /// Encodes legacy prefixes
+ pub fn legacyPrefixes(self: Self, prefixes: LegacyPrefixes) void {
+ if (@bitCast(u16, prefixes) != 0) {
+ // Hopefully this path isn't taken very often, so we'll do it the slow way for now
+
+ // LOCK
+ if (prefixes.prefix_f0) self.code.appendAssumeCapacity(0xf0);
+ // REPNZ, REPNE, REP, Scalar Double-precision
+ if (prefixes.prefix_f2) self.code.appendAssumeCapacity(0xf2);
+ // REPZ, REPE, REP, Scalar Single-precision
+ if (prefixes.prefix_f3) self.code.appendAssumeCapacity(0xf3);
+
+ // CS segment override or Branch not taken
+ if (prefixes.prefix_2e) self.code.appendAssumeCapacity(0x2e);
+ // DS segment override
+ if (prefixes.prefix_36) self.code.appendAssumeCapacity(0x36);
+ // ES segment override
+ if (prefixes.prefix_26) self.code.appendAssumeCapacity(0x26);
+ // FS segment override
+ if (prefixes.prefix_64) self.code.appendAssumeCapacity(0x64);
+ // GS segment override
+ if (prefixes.prefix_65) self.code.appendAssumeCapacity(0x65);
+
+ // Branch taken
+ if (prefixes.prefix_3e) self.code.appendAssumeCapacity(0x3e);
+
+ // Operand size override
+ if (prefixes.prefix_66) self.code.appendAssumeCapacity(0x66);
+
+ // Address size override
+ if (prefixes.prefix_67) self.code.appendAssumeCapacity(0x67);
+ }
+ }
+
+ /// Use 16 bit operand size
+ ///
+ /// Note that this flag is overridden by REX.W, if both are present.
+ pub fn prefix16BitMode(self: Self) void {
+ self.code.appendAssumeCapacity(0x66);
+ }
+
+ /// From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB
+ pub const Rex = struct {
+ /// Wide, enables 64-bit operation
+ w: bool = false,
+ /// Extends the reg field in the ModR/M byte
+ r: bool = false,
+ /// Extends the index field in the SIB byte
+ x: bool = false,
+ /// Extends the r/m field in the ModR/M byte,
+ /// or the base field in the SIB byte,
+ /// or the reg field in the Opcode byte
+ b: bool = false,
+ };
+
+ /// Encodes a REX prefix byte given all the fields
+ ///
+ /// Use this byte whenever you need 64 bit operation,
+ /// or one of reg, index, r/m, base, or opcode-reg might be extended.
+ ///
+ /// See struct `Rex` for a description of each field.
+ ///
+ /// Does not add a prefix byte if none of the fields are set!
+ pub fn rex(self: Self, byte: Rex) void {
+ var value: u8 = 0b0100_0000;
+
+ if (byte.w) value |= 0b1000;
+ if (byte.r) value |= 0b0100;
+ if (byte.x) value |= 0b0010;
+ if (byte.b) value |= 0b0001;
+
+ if (value != 0b0100_0000) {
+ self.code.appendAssumeCapacity(value);
+ }
+ }
+
+ // ------
+ // Opcode
+ // ------
+
+ /// Encodes a 1 byte opcode
+ pub fn opcode_1byte(self: Self, opcode: u8) void {
+ self.code.appendAssumeCapacity(opcode);
+ }
+
+ /// Encodes a 2 byte opcode
+ ///
+ /// e.g. IMUL has the opcode 0x0f 0xaf, so you use
+ ///
+ /// encoder.opcode_2byte(0x0f, 0xaf);
+ pub fn opcode_2byte(self: Self, prefix: u8, opcode: u8) void {
+ self.code.appendAssumeCapacity(prefix);
+ self.code.appendAssumeCapacity(opcode);
+ }
+
+ /// Encodes a 1 byte opcode with a reg field
+ ///
+ /// Remember to add a REX prefix byte if reg is extended!
+ pub fn opcode_withReg(self: Self, opcode: u8, reg: u3) void {
+ assert(opcode & 0b111 == 0);
+ self.code.appendAssumeCapacity(opcode | reg);
+ }
+
+ // ------
+ // ModR/M
+ // ------
+
+ /// Construct a ModR/M byte given all the fields
+ ///
+ /// Remember to add a REX prefix byte if reg or rm are extended!
+ pub fn modRm(self: Self, mod: u2, reg_or_opx: u3, rm: u3) void {
+ self.code.appendAssumeCapacity(
+ @as(u8, mod) << 6 | @as(u8, reg_or_opx) << 3 | rm,
+ );
+ }
+
+ /// Construct a ModR/M byte using direct r/m addressing
+ /// r/m effective address: r/m
+ ///
+ /// Note reg's effective address is always just reg for the ModR/M byte.
+ /// Remember to add a REX prefix byte if reg or rm are extended!
+ pub fn modRm_direct(self: Self, reg_or_opx: u3, rm: u3) void {
+ self.modRm(0b11, reg_or_opx, rm);
+ }
+
+ /// Construct a ModR/M byte using indirect r/m addressing
+ /// r/m effective address: [r/m]
+ ///
+ /// Note reg's effective address is always just reg for the ModR/M byte.
+ /// Remember to add a REX prefix byte if reg or rm are extended!
+ pub fn modRm_indirectDisp0(self: Self, reg_or_opx: u3, rm: u3) void {
+ assert(rm != 4 and rm != 5);
+ self.modRm(0b00, reg_or_opx, rm);
+ }
+
+ /// Construct a ModR/M byte using indirect SIB addressing
+ /// r/m effective address: [SIB]
+ ///
+ /// Note reg's effective address is always just reg for the ModR/M byte.
+ /// Remember to add a REX prefix byte if reg or rm are extended!
+ pub fn modRm_SIBDisp0(self: Self, reg_or_opx: u3) void {
+ self.modRm(0b00, reg_or_opx, 0b100);
+ }
+
+ /// Construct a ModR/M byte using RIP-relative addressing
+ /// r/m effective address: [RIP + disp32]
+ ///
+ /// Note reg's effective address is always just reg for the ModR/M byte.
+ /// Remember to add a REX prefix byte if reg or rm are extended!
+ pub fn modRm_RIPDisp32(self: Self, reg_or_opx: u3) void {
+ self.modRm(0b00, reg_or_opx, 0b101);
+ }
+
+ /// Construct a ModR/M byte using indirect r/m with a 8bit displacement
+ /// r/m effective address: [r/m + disp8]
+ ///
+ /// Note reg's effective address is always just reg for the ModR/M byte.
+ /// Remember to add a REX prefix byte if reg or rm are extended!
+ pub fn modRm_indirectDisp8(self: Self, reg_or_opx: u3, rm: u3) void {
+ assert(rm != 4);
+ self.modRm(0b01, reg_or_opx, rm);
+ }
+
+ /// Construct a ModR/M byte using indirect SIB with a 8bit displacement
+ /// r/m effective address: [SIB + disp8]
+ ///
+ /// Note reg's effective address is always just reg for the ModR/M byte.
+ /// Remember to add a REX prefix byte if reg or rm are extended!
+ pub fn modRm_SIBDisp8(self: Self, reg_or_opx: u3) void {
+ self.modRm(0b01, reg_or_opx, 0b100);
+ }
+
+ /// Construct a ModR/M byte using indirect r/m with a 32bit displacement
+ /// r/m effective address: [r/m + disp32]
+ ///
+ /// Note reg's effective address is always just reg for the ModR/M byte.
+ /// Remember to add a REX prefix byte if reg or rm are extended!
+ pub fn modRm_indirectDisp32(self: Self, reg_or_opx: u3, rm: u3) void {
+ assert(rm != 4);
+ self.modRm(0b10, reg_or_opx, rm);
+ }
+
+ /// Construct a ModR/M byte using indirect SIB with a 32bit displacement
+ /// r/m effective address: [SIB + disp32]
+ ///
+ /// Note reg's effective address is always just reg for the ModR/M byte.
+ /// Remember to add a REX prefix byte if reg or rm are extended!
+ pub fn modRm_SIBDisp32(self: Self, reg_or_opx: u3) void {
+ self.modRm(0b10, reg_or_opx, 0b100);
+ }
+
+ // ---
+ // SIB
+ // ---
+
+ /// Construct a SIB byte given all the fields
+ ///
+ /// Remember to add a REX prefix byte if index or base are extended!
+ pub fn sib(self: Self, scale: u2, index: u3, base: u3) void {
+ self.code.appendAssumeCapacity(
+ @as(u8, scale) << 6 | @as(u8, index) << 3 | base,
+ );
+ }
+
+ /// Construct a SIB byte with scale * index + base, no frills.
+ /// r/m effective address: [base + scale * index]
+ ///
+ /// Remember to add a REX prefix byte if index or base are extended!
+ pub fn sib_scaleIndexBase(self: Self, scale: u2, index: u3, base: u3) void {
+ assert(base != 5);
+
+ self.sib(scale, index, base);
+ }
+
+ /// Construct a SIB byte with scale * index + disp32
+ /// r/m effective address: [scale * index + disp32]
+ ///
+ /// Remember to add a REX prefix byte if index or base are extended!
+ pub fn sib_scaleIndexDisp32(self: Self, scale: u2, index: u3) void {
+ assert(index != 4);
+
+ // scale is actually ignored
+ // index = 4 means no index
+ // base = 5 means no base, if mod == 0.
+ self.sib(scale, index, 5);
+ }
+
+ /// Construct a SIB byte with just base
+ /// r/m effective address: [base]
+ ///
+ /// Remember to add a REX prefix byte if index or base are extended!
+ pub fn sib_base(self: Self, base: u3) void {
+ assert(base != 5);
+
+ // scale is actually ignored
+ // index = 4 means no index
+ self.sib(0, 4, base);
+ }
+
+ /// Construct a SIB byte with just disp32
+ /// r/m effective address: [disp32]
+ ///
+ /// Remember to add a REX prefix byte if index or base are extended!
+ pub fn sib_disp32(self: Self) void {
+ // scale is actually ignored
+ // index = 4 means no index
+ // base = 5 means no base, if mod == 0.
+ self.sib(0, 4, 5);
+ }
+
+ /// Construct a SIB byte with scale * index + base + disp8
+ /// r/m effective address: [base + scale * index + disp8]
+ ///
+ /// Remember to add a REX prefix byte if index or base are extended!
+ pub fn sib_scaleIndexBaseDisp8(self: Self, scale: u2, index: u3, base: u3) void {
+ self.sib(scale, index, base);
+ }
+
+ /// Construct a SIB byte with base + disp8, no index
+ /// r/m effective address: [base + disp8]
+ ///
+ /// Remember to add a REX prefix byte if index or base are extended!
+ pub fn sib_baseDisp8(self: Self, base: u3) void {
+ // scale is ignored
+ // index = 4 means no index
+ self.sib(0, 4, base);
+ }
+
+ /// Construct a SIB byte with scale * index + base + disp32
+ /// r/m effective address: [base + scale * index + disp32]
+ ///
+ /// Remember to add a REX prefix byte if index or base are extended!
+ pub fn sib_scaleIndexBaseDisp32(self: Self, scale: u2, index: u3, base: u3) void {
+ self.sib(scale, index, base);
+ }
+
+ /// Construct a SIB byte with base + disp32, no index
+ /// r/m effective address: [base + disp32]
+ ///
+ /// Remember to add a REX prefix byte if index or base are extended!
+ pub fn sib_baseDisp32(self: Self, base: u3) void {
+ // scale is ignored
+ // index = 4 means no index
+ self.sib(0, 4, base);
+ }
+
+ // -------------------------
+ // Trivial (no bit fiddling)
+ // -------------------------
+
+ /// Encode an 8 bit immediate
+ ///
+ /// It is sign-extended to 64 bits by the cpu.
+ pub fn imm8(self: Self, imm: i8) void {
+ self.code.appendAssumeCapacity(@bitCast(u8, imm));
+ }
+
+ /// Encode an 8 bit displacement
+ ///
+ /// It is sign-extended to 64 bits by the cpu.
+ pub fn disp8(self: Self, disp: i8) void {
+ self.code.appendAssumeCapacity(@bitCast(u8, disp));
+ }
+
+ /// Encode an 16 bit immediate
+ ///
+ /// It is sign-extended to 64 bits by the cpu.
+ pub fn imm16(self: Self, imm: i16) void {
+ self.writeIntLittle(i16, imm);
+ }
+
+ /// Encode an 32 bit immediate
+ ///
+ /// It is sign-extended to 64 bits by the cpu.
+ pub fn imm32(self: Self, imm: i32) void {
+ self.writeIntLittle(i32, imm);
+ }
+
+ /// Encode an 32 bit displacement
+ ///
+ /// It is sign-extended to 64 bits by the cpu.
+ pub fn disp32(self: Self, disp: i32) void {
+ self.writeIntLittle(i32, disp);
+ }
+
+ /// Encode an 64 bit immediate
+ ///
+ /// It is sign-extended to 64 bits by the cpu.
+ pub fn imm64(self: Self, imm: u64) void {
+ self.writeIntLittle(u64, imm);
+ }
+};
+
+test "x86_64 Encoder helpers" {
+ var code = ArrayList(u8).init(testing.allocator);
+ defer code.deinit();
+
+ // simple integer multiplication
+
+ // imul eax,edi
+ // 0faf c7
+ {
+ try code.resize(0);
+ const encoder = try Encoder.init(&code, 4);
+ encoder.rex(.{
+ .r = Register.eax.isExtended(),
+ .b = Register.edi.isExtended(),
+ });
+ encoder.opcode_2byte(0x0f, 0xaf);
+ encoder.modRm_direct(
+ Register.eax.low_id(),
+ Register.edi.low_id(),
+ );
+
+ try testing.expectEqualSlices(u8, &[_]u8{ 0x0f, 0xaf, 0xc7 }, code.items);
+ }
+
+ // simple mov
+
+ // mov eax,edi
+ // 89 f8
+ {
+ try code.resize(0);
+ const encoder = try Encoder.init(&code, 3);
+ encoder.rex(.{
+ .r = Register.edi.isExtended(),
+ .b = Register.eax.isExtended(),
+ });
+ encoder.opcode_1byte(0x89);
+ encoder.modRm_direct(
+ Register.edi.low_id(),
+ Register.eax.low_id(),
+ );
+
+ try testing.expectEqualSlices(u8, &[_]u8{ 0x89, 0xf8 }, code.items);
+ }
+
+ // signed integer addition of 32-bit sign extended immediate to 64 bit register
+
+ // add rcx, 2147483647
+ //
+ // Using the following opcode: REX.W + 81 /0 id, we expect the following encoding
+ //
+ // 48 : REX.W set for 64 bit operand (*r*cx)
+ // 81 : opcode for " with immediate"
+ // c1 : id = rcx,
+ // : c1 = 11 <-- mod = 11 indicates r/m is register (rcx)
+ // : 000 <-- opcode_extension = 0 because opcode extension is /0. /0 specifies ADD
+ // : 001 <-- 001 is rcx
+ // ffffff7f : 2147483647
+ {
+ try code.resize(0);
+ const encoder = try Encoder.init(&code, 7);
+ encoder.rex(.{ .w = true }); // use 64 bit operation
+ encoder.opcode_1byte(0x81);
+ encoder.modRm_direct(
+ 0,
+ Register.rcx.low_id(),
+ );
+ encoder.imm32(2147483647);
+
+ try testing.expectEqualSlices(u8, &[_]u8{ 0x48, 0x81, 0xc1, 0xff, 0xff, 0xff, 0x7f }, code.items);
+ }
+}
+
+// TODO add these registers to the enum and populate dwarfLocOp
+// // Return Address register. This is stored in `0(%rsp, "")` and is not a physical register.
+// RA = (16, "RA"),
+//
+// XMM0 = (17, "xmm0"),
+// XMM1 = (18, "xmm1"),
+// XMM2 = (19, "xmm2"),
+// XMM3 = (20, "xmm3"),
+// XMM4 = (21, "xmm4"),
+// XMM5 = (22, "xmm5"),
+// XMM6 = (23, "xmm6"),
+// XMM7 = (24, "xmm7"),
+//
+// XMM8 = (25, "xmm8"),
+// XMM9 = (26, "xmm9"),
+// XMM10 = (27, "xmm10"),
+// XMM11 = (28, "xmm11"),
+// XMM12 = (29, "xmm12"),
+// XMM13 = (30, "xmm13"),
+// XMM14 = (31, "xmm14"),
+// XMM15 = (32, "xmm15"),
+//
+// ST0 = (33, "st0"),
+// ST1 = (34, "st1"),
+// ST2 = (35, "st2"),
+// ST3 = (36, "st3"),
+// ST4 = (37, "st4"),
+// ST5 = (38, "st5"),
+// ST6 = (39, "st6"),
+// ST7 = (40, "st7"),
+//
+// MM0 = (41, "mm0"),
+// MM1 = (42, "mm1"),
+// MM2 = (43, "mm2"),
+// MM3 = (44, "mm3"),
+// MM4 = (45, "mm4"),
+// MM5 = (46, "mm5"),
+// MM6 = (47, "mm6"),
+// MM7 = (48, "mm7"),
+//
+// RFLAGS = (49, "rFLAGS"),
+// ES = (50, "es"),
+// CS = (51, "cs"),
+// SS = (52, "ss"),
+// DS = (53, "ds"),
+// FS = (54, "fs"),
+// GS = (55, "gs"),
+//
+// FS_BASE = (58, "fs.base"),
+// GS_BASE = (59, "gs.base"),
+//
+// TR = (62, "tr"),
+// LDTR = (63, "ldtr"),
+// MXCSR = (64, "mxcsr"),
+// FCW = (65, "fcw"),
+// FSW = (66, "fsw"),
+//
+// XMM16 = (67, "xmm16"),
+// XMM17 = (68, "xmm17"),
+// XMM18 = (69, "xmm18"),
+// XMM19 = (70, "xmm19"),
+// XMM20 = (71, "xmm20"),
+// XMM21 = (72, "xmm21"),
+// XMM22 = (73, "xmm22"),
+// XMM23 = (74, "xmm23"),
+// XMM24 = (75, "xmm24"),
+// XMM25 = (76, "xmm25"),
+// XMM26 = (77, "xmm26"),
+// XMM27 = (78, "xmm27"),
+// XMM28 = (79, "xmm28"),
+// XMM29 = (80, "xmm29"),
+// XMM30 = (81, "xmm30"),
+// XMM31 = (82, "xmm31"),
+//
+// K0 = (118, "k0"),
+// K1 = (119, "k1"),
+// K2 = (120, "k2"),
+// K3 = (121, "k3"),
+// K4 = (122, "k4"),
+// K5 = (123, "k5"),
+// K6 = (124, "k6"),
+// K7 = (125, "k7"),
diff --git a/deps/zig/lib.zig b/deps/zig/lib.zig
new file mode 100644
index 00000000..24924dcb
--- /dev/null
+++ b/deps/zig/lib.zig
@@ -0,0 +1,4 @@
+pub const codegen = struct {
+ pub const x86_64 = @import("codegen/x86_64.zig");
+};
+pub const RegisterManager = @import("register_manager.zig").RegisterManager;
diff --git a/deps/zig/register_manager.zig b/deps/zig/register_manager.zig
new file mode 100644
index 00000000..b3fb7cb6
--- /dev/null
+++ b/deps/zig/register_manager.zig
@@ -0,0 +1,407 @@
+const std = @import("std");
+const math = std.math;
+const mem = std.mem;
+const assert = std.debug.assert;
+const Allocator = std.mem.Allocator;
+const Index = u32;
+const expect = std.testing.expect;
+const expectEqual = std.testing.expectEqual;
+const expectEqualSlices = std.testing.expectEqualSlices;
+
+const log = std.log.scoped(.register_manager);
+
+pub fn RegisterManager(
+ comptime Function: type,
+ comptime Register: type,
+ comptime callee_preserved_regs: []const Register,
+) type {
+ return struct {
+ /// The key must be canonical register.
+ registers: [callee_preserved_regs.len]?Index = [_]?Index{null} ** callee_preserved_regs.len,
+ free_registers: FreeRegInt = math.maxInt(FreeRegInt),
+ /// Tracks all registers allocated in the course of this function
+ allocated_registers: FreeRegInt = 0,
+
+ const Self = @This();
+
+ /// An integer whose bits represent all the registers and whether they are free.
+ const FreeRegInt = std.meta.Int(.unsigned, callee_preserved_regs.len);
+ const ShiftInt = math.Log2Int(FreeRegInt);
+
+ fn getFunction(self: *Self) *Function {
+ return @fieldParentPtr(Function, "register_manager", self);
+ }
+
+ fn markRegUsed(self: *Self, reg: Register) void {
+ if (FreeRegInt == u0) return;
+ const index = reg.allocIndex() orelse return;
+ const shift = @intCast(ShiftInt, index);
+ const mask = @as(FreeRegInt, 1) << shift;
+ self.free_registers &= ~mask;
+ self.allocated_registers |= mask;
+ }
+
+ fn markRegFree(self: *Self, reg: Register) void {
+ if (FreeRegInt == u0) return;
+ const index = reg.allocIndex() orelse return;
+ const shift = @intCast(ShiftInt, index);
+ self.free_registers |= @as(FreeRegInt, 1) << shift;
+ }
+
+ /// Returns true when this register is not tracked
+ pub fn isRegFree(self: Self, reg: Register) bool {
+ if (FreeRegInt == u0) return true;
+ const index = reg.allocIndex() orelse return true;
+ const shift = @intCast(ShiftInt, index);
+ return self.free_registers & @as(FreeRegInt, 1) << shift != 0;
+ }
+
+ /// Returns whether this register was allocated in the course
+ /// of this function.
+ /// Returns false when this register is not tracked
+ pub fn isRegAllocated(self: Self, reg: Register) bool {
+ if (FreeRegInt == u0) return false;
+ const index = reg.allocIndex() orelse return false;
+ const shift = @intCast(ShiftInt, index);
+ return self.allocated_registers & @as(FreeRegInt, 1) << shift != 0;
+ }
+
+ /// Allocates a specified number of registers, optionally
+ /// tracking them. Returns `null` if not enough registers are
+ /// free.
+ pub fn tryAllocRegs(
+ self: *Self,
+ comptime count: comptime_int,
+ insts: [count]?Index,
+ exceptions: []const Register,
+ ) ?[count]Register {
+ comptime if (callee_preserved_regs.len == 0) return null;
+ comptime assert(count > 0 and count <= callee_preserved_regs.len);
+ assert(count + exceptions.len <= callee_preserved_regs.len);
+
+ const free_registers = @popCount(FreeRegInt, self.free_registers);
+ if (free_registers < count) return null;
+
+ var regs: [count]Register = undefined;
+ var i: usize = 0;
+ for (callee_preserved_regs) |reg| {
+ if (i >= count) break;
+ if (mem.indexOfScalar(Register, exceptions, reg) != null) continue;
+ if (self.isRegFree(reg)) {
+ regs[i] = reg;
+ i += 1;
+ }
+ }
+
+ if (i == count) {
+ for (regs) |reg, j| {
+ if (insts[j]) |inst| {
+ // Track the register
+ const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null
+ self.registers[index] = inst;
+ self.markRegUsed(reg);
+ }
+ }
+
+ return regs;
+ } else return null;
+ }
+
+ /// Allocates a register and optionally tracks it with a
+ /// corresponding instruction. Returns `null` if all registers
+ /// are allocated.
+ pub fn tryAllocReg(self: *Self, inst: ?Index, exceptions: []const Register) ?Register {
+ return if (tryAllocRegs(self, 1, .{inst}, exceptions)) |regs| regs[0] else null;
+ }
+
+ /// Allocates a specified number of registers, optionally
+ /// tracking them. Asserts that count + exceptions.len is not
+ /// larger than the total number of registers available.
+ pub fn allocRegs(
+ self: *Self,
+ comptime count: comptime_int,
+ insts: [count]?Index,
+ exceptions: []const Register,
+ ) ![count]Register {
+ comptime assert(count > 0 and count <= callee_preserved_regs.len);
+ assert(count + exceptions.len <= callee_preserved_regs.len);
+
+ return self.tryAllocRegs(count, insts, exceptions) orelse blk: {
+ // We'll take over the first count registers. Spill
+ // the instructions that were previously there to a
+ // stack allocations.
+ var regs: [count]Register = undefined;
+ var i: usize = 0;
+ for (callee_preserved_regs) |reg| {
+ if (i >= count) break;
+ if (mem.indexOfScalar(Register, exceptions, reg) != null) continue;
+ regs[i] = reg;
+
+ const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null
+ if (insts[i]) |inst| {
+ // Track the register
+ if (self.isRegFree(reg)) {
+ self.markRegUsed(reg);
+ } else {
+ const spilled_inst = self.registers[index].?;
+ try self.getFunction().spillInstruction(reg, spilled_inst);
+ }
+ self.registers[index] = inst;
+ } else {
+ // Don't track the register
+ if (!self.isRegFree(reg)) {
+ const spilled_inst = self.registers[index].?;
+ try self.getFunction().spillInstruction(reg, spilled_inst);
+ self.freeReg(reg);
+ }
+ }
+
+ i += 1;
+ }
+
+ break :blk regs;
+ };
+ }
+
+ /// Allocates a register and optionally tracks it with a
+ /// corresponding instruction.
+ pub fn allocReg(self: *Self, inst: ?Index, exceptions: []const Register) !Register {
+ return (try self.allocRegs(1, .{inst}, exceptions))[0];
+ }
+
+ /// Spills the register if it is currently allocated. If a
+ /// corresponding instruction is passed, will also track this
+ /// register.
+ pub fn getReg(self: *Self, reg: Register, inst: ?Index) !void {
+ const index = reg.allocIndex() orelse return;
+
+ if (inst) |tracked_inst|
+ if (!self.isRegFree(reg)) {
+ // Move the instruction that was previously there to a
+ // stack allocation.
+ const spilled_inst = self.registers[index].?;
+ self.registers[index] = tracked_inst;
+ try self.getFunction().spillInstruction(reg, spilled_inst);
+ } else {
+ self.getRegAssumeFree(reg, tracked_inst);
+ }
+ else {
+ if (!self.isRegFree(reg)) {
+ // Move the instruction that was previously there to a
+ // stack allocation.
+ const spilled_inst = self.registers[index].?;
+ try self.getFunction().spillInstruction(reg, spilled_inst);
+ self.freeReg(reg);
+ }
+ }
+ }
+
+ /// Allocates the specified register with the specified
+ /// instruction. Asserts that the register is free and no
+ /// spilling is necessary.
+ pub fn getRegAssumeFree(self: *Self, reg: Register, inst: Index) void {
+ const index = reg.allocIndex() orelse return;
+
+ assert(self.registers[index] == null);
+ self.registers[index] = inst;
+ self.markRegUsed(reg);
+ }
+
+ /// Marks the specified register as free
+ pub fn freeReg(self: *Self, reg: Register) void {
+ const index = reg.allocIndex() orelse return;
+
+ self.registers[index] = null;
+ self.markRegFree(reg);
+ }
+ };
+}
+
+const MockRegister1 = enum(u2) {
+ r0,
+ r1,
+ r2,
+ r3,
+
+ pub fn allocIndex(self: MockRegister1) ?u2 {
+ inline for (callee_preserved_regs) |cpreg, i| {
+ if (self == cpreg) return i;
+ }
+ return null;
+ }
+
+ const callee_preserved_regs = [_]MockRegister1{ .r2, .r3 };
+};
+
+const MockRegister2 = enum(u2) {
+ r0,
+ r1,
+ r2,
+ r3,
+
+ pub fn allocIndex(self: MockRegister2) ?u2 {
+ inline for (callee_preserved_regs) |cpreg, i| {
+ if (self == cpreg) return i;
+ }
+ return null;
+ }
+
+ const callee_preserved_regs = [_]MockRegister2{ .r0, .r1, .r2, .r3 };
+};
+
+fn MockFunction(comptime Register: type) type {
+ return struct {
+ allocator: *Allocator,
+ register_manager: RegisterManager(Self, Register, &Register.callee_preserved_regs) = .{},
+ spilled: std.ArrayListUnmanaged(Register) = .{},
+
+ const Self = @This();
+
+ pub fn deinit(self: *Self) void {
+ self.spilled.deinit(self.allocator);
+ }
+
+ pub fn spillInstruction(self: *Self, reg: Register, inst: Index) !void {
+ _ = inst;
+ try self.spilled.append(self.allocator, reg);
+ }
+ };
+}
+
+const MockFunction1 = MockFunction(MockRegister1);
+const MockFunction2 = MockFunction(MockRegister2);
+
+test "default state" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction1{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ try expect(!function.register_manager.isRegAllocated(.r2));
+ try expect(!function.register_manager.isRegAllocated(.r3));
+ try expect(function.register_manager.isRegFree(.r2));
+ try expect(function.register_manager.isRegFree(.r3));
+}
+
+test "tryAllocReg: no spilling" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction1{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ const mock_instruction: Index = 1;
+
+ try expectEqual(@as(?MockRegister1, .r2), function.register_manager.tryAllocReg(mock_instruction, &.{}));
+ try expectEqual(@as(?MockRegister1, .r3), function.register_manager.tryAllocReg(mock_instruction, &.{}));
+ try expectEqual(@as(?MockRegister1, null), function.register_manager.tryAllocReg(mock_instruction, &.{}));
+
+ try expect(function.register_manager.isRegAllocated(.r2));
+ try expect(function.register_manager.isRegAllocated(.r3));
+ try expect(!function.register_manager.isRegFree(.r2));
+ try expect(!function.register_manager.isRegFree(.r3));
+
+ function.register_manager.freeReg(.r2);
+ function.register_manager.freeReg(.r3);
+
+ try expect(function.register_manager.isRegAllocated(.r2));
+ try expect(function.register_manager.isRegAllocated(.r3));
+ try expect(function.register_manager.isRegFree(.r2));
+ try expect(function.register_manager.isRegFree(.r3));
+}
+
+test "allocReg: spilling" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction1{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ const mock_instruction: Index = 1;
+
+ try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction, &.{}));
+ try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, &.{}));
+
+ // Spill a register
+ try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction, &.{}));
+ try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
+
+ // No spilling necessary
+ function.register_manager.freeReg(.r3);
+ try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, &.{}));
+ try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
+
+ // Exceptions
+ function.register_manager.freeReg(.r2);
+ function.register_manager.freeReg(.r3);
+ try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, &.{.r2}));
+}
+
+test "tryAllocRegs" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction2{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, function.register_manager.tryAllocRegs(3, .{ null, null, null }, &.{}).?);
+
+ // Exceptions
+ function.register_manager.freeReg(.r0);
+ function.register_manager.freeReg(.r1);
+ function.register_manager.freeReg(.r2);
+ try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, function.register_manager.tryAllocRegs(3, .{ null, null, null }, &.{.r1}).?);
+}
+
+test "allocRegs" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction2{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ const mock_instruction: Index = 1;
+
+ try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, try function.register_manager.allocRegs(3, .{
+ mock_instruction,
+ mock_instruction,
+ mock_instruction,
+ }, &.{}));
+
+ // Exceptions
+ try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, try function.register_manager.allocRegs(3, .{ null, null, null }, &.{.r1}));
+ try expectEqualSlices(MockRegister2, &[_]MockRegister2{ .r0, .r2 }, function.spilled.items);
+}
+
+test "getReg" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction1{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ const mock_instruction: Index = 1;
+
+ try function.register_manager.getReg(.r3, mock_instruction);
+
+ try expect(!function.register_manager.isRegAllocated(.r2));
+ try expect(function.register_manager.isRegAllocated(.r3));
+ try expect(function.register_manager.isRegFree(.r2));
+ try expect(!function.register_manager.isRegFree(.r3));
+
+ // Spill r3
+ try function.register_manager.getReg(.r3, mock_instruction);
+
+ try expect(!function.register_manager.isRegAllocated(.r2));
+ try expect(function.register_manager.isRegAllocated(.r3));
+ try expect(function.register_manager.isRegFree(.r2));
+ try expect(!function.register_manager.isRegFree(.r3));
+ try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r3}, function.spilled.items);
+}
diff --git a/src/Codegen.zig b/src/Codegen.zig
new file mode 100644
index 00000000..aebeb053
--- /dev/null
+++ b/src/Codegen.zig
@@ -0,0 +1,109 @@
+const std = @import("std");
+const Compilation = @import("Compilation.zig");
+const Tree = @import("Tree.zig");
+const NodeIndex = Tree.NodeIndex;
+const Object = @import("Object.zig");
+const x86_64 = @import("codegen/x86_64.zig");
+
+const Codegen = @This();
+
+comp: *Compilation,
+tree: Tree,
+obj: *Object,
+node_tag: []const Tree.Tag,
+node_data: []const Tree.Node.Data,
+
+pub const Error = Compilation.Error || error{CodegenFailed};
+
+/// Generate tree to an object file.
+/// Caller is responsible for flushing and freeing the returned object.
+pub fn generateTree(comp: *Compilation, tree: Tree) Compilation.Error!*Object {
+ var c = Codegen{
+ .comp = comp,
+ .tree = tree,
+ .obj = try Object.create(comp),
+ .node_tag = tree.nodes.items(.tag),
+ .node_data = tree.nodes.items(.data),
+ };
+ errdefer c.obj.deinit();
+
+ const node_tags = tree.nodes.items(.tag);
+ for (tree.root_decls) |decl| {
+ switch (node_tags[@enumToInt(decl)]) {
+ // these produce no code
+ .static_assert,
+ .typedef,
+ .struct_decl_two,
+ .union_decl_two,
+ .enum_decl_two,
+ .struct_decl,
+ .union_decl,
+ .enum_decl,
+ => {},
+
+ // define symbol
+ .fn_proto,
+ .static_fn_proto,
+ .inline_fn_proto,
+ .inline_static_fn_proto,
+ .noreturn_fn_proto,
+ .noreturn_static_fn_proto,
+ .noreturn_inline_fn_proto,
+ .noreturn_inline_static_fn_proto,
+ .extern_var,
+ .threadlocal_extern_var,
+ => {
+ const name = c.tree.tokSlice(c.node_data[@enumToInt(decl)].decl.name);
+ _ = try c.obj.declareSymbol(.@"undefined", name, .Strong, .external, 0, 0);
+ },
+
+ // function definition
+ .fn_def,
+ .static_fn_def,
+ .inline_fn_def,
+ .inline_static_fn_def,
+ .noreturn_fn_def,
+ .noreturn_static_fn_def,
+ .noreturn_inline_fn_def,
+ .noreturn_inline_static_fn_def,
+ => c.genFn(decl) catch |err| switch (err) {
+ error.FatalError => return error.FatalError,
+ error.OutOfMemory => return error.OutOfMemory,
+ error.CodegenFailed => continue,
+ },
+
+ .@"var",
+ .static_var,
+ .threadlocal_var,
+ .threadlocal_static_var,
+ => c.genVar(decl) catch |err| switch (err) {
+ error.FatalError => return error.FatalError,
+ error.OutOfMemory => return error.OutOfMemory,
+ error.CodegenFailed => continue,
+ },
+
+ else => unreachable,
+ }
+ }
+
+ return c.obj;
+}
+
+fn genFn(c: *Codegen, decl: NodeIndex) Error!void {
+ const section: Object.Section = .func;
+ const data = try c.obj.getSection(section);
+ const start_len = data.items.len;
+ switch (c.comp.target.cpu.arch) {
+ .x86_64 => try x86_64.genFn(c, decl, data),
+ else => unreachable,
+ }
+ const name = c.tree.tokSlice(c.node_data[@enumToInt(decl)].decl.name);
+ _ = try c.obj.declareSymbol(section, name, .Strong, .func, start_len, data.items.len - start_len);
+}
+
+fn genVar(c: *Codegen, decl: NodeIndex) Error!void {
+ switch (c.comp.target.cpu.arch) {
+ .x86_64 => try x86_64.genVar(c, decl),
+ else => unreachable,
+ }
+}
diff --git a/src/Compilation.zig b/src/Compilation.zig
index b3906723..033095ba 100644
--- a/src/Compilation.zig
+++ b/src/Compilation.zig
@@ -26,6 +26,8 @@ output_name: ?[]const u8 = null,
builtin_header_path: ?[]u8 = null,
target: std.Target = std.Target.current,
only_preprocess: bool = false,
+only_compile: bool = false,
+verbose_ast: bool = false,
langopts: LangOpts = .{},
pub fn init(gpa: *Allocator) Compilation {
diff --git a/src/Object.zig b/src/Object.zig
new file mode 100644
index 00000000..c0c4120c
--- /dev/null
+++ b/src/Object.zig
@@ -0,0 +1,73 @@
+const std = @import("std");
+const Compilation = @import("Compilation.zig");
+const Elf = @import("object/Elf.zig");
+
+const Object = @This();
+
+format: std.Target.ObjectFormat,
+comp: *Compilation,
+
+pub fn create(comp: *Compilation) !*Object {
+ switch (comp.target.getObjectFormat()) {
+ .elf => return Elf.create(comp),
+ else => unreachable,
+ }
+}
+
+pub fn deinit(obj: *Object) void {
+ switch (obj.format) {
+ .elf => @fieldParentPtr(Elf, "obj", obj).deinit(),
+ else => unreachable,
+ }
+}
+
+pub const Section = union(enum) {
+ @"undefined",
+ data,
+ read_only_data,
+ func,
+ strings,
+ custom: []const u8,
+};
+
+pub fn getSection(obj: *Object, section: Section) !*std.ArrayList(u8) {
+ switch (obj.format) {
+ .elf => return @fieldParentPtr(Elf, "obj", obj).getSection(section),
+ else => unreachable,
+ }
+}
+
+pub const SymbolType = enum {
+ func,
+ variable,
+ external,
+};
+
+pub fn declareSymbol(
+ obj: *Object,
+ section: Section,
+ name: ?[]const u8,
+ linkage: std.builtin.GlobalLinkage,
+ @"type": SymbolType,
+ offset: u64,
+ size: u64,
+) ![]const u8 {
+ switch (obj.format) {
+ .elf => return @fieldParentPtr(Elf, "obj", obj).declareSymbol(section, name, linkage, @"type", offset, size),
+ else => unreachable,
+ }
+}
+
+pub fn addRelocation(obj: *Object, name: []const u8, section: Section, address: u64, addend: i64) !void {
+ switch (obj.format) {
+ .elf => return @fieldParentPtr(Elf, "obj", obj).addRelocation(name, section, address, addend),
+ else => unreachable,
+ }
+}
+
+pub fn finish(obj: *Object, file: std.fs.File) !void {
+ switch (obj.format) {
+ .elf => return @fieldParentPtr(Elf, "obj", obj).finish(file),
+ else => unreachable,
+ }
+}
diff --git a/src/codegen/x86_64.zig b/src/codegen/x86_64.zig
new file mode 100644
index 00000000..84cc30f4
--- /dev/null
+++ b/src/codegen/x86_64.zig
@@ -0,0 +1,211 @@
+const std = @import("std");
+const Codegen = @import("../Codegen.zig");
+const Tree = @import("../Tree.zig");
+const NodeIndex = Tree.NodeIndex;
+const x86_64 = @import("zig").codegen.x86_64;
+const Register = x86_64.Register;
+const RegisterManager = @import("zig").RegisterManager;
+
+const Fn = @This();
+
+const Value = union(enum) {
+ symbol: []const u8,
+ immediate: i64,
+ register: Register,
+ none,
+};
+
+register_manager: RegisterManager(Fn, Register, &x86_64.callee_preserved_regs) = .{},
+data: *std.ArrayList(u8),
+c: *Codegen,
+
+pub fn deinit(func: *Fn) void {
+ func.* = undefined;
+}
+
+pub fn genFn(c: *Codegen, decl: NodeIndex, data: *std.ArrayList(u8)) Codegen.Error!void {
+ var func = Fn{ .data = data, .c = c };
+ defer func.deinit();
+
+ // function prologue
+ try func.data.appendSlice(&.{
+ 0x55, // push rbp
+ 0x48, 0x89, 0xe5, // mov rbp,rsp
+ });
+ _ = try func.genNode(c.node_data[@enumToInt(decl)].decl.node);
+ // all functions are guaranteed to end in a return statement so no extra work required here
+}
+
+pub fn spillInst(f: *Fn, reg: Register, inst: u32) !void {
+ _ = inst;
+ _ = reg;
+ _ = f;
+}
+
+fn setReg(func: *Fn, val: Value, reg: Register) !void {
+ switch (val) {
+ .none => unreachable,
+ .symbol => |sym| {
+ // lea address with 0 and add relocation
+ const encoder = try x86_64.Encoder.init(func.data, 8);
+ encoder.rex(.{ .w = true });
+ encoder.opcode_1byte(0x8D);
+ encoder.modRm_RIPDisp32(reg.low_id());
+
+ const offset = func.data.items.len;
+ encoder.imm32(0);
+
+ try func.c.obj.addRelocation(sym, .func, offset, -4);
+ },
+ .immediate => |x| if (x == 0) {
+ // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit
+ // register is the fastest way to zero a register.
+ // The encoding for `xor r32, r32` is `0x31 /r`.
+ const encoder = try x86_64.Encoder.init(func.data, 3);
+
+ // If we're accessing e.g. r8d, we need to use a REX prefix before the actual operation. Since
+ // this is a 32-bit operation, the W flag is set to zero. X is also zero, as we're not using a SIB.
+ // Both R and B are set, as we're extending, in effect, the register bits *and* the operand.
+ encoder.rex(.{ .r = reg.isExtended(), .b = reg.isExtended() });
+ encoder.opcode_1byte(0x31);
+ // Section 3.1.1.1 of the Intel x64 Manual states that "/r indicates that the
+ // ModR/M byte of the instruction contains a register operand and an r/m operand."
+ encoder.modRm_direct(reg.low_id(), reg.low_id());
+ } else if (x <= std.math.maxInt(i32)) {
+ // Next best case: if we set the lower four bytes, the upper four will be zeroed.
+ //
+ // The encoding for `mov IMM32 -> REG` is (0xB8 + R) IMM.
+
+ const encoder = try x86_64.Encoder.init(func.data, 6);
+ // Just as with XORing, we need a REX prefix. This time though, we only
+ // need the B bit set, as we're extending the opcode's register field,
+ // and there is no Mod R/M byte.
+ encoder.rex(.{ .b = reg.isExtended() });
+ encoder.opcode_withReg(0xB8, reg.low_id());
+
+ // no ModR/M byte
+
+ // IMM
+ encoder.imm32(@intCast(i32, x));
+ } else {
+ // Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls
+ // this `movabs`, though this is officially just a different variant of the plain `mov`
+ // instruction.
+ //
+ // This encoding is, in fact, the *same* as the one used for 32-bit loads. The only
+ // difference is that we set REX.W before the instruction, which extends the load to
+ // 64-bit and uses the full bit-width of the register.
+ {
+ const encoder = try x86_64.Encoder.init(func.data, 10);
+ encoder.rex(.{ .w = true, .b = reg.isExtended() });
+ encoder.opcode_withReg(0xB8, reg.low_id());
+ encoder.imm64(@bitCast(u64, x));
+ }
+ },
+ .register => |src_reg| {
+ // If the registers are the same, nothing to do.
+ if (src_reg.id() == reg.id())
+ return;
+
+ // This is a variant of 8B /r.
+ const encoder = try x86_64.Encoder.init(func.data, 3);
+ encoder.rex(.{
+ .w = true,
+ .r = reg.isExtended(),
+ .b = src_reg.isExtended(),
+ });
+ encoder.opcode_1byte(0x8B);
+ encoder.modRm_direct(reg.low_id(), src_reg.low_id());
+ },
+ }
+}
+
+fn genNode(func: *Fn, node: NodeIndex) Codegen.Error!Value {
+ if (func.c.tree.value_map.get(node)) |some| return Value{ .immediate = @bitCast(i64, some) };
+
+ const data = func.c.node_data[@enumToInt(node)];
+ switch (func.c.node_tag[@enumToInt(node)]) {
+ .static_assert => return Value{ .none = {} },
+ .compound_stmt_two => {
+ if (data.bin.lhs != .none) _ = try func.genNode(data.bin.lhs);
+ if (data.bin.rhs != .none) _ = try func.genNode(data.bin.rhs);
+ return Value{ .none = {} };
+ },
+ .compound_stmt => {
+ for (func.c.tree.data[data.range.start..data.range.end]) |stmt| {
+ _ = try func.genNode(stmt);
+ }
+ return Value{ .none = {} };
+ },
+ .call_expr_one => if (data.bin.rhs != .none)
+ return func.genCall(data.bin.lhs, &.{data.bin.rhs})
+ else
+ return func.genCall(data.bin.lhs, &.{}),
+ .call_expr => return func.genCall(func.c.tree.data[data.range.start], func.c.tree.data[data.range.start + 1 .. data.range.end]),
+ .function_to_pointer => return func.genNode(data.un), // no-op
+ .array_to_pointer => return func.genNode(data.un), // no-op
+ .decl_ref_expr => {
+ // TODO locals and arguments
+ return Value{ .symbol = func.c.tree.tokSlice(data.decl_ref) };
+ },
+ .return_stmt => {
+ const value = try func.genNode(data.un);
+ try func.setReg(value, x86_64.c_abi_int_return_regs[0]);
+ try func.data.appendSlice(&.{
+ 0x5d, // pop rbp
+ 0xc3, // ret
+ });
+ return Value{ .none = {} };
+ },
+ .implicit_return => {
+ try func.setReg(.{ .immediate = 0 }, x86_64.c_abi_int_return_regs[0]);
+ try func.data.appendSlice(&.{
+ 0x5d, // pop rbp
+ 0xc3, // ret
+ });
+ return Value{ .none = {} };
+ },
+ .int_literal => return Value{ .immediate = @bitCast(i64, data.int) },
+ .string_literal_expr => {
+ const str_bytes = func.c.tree.strings[data.str.index..][0..data.str.len];
+ const section = try func.c.obj.getSection(.strings);
+ const start = section.items.len;
+ try section.appendSlice(str_bytes);
+ const symbol_name = try func.c.obj.declareSymbol(.strings, null, .Internal, .variable, start, str_bytes.len);
+ return Value{ .symbol = symbol_name };
+ },
+ else => return func.c.comp.diag.fatalNoSrc("TODO x86_64 genNode {}\n", .{func.c.node_tag[@enumToInt(node)]}),
+ }
+}
+
+fn genCall(func: *Fn, lhs: NodeIndex, args: []const NodeIndex) Codegen.Error!Value {
+ if (args.len > x86_64.c_abi_int_param_regs.len)
+ return func.c.comp.diag.fatalNoSrc("TODO more than args {d}\n", .{x86_64.c_abi_int_param_regs.len});
+
+ const func_value = try func.genNode(lhs);
+ for (args) |arg, i| {
+ const value = try func.genNode(arg);
+ try func.setReg(value, x86_64.c_abi_int_param_regs[i]);
+ }
+
+ switch (func_value) {
+ .none => unreachable,
+ .symbol => |sym| {
+ const encoder = try x86_64.Encoder.init(func.data, 5);
+ encoder.opcode_1byte(0xe8);
+
+ const offset = func.data.items.len;
+ encoder.imm32(0);
+
+ try func.c.obj.addRelocation(sym, .func, offset, -4);
+ },
+ .immediate => return func.c.comp.diag.fatalNoSrc("TODO call immediate\n", .{}),
+ .register => return func.c.comp.diag.fatalNoSrc("TODO call reg\n", .{}),
+ }
+ return Value{ .register = x86_64.c_abi_int_return_regs[0] };
+}
+
+pub fn genVar(c: *Codegen, decl: NodeIndex) Codegen.Error!void {
+ _ = c;
+ _ = decl;
+}
diff --git a/src/lib.zig b/src/lib.zig
index 7f6e1dae..f5a760a0 100644
--- a/src/lib.zig
+++ b/src/lib.zig
@@ -1,6 +1,8 @@
+pub const Codegen = @import("Codegen.zig");
pub const Compilation = @import("Compilation.zig");
pub const Diagnostics = @import("Diagnostics.zig");
pub const Parser = @import("Parser.zig");
+pub const Object = @import("Object.zig");
pub const Preprocessor = @import("Preprocessor.zig");
pub const Source = @import("Source.zig");
pub const Tokenizer = @import("Tokenizer.zig");
diff --git a/src/main.zig b/src/main.zig
index 771a54fc..54bcb3db 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -2,6 +2,7 @@ const std = @import("std");
const mem = std.mem;
const Allocator = mem.Allocator;
const process = std.process;
+const Codegen = @import("Codegen.zig");
const Compilation = @import("Compilation.zig");
const Source = @import("Source.zig");
const Preprocessor = @import("Preprocessor.zig");
@@ -47,22 +48,27 @@ const usage =
\\ -h, --help Print this message.
\\ -v, --version Print aro version.
\\
- \\Feature options:
- \\ -E Only run the preprocessor
+ \\Compile options:
+ \\ -c Only run preprocess, compile, and assemble steps
\\ -D = Define to (defaults to 1)
- \\ -U Undefine
+ \\ -E Only run the preprocessor
\\ -fcolor-diagnostics Enable colors in diagnostics
\\ -fno-color-diagnostics Disable colors in diagnostics
\\ -I Add directory to include search path
\\ -isystem Add directory to SYSTEM include search path
\\ -o Write output to
\\ -std= Specify language standard
+ \\ --target= Generate code for the given target
+ \\ -U Undefine
\\ -Wall Enable all warnings
\\ -Werror Treat all warnings as errors
\\ -Werror= Treat warning as error
\\ -W Enable the specified warning
\\ -Wno- Disable the specified warning
\\
+ \\Depub options:
+ \\ --verbose-ast Dump produced AST to stdout
+ \\
\\
;
@@ -95,6 +101,8 @@ fn handleArgs(comp: *Compilation, args: [][]const u8) !void {
return comp.diag.fatalNoSrc("{s} when trying to print version", .{@errorName(err)});
};
return;
+ } else if (mem.eql(u8, arg, "-c")) {
+ comp.only_compile = true;
} else if (mem.startsWith(u8, arg, "-D")) {
var macro = arg["-D".len..];
if (macro.len == 0) {
@@ -161,7 +169,15 @@ fn handleArgs(comp: *Compilation, args: [][]const u8) !void {
try comp.diag.set(option, .warning);
} else if (mem.startsWith(u8, arg, "-std=")) {
const standard = arg["-std=".len..];
- comp.langopts.setStandard(standard) catch return comp.diag.fatalNoSrc("Invalid standard '{s}'", .{standard});
+ comp.langopts.setStandard(standard) catch
+ return comp.diag.fatalNoSrc("Invalid standard '{s}'", .{standard});
+ } else if (mem.startsWith(u8, arg, "--target=")) {
+ const triple = arg["--target=".len..];
+ const cross = std.zig.CrossTarget.parse(.{ .arch_os_abi = triple }) catch
+ return comp.diag.fatalNoSrc("Invalid target '{s}'", .{triple});
+ comp.target = cross.toTarget(); // TODO deprecated
+ } else if (mem.eql(u8, arg, "--verbose-ast")) {
+ comp.verbose_ast = true;
} else {
const std_out = std.io.getStdErr().writer();
std_out.print(usage, .{args[0]}) catch {};
@@ -233,12 +249,48 @@ fn processSource(comp: *Compilation, source: Source, builtin: Source, user_macro
var tree = try Parser.parse(&pp);
defer tree.deinit();
+ if (comp.verbose_ast) {
+ var buf_writer = std.io.bufferedWriter(std.io.getStdOut().writer());
+ tree.dump(buf_writer.writer()) catch {};
+ buf_writer.flush() catch {};
+ }
+
+ const prev_errors = comp.diag.errors;
comp.renderErrors();
- tree.dump(std.io.getStdOut().writer()) catch {};
+ if (comp.diag.errors != prev_errors) return; // do not compile if there were errors
+
+ if (comp.target.getObjectFormat() != .elf or comp.target.cpu.arch != .x86_64) {
+ return comp.diag.fatalNoSrc(
+ "unsupported target {s}-{s}-{s}, currently only x86-64 elf is supported",
+ .{ @tagName(comp.target.cpu.arch), @tagName(comp.target.os.tag), @tagName(comp.target.abi) },
+ );
+ }
+
+ const obj = try Codegen.generateTree(comp, tree);
+ defer obj.deinit();
+
+ const basename = std.fs.path.basename(source.path);
+ const out_file_name = comp.output_name orelse try std.fmt.allocPrint(comp.gpa, "{s}{s}", .{
+ basename[0 .. basename.len - std.fs.path.extension(source.path).len],
+ comp.target.getObjectFormat().fileExt(comp.target.cpu.arch),
+ });
+ defer if (comp.output_name == null) comp.gpa.free(out_file_name);
+
+ const out_file = std.fs.cwd().createFile(out_file_name, .{}) catch |err|
+ return comp.diag.fatalNoSrc("could not create output file '{s}': {s}", .{ out_file_name, @errorName(err) });
+ defer out_file.close();
+
+ obj.finish(out_file) catch |err|
+ return comp.diag.fatalNoSrc("could output to object file '{s}': {s}", .{ out_file_name, @errorName(err) });
+
+ if (comp.only_compile) return;
+
+ // TODO invoke linker
}
test {
+ _ = @import("Codegen.zig");
_ = @import("Compilation.zig");
_ = @import("Diagnostics.zig");
_ = @import("InitList.zig");
diff --git a/src/object/Elf.zig b/src/object/Elf.zig
new file mode 100644
index 00000000..b22933bd
--- /dev/null
+++ b/src/object/Elf.zig
@@ -0,0 +1,377 @@
+const std = @import("std");
+const Compilation = @import("../Compilation.zig");
+const Object = @import("../Object.zig");
+
+const Elf = @This();
+
+const Section = struct {
+ data: std.ArrayList(u8),
+ relocations: std.ArrayListUnmanaged(Relocation) = .{},
+ flags: u64,
+ type: u32,
+ index: u16 = undefined,
+};
+
+const Symbol = struct {
+ section: ?*Section,
+ size: u64,
+ offset: u64,
+ index: u16 = undefined,
+ info: u8,
+};
+
+const Relocation = packed struct {
+ symbol: *Symbol,
+ addend: i64,
+ offset: u48,
+ type: u8,
+};
+
+const additional_sections = 3; // null section, strtab, symtab
+const strtab_index = 1;
+const symtab_index = 2;
+const strtab_default = "\x00.strtab\x00.symtab\x00";
+const strtab_name = 1;
+const symtab_name = "\x00.strtab\x00".len;
+
+obj: Object,
+/// The keys are owned by the Codegen.tree
+sections: std.StringHashMapUnmanaged(*Section) = .{},
+local_symbols: std.StringHashMapUnmanaged(*Symbol) = .{},
+global_symbols: std.StringHashMapUnmanaged(*Symbol) = .{},
+unnamed_symbol_mangle: u32 = 0,
+strtab_len: u64 = strtab_default.len,
+arena: std.heap.ArenaAllocator,
+
+pub fn create(comp: *Compilation) !*Object {
+ const elf = try comp.gpa.create(Elf);
+ elf.* = .{
+ .obj = .{ .format = .elf, .comp = comp },
+ .arena = std.heap.ArenaAllocator.init(comp.gpa),
+ };
+ return &elf.obj;
+}
+
+pub fn deinit(elf: *Elf) void {
+ const gpa = elf.arena.child_allocator;
+ {
+ var it = elf.sections.valueIterator();
+ while (it.next()) |sect| {
+ sect.*.data.deinit();
+ sect.*.relocations.deinit(gpa);
+ }
+ }
+ elf.sections.deinit(gpa);
+ elf.local_symbols.deinit(gpa);
+ elf.global_symbols.deinit(gpa);
+ elf.arena.deinit();
+ gpa.destroy(elf);
+}
+
+fn sectionString(sec: Object.Section) []const u8 {
+ return switch (sec) {
+ .@"undefined" => unreachable,
+ .data => "data",
+ .read_only_data => "rodata",
+ .func => "text",
+ .strings => "rodata.str",
+ .custom => |name| name,
+ };
+}
+
+pub fn getSection(elf: *Elf, section_kind: Object.Section) !*std.ArrayList(u8) {
+ const section_name = sectionString(section_kind);
+ const section = elf.sections.get(section_name) orelse blk: {
+ const section = try elf.arena.allocator.create(Section);
+ section.* = .{
+ .data = std.ArrayList(u8).init(elf.arena.child_allocator),
+ .type = std.elf.SHT_PROGBITS,
+ .flags = switch (section_kind) {
+ .func, .custom => std.elf.SHF_ALLOC + std.elf.SHF_EXECINSTR,
+ .strings => std.elf.SHF_ALLOC + std.elf.SHF_MERGE + std.elf.SHF_STRINGS,
+ .read_only_data => std.elf.SHF_ALLOC,
+ .data => std.elf.SHF_ALLOC + std.elf.SHF_WRITE,
+ .@"undefined" => unreachable,
+ },
+ };
+ try elf.sections.putNoClobber(elf.arena.child_allocator, section_name, section);
+ elf.strtab_len += section_name.len + ".\x00".len;
+ break :blk section;
+ };
+ return §ion.data;
+}
+
+pub fn declareSymbol(
+ elf: *Elf,
+ section_kind: Object.Section,
+ maybe_name: ?[]const u8,
+ linkage: std.builtin.GlobalLinkage,
+ @"type": Object.SymbolType,
+ offset: u64,
+ size: u64,
+) ![]const u8 {
+ const section = blk: {
+ if (section_kind == .@"undefined") break :blk null;
+ const section_name = sectionString(section_kind);
+ break :blk elf.sections.get(section_name);
+ };
+ const binding: u8 = switch (linkage) {
+ .Internal => std.elf.STB_LOCAL,
+ .Strong => std.elf.STB_GLOBAL,
+ .Weak => std.elf.STB_WEAK,
+ .LinkOnce => unreachable,
+ };
+ const sym_type: u8 = switch (@"type") {
+ .func => std.elf.STT_FUNC,
+ .variable => std.elf.STT_OBJECT,
+ .external => std.elf.STT_NOTYPE,
+ };
+ const name = if (maybe_name) |some| some else blk: {
+ defer elf.unnamed_symbol_mangle += 1;
+ break :blk try std.fmt.allocPrint(&elf.arena.allocator, ".L.{d}", .{elf.unnamed_symbol_mangle});
+ };
+
+ const gop = if (linkage == .Internal)
+ try elf.local_symbols.getOrPut(elf.arena.child_allocator, name)
+ else
+ try elf.global_symbols.getOrPut(elf.arena.child_allocator, name);
+
+ if (!gop.found_existing) {
+ gop.value_ptr.* = try elf.arena.allocator.create(Symbol);
+ elf.strtab_len += name.len + 1; // +1 for null byte
+ }
+ gop.value_ptr.*.* = .{
+ .section = section,
+ .size = size,
+ .offset = offset,
+ .info = (binding << 4) + sym_type,
+ };
+ return name;
+}
+
+pub fn addRelocation(elf: *Elf, name: []const u8, section_kind: Object.Section, address: u64, addend: i64) !void {
+ const section_name = sectionString(section_kind);
+ const symbol = elf.local_symbols.get(name) orelse elf.global_symbols.get(name).?; // reference to undeclared symbol
+ const section = elf.sections.get(section_name).?;
+ if (section.relocations.items.len == 0) elf.strtab_len += ".rela".len;
+
+ try section.relocations.append(elf.arena.child_allocator, .{
+ .symbol = symbol,
+ .offset = @intCast(u48, address),
+ .addend = addend,
+ .type = if (symbol.section == null) 4 else 2, // TODO
+ });
+}
+
+/// elf header
+/// sections contents
+/// symbols
+/// relocations
+/// strtab
+/// section headers
+pub fn finish(elf: *Elf, file: std.fs.File) !void {
+ var buf_writer = std.io.bufferedWriter(file.writer());
+ const w = buf_writer.writer();
+
+ var num_sections: std.elf.Elf64_Half = additional_sections;
+ var relocations_len: std.elf.Elf64_Off = 0;
+ var sections_len: std.elf.Elf64_Off = 0;
+ {
+ var it = elf.sections.valueIterator();
+ while (it.next()) |sect| {
+ sections_len += sect.*.data.items.len;
+ relocations_len += sect.*.relocations.items.len * @sizeOf(std.elf.Elf64_Rela);
+ sect.*.index = num_sections;
+ num_sections += 1;
+ num_sections += @boolToInt(sect.*.relocations.items.len != 0);
+ }
+ }
+ const symtab_len = (elf.local_symbols.count() + elf.global_symbols.count() + 1) * @sizeOf(std.elf.Elf64_Sym);
+
+ const symtab_offset = @sizeOf(std.elf.Elf64_Ehdr) + sections_len;
+ const symtab_offset_aligned = std.mem.alignForward(symtab_offset, 8);
+ const rela_offset = symtab_offset_aligned + symtab_len;
+ const strtab_offset = rela_offset + relocations_len;
+ const sh_offset = strtab_offset + elf.strtab_len;
+ const sh_offset_aligned = std.mem.alignForward(sh_offset, 16);
+
+ var elf_header = std.elf.Elf64_Ehdr{
+ .e_ident = .{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ .e_type = std.elf.ET.REL, // we only produce relocatables
+ .e_machine = elf.obj.comp.target.cpu.arch.toElfMachine(),
+ .e_version = 1,
+ .e_entry = 0, // linker will handle this
+ .e_phoff = 0, // no program header
+ .e_shoff = sh_offset_aligned, // section headers offset
+ .e_flags = 0, // no flags
+ .e_ehsize = @sizeOf(std.elf.Elf64_Ehdr),
+ .e_phentsize = 0, // no program header
+ .e_phnum = 0, // no program header
+ .e_shentsize = @sizeOf(std.elf.Elf64_Shdr),
+ .e_shnum = num_sections,
+ .e_shstrndx = strtab_index,
+ };
+ try w.writeStruct(elf_header);
+
+ // write contents of sections
+ {
+ var it = elf.sections.valueIterator();
+ while (it.next()) |sect| try w.writeAll(sect.*.data.items);
+ }
+
+ // pad to 8 bytes
+ try w.writeByteNTimes(0, symtab_offset_aligned - symtab_offset);
+
+ var name_offset: u32 = strtab_default.len;
+ // write symbols
+ {
+ // first symbol must be null
+ try w.writeStruct(std.mem.zeroes(std.elf.Elf64_Sym));
+
+ var sym_index: u16 = 1;
+ var it = elf.local_symbols.iterator();
+ while (it.next()) |entry| {
+ const sym = entry.value_ptr.*;
+ try w.writeStruct(std.elf.Elf64_Sym{
+ .st_name = name_offset,
+ .st_info = sym.info,
+ .st_other = 0,
+ .st_shndx = if (sym.section) |some| some.index else 0,
+ .st_value = sym.offset,
+ .st_size = sym.size,
+ });
+ sym.index = sym_index;
+ sym_index += 1;
+ name_offset += @intCast(u32, entry.key_ptr.len + 1); // +1 for null byte
+ }
+ it = elf.global_symbols.iterator();
+ while (it.next()) |entry| {
+ const sym = entry.value_ptr.*;
+ try w.writeStruct(std.elf.Elf64_Sym{
+ .st_name = name_offset,
+ .st_info = sym.info,
+ .st_other = 0,
+ .st_shndx = if (sym.section) |some| some.index else 0,
+ .st_value = sym.offset,
+ .st_size = sym.size,
+ });
+ sym.index = sym_index;
+ sym_index += 1;
+ name_offset += @intCast(u32, entry.key_ptr.len + 1); // +1 for null byte
+ }
+ }
+
+ // write relocations
+ {
+ var it = elf.sections.valueIterator();
+ while (it.next()) |sect| {
+ for (sect.*.relocations.items) |rela| {
+ try w.writeStruct(std.elf.Elf64_Rela{
+ .r_offset = rela.offset,
+ .r_addend = rela.addend,
+ .r_info = (@as(u64, rela.symbol.index) << 32) | rela.type,
+ });
+ }
+ }
+ }
+
+ // write strtab
+ try w.writeAll(strtab_default);
+ {
+ var it = elf.local_symbols.keyIterator();
+ while (it.next()) |key| try w.print("{s}\x00", .{key.*});
+ it = elf.global_symbols.keyIterator();
+ while (it.next()) |key| try w.print("{s}\x00", .{key.*});
+ }
+ {
+ var it = elf.sections.iterator();
+ while (it.next()) |entry| {
+ if (entry.value_ptr.*.relocations.items.len != 0) try w.writeAll(".rela");
+ try w.print(".{s}\x00", .{entry.key_ptr.*});
+ }
+ }
+
+ // pad to 16 bytes
+ try w.writeByteNTimes(0, sh_offset_aligned - sh_offset);
+ // mandatory null header
+ try w.writeStruct(std.mem.zeroes(std.elf.Elf64_Shdr));
+
+ // write strtab section header
+ {
+ var sect_header = std.elf.Elf64_Shdr{
+ .sh_name = strtab_name,
+ .sh_type = std.elf.SHT_STRTAB,
+ .sh_flags = 0,
+ .sh_addr = 0,
+ .sh_offset = strtab_offset,
+ .sh_size = elf.strtab_len,
+ .sh_link = 0,
+ .sh_info = 0,
+ .sh_addralign = 1,
+ .sh_entsize = 0,
+ };
+ try w.writeStruct(sect_header);
+ }
+
+ // write symtab section header
+ {
+ var sect_header = std.elf.Elf64_Shdr{
+ .sh_name = symtab_name,
+ .sh_type = std.elf.SHT_SYMTAB,
+ .sh_flags = 0,
+ .sh_addr = 0,
+ .sh_offset = symtab_offset_aligned,
+ .sh_size = symtab_len,
+ .sh_link = strtab_index,
+ .sh_info = elf.local_symbols.size + 1,
+ .sh_addralign = 8,
+ .sh_entsize = @sizeOf(std.elf.Elf64_Sym),
+ };
+ try w.writeStruct(sect_header);
+ }
+
+ // remaining section headers
+ {
+ var sect_offset: u64 = @sizeOf(std.elf.Elf64_Ehdr);
+ var rela_sect_offset: u64 = rela_offset;
+ var it = elf.sections.iterator();
+ while (it.next()) |entry| {
+ const sect = entry.value_ptr.*;
+ const rela_count = sect.relocations.items.len;
+ const rela_name_offset = if (rela_count != 0) @truncate(u32, ".rela".len) else 0;
+ try w.writeStruct(std.elf.Elf64_Shdr{
+ .sh_name = rela_name_offset + name_offset,
+ .sh_type = sect.type,
+ .sh_flags = sect.flags,
+ .sh_addr = 0,
+ .sh_offset = sect_offset,
+ .sh_size = sect.data.items.len,
+ .sh_link = 0,
+ .sh_info = 0,
+ .sh_addralign = if (sect.flags & std.elf.SHF_EXECINSTR != 0) 16 else 1,
+ .sh_entsize = 0,
+ });
+
+ if (rela_count != 0) {
+ const size = rela_count * @sizeOf(std.elf.Elf64_Rela);
+ try w.writeStruct(std.elf.Elf64_Shdr{
+ .sh_name = name_offset,
+ .sh_type = std.elf.SHT_RELA,
+ .sh_flags = 0,
+ .sh_addr = 0,
+ .sh_offset = rela_sect_offset,
+ .sh_size = rela_count * @sizeOf(std.elf.Elf64_Rela),
+ .sh_link = symtab_index,
+ .sh_info = sect.index,
+ .sh_addralign = 8,
+ .sh_entsize = @sizeOf(std.elf.Elf64_Rela),
+ });
+ rela_sect_offset += size;
+ }
+
+ sect_offset += sect.data.items.len;
+ name_offset += @intCast(u32, entry.key_ptr.len + ".\x00".len) + rela_name_offset;
+ }
+ }
+ try buf_writer.flush();
+}
diff --git a/test/cases/hello world.c b/test/cases/hello world.c
index 0f9454e4..0b04a794 100644
--- a/test/cases/hello world.c
+++ b/test/cases/hello world.c
@@ -1,16 +1,17 @@
-// our builtin macros and include searches are not advanced enough to work in the CI
-#define TESTS_SKIPPED 1
-
-#if 0
-#include
+extern int printf(const char*, ...);
+static int foo(void);
int main(int argc, char **argv) {
+ return foo();
+}
+
+static int foo(void) {
printf("Hello, world!\n");
return 0;
}
-#define EXPECTED_ERRORS "warning: '__WORDSIZE' macro redefined" \
- "warning: '__WORDSIZE_TIME64_COMPAT32' macro redefined" \
- "warning: '__SYSCALL_WORDSIZE' macro redefined" \
- "warning: '__LDOUBLE_REDIRECTS_TO_FLOAT128_ABI' macro redefined"
-#endif
+#if defined __linux__ && defined __x86_64__
+#define EXPECTED_OUTPUT "Hello, world!\n"
+#else
+#define TESTS_SKIPPED 1
+#endif
\ No newline at end of file
diff --git a/test/runner.zig b/test/runner.zig
index dea50ffd..130d4f0f 100644
--- a/test/runner.zig
+++ b/test/runner.zig
@@ -1,6 +1,7 @@
const std = @import("std");
const print = std.debug.print;
const aro = @import("aro");
+const Codegen = aro.Codegen;
const Tree = aro.Tree;
const Token = Tree.Token;
const NodeIndex = Tree.NodeIndex;
@@ -19,8 +20,8 @@ pub fn main() !void {
var args = try std.process.argsAlloc(gpa);
defer std.process.argsFree(gpa, args);
- if (args.len != 2) {
- print("expected test case directory as only argument\n", .{});
+ if (args.len != 3) {
+ print("expected test case directory and zig executable as only arguments\n", .{});
return error.InvalidArguments;
}
@@ -74,6 +75,13 @@ pub fn main() !void {
break :blk source;
};
+ // apparently we can't use setAstCwd without libc on windows yet
+ const win = @import("builtin").os.tag == .windows;
+ var tmp_dir = if (!win) std.testing.tmpDir(.{});
+ defer if (!win) tmp_dir.cleanup();
+
+ if (!win) try tmp_dir.dir.setAsCwd();
+
// iterate over all cases
var ok_count: u32 = 0;
var fail_count: u32 = 0;
@@ -283,6 +291,79 @@ pub fn main() !void {
}
comp.renderErrors();
+
+ if (pp.defines.get("EXPECTED_OUTPUT")) |macro| blk: {
+ if (comp.diag.errors != 0) break :blk;
+
+ if (macro != .simple) {
+ fail_count += 1;
+ progress.log("invalid EXPECTED_OUTPUT {}\n", .{macro});
+ continue;
+ }
+
+ if (macro.simple.tokens.len != 1 or macro.simple.tokens[0].id != .string_literal) {
+ fail_count += 1;
+ progress.log("EXPECTED_OUTPUT takes exactly one string", .{});
+ continue;
+ }
+
+ const start = path_buf.items.len;
+ defer path_buf.items.len = start;
+ // realistically the strings will only contain \" if any escapes so we can use Zig's string parsing
+ std.debug.assert((try std.zig.string_literal.parseAppend(&path_buf, pp.tokSliceSafe(macro.simple.tokens[0]))) == .success);
+ const expected_output = path_buf.items[start..];
+
+ const obj_name = "test_object.o";
+ {
+ const obj = try Codegen.generateTree(&comp, tree);
+ defer obj.deinit();
+
+ const out_file = try std.fs.cwd().createFile(obj_name, .{});
+ defer out_file.close();
+
+ try obj.finish(out_file);
+ }
+
+ var child = try std.ChildProcess.init(&.{ args[2], "run", "-lc", obj_name }, comp.gpa);
+ defer child.deinit();
+
+ child.stdout_behavior = .Pipe;
+
+ try child.spawn();
+
+ const stdout = try child.stdout.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(u16));
+ defer comp.gpa.free(stdout);
+
+ switch (try child.wait()) {
+ .Exited => |code| if (code != 0) {
+ fail_count += 1;
+ continue;
+ },
+ else => {
+ fail_count += 1;
+ continue;
+ },
+ }
+
+ if (!std.mem.eql(u8, expected_output, stdout)) {
+ fail_count += 1;
+ progress.log(
+ \\
+ \\======= expected output =======
+ \\{s}
+ \\
+ \\=== but output does not contain it ===
+ \\{s}
+ \\
+ \\
+ , .{ expected_output, stdout });
+ break;
+ }
+
+ ok_count += 1;
+ continue;
+ }
+
if (comp.diag.errors != 0) fail_count += 1 else ok_count += 1;
}