Commit 798162e509

Jacob G-W <jacoblevgw@gmail.com>
2021-06-02 04:48:20
plan9 linker: make runnable binaries
We can now run binaries! (they segfault, but still run!)
1 parent 34c21af
Changed files (5)
src/link/plan9/a.out.zig
@@ -0,0 +1,122 @@
+// Copyright © 2021 Plan 9 Foundation
+// Copyright © 20XX 9front authors
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// Idomatic translation of 9front a.out.h
+const std = @import("std");
+// all integers are in big-endian format (needs a byteswap)
+pub const ExecHdr = struct {
+    magic: u32,
+    text: u32,
+    data: u32,
+    bss: u32,
+    syms: u32,
+    entry: u32,
+    spsz: u32,
+    pcsz: u32,
+    pub fn size() u8 {
+        return 32;
+    }
+};
+
+// uchar value[4];
+// char  type;
+// char  name[n];   /* NUL-terminated */
+pub const Sym32 = struct {
+    value: u32, // big endian in the file
+    type: SymType,
+    name: [*:0]const u8,
+};
+// uchar value[8];
+// char  type;
+// char  name[n];   /* NUL-terminated */
+pub const Sym64 = struct {
+    value: u64, // big endian in the file
+    type: SymType,
+    name: [*:0]const u8,
+};
+// The type field is one of the following characters with the
+// high bit set:
+// T    text segment symbol
+// t    static text segment symbol
+// L    leaf function text segment symbol
+// l    static leaf function text segment symbol
+// D    data segment symbol
+// d    static data segment symbol
+// B    bss segment symbol
+// b    static bss segment symbol
+// a    automatic (local) variable symbol
+// p    function parameter symbol
+// f    source file name components
+// z    source file name
+// Z    source file line offset
+// m for '.frame'
+pub const SymType = enum(u8) {
+    T = 0x80 | 'T',
+    t = 0x80 | 't',
+    L = 0x80 | 'L',
+    l = 0x80 | 'l',
+    D = 0x80 | 'D',
+    d = 0x80 | 'd',
+    B = 0x80 | 'B',
+    b = 0x80 | 'b',
+    a = 0x80 | 'a',
+    p = 0x80 | 'p',
+    f = 0x80 | 'f',
+    z = 0x80 | 'z',
+    Z = 0x80 | 'Z',
+    m = 0x80 | 'm',
+};
+
+pub const HDR_MAGIC = @import("std").meta.promoteIntLiteral(c_int, 0x00008000, .hexadecimal);
+pub inline fn _MAGIC(f: anytype, b: anytype) @TypeOf(f | ((((@as(c_int, 4) * b) + @as(c_int, 0)) * b) + @as(c_int, 7))) {
+    return f | ((((@as(c_int, 4) * b) + @as(c_int, 0)) * b) + @as(c_int, 7));
+}
+pub const A_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 8)); // 68020
+pub const I_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 11)); // intel 386
+pub const J_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 12)); // intel 960 (retired)
+pub const K_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 13)); // sparc
+pub const V_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 16)); // mips 3000 BE
+pub const X_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 17)); // att dsp 3210 (retired)
+pub const M_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 18)); // mips 4000 BE
+pub const D_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 19)); // amd 29000 (retired)
+pub const E_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 20)); // arm
+pub const Q_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 21)); // powerpc
+pub const N_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 22)); // mips 4000 LE
+pub const L_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 23)); // dec alpha (retired)
+pub const P_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 24)); // mips 3000 LE
+pub const U_MAGIC = _MAGIC(@as(c_int, 0), @as(c_int, 25)); // sparc64
+pub const S_MAGIC = _MAGIC(HDR_MAGIC, @as(c_int, 26)); // amd64
+pub const T_MAGIC = _MAGIC(HDR_MAGIC, @as(c_int, 27)); // powerpc64
+pub const R_MAGIC = _MAGIC(HDR_MAGIC, @as(c_int, 28)); // arm64
+
+pub fn magicFromArch(arch: std.Target.Cpu.Arch) !u32 {
+    return switch (arch) {
+        .i386 => I_MAGIC,
+        .sparc => K_MAGIC, // TODO should sparcv9 and sparcel go here?
+        .mips => V_MAGIC,
+        .arm => E_MAGIC,
+        .aarch64 => R_MAGIC,
+        .powerpc => Q_MAGIC,
+        .powerpc64 => T_MAGIC,
+        .x86_64 => S_MAGIC,
+        else => error.ArchNotSupportedByPlan9,
+    };
+}
src/link/Plan9.zig
@@ -4,35 +4,66 @@ const std = @import("std");
 const link = @import("../link.zig");
 const Module = @import("../Module.zig");
 const Compilation = @import("../Compilation.zig");
+const aout = @import("plan9/a.out.zig");
+const codegen = @import("../codegen.zig");
+const trace = @import("../tracy.zig").trace;
+const mem = std.mem;
 const File = link.File;
 const Allocator = std.mem.Allocator;
 
 const log = std.log.scoped(.link);
+const assert = std.debug.assert;
+
+// TODO use incremental compilation
 
 base: link.File,
+ptr_width: PtrWidth,
 error_flags: File.ErrorFlags = File.ErrorFlags{},
 
-pub const SrcFn = struct {
-    /// Offset from the beginning of the Debug Line Program header that contains this function.
-    off: u32,
-    /// Size of the line number program component belonging to this function, not
-    /// including padding.
-    len: u32,
-
-    /// Points to the previous and next neighbors, based on the offset from .debug_line.
-    /// This can be used to find, for example, the capacity of this `SrcFn`.
-    prev: ?*SrcFn,
-    next: ?*SrcFn,
-
-    pub const empty: SrcFn = .{
-        .off = 0,
-        .len = 0,
-        .prev = null,
-        .next = null,
+decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, void) = .{},
+/// is just casted down when 32 bit
+syms: std.ArrayListUnmanaged(aout.Sym64) = .{},
+call_relocs: std.ArrayListUnmanaged(CallReloc) = .{},
+text_buf: std.ArrayListUnmanaged(u8) = .{},
+data_buf: std.ArrayListUnmanaged(u8) = .{},
+
+cur_decl: *Module.Decl = undefined,
+hdr: aout.ExecHdr = undefined,
+
+fn headerSize(self: Plan9) u32 {
+    // fat header (currently unused)
+    const fat: u4 = if (self.ptr_width == .p64) 8 else 0;
+    return aout.ExecHdr.size() + fat;
+}
+pub const DeclBlock = struct {
+    type: enum { text, data },
+    // offset in the text or data sects
+    offset: u32,
+    pub const empty = DeclBlock{
+        .type = .text,
+        .offset = 0,
     };
 };
 
+// TODO change base addr based on target (right now it just works on amd64)
+const default_base_addr = 0x00200000;
+
+pub const CallReloc = struct {
+    caller: *Module.Decl,
+    callee: *Module.Decl,
+    offset_in_caller: usize,
+};
+
+pub const PtrWidth = enum { p32, p64 };
+
 pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Plan9 {
+    if (options.use_llvm)
+        return error.LLVMBackendDoesNotSupportPlan9;
+    const ptr_width: PtrWidth = switch (options.target.cpu.arch.ptrBitWidth()) {
+        0...32 => .p32,
+        33...64 => .p64,
+        else => return error.UnsupportedELFArchitecture,
+    };
     const self = try gpa.create(Plan9);
     self.* = .{
         .base = .{
@@ -41,25 +72,157 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Plan9 {
             .allocator = gpa,
             .file = null,
         },
+        .ptr_width = ptr_width,
     };
     return self;
 }
 
-pub fn updateDecl(self: *Plan9, module: *Module, decl: *Module.Decl) !void {}
+pub fn updateDecl(self: *Plan9, module: *Module, decl: *Module.Decl) !void {
+    _ = try self.decl_table.getOrPut(self.base.allocator, decl);
+}
+
+pub fn flush(self: *Plan9, comp: *Compilation) !void {
+    assert(!self.base.options.use_lld);
+
+    switch (self.base.options.effectiveOutputMode()) {
+        .Exe => {},
+        // plan9 object files are totally different
+        .Obj => return error.TODOImplementPlan9Objs,
+        .Lib => return error.TODOImplementWritingLibFiles,
+    }
+    return self.flushModule(comp);
+}
+pub fn flushModule(self: *Plan9, comp: *Compilation) !void {
+    const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
 
-pub fn allocateDeclIndexes(self: *Plan9, decl: *Module.Decl) !void {}
+    // generate the header
+    self.hdr.magic = try aout.magicFromArch(self.base.options.target.cpu.arch);
+    const file = self.base.file.?;
+    try file.seekTo(self.headerSize());
+
+    // temporary buffer
+    var code_buffer = std.ArrayList(u8).init(self.base.allocator);
+    defer code_buffer.deinit();
+    {
+        for (self.decl_table.keys()) |decl| {
+            if (!decl.has_tv) continue;
+            self.cur_decl = decl;
+            const is_fn = (decl.ty.zigTypeTag() == .Fn);
+            decl.link.plan9 = if (is_fn) .{
+                .offset = @intCast(u32, self.text_buf.items.len),
+                .type = .text,
+            } else .{
+                .offset = @intCast(u32, self.data_buf.items.len),
+                .type = .data,
+            };
+            const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
+                .ty = decl.ty,
+                .val = decl.val,
+            }, &code_buffer, .{ .none = {} });
+            const code = switch (res) {
+                .externally_managed => |x| x,
+                .appended => code_buffer.items,
+                .fail => |em| {
+                    decl.analysis = .codegen_failure;
+                    try module.failed_decls.put(module.gpa, decl, em);
+                    // TODO try to do more decls
+                    return;
+                },
+            };
+            if (is_fn)
+                try self.text_buf.appendSlice(self.base.allocator, code)
+            else
+                try self.data_buf.appendSlice(self.base.allocator, code);
+            code_buffer.items.len = 0;
+        }
+    }
+    try file.writeAll(self.text_buf.items);
+    try file.writeAll(self.data_buf.items);
+    try file.seekTo(0);
+    self.hdr.text = @intCast(u32, self.text_buf.items.len);
+    self.hdr.data = @intCast(u32, self.data_buf.items.len);
+    self.hdr.pcsz = 0;
+    self.hdr.spsz = 0;
+    inline for (std.meta.fields(aout.ExecHdr)) |f| {
+        try file.writer().writeIntBig(f.field_type, @field(self.hdr, f.name));
+    }
+}
+pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void {
+    assert(self.decl_table.swapRemove(decl));
+}
 
-pub fn flush(self: *Plan9, comp: *Compilation) !void {}
-pub fn flushModule(self: *Plan9, comp: *Compilation) !void {}
-pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void {}
 pub fn updateDeclExports(
     self: *Plan9,
     module: *Module,
     decl: *Module.Decl,
     exports: []const *Module.Export,
-) !void {}
-pub fn deinit(self: *Plan9) void {}
+) !void {
+    for (exports) |exp| {
+        if (exp.options.section) |section_name| {
+            if (!mem.eql(u8, section_name, ".text")) {
+                try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.count() + 1);
+                module.failed_exports.putAssumeCapacityNoClobber(
+                    exp,
+                    try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "plan9 does not support extra sections", .{}),
+                );
+                continue;
+            }
+        }
+        if (std.mem.eql(u8, exp.options.name, "_start")) {
+            std.debug.assert(decl.link.plan9.type == .text); // we tried to link a non-function as _start
+            self.hdr.entry = Plan9.default_base_addr + self.headerSize() + decl.link.plan9.offset;
+        }
+        if (exp.link.plan9) |i| {
+            const sym = &self.syms.items[i];
+            sym.* = .{
+                .value = decl.link.plan9.offset,
+                .type = switch (decl.link.plan9.type) {
+                    .text => .T,
+                    .data => .D,
+                },
+                .name = decl.name,
+            };
+        } else {
+            try self.syms.append(self.base.allocator, .{
+                .value = decl.link.plan9.offset,
+                .type = switch (decl.link.plan9.type) {
+                    .text => .T,
+                    .data => .D,
+                },
+                .name = decl.name,
+            });
+        }
+    }
+}
+pub fn deinit(self: *Plan9) void {
+    self.decl_table.deinit(self.base.allocator);
+    self.call_relocs.deinit(self.base.allocator);
+    self.syms.deinit(self.base.allocator);
+    self.text_buf.deinit(self.base.allocator);
+    self.data_buf.deinit(self.base.allocator);
+}
 
-pub const Export = struct {
-    sym_index: ?u32 = null,
-};
+pub const Export = ?usize;
+pub const base_tag = .plan9;
+pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Plan9 {
+    if (options.use_llvm)
+        return error.LLVMBackendDoesNotSupportPlan9;
+    assert(options.object_format == .plan9);
+    const file = try options.emit.?.directory.handle.createFile(sub_path, .{
+        .truncate = false,
+        .read = true,
+        .mode = link.determineMode(options),
+    });
+    errdefer file.close();
+
+    const self = try createEmpty(allocator, options);
+    errdefer self.base.destroy();
+
+    self.base.file = file;
+    return self;
+}
+
+pub fn addCallReloc(self: *Plan9, code: *std.ArrayList(u8), reloc: CallReloc) !void {
+    try self.call_relocs.append(self.base.allocator, reloc);
+    try code.writer().writeIntBig(u64, 0xdeadbeef);
+}
src/codegen.zig
@@ -2556,9 +2556,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 } else {
                     return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
                 }
-            } else {
-                unreachable;
-            }
+            } else if (self.bin_file.cast(link.File.Plan9)) |p9| {
+                if (inst.func.value()) |func_value| {
+                    if (func_value.castTag(.function)) |func_payload| {
+                        try p9.addCallReloc(self.code, .{
+                            .caller = p9.cur_decl,
+                            .callee = func_payload.data.owner_decl,
+                            .offset_in_caller = self.code.items.len,
+                        });
+                    } else return self.fail(inst.base.src, "TODO implement calling extern fn on plan9", .{});
+                } else {
+                    return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
+                }
+            } else unreachable;
 
             switch (info.return_value) {
                 .register => |reg| {
src/link.zig
@@ -141,6 +141,7 @@ pub const File = struct {
         elf: Elf.TextBlock,
         coff: Coff.TextBlock,
         macho: MachO.TextBlock,
+        plan9: Plan9.DeclBlock,
         c: C.DeclBlock,
         wasm: Wasm.DeclBlock,
         spirv: void,
@@ -150,7 +151,7 @@ pub const File = struct {
         elf: Elf.SrcFn,
         coff: Coff.SrcFn,
         macho: MachO.SrcFn,
-        plan9: Plan9.SrcFn,
+        plan9: void,
         c: C.FnBlock,
         wasm: Wasm.FnData,
         spirv: SpirV.FnData,
@@ -207,12 +208,12 @@ pub const File = struct {
                     .coff, .pe => &(try Coff.createEmpty(allocator, options)).base,
                     .elf => &(try Elf.createEmpty(allocator, options)).base,
                     .macho => &(try MachO.createEmpty(allocator, options)).base,
+                    .plan9 => &(try Plan9.createEmpty(allocator, options)).base,
                     .wasm => &(try Wasm.createEmpty(allocator, options)).base,
                     .c => unreachable, // Reported error earlier.
                     .spirv => &(try SpirV.createEmpty(allocator, options)).base,
                     .hex => return error.HexObjectFormatUnimplemented,
                     .raw => return error.RawObjectFormatUnimplemented,
-                    .plan9 => return error.Plan9ObjectFormatUnimplemented,
                 };
             }
             // Open a temporary object file, not the final output file because we want to link with LLD.
@@ -224,12 +225,12 @@ pub const File = struct {
             .coff, .pe => &(try Coff.openPath(allocator, sub_path, options)).base,
             .elf => &(try Elf.openPath(allocator, sub_path, options)).base,
             .macho => &(try MachO.openPath(allocator, sub_path, options)).base,
+            .plan9 => &(try Plan9.openPath(allocator, sub_path, options)).base,
             .wasm => &(try Wasm.openPath(allocator, sub_path, options)).base,
             .c => &(try C.openPath(allocator, sub_path, options)).base,
             .spirv => &(try SpirV.openPath(allocator, sub_path, options)).base,
             .hex => return error.HexObjectFormatUnimplemented,
             .raw => return error.RawObjectFormatUnimplemented,
-            .plan9 => return error.Plan9ObjectFormatUnimplemented,
         };
 
         if (use_lld) {
@@ -347,7 +348,7 @@ pub const File = struct {
             .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
             .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
             .wasm => return @fieldParentPtr(Wasm, "base", base).allocateDeclIndexes(decl),
-            .plan9 => return @fieldParentPtr(Plan9, "base", base).allocateDeclIndexes(decl),
+            .plan9 => {},
             .spirv => {},
         }
     }
src/Module.zig
@@ -3517,7 +3517,7 @@ pub fn clearDecl(
                 .coff => .{ .coff = link.File.Coff.TextBlock.empty },
                 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
                 .macho => .{ .macho = link.File.MachO.TextBlock.empty },
-                .plan9 => @panic("plan9 link"),
+                .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty },
                 .c => .{ .c = link.File.C.DeclBlock.empty },
                 .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
                 .spirv => .{ .spirv = {} },
@@ -3526,7 +3526,7 @@ pub fn clearDecl(
                 .coff => .{ .coff = {} },
                 .elf => .{ .elf = link.File.Elf.SrcFn.empty },
                 .macho => .{ .macho = link.File.MachO.SrcFn.empty },
-                .plan9 => @panic("plan9 fn_link"),
+                .plan9 => .{ .plan9 = {} },
                 .c => .{ .c = link.File.C.FnBlock.empty },
                 .wasm => .{ .wasm = link.File.Wasm.FnData.empty },
                 .spirv => .{ .spirv = .{} },
@@ -3694,7 +3694,7 @@ fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node
             .coff => .{ .coff = link.File.Coff.TextBlock.empty },
             .elf => .{ .elf = link.File.Elf.TextBlock.empty },
             .macho => .{ .macho = link.File.MachO.TextBlock.empty },
-            .plan9 => @panic("PLan9 export"),
+            .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty },
             .c => .{ .c = link.File.C.DeclBlock.empty },
             .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
             .spirv => .{ .spirv = {} },
@@ -3703,7 +3703,7 @@ fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node
             .coff => .{ .coff = {} },
             .elf => .{ .elf = link.File.Elf.SrcFn.empty },
             .macho => .{ .macho = link.File.MachO.SrcFn.empty },
-            .plan9 => .{ .plan9 = link.File.Plan9.SrcFn.empty },
+            .plan9 => .{ .plan9 = {} },
             .c => .{ .c = link.File.C.FnBlock.empty },
             .wasm => .{ .wasm = link.File.Wasm.FnData.empty },
             .spirv => .{ .spirv = .{} },
@@ -3773,7 +3773,7 @@ pub fn analyzeExport(
             .coff => .{ .coff = {} },
             .elf => .{ .elf = link.File.Elf.Export{} },
             .macho => .{ .macho = link.File.MachO.Export{} },
-            .plan9 => @panic("plan9 link"),
+            .plan9 => .{ .plan9 = null },
             .c => .{ .c = {} },
             .wasm => .{ .wasm = {} },
             .spirv => .{ .spirv = {} },