aboutsummaryrefslogtreecommitdiff
path: root/src/codegen/spirv/Module.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/codegen/spirv/Module.zig')
-rw-r--r--src/codegen/spirv/Module.zig207
1 files changed, 181 insertions, 26 deletions
diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig
index f4af74b3aa..317e32c878 100644
--- a/src/codegen/spirv/Module.zig
+++ b/src/codegen/spirv/Module.zig
@@ -10,6 +10,8 @@ const Module = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
+const autoHashStrat = std.hash.autoHashStrat;
+const Wyhash = std.hash.Wyhash;
const spec = @import("spec.zig");
const Word = spec.Word;
@@ -19,6 +21,19 @@ const IdResultType = spec.IdResultType;
const Section = @import("Section.zig");
+/// Helper HashMap type to hash deeply
+fn DeepHashMap(K: type, V: type) type {
+ return std.HashMapUnmanaged(K, V, struct {
+ pub fn hash(ctx: @This(), key: K) u64 {
+ _ = ctx;
+ var hasher = Wyhash.init(0);
+ autoHashStrat(&hasher, key, .Deep);
+ return hasher.final();
+ }
+ pub const eql = std.hash_map.getAutoEqlFn(K, @This());
+ }, std.hash_map.default_max_load_percentage);
+}
+
/// This structure represents a function that isc in-progress of being emitted.
/// Commonly, the contents of this structure will be merged with the appropriate
/// sections of the module and re-used. Note that the SPIR-V module system makes
@@ -103,6 +118,12 @@ gpa: Allocator,
/// Arena for things that need to live for the length of this program.
arena: std.heap.ArenaAllocator,
+/// Target info
+target: std.Target,
+
+/// The target SPIR-V version
+version: spec.Version,
+
/// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module".
sections: struct {
/// Capability instructions
@@ -159,8 +180,16 @@ cache: struct {
// This cache is required so that @Vector(X, u1) in direct representation has the
// same ID as @Vector(X, bool) in indirect representation.
vector_types: std.AutoHashMapUnmanaged(struct { IdRef, u32 }, IdRef) = .empty,
+ array_types: std.AutoHashMapUnmanaged(struct { IdRef, IdRef }, IdRef) = .empty,
+ function_types: DeepHashMap(struct { IdRef, []const IdRef }, IdRef) = .empty,
+ capabilities: std.AutoHashMapUnmanaged(spec.Capability, void) = .empty,
+ extensions: std.StringHashMapUnmanaged(void) = .empty,
+ extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, IdRef) = .empty,
+ decorations: std.AutoHashMapUnmanaged(struct { IdRef, spec.Decoration }, void) = .empty,
builtins: std.AutoHashMapUnmanaged(struct { IdRef, spec.BuiltIn }, Decl.Index) = .empty,
+
+ bool_const: [2]?IdRef = .{ null, null },
} = .{},
/// Set of Decls, referred to by Decl.Index.
@@ -173,13 +202,23 @@ decl_deps: std.ArrayListUnmanaged(Decl.Index) = .empty,
/// The list of entry points that should be exported from this module.
entry_points: std.ArrayListUnmanaged(EntryPoint) = .empty,
-/// The list of extended instruction sets that should be imported.
-extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, IdRef) = .empty,
+pub fn init(gpa: Allocator, target: std.Target) Module {
+ const version_minor: u8 = blk: {
+ // Prefer higher versions
+ if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_6)) break :blk 6;
+ if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_5)) break :blk 5;
+ if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_4)) break :blk 4;
+ if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_3)) break :blk 3;
+ if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_2)) break :blk 2;
+ if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_1)) break :blk 1;
+ break :blk 0;
+ };
-pub fn init(gpa: Allocator) Module {
return .{
.gpa = gpa,
.arena = std.heap.ArenaAllocator.init(gpa),
+ .target = target,
+ .version = .{ .major = 1, .minor = version_minor },
.next_result_id = 1, // 0 is an invalid SPIR-V result id, so start counting at 1.
};
}
@@ -201,14 +240,18 @@ pub fn deinit(self: *Module) void {
self.cache.int_types.deinit(self.gpa);
self.cache.float_types.deinit(self.gpa);
self.cache.vector_types.deinit(self.gpa);
+ self.cache.array_types.deinit(self.gpa);
+ self.cache.function_types.deinit(self.gpa);
+ self.cache.capabilities.deinit(self.gpa);
+ self.cache.extensions.deinit(self.gpa);
+ self.cache.extended_instruction_set.deinit(self.gpa);
+ self.cache.decorations.deinit(self.gpa);
self.cache.builtins.deinit(self.gpa);
self.decls.deinit(self.gpa);
self.decl_deps.deinit(self.gpa);
-
self.entry_points.deinit(self.gpa);
- self.extended_instruction_set.deinit(self.gpa);
self.arena.deinit();
self.* = undefined;
@@ -240,6 +283,10 @@ pub fn idBound(self: Module) Word {
return self.next_result_id;
}
+pub fn hasFeature(self: *Module, feature: std.Target.spirv.Feature) bool {
+ return std.Target.spirv.featureSetHas(self.target.cpu.features, feature);
+}
+
fn addEntryPointDeps(
self: *Module,
decl_index: Decl.Index,
@@ -292,25 +339,68 @@ fn entryPoints(self: *Module) !Section {
return entry_points;
}
-pub fn finalize(self: *Module, a: Allocator, target: std.Target) ![]Word {
+pub fn finalize(self: *Module, a: Allocator) ![]Word {
+ // Emit capabilities and extensions
+ for (std.Target.spirv.all_features) |feature| {
+ if (self.target.cpu.features.isEnabled(feature.index)) {
+ const feature_tag: std.Target.spirv.Feature = @enumFromInt(feature.index);
+ switch (feature_tag) {
+ .v1_0, .v1_1, .v1_2, .v1_3, .v1_4, .v1_5, .v1_6 => {},
+ .int8 => try self.addCapability(.Int8),
+ .int16 => try self.addCapability(.Int16),
+ .int64 => try self.addCapability(.Int64),
+ .float16 => try self.addCapability(.Float16),
+ .float64 => try self.addCapability(.Float64),
+ .addresses => if (self.hasFeature(.shader)) {
+ try self.addCapability(.PhysicalStorageBufferAddresses);
+ try self.addExtension("SPV_KHR_physical_storage_buffer");
+ } else {
+ try self.addCapability(.Addresses);
+ },
+ .matrix => try self.addCapability(.Matrix),
+ .kernel => try self.addCapability(.Kernel),
+ .generic_pointer => try self.addCapability(.GenericPointer),
+ .vector16 => try self.addCapability(.Vector16),
+ .shader => try self.addCapability(.Shader),
+ }
+ }
+ }
+
+ // Emit memory model
+ const addressing_model: spec.AddressingModel = blk: {
+ if (self.hasFeature(.shader)) {
+ break :blk switch (self.target.cpu.arch) {
+ .spirv32 => .Logical, // TODO: I don't think this will ever be implemented.
+ .spirv64 => .PhysicalStorageBuffer64,
+ else => unreachable,
+ };
+ } else if (self.hasFeature(.kernel)) {
+ break :blk switch (self.target.cpu.arch) {
+ .spirv32 => .Physical32,
+ .spirv64 => .Physical64,
+ else => unreachable,
+ };
+ }
+
+ unreachable;
+ };
+ try self.sections.memory_model.emit(self.gpa, .OpMemoryModel, .{
+ .addressing_model = addressing_model,
+ .memory_model = switch (self.target.os.tag) {
+ .opencl => .OpenCL,
+ .vulkan, .opengl => .GLSL450,
+ else => unreachable,
+ },
+ });
+
// See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
// TODO: Audit calls to allocId() in this function to make it idempotent.
-
var entry_points = try self.entryPoints();
defer entry_points.deinit(self.gpa);
const header = [_]Word{
spec.magic_number,
- // TODO: From cpu features
- spec.Version.toWord(.{
- .major = 1,
- .minor = switch (target.os.tag) {
- // Emit SPIR-V 1.3 for now. This is the highest version that Vulkan 1.1 supports.
- .vulkan => 3,
- // Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports.
- else => 4,
- },
- }),
+ self.version.toWord(),
spec.zig_generator_id,
self.idBound(),
0, // Schema (currently reserved for future use)
@@ -319,7 +409,7 @@ pub fn finalize(self: *Module, a: Allocator, target: std.Target) ![]Word {
var source = Section{};
defer source.deinit(self.gpa);
try self.sections.debug_strings.emit(self.gpa, .OpSource, .{
- .source_language = .Unknown,
+ .source_language = .Zig,
.version = 0,
// We cannot emit these because the Khronos translator does not parse this instruction
// correctly.
@@ -368,11 +458,23 @@ pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void {
try self.declareDeclDeps(decl_index, func.decl_deps.keys());
}
+pub fn addCapability(self: *Module, cap: spec.Capability) !void {
+ const entry = try self.cache.capabilities.getOrPut(self.gpa, cap);
+ if (entry.found_existing) return;
+ try self.sections.capabilities.emit(self.gpa, .OpCapability, .{ .capability = cap });
+}
+
+pub fn addExtension(self: *Module, ext: []const u8) !void {
+ const entry = try self.cache.extensions.getOrPut(self.gpa, ext);
+ if (entry.found_existing) return;
+ try self.sections.extensions.emit(self.gpa, .OpExtension, .{ .name = ext });
+}
+
/// Imports or returns the existing id of an extended instruction set
pub fn importInstructionSet(self: *Module, set: spec.InstructionSet) !IdRef {
assert(set != .core);
- const gop = try self.extended_instruction_set.getOrPut(self.gpa, set);
+ const gop = try self.cache.extended_instruction_set.getOrPut(self.gpa, set);
if (gop.found_existing) return gop.value_ptr.*;
const result_id = self.allocId();
@@ -477,20 +579,69 @@ pub fn floatType(self: *Module, bits: u16) !IdRef {
return entry.value_ptr.*;
}
-pub fn vectorType(self: *Module, len: u32, child_id: IdRef) !IdRef {
- const entry = try self.cache.vector_types.getOrPut(self.gpa, .{ child_id, len });
+pub fn vectorType(self: *Module, len: u32, child_ty_id: IdRef) !IdRef {
+ const entry = try self.cache.vector_types.getOrPut(self.gpa, .{ child_ty_id, len });
if (!entry.found_existing) {
const result_id = self.allocId();
entry.value_ptr.* = result_id;
try self.sections.types_globals_constants.emit(self.gpa, .OpTypeVector, .{
.id_result = result_id,
- .component_type = child_id,
+ .component_type = child_ty_id,
.component_count = len,
});
}
return entry.value_ptr.*;
}
+pub fn arrayType(self: *Module, len_id: IdRef, child_ty_id: IdRef) !IdRef {
+ const entry = try self.cache.array_types.getOrPut(self.gpa, .{ child_ty_id, len_id });
+ if (!entry.found_existing) {
+ const result_id = self.allocId();
+ entry.value_ptr.* = result_id;
+ try self.sections.types_globals_constants.emit(self.gpa, .OpTypeArray, .{
+ .id_result = result_id,
+ .element_type = child_ty_id,
+ .length = len_id,
+ });
+ }
+ return entry.value_ptr.*;
+}
+
+pub fn functionType(self: *Module, return_ty_id: IdRef, param_type_ids: []const IdRef) !IdRef {
+ const entry = try self.cache.function_types.getOrPut(self.gpa, .{ return_ty_id, param_type_ids });
+ if (!entry.found_existing) {
+ const result_id = self.allocId();
+ entry.value_ptr.* = result_id;
+ try self.sections.types_globals_constants.emit(self.gpa, .OpTypeFunction, .{
+ .id_result = result_id,
+ .return_type = return_ty_id,
+ .id_ref_2 = param_type_ids,
+ });
+ }
+ return entry.value_ptr.*;
+}
+
+pub fn constBool(self: *Module, value: bool) !IdRef {
+ if (self.cache.bool_const[@intFromBool(value)]) |b| return b;
+
+ const result_ty_id = try self.boolType();
+ const result_id = self.allocId();
+ self.cache.bool_const[@intFromBool(value)] = result_id;
+
+ switch (value) {
+ inline else => |value_ct| try self.sections.types_globals_constants.emit(
+ self.gpa,
+ if (value_ct) .OpConstantTrue else .OpConstantFalse,
+ .{
+ .id_result_type = result_ty_id,
+ .id_result = result_id,
+ },
+ ),
+ }
+
+ return result_id;
+}
+
/// Return a pointer to a builtin variable. `result_ty_id` must be a **pointer**
/// with storage class `.Input`.
pub fn builtin(self: *Module, result_ty_id: IdRef, spirv_builtin: spec.BuiltIn) !Decl.Index {
@@ -534,13 +685,17 @@ pub fn decorate(
target: IdRef,
decoration: spec.Decoration.Extended,
) !void {
- try self.sections.annotations.emit(self.gpa, .OpDecorate, .{
- .target = target,
- .decoration = decoration,
- });
+ const entry = try self.cache.decorations.getOrPut(self.gpa, .{ target, decoration });
+ if (!entry.found_existing) {
+ try self.sections.annotations.emit(self.gpa, .OpDecorate, .{
+ .target = target,
+ .decoration = decoration,
+ });
+ }
}
/// Decorate a result-id which is a member of some struct.
+/// We really don't have to and shouldn't need to cache this.
pub fn decorateMember(
self: *Module,
structure_type: IdRef,