| 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; |
| 3 | const testing = std.testing; |
| 4 | const Target = std.Target; |
| 5 | |
| 6 | const Zcu = @import("../../Zcu.zig"); |
| 7 | const link = @import("../../link.zig"); |
| 8 | const Mir = @import("Mir.zig"); |
| 9 | const abi = @import("abi.zig"); |
| 10 | |
| 11 | pub const Memory = struct { |
| 12 | base: Base, |
| 13 | mod: Mod, |
| 14 | |
| 15 | pub const Base = union(enum) { |
| 16 | reg: Register, |
| 17 | frame: FrameIndex, |
| 18 | }; |
| 19 | |
| 20 | pub const Mod = struct { |
| 21 | size: Size, |
| 22 | unsigned: bool, |
| 23 | disp: i32 = 0, |
| 24 | }; |
| 25 | |
| 26 | pub const Size = enum(u4) { |
| 27 | /// Byte, 1 byte |
| 28 | byte, |
| 29 | /// Half word, 2 bytes |
| 30 | hword, |
| 31 | /// Word, 4 bytes |
| 32 | word, |
| 33 | /// Double word, 8 Bytes |
| 34 | dword, |
| 35 | |
| 36 | pub fn fromByteSize(size: u64) Size { |
| 37 | return switch (size) { |
| 38 | 1...1 => .byte, |
| 39 | 2...2 => .hword, |
| 40 | 3...4 => .word, |
| 41 | 5...8 => .dword, |
| 42 | else => std.debug.panic("fromByteSize {}", .{size}), |
| 43 | }; |
| 44 | } |
| 45 | |
| 46 | pub fn fromBitSize(bit_size: u64) Size { |
| 47 | return switch (bit_size) { |
| 48 | 8 => .byte, |
| 49 | 16 => .hword, |
| 50 | 32 => .word, |
| 51 | 64 => .dword, |
| 52 | else => unreachable, |
| 53 | }; |
| 54 | } |
| 55 | |
| 56 | pub fn bitSize(s: Size) u64 { |
| 57 | return switch (s) { |
| 58 | .byte => 8, |
| 59 | .hword => 16, |
| 60 | .word => 32, |
| 61 | .dword => 64, |
| 62 | }; |
| 63 | } |
| 64 | }; |
| 65 | |
| 66 | /// Asserts `mem` can be represented as a `FrameLoc`. |
| 67 | pub fn toFrameLoc(mem: Memory, mir: Mir) Mir.FrameLoc { |
| 68 | const offset: i32 = mem.mod.disp; |
| 69 | |
| 70 | switch (mem.base) { |
| 71 | .reg => |reg| { |
| 72 | return .{ |
| 73 | .base = reg, |
| 74 | .disp = offset, |
| 75 | }; |
| 76 | }, |
| 77 | .frame => |index| { |
| 78 | const base_loc = mir.frame_locs.get(@backingInt(index)); |
| 79 | return .{ |
| 80 | .base = base_loc.base, |
| 81 | .disp = base_loc.disp + offset, |
| 82 | }; |
| 83 | }, |
| 84 | } |
| 85 | } |
| 86 | }; |
| 87 | |
| 88 | pub const Immediate = union(enum) { |
| 89 | signed: i32, |
| 90 | unsigned: u64, |
| 91 | |
| 92 | pub fn u(x: u64) Immediate { |
| 93 | return .{ .unsigned = x }; |
| 94 | } |
| 95 | |
| 96 | pub fn s(x: i32) Immediate { |
| 97 | return .{ .signed = x }; |
| 98 | } |
| 99 | |
| 100 | pub fn asSigned(imm: Immediate, bit_size: u64) i64 { |
| 101 | return switch (imm) { |
| 102 | .signed => |x| switch (bit_size) { |
| 103 | 1, 8 => @as(i8, @intCast(x)), |
| 104 | 16 => @as(i16, @intCast(x)), |
| 105 | 32, 64 => x, |
| 106 | else => unreachable, |
| 107 | }, |
| 108 | .unsigned => |x| switch (bit_size) { |
| 109 | 1, 8 => @as(i8, @bitCast(@as(u8, @intCast(x)))), |
| 110 | 16 => @as(i16, @bitCast(@as(u16, @intCast(x)))), |
| 111 | 32 => @as(i32, @bitCast(@as(u32, @intCast(x)))), |
| 112 | 64 => @bitCast(x), |
| 113 | else => unreachable, |
| 114 | }, |
| 115 | }; |
| 116 | } |
| 117 | |
| 118 | pub fn asBits(imm: Immediate, comptime T: type) T { |
| 119 | const int_info = @typeInfo(T).int; |
| 120 | if (int_info.signedness != .unsigned) @compileError("Immediate.asBits needs unsigned T"); |
| 121 | return switch (imm) { |
| 122 | .signed => |x| @bitCast(@as(@Int(.signed, int_info.bits), @intCast(x))), |
| 123 | .unsigned => |x| @intCast(x), |
| 124 | }; |
| 125 | } |
| 126 | }; |
| 127 | |
| 128 | pub const CSR = enum(u12) { |
| 129 | vl = 0xC20, |
| 130 | vtype = 0xC21, |
| 131 | vlenb = 0xC22, |
| 132 | }; |
| 133 | |
| 134 | pub const Register = enum(u8) { |
| 135 | // zig fmt: off |
| 136 | |
| 137 | // base extension registers |
| 138 | |
| 139 | zero, // zero |
| 140 | ra, // return address. caller saved |
| 141 | sp, // stack pointer. callee saved. |
| 142 | gp, // global pointer |
| 143 | tp, // thread pointer |
| 144 | t0, t1, t2, // temporaries. caller saved. |
| 145 | s0, // s0/fp, callee saved. |
| 146 | s1, // callee saved. |
| 147 | a0, a1, // fn args/return values. caller saved. |
| 148 | a2, a3, a4, a5, a6, a7, // fn args. caller saved. |
| 149 | s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, // saved registers. callee saved. |
| 150 | t3, t4, t5, t6, // caller saved |
| 151 | |
| 152 | x0, x1, x2, x3, x4, x5, x6, x7, |
| 153 | x8, x9, x10, x11, x12, x13, x14, x15, |
| 154 | x16, x17, x18, x19, x20, x21, x22, x23, |
| 155 | x24, x25, x26, x27, x28, x29, x30, x31, |
| 156 | |
| 157 | |
| 158 | // F extension registers |
| 159 | |
| 160 | ft0, ft1, ft2, ft3, ft4, ft5, ft6, ft7, // float temporaries. caller saved. |
| 161 | fs0, fs1, // float saved. callee saved. |
| 162 | fa0, fa1, // float arg/ret. caller saved. |
| 163 | fa2, fa3, fa4, fa5, fa6, fa7, // float arg. called saved. |
| 164 | fs2, fs3, fs4, fs5, fs6, fs7, fs8, fs9, fs10, fs11, // float saved. callee saved. |
| 165 | ft8, ft9, ft10, ft11, // foat temporaries. calller saved. |
| 166 | |
| 167 | // this register is accessed only through API instructions instead of directly |
| 168 | // fcsr, |
| 169 | |
| 170 | f0, f1, f2, f3, f4, f5, f6, f7, |
| 171 | f8, f9, f10, f11, f12, f13, f14, f15, |
| 172 | f16, f17, f18, f19, f20, f21, f22, f23, |
| 173 | f24, f25, f26, f27, f28, f29, f30, f31, |
| 174 | |
| 175 | |
| 176 | // V extension registers |
| 177 | v0, v1, v2, v3, v4, v5, v6, v7, |
| 178 | v8, v9, v10, v11, v12, v13, v14, v15, |
| 179 | v16, v17, v18, v19, v20, v21, v22, v23, |
| 180 | v24, v25, v26, v27, v28, v29, v30, v31, |
| 181 | |
| 182 | // zig fmt: on |
| 183 | |
| 184 | /// in RISC-V registers are stored as 5 bit IDs and a register can have |
| 185 | /// two names. Example being `zero` and `x0` are the same register and have the |
| 186 | /// same ID, but are two different entries in the enum. We store floating point |
| 187 | /// registers in the same enum. RISC-V uses the same IDs for `f0` and `x0` by |
| 188 | /// infering which register is being talked about given the instruction it's in. |
| 189 | /// |
| 190 | /// The goal of this function is to return the same ID for `zero` and `x0` but two |
| 191 | /// seperate IDs for `x0` and `f0`. We will assume that each register set has 32 registers |
| 192 | /// and is repeated twice, once for the named version, once for the number version. |
| 193 | pub fn id(reg: Register) std.math.IntFittingRange(0, @typeInfo(Register).@"enum".field_names.len) { |
| 194 | const base = switch (@backingInt(reg)) { |
| 195 | // zig fmt: off |
| 196 | @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => @intFromEnum(Register.zero), |
| 197 | @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => @intFromEnum(Register.ft0), |
| 198 | @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => @intFromEnum(Register.v0), |
| 199 | else => unreachable, |
| 200 | // zig fmt: on |
| 201 | }; |
| 202 | |
| 203 | return @intCast(base + reg.encodeId()); |
| 204 | } |
| 205 | |
| 206 | pub fn encodeId(reg: Register) u5 { |
| 207 | return @truncate(@backingInt(reg)); |
| 208 | } |
| 209 | |
| 210 | pub fn dwarfNum(reg: Register) u8 { |
| 211 | return reg.id(); |
| 212 | } |
| 213 | |
| 214 | pub fn bitSize(reg: Register, zcu: *const Zcu) u32 { |
| 215 | return switch (@backingInt(reg)) { |
| 216 | // zig fmt: off |
| 217 | @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => 64, |
| 218 | @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => if (zcu.getTarget().cpu.has(.riscv, .d)) 64 else 32, |
| 219 | @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => 256, // TODO: look at suggestVectorSize |
| 220 | else => unreachable, |
| 221 | // zig fmt: on |
| 222 | }; |
| 223 | } |
| 224 | |
| 225 | pub fn class(reg: Register) abi.RegisterClass { |
| 226 | return switch (@backingInt(reg)) { |
| 227 | // zig fmt: off |
| 228 | @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => .int, |
| 229 | @intFromEnum(Register.ft0) ... @intFromEnum(Register.f31) => .float, |
| 230 | @intFromEnum(Register.v0) ... @intFromEnum(Register.v31) => .vector, |
| 231 | else => unreachable, |
| 232 | // zig fmt: on |
| 233 | }; |
| 234 | } |
| 235 | }; |
| 236 | |
| 237 | pub const FrameIndex = enum(u32) { |
| 238 | /// This index refers to the return address. |
| 239 | ret_addr, |
| 240 | /// This index refers to the frame pointer. |
| 241 | base_ptr, |
| 242 | /// This index refers to the entire stack frame. |
| 243 | stack_frame, |
| 244 | /// This index referes to where in the stack frame the args are spilled to. |
| 245 | args_frame, |
| 246 | /// This index referes to a frame dedicated to setting up args for function called |
| 247 | /// in this function. Useful for aligning args separately. |
| 248 | call_frame, |
| 249 | /// This index referes to the frame where callee saved registers are spilled and restored from. |
| 250 | spill_frame, |
| 251 | /// Other indices are used for local variable stack slots |
| 252 | _, |
| 253 | |
| 254 | pub const named_count = @typeInfo(FrameIndex).@"enum".field_names.len; |
| 255 | |
| 256 | pub fn isNamed(fi: FrameIndex) bool { |
| 257 | return @backingInt(fi) < named_count; |
| 258 | } |
| 259 | }; |
| 260 | |
| 261 | /// A linker symbol not yet allocated in VM. |
| 262 | pub const Symbol = struct { |
| 263 | /// Index of the containing atom. |
| 264 | atom_index: link.File.AtomId, |
| 265 | /// Index into the linker's symbol table. |
| 266 | sym_index: link.File.SymbolId, |
| 267 | }; |
| 268 | |
| 269 | pub const VType = packed struct(u8) { |
| 270 | vlmul: VlMul, |
| 271 | vsew: VSew, |
| 272 | vta: bool, |
| 273 | vma: bool, |
| 274 | }; |
| 275 | |
| 276 | const VSew = enum(u3) { |
| 277 | @"8" = 0b000, |
| 278 | @"16" = 0b001, |
| 279 | @"32" = 0b010, |
| 280 | @"64" = 0b011, |
| 281 | }; |
| 282 | |
| 283 | const VlMul = enum(u3) { |
| 284 | mf8 = 0b101, |
| 285 | mf4 = 0b110, |
| 286 | mf2 = 0b111, |
| 287 | m1 = 0b000, |
| 288 | m2 = 0b001, |
| 289 | m4 = 0b010, |
| 290 | m8 = 0b011, |
| 291 | }; |