spirv: make generic globals invocation-local
This commit is contained in:
@@ -72,9 +72,20 @@ pub const Decl = struct {
|
||||
/// Index to refer to a Decl by.
|
||||
pub const Index = enum(u32) { _ };
|
||||
|
||||
/// The result-id to be used for this declaration. This is the final result-id
|
||||
/// of the decl, which may be an OpFunction, OpVariable, or the result of a sequence
|
||||
/// of OpSpecConstantOp operations.
|
||||
/// Useful to tell what kind of decl this is, and hold the result-id or field index
|
||||
/// to be used for this decl.
|
||||
pub const Kind = enum {
|
||||
func,
|
||||
global,
|
||||
invocation_global,
|
||||
};
|
||||
|
||||
/// See comment on Kind
|
||||
kind: Kind,
|
||||
/// The result-id associated to this decl. The specific meaning of this depends on `kind`:
|
||||
/// - For `func`, this is the result-id of the associated OpFunction instruction.
|
||||
/// - For `global`, this is the result-id of the associated OpVariable instruction.
|
||||
/// - For `invocation_global`, this is the result-id of the associated InvocationGlobal instruction.
|
||||
result_id: IdRef,
|
||||
/// The offset of the first dependency of this decl in the `decl_deps` array.
|
||||
begin_dep: u32,
|
||||
@@ -82,20 +93,6 @@ pub const Decl = struct {
|
||||
end_dep: u32,
|
||||
};
|
||||
|
||||
/// Globals must be kept in order: operations involving globals must be ordered
|
||||
/// so that the global declaration precedes any usage.
|
||||
pub const Global = struct {
|
||||
/// This is the result-id of the OpVariable instruction that declares the global.
|
||||
result_id: IdRef,
|
||||
/// The offset into `self.globals.section` of the first instruction of this global
|
||||
/// declaration.
|
||||
begin_inst: u32,
|
||||
/// The past-end offset into `self.flobals.section`.
|
||||
end_inst: u32,
|
||||
/// The result-id of the function that initializes this value.
|
||||
initializer_id: ?IdRef,
|
||||
};
|
||||
|
||||
/// This models a kernel entry point.
|
||||
pub const EntryPoint = struct {
|
||||
/// The declaration that should be exported.
|
||||
@@ -165,18 +162,8 @@ decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{},
|
||||
/// The list of entry points that should be exported from this module.
|
||||
entry_points: std.ArrayListUnmanaged(EntryPoint) = .{},
|
||||
|
||||
/// The fields in this structure help to maintain the required order for global variables.
|
||||
globals: struct {
|
||||
/// Set of globals, referred to by Decl.Index.
|
||||
globals: std.AutoArrayHashMapUnmanaged(Decl.Index, Global) = .{},
|
||||
/// This pseudo-section contains the initialization code for all the globals. Instructions from
|
||||
/// here are reordered when flushing the module. Its contents should be part of the
|
||||
/// `types_globals_constants` SPIR-V section when the module is emitted.
|
||||
section: Section = .{},
|
||||
} = .{},
|
||||
|
||||
/// The list of extended instruction sets that should be imported.
|
||||
extended_instruction_set: std.AutoHashMapUnmanaged(ExtendedInstructionSet, IdRef) = .{},
|
||||
extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, IdRef) = .{},
|
||||
|
||||
pub fn init(gpa: Allocator) Module {
|
||||
return .{
|
||||
@@ -205,9 +192,6 @@ pub fn deinit(self: *Module) void {
|
||||
|
||||
self.entry_points.deinit(self.gpa);
|
||||
|
||||
self.globals.globals.deinit(self.gpa);
|
||||
self.globals.section.deinit(self.gpa);
|
||||
|
||||
self.extended_instruction_set.deinit(self.gpa);
|
||||
|
||||
self.* = undefined;
|
||||
@@ -243,46 +227,6 @@ pub fn resolveString(self: *Module, str: []const u8) !CacheString {
|
||||
return try self.cache.addString(self, str);
|
||||
}
|
||||
|
||||
fn orderGlobalsInto(
|
||||
self: *Module,
|
||||
decl_index: Decl.Index,
|
||||
section: *Section,
|
||||
seen: *std.DynamicBitSetUnmanaged,
|
||||
) !void {
|
||||
const decl = self.declPtr(decl_index);
|
||||
const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep];
|
||||
const global = self.globalPtr(decl_index).?;
|
||||
const insts = self.globals.section.instructions.items[global.begin_inst..global.end_inst];
|
||||
|
||||
seen.set(@intFromEnum(decl_index));
|
||||
|
||||
for (deps) |dep| {
|
||||
if (!seen.isSet(@intFromEnum(dep))) {
|
||||
try self.orderGlobalsInto(dep, section, seen);
|
||||
}
|
||||
}
|
||||
|
||||
try section.instructions.appendSlice(self.gpa, insts);
|
||||
}
|
||||
|
||||
fn orderGlobals(self: *Module) !Section {
|
||||
const globals = self.globals.globals.keys();
|
||||
|
||||
var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len);
|
||||
defer seen.deinit(self.gpa);
|
||||
|
||||
var ordered_globals = Section{};
|
||||
errdefer ordered_globals.deinit(self.gpa);
|
||||
|
||||
for (globals) |decl_index| {
|
||||
if (!seen.isSet(@intFromEnum(decl_index))) {
|
||||
try self.orderGlobalsInto(decl_index, &ordered_globals, &seen);
|
||||
}
|
||||
}
|
||||
|
||||
return ordered_globals;
|
||||
}
|
||||
|
||||
fn addEntryPointDeps(
|
||||
self: *Module,
|
||||
decl_index: Decl.Index,
|
||||
@@ -298,8 +242,8 @@ fn addEntryPointDeps(
|
||||
|
||||
seen.set(@intFromEnum(decl_index));
|
||||
|
||||
if (self.globalPtr(decl_index)) |global| {
|
||||
try interface.append(global.result_id);
|
||||
if (decl.kind == .global) {
|
||||
try interface.append(decl.result_id);
|
||||
}
|
||||
|
||||
for (deps) |dep| {
|
||||
@@ -335,81 +279,9 @@ fn entryPoints(self: *Module) !Section {
|
||||
return entry_points;
|
||||
}
|
||||
|
||||
/// Generate a function that calls all initialization functions,
|
||||
/// in unspecified order (an order should not be required here).
|
||||
/// It generated as follows:
|
||||
/// %init = OpFunction %void None
|
||||
/// foreach %initializer:
|
||||
/// OpFunctionCall %initializer
|
||||
/// OpReturn
|
||||
/// OpFunctionEnd
|
||||
fn initializer(self: *Module, entry_points: *Section) !Section {
|
||||
var section = Section{};
|
||||
errdefer section.deinit(self.gpa);
|
||||
|
||||
// const void_ty_ref = try self.resolveType(Type.void, .direct);
|
||||
const void_ty_ref = try self.resolve(.void_type);
|
||||
const void_ty_id = self.resultId(void_ty_ref);
|
||||
const init_proto_ty_ref = try self.resolve(.{ .function_type = .{
|
||||
.return_type = void_ty_ref,
|
||||
.parameters = &.{},
|
||||
} });
|
||||
|
||||
const init_id = self.allocId();
|
||||
try section.emit(self.gpa, .OpFunction, .{
|
||||
.id_result_type = void_ty_id,
|
||||
.id_result = init_id,
|
||||
.function_control = .{},
|
||||
.function_type = self.resultId(init_proto_ty_ref),
|
||||
});
|
||||
try section.emit(self.gpa, .OpLabel, .{
|
||||
.id_result = self.allocId(),
|
||||
});
|
||||
|
||||
var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len);
|
||||
defer seen.deinit(self.gpa);
|
||||
|
||||
var interface = std.ArrayList(IdRef).init(self.gpa);
|
||||
defer interface.deinit();
|
||||
|
||||
for (self.globals.globals.keys(), self.globals.globals.values()) |decl_index, global| {
|
||||
try self.addEntryPointDeps(decl_index, &seen, &interface);
|
||||
if (global.initializer_id) |initializer_id| {
|
||||
try section.emit(self.gpa, .OpFunctionCall, .{
|
||||
.id_result_type = void_ty_id,
|
||||
.id_result = self.allocId(),
|
||||
.function = initializer_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try section.emit(self.gpa, .OpReturn, {});
|
||||
try section.emit(self.gpa, .OpFunctionEnd, {});
|
||||
|
||||
try entry_points.emit(self.gpa, .OpEntryPoint, .{
|
||||
// TODO: Rusticl does not support this because its poorly defined.
|
||||
// Do we need to generate a workaround here?
|
||||
.execution_model = .Kernel,
|
||||
.entry_point = init_id,
|
||||
.name = "zig global initializer",
|
||||
.interface = interface.items,
|
||||
});
|
||||
|
||||
try self.sections.execution_modes.emit(self.gpa, .OpExecutionMode, .{
|
||||
.entry_point = init_id,
|
||||
.mode = .Initializer,
|
||||
});
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
/// Emit this module as a spir-v binary.
|
||||
pub fn flush(self: *Module, file: std.fs.File, target: std.Target) !void {
|
||||
pub fn finalize(self: *Module, a: Allocator, target: std.Target) ![]Word {
|
||||
// See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
|
||||
|
||||
// TODO: Perform topological sort on the globals.
|
||||
var globals = try self.orderGlobals();
|
||||
defer globals.deinit(self.gpa);
|
||||
// TODO: Audit calls to allocId() in this function to make it idempotent.
|
||||
|
||||
var entry_points = try self.entryPoints();
|
||||
defer entry_points.deinit(self.gpa);
|
||||
@@ -417,13 +289,6 @@ pub fn flush(self: *Module, file: std.fs.File, target: std.Target) !void {
|
||||
var types_constants = try self.cache.materialize(self);
|
||||
defer types_constants.deinit(self.gpa);
|
||||
|
||||
// // TODO: Pass global variables as function parameters
|
||||
// var init_func = if (target.os.tag != .vulkan)
|
||||
// try self.initializer(&entry_points)
|
||||
// else
|
||||
// Section{};
|
||||
// defer init_func.deinit(self.gpa);
|
||||
|
||||
const header = [_]Word{
|
||||
spec.magic_number,
|
||||
// TODO: From cpu features
|
||||
@@ -436,7 +301,7 @@ pub fn flush(self: *Module, file: std.fs.File, target: std.Target) !void {
|
||||
else => 4,
|
||||
},
|
||||
}),
|
||||
0, // TODO: Register Zig compiler magic number.
|
||||
spec.zig_generator_id,
|
||||
self.idBound(),
|
||||
0, // Schema (currently reserved for future use)
|
||||
};
|
||||
@@ -468,30 +333,23 @@ pub fn flush(self: *Module, file: std.fs.File, target: std.Target) !void {
|
||||
self.sections.annotations.toWords(),
|
||||
types_constants.toWords(),
|
||||
self.sections.types_globals_constants.toWords(),
|
||||
globals.toWords(),
|
||||
self.sections.functions.toWords(),
|
||||
};
|
||||
|
||||
if (builtin.zig_backend == .stage2_x86_64) {
|
||||
for (buffers) |buf| {
|
||||
try file.writeAll(std.mem.sliceAsBytes(buf));
|
||||
}
|
||||
} else {
|
||||
// miscompiles with x86_64 backend
|
||||
var iovc_buffers: [buffers.len]std.os.iovec_const = undefined;
|
||||
var file_size: u64 = 0;
|
||||
for (&iovc_buffers, 0..) |*iovc, i| {
|
||||
// Note, since spir-v supports both little and big endian we can ignore byte order here and
|
||||
// just treat the words as a sequence of bytes.
|
||||
const bytes = std.mem.sliceAsBytes(buffers[i]);
|
||||
iovc.* = .{ .iov_base = bytes.ptr, .iov_len = bytes.len };
|
||||
file_size += bytes.len;
|
||||
}
|
||||
|
||||
try file.seekTo(0);
|
||||
try file.setEndPos(file_size);
|
||||
try file.pwritevAll(&iovc_buffers, 0);
|
||||
var total_result_size: usize = 0;
|
||||
for (buffers) |buffer| {
|
||||
total_result_size += buffer.len;
|
||||
}
|
||||
const result = try a.alloc(Word, total_result_size);
|
||||
errdefer a.free(result);
|
||||
|
||||
var offset: usize = 0;
|
||||
for (buffers) |buffer| {
|
||||
@memcpy(result[offset..][0..buffer.len], buffer);
|
||||
offset += buffer.len;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Merge the sections making up a function declaration into this module.
|
||||
@@ -501,23 +359,17 @@ pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void {
|
||||
try self.declareDeclDeps(decl_index, func.decl_deps.keys());
|
||||
}
|
||||
|
||||
pub const ExtendedInstructionSet = enum {
|
||||
glsl,
|
||||
opencl,
|
||||
};
|
||||
|
||||
/// Imports or returns the existing id of an extended instruction set
|
||||
pub fn importInstructionSet(self: *Module, set: ExtendedInstructionSet) !IdRef {
|
||||
pub fn importInstructionSet(self: *Module, set: spec.InstructionSet) !IdRef {
|
||||
assert(set != .core);
|
||||
|
||||
const gop = try self.extended_instruction_set.getOrPut(self.gpa, set);
|
||||
if (gop.found_existing) return gop.value_ptr.*;
|
||||
|
||||
const result_id = self.allocId();
|
||||
try self.sections.extended_instruction_set.emit(self.gpa, .OpExtInstImport, .{
|
||||
.id_result = result_id,
|
||||
.name = switch (set) {
|
||||
.glsl => "GLSL.std.450",
|
||||
.opencl => "OpenCL.std",
|
||||
},
|
||||
.name = @tagName(set),
|
||||
});
|
||||
gop.value_ptr.* = result_id;
|
||||
|
||||
@@ -631,40 +483,21 @@ pub fn decorateMember(
|
||||
});
|
||||
}
|
||||
|
||||
pub const DeclKind = enum {
|
||||
func,
|
||||
global,
|
||||
};
|
||||
|
||||
pub fn allocDecl(self: *Module, kind: DeclKind) !Decl.Index {
|
||||
pub fn allocDecl(self: *Module, kind: Decl.Kind) !Decl.Index {
|
||||
try self.decls.append(self.gpa, .{
|
||||
.kind = kind,
|
||||
.result_id = self.allocId(),
|
||||
.begin_dep = undefined,
|
||||
.end_dep = undefined,
|
||||
});
|
||||
const index = @as(Decl.Index, @enumFromInt(@as(u32, @intCast(self.decls.items.len - 1))));
|
||||
switch (kind) {
|
||||
.func => {},
|
||||
// If the decl represents a global, also allocate a global node.
|
||||
.global => try self.globals.globals.putNoClobber(self.gpa, index, .{
|
||||
.result_id = undefined,
|
||||
.begin_inst = undefined,
|
||||
.end_inst = undefined,
|
||||
.initializer_id = undefined,
|
||||
}),
|
||||
}
|
||||
|
||||
return index;
|
||||
return @as(Decl.Index, @enumFromInt(@as(u32, @intCast(self.decls.items.len - 1))));
|
||||
}
|
||||
|
||||
pub fn declPtr(self: *Module, index: Decl.Index) *Decl {
|
||||
return &self.decls.items[@intFromEnum(index)];
|
||||
}
|
||||
|
||||
pub fn globalPtr(self: *Module, index: Decl.Index) ?*Global {
|
||||
return self.globals.globals.getPtr(index);
|
||||
}
|
||||
|
||||
/// Declare ALL dependencies for a decl.
|
||||
pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl.Index) !void {
|
||||
const begin_dep = @as(u32, @intCast(self.decl_deps.items.len));
|
||||
@@ -676,26 +509,9 @@ pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl
|
||||
decl.end_dep = end_dep;
|
||||
}
|
||||
|
||||
pub fn beginGlobal(self: *Module) u32 {
|
||||
return @as(u32, @intCast(self.globals.section.instructions.items.len));
|
||||
}
|
||||
|
||||
pub fn endGlobal(
|
||||
self: *Module,
|
||||
global_index: Decl.Index,
|
||||
begin_inst: u32,
|
||||
result_id: IdRef,
|
||||
initializer_id: ?IdRef,
|
||||
) void {
|
||||
const global = self.globalPtr(global_index).?;
|
||||
global.* = .{
|
||||
.result_id = result_id,
|
||||
.begin_inst = begin_inst,
|
||||
.end_inst = @intCast(self.globals.section.instructions.items.len),
|
||||
.initializer_id = initializer_id,
|
||||
};
|
||||
}
|
||||
|
||||
/// Declare a SPIR-V function as an entry point. This causes an extra wrapper
|
||||
/// function to be generated, which is then exported as the real entry point. The purpose of this
|
||||
/// wrapper is to allocate and initialize the structure holding the instance globals.
|
||||
pub fn declareEntryPoint(
|
||||
self: *Module,
|
||||
decl_index: Decl.Index,
|
||||
|
||||
Reference in New Issue
Block a user