mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
spirv: remove deduplication ISel
This commit is contained in:
parent
31de2c873f
commit
5525a90a47
@ -553,11 +553,6 @@ set(ZIG_STAGE2_SOURCES
|
|||||||
src/codegen/c/Type.zig
|
src/codegen/c/Type.zig
|
||||||
src/codegen/llvm.zig
|
src/codegen/llvm.zig
|
||||||
src/codegen/llvm/bindings.zig
|
src/codegen/llvm/bindings.zig
|
||||||
src/codegen/spirv.zig
|
|
||||||
src/codegen/spirv/Assembler.zig
|
|
||||||
src/codegen/spirv/Module.zig
|
|
||||||
src/codegen/spirv/Section.zig
|
|
||||||
src/codegen/spirv/spec.zig
|
|
||||||
src/crash_report.zig
|
src/crash_report.zig
|
||||||
src/dev.zig
|
src/dev.zig
|
||||||
src/libs/freebsd.zig
|
src/libs/freebsd.zig
|
||||||
@ -620,11 +615,6 @@ set(ZIG_STAGE2_SOURCES
|
|||||||
src/link/Plan9.zig
|
src/link/Plan9.zig
|
||||||
src/link/Plan9/aout.zig
|
src/link/Plan9/aout.zig
|
||||||
src/link/Queue.zig
|
src/link/Queue.zig
|
||||||
src/link/SpirV.zig
|
|
||||||
src/link/SpirV/BinaryModule.zig
|
|
||||||
src/link/SpirV/deduplicate.zig
|
|
||||||
src/link/SpirV/lower_invocation_globals.zig
|
|
||||||
src/link/SpirV/prune_unused.zig
|
|
||||||
src/link/StringTable.zig
|
src/link/StringTable.zig
|
||||||
src/link/Wasm.zig
|
src/link/Wasm.zig
|
||||||
src/link/Wasm/Archive.zig
|
src/link/Wasm/Archive.zig
|
||||||
|
|||||||
@ -3646,9 +3646,7 @@ pub fn errorSetBits(zcu: *const Zcu) u16 {
|
|||||||
|
|
||||||
if (zcu.error_limit == 0) return 0;
|
if (zcu.error_limit == 0) return 0;
|
||||||
if (target.cpu.arch.isSpirV()) {
|
if (target.cpu.arch.isSpirV()) {
|
||||||
if (!target.cpu.has(.spirv, .storage_push_constant16)) {
|
if (zcu.comp.config.is_test) return 32;
|
||||||
return 32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return @as(u16, std.math.log2_int(ErrorInt, zcu.error_limit)) + 1;
|
return @as(u16, std.math.log2_int(ErrorInt, zcu.error_limit)) + 1;
|
||||||
|
|||||||
@ -267,9 +267,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
|
|||||||
const ids = try gpa.alloc(Id, operands[1..].len);
|
const ids = try gpa.alloc(Id, operands[1..].len);
|
||||||
defer gpa.free(ids);
|
defer gpa.free(ids);
|
||||||
for (operands[1..], ids) |op, *id| id.* = try self.resolveRefId(op.ref_id);
|
for (operands[1..], ids) |op, *id| id.* = try self.resolveRefId(op.ref_id);
|
||||||
const result_id = module.allocId();
|
break :blk try module.structType(ids, null, null, .none);
|
||||||
try module.structType(result_id, ids, null);
|
|
||||||
break :blk result_id;
|
|
||||||
},
|
},
|
||||||
.OpTypeImage => blk: {
|
.OpTypeImage => blk: {
|
||||||
const sampled_type = try self.resolveRefId(operands[1].ref_id);
|
const sampled_type = try self.resolveRefId(operands[1].ref_id);
|
||||||
@ -324,6 +322,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
|
|||||||
/// - Target section is determined from instruction type.
|
/// - Target section is determined from instruction type.
|
||||||
fn processGenericInstruction(self: *Assembler) !?AsmValue {
|
fn processGenericInstruction(self: *Assembler) !?AsmValue {
|
||||||
const module = self.cg.module;
|
const module = self.cg.module;
|
||||||
|
const target = module.zcu.getTarget();
|
||||||
const operands = self.inst.operands.items;
|
const operands = self.inst.operands.items;
|
||||||
var maybe_spv_decl_index: ?Decl.Index = null;
|
var maybe_spv_decl_index: ?Decl.Index = null;
|
||||||
const section = switch (self.inst.opcode.class()) {
|
const section = switch (self.inst.opcode.class()) {
|
||||||
@ -337,7 +336,7 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
|
|||||||
const storage_class: spec.StorageClass = @enumFromInt(operands[2].value);
|
const storage_class: spec.StorageClass = @enumFromInt(operands[2].value);
|
||||||
if (storage_class == .function) break :section &self.cg.prologue;
|
if (storage_class == .function) break :section &self.cg.prologue;
|
||||||
maybe_spv_decl_index = try module.allocDecl(.global);
|
maybe_spv_decl_index = try module.allocDecl(.global);
|
||||||
if (!module.target.cpu.has(.spirv, .v1_4) and storage_class != .input and storage_class != .output) {
|
if (!target.cpu.has(.spirv, .v1_4) and storage_class != .input and storage_class != .output) {
|
||||||
// Before version 1.4, the interface’s storage classes are limited to the Input and Output
|
// Before version 1.4, the interface’s storage classes are limited to the Input and Output
|
||||||
break :section &module.sections.globals;
|
break :section &module.sections.globals;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -7,20 +7,96 @@
|
|||||||
//! is detected by the magic word in the header. Therefore, we can ignore any byte
|
//! is detected by the magic word in the header. Therefore, we can ignore any byte
|
||||||
//! order throughout the implementation, and just use the host byte order, and make
|
//! order throughout the implementation, and just use the host byte order, and make
|
||||||
//! this a problem for the consumer.
|
//! this a problem for the consumer.
|
||||||
const Module = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const autoHashStrat = std.hash.autoHashStrat;
|
|
||||||
const Wyhash = std.hash.Wyhash;
|
|
||||||
|
|
||||||
|
const Zcu = @import("../../Zcu.zig");
|
||||||
const InternPool = @import("../../InternPool.zig");
|
const InternPool = @import("../../InternPool.zig");
|
||||||
|
const Section = @import("Section.zig");
|
||||||
const spec = @import("spec.zig");
|
const spec = @import("spec.zig");
|
||||||
const Word = spec.Word;
|
const Word = spec.Word;
|
||||||
const Id = spec.Id;
|
const Id = spec.Id;
|
||||||
|
|
||||||
const Section = @import("Section.zig");
|
const Module = @This();
|
||||||
|
|
||||||
|
gpa: Allocator,
|
||||||
|
arena: Allocator,
|
||||||
|
zcu: *Zcu,
|
||||||
|
nav_link: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Decl.Index) = .empty,
|
||||||
|
uav_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Decl.Index) = .empty,
|
||||||
|
intern_map: std.AutoHashMapUnmanaged(struct { InternPool.Index, Repr }, Id) = .empty,
|
||||||
|
decls: std.ArrayListUnmanaged(Decl) = .empty,
|
||||||
|
decl_deps: std.ArrayListUnmanaged(Decl.Index) = .empty,
|
||||||
|
entry_points: std.AutoArrayHashMapUnmanaged(Id, EntryPoint) = .empty,
|
||||||
|
/// This map serves a dual purpose:
|
||||||
|
/// - It keeps track of pointers that are currently being emitted, so that we can tell
|
||||||
|
/// if they are recursive and need an OpTypeForwardPointer.
|
||||||
|
/// - It caches pointers by child-type. This is required because sometimes we rely on
|
||||||
|
/// ID-equality for pointers, and pointers constructed via `ptrType()` aren't interned
|
||||||
|
/// via the usual `intern_map` mechanism.
|
||||||
|
ptr_types: std.AutoHashMapUnmanaged(
|
||||||
|
struct { Id, spec.StorageClass },
|
||||||
|
struct { ty_id: Id, fwd_emitted: bool },
|
||||||
|
) = .{},
|
||||||
|
/// For test declarations compiled for Vulkan target, we have to add a buffer.
|
||||||
|
/// We only need to generate this once, this holds the link information related to that.
|
||||||
|
error_buffer: ?Decl.Index = null,
|
||||||
|
/// SPIR-V instructions return result-ids.
|
||||||
|
/// This variable holds the module-wide counter for these.
|
||||||
|
next_result_id: Word = 1,
|
||||||
|
/// Some types shouldn't be emitted more than one time, but cannot be caught by
|
||||||
|
/// the `intern_map` during codegen. Sometimes, IDs are compared to check if
|
||||||
|
/// types are the same, so we can't delay until the dedup pass. Therefore,
|
||||||
|
/// this is an ad-hoc structure to cache types where required.
|
||||||
|
/// According to the SPIR-V specification, section 2.8, this includes all non-aggregate
|
||||||
|
/// non-pointer types.
|
||||||
|
/// Additionally, this is used for other values which can be cached, for example,
|
||||||
|
/// built-in variables.
|
||||||
|
cache: struct {
|
||||||
|
bool_type: ?Id = null,
|
||||||
|
void_type: ?Id = null,
|
||||||
|
opaque_types: std.StringHashMapUnmanaged(Id) = .empty,
|
||||||
|
int_types: std.AutoHashMapUnmanaged(std.builtin.Type.Int, Id) = .empty,
|
||||||
|
float_types: std.AutoHashMapUnmanaged(std.builtin.Type.Float, Id) = .empty,
|
||||||
|
vector_types: std.AutoHashMapUnmanaged(struct { Id, u32 }, Id) = .empty,
|
||||||
|
array_types: std.AutoHashMapUnmanaged(struct { Id, Id }, Id) = .empty,
|
||||||
|
struct_types: std.ArrayHashMapUnmanaged(StructType, Id, StructType.HashContext, true) = .empty,
|
||||||
|
fn_types: std.ArrayHashMapUnmanaged(FnType, Id, FnType.HashContext, true) = .empty,
|
||||||
|
|
||||||
|
capabilities: std.AutoHashMapUnmanaged(spec.Capability, void) = .empty,
|
||||||
|
extensions: std.StringHashMapUnmanaged(void) = .empty,
|
||||||
|
extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, Id) = .empty,
|
||||||
|
decorations: std.AutoHashMapUnmanaged(struct { Id, spec.Decoration }, void) = .empty,
|
||||||
|
builtins: std.AutoHashMapUnmanaged(struct { Id, spec.BuiltIn }, Decl.Index) = .empty,
|
||||||
|
strings: std.StringArrayHashMapUnmanaged(Id) = .empty,
|
||||||
|
|
||||||
|
bool_const: [2]?Id = .{ null, null },
|
||||||
|
constants: std.ArrayHashMapUnmanaged(Constant, Id, Constant.HashContext, true) = .empty,
|
||||||
|
} = .{},
|
||||||
|
/// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module".
|
||||||
|
sections: struct {
|
||||||
|
capabilities: Section = .{},
|
||||||
|
extensions: Section = .{},
|
||||||
|
extended_instruction_set: Section = .{},
|
||||||
|
memory_model: Section = .{},
|
||||||
|
execution_modes: Section = .{},
|
||||||
|
debug_strings: Section = .{},
|
||||||
|
debug_names: Section = .{},
|
||||||
|
annotations: Section = .{},
|
||||||
|
globals: Section = .{},
|
||||||
|
functions: Section = .{},
|
||||||
|
} = .{},
|
||||||
|
|
||||||
|
/// Data can be lowered into in two basic representations: indirect, which is when
|
||||||
|
/// a type is stored in memory, and direct, which is how a type is stored when its
|
||||||
|
/// a direct SPIR-V value.
|
||||||
|
pub const Repr = enum {
|
||||||
|
/// A SPIR-V value as it would be used in operations.
|
||||||
|
direct,
|
||||||
|
/// A SPIR-V value as it is stored in memory.
|
||||||
|
indirect,
|
||||||
|
};
|
||||||
|
|
||||||
/// Declarations, both functions and globals, can have dependencies. These are used for 2 things:
|
/// Declarations, both functions and globals, can have dependencies. These are used for 2 things:
|
||||||
/// - Globals must be declared before they are used, also between globals. The compiler processes
|
/// - Globals must be declared before they are used, also between globals. The compiler processes
|
||||||
@ -66,76 +142,68 @@ pub const EntryPoint = struct {
|
|||||||
exec_mode: ?spec.ExecutionMode = null,
|
exec_mode: ?spec.ExecutionMode = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
gpa: Allocator,
|
const StructType = struct {
|
||||||
target: *const std.Target,
|
fields: []const Id,
|
||||||
nav_link: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Decl.Index) = .empty,
|
ip_index: InternPool.Index,
|
||||||
uav_link: std.AutoHashMapUnmanaged(struct { InternPool.Index, spec.StorageClass }, Decl.Index) = .empty,
|
|
||||||
intern_map: std.AutoHashMapUnmanaged(struct { InternPool.Index, Repr }, Id) = .empty,
|
|
||||||
decls: std.ArrayListUnmanaged(Decl) = .empty,
|
|
||||||
decl_deps: std.ArrayListUnmanaged(Decl.Index) = .empty,
|
|
||||||
entry_points: std.AutoArrayHashMapUnmanaged(Id, EntryPoint) = .empty,
|
|
||||||
/// This map serves a dual purpose:
|
|
||||||
/// - It keeps track of pointers that are currently being emitted, so that we can tell
|
|
||||||
/// if they are recursive and need an OpTypeForwardPointer.
|
|
||||||
/// - It caches pointers by child-type. This is required because sometimes we rely on
|
|
||||||
/// ID-equality for pointers, and pointers constructed via `ptrType()` aren't interned
|
|
||||||
/// via the usual `intern_map` mechanism.
|
|
||||||
ptr_types: std.AutoHashMapUnmanaged(
|
|
||||||
struct { InternPool.Index, spec.StorageClass, Repr },
|
|
||||||
struct { ty_id: Id, fwd_emitted: bool },
|
|
||||||
) = .{},
|
|
||||||
/// For test declarations compiled for Vulkan target, we have to add a buffer.
|
|
||||||
/// We only need to generate this once, this holds the link information related to that.
|
|
||||||
error_buffer: ?Decl.Index = null,
|
|
||||||
/// SPIR-V instructions return result-ids.
|
|
||||||
/// This variable holds the module-wide counter for these.
|
|
||||||
next_result_id: Word = 1,
|
|
||||||
/// Some types shouldn't be emitted more than one time, but cannot be caught by
|
|
||||||
/// the `intern_map` during codegen. Sometimes, IDs are compared to check if
|
|
||||||
/// types are the same, so we can't delay until the dedup pass. Therefore,
|
|
||||||
/// this is an ad-hoc structure to cache types where required.
|
|
||||||
/// According to the SPIR-V specification, section 2.8, this includes all non-aggregate
|
|
||||||
/// non-pointer types.
|
|
||||||
/// Additionally, this is used for other values which can be cached, for example,
|
|
||||||
/// built-in variables.
|
|
||||||
cache: struct {
|
|
||||||
bool_type: ?Id = null,
|
|
||||||
void_type: ?Id = null,
|
|
||||||
int_types: std.AutoHashMapUnmanaged(std.builtin.Type.Int, Id) = .empty,
|
|
||||||
float_types: std.AutoHashMapUnmanaged(std.builtin.Type.Float, Id) = .empty,
|
|
||||||
vector_types: std.AutoHashMapUnmanaged(struct { Id, u32 }, Id) = .empty,
|
|
||||||
array_types: std.AutoHashMapUnmanaged(struct { Id, Id }, Id) = .empty,
|
|
||||||
|
|
||||||
capabilities: std.AutoHashMapUnmanaged(spec.Capability, void) = .empty,
|
const HashContext = struct {
|
||||||
extensions: std.StringHashMapUnmanaged(void) = .empty,
|
pub fn hash(_: @This(), ty: StructType) u32 {
|
||||||
extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, Id) = .empty,
|
var hasher = std.hash.Wyhash.init(0);
|
||||||
decorations: std.AutoHashMapUnmanaged(struct { Id, spec.Decoration }, void) = .empty,
|
hasher.update(std.mem.sliceAsBytes(ty.fields));
|
||||||
builtins: std.AutoHashMapUnmanaged(struct { Id, spec.BuiltIn }, Decl.Index) = .empty,
|
hasher.update(std.mem.asBytes(&ty.ip_index));
|
||||||
|
return @truncate(hasher.final());
|
||||||
|
}
|
||||||
|
|
||||||
bool_const: [2]?Id = .{ null, null },
|
pub fn eql(_: @This(), a: StructType, b: StructType, _: usize) bool {
|
||||||
} = .{},
|
return a.ip_index == b.ip_index and std.mem.eql(Id, a.fields, b.fields);
|
||||||
/// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module".
|
}
|
||||||
sections: struct {
|
};
|
||||||
capabilities: Section = .{},
|
};
|
||||||
extensions: Section = .{},
|
|
||||||
extended_instruction_set: Section = .{},
|
|
||||||
memory_model: Section = .{},
|
|
||||||
execution_modes: Section = .{},
|
|
||||||
debug_strings: Section = .{},
|
|
||||||
debug_names: Section = .{},
|
|
||||||
annotations: Section = .{},
|
|
||||||
globals: Section = .{},
|
|
||||||
functions: Section = .{},
|
|
||||||
} = .{},
|
|
||||||
|
|
||||||
/// Data can be lowered into in two basic representations: indirect, which is when
|
const FnType = struct {
|
||||||
/// a type is stored in memory, and direct, which is how a type is stored when its
|
return_ty: Id,
|
||||||
/// a direct SPIR-V value.
|
params: []const Id,
|
||||||
pub const Repr = enum {
|
|
||||||
/// A SPIR-V value as it would be used in operations.
|
const HashContext = struct {
|
||||||
direct,
|
pub fn hash(_: @This(), ty: FnType) u32 {
|
||||||
/// A SPIR-V value as it is stored in memory.
|
var hasher = std.hash.Wyhash.init(0);
|
||||||
indirect,
|
hasher.update(std.mem.asBytes(&ty.return_ty));
|
||||||
|
hasher.update(std.mem.sliceAsBytes(ty.params));
|
||||||
|
return @truncate(hasher.final());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eql(_: @This(), a: FnType, b: FnType, _: usize) bool {
|
||||||
|
return a.return_ty == b.return_ty and
|
||||||
|
std.mem.eql(Id, a.params, b.params);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Constant = struct {
|
||||||
|
ty: Id,
|
||||||
|
value: spec.LiteralContextDependentNumber,
|
||||||
|
|
||||||
|
const HashContext = struct {
|
||||||
|
pub fn hash(_: @This(), value: Constant) u32 {
|
||||||
|
const Tag = @typeInfo(spec.LiteralContextDependentNumber).@"union".tag_type.?;
|
||||||
|
var hasher = std.hash.Wyhash.init(0);
|
||||||
|
hasher.update(std.mem.asBytes(&value.ty));
|
||||||
|
hasher.update(std.mem.asBytes(&@as(Tag, value.value)));
|
||||||
|
switch (value.value) {
|
||||||
|
inline else => |v| hasher.update(std.mem.asBytes(&v)),
|
||||||
|
}
|
||||||
|
return @truncate(hasher.final());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eql(_: @This(), a: Constant, b: Constant, _: usize) bool {
|
||||||
|
if (a.ty != b.ty) return false;
|
||||||
|
const Tag = @typeInfo(spec.LiteralContextDependentNumber).@"union".tag_type.?;
|
||||||
|
if (@as(Tag, a.value) != @as(Tag, b.value)) return false;
|
||||||
|
return switch (a.value) {
|
||||||
|
inline else => |v, tag| v == @field(b.value, @tagName(tag)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn deinit(module: *Module) void {
|
pub fn deinit(module: *Module) void {
|
||||||
@ -155,15 +223,21 @@ pub fn deinit(module: *Module) void {
|
|||||||
module.sections.globals.deinit(module.gpa);
|
module.sections.globals.deinit(module.gpa);
|
||||||
module.sections.functions.deinit(module.gpa);
|
module.sections.functions.deinit(module.gpa);
|
||||||
|
|
||||||
|
module.cache.opaque_types.deinit(module.gpa);
|
||||||
module.cache.int_types.deinit(module.gpa);
|
module.cache.int_types.deinit(module.gpa);
|
||||||
module.cache.float_types.deinit(module.gpa);
|
module.cache.float_types.deinit(module.gpa);
|
||||||
module.cache.vector_types.deinit(module.gpa);
|
module.cache.vector_types.deinit(module.gpa);
|
||||||
module.cache.array_types.deinit(module.gpa);
|
module.cache.array_types.deinit(module.gpa);
|
||||||
|
module.cache.struct_types.deinit(module.gpa);
|
||||||
|
module.cache.fn_types.deinit(module.gpa);
|
||||||
module.cache.capabilities.deinit(module.gpa);
|
module.cache.capabilities.deinit(module.gpa);
|
||||||
module.cache.extensions.deinit(module.gpa);
|
module.cache.extensions.deinit(module.gpa);
|
||||||
module.cache.extended_instruction_set.deinit(module.gpa);
|
module.cache.extended_instruction_set.deinit(module.gpa);
|
||||||
module.cache.decorations.deinit(module.gpa);
|
module.cache.decorations.deinit(module.gpa);
|
||||||
module.cache.builtins.deinit(module.gpa);
|
module.cache.builtins.deinit(module.gpa);
|
||||||
|
module.cache.strings.deinit(module.gpa);
|
||||||
|
|
||||||
|
module.cache.constants.deinit(module.gpa);
|
||||||
|
|
||||||
module.decls.deinit(module.gpa);
|
module.decls.deinit(module.gpa);
|
||||||
module.decl_deps.deinit(module.gpa);
|
module.decl_deps.deinit(module.gpa);
|
||||||
@ -234,6 +308,8 @@ pub fn addEntryPointDeps(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn entryPoints(module: *Module) !Section {
|
fn entryPoints(module: *Module) !Section {
|
||||||
|
const target = module.zcu.getTarget();
|
||||||
|
|
||||||
var entry_points = Section{};
|
var entry_points = Section{};
|
||||||
errdefer entry_points.deinit(module.gpa);
|
errdefer entry_points.deinit(module.gpa);
|
||||||
|
|
||||||
@ -256,7 +332,7 @@ fn entryPoints(module: *Module) !Section {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (entry_point.exec_mode == null and entry_point.exec_model == .fragment) {
|
if (entry_point.exec_mode == null and entry_point.exec_model == .fragment) {
|
||||||
switch (module.target.os.tag) {
|
switch (target.os.tag) {
|
||||||
.vulkan, .opengl => |tag| {
|
.vulkan, .opengl => |tag| {
|
||||||
try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
|
try module.sections.execution_modes.emit(module.gpa, .OpExecutionMode, .{
|
||||||
.entry_point = entry_point_id,
|
.entry_point = entry_point_id,
|
||||||
@ -273,7 +349,7 @@ fn entryPoints(module: *Module) !Section {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn finalize(module: *Module, gpa: Allocator) ![]Word {
|
pub fn finalize(module: *Module, gpa: Allocator) ![]Word {
|
||||||
const target = module.target;
|
const target = module.zcu.getTarget();
|
||||||
|
|
||||||
// Emit capabilities and extensions
|
// Emit capabilities and extensions
|
||||||
switch (target.os.tag) {
|
switch (target.os.tag) {
|
||||||
@ -434,20 +510,6 @@ pub fn importInstructionSet(module: *Module, set: spec.InstructionSet) !Id {
|
|||||||
return result_id;
|
return result_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn structType(module: *Module, result_id: Id, types: []const Id, maybe_names: ?[]const []const u8) !void {
|
|
||||||
try module.sections.globals.emit(module.gpa, .OpTypeStruct, .{
|
|
||||||
.id_result = result_id,
|
|
||||||
.id_ref = types,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (maybe_names) |names| {
|
|
||||||
assert(names.len == types.len);
|
|
||||||
for (names, 0..) |name, i| {
|
|
||||||
try module.memberDebugName(result_id, @intCast(i), name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn boolType(module: *Module) !Id {
|
pub fn boolType(module: *Module) !Id {
|
||||||
if (module.cache.bool_type) |id| return id;
|
if (module.cache.bool_type) |id| return id;
|
||||||
|
|
||||||
@ -471,6 +533,19 @@ pub fn voidType(module: *Module) !Id {
|
|||||||
return result_id;
|
return result_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn opaqueType(module: *Module, name: []const u8) !Id {
|
||||||
|
if (module.cache.opaque_types.get(name)) |id| return id;
|
||||||
|
const result_id = module.allocId();
|
||||||
|
const name_dup = try module.arena.dupe(u8, name);
|
||||||
|
try module.sections.globals.emit(module.gpa, .OpTypeOpaque, .{
|
||||||
|
.id_result = result_id,
|
||||||
|
.literal_string = name_dup,
|
||||||
|
});
|
||||||
|
try module.debugName(result_id, name_dup);
|
||||||
|
try module.cache.opaque_types.put(module.gpa, name_dup, result_id);
|
||||||
|
return result_id;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn intType(module: *Module, signedness: std.builtin.Signedness, bits: u16) !Id {
|
pub fn intType(module: *Module, signedness: std.builtin.Signedness, bits: u16) !Id {
|
||||||
assert(bits > 0);
|
assert(bits > 0);
|
||||||
const entry = try module.cache.int_types.getOrPut(module.gpa, .{ .signedness = signedness, .bits = bits });
|
const entry = try module.cache.int_types.getOrPut(module.gpa, .{ .signedness = signedness, .bits = bits });
|
||||||
@ -537,27 +612,89 @@ pub fn arrayType(module: *Module, len_id: Id, child_ty_id: Id) !Id {
|
|||||||
return entry.value_ptr.*;
|
return entry.value_ptr.*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn functionType(module: *Module, return_ty_id: Id, param_type_ids: []const Id) !Id {
|
pub fn structType(
|
||||||
|
module: *Module,
|
||||||
|
types: []const Id,
|
||||||
|
maybe_names: ?[]const []const u8,
|
||||||
|
maybe_offsets: ?[]const u32,
|
||||||
|
ip_index: InternPool.Index,
|
||||||
|
) !Id {
|
||||||
|
const target = module.zcu.getTarget();
|
||||||
|
|
||||||
|
if (module.cache.struct_types.get(.{ .fields = types, .ip_index = ip_index })) |id| return id;
|
||||||
const result_id = module.allocId();
|
const result_id = module.allocId();
|
||||||
try module.sections.globals.emit(module.gpa, .OpTypeFunction, .{
|
const types_dup = try module.arena.dupe(Id, types);
|
||||||
|
try module.sections.globals.emit(module.gpa, .OpTypeStruct, .{
|
||||||
.id_result = result_id,
|
.id_result = result_id,
|
||||||
.return_type = return_ty_id,
|
.id_ref = types_dup,
|
||||||
.id_ref_2 = param_type_ids,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (maybe_names) |names| {
|
||||||
|
assert(names.len == types.len);
|
||||||
|
for (names, 0..) |name, i| {
|
||||||
|
try module.memberDebugName(result_id, @intCast(i), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (target.os.tag) {
|
||||||
|
.vulkan, .opengl => {
|
||||||
|
if (maybe_offsets) |offsets| {
|
||||||
|
assert(offsets.len == types.len);
|
||||||
|
for (offsets, 0..) |offset, i| {
|
||||||
|
try module.decorateMember(
|
||||||
|
result_id,
|
||||||
|
@intCast(i),
|
||||||
|
.{ .offset = .{ .byte_offset = offset } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
try module.cache.struct_types.put(
|
||||||
|
module.gpa,
|
||||||
|
.{
|
||||||
|
.fields = types_dup,
|
||||||
|
.ip_index = if (module.zcu.comp.config.root_strip) .none else ip_index,
|
||||||
|
},
|
||||||
|
result_id,
|
||||||
|
);
|
||||||
return result_id;
|
return result_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn constant(module: *Module, result_ty_id: Id, value: spec.LiteralContextDependentNumber) !Id {
|
pub fn functionType(module: *Module, return_ty_id: Id, param_type_ids: []const Id) !Id {
|
||||||
|
if (module.cache.fn_types.get(.{
|
||||||
|
.return_ty = return_ty_id,
|
||||||
|
.params = param_type_ids,
|
||||||
|
})) |id| return id;
|
||||||
const result_id = module.allocId();
|
const result_id = module.allocId();
|
||||||
const section = &module.sections.globals;
|
const params_dup = try module.arena.dupe(Id, param_type_ids);
|
||||||
try section.emit(module.gpa, .OpConstant, .{
|
try module.sections.globals.emit(module.gpa, .OpTypeFunction, .{
|
||||||
.id_result_type = result_ty_id,
|
|
||||||
.id_result = result_id,
|
.id_result = result_id,
|
||||||
.value = value,
|
.return_type = return_ty_id,
|
||||||
|
.id_ref_2 = params_dup,
|
||||||
});
|
});
|
||||||
|
try module.cache.fn_types.put(module.gpa, .{
|
||||||
|
.return_ty = return_ty_id,
|
||||||
|
.params = params_dup,
|
||||||
|
}, result_id);
|
||||||
return result_id;
|
return result_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn constant(module: *Module, ty_id: Id, value: spec.LiteralContextDependentNumber) !Id {
|
||||||
|
const entry = try module.cache.constants.getOrPut(module.gpa, .{ .ty = ty_id, .value = value });
|
||||||
|
if (!entry.found_existing) {
|
||||||
|
entry.value_ptr.* = module.allocId();
|
||||||
|
try module.sections.globals.emit(module.gpa, .OpConstant, .{
|
||||||
|
.id_result_type = ty_id,
|
||||||
|
.id_result = entry.value_ptr.*,
|
||||||
|
.value = value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return entry.value_ptr.*;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn constBool(module: *Module, value: bool) !Id {
|
pub fn constBool(module: *Module, value: bool) !Id {
|
||||||
if (module.cache.bool_const[@intFromBool(value)]) |b| return b;
|
if (module.cache.bool_const[@intFromBool(value)]) |b| return b;
|
||||||
|
|
||||||
@ -711,28 +848,31 @@ pub fn memberDebugName(module: *Module, target: Id, member: u32, name: []const u
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn debugString(module: *Module, string: []const u8) !Id {
|
||||||
|
const entry = try module.cache.strings.getOrPut(module.gpa, string);
|
||||||
|
if (!entry.found_existing) {
|
||||||
|
entry.value_ptr.* = module.allocId();
|
||||||
|
try module.sections.debug_strings.emit(module.gpa, .OpString, .{
|
||||||
|
.id_result = entry.value_ptr.*,
|
||||||
|
.string = string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return entry.value_ptr.*;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn storageClass(module: *Module, as: std.builtin.AddressSpace) spec.StorageClass {
|
pub fn storageClass(module: *Module, as: std.builtin.AddressSpace) spec.StorageClass {
|
||||||
|
const target = module.zcu.getTarget();
|
||||||
return switch (as) {
|
return switch (as) {
|
||||||
.generic => if (module.target.cpu.has(.spirv, .generic_pointer)) .generic else .function,
|
.generic => if (target.cpu.has(.spirv, .generic_pointer)) .generic else .function,
|
||||||
.global => switch (module.target.os.tag) {
|
.global => switch (target.os.tag) {
|
||||||
.opencl, .amdhsa => .cross_workgroup,
|
.opencl, .amdhsa => .cross_workgroup,
|
||||||
else => .storage_buffer,
|
else => .storage_buffer,
|
||||||
},
|
},
|
||||||
.push_constant => {
|
.push_constant => .push_constant,
|
||||||
return .push_constant;
|
.output => .output,
|
||||||
},
|
.uniform => .uniform,
|
||||||
.output => {
|
.storage_buffer => .storage_buffer,
|
||||||
return .output;
|
.physical_storage_buffer => .physical_storage_buffer,
|
||||||
},
|
|
||||||
.uniform => {
|
|
||||||
return .uniform;
|
|
||||||
},
|
|
||||||
.storage_buffer => {
|
|
||||||
return .storage_buffer;
|
|
||||||
},
|
|
||||||
.physical_storage_buffer => {
|
|
||||||
return .physical_storage_buffer;
|
|
||||||
},
|
|
||||||
.constant => .uniform_constant,
|
.constant => .uniform_constant,
|
||||||
.shared => .workgroup,
|
.shared => .workgroup,
|
||||||
.local => .function,
|
.local => .function,
|
||||||
|
|||||||
@ -46,8 +46,8 @@ pub fn createEmpty(
|
|||||||
else => unreachable, // Caught by Compilation.Config.resolve.
|
else => unreachable, // Caught by Compilation.Config.resolve.
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = try arena.create(Linker);
|
const linker = try arena.create(Linker);
|
||||||
self.* = .{
|
linker.* = .{
|
||||||
.base = .{
|
.base = .{
|
||||||
.tag = .spirv,
|
.tag = .spirv,
|
||||||
.comp = comp,
|
.comp = comp,
|
||||||
@ -59,16 +59,20 @@ pub fn createEmpty(
|
|||||||
.file = null,
|
.file = null,
|
||||||
.build_id = options.build_id,
|
.build_id = options.build_id,
|
||||||
},
|
},
|
||||||
.module = .{ .gpa = gpa, .target = comp.getTarget() },
|
.module = .{
|
||||||
|
.gpa = gpa,
|
||||||
|
.arena = arena,
|
||||||
|
.zcu = comp.zcu.?,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
errdefer self.deinit();
|
errdefer linker.deinit();
|
||||||
|
|
||||||
self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
|
linker.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
|
||||||
.truncate = true,
|
.truncate = true,
|
||||||
.read = true,
|
.read = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return self;
|
return linker;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(
|
pub fn open(
|
||||||
@ -80,12 +84,12 @@ pub fn open(
|
|||||||
return createEmpty(arena, comp, emit, options);
|
return createEmpty(arena, comp, emit, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Linker) void {
|
pub fn deinit(linker: *Linker) void {
|
||||||
self.module.deinit();
|
linker.module.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genNav(
|
fn generate(
|
||||||
self: *Linker,
|
linker: *Linker,
|
||||||
pt: Zcu.PerThread,
|
pt: Zcu.PerThread,
|
||||||
nav_index: InternPool.Nav.Index,
|
nav_index: InternPool.Nav.Index,
|
||||||
air: Air,
|
air: Air,
|
||||||
@ -96,9 +100,9 @@ fn genNav(
|
|||||||
const gpa = zcu.gpa;
|
const gpa = zcu.gpa;
|
||||||
const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg;
|
const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg;
|
||||||
|
|
||||||
var nav_gen: CodeGen = .{
|
var cg: CodeGen = .{
|
||||||
.pt = pt,
|
.pt = pt,
|
||||||
.module = &self.module,
|
.module = &linker.module,
|
||||||
.owner_nav = nav_index,
|
.owner_nav = nav_index,
|
||||||
.air = air,
|
.air = air,
|
||||||
.liveness = liveness,
|
.liveness = liveness,
|
||||||
@ -108,17 +112,17 @@ fn genNav(
|
|||||||
},
|
},
|
||||||
.base_line = zcu.navSrcLine(nav_index),
|
.base_line = zcu.navSrcLine(nav_index),
|
||||||
};
|
};
|
||||||
defer nav_gen.deinit();
|
defer cg.deinit();
|
||||||
|
|
||||||
nav_gen.genNav(do_codegen) catch |err| switch (err) {
|
cg.genNav(do_codegen) catch |err| switch (err) {
|
||||||
error.CodegenFail => switch (zcu.codegenFailMsg(nav_index, nav_gen.error_msg.?)) {
|
error.CodegenFail => switch (zcu.codegenFailMsg(nav_index, cg.error_msg.?)) {
|
||||||
error.CodegenFail => {},
|
error.CodegenFail => {},
|
||||||
error.OutOfMemory => |e| return e,
|
error.OutOfMemory => |e| return e,
|
||||||
},
|
},
|
||||||
else => |other| {
|
else => |other| {
|
||||||
// There might be an error that happened *after* self.error_msg
|
// There might be an error that happened *after* linker.error_msg
|
||||||
// was already allocated, so be sure to free it.
|
// was already allocated, so be sure to free it.
|
||||||
if (nav_gen.error_msg) |error_msg| {
|
if (cg.error_msg) |error_msg| {
|
||||||
error_msg.deinit(gpa);
|
error_msg.deinit(gpa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +132,7 @@ fn genNav(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateFunc(
|
pub fn updateFunc(
|
||||||
self: *Linker,
|
linker: *Linker,
|
||||||
pt: Zcu.PerThread,
|
pt: Zcu.PerThread,
|
||||||
func_index: InternPool.Index,
|
func_index: InternPool.Index,
|
||||||
air: *const Air,
|
air: *const Air,
|
||||||
@ -136,17 +140,17 @@ pub fn updateFunc(
|
|||||||
) !void {
|
) !void {
|
||||||
const nav = pt.zcu.funcInfo(func_index).owner_nav;
|
const nav = pt.zcu.funcInfo(func_index).owner_nav;
|
||||||
// TODO: Separate types for generating decls and functions?
|
// TODO: Separate types for generating decls and functions?
|
||||||
try self.genNav(pt, nav, air.*, liveness.*.?, true);
|
try linker.generate(pt, nav, air.*, liveness.*.?, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateNav(self: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
|
pub fn updateNav(linker: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
|
||||||
const ip = &pt.zcu.intern_pool;
|
const ip = &pt.zcu.intern_pool;
|
||||||
log.debug("lowering nav {f}({d})", .{ ip.getNav(nav).fqn.fmt(ip), nav });
|
log.debug("lowering nav {f}({d})", .{ ip.getNav(nav).fqn.fmt(ip), nav });
|
||||||
try self.genNav(pt, nav, undefined, undefined, false);
|
try linker.generate(pt, nav, undefined, undefined, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateExports(
|
pub fn updateExports(
|
||||||
self: *Linker,
|
linker: *Linker,
|
||||||
pt: Zcu.PerThread,
|
pt: Zcu.PerThread,
|
||||||
exported: Zcu.Exported,
|
exported: Zcu.Exported,
|
||||||
export_indices: []const Zcu.Export.Index,
|
export_indices: []const Zcu.Export.Index,
|
||||||
@ -163,7 +167,7 @@ pub fn updateExports(
|
|||||||
const nav_ty = ip.getNav(nav_index).typeOf(ip);
|
const nav_ty = ip.getNav(nav_index).typeOf(ip);
|
||||||
const target = zcu.getTarget();
|
const target = zcu.getTarget();
|
||||||
if (ip.isFunctionType(nav_ty)) {
|
if (ip.isFunctionType(nav_ty)) {
|
||||||
const spv_decl_index = try self.module.resolveNav(ip, nav_index);
|
const spv_decl_index = try linker.module.resolveNav(ip, nav_index);
|
||||||
const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
|
const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
|
||||||
const exec_model: spec.ExecutionModel = switch (target.os.tag) {
|
const exec_model: spec.ExecutionModel = switch (target.os.tag) {
|
||||||
.vulkan, .opengl => switch (cc) {
|
.vulkan, .opengl => switch (cc) {
|
||||||
@ -185,7 +189,7 @@ pub fn updateExports(
|
|||||||
|
|
||||||
for (export_indices) |export_idx| {
|
for (export_indices) |export_idx| {
|
||||||
const exp = export_idx.ptr(zcu);
|
const exp = export_idx.ptr(zcu);
|
||||||
try self.module.declareEntryPoint(
|
try linker.module.declareEntryPoint(
|
||||||
spv_decl_index,
|
spv_decl_index,
|
||||||
exp.opts.name.toSlice(ip),
|
exp.opts.name.toSlice(ip),
|
||||||
exec_model,
|
exec_model,
|
||||||
@ -198,7 +202,7 @@ pub fn updateExports(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush(
|
pub fn flush(
|
||||||
self: *Linker,
|
linker: *Linker,
|
||||||
arena: Allocator,
|
arena: Allocator,
|
||||||
tid: Zcu.PerThread.Id,
|
tid: Zcu.PerThread.Id,
|
||||||
prog_node: std.Progress.Node,
|
prog_node: std.Progress.Node,
|
||||||
@ -214,18 +218,18 @@ pub fn flush(
|
|||||||
const sub_prog_node = prog_node.start("Flush Module", 0);
|
const sub_prog_node = prog_node.start("Flush Module", 0);
|
||||||
defer sub_prog_node.end();
|
defer sub_prog_node.end();
|
||||||
|
|
||||||
const comp = self.base.comp;
|
const comp = linker.base.comp;
|
||||||
const diags = &comp.link_diags;
|
const diags = &comp.link_diags;
|
||||||
const gpa = comp.gpa;
|
const gpa = comp.gpa;
|
||||||
|
|
||||||
// We need to export the list of error names somewhere so that we can pretty-print them in the
|
// 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
|
// 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.
|
// nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.
|
||||||
var error_info: std.io.Writer.Allocating = .init(self.module.gpa);
|
var error_info: std.io.Writer.Allocating = .init(linker.module.gpa);
|
||||||
defer error_info.deinit();
|
defer error_info.deinit();
|
||||||
|
|
||||||
error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
|
error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
|
||||||
const ip = &self.base.comp.zcu.?.intern_pool;
|
const ip = &linker.base.comp.zcu.?.intern_pool;
|
||||||
for (ip.global_error_set.getNamesFromMainThread()) |name| {
|
for (ip.global_error_set.getNamesFromMainThread()) |name| {
|
||||||
// Errors can contain pretty much any character - to encode them in a string we must escape
|
// 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
|
// them somehow. Easiest here is to use some established scheme, one which also preseves the
|
||||||
@ -245,28 +249,27 @@ pub fn flush(
|
|||||||
}.isValidChar,
|
}.isValidChar,
|
||||||
) catch return error.OutOfMemory;
|
) catch return error.OutOfMemory;
|
||||||
}
|
}
|
||||||
try self.module.sections.debug_strings.emit(gpa, .OpSourceExtension, .{
|
try linker.module.sections.debug_strings.emit(gpa, .OpSourceExtension, .{
|
||||||
.extension = error_info.getWritten(),
|
.extension = error_info.getWritten(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const module = try self.module.finalize(arena);
|
const module = try linker.module.finalize(arena);
|
||||||
errdefer arena.free(module);
|
errdefer arena.free(module);
|
||||||
|
|
||||||
const linked_module = self.linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
|
const linked_module = linker.linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
|
||||||
error.OutOfMemory => return error.OutOfMemory,
|
error.OutOfMemory => return error.OutOfMemory,
|
||||||
else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),
|
else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module)) catch |err|
|
linker.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module)) catch |err|
|
||||||
return diags.fail("failed to write: {s}", .{@errorName(err)});
|
return diags.fail("failed to write: {s}", .{@errorName(err)});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn linkModule(self: *Linker, arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
|
fn linkModule(linker: *Linker, arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
|
||||||
_ = self;
|
_ = linker;
|
||||||
|
|
||||||
const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
|
const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
|
||||||
const prune_unused = @import("SpirV/prune_unused.zig");
|
const prune_unused = @import("SpirV/prune_unused.zig");
|
||||||
const dedup = @import("SpirV/deduplicate.zig");
|
|
||||||
|
|
||||||
var parser = try BinaryModule.Parser.init(arena);
|
var parser = try BinaryModule.Parser.init(arena);
|
||||||
defer parser.deinit();
|
defer parser.deinit();
|
||||||
@ -274,7 +277,6 @@ fn linkModule(self: *Linker, arena: Allocator, module: []Word, progress: std.Pro
|
|||||||
|
|
||||||
try lower_invocation_globals.run(&parser, &binary, progress);
|
try lower_invocation_globals.run(&parser, &binary, progress);
|
||||||
try prune_unused.run(&parser, &binary, progress);
|
try prune_unused.run(&parser, &binary, progress);
|
||||||
try dedup.run(&parser, &binary, progress);
|
|
||||||
|
|
||||||
return binary.finalize(arena);
|
return binary.finalize(arena);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,553 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const log = std.log.scoped(.spirv_link);
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
|
|
||||||
const BinaryModule = @import("BinaryModule.zig");
|
|
||||||
const Section = @import("../../arch/spirv/Section.zig");
|
|
||||||
const spec = @import("../../arch/spirv/spec.zig");
|
|
||||||
const Opcode = spec.Opcode;
|
|
||||||
const ResultId = spec.Id;
|
|
||||||
const Word = spec.Word;
|
|
||||||
|
|
||||||
fn canDeduplicate(opcode: Opcode) bool {
|
|
||||||
return switch (opcode) {
|
|
||||||
.OpTypeForwardPointer => false, // Don't need to handle these
|
|
||||||
.OpGroupDecorate, .OpGroupMemberDecorate => {
|
|
||||||
// These are deprecated, so don't bother supporting them for now.
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
// Debug decoration-style instructions
|
|
||||||
.OpName, .OpMemberName => true,
|
|
||||||
else => switch (opcode.class()) {
|
|
||||||
.type_declaration,
|
|
||||||
.constant_creation,
|
|
||||||
.annotation,
|
|
||||||
=> true,
|
|
||||||
else => false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ModuleInfo = struct {
|
|
||||||
/// This models a type, decoration or constant instruction
|
|
||||||
/// and its dependencies.
|
|
||||||
const Entity = struct {
|
|
||||||
/// The type that this entity represents. This is just
|
|
||||||
/// the instruction opcode.
|
|
||||||
kind: Opcode,
|
|
||||||
/// The offset of this entity's operands, in
|
|
||||||
/// `binary.instructions`.
|
|
||||||
first_operand: u32,
|
|
||||||
/// The number of operands in this entity
|
|
||||||
num_operands: u16,
|
|
||||||
/// The (first_operand-relative) offset of the result-id,
|
|
||||||
/// or the entity that is affected by this entity if this entity
|
|
||||||
/// is a decoration.
|
|
||||||
result_id_index: u16,
|
|
||||||
/// The first decoration in `self.decorations`.
|
|
||||||
first_decoration: u32,
|
|
||||||
|
|
||||||
fn operands(self: Entity, binary: *const BinaryModule) []const Word {
|
|
||||||
return binary.instructions[self.first_operand..][0..self.num_operands];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Maps result-id to Entity's
|
|
||||||
entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),
|
|
||||||
/// A bit set that keeps track of which operands are result-ids.
|
|
||||||
/// Note: This also includes any result-id!
|
|
||||||
/// Because we need these values when recoding the module anyway,
|
|
||||||
/// it contains the status of ALL operands in the module.
|
|
||||||
operand_is_id: std.DynamicBitSetUnmanaged,
|
|
||||||
/// Store of decorations for each entity.
|
|
||||||
decorations: []const Entity,
|
|
||||||
|
|
||||||
pub fn parse(
|
|
||||||
arena: Allocator,
|
|
||||||
parser: *BinaryModule.Parser,
|
|
||||||
binary: BinaryModule,
|
|
||||||
) !ModuleInfo {
|
|
||||||
var entities = std.AutoArrayHashMap(ResultId, Entity).init(arena);
|
|
||||||
var id_offsets = std.ArrayList(u16).init(arena);
|
|
||||||
var operand_is_id = try std.DynamicBitSetUnmanaged.initEmpty(arena, binary.instructions.len);
|
|
||||||
var decorations = std.MultiArrayList(struct { target_id: ResultId, entity: Entity }){};
|
|
||||||
|
|
||||||
var it = binary.iterateInstructions();
|
|
||||||
while (it.next()) |inst| {
|
|
||||||
id_offsets.items.len = 0;
|
|
||||||
try parser.parseInstructionResultIds(binary, inst, &id_offsets);
|
|
||||||
|
|
||||||
const first_operand_offset: u32 = @intCast(inst.offset + 1);
|
|
||||||
for (id_offsets.items) |offset| {
|
|
||||||
operand_is_id.set(first_operand_offset + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canDeduplicate(inst.opcode)) continue;
|
|
||||||
|
|
||||||
const result_id_index: u16 = switch (inst.opcode.class()) {
|
|
||||||
.type_declaration, .annotation, .debug => 0,
|
|
||||||
.constant_creation => 1,
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result_id: ResultId = @enumFromInt(inst.operands[id_offsets.items[result_id_index]]);
|
|
||||||
const entity = Entity{
|
|
||||||
.kind = inst.opcode,
|
|
||||||
.first_operand = first_operand_offset,
|
|
||||||
.num_operands = @intCast(inst.operands.len),
|
|
||||||
.result_id_index = result_id_index,
|
|
||||||
.first_decoration = undefined, // Filled in later
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (inst.opcode.class()) {
|
|
||||||
.annotation, .debug => {
|
|
||||||
try decorations.append(arena, .{
|
|
||||||
.target_id = result_id,
|
|
||||||
.entity = entity,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
.type_declaration, .constant_creation => {
|
|
||||||
const entry = try entities.getOrPut(result_id);
|
|
||||||
if (entry.found_existing) {
|
|
||||||
log.err("type or constant {f} has duplicate definition", .{result_id});
|
|
||||||
return error.DuplicateId;
|
|
||||||
}
|
|
||||||
entry.value_ptr.* = entity;
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort decorations by the index of the result-id in `entities.
|
|
||||||
// This ensures not only that the decorations of a particular reuslt-id
|
|
||||||
// are continuous, but the subsequences also appear in the same order as in `entities`.
|
|
||||||
|
|
||||||
const SortContext = struct {
|
|
||||||
entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),
|
|
||||||
ids: []const ResultId,
|
|
||||||
|
|
||||||
pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
|
|
||||||
// If any index is not in the entities set, its because its not a
|
|
||||||
// deduplicatable result-id. Those should be considered largest and
|
|
||||||
// float to the end.
|
|
||||||
const entity_index_a = ctx.entities.getIndex(ctx.ids[a_index]) orelse return false;
|
|
||||||
const entity_index_b = ctx.entities.getIndex(ctx.ids[b_index]) orelse return true;
|
|
||||||
|
|
||||||
return entity_index_a < entity_index_b;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
decorations.sort(SortContext{
|
|
||||||
.entities = entities.unmanaged,
|
|
||||||
.ids = decorations.items(.target_id),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now go through the decorations and add the offsets to the entities list.
|
|
||||||
var decoration_i: u32 = 0;
|
|
||||||
const target_ids = decorations.items(.target_id);
|
|
||||||
for (entities.keys(), entities.values()) |id, *entity| {
|
|
||||||
entity.first_decoration = decoration_i;
|
|
||||||
|
|
||||||
// Scan ahead to the next decoration
|
|
||||||
while (decoration_i < target_ids.len and target_ids[decoration_i] == id) {
|
|
||||||
decoration_i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.entities = entities.unmanaged,
|
|
||||||
.operand_is_id = operand_is_id,
|
|
||||||
// There may be unrelated decorations at the end, so make sure to
|
|
||||||
// slice those off.
|
|
||||||
.decorations = decorations.items(.entity)[0..decoration_i],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn entityDecorationsByIndex(self: ModuleInfo, index: usize) []const Entity {
|
|
||||||
const values = self.entities.values();
|
|
||||||
const first_decoration = values[index].first_decoration;
|
|
||||||
if (index == values.len - 1) {
|
|
||||||
return self.decorations[first_decoration..];
|
|
||||||
} else {
|
|
||||||
const next_first_decoration = values[index + 1].first_decoration;
|
|
||||||
return self.decorations[first_decoration..next_first_decoration];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const EntityContext = struct {
|
|
||||||
a: Allocator,
|
|
||||||
ptr_map_a: std.AutoArrayHashMapUnmanaged(ResultId, void) = .empty,
|
|
||||||
ptr_map_b: std.AutoArrayHashMapUnmanaged(ResultId, void) = .empty,
|
|
||||||
info: *const ModuleInfo,
|
|
||||||
binary: *const BinaryModule,
|
|
||||||
|
|
||||||
fn deinit(self: *EntityContext) void {
|
|
||||||
self.ptr_map_a.deinit(self.a);
|
|
||||||
self.ptr_map_b.deinit(self.a);
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn equalizeMapCapacity(self: *EntityContext) !void {
|
|
||||||
const cap = @max(self.ptr_map_a.capacity(), self.ptr_map_b.capacity());
|
|
||||||
try self.ptr_map_a.ensureTotalCapacity(self.a, cap);
|
|
||||||
try self.ptr_map_b.ensureTotalCapacity(self.a, cap);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash(self: *EntityContext, id: ResultId) !u64 {
|
|
||||||
var hasher = std.hash.Wyhash.init(0);
|
|
||||||
self.ptr_map_a.clearRetainingCapacity();
|
|
||||||
try self.hashInner(&hasher, id);
|
|
||||||
return hasher.final();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hashInner(self: *EntityContext, hasher: *std.hash.Wyhash, id: ResultId) error{OutOfMemory}!void {
|
|
||||||
const index = self.info.entities.getIndex(id) orelse {
|
|
||||||
// Index unknown, the type or constant may depend on another result-id
|
|
||||||
// that couldn't be deduplicated and so it wasn't added to info.entities.
|
|
||||||
// In this case, just has the ID itself.
|
|
||||||
std.hash.autoHash(hasher, id);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
const entity = self.info.entities.values()[index];
|
|
||||||
|
|
||||||
// If the current pointer is recursive, don't immediately add it to the map. This is to ensure that
|
|
||||||
// if the current pointer is already recursive, it gets the same hash a pointer that points to the
|
|
||||||
// same child but has a different result-id.
|
|
||||||
if (entity.kind == .OpTypePointer) {
|
|
||||||
// This may be either a pointer that is forward-referenced in the future,
|
|
||||||
// or a forward reference to a pointer.
|
|
||||||
// Note: We use the **struct** here instead of the pointer itself, to avoid an edge case like this:
|
|
||||||
//
|
|
||||||
// A - C*'
|
|
||||||
// \
|
|
||||||
// C - C*'
|
|
||||||
// /
|
|
||||||
// B - C*"
|
|
||||||
//
|
|
||||||
// In this case, hashing A goes like
|
|
||||||
// A -> C*' -> C -> C*' recursion
|
|
||||||
// And hashing B goes like
|
|
||||||
// B -> C*" -> C -> C*' -> C -> C*' recursion
|
|
||||||
// The are several calls to ptrType in codegen that may C*' and C*" to be generated as separate
|
|
||||||
// types. This is not a problem for C itself though - this can only be generated through resolveType()
|
|
||||||
// and so ensures equality by Zig's type system. Technically the above problem is still present, but it
|
|
||||||
// would only be present in a structure such as
|
|
||||||
//
|
|
||||||
// A - C*' - C'
|
|
||||||
// \
|
|
||||||
// C*" - C - C*
|
|
||||||
// /
|
|
||||||
// B
|
|
||||||
//
|
|
||||||
// where there is a duplicate definition of struct C. Resolving this requires a much more time consuming
|
|
||||||
// algorithm though, and because we don't expect any correctness issues with it, we leave that for now.
|
|
||||||
|
|
||||||
// TODO: Do we need to mind the storage class here? Its going to be recursive regardless, right?
|
|
||||||
const struct_id: ResultId = @enumFromInt(entity.operands(self.binary)[2]);
|
|
||||||
const entry = try self.ptr_map_a.getOrPut(self.a, struct_id);
|
|
||||||
if (entry.found_existing) {
|
|
||||||
// Pointer already seen. Hash the index instead of recursing into its children.
|
|
||||||
std.hash.autoHash(hasher, entry.index);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.hashEntity(hasher, entity);
|
|
||||||
|
|
||||||
// Process decorations.
|
|
||||||
const decorations = self.info.entityDecorationsByIndex(index);
|
|
||||||
for (decorations) |decoration| {
|
|
||||||
try self.hashEntity(hasher, decoration);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity.kind == .OpTypePointer) {
|
|
||||||
const struct_id: ResultId = @enumFromInt(entity.operands(self.binary)[2]);
|
|
||||||
assert(self.ptr_map_a.swapRemove(struct_id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hashEntity(self: *EntityContext, hasher: *std.hash.Wyhash, entity: ModuleInfo.Entity) !void {
|
|
||||||
std.hash.autoHash(hasher, entity.kind);
|
|
||||||
// Process operands
|
|
||||||
const operands = entity.operands(self.binary);
|
|
||||||
for (operands, 0..) |operand, i| {
|
|
||||||
if (i == entity.result_id_index) {
|
|
||||||
// Not relevant, skip...
|
|
||||||
continue;
|
|
||||||
} else if (self.info.operand_is_id.isSet(entity.first_operand + i)) {
|
|
||||||
// Operand is ID
|
|
||||||
try self.hashInner(hasher, @enumFromInt(operand));
|
|
||||||
} else {
|
|
||||||
// Operand is merely data
|
|
||||||
std.hash.autoHash(hasher, operand);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eql(self: *EntityContext, a: ResultId, b: ResultId) !bool {
|
|
||||||
self.ptr_map_a.clearRetainingCapacity();
|
|
||||||
self.ptr_map_b.clearRetainingCapacity();
|
|
||||||
|
|
||||||
return try self.eqlInner(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eqlInner(self: *EntityContext, id_a: ResultId, id_b: ResultId) error{OutOfMemory}!bool {
|
|
||||||
const maybe_index_a = self.info.entities.getIndex(id_a);
|
|
||||||
const maybe_index_b = self.info.entities.getIndex(id_b);
|
|
||||||
|
|
||||||
if (maybe_index_a == null and maybe_index_b == null) {
|
|
||||||
// Both indices unknown. In this case the type or constant
|
|
||||||
// may depend on another result-id that couldn't be deduplicated
|
|
||||||
// (so it wasn't added to info.entities). In this case, that particular
|
|
||||||
// result-id should be the same one.
|
|
||||||
return id_a == id_b;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index_a = maybe_index_a orelse return false;
|
|
||||||
const index_b = maybe_index_b orelse return false;
|
|
||||||
|
|
||||||
const entity_a = self.info.entities.values()[index_a];
|
|
||||||
const entity_b = self.info.entities.values()[index_b];
|
|
||||||
|
|
||||||
if (entity_a.kind != entity_b.kind) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity_a.kind == .OpTypePointer) {
|
|
||||||
// May be a forward reference, or should be saved as a potential
|
|
||||||
// forward reference in the future. Whatever the case, it should
|
|
||||||
// be the same for both a and b.
|
|
||||||
const struct_id_a: ResultId = @enumFromInt(entity_a.operands(self.binary)[2]);
|
|
||||||
const struct_id_b: ResultId = @enumFromInt(entity_b.operands(self.binary)[2]);
|
|
||||||
|
|
||||||
const entry_a = try self.ptr_map_a.getOrPut(self.a, struct_id_a);
|
|
||||||
const entry_b = try self.ptr_map_b.getOrPut(self.a, struct_id_b);
|
|
||||||
|
|
||||||
if (entry_a.found_existing != entry_b.found_existing) return false;
|
|
||||||
if (entry_a.index != entry_b.index) return false;
|
|
||||||
|
|
||||||
if (entry_a.found_existing) {
|
|
||||||
// No need to recurse.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!try self.eqlEntities(entity_a, entity_b)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare decorations.
|
|
||||||
const decorations_a = self.info.entityDecorationsByIndex(index_a);
|
|
||||||
const decorations_b = self.info.entityDecorationsByIndex(index_b);
|
|
||||||
if (decorations_a.len != decorations_b.len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (decorations_a, decorations_b) |decoration_a, decoration_b| {
|
|
||||||
if (!try self.eqlEntities(decoration_a, decoration_b)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity_a.kind == .OpTypePointer) {
|
|
||||||
const struct_id_a: ResultId = @enumFromInt(entity_a.operands(self.binary)[2]);
|
|
||||||
const struct_id_b: ResultId = @enumFromInt(entity_b.operands(self.binary)[2]);
|
|
||||||
|
|
||||||
assert(self.ptr_map_a.swapRemove(struct_id_a));
|
|
||||||
assert(self.ptr_map_b.swapRemove(struct_id_b));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eqlEntities(self: *EntityContext, entity_a: ModuleInfo.Entity, entity_b: ModuleInfo.Entity) !bool {
|
|
||||||
if (entity_a.kind != entity_b.kind) {
|
|
||||||
return false;
|
|
||||||
} else if (entity_a.result_id_index != entity_a.result_id_index) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const operands_a = entity_a.operands(self.binary);
|
|
||||||
const operands_b = entity_b.operands(self.binary);
|
|
||||||
|
|
||||||
// Note: returns false for operands that have explicit defaults in optional operands... oh well
|
|
||||||
if (operands_a.len != operands_b.len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (operands_a, operands_b, 0..) |operand_a, operand_b, i| {
|
|
||||||
const a_is_id = self.info.operand_is_id.isSet(entity_a.first_operand + i);
|
|
||||||
const b_is_id = self.info.operand_is_id.isSet(entity_b.first_operand + i);
|
|
||||||
if (a_is_id != b_is_id) {
|
|
||||||
return false;
|
|
||||||
} else if (i == entity_a.result_id_index) {
|
|
||||||
// result-id for both...
|
|
||||||
continue;
|
|
||||||
} else if (a_is_id) {
|
|
||||||
// Both are IDs, so recurse.
|
|
||||||
if (!try self.eqlInner(@enumFromInt(operand_a), @enumFromInt(operand_b))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (operand_a != operand_b) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// This struct is a wrapper around EntityContext that adapts it for
|
|
||||||
/// use in a hash map. Because EntityContext allocates, it cannot be
|
|
||||||
/// used. This wrapper simply assumes that the maps have been allocated
|
|
||||||
/// the max amount of memory they are going to use.
|
|
||||||
/// This is done by pre-hashing all keys.
|
|
||||||
const EntityHashContext = struct {
|
|
||||||
entity_context: *EntityContext,
|
|
||||||
|
|
||||||
pub fn hash(self: EntityHashContext, key: ResultId) u64 {
|
|
||||||
return self.entity_context.hash(key) catch unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eql(self: EntityHashContext, a: ResultId, b: ResultId) bool {
|
|
||||||
return self.entity_context.eql(a, b) catch unreachable;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Progress.Node) !void {
|
|
||||||
const sub_node = progress.start("deduplicate", 0);
|
|
||||||
defer sub_node.end();
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(parser.a);
|
|
||||||
defer arena.deinit();
|
|
||||||
const a = arena.allocator();
|
|
||||||
|
|
||||||
const info = try ModuleInfo.parse(a, parser, binary.*);
|
|
||||||
|
|
||||||
// Hash all keys once so that the maps can be allocated the right size.
|
|
||||||
var ctx = EntityContext{
|
|
||||||
.a = a,
|
|
||||||
.info = &info,
|
|
||||||
.binary = binary,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (info.entities.keys()) |id| {
|
|
||||||
_ = try ctx.hash(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hash only uses ptr_map_a, so allocate ptr_map_b too
|
|
||||||
try ctx.equalizeMapCapacity();
|
|
||||||
|
|
||||||
// Figure out which entities can be deduplicated.
|
|
||||||
var map = std.HashMap(ResultId, void, EntityHashContext, 80).initContext(a, .{
|
|
||||||
.entity_context = &ctx,
|
|
||||||
});
|
|
||||||
var replace = std.AutoArrayHashMap(ResultId, ResultId).init(a);
|
|
||||||
for (info.entities.keys()) |id| {
|
|
||||||
const entry = try map.getOrPut(id);
|
|
||||||
if (entry.found_existing) {
|
|
||||||
try replace.putNoClobber(id, entry.key_ptr.*);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub_node.setEstimatedTotalItems(binary.instructions.len);
|
|
||||||
|
|
||||||
// Now process the module, and replace instructions where needed.
|
|
||||||
var section = Section{};
|
|
||||||
var it = binary.iterateInstructions();
|
|
||||||
var new_functions_section: ?usize = null;
|
|
||||||
var new_operands = std.ArrayList(u32).init(a);
|
|
||||||
var emitted_ptrs = std.AutoHashMap(ResultId, void).init(a);
|
|
||||||
while (it.next()) |inst| {
|
|
||||||
defer sub_node.setCompletedItems(inst.offset);
|
|
||||||
|
|
||||||
// Result-id can only be the first or second operand
|
|
||||||
const inst_spec = parser.getInstSpec(inst.opcode).?;
|
|
||||||
|
|
||||||
const maybe_result_id_offset: ?u16 = for (0..2) |i| {
|
|
||||||
if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .id_result) {
|
|
||||||
break @intCast(i);
|
|
||||||
}
|
|
||||||
} else null;
|
|
||||||
|
|
||||||
if (maybe_result_id_offset) |offset| {
|
|
||||||
const result_id: ResultId = @enumFromInt(inst.operands[offset]);
|
|
||||||
if (replace.contains(result_id)) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (inst.opcode) {
|
|
||||||
.OpFunction => if (new_functions_section == null) {
|
|
||||||
new_functions_section = section.instructions.items.len;
|
|
||||||
},
|
|
||||||
.OpTypeForwardPointer => continue, // We re-emit these where needed
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (inst.opcode.class()) {
|
|
||||||
.annotation, .debug => {
|
|
||||||
// For decoration-style instructions, only emit them
|
|
||||||
// if the target is not removed.
|
|
||||||
const target: ResultId = @enumFromInt(inst.operands[0]);
|
|
||||||
if (replace.contains(target)) continue;
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-emit the instruction, but replace all the IDs.
|
|
||||||
|
|
||||||
new_operands.items.len = 0;
|
|
||||||
try new_operands.appendSlice(inst.operands);
|
|
||||||
|
|
||||||
for (new_operands.items, 0..) |*operand, i| {
|
|
||||||
const is_id = info.operand_is_id.isSet(inst.offset + 1 + i);
|
|
||||||
if (!is_id) continue;
|
|
||||||
|
|
||||||
if (replace.get(@enumFromInt(operand.*))) |new_id| {
|
|
||||||
operand.* = @intFromEnum(new_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maybe_result_id_offset == null or maybe_result_id_offset.? != i) {
|
|
||||||
// Only emit forward pointers before type, constant, and global instructions.
|
|
||||||
// Debug and Annotation instructions don't need the forward pointer, and it
|
|
||||||
// messes up the logical layout of the module.
|
|
||||||
switch (inst.opcode.class()) {
|
|
||||||
.type_declaration, .constant_creation, .memory => {},
|
|
||||||
else => continue,
|
|
||||||
}
|
|
||||||
|
|
||||||
const id: ResultId = @enumFromInt(operand.*);
|
|
||||||
const index = info.entities.getIndex(id) orelse continue;
|
|
||||||
const entity = info.entities.values()[index];
|
|
||||||
if (entity.kind == .OpTypePointer and !emitted_ptrs.contains(id)) {
|
|
||||||
// Grab the pointer's storage class from its operands in the original
|
|
||||||
// module.
|
|
||||||
const storage_class: spec.StorageClass = @enumFromInt(entity.operands(binary)[1]);
|
|
||||||
try section.emit(a, .OpTypeForwardPointer, .{
|
|
||||||
.pointer_type = id,
|
|
||||||
.storage_class = storage_class,
|
|
||||||
});
|
|
||||||
try emitted_ptrs.put(id, {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inst.opcode == .OpTypePointer) {
|
|
||||||
const result_id: ResultId = @enumFromInt(new_operands.items[maybe_result_id_offset.?]);
|
|
||||||
try emitted_ptrs.put(result_id, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
try section.emitRawInstruction(a, inst.opcode, new_operands.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (replace.keys()) |key| {
|
|
||||||
_ = binary.ext_inst_map.remove(key);
|
|
||||||
_ = binary.arith_type_width.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
binary.instructions = try parser.a.dupe(Word, section.toWords());
|
|
||||||
binary.sections.functions = new_functions_section orelse binary.instructions.len;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user