| author | |
| committer | |
| log | 98ee39d1b0ed516428c611d8dc1e52d21c786f97 |
| tree | a5667f3012952c1301b5d8631d86b753d0f71704 |
| parent | 1b6ebce0da45169979b0f51a07274ff6fb5590bc |
4 files changed, 855 insertions(+), 138 deletions(-)
src/codegen/spirv.zig+83-95| ... | ... | @@ -21,8 +21,8 @@ const IdResultType = spec.IdResultType; |
| 21 | 21 | |
| 22 | 22 | const SpvModule = @import("spirv/Module.zig"); |
| 23 | 23 | const SpvSection = @import("spirv/Section.zig"); |
| 24 | const SpvType = @import("spirv/type.zig").Type; | |
| 24 | 25 | |
| 25 | const TypeCache = std.HashMapUnmanaged(Type, IdResultType, Type.HashContext64, std.hash_map.default_max_load_percentage); | |
| 26 | 26 | const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef); |
| 27 | 27 | |
| 28 | 28 | const IncomingBlock = struct { |
| ... | ... | @@ -61,10 +61,6 @@ pub const DeclGen = struct { |
| 61 | 61 | /// A counter to keep track of how many `arg` instructions we've seen yet. |
| 62 | 62 | next_arg_index: u32, |
| 63 | 63 | |
| 64 | /// A cache for zig types to prevent having to re-process a particular type. This structure is kept around | |
| 65 | /// after a call to `gen` so that they don't have to be re-resolved for different decls. | |
| 66 | type_cache: TypeCache = .{}, | |
| 67 | ||
| 68 | 64 | /// A map keeping track of which instruction generated which result-id. |
| 69 | 65 | inst_results: InstMap = .{}, |
| 70 | 66 | |
| ... | ... | @@ -159,7 +155,6 @@ pub const DeclGen = struct { |
| 159 | 155 | self.liveness = liveness; |
| 160 | 156 | self.args.items.len = 0; |
| 161 | 157 | self.next_arg_index = 0; |
| 162 | // Note: don't clear type_cache. | |
| 163 | 158 | self.inst_results.clearRetainingCapacity(); |
| 164 | 159 | self.blocks.clearRetainingCapacity(); |
| 165 | 160 | self.current_block_label_id = undefined; |
| ... | ... | @@ -177,7 +172,6 @@ pub const DeclGen = struct { |
| 177 | 172 | /// Free resources owned by the DeclGen. |
| 178 | 173 | pub fn deinit(self: *DeclGen) void { |
| 179 | 174 | self.args.deinit(self.spv.gpa); |
| 180 | self.type_cache.deinit(self.spv.gpa); | |
| 181 | 175 | self.inst_results.deinit(self.spv.gpa); |
| 182 | 176 | self.blocks.deinit(self.spv.gpa); |
| 183 | 177 | self.code.deinit(self.spv.gpa); |
| ... | ... | @@ -220,7 +214,7 @@ pub const DeclGen = struct { |
| 220 | 214 | /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to |
| 221 | 215 | /// keep track of the previous block. |
| 222 | 216 | fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void { |
| 223 | try self.code.emit(self.spv.gpa, .OpLabel, .{.id_result = label_id}); | |
| 217 | try self.code.emit(self.spv.gpa, .OpLabel, .{ .id_result = label_id }); | |
| 224 | 218 | self.current_block_label_id = label_id.toRef(); |
| 225 | 219 | } |
| 226 | 220 | |
| ... | ... | @@ -317,9 +311,9 @@ pub const DeclGen = struct { |
| 317 | 311 | }; |
| 318 | 312 | }, |
| 319 | 313 | // As of yet, there is no vector support in the self-hosted compiler. |
| 320 | .Vector => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}), | |
| 314 | .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}), | |
| 321 | 315 | // TODO: For which types is this the case? |
| 322 | else => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for {}", .{ty}), | |
| 316 | else => self.todo("implement arithmeticTypeInfo for {}", .{ty}), | |
| 323 | 317 | }; |
| 324 | 318 | } |
| 325 | 319 | |
| ... | ... | @@ -329,7 +323,7 @@ pub const DeclGen = struct { |
| 329 | 323 | const target = self.getTarget(); |
| 330 | 324 | const section = &self.spv.sections.types_globals_constants; |
| 331 | 325 | const result_id = self.spv.allocId(); |
| 332 | const result_type_id = try self.genType(ty); | |
| 326 | const result_type_id = try self.resolveTypeId(ty); | |
| 333 | 327 | |
| 334 | 328 | if (val.isUndef()) { |
| 335 | 329 | try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_type_id, .id_result = result_id }); |
| ... | ... | @@ -341,7 +335,7 @@ pub const DeclGen = struct { |
| 341 | 335 | const int_info = ty.intInfo(target); |
| 342 | 336 | const backing_bits = self.backingIntBits(int_info.bits) orelse { |
| 343 | 337 | // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits. |
| 344 | return self.fail("TODO: SPIR-V backend: implement composite int constants for {}", .{ty}); | |
| 338 | return self.todo("implement composite int constants for {}", .{ty}); | |
| 345 | 339 | }; |
| 346 | 340 | |
| 347 | 341 | // We can just use toSignedInt/toUnsignedInt here as it returns u64 - a type large enough to hold any |
| ... | ... | @@ -354,8 +348,8 @@ pub const DeclGen = struct { |
| 354 | 348 | var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt()) else val.toUnsignedInt(); |
| 355 | 349 | |
| 356 | 350 | const value: spec.LiteralContextDependentNumber = switch (backing_bits) { |
| 357 | 1...32 => .{.uint32 = @truncate(u32, int_bits)}, | |
| 358 | 33...64 => .{.uint64 = int_bits}, | |
| 351 | 1...32 => .{ .uint32 = @truncate(u32, int_bits) }, | |
| 352 | 33...64 => .{ .uint64 = int_bits }, | |
| 359 | 353 | else => unreachable, |
| 360 | 354 | }; |
| 361 | 355 | |
| ... | ... | @@ -375,14 +369,14 @@ pub const DeclGen = struct { |
| 375 | 369 | }, |
| 376 | 370 | .Float => { |
| 377 | 371 | // At this point we are guaranteed that the target floating point type is supported, otherwise the function |
| 378 | // would have exited at genType(ty). | |
| 372 | // would have exited at resolveTypeId(ty). | |
| 379 | 373 | |
| 380 | 374 | const value: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) { |
| 381 | 375 | // Prevent upcasting to f32 by bitcasting and writing as a uint32. |
| 382 | 16 => .{.uint32 = @bitCast(u16, val.toFloat(f16))}, | |
| 383 | 32 => .{.float32 = val.toFloat(f32)}, | |
| 384 | 64 => .{.float64 = val.toFloat(f64)}, | |
| 385 | 128 => unreachable, // Filtered out in the call to genType. | |
| 376 | 16 => .{ .uint32 = @bitCast(u16, val.toFloat(f16)) }, | |
| 377 | 32 => .{ .float32 = val.toFloat(f32) }, | |
| 378 | 64 => .{ .float64 = val.toFloat(f64) }, | |
| 379 | 128 => unreachable, // Filtered out in the call to resolveTypeId. | |
| 386 | 380 | // TODO: Insert case for long double when the layout for that is determined? |
| 387 | 381 | else => unreachable, |
| 388 | 382 | }; |
| ... | ... | @@ -394,43 +388,43 @@ pub const DeclGen = struct { |
| 394 | 388 | }); |
| 395 | 389 | }, |
| 396 | 390 | .Void => unreachable, |
| 397 | else => return self.fail("TODO: SPIR-V backend: constant generation of type {}", .{ty}), | |
| 391 | else => return self.todo("constant generation of type {}", .{ty}), | |
| 398 | 392 | } |
| 399 | 393 | |
| 400 | 394 | return result_id.toRef(); |
| 401 | 395 | } |
| 402 | 396 | |
| 403 | fn genType(self: *DeclGen, ty: Type) Error!IdResultType { | |
| 404 | // We can't use getOrPut here so we can recursively generate types. | |
| 405 | if (self.type_cache.get(ty)) |already_generated| { | |
| 406 | return already_generated; | |
| 407 | } | |
| 397 | /// Turn a Zig type into a SPIR-V Type, and return its type result-id. | |
| 398 | fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType { | |
| 399 | return self.spv.typeResultId(try self.resolveType(ty)); | |
| 400 | } | |
| 408 | 401 | |
| 402 | /// Turn a Zig type into a SPIR-V Type, and return a reference to it. | |
| 403 | fn resolveType(self: *DeclGen, ty: Type) Error!SpvType.Ref { | |
| 409 | 404 | const target = self.getTarget(); |
| 410 | const section = &self.spv.sections.types_globals_constants; | |
| 411 | const result_id = self.spv.allocId(); | |
| 412 | ||
| 413 | switch (ty.zigTypeTag()) { | |
| 414 | .Void => try section.emit(self.spv.gpa, .OpTypeVoid, .{.id_result = result_id}), | |
| 415 | .Bool => try section.emit(self.spv.gpa, .OpTypeBool, .{.id_result = result_id}), | |
| 416 | .Int => { | |
| 405 | return switch (ty.zigTypeTag()) { | |
| 406 | .Void => try self.spv.resolveType(SpvType.initTag(.void)), | |
| 407 | .Bool => blk: { | |
| 408 | // TODO: SPIR-V booleans are opaque. For local variables this is fine, but for structs | |
| 409 | // members we want to use integer types instead. | |
| 410 | break :blk try self.spv.resolveType(SpvType.initTag(.bool)); | |
| 411 | }, | |
| 412 | .Int => blk: { | |
| 417 | 413 | const int_info = ty.intInfo(target); |
| 418 | 414 | const backing_bits = self.backingIntBits(int_info.bits) orelse { |
| 419 | // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits. | |
| 420 | return self.fail("TODO: SPIR-V backend: implement composite int {}", .{ty}); | |
| 415 | // TODO: Integers too big for any native type are represented as "composite integers": | |
| 416 | // An array of largestSupportedIntBits. | |
| 417 | return self.todo("Implement composite int type {}", .{ty}); | |
| 421 | 418 | }; |
| 422 | 419 | |
| 423 | // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here. | |
| 424 | try section.emit(self.spv.gpa, .OpTypeInt, .{ | |
| 425 | .id_result = result_id, | |
| 420 | const payload = try self.spv.arena.create(SpvType.Payload.Int); | |
| 421 | payload.* = .{ | |
| 426 | 422 | .width = backing_bits, |
| 427 | .signedness = switch (int_info.signedness) { | |
| 428 | .unsigned => @as(spec.LiteralInteger, 0), | |
| 429 | .signed => 1, | |
| 430 | }, | |
| 431 | }); | |
| 423 | .signedness = int_info.signedness, | |
| 424 | }; | |
| 425 | break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); | |
| 432 | 426 | }, |
| 433 | .Float => { | |
| 427 | .Float => blk: { | |
| 434 | 428 | // We can (and want) not really emulate floating points with other floating point types like with the integer types, |
| 435 | 429 | // so if the float is not supported, just return an error. |
| 436 | 430 | const bits = ty.floatBits(target); |
| ... | ... | @@ -446,39 +440,34 @@ pub const DeclGen = struct { |
| 446 | 440 | return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits}); |
| 447 | 441 | } |
| 448 | 442 | |
| 449 | try section.emit(self.spv.gpa, .OpTypeFloat, .{.id_result = result_id, .width = bits}); | |
| 443 | const payload = try self.spv.arena.create(SpvType.Payload.Float); | |
| 444 | payload.* = .{ | |
| 445 | .width = bits, | |
| 446 | }; | |
| 447 | break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); | |
| 450 | 448 | }, |
| 451 | .Fn => { | |
| 449 | .Fn => blk: { | |
| 452 | 450 | // We only support zig-calling-convention functions, no varargs. |
| 453 | 451 | if (ty.fnCallingConvention() != .Unspecified) |
| 454 | 452 | return self.fail("Unsupported calling convention for SPIR-V", .{}); |
| 455 | 453 | if (ty.fnIsVarArgs()) |
| 456 | return self.fail("VarArgs unsupported for SPIR-V", .{}); | |
| 457 | ||
| 458 | // In order to avoid a temporary here, first generate all the required types and then simply look them up | |
| 459 | // when generating the function type. | |
| 460 | const params = ty.fnParamLen(); | |
| 461 | var i: usize = 0; | |
| 462 | while (i < params) : (i += 1) { | |
| 463 | _ = try self.genType(ty.fnParamType(i)); | |
| 464 | } | |
| 465 | ||
| 466 | const return_type_id = try self.genType(ty.fnReturnType()); | |
| 454 | return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); | |
| 467 | 455 | |
| 468 | try section.emitRaw(self.spv.gpa, .OpTypeFunction, 2 + @intCast(u16, ty.fnParamLen())); | |
| 456 | const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen()); | |
| 457 | for (param_types) |*param, i| { | |
| 458 | param.* = try self.resolveType(ty.fnParamType(i)); | |
| 459 | } | |
| 469 | 460 | |
| 470 | // result id + result type id + parameter type ids. | |
| 471 | section.writeOperand(IdResult, result_id); | |
| 472 | section.writeOperand(IdResultType, return_type_id); | |
| 461 | const return_type = try self.resolveType(ty.fnReturnType()); | |
| 473 | 462 | |
| 474 | i = 0; | |
| 475 | while (i < params) : (i += 1) { | |
| 476 | const param_type_id = self.type_cache.get(ty.fnParamType(i)).?; | |
| 477 | section.writeOperand(IdRef, param_type_id.toRef()); | |
| 478 | } | |
| 463 | const payload = try self.spv.arena.create(SpvType.Payload.Function); | |
| 464 | payload.* = .{ .return_type = return_type, .parameters = param_types }; | |
| 465 | break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base)); | |
| 466 | }, | |
| 467 | .Pointer => { | |
| 468 | // This type can now be properly implemented, but we still need to implement the storage classes as proper address spaces. | |
| 469 | return self.todo("Implement type Pointer properly", .{}); | |
| 479 | 470 | }, |
| 480 | // When recursively generating a type, we cannot infer the pointer's storage class. See genPointerType. | |
| 481 | .Pointer => return self.fail("Cannot create pointer with unknown storage class", .{}), | |
| 482 | 471 | .Vector => { |
| 483 | 472 | // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations |
| 484 | 473 | // which work on them), so simply use those. |
| ... | ... | @@ -488,23 +477,21 @@ pub const DeclGen = struct { |
| 488 | 477 | // is adequate at all for this. |
| 489 | 478 | |
| 490 | 479 | // TODO: Vectors are not yet supported by the self-hosted compiler itself it seems. |
| 491 | return self.fail("TODO: SPIR-V backend: implement type Vector", .{}); | |
| 480 | return self.todo("Implement type Vector", .{}); | |
| 492 | 481 | }, |
| 482 | ||
| 493 | 483 | .Null, |
| 494 | 484 | .Undefined, |
| 495 | 485 | .EnumLiteral, |
| 496 | 486 | .ComptimeFloat, |
| 497 | 487 | .ComptimeInt, |
| 498 | 488 | .Type, |
| 499 | => unreachable, // Must be const or comptime. | |
| 489 | => unreachable, // Must be comptime. | |
| 500 | 490 | |
| 501 | 491 | .BoundFn => unreachable, // this type will be deleted from the language. |
| 502 | 492 | |
| 503 | else => |tag| return self.fail("TODO: SPIR-V backend: implement type {}s", .{tag}), | |
| 504 | } | |
| 505 | ||
| 506 | try self.type_cache.putNoClobber(self.spv.gpa, ty, result_id.toResultType()); | |
| 507 | return result_id.toResultType(); | |
| 493 | else => |tag| return self.todo("Implement zig type '{}'", .{tag}), | |
| 494 | }; | |
| 508 | 495 | } |
| 509 | 496 | |
| 510 | 497 | /// SPIR-V requires pointers to have a storage class (address space), and so we have a special function for that. |
| ... | ... | @@ -517,7 +504,7 @@ pub const DeclGen = struct { |
| 517 | 504 | // TODO: There are many constraints which are ignored for now: We may only create pointers to certain types, and to other types |
| 518 | 505 | // if more capabilities are enabled. For example, we may only create pointers to f16 if Float16Buffer is enabled. |
| 519 | 506 | // These also relates to the pointer's address space. |
| 520 | const child_id = try self.genType(ty.elemType()); | |
| 507 | const child_id = try self.resolveTypeId(ty.elemType()); | |
| 521 | 508 | |
| 522 | 509 | try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{ |
| 523 | 510 | .id_result = result_id, |
| ... | ... | @@ -534,9 +521,9 @@ pub const DeclGen = struct { |
| 534 | 521 | |
| 535 | 522 | if (decl.val.castTag(.function)) |_| { |
| 536 | 523 | assert(decl.ty.zigTypeTag() == .Fn); |
| 537 | const prototype_id = try self.genType(decl.ty); | |
| 524 | const prototype_id = try self.resolveTypeId(decl.ty); | |
| 538 | 525 | try self.spv.sections.functions.emit(self.spv.gpa, .OpFunction, .{ |
| 539 | .id_result_type = self.type_cache.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype. | |
| 526 | .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()), | |
| 540 | 527 | .id_result = result_id, |
| 541 | 528 | .function_control = .{}, // TODO: We can set inline here if the type requires it. |
| 542 | 529 | .function_type = prototype_id.toRef(), |
| ... | ... | @@ -547,7 +534,7 @@ pub const DeclGen = struct { |
| 547 | 534 | |
| 548 | 535 | try self.args.ensureUnusedCapacity(self.spv.gpa, params); |
| 549 | 536 | while (i < params) : (i += 1) { |
| 550 | const param_type_id = self.type_cache.get(decl.ty.fnParamType(i)).?; | |
| 537 | const param_type_id = try self.resolveTypeId(decl.ty.fnParamType(i)); | |
| 551 | 538 | const arg_result_id = self.spv.allocId(); |
| 552 | 539 | try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionParameter, .{ |
| 553 | 540 | .id_result_type = param_type_id, |
| ... | ... | @@ -573,7 +560,8 @@ pub const DeclGen = struct { |
| 573 | 560 | try self.spv.sections.functions.append(self.spv.gpa, self.code); |
| 574 | 561 | try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionEnd, {}); |
| 575 | 562 | } else { |
| 576 | return self.fail("TODO: SPIR-V backend: generate decl type {}", .{decl.ty.zigTypeTag()}); | |
| 563 | // TODO | |
| 564 | // return self.todo("generate decl type {}", .{decl.ty.zigTypeTag()}); | |
| 577 | 565 | } |
| 578 | 566 | } |
| 579 | 567 | |
| ... | ... | @@ -622,7 +610,7 @@ pub const DeclGen = struct { |
| 622 | 610 | .unreach => return self.airUnreach(), |
| 623 | 611 | // zig fmt: on |
| 624 | 612 | |
| 625 | else => |tag| return self.fail("TODO: SPIR-V backend: implement AIR tag {s}", .{ | |
| 613 | else => |tag| return self.todo("implement AIR tag {s}", .{ | |
| 626 | 614 | @tagName(tag), |
| 627 | 615 | }), |
| 628 | 616 | }; |
| ... | ... | @@ -635,7 +623,7 @@ pub const DeclGen = struct { |
| 635 | 623 | const lhs_id = try self.resolve(bin_op.lhs); |
| 636 | 624 | const rhs_id = try self.resolve(bin_op.rhs); |
| 637 | 625 | const result_id = self.spv.allocId(); |
| 638 | const result_type_id = try self.genType(self.air.typeOfIndex(inst)); | |
| 626 | const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst)); | |
| 639 | 627 | try self.code.emit(self.spv.gpa, opcode, .{ |
| 640 | 628 | .id_result_type = result_type_id, |
| 641 | 629 | .id_result = result_id, |
| ... | ... | @@ -654,7 +642,7 @@ pub const DeclGen = struct { |
| 654 | 642 | const rhs_id = try self.resolve(bin_op.rhs); |
| 655 | 643 | |
| 656 | 644 | const result_id = self.spv.allocId(); |
| 657 | const result_type_id = try self.genType(ty); | |
| 645 | const result_type_id = try self.resolveTypeId(ty); | |
| 658 | 646 | |
| 659 | 647 | assert(self.air.typeOf(bin_op.lhs).eql(ty)); |
| 660 | 648 | assert(self.air.typeOf(bin_op.rhs).eql(ty)); |
| ... | ... | @@ -665,10 +653,10 @@ pub const DeclGen = struct { |
| 665 | 653 | |
| 666 | 654 | const opcode_index: usize = switch (info.class) { |
| 667 | 655 | .composite_integer => { |
| 668 | return self.fail("TODO: SPIR-V backend: binary operations for composite integers", .{}); | |
| 656 | return self.todo("binary operations for composite integers", .{}); | |
| 669 | 657 | }, |
| 670 | 658 | .strange_integer => { |
| 671 | return self.fail("TODO: SPIR-V backend: binary operations for strange integers", .{}); | |
| 659 | return self.todo("binary operations for strange integers", .{}); | |
| 672 | 660 | }, |
| 673 | 661 | .integer => switch (info.signedness) { |
| 674 | 662 | .signed => @as(usize, 1), |
| ... | ... | @@ -702,7 +690,7 @@ pub const DeclGen = struct { |
| 702 | 690 | const lhs_id = try self.resolve(bin_op.lhs); |
| 703 | 691 | const rhs_id = try self.resolve(bin_op.rhs); |
| 704 | 692 | const result_id = self.spv.allocId(); |
| 705 | const result_type_id = try self.genType(Type.initTag(.bool)); | |
| 693 | const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); | |
| 706 | 694 | const op_ty = self.air.typeOf(bin_op.lhs); |
| 707 | 695 | assert(op_ty.eql(self.air.typeOf(bin_op.rhs))); |
| 708 | 696 | |
| ... | ... | @@ -712,10 +700,10 @@ pub const DeclGen = struct { |
| 712 | 700 | |
| 713 | 701 | const opcode_index: usize = switch (info.class) { |
| 714 | 702 | .composite_integer => { |
| 715 | return self.fail("TODO: SPIR-V backend: binary operations for composite integers", .{}); | |
| 703 | return self.todo("binary operations for composite integers", .{}); | |
| 716 | 704 | }, |
| 717 | 705 | .strange_integer => { |
| 718 | return self.fail("TODO: SPIR-V backend: comparison for strange integers", .{}); | |
| 706 | return self.todo("comparison for strange integers", .{}); | |
| 719 | 707 | }, |
| 720 | 708 | .float => 0, |
| 721 | 709 | .bool => 1, |
| ... | ... | @@ -746,7 +734,7 @@ pub const DeclGen = struct { |
| 746 | 734 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 747 | 735 | const operand_id = try self.resolve(ty_op.operand); |
| 748 | 736 | const result_id = self.spv.allocId(); |
| 749 | const result_type_id = try self.genType(Type.initTag(.bool)); | |
| 737 | const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); | |
| 750 | 738 | try self.code.emit(self.spv.gpa, .OpLogicalNot, .{ |
| 751 | 739 | .id_result_type = result_type_id, |
| 752 | 740 | .id_result = result_id, |
| ... | ... | @@ -813,9 +801,9 @@ pub const DeclGen = struct { |
| 813 | 801 | const result_id = self.spv.allocId(); |
| 814 | 802 | |
| 815 | 803 | // TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types |
| 816 | // are not allowed to be created from a phi node, and throw an error for those. For now, genType already throws | |
| 804 | // are not allowed to be created from a phi node, and throw an error for those. For now, resolveTypeId already throws | |
| 817 | 805 | // an error for pointers. |
| 818 | const result_type_id = try self.genType(ty); | |
| 806 | const result_type_id = try self.resolveTypeId(ty); | |
| 819 | 807 | _ = result_type_id; |
| 820 | 808 | |
| 821 | 809 | try self.code.emitRaw(self.spv.gpa, .OpPhi, 2 + @intCast(u16, incoming_blocks.items.len * 2)); // result type + result + variable/parent... |
| ... | ... | @@ -838,7 +826,7 @@ pub const DeclGen = struct { |
| 838 | 826 | try block.incoming_blocks.append(self.spv.gpa, .{ .src_label_id = self.current_block_label_id, .break_value_id = operand_id }); |
| 839 | 827 | } |
| 840 | 828 | |
| 841 | try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = block.label_id}); | |
| 829 | try self.code.emit(self.spv.gpa, .OpBranch, .{ .target_label = block.label_id }); | |
| 842 | 830 | } |
| 843 | 831 | |
| 844 | 832 | fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void { |
| ... | ... | @@ -882,7 +870,7 @@ pub const DeclGen = struct { |
| 882 | 870 | const operand_id = try self.resolve(ty_op.operand); |
| 883 | 871 | const ty = self.air.typeOfIndex(inst); |
| 884 | 872 | |
| 885 | const result_type_id = try self.genType(ty); | |
| 873 | const result_type_id = try self.resolveTypeId(ty); | |
| 886 | 874 | const result_id = self.spv.allocId(); |
| 887 | 875 | |
| 888 | 876 | const access = spec.MemoryAccess.Extended{ |
| ... | ... | @@ -906,13 +894,13 @@ pub const DeclGen = struct { |
| 906 | 894 | const loop_label_id = self.spv.allocId(); |
| 907 | 895 | |
| 908 | 896 | // Jump to the loop entry point |
| 909 | try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = loop_label_id.toRef()}); | |
| 897 | try self.code.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() }); | |
| 910 | 898 | |
| 911 | 899 | // TODO: Look into OpLoopMerge. |
| 912 | 900 | try self.beginSpvBlock(loop_label_id); |
| 913 | 901 | try self.genBody(body); |
| 914 | 902 | |
| 915 | try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = loop_label_id.toRef()}); | |
| 903 | try self.code.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() }); | |
| 916 | 904 | } |
| 917 | 905 | |
| 918 | 906 | fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void { |
| ... | ... | @@ -920,7 +908,7 @@ pub const DeclGen = struct { |
| 920 | 908 | const operand_ty = self.air.typeOf(operand); |
| 921 | 909 | if (operand_ty.hasRuntimeBits()) { |
| 922 | 910 | const operand_id = try self.resolve(operand); |
| 923 | try self.code.emit(self.spv.gpa, .OpReturnValue, .{.value = operand_id}); | |
| 911 | try self.code.emit(self.spv.gpa, .OpReturnValue, .{ .value = operand_id }); | |
| 924 | 912 | } else { |
| 925 | 913 | try self.code.emit(self.spv.gpa, .OpReturn, {}); |
| 926 | 914 | } |
src/codegen/spirv/Module.zig+301-26| ... | ... | @@ -9,14 +9,20 @@ const Module = @This(); |
| 9 | 9 | |
| 10 | 10 | const std = @import("std"); |
| 11 | 11 | const Allocator = std.mem.Allocator; |
| 12 | const assert = std.debug.assert; | |
| 12 | 13 | |
| 13 | 14 | const ZigDecl = @import("../../Module.zig").Decl; |
| 14 | 15 | |
| 15 | 16 | const spec = @import("spec.zig"); |
| 16 | 17 | const Word = spec.Word; |
| 17 | 18 | const IdRef = spec.IdRef; |
| 19 | const IdResult = spec.IdResult; | |
| 20 | const IdResultType = spec.IdResultType; | |
| 18 | 21 | |
| 19 | 22 | const Section = @import("Section.zig"); |
| 23 | const Type = @import("type.zig").Type; | |
| 24 | ||
| 25 | const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true); | |
| 20 | 26 | |
| 21 | 27 | /// A general-purpose allocator which may be used to allocate resources for this module |
| 22 | 28 | gpa: Allocator, |
| ... | ... | @@ -57,6 +63,12 @@ next_result_id: Word, |
| 57 | 63 | /// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource. |
| 58 | 64 | source_file_names: std.StringHashMapUnmanaged(IdRef) = .{}, |
| 59 | 65 | |
| 66 | /// SPIR-V type cache. Note that according to SPIR-V spec section 2.8, Types and Variables, non-pointer | |
| 67 | /// non-aggrerate types (which includes matrices and vectors) must have a _unique_ representation in | |
| 68 | /// the final binary. | |
| 69 | /// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref). | |
| 70 | type_cache: TypeCache = .{}, | |
| 71 | ||
| 60 | 72 | pub fn init(gpa: Allocator, arena: Allocator) Module { |
| 61 | 73 | return .{ |
| 62 | 74 | .gpa = gpa, |
| ... | ... | @@ -75,44 +87,20 @@ pub fn deinit(self: *Module) void { |
| 75 | 87 | self.sections.functions.deinit(self.gpa); |
| 76 | 88 | |
| 77 | 89 | self.source_file_names.deinit(self.gpa); |
| 90 | self.type_cache.deinit(self.gpa); | |
| 78 | 91 | |
| 79 | 92 | self.* = undefined; |
| 80 | 93 | } |
| 81 | 94 | |
| 82 | 95 | pub fn allocId(self: *Module) spec.IdResult { |
| 83 | 96 | defer self.next_result_id += 1; |
| 84 | return .{.id = self.next_result_id}; | |
| 97 | return .{ .id = self.next_result_id }; | |
| 85 | 98 | } |
| 86 | 99 | |
| 87 | 100 | pub fn idBound(self: Module) Word { |
| 88 | 101 | return self.next_result_id; |
| 89 | 102 | } |
| 90 | 103 | |
| 91 | /// Fetch the result-id of an OpString instruction that encodes the path of the source | |
| 92 | /// file of the decl. This function may also emit an OpSource with source-level information regarding | |
| 93 | /// the decl. | |
| 94 | pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef { | |
| 95 | const path = decl.getFileScope().sub_file_path; | |
| 96 | const result = try self.source_file_names.getOrPut(self.gpa, path); | |
| 97 | if (!result.found_existing) { | |
| 98 | const file_result_id = self.allocId(); | |
| 99 | result.value_ptr.* = file_result_id.toRef(); | |
| 100 | try self.sections.debug_strings.emit(self.gpa, .OpString, .{ | |
| 101 | .id_result = file_result_id, | |
| 102 | .string = path, | |
| 103 | }); | |
| 104 | ||
| 105 | try self.sections.debug_strings.emit(self.gpa, .OpSource, .{ | |
| 106 | .source_language = .Unknown, // TODO: Register Zig source language. | |
| 107 | .version = 0, // TODO: Zig version as u32? | |
| 108 | .file = file_result_id.toRef(), | |
| 109 | .source = null, // TODO: Store actual source also? | |
| 110 | }); | |
| 111 | } | |
| 112 | ||
| 113 | return result.value_ptr.*; | |
| 114 | } | |
| 115 | ||
| 116 | 104 | /// Emit this module as a spir-v binary. |
| 117 | 105 | pub fn flush(self: Module, file: std.fs.File) !void { |
| 118 | 106 | // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction" |
| ... | ... | @@ -151,3 +139,290 @@ pub fn flush(self: Module, file: std.fs.File) !void { |
| 151 | 139 | try file.setEndPos(file_size); |
| 152 | 140 | try file.pwritevAll(&iovc_buffers, 0); |
| 153 | 141 | } |
| 142 | ||
| 143 | /// Fetch the result-id of an OpString instruction that encodes the path of the source | |
| 144 | /// file of the decl. This function may also emit an OpSource with source-level information regarding | |
| 145 | /// the decl. | |
| 146 | pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef { | |
| 147 | const path = decl.getFileScope().sub_file_path; | |
| 148 | const result = try self.source_file_names.getOrPut(self.gpa, path); | |
| 149 | if (!result.found_existing) { | |
| 150 | const file_result_id = self.allocId(); | |
| 151 | result.value_ptr.* = file_result_id.toRef(); | |
| 152 | try self.sections.debug_strings.emit(self.gpa, .OpString, .{ | |
| 153 | .id_result = file_result_id, | |
| 154 | .string = path, | |
| 155 | }); | |
| 156 | ||
| 157 | try self.sections.debug_strings.emit(self.gpa, .OpSource, .{ | |
| 158 | .source_language = .Unknown, // TODO: Register Zig source language. | |
| 159 | .version = 0, // TODO: Zig version as u32? | |
| 160 | .file = file_result_id.toRef(), | |
| 161 | .source = null, // TODO: Store actual source also? | |
| 162 | }); | |
| 163 | } | |
| 164 | ||
| 165 | return result.value_ptr.*; | |
| 166 | } | |
| 167 | ||
| 168 | /// Fetch a result-id for a spir-v type. This function deduplicates the type as appropriate, | |
| 169 | /// and returns a cached version if that exists. | |
| 170 | /// Note: This function does not attempt to perform any validation on the type. | |
| 171 | /// The type is emitted in a shallow fashion; any child types should already | |
| 172 | /// be emitted at this point. | |
| 173 | pub fn resolveType(self: *Module, ty: Type) !Type.Ref { | |
| 174 | const result = try self.type_cache.getOrPut(self.gpa, ty); | |
| 175 | if (!result.found_existing) { | |
| 176 | result.value_ptr.* = try self.emitType(ty); | |
| 177 | } | |
| 178 | return result.index; | |
| 179 | } | |
| 180 | ||
| 181 | pub fn resolveTypeId(self: *Module, ty: Type) !IdRef { | |
| 182 | return self.typeResultId(try self.resolveType(ty)); | |
| 183 | } | |
| 184 | ||
| 185 | /// Get the result-id of a particular type, by reference. Asserts type_ref is valid. | |
| 186 | pub fn typeResultId(self: Module, type_ref: Type.Ref) IdResultType { | |
| 187 | return self.type_cache.values()[type_ref]; | |
| 188 | } | |
| 189 | ||
| 190 | /// Get the result-id of a particular type as IdRef, by Type.Ref. Asserts type_ref is valid. | |
| 191 | pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { | |
| 192 | return self.type_cache.values()[type_ref].toRef(); | |
| 193 | } | |
| 194 | ||
| 195 | /// Unconditionally emit a spir-v type into the appropriate section. | |
| 196 | /// Note: If this function is called with a type that is already generated, it may yield an invalid module | |
| 197 | /// as non-pointer non-aggregrate types must me unique! | |
| 198 | /// Note: This function does not attempt to perform any validation on the type. | |
| 199 | /// The type is emitted in a shallow fashion; any child types should already | |
| 200 | /// be emitted at this point. | |
| 201 | pub fn emitType(self: *Module, ty: Type) !IdResultType { | |
| 202 | const result_id = self.allocId(); | |
| 203 | const ref_id = result_id.toRef(); | |
| 204 | const types = &self.sections.types_globals_constants; | |
| 205 | const annotations = &self.sections.annotations; | |
| 206 | const result_id_operand = .{ .id_result = result_id }; | |
| 207 | ||
| 208 | switch (ty.tag()) { | |
| 209 | .void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand), | |
| 210 | .bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand), | |
| 211 | .int => try types.emit(self.gpa, .OpTypeInt, .{ | |
| 212 | .id_result = result_id, | |
| 213 | .width = ty.payload(.int).width, | |
| 214 | .signedness = switch (ty.payload(.int).signedness) { | |
| 215 | .unsigned => @as(spec.LiteralInteger, 0), | |
| 216 | .signed => 1, | |
| 217 | }, | |
| 218 | }), | |
| 219 | .float => try types.emit(self.gpa, .OpTypeFloat, .{ | |
| 220 | .id_result = result_id, | |
| 221 | .width = ty.payload(.float).width, | |
| 222 | }), | |
| 223 | .vector => try types.emit(self.gpa, .OpTypeVector, .{ | |
| 224 | .id_result = result_id, | |
| 225 | .component_type = self.typeResultId(ty.childType()).toRef(), | |
| 226 | .component_count = ty.payload(.vector).component_count, | |
| 227 | }), | |
| 228 | .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{ | |
| 229 | .id_result = result_id, | |
| 230 | .column_type = self.typeResultId(ty.childType()).toRef(), | |
| 231 | .column_count = ty.payload(.matrix).column_count, | |
| 232 | }), | |
| 233 | .image => { | |
| 234 | const info = ty.payload(.image); | |
| 235 | try types.emit(self.gpa, .OpTypeImage, .{ | |
| 236 | .id_result = result_id, | |
| 237 | .sampled_type = self.typeResultId(ty.childType()).toRef(), | |
| 238 | .dim = info.dim, | |
| 239 | .depth = @enumToInt(info.depth), | |
| 240 | .arrayed = @boolToInt(info.arrayed), | |
| 241 | .ms = @boolToInt(info.multisampled), | |
| 242 | .sampled = @enumToInt(info.sampled), | |
| 243 | .image_format = info.format, | |
| 244 | .access_qualifier = info.access_qualifier, | |
| 245 | }); | |
| 246 | }, | |
| 247 | .sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand), | |
| 248 | .sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{ | |
| 249 | .id_result = result_id, | |
| 250 | .image_type = self.typeResultId(ty.childType()).toRef(), | |
| 251 | }), | |
| 252 | .array => { | |
| 253 | const info = ty.payload(.array); | |
| 254 | assert(info.length != 0); | |
| 255 | try types.emit(self.gpa, .OpTypeArray, .{ | |
| 256 | .id_result = result_id, | |
| 257 | .element_type = self.typeResultId(ty.childType()).toRef(), | |
| 258 | .length = .{ .id = 0 }, // TODO: info.length must be emitted as constant! | |
| 259 | }); | |
| 260 | if (info.array_stride != 0) { | |
| 261 | try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); | |
| 262 | } | |
| 263 | }, | |
| 264 | .runtime_array => { | |
| 265 | const info = ty.payload(.runtime_array); | |
| 266 | try types.emit(self.gpa, .OpTypeRuntimeArray, .{ | |
| 267 | .id_result = result_id, | |
| 268 | .element_type = self.typeResultId(ty.childType()).toRef(), | |
| 269 | }); | |
| 270 | if (info.array_stride != 0) { | |
| 271 | try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); | |
| 272 | } | |
| 273 | }, | |
| 274 | .@"struct" => { | |
| 275 | const info = ty.payload(.@"struct"); | |
| 276 | try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len); | |
| 277 | types.writeOperand(IdResult, result_id); | |
| 278 | for (info.members) |member| { | |
| 279 | types.writeOperand(IdRef, self.typeResultId(member.ty).toRef()); | |
| 280 | } | |
| 281 | try self.decorateStruct(ref_id, info); | |
| 282 | }, | |
| 283 | .@"opaque" => try types.emit(self.gpa, .OpTypeOpaque, .{ | |
| 284 | .id_result = result_id, | |
| 285 | .literal_string = ty.payload(.@"opaque").name, | |
| 286 | }), | |
| 287 | .pointer => { | |
| 288 | const info = ty.payload(.pointer); | |
| 289 | try types.emit(self.gpa, .OpTypePointer, .{ | |
| 290 | .id_result = result_id, | |
| 291 | .storage_class = info.storage_class, | |
| 292 | .type = self.typeResultId(ty.childType()).toRef(), | |
| 293 | }); | |
| 294 | if (info.array_stride != 0) { | |
| 295 | try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); | |
| 296 | } | |
| 297 | if (info.alignment) |alignment| { | |
| 298 | try annotations.decorate(self.gpa, ref_id, .{ .Alignment = .{ .alignment = alignment } }); | |
| 299 | } | |
| 300 | if (info.max_byte_offset) |max_byte_offset| { | |
| 301 | try annotations.decorate(self.gpa, ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } }); | |
| 302 | } | |
| 303 | }, | |
| 304 | .function => { | |
| 305 | const info = ty.payload(.function); | |
| 306 | try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len); | |
| 307 | types.writeOperand(IdResult, result_id); | |
| 308 | types.writeOperand(IdRef, self.typeResultId(info.return_type).toRef()); | |
| 309 | for (info.parameters) |parameter_type| { | |
| 310 | types.writeOperand(IdRef, self.typeResultId(parameter_type).toRef()); | |
| 311 | } | |
| 312 | }, | |
| 313 | .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand), | |
| 314 | .device_event => try types.emit(self.gpa, .OpTypeDeviceEvent, result_id_operand), | |
| 315 | .reserve_id => try types.emit(self.gpa, .OpTypeReserveId, result_id_operand), | |
| 316 | .queue => try types.emit(self.gpa, .OpTypeQueue, result_id_operand), | |
| 317 | .pipe => try types.emit(self.gpa, .OpTypePipe, .{ | |
| 318 | .id_result = result_id, | |
| 319 | .qualifier = ty.payload(.pipe).qualifier, | |
| 320 | }), | |
| 321 | .pipe_storage => try types.emit(self.gpa, .OpTypePipeStorage, result_id_operand), | |
| 322 | .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand), | |
| 323 | } | |
| 324 | ||
| 325 | return result_id.toResultType(); | |
| 326 | } | |
| 327 | ||
| 328 | fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void { | |
| 329 | const annotations = &self.sections.annotations; | |
| 330 | ||
| 331 | // Decorations for the struct type itself. | |
| 332 | if (info.decorations.block) | |
| 333 | try annotations.decorate(self.gpa, target, .Block); | |
| 334 | if (info.decorations.buffer_block) | |
| 335 | try annotations.decorate(self.gpa, target, .BufferBlock); | |
| 336 | if (info.decorations.glsl_shared) | |
| 337 | try annotations.decorate(self.gpa, target, .GLSLShared); | |
| 338 | if (info.decorations.glsl_packed) | |
| 339 | try annotations.decorate(self.gpa, target, .GLSLPacked); | |
| 340 | if (info.decorations.c_packed) | |
| 341 | try annotations.decorate(self.gpa, target, .CPacked); | |
| 342 | ||
| 343 | // Decorations for the struct members. | |
| 344 | const extra = info.member_decoration_extra; | |
| 345 | var extra_i: u32 = 0; | |
| 346 | for (info.members) |member, i| { | |
| 347 | const d = member.decorations; | |
| 348 | const index = @intCast(Word, i); | |
| 349 | switch (d.matrix_layout) { | |
| 350 | .row_major => try annotations.decorateMember(self.gpa, target, index, .RowMajor), | |
| 351 | .col_major => try annotations.decorateMember(self.gpa, target, index, .ColMajor), | |
| 352 | .none => {}, | |
| 353 | } | |
| 354 | if (d.matrix_layout != .none) { | |
| 355 | try annotations.decorateMember(self.gpa, target, index, .{ | |
| 356 | .MatrixStride = .{ .matrix_stride = extra[extra_i] }, | |
| 357 | }); | |
| 358 | extra_i += 1; | |
| 359 | } | |
| 360 | ||
| 361 | if (d.no_perspective) | |
| 362 | try annotations.decorateMember(self.gpa, target, index, .NoPerspective); | |
| 363 | if (d.flat) | |
| 364 | try annotations.decorateMember(self.gpa, target, index, .Flat); | |
| 365 | if (d.patch) | |
| 366 | try annotations.decorateMember(self.gpa, target, index, .Patch); | |
| 367 | if (d.centroid) | |
| 368 | try annotations.decorateMember(self.gpa, target, index, .Centroid); | |
| 369 | if (d.sample) | |
| 370 | try annotations.decorateMember(self.gpa, target, index, .Sample); | |
| 371 | if (d.invariant) | |
| 372 | try annotations.decorateMember(self.gpa, target, index, .Invariant); | |
| 373 | if (d.@"volatile") | |
| 374 | try annotations.decorateMember(self.gpa, target, index, .Volatile); | |
| 375 | if (d.coherent) | |
| 376 | try annotations.decorateMember(self.gpa, target, index, .Coherent); | |
| 377 | if (d.non_writable) | |
| 378 | try annotations.decorateMember(self.gpa, target, index, .NonWritable); | |
| 379 | if (d.non_readable) | |
| 380 | try annotations.decorateMember(self.gpa, target, index, .NonReadable); | |
| 381 | ||
| 382 | if (d.builtin) { | |
| 383 | try annotations.decorateMember(self.gpa, target, index, .{ | |
| 384 | .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) }, | |
| 385 | }); | |
| 386 | extra_i += 1; | |
| 387 | } | |
| 388 | if (d.stream) { | |
| 389 | try annotations.decorateMember(self.gpa, target, index, .{ | |
| 390 | .Stream = .{ .stream_number = extra[extra_i] }, | |
| 391 | }); | |
| 392 | extra_i += 1; | |
| 393 | } | |
| 394 | if (d.location) { | |
| 395 | try annotations.decorateMember(self.gpa, target, index, .{ | |
| 396 | .Location = .{ .location = extra[extra_i] }, | |
| 397 | }); | |
| 398 | extra_i += 1; | |
| 399 | } | |
| 400 | if (d.component) { | |
| 401 | try annotations.decorateMember(self.gpa, target, index, .{ | |
| 402 | .Component = .{ .component = extra[extra_i] }, | |
| 403 | }); | |
| 404 | extra_i += 1; | |
| 405 | } | |
| 406 | if (d.xfb_buffer) { | |
| 407 | try annotations.decorateMember(self.gpa, target, index, .{ | |
| 408 | .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] }, | |
| 409 | }); | |
| 410 | extra_i += 1; | |
| 411 | } | |
| 412 | if (d.xfb_stride) { | |
| 413 | try annotations.decorateMember(self.gpa, target, index, .{ | |
| 414 | .XfbStride = .{ .xfb_stride = extra[extra_i] }, | |
| 415 | }); | |
| 416 | extra_i += 1; | |
| 417 | } | |
| 418 | if (d.user_semantic) { | |
| 419 | const len = extra[extra_i]; | |
| 420 | extra_i += 1; | |
| 421 | const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len]; | |
| 422 | try annotations.decorateMember(self.gpa, target, index, .{ | |
| 423 | .UserSemantic = .{ .semantic = semantic }, | |
| 424 | }); | |
| 425 | extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable; | |
| 426 | } | |
| 427 | } | |
| 428 | } |
src/codegen/spirv/Section.zig+38-17| ... | ... | @@ -32,11 +32,7 @@ pub fn toWords(section: Section) []Word { |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | 34 | /// Append the instructions from another section into this section. |
| 35 | pub fn append( | |
| 36 | section: *Section, | |
| 37 | allocator: Allocator, | |
| 38 | other_section: Section | |
| 39 | ) !void { | |
| 35 | pub fn append(section: *Section, allocator: Allocator, other_section: Section) !void { | |
| 40 | 36 | try section.instructions.appendSlice(allocator, other_section.instructions.items); |
| 41 | 37 | } |
| 42 | 38 | |
| ... | ... | @@ -64,6 +60,34 @@ pub fn emit( |
| 64 | 60 | section.writeOperands(opcode.Operands(), operands); |
| 65 | 61 | } |
| 66 | 62 | |
| 63 | /// Decorate a result-id. | |
| 64 | pub fn decorate( | |
| 65 | section: *Section, | |
| 66 | allocator: Allocator, | |
| 67 | target: spec.IdRef, | |
| 68 | decoration: spec.Decoration.Extended, | |
| 69 | ) !void { | |
| 70 | try section.emit(allocator, .OpDecorate, .{ | |
| 71 | .target = target, | |
| 72 | .decoration = decoration, | |
| 73 | }); | |
| 74 | } | |
| 75 | ||
| 76 | /// Decorate a result-id which is a member of some struct. | |
| 77 | pub fn decorateMember( | |
| 78 | section: *Section, | |
| 79 | allocator: Allocator, | |
| 80 | structure_type: spec.IdRef, | |
| 81 | member: u32, | |
| 82 | decoration: spec.Decoration.Extended, | |
| 83 | ) !void { | |
| 84 | try section.emit(allocator, .OpMemberDecorate, .{ | |
| 85 | .structure_type = structure_type, | |
| 86 | .member = member, | |
| 87 | .decoration = decoration, | |
| 88 | }); | |
| 89 | } | |
| 90 | ||
| 67 | 91 | pub fn writeWord(section: *Section, word: Word) void { |
| 68 | 92 | section.instructions.appendAssumeCapacity(word); |
| 69 | 93 | } |
| ... | ... | @@ -93,10 +117,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands) |
| 93 | 117 | |
| 94 | 118 | pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void { |
| 95 | 119 | switch (Operand) { |
| 96 | spec.IdResultType, | |
| 97 | spec.IdResult, | |
| 98 | spec.IdRef | |
| 99 | => section.writeWord(operand.id), | |
| 120 | spec.IdResultType, spec.IdResult, spec.IdRef => section.writeWord(operand.id), | |
| 100 | 121 | |
| 101 | 122 | spec.LiteralInteger => section.writeWord(operand), |
| 102 | 123 | |
| ... | ... | @@ -320,8 +341,8 @@ test "SPIR-V Section emit() - simple" { |
| 320 | 341 | defer section.deinit(std.testing.allocator); |
| 321 | 342 | |
| 322 | 343 | try section.emit(std.testing.allocator, .OpUndef, .{ |
| 323 | .id_result_type = .{.id = 0}, | |
| 324 | .id_result = .{.id = 1}, | |
| 344 | .id_result_type = .{ .id = 0 }, | |
| 345 | .id_result = .{ .id = 1 }, | |
| 325 | 346 | }); |
| 326 | 347 | |
| 327 | 348 | try testing.expectEqualSlices(Word, &.{ |
| ... | ... | @@ -338,7 +359,7 @@ test "SPIR-V Section emit() - string" { |
| 338 | 359 | try section.emit(std.testing.allocator, .OpSource, .{ |
| 339 | 360 | .source_language = .Unknown, |
| 340 | 361 | .version = 123, |
| 341 | .file = .{.id = 456}, | |
| 362 | .file = .{ .id = 456 }, | |
| 342 | 363 | .source = "pub fn main() void {}", |
| 343 | 364 | }); |
| 344 | 365 | |
| ... | ... | @@ -361,8 +382,8 @@ test "SPIR-V Section emit()- extended mask" { |
| 361 | 382 | defer section.deinit(std.testing.allocator); |
| 362 | 383 | |
| 363 | 384 | try section.emit(std.testing.allocator, .OpLoopMerge, .{ |
| 364 | .merge_block = .{.id = 10}, | |
| 365 | .continue_target = .{.id = 20}, | |
| 385 | .merge_block = .{ .id = 10 }, | |
| 386 | .continue_target = .{ .id = 20 }, | |
| 366 | 387 | .loop_control = .{ |
| 367 | 388 | .Unroll = true, |
| 368 | 389 | .DependencyLength = .{ |
| ... | ... | @@ -375,7 +396,7 @@ test "SPIR-V Section emit()- extended mask" { |
| 375 | 396 | (@as(Word, 5) << 16) | @enumToInt(Opcode.OpLoopMerge), |
| 376 | 397 | 10, |
| 377 | 398 | 20, |
| 378 | @bitCast(Word, spec.LoopControl{.Unroll = true, .DependencyLength = true}), | |
| 399 | @bitCast(Word, spec.LoopControl{ .Unroll = true, .DependencyLength = true }), | |
| 379 | 400 | 2, |
| 380 | 401 | }, section.instructions.items); |
| 381 | 402 | } |
| ... | ... | @@ -385,9 +406,9 @@ test "SPIR-V Section emit() - extended union" { |
| 385 | 406 | defer section.deinit(std.testing.allocator); |
| 386 | 407 | |
| 387 | 408 | try section.emit(std.testing.allocator, .OpExecutionMode, .{ |
| 388 | .entry_point = .{.id = 888}, | |
| 409 | .entry_point = .{ .id = 888 }, | |
| 389 | 410 | .mode = .{ |
| 390 | .LocalSize = .{.x_size = 4, .y_size = 8, .z_size = 16}, | |
| 411 | .LocalSize = .{ .x_size = 4, .y_size = 8, .z_size = 16 }, | |
| 391 | 412 | }, |
| 392 | 413 | }); |
| 393 | 414 |
src/codegen/spirv/type.zig created+433| ... | ... | @@ -0,0 +1,433 @@ |
| 1 | //! This module models a SPIR-V Type. These are distinct from Zig types, with some types | |
| 2 | //! which are not representable by Zig directly. | |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const assert = std.debug.assert; | |
| 6 | ||
| 7 | const spec = @import("spec.zig"); | |
| 8 | ||
| 9 | pub const Type = extern union { | |
| 10 | tag_if_small_enough: Tag, | |
| 11 | ptr_otherwise: *Payload, | |
| 12 | ||
| 13 | /// A reference to another SPIR-V type. | |
| 14 | pub const Ref = usize; | |
| 15 | ||
| 16 | pub fn initTag(comptime small_tag: Tag) Type { | |
| 17 | comptime assert(@enumToInt(small_tag) < Tag.no_payload_count); | |
| 18 | return .{ .tag_if_small_enough = small_tag }; | |
| 19 | } | |
| 20 | ||
| 21 | pub fn initPayload(pl: *Payload) Type { | |
| 22 | assert(@enumToInt(pl.tag) >= Tag.no_payload_count); | |
| 23 | return .{ .ptr_otherwise = pl }; | |
| 24 | } | |
| 25 | ||
| 26 | pub fn tag(self: Type) Tag { | |
| 27 | if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) { | |
| 28 | return self.tag_if_small_enough; | |
| 29 | } else { | |
| 30 | return self.ptr_otherwise.tag; | |
| 31 | } | |
| 32 | } | |
| 33 | ||
| 34 | pub fn castTag(self: Type, comptime t: Tag) ?*t.Type() { | |
| 35 | if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) | |
| 36 | return null; | |
| 37 | ||
| 38 | if (self.ptr_otherwise.tag == t) | |
| 39 | return self.payload(t); | |
| 40 | ||
| 41 | return null; | |
| 42 | } | |
| 43 | ||
| 44 | /// Access the payload of a type directly. | |
| 45 | pub fn payload(self: Type, comptime t: Tag) *t.Type() { | |
| 46 | assert(self.tag() == t); | |
| 47 | return @fieldParentPtr(t.Type(), "base", self.ptr_otherwise); | |
| 48 | } | |
| 49 | ||
| 50 | /// Perform a shallow equality test, comparing two types while assuming that any child types | |
| 51 | /// are equal only if their references are equal. | |
| 52 | pub fn eqlShallow(a: Type, b: Type) bool { | |
| 53 | if (a.tag_if_small_enough == b.tag_if_small_enough) | |
| 54 | return true; | |
| 55 | ||
| 56 | const tag_a = a.tag(); | |
| 57 | const tag_b = b.tag(); | |
| 58 | if (tag_a != tag_b) | |
| 59 | return false; | |
| 60 | ||
| 61 | inline for (@typeInfo(Tag).Enum.fields) |field| { | |
| 62 | const t = @field(Tag, field.name); | |
| 63 | if (t == tag_a) { | |
| 64 | return eqlPayloads(t, a, b); | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | unreachable; | |
| 69 | } | |
| 70 | ||
| 71 | /// Compare the payload of two compatible tags, given that we already know the tag of both types. | |
| 72 | fn eqlPayloads(comptime t: Tag, a: Type, b: Type) bool { | |
| 73 | switch (t) { | |
| 74 | .void, | |
| 75 | .bool, | |
| 76 | .sampler, | |
| 77 | .event, | |
| 78 | .device_event, | |
| 79 | .reserve_id, | |
| 80 | .queue, | |
| 81 | .pipe_storage, | |
| 82 | .named_barrier, | |
| 83 | => return true, | |
| 84 | .int, | |
| 85 | .float, | |
| 86 | .vector, | |
| 87 | .matrix, | |
| 88 | .sampled_image, | |
| 89 | .array, | |
| 90 | .runtime_array, | |
| 91 | .@"opaque", | |
| 92 | .pointer, | |
| 93 | .pipe, | |
| 94 | .image, | |
| 95 | => return std.meta.eql(a.payload(t).*, b.payload(t).*), | |
| 96 | .@"struct" => { | |
| 97 | const struct_a = a.payload(.@"struct"); | |
| 98 | const struct_b = b.payload(.@"struct"); | |
| 99 | if (struct_a.members.len != struct_b.members.len) | |
| 100 | return false; | |
| 101 | for (struct_a.members) |mem_a, i| { | |
| 102 | if (!std.meta.eql(mem_a, struct_b.members[i])) | |
| 103 | return false; | |
| 104 | } | |
| 105 | return true; | |
| 106 | }, | |
| 107 | .@"function" => { | |
| 108 | const fn_a = a.payload(.function); | |
| 109 | const fn_b = b.payload(.function); | |
| 110 | if (fn_a.return_type != fn_b.return_type) | |
| 111 | return false; | |
| 112 | return std.mem.eql(Ref, fn_a.parameters, fn_b.parameters); | |
| 113 | }, | |
| 114 | } | |
| 115 | } | |
| 116 | ||
| 117 | /// Perform a shallow hash, which hashes the reference value of child types instead of recursing. | |
| 118 | pub fn hashShallow(self: Type) u64 { | |
| 119 | var hasher = std.hash.Wyhash.init(0); | |
| 120 | const t = self.tag(); | |
| 121 | std.hash.autoHash(&hasher, t); | |
| 122 | ||
| 123 | inline for (@typeInfo(Tag).Enum.fields) |field| { | |
| 124 | if (@field(Tag, field.name) == t) { | |
| 125 | switch (@field(Tag, field.name)) { | |
| 126 | .void, | |
| 127 | .bool, | |
| 128 | .sampler, | |
| 129 | .event, | |
| 130 | .device_event, | |
| 131 | .reserve_id, | |
| 132 | .queue, | |
| 133 | .pipe_storage, | |
| 134 | .named_barrier, | |
| 135 | => {}, | |
| 136 | else => self.hashPayload(@field(Tag, field.name), &hasher), | |
| 137 | } | |
| 138 | } | |
| 139 | } | |
| 140 | ||
| 141 | return hasher.final(); | |
| 142 | } | |
| 143 | ||
| 144 | /// Perform a shallow hash, given that we know the tag of the field ahead of time. | |
| 145 | fn hashPayload(self: Type, comptime t: Tag, hasher: *std.hash.Wyhash) void { | |
| 146 | const fields = @typeInfo(t.Type()).Struct.fields; | |
| 147 | const pl = self.payload(t); | |
| 148 | comptime assert(std.mem.eql(u8, fields[0].name, "base")); | |
| 149 | inline for (fields[1..]) |field| { // Skip the 'base' field. | |
| 150 | std.hash.autoHashStrat(hasher, @field(pl, field.name), .DeepRecursive); | |
| 151 | } | |
| 152 | } | |
| 153 | ||
| 154 | /// Hash context that hashes and compares types in a shallow fashion, useful for type caches. | |
| 155 | pub const ShallowHashContext32 = struct { | |
| 156 | pub fn hash(self: @This(), t: Type) u32 { | |
| 157 | _ = self; | |
| 158 | return @truncate(u32, t.hashShallow()); | |
| 159 | } | |
| 160 | pub fn eql(self: @This(), a: Type, b: Type) bool { | |
| 161 | _ = self; | |
| 162 | return a.eqlShallow(b); | |
| 163 | } | |
| 164 | }; | |
| 165 | ||
| 166 | /// Return the reference to any child type. Asserts the type is one of: | |
| 167 | /// - Vectors | |
| 168 | /// - Matrices | |
| 169 | /// - Images | |
| 170 | /// - SampledImages, | |
| 171 | /// - Arrays | |
| 172 | /// - RuntimeArrays | |
| 173 | /// - Pointers | |
| 174 | pub fn childType(self: Type) Ref { | |
| 175 | return switch (self.tag()) { | |
| 176 | .vector => self.payload(.vector).component_type, | |
| 177 | .matrix => self.payload(.matrix).column_type, | |
| 178 | .image => self.payload(.image).sampled_type, | |
| 179 | .sampled_image => self.payload(.sampled_image).image_type, | |
| 180 | .array => self.payload(.array).element_type, | |
| 181 | .runtime_array => self.payload(.runtime_array).element_type, | |
| 182 | .pointer => self.payload(.pointer).child_type, | |
| 183 | else => unreachable, | |
| 184 | }; | |
| 185 | } | |
| 186 | ||
| 187 | pub const Tag = enum(usize) { | |
| 188 | void, | |
| 189 | bool, | |
| 190 | sampler, | |
| 191 | event, | |
| 192 | device_event, | |
| 193 | reserve_id, | |
| 194 | queue, | |
| 195 | pipe_storage, | |
| 196 | named_barrier, | |
| 197 | ||
| 198 | // After this, the tag requires a payload. | |
| 199 | int, | |
| 200 | float, | |
| 201 | vector, | |
| 202 | matrix, | |
| 203 | image, | |
| 204 | sampled_image, | |
| 205 | array, | |
| 206 | runtime_array, | |
| 207 | @"struct", | |
| 208 | @"opaque", | |
| 209 | pointer, | |
| 210 | function, | |
| 211 | pipe, | |
| 212 | ||
| 213 | pub const last_no_payload_tag = Tag.named_barrier; | |
| 214 | pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; | |
| 215 | ||
| 216 | pub fn Type(comptime t: Tag) type { | |
| 217 | return switch (t) { | |
| 218 | .void, .bool, .sampler, .event, .device_event, .reserve_id, .queue, .pipe_storage, .named_barrier => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), | |
| 219 | .int => Payload.Int, | |
| 220 | .float => Payload.Float, | |
| 221 | .vector => Payload.Vector, | |
| 222 | .matrix => Payload.Matrix, | |
| 223 | .image => Payload.Image, | |
| 224 | .sampled_image => Payload.SampledImage, | |
| 225 | .array => Payload.Array, | |
| 226 | .runtime_array => Payload.RuntimeArray, | |
| 227 | .@"struct" => Payload.Struct, | |
| 228 | .@"opaque" => Payload.Opaque, | |
| 229 | .pointer => Payload.Pointer, | |
| 230 | .function => Payload.Function, | |
| 231 | .pipe => Payload.Pipe, | |
| 232 | }; | |
| 233 | } | |
| 234 | }; | |
| 235 | ||
| 236 | pub const Payload = struct { | |
| 237 | tag: Tag, | |
| 238 | ||
| 239 | pub const Int = struct { | |
| 240 | base: Payload = .{ .tag = .int }, | |
| 241 | width: u32, | |
| 242 | signedness: std.builtin.Signedness, | |
| 243 | }; | |
| 244 | ||
| 245 | pub const Float = struct { | |
| 246 | base: Payload = .{ .tag = .float }, | |
| 247 | width: u32, | |
| 248 | }; | |
| 249 | ||
| 250 | pub const Vector = struct { | |
| 251 | base: Payload = .{ .tag = .vector }, | |
| 252 | component_type: Ref, | |
| 253 | component_count: u32, | |
| 254 | }; | |
| 255 | ||
| 256 | pub const Matrix = struct { | |
| 257 | base: Payload = .{ .tag = .matrix }, | |
| 258 | column_type: Ref, | |
| 259 | column_count: u32, | |
| 260 | }; | |
| 261 | ||
| 262 | pub const Image = struct { | |
| 263 | base: Payload = .{ .tag = .image }, | |
| 264 | sampled_type: Ref, | |
| 265 | dim: spec.Dim, | |
| 266 | depth: enum(u2) { | |
| 267 | no = 0, | |
| 268 | yes = 1, | |
| 269 | maybe = 2, | |
| 270 | }, | |
| 271 | arrayed: bool, | |
| 272 | multisampled: bool, | |
| 273 | sampled: enum(u2) { | |
| 274 | known_at_runtime = 0, | |
| 275 | with_sampler = 1, | |
| 276 | without_sampler = 2, | |
| 277 | }, | |
| 278 | format: spec.ImageFormat, | |
| 279 | access_qualifier: ?spec.AccessQualifier, | |
| 280 | }; | |
| 281 | ||
| 282 | pub const SampledImage = struct { | |
| 283 | base: Payload = .{ .tag = .sampled_image }, | |
| 284 | image_type: Ref, | |
| 285 | }; | |
| 286 | ||
| 287 | pub const Array = struct { | |
| 288 | base: Payload = .{ .tag = .array }, | |
| 289 | element_type: Ref, | |
| 290 | /// Note: Must be emitted as constant, not as literal! | |
| 291 | length: u32, | |
| 292 | /// Type has the 'ArrayStride' decoration. | |
| 293 | /// If zero, no stride is present. | |
| 294 | array_stride: u32, | |
| 295 | }; | |
| 296 | ||
| 297 | pub const RuntimeArray = struct { | |
| 298 | base: Payload = .{ .tag = .runtime_array }, | |
| 299 | element_type: Ref, | |
| 300 | /// Type has the 'ArrayStride' decoration. | |
| 301 | /// If zero, no stride is present. | |
| 302 | array_stride: u32, | |
| 303 | }; | |
| 304 | ||
| 305 | pub const Struct = struct { | |
| 306 | base: Payload = .{ .tag = .@"struct" }, | |
| 307 | members: []Member, | |
| 308 | decorations: StructDecorations, | |
| 309 | ||
| 310 | /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by | |
| 311 | /// order of the `members` slice and `MemberDecorations` struct. | |
| 312 | member_decoration_extra: []u32, | |
| 313 | ||
| 314 | pub const Member = struct { | |
| 315 | ty: Ref, | |
| 316 | offset: u32, | |
| 317 | decorations: MemberDecorations, | |
| 318 | }; | |
| 319 | ||
| 320 | pub const StructDecorations = packed struct { | |
| 321 | /// Type has the 'Block' decoration. | |
| 322 | block: bool, | |
| 323 | /// Type has the 'BufferBlock' decoration. | |
| 324 | buffer_block: bool, | |
| 325 | /// Type has the 'GLSLShared' decoration. | |
| 326 | glsl_shared: bool, | |
| 327 | /// Type has the 'GLSLPacked' decoration. | |
| 328 | glsl_packed: bool, | |
| 329 | /// Type has the 'CPacked' decoration. | |
| 330 | c_packed: bool, | |
| 331 | }; | |
| 332 | ||
| 333 | pub const MemberDecorations = packed struct { | |
| 334 | /// Matrix layout for (arrays of) matrices. If this field is not .none, | |
| 335 | /// then there is also an extra field containing the matrix stride corresponding | |
| 336 | /// to the 'MatrixStride' decoration. | |
| 337 | matrix_layout: enum(u2) { | |
| 338 | /// Member has the 'RowMajor' decoration. The member type | |
| 339 | /// must be a matrix or an array of matrices. | |
| 340 | row_major, | |
| 341 | /// Member has the 'ColMajor' decoration. The member type | |
| 342 | /// must be a matrix or an array of matrices. | |
| 343 | col_major, | |
| 344 | /// Member is not a matrix or array of matrices. | |
| 345 | none, | |
| 346 | }, | |
| 347 | ||
| 348 | // Regular decorations, these do not imply extra fields. | |
| 349 | ||
| 350 | /// Member has the 'NoPerspective' decoration. | |
| 351 | no_perspective: bool, | |
| 352 | /// Member has the 'Flat' decoration. | |
| 353 | flat: bool, | |
| 354 | /// Member has the 'Patch' decoration. | |
| 355 | patch: bool, | |
| 356 | /// Member has the 'Centroid' decoration. | |
| 357 | centroid: bool, | |
| 358 | /// Member has the 'Sample' decoration. | |
| 359 | sample: bool, | |
| 360 | /// Member has the 'Invariant' decoration. | |
| 361 | /// Note: requires parent struct to have 'Block'. | |
| 362 | invariant: bool, | |
| 363 | /// Member has the 'Volatile' decoration. | |
| 364 | @"volatile": bool, | |
| 365 | /// Member has the 'Coherent' decoration. | |
| 366 | coherent: bool, | |
| 367 | /// Member has the 'NonWritable' decoration. | |
| 368 | non_writable: bool, | |
| 369 | /// Member has the 'NonReadable' decoration. | |
| 370 | non_readable: bool, | |
| 371 | ||
| 372 | // The following decorations all imply extra field(s). | |
| 373 | ||
| 374 | /// Member has the 'BuiltIn' decoration. | |
| 375 | /// This decoration has an extra field of type `spec.BuiltIn`. | |
| 376 | /// Note: If any member of a struct has the BuiltIn decoration, all members must have one. | |
| 377 | /// Note: Each builtin may only be reachable once for a particular entry point. | |
| 378 | /// Note: The member type may be constrained by a particular built-in, defined in the client API specification. | |
| 379 | builtin: bool, | |
| 380 | /// Member has the 'Stream' decoration. | |
| 381 | /// This member has an extra field of type `u32`. | |
| 382 | stream: bool, | |
| 383 | /// Member has the 'Location' decoration. | |
| 384 | /// This member has an extra field of type `u32`. | |
| 385 | location: bool, | |
| 386 | /// Member has the 'Component' decoration. | |
| 387 | /// This member has an extra field of type `u32`. | |
| 388 | component: bool, | |
| 389 | /// Member has the 'XfbBuffer' decoration. | |
| 390 | /// This member has an extra field of type `u32`. | |
| 391 | xfb_buffer: bool, | |
| 392 | /// Member has the 'XfbStride' decoration. | |
| 393 | /// This member has an extra field of type `u32`. | |
| 394 | xfb_stride: bool, | |
| 395 | /// Member has the 'UserSemantic' decoration. | |
| 396 | /// This member has an extra field of type `[]u8`, which is encoded | |
| 397 | /// by an `u32` containing the number of chars exactly, and then the string padded to | |
| 398 | /// a multiple of 4 bytes with zeroes. | |
| 399 | user_semantic: bool, | |
| 400 | }; | |
| 401 | }; | |
| 402 | ||
| 403 | pub const Opaque = struct { | |
| 404 | base: Payload = .{ .tag = .@"opaque" }, | |
| 405 | name: []u8, | |
| 406 | }; | |
| 407 | ||
| 408 | pub const Pointer = struct { | |
| 409 | base: Payload = .{ .tag = .pointer }, | |
| 410 | storage_class: spec.StorageClass, | |
| 411 | child_type: Ref, | |
| 412 | /// Type has the 'ArrayStride' decoration. | |
| 413 | /// This is valid for pointers to elements of an array. | |
| 414 | /// If zero, no stride is present. | |
| 415 | array_stride: u32, | |
| 416 | /// Type has the 'Alignment' decoration. | |
| 417 | alignment: ?u32, | |
| 418 | /// Type has the 'MaxByteOffset' decoration. | |
| 419 | max_byte_offset: ?u32, | |
| 420 | }; | |
| 421 | ||
| 422 | pub const Function = struct { | |
| 423 | base: Payload = .{ .tag = .function }, | |
| 424 | return_type: Ref, | |
| 425 | parameters: []Ref, | |
| 426 | }; | |
| 427 | ||
| 428 | pub const Pipe = struct { | |
| 429 | base: Payload = .{ .tag = .pipe }, | |
| 430 | qualifier: spec.AccessQualifier, | |
| 431 | }; | |
| 432 | }; | |
| 433 | }; |