1//! Example usage:
2//! git clone https://github.com/loongson-community/loongarch-opcodes.git ../loongarch-opcodes
3//! zig run tools/gen_loongarch_encoding.zig -- ../loongarch-opcodes .
4
5const std = @import("std");
6const fs = std.fs;
7const Allocator = std.mem.Allocator;
8const print = std.debug.print;
9const Writer = std.Io.Writer;
10const ZonSerializer = std.zon.Serializer;
11
12const OpcodeDesc = @import("loongarch/OpcodeDesc.zig");
13const decode_tree = @import("loongarch/decode_tree.zig");
14
15pub fn main(init: std.process.Init) !void {
16 const arena = init.arena.allocator();
17 const io = init.io;
18
19 var args = try init.minimal.args.iterateAllocator(arena);
20 const arg0 = args.next().?;
21 const opcodes_path = args.next() orelse usageAndExit(arg0, 0);
22 const zig_path = args.next() orelse usageAndExit(arg0, 1);
23 args.deinit();
24
25 var desc: OpcodeDesc = .{};
26 defer desc.deinit(arena);
27
28 var zig_dir = try std.Io.Dir.cwd().openDir(io, zig_path, .{});
29 defer zig_dir.close(io);
30
31 // load opcode data
32 {
33 print("Loading description files ..\n", .{});
34 var opcodes_dir = try std.Io.Dir.cwd().openDir(io, opcodes_path, .{ .iterate = true });
35 defer opcodes_dir.close(io);
36 var opcodes_iter = opcodes_dir.iterateAssumeFirstIteration();
37 while (try opcodes_iter.next(io)) |opcodes_file| {
38 if (opcodes_file.kind != .file) continue;
39 if (!std.mem.endsWith(u8, opcodes_file.name, ".txt")) continue;
40
41 print("Loading {s} ...\n", .{opcodes_file.name});
42 const data = try opcodes_dir.readFileAlloc(io, opcodes_file.name, arena, .unlimited);
43 try desc.parse(arena, data);
44 // `data` is intentionally leaked here because it must live longer than `desc`
45 // ArenaAllocator should clean them up.
46 }
47
48 print("Loading extra.txt ...\n", .{});
49 const data = try zig_dir.readFileAlloc(io, "tools/loongarch/extra.txt", arena, .unlimited);
50 try desc.parse(arena, data);
51
52 print("Loaded {} instructions, {} formats\n", .{ desc.opcode.items.len, desc.format.count() });
53 desc.sort();
54 print("Sorted data\n", .{});
55 }
56
57 // generate encoding.zig
58 {
59 print("Writing encoding.zig ...\n", .{});
60 var buffer: Writer.Allocating = .init(arena);
61 defer buffer.deinit();
62 const writer = &buffer.writer;
63
64 try writer.print(
65 \\// DO NOT MODIFY. This file is generated by tools/gen_loongarch_encoding.zig.
66 \\const Register = @import("bits.zig").Register;
67 \\
68 , .{});
69
70 // mnemonic enum
71 {
72 try writer.print("\npub const Mnemonic = enum {{\n", .{});
73 for (desc.opcode.items) |*opcode| {
74 try writer.print(" {f},\n", .{std.zig.fmtIdPU(opcode.name)});
75 }
76 try writer.print("}};\n", .{});
77 }
78
79 // instruction struct
80 {
81 try writer.print(
82 \\
83 \\pub const Instruction = packed union {{
84 \\ word: u32,
85 \\
86 , .{});
87
88 // format-based variants
89 {
90 var format_iter = desc.format.iterator();
91 while (format_iter.next()) |entry|
92 try printFormatStruct(writer, entry.key_ptr.*, entry.value_ptr.*);
93 }
94
95 // format-based encoders
96 {
97 var format_iter = desc.format.iterator();
98 while (format_iter.next()) |entry|
99 try printFormatEncoder(writer, entry.key_ptr.*, entry.value_ptr.*);
100 }
101
102 // opcode-based encoders
103 for (desc.opcode.items) |*opcode| {
104 const encoder_format = if (std.mem.eql(u8, opcode.name, opcode.orig_name)) opcode.orig_format else opcode.format;
105 try printInstructionEncoder(writer, opcode, encoder_format);
106 }
107
108 try writer.print("}};\n", .{});
109 }
110
111 try zig_dir.writeFile(io, .{
112 .sub_path = "src/codegen/loongarch/encoding.zig",
113 .data = buffer.written(),
114 });
115 }
116
117 // generate decode_tree.zon
118 {
119 print("Writing decode_tree.zon ...\n", .{});
120 var buffer: Writer.Allocating = .init(arena);
121 defer buffer.deinit();
122 const writer = &buffer.writer;
123
124 try writer.print(
125 \\// DO NOT MODIFY. This file is generated by tools/gen_loongarch_encoding.zig.
126 \\
127 , .{});
128
129 try printDecodeTree(writer, arena, &desc);
130
131 try writer.writeAll("\n");
132
133 try zig_dir.writeFile(io, .{
134 .sub_path = "src/codegen/loongarch/decode_tree.zon",
135 .data = buffer.written(),
136 });
137 }
138
139 // generate inst_formats.zon
140 {
141 print("Writing inst_formats.zon ...\n", .{});
142 var buffer: Writer.Allocating = .init(arena);
143 defer buffer.deinit();
144 const writer = &buffer.writer;
145
146 try writer.print(
147 \\// DO NOT MODIFY. This file is generated by tools/gen_loongarch_encoding.zig.
148 \\
149 , .{});
150
151 var s: ZonSerializer = .{
152 .writer = writer,
153 .options = .{},
154 };
155 try serializeInstFormats(&s, &desc);
156
157 try writer.writeAll("\n");
158
159 try zig_dir.writeFile(io, .{
160 .sub_path = "src/codegen/loongarch/inst_formats.zon",
161 .data = buffer.written(),
162 });
163 }
164
165 print("Done.\n", .{});
166}
167
168fn usageAndExit(arg0: []const u8, code: u8) noreturn {
169 print(
170 \\Usage: {s} /path/loongarch-opcodes /path/zig
171 \\
172 \\Updates LoongArch encoding data from loongarch-opcodes.git.
173 \\
174 , .{arg0});
175 std.process.exit(code);
176}
177
178fn printFormatStruct(writer: *Writer, name: []const u8, format: *const OpcodeDesc.Format) !void {
179 if (format.slots[0].tag == .none) return; // skips EMPTY format
180
181 try writer.print(" /// Fields of a `{s}` instruction.", .{name});
182 try writer.print("\n {s}: packed struct {{ ", .{name});
183 const Field = union(enum) {
184 funct: struct { width: u5 },
185 immediate: struct {
186 signedness: std.builtin.Signedness,
187 width: u5,
188 },
189 register: struct {
190 index: OpcodeDesc.Slot.Index,
191 width: u5,
192 },
193 };
194 var fields_buf: [4 * 2 + 1]Field = undefined;
195 var fields: std.ArrayList(Field) = .initBuffer(&fields_buf);
196
197 // collect fields
198 var bit_offset: u6 = 0;
199 var slots = format.slots;
200 std.mem.sort(OpcodeDesc.Slot, &slots, false, struct {
201 fn cmp(_: bool, lhs: OpcodeDesc.Slot, rhs: OpcodeDesc.Slot) bool {
202 if (lhs.tag == .none) return false;
203 if (rhs.tag == .none) return true;
204 return lhs.offset() < rhs.offset();
205 }
206 }.cmp);
207 for (slots) |slot| {
208 if (slot.tag == .none) break;
209 const slot_offset = slot.offset();
210 const slot_width = slot.width();
211 if (bit_offset != slot_offset)
212 fields.appendAssumeCapacity(.{ .funct = .{ .width = @truncate(slot_offset - bit_offset) } });
213
214 switch (slot.tag) {
215 .none => unreachable,
216 .imm => {
217 fields.appendAssumeCapacity(.{ .immediate = .{
218 .signedness = slot.payload.imm.signedness,
219 .width = slot_width,
220 } });
221 },
222 .reg => fields.appendAssumeCapacity(.{ .register = .{
223 .index = slot.payload.reg.index,
224 .width = slot_width,
225 } }),
226 }
227 bit_offset = slot_offset + slot_width;
228 }
229 if (bit_offset != 32)
230 fields.appendAssumeCapacity(.{ .funct = .{ .width = @intCast(@as(u6, 32) - bit_offset) } });
231
232 // detect shadowed immediate field names
233 const imm_shadowed = imm_shadowed: {
234 var imm_names: [std.math.maxInt(u6) + 1]bool = @splat(false);
235 for (fields.items) |field| {
236 switch (field) {
237 else => {},
238 .immediate => |imm_field| {
239 var imm_name_key: u6 = imm_field.width;
240 if (imm_field.signedness == .signed) imm_name_key |= std.math.maxInt(u5) + 1;
241 if (imm_names[imm_name_key]) break :imm_shadowed true;
242 imm_names[imm_name_key] = true;
243 },
244 }
245 }
246 break :imm_shadowed false;
247 };
248
249 // print fields
250 bit_offset = 0;
251 for (fields.items, 0..) |field, field_i| {
252 if (field_i != 0) try writer.writeAll(", ");
253 switch (field) {
254 .funct => |pl| {
255 try writer.print("funct{}: u{}", .{ bit_offset, pl.width });
256 bit_offset += pl.width;
257 },
258 .immediate => |pl| {
259 if (imm_shadowed) {
260 try writer.print("imm{}: {c}{}", .{
261 bit_offset,
262 @as(u8, switch (pl.signedness) {
263 .signed => 'i',
264 .unsigned => 'u',
265 }),
266 pl.width,
267 });
268 } else {
269 try writer.print("{c}i{}: {c}{}", .{
270 @as(u8, switch (pl.signedness) {
271 .signed => 's',
272 .unsigned => 'u',
273 }),
274 pl.width,
275 @as(u8, switch (pl.signedness) {
276 .signed => 'i',
277 .unsigned => 'u',
278 }),
279 pl.width,
280 });
281 }
282 bit_offset += pl.width;
283 },
284 .register => |pl| {
285 try writer.print("r{s}: u{}", .{ @tagName(pl.index), pl.width });
286 bit_offset += pl.width;
287 },
288 }
289 }
290 try writer.print(" }},\n", .{});
291}
292
293fn printFormatEncoder(writer: *Writer, name: []const u8, format: *const OpcodeDesc.Format) !void {
294 try writer.print("\n /// Encodes a `{s}` instruction.", .{name});
295 try writer.print("\n pub inline fn encode{s}(word: u32", .{name});
296 for (format.slots, 0..) |slot, slot_i| {
297 switch (slot.tag) {
298 .none => break,
299 .imm => {
300 const pl = slot.payload.imm;
301
302 try writer.print(", p{}: {c}{}", .{
303 slot_i,
304 @as(u8, switch (pl.signedness) {
305 .signed => 'i',
306 .unsigned => 'u',
307 }),
308 pl.length,
309 });
310 },
311 .reg => try writer.print(", p{}: Register", .{slot_i}),
312 }
313 }
314 try writer.print(") Instruction {{\n", .{});
315 if (format.slots[0].tag == .none) {
316 try writer.print(" return .{{ .word = word }};\n", .{});
317 } else {
318 try writer.print(" return .{{ .word = word", .{});
319 for (format.slots, 0..) |slot, slot_i| {
320 switch (slot.tag) {
321 .none => break,
322 .imm => {
323 const pl = slot.payload.imm;
324 try writer.print(" |\n (", .{});
325 try writer.print("(@as(u32, @as(u{}, @bitCast(p{})))", .{ pl.length, slot_i });
326
327 switch (pl.post_proc.tag) {
328 .none => {},
329 .add => try writer.print(" - {}", .{pl.post_proc.payload.add}),
330 .shl => try writer.print(" >> {}", .{pl.post_proc.payload.shl}),
331 }
332
333 try writer.print(") << {})", .{pl.index.offset()});
334 },
335 .reg => {
336 const pl = slot.payload.reg;
337 try writer.print(" |\n (@as(u32, p{}.encode()) << {})", .{ slot_i, pl.index.offset() });
338 },
339 }
340 }
341 try writer.print(" }};\n", .{});
342 }
343 try writer.print(" }}\n", .{});
344}
345
346fn printInstructionEncoder(writer: *Writer, opcode: *OpcodeDesc.Opcode, format: *const OpcodeDesc.Format) !void {
347 try writer.print("\n /// Encodes a `{s}` instruction", .{opcode.name});
348 if (opcode.required_features != OpcodeDesc.Opcode.RequiredFeatures{}) {
349 try writer.writeAll(" (requires ");
350 var first = true;
351 const feature_fields = comptime std.meta.fieldNames(OpcodeDesc.Opcode.RequiredFeatures);
352 inline for (feature_fields) |field| {
353 if (@field(opcode.required_features, field)) {
354 if (first) first = false else try writer.writeAll(" & ");
355 try writer.writeAll(field);
356 }
357 }
358 try writer.writeByte(')');
359 }
360 try writer.writeByte('.');
361 try writer.print("\n pub inline fn {f}(", .{std.zig.fmtIdPU(opcode.name)});
362 for (format.slots, 0..) |slot, slot_i| {
363 if (slot_i != 0 and slot.tag != .none)
364 try writer.print(", ", .{});
365 switch (slot.tag) {
366 .none => break,
367 .imm => {
368 const pl = slot.payload.imm;
369
370 try writer.print("p{}: {c}{}", .{
371 slot_i,
372 @as(u8, switch (pl.signedness) {
373 .signed => 'i',
374 .unsigned => 'u',
375 }),
376 pl.length,
377 });
378 },
379 .reg => try writer.print("p{}: Register", .{slot_i}),
380 }
381 }
382 try writer.print(") Instruction {{\n", .{});
383 try writer.print(" return encode{s}(0x{x:0>8}", .{ format.name, opcode.word });
384 for (format.slots, 0..) |slot, slot_i| {
385 switch (slot.tag) {
386 .none => break,
387 else => try writer.print(", p{}", .{slot_i}),
388 }
389 }
390 try writer.print(");\n", .{});
391 try writer.print(" }}\n", .{});
392}
393
394fn printDecodeTree(writer: *Writer, gpa: Allocator, desc: *const OpcodeDesc) !void {
395 var arena: std.heap.ArenaAllocator = .init(gpa);
396 defer arena.deinit();
397 const root_node = try decode_tree.populate(arena.allocator(), desc);
398 try printDecodeTreeNode(writer, root_node, 0);
399}
400
401fn printDecodeTreeNode(writer: *Writer, node: *const decode_tree.Node, indent: usize) !void {
402 if (node.mask == 0) {
403 try writer.print(".{{ .instruction = .{f} }}", .{std.zig.fmtId(node.next.instruction.name)});
404 } else {
405 try writer.print(".{{ .mask = 0x{x:0>8}, .cases = .{{\n", .{node.mask});
406 for (node.next.cases) |*case| {
407 try printIndentation(writer, indent + 1);
408 if (case.catch_all) {
409 try writer.print(".{{ .then = ", .{});
410 try printDecodeTreeNode(writer, case.child, indent + 1);
411 try writer.print(" }},\n", .{});
412 } else {
413 try writer.print(".{{ .value = 0x{x:0>8}, .then = ", .{case.variant});
414 try printDecodeTreeNode(writer, case.child, indent + 1);
415 try writer.print(" }},\n", .{});
416 }
417 }
418 try printIndentation(writer, indent);
419 try writer.print("}} }}", .{});
420 }
421}
422
423fn printIndentation(writer: *Writer, indent: usize) !void {
424 try writer.splatByteAll(' ', 4 * indent);
425}
426
427fn serializeInstFormats(s: *ZonSerializer, desc: *const OpcodeDesc) !void {
428 var root_struct = try s.beginStruct(.{});
429
430 {
431 var instructions_s = try root_struct.beginStructField("instructions", .{});
432 for (desc.opcode.items) |*opcode| {
433 var opcode_s = try instructions_s.beginStructField(opcode.name, .{});
434
435 try opcode_s.fieldPrefix("word");
436 try s.writer.writeAll("0x");
437 try s.writer.printInt(opcode.word, 16, .lower, .{
438 .alignment = .right,
439 .fill = '0',
440 .width = 8,
441 });
442
443 try opcode_s.fieldPrefix("format");
444 try s.ident(opcode.format.name);
445 if (opcode.orig_format != opcode.format) {
446 try opcode_s.fieldPrefix("orig_format");
447 try s.ident(opcode.orig_format.name);
448 }
449
450 if (opcode.orig_name.ptr != opcode.name.ptr)
451 try opcode_s.field("orig_name", opcode.orig_name, .{});
452
453 const field_names = comptime std.meta.fieldNames(OpcodeDesc.Opcode.RequiredFeatures);
454 var num_features: u32 = 0;
455 inline for (field_names) |field| {
456 if (@field(opcode.required_features, field))
457 num_features += 1;
458 }
459 var features_s = try opcode_s.beginTupleField("features", .{ .whitespace_style = .{ .fields = num_features } });
460 inline for (field_names) |field| {
461 if (@field(opcode.required_features, field)) {
462 try features_s.fieldPrefix();
463 try s.ident(field);
464 }
465 }
466 try features_s.end();
467
468 try opcode_s.end();
469 }
470 try instructions_s.end();
471 }
472
473 {
474 var formats_s = try root_struct.beginStructField("formats", .{});
475 for (desc.format.values()) |format| {
476 var format_s = try formats_s.beginStructField(format.name, .{ .whitespace_style = .{ .wrap = false } });
477
478 var slots_s = try format_s.beginTupleField("slots", .{});
479 for (format.slots) |slot| {
480 if (slot.tag == .none) break;
481 var slot_s = try slots_s.beginStructField(.{ .whitespace_style = .{ .wrap = false } });
482 switch (slot.tag) {
483 .none => unreachable,
484 .reg => {
485 const pl = slot.payload.reg;
486 var reg_s = try slot_s.beginStructField("reg", .{ .whitespace_style = .{ .fields = 2 } });
487 try reg_s.field("location", pl.index.offset(), .{});
488
489 try reg_s.fieldPrefix("class");
490 try s.ident(@tagName(pl.class));
491
492 try reg_s.end();
493 },
494 .imm => {
495 const pl = slot.payload.imm;
496 var imm_s = try slot_s.beginStructField("imm", .{ .whitespace_style = .{ .wrap = false } });
497 try imm_s.field("location", pl.index.offset(), .{});
498 try imm_s.field("length", pl.length, .{});
499
500 try imm_s.fieldPrefix("signedness");
501 try s.ident(@tagName(pl.signedness));
502
503 if (pl.post_proc.tag != .none) {
504 var pp_s = try imm_s.beginStructField("post_proc", .{ .whitespace_style = .{ .fields = 1 } });
505 switch (pl.post_proc.tag) {
506 .none => unreachable,
507 .add => try pp_s.field("add", pl.post_proc.payload.add, .{}),
508 .shl => try pp_s.field("shl", pl.post_proc.payload.shl, .{}),
509 }
510 try pp_s.end();
511 }
512
513 try imm_s.end();
514 },
515 }
516 try slot_s.end();
517 }
518 try slots_s.end();
519
520 try format_s.end();
521 }
522 try formats_s.end();
523 }
524
525 try root_struct.end();
526}