authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-05-29 23:57:36+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-05-30 19:43:37+02:00
log0c41945a01a3a5254e8b4266a0677f2d8224aa1a
tree5d096152fa91ad2990c1563c8d783a8dcedd3849
parent0552a8b11f973fc9621971e2130c25ad3a4af0ad
signaturelock-open Commit is signed but in an unrecognized format.

spirv: rename TypeConstantCache -> Cache


3 files changed, 1047 insertions(+), 1047 deletions(-)

src/codegen/spirv/Cache.zig created+1046
...@@ -0,0 +1,1046 @@
1//! This file implements an InternPool-like structure that caches
2//! SPIR-V types and constants. Instead of generating type and
3//! constant instructions directly, we first keep a representation
4//! in a compressed database. This is then only later turned into
5//! actual SPIR-V instructions.
6//! Note: This cache is insertion-ordered. This means that we
7//! can materialize the SPIR-V instructions in the proper order,
8//! as SPIR-V requires that the type is emitted before use.
9//! Note: According to SPIR-V spec section 2.8, Types and Variables,
10//! non-pointer non-aggrerate types (which includes matrices and
11//! vectors) must have a _unique_ representation in the final binary.
12
13const std = @import("std");
14const assert = std.debug.assert;
15const Allocator = std.mem.Allocator;
16
17const Section = @import("Section.zig");
18const Module = @import("Module.zig");
19
20const spec = @import("spec.zig");
21const Opcode = spec.Opcode;
22const IdResult = spec.IdResult;
23const StorageClass = spec.StorageClass;
24
25const Self = @This();
26
27map: std.AutoArrayHashMapUnmanaged(void, void) = .{},
28items: std.MultiArrayList(Item) = .{},
29extra: std.ArrayListUnmanaged(u32) = .{},
30
31string_bytes: std.ArrayListUnmanaged(u8) = .{},
32strings: std.AutoArrayHashMapUnmanaged(void, u32) = .{},
33
34const Item = struct {
35 tag: Tag,
36 /// The result-id that this item uses.
37 result_id: IdResult,
38 /// The Tag determines how this should be interpreted.
39 data: u32,
40};
41
42const Tag = enum {
43 // -- Types
44 /// Simple type that has no additional data.
45 /// data is SimpleType.
46 type_simple,
47 /// Signed integer type
48 /// data is number of bits
49 type_int_signed,
50 /// Unsigned integer type
51 /// data is number of bits
52 type_int_unsigned,
53 /// Floating point type
54 /// data is number of bits
55 type_float,
56 /// Vector type
57 /// data is payload to VectorType
58 type_vector,
59 /// Array type
60 /// data is payload to ArrayType
61 type_array,
62 /// Function (proto)type
63 /// data is payload to FunctionType
64 type_function,
65 /// Pointer type in the CrossWorkgroup storage class
66 /// data is child type
67 type_ptr_generic,
68 /// Pointer type in the CrossWorkgroup storage class
69 /// data is child type
70 type_ptr_crosswgp,
71 /// Pointer type in the Function storage class
72 /// data is child type
73 type_ptr_function,
74 /// Simple pointer type that does not have any decorations.
75 /// data is payload to SimplePointerType
76 type_ptr_simple,
77 /// Simple structure type that does not have any decorations.
78 /// data is payload to SimpleStructType
79 type_struct_simple,
80 /// Simple structure type that does not have any decorations, but does
81 /// have member names trailing.
82 /// data is payload to SimpleStructType
83 type_struct_simple_with_member_names,
84
85 // -- Values
86 /// Value of type u8
87 /// data is value
88 uint8,
89 /// Value of type u32
90 /// data is value
91 uint32,
92 // TODO: More specialized tags here.
93 /// Integer value for signed values that are smaller than 32 bits.
94 /// data is pointer to Int32
95 int_small,
96 /// Integer value for unsigned values that are smaller than 32 bits.
97 /// data is pointer to UInt32
98 uint_small,
99 /// Integer value for signed values that are beteen 32 and 64 bits.
100 /// data is pointer to Int64
101 int_large,
102 /// Integer value for unsinged values that are beteen 32 and 64 bits.
103 /// data is pointer to UInt64
104 uint_large,
105 /// Value of type f16
106 /// data is value
107 float16,
108 /// Value of type f32
109 /// data is value
110 float32,
111 /// Value of type f64
112 /// data is payload to Float16
113 float64,
114 /// Undefined value
115 /// data is type
116 undef,
117 /// Null value
118 /// data is type
119 null,
120 /// Bool value that is true
121 /// data is (bool) type
122 bool_true,
123 /// Bool value that is false
124 /// data is (bool) type
125 bool_false,
126
127 const SimpleType = enum { void, bool };
128
129 const VectorType = Key.VectorType;
130 const ArrayType = Key.ArrayType;
131
132 // Trailing:
133 // - [param_len]Ref: parameter types.
134 const FunctionType = struct {
135 param_len: u32,
136 return_type: Ref,
137 };
138
139 const SimplePointerType = struct {
140 storage_class: StorageClass,
141 child_type: Ref,
142 };
143
144 /// Trailing:
145 /// - [members_len]Ref: Member types.
146 /// - [members_len]String: Member names, -- ONLY if the tag is type_struct_simple_with_member_names
147 const SimpleStructType = struct {
148 /// (optional) The name of the struct.
149 name: String,
150 /// Number of members that this struct has.
151 members_len: u32,
152 };
153
154 const Float64 = struct {
155 // Low-order 32 bits of the value.
156 low: u32,
157 // High-order 32 bits of the value.
158 high: u32,
159
160 fn encode(value: f64) Float64 {
161 const bits = @bitCast(u64, value);
162 return .{
163 .low = @truncate(u32, bits),
164 .high = @truncate(u32, bits >> 32),
165 };
166 }
167
168 fn decode(self: Float64) f64 {
169 const bits = @as(u64, self.low) | (@as(u64, self.high) << 32);
170 return @bitCast(f64, bits);
171 }
172 };
173
174 const Int32 = struct {
175 ty: Ref,
176 value: i32,
177 };
178
179 const UInt32 = struct {
180 ty: Ref,
181 value: u32,
182 };
183
184 const UInt64 = struct {
185 ty: Ref,
186 low: u32,
187 high: u32,
188
189 fn encode(ty: Ref, value: u64) Int64 {
190 return .{
191 .ty = ty,
192 .low = @truncate(u32, value),
193 .high = @truncate(u32, value >> 32),
194 };
195 }
196
197 fn decode(self: UInt64) u64 {
198 return @as(u64, self.low) | (@as(u64, self.high) << 32);
199 }
200 };
201
202 const Int64 = struct {
203 ty: Ref,
204 low: u32,
205 high: u32,
206
207 fn encode(ty: Ref, value: i64) Int64 {
208 return .{
209 .ty = ty,
210 .low = @truncate(u32, @bitCast(u64, value)),
211 .high = @truncate(u32, @bitCast(u64, value) >> 32),
212 };
213 }
214
215 fn decode(self: Int64) i64 {
216 return @bitCast(i64, @as(u64, self.low) | (@as(u64, self.high) << 32));
217 }
218 };
219};
220
221pub const Ref = enum(u32) { _ };
222
223/// This union represents something that can be interned. This includes
224/// types and constants. This structure is used for interfacing with the
225/// database: Values described for this structure are ephemeral and stored
226/// in a more memory-efficient manner internally.
227pub const Key = union(enum) {
228 // -- Types
229 void_type,
230 bool_type,
231 int_type: IntType,
232 float_type: FloatType,
233 vector_type: VectorType,
234 array_type: ArrayType,
235 function_type: FunctionType,
236 ptr_type: PointerType,
237 struct_type: StructType,
238
239 // -- values
240 int: Int,
241 float: Float,
242 undef: Undef,
243 null: Null,
244 bool: Bool,
245
246 pub const IntType = std.builtin.Type.Int;
247 pub const FloatType = std.builtin.Type.Float;
248
249 pub const VectorType = struct {
250 component_type: Ref,
251 component_count: u32,
252 };
253
254 pub const ArrayType = struct {
255 /// Child type of this array.
256 element_type: Ref,
257 /// Reference to a constant.
258 length: Ref,
259 /// Type has the 'ArrayStride' decoration.
260 /// If zero, no stride is present.
261 stride: u32 = 0,
262 };
263
264 pub const FunctionType = struct {
265 return_type: Ref,
266 parameters: []const Ref,
267 };
268
269 pub const PointerType = struct {
270 storage_class: StorageClass,
271 child_type: Ref,
272 // TODO: Decorations:
273 // - Alignment
274 // - ArrayStride,
275 // - MaxByteOffset,
276 };
277
278 pub const StructType = struct {
279 // TODO: Decorations.
280 /// The name of the structure. Can be `.none`.
281 name: String = .none,
282 /// The type of each member.
283 member_types: []const Ref,
284 /// Name for each member. May be omitted.
285 member_names: ?[]const String = null,
286
287 fn memberNames(self: @This()) []const String {
288 return if (self.member_names) |member_names| member_names else &.{};
289 }
290 };
291
292 pub const Int = struct {
293 /// The type: any bitness integer.
294 ty: Ref,
295 /// The actual value. Only uint64 and int64 types
296 /// are available here: Smaller types should use these
297 /// fields.
298 value: Value,
299
300 pub const Value = union(enum) {
301 uint64: u64,
302 int64: i64,
303 };
304
305 /// Turns this value into the corresponding 32-bit literal, 2s complement signed.
306 fn toBits32(self: Int) u32 {
307 return switch (self.value) {
308 .uint64 => |val| @intCast(u32, val),
309 .int64 => |val| if (val < 0) @bitCast(u32, @intCast(i32, val)) else @intCast(u32, val),
310 };
311 }
312
313 fn toBits64(self: Int) u64 {
314 return switch (self.value) {
315 .uint64 => |val| val,
316 .int64 => |val| @bitCast(u64, val),
317 };
318 }
319
320 fn to(self: Int, comptime T: type) T {
321 return switch (self.value) {
322 inline else => |val| @intCast(T, val),
323 };
324 }
325 };
326
327 /// Represents a numberic value of some type.
328 pub const Float = struct {
329 /// The type: 16, 32, or 64-bit float.
330 ty: Ref,
331 /// The actual value.
332 value: Value,
333
334 pub const Value = union(enum) {
335 float16: f16,
336 float32: f32,
337 float64: f64,
338 };
339 };
340
341 pub const Undef = struct {
342 ty: Ref,
343 };
344
345 pub const Null = struct {
346 ty: Ref,
347 };
348
349 pub const Bool = struct {
350 ty: Ref,
351 value: bool,
352 };
353
354 fn hash(self: Key) u32 {
355 var hasher = std.hash.Wyhash.init(0);
356 switch (self) {
357 .float => |float| {
358 std.hash.autoHash(&hasher, float.ty);
359 switch (float.value) {
360 .float16 => |value| std.hash.autoHash(&hasher, @bitCast(u16, value)),
361 .float32 => |value| std.hash.autoHash(&hasher, @bitCast(u32, value)),
362 .float64 => |value| std.hash.autoHash(&hasher, @bitCast(u64, value)),
363 }
364 },
365 .function_type => |func| {
366 std.hash.autoHash(&hasher, func.return_type);
367 for (func.parameters) |param_type| {
368 std.hash.autoHash(&hasher, param_type);
369 }
370 },
371 .struct_type => |struct_type| {
372 std.hash.autoHash(&hasher, struct_type.name);
373 for (struct_type.member_types) |member_type| {
374 std.hash.autoHash(&hasher, member_type);
375 }
376 for (struct_type.memberNames()) |member_name| {
377 std.hash.autoHash(&hasher, member_name);
378 }
379 },
380 inline else => |key| std.hash.autoHash(&hasher, key),
381 }
382 return @truncate(u32, hasher.final());
383 }
384
385 fn eql(a: Key, b: Key) bool {
386 const KeyTag = @typeInfo(Key).Union.tag_type.?;
387 const a_tag: KeyTag = a;
388 const b_tag: KeyTag = b;
389 if (a_tag != b_tag) {
390 return false;
391 }
392 return switch (a) {
393 .function_type => |a_func| {
394 const b_func = b.function_type;
395 return a_func.return_type == b_func.return_type and
396 std.mem.eql(Ref, a_func.parameters, b_func.parameters);
397 },
398 .struct_type => |a_struct| {
399 const b_struct = b.struct_type;
400 return a_struct.name == b_struct.name and
401 std.mem.eql(Ref, a_struct.member_types, b_struct.member_types) and
402 std.mem.eql(String, a_struct.memberNames(), b_struct.memberNames());
403 },
404 // TODO: Unroll?
405 else => std.meta.eql(a, b),
406 };
407 }
408
409 pub const Adapter = struct {
410 self: *const Self,
411
412 pub fn eql(ctx: @This(), a: Key, b_void: void, b_index: usize) bool {
413 _ = b_void;
414 return ctx.self.lookup(@intToEnum(Ref, b_index)).eql(a);
415 }
416
417 pub fn hash(ctx: @This(), a: Key) u32 {
418 _ = ctx;
419 return a.hash();
420 }
421 };
422
423 fn toSimpleType(self: Key) Tag.SimpleType {
424 return switch (self) {
425 .void_type => .void,
426 .bool_type => .bool,
427 else => unreachable,
428 };
429 }
430};
431
432pub fn deinit(self: *Self, spv: *const Module) void {
433 self.map.deinit(spv.gpa);
434 self.items.deinit(spv.gpa);
435 self.extra.deinit(spv.gpa);
436 self.string_bytes.deinit(spv.gpa);
437 self.strings.deinit(spv.gpa);
438}
439
440/// Actually materialize the database into spir-v instructions.
441/// This function returns a spir-v section of (only) constant and type instructions.
442/// Additionally, decorations, debug names, etc, are all directly emitted into the
443/// `spv` module. The section is allocated with `spv.gpa`.
444pub fn materialize(self: *const Self, spv: *Module) !Section {
445 var section = Section{};
446 errdefer section.deinit(spv.gpa);
447 for (self.items.items(.result_id), 0..) |result_id, index| {
448 try self.emit(spv, result_id, @intToEnum(Ref, index), &section);
449 }
450 return section;
451}
452
453fn emit(
454 self: *const Self,
455 spv: *Module,
456 result_id: IdResult,
457 ref: Ref,
458 section: *Section,
459) !void {
460 const key = self.lookup(ref);
461 const Lit = spec.LiteralContextDependentNumber;
462 switch (key) {
463 .void_type => {
464 try section.emit(spv.gpa, .OpTypeVoid, .{ .id_result = result_id });
465 try spv.debugName(result_id, "void", .{});
466 },
467 .bool_type => {
468 try section.emit(spv.gpa, .OpTypeBool, .{ .id_result = result_id });
469 try spv.debugName(result_id, "bool", .{});
470 },
471 .int_type => |int| {
472 try section.emit(spv.gpa, .OpTypeInt, .{
473 .id_result = result_id,
474 .width = int.bits,
475 .signedness = switch (int.signedness) {
476 .unsigned => @as(spec.Word, 0),
477 .signed => 1,
478 },
479 });
480 const ui: []const u8 = switch (int.signedness) {
481 .unsigned => "u",
482 .signed => "i",
483 };
484 try spv.debugName(result_id, "{s}{}", .{ ui, int.bits });
485 },
486 .float_type => |float| {
487 try section.emit(spv.gpa, .OpTypeFloat, .{
488 .id_result = result_id,
489 .width = float.bits,
490 });
491 try spv.debugName(result_id, "f{}", .{float.bits});
492 },
493 .vector_type => |vector| {
494 try section.emit(spv.gpa, .OpTypeVector, .{
495 .id_result = result_id,
496 .component_type = self.resultId(vector.component_type),
497 .component_count = vector.component_count,
498 });
499 },
500 .array_type => |array| {
501 try section.emit(spv.gpa, .OpTypeArray, .{
502 .id_result = result_id,
503 .element_type = self.resultId(array.element_type),
504 .length = self.resultId(array.length),
505 });
506 if (array.stride != 0) {
507 try spv.decorate(result_id, .{ .ArrayStride = .{ .array_stride = array.stride } });
508 }
509 },
510 .function_type => |function| {
511 try section.emitRaw(spv.gpa, .OpTypeFunction, 2 + function.parameters.len);
512 section.writeOperand(IdResult, result_id);
513 section.writeOperand(IdResult, self.resultId(function.return_type));
514 for (function.parameters) |param_type| {
515 section.writeOperand(IdResult, self.resultId(param_type));
516 }
517 },
518 .ptr_type => |ptr| {
519 try section.emit(spv.gpa, .OpTypePointer, .{
520 .id_result = result_id,
521 .storage_class = ptr.storage_class,
522 .type = self.resultId(ptr.child_type),
523 });
524 // TODO: Decorations?
525 },
526 .struct_type => |struct_type| {
527 try section.emitRaw(spv.gpa, .OpTypeStruct, 1 + struct_type.member_types.len);
528 section.writeOperand(IdResult, result_id);
529 for (struct_type.member_types) |member_type| {
530 section.writeOperand(IdResult, self.resultId(member_type));
531 }
532 if (self.getString(struct_type.name)) |name| {
533 try spv.debugName(result_id, "{s}", .{name});
534 }
535 for (struct_type.memberNames(), 0..) |member_name, i| {
536 if (self.getString(member_name)) |name| {
537 try spv.memberDebugName(result_id, @intCast(u32, i), "{s}", .{name});
538 }
539 }
540 // TODO: Decorations?
541 },
542 .int => |int| {
543 const int_type = self.lookup(int.ty).int_type;
544 const ty_id = self.resultId(int.ty);
545 const lit: Lit = switch (int_type.bits) {
546 1...32 => .{ .uint32 = int.toBits32() },
547 33...64 => .{ .uint64 = int.toBits64() },
548 else => unreachable,
549 };
550
551 try section.emit(spv.gpa, .OpConstant, .{
552 .id_result_type = ty_id,
553 .id_result = result_id,
554 .value = lit,
555 });
556 },
557 .float => |float| {
558 const ty_id = self.resultId(float.ty);
559 const lit: Lit = switch (float.value) {
560 .float16 => |value| .{ .uint32 = @bitCast(u16, value) },
561 .float32 => |value| .{ .float32 = value },
562 .float64 => |value| .{ .float64 = value },
563 };
564 try section.emit(spv.gpa, .OpConstant, .{
565 .id_result_type = ty_id,
566 .id_result = result_id,
567 .value = lit,
568 });
569 },
570 .undef => |undef| {
571 try section.emit(spv.gpa, .OpUndef, .{
572 .id_result_type = self.resultId(undef.ty),
573 .id_result = result_id,
574 });
575 },
576 .null => |null_info| {
577 try section.emit(spv.gpa, .OpConstantNull, .{
578 .id_result_type = self.resultId(null_info.ty),
579 .id_result = result_id,
580 });
581 },
582 .bool => |bool_info| switch (bool_info.value) {
583 true => {
584 try section.emit(spv.gpa, .OpConstantTrue, .{
585 .id_result_type = self.resultId(bool_info.ty),
586 .id_result = result_id,
587 });
588 },
589 false => {
590 try section.emit(spv.gpa, .OpConstantFalse, .{
591 .id_result_type = self.resultId(bool_info.ty),
592 .id_result = result_id,
593 });
594 },
595 },
596 }
597}
598
599/// Add a key to this cache. Returns a reference to the key that
600/// was added. The corresponding result-id can be queried using
601/// self.resultId with the result.
602pub fn resolve(self: *Self, spv: *Module, key: Key) !Ref {
603 const adapter: Key.Adapter = .{ .self = self };
604 const entry = try self.map.getOrPutAdapted(spv.gpa, key, adapter);
605 if (entry.found_existing) {
606 return @intToEnum(Ref, entry.index);
607 }
608 const result_id = spv.allocId();
609 const item: Item = switch (key) {
610 inline .void_type, .bool_type => .{
611 .tag = .type_simple,
612 .result_id = result_id,
613 .data = @enumToInt(key.toSimpleType()),
614 },
615 .int_type => |int| blk: {
616 const t: Tag = switch (int.signedness) {
617 .signed => .type_int_signed,
618 .unsigned => .type_int_unsigned,
619 };
620 break :blk .{
621 .tag = t,
622 .result_id = result_id,
623 .data = int.bits,
624 };
625 },
626 .float_type => |float| .{
627 .tag = .type_float,
628 .result_id = result_id,
629 .data = float.bits,
630 },
631 .vector_type => |vector| .{
632 .tag = .type_vector,
633 .result_id = result_id,
634 .data = try self.addExtra(spv, vector),
635 },
636 .array_type => |array| .{
637 .tag = .type_array,
638 .result_id = result_id,
639 .data = try self.addExtra(spv, array),
640 },
641 .function_type => |function| blk: {
642 const extra = try self.addExtra(spv, Tag.FunctionType{
643 .param_len = @intCast(u32, function.parameters.len),
644 .return_type = function.return_type,
645 });
646 try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, function.parameters));
647 break :blk .{
648 .tag = .type_function,
649 .result_id = result_id,
650 .data = extra,
651 };
652 },
653 .ptr_type => |ptr| switch (ptr.storage_class) {
654 .Generic => Item{
655 .tag = .type_ptr_generic,
656 .result_id = result_id,
657 .data = @enumToInt(ptr.child_type),
658 },
659 .CrossWorkgroup => Item{
660 .tag = .type_ptr_crosswgp,
661 .result_id = result_id,
662 .data = @enumToInt(ptr.child_type),
663 },
664 .Function => Item{
665 .tag = .type_ptr_function,
666 .result_id = result_id,
667 .data = @enumToInt(ptr.child_type),
668 },
669 else => |storage_class| Item{
670 .tag = .type_ptr_simple,
671 .result_id = result_id,
672 .data = try self.addExtra(spv, Tag.SimplePointerType{
673 .storage_class = storage_class,
674 .child_type = ptr.child_type,
675 }),
676 },
677 },
678 .struct_type => |struct_type| blk: {
679 const extra = try self.addExtra(spv, Tag.SimpleStructType{
680 .name = struct_type.name,
681 .members_len = @intCast(u32, struct_type.member_types.len),
682 });
683 try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, struct_type.member_types));
684
685 if (struct_type.member_names) |member_names| {
686 try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, member_names));
687 break :blk Item{
688 .tag = .type_struct_simple_with_member_names,
689 .result_id = result_id,
690 .data = extra,
691 };
692 } else {
693 break :blk Item{
694 .tag = .type_struct_simple,
695 .result_id = result_id,
696 .data = extra,
697 };
698 }
699 },
700 .int => |int| blk: {
701 const int_type = self.lookup(int.ty).int_type;
702 if (int_type.signedness == .unsigned and int_type.bits == 8) {
703 break :blk .{
704 .tag = .uint8,
705 .result_id = result_id,
706 .data = int.to(u8),
707 };
708 } else if (int_type.signedness == .unsigned and int_type.bits == 32) {
709 break :blk .{
710 .tag = .uint32,
711 .result_id = result_id,
712 .data = int.to(u32),
713 };
714 }
715
716 switch (int.value) {
717 inline else => |val| {
718 if (val >= 0 and val <= std.math.maxInt(u32)) {
719 break :blk .{
720 .tag = .uint_small,
721 .result_id = result_id,
722 .data = try self.addExtra(spv, Tag.UInt32{
723 .ty = int.ty,
724 .value = @intCast(u32, val),
725 }),
726 };
727 } else if (val >= std.math.minInt(i32) and val <= std.math.maxInt(i32)) {
728 break :blk .{
729 .tag = .int_small,
730 .result_id = result_id,
731 .data = try self.addExtra(spv, Tag.Int32{
732 .ty = int.ty,
733 .value = @intCast(i32, val),
734 }),
735 };
736 } else if (val < 0) {
737 break :blk .{
738 .tag = .int_large,
739 .result_id = result_id,
740 .data = try self.addExtra(spv, Tag.Int64.encode(int.ty, @intCast(i64, val))),
741 };
742 } else {
743 break :blk .{
744 .tag = .uint_large,
745 .result_id = result_id,
746 .data = try self.addExtra(spv, Tag.UInt64.encode(int.ty, @intCast(u64, val))),
747 };
748 }
749 },
750 }
751 },
752 .float => |float| switch (self.lookup(float.ty).float_type.bits) {
753 16 => .{
754 .tag = .float16,
755 .result_id = result_id,
756 .data = @bitCast(u16, float.value.float16),
757 },
758 32 => .{
759 .tag = .float32,
760 .result_id = result_id,
761 .data = @bitCast(u32, float.value.float32),
762 },
763 64 => .{
764 .tag = .float64,
765 .result_id = result_id,
766 .data = try self.addExtra(spv, Tag.Float64.encode(float.value.float64)),
767 },
768 else => unreachable,
769 },
770 .undef => |undef| .{
771 .tag = .undef,
772 .result_id = result_id,
773 .data = @enumToInt(undef.ty),
774 },
775 .null => |null_info| .{
776 .tag = .null,
777 .result_id = result_id,
778 .data = @enumToInt(null_info.ty),
779 },
780 .bool => |bool_info| .{
781 .tag = switch (bool_info.value) {
782 true => Tag.bool_true,
783 false => Tag.bool_false,
784 },
785 .result_id = result_id,
786 .data = @enumToInt(bool_info.ty),
787 },
788 };
789 try self.items.append(spv.gpa, item);
790
791 return @intToEnum(Ref, entry.index);
792}
793
794/// Turn a Ref back into a Key.
795/// The Key is valid until the next call to resolve().
796pub fn lookup(self: *const Self, ref: Ref) Key {
797 const item = self.items.get(@enumToInt(ref));
798 const data = item.data;
799 return switch (item.tag) {
800 .type_simple => switch (@intToEnum(Tag.SimpleType, data)) {
801 .void => .void_type,
802 .bool => .bool_type,
803 },
804 .type_int_signed => .{ .int_type = .{
805 .signedness = .signed,
806 .bits = @intCast(u16, data),
807 } },
808 .type_int_unsigned => .{ .int_type = .{
809 .signedness = .unsigned,
810 .bits = @intCast(u16, data),
811 } },
812 .type_float => .{ .float_type = .{
813 .bits = @intCast(u16, data),
814 } },
815 .type_vector => .{ .vector_type = self.extraData(Tag.VectorType, data) },
816 .type_array => .{ .array_type = self.extraData(Tag.ArrayType, data) },
817 .type_function => {
818 const payload = self.extraDataTrail(Tag.FunctionType, data);
819 return .{
820 .function_type = .{
821 .return_type = payload.data.return_type,
822 .parameters = @ptrCast([]const Ref, self.extra.items[payload.trail..][0..payload.data.param_len]),
823 },
824 };
825 },
826 .type_ptr_generic => .{
827 .ptr_type = .{
828 .storage_class = .Generic,
829 .child_type = @intToEnum(Ref, data),
830 },
831 },
832 .type_ptr_crosswgp => .{
833 .ptr_type = .{
834 .storage_class = .CrossWorkgroup,
835 .child_type = @intToEnum(Ref, data),
836 },
837 },
838 .type_ptr_function => .{
839 .ptr_type = .{
840 .storage_class = .Function,
841 .child_type = @intToEnum(Ref, data),
842 },
843 },
844 .type_ptr_simple => {
845 const payload = self.extraData(Tag.SimplePointerType, data);
846 return .{
847 .ptr_type = .{
848 .storage_class = payload.storage_class,
849 .child_type = payload.child_type,
850 },
851 };
852 },
853 .type_struct_simple => {
854 const payload = self.extraDataTrail(Tag.SimpleStructType, data);
855 const member_types = @ptrCast([]const Ref, self.extra.items[payload.trail..][0..payload.data.members_len]);
856 return .{
857 .struct_type = .{
858 .name = payload.data.name,
859 .member_types = member_types,
860 .member_names = null,
861 },
862 };
863 },
864 .type_struct_simple_with_member_names => {
865 const payload = self.extraDataTrail(Tag.SimpleStructType, data);
866 const trailing = self.extra.items[payload.trail..];
867 const member_types = @ptrCast([]const Ref, trailing[0..payload.data.members_len]);
868 const member_names = @ptrCast([]const String, trailing[payload.data.members_len..][0..payload.data.members_len]);
869 return .{
870 .struct_type = .{
871 .name = payload.data.name,
872 .member_types = member_types,
873 .member_names = member_names,
874 },
875 };
876 },
877 .float16 => .{ .float = .{
878 .ty = self.get(.{ .float_type = .{ .bits = 16 } }),
879 .value = .{ .float16 = @bitCast(f16, @intCast(u16, data)) },
880 } },
881 .float32 => .{ .float = .{
882 .ty = self.get(.{ .float_type = .{ .bits = 32 } }),
883 .value = .{ .float32 = @bitCast(f32, data) },
884 } },
885 .float64 => .{ .float = .{
886 .ty = self.get(.{ .float_type = .{ .bits = 64 } }),
887 .value = .{ .float64 = self.extraData(Tag.Float64, data).decode() },
888 } },
889 .uint8 => .{ .int = .{
890 .ty = self.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 8 } }),
891 .value = .{ .uint64 = data },
892 } },
893 .uint32 => .{ .int = .{
894 .ty = self.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 32 } }),
895 .value = .{ .uint64 = data },
896 } },
897 .int_small => {
898 const payload = self.extraData(Tag.Int32, data);
899 return .{ .int = .{
900 .ty = payload.ty,
901 .value = .{ .int64 = payload.value },
902 } };
903 },
904 .uint_small => {
905 const payload = self.extraData(Tag.UInt32, data);
906 return .{ .int = .{
907 .ty = payload.ty,
908 .value = .{ .uint64 = payload.value },
909 } };
910 },
911 .int_large => {
912 const payload = self.extraData(Tag.Int64, data);
913 return .{ .int = .{
914 .ty = payload.ty,
915 .value = .{ .int64 = payload.decode() },
916 } };
917 },
918 .uint_large => {
919 const payload = self.extraData(Tag.UInt64, data);
920 return .{ .int = .{
921 .ty = payload.ty,
922 .value = .{ .uint64 = payload.decode() },
923 } };
924 },
925 .undef => .{ .undef = .{
926 .ty = @intToEnum(Ref, data),
927 } },
928 .null => .{ .null = .{
929 .ty = @intToEnum(Ref, data),
930 } },
931 .bool_true => .{ .bool = .{
932 .ty = @intToEnum(Ref, data),
933 .value = true,
934 } },
935 .bool_false => .{ .bool = .{
936 .ty = @intToEnum(Ref, data),
937 .value = false,
938 } },
939 };
940}
941
942/// Look op the result-id that corresponds to a particular
943/// ref.
944pub fn resultId(self: Self, ref: Ref) IdResult {
945 return self.items.items(.result_id)[@enumToInt(ref)];
946}
947
948/// Get the ref for a key that has already been added to the cache.
949fn get(self: *const Self, key: Key) Ref {
950 const adapter: Key.Adapter = .{ .self = self };
951 const index = self.map.getIndexAdapted(key, adapter).?;
952 return @intToEnum(Ref, index);
953}
954
955fn addExtra(self: *Self, spv: *Module, extra: anytype) !u32 {
956 const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
957 try self.extra.ensureUnusedCapacity(spv.gpa, fields.len);
958 return try self.addExtraAssumeCapacity(extra);
959}
960
961fn addExtraAssumeCapacity(self: *Self, extra: anytype) !u32 {
962 const payload_offset = @intCast(u32, self.extra.items.len);
963 inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| {
964 const field_val = @field(extra, field.name);
965 const word = switch (field.type) {
966 u32 => field_val,
967 i32 => @bitCast(u32, field_val),
968 Ref => @enumToInt(field_val),
969 StorageClass => @enumToInt(field_val),
970 String => @enumToInt(field_val),
971 else => @compileError("Invalid type: " ++ @typeName(field.type)),
972 };
973 self.extra.appendAssumeCapacity(word);
974 }
975 return payload_offset;
976}
977
978fn extraData(self: Self, comptime T: type, offset: u32) T {
979 return self.extraDataTrail(T, offset).data;
980}
981
982fn extraDataTrail(self: Self, comptime T: type, offset: u32) struct { data: T, trail: u32 } {
983 var result: T = undefined;
984 const fields = @typeInfo(T).Struct.fields;
985 inline for (fields, 0..) |field, i| {
986 const word = self.extra.items[offset + i];
987 @field(result, field.name) = switch (field.type) {
988 u32 => word,
989 i32 => @bitCast(i32, word),
990 Ref => @intToEnum(Ref, word),
991 StorageClass => @intToEnum(StorageClass, word),
992 String => @intToEnum(String, word),
993 else => @compileError("Invalid type: " ++ @typeName(field.type)),
994 };
995 }
996 return .{
997 .data = result,
998 .trail = offset + @intCast(u32, fields.len),
999 };
1000}
1001
1002/// Represents a reference to some null-terminated string.
1003pub const String = enum(u32) {
1004 none = std.math.maxInt(u32),
1005 _,
1006
1007 pub const Adapter = struct {
1008 self: *const Self,
1009
1010 pub fn eql(ctx: @This(), a: []const u8, _: void, b_index: usize) bool {
1011 const offset = ctx.self.strings.values()[b_index];
1012 const b = std.mem.sliceTo(ctx.self.string_bytes.items[offset..], 0);
1013 return std.mem.eql(u8, a, b);
1014 }
1015
1016 pub fn hash(ctx: @This(), a: []const u8) u32 {
1017 _ = ctx;
1018 var hasher = std.hash.Wyhash.init(0);
1019 hasher.update(a);
1020 return @truncate(u32, hasher.final());
1021 }
1022 };
1023};
1024
1025/// Add a string to the cache. Must not contain any 0 values.
1026pub fn addString(self: *Self, spv: *Module, str: []const u8) !String {
1027 assert(std.mem.indexOfScalar(u8, str, 0) == null);
1028 const adapter = String.Adapter{ .self = self };
1029 const entry = try self.strings.getOrPutAdapted(spv.gpa, str, adapter);
1030 if (!entry.found_existing) {
1031 const offset = self.string_bytes.items.len;
1032 try self.string_bytes.ensureUnusedCapacity(spv.gpa, 1 + str.len);
1033 self.string_bytes.appendSliceAssumeCapacity(str);
1034 self.string_bytes.appendAssumeCapacity(0);
1035 entry.value_ptr.* = @intCast(u32, offset);
1036 }
1037
1038 return @intToEnum(String, entry.index);
1039}
1040
1041pub fn getString(self: *const Self, ref: String) ?[]const u8 {
1042 return switch (ref) {
1043 .none => null,
1044 else => std.mem.sliceTo(self.string_bytes.items[self.strings.values()[@enumToInt(ref)]..], 0),
1045 };
1046}
src/codegen/spirv/Module.zig+1-1
...@@ -21,7 +21,7 @@ const IdResultType = spec.IdResultType;...@@ -21,7 +21,7 @@ const IdResultType = spec.IdResultType;
2121
22const Section = @import("Section.zig");22const Section = @import("Section.zig");
2323
24const Cache = @import("TypeConstantCache.zig");24const Cache = @import("Cache.zig");
25pub const CacheKey = Cache.Key;25pub const CacheKey = Cache.Key;
26pub const CacheRef = Cache.Ref;26pub const CacheRef = Cache.Ref;
27pub const CacheString = Cache.String;27pub const CacheString = Cache.String;
src/codegen/spirv/TypeConstantCache.zig deleted-1046
...@@ -1,1046 +0,0 @@
1//! This file implements an InternPool-like structure that caches
2//! SPIR-V types and constants. Instead of generating type and
3//! constant instructions directly, we first keep a representation
4//! in a compressed database. This is then only later turned into
5//! actual SPIR-V instructions.
6//! Note: This cache is insertion-ordered. This means that we
7//! can materialize the SPIR-V instructions in the proper order,
8//! as SPIR-V requires that the type is emitted before use.
9//! Note: According to SPIR-V spec section 2.8, Types and Variables,
10//! non-pointer non-aggrerate types (which includes matrices and
11//! vectors) must have a _unique_ representation in the final binary.
12
13const std = @import("std");
14const assert = std.debug.assert;
15const Allocator = std.mem.Allocator;
16
17const Section = @import("Section.zig");
18const Module = @import("Module.zig");
19
20const spec = @import("spec.zig");
21const Opcode = spec.Opcode;
22const IdResult = spec.IdResult;
23const StorageClass = spec.StorageClass;
24
25const Self = @This();
26
27map: std.AutoArrayHashMapUnmanaged(void, void) = .{},
28items: std.MultiArrayList(Item) = .{},
29extra: std.ArrayListUnmanaged(u32) = .{},
30
31string_bytes: std.ArrayListUnmanaged(u8) = .{},
32strings: std.AutoArrayHashMapUnmanaged(void, u32) = .{},
33
34const Item = struct {
35 tag: Tag,
36 /// The result-id that this item uses.
37 result_id: IdResult,
38 /// The Tag determines how this should be interpreted.
39 data: u32,
40};
41
42const Tag = enum {
43 // -- Types
44 /// Simple type that has no additional data.
45 /// data is SimpleType.
46 type_simple,
47 /// Signed integer type
48 /// data is number of bits
49 type_int_signed,
50 /// Unsigned integer type
51 /// data is number of bits
52 type_int_unsigned,
53 /// Floating point type
54 /// data is number of bits
55 type_float,
56 /// Vector type
57 /// data is payload to VectorType
58 type_vector,
59 /// Array type
60 /// data is payload to ArrayType
61 type_array,
62 /// Function (proto)type
63 /// data is payload to FunctionType
64 type_function,
65 /// Pointer type in the CrossWorkgroup storage class
66 /// data is child type
67 type_ptr_generic,
68 /// Pointer type in the CrossWorkgroup storage class
69 /// data is child type
70 type_ptr_crosswgp,
71 /// Pointer type in the Function storage class
72 /// data is child type
73 type_ptr_function,
74 /// Simple pointer type that does not have any decorations.
75 /// data is payload to SimplePointerType
76 type_ptr_simple,
77 /// Simple structure type that does not have any decorations.
78 /// data is payload to SimpleStructType
79 type_struct_simple,
80 /// Simple structure type that does not have any decorations, but does
81 /// have member names trailing.
82 /// data is payload to SimpleStructType
83 type_struct_simple_with_member_names,
84
85 // -- Values
86 /// Value of type u8
87 /// data is value
88 uint8,
89 /// Value of type u32
90 /// data is value
91 uint32,
92 // TODO: More specialized tags here.
93 /// Integer value for signed values that are smaller than 32 bits.
94 /// data is pointer to Int32
95 int_small,
96 /// Integer value for unsigned values that are smaller than 32 bits.
97 /// data is pointer to UInt32
98 uint_small,
99 /// Integer value for signed values that are beteen 32 and 64 bits.
100 /// data is pointer to Int64
101 int_large,
102 /// Integer value for unsinged values that are beteen 32 and 64 bits.
103 /// data is pointer to UInt64
104 uint_large,
105 /// Value of type f16
106 /// data is value
107 float16,
108 /// Value of type f32
109 /// data is value
110 float32,
111 /// Value of type f64
112 /// data is payload to Float16
113 float64,
114 /// Undefined value
115 /// data is type
116 undef,
117 /// Null value
118 /// data is type
119 null,
120 /// Bool value that is true
121 /// data is (bool) type
122 bool_true,
123 /// Bool value that is false
124 /// data is (bool) type
125 bool_false,
126
127 const SimpleType = enum { void, bool };
128
129 const VectorType = Key.VectorType;
130 const ArrayType = Key.ArrayType;
131
132 // Trailing:
133 // - [param_len]Ref: parameter types.
134 const FunctionType = struct {
135 param_len: u32,
136 return_type: Ref,
137 };
138
139 const SimplePointerType = struct {
140 storage_class: StorageClass,
141 child_type: Ref,
142 };
143
144 /// Trailing:
145 /// - [members_len]Ref: Member types.
146 /// - [members_len]String: Member names, -- ONLY if the tag is type_struct_simple_with_member_names
147 const SimpleStructType = struct {
148 /// (optional) The name of the struct.
149 name: String,
150 /// Number of members that this struct has.
151 members_len: u32,
152 };
153
154 const Float64 = struct {
155 // Low-order 32 bits of the value.
156 low: u32,
157 // High-order 32 bits of the value.
158 high: u32,
159
160 fn encode(value: f64) Float64 {
161 const bits = @bitCast(u64, value);
162 return .{
163 .low = @truncate(u32, bits),
164 .high = @truncate(u32, bits >> 32),
165 };
166 }
167
168 fn decode(self: Float64) f64 {
169 const bits = @as(u64, self.low) | (@as(u64, self.high) << 32);
170 return @bitCast(f64, bits);
171 }
172 };
173
174 const Int32 = struct {
175 ty: Ref,
176 value: i32,
177 };
178
179 const UInt32 = struct {
180 ty: Ref,
181 value: u32,
182 };
183
184 const UInt64 = struct {
185 ty: Ref,
186 low: u32,
187 high: u32,
188
189 fn encode(ty: Ref, value: u64) Int64 {
190 return .{
191 .ty = ty,
192 .low = @truncate(u32, value),
193 .high = @truncate(u32, value >> 32),
194 };
195 }
196
197 fn decode(self: UInt64) u64 {
198 return @as(u64, self.low) | (@as(u64, self.high) << 32);
199 }
200 };
201
202 const Int64 = struct {
203 ty: Ref,
204 low: u32,
205 high: u32,
206
207 fn encode(ty: Ref, value: i64) Int64 {
208 return .{
209 .ty = ty,
210 .low = @truncate(u32, @bitCast(u64, value)),
211 .high = @truncate(u32, @bitCast(u64, value) >> 32),
212 };
213 }
214
215 fn decode(self: Int64) i64 {
216 return @bitCast(i64, @as(u64, self.low) | (@as(u64, self.high) << 32));
217 }
218 };
219};
220
221pub const Ref = enum(u32) { _ };
222
223/// This union represents something that can be interned. This includes
224/// types and constants. This structure is used for interfacing with the
225/// database: Values described for this structure are ephemeral and stored
226/// in a more memory-efficient manner internally.
227pub const Key = union(enum) {
228 // -- Types
229 void_type,
230 bool_type,
231 int_type: IntType,
232 float_type: FloatType,
233 vector_type: VectorType,
234 array_type: ArrayType,
235 function_type: FunctionType,
236 ptr_type: PointerType,
237 struct_type: StructType,
238
239 // -- values
240 int: Int,
241 float: Float,
242 undef: Undef,
243 null: Null,
244 bool: Bool,
245
246 pub const IntType = std.builtin.Type.Int;
247 pub const FloatType = std.builtin.Type.Float;
248
249 pub const VectorType = struct {
250 component_type: Ref,
251 component_count: u32,
252 };
253
254 pub const ArrayType = struct {
255 /// Child type of this array.
256 element_type: Ref,
257 /// Reference to a constant.
258 length: Ref,
259 /// Type has the 'ArrayStride' decoration.
260 /// If zero, no stride is present.
261 stride: u32 = 0,
262 };
263
264 pub const FunctionType = struct {
265 return_type: Ref,
266 parameters: []const Ref,
267 };
268
269 pub const PointerType = struct {
270 storage_class: StorageClass,
271 child_type: Ref,
272 // TODO: Decorations:
273 // - Alignment
274 // - ArrayStride,
275 // - MaxByteOffset,
276 };
277
278 pub const StructType = struct {
279 // TODO: Decorations.
280 /// The name of the structure. Can be `.none`.
281 name: String = .none,
282 /// The type of each member.
283 member_types: []const Ref,
284 /// Name for each member. May be omitted.
285 member_names: ?[]const String = null,
286
287 fn memberNames(self: @This()) []const String {
288 return if (self.member_names) |member_names| member_names else &.{};
289 }
290 };
291
292 pub const Int = struct {
293 /// The type: any bitness integer.
294 ty: Ref,
295 /// The actual value. Only uint64 and int64 types
296 /// are available here: Smaller types should use these
297 /// fields.
298 value: Value,
299
300 pub const Value = union(enum) {
301 uint64: u64,
302 int64: i64,
303 };
304
305 /// Turns this value into the corresponding 32-bit literal, 2s complement signed.
306 fn toBits32(self: Int) u32 {
307 return switch (self.value) {
308 .uint64 => |val| @intCast(u32, val),
309 .int64 => |val| if (val < 0) @bitCast(u32, @intCast(i32, val)) else @intCast(u32, val),
310 };
311 }
312
313 fn toBits64(self: Int) u64 {
314 return switch (self.value) {
315 .uint64 => |val| val,
316 .int64 => |val| @bitCast(u64, val),
317 };
318 }
319
320 fn to(self: Int, comptime T: type) T {
321 return switch (self.value) {
322 inline else => |val| @intCast(T, val),
323 };
324 }
325 };
326
327 /// Represents a numberic value of some type.
328 pub const Float = struct {
329 /// The type: 16, 32, or 64-bit float.
330 ty: Ref,
331 /// The actual value.
332 value: Value,
333
334 pub const Value = union(enum) {
335 float16: f16,
336 float32: f32,
337 float64: f64,
338 };
339 };
340
341 pub const Undef = struct {
342 ty: Ref,
343 };
344
345 pub const Null = struct {
346 ty: Ref,
347 };
348
349 pub const Bool = struct {
350 ty: Ref,
351 value: bool,
352 };
353
354 fn hash(self: Key) u32 {
355 var hasher = std.hash.Wyhash.init(0);
356 switch (self) {
357 .float => |float| {
358 std.hash.autoHash(&hasher, float.ty);
359 switch (float.value) {
360 .float16 => |value| std.hash.autoHash(&hasher, @bitCast(u16, value)),
361 .float32 => |value| std.hash.autoHash(&hasher, @bitCast(u32, value)),
362 .float64 => |value| std.hash.autoHash(&hasher, @bitCast(u64, value)),
363 }
364 },
365 .function_type => |func| {
366 std.hash.autoHash(&hasher, func.return_type);
367 for (func.parameters) |param_type| {
368 std.hash.autoHash(&hasher, param_type);
369 }
370 },
371 .struct_type => |struct_type| {
372 std.hash.autoHash(&hasher, struct_type.name);
373 for (struct_type.member_types) |member_type| {
374 std.hash.autoHash(&hasher, member_type);
375 }
376 for (struct_type.memberNames()) |member_name| {
377 std.hash.autoHash(&hasher, member_name);
378 }
379 },
380 inline else => |key| std.hash.autoHash(&hasher, key),
381 }
382 return @truncate(u32, hasher.final());
383 }
384
385 fn eql(a: Key, b: Key) bool {
386 const KeyTag = @typeInfo(Key).Union.tag_type.?;
387 const a_tag: KeyTag = a;
388 const b_tag: KeyTag = b;
389 if (a_tag != b_tag) {
390 return false;
391 }
392 return switch (a) {
393 .function_type => |a_func| {
394 const b_func = b.function_type;
395 return a_func.return_type == b_func.return_type and
396 std.mem.eql(Ref, a_func.parameters, b_func.parameters);
397 },
398 .struct_type => |a_struct| {
399 const b_struct = b.struct_type;
400 return a_struct.name == b_struct.name and
401 std.mem.eql(Ref, a_struct.member_types, b_struct.member_types) and
402 std.mem.eql(String, a_struct.memberNames(), b_struct.memberNames());
403 },
404 // TODO: Unroll?
405 else => std.meta.eql(a, b),
406 };
407 }
408
409 pub const Adapter = struct {
410 self: *const Self,
411
412 pub fn eql(ctx: @This(), a: Key, b_void: void, b_index: usize) bool {
413 _ = b_void;
414 return ctx.self.lookup(@intToEnum(Ref, b_index)).eql(a);
415 }
416
417 pub fn hash(ctx: @This(), a: Key) u32 {
418 _ = ctx;
419 return a.hash();
420 }
421 };
422
423 fn toSimpleType(self: Key) Tag.SimpleType {
424 return switch (self) {
425 .void_type => .void,
426 .bool_type => .bool,
427 else => unreachable,
428 };
429 }
430};
431
432pub fn deinit(self: *Self, spv: *const Module) void {
433 self.map.deinit(spv.gpa);
434 self.items.deinit(spv.gpa);
435 self.extra.deinit(spv.gpa);
436 self.string_bytes.deinit(spv.gpa);
437 self.strings.deinit(spv.gpa);
438}
439
440/// Actually materialize the database into spir-v instructions.
441/// This function returns a spir-v section of (only) constant and type instructions.
442/// Additionally, decorations, debug names, etc, are all directly emitted into the
443/// `spv` module. The section is allocated with `spv.gpa`.
444pub fn materialize(self: *const Self, spv: *Module) !Section {
445 var section = Section{};
446 errdefer section.deinit(spv.gpa);
447 for (self.items.items(.result_id), 0..) |result_id, index| {
448 try self.emit(spv, result_id, @intToEnum(Ref, index), &section);
449 }
450 return section;
451}
452
453fn emit(
454 self: *const Self,
455 spv: *Module,
456 result_id: IdResult,
457 ref: Ref,
458 section: *Section,
459) !void {
460 const key = self.lookup(ref);
461 const Lit = spec.LiteralContextDependentNumber;
462 switch (key) {
463 .void_type => {
464 try section.emit(spv.gpa, .OpTypeVoid, .{ .id_result = result_id });
465 try spv.debugName(result_id, "void", .{});
466 },
467 .bool_type => {
468 try section.emit(spv.gpa, .OpTypeBool, .{ .id_result = result_id });
469 try spv.debugName(result_id, "bool", .{});
470 },
471 .int_type => |int| {
472 try section.emit(spv.gpa, .OpTypeInt, .{
473 .id_result = result_id,
474 .width = int.bits,
475 .signedness = switch (int.signedness) {
476 .unsigned => @as(spec.Word, 0),
477 .signed => 1,
478 },
479 });
480 const ui: []const u8 = switch (int.signedness) {
481 .unsigned => "u",
482 .signed => "i",
483 };
484 try spv.debugName(result_id, "{s}{}", .{ ui, int.bits });
485 },
486 .float_type => |float| {
487 try section.emit(spv.gpa, .OpTypeFloat, .{
488 .id_result = result_id,
489 .width = float.bits,
490 });
491 try spv.debugName(result_id, "f{}", .{float.bits});
492 },
493 .vector_type => |vector| {
494 try section.emit(spv.gpa, .OpTypeVector, .{
495 .id_result = result_id,
496 .component_type = self.resultId(vector.component_type),
497 .component_count = vector.component_count,
498 });
499 },
500 .array_type => |array| {
501 try section.emit(spv.gpa, .OpTypeArray, .{
502 .id_result = result_id,
503 .element_type = self.resultId(array.element_type),
504 .length = self.resultId(array.length),
505 });
506 if (array.stride != 0) {
507 try spv.decorate(result_id, .{ .ArrayStride = .{ .array_stride = array.stride } });
508 }
509 },
510 .function_type => |function| {
511 try section.emitRaw(spv.gpa, .OpTypeFunction, 2 + function.parameters.len);
512 section.writeOperand(IdResult, result_id);
513 section.writeOperand(IdResult, self.resultId(function.return_type));
514 for (function.parameters) |param_type| {
515 section.writeOperand(IdResult, self.resultId(param_type));
516 }
517 },
518 .ptr_type => |ptr| {
519 try section.emit(spv.gpa, .OpTypePointer, .{
520 .id_result = result_id,
521 .storage_class = ptr.storage_class,
522 .type = self.resultId(ptr.child_type),
523 });
524 // TODO: Decorations?
525 },
526 .struct_type => |struct_type| {
527 try section.emitRaw(spv.gpa, .OpTypeStruct, 1 + struct_type.member_types.len);
528 section.writeOperand(IdResult, result_id);
529 for (struct_type.member_types) |member_type| {
530 section.writeOperand(IdResult, self.resultId(member_type));
531 }
532 if (self.getString(struct_type.name)) |name| {
533 try spv.debugName(result_id, "{s}", .{name});
534 }
535 for (struct_type.memberNames(), 0..) |member_name, i| {
536 if (self.getString(member_name)) |name| {
537 try spv.memberDebugName(result_id, @intCast(u32, i), "{s}", .{name});
538 }
539 }
540 // TODO: Decorations?
541 },
542 .int => |int| {
543 const int_type = self.lookup(int.ty).int_type;
544 const ty_id = self.resultId(int.ty);
545 const lit: Lit = switch (int_type.bits) {
546 1...32 => .{ .uint32 = int.toBits32() },
547 33...64 => .{ .uint64 = int.toBits64() },
548 else => unreachable,
549 };
550
551 try section.emit(spv.gpa, .OpConstant, .{
552 .id_result_type = ty_id,
553 .id_result = result_id,
554 .value = lit,
555 });
556 },
557 .float => |float| {
558 const ty_id = self.resultId(float.ty);
559 const lit: Lit = switch (float.value) {
560 .float16 => |value| .{ .uint32 = @bitCast(u16, value) },
561 .float32 => |value| .{ .float32 = value },
562 .float64 => |value| .{ .float64 = value },
563 };
564 try section.emit(spv.gpa, .OpConstant, .{
565 .id_result_type = ty_id,
566 .id_result = result_id,
567 .value = lit,
568 });
569 },
570 .undef => |undef| {
571 try section.emit(spv.gpa, .OpUndef, .{
572 .id_result_type = self.resultId(undef.ty),
573 .id_result = result_id,
574 });
575 },
576 .null => |null_info| {
577 try section.emit(spv.gpa, .OpConstantNull, .{
578 .id_result_type = self.resultId(null_info.ty),
579 .id_result = result_id,
580 });
581 },
582 .bool => |bool_info| switch (bool_info.value) {
583 true => {
584 try section.emit(spv.gpa, .OpConstantTrue, .{
585 .id_result_type = self.resultId(bool_info.ty),
586 .id_result = result_id,
587 });
588 },
589 false => {
590 try section.emit(spv.gpa, .OpConstantFalse, .{
591 .id_result_type = self.resultId(bool_info.ty),
592 .id_result = result_id,
593 });
594 },
595 },
596 }
597}
598
599/// Add a key to this cache. Returns a reference to the key that
600/// was added. The corresponding result-id can be queried using
601/// self.resultId with the result.
602pub fn resolve(self: *Self, spv: *Module, key: Key) !Ref {
603 const adapter: Key.Adapter = .{ .self = self };
604 const entry = try self.map.getOrPutAdapted(spv.gpa, key, adapter);
605 if (entry.found_existing) {
606 return @intToEnum(Ref, entry.index);
607 }
608 const result_id = spv.allocId();
609 const item: Item = switch (key) {
610 inline .void_type, .bool_type => .{
611 .tag = .type_simple,
612 .result_id = result_id,
613 .data = @enumToInt(key.toSimpleType()),
614 },
615 .int_type => |int| blk: {
616 const t: Tag = switch (int.signedness) {
617 .signed => .type_int_signed,
618 .unsigned => .type_int_unsigned,
619 };
620 break :blk .{
621 .tag = t,
622 .result_id = result_id,
623 .data = int.bits,
624 };
625 },
626 .float_type => |float| .{
627 .tag = .type_float,
628 .result_id = result_id,
629 .data = float.bits,
630 },
631 .vector_type => |vector| .{
632 .tag = .type_vector,
633 .result_id = result_id,
634 .data = try self.addExtra(spv, vector),
635 },
636 .array_type => |array| .{
637 .tag = .type_array,
638 .result_id = result_id,
639 .data = try self.addExtra(spv, array),
640 },
641 .function_type => |function| blk: {
642 const extra = try self.addExtra(spv, Tag.FunctionType{
643 .param_len = @intCast(u32, function.parameters.len),
644 .return_type = function.return_type,
645 });
646 try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, function.parameters));
647 break :blk .{
648 .tag = .type_function,
649 .result_id = result_id,
650 .data = extra,
651 };
652 },
653 .ptr_type => |ptr| switch (ptr.storage_class) {
654 .Generic => Item{
655 .tag = .type_ptr_generic,
656 .result_id = result_id,
657 .data = @enumToInt(ptr.child_type),
658 },
659 .CrossWorkgroup => Item{
660 .tag = .type_ptr_crosswgp,
661 .result_id = result_id,
662 .data = @enumToInt(ptr.child_type),
663 },
664 .Function => Item{
665 .tag = .type_ptr_function,
666 .result_id = result_id,
667 .data = @enumToInt(ptr.child_type),
668 },
669 else => |storage_class| Item{
670 .tag = .type_ptr_simple,
671 .result_id = result_id,
672 .data = try self.addExtra(spv, Tag.SimplePointerType{
673 .storage_class = storage_class,
674 .child_type = ptr.child_type,
675 }),
676 },
677 },
678 .struct_type => |struct_type| blk: {
679 const extra = try self.addExtra(spv, Tag.SimpleStructType{
680 .name = struct_type.name,
681 .members_len = @intCast(u32, struct_type.member_types.len),
682 });
683 try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, struct_type.member_types));
684
685 if (struct_type.member_names) |member_names| {
686 try self.extra.appendSlice(spv.gpa, @ptrCast([]const u32, member_names));
687 break :blk Item{
688 .tag = .type_struct_simple_with_member_names,
689 .result_id = result_id,
690 .data = extra,
691 };
692 } else {
693 break :blk Item{
694 .tag = .type_struct_simple,
695 .result_id = result_id,
696 .data = extra,
697 };
698 }
699 },
700 .int => |int| blk: {
701 const int_type = self.lookup(int.ty).int_type;
702 if (int_type.signedness == .unsigned and int_type.bits == 8) {
703 break :blk .{
704 .tag = .uint8,
705 .result_id = result_id,
706 .data = int.to(u8),
707 };
708 } else if (int_type.signedness == .unsigned and int_type.bits == 32) {
709 break :blk .{
710 .tag = .uint32,
711 .result_id = result_id,
712 .data = int.to(u32),
713 };
714 }
715
716 switch (int.value) {
717 inline else => |val| {
718 if (val >= 0 and val <= std.math.maxInt(u32)) {
719 break :blk .{
720 .tag = .uint_small,
721 .result_id = result_id,
722 .data = try self.addExtra(spv, Tag.UInt32{
723 .ty = int.ty,
724 .value = @intCast(u32, val),
725 }),
726 };
727 } else if (val >= std.math.minInt(i32) and val <= std.math.maxInt(i32)) {
728 break :blk .{
729 .tag = .int_small,
730 .result_id = result_id,
731 .data = try self.addExtra(spv, Tag.Int32{
732 .ty = int.ty,
733 .value = @intCast(i32, val),
734 }),
735 };
736 } else if (val < 0) {
737 break :blk .{
738 .tag = .int_large,
739 .result_id = result_id,
740 .data = try self.addExtra(spv, Tag.Int64.encode(int.ty, @intCast(i64, val))),
741 };
742 } else {
743 break :blk .{
744 .tag = .uint_large,
745 .result_id = result_id,
746 .data = try self.addExtra(spv, Tag.UInt64.encode(int.ty, @intCast(u64, val))),
747 };
748 }
749 },
750 }
751 },
752 .float => |float| switch (self.lookup(float.ty).float_type.bits) {
753 16 => .{
754 .tag = .float16,
755 .result_id = result_id,
756 .data = @bitCast(u16, float.value.float16),
757 },
758 32 => .{
759 .tag = .float32,
760 .result_id = result_id,
761 .data = @bitCast(u32, float.value.float32),
762 },
763 64 => .{
764 .tag = .float64,
765 .result_id = result_id,
766 .data = try self.addExtra(spv, Tag.Float64.encode(float.value.float64)),
767 },
768 else => unreachable,
769 },
770 .undef => |undef| .{
771 .tag = .undef,
772 .result_id = result_id,
773 .data = @enumToInt(undef.ty),
774 },
775 .null => |null_info| .{
776 .tag = .null,
777 .result_id = result_id,
778 .data = @enumToInt(null_info.ty),
779 },
780 .bool => |bool_info| .{
781 .tag = switch (bool_info.value) {
782 true => Tag.bool_true,
783 false => Tag.bool_false,
784 },
785 .result_id = result_id,
786 .data = @enumToInt(bool_info.ty),
787 },
788 };
789 try self.items.append(spv.gpa, item);
790
791 return @intToEnum(Ref, entry.index);
792}
793
794/// Turn a Ref back into a Key.
795/// The Key is valid until the next call to resolve().
796pub fn lookup(self: *const Self, ref: Ref) Key {
797 const item = self.items.get(@enumToInt(ref));
798 const data = item.data;
799 return switch (item.tag) {
800 .type_simple => switch (@intToEnum(Tag.SimpleType, data)) {
801 .void => .void_type,
802 .bool => .bool_type,
803 },
804 .type_int_signed => .{ .int_type = .{
805 .signedness = .signed,
806 .bits = @intCast(u16, data),
807 } },
808 .type_int_unsigned => .{ .int_type = .{
809 .signedness = .unsigned,
810 .bits = @intCast(u16, data),
811 } },
812 .type_float => .{ .float_type = .{
813 .bits = @intCast(u16, data),
814 } },
815 .type_vector => .{ .vector_type = self.extraData(Tag.VectorType, data) },
816 .type_array => .{ .array_type = self.extraData(Tag.ArrayType, data) },
817 .type_function => {
818 const payload = self.extraDataTrail(Tag.FunctionType, data);
819 return .{
820 .function_type = .{
821 .return_type = payload.data.return_type,
822 .parameters = @ptrCast([]const Ref, self.extra.items[payload.trail..][0..payload.data.param_len]),
823 },
824 };
825 },
826 .type_ptr_generic => .{
827 .ptr_type = .{
828 .storage_class = .Generic,
829 .child_type = @intToEnum(Ref, data),
830 },
831 },
832 .type_ptr_crosswgp => .{
833 .ptr_type = .{
834 .storage_class = .CrossWorkgroup,
835 .child_type = @intToEnum(Ref, data),
836 },
837 },
838 .type_ptr_function => .{
839 .ptr_type = .{
840 .storage_class = .Function,
841 .child_type = @intToEnum(Ref, data),
842 },
843 },
844 .type_ptr_simple => {
845 const payload = self.extraData(Tag.SimplePointerType, data);
846 return .{
847 .ptr_type = .{
848 .storage_class = payload.storage_class,
849 .child_type = payload.child_type,
850 },
851 };
852 },
853 .type_struct_simple => {
854 const payload = self.extraDataTrail(Tag.SimpleStructType, data);
855 const member_types = @ptrCast([]const Ref, self.extra.items[payload.trail..][0..payload.data.members_len]);
856 return .{
857 .struct_type = .{
858 .name = payload.data.name,
859 .member_types = member_types,
860 .member_names = null,
861 },
862 };
863 },
864 .type_struct_simple_with_member_names => {
865 const payload = self.extraDataTrail(Tag.SimpleStructType, data);
866 const trailing = self.extra.items[payload.trail..];
867 const member_types = @ptrCast([]const Ref, trailing[0..payload.data.members_len]);
868 const member_names = @ptrCast([]const String, trailing[payload.data.members_len..][0..payload.data.members_len]);
869 return .{
870 .struct_type = .{
871 .name = payload.data.name,
872 .member_types = member_types,
873 .member_names = member_names,
874 },
875 };
876 },
877 .float16 => .{ .float = .{
878 .ty = self.get(.{ .float_type = .{ .bits = 16 } }),
879 .value = .{ .float16 = @bitCast(f16, @intCast(u16, data)) },
880 } },
881 .float32 => .{ .float = .{
882 .ty = self.get(.{ .float_type = .{ .bits = 32 } }),
883 .value = .{ .float32 = @bitCast(f32, data) },
884 } },
885 .float64 => .{ .float = .{
886 .ty = self.get(.{ .float_type = .{ .bits = 64 } }),
887 .value = .{ .float64 = self.extraData(Tag.Float64, data).decode() },
888 } },
889 .uint8 => .{ .int = .{
890 .ty = self.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 8 } }),
891 .value = .{ .uint64 = data },
892 } },
893 .uint32 => .{ .int = .{
894 .ty = self.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 32 } }),
895 .value = .{ .uint64 = data },
896 } },
897 .int_small => {
898 const payload = self.extraData(Tag.Int32, data);
899 return .{ .int = .{
900 .ty = payload.ty,
901 .value = .{ .int64 = payload.value },
902 } };
903 },
904 .uint_small => {
905 const payload = self.extraData(Tag.UInt32, data);
906 return .{ .int = .{
907 .ty = payload.ty,
908 .value = .{ .uint64 = payload.value },
909 } };
910 },
911 .int_large => {
912 const payload = self.extraData(Tag.Int64, data);
913 return .{ .int = .{
914 .ty = payload.ty,
915 .value = .{ .int64 = payload.decode() },
916 } };
917 },
918 .uint_large => {
919 const payload = self.extraData(Tag.UInt64, data);
920 return .{ .int = .{
921 .ty = payload.ty,
922 .value = .{ .uint64 = payload.decode() },
923 } };
924 },
925 .undef => .{ .undef = .{
926 .ty = @intToEnum(Ref, data),
927 } },
928 .null => .{ .null = .{
929 .ty = @intToEnum(Ref, data),
930 } },
931 .bool_true => .{ .bool = .{
932 .ty = @intToEnum(Ref, data),
933 .value = true,
934 } },
935 .bool_false => .{ .bool = .{
936 .ty = @intToEnum(Ref, data),
937 .value = false,
938 } },
939 };
940}
941
942/// Look op the result-id that corresponds to a particular
943/// ref.
944pub fn resultId(self: Self, ref: Ref) IdResult {
945 return self.items.items(.result_id)[@enumToInt(ref)];
946}
947
948/// Get the ref for a key that has already been added to the cache.
949fn get(self: *const Self, key: Key) Ref {
950 const adapter: Key.Adapter = .{ .self = self };
951 const index = self.map.getIndexAdapted(key, adapter).?;
952 return @intToEnum(Ref, index);
953}
954
955fn addExtra(self: *Self, spv: *Module, extra: anytype) !u32 {
956 const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
957 try self.extra.ensureUnusedCapacity(spv.gpa, fields.len);
958 return try self.addExtraAssumeCapacity(extra);
959}
960
961fn addExtraAssumeCapacity(self: *Self, extra: anytype) !u32 {
962 const payload_offset = @intCast(u32, self.extra.items.len);
963 inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| {
964 const field_val = @field(extra, field.name);
965 const word = switch (field.type) {
966 u32 => field_val,
967 i32 => @bitCast(u32, field_val),
968 Ref => @enumToInt(field_val),
969 StorageClass => @enumToInt(field_val),
970 String => @enumToInt(field_val),
971 else => @compileError("Invalid type: " ++ @typeName(field.type)),
972 };
973 self.extra.appendAssumeCapacity(word);
974 }
975 return payload_offset;
976}
977
978fn extraData(self: Self, comptime T: type, offset: u32) T {
979 return self.extraDataTrail(T, offset).data;
980}
981
982fn extraDataTrail(self: Self, comptime T: type, offset: u32) struct { data: T, trail: u32 } {
983 var result: T = undefined;
984 const fields = @typeInfo(T).Struct.fields;
985 inline for (fields, 0..) |field, i| {
986 const word = self.extra.items[offset + i];
987 @field(result, field.name) = switch (field.type) {
988 u32 => word,
989 i32 => @bitCast(i32, word),
990 Ref => @intToEnum(Ref, word),
991 StorageClass => @intToEnum(StorageClass, word),
992 String => @intToEnum(String, word),
993 else => @compileError("Invalid type: " ++ @typeName(field.type)),
994 };
995 }
996 return .{
997 .data = result,
998 .trail = offset + @intCast(u32, fields.len),
999 };
1000}
1001
1002/// Represents a reference to some null-terminated string.
1003pub const String = enum(u32) {
1004 none = std.math.maxInt(u32),
1005 _,
1006
1007 pub const Adapter = struct {
1008 self: *const Self,
1009
1010 pub fn eql(ctx: @This(), a: []const u8, _: void, b_index: usize) bool {
1011 const offset = ctx.self.strings.values()[b_index];
1012 const b = std.mem.sliceTo(ctx.self.string_bytes.items[offset..], 0);
1013 return std.mem.eql(u8, a, b);
1014 }
1015
1016 pub fn hash(ctx: @This(), a: []const u8) u32 {
1017 _ = ctx;
1018 var hasher = std.hash.Wyhash.init(0);
1019 hasher.update(a);
1020 return @truncate(u32, hasher.final());
1021 }
1022 };
1023};
1024
1025/// Add a string to the cache. Must not contain any 0 values.
1026pub fn addString(self: *Self, spv: *Module, str: []const u8) !String {
1027 assert(std.mem.indexOfScalar(u8, str, 0) == null);
1028 const adapter = String.Adapter{ .self = self };
1029 const entry = try self.strings.getOrPutAdapted(spv.gpa, str, adapter);
1030 if (!entry.found_existing) {
1031 const offset = self.string_bytes.items.len;
1032 try self.string_bytes.ensureUnusedCapacity(spv.gpa, 1 + str.len);
1033 self.string_bytes.appendSliceAssumeCapacity(str);
1034 self.string_bytes.appendAssumeCapacity(0);
1035 entry.value_ptr.* = @intCast(u32, offset);
1036 }
1037
1038 return @intToEnum(String, entry.index);
1039}
1040
1041pub fn getString(self: *const Self, ref: String) ?[]const u8 {
1042 return switch (ref) {
1043 .none => null,
1044 else => std.mem.sliceTo(self.string_bytes.items[self.strings.values()[@enumToInt(ref)]..], 0),
1045 };
1046}