Commit b2254023e4

joachimschmidt557 <joachim.schmidt557@outlook.com>
2020-08-23 17:43:00
stage2: Implement setReg, call, ret, asm for ARM
These changes enable a Hello World example. However, all implemented codegen is not yet feature-complete. - asm only supports 'svc #0' at the moment - call only supports leaf functions at the moment - setReg uses a naive method at the moment
1 parent 1c53c07
Changed files (3)
src-self-hosted
src-self-hosted/codegen/arm.zig
@@ -157,7 +157,7 @@ pub const Instruction = union(enum) {
         fixed_2: u22 = 0b0001_0010_1111_1111_1111_00,
         cond: u4,
     },
-    SoftwareInterrupt: packed struct {
+    SupervisorCall: packed struct {
         comment: u24,
         fixed: u4 = 0b1111,
         cond: u4,
@@ -344,7 +344,7 @@ pub const Instruction = union(enum) {
             .SingleDataTransfer => |v| @bitCast(u32, v),
             .Branch => |v| @bitCast(u32, v),
             .BranchExchange => |v| @bitCast(u32, v),
-            .SoftwareInterrupt => |v| @bitCast(u32, v),
+            .SupervisorCall => |v| @bitCast(u32, v),
             .Breakpoint => |v| @intCast(u32, v.imm4) | (@intCast(u32, v.fixed_1) << 4) | (@intCast(u32, v.imm12) << 8) | (@intCast(u32, v.fixed_2_and_cond) << 20),
         };
     }
@@ -355,8 +355,8 @@ pub const Instruction = union(enum) {
         cond: Condition,
         opcode: Opcode,
         s: u1,
-        rn: Register,
         rd: Register,
+        rn: Register,
         op2: Operand,
     ) Instruction {
         return Instruction{
@@ -394,7 +394,7 @@ pub const Instruction = union(enum) {
                 .b = byte_word,
                 .u = up_down,
                 .p = pre_post,
-                .i = if (offset == .Immediate) 1 else 0,
+                .i = if (offset == .Immediate) 0 else 1,
             },
         };
     }
@@ -419,9 +419,9 @@ pub const Instruction = union(enum) {
         };
     }
 
-    fn softwareInterrupt(cond: Condition, comment: u24) Instruction {
+    fn supervisorCall(cond: Condition, comment: u24) Instruction {
         return Instruction{
-            .SoftwareInterrupt = .{
+            .SupervisorCall = .{
                 .cond = @enumToInt(cond),
                 .comment = comment,
             },
@@ -536,10 +536,12 @@ pub const Instruction = union(enum) {
         return branchExchange(cond, rn, 1);
     }
 
-    // Software interrupt
+    // Supervisor Call
+
+    pub const swi = svc;
 
-    pub fn swi(cond: Condition, comment: u24) Instruction {
-        return softwareInterrupt(cond, comment);
+    pub fn svc(cond: Condition, comment: u24) Instruction {
+        return supervisorCall(cond, comment);
     }
 
     // Breakpoint
@@ -562,7 +564,7 @@ test "serialize instructions" {
         },
         .{ // 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,
+            .expected = 0b1110_00_0_1101_0_0000_0100_00000000_0010,
         },
         .{ // mov r0, #42
             .inst = Instruction.mov(.al, 0, .r0, Instruction.Operand.imm(42, 0)),
@@ -570,11 +572,11 @@ test "serialize instructions" {
         },
         .{ // 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,
+            .expected = 0b1110_01_0_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,
+            .expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000,
         },
         .{ // b #12
             .inst = Instruction.b(.al, 12),
@@ -588,8 +590,8 @@ test "serialize instructions" {
             .inst = Instruction.bx(.al, .lr),
             .expected = 0b1110_0001_0010_1111_1111_1111_0001_1110,
         },
-        .{ // swi #0
-            .inst = Instruction.swi(.al, 0),
+        .{ // svc #0
+            .inst = Instruction.svc(.al, 0),
             .expected = 0b1110_1111_0000_0000_0000_0000_0000_0000,
         },
         .{ // bkpt #42
src-self-hosted/codegen.zig
@@ -1399,6 +1399,31 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
                         }
                     },
+                    .arm => {
+                        if (info.args.len > 0) return self.fail(inst.base.src, "TODO implement fn args for {}", .{self.target.cpu.arch});
+
+                        if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
+                            if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
+                                const func = func_val.func;
+                                const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
+                                const ptr_bits = self.target.cpu.arch.ptrBitWidth();
+                                const ptr_bytes: u64 = @divExact(ptr_bits, 8);
+                                const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * ptr_bytes);
+
+                                // 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());
+                            } else {
+                                return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
+                            }
+                        } else {
+                            return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
+                        }
+                    },
                     else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
                 }
             } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
@@ -1455,6 +1480,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .riscv64 => {
                     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());
+                },
                 else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),
             }
             return .unreach;
@@ -1705,6 +1733,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         return self.fail(inst.base.src, "TODO implement support for more SPU II assembly instructions", .{});
                     }
                 },
+                .arm => {
+                    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, "svc #0")) {
+                        mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.svc(.al, 0).toU32());
+                    } else {
+                        return self.fail(inst.base.src, "TODO implement support for more arm 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;
+                    }
+                },
                 .riscv64 => {
                     for (inst.inputs) |input, i| {
                         if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
@@ -1898,6 +1956,58 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
         fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) InnerError!void {
             switch (arch) {
+                .arm => switch (mcv) {
+                    .dead => unreachable,
+                    .ptr_stack_offset => unreachable,
+                    .ptr_embedded_in_code => unreachable,
+                    .unreach, .none => return, // Nothing to do.
+                    .undef => {
+                        if (!self.wantSafety())
+                            return; // The already existing value will do just fine.
+                        // Write the debug undefined value.
+                        return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa });
+                    },
+                    .immediate => |x| {
+                        // TODO better analysis of x to determine the
+                        // 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());
+                            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());
+                        } else if (x <= math.maxInt(u32)) {
+                            // TODO Use movw and movt Note: Not
+                            // supported on all ARM targets! Also TODO
+                            // write constant to code and load
+                            // relative to pc
+
+                            // immediate: 0xaabbccdd
+                            // mov reg, #0xaa
+                            // 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());
+                            return;
+                        } else {
+                            return self.fail(src, "ARM registers are 32-bit wide", .{});
+                        }
+                    },
+                    .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());
+                    },
+                    else => return self.fail(src, "TODO implement getSetReg for arm {}", .{mcv}),
+                },
                 .riscv64 => switch (mcv) {
                     .dead => unreachable,
                     .ptr_stack_offset => unreachable,
src-self-hosted/type.zig
@@ -756,6 +756,7 @@ pub const Type = extern union {
             .fn_ccc_void_no_args, // represents machine code; not a pointer
             .function, // represents machine code; not a pointer
             => return switch (target.cpu.arch) {
+                .arm => 4,
                 .riscv64 => 2,
                 else => 1,
             },