diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 7c2d8ca89..9c63125da 100644 --- a/src-self-hosted/codegen.zig +++ b/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, diff --git a/src-self-hosted/codegen/riscv64.zig b/src-self-hosted/codegen/riscv64.zig new file mode 100644 index 000000000..b6b4e099f --- /dev/null +++ b/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, +}; diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index bf6a01f48..b461e2114 100644 --- a/test/stage2/compare_output.zig +++ b/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)