authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-04-06 03:14:36+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-04-06 13:37:40+02:00
log3e388faecdb7100403d466df20086eb6781409e2
tree2152d3f032e71c09d23ed1ac34d5e44d96990e06
parentef638502d472e3c3430044c6ed908f30f17d2796
signaturebadge-check Signed by SSH key SHA256:ZS52FNyUv2WUXvO4njmVaFVO46RHojFuOrxRc4LuKzg

spirv: yeet cache


3 files changed, 11 insertions(+), 1171 deletions(-)

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