| author | |
| committer | |
| log | 0fe189415864329769ae5967d504c0574fe41060 |
| tree | 0b0f425e459e7bfb47e10cbcabf0cef491955fe7 |
| parent | 2597da02544c05f7de6c209d9d4d509b74e63f73 |
| signature |
This tool reads data from loongarch-opcodes.git
and generates three data files.
encoding.zig contains an enum of mnemonics, a packed
union of packed structs to represent bit-fields
in instructions and encoder functions for each
instruction.
inst_formats.zon contains required target feature
set for each instruction, which can be used by
the backend to, under debug mode, verify if it
emits any instruction that is not supported by
the compilation target.
It also contains information about slots in each
instruction format, their type, location and
postprocessing, which can be used by disassemblers.
decode_tree.zon is the auto-generated decode tree,
which will be used by disassemblers.
Signed-off-by: xtex <xtex@astrafall.org>4 files changed, 1139 insertions(+), 0 deletions(-)
tools/gen_loongarch_encoding.zig created+526| ... | ... | @@ -0,0 +1,526 @@ |
| 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 | ||
| 5 | const std = @import("std"); | |
| 6 | const fs = std.fs; | |
| 7 | const Allocator = std.mem.Allocator; | |
| 8 | const print = std.debug.print; | |
| 9 | const Writer = std.Io.Writer; | |
| 10 | const ZonSerializer = std.zon.Serializer; | |
| 11 | ||
| 12 | const OpcodeDesc = @import("loongarch/OpcodeDesc.zig"); | |
| 13 | const decode_tree = @import("loongarch/decode_tree.zig"); | |
| 14 | ||
| 15 | pub 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 | ||
| 168 | fn 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 | ||
| 178 | fn 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 | ||
| 293 | fn 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 | ||
| 346 | fn 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 | ||
| 394 | fn 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 | ||
| 401 | fn 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 | ||
| 423 | fn printIndentation(writer: *Writer, indent: usize) !void { | |
| 424 | try writer.splatByteAll(' ', 4 * indent); | |
| 425 | } | |
| 426 | ||
| 427 | fn 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 | } |
tools/loongarch/OpcodeDesc.zig created+455| ... | ... | @@ -0,0 +1,455 @@ |
| 1 | //! Parser for format description files in | |
| 2 | //! https://github.com/loongson-community/loongarch-opcodes. | |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const mem = std.mem; | |
| 6 | const Allocator = mem.Allocator; | |
| 7 | const Reader = std.Io.Reader; | |
| 8 | ||
| 9 | const OpcodeDesc = @This(); | |
| 10 | ||
| 11 | /// Maximum number of slots in one instruction format. | |
| 12 | const max_slots = 4; | |
| 13 | ||
| 14 | opcode: std.ArrayList(Opcode) = .empty, | |
| 15 | format_pool: std.heap.MemoryPool(Format) = .empty, | |
| 16 | format: std.StringArrayHashMapUnmanaged(*Format) = .empty, | |
| 17 | ||
| 18 | pub 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. | |
| 25 | pub 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 | ||
| 123 | test "parse format" { | |
| 124 | _ = try Format.parse("DJFmSk12m13ps3"); | |
| 125 | _ = try Format.parse("DJSk12m13ps3U16pp1"); | |
| 126 | _ = try Format.parse("DJK"); | |
| 127 | } | |
| 128 | ||
| 129 | pub 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 | ||
| 253 | test "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 | ||
| 280 | fn 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 | ||
| 308 | test 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 | ||
| 321 | pub 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. | |
| 345 | pub 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. | |
| 429 | pub 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 | ||
| 442 | pub 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 | } |
tools/loongarch/decode_tree.zig created+149| ... | ... | @@ -0,0 +1,149 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Allocator = std.mem.Allocator; | |
| 3 | const log = std.log.scoped(.loongarch_decode_tree); | |
| 4 | ||
| 5 | const OpcodeDesc = @import("OpcodeDesc.zig"); | |
| 6 | const Opcode = OpcodeDesc.Opcode; | |
| 7 | ||
| 8 | pub const Node = struct { | |
| 9 | mask: u32, | |
| 10 | /// `cases` when mask is non-zero. `instruction` when mask is zero. | |
| 11 | next: union { | |
| 12 | /// catch-all case | |
| 13 | cases: []const Case, | |
| 14 | instruction: *const Opcode, | |
| 15 | }, | |
| 16 | }; | |
| 17 | ||
| 18 | pub const Case = struct { | |
| 19 | catch_all: bool, | |
| 20 | variant: u32, // valid only when catch-all is not set | |
| 21 | child: *const Node, | |
| 22 | }; | |
| 23 | ||
| 24 | pub fn populate(arena: Allocator, desc: *const OpcodeDesc) !*Node { | |
| 25 | var ops: std.ArrayList(*const Opcode) = .empty; | |
| 26 | defer ops.deinit(arena); | |
| 27 | try ops.ensureUnusedCapacity(arena, desc.opcode.items.len); | |
| 28 | for (desc.opcode.items) |*op| ops.appendAssumeCapacity(op); | |
| 29 | ||
| 30 | return try populateAdvanced(arena, ops.items, 0); | |
| 31 | } | |
| 32 | ||
| 33 | pub fn populateAdvanced(arena: Allocator, ops: []const *const Opcode, checked_mask: u32) !*Node { | |
| 34 | if (ops.len == 1) { | |
| 35 | const node = try arena.create(Node); | |
| 36 | node.* = .{ .mask = 0, .next = .{ .instruction = ops[0] } }; | |
| 37 | return node; | |
| 38 | } | |
| 39 | ||
| 40 | // look for unchecked common static bits | |
| 41 | common_static_bits: { | |
| 42 | var common_mask: u32 = ~checked_mask; | |
| 43 | for (ops) |op| { | |
| 44 | for (op.format.slots) |slot| { | |
| 45 | if (slot.tag == .none) break; | |
| 46 | common_mask &= ~slot.mask(); | |
| 47 | } | |
| 48 | } | |
| 49 | if (common_mask == 0) break :common_static_bits; | |
| 50 | ||
| 51 | // there are some common bits to check | |
| 52 | var cases: std.ArrayList(Case) = .empty; | |
| 53 | defer cases.deinit(arena); | |
| 54 | var known_variants: std.ArrayList(u32) = .empty; | |
| 55 | defer known_variants.deinit(arena); | |
| 56 | ||
| 57 | for (ops) |op| { | |
| 58 | const variant = op.word & common_mask; | |
| 59 | if (std.mem.indexOfScalar(u32, known_variants.items, variant) == null) { | |
| 60 | // new variant | |
| 61 | try known_variants.append(arena, variant); | |
| 62 | ||
| 63 | var variant_ops: std.ArrayList(*const Opcode) = .empty; | |
| 64 | defer variant_ops.deinit(arena); | |
| 65 | variant_ops.ensureTotalCapacity(arena, ops.len / 2) catch {}; | |
| 66 | for (ops) |op1| | |
| 67 | if ((op1.word & common_mask) == variant) try variant_ops.append(arena, op1); | |
| 68 | ||
| 69 | const child = try populateAdvanced(arena, variant_ops.items, checked_mask | common_mask); | |
| 70 | try cases.append(arena, .{ | |
| 71 | .catch_all = false, | |
| 72 | .variant = variant, | |
| 73 | .child = child, | |
| 74 | }); | |
| 75 | } | |
| 76 | } | |
| 77 | ||
| 78 | const node = try arena.create(Node); | |
| 79 | node.* = .{ | |
| 80 | .mask = common_mask, | |
| 81 | .next = .{ .cases = try cases.toOwnedSlice(arena) }, | |
| 82 | }; | |
| 83 | return node; | |
| 84 | } | |
| 85 | ||
| 86 | // look for bits that are static for some opcodes but dynamic for one opcode, e.g. csrxchg | |
| 87 | half_static_bits: { | |
| 88 | // these bits are static in at least one opcode | |
| 89 | var half_static_mask: u32 = 0; | |
| 90 | for (ops) |op| { | |
| 91 | var op_static_mask: u32 = 0xffffffff; | |
| 92 | for (op.format.slots) |slot| { | |
| 93 | if (slot.tag == .none) break; | |
| 94 | op_static_mask &= ~slot.mask(); | |
| 95 | } | |
| 96 | half_static_mask |= op_static_mask; | |
| 97 | } | |
| 98 | half_static_mask &= ~checked_mask; | |
| 99 | if (half_static_mask == 0) break :half_static_bits; | |
| 100 | ||
| 101 | var cases: std.ArrayList(Case) = .empty; | |
| 102 | defer cases.deinit(arena); | |
| 103 | var maybe_dynamic_op: ?*const Opcode = null; | |
| 104 | ||
| 105 | for (ops) |op| { | |
| 106 | const variant = op.word & half_static_mask; | |
| 107 | ||
| 108 | var op_static_mask = ~checked_mask; | |
| 109 | for (op.format.slots) |slot| { | |
| 110 | if (slot.tag == .none) break; | |
| 111 | op_static_mask &= ~slot.mask(); | |
| 112 | } | |
| 113 | if (op_static_mask != half_static_mask) { | |
| 114 | if (maybe_dynamic_op) |dynamic_op| { | |
| 115 | log.err("unsupported: {s}, {s}", .{ dynamic_op.name, op.name }); | |
| 116 | return error.Unsupported; | |
| 117 | } else { | |
| 118 | maybe_dynamic_op = op; | |
| 119 | continue; | |
| 120 | } | |
| 121 | } | |
| 122 | ||
| 123 | const child = try populateAdvanced(arena, &.{op}, checked_mask | half_static_mask); | |
| 124 | try cases.append(arena, .{ | |
| 125 | .catch_all = false, | |
| 126 | .variant = variant, | |
| 127 | .child = child, | |
| 128 | }); | |
| 129 | } | |
| 130 | ||
| 131 | if (maybe_dynamic_op) |dynamic_op| { | |
| 132 | const child = try populateAdvanced(arena, &.{dynamic_op}, checked_mask | half_static_mask); | |
| 133 | try cases.append(arena, .{ | |
| 134 | .catch_all = true, | |
| 135 | .variant = 0, | |
| 136 | .child = child, | |
| 137 | }); | |
| 138 | } else unreachable; | |
| 139 | ||
| 140 | const node = try arena.create(Node); | |
| 141 | node.* = .{ | |
| 142 | .mask = half_static_mask, | |
| 143 | .next = .{ .cases = try cases.toOwnedSlice(arena) }, | |
| 144 | }; | |
| 145 | return node; | |
| 146 | } | |
| 147 | ||
| 148 | return error.Unsupported; | |
| 149 | } |
tools/loongarch/extra.txt created+9| ... | ... | @@ -0,0 +1,9 @@ |
| 1 | # loongarch-opcodes removed these instructions as their encodings | |
| 2 | # collide with csrxchg/gcsrxchg. | |
| 3 | # They are added back here to support assembler and disassembler | |
| 4 | # as decode_tree.zig has supported generating decode-trees for these | |
| 5 | # colliding encodings. | |
| 6 | 04000000 csrrd DUk14 @primary | |
| 7 | 04000032 csrwr DUk14 @primary | |
| 8 | 05000000 gcsrrd DUk14 @lvz | |
| 9 | 05000032 gcsrwr DUk14 @lvz |