| author | |
| committer | |
| log | 385786eedc7927a76006741218b949a87d42ad76 |
| tree | bc944f09d9408c79ec24e12770059e8a74b2e4f7 |
| parent | 4bbea3422a7b312a4996e0d6fc35c30dabd692b8 |
| parent | 4f2618e75bfbaaea755c7fec14f5ae844eb3d0f8 |
| signature |
Start working on stage2 ARM backend4 files changed, 768 insertions(+), 2 deletions(-)
src-self-hosted/codegen.zig+120-2| ... | @@ -76,8 +76,8 @@ pub fn generateSymbol( | ... | @@ -76,8 +76,8 @@ pub fn generateSymbol( |
| 76 | switch (bin_file.options.target.cpu.arch) { | 76 | switch (bin_file.options.target.cpu.arch) { |
| 77 | .wasm32 => unreachable, // has its own code path | 77 | .wasm32 => unreachable, // has its own code path |
| 78 | .wasm64 => unreachable, // has its own code path | 78 | .wasm64 => unreachable, // has its own code path |
| 79 | //.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 79 | .arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| 80 | //.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 80 | .armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| 81 | //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 81 | //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| 82 | //.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 82 | //.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| 83 | //.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 83 | //.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| ... | @@ -1270,6 +1270,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1270,6 +1270,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1270 | var instr = Instruction{ .condition = .always, .input0 = .zero, .input1 = .zero, .modify_flags = false, .output = .discard, .command = .undefined1 }; | 1270 | var instr = Instruction{ .condition = .always, .input0 = .zero, .input1 = .zero, .modify_flags = false, .output = .discard, .command = .undefined1 }; |
| 1271 | mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], @bitCast(u16, instr)); | 1271 | mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], @bitCast(u16, instr)); |
| 1272 | }, | 1272 | }, |
| 1273 | .arm => { | ||
| 1274 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bkpt(0).toU32()); | ||
| 1275 | }, | ||
| 1276 | .armeb => { | ||
| 1277 | mem.writeIntBig(u32, try self.code.addManyAsArray(4), Instruction.bkpt(0).toU32()); | ||
| 1278 | }, | ||
| 1273 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), | 1279 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), |
| 1274 | } | 1280 | } |
| 1275 | return .none; | 1281 | return .none; |
| ... | @@ -1393,6 +1399,31 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1393,6 +1399,31 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1393 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | 1399 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); |
| 1394 | } | 1400 | } |
| 1395 | }, | 1401 | }, |
| 1402 | .arm => { | ||
| 1403 | if (info.args.len > 0) return self.fail(inst.base.src, "TODO implement fn args for {}", .{self.target.cpu.arch}); | ||
| 1404 | |||
| 1405 | if (inst.func.cast(ir.Inst.Constant)) |func_inst| { | ||
| 1406 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { | ||
| 1407 | const func = func_val.func; | ||
| 1408 | const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; | ||
| 1409 | const ptr_bits = self.target.cpu.arch.ptrBitWidth(); | ||
| 1410 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); | ||
| 1411 | const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * ptr_bytes); | ||
| 1412 | |||
| 1413 | // TODO only works with leaf functions | ||
| 1414 | // at the moment, which works fine for | ||
| 1415 | // Hello World, but not for real code | ||
| 1416 | // of course. Add pushing lr to stack | ||
| 1417 | // and popping after call | ||
| 1418 | try self.genSetReg(inst.base.src, .lr, .{ .memory = got_addr }); | ||
| 1419 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.blx(.al, .lr).toU32()); | ||
| 1420 | } else { | ||
| 1421 | return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); | ||
| 1422 | } | ||
| 1423 | } else { | ||
| 1424 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | ||
| 1425 | } | ||
| 1426 | }, | ||
| 1396 | else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), | 1427 | else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), |
| 1397 | } | 1428 | } |
| 1398 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | 1429 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { |
| ... | @@ -1449,6 +1480,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1449,6 +1480,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1449 | .riscv64 => { | 1480 | .riscv64 => { |
| 1450 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.jalr(.zero, 0, .ra).toU32()); | 1481 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.jalr(.zero, 0, .ra).toU32()); |
| 1451 | }, | 1482 | }, |
| 1483 | .arm => { | ||
| 1484 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bx(.al, .lr).toU32()); | ||
| 1485 | }, | ||
| 1452 | else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), | 1486 | else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), |
| 1453 | } | 1487 | } |
| 1454 | return .unreach; | 1488 | return .unreach; |
| ... | @@ -1699,6 +1733,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1699,6 +1733,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1699 | return self.fail(inst.base.src, "TODO implement support for more SPU II assembly instructions", .{}); | 1733 | return self.fail(inst.base.src, "TODO implement support for more SPU II assembly instructions", .{}); |
| 1700 | } | 1734 | } |
| 1701 | }, | 1735 | }, |
| 1736 | .arm => { | ||
| 1737 | for (inst.inputs) |input, i| { | ||
| 1738 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { | ||
| 1739 | return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); | ||
| 1740 | } | ||
| 1741 | const reg_name = input[1 .. input.len - 1]; | ||
| 1742 | const reg = parseRegName(reg_name) orelse | ||
| 1743 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | ||
| 1744 | const arg = try self.resolveInst(inst.args[i]); | ||
| 1745 | try self.genSetReg(inst.base.src, reg, arg); | ||
| 1746 | } | ||
| 1747 | |||
| 1748 | if (mem.eql(u8, inst.asm_source, "svc #0")) { | ||
| 1749 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.svc(.al, 0).toU32()); | ||
| 1750 | } else { | ||
| 1751 | return self.fail(inst.base.src, "TODO implement support for more arm assembly instructions", .{}); | ||
| 1752 | } | ||
| 1753 | |||
| 1754 | if (inst.output) |output| { | ||
| 1755 | if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { | ||
| 1756 | return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); | ||
| 1757 | } | ||
| 1758 | const reg_name = output[2 .. output.len - 1]; | ||
| 1759 | const reg = parseRegName(reg_name) orelse | ||
| 1760 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | ||
| 1761 | return MCValue{ .register = reg }; | ||
| 1762 | } else { | ||
| 1763 | return MCValue.none; | ||
| 1764 | } | ||
| 1765 | }, | ||
| 1702 | .riscv64 => { | 1766 | .riscv64 => { |
| 1703 | for (inst.inputs) |input, i| { | 1767 | for (inst.inputs) |input, i| { |
| 1704 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { | 1768 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { |
| ... | @@ -1892,6 +1956,58 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1892,6 +1956,58 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1892 | 1956 | ||
| 1893 | fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) InnerError!void { | 1957 | fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) InnerError!void { |
| 1894 | switch (arch) { | 1958 | switch (arch) { |
| 1959 | .arm => switch (mcv) { | ||
| 1960 | .dead => unreachable, | ||
| 1961 | .ptr_stack_offset => unreachable, | ||
| 1962 | .ptr_embedded_in_code => unreachable, | ||
| 1963 | .unreach, .none => return, // Nothing to do. | ||
| 1964 | .undef => { | ||
| 1965 | if (!self.wantSafety()) | ||
| 1966 | return; // The already existing value will do just fine. | ||
| 1967 | // Write the debug undefined value. | ||
| 1968 | return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }); | ||
| 1969 | }, | ||
| 1970 | .immediate => |x| { | ||
| 1971 | // TODO better analysis of x to determine the | ||
| 1972 | // least amount of necessary instructions (use | ||
| 1973 | // more intelligent rotating) | ||
| 1974 | if (x <= math.maxInt(u8)) { | ||
| 1975 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); | ||
| 1976 | return; | ||
| 1977 | } else if (x <= math.maxInt(u16)) { | ||
| 1978 | // TODO Use movw Note: Not supported on | ||
| 1979 | // all ARM targets! | ||
| 1980 | |||
| 1981 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); | ||
| 1982 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); | ||
| 1983 | } else if (x <= math.maxInt(u32)) { | ||
| 1984 | // TODO Use movw and movt Note: Not | ||
| 1985 | // supported on all ARM targets! Also TODO | ||
| 1986 | // write constant to code and load | ||
| 1987 | // relative to pc | ||
| 1988 | |||
| 1989 | // immediate: 0xaabbccdd | ||
| 1990 | // mov reg, #0xaa | ||
| 1991 | // orr reg, reg, #0xbb, 24 | ||
| 1992 | // orr reg, reg, #0xcc, 16 | ||
| 1993 | // orr reg, reg, #0xdd, 8 | ||
| 1994 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); | ||
| 1995 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); | ||
| 1996 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 16), 8)).toU32()); | ||
| 1997 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 24), 4)).toU32()); | ||
| 1998 | return; | ||
| 1999 | } else { | ||
| 2000 | return self.fail(src, "ARM registers are 32-bit wide", .{}); | ||
| 2001 | } | ||
| 2002 | }, | ||
| 2003 | .memory => |addr| { | ||
| 2004 | // The value is in memory at a hard-coded address. | ||
| 2005 | // If the type is a pointer, it means the pointer address is at this memory location. | ||
| 2006 | try self.genSetReg(src, reg, .{ .immediate = addr }); | ||
| 2007 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.al, reg, reg, Instruction.Offset.none).toU32()); | ||
| 2008 | }, | ||
| 2009 | else => return self.fail(src, "TODO implement getSetReg for arm {}", .{mcv}), | ||
| 2010 | }, | ||
| 1895 | .riscv64 => switch (mcv) { | 2011 | .riscv64 => switch (mcv) { |
| 1896 | .dead => unreachable, | 2012 | .dead => unreachable, |
| 1897 | .ptr_stack_offset => unreachable, | 2013 | .ptr_stack_offset => unreachable, |
| ... | @@ -2372,6 +2488,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -2372,6 +2488,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2372 | .x86_64 => @import("codegen/x86_64.zig"), | 2488 | .x86_64 => @import("codegen/x86_64.zig"), |
| 2373 | .riscv64 => @import("codegen/riscv64.zig"), | 2489 | .riscv64 => @import("codegen/riscv64.zig"), |
| 2374 | .spu_2 => @import("codegen/spu-mk2.zig"), | 2490 | .spu_2 => @import("codegen/spu-mk2.zig"), |
| 2491 | .arm => @import("codegen/arm.zig"), | ||
| 2492 | .armeb => @import("codegen/arm.zig"), | ||
| 2375 | else => struct { | 2493 | else => struct { |
| 2376 | pub const Register = enum { | 2494 | pub const Register = enum { |
| 2377 | dummy, | 2495 | dummy, |
src-self-hosted/codegen/arm.zig created+607| ... | @@ -0,0 +1,607 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const DW = std.dwarf; | ||
| 3 | const testing = std.testing; | ||
| 4 | |||
| 5 | /// The condition field specifies the flags neccessary for an | ||
| 6 | /// Instruction to be executed | ||
| 7 | pub const Condition = enum(u4) { | ||
| 8 | /// equal | ||
| 9 | eq, | ||
| 10 | /// not equal | ||
| 11 | ne, | ||
| 12 | /// unsigned higher or same | ||
| 13 | cs, | ||
| 14 | /// unsigned lower | ||
| 15 | cc, | ||
| 16 | /// negative | ||
| 17 | mi, | ||
| 18 | /// positive or zero | ||
| 19 | pl, | ||
| 20 | /// overflow | ||
| 21 | vs, | ||
| 22 | /// no overflow | ||
| 23 | vc, | ||
| 24 | /// unsigned higer | ||
| 25 | hi, | ||
| 26 | /// unsigned lower or same | ||
| 27 | ls, | ||
| 28 | /// greater or equal | ||
| 29 | ge, | ||
| 30 | /// less than | ||
| 31 | lt, | ||
| 32 | /// greater than | ||
| 33 | gt, | ||
| 34 | /// less than or equal | ||
| 35 | le, | ||
| 36 | /// always | ||
| 37 | al, | ||
| 38 | }; | ||
| 39 | |||
| 40 | /// Represents a register in the ARM instruction set architecture | ||
| 41 | pub const Register = enum(u5) { | ||
| 42 | r0, | ||
| 43 | r1, | ||
| 44 | r2, | ||
| 45 | r3, | ||
| 46 | r4, | ||
| 47 | r5, | ||
| 48 | r6, | ||
| 49 | r7, | ||
| 50 | r8, | ||
| 51 | r9, | ||
| 52 | r10, | ||
| 53 | r11, | ||
| 54 | r12, | ||
| 55 | r13, | ||
| 56 | r14, | ||
| 57 | r15, | ||
| 58 | |||
| 59 | /// Argument / result / scratch register 1 | ||
| 60 | a1, | ||
| 61 | /// Argument / result / scratch register 2 | ||
| 62 | a2, | ||
| 63 | /// Argument / scratch register 3 | ||
| 64 | a3, | ||
| 65 | /// Argument / scratch register 4 | ||
| 66 | a4, | ||
| 67 | /// Variable-register 1 | ||
| 68 | v1, | ||
| 69 | /// Variable-register 2 | ||
| 70 | v2, | ||
| 71 | /// Variable-register 3 | ||
| 72 | v3, | ||
| 73 | /// Variable-register 4 | ||
| 74 | v4, | ||
| 75 | /// Variable-register 5 | ||
| 76 | v5, | ||
| 77 | /// Platform register | ||
| 78 | v6, | ||
| 79 | /// Variable-register 7 | ||
| 80 | v7, | ||
| 81 | /// Frame pointer or Variable-register 8 | ||
| 82 | fp, | ||
| 83 | /// Intra-Procedure-call scratch register | ||
| 84 | ip, | ||
| 85 | /// Stack pointer | ||
| 86 | sp, | ||
| 87 | /// Link register | ||
| 88 | lr, | ||
| 89 | /// Program counter | ||
| 90 | pc, | ||
| 91 | |||
| 92 | /// Returns the unique 4-bit ID of this register which is used in | ||
| 93 | /// the machine code | ||
| 94 | pub fn id(self: Register) u4 { | ||
| 95 | return @truncate(u4, @enumToInt(self)); | ||
| 96 | } | ||
| 97 | |||
| 98 | /// Returns the index into `callee_preserved_regs`. | ||
| 99 | pub fn allocIndex(self: Register) ?u4 { | ||
| 100 | inline for (callee_preserved_regs) |cpreg, i| { | ||
| 101 | if (self.id() == cpreg.id()) return i; | ||
| 102 | } | ||
| 103 | return null; | ||
| 104 | } | ||
| 105 | |||
| 106 | pub fn dwarfLocOp(self: Register) u8 { | ||
| 107 | return @as(u8, self.id()) + DW.OP_reg0; | ||
| 108 | } | ||
| 109 | }; | ||
| 110 | |||
| 111 | test "Register.id" { | ||
| 112 | testing.expectEqual(@as(u4, 15), Register.r15.id()); | ||
| 113 | testing.expectEqual(@as(u4, 15), Register.pc.id()); | ||
| 114 | } | ||
| 115 | |||
| 116 | pub const callee_preserved_regs = [_]Register{ .r0, .r1, .r2, .r3, .r4, .r5, .r6, .r7, .r8, .r10 }; | ||
| 117 | pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 }; | ||
| 118 | pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 }; | ||
| 119 | |||
| 120 | /// Represents an instruction in the ARM instruction set architecture | ||
| 121 | pub const Instruction = union(enum) { | ||
| 122 | DataProcessing: packed struct { | ||
| 123 | // Note to self: The order of the fields top-to-bottom is | ||
| 124 | // right-to-left in the actual 32-bit int representation | ||
| 125 | op2: u12, | ||
| 126 | rd: u4, | ||
| 127 | rn: u4, | ||
| 128 | s: u1, | ||
| 129 | opcode: u4, | ||
| 130 | i: u1, | ||
| 131 | fixed: u2 = 0b00, | ||
| 132 | cond: u4, | ||
| 133 | }, | ||
| 134 | SingleDataTransfer: packed struct { | ||
| 135 | offset: u12, | ||
| 136 | rd: u4, | ||
| 137 | rn: u4, | ||
| 138 | l: u1, | ||
| 139 | w: u1, | ||
| 140 | b: u1, | ||
| 141 | u: u1, | ||
| 142 | p: u1, | ||
| 143 | i: u1, | ||
| 144 | fixed: u2 = 0b01, | ||
| 145 | cond: u4, | ||
| 146 | }, | ||
| 147 | Branch: packed struct { | ||
| 148 | offset: u24, | ||
| 149 | link: u1, | ||
| 150 | fixed: u3 = 0b101, | ||
| 151 | cond: u4, | ||
| 152 | }, | ||
| 153 | BranchExchange: packed struct { | ||
| 154 | rn: u4, | ||
| 155 | fixed_1: u1 = 0b1, | ||
| 156 | link: u1, | ||
| 157 | fixed_2: u22 = 0b0001_0010_1111_1111_1111_00, | ||
| 158 | cond: u4, | ||
| 159 | }, | ||
| 160 | SupervisorCall: packed struct { | ||
| 161 | comment: u24, | ||
| 162 | fixed: u4 = 0b1111, | ||
| 163 | cond: u4, | ||
| 164 | }, | ||
| 165 | Breakpoint: packed struct { | ||
| 166 | imm4: u4, | ||
| 167 | fixed_1: u4 = 0b0111, | ||
| 168 | imm12: u12, | ||
| 169 | fixed_2_and_cond: u12 = 0b1110_0001_0010, | ||
| 170 | }, | ||
| 171 | |||
| 172 | /// Represents the possible operations which can be performed by a | ||
| 173 | /// DataProcessing instruction | ||
| 174 | const Opcode = enum(u4) { | ||
| 175 | // Rd := Op1 AND Op2 | ||
| 176 | @"and", | ||
| 177 | // Rd := Op1 EOR Op2 | ||
| 178 | eor, | ||
| 179 | // Rd := Op1 - Op2 | ||
| 180 | sub, | ||
| 181 | // Rd := Op2 - Op1 | ||
| 182 | rsb, | ||
| 183 | // Rd := Op1 + Op2 | ||
| 184 | add, | ||
| 185 | // Rd := Op1 + Op2 + C | ||
| 186 | adc, | ||
| 187 | // Rd := Op1 - Op2 + C - 1 | ||
| 188 | sbc, | ||
| 189 | // Rd := Op2 - Op1 + C - 1 | ||
| 190 | rsc, | ||
| 191 | // set condition codes on Op1 AND Op2 | ||
| 192 | tst, | ||
| 193 | // set condition codes on Op1 EOR Op2 | ||
| 194 | teq, | ||
| 195 | // set condition codes on Op1 - Op2 | ||
| 196 | cmp, | ||
| 197 | // set condition codes on Op1 + Op2 | ||
| 198 | cmn, | ||
| 199 | // Rd := Op1 OR Op2 | ||
| 200 | orr, | ||
| 201 | // Rd := Op2 | ||
| 202 | mov, | ||
| 203 | // Rd := Op1 AND NOT Op2 | ||
| 204 | bic, | ||
| 205 | // Rd := NOT Op2 | ||
| 206 | mvn, | ||
| 207 | }; | ||
| 208 | |||
| 209 | /// Represents the second operand to a data processing instruction | ||
| 210 | /// which can either be content from a register or an immediate | ||
| 211 | /// value | ||
| 212 | pub const Operand = union(enum) { | ||
| 213 | Register: packed struct { | ||
| 214 | rm: u4, | ||
| 215 | shift: u8, | ||
| 216 | }, | ||
| 217 | Immediate: packed struct { | ||
| 218 | imm: u8, | ||
| 219 | rotate: u4, | ||
| 220 | }, | ||
| 221 | |||
| 222 | /// Represents multiple ways a register can be shifted. A | ||
| 223 | /// register can be shifted by a specific immediate value or | ||
| 224 | /// by the contents of another register | ||
| 225 | pub const Shift = union(enum) { | ||
| 226 | Immediate: packed struct { | ||
| 227 | fixed: u1 = 0b0, | ||
| 228 | typ: u2, | ||
| 229 | amount: u5, | ||
| 230 | }, | ||
| 231 | Register: packed struct { | ||
| 232 | fixed_1: u1 = 0b1, | ||
| 233 | typ: u2, | ||
| 234 | fixed_2: u1 = 0b0, | ||
| 235 | rs: u4, | ||
| 236 | }, | ||
| 237 | |||
| 238 | const Type = enum(u2) { | ||
| 239 | LogicalLeft, | ||
| 240 | LogicalRight, | ||
| 241 | ArithmeticRight, | ||
| 242 | RotateRight, | ||
| 243 | }; | ||
| 244 | |||
| 245 | const none = Shift{ | ||
| 246 | .Immediate = .{ | ||
| 247 | .amount = 0, | ||
| 248 | .typ = 0, | ||
| 249 | }, | ||
| 250 | }; | ||
| 251 | |||
| 252 | pub fn toU8(self: Shift) u8 { | ||
| 253 | return switch (self) { | ||
| 254 | .Register => |v| @bitCast(u8, v), | ||
| 255 | .Immediate => |v| @bitCast(u8, v), | ||
| 256 | }; | ||
| 257 | } | ||
| 258 | |||
| 259 | pub fn reg(rs: Register, typ: Type) Shift { | ||
| 260 | return Shift{ | ||
| 261 | .Register = .{ | ||
| 262 | .rs = rs.id(), | ||
| 263 | .typ = @enumToInt(typ), | ||
| 264 | }, | ||
| 265 | }; | ||
| 266 | } | ||
| 267 | |||
| 268 | pub fn imm(amount: u5, typ: Type) Shift { | ||
| 269 | return Shift{ | ||
| 270 | .Immediate = .{ | ||
| 271 | .amount = amount, | ||
| 272 | .typ = @enumToInt(typ), | ||
| 273 | }, | ||
| 274 | }; | ||
| 275 | } | ||
| 276 | }; | ||
| 277 | |||
| 278 | pub fn toU12(self: Operand) u12 { | ||
| 279 | return switch (self) { | ||
| 280 | .Register => |v| @bitCast(u12, v), | ||
| 281 | .Immediate => |v| @bitCast(u12, v), | ||
| 282 | }; | ||
| 283 | } | ||
| 284 | |||
| 285 | pub fn reg(rm: Register, shift: Shift) Operand { | ||
| 286 | return Operand{ | ||
| 287 | .Register = .{ | ||
| 288 | .rm = rm.id(), | ||
| 289 | .shift = shift.toU8(), | ||
| 290 | }, | ||
| 291 | }; | ||
| 292 | } | ||
| 293 | |||
| 294 | pub fn imm(immediate: u8, rotate: u4) Operand { | ||
| 295 | return Operand{ | ||
| 296 | .Immediate = .{ | ||
| 297 | .imm = immediate, | ||
| 298 | .rotate = rotate, | ||
| 299 | }, | ||
| 300 | }; | ||
| 301 | } | ||
| 302 | }; | ||
| 303 | |||
| 304 | /// Represents the offset operand of a load or store | ||
| 305 | /// instruction. Data can be loaded from memory with either an | ||
| 306 | /// immediate offset or an offset that is stored in some register. | ||
| 307 | pub const Offset = union(enum) { | ||
| 308 | Immediate: u12, | ||
| 309 | Register: packed struct { | ||
| 310 | rm: u4, | ||
| 311 | shift: u8, | ||
| 312 | }, | ||
| 313 | |||
| 314 | pub const none = Offset{ | ||
| 315 | .Immediate = 0, | ||
| 316 | }; | ||
| 317 | |||
| 318 | pub fn toU12(self: Offset) u12 { | ||
| 319 | return switch (self) { | ||
| 320 | .Register => |v| @bitCast(u12, v), | ||
| 321 | .Immediate => |v| v, | ||
| 322 | }; | ||
| 323 | } | ||
| 324 | |||
| 325 | pub fn reg(rm: Register, shift: u8) Offset { | ||
| 326 | return Offset{ | ||
| 327 | .Register = .{ | ||
| 328 | .rm = rm.id(), | ||
| 329 | .shift = shift, | ||
| 330 | }, | ||
| 331 | }; | ||
| 332 | } | ||
| 333 | |||
| 334 | pub fn imm(immediate: u8) Offset { | ||
| 335 | return Offset{ | ||
| 336 | .Immediate = immediate, | ||
| 337 | }; | ||
| 338 | } | ||
| 339 | }; | ||
| 340 | |||
| 341 | pub fn toU32(self: Instruction) u32 { | ||
| 342 | return switch (self) { | ||
| 343 | .DataProcessing => |v| @bitCast(u32, v), | ||
| 344 | .SingleDataTransfer => |v| @bitCast(u32, v), | ||
| 345 | .Branch => |v| @bitCast(u32, v), | ||
| 346 | .BranchExchange => |v| @bitCast(u32, v), | ||
| 347 | .SupervisorCall => |v| @bitCast(u32, v), | ||
| 348 | .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), | ||
| 349 | }; | ||
| 350 | } | ||
| 351 | |||
| 352 | // Helper functions for the "real" functions below | ||
| 353 | |||
| 354 | fn dataProcessing( | ||
| 355 | cond: Condition, | ||
| 356 | opcode: Opcode, | ||
| 357 | s: u1, | ||
| 358 | rd: Register, | ||
| 359 | rn: Register, | ||
| 360 | op2: Operand, | ||
| 361 | ) Instruction { | ||
| 362 | return Instruction{ | ||
| 363 | .DataProcessing = .{ | ||
| 364 | .cond = @enumToInt(cond), | ||
| 365 | .i = if (op2 == .Immediate) 1 else 0, | ||
| 366 | .opcode = @enumToInt(opcode), | ||
| 367 | .s = s, | ||
| 368 | .rn = rn.id(), | ||
| 369 | .rd = rd.id(), | ||
| 370 | .op2 = op2.toU12(), | ||
| 371 | }, | ||
| 372 | }; | ||
| 373 | } | ||
| 374 | |||
| 375 | fn singleDataTransfer( | ||
| 376 | cond: Condition, | ||
| 377 | rd: Register, | ||
| 378 | rn: Register, | ||
| 379 | offset: Offset, | ||
| 380 | pre_post: u1, | ||
| 381 | up_down: u1, | ||
| 382 | byte_word: u1, | ||
| 383 | writeback: u1, | ||
| 384 | load_store: u1, | ||
| 385 | ) Instruction { | ||
| 386 | return Instruction{ | ||
| 387 | .SingleDataTransfer = .{ | ||
| 388 | .cond = @enumToInt(cond), | ||
| 389 | .rn = rn.id(), | ||
| 390 | .rd = rd.id(), | ||
| 391 | .offset = offset.toU12(), | ||
| 392 | .l = load_store, | ||
| 393 | .w = writeback, | ||
| 394 | .b = byte_word, | ||
| 395 | .u = up_down, | ||
| 396 | .p = pre_post, | ||
| 397 | .i = if (offset == .Immediate) 0 else 1, | ||
| 398 | }, | ||
| 399 | }; | ||
| 400 | } | ||
| 401 | |||
| 402 | fn branch(cond: Condition, offset: i24, link: u1) Instruction { | ||
| 403 | return Instruction{ | ||
| 404 | .Branch = .{ | ||
| 405 | .cond = @enumToInt(cond), | ||
| 406 | .link = link, | ||
| 407 | .offset = @bitCast(u24, offset), | ||
| 408 | }, | ||
| 409 | }; | ||
| 410 | } | ||
| 411 | |||
| 412 | fn branchExchange(cond: Condition, rn: Register, link: u1) Instruction { | ||
| 413 | return Instruction{ | ||
| 414 | .BranchExchange = .{ | ||
| 415 | .cond = @enumToInt(cond), | ||
| 416 | .link = link, | ||
| 417 | .rn = rn.id(), | ||
| 418 | }, | ||
| 419 | }; | ||
| 420 | } | ||
| 421 | |||
| 422 | fn supervisorCall(cond: Condition, comment: u24) Instruction { | ||
| 423 | return Instruction{ | ||
| 424 | .SupervisorCall = .{ | ||
| 425 | .cond = @enumToInt(cond), | ||
| 426 | .comment = comment, | ||
| 427 | }, | ||
| 428 | }; | ||
| 429 | } | ||
| 430 | |||
| 431 | fn breakpoint(imm: u16) Instruction { | ||
| 432 | return Instruction{ | ||
| 433 | .Breakpoint = .{ | ||
| 434 | .imm12 = @truncate(u12, imm >> 4), | ||
| 435 | .imm4 = @truncate(u4, imm), | ||
| 436 | }, | ||
| 437 | }; | ||
| 438 | } | ||
| 439 | |||
| 440 | // Public functions replicating assembler syntax as closely as | ||
| 441 | // possible | ||
| 442 | |||
| 443 | // Data processing | ||
| 444 | |||
| 445 | pub fn @"and"(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { | ||
| 446 | return dataProcessing(cond, .@"and", s, rd, rn, op2); | ||
| 447 | } | ||
| 448 | |||
| 449 | pub fn eor(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { | ||
| 450 | return dataProcessing(cond, .eor, s, rd, rn, op2); | ||
| 451 | } | ||
| 452 | |||
| 453 | pub fn sub(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { | ||
| 454 | return dataProcessing(cond, .sub, s, rd, rn, op2); | ||
| 455 | } | ||
| 456 | |||
| 457 | pub fn rsb(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { | ||
| 458 | return dataProcessing(cond, .rsb, s, rd, rn, op2); | ||
| 459 | } | ||
| 460 | |||
| 461 | pub fn add(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { | ||
| 462 | return dataProcessing(cond, .add, s, rd, rn, op2); | ||
| 463 | } | ||
| 464 | |||
| 465 | pub fn adc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { | ||
| 466 | return dataProcessing(cond, .adc, s, rd, rn, op2); | ||
| 467 | } | ||
| 468 | |||
| 469 | pub fn sbc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { | ||
| 470 | return dataProcessing(cond, .sbc, s, rd, rn, op2); | ||
| 471 | } | ||
| 472 | |||
| 473 | pub fn rsc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { | ||
| 474 | return dataProcessing(cond, .rsc, s, rd, rn, op2); | ||
| 475 | } | ||
| 476 | |||
| 477 | pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction { | ||
| 478 | return dataProcessing(cond, .tst, 1, .r0, rn, op2); | ||
| 479 | } | ||
| 480 | |||
| 481 | pub fn teq(cond: Condition, rn: Register, op2: Operand) Instruction { | ||
| 482 | return dataProcessing(cond, .teq, 1, .r0, rn, op2); | ||
| 483 | } | ||
| 484 | |||
| 485 | pub fn cmp(cond: Condition, rn: Register, op2: Operand) Instruction { | ||
| 486 | return dataProcessing(cond, .cmp, 1, .r0, rn, op2); | ||
| 487 | } | ||
| 488 | |||
| 489 | pub fn cmn(cond: Condition, rn: Register, op2: Operand) Instruction { | ||
| 490 | return dataProcessing(cond, .cmn, 1, .r0, rn, op2); | ||
| 491 | } | ||
| 492 | |||
| 493 | pub fn orr(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { | ||
| 494 | return dataProcessing(cond, .orr, s, rd, rn, op2); | ||
| 495 | } | ||
| 496 | |||
| 497 | pub fn mov(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { | ||
| 498 | return dataProcessing(cond, .mov, s, rd, .r0, op2); | ||
| 499 | } | ||
| 500 | |||
| 501 | pub fn bic(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { | ||
| 502 | return dataProcessing(cond, .bic, s, rd, rn, op2); | ||
| 503 | } | ||
| 504 | |||
| 505 | pub fn mvn(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { | ||
| 506 | return dataProcessing(cond, .mvn, s, rd, .r0, op2); | ||
| 507 | } | ||
| 508 | |||
| 509 | // Single data transfer | ||
| 510 | |||
| 511 | pub fn ldr(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction { | ||
| 512 | return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 1); | ||
| 513 | } | ||
| 514 | |||
| 515 | pub fn str(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction { | ||
| 516 | return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 0); | ||
| 517 | } | ||
| 518 | |||
| 519 | // Branch | ||
| 520 | |||
| 521 | pub fn b(cond: Condition, offset: i24) Instruction { | ||
| 522 | return branch(cond, offset, 0); | ||
| 523 | } | ||
| 524 | |||
| 525 | pub fn bl(cond: Condition, offset: i24) Instruction { | ||
| 526 | return branch(cond, offset, 1); | ||
| 527 | } | ||
| 528 | |||
| 529 | // Branch and exchange | ||
| 530 | |||
| 531 | pub fn bx(cond: Condition, rn: Register) Instruction { | ||
| 532 | return branchExchange(cond, rn, 0); | ||
| 533 | } | ||
| 534 | |||
| 535 | pub fn blx(cond: Condition, rn: Register) Instruction { | ||
| 536 | return branchExchange(cond, rn, 1); | ||
| 537 | } | ||
| 538 | |||
| 539 | // Supervisor Call | ||
| 540 | |||
| 541 | pub const swi = svc; | ||
| 542 | |||
| 543 | pub fn svc(cond: Condition, comment: u24) Instruction { | ||
| 544 | return supervisorCall(cond, comment); | ||
| 545 | } | ||
| 546 | |||
| 547 | // Breakpoint | ||
| 548 | |||
| 549 | pub fn bkpt(imm: u16) Instruction { | ||
| 550 | return breakpoint(imm); | ||
| 551 | } | ||
| 552 | }; | ||
| 553 | |||
| 554 | test "serialize instructions" { | ||
| 555 | const Testcase = struct { | ||
| 556 | inst: Instruction, | ||
| 557 | expected: u32, | ||
| 558 | }; | ||
| 559 | |||
| 560 | const testcases = [_]Testcase{ | ||
| 561 | .{ // add r0, r0, r0 | ||
| 562 | .inst = Instruction.add(.al, 0, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)), | ||
| 563 | .expected = 0b1110_00_0_0100_0_0000_0000_00000000_0000, | ||
| 564 | }, | ||
| 565 | .{ // mov r4, r2 | ||
| 566 | .inst = Instruction.mov(.al, 0, .r4, Instruction.Operand.reg(.r2, Instruction.Operand.Shift.none)), | ||
| 567 | .expected = 0b1110_00_0_1101_0_0000_0100_00000000_0010, | ||
| 568 | }, | ||
| 569 | .{ // mov r0, #42 | ||
| 570 | .inst = Instruction.mov(.al, 0, .r0, Instruction.Operand.imm(42, 0)), | ||
| 571 | .expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010, | ||
| 572 | }, | ||
| 573 | .{ // ldr r0, [r2, #42] | ||
| 574 | .inst = Instruction.ldr(.al, .r0, .r2, Instruction.Offset.imm(42)), | ||
| 575 | .expected = 0b1110_01_0_1_1_0_0_1_0010_0000_000000101010, | ||
| 576 | }, | ||
| 577 | .{ // str r0, [r3] | ||
| 578 | .inst = Instruction.str(.al, .r0, .r3, Instruction.Offset.none), | ||
| 579 | .expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000, | ||
| 580 | }, | ||
| 581 | .{ // b #12 | ||
| 582 | .inst = Instruction.b(.al, 12), | ||
| 583 | .expected = 0b1110_101_0_0000_0000_0000_0000_0000_1100, | ||
| 584 | }, | ||
| 585 | .{ // bl #-4 | ||
| 586 | .inst = Instruction.bl(.al, -4), | ||
| 587 | .expected = 0b1110_101_1_1111_1111_1111_1111_1111_1100, | ||
| 588 | }, | ||
| 589 | .{ // bx lr | ||
| 590 | .inst = Instruction.bx(.al, .lr), | ||
| 591 | .expected = 0b1110_0001_0010_1111_1111_1111_0001_1110, | ||
| 592 | }, | ||
| 593 | .{ // svc #0 | ||
| 594 | .inst = Instruction.svc(.al, 0), | ||
| 595 | .expected = 0b1110_1111_0000_0000_0000_0000_0000_0000, | ||
| 596 | }, | ||
| 597 | .{ // bkpt #42 | ||
| 598 | .inst = Instruction.bkpt(42), | ||
| 599 | .expected = 0b1110_0001_0010_000000000010_0111_1010, | ||
| 600 | }, | ||
| 601 | }; | ||
| 602 | |||
| 603 | for (testcases) |case| { | ||
| 604 | const actual = case.inst.toU32(); | ||
| 605 | testing.expectEqual(case.expected, actual); | ||
| 606 | } | ||
| 607 | } | ||
src-self-hosted/type.zig+1| ... | @@ -756,6 +756,7 @@ pub const Type = extern union { | ... | @@ -756,6 +756,7 @@ pub const Type = extern union { |
| 756 | .fn_ccc_void_no_args, // represents machine code; not a pointer | 756 | .fn_ccc_void_no_args, // represents machine code; not a pointer |
| 757 | .function, // represents machine code; not a pointer | 757 | .function, // represents machine code; not a pointer |
| 758 | => return switch (target.cpu.arch) { | 758 | => return switch (target.cpu.arch) { |
| 759 | .arm => 4, | ||
| 759 | .riscv64 => 2, | 760 | .riscv64 => 2, |
| 760 | else => 1, | 761 | else => 1, |
| 761 | }, | 762 | }, |
test/stage2/test.zig+40| ... | @@ -18,6 +18,11 @@ const linux_riscv64 = std.zig.CrossTarget{ | ... | @@ -18,6 +18,11 @@ const linux_riscv64 = std.zig.CrossTarget{ |
| 18 | .os_tag = .linux, | 18 | .os_tag = .linux, |
| 19 | }; | 19 | }; |
| 20 | 20 | ||
| 21 | const linux_arm = std.zig.CrossTarget{ | ||
| 22 | .cpu_arch = .arm, | ||
| 23 | .os_tag = .linux, | ||
| 24 | }; | ||
| 25 | |||
| 21 | const wasi = std.zig.CrossTarget{ | 26 | const wasi = std.zig.CrossTarget{ |
| 22 | .cpu_arch = .wasm32, | 27 | .cpu_arch = .wasm32, |
| 23 | .os_tag = .wasi, | 28 | .os_tag = .wasi, |
| ... | @@ -181,6 +186,41 @@ pub fn addCases(ctx: *TestContext) !void { | ... | @@ -181,6 +186,41 @@ pub fn addCases(ctx: *TestContext) !void { |
| 181 | ); | 186 | ); |
| 182 | } | 187 | } |
| 183 | 188 | ||
| 189 | { | ||
| 190 | var case = ctx.exe("hello world", linux_arm); | ||
| 191 | // Regular old hello world | ||
| 192 | case.addCompareOutput( | ||
| 193 | \\export fn _start() noreturn { | ||
| 194 | \\ print(); | ||
| 195 | \\ exit(); | ||
| 196 | \\} | ||
| 197 | \\ | ||
| 198 | \\fn print() void { | ||
| 199 | \\ asm volatile ("svc #0" | ||
| 200 | \\ : | ||
| 201 | \\ : [number] "{r7}" (4), | ||
| 202 | \\ [arg1] "{r0}" (1), | ||
| 203 | \\ [arg2] "{r1}" (@ptrToInt("Hello, World!\n")), | ||
| 204 | \\ [arg3] "{r2}" (14) | ||
| 205 | \\ : "memory" | ||
| 206 | \\ ); | ||
| 207 | \\ return; | ||
| 208 | \\} | ||
| 209 | \\ | ||
| 210 | \\fn exit() noreturn { | ||
| 211 | \\ asm volatile ("svc #0" | ||
| 212 | \\ : | ||
| 213 | \\ : [number] "{r7}" (1), | ||
| 214 | \\ [arg1] "{r0}" (0) | ||
| 215 | \\ : "memory" | ||
| 216 | \\ ); | ||
| 217 | \\ unreachable; | ||
| 218 | \\} | ||
| 219 | , | ||
| 220 | "Hello, World!\n", | ||
| 221 | ); | ||
| 222 | } | ||
| 223 | |||
| 184 | { | 224 | { |
| 185 | var case = ctx.exe("adding numbers at comptime", linux_x64); | 225 | var case = ctx.exe("adding numbers at comptime", linux_x64); |
| 186 | case.addCompareOutput( | 226 | case.addCompareOutput( |