diff --git a/src/codegen.zig b/src/codegen.zig index a1d3cc2fc4..9bb53c5cdc 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1461,7 +1461,35 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } }, .arm => { - if (info.args.len > 0) return self.fail(inst.base.src, "TODO implement fn args for {}", .{self.target.cpu.arch}); + for (info.args) |mc_arg, arg_i| { + const arg = inst.args[arg_i]; + const arg_mcv = try self.resolveInst(inst.args[arg_i]); + + switch (mc_arg) { + .none => continue, + .undef => unreachable, + .immediate => unreachable, + .unreach => unreachable, + .dead => unreachable, + .embedded_in_code => unreachable, + .memory => unreachable, + .compare_flags_signed => unreachable, + .compare_flags_unsigned => unreachable, + .register => |reg| { + try self.genSetReg(arg.src, reg, arg_mcv); + // TODO interact with the register allocator to mark the instruction as moved. + }, + .stack_offset => { + return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); + }, + .ptr_stack_offset => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{}); + }, + .ptr_embedded_in_code => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); + }, + } + } if (inst.func.cast(ir.Inst.Constant)) |func_inst| { if (func_inst.val.cast(Value.Payload.Function)) |func_val| { @@ -1482,7 +1510,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // of course. Add pushing lr to stack // and popping after call try self.genSetReg(inst.base.src, .lr, .{ .memory = got_addr }); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.blx(.al, .lr).toU32()); + + if (Target.arm.featureSetHas(self.target.cpu.features, .has_v5t)) { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.blx(.al, .lr).toU32()); + } else { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, .lr, Instruction.Operand.reg(.pc, Instruction.Operand.Shift.none)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bx(.al, .lr).toU32()); + } } else { return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); } @@ -2213,14 +2247,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // least amount of necessary instructions (use // more intelligent rotating) if (x <= math.maxInt(u8)) { - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); return; } else if (x <= math.maxInt(u16)) { // TODO Use movw Note: Not supported on // all ARM targets! - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); } else if (x <= math.maxInt(u32)) { // TODO Use movw and movt Note: Not // supported on all ARM targets! Also TODO @@ -2232,15 +2266,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // orr reg, reg, #0xbb, 24 // orr reg, reg, #0xcc, 16 // orr reg, reg, #0xdd, 8 - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 16), 8)).toU32()); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 24), 4)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 16), 8)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 24), 4)).toU32()); return; } else { return self.fail(src, "ARM registers are 32-bit wide", .{}); } }, + .register => |src_reg| { + // If the registers are the same, nothing to do. + if (src_reg.id() == reg.id()) + return; + + // mov reg, src_reg + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, Instruction.Operand.reg(src_reg, Instruction.Operand.Shift.none)).toU32()); + }, .memory => |addr| { // The value is in memory at a hard-coded address. // If the type is a pointer, it means the pointer address is at this memory location. @@ -2701,6 +2743,53 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { else => return self.fail(src, "TODO implement function parameters for {} on x86_64", .{cc}), } }, + .arm => { + switch (cc) { + .Naked => { + assert(result.args.len == 0); + result.return_value = .{ .unreach = {} }; + result.stack_byte_count = 0; + result.stack_align = 1; + return result; + }, + .Unspecified, .C => { + // ARM Procedure Call Standard, Chapter 6.5 + var ncrn: usize = 0; // Next Core Register Number + var nsaa: u32 = 0; // Next stacked argument address + + for (param_types) |ty, i| { + if (ty.abiAlignment(self.target.*) == 8) { + // Round up NCRN to the next even number + ncrn += ncrn % 2; + } + + const param_size = @intCast(u32, ty.abiSize(self.target.*)); + if (std.math.divCeil(u32, param_size, 4) catch unreachable <= 4 - ncrn) { + if (param_size <= 4) { + result.args[i] = .{ .register = c_abi_int_param_regs[ncrn] }; + ncrn += 1; + } else { + return self.fail(src, "TODO MCValues with multiple registers", .{}); + } + } else { + ncrn = 4; + if (ty.abiAlignment(self.target.*) == 8) { + if (nsaa % 8 != 0) { + nsaa += 8 - (nsaa % 8); + } + } + + result.args[i] = .{ .stack_offset = nsaa }; + nsaa += param_size; + } + } + + result.stack_byte_count = nsaa; + result.stack_align = 4; + }, + else => return self.fail(src, "TODO implement function parameters for {} on arm", .{cc}), + } + }, else => if (param_types.len != 0) return self.fail(src, "TODO implement codegen parameters for {}", .{self.target.cpu.arch}), } @@ -2719,6 +2808,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, else => return self.fail(src, "TODO implement function return values for {}", .{cc}), }, + .arm => switch (cc) { + .Naked => unreachable, + .Unspecified, .C => { + const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); + if (ret_ty_size <= 4) { + result.return_value = .{ .register = c_abi_int_return_regs[0] }; + } else { + return self.fail(src, "TODO support more return types for ARM backend", .{}); + } + }, + else => return self.fail(src, "TODO implement function return values for {}", .{cc}), + }, else => return self.fail(src, "TODO implement codegen return values for {}", .{self.target.cpu.arch}), } return result; diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig index 05178ea7d3..096012a1e8 100644 --- a/src/codegen/arm.zig +++ b/src/codegen/arm.zig @@ -113,6 +113,13 @@ test "Register.id" { testing.expectEqual(@as(u4, 15), Register.pc.id()); } +/// Program status registers containing flags, mode bits and other +/// vital information +pub const Psr = enum { + cpsr, + spsr, +}; + 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 }; @@ -135,15 +142,26 @@ pub const Instruction = union(enum) { offset: u12, rd: u4, rn: u4, - l: u1, - w: u1, - b: u1, - u: u1, - p: u1, - i: u1, + load_store: u1, + write_back: u1, + byte_word: u1, + up_down: u1, + pre_post: u1, + imm: u1, fixed: u2 = 0b01, cond: u4, }, + BlockDataTransfer: packed struct { + register_list: u16, + rn: u4, + load_store: u1, + write_back: u1, + psr_or_user: u1, + up_down: u1, + pre_post: u1, + fixed: u3 = 0b100, + cond: u4, + }, Branch: packed struct { offset: u24, link: u1, @@ -235,14 +253,14 @@ pub const Instruction = union(enum) { rs: u4, }, - const Type = enum(u2) { - LogicalLeft, - LogicalRight, - ArithmeticRight, - RotateRight, + pub const Type = enum(u2) { + logical_left, + logical_right, + arithmetic_right, + rotate_right, }; - const none = Shift{ + pub const none = Shift{ .Immediate = .{ .amount = 0, .typ = 0, @@ -338,10 +356,32 @@ pub const Instruction = union(enum) { } }; + /// Represents the register list operand to a block data transfer + /// instruction + pub const RegisterList = packed struct { + r0: bool = false, + r1: bool = false, + r2: bool = false, + r3: bool = false, + r4: bool = false, + r5: bool = false, + r6: bool = false, + r7: bool = false, + r8: bool = false, + r9: bool = false, + r10: bool = false, + r11: bool = false, + r12: bool = false, + r13: bool = false, + r14: bool = false, + r15: bool = false, + }; + pub fn toU32(self: Instruction) u32 { return switch (self) { .DataProcessing => |v| @bitCast(u32, v), .SingleDataTransfer => |v| @bitCast(u32, v), + .BlockDataTransfer => |v| @bitCast(u32, v), .Branch => |v| @bitCast(u32, v), .BranchExchange => |v| @bitCast(u32, v), .SupervisorCall => |v| @bitCast(u32, v), @@ -380,7 +420,7 @@ pub const Instruction = union(enum) { pre_post: u1, up_down: u1, byte_word: u1, - writeback: u1, + write_back: u1, load_store: u1, ) Instruction { return Instruction{ @@ -389,12 +429,36 @@ pub const Instruction = union(enum) { .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, + .load_store = load_store, + .write_back = write_back, + .byte_word = byte_word, + .up_down = up_down, + .pre_post = pre_post, + .imm = if (offset == .Immediate) 0 else 1, + }, + }; + } + + fn blockDataTransfer( + cond: Condition, + rn: Register, + reg_list: RegisterList, + pre_post: u1, + up_down: u1, + psr_or_user: u1, + write_back: u1, + load_store: u1, + ) Instruction { + return Instruction{ + .BlockDataTransfer = .{ + .register_list = @bitCast(u16, reg_list), + .rn = rn.id(), + .load_store = load_store, + .write_back = write_back, + .psr_or_user = psr_or_user, + .up_down = up_down, + .pre_post = pre_post, + .cond = @enumToInt(cond), }, }; } @@ -442,36 +506,68 @@ pub const Instruction = union(enum) { // 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 @"and"(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .@"and", 0, 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 ands(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .@"and", 1, 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 eor(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .eor, 0, 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 eors(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .eor, 1, 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 sub(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sub, 0, 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 subs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sub, 1, 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 rsb(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsb, 0, 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 rsbs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsb, 1, rd, rn, op2); + } + + pub fn add(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .add, 0, rd, rn, op2); + } + + pub fn adds(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .add, 1, rd, rn, op2); + } + + pub fn adc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .adc, 0, rd, rn, op2); + } + + pub fn adcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .adc, 1, rd, rn, op2); + } + + pub fn sbc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sbc, 0, rd, rn, op2); + } + + pub fn sbcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sbc, 1, rd, rn, op2); + } + + pub fn rsc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsc, 0, rd, rn, op2); + } + + pub fn rscs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsc, 1, rd, rn, op2); } pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction { @@ -490,20 +586,42 @@ pub const Instruction = union(enum) { 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 orr(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .orr, 0, 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 orrs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .orr, 1, rd, rn, op2); } - pub fn bic(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .bic, s, rd, rn, op2); + pub fn mov(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mov, 0, rd, .r0, op2); } - pub fn mvn(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .mvn, s, rd, .r0, op2); + pub fn movs(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mov, 1, rd, .r0, op2); + } + + pub fn bic(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .bic, 0, rd, rn, op2); + } + + pub fn bics(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .bic, 1, rd, rn, op2); + } + + pub fn mvn(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mvn, 0, rd, .r0, op2); + } + + pub fn mvns(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mvn, 1, rd, .r0, op2); + } + + // PSR transfer + + pub fn mrs(cond: Condition, rd: Register, psr: Psr) Instruction { + return dataProcessing(cond, if (psr == .cpsr) .tst else .cmp, 0, rd, .r15, Operand.reg(.r0, Operand.Shift.none)); } // Single data transfer @@ -512,10 +630,28 @@ pub const Instruction = union(enum) { return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 1); } + pub fn ldrb(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction { + return singleDataTransfer(cond, rd, rn, offset, 1, 1, 1, 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); } + pub fn strb(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction { + return singleDataTransfer(cond, rd, rn, offset, 1, 1, 1, 0, 0); + } + + // Block data transfer + + pub fn ldm(cond: Condition, rn: Register, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, 0, 1); + } + + pub fn stm(cond: Condition, rn: Register, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, 0, 0); + } + // Branch pub fn b(cond: Condition, offset: i24) Instruction { @@ -559,17 +695,21 @@ test "serialize instructions" { const testcases = [_]Testcase{ .{ // add r0, r0, r0 - .inst = Instruction.add(.al, 0, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)), + .inst = Instruction.add(.al, .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)), + .inst = Instruction.mov(.al, .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)), + .inst = Instruction.mov(.al, .r0, Instruction.Operand.imm(42, 0)), .expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010, }, + .{ // mrs r5, cpsr + .inst = Instruction.mrs(.al, .r5, .cpsr), + .expected = 0b1110_00010_0_001111_0101_000000000000, + }, .{ // 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, @@ -598,6 +738,10 @@ test "serialize instructions" { .inst = Instruction.bkpt(42), .expected = 0b1110_0001_0010_000000000010_0111_1010, }, + .{ // stmfd r9, {r0} + .inst = Instruction.stm(.al, .r9, .{ .r0 = true }), + .expected = 0b1110_100_1_0_0_0_0_1001_0000000000000001, + }, }; for (testcases) |case| {