diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/codegen.zig | 170 | ||||
| -rw-r--r-- | src/codegen/arm.zig | 397 |
2 files changed, 496 insertions, 71 deletions
diff --git a/src/codegen.zig b/src/codegen.zig index 57d2f8ff1f..d50dcc3a23 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -570,6 +570,39 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.dbgSetEpilogueBegin(); } }, + .arm => { + const cc = self.fn_type.fnCallingConvention(); + if (cc != .Naked) { + // push {fp, lr} + // mov fp, sp + // sub sp, sp, #reloc + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.push(.al, .{ .fp, .lr }).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, .fp, Instruction.Operand.reg(.sp, Instruction.Operand.Shift.none)).toU32()); + // TODO: prepare stack for local variables + // const backpatch_reloc = try self.code.addManyAsArray(4); + + try self.dbgSetPrologueEnd(); + + try self.genBody(self.mod_fn.analysis.success); + + // Backpatch stack offset + // const stack_end = self.max_end_stack; + // const aligned_stack_end = mem.alignForward(stack_end, self.stack_align); + // mem.writeIntLittle(u32, backpatch_reloc, Instruction.sub(.al, .sp, .sp, Instruction.Operand.imm())); + + try self.dbgSetEpilogueBegin(); + + // mov sp, fp + // pop {fp, pc} + // TODO: return by jumping to this code, use relocations + // mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, .sp, Instruction.Operand.reg(.fp, Instruction.Operand.Shift.none)).toU32()); + // mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.pop(.al, .{ .fp, .pc }).toU32()); + } else { + try self.dbgSetPrologueEnd(); + try self.genBody(self.mod_fn.analysis.success); + try self.dbgSetEpilogueBegin(); + } + }, else => { try self.dbgSetPrologueEnd(); try self.genBody(self.mod_fn.analysis.success); @@ -1461,7 +1494,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| { @@ -1476,13 +1537,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { else unreachable; - // TODO only works with leaf functions - // at the moment, which works fine for - // Hello World, but not for real code - // 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()); + + // TODO: add Instruction.supportedOn + // function for ARM + 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", .{}); } @@ -1602,7 +1666,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.jalr(.zero, 0, .ra).toU32()); }, .arm => { - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bx(.al, .lr).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, .sp, Instruction.Operand.reg(.fp, Instruction.Operand.Shift.none)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.pop(.al, .{ .fp, .pc }).toU32()); + // TODO: jump to the end with relocation + // // Just add space for an instruction, patch this later + // try self.code.resize(self.code.items.len + 4); + // try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4); }, else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), } @@ -2214,14 +2283,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 @@ -2233,20 +2302,28 @@ 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. try self.genSetReg(src, reg, .{ .immediate = addr }); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.al, reg, reg, Instruction.Offset.none).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.al, reg, reg, .{ .offset = Instruction.Offset.none }).toU32()); }, else => return self.fail(src, "TODO implement getSetReg for arm {}", .{mcv}), }, @@ -2702,6 +2779,55 @@ 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 if (ncrn < 4 and nsaa == 0) { + return self.fail(src, "TODO MCValues split between registers and stack", .{}); + } 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}), } @@ -2720,6 +2846,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..36fb858022 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), @@ -362,7 +402,7 @@ pub const Instruction = union(enum) { return Instruction{ .DataProcessing = .{ .cond = @enumToInt(cond), - .i = if (op2 == .Immediate) 1 else 0, + .i = @boolToInt(op2 == .Immediate), .opcode = @enumToInt(opcode), .s = s, .rn = rn.id(), @@ -377,10 +417,10 @@ pub const Instruction = union(enum) { rd: Register, rn: Register, offset: Offset, - pre_post: u1, - up_down: u1, + pre_index: bool, + positive: bool, byte_word: u1, - writeback: u1, + write_back: bool, 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 = @boolToInt(write_back), + .byte_word = byte_word, + .up_down = @boolToInt(positive), + .pre_post = @boolToInt(pre_index), + .imm = @boolToInt(offset != .Immediate), + }, + }; + } + + fn blockDataTransfer( + cond: Condition, + rn: Register, + reg_list: RegisterList, + pre_post: u1, + up_down: u1, + psr_or_user: u1, + write_back: bool, + load_store: u1, + ) Instruction { + return Instruction{ + .BlockDataTransfer = .{ + .register_list = @bitCast(u16, reg_list), + .rn = rn.id(), + .load_store = load_store, + .write_back = @boolToInt(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 ands(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .@"and", 1, 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 eors(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .eor, 1, 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 subs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sub, 1, 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 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 eor(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .eor, s, 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 sub(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .sub, s, 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 rsb(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .rsb, s, 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 add(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .add, s, 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 adc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { - return dataProcessing(cond, .adc, s, 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 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, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsc, 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 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,32 +586,115 @@ 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 orrs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .orr, 1, rd, rn, op2); + } + + pub fn mov(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mov, 0, rd, .r0, op2); } - pub fn mov(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .mov, 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, s: u1, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .bic, s, rd, rn, op2); + pub fn bic(cond: Condition, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .bic, 0, rd, rn, op2); } - pub fn mvn(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { - return dataProcessing(cond, .mvn, s, rd, .r0, 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 - pub fn ldr(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction { - return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 1); + pub const OffsetArgs = struct { + pre_index: bool = true, + positive: bool = true, + offset: Offset, + write_back: bool = false, + }; + + pub fn ldr(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 0, args.write_back, 1); + } + + pub fn ldrb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 1); + } + + pub fn str(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 0, args.write_back, 0); + } + + pub fn strb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction { + return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 0); + } + + // Block data transfer + + pub fn ldmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 1); + } + + pub fn ldmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 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 ldmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 1); } + pub fn ldmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 1); + } + + pub const ldmfa = ldmda; + pub const ldmea = ldmdb; + pub const ldmed = ldmib; + pub const ldmfd = ldmia; + pub const ldm = ldmia; + + pub fn stmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 0); + } + + pub fn stmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 0); + } + + pub fn stmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 0); + } + + pub fn stmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { + return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 0); + } + + pub const stmed = stmda; + pub const stmfd = stmdb; + pub const stmfa = stmib; + pub const stmea = stmia; + pub const stm = stmia; + // Branch pub fn b(cond: Condition, offset: i24) Instruction { @@ -549,6 +728,58 @@ pub const Instruction = union(enum) { pub fn bkpt(imm: u16) Instruction { return breakpoint(imm); } + + // Aliases + + pub fn pop(cond: Condition, args: anytype) Instruction { + if (@typeInfo(@TypeOf(args)) != .Struct) { + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); + } + + if (args.len < 1) { + @compileError("Expected at least one register"); + } else if (args.len == 1) { + const reg = args[0]; + return ldr(cond, reg, .sp, .{ + .pre_index = false, + .positive = true, + .offset = Offset.imm(4), + .write_back = false, + }); + } else { + var register_list: u16 = 0; + inline for (args) |arg| { + const reg = @as(Register, arg); + register_list |= @as(u16, 1) << reg.id(); + } + return ldm(cond, .sp, true, @bitCast(RegisterList, register_list)); + } + } + + pub fn push(cond: Condition, args: anytype) Instruction { + if (@typeInfo(@TypeOf(args)) != .Struct) { + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); + } + + if (args.len < 1) { + @compileError("Expected at least one register"); + } else if (args.len == 1) { + const reg = args[0]; + return str(cond, reg, .sp, .{ + .pre_index = true, + .positive = false, + .offset = Offset.imm(4), + .write_back = true, + }); + } else { + var register_list: u16 = 0; + inline for (args) |arg| { + const reg = @as(Register, arg); + register_list |= @as(u16, 1) << reg.id(); + } + return stmdb(cond, .sp, true, @bitCast(RegisterList, register_list)); + } + } }; test "serialize instructions" { @@ -559,23 +790,31 @@ 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)), + .inst = Instruction.ldr(.al, .r0, .r2, .{ + .offset = 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), + .inst = Instruction.str(.al, .r0, .r3, .{ + .offset = Instruction.Offset.none, + }), .expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000, }, .{ // b #12 @@ -598,6 +837,14 @@ test "serialize instructions" { .inst = Instruction.bkpt(42), .expected = 0b1110_0001_0010_000000000010_0111_1010, }, + .{ // stmdb r9, {r0} + .inst = Instruction.stmdb(.al, .r9, false, .{ .r0 = true }), + .expected = 0b1110_100_1_0_0_0_0_1001_0000000000000001, + }, + .{ // ldmea r4!, {r2, r5} + .inst = Instruction.ldmea(.al, .r4, true, .{ .r2 = true, .r5 = true }), + .expected = 0b1110_100_1_0_0_1_1_0100_0000000000100100, + }, }; for (testcases) |case| { @@ -605,3 +852,43 @@ test "serialize instructions" { testing.expectEqual(case.expected, actual); } } + +test "aliases" { + const Testcase = struct { + expected: Instruction, + actual: Instruction, + }; + + const testcases = [_]Testcase{ + .{ // pop { r6 } + .actual = Instruction.pop(.al, .{.r6}), + .expected = Instruction.ldr(.al, .r6, .sp, .{ + .pre_index = false, + .positive = true, + .offset = Instruction.Offset.imm(4), + .write_back = false, + }), + }, + .{ // pop { r1, r5 } + .actual = Instruction.pop(.al, .{ .r1, .r5 }), + .expected = Instruction.ldm(.al, .sp, true, .{ .r1 = true, .r5 = true }), + }, + .{ // push { r3 } + .actual = Instruction.push(.al, .{.r3}), + .expected = Instruction.str(.al, .r3, .sp, .{ + .pre_index = true, + .positive = false, + .offset = Instruction.Offset.imm(4), + .write_back = true, + }), + }, + .{ // push { r0, r2 } + .actual = Instruction.push(.al, .{ .r0, .r2 }), + .expected = Instruction.stmdb(.al, .sp, true, .{ .r0 = true, .r2 = true }), + }, + }; + + for (testcases) |case| { + testing.expectEqual(case.expected.toU32(), case.actual.toU32()); + } +} |
