| author | |
| committer | |
| log | 0552a8b11f973fc9621971e2130c25ad3a4af0ad |
| tree | 5d9fd2ed5cdc4a8a00b1047dfb0721145d752a2c |
| parent | fcb422585c1a9e91933ff998417eb8682a4ffbcc |
| signature |
5 files changed, 295 insertions(+), 1556 deletions(-)
src/codegen/spirv.zig+122-459| ... | ... | @@ -22,11 +22,10 @@ const IdResultType = spec.IdResultType; |
| 22 | 22 | const StorageClass = spec.StorageClass; |
| 23 | 23 | |
| 24 | 24 | const SpvModule = @import("spirv/Module.zig"); |
| 25 | const SpvCacheRef = SpvModule.TypeConstantCache.Ref; | |
| 26 | const SpvCacheString = SpvModule.TypeConstantCache.String; | |
| 25 | const CacheRef = SpvModule.CacheRef; | |
| 26 | const CacheString = SpvModule.CacheString; | |
| 27 | 27 | |
| 28 | 28 | const SpvSection = @import("spirv/Section.zig"); |
| 29 | const SpvType = @import("spirv/type.zig").Type; | |
| 30 | 29 | const SpvAssembler = @import("spirv/Assembler.zig"); |
| 31 | 30 | |
| 32 | 31 | const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef); |
| ... | ... | @@ -380,74 +379,23 @@ pub const DeclGen = struct { |
| 380 | 379 | }; |
| 381 | 380 | } |
| 382 | 381 | |
| 383 | fn genConstInt(self: *DeclGen, ty_ref: SpvType.Ref, result_id: IdRef, value: anytype) !void { | |
| 384 | const ty = self.spv.typeRefType(ty_ref); | |
| 385 | const ty_id = self.typeId(ty_ref); | |
| 386 | ||
| 387 | const Lit = spec.LiteralContextDependentNumber; | |
| 388 | const literal = switch (ty.intSignedness()) { | |
| 389 | .signed => switch (ty.intFloatBits()) { | |
| 390 | 1...32 => Lit{ .int32 = @intCast(i32, value) }, | |
| 391 | 33...64 => Lit{ .int64 = @intCast(i64, value) }, | |
| 392 | else => unreachable, // TODO: composite integer literals | |
| 393 | }, | |
| 394 | .unsigned => switch (ty.intFloatBits()) { | |
| 395 | 1...32 => Lit{ .uint32 = @intCast(u32, value) }, | |
| 396 | 33...64 => Lit{ .uint64 = @intCast(u64, value) }, | |
| 397 | else => unreachable, | |
| 398 | }, | |
| 399 | }; | |
| 400 | ||
| 401 | try self.spv.emitConstant(ty_id, result_id, literal); | |
| 402 | } | |
| 403 | ||
| 404 | fn constInt(self: *DeclGen, ty_ref: SpvType.Ref, value: anytype) !IdRef { | |
| 405 | const result_id = self.spv.allocId(); | |
| 406 | try self.genConstInt(ty_ref, result_id, value); | |
| 407 | return result_id; | |
| 408 | } | |
| 409 | ||
| 410 | fn constUndef(self: *DeclGen, ty_ref: SpvType.Ref) !IdRef { | |
| 411 | const result_id = self.spv.allocId(); | |
| 412 | try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpUndef, .{ | |
| 413 | .id_result_type = self.typeId(ty_ref), | |
| 414 | .id_result = result_id, | |
| 415 | }); | |
| 416 | return result_id; | |
| 417 | } | |
| 418 | ||
| 419 | fn constNull(self: *DeclGen, ty_ref: SpvType.Ref) !IdRef { | |
| 420 | const result_id = self.spv.allocId(); | |
| 421 | try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantNull, .{ | |
| 422 | .id_result_type = self.typeId(ty_ref), | |
| 423 | .id_result = result_id, | |
| 424 | }); | |
| 425 | return result_id; | |
| 426 | } | |
| 427 | ||
| 382 | /// Emits a bool constant in a particular representation. | |
| 428 | 383 | fn constBool(self: *DeclGen, value: bool, repr: Repr) !IdRef { |
| 429 | 384 | switch (repr) { |
| 430 | 385 | .indirect => { |
| 431 | 386 | const int_ty_ref = try self.intType(.unsigned, 1); |
| 432 | return self.constInt(int_ty_ref, @boolToInt(value)); | |
| 387 | return self.spv.constInt(int_ty_ref, @boolToInt(value)); | |
| 433 | 388 | }, |
| 434 | 389 | .direct => { |
| 435 | 390 | const bool_ty_ref = try self.resolveType(Type.bool, .direct); |
| 436 | const result_id = self.spv.allocId(); | |
| 437 | const operands = .{ .id_result_type = self.typeId(bool_ty_ref), .id_result = result_id }; | |
| 438 | if (value) { | |
| 439 | try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantTrue, operands); | |
| 440 | } else { | |
| 441 | try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpConstantFalse, operands); | |
| 442 | } | |
| 443 | return result_id; | |
| 391 | return self.spv.constBool(bool_ty_ref, value); | |
| 444 | 392 | }, |
| 445 | 393 | } |
| 446 | 394 | } |
| 447 | 395 | |
| 448 | 396 | /// Construct a struct at runtime. |
| 449 | 397 | /// result_ty_ref must be a struct type. |
| 450 | 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 { | |
| 451 | 399 | // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which' |
| 452 | 400 | // operands are not constant. |
| 453 | 401 | // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349 |
| ... | ... | @@ -456,11 +404,13 @@ pub const DeclGen = struct { |
| 456 | 404 | const ptr_composite_id = try self.alloc(result_ty_ref, null); |
| 457 | 405 | // Note: using 32-bit ints here because usize crashes the translator as well |
| 458 | 406 | const index_ty_ref = try self.intType(.unsigned, 32); |
| 459 | const spv_composite_ty = self.spv.typeRefType(result_ty_ref); | |
| 460 | const members = spv_composite_ty.payload(.@"struct").members; | |
| 461 | for (constituents, members, 0..) |constitent_id, member, index| { | |
| 462 | const index_id = try self.constInt(index_ty_ref, index); | |
| 463 | 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); | |
| 464 | 414 | const ptr_id = try self.accessChain(ptr_member_ty_ref, ptr_composite_id, &.{index_id}); |
| 465 | 415 | try self.func.body.emit(self.spv.gpa, .OpStore, .{ |
| 466 | 416 | .pointer = ptr_id, |
| ... | ... | @@ -481,11 +431,11 @@ pub const DeclGen = struct { |
| 481 | 431 | |
| 482 | 432 | dg: *DeclGen, |
| 483 | 433 | /// Cached reference of the u32 type. |
| 484 | u32_ty_ref: SpvType.Ref, | |
| 434 | u32_ty_ref: CacheRef, | |
| 485 | 435 | /// Cached type id of the u32 type. |
| 486 | 436 | u32_ty_id: IdRef, |
| 487 | 437 | /// The members of the resulting structure type |
| 488 | members: std.ArrayList(SpvType.Payload.Struct.Member), | |
| 438 | members: std.ArrayList(CacheRef), | |
| 489 | 439 | /// The initializers of each of the members. |
| 490 | 440 | initializers: std.ArrayList(IdRef), |
| 491 | 441 | /// The current size of the structure. Includes |
| ... | ... | @@ -519,7 +469,7 @@ pub const DeclGen = struct { |
| 519 | 469 | const result_id = self.dg.spv.allocId(); |
| 520 | 470 | // TODO: Integrate with caching mechanism |
| 521 | 471 | try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word }); |
| 522 | try self.members.append(.{ .ty = self.u32_ty_ref }); | |
| 472 | try self.members.append(self.u32_ty_ref); | |
| 523 | 473 | try self.initializers.append(result_id); |
| 524 | 474 | |
| 525 | 475 | self.partial_word.len = 0; |
| ... | ... | @@ -555,7 +505,7 @@ pub const DeclGen = struct { |
| 555 | 505 | } |
| 556 | 506 | } |
| 557 | 507 | |
| 558 | fn addPtr(self: *@This(), ptr_ty_ref: SpvType.Ref, ptr_id: IdRef) !void { | |
| 508 | fn addPtr(self: *@This(), ptr_ty_ref: CacheRef, ptr_id: IdRef) !void { | |
| 559 | 509 | // TODO: Double check pointer sizes here. |
| 560 | 510 | // shared pointers might be u32... |
| 561 | 511 | const target = self.dg.getTarget(); |
| ... | ... | @@ -563,12 +513,12 @@ pub const DeclGen = struct { |
| 563 | 513 | if (self.size % width != 0) { |
| 564 | 514 | return self.dg.todo("misaligned pointer constants", .{}); |
| 565 | 515 | } |
| 566 | try self.members.append(.{ .ty = ptr_ty_ref }); | |
| 516 | try self.members.append(ptr_ty_ref); | |
| 567 | 517 | try self.initializers.append(ptr_id); |
| 568 | 518 | self.size += width; |
| 569 | 519 | } |
| 570 | 520 | |
| 571 | fn addNullPtr(self: *@This(), ptr_ty_ref: SpvType.Ref) !void { | |
| 521 | fn addNullPtr(self: *@This(), ptr_ty_ref: CacheRef) !void { | |
| 572 | 522 | const result_id = self.dg.spv.allocId(); |
| 573 | 523 | try self.dg.spv.sections.types_globals_constants.emit(self.dg.spv.gpa, .OpConstantNull, .{ |
| 574 | 524 | .id_result_type = self.dg.typeId(ptr_ty_ref), |
| ... | ... | @@ -931,7 +881,7 @@ pub const DeclGen = struct { |
| 931 | 881 | const section = &self.spv.globals.section; |
| 932 | 882 | |
| 933 | 883 | const ty_ref = try self.resolveType(ty, .indirect); |
| 934 | const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, 0); | |
| 884 | const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class); | |
| 935 | 885 | |
| 936 | 886 | // const target = self.getTarget(); |
| 937 | 887 | |
| ... | ... | @@ -960,7 +910,7 @@ pub const DeclGen = struct { |
| 960 | 910 | .dg = self, |
| 961 | 911 | .u32_ty_ref = u32_ty_ref, |
| 962 | 912 | .u32_ty_id = self.typeId(u32_ty_ref), |
| 963 | .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa), | |
| 913 | .members = std.ArrayList(CacheRef).init(self.gpa), | |
| 964 | 914 | .initializers = std.ArrayList(IdRef).init(self.gpa), |
| 965 | 915 | .decl_deps = std.AutoArrayHashMap(SpvModule.Decl.Index, void).init(self.gpa), |
| 966 | 916 | }; |
| ... | ... | @@ -972,8 +922,10 @@ pub const DeclGen = struct { |
| 972 | 922 | try icl.lower(ty, val); |
| 973 | 923 | try icl.flush(); |
| 974 | 924 | |
| 975 | const constant_struct_ty_ref = try self.spv.simpleStructType(icl.members.items); | |
| 976 | const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, 0); | |
| 925 | const constant_struct_ty_ref = try self.spv.resolve(.{ .struct_type = .{ | |
| 926 | .member_types = icl.members.items, | |
| 927 | } }); | |
| 928 | const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class); | |
| 977 | 929 | |
| 978 | 930 | const constant_struct_id = self.spv.allocId(); |
| 979 | 931 | try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ |
| ... | ... | @@ -1007,7 +959,7 @@ pub const DeclGen = struct { |
| 1007 | 959 | }); |
| 1008 | 960 | |
| 1009 | 961 | if (cast_to_generic) { |
| 1010 | const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0); | |
| 962 | const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic); | |
| 1011 | 963 | try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ |
| 1012 | 964 | .id_result_type = self.typeId(generic_ptr_ty_ref), |
| 1013 | 965 | .id_result = result_id, |
| ... | ... | @@ -1044,9 +996,9 @@ pub const DeclGen = struct { |
| 1044 | 996 | switch (ty.zigTypeTag()) { |
| 1045 | 997 | .Int => { |
| 1046 | 998 | if (ty.isSignedInt()) { |
| 1047 | return try self.constInt(result_ty_ref, val.toSignedInt(target)); | |
| 999 | return try self.spv.constInt(result_ty_ref, val.toSignedInt(target)); | |
| 1048 | 1000 | } else { |
| 1049 | return try self.constInt(result_ty_ref, val.toUnsignedInt(target)); | |
| 1001 | return try self.spv.constInt(result_ty_ref, val.toUnsignedInt(target)); | |
| 1050 | 1002 | } |
| 1051 | 1003 | }, |
| 1052 | 1004 | .Bool => switch (repr) { |
| ... | ... | @@ -1060,7 +1012,7 @@ pub const DeclGen = struct { |
| 1060 | 1012 | } |
| 1061 | 1013 | return result_id; |
| 1062 | 1014 | }, |
| 1063 | .indirect => return try self.constInt(result_ty_ref, @boolToInt(val.toBool())), | |
| 1015 | .indirect => return try self.spv.constInt(result_ty_ref, @boolToInt(val.toBool())), | |
| 1064 | 1016 | }, |
| 1065 | 1017 | .Float => { |
| 1066 | 1018 | const result_id = self.spv.allocId(); |
| ... | ... | @@ -1084,7 +1036,7 @@ pub const DeclGen = struct { |
| 1084 | 1036 | else => unreachable, |
| 1085 | 1037 | }; |
| 1086 | 1038 | |
| 1087 | return try self.constInt(result_ty_ref, value); | |
| 1039 | return try self.spv.constInt(result_ty_ref, value); | |
| 1088 | 1040 | }, |
| 1089 | 1041 | .ErrorUnion => { |
| 1090 | 1042 | const payload_ty = ty.errorUnionPayload(); |
| ... | ... | @@ -1143,45 +1095,31 @@ pub const DeclGen = struct { |
| 1143 | 1095 | /// Turn a Zig type into a SPIR-V Type, and return its type result-id. |
| 1144 | 1096 | fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType { |
| 1145 | 1097 | const type_ref = try self.resolveType(ty, .direct); |
| 1146 | return self.typeId(type_ref); | |
| 1098 | return self.spv.resultId(type_ref); | |
| 1147 | 1099 | } |
| 1148 | 1100 | |
| 1149 | fn typeId(self: *DeclGen, ty_ref: SpvType.Ref) IdRef { | |
| 1150 | return self.spv.typeId(ty_ref); | |
| 1101 | fn typeId(self: *DeclGen, ty_ref: CacheRef) IdRef { | |
| 1102 | return self.spv.resultId(ty_ref); | |
| 1151 | 1103 | } |
| 1152 | 1104 | |
| 1153 | 1105 | /// Create an integer type suitable for storing at least 'bits' bits. |
| 1154 | fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvType.Ref { | |
| 1106 | /// The integer type that is returned by this function is the type that is used to perform | |
| 1107 | /// actual operations (as well as store) a Zig type of a particular number of bits. To create | |
| 1108 | /// a type with an exact size, use SpvModule.intType. | |
| 1109 | fn intType(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !CacheRef { | |
| 1155 | 1110 | const backing_bits = self.backingIntBits(bits) orelse { |
| 1156 | 1111 | // TODO: Integers too big for any native type are represented as "composite integers": |
| 1157 | 1112 | // An array of largestSupportedIntBits. |
| 1158 | 1113 | return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits }); |
| 1159 | 1114 | }; |
| 1160 | ||
| 1161 | return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits)); | |
| 1162 | } | |
| 1163 | ||
| 1164 | fn intType2(self: *DeclGen, signedness: std.builtin.Signedness, bits: u16) !SpvCacheRef { | |
| 1165 | const backing_bits = self.backingIntBits(bits) orelse { | |
| 1166 | // TODO: Integers too big for any native type are represented as "composite integers": | |
| 1167 | // An array of largestSupportedIntBits. | |
| 1168 | return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits }); | |
| 1169 | }; | |
| 1170 | return try self.spv.resolve(.{ .int_type = .{ | |
| 1171 | .signedness = signedness, | |
| 1172 | .bits = backing_bits, | |
| 1173 | } }); | |
| 1115 | return self.spv.intType(signedness, backing_bits); | |
| 1174 | 1116 | } |
| 1175 | 1117 | |
| 1176 | 1118 | /// Create an integer type that represents 'usize'. |
| 1177 | fn sizeType(self: *DeclGen) !SpvType.Ref { | |
| 1119 | fn sizeType(self: *DeclGen) !CacheRef { | |
| 1178 | 1120 | return try self.intType(.unsigned, self.getTarget().ptrBitWidth()); |
| 1179 | 1121 | } |
| 1180 | 1122 | |
| 1181 | fn sizeType2(self: *DeclGen) !SpvCacheRef { | |
| 1182 | return try self.intType2(.unsigned, self.getTarget().ptrBitWidth()); | |
| 1183 | } | |
| 1184 | ||
| 1185 | 1123 | /// Generate a union type, optionally with a known field. If the tag alignment is greater |
| 1186 | 1124 | /// than that of the payload, a regular union (non-packed, with both tag and payload), will |
| 1187 | 1125 | /// be generated as follows: |
| ... | ... | @@ -1204,7 +1142,7 @@ pub const DeclGen = struct { |
| 1204 | 1142 | /// If any of the fields' size is 0, it will be omitted. |
| 1205 | 1143 | /// NOTE: When the active field is set to something other than the most aligned field, the |
| 1206 | 1144 | /// resulting struct will be *underaligned*. |
| 1207 | fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvType.Ref { | |
| 1145 | fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !CacheRef { | |
| 1208 | 1146 | const target = self.getTarget(); |
| 1209 | 1147 | const layout = ty.unionGetLayout(target); |
| 1210 | 1148 | const union_ty = ty.cast(Type.Payload.Union).?.data; |
| ... | ... | @@ -1218,7 +1156,8 @@ pub const DeclGen = struct { |
| 1218 | 1156 | return try self.resolveType(union_ty.tag_ty, .indirect); |
| 1219 | 1157 | } |
| 1220 | 1158 | |
| 1221 | var members = std.BoundedArray(SpvType.Payload.Struct.Member, 4){}; | |
| 1159 | var member_types = std.BoundedArray(CacheRef, 4){}; | |
| 1160 | var member_names = std.BoundedArray(CacheString, 4){}; | |
| 1222 | 1161 | |
| 1223 | 1162 | const has_tag = layout.tag_size != 0; |
| 1224 | 1163 | const tag_first = layout.tag_align >= layout.payload_align; |
| ... | ... | @@ -1226,82 +1165,6 @@ pub const DeclGen = struct { |
| 1226 | 1165 | |
| 1227 | 1166 | if (has_tag and tag_first) { |
| 1228 | 1167 | const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect); |
| 1229 | members.appendAssumeCapacity(.{ .name = "tag", .ty = tag_ty_ref }); | |
| 1230 | } | |
| 1231 | ||
| 1232 | const active_field = maybe_active_field orelse layout.most_aligned_field; | |
| 1233 | const active_field_ty = union_ty.fields.values()[active_field].ty; | |
| 1234 | ||
| 1235 | const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: { | |
| 1236 | const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect); | |
| 1237 | members.appendAssumeCapacity(.{ .name = "payload", .ty = active_payload_ty_ref }); | |
| 1238 | break :blk active_field_ty.abiSize(target); | |
| 1239 | } else 0; | |
| 1240 | ||
| 1241 | const payload_padding_len = layout.payload_size - active_field_size; | |
| 1242 | if (payload_padding_len != 0) { | |
| 1243 | const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref); | |
| 1244 | members.appendAssumeCapacity(.{ .name = "padding_payload", .ty = payload_padding_ty_ref }); | |
| 1245 | } | |
| 1246 | ||
| 1247 | if (has_tag and !tag_first) { | |
| 1248 | const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect); | |
| 1249 | members.appendAssumeCapacity(.{ .name = "tag", .ty = tag_ty_ref }); | |
| 1250 | } | |
| 1251 | ||
| 1252 | if (layout.padding != 0) { | |
| 1253 | const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref); | |
| 1254 | members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref }); | |
| 1255 | } | |
| 1256 | ||
| 1257 | return try self.spv.simpleStructType(members.slice()); | |
| 1258 | } | |
| 1259 | ||
| 1260 | /// Generate a union type, optionally with a known field. If the tag alignment is greater | |
| 1261 | /// than that of the payload, a regular union (non-packed, with both tag and payload), will | |
| 1262 | /// be generated as follows: | |
| 1263 | /// If the active field is known: | |
| 1264 | /// struct { | |
| 1265 | /// tag: TagType, | |
| 1266 | /// payload: ActivePayloadType, | |
| 1267 | /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8, | |
| 1268 | /// padding: [padding_size]u8, | |
| 1269 | /// } | |
| 1270 | /// If the payload alignment is greater than that of the tag: | |
| 1271 | /// struct { | |
| 1272 | /// payload: ActivePayloadType, | |
| 1273 | /// payload_padding: [payload_size - @sizeOf(ActivePayloadType)]u8, | |
| 1274 | /// tag: TagType, | |
| 1275 | /// padding: [padding_size]u8, | |
| 1276 | /// } | |
| 1277 | /// If the active payload is unknown, it will default back to the most aligned field. This is | |
| 1278 | /// to make sure that the overal struct has the correct alignment in spir-v. | |
| 1279 | /// If any of the fields' size is 0, it will be omitted. | |
| 1280 | /// NOTE: When the active field is set to something other than the most aligned field, the | |
| 1281 | /// resulting struct will be *underaligned*. | |
| 1282 | fn resolveUnionType2(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !SpvCacheRef { | |
| 1283 | const target = self.getTarget(); | |
| 1284 | const layout = ty.unionGetLayout(target); | |
| 1285 | const union_ty = ty.cast(Type.Payload.Union).?.data; | |
| 1286 | ||
| 1287 | if (union_ty.layout == .Packed) { | |
| 1288 | return self.todo("packed union types", .{}); | |
| 1289 | } | |
| 1290 | ||
| 1291 | if (layout.payload_size == 0) { | |
| 1292 | // No payload, so represent this as just the tag type. | |
| 1293 | return try self.resolveType2(union_ty.tag_ty, .indirect); | |
| 1294 | } | |
| 1295 | ||
| 1296 | var member_types = std.BoundedArray(SpvCacheRef, 4){}; | |
| 1297 | var member_names = std.BoundedArray(SpvCacheString, 4){}; | |
| 1298 | ||
| 1299 | const has_tag = layout.tag_size != 0; | |
| 1300 | const tag_first = layout.tag_align >= layout.payload_align; | |
| 1301 | const u8_ty_ref = try self.intType2(.unsigned, 8); // TODO: What if Int8Type is not enabled? | |
| 1302 | ||
| 1303 | if (has_tag and tag_first) { | |
| 1304 | const tag_ty_ref = try self.resolveType2(union_ty.tag_ty, .indirect); | |
| 1305 | 1168 | member_types.appendAssumeCapacity(tag_ty_ref); |
| 1306 | 1169 | member_names.appendAssumeCapacity(try self.spv.resolveString("tag")); |
| 1307 | 1170 | } |
| ... | ... | @@ -1310,7 +1173,7 @@ pub const DeclGen = struct { |
| 1310 | 1173 | const active_field_ty = union_ty.fields.values()[active_field].ty; |
| 1311 | 1174 | |
| 1312 | 1175 | const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: { |
| 1313 | const active_payload_ty_ref = try self.resolveType2(active_field_ty, .indirect); | |
| 1176 | const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect); | |
| 1314 | 1177 | member_types.appendAssumeCapacity(active_payload_ty_ref); |
| 1315 | 1178 | member_names.appendAssumeCapacity(try self.spv.resolveString("payload")); |
| 1316 | 1179 | break :blk active_field_ty.abiSize(target); |
| ... | ... | @@ -1318,19 +1181,19 @@ pub const DeclGen = struct { |
| 1318 | 1181 | |
| 1319 | 1182 | const payload_padding_len = layout.payload_size - active_field_size; |
| 1320 | 1183 | if (payload_padding_len != 0) { |
| 1321 | const payload_padding_ty_ref = try self.spv.arrayType2(@intCast(u32, payload_padding_len), u8_ty_ref); | |
| 1184 | const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref); | |
| 1322 | 1185 | member_types.appendAssumeCapacity(payload_padding_ty_ref); |
| 1323 | 1186 | member_names.appendAssumeCapacity(try self.spv.resolveString("payload_padding")); |
| 1324 | 1187 | } |
| 1325 | 1188 | |
| 1326 | 1189 | if (has_tag and !tag_first) { |
| 1327 | const tag_ty_ref = try self.resolveType2(union_ty.tag_ty, .indirect); | |
| 1190 | const tag_ty_ref = try self.resolveType(union_ty.tag_ty, .indirect); | |
| 1328 | 1191 | member_types.appendAssumeCapacity(tag_ty_ref); |
| 1329 | 1192 | member_names.appendAssumeCapacity(try self.spv.resolveString("tag")); |
| 1330 | 1193 | } |
| 1331 | 1194 | |
| 1332 | 1195 | if (layout.padding != 0) { |
| 1333 | const padding_ty_ref = try self.spv.arrayType2(layout.padding, u8_ty_ref); | |
| 1196 | const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref); | |
| 1334 | 1197 | member_types.appendAssumeCapacity(padding_ty_ref); |
| 1335 | 1198 | member_names.appendAssumeCapacity(try self.spv.resolveString("padding")); |
| 1336 | 1199 | } |
| ... | ... | @@ -1341,22 +1204,24 @@ pub const DeclGen = struct { |
| 1341 | 1204 | } }); |
| 1342 | 1205 | } |
| 1343 | 1206 | |
| 1344 | fn resolveType2(self: *DeclGen, ty: Type, repr: Repr) Error!SpvCacheRef { | |
| 1207 | /// Turn a Zig type into a SPIR-V Type, and return a reference to it. | |
| 1208 | fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!CacheRef { | |
| 1209 | log.debug("resolveType: ty = {}", .{ty.fmt(self.module)}); | |
| 1345 | 1210 | const target = self.getTarget(); |
| 1346 | 1211 | switch (ty.zigTypeTag()) { |
| 1347 | 1212 | .Void, .NoReturn => return try self.spv.resolve(.void_type), |
| 1348 | 1213 | .Bool => switch (repr) { |
| 1349 | 1214 | .direct => return try self.spv.resolve(.bool_type), |
| 1350 | .indirect => return try self.intType2(.unsigned, 1), | |
| 1215 | .indirect => return try self.intType(.unsigned, 1), | |
| 1351 | 1216 | }, |
| 1352 | 1217 | .Int => { |
| 1353 | 1218 | const int_info = ty.intInfo(target); |
| 1354 | return try self.intType2(int_info.signedness, int_info.bits); | |
| 1219 | return try self.intType(int_info.signedness, int_info.bits); | |
| 1355 | 1220 | }, |
| 1356 | 1221 | .Enum => { |
| 1357 | 1222 | var buffer: Type.Payload.Bits = undefined; |
| 1358 | 1223 | const tag_ty = ty.intTagType(&buffer); |
| 1359 | return self.resolveType2(tag_ty, repr); | |
| 1224 | return self.resolveType(tag_ty, repr); | |
| 1360 | 1225 | }, |
| 1361 | 1226 | .Float => { |
| 1362 | 1227 | // We can (and want) not really emulate floating points with other floating point types like with the integer types, |
| ... | ... | @@ -1378,11 +1243,11 @@ pub const DeclGen = struct { |
| 1378 | 1243 | }, |
| 1379 | 1244 | .Array => { |
| 1380 | 1245 | const elem_ty = ty.childType(); |
| 1381 | const elem_ty_ref = try self.resolveType2(elem_ty, .direct); | |
| 1246 | const elem_ty_ref = try self.resolveType(elem_ty, .direct); | |
| 1382 | 1247 | const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse { |
| 1383 | 1248 | return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()}); |
| 1384 | 1249 | }; |
| 1385 | return self.spv.arrayType2(total_len, elem_ty_ref); | |
| 1250 | return self.spv.arrayType(total_len, elem_ty_ref); | |
| 1386 | 1251 | }, |
| 1387 | 1252 | .Fn => switch (repr) { |
| 1388 | 1253 | .direct => { |
| ... | ... | @@ -1390,12 +1255,12 @@ pub const DeclGen = struct { |
| 1390 | 1255 | if (ty.fnIsVarArgs()) |
| 1391 | 1256 | return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); |
| 1392 | 1257 | |
| 1393 | const param_ty_refs = try self.gpa.alloc(SpvCacheRef, ty.fnParamLen()); | |
| 1258 | const param_ty_refs = try self.gpa.alloc(CacheRef, ty.fnParamLen()); | |
| 1394 | 1259 | defer self.gpa.free(param_ty_refs); |
| 1395 | 1260 | for (param_ty_refs, 0..) |*param_type, i| { |
| 1396 | param_type.* = try self.resolveType2(ty.fnParamType(i), .direct); | |
| 1261 | param_type.* = try self.resolveType(ty.fnParamType(i), .direct); | |
| 1397 | 1262 | } |
| 1398 | const return_ty_ref = try self.resolveType2(ty.fnReturnType(), .direct); | |
| 1263 | const return_ty_ref = try self.resolveType(ty.fnReturnType(), .direct); | |
| 1399 | 1264 | |
| 1400 | 1265 | return try self.spv.resolve(.{ .function_type = .{ |
| 1401 | 1266 | .return_type = return_ty_ref, |
| ... | ... | @@ -1405,14 +1270,14 @@ pub const DeclGen = struct { |
| 1405 | 1270 | .indirect => { |
| 1406 | 1271 | // TODO: Represent function pointers properly. |
| 1407 | 1272 | // For now, just use an usize type. |
| 1408 | return try self.sizeType2(); | |
| 1273 | return try self.sizeType(); | |
| 1409 | 1274 | }, |
| 1410 | 1275 | }, |
| 1411 | 1276 | .Pointer => { |
| 1412 | 1277 | const ptr_info = ty.ptrInfo().data; |
| 1413 | 1278 | |
| 1414 | 1279 | const storage_class = spvStorageClass(ptr_info.@"addrspace"); |
| 1415 | const child_ty_ref = try self.resolveType2(ptr_info.pointee_type, .indirect); | |
| 1280 | const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect); | |
| 1416 | 1281 | const ptr_ty_ref = try self.spv.resolve(.{ .ptr_type = .{ |
| 1417 | 1282 | .storage_class = storage_class, |
| 1418 | 1283 | .child_type = child_ty_ref, |
| ... | ... | @@ -1420,7 +1285,15 @@ pub const DeclGen = struct { |
| 1420 | 1285 | if (ptr_info.size != .Slice) { |
| 1421 | 1286 | return ptr_ty_ref; |
| 1422 | 1287 | } |
| 1423 | unreachable; // TODO | |
| 1288 | ||
| 1289 | const size_ty_ref = try self.sizeType(); | |
| 1290 | return self.spv.resolve(.{ .struct_type = .{ | |
| 1291 | .member_types = &.{ ptr_ty_ref, size_ty_ref }, | |
| 1292 | .member_names = &.{ | |
| 1293 | try self.spv.resolveString("ptr"), | |
| 1294 | try self.spv.resolveString("len"), | |
| 1295 | }, | |
| 1296 | } }); | |
| 1424 | 1297 | }, |
| 1425 | 1298 | .Vector => { |
| 1426 | 1299 | // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations |
| ... | ... | @@ -1433,33 +1306,47 @@ pub const DeclGen = struct { |
| 1433 | 1306 | // TODO: Properly verify sizes and child type. |
| 1434 | 1307 | |
| 1435 | 1308 | return try self.spv.resolve(.{ .vector_type = .{ |
| 1436 | .component_type = try self.resolveType2(ty.elemType(), repr), | |
| 1309 | .component_type = try self.resolveType(ty.elemType(), repr), | |
| 1437 | 1310 | .component_count = @intCast(u32, ty.vectorLen()), |
| 1438 | 1311 | } }); |
| 1439 | 1312 | }, |
| 1440 | 1313 | .Struct => { |
| 1441 | 1314 | if (ty.isSimpleTupleOrAnonStruct()) { |
| 1442 | unreachable; // TODO | |
| 1315 | const tuple = ty.tupleFields(); | |
| 1316 | const member_types = try self.gpa.alloc(CacheRef, tuple.types.len); | |
| 1317 | defer self.gpa.free(member_types); | |
| 1318 | ||
| 1319 | var member_index: usize = 0; | |
| 1320 | for (tuple.types, 0..) |field_ty, i| { | |
| 1321 | const field_val = tuple.values[i]; | |
| 1322 | if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue; | |
| 1323 | ||
| 1324 | member_types[member_index] = try self.resolveType(field_ty, .indirect); | |
| 1325 | member_index += 1; | |
| 1326 | } | |
| 1327 | ||
| 1328 | return try self.spv.resolve(.{ .struct_type = .{ | |
| 1329 | .member_types = member_types[0..member_index], | |
| 1330 | } }); | |
| 1443 | 1331 | } |
| 1444 | 1332 | |
| 1445 | 1333 | const struct_ty = ty.castTag(.@"struct").?.data; |
| 1446 | 1334 | |
| 1447 | 1335 | if (struct_ty.layout == .Packed) { |
| 1448 | return try self.resolveType2(struct_ty.backing_int_ty, .direct); | |
| 1336 | return try self.resolveType(struct_ty.backing_int_ty, .direct); | |
| 1449 | 1337 | } |
| 1450 | 1338 | |
| 1451 | const member_types = try self.gpa.alloc(SpvCacheRef, struct_ty.fields.count()); | |
| 1339 | const member_types = try self.gpa.alloc(CacheRef, struct_ty.fields.count()); | |
| 1452 | 1340 | defer self.gpa.free(member_types); |
| 1453 | 1341 | |
| 1454 | const member_names = try self.gpa.alloc(SpvCacheString, struct_ty.fields.count()); | |
| 1342 | const member_names = try self.gpa.alloc(CacheString, struct_ty.fields.count()); | |
| 1455 | 1343 | defer self.gpa.free(member_names); |
| 1456 | 1344 | |
| 1457 | // const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count()); | |
| 1458 | 1345 | var member_index: usize = 0; |
| 1459 | 1346 | for (struct_ty.fields.values(), 0..) |field, i| { |
| 1460 | 1347 | if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; |
| 1461 | 1348 | |
| 1462 | member_types[member_index] = try self.resolveType2(field.ty, .indirect); | |
| 1349 | member_types[member_index] = try self.resolveType(field.ty, .indirect); | |
| 1463 | 1350 | member_names[member_index] = try self.spv.resolveString(struct_ty.fields.keys()[i]); |
| 1464 | 1351 | member_index += 1; |
| 1465 | 1352 | } |
| ... | ... | @@ -1480,16 +1367,16 @@ pub const DeclGen = struct { |
| 1480 | 1367 | // Just use a bool. |
| 1481 | 1368 | // Note: Always generate the bool with indirect format, to save on some sanity |
| 1482 | 1369 | // Perform the conversion to a direct bool when the field is extracted. |
| 1483 | return try self.resolveType2(Type.bool, .indirect); | |
| 1370 | return try self.resolveType(Type.bool, .indirect); | |
| 1484 | 1371 | } |
| 1485 | 1372 | |
| 1486 | const payload_ty_ref = try self.resolveType2(payload_ty, .indirect); | |
| 1373 | const payload_ty_ref = try self.resolveType(payload_ty, .indirect); | |
| 1487 | 1374 | if (ty.optionalReprIsPayload()) { |
| 1488 | 1375 | // Optional is actually a pointer or a slice. |
| 1489 | 1376 | return payload_ty_ref; |
| 1490 | 1377 | } |
| 1491 | 1378 | |
| 1492 | const bool_ty_ref = try self.resolveType2(Type.bool, .indirect); | |
| 1379 | const bool_ty_ref = try self.resolveType(Type.bool, .indirect); | |
| 1493 | 1380 | |
| 1494 | 1381 | return try self.spv.resolve(.{ .struct_type = .{ |
| 1495 | 1382 | .member_types = &.{ payload_ty_ref, bool_ty_ref }, |
| ... | ... | @@ -1499,21 +1386,21 @@ pub const DeclGen = struct { |
| 1499 | 1386 | }, |
| 1500 | 1387 | } }); |
| 1501 | 1388 | }, |
| 1502 | .Union => return try self.resolveUnionType2(ty, null), | |
| 1503 | .ErrorSet => return try self.intType2(.unsigned, 16), | |
| 1389 | .Union => return try self.resolveUnionType(ty, null), | |
| 1390 | .ErrorSet => return try self.intType(.unsigned, 16), | |
| 1504 | 1391 | .ErrorUnion => { |
| 1505 | 1392 | const payload_ty = ty.errorUnionPayload(); |
| 1506 | const error_ty_ref = try self.resolveType2(Type.anyerror, .indirect); | |
| 1393 | const error_ty_ref = try self.resolveType(Type.anyerror, .indirect); | |
| 1507 | 1394 | |
| 1508 | 1395 | const eu_layout = self.errorUnionLayout(payload_ty); |
| 1509 | 1396 | if (!eu_layout.payload_has_bits) { |
| 1510 | 1397 | return error_ty_ref; |
| 1511 | 1398 | } |
| 1512 | 1399 | |
| 1513 | const payload_ty_ref = try self.resolveType2(payload_ty, .indirect); | |
| 1400 | const payload_ty_ref = try self.resolveType(payload_ty, .indirect); | |
| 1514 | 1401 | |
| 1515 | var member_types: [2]SpvCacheRef = undefined; | |
| 1516 | var member_names: [2]SpvCacheString = undefined; | |
| 1402 | var member_types: [2]CacheRef = undefined; | |
| 1403 | var member_names: [2]CacheString = undefined; | |
| 1517 | 1404 | if (eu_layout.error_first) { |
| 1518 | 1405 | // Put the error first |
| 1519 | 1406 | member_types = .{ error_ty_ref, payload_ty_ref }; |
| ... | ... | @@ -1550,226 +1437,6 @@ pub const DeclGen = struct { |
| 1550 | 1437 | } |
| 1551 | 1438 | } |
| 1552 | 1439 | |
| 1553 | /// Turn a Zig type into a SPIR-V Type, and return a reference to it. | |
| 1554 | fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!SpvType.Ref { | |
| 1555 | log.debug("resolveType: ty = {}", .{ty.fmt(self.module)}); | |
| 1556 | _ = try self.resolveType2(ty, repr); | |
| 1557 | const target = self.getTarget(); | |
| 1558 | switch (ty.zigTypeTag()) { | |
| 1559 | .Void, .NoReturn => return try self.spv.resolveType(SpvType.initTag(.void)), | |
| 1560 | .Bool => switch (repr) { | |
| 1561 | .direct => return try self.spv.resolveType(SpvType.initTag(.bool)), | |
| 1562 | // SPIR-V booleans are opaque, which is fine for operations, but they cant be stored. | |
| 1563 | // This function returns the *stored* type, for values directly we convert this into a bool when | |
| 1564 | // it is loaded, and convert it back to this type when stored. | |
| 1565 | .indirect => return try self.intType(.unsigned, 1), | |
| 1566 | }, | |
| 1567 | .Int => { | |
| 1568 | const int_info = ty.intInfo(target); | |
| 1569 | return try self.intType(int_info.signedness, int_info.bits); | |
| 1570 | }, | |
| 1571 | .Enum => { | |
| 1572 | var buffer: Type.Payload.Bits = undefined; | |
| 1573 | const tag_ty = ty.intTagType(&buffer); | |
| 1574 | return self.resolveType(tag_ty, repr); | |
| 1575 | }, | |
| 1576 | .Float => { | |
| 1577 | // We can (and want) not really emulate floating points with other floating point types like with the integer types, | |
| 1578 | // so if the float is not supported, just return an error. | |
| 1579 | const bits = ty.floatBits(target); | |
| 1580 | const supported = switch (bits) { | |
| 1581 | 16 => Target.spirv.featureSetHas(target.cpu.features, .Float16), | |
| 1582 | // 32-bit floats are always supported (see spec, 2.16.1, Data rules). | |
| 1583 | 32 => true, | |
| 1584 | 64 => Target.spirv.featureSetHas(target.cpu.features, .Float64), | |
| 1585 | else => false, | |
| 1586 | }; | |
| 1587 | ||
| 1588 | if (!supported) { | |
| 1589 | return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits}); | |
| 1590 | } | |
| 1591 | ||
| 1592 | return try self.spv.resolveType(SpvType.float(bits)); | |
| 1593 | }, | |
| 1594 | .Array => { | |
| 1595 | const elem_ty = ty.childType(); | |
| 1596 | const elem_ty_ref = try self.resolveType(elem_ty, .indirect); | |
| 1597 | const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse { | |
| 1598 | return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()}); | |
| 1599 | }; | |
| 1600 | return try self.spv.arrayType(total_len, elem_ty_ref); | |
| 1601 | }, | |
| 1602 | .Fn => switch (repr) { | |
| 1603 | .direct => { | |
| 1604 | // TODO: Put this somewhere in Sema.zig | |
| 1605 | if (ty.fnIsVarArgs()) | |
| 1606 | return self.fail("VarArgs functions are unsupported for SPIR-V", .{}); | |
| 1607 | ||
| 1608 | // TODO: Parameter passing convention etc. | |
| 1609 | ||
| 1610 | const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen()); | |
| 1611 | for (param_types, 0..) |*param, i| { | |
| 1612 | param.* = try self.resolveType(ty.fnParamType(i), .direct); | |
| 1613 | } | |
| 1614 | ||
| 1615 | const return_type = try self.resolveType(ty.fnReturnType(), .direct); | |
| 1616 | ||
| 1617 | const payload = try self.spv.arena.create(SpvType.Payload.Function); | |
| 1618 | payload.* = .{ .return_type = return_type, .parameters = param_types }; | |
| 1619 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); | |
| 1620 | }, | |
| 1621 | .indirect => { | |
| 1622 | // TODO: Represent function pointers properly. | |
| 1623 | // For now, just use an usize type. | |
| 1624 | return try self.sizeType(); | |
| 1625 | }, | |
| 1626 | }, | |
| 1627 | .Pointer => { | |
| 1628 | const ptr_info = ty.ptrInfo().data; | |
| 1629 | ||
| 1630 | const storage_class = spvStorageClass(ptr_info.@"addrspace"); | |
| 1631 | const child_ty_ref = try self.resolveType(ptr_info.pointee_type, .indirect); | |
| 1632 | const ptr_ty_ref = try self.spv.ptrType(child_ty_ref, storage_class, 0); | |
| 1633 | ||
| 1634 | if (ptr_info.size != .Slice) { | |
| 1635 | return ptr_ty_ref; | |
| 1636 | } | |
| 1637 | ||
| 1638 | return try self.spv.simpleStructType(&.{ | |
| 1639 | .{ .ty = ptr_ty_ref, .name = "ptr" }, | |
| 1640 | .{ .ty = try self.sizeType(), .name = "len" }, | |
| 1641 | }); | |
| 1642 | }, | |
| 1643 | .Vector => { | |
| 1644 | // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations | |
| 1645 | // which work on them), so simply use those. | |
| 1646 | // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way. | |
| 1647 | // "composite integers" (larger than the largest supported native type) can probably be represented by an array of vectors. | |
| 1648 | // TODO: The SPIR-V spec mentions that vector sizes may be quite restricted! look into which we can use, and whether OpTypeVector | |
| 1649 | // is adequate at all for this. | |
| 1650 | ||
| 1651 | // TODO: Properly verify sizes and child type. | |
| 1652 | ||
| 1653 | const payload = try self.spv.arena.create(SpvType.Payload.Vector); | |
| 1654 | payload.* = .{ | |
| 1655 | .component_type = try self.resolveType(ty.elemType(), repr), | |
| 1656 | .component_count = @intCast(u32, ty.vectorLen()), | |
| 1657 | }; | |
| 1658 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); | |
| 1659 | }, | |
| 1660 | .Struct => { | |
| 1661 | if (ty.isSimpleTupleOrAnonStruct()) { | |
| 1662 | const tuple = ty.tupleFields(); | |
| 1663 | const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, tuple.types.len); | |
| 1664 | var member_index: u32 = 0; | |
| 1665 | for (tuple.types, 0..) |field_ty, i| { | |
| 1666 | const field_val = tuple.values[i]; | |
| 1667 | if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBitsIgnoreComptime()) continue; | |
| 1668 | members[member_index] = .{ | |
| 1669 | .ty = try self.resolveType(field_ty, .indirect), | |
| 1670 | }; | |
| 1671 | member_index += 1; | |
| 1672 | } | |
| 1673 | const payload = try self.spv.arena.create(SpvType.Payload.Struct); | |
| 1674 | payload.* = .{ | |
| 1675 | .members = members[0..member_index], | |
| 1676 | }; | |
| 1677 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); | |
| 1678 | } | |
| 1679 | ||
| 1680 | const struct_ty = ty.castTag(.@"struct").?.data; | |
| 1681 | ||
| 1682 | if (struct_ty.layout == .Packed) { | |
| 1683 | return try self.resolveType(struct_ty.backing_int_ty, .indirect); | |
| 1684 | } | |
| 1685 | ||
| 1686 | const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count()); | |
| 1687 | var member_index: usize = 0; | |
| 1688 | for (struct_ty.fields.values(), 0..) |field, i| { | |
| 1689 | if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; | |
| 1690 | ||
| 1691 | members[member_index] = .{ | |
| 1692 | .ty = try self.resolveType(field.ty, .indirect), | |
| 1693 | .name = struct_ty.fields.keys()[i], | |
| 1694 | }; | |
| 1695 | member_index += 1; | |
| 1696 | } | |
| 1697 | ||
| 1698 | const name = try struct_ty.getFullyQualifiedName(self.module); | |
| 1699 | defer self.module.gpa.free(name); | |
| 1700 | ||
| 1701 | const payload = try self.spv.arena.create(SpvType.Payload.Struct); | |
| 1702 | payload.* = .{ | |
| 1703 | .members = members[0..member_index], | |
| 1704 | .name = try self.spv.arena.dupe(u8, name), | |
| 1705 | }; | |
| 1706 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); | |
| 1707 | }, | |
| 1708 | .Optional => { | |
| 1709 | var buf: Type.Payload.ElemType = undefined; | |
| 1710 | const payload_ty = ty.optionalChild(&buf); | |
| 1711 | if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { | |
| 1712 | // Just use a bool. | |
| 1713 | // Note: Always generate the bool with indirect format, to save on some sanity | |
| 1714 | // Perform the converison to a direct bool when the field is extracted. | |
| 1715 | return try self.resolveType(Type.bool, .indirect); | |
| 1716 | } | |
| 1717 | ||
| 1718 | const payload_ty_ref = try self.resolveType(payload_ty, .indirect); | |
| 1719 | if (ty.optionalReprIsPayload()) { | |
| 1720 | // Optional is actually a pointer or a slice. | |
| 1721 | return payload_ty_ref; | |
| 1722 | } | |
| 1723 | ||
| 1724 | const bool_ty_ref = try self.resolveType(Type.bool, .indirect); | |
| 1725 | ||
| 1726 | // its an actual optional | |
| 1727 | return try self.spv.simpleStructType(&.{ | |
| 1728 | .{ .ty = payload_ty_ref, .name = "payload" }, | |
| 1729 | .{ .ty = bool_ty_ref, .name = "valid" }, | |
| 1730 | }); | |
| 1731 | }, | |
| 1732 | .Union => return try self.resolveUnionType(ty, null), | |
| 1733 | .ErrorSet => return try self.intType(.unsigned, 16), | |
| 1734 | .ErrorUnion => { | |
| 1735 | const payload_ty = ty.errorUnionPayload(); | |
| 1736 | const error_ty_ref = try self.resolveType(Type.anyerror, .indirect); | |
| 1737 | ||
| 1738 | const eu_layout = self.errorUnionLayout(payload_ty); | |
| 1739 | if (!eu_layout.payload_has_bits) { | |
| 1740 | return error_ty_ref; | |
| 1741 | } | |
| 1742 | ||
| 1743 | const payload_ty_ref = try self.resolveType(payload_ty, .indirect); | |
| 1744 | ||
| 1745 | var members = std.BoundedArray(SpvType.Payload.Struct.Member, 2){}; | |
| 1746 | if (eu_layout.error_first) { | |
| 1747 | // Put the error first | |
| 1748 | members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" }); | |
| 1749 | members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" }); | |
| 1750 | // TODO: ABI padding? | |
| 1751 | } else { | |
| 1752 | // Put the payload first. | |
| 1753 | members.appendAssumeCapacity(.{ .ty = payload_ty_ref, .name = "payload" }); | |
| 1754 | members.appendAssumeCapacity(.{ .ty = error_ty_ref, .name = "error" }); | |
| 1755 | // TODO: ABI padding? | |
| 1756 | } | |
| 1757 | ||
| 1758 | return try self.spv.simpleStructType(members.slice()); | |
| 1759 | }, | |
| 1760 | ||
| 1761 | .Null, | |
| 1762 | .Undefined, | |
| 1763 | .EnumLiteral, | |
| 1764 | .ComptimeFloat, | |
| 1765 | .ComptimeInt, | |
| 1766 | .Type, | |
| 1767 | => unreachable, // Must be comptime. | |
| 1768 | ||
| 1769 | else => |tag| return self.todo("Implement zig type '{}'", .{tag}), | |
| 1770 | } | |
| 1771 | } | |
| 1772 | ||
| 1773 | 1440 | fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass { |
| 1774 | 1441 | return switch (as) { |
| 1775 | 1442 | .generic => .Generic, |
| ... | ... | @@ -1839,17 +1506,13 @@ pub const DeclGen = struct { |
| 1839 | 1506 | /// the name of an error in the text executor. |
| 1840 | 1507 | fn generateTestEntryPoint(self: *DeclGen, name: []const u8, spv_test_decl_index: SpvModule.Decl.Index) !void { |
| 1841 | 1508 | const anyerror_ty_ref = try self.resolveType(Type.anyerror, .direct); |
| 1842 | const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup, 0); | |
| 1509 | const ptr_anyerror_ty_ref = try self.spv.ptrType(anyerror_ty_ref, .CrossWorkgroup); | |
| 1843 | 1510 | const void_ty_ref = try self.resolveType(Type.void, .direct); |
| 1844 | 1511 | |
| 1845 | const kernel_proto_ty_ref = blk: { | |
| 1846 | const proto_payload = try self.spv.arena.create(SpvType.Payload.Function); | |
| 1847 | proto_payload.* = .{ | |
| 1848 | .return_type = void_ty_ref, | |
| 1849 | .parameters = try self.spv.arena.dupe(SpvType.Ref, &.{ptr_anyerror_ty_ref}), | |
| 1850 | }; | |
| 1851 | break :blk try self.spv.resolveType(SpvType.initPayload(&proto_payload.base)); | |
| 1852 | }; | |
| 1512 | const kernel_proto_ty_ref = try self.spv.resolve(.{ .function_type = .{ | |
| 1513 | .return_type = void_ty_ref, | |
| 1514 | .parameters = &.{ptr_anyerror_ty_ref}, | |
| 1515 | } }); | |
| 1853 | 1516 | |
| 1854 | 1517 | const test_id = self.spv.declPtr(spv_test_decl_index).result_id; |
| 1855 | 1518 | |
| ... | ... | @@ -1983,9 +1646,9 @@ pub const DeclGen = struct { |
| 1983 | 1646 | } |
| 1984 | 1647 | } |
| 1985 | 1648 | |
| 1986 | fn boolToInt(self: *DeclGen, result_ty_ref: SpvType.Ref, condition_id: IdRef) !IdRef { | |
| 1987 | const zero_id = try self.constInt(result_ty_ref, 0); | |
| 1988 | const one_id = try self.constInt(result_ty_ref, 1); | |
| 1649 | fn boolToInt(self: *DeclGen, result_ty_ref: CacheRef, condition_id: IdRef) !IdRef { | |
| 1650 | const zero_id = try self.spv.constInt(result_ty_ref, 0); | |
| 1651 | const one_id = try self.spv.constInt(result_ty_ref, 1); | |
| 1989 | 1652 | const result_id = self.spv.allocId(); |
| 1990 | 1653 | try self.func.body.emit(self.spv.gpa, .OpSelect, .{ |
| 1991 | 1654 | .id_result_type = self.typeId(result_ty_ref), |
| ... | ... | @@ -2004,7 +1667,7 @@ pub const DeclGen = struct { |
| 2004 | 1667 | .Bool => blk: { |
| 2005 | 1668 | const direct_bool_ty_ref = try self.resolveType(ty, .direct); |
| 2006 | 1669 | const indirect_bool_ty_ref = try self.resolveType(ty, .indirect); |
| 2007 | const zero_id = try self.constInt(indirect_bool_ty_ref, 0); | |
| 1670 | const zero_id = try self.spv.constInt(indirect_bool_ty_ref, 0); | |
| 2008 | 1671 | const result_id = self.spv.allocId(); |
| 2009 | 1672 | try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{ |
| 2010 | 1673 | .id_result_type = self.typeId(direct_bool_ty_ref), |
| ... | ... | @@ -2242,10 +1905,10 @@ pub const DeclGen = struct { |
| 2242 | 1905 | return result_id; |
| 2243 | 1906 | } |
| 2244 | 1907 | |
| 2245 | fn maskStrangeInt(self: *DeclGen, ty_ref: SpvType.Ref, value_id: IdRef, bits: u16) !IdRef { | |
| 1908 | fn maskStrangeInt(self: *DeclGen, ty_ref: CacheRef, value_id: IdRef, bits: u16) !IdRef { | |
| 2246 | 1909 | const mask_value = if (bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @intCast(u6, bits)) - 1; |
| 2247 | 1910 | const result_id = self.spv.allocId(); |
| 2248 | const mask_id = try self.constInt(ty_ref, mask_value); | |
| 1911 | const mask_id = try self.spv.constInt(ty_ref, mask_value); | |
| 2249 | 1912 | try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ |
| 2250 | 1913 | .id_result_type = self.typeId(ty_ref), |
| 2251 | 1914 | .id_result = result_id, |
| ... | ... | @@ -2384,7 +2047,7 @@ pub const DeclGen = struct { |
| 2384 | 2047 | // Note that signed overflow is also wrapping in spir-v. |
| 2385 | 2048 | |
| 2386 | 2049 | const rhs_lt_zero_id = self.spv.allocId(); |
| 2387 | const zero_id = try self.constInt(operand_ty_ref, 0); | |
| 2050 | const zero_id = try self.spv.constInt(operand_ty_ref, 0); | |
| 2388 | 2051 | try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{ |
| 2389 | 2052 | .id_result_type = self.typeId(bool_ty_ref), |
| 2390 | 2053 | .id_result = rhs_lt_zero_id, |
| ... | ... | @@ -2463,7 +2126,7 @@ pub const DeclGen = struct { |
| 2463 | 2126 | /// is the latter and PtrAccessChain is the former. |
| 2464 | 2127 | fn accessChain( |
| 2465 | 2128 | self: *DeclGen, |
| 2466 | result_ty_ref: SpvType.Ref, | |
| 2129 | result_ty_ref: CacheRef, | |
| 2467 | 2130 | base: IdRef, |
| 2468 | 2131 | indexes: []const IdRef, |
| 2469 | 2132 | ) !IdRef { |
| ... | ... | @@ -2479,7 +2142,7 @@ pub const DeclGen = struct { |
| 2479 | 2142 | |
| 2480 | 2143 | fn ptrAccessChain( |
| 2481 | 2144 | self: *DeclGen, |
| 2482 | result_ty_ref: SpvType.Ref, | |
| 2145 | result_ty_ref: CacheRef, | |
| 2483 | 2146 | base: IdRef, |
| 2484 | 2147 | element: IdRef, |
| 2485 | 2148 | indexes: []const IdRef, |
| ... | ... | @@ -2854,7 +2517,7 @@ pub const DeclGen = struct { |
| 2854 | 2517 | // Construct new pointer type for the resulting pointer |
| 2855 | 2518 | const elem_ty = ptr_ty.elemType2(); // use elemType() so that we get T for *[N]T. |
| 2856 | 2519 | const elem_ty_ref = try self.resolveType(elem_ty, .direct); |
| 2857 | const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace()), 0); | |
| 2520 | const elem_ptr_ty_ref = try self.spv.ptrType(elem_ty_ref, spvStorageClass(ptr_ty.ptrAddressSpace())); | |
| 2858 | 2521 | if (ptr_ty.isSinglePointer()) { |
| 2859 | 2522 | // Pointer-to-array. In this case, the resulting pointer is not of the same type |
| 2860 | 2523 | // as the ptr_ty (we want a *T, not a *[N]T), and hence we need to use accessChain. |
| ... | ... | @@ -2970,7 +2633,7 @@ pub const DeclGen = struct { |
| 2970 | 2633 | fn makePointerConstant( |
| 2971 | 2634 | self: *DeclGen, |
| 2972 | 2635 | section: *SpvSection, |
| 2973 | ptr_ty_ref: SpvType.Ref, | |
| 2636 | ptr_ty_ref: CacheRef, | |
| 2974 | 2637 | ptr_id: IdRef, |
| 2975 | 2638 | ) !IdRef { |
| 2976 | 2639 | const result_id = self.spv.allocId(); |
| ... | ... | @@ -2988,11 +2651,11 @@ pub const DeclGen = struct { |
| 2988 | 2651 | // placed in the Function address space. |
| 2989 | 2652 | fn alloc( |
| 2990 | 2653 | self: *DeclGen, |
| 2991 | ty_ref: SpvType.Ref, | |
| 2654 | ty_ref: CacheRef, | |
| 2992 | 2655 | initializer: ?IdRef, |
| 2993 | 2656 | ) !IdRef { |
| 2994 | const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function, 0); | |
| 2995 | const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, 0); | |
| 2657 | const fn_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Function); | |
| 2658 | const general_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic); | |
| 2996 | 2659 | |
| 2997 | 2660 | // SPIR-V requires that OpVariable declarations for locals go into the first block, so we are just going to |
| 2998 | 2661 | // directly generate them into func.prologue instead of the body. |
| ... | ... | @@ -3146,7 +2809,7 @@ pub const DeclGen = struct { |
| 3146 | 2809 | |
| 3147 | 2810 | const val_is_undef = if (self.air.value(bin_op.rhs)) |val| val.isUndefDeep() else false; |
| 3148 | 2811 | if (val_is_undef) { |
| 3149 | const undef = try self.constUndef(ptr_ty_ref); | |
| 2812 | const undef = try self.spv.constUndef(ptr_ty_ref); | |
| 3150 | 2813 | try self.store(ptr_ty, ptr, undef); |
| 3151 | 2814 | } else { |
| 3152 | 2815 | try self.store(ptr_ty, ptr, value); |
| ... | ... | @@ -3217,7 +2880,7 @@ pub const DeclGen = struct { |
| 3217 | 2880 | else |
| 3218 | 2881 | err_union_id; |
| 3219 | 2882 | |
| 3220 | const zero_id = try self.constInt(err_ty_ref, 0); | |
| 2883 | const zero_id = try self.spv.constInt(err_ty_ref, 0); | |
| 3221 | 2884 | const is_err_id = self.spv.allocId(); |
| 3222 | 2885 | try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{ |
| 3223 | 2886 | .id_result_type = self.typeId(bool_ty_ref), |
| ... | ... | @@ -3266,7 +2929,7 @@ pub const DeclGen = struct { |
| 3266 | 2929 | |
| 3267 | 2930 | if (err_union_ty.errorUnionSet().errorSetIsEmpty()) { |
| 3268 | 2931 | // No error possible, so just return undefined. |
| 3269 | return try self.constUndef(err_ty_ref); | |
| 2932 | return try self.spv.constUndef(err_ty_ref); | |
| 3270 | 2933 | } |
| 3271 | 2934 | |
| 3272 | 2935 | const payload_ty = err_union_ty.errorUnionPayload(); |
| ... | ... | @@ -3295,7 +2958,7 @@ pub const DeclGen = struct { |
| 3295 | 2958 | |
| 3296 | 2959 | const payload_ty_ref = try self.resolveType(payload_ty, .indirect); |
| 3297 | 2960 | var members = std.BoundedArray(IdRef, 2){}; |
| 3298 | const payload_id = try self.constUndef(payload_ty_ref); | |
| 2961 | const payload_id = try self.spv.constUndef(payload_ty_ref); | |
| 3299 | 2962 | if (eu_layout.error_first) { |
| 3300 | 2963 | members.appendAssumeCapacity(operand_id); |
| 3301 | 2964 | members.appendAssumeCapacity(payload_id); |
| ... | ... | @@ -3337,7 +3000,7 @@ pub const DeclGen = struct { |
| 3337 | 3000 | operand_id; |
| 3338 | 3001 | |
| 3339 | 3002 | const payload_ty_ref = try self.resolveType(ptr_ty, .direct); |
| 3340 | const null_id = try self.constNull(payload_ty_ref); | |
| 3003 | const null_id = try self.spv.constNull(payload_ty_ref); | |
| 3341 | 3004 | const result_id = self.spv.allocId(); |
| 3342 | 3005 | const operands = .{ |
| 3343 | 3006 | .id_result_type = self.typeId(bool_ty_ref), |
src/codegen/spirv/Assembler.zig+36-134| ... | ... | @@ -11,7 +11,8 @@ const IdRef = spec.IdRef; |
| 11 | 11 | const IdResult = spec.IdResult; |
| 12 | 12 | |
| 13 | 13 | const SpvModule = @import("Module.zig"); |
| 14 | const SpvType = @import("type.zig").Type; | |
| 14 | const CacheRef = SpvModule.CacheRef; | |
| 15 | const CacheKey = SpvModule.CacheKey; | |
| 15 | 16 | |
| 16 | 17 | /// Represents a token in the assembly template. |
| 17 | 18 | const Token = struct { |
| ... | ... | @@ -126,7 +127,7 @@ const AsmValue = union(enum) { |
| 126 | 127 | value: IdRef, |
| 127 | 128 | |
| 128 | 129 | /// This result-value represents a type registered into the module's type system. |
| 129 | ty: SpvType.Ref, | |
| 130 | ty: CacheRef, | |
| 130 | 131 | |
| 131 | 132 | /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue |
| 132 | 133 | /// is of a variant that allows the result to be obtained (not an unresolved |
| ... | ... | @@ -135,7 +136,7 @@ const AsmValue = union(enum) { |
| 135 | 136 | return switch (self) { |
| 136 | 137 | .just_declared, .unresolved_forward_reference => unreachable, |
| 137 | 138 | .value => |result| result, |
| 138 | .ty => |ref| spv.typeId(ref), | |
| 139 | .ty => |ref| spv.resultId(ref), | |
| 139 | 140 | }; |
| 140 | 141 | } |
| 141 | 142 | }; |
| ... | ... | @@ -267,9 +268,9 @@ fn processInstruction(self: *Assembler) !void { |
| 267 | 268 | /// refers to the result. |
| 268 | 269 | fn processTypeInstruction(self: *Assembler) !AsmValue { |
| 269 | 270 | 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), | |
| 273 | 274 | .OpTypeInt => blk: { |
| 274 | 275 | const signedness: std.builtin.Signedness = switch (operands[2].literal32) { |
| 275 | 276 | 0 => .unsigned, |
| ... | ... | @@ -282,7 +283,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { |
| 282 | 283 | const width = std.math.cast(u16, operands[1].literal32) orelse { |
| 283 | 284 | return self.fail(0, "int type of {} bits is too large", .{operands[1].literal32}); |
| 284 | 285 | }; |
| 285 | break :blk try SpvType.int(self.spv.arena, signedness, width); | |
| 286 | break :blk try self.spv.intType(signedness, width); | |
| 286 | 287 | }, |
| 287 | 288 | .OpTypeFloat => blk: { |
| 288 | 289 | const bits = operands[1].literal32; |
| ... | ... | @@ -292,136 +293,36 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { |
| 292 | 293 | return self.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits}); |
| 293 | 294 | }, |
| 294 | 295 | } |
| 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) } }); | |
| 363 | 297 | }, |
| 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 | } }), | |
| 364 | 302 | .OpTypeArray => { |
| 365 | 303 | // TODO: The length of an OpTypeArray is determined by a constant (which may be a spec constant), |
| 366 | 304 | // and so some consideration must be taken when entering this in the type system. |
| 367 | 305 | return self.todo("process OpTypeArray", .{}); |
| 368 | 306 | }, |
| 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 | ), | |
| 395 | 311 | .OpTypeFunction => blk: { |
| 396 | 312 | 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); | |
| 398 | 315 | for (param_types, 0..) |*param, i| { |
| 399 | 316 | param.* = try self.resolveTypeRef(param_operands[i].ref_id); |
| 400 | 317 | } |
| 401 | const payload = try self.spv.arena.create(SpvType.Payload.Function); | |
| 402 | payload.* = .{ | |
| 318 | break :blk try self.spv.resolve(.{ .function_type = .{ | |
| 403 | 319 | .return_type = try self.resolveTypeRef(operands[1].ref_id), |
| 404 | 320 | .parameters = param_types, |
| 405 | }; | |
| 406 | break :blk SpvType.initPayload(&payload.base); | |
| 321 | } }); | |
| 407 | 322 | }, |
| 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), | |
| 421 | 323 | else => return self.todo("process type instruction {s}", .{@tagName(self.inst.opcode)}), |
| 422 | 324 | }; |
| 423 | 325 | |
| 424 | const ref = try self.spv.resolveType(ty); | |
| 425 | 326 | return AsmValue{ .ty = ref }; |
| 426 | 327 | } |
| 427 | 328 | |
| ... | ... | @@ -528,7 +429,7 @@ fn resolveRef(self: *Assembler, ref: AsmValue.Ref) !AsmValue { |
| 528 | 429 | } |
| 529 | 430 | |
| 530 | 431 | /// Resolve a value reference as type. |
| 531 | fn resolveTypeRef(self: *Assembler, ref: AsmValue.Ref) !SpvType.Ref { | |
| 432 | fn resolveTypeRef(self: *Assembler, ref: AsmValue.Ref) !CacheRef { | |
| 532 | 433 | const value = try self.resolveRef(ref); |
| 533 | 434 | switch (value) { |
| 534 | 435 | .just_declared, .unresolved_forward_reference => unreachable, |
| ... | ... | @@ -761,19 +662,20 @@ fn parseContextDependentNumber(self: *Assembler) !void { |
| 761 | 662 | |
| 762 | 663 | const tok = self.currentToken(); |
| 763 | 664 | 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", .{}), | |
| 777 | 679 | } |
| 778 | 680 | } |
| 779 | 681 |
src/codegen/spirv/Module.zig+51-396| ... | ... | @@ -20,12 +20,13 @@ const IdResult = spec.IdResult; |
| 20 | 20 | const IdResultType = spec.IdResultType; |
| 21 | 21 | |
| 22 | 22 | const Section = @import("Section.zig"); |
| 23 | const Type = @import("type.zig").Type; | |
| 24 | pub const TypeConstantCache = @import("TypeConstantCache.zig"); | |
| 25 | 23 | |
| 26 | const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true); | |
| 24 | const Cache = @import("TypeConstantCache.zig"); | |
| 25 | pub const CacheKey = Cache.Key; | |
| 26 | pub const CacheRef = Cache.Ref; | |
| 27 | pub const CacheString = Cache.String; | |
| 27 | 28 | |
| 28 | /// 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. | |
| 29 | 30 | /// Commonly, the contents of this structure will be merged with the appropriate |
| 30 | 31 | /// sections of the module and re-used. Note that the SPIR-V module system makes |
| 31 | 32 | /// no attempt of compacting result-id's, so any Fn instance should ultimately |
| ... | ... | @@ -130,7 +131,7 @@ sections: struct { |
| 130 | 131 | /// From this section, OpLine and OpNoLine is allowed. |
| 131 | 132 | /// According to the SPIR-V documentation, this section normally |
| 132 | 133 | /// also holds type and constant instructions. These are managed |
| 133 | /// via the tc_cache instead, which is the sole structure that | |
| 134 | /// via the cache instead, which is the sole structure that | |
| 134 | 135 | /// manages that section. These will be inserted between this and |
| 135 | 136 | /// the previous section when emitting the final binary. |
| 136 | 137 | /// TODO: Do we need this section? Globals are also managed with another mechanism. |
| ... | ... | @@ -152,10 +153,9 @@ next_result_id: Word, |
| 152 | 153 | /// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource. |
| 153 | 154 | source_file_names: std.StringHashMapUnmanaged(IdRef) = .{}, |
| 154 | 155 | |
| 155 | type_cache: TypeCache = .{}, | |
| 156 | 156 | /// SPIR-V type- and constant cache. This structure is used to store information about these in a more |
| 157 | 157 | /// efficient manner. |
| 158 | tc_cache: TypeConstantCache = .{}, | |
| 158 | cache: Cache = .{}, | |
| 159 | 159 | |
| 160 | 160 | /// Set of Decls, referred to by Decl.Index. |
| 161 | 161 | decls: std.ArrayListUnmanaged(Decl) = .{}, |
| ... | ... | @@ -196,7 +196,7 @@ pub fn deinit(self: *Module) void { |
| 196 | 196 | self.sections.functions.deinit(self.gpa); |
| 197 | 197 | |
| 198 | 198 | self.source_file_names.deinit(self.gpa); |
| 199 | self.tc_cache.deinit(self); | |
| 199 | self.cache.deinit(self); | |
| 200 | 200 | |
| 201 | 201 | self.decls.deinit(self.gpa); |
| 202 | 202 | self.decl_deps.deinit(self.gpa); |
| ... | ... | @@ -223,20 +223,20 @@ pub fn idBound(self: Module) Word { |
| 223 | 223 | return self.next_result_id; |
| 224 | 224 | } |
| 225 | 225 | |
| 226 | pub fn resolve(self: *Module, key: TypeConstantCache.Key) !TypeConstantCache.Ref { | |
| 227 | return self.tc_cache.resolve(self, key); | |
| 226 | pub fn resolve(self: *Module, key: CacheKey) !CacheRef { | |
| 227 | return self.cache.resolve(self, key); | |
| 228 | 228 | } |
| 229 | 229 | |
| 230 | pub fn resultId(self: *Module, ref: TypeConstantCache.Ref) IdResult { | |
| 231 | return self.tc_cache.resultId(ref); | |
| 230 | pub fn resultId(self: *const Module, ref: CacheRef) IdResult { | |
| 231 | return self.cache.resultId(ref); | |
| 232 | 232 | } |
| 233 | 233 | |
| 234 | pub fn resolveId(self: *Module, key: TypeConstantCache.Key) !IdResult { | |
| 234 | pub fn resolveId(self: *Module, key: CacheKey) !IdResult { | |
| 235 | 235 | return self.resultId(try self.resolve(key)); |
| 236 | 236 | } |
| 237 | 237 | |
| 238 | pub fn resolveString(self: *Module, str: []const u8) !TypeConstantCache.String { | |
| 239 | return try self.tc_cache.addString(self, str); | |
| 238 | pub fn resolveString(self: *Module, str: []const u8) !CacheString { | |
| 239 | return try self.cache.addString(self, str); | |
| 240 | 240 | } |
| 241 | 241 | |
| 242 | 242 | fn orderGlobalsInto( |
| ... | ... | @@ -350,7 +350,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void { |
| 350 | 350 | var entry_points = try self.entryPoints(); |
| 351 | 351 | defer entry_points.deinit(self.gpa); |
| 352 | 352 | |
| 353 | var types_constants = try self.tc_cache.materialize(self); | |
| 353 | var types_constants = try self.cache.materialize(self); | |
| 354 | 354 | defer types_constants.deinit(self.gpa); |
| 355 | 355 | |
| 356 | 356 | // Note: needs to be kept in order according to section 2.3! |
| ... | ... | @@ -364,6 +364,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void { |
| 364 | 364 | self.sections.debug_names.toWords(), |
| 365 | 365 | self.sections.annotations.toWords(), |
| 366 | 366 | types_constants.toWords(), |
| 367 | self.sections.types_globals_constants.toWords(), | |
| 367 | 368 | self.sections.globals.toWords(), |
| 368 | 369 | globals.toWords(), |
| 369 | 370 | self.sections.functions.toWords(), |
| ... | ... | @@ -416,364 +417,14 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef { |
| 416 | 417 | return result.value_ptr.*; |
| 417 | 418 | } |
| 418 | 419 | |
| 419 | /// Fetch a result-id for a spir-v type. This function deduplicates the type as appropriate, | |
| 420 | /// and returns a cached version if that exists. | |
| 421 | /// Note: This function does not attempt to perform any validation on the type. | |
| 422 | /// The type is emitted in a shallow fashion; any child types should already | |
| 423 | /// be emitted at this point. | |
| 424 | pub fn resolveType(self: *Module, ty: Type) !Type.Ref { | |
| 425 | const result = try self.type_cache.getOrPut(self.gpa, ty); | |
| 426 | const index = @intToEnum(Type.Ref, result.index); | |
| 427 | ||
| 428 | if (!result.found_existing) { | |
| 429 | const ref = try self.emitType(ty); | |
| 430 | self.type_cache.values()[result.index] = ref; | |
| 431 | } | |
| 432 | ||
| 433 | return index; | |
| 434 | } | |
| 435 | ||
| 436 | pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType { | |
| 437 | const ty_ref = try self.resolveType(ty); | |
| 438 | return self.typeId(ty_ref); | |
| 439 | } | |
| 440 | ||
| 441 | pub fn typeRefType(self: Module, ty_ref: Type.Ref) Type { | |
| 442 | return self.type_cache.keys()[@enumToInt(ty_ref)]; | |
| 443 | } | |
| 444 | ||
| 445 | /// Get the result-id of a particular type, by reference. Asserts type_ref is valid. | |
| 446 | pub fn typeId(self: Module, ty_ref: Type.Ref) IdResultType { | |
| 447 | return self.type_cache.values()[@enumToInt(ty_ref)]; | |
| 448 | } | |
| 449 | ||
| 450 | /// Unconditionally emit a spir-v type into the appropriate section. | |
| 451 | /// Note: If this function is called with a type that is already generated, it may yield an invalid module | |
| 452 | /// as non-pointer non-aggregrate types must me unique! | |
| 453 | /// Note: This function does not attempt to perform any validation on the type. | |
| 454 | /// The type is emitted in a shallow fashion; any child types should already | |
| 455 | /// be emitted at this point. | |
| 456 | pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { | |
| 457 | const result_id = self.allocId(); | |
| 458 | const ref_id = result_id; | |
| 459 | const types = &self.sections.types_globals_constants; | |
| 460 | const debug_names = &self.sections.debug_names; | |
| 461 | const result_id_operand = .{ .id_result = result_id }; | |
| 462 | ||
| 463 | switch (ty.tag()) { | |
| 464 | .void => { | |
| 465 | try types.emit(self.gpa, .OpTypeVoid, result_id_operand); | |
| 466 | try debug_names.emit(self.gpa, .OpName, .{ | |
| 467 | .target = result_id, | |
| 468 | .name = "void", | |
| 469 | }); | |
| 470 | }, | |
| 471 | .bool => { | |
| 472 | try types.emit(self.gpa, .OpTypeBool, result_id_operand); | |
| 473 | try debug_names.emit(self.gpa, .OpName, .{ | |
| 474 | .target = result_id, | |
| 475 | .name = "bool", | |
| 476 | }); | |
| 477 | }, | |
| 478 | .u8, | |
| 479 | .u16, | |
| 480 | .u32, | |
| 481 | .u64, | |
| 482 | .i8, | |
| 483 | .i16, | |
| 484 | .i32, | |
| 485 | .i64, | |
| 486 | .int, | |
| 487 | => { | |
| 488 | // TODO: Kernels do not support OpTypeInt that is signed. We can probably | |
| 489 | // can get rid of the signedness all together, in Shaders also. | |
| 490 | const bits = ty.intFloatBits(); | |
| 491 | const signedness: spec.LiteralInteger = switch (ty.intSignedness()) { | |
| 492 | .unsigned => 0, | |
| 493 | .signed => 1, | |
| 494 | }; | |
| 495 | ||
| 496 | try types.emit(self.gpa, .OpTypeInt, .{ | |
| 497 | .id_result = result_id, | |
| 498 | .width = bits, | |
| 499 | .signedness = signedness, | |
| 500 | }); | |
| 501 | ||
| 502 | const ui: []const u8 = switch (signedness) { | |
| 503 | 0 => "u", | |
| 504 | 1 => "i", | |
| 505 | else => unreachable, | |
| 506 | }; | |
| 507 | const name = try std.fmt.allocPrint(self.gpa, "{s}{}", .{ ui, bits }); | |
| 508 | defer self.gpa.free(name); | |
| 509 | ||
| 510 | try debug_names.emit(self.gpa, .OpName, .{ | |
| 511 | .target = result_id, | |
| 512 | .name = name, | |
| 513 | }); | |
| 514 | }, | |
| 515 | .f16, .f32, .f64 => { | |
| 516 | const bits = ty.intFloatBits(); | |
| 517 | try types.emit(self.gpa, .OpTypeFloat, .{ | |
| 518 | .id_result = result_id, | |
| 519 | .width = bits, | |
| 520 | }); | |
| 521 | ||
| 522 | const name = try std.fmt.allocPrint(self.gpa, "f{}", .{bits}); | |
| 523 | defer self.gpa.free(name); | |
| 524 | try debug_names.emit(self.gpa, .OpName, .{ | |
| 525 | .target = result_id, | |
| 526 | .name = name, | |
| 527 | }); | |
| 528 | }, | |
| 529 | .vector => try types.emit(self.gpa, .OpTypeVector, .{ | |
| 530 | .id_result = result_id, | |
| 531 | .component_type = self.typeId(ty.childType()), | |
| 532 | .component_count = ty.payload(.vector).component_count, | |
| 533 | }), | |
| 534 | .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{ | |
| 535 | .id_result = result_id, | |
| 536 | .column_type = self.typeId(ty.childType()), | |
| 537 | .column_count = ty.payload(.matrix).column_count, | |
| 538 | }), | |
| 539 | .image => { | |
| 540 | const info = ty.payload(.image); | |
| 541 | try types.emit(self.gpa, .OpTypeImage, .{ | |
| 542 | .id_result = result_id, | |
| 543 | .sampled_type = self.typeId(ty.childType()), | |
| 544 | .dim = info.dim, | |
| 545 | .depth = @enumToInt(info.depth), | |
| 546 | .arrayed = @boolToInt(info.arrayed), | |
| 547 | .ms = @boolToInt(info.multisampled), | |
| 548 | .sampled = @enumToInt(info.sampled), | |
| 549 | .image_format = info.format, | |
| 550 | .access_qualifier = info.access_qualifier, | |
| 551 | }); | |
| 552 | }, | |
| 553 | .sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand), | |
| 554 | .sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{ | |
| 555 | .id_result = result_id, | |
| 556 | .image_type = self.typeId(ty.childType()), | |
| 557 | }), | |
| 558 | .array => { | |
| 559 | const info = ty.payload(.array); | |
| 560 | assert(info.length != 0); | |
| 561 | ||
| 562 | const size_type = Type.initTag(.u32); | |
| 563 | const size_type_id = try self.resolveTypeId(size_type); | |
| 564 | const length_id = self.allocId(); | |
| 565 | try self.emitConstant(size_type_id, length_id, .{ .uint32 = info.length }); | |
| 566 | ||
| 567 | try types.emit(self.gpa, .OpTypeArray, .{ | |
| 568 | .id_result = result_id, | |
| 569 | .element_type = self.typeId(ty.childType()), | |
| 570 | .length = length_id, | |
| 571 | }); | |
| 572 | if (info.array_stride != 0) { | |
| 573 | try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); | |
| 574 | } | |
| 575 | }, | |
| 576 | .runtime_array => { | |
| 577 | const info = ty.payload(.runtime_array); | |
| 578 | try types.emit(self.gpa, .OpTypeRuntimeArray, .{ | |
| 579 | .id_result = result_id, | |
| 580 | .element_type = self.typeId(ty.childType()), | |
| 581 | }); | |
| 582 | if (info.array_stride != 0) { | |
| 583 | try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); | |
| 584 | } | |
| 585 | }, | |
| 586 | .@"struct" => { | |
| 587 | const info = ty.payload(.@"struct"); | |
| 588 | try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len); | |
| 589 | types.writeOperand(IdResult, result_id); | |
| 590 | for (info.members) |member| { | |
| 591 | types.writeOperand(IdRef, self.typeId(member.ty)); | |
| 592 | } | |
| 593 | try self.decorateStruct(ref_id, info); | |
| 594 | }, | |
| 595 | .@"opaque" => try types.emit(self.gpa, .OpTypeOpaque, .{ | |
| 596 | .id_result = result_id, | |
| 597 | .literal_string = ty.payload(.@"opaque").name, | |
| 598 | }), | |
| 599 | .pointer => { | |
| 600 | const info = ty.payload(.pointer); | |
| 601 | try types.emit(self.gpa, .OpTypePointer, .{ | |
| 602 | .id_result = result_id, | |
| 603 | .storage_class = info.storage_class, | |
| 604 | .type = self.typeId(ty.childType()), | |
| 605 | }); | |
| 606 | if (info.array_stride != 0) { | |
| 607 | try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); | |
| 608 | } | |
| 609 | if (info.alignment != 0) { | |
| 610 | try self.decorate(ref_id, .{ .Alignment = .{ .alignment = info.alignment } }); | |
| 611 | } | |
| 612 | if (info.max_byte_offset) |max_byte_offset| { | |
| 613 | try self.decorate(ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } }); | |
| 614 | } | |
| 615 | }, | |
| 616 | .function => { | |
| 617 | const info = ty.payload(.function); | |
| 618 | try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len); | |
| 619 | types.writeOperand(IdResult, result_id); | |
| 620 | types.writeOperand(IdRef, self.typeId(info.return_type)); | |
| 621 | for (info.parameters) |parameter_type| { | |
| 622 | types.writeOperand(IdRef, self.typeId(parameter_type)); | |
| 623 | } | |
| 624 | }, | |
| 625 | .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand), | |
| 626 | .device_event => try types.emit(self.gpa, .OpTypeDeviceEvent, result_id_operand), | |
| 627 | .reserve_id => try types.emit(self.gpa, .OpTypeReserveId, result_id_operand), | |
| 628 | .queue => try types.emit(self.gpa, .OpTypeQueue, result_id_operand), | |
| 629 | .pipe => try types.emit(self.gpa, .OpTypePipe, .{ | |
| 630 | .id_result = result_id, | |
| 631 | .qualifier = ty.payload(.pipe).qualifier, | |
| 632 | }), | |
| 633 | .pipe_storage => try types.emit(self.gpa, .OpTypePipeStorage, result_id_operand), | |
| 634 | .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand), | |
| 635 | } | |
| 636 | ||
| 637 | return result_id; | |
| 638 | } | |
| 639 | ||
| 640 | fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void { | |
| 641 | const debug_names = &self.sections.debug_names; | |
| 642 | ||
| 643 | if (info.name.len != 0) { | |
| 644 | try debug_names.emit(self.gpa, .OpName, .{ | |
| 645 | .target = target, | |
| 646 | .name = info.name, | |
| 647 | }); | |
| 648 | } | |
| 649 | ||
| 650 | // Decorations for the struct type itself. | |
| 651 | if (info.decorations.block) | |
| 652 | try self.decorate(target, .Block); | |
| 653 | if (info.decorations.buffer_block) | |
| 654 | try self.decorate(target, .BufferBlock); | |
| 655 | if (info.decorations.glsl_shared) | |
| 656 | try self.decorate(target, .GLSLShared); | |
| 657 | if (info.decorations.glsl_packed) | |
| 658 | try self.decorate(target, .GLSLPacked); | |
| 659 | if (info.decorations.c_packed) | |
| 660 | try self.decorate(target, .CPacked); | |
| 661 | ||
| 662 | // Decorations for the struct members. | |
| 663 | const extra = info.member_decoration_extra; | |
| 664 | var extra_i: u32 = 0; | |
| 665 | for (info.members, 0..) |member, i| { | |
| 666 | const d = member.decorations; | |
| 667 | const index = @intCast(Word, i); | |
| 668 | ||
| 669 | if (member.name.len != 0) { | |
| 670 | try debug_names.emit(self.gpa, .OpMemberName, .{ | |
| 671 | .type = target, | |
| 672 | .member = index, | |
| 673 | .name = member.name, | |
| 674 | }); | |
| 675 | } | |
| 676 | ||
| 677 | switch (member.offset) { | |
| 678 | .none => {}, | |
| 679 | else => try self.decorateMember( | |
| 680 | target, | |
| 681 | index, | |
| 682 | .{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } }, | |
| 683 | ), | |
| 684 | } | |
| 685 | ||
| 686 | switch (d.matrix_layout) { | |
| 687 | .row_major => try self.decorateMember(target, index, .RowMajor), | |
| 688 | .col_major => try self.decorateMember(target, index, .ColMajor), | |
| 689 | .none => {}, | |
| 690 | } | |
| 691 | if (d.matrix_layout != .none) { | |
| 692 | try self.decorateMember(target, index, .{ | |
| 693 | .MatrixStride = .{ .matrix_stride = extra[extra_i] }, | |
| 694 | }); | |
| 695 | extra_i += 1; | |
| 696 | } | |
| 697 | ||
| 698 | if (d.no_perspective) | |
| 699 | try self.decorateMember(target, index, .NoPerspective); | |
| 700 | if (d.flat) | |
| 701 | try self.decorateMember(target, index, .Flat); | |
| 702 | if (d.patch) | |
| 703 | try self.decorateMember(target, index, .Patch); | |
| 704 | if (d.centroid) | |
| 705 | try self.decorateMember(target, index, .Centroid); | |
| 706 | if (d.sample) | |
| 707 | try self.decorateMember(target, index, .Sample); | |
| 708 | if (d.invariant) | |
| 709 | try self.decorateMember(target, index, .Invariant); | |
| 710 | if (d.@"volatile") | |
| 711 | try self.decorateMember(target, index, .Volatile); | |
| 712 | if (d.coherent) | |
| 713 | try self.decorateMember(target, index, .Coherent); | |
| 714 | if (d.non_writable) | |
| 715 | try self.decorateMember(target, index, .NonWritable); | |
| 716 | if (d.non_readable) | |
| 717 | try self.decorateMember(target, index, .NonReadable); | |
| 718 | ||
| 719 | if (d.builtin) { | |
| 720 | try self.decorateMember(target, index, .{ | |
| 721 | .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) }, | |
| 722 | }); | |
| 723 | extra_i += 1; | |
| 724 | } | |
| 725 | if (d.stream) { | |
| 726 | try self.decorateMember(target, index, .{ | |
| 727 | .Stream = .{ .stream_number = extra[extra_i] }, | |
| 728 | }); | |
| 729 | extra_i += 1; | |
| 730 | } | |
| 731 | if (d.location) { | |
| 732 | try self.decorateMember(target, index, .{ | |
| 733 | .Location = .{ .location = extra[extra_i] }, | |
| 734 | }); | |
| 735 | extra_i += 1; | |
| 736 | } | |
| 737 | if (d.component) { | |
| 738 | try self.decorateMember(target, index, .{ | |
| 739 | .Component = .{ .component = extra[extra_i] }, | |
| 740 | }); | |
| 741 | extra_i += 1; | |
| 742 | } | |
| 743 | if (d.xfb_buffer) { | |
| 744 | try self.decorateMember(target, index, .{ | |
| 745 | .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] }, | |
| 746 | }); | |
| 747 | extra_i += 1; | |
| 748 | } | |
| 749 | if (d.xfb_stride) { | |
| 750 | try self.decorateMember(target, index, .{ | |
| 751 | .XfbStride = .{ .xfb_stride = extra[extra_i] }, | |
| 752 | }); | |
| 753 | extra_i += 1; | |
| 754 | } | |
| 755 | if (d.user_semantic) { | |
| 756 | const len = extra[extra_i]; | |
| 757 | extra_i += 1; | |
| 758 | const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len]; | |
| 759 | try self.decorateMember(target, index, .{ | |
| 760 | .UserSemantic = .{ .semantic = semantic }, | |
| 761 | }); | |
| 762 | extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable; | |
| 763 | } | |
| 764 | } | |
| 765 | } | |
| 766 | ||
| 767 | pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Member) !Type.Ref { | |
| 768 | const payload = try self.arena.create(Type.Payload.Struct); | |
| 769 | payload.* = .{ | |
| 770 | .members = try self.arena.dupe(Type.Payload.Struct.Member, members), | |
| 771 | .decorations = .{}, | |
| 772 | }; | |
| 773 | return try self.resolveType(Type.initPayload(&payload.base)); | |
| 420 | pub fn intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !CacheRef { | |
| 421 | return try self.resolve(.{ .int_type = .{ | |
| 422 | .signedness = signedness, | |
| 423 | .bits = bits, | |
| 424 | } }); | |
| 774 | 425 | } |
| 775 | 426 | |
| 776 | pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) !TypeConstantCache.Ref { | |
| 427 | pub fn arrayType(self: *Module, len: u32, elem_ty_ref: CacheRef) !CacheRef { | |
| 777 | 428 | const len_ty_ref = try self.resolve(.{ .int_type = .{ |
| 778 | 429 | .signedness = .unsigned, |
| 779 | 430 | .bits = 32, |
| ... | ... | @@ -788,41 +439,45 @@ pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) ! |
| 788 | 439 | } }); |
| 789 | 440 | } |
| 790 | 441 | |
| 791 | pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref { | |
| 792 | const payload = try self.arena.create(Type.Payload.Array); | |
| 793 | payload.* = .{ | |
| 794 | .element_type = ty, | |
| 795 | .length = len, | |
| 796 | }; | |
| 797 | return try self.resolveType(Type.initPayload(&payload.base)); | |
| 798 | } | |
| 799 | ||
| 800 | 442 | pub fn ptrType( |
| 801 | 443 | self: *Module, |
| 802 | child: Type.Ref, | |
| 444 | child: CacheRef, | |
| 803 | 445 | storage_class: spec.StorageClass, |
| 804 | alignment: u32, | |
| 805 | ) !Type.Ref { | |
| 806 | const ptr_payload = try self.arena.create(Type.Payload.Pointer); | |
| 807 | ptr_payload.* = .{ | |
| 446 | ) !CacheRef { | |
| 447 | return try self.resolve(.{ .ptr_type = .{ | |
| 808 | 448 | .storage_class = storage_class, |
| 809 | 449 | .child_type = child, |
| 810 | .alignment = alignment, | |
| 811 | }; | |
| 812 | return try self.resolveType(Type.initPayload(&ptr_payload.base)); | |
| 450 | } }); | |
| 451 | } | |
| 452 | ||
| 453 | pub fn constInt(self: *Module, ty_ref: CacheRef, value: anytype) !IdRef { | |
| 454 | const ty = self.cache.lookup(ty_ref).int_type; | |
| 455 | const Value = Cache.Key.Int.Value; | |
| 456 | return try self.resolveId(.{ .int = .{ | |
| 457 | .ty = ty_ref, | |
| 458 | .value = switch (ty.signedness) { | |
| 459 | .signed => Value{ .int64 = @intCast(i64, value) }, | |
| 460 | .unsigned => Value{ .uint64 = @intCast(u64, value) }, | |
| 461 | }, | |
| 462 | } }); | |
| 463 | } | |
| 464 | ||
| 465 | pub fn constUndef(self: *Module, ty_ref: CacheRef) !IdRef { | |
| 466 | return try self.resolveId(.{ .undef = .{ .ty = ty_ref } }); | |
| 467 | } | |
| 468 | ||
| 469 | pub fn constNull(self: *Module, ty_ref: CacheRef) !IdRef { | |
| 470 | return try self.resolveId(.{ .null = .{ .ty = ty_ref } }); | |
| 813 | 471 | } |
| 814 | 472 | |
| 815 | pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref { | |
| 816 | const payload = try self.arena.create(Type.Payload.Pointer); | |
| 817 | payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*; | |
| 818 | payload.storage_class = new_storage_class; | |
| 819 | return try self.resolveType(Type.initPayload(&payload.base)); | |
| 473 | pub fn constBool(self: *Module, ty_ref: CacheRef, value: bool) !IdRef { | |
| 474 | return try self.resolveId(.{ .bool = .{ .ty = ty_ref, .value = value } }); | |
| 820 | 475 | } |
| 821 | 476 | |
| 822 | pub fn constComposite(self: *Module, ty_ref: Type.Ref, members: []const IdRef) !IdRef { | |
| 477 | pub fn constComposite(self: *Module, ty_ref: CacheRef, members: []const IdRef) !IdRef { | |
| 823 | 478 | const result_id = self.allocId(); |
| 824 | 479 | try self.sections.types_globals_constants.emit(self.gpa, .OpSpecConstantComposite, .{ |
| 825 | .id_result_type = self.typeId(ty_ref), | |
| 480 | .id_result_type = self.resultId(ty_ref), | |
| 826 | 481 | .id_result = result_id, |
| 827 | 482 | .constituents = members, |
| 828 | 483 | }); |
src/codegen/spirv/TypeConstantCache.zig+86| ... | ... | @@ -111,6 +111,18 @@ const Tag = enum { |
| 111 | 111 | /// Value of type f64 |
| 112 | 112 | /// data is payload to Float16 |
| 113 | 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, | |
| 114 | 126 | |
| 115 | 127 | const SimpleType = enum { void, bool }; |
| 116 | 128 | |
| ... | ... | @@ -227,6 +239,9 @@ pub const Key = union(enum) { |
| 227 | 239 | // -- values |
| 228 | 240 | int: Int, |
| 229 | 241 | float: Float, |
| 242 | undef: Undef, | |
| 243 | null: Null, | |
| 244 | bool: Bool, | |
| 230 | 245 | |
| 231 | 246 | pub const IntType = std.builtin.Type.Int; |
| 232 | 247 | pub const FloatType = std.builtin.Type.Float; |
| ... | ... | @@ -323,6 +338,19 @@ pub const Key = union(enum) { |
| 323 | 338 | }; |
| 324 | 339 | }; |
| 325 | 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 | ||
| 326 | 354 | fn hash(self: Key) u32 { |
| 327 | 355 | var hasher = std.hash.Wyhash.init(0); |
| 328 | 356 | switch (self) { |
| ... | ... | @@ -539,6 +567,32 @@ fn emit( |
| 539 | 567 | .value = lit, |
| 540 | 568 | }); |
| 541 | 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 | }, | |
| 542 | 596 | } |
| 543 | 597 | } |
| 544 | 598 | |
| ... | ... | @@ -713,6 +767,24 @@ pub fn resolve(self: *Self, spv: *Module, key: Key) !Ref { |
| 713 | 767 | }, |
| 714 | 768 | else => unreachable, |
| 715 | 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 | }, | |
| 716 | 788 | }; |
| 717 | 789 | try self.items.append(spv.gpa, item); |
| 718 | 790 | |
| ... | ... | @@ -850,6 +922,20 @@ pub fn lookup(self: *const Self, ref: Ref) Key { |
| 850 | 922 | .value = .{ .uint64 = payload.decode() }, |
| 851 | 923 | } }; |
| 852 | 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 | } }, | |
| 853 | 939 | }; |
| 854 | 940 | } |
| 855 | 941 |
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 | ||
| 4 | const std = @import("std"); | |
| 5 | const assert = std.debug.assert; | |
| 6 | const Signedness = std.builtin.Signedness; | |
| 7 | const Allocator = std.mem.Allocator; | |
| 8 | ||
| 9 | const spec = @import("spec.zig"); | |
| 10 | ||
| 11 | pub 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 | }; |