aboutsummaryrefslogtreecommitdiff
path: root/src/codegen
diff options
context:
space:
mode:
Diffstat (limited to 'src/codegen')
-rw-r--r--src/codegen/arm.zig607
-rw-r--r--src/codegen/c.zig299
-rw-r--r--src/codegen/llvm.zig125
-rw-r--r--src/codegen/riscv64.zig433
-rw-r--r--src/codegen/spu-mk2.zig170
-rw-r--r--src/codegen/spu-mk2/interpreter.zig166
-rw-r--r--src/codegen/wasm.zig142
-rw-r--r--src/codegen/x86.zig123
-rw-r--r--src/codegen/x86_64.zig220
9 files changed, 2285 insertions, 0 deletions
diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig
new file mode 100644
index 0000000000..05178ea7d3
--- /dev/null
+++ b/src/codegen/arm.zig
@@ -0,0 +1,607 @@
+const std = @import("std");
+const DW = std.dwarf;
+const testing = std.testing;
+
+/// The condition field specifies the flags neccessary for an
+/// Instruction to be executed
+pub const Condition = enum(u4) {
+ /// equal
+ eq,
+ /// not equal
+ ne,
+ /// unsigned higher or same
+ cs,
+ /// unsigned lower
+ cc,
+ /// negative
+ mi,
+ /// positive or zero
+ pl,
+ /// overflow
+ vs,
+ /// no overflow
+ vc,
+ /// unsigned higer
+ hi,
+ /// unsigned lower or same
+ ls,
+ /// greater or equal
+ ge,
+ /// less than
+ lt,
+ /// greater than
+ gt,
+ /// less than or equal
+ le,
+ /// always
+ al,
+};
+
+/// Represents a register in the ARM instruction set architecture
+pub const Register = enum(u5) {
+ r0,
+ r1,
+ r2,
+ r3,
+ r4,
+ r5,
+ r6,
+ r7,
+ r8,
+ r9,
+ r10,
+ r11,
+ r12,
+ r13,
+ r14,
+ r15,
+
+ /// Argument / result / scratch register 1
+ a1,
+ /// Argument / result / scratch register 2
+ a2,
+ /// Argument / scratch register 3
+ a3,
+ /// Argument / scratch register 4
+ a4,
+ /// Variable-register 1
+ v1,
+ /// Variable-register 2
+ v2,
+ /// Variable-register 3
+ v3,
+ /// Variable-register 4
+ v4,
+ /// Variable-register 5
+ v5,
+ /// Platform register
+ v6,
+ /// Variable-register 7
+ v7,
+ /// Frame pointer or Variable-register 8
+ fp,
+ /// Intra-Procedure-call scratch register
+ ip,
+ /// Stack pointer
+ sp,
+ /// Link register
+ lr,
+ /// Program counter
+ pc,
+
+ /// Returns the unique 4-bit ID of this register which is used in
+ /// the machine code
+ pub fn id(self: Register) u4 {
+ return @truncate(u4, @enumToInt(self));
+ }
+
+ /// Returns the index into `callee_preserved_regs`.
+ pub fn allocIndex(self: Register) ?u4 {
+ inline for (callee_preserved_regs) |cpreg, i| {
+ if (self.id() == cpreg.id()) return i;
+ }
+ return null;
+ }
+
+ pub fn dwarfLocOp(self: Register) u8 {
+ return @as(u8, self.id()) + DW.OP_reg0;
+ }
+};
+
+test "Register.id" {
+ testing.expectEqual(@as(u4, 15), Register.r15.id());
+ testing.expectEqual(@as(u4, 15), Register.pc.id());
+}
+
+pub const callee_preserved_regs = [_]Register{ .r0, .r1, .r2, .r3, .r4, .r5, .r6, .r7, .r8, .r10 };
+pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 };
+pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 };
+
+/// Represents an instruction in the ARM instruction set architecture
+pub const Instruction = union(enum) {
+ DataProcessing: packed struct {
+ // Note to self: The order of the fields top-to-bottom is
+ // right-to-left in the actual 32-bit int representation
+ op2: u12,
+ rd: u4,
+ rn: u4,
+ s: u1,
+ opcode: u4,
+ i: u1,
+ fixed: u2 = 0b00,
+ cond: u4,
+ },
+ SingleDataTransfer: packed struct {
+ offset: u12,
+ rd: u4,
+ rn: u4,
+ l: u1,
+ w: u1,
+ b: u1,
+ u: u1,
+ p: u1,
+ i: u1,
+ fixed: u2 = 0b01,
+ cond: u4,
+ },
+ Branch: packed struct {
+ offset: u24,
+ link: u1,
+ fixed: u3 = 0b101,
+ cond: u4,
+ },
+ BranchExchange: packed struct {
+ rn: u4,
+ fixed_1: u1 = 0b1,
+ link: u1,
+ fixed_2: u22 = 0b0001_0010_1111_1111_1111_00,
+ cond: u4,
+ },
+ SupervisorCall: packed struct {
+ comment: u24,
+ fixed: u4 = 0b1111,
+ cond: u4,
+ },
+ Breakpoint: packed struct {
+ imm4: u4,
+ fixed_1: u4 = 0b0111,
+ imm12: u12,
+ fixed_2_and_cond: u12 = 0b1110_0001_0010,
+ },
+
+ /// Represents the possible operations which can be performed by a
+ /// DataProcessing instruction
+ const Opcode = enum(u4) {
+ // Rd := Op1 AND Op2
+ @"and",
+ // Rd := Op1 EOR Op2
+ eor,
+ // Rd := Op1 - Op2
+ sub,
+ // Rd := Op2 - Op1
+ rsb,
+ // Rd := Op1 + Op2
+ add,
+ // Rd := Op1 + Op2 + C
+ adc,
+ // Rd := Op1 - Op2 + C - 1
+ sbc,
+ // Rd := Op2 - Op1 + C - 1
+ rsc,
+ // set condition codes on Op1 AND Op2
+ tst,
+ // set condition codes on Op1 EOR Op2
+ teq,
+ // set condition codes on Op1 - Op2
+ cmp,
+ // set condition codes on Op1 + Op2
+ cmn,
+ // Rd := Op1 OR Op2
+ orr,
+ // Rd := Op2
+ mov,
+ // Rd := Op1 AND NOT Op2
+ bic,
+ // Rd := NOT Op2
+ mvn,
+ };
+
+ /// Represents the second operand to a data processing instruction
+ /// which can either be content from a register or an immediate
+ /// value
+ pub const Operand = union(enum) {
+ Register: packed struct {
+ rm: u4,
+ shift: u8,
+ },
+ Immediate: packed struct {
+ imm: u8,
+ rotate: u4,
+ },
+
+ /// Represents multiple ways a register can be shifted. A
+ /// register can be shifted by a specific immediate value or
+ /// by the contents of another register
+ pub const Shift = union(enum) {
+ Immediate: packed struct {
+ fixed: u1 = 0b0,
+ typ: u2,
+ amount: u5,
+ },
+ Register: packed struct {
+ fixed_1: u1 = 0b1,
+ typ: u2,
+ fixed_2: u1 = 0b0,
+ rs: u4,
+ },
+
+ const Type = enum(u2) {
+ LogicalLeft,
+ LogicalRight,
+ ArithmeticRight,
+ RotateRight,
+ };
+
+ const none = Shift{
+ .Immediate = .{
+ .amount = 0,
+ .typ = 0,
+ },
+ };
+
+ pub fn toU8(self: Shift) u8 {
+ return switch (self) {
+ .Register => |v| @bitCast(u8, v),
+ .Immediate => |v| @bitCast(u8, v),
+ };
+ }
+
+ pub fn reg(rs: Register, typ: Type) Shift {
+ return Shift{
+ .Register = .{
+ .rs = rs.id(),
+ .typ = @enumToInt(typ),
+ },
+ };
+ }
+
+ pub fn imm(amount: u5, typ: Type) Shift {
+ return Shift{
+ .Immediate = .{
+ .amount = amount,
+ .typ = @enumToInt(typ),
+ },
+ };
+ }
+ };
+
+ pub fn toU12(self: Operand) u12 {
+ return switch (self) {
+ .Register => |v| @bitCast(u12, v),
+ .Immediate => |v| @bitCast(u12, v),
+ };
+ }
+
+ pub fn reg(rm: Register, shift: Shift) Operand {
+ return Operand{
+ .Register = .{
+ .rm = rm.id(),
+ .shift = shift.toU8(),
+ },
+ };
+ }
+
+ pub fn imm(immediate: u8, rotate: u4) Operand {
+ return Operand{
+ .Immediate = .{
+ .imm = immediate,
+ .rotate = rotate,
+ },
+ };
+ }
+ };
+
+ /// Represents the offset operand of a load or store
+ /// instruction. Data can be loaded from memory with either an
+ /// immediate offset or an offset that is stored in some register.
+ pub const Offset = union(enum) {
+ Immediate: u12,
+ Register: packed struct {
+ rm: u4,
+ shift: u8,
+ },
+
+ pub const none = Offset{
+ .Immediate = 0,
+ };
+
+ pub fn toU12(self: Offset) u12 {
+ return switch (self) {
+ .Register => |v| @bitCast(u12, v),
+ .Immediate => |v| v,
+ };
+ }
+
+ pub fn reg(rm: Register, shift: u8) Offset {
+ return Offset{
+ .Register = .{
+ .rm = rm.id(),
+ .shift = shift,
+ },
+ };
+ }
+
+ pub fn imm(immediate: u8) Offset {
+ return Offset{
+ .Immediate = immediate,
+ };
+ }
+ };
+
+ pub fn toU32(self: Instruction) u32 {
+ return switch (self) {
+ .DataProcessing => |v| @bitCast(u32, v),
+ .SingleDataTransfer => |v| @bitCast(u32, v),
+ .Branch => |v| @bitCast(u32, v),
+ .BranchExchange => |v| @bitCast(u32, v),
+ .SupervisorCall => |v| @bitCast(u32, v),
+ .Breakpoint => |v| @intCast(u32, v.imm4) | (@intCast(u32, v.fixed_1) << 4) | (@intCast(u32, v.imm12) << 8) | (@intCast(u32, v.fixed_2_and_cond) << 20),
+ };
+ }
+
+ // Helper functions for the "real" functions below
+
+ fn dataProcessing(
+ cond: Condition,
+ opcode: Opcode,
+ s: u1,
+ rd: Register,
+ rn: Register,
+ op2: Operand,
+ ) Instruction {
+ return Instruction{
+ .DataProcessing = .{
+ .cond = @enumToInt(cond),
+ .i = if (op2 == .Immediate) 1 else 0,
+ .opcode = @enumToInt(opcode),
+ .s = s,
+ .rn = rn.id(),
+ .rd = rd.id(),
+ .op2 = op2.toU12(),
+ },
+ };
+ }
+
+ fn singleDataTransfer(
+ cond: Condition,
+ rd: Register,
+ rn: Register,
+ offset: Offset,
+ pre_post: u1,
+ up_down: u1,
+ byte_word: u1,
+ writeback: u1,
+ load_store: u1,
+ ) Instruction {
+ return Instruction{
+ .SingleDataTransfer = .{
+ .cond = @enumToInt(cond),
+ .rn = rn.id(),
+ .rd = rd.id(),
+ .offset = offset.toU12(),
+ .l = load_store,
+ .w = writeback,
+ .b = byte_word,
+ .u = up_down,
+ .p = pre_post,
+ .i = if (offset == .Immediate) 0 else 1,
+ },
+ };
+ }
+
+ fn branch(cond: Condition, offset: i24, link: u1) Instruction {
+ return Instruction{
+ .Branch = .{
+ .cond = @enumToInt(cond),
+ .link = link,
+ .offset = @bitCast(u24, offset),
+ },
+ };
+ }
+
+ fn branchExchange(cond: Condition, rn: Register, link: u1) Instruction {
+ return Instruction{
+ .BranchExchange = .{
+ .cond = @enumToInt(cond),
+ .link = link,
+ .rn = rn.id(),
+ },
+ };
+ }
+
+ fn supervisorCall(cond: Condition, comment: u24) Instruction {
+ return Instruction{
+ .SupervisorCall = .{
+ .cond = @enumToInt(cond),
+ .comment = comment,
+ },
+ };
+ }
+
+ fn breakpoint(imm: u16) Instruction {
+ return Instruction{
+ .Breakpoint = .{
+ .imm12 = @truncate(u12, imm >> 4),
+ .imm4 = @truncate(u4, imm),
+ },
+ };
+ }
+
+ // Public functions replicating assembler syntax as closely as
+ // possible
+
+ // Data processing
+
+ pub fn @"and"(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .@"and", s, rd, rn, op2);
+ }
+
+ pub fn eor(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .eor, s, rd, rn, op2);
+ }
+
+ pub fn sub(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .sub, s, rd, rn, op2);
+ }
+
+ pub fn rsb(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .rsb, s, rd, rn, op2);
+ }
+
+ pub fn add(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .add, s, rd, rn, op2);
+ }
+
+ pub fn adc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .adc, s, rd, rn, op2);
+ }
+
+ pub fn sbc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .sbc, s, rd, rn, op2);
+ }
+
+ pub fn rsc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .rsc, s, rd, rn, op2);
+ }
+
+ pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .tst, 1, .r0, rn, op2);
+ }
+
+ pub fn teq(cond: Condition, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .teq, 1, .r0, rn, op2);
+ }
+
+ pub fn cmp(cond: Condition, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .cmp, 1, .r0, rn, op2);
+ }
+
+ pub fn cmn(cond: Condition, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .cmn, 1, .r0, rn, op2);
+ }
+
+ pub fn orr(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .orr, s, rd, rn, op2);
+ }
+
+ pub fn mov(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .mov, s, rd, .r0, op2);
+ }
+
+ pub fn bic(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .bic, s, rd, rn, op2);
+ }
+
+ pub fn mvn(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .mvn, s, rd, .r0, op2);
+ }
+
+ // Single data transfer
+
+ pub fn ldr(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction {
+ return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 1);
+ }
+
+ pub fn str(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction {
+ return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 0);
+ }
+
+ // Branch
+
+ pub fn b(cond: Condition, offset: i24) Instruction {
+ return branch(cond, offset, 0);
+ }
+
+ pub fn bl(cond: Condition, offset: i24) Instruction {
+ return branch(cond, offset, 1);
+ }
+
+ // Branch and exchange
+
+ pub fn bx(cond: Condition, rn: Register) Instruction {
+ return branchExchange(cond, rn, 0);
+ }
+
+ pub fn blx(cond: Condition, rn: Register) Instruction {
+ return branchExchange(cond, rn, 1);
+ }
+
+ // Supervisor Call
+
+ pub const swi = svc;
+
+ pub fn svc(cond: Condition, comment: u24) Instruction {
+ return supervisorCall(cond, comment);
+ }
+
+ // Breakpoint
+
+ pub fn bkpt(imm: u16) Instruction {
+ return breakpoint(imm);
+ }
+};
+
+test "serialize instructions" {
+ const Testcase = struct {
+ inst: Instruction,
+ expected: u32,
+ };
+
+ const testcases = [_]Testcase{
+ .{ // add r0, r0, r0
+ .inst = Instruction.add(.al, 0, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)),
+ .expected = 0b1110_00_0_0100_0_0000_0000_00000000_0000,
+ },
+ .{ // mov r4, r2
+ .inst = Instruction.mov(.al, 0, .r4, Instruction.Operand.reg(.r2, Instruction.Operand.Shift.none)),
+ .expected = 0b1110_00_0_1101_0_0000_0100_00000000_0010,
+ },
+ .{ // mov r0, #42
+ .inst = Instruction.mov(.al, 0, .r0, Instruction.Operand.imm(42, 0)),
+ .expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010,
+ },
+ .{ // ldr r0, [r2, #42]
+ .inst = Instruction.ldr(.al, .r0, .r2, Instruction.Offset.imm(42)),
+ .expected = 0b1110_01_0_1_1_0_0_1_0010_0000_000000101010,
+ },
+ .{ // str r0, [r3]
+ .inst = Instruction.str(.al, .r0, .r3, Instruction.Offset.none),
+ .expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000,
+ },
+ .{ // b #12
+ .inst = Instruction.b(.al, 12),
+ .expected = 0b1110_101_0_0000_0000_0000_0000_0000_1100,
+ },
+ .{ // bl #-4
+ .inst = Instruction.bl(.al, -4),
+ .expected = 0b1110_101_1_1111_1111_1111_1111_1111_1100,
+ },
+ .{ // bx lr
+ .inst = Instruction.bx(.al, .lr),
+ .expected = 0b1110_0001_0010_1111_1111_1111_0001_1110,
+ },
+ .{ // svc #0
+ .inst = Instruction.svc(.al, 0),
+ .expected = 0b1110_1111_0000_0000_0000_0000_0000_0000,
+ },
+ .{ // bkpt #42
+ .inst = Instruction.bkpt(42),
+ .expected = 0b1110_0001_0010_000000000010_0111_1010,
+ },
+ };
+
+ for (testcases) |case| {
+ const actual = case.inst.toU32();
+ testing.expectEqual(case.expected, actual);
+ }
+}
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
new file mode 100644
index 0000000000..34ddcfbb3b
--- /dev/null
+++ b/src/codegen/c.zig
@@ -0,0 +1,299 @@
+const std = @import("std");
+
+const link = @import("../link.zig");
+const Module = @import("../Module.zig");
+
+const Inst = @import("../ir.zig").Inst;
+const Value = @import("../value.zig").Value;
+const Type = @import("../type.zig").Type;
+
+const C = link.File.C;
+const Decl = Module.Decl;
+const mem = std.mem;
+
+/// Maps a name from Zig source to C. Currently, this will always give the same
+/// output for any given input, sometimes resulting in broken identifiers.
+fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
+ return allocator.dupe(u8, name);
+}
+
+fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void {
+ switch (T.zigTypeTag()) {
+ .NoReturn => {
+ try writer.writeAll("zig_noreturn void");
+ },
+ .Void => try writer.writeAll("void"),
+ .Int => {
+ if (T.tag() == .u8) {
+ ctx.file.need_stdint = true;
+ try writer.writeAll("uint8_t");
+ } else if (T.tag() == .usize) {
+ ctx.file.need_stddef = true;
+ try writer.writeAll("size_t");
+ } else {
+ return ctx.file.fail(ctx.decl.src(), "TODO implement int types", .{});
+ }
+ },
+ else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement type {}", .{e}),
+ }
+}
+
+fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Value) !void {
+ switch (T.zigTypeTag()) {
+ .Int => {
+ if (T.isSignedInt())
+ return writer.print("{}", .{val.toSignedInt()});
+ return writer.print("{}", .{val.toUnsignedInt()});
+ },
+ else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement value {}", .{e}),
+ }
+}
+
+fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl: *Decl) !void {
+ const tv = decl.typed_value.most_recent.typed_value;
+ try renderType(ctx, writer, tv.ty.fnReturnType());
+ const name = try map(ctx.file.base.allocator, mem.spanZ(decl.name));
+ defer ctx.file.base.allocator.free(name);
+ try writer.print(" {}(", .{name});
+ var param_len = tv.ty.fnParamLen();
+ if (param_len == 0)
+ try writer.writeAll("void")
+ else {
+ var index: usize = 0;
+ while (index < param_len) : (index += 1) {
+ if (index > 0) {
+ try writer.writeAll(", ");
+ }
+ try renderType(ctx, writer, tv.ty.fnParamType(index));
+ try writer.print(" arg{}", .{index});
+ }
+ }
+ try writer.writeByte(')');
+}
+
+pub fn generate(file: *C, decl: *Decl) !void {
+ switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
+ .Fn => try genFn(file, decl),
+ .Array => try genArray(file, decl),
+ else => |e| return file.fail(decl.src(), "TODO {}", .{e}),
+ }
+}
+
+fn genArray(file: *C, decl: *Decl) !void {
+ const tv = decl.typed_value.most_recent.typed_value;
+ // TODO: prevent inline asm constants from being emitted
+ const name = try map(file.base.allocator, mem.span(decl.name));
+ defer file.base.allocator.free(name);
+ if (tv.val.cast(Value.Payload.Bytes)) |payload|
+ if (tv.ty.sentinel()) |sentinel|
+ if (sentinel.toUnsignedInt() == 0)
+ try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data })
+ else
+ return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{})
+ else
+ return file.fail(decl.src(), "TODO byte arrays without sentinels", .{})
+ else
+ return file.fail(decl.src(), "TODO non-byte arrays", .{});
+}
+
+const Context = struct {
+ file: *C,
+ decl: *Decl,
+ inst_map: std.AutoHashMap(*Inst, []u8),
+ argdex: usize = 0,
+ unnamed_index: usize = 0,
+
+ fn name(self: *Context) ![]u8 {
+ const val = try std.fmt.allocPrint(self.file.base.allocator, "__temp_{}", .{self.unnamed_index});
+ self.unnamed_index += 1;
+ return val;
+ }
+
+ fn deinit(self: *Context) void {
+ var it = self.inst_map.iterator();
+ while (it.next()) |kv| {
+ self.file.base.allocator.free(kv.value);
+ }
+ self.inst_map.deinit();
+ self.* = undefined;
+ }
+};
+
+fn genFn(file: *C, decl: *Decl) !void {
+ const writer = file.main.writer();
+ const tv = decl.typed_value.most_recent.typed_value;
+
+ var ctx = Context{
+ .file = file,
+ .decl = decl,
+ .inst_map = std.AutoHashMap(*Inst, []u8).init(file.base.allocator),
+ };
+ defer ctx.deinit();
+
+ try renderFunctionSignature(&ctx, writer, decl);
+
+ try writer.writeAll(" {");
+
+ const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func;
+ const instructions = func.analysis.success.instructions;
+ if (instructions.len > 0) {
+ try writer.writeAll("\n");
+ for (instructions) |inst| {
+ if (switch (inst.tag) {
+ .assembly => try genAsm(&ctx, inst.castTag(.assembly).?),
+ .call => try genCall(&ctx, inst.castTag(.call).?),
+ .ret => try genRet(&ctx, inst.castTag(.ret).?),
+ .retvoid => try genRetVoid(&ctx),
+ .arg => try genArg(&ctx),
+ .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?),
+ .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?),
+ .unreach => try genUnreach(&ctx, inst.castTag(.unreach).?),
+ .intcast => try genIntCast(&ctx, inst.castTag(.intcast).?),
+ else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
+ }) |name| {
+ try ctx.inst_map.putNoClobber(inst, name);
+ }
+ }
+ }
+
+ try writer.writeAll("}\n\n");
+}
+
+fn genArg(ctx: *Context) !?[]u8 {
+ const name = try std.fmt.allocPrint(ctx.file.base.allocator, "arg{}", .{ctx.argdex});
+ ctx.argdex += 1;
+ return name;
+}
+
+fn genRetVoid(ctx: *Context) !?[]u8 {
+ try ctx.file.main.writer().print(" return;\n", .{});
+ return null;
+}
+
+fn genRet(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {
+ return ctx.file.fail(ctx.decl.src(), "TODO return", .{});
+}
+
+fn genIntCast(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {
+ if (inst.base.isUnused())
+ return null;
+ const op = inst.operand;
+ const writer = ctx.file.main.writer();
+ const name = try ctx.name();
+ const from = ctx.inst_map.get(op) orelse
+ return ctx.file.fail(ctx.decl.src(), "Internal error in C backend: intCast argument not found in inst_map", .{});
+ try writer.writeAll(" const ");
+ try renderType(ctx, writer, inst.base.ty);
+ try writer.print(" {} = (", .{name});
+ try renderType(ctx, writer, inst.base.ty);
+ try writer.print("){};\n", .{from});
+ return name;
+}
+
+fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
+ const writer = ctx.file.main.writer();
+ const header = ctx.file.header.writer();
+ try writer.writeAll(" ");
+ if (inst.func.castTag(.constant)) |func_inst| {
+ if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
+ const target = func_val.func.owner_decl;
+ const target_ty = target.typed_value.most_recent.typed_value.ty;
+ const ret_ty = target_ty.fnReturnType().tag();
+ if (target_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) {
+ try writer.print("(void)", .{});
+ }
+ const tname = mem.spanZ(target.name);
+ if (ctx.file.called.get(tname) == null) {
+ try ctx.file.called.put(tname, void{});
+ try renderFunctionSignature(ctx, header, target);
+ try header.writeAll(";\n");
+ }
+ try writer.print("{}(", .{tname});
+ if (inst.args.len != 0) {
+ for (inst.args) |arg, i| {
+ if (i > 0) {
+ try writer.writeAll(", ");
+ }
+ if (arg.cast(Inst.Constant)) |con| {
+ try renderValue(ctx, writer, arg.ty, con.val);
+ } else {
+ return ctx.file.fail(ctx.decl.src(), "TODO call pass arg {}", .{arg});
+ }
+ }
+ }
+ try writer.writeAll(");\n");
+ } else {
+ return ctx.file.fail(ctx.decl.src(), "TODO non-function call target?", .{});
+ }
+ } else {
+ return ctx.file.fail(ctx.decl.src(), "TODO non-constant call inst?", .{});
+ }
+ return null;
+}
+
+fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
+ // TODO emit #line directive here with line number and filename
+ return null;
+}
+
+fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
+ // TODO ??
+ return null;
+}
+
+fn genUnreach(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
+ try ctx.file.main.writer().writeAll(" zig_unreachable();\n");
+ return null;
+}
+
+fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {
+ const writer = ctx.file.main.writer();
+ try writer.writeAll(" ");
+ for (as.inputs) |i, index| {
+ if (i[0] == '{' and i[i.len - 1] == '}') {
+ const reg = i[1 .. i.len - 1];
+ const arg = as.args[index];
+ try writer.writeAll("register ");
+ try renderType(ctx, writer, arg.ty);
+ try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg });
+ // TODO merge constant handling into inst_map as well
+ if (arg.castTag(.constant)) |c| {
+ try renderValue(ctx, writer, arg.ty, c.val);
+ try writer.writeAll(";\n ");
+ } else {
+ const gop = try ctx.inst_map.getOrPut(arg);
+ if (!gop.found_existing) {
+ return ctx.file.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{});
+ }
+ try writer.print("{};\n ", .{gop.entry.value});
+ }
+ } else {
+ return ctx.file.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{});
+ }
+ }
+ try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
+ if (as.output) |o| {
+ return ctx.file.fail(ctx.decl.src(), "TODO inline asm output", .{});
+ }
+ if (as.inputs.len > 0) {
+ if (as.output == null) {
+ try writer.writeAll(" :");
+ }
+ try writer.writeAll(": ");
+ for (as.inputs) |i, index| {
+ if (i[0] == '{' and i[i.len - 1] == '}') {
+ const reg = i[1 .. i.len - 1];
+ const arg = as.args[index];
+ if (index > 0) {
+ try writer.writeAll(", ");
+ }
+ try writer.print("\"\"({}_constant)", .{reg});
+ } else {
+ // This is blocked by the earlier test
+ unreachable;
+ }
+ }
+ }
+ try writer.writeAll(");\n");
+ return null;
+}
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
new file mode 100644
index 0000000000..01fa0baf02
--- /dev/null
+++ b/src/codegen/llvm.zig
@@ -0,0 +1,125 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+
+pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 {
+ const llvm_arch = switch (target.cpu.arch) {
+ .arm => "arm",
+ .armeb => "armeb",
+ .aarch64 => "aarch64",
+ .aarch64_be => "aarch64_be",
+ .aarch64_32 => "aarch64_32",
+ .arc => "arc",
+ .avr => "avr",
+ .bpfel => "bpfel",
+ .bpfeb => "bpfeb",
+ .hexagon => "hexagon",
+ .mips => "mips",
+ .mipsel => "mipsel",
+ .mips64 => "mips64",
+ .mips64el => "mips64el",
+ .msp430 => "msp430",
+ .powerpc => "powerpc",
+ .powerpc64 => "powerpc64",
+ .powerpc64le => "powerpc64le",
+ .r600 => "r600",
+ .amdgcn => "amdgcn",
+ .riscv32 => "riscv32",
+ .riscv64 => "riscv64",
+ .sparc => "sparc",
+ .sparcv9 => "sparcv9",
+ .sparcel => "sparcel",
+ .s390x => "s390x",
+ .tce => "tce",
+ .tcele => "tcele",
+ .thumb => "thumb",
+ .thumbeb => "thumbeb",
+ .i386 => "i386",
+ .x86_64 => "x86_64",
+ .xcore => "xcore",
+ .nvptx => "nvptx",
+ .nvptx64 => "nvptx64",
+ .le32 => "le32",
+ .le64 => "le64",
+ .amdil => "amdil",
+ .amdil64 => "amdil64",
+ .hsail => "hsail",
+ .hsail64 => "hsail64",
+ .spir => "spir",
+ .spir64 => "spir64",
+ .kalimba => "kalimba",
+ .shave => "shave",
+ .lanai => "lanai",
+ .wasm32 => "wasm32",
+ .wasm64 => "wasm64",
+ .renderscript32 => "renderscript32",
+ .renderscript64 => "renderscript64",
+ .ve => "ve",
+ .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII,
+ };
+ // TODO Add a sub-arch for some architectures depending on CPU features.
+
+ const llvm_os = switch (target.os.tag) {
+ .freestanding => "unknown",
+ .ananas => "ananas",
+ .cloudabi => "cloudabi",
+ .dragonfly => "dragonfly",
+ .freebsd => "freebsd",
+ .fuchsia => "fuchsia",
+ .ios => "ios",
+ .kfreebsd => "kfreebsd",
+ .linux => "linux",
+ .lv2 => "lv2",
+ .macosx => "macosx",
+ .netbsd => "netbsd",
+ .openbsd => "openbsd",
+ .solaris => "solaris",
+ .windows => "windows",
+ .haiku => "haiku",
+ .minix => "minix",
+ .rtems => "rtems",
+ .nacl => "nacl",
+ .cnk => "cnk",
+ .aix => "aix",
+ .cuda => "cuda",
+ .nvcl => "nvcl",
+ .amdhsa => "amdhsa",
+ .ps4 => "ps4",
+ .elfiamcu => "elfiamcu",
+ .tvos => "tvos",
+ .watchos => "watchos",
+ .mesa3d => "mesa3d",
+ .contiki => "contiki",
+ .amdpal => "amdpal",
+ .hermit => "hermit",
+ .hurd => "hurd",
+ .wasi => "wasi",
+ .emscripten => "emscripten",
+ .uefi => "windows",
+ .other => "unknown",
+ };
+
+ const llvm_abi = switch (target.abi) {
+ .none => "unknown",
+ .gnu => "gnu",
+ .gnuabin32 => "gnuabin32",
+ .gnuabi64 => "gnuabi64",
+ .gnueabi => "gnueabi",
+ .gnueabihf => "gnueabihf",
+ .gnux32 => "gnux32",
+ .code16 => "code16",
+ .eabi => "eabi",
+ .eabihf => "eabihf",
+ .android => "android",
+ .musl => "musl",
+ .musleabi => "musleabi",
+ .musleabihf => "musleabihf",
+ .msvc => "msvc",
+ .itanium => "itanium",
+ .cygnus => "cygnus",
+ .coreclr => "coreclr",
+ .simulator => "simulator",
+ .macabi => "macabi",
+ };
+
+ return std.fmt.allocPrint(allocator, "{}-unknown-{}-{}", .{ llvm_arch, llvm_os, llvm_abi });
+}
diff --git a/src/codegen/riscv64.zig b/src/codegen/riscv64.zig
new file mode 100644
index 0000000000..96b9c58f9c
--- /dev/null
+++ b/src/codegen/riscv64.zig
@@ -0,0 +1,433 @@
+const std = @import("std");
+const DW = std.dwarf;
+
+// TODO: this is only tagged to facilitate the monstrosity.
+// Once packed structs work make it packed.
+pub const Instruction = union(enum) {
+ R: packed struct {
+ opcode: u7,
+ rd: u5,
+ funct3: u3,
+ rs1: u5,
+ rs2: u5,
+ funct7: u7,
+ },
+ I: packed struct {
+ opcode: u7,
+ rd: u5,
+ funct3: u3,
+ rs1: u5,
+ imm0_11: u12,
+ },
+ S: packed struct {
+ opcode: u7,
+ imm0_4: u5,
+ funct3: u3,
+ rs1: u5,
+ rs2: u5,
+ imm5_11: u7,
+ },
+ B: packed struct {
+ opcode: u7,
+ imm11: u1,
+ imm1_4: u4,
+ funct3: u3,
+ rs1: u5,
+ rs2: u5,
+ imm5_10: u6,
+ imm12: u1,
+ },
+ U: packed struct {
+ opcode: u7,
+ rd: u5,
+ imm12_31: u20,
+ },
+ J: packed struct {
+ opcode: u7,
+ rd: u5,
+ imm12_19: u8,
+ imm11: u1,
+ imm1_10: u10,
+ imm20: u1,
+ },
+
+ // TODO: once packed structs work we can remove this monstrosity.
+ pub fn toU32(self: Instruction) u32 {
+ return switch (self) {
+ .R => |v| @bitCast(u32, v),
+ .I => |v| @bitCast(u32, v),
+ .S => |v| @bitCast(u32, v),
+ .B => |v| @intCast(u32, v.opcode) + (@intCast(u32, v.imm11) << 7) + (@intCast(u32, v.imm1_4) << 8) + (@intCast(u32, v.funct3) << 12) + (@intCast(u32, v.rs1) << 15) + (@intCast(u32, v.rs2) << 20) + (@intCast(u32, v.imm5_10) << 25) + (@intCast(u32, v.imm12) << 31),
+ .U => |v| @bitCast(u32, v),
+ .J => |v| @bitCast(u32, v),
+ };
+ }
+
+ fn rType(op: u7, fn3: u3, fn7: u7, rd: Register, r1: Register, r2: Register) Instruction {
+ return Instruction{
+ .R = .{
+ .opcode = op,
+ .funct3 = fn3,
+ .funct7 = fn7,
+ .rd = @enumToInt(rd),
+ .rs1 = @enumToInt(r1),
+ .rs2 = @enumToInt(r2),
+ },
+ };
+ }
+
+ // RISC-V is all signed all the time -- convert immediates to unsigned for processing
+ fn iType(op: u7, fn3: u3, rd: Register, r1: Register, imm: i12) Instruction {
+ const umm = @bitCast(u12, imm);
+
+ return Instruction{
+ .I = .{
+ .opcode = op,
+ .funct3 = fn3,
+ .rd = @enumToInt(rd),
+ .rs1 = @enumToInt(r1),
+ .imm0_11 = umm,
+ },
+ };
+ }
+
+ fn sType(op: u7, fn3: u3, r1: Register, r2: Register, imm: i12) Instruction {
+ const umm = @bitCast(u12, imm);
+
+ return Instruction{
+ .S = .{
+ .opcode = op,
+ .funct3 = fn3,
+ .rs1 = @enumToInt(r1),
+ .rs2 = @enumToInt(r2),
+ .imm0_4 = @truncate(u5, umm),
+ .imm5_11 = @truncate(u7, umm >> 5),
+ },
+ };
+ }
+
+ // Use significance value rather than bit value, same for J-type
+ // -- less burden on callsite, bonus semantic checking
+ fn bType(op: u7, fn3: u3, r1: Register, r2: Register, imm: i13) Instruction {
+ const umm = @bitCast(u13, imm);
+ if (umm % 2 != 0) @panic("Internal error: misaligned branch target");
+
+ return Instruction{
+ .B = .{
+ .opcode = op,
+ .funct3 = fn3,
+ .rs1 = @enumToInt(r1),
+ .rs2 = @enumToInt(r2),
+ .imm1_4 = @truncate(u4, umm >> 1),
+ .imm5_10 = @truncate(u6, umm >> 5),
+ .imm11 = @truncate(u1, umm >> 11),
+ .imm12 = @truncate(u1, umm >> 12),
+ },
+ };
+ }
+
+ // We have to extract the 20 bits anyway -- let's not make it more painful
+ fn uType(op: u7, rd: Register, imm: i20) Instruction {
+ const umm = @bitCast(u20, imm);
+
+ return Instruction{
+ .U = .{
+ .opcode = op,
+ .rd = @enumToInt(rd),
+ .imm12_31 = umm,
+ },
+ };
+ }
+
+ fn jType(op: u7, rd: Register, imm: i21) Instruction {
+ const umm = @bitcast(u21, imm);
+ if (umm % 2 != 0) @panic("Internal error: misaligned jump target");
+
+ return Instruction{
+ .J = .{
+ .opcode = op,
+ .rd = @enumToInt(rd),
+ .imm1_10 = @truncate(u10, umm >> 1),
+ .imm11 = @truncate(u1, umm >> 1),
+ .imm12_19 = @truncate(u8, umm >> 12),
+ .imm20 = @truncate(u1, umm >> 20),
+ },
+ };
+ }
+
+ // The meat and potatoes. Arguments are in the order in which they would appear in assembly code.
+
+ // Arithmetic/Logical, Register-Register
+
+ pub fn add(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0110011, 0b000, 0b0000000, rd, r1, r2);
+ }
+
+ pub fn sub(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0110011, 0b000, 0b0100000, rd, r1, r2);
+ }
+
+ pub fn @"and"(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0110011, 0b111, 0b0000000, rd, r1, r2);
+ }
+
+ pub fn @"or"(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0110011, 0b110, 0b0000000, rd, r1, r2);
+ }
+
+ pub fn xor(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0110011, 0b100, 0b0000000, rd, r1, r2);
+ }
+
+ pub fn sll(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0110011, 0b001, 0b0000000, rd, r1, r2);
+ }
+
+ pub fn srl(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0110011, 0b101, 0b0000000, rd, r1, r2);
+ }
+
+ pub fn sra(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0110011, 0b101, 0b0100000, rd, r1, r2);
+ }
+
+ pub fn slt(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0110011, 0b010, 0b0000000, rd, r1, r2);
+ }
+
+ pub fn sltu(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0110011, 0b011, 0b0000000, rd, r1, r2);
+ }
+
+ // Arithmetic/Logical, Register-Register (32-bit)
+
+ pub fn addw(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0111011, 0b000, rd, r1, r2);
+ }
+
+ pub fn subw(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0111011, 0b000, 0b0100000, rd, r1, r2);
+ }
+
+ pub fn sllw(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0111011, 0b001, 0b0000000, rd, r1, r2);
+ }
+
+ pub fn srlw(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0111011, 0b101, 0b0000000, rd, r1, r2);
+ }
+
+ pub fn sraw(rd: Register, r1: Register, r2: Register) Instruction {
+ return rType(0b0111011, 0b101, 0b0100000, rd, r1, r2);
+ }
+
+ // Arithmetic/Logical, Register-Immediate
+
+ pub fn addi(rd: Register, r1: Register, imm: i12) Instruction {
+ return iType(0b0010011, 0b000, rd, r1, imm);
+ }
+
+ pub fn andi(rd: Register, r1: Register, imm: i12) Instruction {
+ return iType(0b0010011, 0b111, rd, r1, imm);
+ }
+
+ pub fn ori(rd: Register, r1: Register, imm: i12) Instruction {
+ return iType(0b0010011, 0b110, rd, r1, imm);
+ }
+
+ pub fn xori(rd: Register, r1: Register, imm: i12) Instruction {
+ return iType(0b0010011, 0b100, rd, r1, imm);
+ }
+
+ pub fn slli(rd: Register, r1: Register, shamt: u6) Instruction {
+ return iType(0b0010011, 0b001, rd, r1, shamt);
+ }
+
+ pub fn srli(rd: Register, r1: Register, shamt: u6) Instruction {
+ return iType(0b0010011, 0b101, rd, r1, shamt);
+ }
+
+ pub fn srai(rd: Register, r1: Register, shamt: u6) Instruction {
+ return iType(0b0010011, 0b101, rd, r1, (1 << 10) + shamt);
+ }
+
+ pub fn slti(rd: Register, r1: Register, imm: i12) Instruction {
+ return iType(0b0010011, 0b010, rd, r1, imm);
+ }
+
+ pub fn sltiu(rd: Register, r1: Register, imm: u12) Instruction {
+ return iType(0b0010011, 0b011, rd, r1, @bitCast(i12, imm));
+ }
+
+ // Arithmetic/Logical, Register-Immediate (32-bit)
+
+ pub fn addiw(rd: Register, r1: Register, imm: i12) Instruction {
+ return iType(0b0011011, 0b000, rd, r1, imm);
+ }
+
+ pub fn slliw(rd: Register, r1: Register, shamt: u5) Instruction {
+ return iType(0b0011011, 0b001, rd, r1, shamt);
+ }
+
+ pub fn srliw(rd: Register, r1: Register, shamt: u5) Instruction {
+ return iType(0b0011011, 0b101, rd, r1, shamt);
+ }
+
+ pub fn sraiw(rd: Register, r1: Register, shamt: u5) Instruction {
+ return iType(0b0011011, 0b101, rd, r1, (1 << 10) + shamt);
+ }
+
+ // Upper Immediate
+
+ pub fn lui(rd: Register, imm: i20) Instruction {
+ return uType(0b0110111, rd, imm);
+ }
+
+ pub fn auipc(rd: Register, imm: i20) Instruction {
+ return uType(0b0010111, rd, imm);
+ }
+
+ // Load
+
+ pub fn ld(rd: Register, offset: i12, base: Register) Instruction {
+ return iType(0b0000011, 0b011, rd, base, offset);
+ }
+
+ pub fn lw(rd: Register, offset: i12, base: Register) Instruction {
+ return iType(0b0000011, 0b010, rd, base, offset);
+ }
+
+ pub fn lwu(rd: Register, offset: i12, base: Register) Instruction {
+ return iType(0b0000011, 0b110, rd, base, offset);
+ }
+
+ pub fn lh(rd: Register, offset: i12, base: Register) Instruction {
+ return iType(0b0000011, 0b001, rd, base, offset);
+ }
+
+ pub fn lhu(rd: Register, offset: i12, base: Register) Instruction {
+ return iType(0b0000011, 0b101, rd, base, offset);
+ }
+
+ pub fn lb(rd: Register, offset: i12, base: Register) Instruction {
+ return iType(0b0000011, 0b000, rd, base, offset);
+ }
+
+ pub fn lbu(rd: Register, offset: i12, base: Register) Instruction {
+ return iType(0b0000011, 0b100, rd, base, offset);
+ }
+
+ // Store
+
+ pub fn sd(rs: Register, offset: i12, base: Register) Instruction {
+ return sType(0b0100011, 0b011, base, rs, offset);
+ }
+
+ pub fn sw(rs: Register, offset: i12, base: Register) Instruction {
+ return sType(0b0100011, 0b010, base, rs, offset);
+ }
+
+ pub fn sh(rs: Register, offset: i12, base: Register) Instruction {
+ return sType(0b0100011, 0b001, base, rs, offset);
+ }
+
+ pub fn sb(rs: Register, offset: i12, base: Register) Instruction {
+ return sType(0b0100011, 0b000, base, rs, offset);
+ }
+
+ // Fence
+ // TODO: implement fence
+
+ // Branch
+
+ pub fn beq(r1: Register, r2: Register, offset: u13) Instruction {
+ return bType(0b1100011, 0b000, r1, r2, offset);
+ }
+
+ pub fn bne(r1: Register, r2: Register, offset: u13) Instruction {
+ return bType(0b1100011, 0b001, r1, r2, offset);
+ }
+
+ pub fn blt(r1: Register, r2: Register, offset: u13) Instruction {
+ return bType(0b1100011, 0b100, r1, r2, offset);
+ }
+
+ pub fn bge(r1: Register, r2: Register, offset: u13) Instruction {
+ return bType(0b1100011, 0b101, r1, r2, offset);
+ }
+
+ pub fn bltu(r1: Register, r2: Register, offset: u13) Instruction {
+ return bType(0b1100011, 0b110, r1, r2, offset);
+ }
+
+ pub fn bgeu(r1: Register, r2: Register, offset: u13) Instruction {
+ return bType(0b1100011, 0b111, r1, r2, offset);
+ }
+
+ // Jump
+
+ pub fn jal(link: Register, offset: i21) Instruction {
+ return jType(0b1101111, link, offset);
+ }
+
+ pub fn jalr(link: Register, offset: i12, base: Register) Instruction {
+ return iType(0b1100111, 0b000, link, base, offset);
+ }
+
+ // System
+
+ pub const ecall = iType(0b1110011, 0b000, .zero, .zero, 0x000);
+ pub const ebreak = iType(0b1110011, 0b000, .zero, .zero, 0x001);
+};
+
+// zig fmt: off
+pub const RawRegister = enum(u5) {
+ x0, x1, x2, x3, x4, x5, x6, x7,
+ x8, x9, x10, x11, x12, x13, x14, x15,
+ x16, x17, x18, x19, x20, x21, x22, x23,
+ x24, x25, x26, x27, x28, x29, x30, x31,
+
+ pub fn dwarfLocOp(reg: RawRegister) u8 {
+ return @enumToInt(reg) + DW.OP_reg0;
+ }
+};
+
+pub const Register = enum(u5) {
+ // 64 bit registers
+ zero, // zero
+ ra, // return address. caller saved
+ sp, // stack pointer. callee saved.
+ gp, // global pointer
+ tp, // thread pointer
+ t0, t1, t2, // temporaries. caller saved.
+ s0, // s0/fp, callee saved.
+ s1, // callee saved.
+ a0, a1, // fn args/return values. caller saved.
+ a2, a3, a4, a5, a6, a7, // fn args. caller saved.
+ s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, // saved registers. callee saved.
+ t3, t4, t5, t6, // caller saved
+
+ pub fn parseRegName(name: []const u8) ?Register {
+ if(std.meta.stringToEnum(Register, name)) |reg| return reg;
+ if(std.meta.stringToEnum(RawRegister, name)) |rawreg| return @intToEnum(Register, @enumToInt(rawreg));
+ return null;
+ }
+
+ /// Returns the index into `callee_preserved_regs`.
+ pub fn allocIndex(self: Register) ?u4 {
+ inline for(callee_preserved_regs) |cpreg, i| {
+ if(self == cpreg) return i;
+ }
+ return null;
+ }
+
+ pub fn dwarfLocOp(reg: Register) u8 {
+ return @as(u8, @enumToInt(reg)) + DW.OP_reg0;
+ }
+};
+
+// zig fmt: on
+
+pub const callee_preserved_regs = [_]Register{
+ .s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11,
+};
diff --git a/src/codegen/spu-mk2.zig b/src/codegen/spu-mk2.zig
new file mode 100644
index 0000000000..542862caca
--- /dev/null
+++ b/src/codegen/spu-mk2.zig
@@ -0,0 +1,170 @@
+const std = @import("std");
+
+pub const Interpreter = @import("spu-mk2/interpreter.zig").Interpreter;
+
+pub const ExecutionCondition = enum(u3) {
+ always = 0,
+ when_zero = 1,
+ not_zero = 2,
+ greater_zero = 3,
+ less_than_zero = 4,
+ greater_or_equal_zero = 5,
+ less_or_equal_zero = 6,
+ overflow = 7,
+};
+
+pub const InputBehaviour = enum(u2) {
+ zero = 0,
+ immediate = 1,
+ peek = 2,
+ pop = 3,
+};
+
+pub const OutputBehaviour = enum(u2) {
+ discard = 0,
+ push = 1,
+ jump = 2,
+ jump_relative = 3,
+};
+
+pub const Command = enum(u5) {
+ copy = 0,
+ ipget = 1,
+ get = 2,
+ set = 3,
+ store8 = 4,
+ store16 = 5,
+ load8 = 6,
+ load16 = 7,
+ undefined0 = 8,
+ undefined1 = 9,
+ frget = 10,
+ frset = 11,
+ bpget = 12,
+ bpset = 13,
+ spget = 14,
+ spset = 15,
+ add = 16,
+ sub = 17,
+ mul = 18,
+ div = 19,
+ mod = 20,
+ @"and" = 21,
+ @"or" = 22,
+ xor = 23,
+ not = 24,
+ signext = 25,
+ rol = 26,
+ ror = 27,
+ bswap = 28,
+ asr = 29,
+ lsl = 30,
+ lsr = 31,
+};
+
+pub const Instruction = packed struct {
+ condition: ExecutionCondition,
+ input0: InputBehaviour,
+ input1: InputBehaviour,
+ modify_flags: bool,
+ output: OutputBehaviour,
+ command: Command,
+ reserved: u1 = 0,
+
+ pub fn format(instr: Instruction, comptime fmt: []const u8, options: std.fmt.FormatOptions, out: anytype) !void {
+ try std.fmt.format(out, "0x{x:0<4} ", .{@bitCast(u16, instr)});
+ try out.writeAll(switch (instr.condition) {
+ .always => " ",
+ .when_zero => "== 0",
+ .not_zero => "!= 0",
+ .greater_zero => " > 0",
+ .less_than_zero => " < 0",
+ .greater_or_equal_zero => ">= 0",
+ .less_or_equal_zero => "<= 0",
+ .overflow => "ovfl",
+ });
+ try out.writeAll(" ");
+ try out.writeAll(switch (instr.input0) {
+ .zero => "zero",
+ .immediate => "imm ",
+ .peek => "peek",
+ .pop => "pop ",
+ });
+ try out.writeAll(" ");
+ try out.writeAll(switch (instr.input1) {
+ .zero => "zero",
+ .immediate => "imm ",
+ .peek => "peek",
+ .pop => "pop ",
+ });
+ try out.writeAll(" ");
+ try out.writeAll(switch (instr.command) {
+ .copy => "copy ",
+ .ipget => "ipget ",
+ .get => "get ",
+ .set => "set ",
+ .store8 => "store8 ",
+ .store16 => "store16 ",
+ .load8 => "load8 ",
+ .load16 => "load16 ",
+ .undefined0 => "undefined",
+ .undefined1 => "undefined",
+ .frget => "frget ",
+ .frset => "frset ",
+ .bpget => "bpget ",
+ .bpset => "bpset ",
+ .spget => "spget ",
+ .spset => "spset ",
+ .add => "add ",
+ .sub => "sub ",
+ .mul => "mul ",
+ .div => "div ",
+ .mod => "mod ",
+ .@"and" => "and ",
+ .@"or" => "or ",
+ .xor => "xor ",
+ .not => "not ",
+ .signext => "signext ",
+ .rol => "rol ",
+ .ror => "ror ",
+ .bswap => "bswap ",
+ .asr => "asr ",
+ .lsl => "lsl ",
+ .lsr => "lsr ",
+ });
+ try out.writeAll(" ");
+ try out.writeAll(switch (instr.output) {
+ .discard => "discard",
+ .push => "push ",
+ .jump => "jmp ",
+ .jump_relative => "rjmp ",
+ });
+ try out.writeAll(" ");
+ try out.writeAll(if (instr.modify_flags)
+ "+ flags"
+ else
+ " ");
+ }
+};
+
+pub const FlagRegister = packed struct {
+ zero: bool,
+ negative: bool,
+ carry: bool,
+ carry_enabled: bool,
+ interrupt0_enabled: bool,
+ interrupt1_enabled: bool,
+ interrupt2_enabled: bool,
+ interrupt3_enabled: bool,
+ reserved: u8 = 0,
+};
+
+pub const Register = enum {
+ dummy,
+
+ pub fn allocIndex(self: Register) ?u4 {
+ return null;
+ }
+};
+
+pub const callee_preserved_regs = [_]Register{};
diff --git a/src/codegen/spu-mk2/interpreter.zig b/src/codegen/spu-mk2/interpreter.zig
new file mode 100644
index 0000000000..1ec99546c6
--- /dev/null
+++ b/src/codegen/spu-mk2/interpreter.zig
@@ -0,0 +1,166 @@
+const std = @import("std");
+const log = std.log.scoped(.SPU_2_Interpreter);
+const spu = @import("../spu-mk2.zig");
+const FlagRegister = spu.FlagRegister;
+const Instruction = spu.Instruction;
+const ExecutionCondition = spu.ExecutionCondition;
+
+pub fn Interpreter(comptime Bus: type) type {
+ return struct {
+ ip: u16 = 0,
+ sp: u16 = undefined,
+ bp: u16 = undefined,
+ fr: FlagRegister = @bitCast(FlagRegister, @as(u16, 0)),
+ /// This is set to true when we hit an undefined0 instruction, allowing it to
+ /// be used as a trap for testing purposes
+ undefined0: bool = false,
+ /// This is set to true when we hit an undefined1 instruction, allowing it to
+ /// be used as a trap for testing purposes. undefined1 is used as a breakpoint.
+ undefined1: bool = false,
+ bus: Bus,
+
+ pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void {
+ var count: usize = 0;
+ while (size == null or count < size.?) {
+ count += 1;
+ var instruction = @bitCast(Instruction, self.bus.read16(self.ip));
+
+ log.debug("Executing {}\n", .{instruction});
+
+ self.ip +%= 2;
+
+ const execute = switch (instruction.condition) {
+ .always => true,
+ .not_zero => !self.fr.zero,
+ .when_zero => self.fr.zero,
+ .overflow => self.fr.carry,
+ ExecutionCondition.greater_or_equal_zero => !self.fr.negative,
+ else => return error.Unimplemented,
+ };
+
+ if (execute) {
+ const val0 = switch (instruction.input0) {
+ .zero => @as(u16, 0),
+ .immediate => i: {
+ const val = self.bus.read16(@intCast(u16, self.ip));
+ self.ip +%= 2;
+ break :i val;
+ },
+ else => |e| e: {
+ // peek or pop; show value at current SP, and if pop, increment sp
+ const val = self.bus.read16(self.sp);
+ if (e == .pop) {
+ self.sp +%= 2;
+ }
+ break :e val;
+ },
+ };
+ const val1 = switch (instruction.input1) {
+ .zero => @as(u16, 0),
+ .immediate => i: {
+ const val = self.bus.read16(@intCast(u16, self.ip));
+ self.ip +%= 2;
+ break :i val;
+ },
+ else => |e| e: {
+ // peek or pop; show value at current SP, and if pop, increment sp
+ const val = self.bus.read16(self.sp);
+ if (e == .pop) {
+ self.sp +%= 2;
+ }
+ break :e val;
+ },
+ };
+
+ const output: u16 = switch (instruction.command) {
+ .get => self.bus.read16(self.bp +% (2 *% val0)),
+ .set => a: {
+ self.bus.write16(self.bp +% 2 *% val0, val1);
+ break :a val1;
+ },
+ .load8 => self.bus.read8(val0),
+ .load16 => self.bus.read16(val0),
+ .store8 => a: {
+ const val = @truncate(u8, val1);
+ self.bus.write8(val0, val);
+ break :a val;
+ },
+ .store16 => a: {
+ self.bus.write16(val0, val1);
+ break :a val1;
+ },
+ .copy => val0,
+ .add => a: {
+ var val: u16 = undefined;
+ self.fr.carry = @addWithOverflow(u16, val0, val1, &val);
+ break :a val;
+ },
+ .sub => a: {
+ var val: u16 = undefined;
+ self.fr.carry = @subWithOverflow(u16, val0, val1, &val);
+ break :a val;
+ },
+ .spset => a: {
+ self.sp = val0;
+ break :a val0;
+ },
+ .bpset => a: {
+ self.bp = val0;
+ break :a val0;
+ },
+ .frset => a: {
+ const val = (@bitCast(u16, self.fr) & val1) | (val0 & ~val1);
+ self.fr = @bitCast(FlagRegister, val);
+ break :a val;
+ },
+ .bswap => (val0 >> 8) | (val0 << 8),
+ .bpget => self.bp,
+ .spget => self.sp,
+ .ipget => self.ip +% (2 *% val0),
+ .lsl => val0 << 1,
+ .lsr => val0 >> 1,
+ .@"and" => val0 & val1,
+ .@"or" => val0 | val1,
+ .xor => val0 ^ val1,
+ .not => ~val0,
+ .undefined0 => {
+ self.undefined0 = true;
+ // Break out of the loop, and let the caller decide what to do
+ return;
+ },
+ .undefined1 => {
+ self.undefined1 = true;
+ // Break out of the loop, and let the caller decide what to do
+ return;
+ },
+ .signext => if ((val0 & 0x80) != 0)
+ (val0 & 0xFF) | 0xFF00
+ else
+ (val0 & 0xFF),
+ else => return error.Unimplemented,
+ };
+
+ switch (instruction.output) {
+ .discard => {},
+ .push => {
+ self.sp -%= 2;
+ self.bus.write16(self.sp, output);
+ },
+ .jump => {
+ self.ip = output;
+ },
+ else => return error.Unimplemented,
+ }
+ if (instruction.modify_flags) {
+ self.fr.negative = (output & 0x8000) != 0;
+ self.fr.zero = (output == 0x0000);
+ }
+ } else {
+ if (instruction.input0 == .immediate) self.ip +%= 2;
+ if (instruction.input1 == .immediate) self.ip +%= 2;
+ break;
+ }
+ }
+ }
+ };
+}
diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig
new file mode 100644
index 0000000000..4ea8838409
--- /dev/null
+++ b/src/codegen/wasm.zig
@@ -0,0 +1,142 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const ArrayList = std.ArrayList;
+const assert = std.debug.assert;
+const leb = std.debug.leb;
+const mem = std.mem;
+
+const Module = @import("../Module.zig");
+const Decl = Module.Decl;
+const Inst = @import("../ir.zig").Inst;
+const Type = @import("../type.zig").Type;
+const Value = @import("../value.zig").Value;
+
+fn genValtype(ty: Type) u8 {
+ return switch (ty.tag()) {
+ .u32, .i32 => 0x7F,
+ .u64, .i64 => 0x7E,
+ .f32 => 0x7D,
+ .f64 => 0x7C,
+ else => @panic("TODO: Implement more types for wasm."),
+ };
+}
+
+pub fn genFunctype(buf: *ArrayList(u8), decl: *Decl) !void {
+ const ty = decl.typed_value.most_recent.typed_value.ty;
+ const writer = buf.writer();
+
+ // functype magic
+ try writer.writeByte(0x60);
+
+ // param types
+ try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
+ if (ty.fnParamLen() != 0) {
+ const params = try buf.allocator.alloc(Type, ty.fnParamLen());
+ defer buf.allocator.free(params);
+ ty.fnParamTypes(params);
+ for (params) |param_type| try writer.writeByte(genValtype(param_type));
+ }
+
+ // return type
+ const return_type = ty.fnReturnType();
+ switch (return_type.tag()) {
+ .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),
+ else => {
+ try leb.writeULEB128(writer, @as(u32, 1));
+ try writer.writeByte(genValtype(return_type));
+ },
+ }
+}
+
+pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void {
+ assert(buf.items.len == 0);
+ const writer = buf.writer();
+
+ // Reserve space to write the size after generating the code
+ try buf.resize(5);
+
+ // Write the size of the locals vec
+ // TODO: implement locals
+ try leb.writeULEB128(writer, @as(u32, 0));
+
+ // Write instructions
+ // TODO: check for and handle death of instructions
+ const tv = decl.typed_value.most_recent.typed_value;
+ const mod_fn = tv.val.cast(Value.Payload.Function).?.func;
+ for (mod_fn.analysis.success.instructions) |inst| try genInst(buf, decl, inst);
+
+ // Write 'end' opcode
+ try writer.writeByte(0x0B);
+
+ // Fill in the size of the generated code to the reserved space at the
+ // beginning of the buffer.
+ const size = buf.items.len - 5 + decl.fn_link.wasm.?.idx_refs.items.len * 5;
+ leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, size));
+}
+
+fn genInst(buf: *ArrayList(u8), decl: *Decl, inst: *Inst) !void {
+ return switch (inst.tag) {
+ .call => genCall(buf, decl, inst.castTag(.call).?),
+ .constant => genConstant(buf, decl, inst.castTag(.constant).?),
+ .dbg_stmt => {},
+ .ret => genRet(buf, decl, inst.castTag(.ret).?),
+ .retvoid => {},
+ else => error.TODOImplementMoreWasmCodegen,
+ };
+}
+
+fn genConstant(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Constant) !void {
+ const writer = buf.writer();
+ switch (inst.base.ty.tag()) {
+ .u32 => {
+ try writer.writeByte(0x41); // i32.const
+ try leb.writeILEB128(writer, inst.val.toUnsignedInt());
+ },
+ .i32 => {
+ try writer.writeByte(0x41); // i32.const
+ try leb.writeILEB128(writer, inst.val.toSignedInt());
+ },
+ .u64 => {
+ try writer.writeByte(0x42); // i64.const
+ try leb.writeILEB128(writer, inst.val.toUnsignedInt());
+ },
+ .i64 => {
+ try writer.writeByte(0x42); // i64.const
+ try leb.writeILEB128(writer, inst.val.toSignedInt());
+ },
+ .f32 => {
+ try writer.writeByte(0x43); // f32.const
+ // TODO: enforce LE byte order
+ try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
+ },
+ .f64 => {
+ try writer.writeByte(0x44); // f64.const
+ // TODO: enforce LE byte order
+ try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
+ },
+ .void => {},
+ else => return error.TODOImplementMoreWasmCodegen,
+ }
+}
+
+fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void {
+ try genInst(buf, decl, inst.operand);
+}
+
+fn genCall(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Call) !void {
+ const func_inst = inst.func.castTag(.constant).?;
+ const func_val = func_inst.val.cast(Value.Payload.Function).?;
+ const target = func_val.func.owner_decl;
+ const target_ty = target.typed_value.most_recent.typed_value.ty;
+
+ if (inst.args.len != 0) return error.TODOImplementMoreWasmCodegen;
+
+ try buf.append(0x10); // call
+
+ // The function index immediate argument will be filled in using this data
+ // in link.Wasm.flush().
+ try decl.fn_link.wasm.?.idx_refs.append(buf.allocator, .{
+ .offset = @intCast(u32, buf.items.len),
+ .decl = target,
+ });
+}
diff --git a/src/codegen/x86.zig b/src/codegen/x86.zig
new file mode 100644
index 0000000000..fdad4e56db
--- /dev/null
+++ b/src/codegen/x86.zig
@@ -0,0 +1,123 @@
+const std = @import("std");
+const DW = std.dwarf;
+
+// zig fmt: off
+pub const Register = enum(u8) {
+ // 0 through 7, 32-bit registers. id is int value
+ eax, ecx, edx, ebx, esp, ebp, esi, edi,
+
+ // 8-15, 16-bit registers. id is int value - 8.
+ ax, cx, dx, bx, sp, bp, si, di,
+
+ // 16-23, 8-bit registers. id is int value - 16.
+ al, cl, dl, bl, ah, ch, dh, bh,
+
+ /// Returns the bit-width of the register.
+ pub fn size(self: @This()) u7 {
+ return switch (@enumToInt(self)) {
+ 0...7 => 32,
+ 8...15 => 16,
+ 16...23 => 8,
+ else => unreachable,
+ };
+ }
+
+ /// Returns the register's id. This is used in practically every opcode the
+ /// x86 has. It is embedded in some instructions, such as the `B8 +rd` move
+ /// instruction, and is used in the R/M byte.
+ pub fn id(self: @This()) u3 {
+ return @truncate(u3, @enumToInt(self));
+ }
+
+ /// Returns the index into `callee_preserved_regs`.
+ pub fn allocIndex(self: Register) ?u4 {
+ return switch (self) {
+ .eax, .ax, .al => 0,
+ .ecx, .cx, .cl => 1,
+ .edx, .dx, .dl => 2,
+ .esi, .si => 3,
+ .edi, .di => 4,
+ else => null,
+ };
+ }
+
+ /// Convert from any register to its 32 bit alias.
+ pub fn to32(self: Register) Register {
+ return @intToEnum(Register, @as(u8, self.id()));
+ }
+
+ /// Convert from any register to its 16 bit alias.
+ pub fn to16(self: Register) Register {
+ return @intToEnum(Register, @as(u8, self.id()) + 8);
+ }
+
+ /// Convert from any register to its 8 bit alias.
+ pub fn to8(self: Register) Register {
+ return @intToEnum(Register, @as(u8, self.id()) + 16);
+ }
+
+
+ pub fn dwarfLocOp(reg: Register) u8 {
+ return switch (reg.to32()) {
+ .eax => DW.OP_reg0,
+ .ecx => DW.OP_reg1,
+ .edx => DW.OP_reg2,
+ .ebx => DW.OP_reg3,
+ .esp => DW.OP_reg4,
+ .ebp => DW.OP_reg5,
+ .esi => DW.OP_reg6,
+ .edi => DW.OP_reg7,
+ else => unreachable,
+ };
+ }
+};
+
+// zig fmt: on
+
+pub const callee_preserved_regs = [_]Register{ .eax, .ecx, .edx, .esi, .edi };
+
+// TODO add these to Register enum and corresponding dwarfLocOp
+// // Return Address register. This is stored in `0(%esp, "")` and is not a physical register.
+// RA = (8, "RA"),
+//
+// ST0 = (11, "st0"),
+// ST1 = (12, "st1"),
+// ST2 = (13, "st2"),
+// ST3 = (14, "st3"),
+// ST4 = (15, "st4"),
+// ST5 = (16, "st5"),
+// ST6 = (17, "st6"),
+// ST7 = (18, "st7"),
+//
+// XMM0 = (21, "xmm0"),
+// XMM1 = (22, "xmm1"),
+// XMM2 = (23, "xmm2"),
+// XMM3 = (24, "xmm3"),
+// XMM4 = (25, "xmm4"),
+// XMM5 = (26, "xmm5"),
+// XMM6 = (27, "xmm6"),
+// XMM7 = (28, "xmm7"),
+//
+// MM0 = (29, "mm0"),
+// MM1 = (30, "mm1"),
+// MM2 = (31, "mm2"),
+// MM3 = (32, "mm3"),
+// MM4 = (33, "mm4"),
+// MM5 = (34, "mm5"),
+// MM6 = (35, "mm6"),
+// MM7 = (36, "mm7"),
+//
+// MXCSR = (39, "mxcsr"),
+//
+// ES = (40, "es"),
+// CS = (41, "cs"),
+// SS = (42, "ss"),
+// DS = (43, "ds"),
+// FS = (44, "fs"),
+// GS = (45, "gs"),
+//
+// TR = (48, "tr"),
+// LDTR = (49, "ldtr"),
+//
+// FS_BASE = (93, "fs.base"),
+// GS_BASE = (94, "gs.base"),
diff --git a/src/codegen/x86_64.zig b/src/codegen/x86_64.zig
new file mode 100644
index 0000000000..dea39f82cd
--- /dev/null
+++ b/src/codegen/x86_64.zig
@@ -0,0 +1,220 @@
+const std = @import("std");
+const Type = @import("../Type.zig");
+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));
+ }
+
+ /// 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 };
+
+// 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"),