master
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const Path = std.Build.Cache.Path;
4const assert = std.debug.assert;
5const log = std.log.scoped(.link);
6
7const Zcu = @import("../Zcu.zig");
8const InternPool = @import("../InternPool.zig");
9const Compilation = @import("../Compilation.zig");
10const link = @import("../link.zig");
11const Air = @import("../Air.zig");
12const Type = @import("../Type.zig");
13const CodeGen = @import("../codegen/spirv/CodeGen.zig");
14const Module = @import("../codegen/spirv/Module.zig");
15const trace = @import("../tracy.zig").trace;
16const BinaryModule = @import("SpirV/BinaryModule.zig");
17const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
18
19const spec = @import("../codegen/spirv/spec.zig");
20const Id = spec.Id;
21const Word = spec.Word;
22
23const Linker = @This();
24
25base: link.File,
26module: Module,
27cg: CodeGen,
28
29pub fn createEmpty(
30 arena: Allocator,
31 comp: *Compilation,
32 emit: Path,
33 options: link.File.OpenOptions,
34) !*Linker {
35 const gpa = comp.gpa;
36 const target = &comp.root_mod.resolved_target.result;
37
38 assert(!comp.config.use_lld); // Caught by Compilation.Config.resolve
39 assert(!comp.config.use_llvm); // Caught by Compilation.Config.resolve
40 assert(target.ofmt == .spirv); // Caught by Compilation.Config.resolve
41 switch (target.cpu.arch) {
42 .spirv32, .spirv64 => {},
43 else => unreachable, // Caught by Compilation.Config.resolve.
44 }
45 switch (target.os.tag) {
46 .opencl, .opengl, .vulkan => {},
47 else => unreachable, // Caught by Compilation.Config.resolve.
48 }
49
50 const linker = try arena.create(Linker);
51 linker.* = .{
52 .base = .{
53 .tag = .spirv,
54 .comp = comp,
55 .emit = emit,
56 .gc_sections = options.gc_sections orelse false,
57 .print_gc_sections = options.print_gc_sections,
58 .stack_size = options.stack_size orelse 0,
59 .allow_shlib_undefined = options.allow_shlib_undefined orelse false,
60 .file = null,
61 .build_id = options.build_id,
62 },
63 .module = .{
64 .gpa = gpa,
65 .arena = arena,
66 .zcu = comp.zcu.?,
67 },
68 .cg = .{
69 // These fields are populated in generate()
70 .pt = undefined,
71 .air = undefined,
72 .liveness = undefined,
73 .owner_nav = undefined,
74 .module = undefined,
75 .control_flow = .{ .structured = .{} },
76 .base_line = undefined,
77 },
78 };
79 errdefer linker.deinit();
80
81 linker.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
82 .truncate = true,
83 .read = true,
84 });
85
86 return linker;
87}
88
89pub fn open(
90 arena: Allocator,
91 comp: *Compilation,
92 emit: Path,
93 options: link.File.OpenOptions,
94) !*Linker {
95 return createEmpty(arena, comp, emit, options);
96}
97
98pub fn deinit(linker: *Linker) void {
99 linker.cg.deinit();
100 linker.module.deinit();
101}
102
103fn generate(
104 linker: *Linker,
105 pt: Zcu.PerThread,
106 nav_index: InternPool.Nav.Index,
107 air: Air,
108 liveness: Air.Liveness,
109 do_codegen: bool,
110) !void {
111 const zcu = pt.zcu;
112 const gpa = zcu.gpa;
113 const structured_cfg = zcu.navFileScope(nav_index).mod.?.structured_cfg;
114
115 linker.cg.control_flow.deinit(gpa);
116 linker.cg.args.clearRetainingCapacity();
117 linker.cg.inst_results.clearRetainingCapacity();
118 linker.cg.id_scratch.clearRetainingCapacity();
119 linker.cg.prologue.reset();
120 linker.cg.body.reset();
121
122 linker.cg = .{
123 .pt = pt,
124 .air = air,
125 .liveness = liveness,
126 .owner_nav = nav_index,
127 .module = &linker.module,
128 .control_flow = switch (structured_cfg) {
129 true => .{ .structured = .{} },
130 false => .{ .unstructured = .{} },
131 },
132 .base_line = zcu.navSrcLine(nav_index),
133
134 .args = linker.cg.args,
135 .inst_results = linker.cg.inst_results,
136 .id_scratch = linker.cg.id_scratch,
137 .prologue = linker.cg.prologue,
138 .body = linker.cg.body,
139 };
140
141 linker.cg.genNav(do_codegen) catch |err| switch (err) {
142 error.CodegenFail => switch (zcu.codegenFailMsg(nav_index, linker.cg.error_msg.?)) {
143 error.CodegenFail => {},
144 error.OutOfMemory => |e| return e,
145 },
146 else => |other| {
147 // There might be an error that happened *after* linker.error_msg
148 // was already allocated, so be sure to free it.
149 if (linker.cg.error_msg) |error_msg| {
150 error_msg.deinit(gpa);
151 }
152
153 return other;
154 },
155 };
156}
157
158pub fn updateFunc(
159 linker: *Linker,
160 pt: Zcu.PerThread,
161 func_index: InternPool.Index,
162 air: *const Air,
163 liveness: *const ?Air.Liveness,
164) !void {
165 const nav = pt.zcu.funcInfo(func_index).owner_nav;
166 // TODO: Separate types for generating decls and functions?
167 try linker.generate(pt, nav, air.*, liveness.*.?, true);
168}
169
170pub fn updateNav(linker: *Linker, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
171 const ip = &pt.zcu.intern_pool;
172 log.debug("lowering nav {f}({d})", .{ ip.getNav(nav).fqn.fmt(ip), nav });
173 try linker.generate(pt, nav, undefined, undefined, false);
174}
175
176pub fn updateExports(
177 linker: *Linker,
178 pt: Zcu.PerThread,
179 exported: Zcu.Exported,
180 export_indices: []const Zcu.Export.Index,
181) !void {
182 const zcu = pt.zcu;
183 const ip = &zcu.intern_pool;
184 const nav_index = switch (exported) {
185 .nav => |nav| nav,
186 .uav => |uav| {
187 _ = uav;
188 @panic("TODO: implement Linker linker code for exporting a constant value");
189 },
190 };
191 const nav_ty = ip.getNav(nav_index).typeOf(ip);
192 const target = zcu.getTarget();
193 if (ip.isFunctionType(nav_ty)) {
194 const spv_decl_index = try linker.module.resolveNav(ip, nav_index);
195 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
196 const exec_model: spec.ExecutionModel = switch (target.os.tag) {
197 .vulkan, .opengl => switch (cc) {
198 .spirv_vertex => .vertex,
199 .spirv_fragment => .fragment,
200 .spirv_kernel => .gl_compute,
201 // TODO: We should integrate with the Linkage capability and export this function
202 .spirv_device => return,
203 else => unreachable,
204 },
205 .opencl => switch (cc) {
206 .spirv_kernel => .kernel,
207 // TODO: We should integrate with the Linkage capability and export this function
208 .spirv_device => return,
209 else => unreachable,
210 },
211 else => unreachable,
212 };
213
214 for (export_indices) |export_idx| {
215 const exp = export_idx.ptr(zcu);
216 try linker.module.declareEntryPoint(
217 spv_decl_index,
218 exp.opts.name.toSlice(ip),
219 exec_model,
220 null,
221 );
222 }
223 }
224
225 // TODO: Export regular functions, variables, etc using Linkage attributes.
226}
227
228pub fn flush(
229 linker: *Linker,
230 arena: Allocator,
231 tid: Zcu.PerThread.Id,
232 prog_node: std.Progress.Node,
233) link.File.FlushError!void {
234 // The goal is to never use this because it's only needed if we need to
235 // write to InternPool, but flush is too late to be writing to the
236 // InternPool.
237 _ = tid;
238
239 const tracy = trace(@src());
240 defer tracy.end();
241
242 const sub_prog_node = prog_node.start("Flush Module", 0);
243 defer sub_prog_node.end();
244
245 const comp = linker.base.comp;
246 const diags = &comp.link_diags;
247 const gpa = comp.gpa;
248
249 // We need to export the list of error names somewhere so that we can pretty-print them in the
250 // executor. This is not really an important thing though, so we can just dump it in any old
251 // nonsemantic instruction. For now, just put it in OpSourceExtension with a special name.
252 var error_info: std.Io.Writer.Allocating = .init(linker.module.gpa);
253 defer error_info.deinit();
254
255 error_info.writer.writeAll("zig_errors:") catch return error.OutOfMemory;
256 const ip = &linker.base.comp.zcu.?.intern_pool;
257 for (ip.global_error_set.getNamesFromMainThread()) |name| {
258 // Errors can contain pretty much any character - to encode them in a string we must escape
259 // them somehow. Easiest here is to use some established scheme, one which also preseves the
260 // name if it contains no strange characters is nice for debugging. URI encoding fits the bill.
261 // We're using : as separator, which is a reserved character.
262 error_info.writer.writeByte(':') catch return error.OutOfMemory;
263 std.Uri.Component.percentEncode(
264 &error_info.writer,
265 name.toSlice(ip),
266 struct {
267 fn isValidChar(c: u8) bool {
268 return switch (c) {
269 0, '%', ':' => false,
270 else => true,
271 };
272 }
273 }.isValidChar,
274 ) catch return error.OutOfMemory;
275 }
276 try linker.module.sections.debug_strings.emit(gpa, .OpSourceExtension, .{
277 .extension = error_info.written(),
278 });
279
280 const module = try linker.module.finalize(arena);
281 errdefer arena.free(module);
282
283 const linked_module = linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
284 error.OutOfMemory => return error.OutOfMemory,
285 else => |other| return diags.fail("error while linking: {s}", .{@errorName(other)}),
286 };
287
288 // TODO endianness bug. use file writer and call writeSliceEndian instead
289 linker.base.file.?.writeAll(@ptrCast(linked_module)) catch |err|
290 return diags.fail("failed to write: {s}", .{@errorName(err)});
291}
292
293fn linkModule(arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
294 var parser = try BinaryModule.Parser.init(arena);
295 defer parser.deinit();
296 var binary = try parser.parse(module);
297 try lower_invocation_globals.run(&parser, &binary, progress);
298 return binary.finalize(arena);
299}