authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-05-29 03:30:38+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-05-30 19:43:36+02:00
log96a66d14a16a89f759c1ed86a61cef710e3a7d05
tree433f348b1873fa3d65434ff1a76b676505665284
parent76aa1fffb7a06f0be0d803cb3379f3102c0b2590
signaturelock-open Commit is signed but in an unrecognized format.

spirv: TypeConstantCache


2 files changed, 305 insertions(+), 0 deletions(-)

src/codegen/spirv/Module.zig+13
......@@ -125,6 +125,8 @@ sections: struct {
125125 // OpModuleProcessed - skip for now.
126126 /// Annotation instructions (OpDecorate etc).
127127 annotations: Section = .{},
128 /// Type and constant declarations that are generated by the TypeConstantCache.
129 types_and_constants: Section = .{},
128130 /// Type declarations, constants, global variables
129131 /// Below this section, OpLine and OpNoLine is allowed.
130132 types_globals_constants: Section = .{},
......@@ -182,6 +184,7 @@ pub fn deinit(self: *Module) void {
182184 self.sections.debug_strings.deinit(self.gpa);
183185 self.sections.debug_names.deinit(self.gpa);
184186 self.sections.annotations.deinit(self.gpa);
187 self.sections.types_and_constants(self.gpa);
185188 self.sections.types_globals_constants.deinit(self.gpa);
186189 self.sections.functions.deinit(self.gpa);
187190
......@@ -334,6 +337,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
334337 self.sections.debug_strings.toWords(),
335338 self.sections.debug_names.toWords(),
336339 self.sections.annotations.toWords(),
340 self.sections.types_constants.toWords(),
337341 self.sections.types_globals_constants.toWords(),
338342 globals.toWords(),
339343 self.sections.functions.toWords(),
......@@ -883,3 +887,12 @@ pub fn declareEntryPoint(self: *Module, decl_index: Decl.Index, name: []const u8
883887 .name = try self.arena.dupe(u8, name),
884888 });
885889}
890
891pub fn debugName(self: *Module, target: IdResult, comptime fmt: []const u8, args: anytype) !void {
892 const name = try std.fmt.allocPrint(self.gpa, fmt, args);
893 defer self.gpa.free(name);
894 try debug.emit(self.gpa, .OpName, .{
895 .target = result_id,
896 .name = name,
897 });
898}
src/codegen/spirv/TypeConstantCache.zig created+292
......@@ -0,0 +1,292 @@
1//! This file implements an InternPool-like structure that caches
2//! SPIR-V types and constants.
3//! In the case of SPIR-V, the type- and constant instructions
4//! describe the type and constant fully. This means we can save
5//! memory by representing these items directly in spir-v code,
6//! and decoding that when required.
7//! This does not work for OpDecorate instructions though, and for
8//! those we keep some additional metadata.
9
10const std = @import("std");
11const Allocator = std.mem.Allocator;
12
13const Section = @import("section.zig");
14const Module = @import("Module.zig");
15
16const spec = @import("spec.zig");
17const Opcode = spec.Opcode;
18const IdResult = spec.IdResult;
19
20const Self = @This();
21
22map: std.AutoArrayHashMapUnmanaged(void, void) = .{},
23items: std.MultiArrayList(Item) = .{},
24extra: std.ArrayHashMapUnmanaged(u32) = .{},
25
26const Item = struct {
27 tag: Tag,
28 /// The result-id that this item uses.
29 result_id: IdResult,
30 /// The Tag determines how this should be interpreted.
31 data: u32,
32};
33
34const Tag = enum {
35 /// Simple type that has no additional data.
36 /// data is SimpleType.
37 type_simple,
38 /// Signed integer type
39 /// data is number of bits
40 type_int_signed,
41 /// Unsigned integer type
42 /// data is number of bits
43 type_int_unsigned,
44 /// Floating point type
45 /// data is number of bits
46 type_float,
47 /// Vector type
48 /// data is payload to Key.VectorType
49 type_vector,
50
51 const SimpleType = enum {
52 void,
53 bool,
54 };
55};
56
57pub const Ref = enum(u32) { _ };
58
59/// This union represents something that can be interned. This includes
60/// types and constants. This structure is used for interfacing with the
61/// database: Values described for this structure are ephemeral and stored
62/// in a more memory-efficient manner internally.
63pub const Key = union(enum) {
64 void_ty,
65 bool_ty,
66 int_ty: IntType,
67 float_ty: FloatType,
68 vector_ty: VectorType,
69
70 pub const IntType = std.builtin.Type.Int;
71 pub const FloatType = std.builtin.Type.Float;
72
73 pub const VectorType = struct {
74 component_type: Ref,
75 component_count: u32,
76 };
77
78 fn hash(self: Key) u32 {
79 var hasher = std.hash.Wyhash.init(0);
80 std.hash.autoHash(&hasher, self);
81 return @truncate(u32, hasher.final());
82 }
83
84 fn eql(a: Key, b: Key) u32 {
85 return std.meta.eql(a, b);
86 }
87
88 pub const Adapter = struct {
89 self: *const Self,
90
91 pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: u32) bool {
92 _ = b_void;
93 return ctx.self.lookup(@intToEnum(Ref, b_map_index)).eql(a);
94 }
95
96 pub fn hash(ctx: @This(), a: Key) u32 {
97 return ctx.self.hash(a);
98 }
99 };
100
101 fn toSimpleType(self: Key) Tag.SimpleType {
102 return switch (self) {
103 .void_ty => .void,
104 .bool_ty => .bool,
105 else => unreachable,
106 };
107 }
108};
109
110pub fn deinit(self: *Self, spv: Module) void {
111 self.map.deinit(spv.gpa);
112 self.items.deinit(spv.gpa);
113 self.extra.deinit(spv.gpa);
114}
115
116/// Actually materialize the database into spir-v instructions.
117// TODO: This should generate decorations as well as regular instructions.
118// Important is that these are generated in-order, but that should be fine.
119pub fn finalize(self: *Self, spv: *Module) !void {
120 // This function should really be the only one that modifies spv.types_and_constants.
121 // TODO: Make this function return the section instead.
122 std.debug.assert(spv.sections.types_and_constants.instructions.items.len == 0);
123
124 for (self.items.items(.result_id), 0..) |result_id, index| {
125 try self.emit(spv, result_id, @intToEnum(Ref, index));
126 }
127}
128
129fn emit(
130 self: *Self,
131 spv: *Module,
132 result_id: IdResult,
133 ref: Ref,
134) !void {
135 const tc = &spv.sections.types_and_constants;
136 const key = self.lookup(ref);
137 switch (key) {
138 .void_ty => {
139 try tc.emit(spv.gpa, .OpTypeVoid, .{ .id_result = result_id });
140 try spv.debugName(result_id, "void", .{});
141 },
142 .bool_ty => {
143 try tc.emit(spv.gpa, .OpTypeBool, .{ .id_result = result_id });
144 try spv.debugName(result_id, "bool", .{});
145 },
146 .int_ty => |int| {
147 try tc.emit(spv.gpa, .OpTypeInt, .{
148 .id_result = result_id,
149 .width = int.bits,
150 .signedness = switch (int.signedness) {
151 .unsigned => 0,
152 .signed => 1,
153 },
154 });
155 const ui: []const u8 = switch (int.signedness) {
156 0 => "u",
157 1 => "i",
158 else => unreachable,
159 };
160 try spv.debugName(result_id, "{s}{}", .{ ui, int.bits });
161 },
162 .float_ty => |float| {
163 try tc.emit(spv.gpa, .OpTypeFloat, .{
164 .id_result = result_id,
165 .width = float.bits,
166 });
167 try spv.debugName(result_id, "f{}", .{float.bits});
168 },
169 .vector_ty => |vector| {
170 try tc.emit(spv.gpa, .OpTypeVector, .{
171 .id_result = result_id,
172 .component_type = self.resultId(vector.component_type),
173 .component_count = vector.component_count,
174 });
175 },
176 }
177}
178
179/// Add a key to this cache. Returns a reference to the key that
180/// was added. The corresponding result-id can be queried using
181/// self.resultId with the result.
182pub fn add(self: *Self, spv: *Module, key: Key) !Ref {
183 const adapter: Key.Adapter = .{ .self = self };
184 const entry = try self.map.getOrPutAdapted(spv.gpa, key, adapter);
185 if (entry.found_existing) {
186 return @intToEnum(Ref, entry.index);
187 }
188 const result_id = spv.allocId();
189 try self.items.ensureUnusedCapacity(spv.gpa, 1);
190 switch (key) {
191 inline .void_ty, .bool_ty => {
192 self.items.appendAssumeCapacity(.{
193 .tag = .type_simple,
194 .result_id = result_id,
195 .data = @enumToInt(key.toSimpleType()),
196 });
197 },
198 .int_ty => |int| {
199 const t: Tag = switch (int.signedness) {
200 .signed => .type_int_signed,
201 .unsigned => .type_int_unsigned,
202 };
203 self.items.appendAssumeCapacity(.{
204 .tag = t,
205 .result_id = result_id,
206 .data = int.bits,
207 });
208 },
209 .float_ty => |float| {
210 self.items.appendAssumeCapacity(.{
211 .tag = .type_float,
212 .result_id = result_id,
213 .data = float.bits,
214 });
215 },
216 .vector_ty => |vec| {
217 const payload = try self.addExtra(vec);
218 self.items.appendAssumeCapacity(.{
219 .tag = .type_vector,
220 .result_id = result_id,
221 .data = payload,
222 });
223 },
224 }
225
226 return @intToEnum(Ref, entry.index);
227}
228
229/// Look op the result-id that corresponds to a particular
230/// ref.
231pub fn resultId(self: Self, ref: Ref) IdResult {
232 return self.items.items(.result_id)[@enumToInt(ref)];
233}
234
235/// Turn a Ref back into a Key.
236pub fn lookup(self: *const Self, ref: Ref) Key {
237 const item = self.items.get(@enumToInt(ref));
238 const data = item.data;
239 return switch (item.tag) {
240 .type_simple => switch (@intToEnum(Tag.SimpleType, data)) {
241 .void => .void_ty,
242 .bool => .bool_ty,
243 },
244 .type_int_signed => .{ .int_ty = .{
245 .signedness = .signed,
246 .bits = @intCast(u16, data),
247 } },
248 .type_int_unsigned => .{ .int_ty = .{
249 .signedness = .unsigned,
250 .bits = @intCast(u16, data),
251 } },
252 .type_float => .{ .float_ty = .{
253 .bits = @intCast(u16, data),
254 } },
255 .type_vector => .{
256 .vector_ty = self.extraData(Key.VectorType, data),
257 },
258 };
259}
260
261fn addExtra(self: *Self, gpa: Allocator, extra: anytype) !u32 {
262 const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
263 try self.extra.ensureUnusedCapacity(gpa, fields.len);
264 try self.addExtraAssumeCapacity(extra);
265}
266
267fn addExtraAssumeCapacity(self: *Self, extra: anytype) !u32 {
268 const payload_offset = @intCast(u32, self.extra.items.len);
269 inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| {
270 const field_val = @field(field, field.name);
271 const word = switch (field.type) {
272 u32 => field_val,
273 Ref => @enumToInt(field_val),
274 else => @compileError("Invalid type: " ++ @typeName(field.type)),
275 };
276 self.extra.appendAssumeCapacity(word);
277 }
278 return payload_offset;
279}
280
281fn extraData(self: Self, comptime T: type, offset: u32) T {
282 var result: T = undefined;
283 inline for (@typeInfo(T).Struct.fields, 0..) |field, i| {
284 const word = self.extra.items[offset + i];
285 @field(result, field.name) = switch (field.type) {
286 u32 => word,
287 Ref => @intToEnum(Ref, word),
288 else => @compileError("Invalid type: " ++ @typeName(field.type)),
289 };
290 }
291 return result;
292}