Commit f31cee5393

joachimschmidt557 <joachim.schmidt557@outlook.com>
2020-08-19 20:51:56
Start working on stage2 ARM backend
- add codegen/arm.zig with some basic functionality (load/store, data processing, branching, software interrupts)
1 parent 4e63cae
Changed files (2)
src-self-hosted
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,