Commit f31cee5393
Changed files (2)
src-self-hosted
codegen
src-self-hosted/codegen/arm.zig
@@ -0,0 +1,566 @@
+const std = @import("std");
+const testing = std.testing;
+
+/// The condition field specifies the flags neccessary for an
+/// Instruction to be executed
+pub const Condition = enum(u4) {
+ /// equal
+ eq,
+ /// not equal
+ ne,
+ /// unsigned higher or same
+ cs,
+ /// unsigned lower
+ cc,
+ /// negative
+ mi,
+ /// positive or zero
+ pl,
+ /// overflow
+ vs,
+ /// no overflow
+ vc,
+ /// unsigned higer
+ hi,
+ /// unsigned lower or same
+ ls,
+ /// greater or equal
+ ge,
+ /// less than
+ lt,
+ /// greater than
+ gt,
+ /// less than or equal
+ le,
+ /// always
+ al,
+};
+
+/// Represents a register in the ARM instruction set architecture
+pub const Register = enum(u5) {
+ r0,
+ r1,
+ r2,
+ r3,
+ r4,
+ r5,
+ r6,
+ r7,
+ r8,
+ r9,
+ r10,
+ r11,
+ r12,
+ r13,
+ r14,
+ r15,
+
+ /// Argument / result / scratch register 1
+ a1,
+ /// Argument / result / scratch register 2
+ a2,
+ /// Argument / scratch register 3
+ a3,
+ /// Argument / scratch register 4
+ a4,
+ /// Variable-register 1
+ v1,
+ /// Variable-register 2
+ v2,
+ /// Variable-register 3
+ v3,
+ /// Variable-register 4
+ v4,
+ /// Variable-register 5
+ v5,
+ /// Platform register
+ v6,
+ /// Variable-register 7
+ v7,
+ /// Frame pointer or Variable-register 8
+ fp,
+ /// Intra-Procedure-call scratch register
+ ip,
+ /// Stack pointer
+ sp,
+ /// Link register
+ lr,
+ /// Program counter
+ pc,
+
+ /// Returns the unique 4-bit ID of this register which is used in
+ /// the machine code
+ pub fn id(self: Register) u4 {
+ return @truncate(u4, @enumToInt(self));
+ }
+};
+
+test "Register.id" {
+ testing.expectEqual(@as(u4, 15), Register.r15.id());
+ testing.expectEqual(@as(u4, 15), Register.pc.id());
+}
+
+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 };
+
+/// Represents an instruction in the ARM instruction set architecture
+pub const Instruction = union(enum) {
+ DataProcessing: packed struct {
+ // Note to self: The order of the fields top-to-bottom is
+ // right-to-left in the actual 32-bit int representation
+ op2: u12,
+ rd: u4,
+ rn: u4,
+ s: u1,
+ opcode: u4,
+ i: u1,
+ fixed: u2 = 0b00,
+ cond: u4,
+ },
+ SingleDataTransfer: packed struct {
+ offset: u12,
+ rd: u4,
+ rn: u4,
+ l: u1,
+ w: u1,
+ b: u1,
+ u: u1,
+ p: u1,
+ i: u1,
+ fixed: u2 = 0b01,
+ cond: u4,
+ },
+ Branch: packed struct {
+ offset: u24,
+ link: u1,
+ fixed: u3 = 0b101,
+ cond: u4,
+ },
+ BranchExchange: packed struct {
+ rn: u4,
+ fixed_1: u1 = 0b1,
+ link: u1,
+ fixed_2: u22 = 0b0001_0010_1111_1111_1111_00,
+ cond: u4,
+ },
+ SoftwareInterrupt: packed struct {
+ comment: u24,
+ fixed: u4 = 0b1111,
+ cond: u4,
+ },
+
+ /// Represents the possible operations which can be performed by a
+ /// DataProcessing instruction
+ const Opcode = enum(u4) {
+ // Rd := Op1 AND Op2
+ @"and",
+ // Rd := Op1 EOR Op2
+ eor,
+ // Rd := Op1 - Op2
+ sub,
+ // Rd := Op2 - Op1
+ rsb,
+ // Rd := Op1 + Op2
+ add,
+ // Rd := Op1 + Op2 + C
+ adc,
+ // Rd := Op1 - Op2 + C - 1
+ sbc,
+ // Rd := Op2 - Op1 + C - 1
+ rsc,
+ // set condition codes on Op1 AND Op2
+ tst,
+ // set condition codes on Op1 EOR Op2
+ teq,
+ // set condition codes on Op1 - Op2
+ cmp,
+ // set condition codes on Op1 + Op2
+ cmn,
+ // Rd := Op1 OR Op2
+ orr,
+ // Rd := Op2
+ mov,
+ // Rd := Op1 AND NOT Op2
+ bic,
+ // Rd := NOT Op2
+ mvn,
+ };
+
+ /// Represents the second operand to a data processing instruction
+ /// which can either be content from a register or an immediate
+ /// value
+ pub const Operand = union(enum) {
+ Register: packed struct {
+ rm: u4,
+ shift: u8,
+ },
+ Immediate: packed struct {
+ imm: u8,
+ rotate: u4,
+ },
+
+ /// Represents multiple ways a register can be shifted. A
+ /// register can be shifted by a specific immediate value or
+ /// by the contents of another register
+ pub const Shift = union(enum) {
+ Immediate: packed struct {
+ fixed: u1 = 0b0,
+ typ: u2,
+ amount: u5,
+ },
+ Register: packed struct {
+ fixed_1: u1 = 0b1,
+ typ: u2,
+ fixed_2: u1 = 0b0,
+ rs: u4,
+ },
+
+ const Type = enum(u2) {
+ LogicalLeft,
+ LogicalRight,
+ ArithmeticRight,
+ RotateRight,
+ };
+
+ const none = Shift{
+ .Immediate = .{
+ .amount = 0,
+ .typ = 0,
+ },
+ };
+
+ pub fn toU8(self: Shift) u8 {
+ return switch (self) {
+ .Register => |v| @bitCast(u8, v),
+ .Immediate => |v| @bitCast(u8, v),
+ };
+ }
+
+ pub fn reg(rs: Register, typ: Type) Shift {
+ return Shift{
+ .Register = .{
+ .rs = rs.id(),
+ .typ = @enumToInt(typ),
+ },
+ };
+ }
+
+ pub fn imm(amount: u5, typ: Type) Shift {
+ return Shift{
+ .Immediate = .{
+ .amount = amount,
+ .typ = @enumToInt(typ),
+ },
+ };
+ }
+ };
+
+ pub fn toU12(self: Operand) u12 {
+ return switch (self) {
+ .Register => |v| @bitCast(u12, v),
+ .Immediate => |v| @bitCast(u12, v),
+ };
+ }
+
+ pub fn reg(rm: Register, shift: Shift) Operand {
+ return Operand{
+ .Register = .{
+ .rm = rm.id(),
+ .shift = shift.toU8(),
+ },
+ };
+ }
+
+ pub fn imm(immediate: u8, rotate: u4) Operand {
+ return Operand{
+ .Immediate = .{
+ .imm = immediate,
+ .rotate = rotate,
+ },
+ };
+ }
+ };
+
+ /// Represents the offset operand of a load or store
+ /// instruction. Data can be loaded from memory with either an
+ /// immediate offset or an offset that is stored in some register.
+ pub const Offset = union(enum) {
+ Immediate: u12,
+ Register: packed struct {
+ rm: u4,
+ shift: u8,
+ },
+
+ pub const none = Offset{
+ .Immediate = 0,
+ };
+
+ pub fn toU12(self: Offset) u12 {
+ return switch (self) {
+ .Register => |v| @bitCast(u12, v),
+ .Immediate => |v| v,
+ };
+ }
+
+ pub fn reg(rm: Register, shift: u8) Offset {
+ return Offset{
+ .Register = .{
+ .rm = rm.id(),
+ .shift = shift,
+ },
+ };
+ }
+
+ pub fn imm(immediate: u8) Offset {
+ return Offset{
+ .Immediate = immediate,
+ };
+ }
+ };
+
+ pub fn toU32(self: Instruction) u32 {
+ return switch (self) {
+ .DataProcessing => |v| @bitCast(u32, v),
+ .SingleDataTransfer => |v| @bitCast(u32, v),
+ .Branch => |v| @bitCast(u32, v),
+ .BranchExchange => |v| @bitCast(u32, v),
+ .SoftwareInterrupt => |v| @bitCast(u32, v),
+ };
+ }
+
+ // Helper functions for the "real" functions below
+
+ fn dataProcessing(
+ cond: Condition,
+ opcode: Opcode,
+ s: u1,
+ rn: Register,
+ rd: Register,
+ op2: Operand,
+ ) Instruction {
+ return Instruction{
+ .DataProcessing = .{
+ .cond = @enumToInt(cond),
+ .i = if (op2 == .Immediate) 1 else 0,
+ .opcode = @enumToInt(opcode),
+ .s = s,
+ .rn = rn.id(),
+ .rd = rd.id(),
+ .op2 = op2.toU12(),
+ },
+ };
+ }
+
+ fn singleDataTransfer(
+ cond: Condition,
+ rd: Register,
+ rn: Register,
+ offset: Offset,
+ pre_post: u1,
+ up_down: u1,
+ byte_word: u1,
+ writeback: u1,
+ load_store: u1,
+ ) Instruction {
+ return Instruction{
+ .SingleDataTransfer = .{
+ .cond = @enumToInt(cond),
+ .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) 1 else 0,
+ },
+ };
+ }
+
+ fn branch(cond: Condition, offset: i24, link: u1) Instruction {
+ return Instruction{
+ .Branch = .{
+ .cond = @enumToInt(cond),
+ .link = link,
+ .offset = @bitCast(u24, offset),
+ },
+ };
+ }
+
+ fn branchExchange(cond: Condition, rn: Register, link: u1) Instruction {
+ return Instruction{
+ .BranchExchange = .{
+ .cond = @enumToInt(cond),
+ .link = link,
+ .rn = rn.id(),
+ },
+ };
+ }
+
+ fn softwareInterrupt(cond: Condition, comment: u24) Instruction {
+ return Instruction{
+ .SoftwareInterrupt = .{
+ .cond = @enumToInt(cond),
+ .comment = comment,
+ },
+ };
+ }
+
+ // Public functions replicating assembler syntax as closely as
+ // possible
+
+ // 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 eor(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .eor, s, 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 rsb(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .rsb, s, 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 adc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .adc, s, 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, s: u1, rd: Register, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .rsc, s, rd, rn, op2);
+ }
+
+ pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .tst, 1, .r0, rn, op2);
+ }
+
+ pub fn teq(cond: Condition, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .teq, 1, .r0, rn, op2);
+ }
+
+ pub fn cmp(cond: Condition, rn: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .cmp, 1, .r0, rn, op2);
+ }
+
+ pub fn cmn(cond: Condition, rn: Register, op2: Operand) Instruction {
+ 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 mov(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .mov, s, 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 mvn(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction {
+ return dataProcessing(cond, .mvn, s, rd, .r0, op2);
+ }
+
+ // 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 fn str(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction {
+ return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 0);
+ }
+
+ // Branch
+
+ pub fn b(cond: Condition, offset: i24) Instruction {
+ return branch(cond, offset, 0);
+ }
+
+ pub fn bl(cond: Condition, offset: i24) Instruction {
+ return branch(cond, offset, 1);
+ }
+
+ // Branch and exchange
+
+ pub fn bx(cond: Condition, rn: Register) Instruction {
+ return branchExchange(cond, rn, 0);
+ }
+
+ pub fn blx(cond: Condition, rn: Register) Instruction {
+ return branchExchange(cond, rn, 1);
+ }
+
+ // Software interrupt
+
+ pub fn swi(cond: Condition, comment: u24) Instruction {
+ return softwareInterrupt(cond, comment);
+ }
+};
+
+test "serialize instructions" {
+ const Testcase = struct {
+ inst: Instruction,
+ expected: u32,
+ };
+
+ const testcases = [_]Testcase{
+ .{ // add r0, r0, r0
+ .inst = Instruction.add(.al, 0, .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)),
+ .expected = 0b1110_00_0_1101_0_0100_0000_00000000_0010,
+ },
+ .{ // mov r0, #42
+ .inst = Instruction.mov(.al, 0, .r0, Instruction.Operand.imm(42, 0)),
+ .expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010,
+ },
+ .{ // ldr r0, [r2, #42]
+ .inst = Instruction.ldr(.al, .r0, .r2, Instruction.Offset.imm(42)),
+ .expected = 0b1110_01_1_1_1_0_0_1_0010_0000_000000101010,
+ },
+ .{ // str r0, [r3]
+ .inst = Instruction.str(.al, .r0, .r3, Instruction.Offset.none),
+ .expected = 0b1110_01_1_1_1_0_0_0_0011_0000_000000000000,
+ },
+ .{ // b #12
+ .inst = Instruction.b(.al, 12),
+ .expected = 0b1110_101_0_0000_0000_0000_0000_0000_1100,
+ },
+ .{ // bl #-4
+ .inst = Instruction.bl(.al, -4),
+ .expected = 0b1110_101_1_1111_1111_1111_1111_1111_1100,
+ },
+ .{ // bx lr
+ .inst = Instruction.bx(.al, .lr),
+ .expected = 0b1110_0001_0010_1111_1111_1111_0001_1110,
+ },
+ .{ // swi #0
+ .inst = Instruction.swi(.al, 0),
+ .expected = 0b1110_1111_0000_0000_0000_0000_0000_0000,
+ },
+ };
+
+ for (testcases) |case| {
+ const actual = case.inst.toU32();
+ testing.expectEqual(case.expected, actual);
+ }
+}
src-self-hosted/codegen.zig
@@ -2372,6 +2372,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.x86_64 => @import("codegen/x86_64.zig"),
.riscv64 => @import("codegen/riscv64.zig"),
.spu_2 => @import("codegen/spu-mk2.zig"),
+ .arm => @import("codegen/arm.zig"),
else => struct {
pub const Register = enum {
dummy,