1//! Parser for format description files in
2//! https://github.com/loongson-community/loongarch-opcodes.
3
4const std = @import("std");
5const mem = std.mem;
6const Allocator = mem.Allocator;
7const Reader = std.Io.Reader;
8
9const OpcodeDesc = @This();
10
11/// Maximum number of slots in one instruction format.
12const max_slots = 4;
13
14opcode: std.ArrayList(Opcode) = .empty,
15format_pool: std.heap.MemoryPool(Format) = .empty,
16format: std.StringArrayHashMapUnmanaged(*Format) = .empty,
17
18pub fn deinit(desc: *OpcodeDesc, gpa: Allocator) void {
19 desc.opcode.deinit(gpa);
20 desc.format.deinit(gpa);
21 desc.format_pool.deinit(gpa);
22}
23
24/// Instruction format. Slots are filled one by one, ending with reaching max_slots or a .none slot.
25pub const Format = struct {
26 name: []const u8,
27 slots: [max_slots]Slot,
28
29 pub fn parse(name: []const u8) !Format {
30 var format: Format = .{
31 .name = name,
32 .slots = .{ .none, .none, .none, .none },
33 };
34 var reader: Reader = .fixed(name);
35 var slot_index: std.math.IntFittingRange(0, max_slots) = 0;
36 parse_empty: {
37 const str = reader.peekArray(5) catch |err| switch (err) {
38 error.EndOfStream => break :parse_empty,
39 else => return err,
40 };
41 if (mem.eql(u8, &str.*, "EMPTY"))
42 return format;
43 }
44
45 parse_slots: while (slot_index < max_slots) : (slot_index += 1) {
46 switch (reader.takeByte() catch |err| switch (err) {
47 error.EndOfStream => break :parse_slots,
48 else => return err,
49 }) {
50 'D' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
51 .class = .int,
52 .index = .d,
53 } } },
54 'J' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
55 .class = .int,
56 .index = .j,
57 } } },
58 'K' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
59 .class = .int,
60 .index = .k,
61 } } },
62 'A' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
63 .class = .int,
64 .index = .a,
65 } } },
66 'F' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
67 .class = .fp,
68 .index = try .parse(&reader),
69 } } },
70 'C' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
71 .class = .fcc,
72 .index = try .parse(&reader),
73 } } },
74 'T' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
75 .class = .lbt_scratch,
76 .index = try .parse(&reader),
77 } } },
78 'V' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
79 .class = .lsx,
80 .index = try .parse(&reader),
81 } } },
82 'X' => format.slots[slot_index] = .{ .tag = .reg, .payload = .{ .reg = .{
83 .class = .lasx,
84 .index = try .parse(&reader),
85 } } },
86 'S', 'U' => |signedness_ch| {
87 const signedness: std.builtin.Signedness = if (signedness_ch == 'S') .signed else .unsigned;
88 while (slot_index < max_slots and continue_imm_slot: {
89 _ = Slot.Index.fromChar(reader.peekByte() catch |err| switch (err) {
90 error.EndOfStream => break :continue_imm_slot false,
91 else => return err,
92 }) catch break :continue_imm_slot false;
93 break :continue_imm_slot true;
94 }) : (slot_index += 1) {
95 const index: Slot.Index = try .parse(&reader);
96 const length = try takeInteger(u5, &reader);
97 const post_proc = post_proc: {
98 if ('p' == reader.peekByte() catch |err| switch (err) {
99 error.EndOfStream => ' ',
100 else => return err,
101 }) {
102 reader.toss(1);
103 break :post_proc try Slot.PostProcess.parse(&reader);
104 } else break :post_proc Slot.PostProcess.none;
105 };
106 format.slots[slot_index] = .{ .tag = .imm, .payload = .{ .imm = .{
107 .index = index,
108 .length = length,
109 .signedness = signedness,
110 .post_proc = post_proc,
111 } } };
112 }
113 slot_index -= 1;
114 },
115 else => return error.InvalidCharacter,
116 }
117 }
118
119 return format;
120 }
121};
122
123test "parse format" {
124 _ = try Format.parse("DJFmSk12m13ps3");
125 _ = try Format.parse("DJSk12m13ps3U16pp1");
126 _ = try Format.parse("DJK");
127}
128
129pub const Slot = packed struct {
130 tag: Slot.Tag,
131 payload: Slot.Payload,
132
133 comptime {
134 std.debug.assert(@sizeOf(Slot) == 4);
135 }
136
137 const Payload = packed union {
138 none: u16, // unused number, just for padding
139 imm: packed struct {
140 index: Index,
141 length: u5,
142 signedness: std.builtin.Signedness,
143 post_proc: PostProcess = .none,
144 },
145 reg: packed struct {
146 class: enum(u13) { int, fp, fcc, lbt_scratch, lsx, lasx },
147 index: Index,
148 },
149 };
150
151 const Tag = enum(u16) { reg, imm, none };
152
153 pub const none: Slot = .{ .tag = .none, .payload = .{ .none = 0 } };
154
155 pub const Index = enum(u3) {
156 // zig fmt: off
157 d, j, k, a, m, n,
158 // zig fmt: on
159
160 pub fn offset(index: Index) u5 {
161 return switch (index) {
162 .d => 0,
163 .j => 5,
164 .k => 10,
165 .a => 15,
166 .m => 16,
167 .n => 18,
168 };
169 }
170
171 pub fn fromChar(ch: u8) error{UnknownIndexChar}!Index {
172 return switch (ch) {
173 'd' => .d,
174 'j' => .j,
175 'k' => .k,
176 'a' => .a,
177 'm' => .m,
178 'n' => .n,
179 else => return error.UnknownIndexChar,
180 };
181 }
182
183 pub const ParseError = Reader.Error || error{UnknownIndexChar};
184 pub fn parse(reader: *Reader) Index.ParseError!Index {
185 return fromChar(try reader.takeByte());
186 }
187 };
188
189 /// Post-process operations for disassemblying.
190 pub const PostProcess = packed struct {
191 tag: PostProcess.Tag,
192 payload: PostProcess.Payload,
193
194 const Payload = packed union {
195 /// assembly value = encoded value + N
196 add: u5,
197 /// assembly value = encoded value << N
198 shl: u5,
199 none: u5, // unused number, for padding
200 };
201
202 const Tag = std.meta.FieldEnum(PostProcess.Payload);
203
204 pub const none: PostProcess = .{ .tag = .none, .payload = .{ .none = 0 } };
205
206 pub const ParseError = Reader.Error || std.fmt.ParseIntError;
207 pub fn parse(reader: *Reader) PostProcess.ParseError!PostProcess {
208 switch (try reader.takeByte()) {
209 'p' => return .{
210 .tag = .add,
211 .payload = .{ .add = try takeInteger(u4, reader) },
212 },
213 's' => return .{
214 .tag = .shl,
215 .payload = .{ .shl = try takeInteger(u4, reader) },
216 },
217 else => return error.InvalidCharacter,
218 }
219 }
220 };
221
222 pub fn offset(slot: Slot) u5 {
223 return switch (slot.tag) {
224 .none => unreachable,
225 .imm => slot.payload.imm.index.offset(),
226 .reg => slot.payload.reg.index.offset(),
227 };
228 }
229
230 pub fn width(slot: Slot) u5 {
231 return switch (slot.tag) {
232 .none => unreachable,
233 .imm => slot.payload.imm.length,
234 .reg => switch (slot.payload.reg.class) {
235 .fcc => 3,
236 else => 5,
237 },
238 };
239 }
240
241 pub fn mask(slot: Slot) u32 {
242 const off = slot.offset();
243 const size = slot.width();
244 const msb, const overflow = @addWithOverflow(off, size);
245 if (overflow == 1) {
246 @branchHint(.unlikely);
247 return ~((@as(u32, 1) << off) - 1);
248 }
249 return ((@as(u32, 1) << msb) - 1) ^ ((@as(u32, 1) << off) - 1);
250 }
251};
252
253test "mask" {
254 try std.testing.expectEqual(0b111100000, (Slot{ .tag = .imm, .payload = .{ .imm = .{
255 .index = .j,
256 .length = 4,
257 .signedness = .unsigned,
258 } } }).mask());
259 try std.testing.expectEqual(0x7fffffff, (Slot{ .tag = .imm, .payload = .{ .imm = .{
260 .index = .d,
261 .length = 31,
262 .signedness = .unsigned,
263 } } }).mask());
264 try std.testing.expectEqual(0x7fffffff, (Slot{ .tag = .imm, .payload = .{ .imm = .{
265 .index = .d,
266 .length = 31,
267 .signedness = .unsigned,
268 } } }).mask());
269 try std.testing.expectEqual(0xffffffe0, (Slot{ .tag = .imm, .payload = .{ .imm = .{
270 .index = .j,
271 .length = 27,
272 .signedness = .unsigned,
273 } } }).mask());
274 try std.testing.expectEqual(0b111110000000000, (Slot{ .tag = .reg, .payload = .{ .reg = .{
275 .class = .int,
276 .index = .k,
277 } } }).mask());
278}
279
280fn takeInteger(comptime T: type, reader: *Reader) (Reader.Error || std.fmt.ParseIntError)!T {
281 if (std.math.maxInt(T) < 10) {
282 const ch = try reader.takeByte();
283 return std.math.cast(T, ch ^ '0') orelse return error.Overflow;
284 }
285 var v: T = 0;
286
287 var ch: u8 = try reader.peekByte();
288 if (!std.ascii.isDigit(ch)) return error.InvalidCharacter;
289
290 while (std.ascii.isDigit(ch)) : (ch = reader.peekByte() catch |err| switch (err) {
291 error.EndOfStream => break,
292 else => return err,
293 }) {
294 v = try std.math.add(
295 T,
296 try std.math.add(
297 T,
298 try std.math.shlExact(T, v, 3),
299 try std.math.shlExact(T, v, 1),
300 ),
301 std.math.cast(T, ch ^ '0') orelse return error.Overflow,
302 );
303 reader.toss(1);
304 }
305 return v;
306}
307
308test takeInteger {
309 var reader: std.Io.Reader = undefined;
310
311 reader = .fixed("123");
312 try std.testing.expectEqual(123, try takeInteger(u8, &reader));
313 reader = .fixed("123ignored");
314 try std.testing.expectEqual(123, try takeInteger(u8, &reader));
315 reader = .fixed("bad");
316 try std.testing.expectError(error.InvalidCharacter, takeInteger(u8, &reader));
317 reader = .fixed("1");
318 try std.testing.expectEqual(1, try takeInteger(u1, &reader));
319}
320
321pub const Opcode = struct {
322 word: u32,
323 name: []const u8,
324 format: *Format,
325 orig_name: []const u8,
326 orig_format: *Format,
327 required_features: RequiredFeatures,
328
329 pub const RequiredFeatures = packed struct {
330 @"32bit": bool = false,
331 @"32s": bool = false,
332 @"64bit": bool = false,
333 f: bool = false,
334 d: bool = false,
335 lsx: bool = false,
336 lasx: bool = false,
337 lbt: bool = false,
338 lvz: bool = false,
339 };
340};
341
342/// Parses a opcode data file.
343/// Caller owns the data string and the data string must live longer
344/// than the OpcodeDesc.
345pub fn parse(desc: *OpcodeDesc, gpa: Allocator, data: []const u8) !void {
346 var lines = mem.tokenizeScalar(u8, data, '\n');
347 while (lines.next()) |line| {
348 if (line[0] == '#') continue; // skip comments, not used by upstream but used in tools/loongarch/extra.txt
349 var tokens = mem.tokenizeScalar(u8, line, ' ');
350
351 const word_buf = tokens.next() orelse return error.UnexpectedEol;
352 const word = try std.fmt.parseInt(u32, word_buf, 16);
353 const name = tokens.next() orelse return error.UnexpectedEol;
354 const format_str = tokens.next() orelse return error.UnexpectedEol;
355 const format = try desc.getOrParseFormat(gpa, format_str);
356
357 const opcode = try desc.opcode.addOne(gpa);
358 opcode.* = .{
359 .word = word,
360 .name = name,
361 .format = format,
362 .orig_name = name,
363 .orig_format = format,
364 .required_features = .{},
365 };
366
367 // parse attributes
368 while (tokens.next()) |attr| {
369 if (attr[0] != '@') return error.MalformedAttribute;
370 if (mem.indexOfScalar(u8, attr, '=')) |eql_pos| {
371 const attr_name = attr[1..][0 .. eql_pos - 1];
372 const attr_val = attr[eql_pos + 1 ..];
373
374 if (mem.eql(u8, attr_name, "orig_name")) { // manual name
375 opcode.orig_name = attr_val;
376 } else if (mem.eql(u8, attr_name, "orig_fmt")) { // manual format
377 opcode.orig_format = try desc.getOrParseFormat(gpa, attr_val);
378 }
379 } else {
380 const attr_name = attr[1..];
381
382 if (mem.eql(u8, attr_name, "la32")) { // available in LA32S
383 if (!opcode.required_features.@"32bit")
384 opcode.required_features.@"32s" = true;
385 } else if (mem.eql(u8, attr_name, "primary")) { // available in LA32R
386 opcode.required_features.@"32bit" = true;
387 opcode.required_features.@"32s" = false;
388 } else if (mem.eql(u8, attr_name, "lvz")) { // requires LVZ
389 opcode.required_features.lvz = false;
390 } else if (mem.eql(u8, attr_name, "lbt")) { // requires LBT
391 opcode.required_features.lbt = false;
392 }
393 }
394 }
395
396 // determine based on register usages
397 for (opcode.format.slots) |slot| {
398 switch (slot.tag) {
399 .none => break,
400 .imm => {},
401 .reg => {
402 switch (slot.payload.reg.class) {
403 .fp => {
404 opcode.required_features.f = true;
405 if (mem.eql(u8, opcode.name, ".d")) { // requires double-precision FP
406 opcode.required_features.d = true;
407 }
408 },
409 .fcc => opcode.required_features.f = true,
410 .lsx => opcode.required_features.lsx = true,
411 .lasx => opcode.required_features.lasx = true,
412 .lbt_scratch => opcode.required_features.lbt = true,
413 else => {},
414 }
415 },
416 }
417 }
418
419 // if there are not any attributes indicating that the instruction requires
420 // any other features or supports LA32, we assume that it requires LA64.
421 if (opcode.required_features == Opcode.RequiredFeatures{}) {
422 opcode.required_features.@"64bit" = true;
423 }
424 }
425}
426
427/// Gets or parses a format string.
428/// The string must live longer than the OpcodeDesc.
429pub fn getOrParseFormat(desc: *OpcodeDesc, gpa: Allocator, format: []const u8) !*Format {
430 const gop = try desc.format.getOrPut(gpa, format);
431 if (!gop.found_existing) {
432 errdefer _ = desc.format.swapRemove(format);
433 const format_ptr = try desc.format_pool.create(gpa);
434 errdefer desc.format_pool.destroy(format_ptr);
435
436 format_ptr.* = try Format.parse(format);
437 gop.value_ptr.* = format_ptr;
438 }
439 return gop.value_ptr.*;
440}
441
442pub fn sort(desc: *OpcodeDesc) void {
443 mem.sort(Opcode, desc.opcode.items, false, struct {
444 fn cmp(_: bool, lhs: Opcode, rhs: Opcode) bool {
445 return mem.order(u8, lhs.name, rhs.name) == .lt;
446 }
447 }.cmp);
448 desc.format.sort(struct {
449 keys: [][]const u8,
450
451 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
452 return mem.order(u8, ctx.keys[a_index], ctx.keys[b_index]) == .lt;
453 }
454 }{ .keys = desc.format.keys() });
455}