| 1 | const std = @import("std"); |
| 2 | const Target = std.Target; |
| 3 | const assert = std.debug.assert; |
| 4 | const expectEqual = std.testing.expectEqual; |
| 5 | const Writer = std.Io.Writer; |
| 6 | |
| 7 | /// Register, one per set of aliasing registers |
| 8 | pub const Register = enum(u7) { |
| 9 | // zig fmt: off |
| 10 | // integer registers |
| 11 | r0, r1, r2, r3, r4, r5, r6, r7, |
| 12 | r8, r9, r10, r11, r12, r13, r14, r15, |
| 13 | r16, r17, r18, r19, r20, r21, r22, r23, |
| 14 | r24, r25, r26, r27, r28, r29, r30, r31, |
| 15 | |
| 16 | // float-point/LSX/LASX registers |
| 17 | f0, f1, f2, f3, f4, f5, f6, f7, |
| 18 | f8, f9, f10, f11, f12, f13, f14, f15, |
| 19 | f16, f17, f18, f19, f20, f21, f22, f23, |
| 20 | f24, f25, f26, f27, f28, f29, f30, f31, |
| 21 | |
| 22 | // float-point condition code registers |
| 23 | fcc0, fcc1, fcc2, fcc3, fcc4, fcc5, fcc6, fcc7, |
| 24 | // zig fmt: on |
| 25 | |
| 26 | pub const zero: Register = .r0; |
| 27 | pub const ra: Register = .r1; |
| 28 | pub const tp: Register = .r2; |
| 29 | pub const sp: Register = .r3; |
| 30 | pub const fp: Register = .r22; |
| 31 | pub const t0: Register = .r12; |
| 32 | |
| 33 | /// Register banks. |
| 34 | pub const Class = enum { int, fp, fcc }; |
| 35 | |
| 36 | /// Register accessing modifier. |
| 37 | pub const Modifier = enum(u3) { |
| 38 | undef, |
| 39 | integer, |
| 40 | floating32, |
| 41 | floating64, |
| 42 | lsx, |
| 43 | lasx, |
| 44 | fcc, |
| 45 | |
| 46 | pub fn class(modifier: Modifier) Class { |
| 47 | return switch (modifier) { |
| 48 | .undef => unreachable, |
| 49 | .integer => .int, |
| 50 | .floating32, .floating64, .lsx, .lasx => .fp, |
| 51 | .fcc => .fcc, |
| 52 | }; |
| 53 | } |
| 54 | |
| 55 | pub fn bitSize(modifier: Modifier, target: *const Target) u16 { |
| 56 | return switch (modifier) { |
| 57 | .undef => 0, |
| 58 | .integer => switch (target.cpu.arch) { |
| 59 | .loongarch32 => 32, |
| 60 | .loongarch64 => 64, |
| 61 | else => unreachable, |
| 62 | }, |
| 63 | .floating32 => 32, |
| 64 | .floating64 => 64, |
| 65 | .lsx => 128, |
| 66 | .lasx => 256, |
| 67 | .fcc => 1, |
| 68 | }; |
| 69 | } |
| 70 | |
| 71 | /// Upper-rounded byte size. |
| 72 | pub fn byteSize(modifier: Modifier, target: *const Target) u16 { |
| 73 | return switch (modifier) { |
| 74 | .undef => 0, |
| 75 | .integer => switch (target.cpu.arch) { |
| 76 | .loongarch32 => 4, |
| 77 | .loongarch64 => 8, |
| 78 | else => unreachable, |
| 79 | }, |
| 80 | .floating32 => 4, |
| 81 | .floating64 => 8, |
| 82 | .lsx => 16, |
| 83 | .lasx => 32, |
| 84 | .fcc => 1, |
| 85 | }; |
| 86 | } |
| 87 | |
| 88 | pub fn fromFloatBits(bits: u16) Modifier { |
| 89 | return switch (bits) { |
| 90 | else => unreachable, |
| 91 | 32 => .floating32, |
| 92 | 64 => .floating64, |
| 93 | }; |
| 94 | } |
| 95 | }; |
| 96 | |
| 97 | pub const Alias = struct { |
| 98 | reg: Register, |
| 99 | mod: Modifier, |
| 100 | |
| 101 | pub const zero: Alias = .{ .mod = .integer, .reg = .zero }; |
| 102 | |
| 103 | pub fn format(self: Alias, w: *std.Io.Writer) std.Io.Writer.Error!void { |
| 104 | try w.print("${s}{d}", .{ |
| 105 | switch (self.mod) { |
| 106 | .undef => "?", |
| 107 | .integer => "r", |
| 108 | .floating32 => "(s)f", |
| 109 | .floating64 => "(d)f", |
| 110 | .lsx => "v", |
| 111 | .lasx => "x", |
| 112 | .fcc => "fcc", |
| 113 | }, |
| 114 | self.reg.encode(), |
| 115 | }); |
| 116 | } |
| 117 | }; |
| 118 | |
| 119 | pub fn class(reg: Register) Class { |
| 120 | return switch (@backingInt(reg)) { |
| 121 | @backingInt(Register.r0)...@backingInt(Register.r31) => .int, |
| 122 | @backingInt(Register.f0)...@backingInt(Register.f31) => .fp, |
| 123 | @backingInt(Register.fcc0)...@backingInt(Register.fcc7) => .fcc, |
| 124 | else => unreachable, |
| 125 | }; |
| 126 | } |
| 127 | |
| 128 | pub fn encode(reg: Register) u5 { |
| 129 | const base: u7 = switch (@backingInt(reg)) { |
| 130 | @backingInt(Register.r0)...@backingInt(Register.r31) => @backingInt(Register.r0), |
| 131 | @backingInt(Register.f0)...@backingInt(Register.f31) => @backingInt(Register.f0), |
| 132 | @backingInt(Register.fcc0)...@backingInt(Register.fcc7) => @backingInt(Register.fcc0), |
| 133 | else => unreachable, |
| 134 | }; |
| 135 | return @intCast(@backingInt(reg) - base); |
| 136 | } |
| 137 | |
| 138 | pub fn decode(reg_class: Class, reg: u5) Register { |
| 139 | const base: u7 = switch (reg_class) { |
| 140 | .int => @backingInt(Register.r0), |
| 141 | .fp => @backingInt(Register.f0), |
| 142 | .fcc => @backingInt(Register.fcc0), |
| 143 | }; |
| 144 | return @fromBackingInt(base + @as(u7, reg)); |
| 145 | } |
| 146 | |
| 147 | pub fn parse(reg: []const u8) ?Register { |
| 148 | if (reg.len == 0) return null; |
| 149 | if (reg[0] == '$') return parse(reg[1..]); |
| 150 | if (toLowerEqlAssertLower(reg, "zero")) return .zero; |
| 151 | if (toLowerEqlAssertLower(reg, "ra")) return .ra; |
| 152 | if (toLowerEqlAssertLower(reg, "tp")) return .tp; |
| 153 | if (toLowerEqlAssertLower(reg, "sp")) return .sp; |
| 154 | if (toLowerEqlAssertLower(reg, "fp")) return .fp; |
| 155 | return switch (std.ascii.toLower(reg[0])) { |
| 156 | else => null, |
| 157 | 'r' => reg: { |
| 158 | break :reg if (std.fmt.parseInt(u5, reg[1..], 10)) |n| .decode(.int, n) else |_| null; |
| 159 | }, |
| 160 | 'f' => reg: { |
| 161 | if (reg.len == 4 and toLowerEqlAssertLower(reg[0..3], "fcc")) |
| 162 | break :reg if (std.ascii.isDigit(reg[3])) .decode(.fcc, @intCast(reg[3] ^ '0')) else null; |
| 163 | if (reg.len > 2 and toLowerEqlAssertLower(reg[0..2], "fa")) |
| 164 | break :reg if (std.fmt.parseInt(u5, reg[2..], 10)) |n| .decode(.fp, n) else |_| null; |
| 165 | if (reg.len > 2 and toLowerEqlAssertLower(reg[0..2], "ft")) |
| 166 | break :reg if (std.fmt.parseInt(u5, reg[2..], 10)) |n| .decode(.fp, 8 + n) else |_| null; |
| 167 | if (reg.len > 2 and toLowerEqlAssertLower(reg[0..2], "fs")) |
| 168 | break :reg if (std.fmt.parseInt(u5, reg[2..], 10)) |n| .decode(.fp, 24 + n) else |_| null; |
| 169 | |
| 170 | break :reg if (std.fmt.parseInt(u5, reg[1..], 10)) |n| .decode(.fp, n) else |_| null; |
| 171 | }, |
| 172 | 'v', 'x' => reg: { |
| 173 | if (reg.len < 3 or std.ascii.toLower(reg[1]) != 'r') break :reg null; |
| 174 | break :reg if (std.fmt.parseInt(u5, reg[2..], 10)) |n| .decode(.fp, n) else |_| null; |
| 175 | }, |
| 176 | 'a' => if (std.fmt.parseInt(u5, reg[1..], 10)) |n| .decode(.int, 4 + n) else |_| null, |
| 177 | 't' => if (std.fmt.parseInt(u5, reg[1..], 10)) |n| .decode(.int, 12 + n) else |_| null, |
| 178 | 's' => if (std.fmt.parseInt(u5, reg[1..], 10)) |n| reg: { |
| 179 | if (n == 9) break :reg .r22; |
| 180 | break :reg .decode(.int, 23 + n); |
| 181 | } else |_| null, |
| 182 | }; |
| 183 | } |
| 184 | |
| 185 | fn toLowerEqlAssertLower(lhs: []const u8, rhs: []const u8) bool { |
| 186 | if (lhs.len != rhs.len) return false; |
| 187 | for (lhs, rhs) |l, r| { |
| 188 | assert(!std.ascii.isUpper(r)); |
| 189 | if (std.ascii.toLower(l) != r) return false; |
| 190 | } |
| 191 | return true; |
| 192 | } |
| 193 | }; |
| 194 | |
| 195 | test "register classes" { |
| 196 | try expectEqual(.int, Register.r0.class()); |
| 197 | try expectEqual(.int, Register.r31.class()); |
| 198 | try expectEqual(.fp, Register.f0.class()); |
| 199 | try expectEqual(.fp, Register.f31.class()); |
| 200 | try expectEqual(.fcc, Register.fcc0.class()); |
| 201 | try expectEqual(.fcc, Register.fcc7.class()); |
| 202 | } |
| 203 | |
| 204 | test "register encoding" { |
| 205 | try expectEqual(0, Register.r0.encode()); |
| 206 | try expectEqual(31, Register.r31.encode()); |
| 207 | try expectEqual(0, Register.f0.encode()); |
| 208 | try expectEqual(31, Register.f31.encode()); |
| 209 | try expectEqual(0, Register.fcc0.encode()); |
| 210 | try expectEqual(7, Register.fcc7.encode()); |
| 211 | } |
| 212 | |
| 213 | test "register decoding" { |
| 214 | try expectEqual(.r0, Register.decode(.int, 0)); |
| 215 | try expectEqual(.r31, Register.decode(.int, 31)); |
| 216 | try expectEqual(.f0, Register.decode(.fp, 0)); |
| 217 | try expectEqual(.f31, Register.decode(.fp, 31)); |
| 218 | try expectEqual(.fcc0, Register.decode(.fcc, 0)); |
| 219 | try expectEqual(.fcc7, Register.decode(.fcc, 7)); |
| 220 | } |
| 221 | |
| 222 | test "register parsing" { |
| 223 | try expectEqual(.r0, Register.parse("r0").?); |
| 224 | try expectEqual(.r0, Register.parse("ZERO").?); |
| 225 | try expectEqual(.r0, Register.parse("zero").?); |
| 226 | try expectEqual(.r0, Register.parse("$zero").?); |
| 227 | try expectEqual(Register.ra, Register.parse("ra").?); |
| 228 | try expectEqual(Register.tp, Register.parse("tp").?); |
| 229 | try expectEqual(Register.sp, Register.parse("sp").?); |
| 230 | try expectEqual(Register.fp, Register.parse("fp").?); |
| 231 | try expectEqual(.r7, Register.parse("a3").?); |
| 232 | try expectEqual(.r15, Register.parse("t3").?); |
| 233 | try expectEqual(.r26, Register.parse("s3").?); |
| 234 | try expectEqual(.r22, Register.parse("s9").?); |
| 235 | try expectEqual(.fcc0, Register.parse("fcc0").?); |
| 236 | try expectEqual(.fcc7, Register.parse("fcc7").?); |
| 237 | try expectEqual(.f0, Register.parse("f0").?); |
| 238 | try expectEqual(.f3, Register.parse("fa3").?); |
| 239 | try expectEqual(.f11, Register.parse("ft3").?); |
| 240 | try expectEqual(.f27, Register.parse("fs3").?); |
| 241 | try expectEqual(.f0, Register.parse("vr0").?); |
| 242 | try expectEqual(.f0, Register.parse("xr0").?); |
| 243 | } |