authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-06-02 12:17:02-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-06-02 12:17:02-07:00
log9461ed5037de8f3e4f03021c27d7458aa3d1a432
treef6c637ae2a516a0f558fb7c982185d0028ebfe7c
parent879f0b9cee9b409160edf10d8b52f73be2bddb4f
parent3c4cc1eedb959aa804ca752e20268ccecc14ccef
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #15900 from Snektron/spirv-pool

SPIR-V Intern Pool

5 files changed, 1354 insertions(+), 1327 deletions(-)

src/codegen/spirv.zig+172-226
......@@ -22,8 +22,10 @@ const IdResultType = spec.IdResultType;
2222const StorageClass = spec.StorageClass;
2323
2424const SpvModule = @import("spirv/Module.zig");
25const CacheRef = SpvModule.CacheRef;
26const CacheString = SpvModule.CacheString;
27
2528const SpvSection = @import("spirv/Section.zig");
26const SpvType = @import("spirv/type.zig").Type;
2729const SpvAssembler = @import("spirv/Assembler.zig");
2830
2931const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
......@@ -377,74 +379,23 @@ pub const DeclGen = struct {
377379 };
378380 }
379381
380 fn genConstInt(self: *DeclGen, ty_ref: SpvType.Ref, result_id: IdRef, value: anytype) !void {
381 const ty = self.spv.typeRefType(ty_ref);
382 const ty_id = self.typeId(ty_ref);
383
384 const Lit = spec.LiteralContextDependentNumber;
385 const literal = switch (ty.intSignedness()) {
386 .signed => switch (ty.intFloatBits()) {
387 1...32 => Lit{ .int32 = @intCast(i32, value) },
388 33...64 => Lit{ .int64 = @intCast(i64, value) },
389 else => unreachable, // TODO: composite integer literals
390 },
391 .unsigned => switch (ty.intFloatBits()) {
392 1...32 => Lit{ .uint32 = @intCast(u32, value) },
393 33...64 => Lit{ .uint64 = @intCast(u64, value) },
394 else => unreachable,
395 },
396 };
397
398 try self.spv.emitConstant(ty_id, result_id, literal);
399 }
400
401 fn constInt(self: *DeclGen, ty_ref: SpvType.Ref, value: anytype) !IdRef {
402 const result_id = self.spv.allocId();
403 try self.genConstInt(ty_ref, result_id, value);
404 return result_id;
405 }
406
407 fn constUndef(self: *DeclGen, ty_ref: SpvType.Ref) !IdRef {
408 const result_id = self.spv.allocId();
409 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpUndef, .{
410 .id_result_type = self.typeId(ty_ref),
411 .id_result = result_id,
412 });
413 return result_id;
414 }
415
416 fn constNull(self: *DeclGen, ty_ref: SpvType.Ref) !IdRef {
417 const result_id = self.spv.allocId();
418 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantNull, .{
419 .id_result_type = self.typeId(ty_ref),
420 .id_result = result_id,
421 });
422 return result_id;
423 }
424
382 /// Emits a bool constant in a particular representation.
425383 fn constBool(self: *DeclGen, value: bool, repr: Repr) !IdRef {
426384 switch (repr) {
427385 .indirect => {
428386 const int_ty_ref = try self.intType(.unsigned, 1);
429 return self.constInt(int_ty_ref, @boolToInt(value));
387 return self.spv.constInt(int_ty_ref, @boolToInt(value));
430388 },
431389 .direct => {
432390 const bool_ty_ref = try self.resolveType(Type.bool, .direct);
433 const result_id = self.spv.allocId();
434 const operands = .{ .id_result_type = self.typeId(bool_ty_ref), .id_result = result_id };
435 if (value) {
436 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantTrue, operands);
437 } else {
438 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantFalse, operands);
439 }
440 return result_id;
391 return self.spv.constBool(bool_ty_ref, value);
441392 },
442393 }
443394 }
444395
445396 /// Construct a struct at runtime.
446397 /// result_ty_ref must be a struct type.
447 fn constructStruct(self: *DeclGen, result_ty_ref: SpvType.Ref, constituents: []const IdRef) !IdRef {
398 fn constructStruct(self: *DeclGen, result_ty_ref: CacheRef, constituents: []const IdRef) !IdRef {
448399 // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which'
449400 // operands are not constant.
450401 // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349
......@@ -453,11 +404,13 @@ pub const DeclGen = struct {
453404 const ptr_composite_id = try self.alloc(result_ty_ref, null);
454405 // Note: using 32-bit ints here because usize crashes the translator as well
455406 const index_ty_ref = try self.intType(.unsigned, 32);
456 const spv_composite_ty = self.spv.typeRefType(result_ty_ref);
457 const members = spv_composite_ty.payload(.@"struct").members;
458 for (constituents, members, 0..) |constitent_id, member, index| {
459 const index_id = try self.constInt(index_ty_ref, index);
460 const ptr_member_ty_ref = try self.spv.ptrType(member.ty, .Generic, 0);
407
408 const spv_composite_ty = self.spv.cache.lookup(result_ty_ref).struct_type;
409 const member_types = spv_composite_ty.member_types;
410
411 for (constituents, member_types, 0..) |constitent_id, member_ty_ref, index| {
412 const index_id = try self.spv.constInt(index_ty_ref, index);
413 const ptr_member_ty_ref = try self.spv.ptrType(member_ty_ref, .Generic);
461414 const ptr_id = try self.accessChain(ptr_member_ty_ref, ptr_composite_id, &.{index_id});
462415 try self.func.body.emit(self.spv.gpa, .OpStore, .{
463416 .pointer = ptr_id,
......@@ -478,11 +431,9 @@ pub const DeclGen = struct {
478431
479432 dg: *DeclGen,
480433 /// Cached reference of the u32 type.
481 u32_ty_ref: SpvType.Ref,
482 /// Cached type id of the u32 type.
483 u32_ty_id: IdRef,
434 u32_ty_ref: CacheRef,
484435 /// The members of the resulting structure type
485 members: std.ArrayList(SpvType.Payload.Struct.Member),
436 members: std.ArrayList(CacheRef),
486437 /// The initializers of each of the members.
487438 initializers: std.ArrayList(IdRef),
488439 /// The current size of the structure. Includes
......@@ -513,10 +464,8 @@ pub const DeclGen = struct {
513464 }
514465
515466 const word = @bitCast(Word, self.partial_word.buffer);
516 const result_id = self.dg.spv.allocId();
517 // TODO: Integrate with caching mechanism
518 try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word });
519 try self.members.append(.{ .ty = self.u32_ty_ref });
467 const result_id = try self.dg.spv.constInt(self.u32_ty_ref, word);
468 try self.members.append(self.u32_ty_ref);
520469 try self.initializers.append(result_id);
521470
522471 self.partial_word.len = 0;
......@@ -552,7 +501,7 @@ pub const DeclGen = struct {
552501 }
553502 }
554503
555 fn addPtr(self: *@This(), ptr_ty_ref: SpvType.Ref, ptr_id: IdRef) !void {
504 fn addPtr(self: *@This(), ptr_ty_ref: CacheRef, ptr_id: IdRef) !void {
556505 // TODO: Double check pointer sizes here.
557506 // shared pointers might be u32...
558507 const target = self.dg.getTarget();
......@@ -560,17 +509,13 @@ pub const DeclGen = struct {
560509 if (self.size % width != 0) {
561510 return self.dg.todo("misaligned pointer constants", .{});
562511 }
563 try self.members.append(.{ .ty = ptr_ty_ref });
512 try self.members.append(ptr_ty_ref);
564513 try self.initializers.append(ptr_id);
565514 self.size += width;
566515 }
567516
568 fn addNullPtr(self: *@This(), ptr_ty_ref: SpvType.Ref) !void {
569 const result_id = self.dg.spv.allocId();
570 try self.dg.spv.sections.types_globals_constants.emit(self.dg.spv.gpa, .OpConstantNull, .{
571 .id_result_type = self.dg.typeId(ptr_ty_ref),
572 .id_result = result_id,
573 });
517 fn addNullPtr(self: *@This(), ptr_ty_ref: CacheRef) !void {
518 const result_id = try self.dg.spv.constNull(ptr_ty_ref);
574519 try self.addPtr(ptr_ty_ref, result_id);
575520 }
576521
......@@ -928,7 +873,7 @@ pub const DeclGen = struct {
928873 const section = &self.spv.globals.section;
929874
930875 const ty_ref = try self.resolveType(ty, .indirect);
931 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, 0);
876 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class);
932877
933878 // const target = self.getTarget();
934879
......@@ -956,8 +901,7 @@ pub const DeclGen = struct {
956901 var icl = IndirectConstantLowering{
957902 .dg = self,
958903 .u32_ty_ref = u32_ty_ref,
959 .u32_ty_id = self.typeId(u32_ty_ref),
960 .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa),
904 .members = std.ArrayList(CacheRef).init(self.gpa),
961905 .initializers = std.ArrayList(IdRef).init(self.gpa),
962906 .decl_deps = std.AutoArrayHashMap(SpvModule.Decl.Index, void).init(self.gpa),
963907 };
......@@ -969,8 +913,10 @@ pub const DeclGen = struct {
969913 try icl.lower(ty, val);
970914 try icl.flush();
971915
972 const constant_struct_ty_ref = try self.spv.simpleStructType(icl.members.items);
973 const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, 0);
916 const constant_struct_ty_ref = try self.spv.resolve(.{ .struct_type = .{
917 .member_types = icl.members.items,
918 } });
919 const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class);
974920
975921 const constant_struct_id = self.spv.allocId();
976922 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{
......@@ -1004,7 +950,7 @@ pub const DeclGen = struct {
1004950 });
1005951
1006952 if (cast_to_generic) {
1007 const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0);
953 const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic);
1008954 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
1009955 .id_result_type = self.typeId(generic_ptr_ty_ref),
1010956 .id_result = result_id,
......@@ -1023,52 +969,32 @@ pub const DeclGen = struct {
1023969 /// This function should only be called during function code generation.
1024970 fn constant(self: *DeclGen, ty: Type, val: Value, repr: Repr) !IdRef {
1025971 const target = self.getTarget();
1026 const section = &self.spv.sections.types_globals_constants;
1027972 const result_ty_ref = try self.resolveType(ty, repr);
1028 const result_ty_id = self.typeId(result_ty_ref);
1029973
1030974 log.debug("constant: ty = {}, val = {}", .{ ty.fmt(self.module), val.fmtValue(ty, self.module) });
1031975
1032976 if (val.isUndef()) {
1033 const result_id = self.spv.allocId();
1034 try section.emit(self.spv.gpa, .OpUndef, .{
1035 .id_result_type = result_ty_id,
1036 .id_result = result_id,
1037 });
1038 return result_id;
977 return self.spv.constUndef(result_ty_ref);
1039978 }
1040979
1041980 switch (ty.zigTypeTag()) {
1042981 .Int => {
1043982 if (ty.isSignedInt()) {
1044 return try self.constInt(result_ty_ref, val.toSignedInt(target));
983 return try self.spv.constInt(result_ty_ref, val.toSignedInt(target));
1045984 } else {
1046 return try self.constInt(result_ty_ref, val.toUnsignedInt(target));
985 return try self.spv.constInt(result_ty_ref, val.toUnsignedInt(target));
1047986 }
1048987 },
1049988 .Bool => switch (repr) {
1050 .direct => {
1051 const result_id = self.spv.allocId();
1052 const operands = .{ .id_result_type = result_ty_id, .id_result = result_id };
1053 if (val.toBool()) {
1054 try section.emit(self.spv.gpa, .OpConstantTrue, operands);
1055 } else {
1056 try section.emit(self.spv.gpa, .OpConstantFalse, operands);
1057 }
1058 return result_id;
1059 },
1060 .indirect => return try self.constInt(result_ty_ref, @boolToInt(val.toBool())),
989 .direct => return try self.spv.constBool(result_ty_ref, val.toBool()),
990 .indirect => return try self.spv.constInt(result_ty_ref, @boolToInt(val.toBool())),
1061991 },
1062 .Float => {
1063 const result_id = self.spv.allocId();
1064 switch (ty.floatBits(target)) {
1065 16 => try self.spv.emitConstant(result_ty_id, result_id, .{ .float32 = val.toFloat(f16) }),
1066 32 => try self.spv.emitConstant(result_ty_id, result_id, .{ .float32 = val.toFloat(f32) }),
1067 64 => try self.spv.emitConstant(result_ty_id, result_id, .{ .float64 = val.toFloat(f64) }),
1068 80, 128 => unreachable, // TODO
1069 else => unreachable,
1070 }
1071 return result_id;
992 .Float => return switch (ty.floatBits(target)) {
993 16 => try self.spv.resolveId(.{ .float = .{ .ty = result_ty_ref, .value = .{ .float16 = val.toFloat(f16) } } }),
994 32 => try self.spv.resolveId(.{ .float = .{ .ty = result_ty_ref, .value = .{ .float32 = val.toFloat(f32) } } }),
995 64 => try self.spv.resolveId(.{ .float = .{ .ty = result_ty_ref, .value = .{ .float64 = val.toFloat(f64) } } }),
996 80, 128 => unreachable, // TODO
997 else => unreachable,
1072998 },
1073999 .ErrorSet => {
10741000 const value = switch (val.tag()) {
......@@ -1081,7 +1007,7 @@ pub const DeclGen = struct {
10811007 else => unreachable,
10821008 };
10831009
1084 return try self.constInt(result_ty_ref, value);
1010 return try self.spv.constInt(result_ty_ref, value);
10851011 },
10861012 .ErrorUnion => {
10871013 const payload_ty = ty.errorUnionPayload();
......@@ -1126,7 +1052,7 @@ pub const DeclGen = struct {
11261052 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});
11271053
11281054 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
1129 .id_result_type = result_ty_id,
1055 .id_result_type = self.typeId(result_ty_ref),
11301056 .id_result = result_id,
11311057 .pointer = self.spv.declPtr(spv_decl_index).result_id,
11321058 });
......@@ -1140,26 +1066,28 @@ pub const DeclGen = struct {
11401066 /// Turn a Zig type into a SPIR-V Type, and return its type result-id.
11411067 fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType {
11421068 const type_ref = try self.resolveType(ty, .direct);
1143 return self.typeId(type_ref);
1069 return self.spv.resultId(type_ref);
11441070 }
11451071
1146 fn typeId(self: *DeclGen, ty_ref: SpvType.Ref) IdRef {
1147 return self.spv.typeId(ty_ref);
1072 fn typeId(self: *DeclGen, ty_ref: CacheRef) IdRef {
1073 return self.spv.resultId(ty_ref);
11481074 }
11491075
11501076 /// Create an integer type suitable for storing at least 'bits' bits.
1151 fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvType.Ref {
1077 /// The integer type that is returned by this function is the type that is used to perform
1078 /// actual operations (as well as store) a Zig type of a particular number of bits. To create
1079 /// a type with an exact size, use SpvModule.intType.
1080 fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !CacheRef {
11521081 const backing_bits = self.backingIntBits(bits) orelse {
11531082 // TODO: Integers too big for any native type are represented as "composite integers":
11541083 // An array of largestSupportedIntBits.
11551084 return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
11561085 };
1157
1158 return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits));
1086 return self.spv.intType(signedness, backing_bits);
11591087 }
11601088
11611089 /// Create an integer type that represents 'usize'.
1162 fn sizeType(self: *DeclGen) !SpvType.Ref {
1090 fn sizeType(self: *DeclGen) !CacheRef {
11631091 return try self.intType(.unsigned, self.getTarget().ptrBitWidth());
11641092 }
11651093
......@@ -1185,7 +1113,7 @@ pub const DeclGen = struct {
11851113 /// If any of the fields' size is 0, it will be omitted.
11861114 /// NOTE: When the active field is set to something other than the most aligned field, the
11871115 /// resulting struct will be *underaligned*.
1188 fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvType.Ref {
1116 fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !CacheRef {
11891117 const target = self.getTarget();
11901118 const layout = ty.unionGetLayout(target);
11911119 const union_ty = ty.cast(Type.Payload.Union).?.data;
......@@ -1199,7 +1127,8 @@ pub const DeclGen = struct {
11991127 return try self.resolveType(union_ty.tag_ty, .indirect);
12001128 }
12011129
1202 var members = std.BoundedArray(SpvType.Payload.Struct.Member, 4){};
1130 var member_types = std.BoundedArray(CacheRef, 4){};
1131 var member_names = std.BoundedArray(CacheString, 4){};
12031132
12041133 const has_tag = layout.tag_size != 0;
12051134 const tag_first = layout.tag_align >= layout.payload_align;
......@@ -1207,7 +1136,8 @@ pub const DeclGen = struct {
12071136
12081137 if (has_tag and tag_first) {
12091138 const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect);
1210 members.appendAssumeCapacity(.{ .name = "tag", .ty = tag_ty_ref });
1139 member_types.appendAssumeCapacity(tag_ty_ref);
1140 member_names.appendAssumeCapacity(try self.spv.resolveString("tag"));
12111141 }
12121142
12131143 const active_field = maybe_active_field orelse layout.most_aligned_field;
......@@ -1215,40 +1145,44 @@ pub const DeclGen = struct {
12151145
12161146 const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: {
12171147 const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect);
1218 members.appendAssumeCapacity(.{ .name = "payload", .ty = active_payload_ty_ref });
1148 member_types.appendAssumeCapacity(active_payload_ty_ref);
1149 member_names.appendAssumeCapacity(try self.spv.resolveString("payload"));
12191150 break :blk active_field_ty.abiSize(target);
12201151 } else 0;
12211152
12221153 const payload_padding_len = layout.payload_size - active_field_size;
12231154 if (payload_padding_len != 0) {
12241155 const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref);
1225 members.appendAssumeCapacity(.{ .name = "padding_payload", .ty = payload_padding_ty_ref });
1156 member_types.appendAssumeCapacity(payload_padding_ty_ref);
1157 member_names.appendAssumeCapacity(try self.spv.resolveString("payload_padding"));
12261158 }
12271159
12281160 if (has_tag and !tag_first) {
12291161 const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect);
1230 members.appendAssumeCapacity(.{ .name = "tag", .ty = tag_ty_ref });
1162 member_types.appendAssumeCapacity(tag_ty_ref);
1163 member_names.appendAssumeCapacity(try self.spv.resolveString("tag"));
12311164 }
12321165
12331166 if (layout.padding != 0) {
12341167 const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref);
1235 members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref });
1168 member_types.appendAssumeCapacity(padding_ty_ref);
1169 member_names.appendAssumeCapacity(try self.spv.resolveString("padding"));
12361170 }
12371171
1238 return try self.spv.simpleStructType(members.slice());
1172 return try self.spv.resolve(.{ .struct_type = .{
1173 .member_types = member_types.slice(),
1174 .member_names = member_names.slice(),
1175 } });
12391176 }
12401177
12411178 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
1242 fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref {
1179 fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!CacheRef {
12431180 log.debug("resolveType: ty = {}", .{ty.fmt(self.module)});
12441181 const target = self.getTarget();
12451182 switch (ty.zigTypeTag()) {
1246 .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)),
1183 .Void, .NoReturn => return try self.spv.resolve(.void_type),
12471184 .Bool => switch (repr) {
1248 .direct => return try self.spv.resolveType(SpvType.initTag(.bool)),
1249 // SPIR-V booleans are opaque, which is fine for operations, but they cant be stored.
1250 // This function returns the *stored* type, for values directly we convert this into a bool when
1251 // it is loaded, and convert it back to this type when stored.
1185 .direct => return try self.spv.resolve(.bool_type),
12521186 .indirect => return try self.intType(.unsigned, 1),
12531187 },
12541188 .Int => {
......@@ -1276,15 +1210,15 @@ pub const DeclGen = struct {
12761210 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
12771211 }
12781212
1279 return try self.spv.resolveType(SpvType.float(bits));
1213 return try self.spv.resolve(.{ .float_type = .{ .bits = bits } });
12801214 },
12811215 .Array => {
12821216 const elem_ty = ty.childType();
1283 const elem_ty_ref = try self.resolveType(elem_ty, .indirect);
1217 const elem_ty_ref = try self.resolveType(elem_ty, .direct);
12841218 const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse {
12851219 return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()});
12861220 };
1287 return try self.spv.arrayType(total_len, elem_ty_ref);
1221 return self.spv.arrayType(total_len, elem_ty_ref);
12881222 },
12891223 .Fn => switch (repr) {
12901224 .direct => {
......@@ -1292,18 +1226,17 @@ pub const DeclGen = struct {
12921226 if (ty.fnIsVarArgs())
12931227 return self.fail("VarArgs functions are unsupported for SPIR-V", .{});
12941228
1295 // TODO: Parameter passing convention etc.
1296
1297 const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen());
1298 for (param_types, 0..) |*param, i| {
1299 param.* = try self.resolveType(ty.fnParamType(i), .direct);
1229 const param_ty_refs = try self.gpa.alloc(CacheRef, ty.fnParamLen());
1230 defer self.gpa.free(param_ty_refs);
1231 for (param_ty_refs, 0..) |*param_type, i| {
1232 param_type.* = try self.resolveType(ty.fnParamType(i), .direct);
13001233 }
1234 const return_ty_ref = try self.resolveType(ty.fnReturnType(), .direct);
13011235
1302 const return_type = try self.resolveType(ty.fnReturnType(), .direct);
1303
1304 const payload = try self.spv.arena.create(SpvType.Payload.Function);
1305 payload.* = .{ .return_type = return_type, .parameters = param_types };
1306 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
1236 return try self.spv.resolve(.{ .function_type = .{
1237 .return_type = return_ty_ref,
1238 .parameters = param_ty_refs,
1239 } });
13071240 },
13081241 .indirect => {
13091242 // TODO: Represent function pointers properly.
......@@ -1316,16 +1249,22 @@ pub const DeclGen = struct {
13161249
13171250 const storage_class = spvStorageClass(ptr_info.@"addrspace");
13181251 const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect);
1319 const ptr_ty_ref = try self.spv.ptrType(child_ty_ref, storage_class, 0);
1320
1252 const ptr_ty_ref = try self.spv.resolve(.{ .ptr_type = .{
1253 .storage_class = storage_class,
1254 .child_type = child_ty_ref,
1255 } });
13211256 if (ptr_info.size != .Slice) {
13221257 return ptr_ty_ref;
13231258 }
13241259
1325 return try self.spv.simpleStructType(&.{
1326 .{ .ty = ptr_ty_ref, .name = "ptr" },
1327 .{ .ty = try self.sizeType(), .name = "len" },
1328 });
1260 const size_ty_ref = try self.sizeType();
1261 return self.spv.resolve(.{ .struct_type = .{
1262 .member_types = &.{ ptr_ty_ref, size_ty_ref },
1263 .member_names = &.{
1264 try self.spv.resolveString("ptr"),
1265 try self.spv.resolveString("len"),
1266 },
1267 } });
13291268 },
13301269 .Vector => {
13311270 // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
......@@ -1337,60 +1276,60 @@ pub const DeclGen = struct {
13371276
13381277 // TODO: Properly verify sizes and child type.
13391278
1340 const payload = try self.spv.arena.create(SpvType.Payload.Vector);
1341 payload.* = .{
1279 return try self.spv.resolve(.{ .vector_type = .{
13421280 .component_type = try self.resolveType(ty.elemType(), repr),
13431281 .component_count = @intCast(u32, ty.vectorLen()),
1344 };
1345 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
1282 } });
13461283 },
13471284 .Struct => {
13481285 if (ty.isSimpleTupleOrAnonStruct()) {
13491286 const tuple = ty.tupleFields();
1350 const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, tuple.types.len);
1351 var member_index: u32 = 0;
1287 const member_types = try self.gpa.alloc(CacheRef, tuple.types.len);
1288 defer self.gpa.free(member_types);
1289
1290 var member_index: usize = 0;
13521291 for (tuple.types, 0..) |field_ty, i| {
13531292 const field_val = tuple.values[i];
1354 if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBitsIgnoreComptime()) continue;
1355 members[member_index] = .{
1356 .ty = try self.resolveType(field_ty, .indirect),
1357 };
1293 if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue;
1294
1295 member_types[member_index] = try self.resolveType(field_ty, .indirect);
13581296 member_index += 1;
13591297 }
1360 const payload = try self.spv.arena.create(SpvType.Payload.Struct);
1361 payload.* = .{
1362 .members = members[0..member_index],
1363 };
1364 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
1298
1299 return try self.spv.resolve(.{ .struct_type = .{
1300 .member_types = member_types[0..member_index],
1301 } });
13651302 }
13661303
13671304 const struct_ty = ty.castTag(.@"struct").?.data;
13681305
13691306 if (struct_ty.layout == .Packed) {
1370 return try self.resolveType(struct_ty.backing_int_ty, .indirect);
1307 return try self.resolveType(struct_ty.backing_int_ty, .direct);
13711308 }
13721309
1373 const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count());
1310 const member_types = try self.gpa.alloc(CacheRef, struct_ty.fields.count());
1311 defer self.gpa.free(member_types);
1312
1313 const member_names = try self.gpa.alloc(CacheString, struct_ty.fields.count());
1314 defer self.gpa.free(member_names);
1315
13741316 var member_index: usize = 0;
13751317 for (struct_ty.fields.values(), 0..) |field, i| {
13761318 if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
13771319
1378 members[member_index] = .{
1379 .ty = try self.resolveType(field.ty, .indirect),
1380 .name = struct_ty.fields.keys()[i],
1381 };
1320 member_types[member_index] = try self.resolveType(field.ty, .indirect);
1321 member_names[member_index] = try self.spv.resolveString(struct_ty.fields.keys()[i]);
13821322 member_index += 1;
13831323 }
13841324
13851325 const name = try struct_ty.getFullyQualifiedName(self.module);
13861326 defer self.module.gpa.free(name);
13871327
1388 const payload = try self.spv.arena.create(SpvType.Payload.Struct);
1389 payload.* = .{
1390 .members = members[0..member_index],
1391 .name = try self.spv.arena.dupe(u8, name),
1392 };
1393 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
1328 return try self.spv.resolve(.{ .struct_type = .{
1329 .name = try self.spv.resolveString(name),
1330 .member_types = member_types[0..member_index],
1331 .member_names = member_names[0..member_index],
1332 } });
13941333 },
13951334 .Optional => {
13961335 var buf: Type.Payload.ElemType = undefined;
......@@ -1398,7 +1337,7 @@ pub const DeclGen = struct {
13981337 if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
13991338 // Just use a bool.
14001339 // Note: Always generate the bool with indirect format, to save on some sanity
1401 // Perform the converison to a direct bool when the field is extracted.
1340 // Perform the conversion to a direct bool when the field is extracted.
14021341 return try self.resolveType(Type.bool, .indirect);
14031342 }
14041343
......@@ -1410,11 +1349,13 @@ pub const DeclGen = struct {
14101349
14111350 const bool_ty_ref = try self.resolveType(Type.bool, .indirect);
14121351
1413 // its an actual optional
1414 return try self.spv.simpleStructType(&.{
1415 .{ .ty = payload_ty_ref, .name = "payload" },
1416 .{ .ty = bool_ty_ref, .name = "valid" },
1417 });
1352 return try self.spv.resolve(.{ .struct_type = .{
1353 .member_types = &.{ payload_ty_ref, bool_ty_ref },
1354 .member_names = &.{
1355 try self.spv.resolveString("payload"),
1356 try self.spv.resolveString("valid"),
1357 },
1358 } });
14181359 },
14191360 .Union => return try self.resolveUnionType(ty, null),
14201361 .ErrorSet => return try self.intType(.unsigned, 16),
......@@ -1429,20 +1370,30 @@ pub const DeclGen = struct {
14291370
14301371 const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
14311372
1432 var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){};
1373 var member_types: [2]CacheRef = undefined;
1374 var member_names: [2]CacheString = undefined;
14331375 if (eu_layout.error_first) {
14341376 // Put the error first
1435 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
1436 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
1377 member_types = .{ error_ty_ref, payload_ty_ref };
1378 member_names = .{
1379 try self.spv.resolveString("error"),
1380 try self.spv.resolveString("payload"),
1381 };
14371382 // TODO: ABI padding?
14381383 } else {
14391384 // Put the payload first.
1440 members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" });
1441 members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" });
1385 member_types = .{ payload_ty_ref, error_ty_ref };
1386 member_names = .{
1387 try self.spv.resolveString("payload"),
1388 try self.spv.resolveString("error"),
1389 };
14421390 // TODO: ABI padding?
14431391 }
14441392
1445 return try self.spv.simpleStructType(members.slice());
1393 return try self.spv.resolve(.{ .struct_type = .{
1394 .member_types = &member_types,
1395 .member_names = &member_names,
1396 } });
14461397 },
14471398
14481399 .Null,
......@@ -1526,17 +1477,13 @@ pub const DeclGen = struct {
15261477 /// the name of an error in the text executor.
15271478 fn generateTestEntryPoint(self: *DeclGen, name: []const u8, spv_test_decl_index: SpvModule.Decl.Index) !void {
15281479 const anyerror_ty_ref = try self.resolveType(Type.anyerror, .direct);
1529 const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup, 0);
1480 const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup);
15301481 const void_ty_ref = try self.resolveType(Type.void, .direct);
15311482
1532 const kernel_proto_ty_ref = blk: {
1533 const proto_payload = try self.spv.arena.create(SpvType.Payload.Function);
1534 proto_payload.* = .{
1535 .return_type = void_ty_ref,
1536 .parameters = try self.spv.arena.dupe(SpvType.Ref, &.{ptr_anyerror_ty_ref}),
1537 };
1538 break :blk try self.spv.resolveType(SpvType.initPayload(&proto_payload.base));
1539 };
1483 const kernel_proto_ty_ref = try self.spv.resolve(.{ .function_type = .{
1484 .return_type = void_ty_ref,
1485 .parameters = &.{ptr_anyerror_ty_ref},
1486 } });
15401487
15411488 const test_id = self.spv.declPtr(spv_test_decl_index).result_id;
15421489
......@@ -1670,9 +1617,9 @@ pub const DeclGen = struct {
16701617 }
16711618 }
16721619
1673 fn boolToInt(self: *DeclGen, result_ty_ref: SpvType.Ref, condition_id: IdRef) !IdRef {
1674 const zero_id = try self.constInt(result_ty_ref, 0);
1675 const one_id = try self.constInt(result_ty_ref, 1);
1620 fn boolToInt(self: *DeclGen, result_ty_ref: CacheRef, condition_id: IdRef) !IdRef {
1621 const zero_id = try self.spv.constInt(result_ty_ref, 0);
1622 const one_id = try self.spv.constInt(result_ty_ref, 1);
16761623 const result_id = self.spv.allocId();
16771624 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
16781625 .id_result_type = self.typeId(result_ty_ref),
......@@ -1691,7 +1638,7 @@ pub const DeclGen = struct {
16911638 .Bool => blk: {
16921639 const direct_bool_ty_ref = try self.resolveType(ty, .direct);
16931640 const indirect_bool_ty_ref = try self.resolveType(ty, .indirect);
1694 const zero_id = try self.constInt(indirect_bool_ty_ref, 0);
1641 const zero_id = try self.spv.constInt(indirect_bool_ty_ref, 0);
16951642 const result_id = self.spv.allocId();
16961643 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
16971644 .id_result_type = self.typeId(direct_bool_ty_ref),
......@@ -1929,10 +1876,10 @@ pub const DeclGen = struct {
19291876 return result_id;
19301877 }
19311878
1932 fn maskStrangeInt(self: *DeclGen, ty_ref: SpvType.Ref, value_id: IdRef, bits: u16) !IdRef {
1879 fn maskStrangeInt(self: *DeclGen, ty_ref: CacheRef, value_id: IdRef, bits: u16) !IdRef {
19331880 const mask_value = if (bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @intCast(u6, bits)) - 1;
19341881 const result_id = self.spv.allocId();
1935 const mask_id = try self.constInt(ty_ref, mask_value);
1882 const mask_id = try self.spv.constInt(ty_ref, mask_value);
19361883 try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{
19371884 .id_result_type = self.typeId(ty_ref),
19381885 .id_result = result_id,
......@@ -2071,7 +2018,7 @@ pub const DeclGen = struct {
20712018 // Note that signed overflow is also wrapping in spir-v.
20722019
20732020 const rhs_lt_zero_id = self.spv.allocId();
2074 const zero_id = try self.constInt(operand_ty_ref, 0);
2021 const zero_id = try self.spv.constInt(operand_ty_ref, 0);
20752022 try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{
20762023 .id_result_type = self.typeId(bool_ty_ref),
20772024 .id_result = rhs_lt_zero_id,
......@@ -2150,7 +2097,7 @@ pub const DeclGen = struct {
21502097 /// is the latter and PtrAccessChain is the former.
21512098 fn accessChain(
21522099 self: *DeclGen,
2153 result_ty_ref: SpvType.Ref,
2100 result_ty_ref: CacheRef,
21542101 base: IdRef,
21552102 indexes: []const IdRef,
21562103 ) !IdRef {
......@@ -2166,7 +2113,7 @@ pub const DeclGen = struct {
21662113
21672114 fn ptrAccessChain(
21682115 self: *DeclGen,
2169 result_ty_ref: SpvType.Ref,
2116 result_ty_ref: CacheRef,
21702117 base: IdRef,
21712118 element: IdRef,
21722119 indexes: []const IdRef,
......@@ -2541,7 +2488,7 @@ pub const DeclGen = struct {
25412488 // Construct new pointer type for the resulting pointer
25422489 const elem_ty = ptr_ty.elemType2(); // use elemType() so that we get T for *[N]T.
25432490 const elem_ty_ref = try self.resolveType(elem_ty, .direct);
2544 const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace()), 0);
2491 const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace()));
25452492 if (ptr_ty.isSinglePointer()) {
25462493 // Pointer-to-array. In this case, the resulting pointer is not of the same type
25472494 // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain.
......@@ -2631,9 +2578,8 @@ pub const DeclGen = struct {
26312578 .Struct => switch (object_ty.containerLayout()) {
26322579 .Packed => unreachable, // TODO
26332580 else => {
2634 const u32_ty_id = self.typeId(try self.intType(.unsigned, 32));
2635 const field_index_id = self.spv.allocId();
2636 try self.spv.emitConstant(u32_ty_id, field_index_id, .{ .uint32 = field_index });
2581 const field_index_ty_ref = try self.intType(.unsigned, 32);
2582 const field_index_id = try self.spv.constInt(field_index_ty_ref, field_index);
26372583 const result_ty_ref = try self.resolveType(result_ptr_ty, .direct);
26382584 return try self.accessChain(result_ty_ref, object_ptr, &.{field_index_id});
26392585 },
......@@ -2657,7 +2603,7 @@ pub const DeclGen = struct {
26572603 fn makePointerConstant(
26582604 self: *DeclGen,
26592605 section: *SpvSection,
2660 ptr_ty_ref: SpvType.Ref,
2606 ptr_ty_ref: CacheRef,
26612607 ptr_id: IdRef,
26622608 ) !IdRef {
26632609 const result_id = self.spv.allocId();
......@@ -2675,11 +2621,11 @@ pub const DeclGen = struct {
26752621 // placed in the Function address space.
26762622 fn alloc(
26772623 self: *DeclGen,
2678 ty_ref: SpvType.Ref,
2624 ty_ref: CacheRef,
26792625 initializer: ?IdRef,
26802626 ) !IdRef {
2681 const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function, 0);
2682 const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0);
2627 const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function);
2628 const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic);
26832629
26842630 // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to
26852631 // directly generate them into func.prologue instead of the body.
......@@ -2833,7 +2779,7 @@ pub const DeclGen = struct {
28332779
28342780 const val_is_undef = if (self.air.value(bin_op.rhs)) |val| val.isUndefDeep() else false;
28352781 if (val_is_undef) {
2836 const undef = try self.constUndef(ptr_ty_ref);
2782 const undef = try self.spv.constUndef(ptr_ty_ref);
28372783 try self.store(ptr_ty, ptr, undef);
28382784 } else {
28392785 try self.store(ptr_ty, ptr, value);
......@@ -2904,7 +2850,7 @@ pub const DeclGen = struct {
29042850 else
29052851 err_union_id;
29062852
2907 const zero_id = try self.constInt(err_ty_ref, 0);
2853 const zero_id = try self.spv.constInt(err_ty_ref, 0);
29082854 const is_err_id = self.spv.allocId();
29092855 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
29102856 .id_result_type = self.typeId(bool_ty_ref),
......@@ -2953,7 +2899,7 @@ pub const DeclGen = struct {
29532899
29542900 if (err_union_ty.errorUnionSet().errorSetIsEmpty()) {
29552901 // No error possible, so just return undefined.
2956 return try self.constUndef(err_ty_ref);
2902 return try self.spv.constUndef(err_ty_ref);
29572903 }
29582904
29592905 const payload_ty = err_union_ty.errorUnionPayload();
......@@ -2982,7 +2928,7 @@ pub const DeclGen = struct {
29822928
29832929 const payload_ty_ref = try self.resolveType(payload_ty, .indirect);
29842930 var members = std.BoundedArray(IdRef, 2){};
2985 const payload_id = try self.constUndef(payload_ty_ref);
2931 const payload_id = try self.spv.constUndef(payload_ty_ref);
29862932 if (eu_layout.error_first) {
29872933 members.appendAssumeCapacity(operand_id);
29882934 members.appendAssumeCapacity(payload_id);
......@@ -3024,7 +2970,7 @@ pub const DeclGen = struct {
30242970 operand_id;
30252971
30262972 const payload_ty_ref = try self.resolveType(ptr_ty, .direct);
3027 const null_id = try self.constNull(payload_ty_ref);
2973 const null_id = try self.spv.constNull(payload_ty_ref);
30282974 const result_id = self.spv.allocId();
30292975 const operands = .{
30302976 .id_result_type = self.typeId(bool_ty_ref),
src/codegen/spirv/Assembler.zig+36-134
......@@ -11,7 +11,8 @@ const IdRef = spec.IdRef;
1111const IdResult = spec.IdResult;
1212
1313const SpvModule = @import("Module.zig");
14const SpvType = @import("type.zig").Type;
14const CacheRef = SpvModule.CacheRef;
15const CacheKey = SpvModule.CacheKey;
1516
1617/// Represents a token in the assembly template.
1718const Token = struct {
......@@ -126,7 +127,7 @@ const AsmValue = union(enum) {
126127 value: IdRef,
127128
128129 /// This result-value represents a type registered into the module's type system.
129 ty: SpvType.Ref,
130 ty: CacheRef,
130131
131132 /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue
132133 /// is of a variant that allows the result to be obtained (not an unresolved
......@@ -135,7 +136,7 @@ const AsmValue = union(enum) {
135136 return switch (self) {
136137 .just_declared, .unresolved_forward_reference => unreachable,
137138 .value => |result| result,
138 .ty => |ref| spv.typeId(ref),
139 .ty => |ref| spv.resultId(ref),
139140 };
140141 }
141142};
......@@ -267,9 +268,9 @@ fn processInstruction(self: *Assembler) !void {
267268/// refers to the result.
268269fn processTypeInstruction(self: *Assembler) !AsmValue {
269270 const operands = self.inst.operands.items;
270 const ty = switch (self.inst.opcode) {
271 .OpTypeVoid => SpvType.initTag(.void),
272 .OpTypeBool => SpvType.initTag(.bool),
271 const ref = switch (self.inst.opcode) {
272 .OpTypeVoid => try self.spv.resolve(.void_type),
273 .OpTypeBool => try self.spv.resolve(.bool_type),
273274 .OpTypeInt => blk: {
274275 const signedness: std.builtin.Signedness = switch (operands[2].literal32) {
275276 0 => .unsigned,
......@@ -282,7 +283,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
282283 const width = std.math.cast(u16, operands[1].literal32) orelse {
283284 return self.fail(0, "int type of {} bits is too large", .{operands[1].literal32});
284285 };
285 break :blk try SpvType.int(self.spv.arena, signedness, width);
286 break :blk try self.spv.intType(signedness, width);
286287 },
287288 .OpTypeFloat => blk: {
288289 const bits = operands[1].literal32;
......@@ -292,136 +293,36 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
292293 return self.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits});
293294 },
294295 }
295 break :blk SpvType.float(@intCast(u16, bits));
296 },
297 .OpTypeVector => blk: {
298 const payload = try self.spv.arena.create(SpvType.Payload.Vector);
299 payload.* = .{
300 .component_type = try self.resolveTypeRef(operands[1].ref_id),
301 .component_count = operands[2].literal32,
302 };
303 break :blk SpvType.initPayload(&payload.base);
304 },
305 .OpTypeMatrix => blk: {
306 const payload = try self.spv.arena.create(SpvType.Payload.Matrix);
307 payload.* = .{
308 .column_type = try self.resolveTypeRef(operands[1].ref_id),
309 .column_count = operands[2].literal32,
310 };
311 break :blk SpvType.initPayload(&payload.base);
312 },
313 .OpTypeImage => blk: {
314 const payload = try self.spv.arena.create(SpvType.Payload.Image);
315 payload.* = .{
316 .sampled_type = try self.resolveTypeRef(operands[1].ref_id),
317 .dim = @intToEnum(spec.Dim, operands[2].value),
318 .depth = switch (operands[3].literal32) {
319 0 => .no,
320 1 => .yes,
321 2 => .maybe,
322 else => {
323 return self.fail(0, "'{}' is not a valid image depth (expected 0, 1 or 2)", .{operands[3].literal32});
324 },
325 },
326 .arrayed = switch (operands[4].literal32) {
327 0 => false,
328 1 => true,
329 else => {
330 return self.fail(0, "'{}' is not a valid image arrayed-ness (expected 0 or 1)", .{operands[4].literal32});
331 },
332 },
333 .multisampled = switch (operands[5].literal32) {
334 0 => false,
335 1 => true,
336 else => {
337 return self.fail(0, "'{}' is not a valid image multisampled-ness (expected 0 or 1)", .{operands[5].literal32});
338 },
339 },
340 .sampled = switch (operands[6].literal32) {
341 0 => .known_at_runtime,
342 1 => .with_sampler,
343 2 => .without_sampler,
344 else => {
345 return self.fail(0, "'{}' is not a valid image sampled-ness (expected 0, 1 or 2)", .{operands[6].literal32});
346 },
347 },
348 .format = @intToEnum(spec.ImageFormat, operands[7].value),
349 .access_qualifier = if (operands.len > 8)
350 @intToEnum(spec.AccessQualifier, operands[8].value)
351 else
352 null,
353 };
354 break :blk SpvType.initPayload(&payload.base);
355 },
356 .OpTypeSampler => SpvType.initTag(.sampler),
357 .OpTypeSampledImage => blk: {
358 const payload = try self.spv.arena.create(SpvType.Payload.SampledImage);
359 payload.* = .{
360 .image_type = try self.resolveTypeRef(operands[1].ref_id),
361 };
362 break :blk SpvType.initPayload(&payload.base);
296 break :blk try self.spv.resolve(.{ .float_type = .{ .bits = @intCast(u16, bits) } });
363297 },
298 .OpTypeVector => try self.spv.resolve(.{ .vector_type = .{
299 .component_type = try self.resolveTypeRef(operands[1].ref_id),
300 .component_count = operands[2].literal32,
301 } }),
364302 .OpTypeArray => {
365303 // TODO: The length of an OpTypeArray is determined by a constant (which may be a spec constant),
366304 // and so some consideration must be taken when entering this in the type system.
367305 return self.todo("process OpTypeArray", .{});
368306 },
369 .OpTypeRuntimeArray => blk: {
370 const payload = try self.spv.arena.create(SpvType.Payload.RuntimeArray);
371 payload.* = .{
372 .element_type = try self.resolveTypeRef(operands[1].ref_id),
373 // TODO: Fetch array stride from decorations.
374 .array_stride = 0,
375 };
376 break :blk SpvType.initPayload(&payload.base);
377 },
378 .OpTypeOpaque => blk: {
379 const payload = try self.spv.arena.create(SpvType.Payload.Opaque);
380 const name_offset = operands[1].string;
381 payload.* = .{
382 .name = std.mem.sliceTo(self.inst.string_bytes.items[name_offset..], 0),
383 };
384 break :blk SpvType.initPayload(&payload.base);
385 },
386 .OpTypePointer => blk: {
387 const payload = try self.spv.arena.create(SpvType.Payload.Pointer);
388 payload.* = .{
389 .storage_class = @intToEnum(spec.StorageClass, operands[1].value),
390 .child_type = try self.resolveTypeRef(operands[2].ref_id),
391 // TODO: Fetch decorations
392 };
393 break :blk SpvType.initPayload(&payload.base);
394 },
307 .OpTypePointer => try self.spv.ptrType(
308 try self.resolveTypeRef(operands[2].ref_id),
309 @intToEnum(spec.StorageClass, operands[1].value),
310 ),
395311 .OpTypeFunction => blk: {
396312 const param_operands = operands[2..];
397 const param_types = try self.spv.arena.alloc(SpvType.Ref, param_operands.len);
313 const param_types = try self.spv.gpa.alloc(CacheRef, param_operands.len);
314 defer self.spv.gpa.free(param_types);
398315 for (param_types, 0..) |*param, i| {
399316 param.* = try self.resolveTypeRef(param_operands[i].ref_id);
400317 }
401 const payload = try self.spv.arena.create(SpvType.Payload.Function);
402 payload.* = .{
318 break :blk try self.spv.resolve(.{ .function_type = .{
403319 .return_type = try self.resolveTypeRef(operands[1].ref_id),
404320 .parameters = param_types,
405 };
406 break :blk SpvType.initPayload(&payload.base);
321 } });
407322 },
408 .OpTypeEvent => SpvType.initTag(.event),
409 .OpTypeDeviceEvent => SpvType.initTag(.device_event),
410 .OpTypeReserveId => SpvType.initTag(.reserve_id),
411 .OpTypeQueue => SpvType.initTag(.queue),
412 .OpTypePipe => blk: {
413 const payload = try self.spv.arena.create(SpvType.Payload.Pipe);
414 payload.* = .{
415 .qualifier = @intToEnum(spec.AccessQualifier, operands[1].value),
416 };
417 break :blk SpvType.initPayload(&payload.base);
418 },
419 .OpTypePipeStorage => SpvType.initTag(.pipe_storage),
420 .OpTypeNamedBarrier => SpvType.initTag(.named_barrier),
421323 else => return self.todo("process type instruction {s}", .{@tagName(self.inst.opcode)}),
422324 };
423325
424 const ref = try self.spv.resolveType(ty);
425326 return AsmValue{ .ty = ref };
426327}
427328
......@@ -528,7 +429,7 @@ fn resolveRef(self: *Assembler, ref: AsmValue.Ref) !AsmValue {
528429}
529430
530431/// Resolve a value reference as type.
531fn resolveTypeRef(self: *Assembler, ref: AsmValue.Ref) !SpvType.Ref {
432fn resolveTypeRef(self: *Assembler, ref: AsmValue.Ref) !CacheRef {
532433 const value = try self.resolveRef(ref);
533434 switch (value) {
534435 .just_declared, .unresolved_forward_reference => unreachable,
......@@ -761,19 +662,20 @@ fn parseContextDependentNumber(self: *Assembler) !void {
761662
762663 const tok = self.currentToken();
763664 const result_type_ref = try self.resolveTypeRef(self.inst.operands.items[0].ref_id);
764 const result_type = self.spv.type_cache.keys()[@enumToInt(result_type_ref)];
765 if (result_type.isInt()) {
766 try self.parseContextDependentInt(result_type.intSignedness(), result_type.intFloatBits());
767 } else if (result_type.isFloat()) {
768 const width = result_type.intFloatBits();
769 switch (width) {
770 16 => try self.parseContextDependentFloat(16),
771 32 => try self.parseContextDependentFloat(32),
772 64 => try self.parseContextDependentFloat(64),
773 else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),
774 }
775 } else {
776 return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())});
665 const result_type = self.spv.cache.lookup(result_type_ref);
666 switch (result_type) {
667 .int_type => |int| {
668 try self.parseContextDependentInt(int.signedness, int.bits);
669 },
670 .float_type => |float| {
671 switch (float.bits) {
672 16 => try self.parseContextDependentFloat(16),
673 32 => try self.parseContextDependentFloat(32),
674 64 => try self.parseContextDependentFloat(64),
675 else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{float.bits}),
676 }
677 },
678 else => return self.fail(tok.start, "cannot parse literal constant", .{}),
777679 }
778680}
779681
src/codegen/spirv/Cache.zig created+1046
......@@ -0,0 +1,1046 @@
1//! This file implements an InternPool-like structure that caches
2//! SPIR-V types and constants. Instead of generating type and
3//! constant instructions directly, we first keep a representation
4//! in a compressed database. This is then only later turned into
5//! actual SPIR-V instructions.
6//! Note: This cache is insertion-ordered. This means that we
7//! can materialize the SPIR-V instructions in the proper order,
8//! as SPIR-V requires that the type is emitted before use.
9//! Note: According to SPIR-V spec section 2.8, Types and Variables,
10//! non-pointer non-aggrerate types (which includes matrices and
11//! vectors) must have a _unique_ representation in the final binary.
12
13const std = @import("std");
14const assert = std.debug.assert;
15const Allocator = std.mem.Allocator;
16
17const Section = @import("Section.zig");
18const Module = @import("Module.zig");
19
20const spec = @import("spec.zig");
21const Opcode = spec.Opcode;
22const IdResult = spec.IdResult;
23const StorageClass = spec.StorageClass;
24
25const Self = @This();
26
27map: std.AutoArrayHashMapUnmanaged(void, void) = .{},
28items: std.MultiArrayList(Item) = .{},
29extra: std.ArrayListUnmanaged(u32) = .{},
30
31string_bytes: std.ArrayListUnmanaged(u8) = .{},
32strings: std.AutoArrayHashMapUnmanaged(void, u32) = .{},
33
34const Item = struct {
35 tag: Tag,
36 /// The result-id that this item uses.
37 result_id: IdResult,
38 /// The Tag determines how this should be interpreted.
39 data: u32,
40};
41
42const Tag = enum {
43 // -- Types
44 /// Simple type that has no additional data.
45 /// data is SimpleType.
46 type_simple,
47 /// Signed integer type
48 /// data is number of bits
49 type_int_signed,
50 /// Unsigned integer type
51 /// data is number of bits
52 type_int_unsigned,
53 /// Floating point type
54 /// data is number of bits
55 type_float,
56 /// Vector type
57 /// data is payload to VectorType
58 type_vector,
59 /// Array type
60 /// data is payload to ArrayType
61 type_array,
62 /// Function (proto)type
63 /// data is payload to FunctionType
64 type_function,
65 /// Pointer type in the CrossWorkgroup storage class
66 /// data is child type
67 type_ptr_generic,
68 /// Pointer type in the CrossWorkgroup storage class
69 /// data is child type
70 type_ptr_crosswgp,
71 /// Pointer type in the Function storage class
72 /// data is child type
73 type_ptr_function,
74 /// Simple pointer type that does not have any decorations.
75 /// data is payload to SimplePointerType
76 type_ptr_simple,
77 /// Simple structure type that does not have any decorations.
78 /// data is payload to SimpleStructType
79 type_struct_simple,
80 /// Simple structure type that does not have any decorations, but does
81 /// have member names trailing.
82 /// data is payload to SimpleStructType
83 type_struct_simple_with_member_names,
84
85 // -- Values
86 /// Value of type u8
87 /// data is value
88 uint8,
89 /// Value of type u32
90 /// data is value
91 uint32,
92 // TODO: More specialized tags here.
93 /// Integer value for signed values that are smaller than 32 bits.
94 /// data is pointer to Int32
95 int_small,
96 /// Integer value for unsigned values that are smaller than 32 bits.
97 /// data is pointer to UInt32
98 uint_small,
99 /// Integer value for signed values that are beteen 32 and 64 bits.
100 /// data is pointer to Int64
101 int_large,
102 /// Integer value for unsinged values that are beteen 32 and 64 bits.
103 /// data is pointer to UInt64
104 uint_large,
105 /// Value of type f16
106 /// data is value
107 float16,
108 /// Value of type f32
109 /// data is value
110 float32,
111 /// Value of type f64
112 /// data is payload to Float16
113 float64,
114 /// Undefined value
115 /// data is type
116 undef,
117 /// Null value
118 /// data is type
119 null,
120 /// Bool value that is true
121 /// data is (bool) type
122 bool_true,
123 /// Bool value that is false
124 /// data is (bool) type
125 bool_false,
126
127 const SimpleType = enum { void, bool };
128
129 const VectorType = Key.VectorType;
130 const ArrayType = Key.ArrayType;
131
132 // Trailing:
133 // - [param_len]Ref: parameter types.
134 const FunctionType = struct {
135 param_len: u32,
136 return_type: Ref,
137 };
138
139 const SimplePointerType = struct {
140 storage_class: StorageClass,
141 child_type: Ref,
142 };
143
144 /// Trailing:
145 /// - [members_len]Ref: Member types.
146 /// - [members_len]String: Member names, -- ONLY if the tag is type_struct_simple_with_member_names
147 const SimpleStructType = struct {
148 /// (optional) The name of the struct.
149 name: String,
150 /// Number of members that this struct has.
151 members_len: u32,
152 };
153
154 const Float64 = struct {
155 // Low-order 32 bits of the value.
156 low: u32,
157 // High-order 32 bits of the value.
158 high: u32,
159
160 fn encode(value: f64) Float64 {
161 const bits = @bitCast(u64, value);
162 return .{
163 .low = @truncate(u32, bits),
164 .high = @truncate(u32, bits >> 32),
165 };
166 }
167
168 fn decode(self: Float64) f64 {
169 const bits = @as(u64, self.low) | (@as(u64, self.high) << 32);
170 return @bitCast(f64, bits);
171 }
172 };
173
174 const Int32 = struct {
175 ty: Ref,
176 value: i32,
177 };
178
179 const UInt32 = struct {
180 ty: Ref,
181 value: u32,
182 };
183
184 const UInt64 = struct {
185 ty: Ref,
186 low: u32,
187 high: u32,
188
189 fn encode(ty: Ref, value: u64) Int64 {
190 return .{
191 .ty = ty,
192 .low = @truncate(u32, value),
193 .high = @truncate(u32, value >> 32),
194 };
195 }
196
197 fn decode(self: UInt64) u64 {
198 return @as(u64, self.low) | (@as(u64, self.high) << 32);
199 }
200 };
201
202 const Int64 = struct {
203 ty: Ref,
204 low: u32,
205 high: u32,
206
207 fn encode(ty: Ref, value: i64) Int64 {
208 return .{
209 .ty = ty,
210 .low = @truncate(u32, @bitCast(u64, value)),
211 .high = @truncate(u32, @bitCast(u64, value) >> 32),
212 };
213 }
214
215 fn decode(self: Int64) i64 {
216 return @bitCast(i64, @as(u64, self.low) | (@as(u64, self.high) << 32));
217 }
218 };
219};
220
221pub const Ref = enum(u32) { _ };
222
223/// This union represents something that can be interned. This includes
224/// types and constants. This structure is used for interfacing with the
225/// database: Values described for this structure are ephemeral and stored
226/// in a more memory-efficient manner internally.
227pub const Key = union(enum) {
228 // -- Types
229 void_type,
230 bool_type,
231 int_type: IntType,
232 float_type: FloatType,
233 vector_type: VectorType,
234 array_type: ArrayType,
235 function_type: FunctionType,
236 ptr_type: PointerType,
237 struct_type: StructType,
238
239 // -- values
240 int: Int,
241 float: Float,
242 undef: Undef,
243 null: Null,
244 bool: Bool,
245
246 pub const IntType = std.builtin.Type.Int;
247 pub const FloatType = std.builtin.Type.Float;
248
249 pub const VectorType = struct {
250 component_type: Ref,
251 component_count: u32,
252 };
253
254 pub const ArrayType = struct {
255 /// Child type of this array.
256 element_type: Ref,
257 /// Reference to a constant.
258 length: Ref,
259 /// Type has the 'ArrayStride' decoration.
260 /// If zero, no stride is present.
261 stride: u32 = 0,
262 };
263
264 pub const FunctionType = struct {
265 return_type: Ref,
266 parameters: []const Ref,
267 };
268
269 pub const PointerType = struct {
270 storage_class: StorageClass,
271 child_type: Ref,
272 // TODO: Decorations:
273 // - Alignment
274 // - ArrayStride,
275 // - MaxByteOffset,
276 };
277
278 pub const StructType = struct {
279 // TODO: Decorations.
280 /// The name of the structure. Can be `.none`.
281 name: String = .none,
282 /// The type of each member.
283 member_types: []const Ref,
284 /// Name for each member. May be omitted.
285 member_names: ?[]const String = null,
286
287 fn memberNames(self: @This()) []const String {
288 return if (self.member_names) |member_names| member_names else &.{};
289 }
290 };
291
292 pub const Int = struct {
293 /// The type: any bitness integer.
294 ty: Ref,
295 /// The actual value. Only uint64 and int64 types
296 /// are available here: Smaller types should use these
297 /// fields.
298 value: Value,
299
300 pub const Value = union(enum) {
301 uint64: u64,
302 int64: i64,
303 };
304
305 /// Turns this value into the corresponding 32-bit literal, 2s complement signed.
306 fn toBits32(self: Int) u32 {
307 return switch (self.value) {
308 .uint64 => |val| @intCast(u32, val),
309 .int64 => |val| if (val < 0) @bitCast(u32, @intCast(i32, val)) else @intCast(u32, val),
310 };
311 }
312
313 fn toBits64(self: Int) u64 {
314 return switch (self.value) {
315 .uint64 => |val| val,
316 .int64 => |val| @bitCast(u64, val),
317 };
318 }
319
320 fn to(self: Int, comptime T: type) T {
321 return switch (self.value) {
322 inline else => |val| @intCast(T, val),
323 };
324 }
325 };
326
327 /// Represents a numberic value of some type.
328 pub const Float = struct {
329 /// The type: 16, 32, or 64-bit float.
330 ty: Ref,
331 /// The actual value.
332 value: Value,
333
334 pub const Value = union(enum) {
335 float16: f16,
336 float32: f32,
337 float64: f64,
338 };
339 };
340
341 pub const Undef = struct {
342 ty: Ref,
343 };
344
345 pub const Null = struct {
346 ty: Ref,
347 };
348
349 pub const Bool = struct {
350 ty: Ref,
351 value: bool,
352 };
353
354 fn hash(self: Key) u32 {
355 var hasher = std.hash.Wyhash.init(0);
356 switch (self) {
357 .float => |float| {
358 std.hash.autoHash(&hasher, float.ty);
359 switch (float.value) {
360 .float16 => |value| std.hash.autoHash(&hasher, @bitCast(u16, value)),
361 .float32 => |value| std.hash.autoHash(&hasher, @bitCast(u32, value)),
362 .float64 => |value| std.hash.autoHash(&hasher, @bitCast(u64, value)),
363 }
364 },
365 .function_type => |func| {
366 std.hash.autoHash(&hasher, func.return_type);
367 for (func.parameters) |param_type| {
368 std.hash.autoHash(&hasher, param_type);
369 }
370 },
371 .struct_type => |struct_type| {
372 std.hash.autoHash(&hasher, struct_type.name);
373 for (struct_type.member_types) |member_type| {
374 std.hash.autoHash(&hasher, member_type);
375 }
376 for (struct_type.memberNames()) |member_name| {
377 std.hash.autoHash(&hasher, member_name);
378 }
379 },
380 inline else => |key| std.hash.autoHash(&hasher, key),
381 }
382 return @truncate(u32, hasher.final());
383 }
384
385 fn eql(a: Key, b: Key) bool {
386 const KeyTag = @typeInfo(Key).Union.tag_type.?;
387 const a_tag: KeyTag = a;
388 const b_tag: KeyTag = b;
389 if (a_tag != b_tag) {
390 return false;
391 }
392 return switch (a) {
393 .function_type => |a_func| {
394 const b_func = b.function_type;
395 return a_func.return_type == b_func.return_type and
396 std.mem.eql(Ref, a_func.parameters, b_func.parameters);
397 },
398 .struct_type => |a_struct| {
399 const b_struct = b.struct_type;
400 return a_struct.name == b_struct.name and
401 std.mem.eql(Ref, a_struct.member_types, b_struct.member_types) and
402 std.mem.eql(String, a_struct.memberNames(), b_struct.memberNames());
403 },
404 // TODO: Unroll?
405 else => std.meta.eql(a, b),
406 };
407 }
408
409 pub const Adapter = struct {
410 self: *const Self,
411
412 pub fn eql(ctx: @This(), a: Key, b_void: void, b_index: usize) bool {
413 _ = b_void;
414 return ctx.self.lookup(@intToEnum(Ref, b_index)).eql(a);
415 }
416
417 pub fn hash(ctx: @This(), a: Key) u32 {
418 _ = ctx;
419 return a.hash();
420 }
421 };
422
423 fn toSimpleType(self: Key) Tag.SimpleType {
424 return switch (self) {
425 .void_type => .void,
426 .bool_type => .bool,
427 else => unreachable,
428 };
429 }
430};
431
432pub fn deinit(self: *Self, spv: *const Module) void {
433 self.map.deinit(spv.gpa);
434 self.items.deinit(spv.gpa);
435 self.extra.deinit(spv.gpa);
436 self.string_bytes.deinit(spv.gpa);
437 self.strings.deinit(spv.gpa);
438}
439
440/// Actually materialize the database into spir-v instructions.
441/// This function returns a spir-v section of (only) constant and type instructions.
442/// Additionally, decorations, debug names, etc, are all directly emitted into the
443/// `spv` module. The section is allocated with `spv.gpa`.
444pub fn materialize(self: *const Self, spv: *Module) !Section {
445 var section = Section{};
446 errdefer section.deinit(spv.gpa);
447 for (self.items.items(.result_id), 0..) |result_id, index| {
448 try self.emit(spv, result_id, @intToEnum(Ref, index), &section);
449 }
450 return section;
451}
452
453fn emit(
454 self: *const Self,
455 spv: *Module,
456 result_id: IdResult,
457 ref: Ref,
458 section: *Section,
459) !void {
460 const key = self.lookup(ref);
461 const Lit = spec.LiteralContextDependentNumber;
462 switch (key) {
463 .void_type => {
464 try section.emit(spv.gpa, .OpTypeVoid, .{ .id_result = result_id });
465 try spv.debugName(result_id, "void", .{});
466 },
467 .bool_type => {
468 try section.emit(spv.gpa, .OpTypeBool, .{ .id_result = result_id });
469 try spv.debugName(result_id, "bool", .{});
470 },
471 .int_type => |int| {
472 try section.emit(spv.gpa, .OpTypeInt, .{
473 .id_result = result_id,
474 .width = int.bits,
475 .signedness = switch (int.signedness) {
476 .unsigned => @as(spec.Word, 0),
477 .signed => 1,
478 },
479 });
480 const ui: []const u8 = switch (int.signedness) {
481 .unsigned => "u",
482 .signed => "i",
483 };
484 try spv.debugName(result_id, "{s}{}", .{ ui, int.bits });
485 },
486 .float_type => |float| {
487 try section.emit(spv.gpa, .OpTypeFloat, .{
488 .id_result = result_id,
489 .width = float.bits,
490 });
491 try spv.debugName(result_id, "f{}", .{float.bits});
492 },
493 .vector_type => |vector| {
494 try section.emit(spv.gpa, .OpTypeVector, .{
495 .id_result = result_id,
496 .component_type = self.resultId(vector.component_type),
497 .component_count = vector.component_count,
498 });
499 },
500 .array_type => |array| {
501 try section.emit(spv.gpa, .OpTypeArray, .{
502 .id_result = result_id,
503 .element_type = self.resultId(array.element_type),
504 .length = self.resultId(array.length),
505 });
506 if (array.stride != 0) {
507 try spv.decorate(result_id, .{ .ArrayStride = .{ .array_stride = array.stride } });
508 }
509 },
510 .function_type => |function| {
511 try section.emitRaw(spv.gpa, .OpTypeFunction, 2 + function.parameters.len);
512 section.writeOperand(IdResult, result_id);
513 section.writeOperand(IdResult, self.resultId(function.return_type));
514 for (function.parameters) |param_type| {
515 section.writeOperand(IdResult, self.resultId(param_type));
516 }
517 },
518 .ptr_type => |ptr| {
519 try section.emit(spv.gpa, .OpTypePointer, .{
520 .id_result = result_id,
521 .storage_class = ptr.storage_class,
522 .type = self.resultId(ptr.child_type),
523 });
524 // TODO: Decorations?
525 },
526 .struct_type => |struct_type| {
527 try section.emitRaw(spv.gpa, .OpTypeStruct, 1 + struct_type.member_types.len);
528 section.writeOperand(IdResult, result_id);
529 for (struct_type.member_types) |member_type| {
530 section.writeOperand(IdResult, self.resultId(member_type));
531 }
532 if (self.getString(struct_type.name)) |name| {
533 try spv.debugName(result_id, "{s}", .{name});
534 }
535 for (struct_type.memberNames(), 0..) |member_name, i| {
536 if (self.getString(member_name)) |name| {
537 try spv.memberDebugName(result_id, @intCast(u32, i), "{s}", .{name});
538 }
539 }
540 // TODO: Decorations?
541 },
542 .int => |int| {
543 const int_type = self.lookup(int.ty).int_type;
544 const ty_id = self.resultId(int.ty);
545 const lit: Lit = switch (int_type.bits) {
546 1...32 => .{ .uint32 = int.toBits32() },
547 33...64 => .{ .uint64 = int.toBits64() },
548 else => unreachable,
549 };
550
551 try section.emit(spv.gpa, .OpConstant, .{
552 .id_result_type = ty_id,
553 .id_result = result_id,
554 .value = lit,
555 });
556 },
557 .float => |float| {
558 const ty_id = self.resultId(float.ty);
559 const lit: Lit = switch (float.value) {
560 .float16 => |value| .{ .uint32 = @bitCast(u16, value) },
561 .float32 => |value| .{ .float32 = value },
562 .float64 => |value| .{ .float64 = value },
563 };
564 try section.emit(spv.gpa, .OpConstant, .{
565 .id_result_type = ty_id,
566 .id_result = result_id,
567 .value = lit,
568 });
569 },
570 .undef => |undef| {
571 try section.emit(spv.gpa, .OpUndef, .{
572 .id_result_type = self.resultId(undef.ty),
573 .id_result = result_id,
574 });
575 },
576 .null => |null_info| {
577 try section.emit(spv.gpa, .OpConstantNull, .{
578 .id_result_type = self.resultId(null_info.ty),
579 .id_result = result_id,
580 });
581 },
582 .bool => |bool_info| switch (bool_info.value) {
583 true => {
584 try section.emit(spv.gpa, .OpConstantTrue, .{
585 .id_result_type = self.resultId(bool_info.ty),
586 .id_result = result_id,
587 });
588 },
589 false => {
590 try section.emit(spv.gpa, .OpConstantFalse, .{
591 .id_result_type = self.resultId(bool_info.ty),
592 .id_result = result_id,
593 });
594 },
595 },
596 }
597}
598
599/// Add a key to this cache. Returns a reference to the key that
600/// was added. The corresponding result-id can be queried using
601/// self.resultId with the result.
602pub fn resolve(self: *Self, spv: *Module, key: Key) !Ref {
603 const adapter: Key.Adapter = .{ .self = self };
604 const entry = try self.map.getOrPutAdapted(spv.gpa, key, adapter);
605 if (entry.found_existing) {
606 return @intToEnum(Ref, entry.index);
607 }
608 const result_id = spv.allocId();
609 const item: Item = switch (key) {
610 inline .void_type, .bool_type => .{
611 .tag = .type_simple,
612 .result_id = result_id,
613 .data = @enumToInt(key.toSimpleType()),
614 },
615 .int_type => |int| blk: {
616 const t: Tag = switch (int.signedness) {
617 .signed => .type_int_signed,
618 .unsigned => .type_int_unsigned,
619 };
620 break :blk .{
621 .tag = t,
622 .result_id = result_id,
623 .data = int.bits,
624 };
625 },
626 .float_type => |float| .{
627 .tag = .type_float,
628 .result_id = result_id,
629 .data = float.bits,
630 },
631 .vector_type => |vector| .{
632 .tag = .type_vector,
633 .result_id = result_id,
634 .data = try self.addExtra(spv, vector),
635 },
636 .array_type => |array| .{
637 .tag = .type_array,
638 .result_id = result_id,
639 .data = try self.addExtra(spv, array),
640 },
641 .function_type => |function| blk: {
642 const extra = try self.addExtra(spv, Tag.FunctionType{
643 .param_len = @intCast(u32, function.parameters.len),
644 .return_type = function.return_type,
645 });
646 try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, function.parameters));
647 break :blk .{
648 .tag = .type_function,
649 .result_id = result_id,
650 .data = extra,
651 };
652 },
653 .ptr_type => |ptr| switch (ptr.storage_class) {
654 .Generic => Item{
655 .tag = .type_ptr_generic,
656 .result_id = result_id,
657 .data = @enumToInt(ptr.child_type),
658 },
659 .CrossWorkgroup => Item{
660 .tag = .type_ptr_crosswgp,
661 .result_id = result_id,
662 .data = @enumToInt(ptr.child_type),
663 },
664 .Function => Item{
665 .tag = .type_ptr_function,
666 .result_id = result_id,
667 .data = @enumToInt(ptr.child_type),
668 },
669 else => |storage_class| Item{
670 .tag = .type_ptr_simple,
671 .result_id = result_id,
672 .data = try self.addExtra(spv, Tag.SimplePointerType{
673 .storage_class = storage_class,
674 .child_type = ptr.child_type,
675 }),
676 },
677 },
678 .struct_type => |struct_type| blk: {
679 const extra = try self.addExtra(spv, Tag.SimpleStructType{
680 .name = struct_type.name,
681 .members_len = @intCast(u32, struct_type.member_types.len),
682 });
683 try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, struct_type.member_types));
684
685 if (struct_type.member_names) |member_names| {
686 try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, member_names));
687 break :blk Item{
688 .tag = .type_struct_simple_with_member_names,
689 .result_id = result_id,
690 .data = extra,
691 };
692 } else {
693 break :blk Item{
694 .tag = .type_struct_simple,
695 .result_id = result_id,
696 .data = extra,
697 };
698 }
699 },
700 .int => |int| blk: {
701 const int_type = self.lookup(int.ty).int_type;
702 if (int_type.signedness == .unsigned and int_type.bits == 8) {
703 break :blk .{
704 .tag = .uint8,
705 .result_id = result_id,
706 .data = int.to(u8),
707 };
708 } else if (int_type.signedness == .unsigned and int_type.bits == 32) {
709 break :blk .{
710 .tag = .uint32,
711 .result_id = result_id,
712 .data = int.to(u32),
713 };
714 }
715
716 switch (int.value) {
717 inline else => |val| {
718 if (val >= 0 and val <= std.math.maxInt(u32)) {
719 break :blk .{
720 .tag = .uint_small,
721 .result_id = result_id,
722 .data = try self.addExtra(spv, Tag.UInt32{
723 .ty = int.ty,
724 .value = @intCast(u32, val),
725 }),
726 };
727 } else if (val >= std.math.minInt(i32) and val <= std.math.maxInt(i32)) {
728 break :blk .{
729 .tag = .int_small,
730 .result_id = result_id,
731 .data = try self.addExtra(spv, Tag.Int32{
732 .ty = int.ty,
733 .value = @intCast(i32, val),
734 }),
735 };
736 } else if (val < 0) {
737 break :blk .{
738 .tag = .int_large,
739 .result_id = result_id,
740 .data = try self.addExtra(spv, Tag.Int64.encode(int.ty, @intCast(i64, val))),
741 };
742 } else {
743 break :blk .{
744 .tag = .uint_large,
745 .result_id = result_id,
746 .data = try self.addExtra(spv, Tag.UInt64.encode(int.ty, @intCast(u64, val))),
747 };
748 }
749 },
750 }
751 },
752 .float => |float| switch (self.lookup(float.ty).float_type.bits) {
753 16 => .{
754 .tag = .float16,
755 .result_id = result_id,
756 .data = @bitCast(u16, float.value.float16),
757 },
758 32 => .{
759 .tag = .float32,
760 .result_id = result_id,
761 .data = @bitCast(u32, float.value.float32),
762 },
763 64 => .{
764 .tag = .float64,
765 .result_id = result_id,
766 .data = try self.addExtra(spv, Tag.Float64.encode(float.value.float64)),
767 },
768 else => unreachable,
769 },
770 .undef => |undef| .{
771 .tag = .undef,
772 .result_id = result_id,
773 .data = @enumToInt(undef.ty),
774 },
775 .null => |null_info| .{
776 .tag = .null,
777 .result_id = result_id,
778 .data = @enumToInt(null_info.ty),
779 },
780 .bool => |bool_info| .{
781 .tag = switch (bool_info.value) {
782 true => Tag.bool_true,
783 false => Tag.bool_false,
784 },
785 .result_id = result_id,
786 .data = @enumToInt(bool_info.ty),
787 },
788 };
789 try self.items.append(spv.gpa, item);
790
791 return @intToEnum(Ref, entry.index);
792}
793
794/// Turn a Ref back into a Key.
795/// The Key is valid until the next call to resolve().
796pub fn lookup(self: *const Self, ref: Ref) Key {
797 const item = self.items.get(@enumToInt(ref));
798 const data = item.data;
799 return switch (item.tag) {
800 .type_simple => switch (@intToEnum(Tag.SimpleType, data)) {
801 .void => .void_type,
802 .bool => .bool_type,
803 },
804 .type_int_signed => .{ .int_type = .{
805 .signedness = .signed,
806 .bits = @intCast(u16, data),
807 } },
808 .type_int_unsigned => .{ .int_type = .{
809 .signedness = .unsigned,
810 .bits = @intCast(u16, data),
811 } },
812 .type_float => .{ .float_type = .{
813 .bits = @intCast(u16, data),
814 } },
815 .type_vector => .{ .vector_type = self.extraData(Tag.VectorType, data) },
816 .type_array => .{ .array_type = self.extraData(Tag.ArrayType, data) },
817 .type_function => {
818 const payload = self.extraDataTrail(Tag.FunctionType, data);
819 return .{
820 .function_type = .{
821 .return_type = payload.data.return_type,
822 .parameters = @ptrCast([]const Ref, self.extra.items[payload.trail..][0..payload.data.param_len]),
823 },
824 };
825 },
826 .type_ptr_generic => .{
827 .ptr_type = .{
828 .storage_class = .Generic,
829 .child_type = @intToEnum(Ref, data),
830 },
831 },
832 .type_ptr_crosswgp => .{
833 .ptr_type = .{
834 .storage_class = .CrossWorkgroup,
835 .child_type = @intToEnum(Ref, data),
836 },
837 },
838 .type_ptr_function => .{
839 .ptr_type = .{
840 .storage_class = .Function,
841 .child_type = @intToEnum(Ref, data),
842 },
843 },
844 .type_ptr_simple => {
845 const payload = self.extraData(Tag.SimplePointerType, data);
846 return .{
847 .ptr_type = .{
848 .storage_class = payload.storage_class,
849 .child_type = payload.child_type,
850 },
851 };
852 },
853 .type_struct_simple => {
854 const payload = self.extraDataTrail(Tag.SimpleStructType, data);
855 const member_types = @ptrCast([]const Ref, self.extra.items[payload.trail..][0..payload.data.members_len]);
856 return .{
857 .struct_type = .{
858 .name = payload.data.name,
859 .member_types = member_types,
860 .member_names = null,
861 },
862 };
863 },
864 .type_struct_simple_with_member_names => {
865 const payload = self.extraDataTrail(Tag.SimpleStructType, data);
866 const trailing = self.extra.items[payload.trail..];
867 const member_types = @ptrCast([]const Ref, trailing[0..payload.data.members_len]);
868 const member_names = @ptrCast([]const String, trailing[payload.data.members_len..][0..payload.data.members_len]);
869 return .{
870 .struct_type = .{
871 .name = payload.data.name,
872 .member_types = member_types,
873 .member_names = member_names,
874 },
875 };
876 },
877 .float16 => .{ .float = .{
878 .ty = self.get(.{ .float_type = .{ .bits = 16 } }),
879 .value = .{ .float16 = @bitCast(f16, @intCast(u16, data)) },
880 } },
881 .float32 => .{ .float = .{
882 .ty = self.get(.{ .float_type = .{ .bits = 32 } }),
883 .value = .{ .float32 = @bitCast(f32, data) },
884 } },
885 .float64 => .{ .float = .{
886 .ty = self.get(.{ .float_type = .{ .bits = 64 } }),
887 .value = .{ .float64 = self.extraData(Tag.Float64, data).decode() },
888 } },
889 .uint8 => .{ .int = .{
890 .ty = self.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 8 } }),
891 .value = .{ .uint64 = data },
892 } },
893 .uint32 => .{ .int = .{
894 .ty = self.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 32 } }),
895 .value = .{ .uint64 = data },
896 } },
897 .int_small => {
898 const payload = self.extraData(Tag.Int32, data);
899 return .{ .int = .{
900 .ty = payload.ty,
901 .value = .{ .int64 = payload.value },
902 } };
903 },
904 .uint_small => {
905 const payload = self.extraData(Tag.UInt32, data);
906 return .{ .int = .{
907 .ty = payload.ty,
908 .value = .{ .uint64 = payload.value },
909 } };
910 },
911 .int_large => {
912 const payload = self.extraData(Tag.Int64, data);
913 return .{ .int = .{
914 .ty = payload.ty,
915 .value = .{ .int64 = payload.decode() },
916 } };
917 },
918 .uint_large => {
919 const payload = self.extraData(Tag.UInt64, data);
920 return .{ .int = .{
921 .ty = payload.ty,
922 .value = .{ .uint64 = payload.decode() },
923 } };
924 },
925 .undef => .{ .undef = .{
926 .ty = @intToEnum(Ref, data),
927 } },
928 .null => .{ .null = .{
929 .ty = @intToEnum(Ref, data),
930 } },
931 .bool_true => .{ .bool = .{
932 .ty = @intToEnum(Ref, data),
933 .value = true,
934 } },
935 .bool_false => .{ .bool = .{
936 .ty = @intToEnum(Ref, data),
937 .value = false,
938 } },
939 };
940}
941
942/// Look op the result-id that corresponds to a particular
943/// ref.
944pub fn resultId(self: Self, ref: Ref) IdResult {
945 return self.items.items(.result_id)[@enumToInt(ref)];
946}
947
948/// Get the ref for a key that has already been added to the cache.
949fn get(self: *const Self, key: Key) Ref {
950 const adapter: Key.Adapter = .{ .self = self };
951 const index = self.map.getIndexAdapted(key, adapter).?;
952 return @intToEnum(Ref, index);
953}
954
955fn addExtra(self: *Self, spv: *Module, extra: anytype) !u32 {
956 const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
957 try self.extra.ensureUnusedCapacity(spv.gpa, fields.len);
958 return try self.addExtraAssumeCapacity(extra);
959}
960
961fn addExtraAssumeCapacity(self: *Self, extra: anytype) !u32 {
962 const payload_offset = @intCast(u32, self.extra.items.len);
963 inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| {
964 const field_val = @field(extra, field.name);
965 const word = switch (field.type) {
966 u32 => field_val,
967 i32 => @bitCast(u32, field_val),
968 Ref => @enumToInt(field_val),
969 StorageClass => @enumToInt(field_val),
970 String => @enumToInt(field_val),
971 else => @compileError("Invalid type: " ++ @typeName(field.type)),
972 };
973 self.extra.appendAssumeCapacity(word);
974 }
975 return payload_offset;
976}
977
978fn extraData(self: Self, comptime T: type, offset: u32) T {
979 return self.extraDataTrail(T, offset).data;
980}
981
982fn extraDataTrail(self: Self, comptime T: type, offset: u32) struct { data: T, trail: u32 } {
983 var result: T = undefined;
984 const fields = @typeInfo(T).Struct.fields;
985 inline for (fields, 0..) |field, i| {
986 const word = self.extra.items[offset + i];
987 @field(result, field.name) = switch (field.type) {
988 u32 => word,
989 i32 => @bitCast(i32, word),
990 Ref => @intToEnum(Ref, word),
991 StorageClass => @intToEnum(StorageClass, word),
992 String => @intToEnum(String, word),
993 else => @compileError("Invalid type: " ++ @typeName(field.type)),
994 };
995 }
996 return .{
997 .data = result,
998 .trail = offset + @intCast(u32, fields.len),
999 };
1000}
1001
1002/// Represents a reference to some null-terminated string.
1003pub const String = enum(u32) {
1004 none = std.math.maxInt(u32),
1005 _,
1006
1007 pub const Adapter = struct {
1008 self: *const Self,
1009
1010 pub fn eql(ctx: @This(), a: []const u8, _: void, b_index: usize) bool {
1011 const offset = ctx.self.strings.values()[b_index];
1012 const b = std.mem.sliceTo(ctx.self.string_bytes.items[offset..], 0);
1013 return std.mem.eql(u8, a, b);
1014 }
1015
1016 pub fn hash(ctx: @This(), a: []const u8) u32 {
1017 _ = ctx;
1018 var hasher = std.hash.Wyhash.init(0);
1019 hasher.update(a);
1020 return @truncate(u32, hasher.final());
1021 }
1022 };
1023};
1024
1025/// Add a string to the cache. Must not contain any 0 values.
1026pub fn addString(self: *Self, spv: *Module, str: []const u8) !String {
1027 assert(std.mem.indexOfScalar(u8, str, 0) == null);
1028 const adapter = String.Adapter{ .self = self };
1029 const entry = try self.strings.getOrPutAdapted(spv.gpa, str, adapter);
1030 if (!entry.found_existing) {
1031 const offset = self.string_bytes.items.len;
1032 try self.string_bytes.ensureUnusedCapacity(spv.gpa, 1 + str.len);
1033 self.string_bytes.appendSliceAssumeCapacity(str);
1034 self.string_bytes.appendAssumeCapacity(0);
1035 entry.value_ptr.* = @intCast(u32, offset);
1036 }
1037
1038 return @intToEnum(String, entry.index);
1039}
1040
1041pub fn getString(self: *const Self, ref: String) ?[]const u8 {
1042 return switch (ref) {
1043 .none => null,
1044 else => std.mem.sliceTo(self.string_bytes.items[self.strings.values()[@enumToInt(ref)]..], 0),
1045 };
1046}
src/codegen/spirv/Module.zig+100-400
......@@ -20,11 +20,13 @@ const IdResult = spec.IdResult;
2020const IdResultType = spec.IdResultType;
2121
2222const Section = @import("Section.zig");
23const Type = @import("type.zig").Type;
2423
25const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true);
24const Cache = @import("Cache.zig");
25pub const CacheKey = Cache.Key;
26pub const CacheRef = Cache.Ref;
27pub const CacheString = Cache.String;
2628
27/// This structure represents a function that is in-progress of being emitted.
29/// This structure represents a function that isc in-progress of being emitted.
2830/// Commonly, the contents of this structure will be merged with the appropriate
2931/// sections of the module and re-used. Note that the SPIR-V module system makes
3032/// no attempt of compacting result-id's, so any Fn instance should ultimately
......@@ -126,7 +128,13 @@ sections: struct {
126128 /// Annotation instructions (OpDecorate etc).
127129 annotations: Section = .{},
128130 /// Type declarations, constants, global variables
129 /// Below this section, OpLine and OpNoLine is allowed.
131 /// From this section, OpLine and OpNoLine is allowed.
132 /// According to the SPIR-V documentation, this section normally
133 /// also holds type and constant instructions. These are managed
134 /// via the cache instead, which is the sole structure that
135 /// manages that section. These will be inserted between this and
136 /// the previous section when emitting the final binary.
137 /// TODO: Do we need this section? Globals are also managed with another mechanism.
130138 types_globals_constants: Section = .{},
131139 // Functions without a body - skip for now.
132140 /// Regular function definitions.
......@@ -141,11 +149,9 @@ next_result_id: Word,
141149/// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
142150source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
143151
144/// SPIR-V type cache. Note that according to SPIR-V spec section 2.8, Types and Variables, non-pointer
145/// non-aggrerate types (which includes matrices and vectors) must have a _unique_ representation in
146/// the final binary.
147/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).
148type_cache: TypeCache = .{},
152/// SPIR-V type- and constant cache. This structure is used to store information about these in a more
153/// efficient manner.
154cache: Cache = .{},
149155
150156/// Set of Decls, referred to by Decl.Index.
151157decls: std.ArrayListUnmanaged(Decl) = .{},
......@@ -163,7 +169,7 @@ globals: struct {
163169 globals: std.AutoArrayHashMapUnmanaged(Decl.Index, Global) = .{},
164170 /// This pseudo-section contains the initialization code for all the globals. Instructions from
165171 /// here are reordered when flushing the module. Its contents should be part of the
166 /// `types_globals_constants` SPIR-V section.
172 /// `types_globals_constants` SPIR-V section when the module is emitted.
167173 section: Section = .{},
168174} = .{},
169175
......@@ -182,11 +188,10 @@ pub fn deinit(self: *Module) void {
182188 self.sections.debug_strings.deinit(self.gpa);
183189 self.sections.debug_names.deinit(self.gpa);
184190 self.sections.annotations.deinit(self.gpa);
185 self.sections.types_globals_constants.deinit(self.gpa);
186191 self.sections.functions.deinit(self.gpa);
187192
188193 self.source_file_names.deinit(self.gpa);
189 self.type_cache.deinit(self.gpa);
194 self.cache.deinit(self);
190195
191196 self.decls.deinit(self.gpa);
192197 self.decl_deps.deinit(self.gpa);
......@@ -213,6 +218,22 @@ pub fn idBound(self: Module) Word {
213218 return self.next_result_id;
214219}
215220
221pub fn resolve(self: *Module, key: CacheKey) !CacheRef {
222 return self.cache.resolve(self, key);
223}
224
225pub fn resultId(self: *const Module, ref: CacheRef) IdResult {
226 return self.cache.resultId(ref);
227}
228
229pub fn resolveId(self: *Module, key: CacheKey) !IdResult {
230 return self.resultId(try self.resolve(key));
231}
232
233pub fn resolveString(self: *Module, str: []const u8) !CacheString {
234 return try self.cache.addString(self, str);
235}
236
216237fn orderGlobalsInto(
217238 self: *Module,
218239 decl_index: Decl.Index,
......@@ -324,6 +345,9 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
324345 var entry_points = try self.entryPoints();
325346 defer entry_points.deinit(self.gpa);
326347
348 var types_constants = try self.cache.materialize(self);
349 defer types_constants.deinit(self.gpa);
350
327351 // Note: needs to be kept in order according to section 2.3!
328352 const buffers = &[_][]const Word{
329353 &header,
......@@ -334,6 +358,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
334358 self.sections.debug_strings.toWords(),
335359 self.sections.debug_names.toWords(),
336360 self.sections.annotations.toWords(),
361 types_constants.toWords(),
337362 self.sections.types_globals_constants.toWords(),
338363 globals.toWords(),
339364 self.sections.functions.toWords(),
......@@ -386,417 +411,73 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
386411 return result.value_ptr.*;
387412}
388413
389/// Fetch a result-id for a spir-v type. This function deduplicates the type as appropriate,
390/// and returns a cached version if that exists.
391/// Note: This function does not attempt to perform any validation on the type.
392/// The type is emitted in a shallow fashion; any child types should already
393/// be emitted at this point.
394pub fn resolveType(self: *Module, ty: Type) !Type.Ref {
395 const result = try self.type_cache.getOrPut(self.gpa, ty);
396 const index = @intToEnum(Type.Ref, result.index);
397
398 if (!result.found_existing) {
399 const ref = try self.emitType(ty);
400 self.type_cache.values()[result.index] = ref;
401 }
402
403 return index;
404}
405
406pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType {
407 const ty_ref = try self.resolveType(ty);
408 return self.typeId(ty_ref);
414pub fn intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !CacheRef {
415 return try self.resolve(.{ .int_type = .{
416 .signedness = signedness,
417 .bits = bits,
418 } });
409419}
410420
411pub fn typeRefType(self: Module, ty_ref: Type.Ref) Type {
412 return self.type_cache.keys()[@enumToInt(ty_ref)];
421pub fn arrayType(self: *Module, len: u32, elem_ty_ref: CacheRef) !CacheRef {
422 const len_ty_ref = try self.resolve(.{ .int_type = .{
423 .signedness = .unsigned,
424 .bits = 32,
425 } });
426 const len_ref = try self.resolve(.{ .int = .{
427 .ty = len_ty_ref,
428 .value = .{ .uint64 = len },
429 } });
430 return try self.resolve(.{ .array_type = .{
431 .element_type = elem_ty_ref,
432 .length = len_ref,
433 } });
413434}
414435
415/// Get the result-id of a particular type, by reference. Asserts type_ref is valid.
416pub fn typeId(self: Module, ty_ref: Type.Ref) IdResultType {
417 return self.type_cache.values()[@enumToInt(ty_ref)];
436pub fn ptrType(
437 self: *Module,
438 child: CacheRef,
439 storage_class: spec.StorageClass,
440) !CacheRef {
441 return try self.resolve(.{ .ptr_type = .{
442 .storage_class = storage_class,
443 .child_type = child,
444 } });
418445}
419446
420/// Unconditionally emit a spir-v type into the appropriate section.
421/// Note: If this function is called with a type that is already generated, it may yield an invalid module
422/// as non-pointer non-aggregrate types must me unique!
423/// Note: This function does not attempt to perform any validation on the type.
424/// The type is emitted in a shallow fashion; any child types should already
425/// be emitted at this point.
426pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType {
427 const result_id = self.allocId();
428 const ref_id = result_id;
429 const types = &self.sections.types_globals_constants;
430 const debug_names = &self.sections.debug_names;
431 const result_id_operand = .{ .id_result = result_id };
432
433 switch (ty.tag()) {
434 .void => {
435 try types.emit(self.gpa, .OpTypeVoid, result_id_operand);
436 try debug_names.emit(self.gpa, .OpName, .{
437 .target = result_id,
438 .name = "void",
439 });
447pub fn constInt(self: *Module, ty_ref: CacheRef, value: anytype) !IdRef {
448 const ty = self.cache.lookup(ty_ref).int_type;
449 const Value = Cache.Key.Int.Value;
450 return try self.resolveId(.{ .int = .{
451 .ty = ty_ref,
452 .value = switch (ty.signedness) {
453 .signed => Value{ .int64 = @intCast(i64, value) },
454 .unsigned => Value{ .uint64 = @intCast(u64, value) },
440455 },
441 .bool => {
442 try types.emit(self.gpa, .OpTypeBool, result_id_operand);
443 try debug_names.emit(self.gpa, .OpName, .{
444 .target = result_id,
445 .name = "bool",
446 });
447 },
448 .u8,
449 .u16,
450 .u32,
451 .u64,
452 .i8,
453 .i16,
454 .i32,
455 .i64,
456 .int,
457 => {
458 // TODO: Kernels do not support OpTypeInt that is signed. We can probably
459 // can get rid of the signedness all together, in Shaders also.
460 const bits = ty.intFloatBits();
461 const signedness: spec.LiteralInteger = switch (ty.intSignedness()) {
462 .unsigned => 0,
463 .signed => 1,
464 };
465
466 try types.emit(self.gpa, .OpTypeInt, .{
467 .id_result = result_id,
468 .width = bits,
469 .signedness = signedness,
470 });
471
472 const ui: []const u8 = switch (signedness) {
473 0 => "u",
474 1 => "i",
475 else => unreachable,
476 };
477 const name = try std.fmt.allocPrint(self.gpa, "{s}{}", .{ ui, bits });
478 defer self.gpa.free(name);
479
480 try debug_names.emit(self.gpa, .OpName, .{
481 .target = result_id,
482 .name = name,
483 });
484 },
485 .f16, .f32, .f64 => {
486 const bits = ty.intFloatBits();
487 try types.emit(self.gpa, .OpTypeFloat, .{
488 .id_result = result_id,
489 .width = bits,
490 });
491
492 const name = try std.fmt.allocPrint(self.gpa, "f{}", .{bits});
493 defer self.gpa.free(name);
494 try debug_names.emit(self.gpa, .OpName, .{
495 .target = result_id,
496 .name = name,
497 });
498 },
499 .vector => try types.emit(self.gpa, .OpTypeVector, .{
500 .id_result = result_id,
501 .component_type = self.typeId(ty.childType()),
502 .component_count = ty.payload(.vector).component_count,
503 }),
504 .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{
505 .id_result = result_id,
506 .column_type = self.typeId(ty.childType()),
507 .column_count = ty.payload(.matrix).column_count,
508 }),
509 .image => {
510 const info = ty.payload(.image);
511 try types.emit(self.gpa, .OpTypeImage, .{
512 .id_result = result_id,
513 .sampled_type = self.typeId(ty.childType()),
514 .dim = info.dim,
515 .depth = @enumToInt(info.depth),
516 .arrayed = @boolToInt(info.arrayed),
517 .ms = @boolToInt(info.multisampled),
518 .sampled = @enumToInt(info.sampled),
519 .image_format = info.format,
520 .access_qualifier = info.access_qualifier,
521 });
522 },
523 .sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand),
524 .sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{
525 .id_result = result_id,
526 .image_type = self.typeId(ty.childType()),
527 }),
528 .array => {
529 const info = ty.payload(.array);
530 assert(info.length != 0);
531
532 const size_type = Type.initTag(.u32);
533 const size_type_id = try self.resolveTypeId(size_type);
534 const length_id = self.allocId();
535 try self.emitConstant(size_type_id, length_id, .{ .uint32 = info.length });
536
537 try types.emit(self.gpa, .OpTypeArray, .{
538 .id_result = result_id,
539 .element_type = self.typeId(ty.childType()),
540 .length = length_id,
541 });
542 if (info.array_stride != 0) {
543 try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
544 }
545 },
546 .runtime_array => {
547 const info = ty.payload(.runtime_array);
548 try types.emit(self.gpa, .OpTypeRuntimeArray, .{
549 .id_result = result_id,
550 .element_type = self.typeId(ty.childType()),
551 });
552 if (info.array_stride != 0) {
553 try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
554 }
555 },
556 .@"struct" => {
557 const info = ty.payload(.@"struct");
558 try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len);
559 types.writeOperand(IdResult, result_id);
560 for (info.members) |member| {
561 types.writeOperand(IdRef, self.typeId(member.ty));
562 }
563 try self.decorateStruct(ref_id, info);
564 },
565 .@"opaque" => try types.emit(self.gpa, .OpTypeOpaque, .{
566 .id_result = result_id,
567 .literal_string = ty.payload(.@"opaque").name,
568 }),
569 .pointer => {
570 const info = ty.payload(.pointer);
571 try types.emit(self.gpa, .OpTypePointer, .{
572 .id_result = result_id,
573 .storage_class = info.storage_class,
574 .type = self.typeId(ty.childType()),
575 });
576 if (info.array_stride != 0) {
577 try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
578 }
579 if (info.alignment != 0) {
580 try self.decorate(ref_id, .{ .Alignment = .{ .alignment = info.alignment } });
581 }
582 if (info.max_byte_offset) |max_byte_offset| {
583 try self.decorate(ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } });
584 }
585 },
586 .function => {
587 const info = ty.payload(.function);
588 try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len);
589 types.writeOperand(IdResult, result_id);
590 types.writeOperand(IdRef, self.typeId(info.return_type));
591 for (info.parameters) |parameter_type| {
592 types.writeOperand(IdRef, self.typeId(parameter_type));
593 }
594 },
595 .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand),
596 .device_event => try types.emit(self.gpa, .OpTypeDeviceEvent, result_id_operand),
597 .reserve_id => try types.emit(self.gpa, .OpTypeReserveId, result_id_operand),
598 .queue => try types.emit(self.gpa, .OpTypeQueue, result_id_operand),
599 .pipe => try types.emit(self.gpa, .OpTypePipe, .{
600 .id_result = result_id,
601 .qualifier = ty.payload(.pipe).qualifier,
602 }),
603 .pipe_storage => try types.emit(self.gpa, .OpTypePipeStorage, result_id_operand),
604 .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand),
605 }
606
607 return result_id;
456 } });
608457}
609458
610fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void {
611 const debug_names = &self.sections.debug_names;
612
613 if (info.name.len != 0) {
614 try debug_names.emit(self.gpa, .OpName, .{
615 .target = target,
616 .name = info.name,
617 });
618 }
619
620 // Decorations for the struct type itself.
621 if (info.decorations.block)
622 try self.decorate(target, .Block);
623 if (info.decorations.buffer_block)
624 try self.decorate(target, .BufferBlock);
625 if (info.decorations.glsl_shared)
626 try self.decorate(target, .GLSLShared);
627 if (info.decorations.glsl_packed)
628 try self.decorate(target, .GLSLPacked);
629 if (info.decorations.c_packed)
630 try self.decorate(target, .CPacked);
631
632 // Decorations for the struct members.
633 const extra = info.member_decoration_extra;
634 var extra_i: u32 = 0;
635 for (info.members, 0..) |member, i| {
636 const d = member.decorations;
637 const index = @intCast(Word, i);
638
639 if (member.name.len != 0) {
640 try debug_names.emit(self.gpa, .OpMemberName, .{
641 .type = target,
642 .member = index,
643 .name = member.name,
644 });
645 }
646
647 switch (member.offset) {
648 .none => {},
649 else => try self.decorateMember(
650 target,
651 index,
652 .{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } },
653 ),
654 }
655
656 switch (d.matrix_layout) {
657 .row_major => try self.decorateMember(target, index, .RowMajor),
658 .col_major => try self.decorateMember(target, index, .ColMajor),
659 .none => {},
660 }
661 if (d.matrix_layout != .none) {
662 try self.decorateMember(target, index, .{
663 .MatrixStride = .{ .matrix_stride = extra[extra_i] },
664 });
665 extra_i += 1;
666 }
667
668 if (d.no_perspective)
669 try self.decorateMember(target, index, .NoPerspective);
670 if (d.flat)
671 try self.decorateMember(target, index, .Flat);
672 if (d.patch)
673 try self.decorateMember(target, index, .Patch);
674 if (d.centroid)
675 try self.decorateMember(target, index, .Centroid);
676 if (d.sample)
677 try self.decorateMember(target, index, .Sample);
678 if (d.invariant)
679 try self.decorateMember(target, index, .Invariant);
680 if (d.@"volatile")
681 try self.decorateMember(target, index, .Volatile);
682 if (d.coherent)
683 try self.decorateMember(target, index, .Coherent);
684 if (d.non_writable)
685 try self.decorateMember(target, index, .NonWritable);
686 if (d.non_readable)
687 try self.decorateMember(target, index, .NonReadable);
688
689 if (d.builtin) {
690 try self.decorateMember(target, index, .{
691 .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) },
692 });
693 extra_i += 1;
694 }
695 if (d.stream) {
696 try self.decorateMember(target, index, .{
697 .Stream = .{ .stream_number = extra[extra_i] },
698 });
699 extra_i += 1;
700 }
701 if (d.location) {
702 try self.decorateMember(target, index, .{
703 .Location = .{ .location = extra[extra_i] },
704 });
705 extra_i += 1;
706 }
707 if (d.component) {
708 try self.decorateMember(target, index, .{
709 .Component = .{ .component = extra[extra_i] },
710 });
711 extra_i += 1;
712 }
713 if (d.xfb_buffer) {
714 try self.decorateMember(target, index, .{
715 .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] },
716 });
717 extra_i += 1;
718 }
719 if (d.xfb_stride) {
720 try self.decorateMember(target, index, .{
721 .XfbStride = .{ .xfb_stride = extra[extra_i] },
722 });
723 extra_i += 1;
724 }
725 if (d.user_semantic) {
726 const len = extra[extra_i];
727 extra_i += 1;
728 const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len];
729 try self.decorateMember(target, index, .{
730 .UserSemantic = .{ .semantic = semantic },
731 });
732 extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable;
733 }
734 }
735}
736
737pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Member) !Type.Ref {
738 const payload = try self.arena.create(Type.Payload.Struct);
739 payload.* = .{
740 .members = try self.arena.dupe(Type.Payload.Struct.Member, members),
741 .decorations = .{},
742 };
743 return try self.resolveType(Type.initPayload(&payload.base));
744}
745
746pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref {
747 const payload = try self.arena.create(Type.Payload.Array);
748 payload.* = .{
749 .element_type = ty,
750 .length = len,
751 };
752 return try self.resolveType(Type.initPayload(&payload.base));
459pub fn constUndef(self: *Module, ty_ref: CacheRef) !IdRef {
460 return try self.resolveId(.{ .undef = .{ .ty = ty_ref } });
753461}
754462
755pub fn ptrType(
756 self: *Module,
757 child: Type.Ref,
758 storage_class: spec.StorageClass,
759 alignment: u32,
760) !Type.Ref {
761 const ptr_payload = try self.arena.create(Type.Payload.Pointer);
762 ptr_payload.* = .{
763 .storage_class = storage_class,
764 .child_type = child,
765 .alignment = alignment,
766 };
767 return try self.resolveType(Type.initPayload(&ptr_payload.base));
463pub fn constNull(self: *Module, ty_ref: CacheRef) !IdRef {
464 return try self.resolveId(.{ .null = .{ .ty = ty_ref } });
768465}
769466
770pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref {
771 const payload = try self.arena.create(Type.Payload.Pointer);
772 payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*;
773 payload.storage_class = new_storage_class;
774 return try self.resolveType(Type.initPayload(&payload.base));
467pub fn constBool(self: *Module, ty_ref: CacheRef, value: bool) !IdRef {
468 return try self.resolveId(.{ .bool = .{ .ty = ty_ref, .value = value } });
775469}
776470
777pub fn constComposite(self: *Module, ty_ref: Type.Ref, members: []const IdRef) !IdRef {
471pub fn constComposite(self: *Module, ty_ref: CacheRef, members: []const IdRef) !IdRef {
778472 const result_id = self.allocId();
779473 try self.sections.types_globals_constants.emit(self.gpa, .OpSpecConstantComposite, .{
780 .id_result_type = self.typeId(ty_ref),
474 .id_result_type = self.resultId(ty_ref),
781475 .id_result = result_id,
782476 .constituents = members,
783477 });
784478 return result_id;
785479}
786480
787pub fn emitConstant(
788 self: *Module,
789 ty_id: IdRef,
790 result_id: IdRef,
791 value: spec.LiteralContextDependentNumber,
792) !void {
793 try self.sections.types_globals_constants.emit(self.gpa, .OpConstant, .{
794 .id_result_type = ty_id,
795 .id_result = result_id,
796 .value = value,
797 });
798}
799
800481/// Decorate a result-id.
801482pub fn decorate(
802483 self: *Module,
......@@ -883,3 +564,22 @@ pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8
883564 .name = try self.arena.dupe(u8, name),
884565 });
885566}
567
568pub fn debugName(self: *Module, target: IdResult, comptime fmt: []const u8, args: anytype) !void {
569 const name = try std.fmt.allocPrint(self.gpa, fmt, args);
570 defer self.gpa.free(name);
571 try self.sections.debug_names.emit(self.gpa, .OpName, .{
572 .target = target,
573 .name = name,
574 });
575}
576
577pub fn memberDebugName(self: *Module, target: IdResult, member: u32, comptime fmt: []const u8, args: anytype) !void {
578 const name = try std.fmt.allocPrint(self.gpa, fmt, args);
579 defer self.gpa.free(name);
580 try self.sections.debug_names.emit(self.gpa, .OpMemberName, .{
581 .type = target,
582 .member = member,
583 .name = name,
584 });
585}
src/codegen/spirv/type.zig deleted-567
......@@ -1,567 +0,0 @@
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;
6const Signedness = std.builtin.Signedness;
7const Allocator = std.mem.Allocator;
8
9const spec = @import("spec.zig");
10
11pub const Type = extern union {
12 tag_if_small_enough: Tag,
13 ptr_otherwise: *Payload,
14
15 /// A reference to another SPIR-V type.
16 pub const Ref = enum(u32) { _ };
17
18 pub fn initTag(comptime small_tag: Tag) Type {
19 comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
20 return .{ .tag_if_small_enough = small_tag };
21 }
22
23 pub fn initPayload(pl: *Payload) Type {
24 assert(@enumToInt(pl.tag) >= Tag.no_payload_count);
25 return .{ .ptr_otherwise = pl };
26 }
27
28 pub fn int(arena: Allocator, signedness: Signedness, bits: u16) !Type {
29 const bits_and_signedness = switch (signedness) {
30 .signed => -@as(i32, bits),
31 .unsigned => @as(i32, bits),
32 };
33
34 return switch (bits_and_signedness) {
35 8 => initTag(.u8),
36 16 => initTag(.u16),
37 32 => initTag(.u32),
38 64 => initTag(.u64),
39 -8 => initTag(.i8),
40 -16 => initTag(.i16),
41 -32 => initTag(.i32),
42 -64 => initTag(.i64),
43 else => {
44 const int_payload = try arena.create(Payload.Int);
45 int_payload.* = .{
46 .width = bits,
47 .signedness = signedness,
48 };
49 return initPayload(&int_payload.base);
50 },
51 };
52 }
53
54 pub fn float(bits: u16) Type {
55 return switch (bits) {
56 16 => initTag(.f16),
57 32 => initTag(.f32),
58 64 => initTag(.f64),
59 else => unreachable, // Enable more types if required.
60 };
61 }
62
63 pub fn tag(self: Type) Tag {
64 if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) {
65 return self.tag_if_small_enough;
66 } else {
67 return self.ptr_otherwise.tag;
68 }
69 }
70
71 pub fn castTag(self: Type, comptime t: Tag) ?*t.Type() {
72 if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count)
73 return null;
74
75 if (self.ptr_otherwise.tag == t)
76 return self.payload(t);
77
78 return null;
79 }
80
81 /// Access the payload of a type directly.
82 pub fn payload(self: Type, comptime t: Tag) *t.Type() {
83 assert(self.tag() == t);
84 return @fieldParentPtr(t.Type(), "base", self.ptr_otherwise);
85 }
86
87 /// Perform a shallow equality test, comparing two types while assuming that any child types
88 /// are equal only if their references are equal.
89 pub fn eqlShallow(a: Type, b: Type) bool {
90 if (a.tag_if_small_enough == b.tag_if_small_enough)
91 return true;
92
93 const tag_a = a.tag();
94 const tag_b = b.tag();
95 if (tag_a != tag_b)
96 return false;
97
98 inline for (@typeInfo(Tag).Enum.fields) |field| {
99 const t = @field(Tag, field.name);
100 if (t == tag_a) {
101 return eqlPayloads(t, a, b);
102 }
103 }
104
105 unreachable;
106 }
107
108 /// Compare the payload of two compatible tags, given that we already know the tag of both types.
109 fn eqlPayloads(comptime t: Tag, a: Type, b: Type) bool {
110 switch (t) {
111 .void,
112 .bool,
113 .sampler,
114 .event,
115 .device_event,
116 .reserve_id,
117 .queue,
118 .pipe_storage,
119 .named_barrier,
120 .u8,
121 .u16,
122 .u32,
123 .u64,
124 .i8,
125 .i16,
126 .i32,
127 .i64,
128 .f16,
129 .f32,
130 .f64,
131 => return true,
132 .int,
133 .vector,
134 .matrix,
135 .sampled_image,
136 .array,
137 .runtime_array,
138 .@"opaque",
139 .pointer,
140 .pipe,
141 .image,
142 => return std.meta.eql(a.payload(t).*, b.payload(t).*),
143 .@"struct" => {
144 const struct_a = a.payload(.@"struct");
145 const struct_b = b.payload(.@"struct");
146 if (struct_a.members.len != struct_b.members.len)
147 return false;
148 for (struct_a.members, 0..) |mem_a, i| {
149 if (!std.meta.eql(mem_a, struct_b.members[i]))
150 return false;
151 }
152 return true;
153 },
154 .function => {
155 const fn_a = a.payload(.function);
156 const fn_b = b.payload(.function);
157 if (fn_a.return_type != fn_b.return_type)
158 return false;
159 return std.mem.eql(Ref, fn_a.parameters, fn_b.parameters);
160 },
161 }
162 }
163
164 /// Perform a shallow hash, which hashes the reference value of child types instead of recursing.
165 pub fn hashShallow(self: Type) u64 {
166 var hasher = std.hash.Wyhash.init(0);
167 const t = self.tag();
168 std.hash.autoHash(&hasher, t);
169
170 inline for (@typeInfo(Tag).Enum.fields) |field| {
171 if (@field(Tag, field.name) == t) {
172 switch (@field(Tag, field.name)) {
173 .void,
174 .bool,
175 .sampler,
176 .event,
177 .device_event,
178 .reserve_id,
179 .queue,
180 .pipe_storage,
181 .named_barrier,
182 .u8,
183 .u16,
184 .u32,
185 .u64,
186 .i8,
187 .i16,
188 .i32,
189 .i64,
190 .f16,
191 .f32,
192 .f64,
193 => {},
194 else => self.hashPayload(@field(Tag, field.name), &hasher),
195 }
196 }
197 }
198
199 return hasher.final();
200 }
201
202 /// Perform a shallow hash, given that we know the tag of the field ahead of time.
203 fn hashPayload(self: Type, comptime t: Tag, hasher: *std.hash.Wyhash) void {
204 const fields = @typeInfo(t.Type()).Struct.fields;
205 const pl = self.payload(t);
206 comptime assert(std.mem.eql(u8, fields[0].name, "base"));
207 inline for (fields[1..]) |field| { // Skip the 'base' field.
208 std.hash.autoHashStrat(hasher, @field(pl, field.name), .DeepRecursive);
209 }
210 }
211
212 /// Hash context that hashes and compares types in a shallow fashion, useful for type caches.
213 pub const ShallowHashContext32 = struct {
214 pub fn hash(self: @This(), t: Type) u32 {
215 _ = self;
216 return @truncate(u32, t.hashShallow());
217 }
218 pub fn eql(self: @This(), a: Type, b: Type, b_index: usize) bool {
219 _ = self;
220 _ = b_index;
221 return a.eqlShallow(b);
222 }
223 };
224
225 /// Return the reference to any child type. Asserts the type is one of:
226 /// - Vectors
227 /// - Matrices
228 /// - Images
229 /// - SampledImages,
230 /// - Arrays
231 /// - RuntimeArrays
232 /// - Pointers
233 pub fn childType(self: Type) Ref {
234 return switch (self.tag()) {
235 .vector => self.payload(.vector).component_type,
236 .matrix => self.payload(.matrix).column_type,
237 .image => self.payload(.image).sampled_type,
238 .sampled_image => self.payload(.sampled_image).image_type,
239 .array => self.payload(.array).element_type,
240 .runtime_array => self.payload(.runtime_array).element_type,
241 .pointer => self.payload(.pointer).child_type,
242 else => unreachable,
243 };
244 }
245
246 pub fn isInt(self: Type) bool {
247 return switch (self.tag()) {
248 .u8,
249 .u16,
250 .u32,
251 .u64,
252 .i8,
253 .i16,
254 .i32,
255 .i64,
256 .int,
257 => true,
258 else => false,
259 };
260 }
261
262 pub fn isFloat(self: Type) bool {
263 return switch (self.tag()) {
264 .f16, .f32, .f64 => true,
265 else => false,
266 };
267 }
268
269 /// Returns the number of bits that make up an int or float type.
270 /// Asserts type is either int or float.
271 pub fn intFloatBits(self: Type) u16 {
272 return switch (self.tag()) {
273 .u8, .i8 => 8,
274 .u16, .i16, .f16 => 16,
275 .u32, .i32, .f32 => 32,
276 .u64, .i64, .f64 => 64,
277 .int => self.payload(.int).width,
278 else => unreachable,
279 };
280 }
281
282 /// Returns the signedness of an integer type.
283 /// Asserts that the type is an int.
284 pub fn intSignedness(self: Type) Signedness {
285 return switch (self.tag()) {
286 .u8, .u16, .u32, .u64 => .unsigned,
287 .i8, .i16, .i32, .i64 => .signed,
288 .int => self.payload(.int).signedness,
289 else => unreachable,
290 };
291 }
292
293 pub const Tag = enum(usize) {
294 void,
295 bool,
296 sampler,
297 event,
298 device_event,
299 reserve_id,
300 queue,
301 pipe_storage,
302 named_barrier,
303 u8,
304 u16,
305 u32,
306 u64,
307 i8,
308 i16,
309 i32,
310 i64,
311 f16,
312 f32,
313 f64,
314
315 // After this, the tag requires a payload.
316 int,
317 vector,
318 matrix,
319 image,
320 sampled_image,
321 array,
322 runtime_array,
323 @"struct",
324 @"opaque",
325 pointer,
326 function,
327 pipe,
328
329 pub const last_no_payload_tag = Tag.f64;
330 pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
331
332 pub fn Type(comptime t: Tag) type {
333 return switch (t) {
334 .void,
335 .bool,
336 .sampler,
337 .event,
338 .device_event,
339 .reserve_id,
340 .queue,
341 .pipe_storage,
342 .named_barrier,
343 .u8,
344 .u16,
345 .u32,
346 .u64,
347 .i8,
348 .i16,
349 .i32,
350 .i64,
351 .f16,
352 .f32,
353 .f64,
354 => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
355 .int => Payload.Int,
356 .vector => Payload.Vector,
357 .matrix => Payload.Matrix,
358 .image => Payload.Image,
359 .sampled_image => Payload.SampledImage,
360 .array => Payload.Array,
361 .runtime_array => Payload.RuntimeArray,
362 .@"struct" => Payload.Struct,
363 .@"opaque" => Payload.Opaque,
364 .pointer => Payload.Pointer,
365 .function => Payload.Function,
366 .pipe => Payload.Pipe,
367 };
368 }
369 };
370
371 pub const Payload = struct {
372 tag: Tag,
373
374 pub const Int = struct {
375 base: Payload = .{ .tag = .int },
376 width: u16,
377 signedness: Signedness,
378 };
379
380 pub const Vector = struct {
381 base: Payload = .{ .tag = .vector },
382 component_type: Ref,
383 component_count: u32,
384 };
385
386 pub const Matrix = struct {
387 base: Payload = .{ .tag = .matrix },
388 column_type: Ref,
389 column_count: u32,
390 };
391
392 pub const Image = struct {
393 base: Payload = .{ .tag = .image },
394 sampled_type: Ref,
395 dim: spec.Dim,
396 depth: enum(u2) {
397 no = 0,
398 yes = 1,
399 maybe = 2,
400 },
401 arrayed: bool,
402 multisampled: bool,
403 sampled: enum(u2) {
404 known_at_runtime = 0,
405 with_sampler = 1,
406 without_sampler = 2,
407 },
408 format: spec.ImageFormat,
409 access_qualifier: ?spec.AccessQualifier,
410 };
411
412 pub const SampledImage = struct {
413 base: Payload = .{ .tag = .sampled_image },
414 image_type: Ref,
415 };
416
417 pub const Array = struct {
418 base: Payload = .{ .tag = .array },
419 element_type: Ref,
420 /// Note: Must be emitted as constant, not as literal!
421 length: u32,
422 /// Type has the 'ArrayStride' decoration.
423 /// If zero, no stride is present.
424 array_stride: u32 = 0,
425 };
426
427 pub const RuntimeArray = struct {
428 base: Payload = .{ .tag = .runtime_array },
429 element_type: Ref,
430 /// Type has the 'ArrayStride' decoration.
431 /// If zero, no stride is present.
432 array_stride: u32 = 0,
433 };
434
435 pub const Struct = struct {
436 base: Payload = .{ .tag = .@"struct" },
437 members: []Member,
438 name: []const u8 = "",
439 decorations: StructDecorations = .{},
440
441 /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by
442 /// order of the `members` slice and `MemberDecorations` struct.
443 member_decoration_extra: []u32 = &.{},
444
445 pub const Member = struct {
446 ty: Ref,
447 name: []const u8 = "",
448 offset: MemberOffset = .none,
449 decorations: MemberDecorations = .{},
450 };
451
452 pub const MemberOffset = enum(u32) { none = 0xFFFF_FFFF, _ };
453
454 pub const StructDecorations = packed struct {
455 /// Type has the 'Block' decoration.
456 block: bool = false,
457 /// Type has the 'BufferBlock' decoration.
458 buffer_block: bool = false,
459 /// Type has the 'GLSLShared' decoration.
460 glsl_shared: bool = false,
461 /// Type has the 'GLSLPacked' decoration.
462 glsl_packed: bool = false,
463 /// Type has the 'CPacked' decoration.
464 c_packed: bool = false,
465 };
466
467 pub const MemberDecorations = packed struct {
468 /// Matrix layout for (arrays of) matrices. If this field is not .none,
469 /// then there is also an extra field containing the matrix stride corresponding
470 /// to the 'MatrixStride' decoration.
471 matrix_layout: enum(u2) {
472 /// Member has the 'RowMajor' decoration. The member type
473 /// must be a matrix or an array of matrices.
474 row_major,
475 /// Member has the 'ColMajor' decoration. The member type
476 /// must be a matrix or an array of matrices.
477 col_major,
478 /// Member is not a matrix or array of matrices.
479 none,
480 } = .none,
481
482 // Regular decorations, these do not imply extra fields.
483
484 /// Member has the 'NoPerspective' decoration.
485 no_perspective: bool = false,
486 /// Member has the 'Flat' decoration.
487 flat: bool = false,
488 /// Member has the 'Patch' decoration.
489 patch: bool = false,
490 /// Member has the 'Centroid' decoration.
491 centroid: bool = false,
492 /// Member has the 'Sample' decoration.
493 sample: bool = false,
494 /// Member has the 'Invariant' decoration.
495 /// Note: requires parent struct to have 'Block'.
496 invariant: bool = false,
497 /// Member has the 'Volatile' decoration.
498 @"volatile": bool = false,
499 /// Member has the 'Coherent' decoration.
500 coherent: bool = false,
501 /// Member has the 'NonWritable' decoration.
502 non_writable: bool = false,
503 /// Member has the 'NonReadable' decoration.
504 non_readable: bool = false,
505
506 // The following decorations all imply extra field(s).
507
508 /// Member has the 'BuiltIn' decoration.
509 /// This decoration has an extra field of type `spec.BuiltIn`.
510 /// Note: If any member of a struct has the BuiltIn decoration, all members must have one.
511 /// Note: Each builtin may only be reachable once for a particular entry point.
512 /// Note: The member type may be constrained by a particular built-in, defined in the client API specification.
513 builtin: bool = false,
514 /// Member has the 'Stream' decoration.
515 /// This member has an extra field of type `u32`.
516 stream: bool = false,
517 /// Member has the 'Location' decoration.
518 /// This member has an extra field of type `u32`.
519 location: bool = false,
520 /// Member has the 'Component' decoration.
521 /// This member has an extra field of type `u32`.
522 component: bool = false,
523 /// Member has the 'XfbBuffer' decoration.
524 /// This member has an extra field of type `u32`.
525 xfb_buffer: bool = false,
526 /// Member has the 'XfbStride' decoration.
527 /// This member has an extra field of type `u32`.
528 xfb_stride: bool = false,
529 /// Member has the 'UserSemantic' decoration.
530 /// This member has an extra field of type `[]u8`, which is encoded
531 /// by an `u32` containing the number of chars exactly, and then the string padded to
532 /// a multiple of 4 bytes with zeroes.
533 user_semantic: bool = false,
534 };
535 };
536
537 pub const Opaque = struct {
538 base: Payload = .{ .tag = .@"opaque" },
539 name: []u8,
540 };
541
542 pub const Pointer = struct {
543 base: Payload = .{ .tag = .pointer },
544 storage_class: spec.StorageClass,
545 child_type: Ref,
546 /// Type has the 'ArrayStride' decoration.
547 /// This is valid for pointers to elements of an array.
548 /// If zero, no stride is present.
549 array_stride: u32 = 0,
550 /// If nonzero, type has the 'Alignment' decoration.
551 alignment: u32 = 0,
552 /// Type has the 'MaxByteOffset' decoration.
553 max_byte_offset: ?u32 = null,
554 };
555
556 pub const Function = struct {
557 base: Payload = .{ .tag = .function },
558 return_type: Ref,
559 parameters: []Ref,
560 };
561
562 pub const Pipe = struct {
563 base: Payload = .{ .tag = .pipe },
564 qualifier: spec.AccessQualifier,
565 };
566 };
567};