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}