Commit 52ae2b10aa

pfg <pfg@pfg.pw>
2020-08-04 07:38:01
stage2: starting on a riscv64 backend
1 parent 952a397
Changed files (3)
src-self-hosted
test
src-self-hosted/codegen/riscv64.zig
@@ -0,0 +1,68 @@
+pub const Instructions = struct {
+    pub const CallBreak = packed struct {
+        pub const Mode = packed enum(u12) { ecall, ebreak };
+        opcode: u7 = 0b1110011,
+        unused1: u5 = 0,
+        unused2: u3 = 0,
+        unused3: u5 = 0,
+        mode: u12,
+    };
+    pub const Addi = packed struct {
+        pub const Mode = packed enum(u3) { addi = 0b000, slti = 0b010, sltiu = 0b011, xori = 0b100, ori = 0b110, andi = 0b111 };
+        opcode: u7 = 0b0010011,
+        rd: u5,
+        mode: u3,
+        rsi1: u5,
+        imm: u11,
+        signextend: u1 = 0,
+    };
+};
+
+// zig fmt: off
+pub const Register = enum(u8) {
+    // 64 bit registers
+    zero = 0, // zero
+    ra = 1, // return address. caller saved
+    sp = 2, // stack pointer. callee saved.
+    gp = 3, // global pointer
+    tp = 4, // thread pointer
+    t0 = 5, t1 = 6, t2 = 7, // temporaries. caller saved.
+    s0 = 8, // 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
+
+    /// Returns the bit-width of the register.
+    pub fn size(self: @This()) u7 {
+        return switch (@enumToInt(self)) {
+            0...31 => 64,
+            else => unreachable,
+        };
+    }
+    
+    pub fn to64(self: @This()) Register {
+        return self;
+    }
+
+    /// Returns the register's id. This is used in practically every opcode the
+    /// riscv64 has.
+    pub fn id(self: @This()) u5 {
+        return @truncate(u5, @enumToInt(self));
+    }
+
+    /// 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;
+    }
+};
+
+// zig fmt: on
+
+pub const callee_preserved_regs = [_]Register{
+    .s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11,
+};
src-self-hosted/codegen.zig
@@ -78,7 +78,7 @@ pub fn generateSymbol(
                 //.r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code, dbg_line),
                 //.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code, dbg_line),
                 //.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
-                //.riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+                .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
                 //.sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code, dbg_line),
                 //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line),
                 //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line),
@@ -1023,6 +1023,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .i386, .x86_64 => {
                     try self.code.append(0xcc); // int3
                 },
+                .riscv64 => {
+                    const full = @bitCast(u32, Instructions.CallBreak{
+                        .mode = @enumToInt(Instructions.CallBreak.Mode.ebreak),
+                    });
+
+                    try self.code.resize(self.code.items.len + 4);
+                    mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], full);
+                },
                 else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}),
             }
             return .none;
@@ -1325,36 +1333,73 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         fn genAsm(self: *Self, inst: *ir.Inst.Assembly) !MCValue {
             if (!inst.is_volatile and inst.base.isUnused())
                 return MCValue.dead;
-            if (arch != .x86_64 and arch != .i386) {
-                return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{});
-            }
-            for (inst.inputs) |input, i| {
-                if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
-                    return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input});
-                }
-                const reg_name = input[1 .. input.len - 1];
-                const reg = parseRegName(reg_name) orelse
-                    return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
-                const arg = try self.resolveInst(inst.args[i]);
-                try self.genSetReg(inst.base.src, reg, arg);
-            }
+            switch (arch) {
+                .riscv64 => {
+                    for (inst.inputs) |input, i| {
+                        if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
+                            return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input});
+                        }
+                        const reg_name = input[1 .. input.len - 1];
+                        const reg = parseRegName(reg_name) orelse
+                            return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
+                        const arg = try self.resolveInst(inst.args[i]);
+                        try self.genSetReg(inst.base.src, reg, arg);
+                    }
 
-            if (mem.eql(u8, inst.asm_source, "syscall")) {
-                try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 });
-            } else {
-                return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{});
-            }
+                    if (mem.eql(u8, inst.asm_source, "ecall")) {
+                        const full = @bitCast(u32, Instructions.CallBreak{
+                            .mode = @enumToInt(Instructions.CallBreak.Mode.ecall),
+                        });
 
-            if (inst.output) |output| {
-                if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
-                    return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output});
-                }
-                const reg_name = output[2 .. output.len - 1];
-                const reg = parseRegName(reg_name) orelse
-                    return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
-                return MCValue{ .register = reg };
-            } else {
-                return MCValue.none;
+                        try self.code.resize(self.code.items.len + 4);
+                        mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], full);
+                    } else {
+                        return self.fail(inst.base.src, "TODO implement support for more riscv64 assembly instructions", .{});
+                    }
+
+                    if (inst.output) |output| {
+                        if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
+                            return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output});
+                        }
+                        const reg_name = output[2 .. output.len - 1];
+                        const reg = parseRegName(reg_name) orelse
+                            return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
+                        return MCValue{ .register = reg };
+                    } else {
+                        return MCValue.none;
+                    }
+                },
+                .x86_64, .i386 => {
+                    for (inst.inputs) |input, i| {
+                        if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
+                            return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input});
+                        }
+                        const reg_name = input[1 .. input.len - 1];
+                        const reg = parseRegName(reg_name) orelse
+                            return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
+                        const arg = try self.resolveInst(inst.args[i]);
+                        try self.genSetReg(inst.base.src, reg, arg);
+                    }
+
+                    if (mem.eql(u8, inst.asm_source, "syscall")) {
+                        try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 });
+                    } else {
+                        return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{});
+                    }
+
+                    if (inst.output) |output| {
+                        if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
+                            return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output});
+                        }
+                        const reg_name = output[2 .. output.len - 1];
+                        const reg = parseRegName(reg_name) orelse
+                            return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
+                        return MCValue{ .register = reg };
+                    } else {
+                        return MCValue.none;
+                    }
+                },
+                else => return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{}),
             }
         }
 
@@ -1500,6 +1545,31 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
         fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) InnerError!void {
             switch (arch) {
+                .riscv64 => switch (mcv) {
+                    .immediate => |x| {
+                        if (x > math.maxInt(u11)) {
+                            return self.fail(src, "TODO genSetReg 12+ bit immediates for riscv64", .{});
+                        }
+                        const Instruction = packed struct {
+                            opcode: u7,
+                            rd: u5,
+                            mode: u3,
+                            rsi1: u5,
+                            imm: u11,
+                            signextend: u1 = 0,
+                        };
+                        const full = @bitCast(u32, Instructions.Addi{
+                            .imm = @intCast(u11, x),
+                            .rsi1 = Register.zero.id(),
+                            .mode = @enumToInt(Instructions.Addi.Mode.addi),
+                            .rd = reg.id(),
+                        });
+
+                        try self.code.resize(self.code.items.len + 4);
+                        mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], full);
+                    },
+                    else => return self.fail(src, "TODO implement getSetReg for riscv64 MCValue {}", .{mcv}),
+                },
                 .x86_64 => switch (mcv) {
                     .dead => unreachable,
                     .ptr_stack_offset => unreachable,
@@ -1873,7 +1943,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         else => return self.fail(src, "TODO implement function parameters for {}", .{cc}),
                     }
                 },
-                else => return self.fail(src, "TODO implement codegen parameters for {}", .{self.target.cpu.arch}),
+                else => if (param_types.len != 0)
+                    return self.fail(src, "TODO implement codegen parameters for {}", .{self.target.cpu.arch}),
             }
 
             if (ret_ty.zigTypeTag() == .NoReturn) {
@@ -1915,6 +1986,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         usingnamespace switch (arch) {
             .i386 => @import("codegen/x86.zig"),
             .x86_64 => @import("codegen/x86_64.zig"),
+            .riscv64 => @import("codegen/riscv64.zig"),
             else => struct {
                 pub const Register = enum {
                     dummy,
test/stage2/compare_output.zig
@@ -7,6 +7,11 @@ const linux_x64 = std.zig.CrossTarget{
     .os_tag = .linux,
 };
 
+const riscv64 = std.zig.CrossTarget{
+    .cpu_arch = .riscv64,
+    .os_tag = .linux,
+};
+
 pub fn addCases(ctx: *TestContext) !void {
     if (std.Target.current.os.tag != .linux or
         std.Target.current.cpu.arch != .x86_64)