| ... | @@ -1,16 +1,19 @@ | ... | @@ -1,16 +1,19 @@ |
| 1 | //! This file implements an InternPool-like structure that caches | 1 | //! This file implements an InternPool-like structure that caches |
| 2 | //! SPIR-V types and constants. | 2 | //! SPIR-V types and constants. Instead of generating type and |
| 3 | //! In the case of SPIR-V, the type- and constant instructions | 3 | //! constant instructions directly, we first keep a representation |
| 4 | //! describe the type and constant fully. This means we can save | 4 | //! in a compressed database. This is then only later turned into |
| 5 | //! memory by representing these items directly in spir-v code, | 5 | //! actual SPIR-V instructions. |
| 6 | //! and decoding that when required. | 6 | //! Note: This cache is insertion-ordered. This means that we |
| 7 | //! This does not work for OpDecorate instructions though, and for | 7 | //! can materialize the SPIR-V instructions in the proper order, |
| 8 | //! those we keep some additional metadata. | 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. |
| 9 | | 12 | |
| 10 | const std = @import("std"); | 13 | const std = @import("std"); |
| 11 | const Allocator = std.mem.Allocator; | 14 | const Allocator = std.mem.Allocator; |
| 12 | | 15 | |
| 13 | const Section = @import("section.zig"); | 16 | const Section = @import("Section.zig"); |
| 14 | const Module = @import("Module.zig"); | 17 | const Module = @import("Module.zig"); |
| 15 | | 18 | |
| 16 | const spec = @import("spec.zig"); | 19 | const spec = @import("spec.zig"); |
| ... | @@ -21,7 +24,7 @@ const Self = @This(); | ... | @@ -21,7 +24,7 @@ const Self = @This(); |
| 21 | | 24 | |
| 22 | map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, | 25 | map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, |
| 23 | items: std.MultiArrayList(Item) = .{}, | 26 | items: std.MultiArrayList(Item) = .{}, |
| 24 | extra: std.ArrayHashMapUnmanaged(u32) = .{}, | 27 | extra: std.ArrayListUnmanaged(u32) = .{}, |
| 25 | | 28 | |
| 26 | const Item = struct { | 29 | const Item = struct { |
| 27 | tag: Tag, | 30 | tag: Tag, |
| ... | @@ -32,6 +35,7 @@ const Item = struct { | ... | @@ -32,6 +35,7 @@ const Item = struct { |
| 32 | }; | 35 | }; |
| 33 | | 36 | |
| 34 | const Tag = enum { | 37 | const Tag = enum { |
| | 38 | // -- Types |
| 35 | /// Simple type that has no additional data. | 39 | /// Simple type that has no additional data. |
| 36 | /// data is SimpleType. | 40 | /// data is SimpleType. |
| 37 | type_simple, | 41 | type_simple, |
| ... | @@ -45,13 +49,18 @@ const Tag = enum { | ... | @@ -45,13 +49,18 @@ const Tag = enum { |
| 45 | /// data is number of bits | 49 | /// data is number of bits |
| 46 | type_float, | 50 | type_float, |
| 47 | /// Vector type | 51 | /// Vector type |
| 48 | /// data is payload to Key.VectorType | 52 | /// data is payload to VectorType |
| 49 | type_vector, | 53 | type_vector, |
| | 54 | /// Array type |
| | 55 | /// data is payload to ArrayType |
| | 56 | type_array, |
| 50 | | 57 | |
| 51 | const SimpleType = enum { | 58 | // -- Values |
| 52 | void, | 59 | |
| 53 | bool, | 60 | const SimpleType = enum { void, bool }; |
| 54 | }; | 61 | |
| | 62 | const VectorType = Key.VectorType; |
| | 63 | const ArrayType = Key.ArrayType; |
| 55 | }; | 64 | }; |
| 56 | | 65 | |
| 57 | pub const Ref = enum(u32) { _ }; | 66 | pub const Ref = enum(u32) { _ }; |
| ... | @@ -61,11 +70,15 @@ pub const Ref = enum(u32) { _ }; | ... | @@ -61,11 +70,15 @@ pub const Ref = enum(u32) { _ }; |
| 61 | /// database: Values described for this structure are ephemeral and stored | 70 | /// database: Values described for this structure are ephemeral and stored |
| 62 | /// in a more memory-efficient manner internally. | 71 | /// in a more memory-efficient manner internally. |
| 63 | pub const Key = union(enum) { | 72 | pub const Key = union(enum) { |
| 64 | void_ty, | 73 | // -- Types |
| 65 | bool_ty, | 74 | void_type, |
| 66 | int_ty: IntType, | 75 | bool_type, |
| 67 | float_ty: FloatType, | 76 | int_type: IntType, |
| 68 | vector_ty: VectorType, | 77 | float_type: FloatType, |
| | 78 | vector_type: VectorType, |
| | 79 | array_type: ArrayType, |
| | 80 | |
| | 81 | // -- values |
| 69 | | 82 | |
| 70 | pub const IntType = std.builtin.Type.Int; | 83 | pub const IntType = std.builtin.Type.Int; |
| 71 | pub const FloatType = std.builtin.Type.Float; | 84 | pub const FloatType = std.builtin.Type.Float; |
| ... | @@ -75,55 +88,66 @@ pub const Key = union(enum) { | ... | @@ -75,55 +88,66 @@ pub const Key = union(enum) { |
| 75 | component_count: u32, | 88 | component_count: u32, |
| 76 | }; | 89 | }; |
| 77 | | 90 | |
| | 91 | pub const ArrayType = struct { |
| | 92 | /// Child type of this array. |
| | 93 | element_type: Ref, |
| | 94 | /// Reference to a constant. |
| | 95 | length: Ref, |
| | 96 | /// Type has the 'ArrayStride' decoration. |
| | 97 | /// If zero, no stride is present. |
| | 98 | stride: u32 = 0, |
| | 99 | }; |
| | 100 | |
| 78 | fn hash(self: Key) u32 { | 101 | fn hash(self: Key) u32 { |
| 79 | var hasher = std.hash.Wyhash.init(0); | 102 | var hasher = std.hash.Wyhash.init(0); |
| 80 | std.hash.autoHash(&hasher, self); | 103 | std.hash.autoHash(&hasher, self); |
| 81 | return @truncate(u32, hasher.final()); | 104 | return @truncate(u32, hasher.final()); |
| 82 | } | 105 | } |
| 83 | | 106 | |
| 84 | fn eql(a: Key, b: Key) u32 { | 107 | fn eql(a: Key, b: Key) bool { |
| 85 | return std.meta.eql(a, b); | 108 | return std.meta.eql(a, b); |
| 86 | } | 109 | } |
| 87 | | 110 | |
| 88 | pub const Adapter = struct { | 111 | pub const Adapter = struct { |
| 89 | self: *const Self, | 112 | self: *const Self, |
| 90 | | 113 | |
| 91 | pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: u32) bool { | 114 | pub fn eql(ctx: @This(), a: Key, b_void: void, b_index: usize) bool { |
| 92 | _ = b_void; | 115 | _ = b_void; |
| 93 | return ctx.self.lookup(@intToEnum(Ref, b_map_index)).eql(a); | 116 | return ctx.self.lookup(@intToEnum(Ref, b_index)).eql(a); |
| 94 | } | 117 | } |
| 95 | | 118 | |
| 96 | pub fn hash(ctx: @This(), a: Key) u32 { | 119 | pub fn hash(ctx: @This(), a: Key) u32 { |
| 97 | return ctx.self.hash(a); | 120 | _ = ctx; |
| | 121 | return a.hash(); |
| 98 | } | 122 | } |
| 99 | }; | 123 | }; |
| 100 | | 124 | |
| 101 | fn toSimpleType(self: Key) Tag.SimpleType { | 125 | fn toSimpleType(self: Key) Tag.SimpleType { |
| 102 | return switch (self) { | 126 | return switch (self) { |
| 103 | .void_ty => .void, | 127 | .void_type => .void, |
| 104 | .bool_ty => .bool, | 128 | .bool_type => .bool, |
| 105 | else => unreachable, | 129 | else => unreachable, |
| 106 | }; | 130 | }; |
| 107 | } | 131 | } |
| 108 | }; | 132 | }; |
| 109 | | 133 | |
| 110 | pub fn deinit(self: *Self, spv: Module) void { | 134 | pub fn deinit(self: *Self, spv: *const Module) void { |
| 111 | self.map.deinit(spv.gpa); | 135 | self.map.deinit(spv.gpa); |
| 112 | self.items.deinit(spv.gpa); | 136 | self.items.deinit(spv.gpa); |
| 113 | self.extra.deinit(spv.gpa); | 137 | self.extra.deinit(spv.gpa); |
| 114 | } | 138 | } |
| 115 | | 139 | |
| 116 | /// Actually materialize the database into spir-v instructions. | 140 | /// Actually materialize the database into spir-v instructions. |
| 117 | // TODO: This should generate decorations as well as regular instructions. | 141 | /// This function returns a spir-v section of (only) constant and type instructions. |
| 118 | // Important is that these are generated in-order, but that should be fine. | 142 | /// Additionally, decorations, debug names, etc, are all directly emitted into the |
| 119 | pub fn finalize(self: *Self, spv: *Module) !void { | 143 | /// `spv` module. The section is allocated with `spv.gpa`. |
| 120 | // This function should really be the only one that modifies spv.types_and_constants. | 144 | pub fn materialize(self: *Self, spv: *Module) !Section { |
| 121 | // TODO: Make this function return the section instead. | 145 | var section = Section{}; |
| 122 | std.debug.assert(spv.sections.types_and_constants.instructions.items.len == 0); | 146 | errdefer section.deinit(spv.gpa); |
| 123 | | | |
| 124 | for (self.items.items(.result_id), 0..) |result_id, index| { | 147 | for (self.items.items(.result_id), 0..) |result_id, index| { |
| 125 | try self.emit(spv, result_id, @intToEnum(Ref, index)); | 148 | try self.emit(spv, result_id, @intToEnum(Ref, index), &section); |
| 126 | } | 149 | } |
| | 150 | return section; |
| 127 | } | 151 | } |
| 128 | | 152 | |
| 129 | fn emit( | 153 | fn emit( |
| ... | @@ -131,97 +155,104 @@ fn emit( | ... | @@ -131,97 +155,104 @@ fn emit( |
| 131 | spv: *Module, | 155 | spv: *Module, |
| 132 | result_id: IdResult, | 156 | result_id: IdResult, |
| 133 | ref: Ref, | 157 | ref: Ref, |
| | 158 | section: *Section, |
| 134 | ) !void { | 159 | ) !void { |
| 135 | const tc = &spv.sections.types_and_constants; | | |
| 136 | const key = self.lookup(ref); | 160 | const key = self.lookup(ref); |
| 137 | switch (key) { | 161 | switch (key) { |
| 138 | .void_ty => { | 162 | .void_type => { |
| 139 | try tc.emit(spv.gpa, .OpTypeVoid, .{ .id_result = result_id }); | 163 | try section.emit(spv.gpa, .OpTypeVoid, .{ .id_result = result_id }); |
| 140 | try spv.debugName(result_id, "void", .{}); | 164 | try spv.debugName(result_id, "void", .{}); |
| 141 | }, | 165 | }, |
| 142 | .bool_ty => { | 166 | .bool_type => { |
| 143 | try tc.emit(spv.gpa, .OpTypeBool, .{ .id_result = result_id }); | 167 | try section.emit(spv.gpa, .OpTypeBool, .{ .id_result = result_id }); |
| 144 | try spv.debugName(result_id, "bool", .{}); | 168 | try spv.debugName(result_id, "bool", .{}); |
| 145 | }, | 169 | }, |
| 146 | .int_ty => |int| { | 170 | .int_type => |int| { |
| 147 | try tc.emit(spv.gpa, .OpTypeInt, .{ | 171 | try section.emit(spv.gpa, .OpTypeInt, .{ |
| 148 | .id_result = result_id, | 172 | .id_result = result_id, |
| 149 | .width = int.bits, | 173 | .width = int.bits, |
| 150 | .signedness = switch (int.signedness) { | 174 | .signedness = switch (int.signedness) { |
| 151 | .unsigned => 0, | 175 | .unsigned => @as(spec.Word, 0), |
| 152 | .signed => 1, | 176 | .signed => 1, |
| 153 | }, | 177 | }, |
| 154 | }); | 178 | }); |
| 155 | const ui: []const u8 = switch (int.signedness) { | 179 | const ui: []const u8 = switch (int.signedness) { |
| 156 | 0 => "u", | 180 | .unsigned => "u", |
| 157 | 1 => "i", | 181 | .signed => "i", |
| 158 | else => unreachable, | | |
| 159 | }; | 182 | }; |
| 160 | try spv.debugName(result_id, "{s}{}", .{ ui, int.bits }); | 183 | try spv.debugName(result_id, "{s}{}", .{ ui, int.bits }); |
| 161 | }, | 184 | }, |
| 162 | .float_ty => |float| { | 185 | .float_type => |float| { |
| 163 | try tc.emit(spv.gpa, .OpTypeFloat, .{ | 186 | try section.emit(spv.gpa, .OpTypeFloat, .{ |
| 164 | .id_result = result_id, | 187 | .id_result = result_id, |
| 165 | .width = float.bits, | 188 | .width = float.bits, |
| 166 | }); | 189 | }); |
| 167 | try spv.debugName(result_id, "f{}", .{float.bits}); | 190 | try spv.debugName(result_id, "f{}", .{float.bits}); |
| 168 | }, | 191 | }, |
| 169 | .vector_ty => |vector| { | 192 | .vector_type => |vector| { |
| 170 | try tc.emit(spv.gpa, .OpTypeVector, .{ | 193 | try section.emit(spv.gpa, .OpTypeVector, .{ |
| 171 | .id_result = result_id, | 194 | .id_result = result_id, |
| 172 | .component_type = self.resultId(vector.component_type), | 195 | .component_type = self.resultId(vector.component_type), |
| 173 | .component_count = vector.component_count, | 196 | .component_count = vector.component_count, |
| 174 | }); | 197 | }); |
| 175 | }, | 198 | }, |
| | 199 | .array_type => |array| { |
| | 200 | try section.emit(spv.gpa, .OpTypeArray, .{ |
| | 201 | .id_result = result_id, |
| | 202 | .element_type = self.resultId(array.element_type), |
| | 203 | .length = self.resultId(array.length), |
| | 204 | }); |
| | 205 | if (array.stride != 0) { |
| | 206 | try spv.decorate(result_id, .{ .ArrayStride = .{ .array_stride = array.stride } }); |
| | 207 | } |
| | 208 | }, |
| 176 | } | 209 | } |
| 177 | } | 210 | } |
| 178 | | 211 | |
| 179 | /// Add a key to this cache. Returns a reference to the key that | 212 | /// Add a key to this cache. Returns a reference to the key that |
| 180 | /// was added. The corresponding result-id can be queried using | 213 | /// was added. The corresponding result-id can be queried using |
| 181 | /// self.resultId with the result. | 214 | /// self.resultId with the result. |
| 182 | pub fn add(self: *Self, spv: *Module, key: Key) !Ref { | 215 | pub fn resolve(self: *Self, spv: *Module, key: Key) !Ref { |
| 183 | const adapter: Key.Adapter = .{ .self = self }; | 216 | const adapter: Key.Adapter = .{ .self = self }; |
| 184 | const entry = try self.map.getOrPutAdapted(spv.gpa, key, adapter); | 217 | const entry = try self.map.getOrPutAdapted(spv.gpa, key, adapter); |
| 185 | if (entry.found_existing) { | 218 | if (entry.found_existing) { |
| 186 | return @intToEnum(Ref, entry.index); | 219 | return @intToEnum(Ref, entry.index); |
| 187 | } | 220 | } |
| 188 | const result_id = spv.allocId(); | 221 | const result_id = spv.allocId(); |
| 189 | try self.items.ensureUnusedCapacity(spv.gpa, 1); | 222 | const item: Item = switch (key) { |
| 190 | switch (key) { | 223 | inline .void_type, .bool_type => .{ |
| 191 | inline .void_ty, .bool_ty => { | 224 | .tag = .type_simple, |
| 192 | self.items.appendAssumeCapacity(.{ | 225 | .result_id = result_id, |
| 193 | .tag = .type_simple, | 226 | .data = @enumToInt(key.toSimpleType()), |
| 194 | .result_id = result_id, | | |
| 195 | .data = @enumToInt(key.toSimpleType()), | | |
| 196 | }); | | |
| 197 | }, | 227 | }, |
| 198 | .int_ty => |int| { | 228 | .int_type => |int| blk: { |
| 199 | const t: Tag = switch (int.signedness) { | 229 | const t: Tag = switch (int.signedness) { |
| 200 | .signed => .type_int_signed, | 230 | .signed => .type_int_signed, |
| 201 | .unsigned => .type_int_unsigned, | 231 | .unsigned => .type_int_unsigned, |
| 202 | }; | 232 | }; |
| 203 | self.items.appendAssumeCapacity(.{ | 233 | break :blk .{ |
| 204 | .tag = t, | 234 | .tag = t, |
| 205 | .result_id = result_id, | 235 | .result_id = result_id, |
| 206 | .data = int.bits, | 236 | .data = int.bits, |
| 207 | }); | 237 | }; |
| 208 | }, | 238 | }, |
| 209 | .float_ty => |float| { | 239 | .float_type => |float| .{ |
| 210 | self.items.appendAssumeCapacity(.{ | 240 | .tag = .type_float, |
| 211 | .tag = .type_float, | 241 | .result_id = result_id, |
| 212 | .result_id = result_id, | 242 | .data = float.bits, |
| 213 | .data = float.bits, | | |
| 214 | }); | | |
| 215 | }, | 243 | }, |
| 216 | .vector_ty => |vec| { | 244 | .vector_type => |vector| .{ |
| 217 | const payload = try self.addExtra(vec); | 245 | .tag = .type_vector, |
| 218 | self.items.appendAssumeCapacity(.{ | 246 | .result_id = result_id, |
| 219 | .tag = .type_vector, | 247 | .data = try self.addExtra(spv, vector), |
| 220 | .result_id = result_id, | | |
| 221 | .data = payload, | | |
| 222 | }); | | |
| 223 | }, | 248 | }, |
| 224 | } | 249 | .array_type => |array| .{ |
| | 250 | .tag = .type_array, |
| | 251 | .result_id = result_id, |
| | 252 | .data = try self.addExtra(spv, array), |
| | 253 | }, |
| | 254 | }; |
| | 255 | try self.items.append(spv.gpa, item); |
| 225 | | 256 | |
| 226 | return @intToEnum(Ref, entry.index); | 257 | return @intToEnum(Ref, entry.index); |
| 227 | } | 258 | } |
| ... | @@ -238,36 +269,35 @@ pub fn lookup(self: *const Self, ref: Ref) Key { | ... | @@ -238,36 +269,35 @@ pub fn lookup(self: *const Self, ref: Ref) Key { |
| 238 | const data = item.data; | 269 | const data = item.data; |
| 239 | return switch (item.tag) { | 270 | return switch (item.tag) { |
| 240 | .type_simple => switch (@intToEnum(Tag.SimpleType, data)) { | 271 | .type_simple => switch (@intToEnum(Tag.SimpleType, data)) { |
| 241 | .void => .void_ty, | 272 | .void => .void_type, |
| 242 | .bool => .bool_ty, | 273 | .bool => .bool_type, |
| 243 | }, | 274 | }, |
| 244 | .type_int_signed => .{ .int_ty = .{ | 275 | .type_int_signed => .{ .int_type = .{ |
| 245 | .signedness = .signed, | 276 | .signedness = .signed, |
| 246 | .bits = @intCast(u16, data), | 277 | .bits = @intCast(u16, data), |
| 247 | } }, | 278 | } }, |
| 248 | .type_int_unsigned => .{ .int_ty = .{ | 279 | .type_int_unsigned => .{ .int_type = .{ |
| 249 | .signedness = .unsigned, | 280 | .signedness = .unsigned, |
| 250 | .bits = @intCast(u16, data), | 281 | .bits = @intCast(u16, data), |
| 251 | } }, | 282 | } }, |
| 252 | .type_float => .{ .float_ty = .{ | 283 | .type_float => .{ .float_type = .{ |
| 253 | .bits = @intCast(u16, data), | 284 | .bits = @intCast(u16, data), |
| 254 | } }, | 285 | } }, |
| 255 | .type_vector => .{ | 286 | .type_vector => .{ .vector_type = self.extraData(Tag.VectorType, data) }, |
| 256 | .vector_ty = self.extraData(Key.VectorType, data), | 287 | .type_array => .{ .array_type = self.extraData(Tag.ArrayType, data) }, |
| 257 | }, | | |
| 258 | }; | 288 | }; |
| 259 | } | 289 | } |
| 260 | | 290 | |
| 261 | fn addExtra(self: *Self, gpa: Allocator, extra: anytype) !u32 { | 291 | fn addExtra(self: *Self, spv: *Module, extra: anytype) !u32 { |
| 262 | const fields = @typeInfo(@TypeOf(extra)).Struct.fields; | 292 | const fields = @typeInfo(@TypeOf(extra)).Struct.fields; |
| 263 | try self.extra.ensureUnusedCapacity(gpa, fields.len); | 293 | try self.extra.ensureUnusedCapacity(spv.gpa, fields.len); |
| 264 | try self.addExtraAssumeCapacity(extra); | 294 | return try self.addExtraAssumeCapacity(extra); |
| 265 | } | 295 | } |
| 266 | | 296 | |
| 267 | fn addExtraAssumeCapacity(self: *Self, extra: anytype) !u32 { | 297 | fn addExtraAssumeCapacity(self: *Self, extra: anytype) !u32 { |
| 268 | const payload_offset = @intCast(u32, self.extra.items.len); | 298 | const payload_offset = @intCast(u32, self.extra.items.len); |
| 269 | inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| { | 299 | inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| { |
| 270 | const field_val = @field(field, field.name); | 300 | const field_val = @field(extra, field.name); |
| 271 | const word = switch (field.type) { | 301 | const word = switch (field.type) { |
| 272 | u32 => field_val, | 302 | u32 => field_val, |
| 273 | Ref => @enumToInt(field_val), | 303 | Ref => @enumToInt(field_val), |
| ... | @@ -279,8 +309,13 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) !u32 { | ... | @@ -279,8 +309,13 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) !u32 { |
| 279 | } | 309 | } |
| 280 | | 310 | |
| 281 | fn extraData(self: Self, comptime T: type, offset: u32) T { | 311 | fn extraData(self: Self, comptime T: type, offset: u32) T { |
| | 312 | return self.extraDataTrail(T, offset).data; |
| | 313 | } |
| | 314 | |
| | 315 | fn extraDataTrail(self: Self, comptime T: type, offset: u32) struct { data: T, trail: u32 } { |
| 282 | var result: T = undefined; | 316 | var result: T = undefined; |
| 283 | inline for (@typeInfo(T).Struct.fields, 0..) |field, i| { | 317 | const fields = @typeInfo(T).Struct.fields; |
| | 318 | inline for (fields, 0..) |field, i| { |
| 284 | const word = self.extra.items[offset + i]; | 319 | const word = self.extra.items[offset + i]; |
| 285 | @field(result, field.name) = switch (field.type) { | 320 | @field(result, field.name) = switch (field.type) { |
| 286 | u32 => word, | 321 | u32 => word, |
| ... | @@ -288,5 +323,8 @@ fn extraData(self: Self, comptime T: type, offset: u32) T { | ... | @@ -288,5 +323,8 @@ fn extraData(self: Self, comptime T: type, offset: u32) T { |
| 288 | else => @compileError("Invalid type: " ++ @typeName(field.type)), | 323 | else => @compileError("Invalid type: " ++ @typeName(field.type)), |
| 289 | }; | 324 | }; |
| 290 | } | 325 | } |
| 291 | return result; | 326 | return .{ |
| | 327 | .data = result, |
| | 328 | .trail = offset + @intCast(u32, fields.len), |
| | 329 | }; |
| 292 | } | 330 | } |