Commit 52ae2b10aa
Changed files (3)
src-self-hosted
codegen
test
stage2
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)