1const std = @import("std");
2const Target = std.Target;
3const assert = std.debug.assert;
4const expectEqual = std.testing.expectEqual;
5const Writer = std.Io.Writer;
6
7/// Register, one per set of aliasing registers
8pub 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
195test "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
204test "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
213test "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
222test "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}