| 1 | const std = @import("std"); |
| 2 | const Allocator = std.mem.Allocator; |
| 3 | const Path = std.Build.Cache.Path; |
| 4 | const assert = std.debug.assert; |
| 5 | const Zcu = @import("../Zcu.zig"); |
| 6 | const InternPool = @import("../InternPool.zig"); |
| 7 | const Compilation = @import("../Compilation.zig"); |
| 8 | const link = @import("../link.zig"); |
| 9 | const codegen = @import("../codegen.zig"); |
| 10 | const CodeGen = @import("../codegen/spirv/CodeGen.zig"); |
| 11 | const Mir = @import("../codegen/spirv/Mir.zig"); |
| 12 | const spec = @import("../codegen/spirv/spec.zig"); |
| 13 | const Section = @import("../codegen/spirv/Section.zig"); |
| 14 | const Flush = @import("Spirv/Flush.zig"); |
| 15 | const Word = spec.Word; |
| 16 | const Linker = @This(); |
| 17 | |
| 18 | base: link.File, |
| 19 | mirs: std.array_hash_map.Auto(InternPool.Nav.Index, Mir), |
| 20 | objects: std.ArrayList(Object), |
| 21 | exports: std.array_hash_map.Auto(InternPool.Nav.Index, InternPool.NullTerminatedString), |
| 22 | |
| 23 | pub const Object = struct { path: Path, mir: Mir }; |
| 24 | |
| 25 | pub fn createEmpty( |
| 26 | arena: Allocator, |
| 27 | comp: *Compilation, |
| 28 | emit: Path, |
| 29 | options: link.File.OpenOptions, |
| 30 | ) !*Linker { |
| 31 | const file = try emit.root_dir.handle.createFile(comp.io, emit.sub_path, .{}); |
| 32 | errdefer file.close(comp.io); |
| 33 | const linker = try arena.create(Linker); |
| 34 | linker.* = .{ |
| 35 | .base = .{ |
| 36 | .tag = .spirv, |
| 37 | .comp = comp, |
| 38 | .emit = emit, |
| 39 | .gc_sections = options.gc_sections orelse false, |
| 40 | .print_gc_sections = options.print_gc_sections, |
| 41 | .stack_size = options.stack_size orelse 0, |
| 42 | .allow_shlib_undefined = options.allow_shlib_undefined orelse false, |
| 43 | .file = file, |
| 44 | .build_id = options.build_id, |
| 45 | }, |
| 46 | .mirs = .empty, |
| 47 | .objects = .empty, |
| 48 | .exports = .empty, |
| 49 | }; |
| 50 | return linker; |
| 51 | } |
| 52 | |
| 53 | pub fn open( |
| 54 | arena: Allocator, |
| 55 | comp: *Compilation, |
| 56 | emit: Path, |
| 57 | options: link.File.OpenOptions, |
| 58 | ) !*Linker { |
| 59 | return createEmpty(arena, comp, emit, options); |
| 60 | } |
| 61 | |
| 62 | pub fn deinit(linker: *Linker) void { |
| 63 | const gpa = linker.base.comp.gpa; |
| 64 | for (linker.mirs.values()) |*mir| mir.deinit(gpa); |
| 65 | linker.mirs.deinit(gpa); |
| 66 | for (linker.objects.items) |*object| object.mir.deinit(gpa); |
| 67 | linker.objects.deinit(gpa); |
| 68 | linker.exports.deinit(gpa); |
| 69 | } |
| 70 | |
| 71 | pub fn loadInput(linker: *Linker, input: link.Input) !void { |
| 72 | const diags = &linker.base.comp.link_diags; |
| 73 | switch (input) { |
| 74 | .object => |obj| try linker.loadObject(obj), |
| 75 | else => return diags.fail("unsupported link input for SPIR-V target", .{}), |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | fn loadObject(linker: *Linker, obj: link.Input.Object) !void { |
| 80 | const comp = linker.base.comp; |
| 81 | const gpa = comp.gpa; |
| 82 | const io = comp.io; |
| 83 | const diags = &comp.link_diags; |
| 84 | |
| 85 | const file_size = obj.file.length(io) catch |err| { |
| 86 | return diags.failParse(obj.path, "failed to get file size: {t}", .{err}); |
| 87 | }; |
| 88 | if (file_size % @sizeOf(Word) != 0) { |
| 89 | return diags.failParse(obj.path, "file size is not a multiple of {d}", .{ |
| 90 | @sizeOf(Word), |
| 91 | }); |
| 92 | } |
| 93 | |
| 94 | const words = try gpa.alloc(Word, @intCast(file_size / @sizeOf(Word))); |
| 95 | defer gpa.free(words); |
| 96 | const bytes = std.mem.sliceAsBytes(words); |
| 97 | const n_read = obj.file.readPositionalAll(io, bytes, 0) catch |err| { |
| 98 | return diags.failParse(obj.path, "failed to read: {t}", .{err}); |
| 99 | }; |
| 100 | |
| 101 | if (n_read != bytes.len) { |
| 102 | return diags.failParse(obj.path, "incomplete read", .{}); |
| 103 | } |
| 104 | const header_len = @sizeOf(spec.Header) / @sizeOf(Word); |
| 105 | if (words.len < header_len) { |
| 106 | return diags.failParse(obj.path, "header too small", .{}); |
| 107 | } |
| 108 | const header = std.mem.bytesToValue(spec.Header, std.mem.sliceAsBytes(words[0..header_len])); |
| 109 | if (header.magic != spec.magic_number) { |
| 110 | return diags.failParse(obj.path, "invalid magic number", .{}); |
| 111 | } |
| 112 | if (header.id_bound == 0) { |
| 113 | return diags.failParse(obj.path, "invalid id bound", .{}); |
| 114 | } |
| 115 | |
| 116 | const instructions = words[header_len..]; |
| 117 | var module = Section.fromWords(gpa, instructions, header.id_bound) catch |err| switch (err) { |
| 118 | error.OutOfMemory => |e| return e, |
| 119 | else => return diags.failParse(obj.path, "failed to parse instructions: {t}", .{err}), |
| 120 | }; |
| 121 | errdefer module.deinit(gpa); |
| 122 | try linker.objects.append(gpa, .{ .path = obj.path, .mir = .{ |
| 123 | .id_bound = header.id_bound, |
| 124 | .module = module, |
| 125 | .nav_refs = &.{}, |
| 126 | .externs = &.{}, |
| 127 | .entry_points = &.{}, |
| 128 | } }); |
| 129 | } |
| 130 | |
| 131 | pub fn updateFunc( |
| 132 | linker: *Linker, |
| 133 | pt: Zcu.PerThread, |
| 134 | func_index: InternPool.Index, |
| 135 | mir: *codegen.AnyMir, |
| 136 | ) !void { |
| 137 | const gpa = linker.base.comp.gpa; |
| 138 | const nav_index = pt.zcu.funcInfo(func_index).owner_nav; |
| 139 | const gop = try linker.mirs.getOrPut(gpa, nav_index); |
| 140 | if (gop.found_existing) gop.value_ptr.deinit(gpa); |
| 141 | gop.value_ptr.* = mir.spirv; |
| 142 | mir.spirv = .{ |
| 143 | .id_bound = 1, |
| 144 | .module = .empty, |
| 145 | .nav_refs = &.{}, |
| 146 | .externs = &.{}, |
| 147 | .entry_points = &.{}, |
| 148 | }; |
| 149 | } |
| 150 | |
| 151 | pub fn updateNav( |
| 152 | linker: *Linker, |
| 153 | pt: Zcu.PerThread, |
| 154 | nav_index: InternPool.Nav.Index, |
| 155 | ) link.Error!void { |
| 156 | const gpa = linker.base.comp.gpa; |
| 157 | const ip = &pt.zcu.intern_pool; |
| 158 | const resolved = ip.getNav(nav_index).resolved.?; |
| 159 | switch (ip.indexToKey(resolved.value)) { |
| 160 | .func => return, |
| 161 | .@"extern" => |@"extern"| if (ip.isFunctionType(@"extern".ty)) return, |
| 162 | else => if (!resolved.@"const") assert(resolved.@"threadlocal") else return, |
| 163 | } |
| 164 | var mir = CodeGen.generateNav(pt, nav_index) catch |err| switch (err) { |
| 165 | error.AlreadyReported => return, |
| 166 | else => |e| return e, |
| 167 | }; |
| 168 | errdefer mir.deinit(gpa); |
| 169 | const gop = try linker.mirs.getOrPut(gpa, nav_index); |
| 170 | if (gop.found_existing) gop.value_ptr.deinit(gpa); |
| 171 | gop.value_ptr.* = mir; |
| 172 | } |
| 173 | |
| 174 | pub fn updateExports( |
| 175 | linker: *Linker, |
| 176 | pt: Zcu.PerThread, |
| 177 | export_indices: []const Zcu.Export.Index, |
| 178 | ) link.Error!void { |
| 179 | const zcu = pt.zcu; |
| 180 | const ip = &zcu.intern_pool; |
| 181 | const gpa = linker.base.comp.gpa; |
| 182 | const diags = &linker.base.comp.link_diags; |
| 183 | |
| 184 | linker.exports.clearRetainingCapacity(); |
| 185 | try linker.exports.ensureUnusedCapacity(gpa, export_indices.len); |
| 186 | for (export_indices) |exp_index| { |
| 187 | const exp = exp_index.ptr(zcu); |
| 188 | const nav_index = switch (exp.exported) { |
| 189 | .nav => |nav| nav, |
| 190 | .uav => return diags.fail("cannot export a uav constant for SPIR-V target", .{}), |
| 191 | }; |
| 192 | const nav = ip.getNav(nav_index); |
| 193 | const resolved = nav.resolved.?; |
| 194 | if (!ip.isFunctionType(resolved.type) and !resolved.@"threadlocal") { |
| 195 | return diags.fail("cannot export constant '{f}' for SPIR-V target", .{ |
| 196 | nav.fqn.fmt(ip), |
| 197 | }); |
| 198 | } |
| 199 | linker.exports.putAssumeCapacity(nav_index, exp.opts.name); |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | pub fn flush( |
| 204 | linker: *Linker, |
| 205 | arena: Allocator, |
| 206 | tid: Zcu.PerThread.Id, |
| 207 | prog_node: std.Progress.Node, |
| 208 | ) link.Error!void { |
| 209 | _ = tid; |
| 210 | const comp = linker.base.comp; |
| 211 | const gpa = comp.gpa; |
| 212 | const diags = &comp.link_diags; |
| 213 | |
| 214 | const sub_node = prog_node.start("Flush Module", 1); |
| 215 | defer sub_node.end(); |
| 216 | |
| 217 | var f: Flush = .{ |
| 218 | .linker = linker, |
| 219 | .arena = arena, |
| 220 | .id_bound = 1, |
| 221 | .nonsemantic_info = false, |
| 222 | .navs = .empty, |
| 223 | .symbols = .empty, |
| 224 | .interned = .empty, |
| 225 | .variables = .empty, |
| 226 | .fn_refs = .empty, |
| 227 | .entry_points = .empty, |
| 228 | .deps = .empty, |
| 229 | .scratch = .empty, |
| 230 | .extended_instruction_sets = .empty, |
| 231 | .object_entry_points = .empty, |
| 232 | .object_execution_modes = .empty, |
| 233 | .debug_strings = .empty, |
| 234 | .names = .empty, |
| 235 | .decorations = .empty, |
| 236 | .globals = .empty, |
| 237 | .declarations = .empty, |
| 238 | .functions = .empty, |
| 239 | }; |
| 240 | var out: Section = .empty; |
| 241 | defer out.deinit(gpa); |
| 242 | |
| 243 | try f.emitModule(&out); |
| 244 | sub_node.completeOne(); |
| 245 | |
| 246 | var writer = linker.base.file.?.writer(comp.io, &.{}); |
| 247 | writer.interface.writeSliceEndian(Word, out.instructions.items, .little) catch { |
| 248 | return diags.fail("failed to write {f}: {t}", .{ linker.base.emit, writer.err.? }); |
| 249 | }; |
| 250 | writer.end() catch |err| { |
| 251 | return diags.fail("failed to write {f}: {t}", .{ linker.base.emit, err }); |
| 252 | }; |
| 253 | } |