| ... | ... | @@ -0,0 +1,597 @@ |
| 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; |
| 3 | const Allocator = std.mem.Allocator; |
| 4 | const log = std.log.scoped(.spirv_parse); |
| 5 | |
| 6 | const spec = @import("../../codegen/spirv/spec.zig"); |
| 7 | const Opcode = spec.Opcode; |
| 8 | const Word = spec.Word; |
| 9 | const InstructionSet = spec.InstructionSet; |
| 10 | const ResultId = spec.IdResult; |
| 11 | |
| 12 | const BinaryModule = @This(); |
| 13 | |
| 14 | pub const header_words = 5; |
| 15 | |
| 16 | /// The module SPIR-V version. |
| 17 | version: spec.Version, |
| 18 | |
| 19 | /// The generator magic number. |
| 20 | generator_magic: u32, |
| 21 | |
| 22 | /// The result-id bound of this SPIR-V module. |
| 23 | id_bound: u32, |
| 24 | |
| 25 | /// The instructions of this module. This does not contain the header. |
| 26 | instructions: []const Word, |
| 27 | |
| 28 | /// Maps OpExtInstImport result-ids to their InstructionSet. |
| 29 | ext_inst_map: std.AutoHashMapUnmanaged(ResultId, InstructionSet), |
| 30 | |
| 31 | /// This map contains the width of arithmetic types (OpTypeInt and |
| 32 | /// OpTypeFloat). We need this information to correctly parse the operands |
| 33 | /// of Op(Spec)Constant and OpSwitch. |
| 34 | arith_type_width: std.AutoHashMapUnmanaged(ResultId, u16), |
| 35 | |
| 36 | pub fn deinit(self: *BinaryModule, a: Allocator) void { |
| 37 | self.ext_inst_map.deinit(a); |
| 38 | self.arith_type_width.deinit(a); |
| 39 | self.* = undefined; |
| 40 | } |
| 41 | |
| 42 | pub fn iterateInstructions(self: BinaryModule) Instruction.Iterator { |
| 43 | return Instruction.Iterator.init(self.instructions); |
| 44 | } |
| 45 | |
| 46 | /// Errors that can be raised when the module is not correct. |
| 47 | /// Note that the parser doesn't validate SPIR-V modules by a |
| 48 | /// long shot. It only yields errors that critically prevent |
| 49 | /// further analysis of the module. |
| 50 | pub const ParseError = error{ |
| 51 | /// Raised when the module doesn't start with the SPIR-V magic. |
| 52 | /// This usually means that the module isn't actually SPIR-V. |
| 53 | InvalidMagic, |
| 54 | /// Raised when the module has an invalid "physical" format: |
| 55 | /// For example when the header is incomplete, or an instruction |
| 56 | /// has an illegal format. |
| 57 | InvalidPhysicalFormat, |
| 58 | /// OpExtInstImport was used with an unknown extension string. |
| 59 | InvalidExtInstImport, |
| 60 | /// The module had an instruction with an invalid (unknown) opcode. |
| 61 | InvalidOpcode, |
| 62 | /// An instruction's operands did not conform to the SPIR-V specification |
| 63 | /// for that instruction. |
| 64 | InvalidOperands, |
| 65 | /// A result-id was declared more than once. |
| 66 | DuplicateId, |
| 67 | /// Some ID did not resolve. |
| 68 | InvalidId, |
| 69 | /// Parser ran out of memory. |
| 70 | OutOfMemory, |
| 71 | }; |
| 72 | |
| 73 | pub const Instruction = struct { |
| 74 | pub const Iterator = struct { |
| 75 | words: []const Word, |
| 76 | index: usize = 0, |
| 77 | offset: usize = 0, |
| 78 | |
| 79 | pub fn init(words: []const Word) Iterator { |
| 80 | return .{ .words = words }; |
| 81 | } |
| 82 | |
| 83 | pub fn next(self: *Iterator) ?Instruction { |
| 84 | if (self.offset >= self.words.len) return null; |
| 85 | |
| 86 | const instruction_len = self.words[self.offset] >> 16; |
| 87 | defer self.offset += instruction_len; |
| 88 | defer self.index += 1; |
| 89 | assert(instruction_len != 0 and self.offset < self.words.len); // Verified in BinaryModule.parse. |
| 90 | |
| 91 | return Instruction{ |
| 92 | .opcode = @enumFromInt(self.words[self.offset] & 0xFFFF), |
| 93 | .index = self.index, |
| 94 | .offset = self.offset, |
| 95 | .operands = self.words[self.offset..][1..instruction_len], |
| 96 | }; |
| 97 | } |
| 98 | }; |
| 99 | |
| 100 | /// The opcode for this instruction. |
| 101 | opcode: Opcode, |
| 102 | /// The instruction's index. |
| 103 | index: usize, |
| 104 | /// The instruction's word offset in the module. |
| 105 | offset: usize, |
| 106 | /// The raw (unparsed) operands for this instruction. |
| 107 | operands: []const Word, |
| 108 | }; |
| 109 | |
| 110 | /// This struct is used to return information about |
| 111 | /// a module's functions - entry points, functions, |
| 112 | /// list of callees. |
| 113 | pub const FunctionInfo = struct { |
| 114 | /// Information that is gathered about a particular function. |
| 115 | pub const Fn = struct { |
| 116 | /// The word-offset of the first word (of the OpFunction instruction) |
| 117 | /// of this instruction. |
| 118 | begin_offset: usize, |
| 119 | /// The past-end offset of the end (including operands) of the last |
| 120 | /// instruction of the function. |
| 121 | end_offset: usize, |
| 122 | /// The index of the first callee in `callee_store`. |
| 123 | first_callee: usize, |
| 124 | /// The module offset of the OpTypeFunction instruction corresponding |
| 125 | /// to this function. |
| 126 | /// We use an offset so that we don't need to keep a separate map. |
| 127 | type_offset: usize, |
| 128 | }; |
| 129 | |
| 130 | /// Maps function result-id -> Function information structure. |
| 131 | functions: std.AutoArrayHashMapUnmanaged(ResultId, Fn), |
| 132 | /// List of entry points in this module. Contains OpFunction result-ids. |
| 133 | entry_points: []const ResultId, |
| 134 | /// For each function, a list of function result-ids that it calls. |
| 135 | callee_store: []const ResultId, |
| 136 | |
| 137 | pub fn deinit(self: *FunctionInfo, a: Allocator) void { |
| 138 | self.functions.deinit(a); |
| 139 | a.free(self.entry_points); |
| 140 | a.free(self.callee_store); |
| 141 | self.* = undefined; |
| 142 | } |
| 143 | |
| 144 | /// Fetch the list of callees per function. Guaranteed to contain only unique IDs. |
| 145 | pub fn callees(self: FunctionInfo, fn_id: ResultId) []const ResultId { |
| 146 | const fn_index = self.functions.getIndex(fn_id).?; |
| 147 | const values = self.functions.values(); |
| 148 | const first_callee = values[fn_index].first_callee; |
| 149 | if (fn_index == values.len - 1) { |
| 150 | return self.callee_store[first_callee..]; |
| 151 | } else { |
| 152 | const next_first_callee = values[fn_index + 1].first_callee; |
| 153 | return self.callee_store[first_callee..next_first_callee]; |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | /// Returns a topological ordering of the functions: For each item |
| 158 | /// in the returned list of OpFunction result-ids, it is guaranteed that |
| 159 | /// the callees have a lower index. Note that SPIR-V does not support |
| 160 | /// any recursion, so this always works. |
| 161 | pub fn topologicalSort(self: FunctionInfo, a: Allocator) ![]const ResultId { |
| 162 | var sort = std.ArrayList(ResultId).init(a); |
| 163 | defer sort.deinit(); |
| 164 | |
| 165 | var seen = try std.DynamicBitSetUnmanaged.initEmpty(a, self.functions.count()); |
| 166 | defer seen.deinit(a); |
| 167 | |
| 168 | var stack = std.ArrayList(ResultId).init(a); |
| 169 | defer stack.deinit(); |
| 170 | |
| 171 | for (self.functions.keys()) |id| { |
| 172 | try self.topologicalSortStep(id, &sort, &seen); |
| 173 | } |
| 174 | |
| 175 | return try sort.toOwnedSlice(); |
| 176 | } |
| 177 | |
| 178 | fn topologicalSortStep( |
| 179 | self: FunctionInfo, |
| 180 | id: ResultId, |
| 181 | sort: *std.ArrayList(ResultId), |
| 182 | seen: *std.DynamicBitSetUnmanaged, |
| 183 | ) !void { |
| 184 | const fn_index = self.functions.getIndex(id) orelse { |
| 185 | log.err("function calls invalid callee-id {}", .{@intFromEnum(id)}); |
| 186 | return error.InvalidId; |
| 187 | }; |
| 188 | if (seen.isSet(fn_index)) { |
| 189 | return; |
| 190 | } |
| 191 | |
| 192 | seen.set(fn_index); |
| 193 | for (self.callees(id)) |callee| { |
| 194 | try self.topologicalSortStep(callee, sort, seen); |
| 195 | } |
| 196 | |
| 197 | try sort.append(id); |
| 198 | } |
| 199 | }; |
| 200 | |
| 201 | /// This parser contains information (acceleration tables) |
| 202 | /// that can be persisted across different modules. This is |
| 203 | /// used to initialize the module, and is also used when |
| 204 | /// further analyzing it. |
| 205 | pub const Parser = struct { |
| 206 | /// The allocator used to allocate this parser's structures, |
| 207 | /// and also the structures of any parsed module. |
| 208 | a: Allocator, |
| 209 | |
| 210 | /// Maps (instruction set, opcode) => instruction index (for instruction set) |
| 211 | opcode_table: std.AutoHashMapUnmanaged(u32, u16) = .{}, |
| 212 | |
| 213 | pub fn init(a: Allocator) !Parser { |
| 214 | var self = Parser{ |
| 215 | .a = a, |
| 216 | }; |
| 217 | errdefer self.deinit(); |
| 218 | |
| 219 | inline for (std.meta.tags(InstructionSet)) |set| { |
| 220 | const instructions = set.instructions(); |
| 221 | try self.opcode_table.ensureUnusedCapacity(a, @intCast(instructions.len)); |
| 222 | for (instructions, 0..) |inst, i| { |
| 223 | // Note: Some instructions may alias another. In this case we don't really care |
| 224 | // which one is first: they all (should) have the same operands anyway. Just pick |
| 225 | // the first, which is usually the core, KHR or EXT variant. |
| 226 | const entry = self.opcode_table.getOrPutAssumeCapacity(mapSetAndOpcode(set, @intCast(inst.opcode))); |
| 227 | if (!entry.found_existing) { |
| 228 | entry.value_ptr.* = @intCast(i); |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | return self; |
| 234 | } |
| 235 | |
| 236 | pub fn deinit(self: *Parser) void { |
| 237 | self.opcode_table.deinit(self.a); |
| 238 | } |
| 239 | |
| 240 | fn mapSetAndOpcode(set: InstructionSet, opcode: u16) u32 { |
| 241 | return (@as(u32, @intFromEnum(set)) << 16) | opcode; |
| 242 | } |
| 243 | |
| 244 | pub fn parse(self: *Parser, module: []const u32) ParseError!BinaryModule { |
| 245 | if (module[0] != spec.magic_number) { |
| 246 | return error.InvalidMagic; |
| 247 | } else if (module.len < header_words) { |
| 248 | log.err("module only has {}/{} header words", .{ module.len, header_words }); |
| 249 | return error.InvalidPhysicalFormat; |
| 250 | } |
| 251 | |
| 252 | var binary = BinaryModule{ |
| 253 | .version = @bitCast(module[1]), |
| 254 | .generator_magic = module[2], |
| 255 | .id_bound = module[3], |
| 256 | .instructions = module[header_words..], |
| 257 | .ext_inst_map = .{}, |
| 258 | .arith_type_width = .{}, |
| 259 | }; |
| 260 | |
| 261 | // First pass through the module to verify basic structure and |
| 262 | // to gather some initial stuff for more detailed analysis. |
| 263 | // We want to check some stuff that Instruction.Iterator is no good for, |
| 264 | // so just iterate manually. |
| 265 | var offset: usize = 0; |
| 266 | while (offset < binary.instructions.len) { |
| 267 | const len = binary.instructions[offset] >> 16; |
| 268 | if (len == 0 or len + offset > binary.instructions.len) { |
| 269 | log.err("invalid instruction format: len={}, end={}, module len={}", .{ len, len + offset, binary.instructions.len }); |
| 270 | return error.InvalidPhysicalFormat; |
| 271 | } |
| 272 | defer offset += len; |
| 273 | |
| 274 | // We can't really efficiently use non-exhaustive enums here, because we would |
| 275 | // need to manually write out all valid cases. Since we have this map anyway, just |
| 276 | // use that. |
| 277 | const opcode_num: u16 = @truncate(binary.instructions[offset]); |
| 278 | const index = self.opcode_table.get(mapSetAndOpcode(.core, opcode_num)) orelse { |
| 279 | log.err("invalid opcode for core set: {}", .{opcode_num}); |
| 280 | return error.InvalidOpcode; |
| 281 | }; |
| 282 | |
| 283 | const opcode: Opcode = @enumFromInt(opcode_num); |
| 284 | const operands = binary.instructions[offset..][1..len]; |
| 285 | switch (opcode) { |
| 286 | .OpExtInstImport => { |
| 287 | const set_name = std.mem.sliceTo(std.mem.sliceAsBytes(operands[1..]), 0); |
| 288 | const set = std.meta.stringToEnum(InstructionSet, set_name) orelse { |
| 289 | log.err("invalid instruction set '{s}'", .{set_name}); |
| 290 | return error.InvalidExtInstImport; |
| 291 | }; |
| 292 | if (set == .core) return error.InvalidExtInstImport; |
| 293 | try binary.ext_inst_map.put(self.a, @enumFromInt(operands[0]), set); |
| 294 | }, |
| 295 | .OpTypeInt, .OpTypeFloat => { |
| 296 | const entry = try binary.arith_type_width.getOrPut(self.a, @enumFromInt(operands[0])); |
| 297 | if (entry.found_existing) return error.DuplicateId; |
| 298 | entry.value_ptr.* = std.math.cast(u16, operands[1]) orelse return error.InvalidOperands; |
| 299 | }, |
| 300 | else => {}, |
| 301 | } |
| 302 | |
| 303 | // OpSwitch takes a value as argument, not an OpType... hence we need to populate arith_type_width |
| 304 | // with ALL operations that return an int or float. |
| 305 | const proper_operands = InstructionSet.core.instructions()[index].operands; |
| 306 | |
| 307 | if (proper_operands.len >= 2 and |
| 308 | proper_operands[0].kind == .IdResultType and |
| 309 | proper_operands[1].kind == .IdResult) |
| 310 | { |
| 311 | if (operands.len < 2) return error.InvalidOperands; |
| 312 | if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| { |
| 313 | const entry = try binary.arith_type_width.getOrPut(self.a, @enumFromInt(operands[1])); |
| 314 | if (entry.found_existing) return error.DuplicateId; |
| 315 | entry.value_ptr.* = width; |
| 316 | } |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | return binary; |
| 321 | } |
| 322 | |
| 323 | pub fn parseFunctionInfo(self: *Parser, binary: BinaryModule) ParseError!FunctionInfo { |
| 324 | var entry_points = std.AutoArrayHashMap(ResultId, void).init(self.a); |
| 325 | defer entry_points.deinit(); |
| 326 | |
| 327 | var functions = std.AutoArrayHashMap(ResultId, FunctionInfo.Fn).init(self.a); |
| 328 | errdefer functions.deinit(); |
| 329 | |
| 330 | var fn_ty_decls = std.AutoHashMap(ResultId, usize).init(self.a); |
| 331 | defer fn_ty_decls.deinit(); |
| 332 | |
| 333 | var calls = std.AutoArrayHashMap(ResultId, void).init(self.a); |
| 334 | defer calls.deinit(); |
| 335 | |
| 336 | var callee_store = std.ArrayList(ResultId).init(self.a); |
| 337 | defer callee_store.deinit(); |
| 338 | |
| 339 | var maybe_current_function: ?ResultId = null; |
| 340 | var begin: usize = undefined; |
| 341 | var fn_ty_id: ResultId = undefined; |
| 342 | |
| 343 | var it = binary.iterateInstructions(); |
| 344 | while (it.next()) |inst| { |
| 345 | switch (inst.opcode) { |
| 346 | .OpEntryPoint => { |
| 347 | const entry = try entry_points.getOrPut(@enumFromInt(inst.operands[1])); |
| 348 | if (entry.found_existing) return error.DuplicateId; |
| 349 | }, |
| 350 | .OpTypeFunction => { |
| 351 | const entry = try fn_ty_decls.getOrPut(@enumFromInt(inst.operands[0])); |
| 352 | if (entry.found_existing) return error.DuplicateId; |
| 353 | entry.value_ptr.* = inst.offset; |
| 354 | }, |
| 355 | .OpFunction => { |
| 356 | maybe_current_function = @enumFromInt(inst.operands[1]); |
| 357 | begin = inst.offset; |
| 358 | fn_ty_id = @enumFromInt(inst.operands[3]); |
| 359 | }, |
| 360 | .OpFunctionCall => { |
| 361 | const callee: ResultId = @enumFromInt(inst.operands[2]); |
| 362 | try calls.put(callee, {}); |
| 363 | }, |
| 364 | .OpFunctionEnd => { |
| 365 | const current_function = maybe_current_function orelse { |
| 366 | log.err("encountered OpFunctionEnd without corresponding OpFunction", .{}); |
| 367 | return error.InvalidPhysicalFormat; |
| 368 | }; |
| 369 | const entry = try functions.getOrPut(current_function); |
| 370 | if (entry.found_existing) return error.DuplicateId; |
| 371 | |
| 372 | const first_callee = callee_store.items.len; |
| 373 | try callee_store.appendSlice(calls.keys()); |
| 374 | |
| 375 | const type_offset = fn_ty_decls.get(fn_ty_id) orelse { |
| 376 | log.err("Invalid OpFunction type", .{}); |
| 377 | return error.InvalidId; |
| 378 | }; |
| 379 | |
| 380 | entry.value_ptr.* = .{ |
| 381 | .begin_offset = begin, |
| 382 | .end_offset = it.offset, // Use past-end offset |
| 383 | .first_callee = first_callee, |
| 384 | .type_offset = type_offset, |
| 385 | }; |
| 386 | maybe_current_function = null; |
| 387 | calls.clearRetainingCapacity(); |
| 388 | }, |
| 389 | else => {}, |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | if (maybe_current_function != null) { |
| 394 | log.err("final OpFunction does not have an OpFunctionEnd", .{}); |
| 395 | return error.InvalidPhysicalFormat; |
| 396 | } |
| 397 | |
| 398 | return FunctionInfo{ |
| 399 | .functions = functions.unmanaged, |
| 400 | .entry_points = try self.a.dupe(ResultId, entry_points.keys()), |
| 401 | .callee_store = try callee_store.toOwnedSlice(), |
| 402 | }; |
| 403 | } |
| 404 | |
| 405 | /// Parse offsets in the instruction that contain result-ids. |
| 406 | /// Returned offsets are relative to inst.operands. |
| 407 | /// Returns in an arraylist to armortize allocations. |
| 408 | pub fn parseInstructionResultIds( |
| 409 | self: *Parser, |
| 410 | binary: BinaryModule, |
| 411 | inst: Instruction, |
| 412 | offsets: *std.ArrayList(u16), |
| 413 | ) !void { |
| 414 | const index = self.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(inst.opcode))).?; |
| 415 | const operands = InstructionSet.core.instructions()[index].operands; |
| 416 | |
| 417 | var offset: usize = 0; |
| 418 | switch (inst.opcode) { |
| 419 | .OpSpecConstantOp => { |
| 420 | assert(operands[0].kind == .IdResultType); |
| 421 | assert(operands[1].kind == .IdResult); |
| 422 | offset = try self.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets); |
| 423 | |
| 424 | if (offset >= inst.operands.len) return error.InvalidPhysicalFormat; |
| 425 | const spec_opcode = std.math.cast(u16, inst.operands[offset]) orelse return error.InvalidPhysicalFormat; |
| 426 | const spec_index = self.opcode_table.get(mapSetAndOpcode(.core, spec_opcode)) orelse |
| 427 | return error.InvalidPhysicalFormat; |
| 428 | const spec_operands = InstructionSet.core.instructions()[spec_index].operands; |
| 429 | assert(spec_operands[0].kind == .IdResultType); |
| 430 | assert(spec_operands[1].kind == .IdResult); |
| 431 | offset = try self.parseOperandsResultIds(binary, inst, spec_operands[2..], offset + 1, offsets); |
| 432 | }, |
| 433 | .OpExtInst => { |
| 434 | assert(operands[0].kind == .IdResultType); |
| 435 | assert(operands[1].kind == .IdResult); |
| 436 | offset = try self.parseOperandsResultIds(binary, inst, operands[0..2], offset, offsets); |
| 437 | |
| 438 | if (offset + 1 >= inst.operands.len) return error.InvalidPhysicalFormat; |
| 439 | const set_id: ResultId = @enumFromInt(inst.operands[offset]); |
| 440 | const set = binary.ext_inst_map.get(set_id) orelse { |
| 441 | log.err("Invalid instruction set {}", .{@intFromEnum(set_id)}); |
| 442 | return error.InvalidId; |
| 443 | }; |
| 444 | const ext_opcode = std.math.cast(u16, inst.operands[offset + 1]) orelse return error.InvalidPhysicalFormat; |
| 445 | const ext_index = self.opcode_table.get(mapSetAndOpcode(set, ext_opcode)) orelse |
| 446 | return error.InvalidPhysicalFormat; |
| 447 | const ext_operands = set.instructions()[ext_index].operands; |
| 448 | offset = try self.parseOperandsResultIds(binary, inst, ext_operands, offset + 2, offsets); |
| 449 | }, |
| 450 | else => { |
| 451 | offset = try self.parseOperandsResultIds(binary, inst, operands, offset, offsets); |
| 452 | }, |
| 453 | } |
| 454 | |
| 455 | if (offset != inst.operands.len) return error.InvalidPhysicalFormat; |
| 456 | } |
| 457 | |
| 458 | fn parseOperandsResultIds( |
| 459 | self: *Parser, |
| 460 | binary: BinaryModule, |
| 461 | inst: Instruction, |
| 462 | operands: []const spec.Operand, |
| 463 | start_offset: usize, |
| 464 | offsets: *std.ArrayList(u16), |
| 465 | ) !usize { |
| 466 | var offset = start_offset; |
| 467 | for (operands) |operand| { |
| 468 | offset = try self.parseOperandResultIds(binary, inst, operand, offset, offsets); |
| 469 | } |
| 470 | return offset; |
| 471 | } |
| 472 | |
| 473 | fn parseOperandResultIds( |
| 474 | self: *Parser, |
| 475 | binary: BinaryModule, |
| 476 | inst: Instruction, |
| 477 | operand: spec.Operand, |
| 478 | start_offset: usize, |
| 479 | offsets: *std.ArrayList(u16), |
| 480 | ) !usize { |
| 481 | var offset = start_offset; |
| 482 | switch (operand.quantifier) { |
| 483 | .variadic => while (offset < inst.operands.len) { |
| 484 | offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); |
| 485 | }, |
| 486 | .optional => if (offset < inst.operands.len) { |
| 487 | offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); |
| 488 | }, |
| 489 | .required => { |
| 490 | offset = try self.parseOperandKindResultIds(binary, inst, operand.kind, offset, offsets); |
| 491 | }, |
| 492 | } |
| 493 | return offset; |
| 494 | } |
| 495 | |
| 496 | fn parseOperandKindResultIds( |
| 497 | self: *Parser, |
| 498 | binary: BinaryModule, |
| 499 | inst: Instruction, |
| 500 | kind: spec.OperandKind, |
| 501 | start_offset: usize, |
| 502 | offsets: *std.ArrayList(u16), |
| 503 | ) !usize { |
| 504 | var offset = start_offset; |
| 505 | if (offset >= inst.operands.len) return error.InvalidPhysicalFormat; |
| 506 | |
| 507 | switch (kind.category()) { |
| 508 | .bit_enum => { |
| 509 | const mask = inst.operands[offset]; |
| 510 | offset += 1; |
| 511 | for (kind.enumerants()) |enumerant| { |
| 512 | if ((mask & enumerant.value) != 0) { |
| 513 | for (enumerant.parameters) |param_kind| { |
| 514 | offset = try self.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets); |
| 515 | } |
| 516 | } |
| 517 | } |
| 518 | }, |
| 519 | .value_enum => { |
| 520 | const value = inst.operands[offset]; |
| 521 | offset += 1; |
| 522 | for (kind.enumerants()) |enumerant| { |
| 523 | if (value == enumerant.value) { |
| 524 | for (enumerant.parameters) |param_kind| { |
| 525 | offset = try self.parseOperandKindResultIds(binary, inst, param_kind, offset, offsets); |
| 526 | } |
| 527 | break; |
| 528 | } |
| 529 | } |
| 530 | }, |
| 531 | .id => { |
| 532 | const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat; |
| 533 | try offsets.append(this_offset); |
| 534 | offset += 1; |
| 535 | }, |
| 536 | else => switch (kind) { |
| 537 | .LiteralInteger, .LiteralFloat => offset += 1, |
| 538 | .LiteralString => while (true) { |
| 539 | if (offset >= inst.operands.len) return error.InvalidPhysicalFormat; |
| 540 | const word = inst.operands[offset]; |
| 541 | offset += 1; |
| 542 | |
| 543 | if (word & 0xFF000000 == 0 or |
| 544 | word & 0x00FF0000 == 0 or |
| 545 | word & 0x0000FF00 == 0 or |
| 546 | word & 0x000000FF == 0) |
| 547 | { |
| 548 | break; |
| 549 | } |
| 550 | }, |
| 551 | .LiteralContextDependentNumber => { |
| 552 | assert(inst.opcode == .OpConstant or inst.opcode == .OpSpecConstantOp); |
| 553 | const bit_width = binary.arith_type_width.get(@enumFromInt(inst.operands[0])) orelse { |
| 554 | log.err("invalid LiteralContextDependentNumber type {}", .{inst.operands[0]}); |
| 555 | return error.InvalidId; |
| 556 | }; |
| 557 | offset += switch (bit_width) { |
| 558 | 1...32 => 1, |
| 559 | 33...64 => 2, |
| 560 | else => unreachable, |
| 561 | }; |
| 562 | }, |
| 563 | .LiteralExtInstInteger => unreachable, |
| 564 | .LiteralSpecConstantOpInteger => unreachable, |
| 565 | .PairLiteralIntegerIdRef => { // Switch case |
| 566 | assert(inst.opcode == .OpSwitch); |
| 567 | const bit_width = binary.arith_type_width.get(@enumFromInt(inst.operands[0])) orelse { |
| 568 | log.err("invalid OpSwitch type {}", .{inst.operands[0]}); |
| 569 | return error.InvalidId; |
| 570 | }; |
| 571 | offset += switch (bit_width) { |
| 572 | 1...32 => 1, |
| 573 | 33...64 => 2, |
| 574 | else => unreachable, |
| 575 | }; |
| 576 | const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat; |
| 577 | try offsets.append(this_offset); |
| 578 | offset += 1; |
| 579 | }, |
| 580 | .PairIdRefLiteralInteger => { |
| 581 | const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat; |
| 582 | try offsets.append(this_offset); |
| 583 | offset += 2; |
| 584 | }, |
| 585 | .PairIdRefIdRef => { |
| 586 | const a = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat; |
| 587 | const b = std.math.cast(u16, offset + 1) orelse return error.InvalidPhysicalFormat; |
| 588 | try offsets.append(a); |
| 589 | try offsets.append(b); |
| 590 | offset += 2; |
| 591 | }, |
| 592 | else => unreachable, |
| 593 | }, |
| 594 | } |
| 595 | return offset; |
| 596 | } |
| 597 | }; |