plan9 linker: make runnable binaries

We can now run binaries! (they segfault, but still run!)
This commit is contained in:
Jacob G-W 2021-06-01 22:48:20 -04:00 committed by Andrew Kelley
parent 34c21affa2
commit 798162e509
5 changed files with 333 additions and 37 deletions

View File

@ -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 = {} },

View File

@ -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 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 {
unreachable;
return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
}
} else unreachable;
switch (info.return_value) {
.register => |reg| {

View File

@ -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 => {},
}
}

View File

@ -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,
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) = .{},
/// 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,
cur_decl: *Module.Decl = undefined,
hdr: aout.ExecHdr = undefined,
pub const empty: SrcFn = .{
.off = 0,
.len = 0,
.prev = null,
.next = null,
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 allocateDeclIndexes(self: *Plan9, decl: *Module.Decl) !void {}
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;
// 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);
}

122
src/link/plan9/a.out.zig Normal file
View File

@ -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,
};
}