Commit 30e254fc31

Alex Rønne Petersen <alex@alexrp.com>
2025-04-27 03:47:58
link: Stub out GOFF/XCOFF linker code based on LLVM.
This allows emitting object files for s390x-zos (GOFF) and powerpc(64)-aix (XCOFF). Note that GOFF emission in LLVM is still being worked on upstream for LLVM 21; the resulting object files are useless right now. Also, -fstrip is required, or LLVM will SIGSEGV during DWARF emission.
1 parent 5668c8b
src/link/Goff.zig
@@ -0,0 +1,120 @@
+//! Stub linker support for GOFF based on LLVM.
+
+const Goff = @This();
+
+const std = @import("std");
+const builtin = @import("builtin");
+
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
+const log = std.log.scoped(.link);
+const Path = std.Build.Cache.Path;
+
+const Zcu = @import("../Zcu.zig");
+const InternPool = @import("../InternPool.zig");
+const Compilation = @import("../Compilation.zig");
+const link = @import("../link.zig");
+const trace = @import("../tracy.zig").trace;
+const build_options = @import("build_options");
+const Air = @import("../Air.zig");
+const Liveness = @import("../Liveness.zig");
+const LlvmObject = @import("../codegen/llvm.zig").Object;
+
+base: link.File,
+llvm_object: LlvmObject.Ptr,
+
+pub fn createEmpty(
+    arena: Allocator,
+    comp: *Compilation,
+    emit: Path,
+    options: link.File.OpenOptions,
+) !*Goff {
+    const target = comp.root_mod.resolved_target.result;
+    const use_lld = build_options.have_llvm and comp.config.use_lld;
+    const use_llvm = comp.config.use_llvm;
+
+    assert(use_llvm); // Caught by Compilation.Config.resolve.
+    assert(!use_lld); // Caught by Compilation.Config.resolve.
+    assert(target.os.tag == .zos); // Caught by Compilation.Config.resolve.
+
+    const llvm_object = try LlvmObject.create(arena, comp);
+    const goff = try arena.create(Goff);
+    goff.* = .{
+        .base = .{
+            .tag = .goff,
+            .comp = comp,
+            .emit = emit,
+            .zcu_object_sub_path = emit.sub_path,
+            .gc_sections = options.gc_sections orelse false,
+            .print_gc_sections = options.print_gc_sections,
+            .stack_size = options.stack_size orelse 0,
+            .allow_shlib_undefined = options.allow_shlib_undefined orelse false,
+            .file = null,
+            .disable_lld_caching = options.disable_lld_caching,
+            .build_id = options.build_id,
+        },
+        .llvm_object = llvm_object,
+    };
+
+    return goff;
+}
+
+pub fn open(
+    arena: Allocator,
+    comp: *Compilation,
+    emit: Path,
+    options: link.File.OpenOptions,
+) !*Goff {
+    const target = comp.root_mod.resolved_target.result;
+    assert(target.ofmt == .goff);
+    return createEmpty(arena, comp, emit, options);
+}
+
+pub fn deinit(self: *Goff) void {
+    self.llvm_object.deinit();
+}
+
+pub fn updateFunc(
+    self: *Goff,
+    pt: Zcu.PerThread,
+    func_index: InternPool.Index,
+    air: Air,
+    liveness: Liveness,
+) link.File.UpdateNavError!void {
+    if (build_options.skip_non_native and builtin.object_format != .goff)
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+
+    try self.llvm_object.updateFunc(pt, func_index, air, liveness);
+}
+
+pub fn updateNav(self: *Goff, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
+    if (build_options.skip_non_native and builtin.object_format != .goff)
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+
+    return self.llvm_object.updateNav(pt, nav);
+}
+
+pub fn updateExports(
+    self: *Goff,
+    pt: Zcu.PerThread,
+    exported: Zcu.Exported,
+    export_indices: []const Zcu.Export.Index,
+) !void {
+    if (build_options.skip_non_native and builtin.object_format != .goff)
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+
+    return self.llvm_object.updateExports(pt, exported, export_indices);
+}
+
+pub fn flush(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
+    return self.flushModule(arena, tid, prog_node);
+}
+
+pub fn flushModule(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
+    if (build_options.skip_non_native and builtin.object_format != .goff)
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+
+    _ = tid;
+
+    try self.base.emitLlvmObject(arena, self.llvm_object, prog_node);
+}
src/link/Xcoff.zig
@@ -0,0 +1,120 @@
+//! Stub linker support for GOFF based on LLVM.
+
+const Xcoff = @This();
+
+const std = @import("std");
+const builtin = @import("builtin");
+
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
+const log = std.log.scoped(.link);
+const Path = std.Build.Cache.Path;
+
+const Zcu = @import("../Zcu.zig");
+const InternPool = @import("../InternPool.zig");
+const Compilation = @import("../Compilation.zig");
+const link = @import("../link.zig");
+const trace = @import("../tracy.zig").trace;
+const build_options = @import("build_options");
+const Air = @import("../Air.zig");
+const Liveness = @import("../Liveness.zig");
+const LlvmObject = @import("../codegen/llvm.zig").Object;
+
+base: link.File,
+llvm_object: LlvmObject.Ptr,
+
+pub fn createEmpty(
+    arena: Allocator,
+    comp: *Compilation,
+    emit: Path,
+    options: link.File.OpenOptions,
+) !*Xcoff {
+    const target = comp.root_mod.resolved_target.result;
+    const use_lld = build_options.have_llvm and comp.config.use_lld;
+    const use_llvm = comp.config.use_llvm;
+
+    assert(use_llvm); // Caught by Compilation.Config.resolve.
+    assert(!use_lld); // Caught by Compilation.Config.resolve.
+    assert(target.os.tag == .aix); // Caught by Compilation.Config.resolve.
+
+    const llvm_object = try LlvmObject.create(arena, comp);
+    const xcoff = try arena.create(Xcoff);
+    xcoff.* = .{
+        .base = .{
+            .tag = .xcoff,
+            .comp = comp,
+            .emit = emit,
+            .zcu_object_sub_path = emit.sub_path,
+            .gc_sections = options.gc_sections orelse false,
+            .print_gc_sections = options.print_gc_sections,
+            .stack_size = options.stack_size orelse 0,
+            .allow_shlib_undefined = options.allow_shlib_undefined orelse false,
+            .file = null,
+            .disable_lld_caching = options.disable_lld_caching,
+            .build_id = options.build_id,
+        },
+        .llvm_object = llvm_object,
+    };
+
+    return xcoff;
+}
+
+pub fn open(
+    arena: Allocator,
+    comp: *Compilation,
+    emit: Path,
+    options: link.File.OpenOptions,
+) !*Xcoff {
+    const target = comp.root_mod.resolved_target.result;
+    assert(target.ofmt == .xcoff);
+    return createEmpty(arena, comp, emit, options);
+}
+
+pub fn deinit(self: *Xcoff) void {
+    self.llvm_object.deinit();
+}
+
+pub fn updateFunc(
+    self: *Xcoff,
+    pt: Zcu.PerThread,
+    func_index: InternPool.Index,
+    air: Air,
+    liveness: Liveness,
+) link.File.UpdateNavError!void {
+    if (build_options.skip_non_native and builtin.object_format != .xcoff)
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+
+    try self.llvm_object.updateFunc(pt, func_index, air, liveness);
+}
+
+pub fn updateNav(self: *Xcoff, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
+    if (build_options.skip_non_native and builtin.object_format != .xcoff)
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+
+    return self.llvm_object.updateNav(pt, nav);
+}
+
+pub fn updateExports(
+    self: *Xcoff,
+    pt: Zcu.PerThread,
+    exported: Zcu.Exported,
+    export_indices: []const Zcu.Export.Index,
+) !void {
+    if (build_options.skip_non_native and builtin.object_format != .xcoff)
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+
+    return self.llvm_object.updateExports(pt, exported, export_indices);
+}
+
+pub fn flush(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
+    return self.flushModule(arena, tid, prog_node);
+}
+
+pub fn flushModule(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
+    if (build_options.skip_non_native and builtin.object_format != .xcoff)
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+
+    _ = tid;
+
+    try self.base.emitLlvmObject(arena, self.llvm_object, prog_node);
+}
src/dev.zig
@@ -82,6 +82,8 @@ pub const Env = enum {
                 .spirv_linker,
                 .plan9_linker,
                 .nvptx_linker,
+                .goff_linker,
+                .xcoff_linker,
                 => true,
                 .cc_command,
                 .translate_c_command,
@@ -228,6 +230,8 @@ pub const Feature = enum {
     spirv_linker,
     plan9_linker,
     nvptx_linker,
+    goff_linker,
+    xcoff_linker,
 };
 
 /// Makes the code following the call to this function unreachable if `feature` is disabled.
src/link.zig
@@ -556,9 +556,9 @@ pub const File = struct {
         const comp = base.comp;
         const gpa = comp.gpa;
         switch (base.tag) {
-            .coff, .elf, .macho, .plan9, .wasm => {
+            .coff, .elf, .macho, .plan9, .wasm, .goff, .xcoff => {
                 if (base.file != null) return;
-                dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker });
+                dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker });
                 const emit = base.emit;
                 if (base.child_pid) |pid| {
                     if (builtin.os.tag == .windows) {
@@ -650,8 +650,8 @@ pub const File = struct {
                     }
                 }
             },
-            .coff, .macho, .plan9, .wasm => if (base.file) |f| {
-                dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker });
+            .coff, .macho, .plan9, .wasm, .goff, .xcoff => if (base.file) |f| {
+                dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker });
                 if (base.zcu_object_sub_path != null) {
                     // The file we have open is not the final file that we want to
                     // make executable, so we don't have to close it.
@@ -767,6 +767,7 @@ pub const File = struct {
 
         switch (base.tag) {
             .spirv, .nvptx => {},
+            .goff, .xcoff => {},
             inline else => |tag| {
                 dev.check(tag.devFeature());
                 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateLineNumber(pt, ti_id);
@@ -902,6 +903,7 @@ pub const File = struct {
             .spirv => unreachable,
             .nvptx => unreachable,
             .wasm => unreachable,
+            .goff, .xcoff => unreachable,
             inline else => |tag| {
                 dev.check(tag.devFeature());
                 return @as(*tag.Type(), @fieldParentPtr("base", base)).getNavVAddr(pt, nav_index, reloc_info);
@@ -921,6 +923,7 @@ pub const File = struct {
             .spirv => unreachable,
             .nvptx => unreachable,
             .wasm => unreachable,
+            .goff, .xcoff => unreachable,
             inline else => |tag| {
                 dev.check(tag.devFeature());
                 return @as(*tag.Type(), @fieldParentPtr("base", base)).lowerUav(pt, decl_val, decl_align, src_loc);
@@ -934,6 +937,7 @@ pub const File = struct {
             .spirv => unreachable,
             .nvptx => unreachable,
             .wasm => unreachable,
+            .goff, .xcoff => unreachable,
             inline else => |tag| {
                 dev.check(tag.devFeature());
                 return @as(*tag.Type(), @fieldParentPtr("base", base)).getUavVAddr(decl_val, reloc_info);
@@ -950,6 +954,8 @@ pub const File = struct {
             .plan9,
             .spirv,
             .nvptx,
+            .goff,
+            .xcoff,
             => {},
 
             inline else => |tag| {
@@ -1246,6 +1252,8 @@ pub const File = struct {
         spirv,
         plan9,
         nvptx,
+        goff,
+        xcoff,
 
         pub fn Type(comptime tag: Tag) type {
             return switch (tag) {
@@ -1257,6 +1265,8 @@ pub const File = struct {
                 .spirv => SpirV,
                 .plan9 => Plan9,
                 .nvptx => NvPtx,
+                .goff => Goff,
+                .xcoff => Xcoff,
             };
         }
 
@@ -1270,8 +1280,8 @@ pub const File = struct {
                 .c => .c,
                 .spirv => .spirv,
                 .nvptx => .nvptx,
-                .goff => @panic("TODO implement goff object format"),
-                .xcoff => @panic("TODO implement xcoff object format"),
+                .goff => .goff,
+                .xcoff => .xcoff,
                 .hex => @panic("TODO implement hex object format"),
                 .raw => @panic("TODO implement raw object format"),
             };
@@ -1377,6 +1387,8 @@ pub const File = struct {
     pub const SpirV = @import("link/SpirV.zig");
     pub const Wasm = @import("link/Wasm.zig");
     pub const NvPtx = @import("link/NvPtx.zig");
+    pub const Goff = @import("link/Goff.zig");
+    pub const Xcoff = @import("link/Xcoff.zig");
     pub const Dwarf = @import("link/Dwarf.zig");
 };
 
CMakeLists.txt
@@ -612,6 +612,7 @@ set(ZIG_STAGE2_SOURCES
     src/link/Elf/relocatable.zig
     src/link/Elf/relocation.zig
     src/link/Elf/synthetic_sections.zig
+    src/link/Goff.zig
     src/link/LdScript.zig
     src/link/MachO.zig
     src/link/MachO/Archive.zig
@@ -652,6 +653,7 @@ set(ZIG_STAGE2_SOURCES
     src/link/Wasm/Archive.zig
     src/link/Wasm/Flush.zig
     src/link/Wasm/Object.zig
+    src/link/Xcoff.zig
     src/link/aarch64.zig
     src/link/riscv.zig
     src/link/table_section.zig