authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-01-22 01:49:47+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-01-28 14:45:23+01:00
log98ee39d1b0ed516428c611d8dc1e52d21c786f97
treea5667f3012952c1301b5d8631d86b753d0f71704
parent1b6ebce0da45169979b0f51a07274ff6fb5590bc

spirv: spir-v dedicated type system


4 files changed, 855 insertions(+), 138 deletions(-)

src/codegen/spirv.zig+83-95
......@@ -21,8 +21,8 @@ const IdResultType = spec.IdResultType;
2121
2222const SpvModule = @import("spirv/Module.zig");
2323const SpvSection = @import("spirv/Section.zig");
24const SpvType = @import("spirv/type.zig").Type;
2425
25const TypeCache = std.HashMapUnmanaged(Type, IdResultType, Type.HashContext64, std.hash_map.default_max_load_percentage);
2626const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
2727
2828const IncomingBlock = struct {
......@@ -61,10 +61,6 @@ pub const DeclGen = struct {
6161 /// A counter to keep track of how many `arg` instructions we've seen yet.
6262 next_arg_index: u32,
6363
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
6864 /// A map keeping track of which instruction generated which result-id.
6965 inst_results: InstMap = .{},
7066
......@@ -159,7 +155,6 @@ pub const DeclGen = struct {
159155 self.liveness = liveness;
160156 self.args.items.len = 0;
161157 self.next_arg_index = 0;
162 // Note: don't clear type_cache.
163158 self.inst_results.clearRetainingCapacity();
164159 self.blocks.clearRetainingCapacity();
165160 self.current_block_label_id = undefined;
......@@ -177,7 +172,6 @@ pub const DeclGen = struct {
177172 /// Free resources owned by the DeclGen.
178173 pub fn deinit(self: *DeclGen) void {
179174 self.args.deinit(self.spv.gpa);
180 self.type_cache.deinit(self.spv.gpa);
181175 self.inst_results.deinit(self.spv.gpa);
182176 self.blocks.deinit(self.spv.gpa);
183177 self.code.deinit(self.spv.gpa);
......@@ -220,7 +214,7 @@ pub const DeclGen = struct {
220214 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
221215 /// keep track of the previous block.
222216 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 });
224218 self.current_block_label_id = label_id.toRef();
225219 }
226220
......@@ -317,9 +311,9 @@ pub const DeclGen = struct {
317311 };
318312 },
319313 // 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", .{}),
321315 // 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}),
323317 };
324318 }
325319
......@@ -329,7 +323,7 @@ pub const DeclGen = struct {
329323 const target = self.getTarget();
330324 const section = &self.spv.sections.types_globals_constants;
331325 const result_id = self.spv.allocId();
332 const result_type_id = try self.genType(ty);
326 const result_type_id = try self.resolveTypeId(ty);
333327
334328 if (val.isUndef()) {
335329 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 {
341335 const int_info = ty.intInfo(target);
342336 const backing_bits = self.backingIntBits(int_info.bits) orelse {
343337 // 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});
345339 };
346340
347341 // 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 {
354348 var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt()) else val.toUnsignedInt();
355349
356350 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 },
359353 else => unreachable,
360354 };
361355
......@@ -375,14 +369,14 @@ pub const DeclGen = struct {
375369 },
376370 .Float => {
377371 // 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).
379373
380374 const value: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) {
381375 // 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.
386380 // TODO: Insert case for long double when the layout for that is determined?
387381 else => unreachable,
388382 };
......@@ -394,43 +388,43 @@ pub const DeclGen = struct {
394388 });
395389 },
396390 .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}),
398392 }
399393
400394 return result_id.toRef();
401395 }
402396
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 }
408401
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 {
409404 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: {
417413 const int_info = ty.intInfo(target);
418414 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});
421418 };
422419
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.* = .{
426422 .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));
432426 },
433 .Float => {
427 .Float => blk: {
434428 // We can (and want) not really emulate floating points with other floating point types like with the integer types,
435429 // so if the float is not supported, just return an error.
436430 const bits = ty.floatBits(target);
......@@ -446,39 +440,34 @@ pub const DeclGen = struct {
446440 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
447441 }
448442
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));
450448 },
451 .Fn => {
449 .Fn => blk: {
452450 // We only support zig-calling-convention functions, no varargs.
453451 if (ty.fnCallingConvention() != .Unspecified)
454452 return self.fail("Unsupported calling convention for SPIR-V", .{});
455453 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", .{});
467455
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 }
469460
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());
473462
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", .{});
479470 },
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", .{}),
482471 .Vector => {
483472 // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
484473 // which work on them), so simply use those.
......@@ -488,23 +477,21 @@ pub const DeclGen = struct {
488477 // is adequate at all for this.
489478
490479 // 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", .{});
492481 },
482
493483 .Null,
494484 .Undefined,
495485 .EnumLiteral,
496486 .ComptimeFloat,
497487 .ComptimeInt,
498488 .Type,
499 => unreachable, // Must be const or comptime.
489 => unreachable, // Must be comptime.
500490
501491 .BoundFn => unreachable, // this type will be deleted from the language.
502492
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 };
508495 }
509496
510497 /// 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 {
517504 // TODO: There are many constraints which are ignored for now: We may only create pointers to certain types, and to other types
518505 // if more capabilities are enabled. For example, we may only create pointers to f16 if Float16Buffer is enabled.
519506 // 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());
521508
522509 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{
523510 .id_result = result_id,
......@@ -534,9 +521,9 @@ pub const DeclGen = struct {
534521
535522 if (decl.val.castTag(.function)) |_| {
536523 assert(decl.ty.zigTypeTag() == .Fn);
537 const prototype_id = try self.genType(decl.ty);
524 const prototype_id = try self.resolveTypeId(decl.ty);
538525 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()),
540527 .id_result = result_id,
541528 .function_control = .{}, // TODO: We can set inline here if the type requires it.
542529 .function_type = prototype_id.toRef(),
......@@ -547,7 +534,7 @@ pub const DeclGen = struct {
547534
548535 try self.args.ensureUnusedCapacity(self.spv.gpa, params);
549536 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));
551538 const arg_result_id = self.spv.allocId();
552539 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionParameter, .{
553540 .id_result_type = param_type_id,
......@@ -573,7 +560,8 @@ pub const DeclGen = struct {
573560 try self.spv.sections.functions.append(self.spv.gpa, self.code);
574561 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionEnd, {});
575562 } 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()});
577565 }
578566 }
579567
......@@ -622,7 +610,7 @@ pub const DeclGen = struct {
622610 .unreach => return self.airUnreach(),
623611 // zig fmt: on
624612
625 else => |tag| return self.fail("TODO: SPIR-V backend: implement AIR tag {s}", .{
613 else => |tag| return self.todo("implement AIR tag {s}", .{
626614 @tagName(tag),
627615 }),
628616 };
......@@ -635,7 +623,7 @@ pub const DeclGen = struct {
635623 const lhs_id = try self.resolve(bin_op.lhs);
636624 const rhs_id = try self.resolve(bin_op.rhs);
637625 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));
639627 try self.code.emit(self.spv.gpa, opcode, .{
640628 .id_result_type = result_type_id,
641629 .id_result = result_id,
......@@ -654,7 +642,7 @@ pub const DeclGen = struct {
654642 const rhs_id = try self.resolve(bin_op.rhs);
655643
656644 const result_id = self.spv.allocId();
657 const result_type_id = try self.genType(ty);
645 const result_type_id = try self.resolveTypeId(ty);
658646
659647 assert(self.air.typeOf(bin_op.lhs).eql(ty));
660648 assert(self.air.typeOf(bin_op.rhs).eql(ty));
......@@ -665,10 +653,10 @@ pub const DeclGen = struct {
665653
666654 const opcode_index: usize = switch (info.class) {
667655 .composite_integer => {
668 return self.fail("TODO: SPIR-V backend: binary operations for composite integers", .{});
656 return self.todo("binary operations for composite integers", .{});
669657 },
670658 .strange_integer => {
671 return self.fail("TODO: SPIR-V backend: binary operations for strange integers", .{});
659 return self.todo("binary operations for strange integers", .{});
672660 },
673661 .integer => switch (info.signedness) {
674662 .signed => @as(usize, 1),
......@@ -702,7 +690,7 @@ pub const DeclGen = struct {
702690 const lhs_id = try self.resolve(bin_op.lhs);
703691 const rhs_id = try self.resolve(bin_op.rhs);
704692 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));
706694 const op_ty = self.air.typeOf(bin_op.lhs);
707695 assert(op_ty.eql(self.air.typeOf(bin_op.rhs)));
708696
......@@ -712,10 +700,10 @@ pub const DeclGen = struct {
712700
713701 const opcode_index: usize = switch (info.class) {
714702 .composite_integer => {
715 return self.fail("TODO: SPIR-V backend: binary operations for composite integers", .{});
703 return self.todo("binary operations for composite integers", .{});
716704 },
717705 .strange_integer => {
718 return self.fail("TODO: SPIR-V backend: comparison for strange integers", .{});
706 return self.todo("comparison for strange integers", .{});
719707 },
720708 .float => 0,
721709 .bool => 1,
......@@ -746,7 +734,7 @@ pub const DeclGen = struct {
746734 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
747735 const operand_id = try self.resolve(ty_op.operand);
748736 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));
750738 try self.code.emit(self.spv.gpa, .OpLogicalNot, .{
751739 .id_result_type = result_type_id,
752740 .id_result = result_id,
......@@ -813,9 +801,9 @@ pub const DeclGen = struct {
813801 const result_id = self.spv.allocId();
814802
815803 // 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
817805 // an error for pointers.
818 const result_type_id = try self.genType(ty);
806 const result_type_id = try self.resolveTypeId(ty);
819807 _ = result_type_id;
820808
821809 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 {
838826 try block.incoming_blocks.append(self.spv.gpa, .{ .src_label_id = self.current_block_label_id, .break_value_id = operand_id });
839827 }
840828
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 });
842830 }
843831
844832 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {
......@@ -882,7 +870,7 @@ pub const DeclGen = struct {
882870 const operand_id = try self.resolve(ty_op.operand);
883871 const ty = self.air.typeOfIndex(inst);
884872
885 const result_type_id = try self.genType(ty);
873 const result_type_id = try self.resolveTypeId(ty);
886874 const result_id = self.spv.allocId();
887875
888876 const access = spec.MemoryAccess.Extended{
......@@ -906,13 +894,13 @@ pub const DeclGen = struct {
906894 const loop_label_id = self.spv.allocId();
907895
908896 // 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() });
910898
911899 // TODO: Look into OpLoopMerge.
912900 try self.beginSpvBlock(loop_label_id);
913901 try self.genBody(body);
914902
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() });
916904 }
917905
918906 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
......@@ -920,7 +908,7 @@ pub const DeclGen = struct {
920908 const operand_ty = self.air.typeOf(operand);
921909 if (operand_ty.hasRuntimeBits()) {
922910 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 });
924912 } else {
925913 try self.code.emit(self.spv.gpa, .OpReturn, {});
926914 }
src/codegen/spirv/Module.zig+301-26
......@@ -9,14 +9,20 @@ const Module = @This();
99
1010const std = @import("std");
1111const Allocator = std.mem.Allocator;
12const assert = std.debug.assert;
1213
1314const ZigDecl = @import("../../Module.zig").Decl;
1415
1516const spec = @import("spec.zig");
1617const Word = spec.Word;
1718const IdRef = spec.IdRef;
19const IdResult = spec.IdResult;
20const IdResultType = spec.IdResultType;
1821
1922const Section = @import("Section.zig");
23const Type = @import("type.zig").Type;
24
25const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true);
2026
2127/// A general-purpose allocator which may be used to allocate resources for this module
2228gpa: Allocator,
......@@ -57,6 +63,12 @@ next_result_id: Word,
5763/// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
5864source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
5965
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).
70type_cache: TypeCache = .{},
71
6072pub fn init(gpa: Allocator, arena: Allocator) Module {
6173 return .{
6274 .gpa = gpa,
......@@ -75,44 +87,20 @@ pub fn deinit(self: *Module) void {
7587 self.sections.functions.deinit(self.gpa);
7688
7789 self.source_file_names.deinit(self.gpa);
90 self.type_cache.deinit(self.gpa);
7891
7992 self.* = undefined;
8093}
8194
8295pub fn allocId(self: *Module) spec.IdResult {
8396 defer self.next_result_id += 1;
84 return .{.id = self.next_result_id};
97 return .{ .id = self.next_result_id };
8598}
8699
87100pub fn idBound(self: Module) Word {
88101 return self.next_result_id;
89102}
90103
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.
94pub 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
116104/// Emit this module as a spir-v binary.
117105pub fn flush(self: Module, file: std.fs.File) !void {
118106 // 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 {
151139 try file.setEndPos(file_size);
152140 try file.pwritevAll(&iovc_buffers, 0);
153141}
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.
146pub 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.
173pub 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
181pub 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.
186pub 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.
191pub 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.
201pub 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
328fn 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 {
3232}
3333
3434/// Append the instructions from another section into this section.
35pub fn append(
36 section: *Section,
37 allocator: Allocator,
38 other_section: Section
39) !void {
35pub fn append(section: *Section, allocator: Allocator, other_section: Section) !void {
4036 try section.instructions.appendSlice(allocator, other_section.instructions.items);
4137}
4238
......@@ -64,6 +60,34 @@ pub fn emit(
6460 section.writeOperands(opcode.Operands(), operands);
6561}
6662
63/// Decorate a result-id.
64pub 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.
77pub 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
6791pub fn writeWord(section: *Section, word: Word) void {
6892 section.instructions.appendAssumeCapacity(word);
6993}
......@@ -93,10 +117,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
93117
94118pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
95119 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),
100121
101122 spec.LiteralInteger => section.writeWord(operand),
102123
......@@ -320,8 +341,8 @@ test "SPIR-V Section emit() - simple" {
320341 defer section.deinit(std.testing.allocator);
321342
322343 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 },
325346 });
326347
327348 try testing.expectEqualSlices(Word, &.{
......@@ -338,7 +359,7 @@ test "SPIR-V Section emit() - string" {
338359 try section.emit(std.testing.allocator, .OpSource, .{
339360 .source_language = .Unknown,
340361 .version = 123,
341 .file = .{.id = 456},
362 .file = .{ .id = 456 },
342363 .source = "pub fn main() void {}",
343364 });
344365
......@@ -361,8 +382,8 @@ test "SPIR-V Section emit()- extended mask" {
361382 defer section.deinit(std.testing.allocator);
362383
363384 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 },
366387 .loop_control = .{
367388 .Unroll = true,
368389 .DependencyLength = .{
......@@ -375,7 +396,7 @@ test "SPIR-V Section emit()- extended mask" {
375396 (@as(Word, 5) << 16) | @enumToInt(Opcode.OpLoopMerge),
376397 10,
377398 20,
378 @bitCast(Word, spec.LoopControl{.Unroll = true, .DependencyLength = true}),
399 @bitCast(Word, spec.LoopControl{ .Unroll = true, .DependencyLength = true }),
379400 2,
380401 }, section.instructions.items);
381402}
......@@ -385,9 +406,9 @@ test "SPIR-V Section emit() - extended union" {
385406 defer section.deinit(std.testing.allocator);
386407
387408 try section.emit(std.testing.allocator, .OpExecutionMode, .{
388 .entry_point = .{.id = 888},
409 .entry_point = .{ .id = 888 },
389410 .mode = .{
390 .LocalSize = .{.x_size = 4, .y_size = 8, .z_size = 16},
411 .LocalSize = .{ .x_size = 4, .y_size = 8, .z_size = 16 },
391412 },
392413 });
393414
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
4const std = @import("std");
5const assert = std.debug.assert;
6
7const spec = @import("spec.zig");
8
9pub 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};