mirror of
https://github.com/ziglang/zig.git
synced 2025-12-29 09:33:18 +00:00
Abridged summary: * Move `Module.Fn` into `InternPool`. * Delete a lot of confusing and problematic `Sema` logic related to generic function calls. This commit removes `Module.Fn` and replaces it with two new `InternPool.Tag` values: * `func_decl` - corresponding to a function declared in the source code. This one contains line/column numbers, zir_body_inst, etc. * `func_instance` - one for each monomorphization of a generic function. Contains a reference to the `func_decl` from whence the instantiation came, along with the `comptime` parameter values (or types in the case of `anytype`) Since `InternPool` provides deduplication on these values, these fields are now deleted from `Module`: * `monomorphed_func_keys` * `monomorphed_funcs` * `align_stack_fns` Instead of these, Sema logic for generic function instantiation now unconditionally evaluates the function prototype expression for every generic callsite. This is technically required in order for type coercions to work. The previous code had some dubious, probably wrong hacks to make things work, such as `hashUncoerced`. I'm not 100% sure how we were able to eliminate that function and still pass all the behavior tests, but I'm pretty sure things were still broken without doing type coercion for every generic function call argument. After the function prototype is evaluated, it produces a deduplicated `func_instance` `InternPool.Index` which can then be used for the generic function call. Some other nice things made by this simplification are the removal of `comptime_args_fn_inst` and `preallocated_new_func` from `Sema`, and the messy logic associated with them. I have not yet been able to measure the perf of this against master branch. On one hand, it reduces memory usage and pointer chasing of the most heavily used `InternPool` Tag - function bodies - but on the other hand, it does evaluate function prototype expressions more than before. We will soon find out.
252 lines
9.1 KiB
Zig
252 lines
9.1 KiB
Zig
//! SPIR-V Spec documentation: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html
|
|
//! According to above documentation, a SPIR-V module has the following logical layout:
|
|
//! Header.
|
|
//! OpCapability instructions.
|
|
//! OpExtension instructions.
|
|
//! OpExtInstImport instructions.
|
|
//! A single OpMemoryModel instruction.
|
|
//! All entry points, declared with OpEntryPoint instructions.
|
|
//! All execution-mode declarators; OpExecutionMode and OpExecutionModeId instructions.
|
|
//! Debug instructions:
|
|
//! - First, OpString, OpSourceExtension, OpSource, OpSourceContinued (no forward references).
|
|
//! - OpName and OpMemberName instructions.
|
|
//! - OpModuleProcessed instructions.
|
|
//! All annotation (decoration) instructions.
|
|
//! All type declaration instructions, constant instructions, global variable declarations, (preferably) OpUndef instructions.
|
|
//! All function declarations without a body (extern functions presumably).
|
|
//! All regular functions.
|
|
|
|
// Because SPIR-V requires re-compilation anyway, and so hot swapping will not work
|
|
// anyway, we simply generate all the code in flushModule. This keeps
|
|
// things considerably simpler.
|
|
|
|
const SpirV = @This();
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
const assert = std.debug.assert;
|
|
const log = std.log.scoped(.link);
|
|
|
|
const Module = @import("../Module.zig");
|
|
const InternPool = @import("../InternPool.zig");
|
|
const Compilation = @import("../Compilation.zig");
|
|
const link = @import("../link.zig");
|
|
const codegen = @import("../codegen/spirv.zig");
|
|
const trace = @import("../tracy.zig").trace;
|
|
const build_options = @import("build_options");
|
|
const Air = @import("../Air.zig");
|
|
const Liveness = @import("../Liveness.zig");
|
|
const Value = @import("../value.zig").Value;
|
|
|
|
const SpvModule = @import("../codegen/spirv/Module.zig");
|
|
const spec = @import("../codegen/spirv/spec.zig");
|
|
const IdResult = spec.IdResult;
|
|
|
|
base: link.File,
|
|
|
|
spv: SpvModule,
|
|
spv_arena: ArenaAllocator,
|
|
decl_link: codegen.DeclLinkMap,
|
|
|
|
pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV {
|
|
const self = try gpa.create(SpirV);
|
|
self.* = .{
|
|
.base = .{
|
|
.tag = .spirv,
|
|
.options = options,
|
|
.file = null,
|
|
.allocator = gpa,
|
|
},
|
|
.spv = undefined,
|
|
.spv_arena = ArenaAllocator.init(gpa),
|
|
.decl_link = codegen.DeclLinkMap.init(self.base.allocator),
|
|
};
|
|
self.spv = SpvModule.init(gpa, self.spv_arena.allocator());
|
|
errdefer self.deinit();
|
|
|
|
// TODO: Figure out where to put all of these
|
|
switch (options.target.cpu.arch) {
|
|
.spirv32, .spirv64 => {},
|
|
else => return error.TODOArchNotSupported,
|
|
}
|
|
|
|
switch (options.target.os.tag) {
|
|
.opencl, .glsl450, .vulkan => {},
|
|
else => return error.TODOOsNotSupported,
|
|
}
|
|
|
|
if (options.target.abi != .none) {
|
|
return error.TODOAbiNotSupported;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*SpirV {
|
|
assert(options.target.ofmt == .spirv);
|
|
|
|
if (options.use_llvm) return error.LLVM_BackendIsTODO_ForSpirV; // TODO: LLVM Doesn't support SpirV at all.
|
|
if (options.use_lld) return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all.
|
|
|
|
const spirv = try createEmpty(allocator, options);
|
|
errdefer spirv.base.destroy();
|
|
|
|
// TODO: read the file and keep valid parts instead of truncating
|
|
const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
|
|
spirv.base.file = file;
|
|
return spirv;
|
|
}
|
|
|
|
pub fn deinit(self: *SpirV) void {
|
|
self.spv.deinit();
|
|
self.spv_arena.deinit();
|
|
self.decl_link.deinit();
|
|
}
|
|
|
|
pub fn updateFunc(self: *SpirV, module: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
|
|
if (build_options.skip_non_native) {
|
|
@panic("Attempted to compile for architecture that was disabled by build configuration");
|
|
}
|
|
|
|
const func = module.funcInfo(func_index);
|
|
|
|
var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link);
|
|
defer decl_gen.deinit();
|
|
|
|
if (try decl_gen.gen(func.owner_decl, air, liveness)) |msg| {
|
|
try module.failed_decls.put(module.gpa, func.owner_decl, msg);
|
|
}
|
|
}
|
|
|
|
pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) !void {
|
|
if (build_options.skip_non_native) {
|
|
@panic("Attempted to compile for architecture that was disabled by build configuration");
|
|
}
|
|
|
|
var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &self.spv, &self.decl_link);
|
|
defer decl_gen.deinit();
|
|
|
|
if (try decl_gen.gen(decl_index, undefined, undefined)) |msg| {
|
|
try module.failed_decls.put(module.gpa, decl_index, msg);
|
|
}
|
|
}
|
|
|
|
pub fn updateDeclExports(
|
|
self: *SpirV,
|
|
mod: *Module,
|
|
decl_index: Module.Decl.Index,
|
|
exports: []const *Module.Export,
|
|
) !void {
|
|
const decl = mod.declPtr(decl_index);
|
|
if (decl.val.isFuncBody(mod) and decl.ty.fnCallingConvention(mod) == .Kernel) {
|
|
// TODO: Unify with resolveDecl in spirv.zig.
|
|
const entry = try self.decl_link.getOrPut(decl_index);
|
|
if (!entry.found_existing) {
|
|
entry.value_ptr.* = try self.spv.allocDecl(.func);
|
|
}
|
|
const spv_decl_index = entry.value_ptr.*;
|
|
|
|
for (exports) |exp| {
|
|
try self.spv.declareEntryPoint(spv_decl_index, mod.intern_pool.stringToSlice(exp.opts.name));
|
|
}
|
|
}
|
|
|
|
// TODO: Export regular functions, variables, etc using Linkage attributes.
|
|
}
|
|
|
|
pub fn freeDecl(self: *SpirV, decl_index: Module.Decl.Index) void {
|
|
_ = self;
|
|
_ = decl_index;
|
|
}
|
|
|
|
pub fn flush(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
|
|
if (build_options.have_llvm and self.base.options.use_lld) {
|
|
return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all.
|
|
} else {
|
|
return self.flushModule(comp, prog_node);
|
|
}
|
|
}
|
|
|
|
pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
|
|
if (build_options.skip_non_native) {
|
|
@panic("Attempted to compile for architecture that was disabled by build configuration");
|
|
}
|
|
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
var sub_prog_node = prog_node.start("Flush Module", 0);
|
|
sub_prog_node.activate();
|
|
defer sub_prog_node.end();
|
|
|
|
const target = comp.getTarget();
|
|
try writeCapabilities(&self.spv, target);
|
|
try writeMemoryModel(&self.spv, target);
|
|
|
|
// We need to export the list of error names somewhere so that we can pretty-print them in the
|
|
// executor. This is not really an important thing though, so we can just dump it in any old
|
|
// nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.
|
|
|
|
var error_info = std.ArrayList(u8).init(self.spv.arena);
|
|
try error_info.appendSlice("zig_errors");
|
|
const module = self.base.options.module.?;
|
|
for (module.global_error_set.keys()) |name_nts| {
|
|
const name = module.intern_pool.stringToSlice(name_nts);
|
|
// Errors can contain pretty much any character - to encode them in a string we must escape
|
|
// them somehow. Easiest here is to use some established scheme, one which also preseves the
|
|
// name if it contains no strange characters is nice for debugging. URI encoding fits the bill.
|
|
// We're using : as separator, which is a reserved character.
|
|
|
|
const escaped_name = try std.Uri.escapeString(self.base.allocator, name);
|
|
defer self.base.allocator.free(escaped_name);
|
|
try error_info.writer().print(":{s}", .{escaped_name});
|
|
}
|
|
try self.spv.sections.debug_strings.emit(self.spv.gpa, .OpSourceExtension, .{
|
|
.extension = error_info.items,
|
|
});
|
|
|
|
try self.spv.flush(self.base.file.?);
|
|
}
|
|
|
|
fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
|
|
// TODO: Integrate with a hypothetical feature system
|
|
const caps: []const spec.Capability = switch (target.os.tag) {
|
|
.opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .GenericPointer },
|
|
.glsl450 => &.{.Shader},
|
|
.vulkan => &.{.Shader},
|
|
else => unreachable, // TODO
|
|
};
|
|
|
|
for (caps) |cap| {
|
|
try spv.sections.capabilities.emit(spv.gpa, .OpCapability, .{
|
|
.capability = cap,
|
|
});
|
|
}
|
|
}
|
|
|
|
fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void {
|
|
const addressing_model = switch (target.os.tag) {
|
|
.opencl => switch (target.cpu.arch) {
|
|
.spirv32 => spec.AddressingModel.Physical32,
|
|
.spirv64 => spec.AddressingModel.Physical64,
|
|
else => unreachable, // TODO
|
|
},
|
|
.glsl450, .vulkan => spec.AddressingModel.Logical,
|
|
else => unreachable, // TODO
|
|
};
|
|
|
|
const memory_model: spec.MemoryModel = switch (target.os.tag) {
|
|
.opencl => .OpenCL,
|
|
.glsl450 => .GLSL450,
|
|
.vulkan => .GLSL450,
|
|
else => unreachable,
|
|
};
|
|
|
|
// TODO: Put this in a proper section.
|
|
try spv.sections.capabilities.emit(spv.gpa, .OpMemoryModel, .{
|
|
.addressing_model = addressing_model,
|
|
.memory_model = memory_model,
|
|
});
|
|
}
|