diff --git a/CMakeLists.txt b/CMakeLists.txt index f279990e27..44417e4159 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -573,6 +573,8 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/C.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" + "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig" + "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/CodeSignature.zig" diff --git a/lib/std/start.zig b/lib/std/start.zig index 0a6c0320d6..dbbab433d0 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -95,30 +95,47 @@ fn _start2() callconv(.Naked) noreturn { } fn exit2(code: usize) noreturn { - switch (builtin.stage2_arch) { - .x86_64 => { - asm volatile ("syscall" - : - : [number] "{rax}" (231), - [arg1] "{rdi}" (code) - : "rcx", "r11", "memory" - ); + switch (builtin.stage2_os) { + .linux => switch (builtin.stage2_arch) { + .x86_64 => { + asm volatile ("syscall" + : + : [number] "{rax}" (231), + [arg1] "{rdi}" (code) + : "rcx", "r11", "memory" + ); + }, + .arm => { + asm volatile ("svc #0" + : + : [number] "{r7}" (1), + [arg1] "{r0}" (code) + : "memory" + ); + }, + .aarch64 => { + asm volatile ("svc #0" + : + : [number] "{x8}" (93), + [arg1] "{x0}" (code) + : "memory", "cc" + ); + }, + else => @compileError("TODO"), }, - .arm => { - asm volatile ("svc #0" - : - : [number] "{r7}" (1), - [arg1] "{r0}" (code) - : "memory" - ); - }, - .aarch64 => { - asm volatile ("svc #0" - : - : [number] "{x8}" (93), - [arg1] "{x0}" (code) - : "memory", "cc" - ); + // exits(0) + .plan9 => switch (builtin.stage2_arch) { + .x86_64 => { + asm volatile ( + \\push $0 + \\push $0 + \\syscall + : + : [syscall_number] "{rbp}" (8) + : "rcx", "r11", "memory" + ); + }, + else => @compileError("TODO"), }, else => @compileError("TODO"), } diff --git a/lib/std/target.zig b/lib/std/target.zig index 507f30c2e0..70626f5051 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -60,6 +60,7 @@ pub const Target = struct { opencl, glsl450, vulkan, + plan9, other, pub fn isDarwin(tag: Tag) bool { @@ -262,6 +263,7 @@ pub const Target = struct { .opencl, // TODO: OpenCL versions .glsl450, // TODO: GLSL versions .vulkan, + .plan9, .other, => return .{ .none = {} }, @@ -420,6 +422,7 @@ pub const Target = struct { .opencl, .glsl450, .vulkan, + .plan9, .other, => false, }; @@ -515,6 +518,7 @@ pub const Target = struct { .opencl, // TODO: SPIR-V ABIs with Linkage capability .glsl450, .vulkan, + .plan9, // TODO specify abi => return .none, } } @@ -554,6 +558,7 @@ pub const Target = struct { spirv, hex, raw, + plan9, }; pub const SubSystem = enum { @@ -1359,6 +1364,8 @@ pub const Target = struct { if (cpu_arch.isSPIRV()) { return .spirv; } + if (os_tag == .plan9) + return .plan9; return .elf; } @@ -1432,6 +1439,7 @@ pub const Target = struct { .opencl, .glsl450, .vulkan, + .plan9, .other, => return false, else => return true, @@ -1616,6 +1624,7 @@ pub const Target = struct { .glsl450, .vulkan, .other, + .plan9, => return result, // TODO revisit when multi-arch for Haiku is available diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 4adbbe0b6a..595dce77c2 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -181,6 +181,30 @@ pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) erro .spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}), .hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}), .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}), + .plan9 => { + // copied from 2c(1) + // 0c spim little-endian MIPS 3000 family + // 1c 68000 Motorola MC68000 + // 2c 68020 Motorola MC68020 + // 5c arm little-endian ARM + // 6c amd64 AMD64 and compatibles (e.g., Intel EM64T) + // 7c arm64 ARM64 (ARMv8) + // 8c 386 Intel i386, i486, Pentium, etc. + // kc sparc Sun SPARC + // qc power Power PC + // vc mips big-endian MIPS 3000 family + const char: u8 = switch (target.cpu.arch) { + .arm => '5', + .x86_64 => '6', + .aarch64 => '7', + .i386 => '8', + .sparc => 'k', + .powerpc, .powerpcle => 'q', + .mips, .mipsel => 'v', + else => 'X', // this arch does not have a char or maybe was not ported to plan9 so we just use X + }; + return std.fmt.allocPrint(allocator, "{s}.{c}", .{ root_name, char }); + }, } } diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig index 488707f113..e6ca0a2baa 100644 --- a/lib/std/zig/cross_target.zig +++ b/lib/std/zig/cross_target.zig @@ -133,6 +133,7 @@ pub const CrossTarget = struct { .opencl, .glsl450, .vulkan, + .plan9, .other, => { self.os_version_min = .{ .none = {} }; @@ -746,6 +747,7 @@ pub const CrossTarget = struct { .opencl, .glsl450, .vulkan, + .plan9, .other, => return error.InvalidOperatingSystemVersion, diff --git a/src/Compilation.zig b/src/Compilation.zig index 1cf4151c62..58425197c1 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3556,6 +3556,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) Alloc \\pub const zig_is_stage2 = {}; \\/// Temporary until self-hosted supports the `cpu.arch` value. \\pub const stage2_arch: std.Target.Cpu.Arch = .{}; + \\/// Temporary until self-hosted supports the `os.tag` value. + \\pub const stage2_os: std.Target.Os.Tag = .{}; \\ \\pub const output_mode = std.builtin.OutputMode.{}; \\pub const link_mode = std.builtin.LinkMode.{}; @@ -3571,6 +3573,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) Alloc build_options.version, !use_stage1, std.zig.fmtId(@tagName(target.cpu.arch)), + std.zig.fmtId(@tagName(target.os.tag)), std.zig.fmtId(@tagName(comp.bin_file.options.output_mode)), std.zig.fmtId(@tagName(comp.bin_file.options.link_mode)), comp.bin_file.options.is_test, diff --git a/src/Module.zig b/src/Module.zig index 8ae184a377..a1f6887fbd 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3437,6 +3437,9 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) InnerError!vo // in `Decl` to notice that the line number did not change. mod.comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl }); }, + .plan9 => { + // TODO implement for plan9 + }, .c, .wasm, .spirv => {}, } } @@ -3514,6 +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 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, .c => .{ .c = link.File.C.DeclBlock.empty }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, .spirv => .{ .spirv = {} }, @@ -3522,6 +3526,7 @@ pub fn clearDecl( .coff => .{ .coff = {} }, .elf => .{ .elf = link.File.Elf.SrcFn.empty }, .macho => .{ .macho = link.File.MachO.SrcFn.empty }, + .plan9 => .{ .plan9 = {} }, .c => .{ .c = link.File.C.FnBlock.empty }, .wasm => .{ .wasm = link.File.Wasm.FnData.empty }, .spirv => .{ .spirv = .{} }, @@ -3689,6 +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 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, .c => .{ .c = link.File.C.DeclBlock.empty }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, .spirv => .{ .spirv = {} }, @@ -3697,6 +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 = {} }, .c => .{ .c = link.File.C.FnBlock.empty }, .wasm => .{ .wasm = link.File.Wasm.FnData.empty }, .spirv => .{ .spirv = .{} }, @@ -3766,6 +3773,7 @@ pub fn analyzeExport( .coff => .{ .coff = {} }, .elf => .{ .elf = link.File.Elf.Export{} }, .macho => .{ .macho = link.File.MachO.Export{} }, + .plan9 => .{ .plan9 = null }, .c => .{ .c = {} }, .wasm => .{ .wasm = {} }, .spirv => .{ .spirv = {} }, diff --git a/src/codegen.zig b/src/codegen.zig index 6b9bd633d0..96a24195ee 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2556,9 +2556,60 @@ 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| { + switch (arch) { + .x86_64 => { + for (info.args) |mc_arg, arg_i| { + const arg = inst.args[arg_i]; + const arg_mcv = try self.resolveInst(inst.args[arg_i]); + // Here we do not use setRegOrMem even though the logic is similar, because + // the function call will move the stack pointer, so the offsets are different. + switch (mc_arg) { + .none => continue, + .register => |reg| { + try self.register_manager.getReg(reg, null); + try self.genSetReg(arg.src, arg.ty, reg, arg_mcv); + }, + .stack_offset => { + // Here we need to emit instructions like this: + // mov qword ptr [rsp + stack_offset], x + return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); + }, + .ptr_stack_offset => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{}); + }, + .ptr_embedded_in_code => { + return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); + }, + .undef => unreachable, + .immediate => unreachable, + .unreach => unreachable, + .dead => unreachable, + .embedded_in_code => unreachable, + .memory => unreachable, + .compare_flags_signed => unreachable, + .compare_flags_unsigned => unreachable, + } + } + if (inst.func.value()) |func_value| { + if (func_value.castTag(.function)) |func_payload| { + const ptr_bits = self.target.cpu.arch.ptrBitWidth(); + const ptr_bytes: u64 = @divExact(ptr_bits, 8); + const got_addr = p9.bases.data; + const got_index = func_payload.data.owner_decl.link.plan9.got_index.?; + // ff 14 25 xx xx xx xx call [addr] + try self.code.ensureCapacity(self.code.items.len + 7); + self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 }); + const fn_got_addr = got_addr + got_index * ptr_bytes; + mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), @intCast(u32, fn_got_addr)); + } 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 => return self.fail(inst.base.src, "TODO implement call on plan9 for {}", .{self.target.cpu.arch}), + } + } else unreachable; switch (info.return_value) { .register => |reg| { @@ -3279,10 +3330,44 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.genSetReg(inst.base.src, arg.ty, reg, arg_mcv); } - if (mem.eql(u8, inst.asm_source, "syscall")) { - try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 }); - } else if (inst.asm_source.len != 0) { - return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{}); + { + var iter = std.mem.tokenize(inst.asm_source, "\n\r"); + while (iter.next()) |ins| { + if (mem.eql(u8, ins, "syscall")) { + try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 }); + } else if (mem.indexOf(u8, ins, "push")) |_| { + const arg = ins[4..]; + if (mem.indexOf(u8, arg, "$")) |l| { + const n = std.fmt.parseInt(u8, ins[4 + l + 1 ..], 10) catch return self.fail(inst.base.src, "TODO implement more inline asm int parsing", .{}); + try self.code.appendSlice(&.{ 0x6a, n }); + } else if (mem.indexOf(u8, arg, "%%")) |l| { + const reg_name = ins[4 + l + 2 ..]; + const reg = parseRegName(reg_name) orelse + return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name}); + const low_id: u8 = reg.low_id(); + if (reg.isExtended()) { + try self.code.appendSlice(&.{ 0x41, 0b1010000 | low_id }); + } else { + try self.code.append(0b1010000 | low_id); + } + } else return self.fail(inst.base.src, "TODO more push operands", .{}); + } else if (mem.indexOf(u8, ins, "pop")) |_| { + const arg = ins[3..]; + if (mem.indexOf(u8, arg, "%%")) |l| { + const reg_name = ins[3 + l + 2 ..]; + const reg = parseRegName(reg_name) orelse + return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name}); + const low_id: u8 = reg.low_id(); + if (reg.isExtended()) { + try self.code.appendSlice(&.{ 0x41, 0b1011000 | low_id }); + } else { + try self.code.append(0b1011000 | low_id); + } + } else return self.fail(inst.base.src, "TODO more pop operands", .{}); + } else { + return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{}); + } + } } if (inst.output_constraint) |output| { @@ -4223,6 +4308,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const decl = payload.data; const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; + } else if (self.bin_file.cast(link.File.Plan9)) |p9| { + const decl = payload.data; + const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; + return MCValue{ .memory = got_addr }; } else { return self.fail(src, "TODO codegen non-ELF const Decl pointer", .{}); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 2baedf8c9d..b8f96891f4 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -117,6 +117,7 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { .opencl => return error.LLVMBackendDoesNotSupportOpenCL, .glsl450 => return error.LLVMBackendDoesNotSupportGLSL450, .vulkan => return error.LLVMBackendDoesNotSupportVulkan, + .plan9 => return error.LLVMBackendDoesNotSupportPlan9, .other => "unknown", }; diff --git a/src/link.zig b/src/link.zig index 9c1be16b9c..61a46e8b06 100644 --- a/src/link.zig +++ b/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,6 +151,7 @@ pub const File = struct { elf: Elf.SrcFn, coff: Coff.SrcFn, macho: MachO.SrcFn, + plan9: void, c: C.FnBlock, wasm: Wasm.FnData, spirv: SpirV.FnData, @@ -159,6 +161,7 @@ pub const File = struct { elf: Elf.Export, coff: void, macho: MachO.Export, + plan9: Plan9.Export, c: void, wasm: void, spirv: void, @@ -189,6 +192,7 @@ pub const File = struct { .elf => &(try Elf.createEmpty(allocator, options)).base, .macho => &(try MachO.createEmpty(allocator, options)).base, .wasm => &(try Wasm.createEmpty(allocator, options)).base, + .plan9 => return &(try Plan9.createEmpty(allocator, options)).base, .c => unreachable, // Reported error earlier. .spirv => &(try SpirV.createEmpty(allocator, options)).base, .hex => return error.HexObjectFormatUnimplemented, @@ -204,6 +208,7 @@ 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, @@ -220,6 +225,7 @@ 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, @@ -243,7 +249,7 @@ pub const File = struct { pub fn makeWritable(base: *File) !void { switch (base.tag) { - .coff, .elf, .macho => { + .coff, .elf, .macho, .plan9 => { if (base.file != null) return; const emit = base.options.emit orelse return; base.file = try emit.directory.handle.createFile(emit.sub_path, .{ @@ -288,7 +294,7 @@ pub const File = struct { f.close(); base.file = null; }, - .coff, .elf => if (base.file) |f| { + .coff, .elf, .plan9 => if (base.file) |f| { if (base.intermediary_basename != 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. @@ -313,6 +319,7 @@ pub const File = struct { .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl), .spirv => return @fieldParentPtr(SpirV, "base", base).updateDecl(module, decl), + .plan9 => return @fieldParentPtr(Plan9, "base", base).updateDecl(module, decl), } } @@ -326,6 +333,7 @@ pub const File = struct { .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl), .c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl), + .plan9 => @panic("TODO: implement updateDeclLineNumber for plan9"), .wasm, .spirv => {}, } } @@ -340,6 +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), .spirv => {}, } } @@ -393,6 +402,11 @@ pub const File = struct { parent.deinit(); base.allocator.destroy(parent); }, + .plan9 => { + const parent = @fieldParentPtr(Plan9, "base", base); + parent.deinit(); + base.allocator.destroy(parent); + }, } } @@ -425,6 +439,7 @@ pub const File = struct { .c => return @fieldParentPtr(C, "base", base).flush(comp), .wasm => return @fieldParentPtr(Wasm, "base", base).flush(comp), .spirv => return @fieldParentPtr(SpirV, "base", base).flush(comp), + .plan9 => return @fieldParentPtr(Plan9, "base", base).flush(comp), } } @@ -438,6 +453,7 @@ pub const File = struct { .c => return @fieldParentPtr(C, "base", base).flushModule(comp), .wasm => return @fieldParentPtr(Wasm, "base", base).flushModule(comp), .spirv => return @fieldParentPtr(SpirV, "base", base).flushModule(comp), + .plan9 => return @fieldParentPtr(Plan9, "base", base).flushModule(comp), } } @@ -451,6 +467,7 @@ pub const File = struct { .c => @fieldParentPtr(C, "base", base).freeDecl(decl), .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl), .spirv => @fieldParentPtr(SpirV, "base", base).freeDecl(decl), + .plan9 => @fieldParentPtr(Plan9, "base", base).freeDecl(decl), } } @@ -459,6 +476,7 @@ pub const File = struct { .coff => return @fieldParentPtr(Coff, "base", base).error_flags, .elf => return @fieldParentPtr(Elf, "base", base).error_flags, .macho => return @fieldParentPtr(MachO, "base", base).error_flags, + .plan9 => return @fieldParentPtr(Plan9, "base", base).error_flags, .c => return .{ .no_entry_point_found = false }, .wasm, .spirv => return ErrorFlags{}, } @@ -481,6 +499,7 @@ pub const File = struct { .c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports), .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports), .spirv => return @fieldParentPtr(SpirV, "base", base).updateDeclExports(module, decl, exports), + .plan9 => return @fieldParentPtr(Plan9, "base", base).updateDeclExports(module, decl, exports), } } @@ -489,6 +508,7 @@ pub const File = struct { .coff => return @fieldParentPtr(Coff, "base", base).getDeclVAddr(decl), .elf => return @fieldParentPtr(Elf, "base", base).getDeclVAddr(decl), .macho => return @fieldParentPtr(MachO, "base", base).getDeclVAddr(decl), + .plan9 => @panic("GET VADDR"), .c => unreachable, .wasm => unreachable, .spirv => unreachable, @@ -633,6 +653,7 @@ pub const File = struct { c, wasm, spirv, + plan9, }; pub const ErrorFlags = struct { @@ -641,6 +662,7 @@ pub const File = struct { pub const C = @import("link/C.zig"); pub const Coff = @import("link/Coff.zig"); + pub const Plan9 = @import("link/Plan9.zig"); pub const Elf = @import("link/Elf.zig"); pub const MachO = @import("link/MachO.zig"); pub const SpirV = @import("link/SpirV.zig"); diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig new file mode 100644 index 0000000000..f880b40d85 --- /dev/null +++ b/src/link/Plan9.zig @@ -0,0 +1,378 @@ +//! This implementation does all the linking work in flush(). A future improvement +//! would be to add incremental linking in a similar way as ELF does. + +const Plan9 = @This(); + +const std = @import("std"); +const link = @import("../link.zig"); +const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); +const aout = @import("Plan9/aout.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; + +base: link.File, +sixtyfour_bit: bool, +error_flags: File.ErrorFlags = File.ErrorFlags{}, +bases: Bases, + +decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, void) = .{}, +/// is just casted down when 32 bit +syms: std.ArrayListUnmanaged(aout.Sym) = .{}, +text_buf: std.ArrayListUnmanaged(u8) = .{}, +data_buf: std.ArrayListUnmanaged(u8) = .{}, + +hdr: aout.ExecHdr = undefined, + +entry_decl: ?*Module.Decl = null, + +got: std.ArrayListUnmanaged(u64) = .{}, +const Bases = struct { + text: u64, + /// the addr of the got + data: u64, +}; + +fn getAddr(self: Plan9, addr: u64, t: aout.Sym.Type) u64 { + return addr + switch (t) { + .T, .t, .l, .L => self.bases.text, + .D, .d, .B, .b => self.bases.data, + else => unreachable, + }; +} +/// opposite of getAddr +fn takeAddr(self: Plan9, addr: u64, t: aout.Sym.Type) u64 { + return addr - switch (t) { + .T, .t, .l, .L => self.bases.text, + .D, .d, .B, .b => self.bases.data, + else => unreachable, + }; +} + +fn getSymAddr(self: Plan9, s: aout.Sym) u64 { + return self.getAddr(s.value, s.type); +} + +pub const DeclBlock = struct { + type: aout.Sym.Type, + /// offset in the text or data sects + offset: ?u64, + /// offset into syms + sym_index: ?usize, + /// offset into got + got_index: ?usize, + pub const empty = DeclBlock{ + .type = .t, + .offset = null, + .sym_index = null, + .got_index = null, + }; +}; + +pub fn defaultBaseAddrs(arch: std.Target.Cpu.Arch) Bases { + return switch (arch) { + .x86_64 => .{ + // 0x28 => 40 == header size + .text = 0x200028, + .data = 0x400000, + }, + .i386 => .{ + // 0x20 => 32 == header size + .text = 0x200020, + .data = 0x400000, + }, + else => std.debug.panic("find default base address for {}", .{arch}), + }; +} + +pub const PtrWidth = enum { p32, p64 }; + +pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Plan9 { + if (options.use_llvm) + return error.LLVMBackendDoesNotSupportPlan9; + const sixtyfour_bit: bool = switch (options.target.cpu.arch.ptrBitWidth()) { + 0...32 => false, + 33...64 => true, + else => return error.UnsupportedP9Architecture, + }; + const self = try gpa.create(Plan9); + self.* = .{ + .base = .{ + .tag = .plan9, + .options = options, + .allocator = gpa, + .file = null, + }, + .sixtyfour_bit = sixtyfour_bit, + .bases = undefined, + }; + return self; +} + +pub fn updateDecl(self: *Plan9, module: *Module, decl: *Module.Decl) !void { + _ = module; + _ = 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 { + _ = comp; + const tracy = trace(@src()); + defer tracy.end(); + + log.debug("flushModule", .{}); + + defer assert(self.hdr.entry != 0x0); + + const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; + + self.text_buf.items.len = 0; + self.data_buf.items.len = 0; + // ensure space to write the got later + assert(self.got.items.len == self.decl_table.count()); + try self.data_buf.appendNTimes(self.base.allocator, 0x69, self.got.items.len * if (!self.sixtyfour_bit) @as(u32, 4) else 8); + // 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; + const is_fn = (decl.ty.zigTypeTag() == .Fn); + + log.debug("update the symbol table and got for decl {*} ({s})", .{ decl, decl.name }); + decl.link.plan9 = if (is_fn) .{ + .offset = self.getAddr(self.text_buf.items.len, .t), + .type = .t, + .sym_index = decl.link.plan9.sym_index, + .got_index = decl.link.plan9.got_index, + } else .{ + .offset = self.getAddr(self.data_buf.items.len, .d), + .type = .d, + .sym_index = decl.link.plan9.sym_index, + .got_index = decl.link.plan9.got_index, + }; + self.got.items[decl.link.plan9.got_index.?] = decl.link.plan9.offset.?; + if (decl.link.plan9.sym_index) |s| { + self.syms.items[s] = .{ + .value = decl.link.plan9.offset.?, + .type = decl.link.plan9.type, + .name = mem.span(decl.name), + }; + } else { + try self.syms.append(self.base.allocator, .{ + .value = decl.link.plan9.offset.?, + .type = decl.link.plan9.type, + .name = mem.span(decl.name), + }); + decl.link.plan9.sym_index = self.syms.items.len - 1; + } + + if (module.decl_exports.get(decl)) |exports| { + for (exports) |exp| { + // plan9 does not support custom sections + if (exp.options.section) |section_name| { + if (!mem.eql(u8, section_name, ".text") or !mem.eql(u8, section_name, ".data")) { + try module.failed_exports.put(module.gpa, exp, try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "plan9 does not support extra sections", .{})); + break; + } + } + if (std.mem.eql(u8, exp.options.name, "_start")) { + std.debug.assert(decl.link.plan9.type == .t); // we tried to link a non-function as the entry + self.entry_decl = decl; + } + if (exp.link.plan9) |i| { + self.syms.items[i] = .{ + .value = decl.link.plan9.offset.?, + .type = decl.link.plan9.type.toGlobal(), + .name = exp.options.name, + }; + } else { + try self.syms.append(self.base.allocator, .{ + .value = decl.link.plan9.offset.?, + .type = decl.link.plan9.type.toGlobal(), + .name = exp.options.name, + }); + exp.link.plan9 = self.syms.items.len - 1; + } + } + } + + log.debug("codegen decl {*} ({s})", .{ decl, decl.name }); + 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); + code_buffer.items.len = 0; + } else { + try self.data_buf.appendSlice(self.base.allocator, code); + code_buffer.items.len = 0; + } + } + } + + // write the got + if (!self.sixtyfour_bit) { + for (self.got.items) |p, i| { + mem.writeInt(u32, self.data_buf.items[i * 4 ..][0..4], @intCast(u32, p), self.base.options.target.cpu.arch.endian()); + } + } else { + for (self.got.items) |p, i| { + mem.writeInt(u64, self.data_buf.items[i * 8 ..][0..8], p, self.base.options.target.cpu.arch.endian()); + } + } + + self.hdr.entry = @truncate(u32, self.entry_decl.?.link.plan9.offset.?); + + // edata, end, etext + self.syms.items[0].value = self.getAddr(0x0, .b); + self.syms.items[1].value = self.getAddr(0x0, .b); + self.syms.items[2].value = self.getAddr(self.text_buf.items.len, .t); + + var sym_buf = std.ArrayList(u8).init(self.base.allocator); + defer sym_buf.deinit(); + try self.writeSyms(&sym_buf); + + // generate the header + self.hdr = .{ + .magic = try aout.magicFromArch(self.base.options.target.cpu.arch), + .text = @intCast(u32, self.text_buf.items.len), + .data = @intCast(u32, self.data_buf.items.len), + .syms = @intCast(u32, sym_buf.items.len), + .bss = 0, + .pcsz = 0, + .spsz = 0, + .entry = self.hdr.entry, + }; + + const file = self.base.file.?; + + var hdr_buf = self.hdr.toU8s(); + const hdr_slice: []const u8 = &hdr_buf; + // account for the fat header + const hdr_size: u8 = if (!self.sixtyfour_bit) 32 else 40; + // write the fat header for 64 bit entry points + if (self.sixtyfour_bit) { + mem.writeIntSliceBig(u64, hdr_buf[32..40], self.hdr.entry); + } + // write it all! + var vectors: [4]std.os.iovec_const = .{ + .{ .iov_base = hdr_slice.ptr, .iov_len = hdr_size }, + .{ .iov_base = self.text_buf.items.ptr, .iov_len = self.text_buf.items.len }, + .{ .iov_base = self.data_buf.items.ptr, .iov_len = self.data_buf.items.len }, + .{ .iov_base = sym_buf.items.ptr, .iov_len = sym_buf.items.len }, + // TODO spsz, pcsz + }; + try file.pwritevAll(&vectors, 0); +} +pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void { + assert(self.decl_table.swapRemove(decl)); +} + +pub fn updateDeclExports( + self: *Plan9, + module: *Module, + decl: *Module.Decl, + exports: []const *Module.Export, +) !void { + // we do all the things in flush + _ = self; + _ = module; + _ = decl; + _ = exports; +} +pub fn deinit(self: *Plan9) void { + self.decl_table.deinit(self.base.allocator); + self.syms.deinit(self.base.allocator); + self.text_buf.deinit(self.base.allocator); + self.data_buf.deinit(self.base.allocator); + self.got.deinit(self.base.allocator); +} + +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.bases = defaultBaseAddrs(options.target.cpu.arch); + + // first 3 symbols in our table are edata, end, etext + try self.syms.appendSlice(self.base.allocator, &.{ + .{ + .value = 0xcafebabe, + .type = .B, + .name = "edata", + }, + .{ + .value = 0xcafebabe, + .type = .B, + .name = "end", + }, + .{ + .value = 0xcafebabe, + .type = .T, + .name = "etext", + }, + }); + + self.base.file = file; + return self; +} + +pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void { + const writer = buf.writer(); + for (self.syms.items) |sym| { + if (!self.sixtyfour_bit) { + try writer.writeIntBig(u32, @intCast(u32, sym.value)); + } else { + try writer.writeIntBig(u64, sym.value); + } + try writer.writeByte(@enumToInt(sym.type)); + try writer.writeAll(std.mem.span(sym.name)); + try writer.writeByte(0); + } +} + +pub fn allocateDeclIndexes(self: *Plan9, decl: *Module.Decl) !void { + try self.got.append(self.base.allocator, 0xdeadbeef); + decl.link.plan9.got_index = self.got.items.len - 1; +} diff --git a/src/link/Plan9/aout.zig b/src/link/Plan9/aout.zig new file mode 100644 index 0000000000..f6dff7437c --- /dev/null +++ b/src/link/Plan9/aout.zig @@ -0,0 +1,114 @@ +const std = @import("std"); +const assert = std.debug.assert; + +/// All integers are in big-endian format (needs a byteswap). +pub const ExecHdr = extern struct { + magic: u32, + text: u32, + data: u32, + bss: u32, + syms: u32, + /// You should truncate this to 32 bits on 64 bit systems, then but the actual 8 bytes + /// in the fat header. + entry: u32, + spsz: u32, + pcsz: u32, + comptime { + assert(@sizeOf(@This()) == 32); + } + /// It is up to the caller to disgard the last 8 bytes if the header is not fat. + pub fn toU8s(self: *@This()) [40]u8 { + var buf: [40]u8 = undefined; + var i: u8 = 0; + inline for (std.meta.fields(@This())) |f| { + std.mem.writeIntSliceBig(u32, buf[i .. i + 4], @field(self, f.name)); + i += 4; + } + return buf; + } +}; + +pub const Sym = struct { + /// Big endian in the file + value: u64, + type: Type, + name: []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 Type = 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 fn toGlobal(self: Type) Type { + return switch (self) { + .t => .T, + .b => .B, + .d => .D, + else => unreachable, + }; + } + }; +}; + +pub const HDR_MAGIC = 0x00008000; +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(0, 8); // 68020 +pub const I_MAGIC = _MAGIC(0, 11); // intel 386 +pub const J_MAGIC = _MAGIC(0, 12); // intel 960 (retired) +pub const K_MAGIC = _MAGIC(0, 13); // sparc +pub const V_MAGIC = _MAGIC(0, 16); // mips 3000 BE +pub const X_MAGIC = _MAGIC(0, 17); // att dsp 3210 (retired) +pub const M_MAGIC = _MAGIC(0, 18); // mips 4000 BE +pub const D_MAGIC = _MAGIC(0, 19); // amd 29000 (retired) +pub const E_MAGIC = _MAGIC(0, 20); // arm +pub const Q_MAGIC = _MAGIC(0, 21); // powerpc +pub const N_MAGIC = _MAGIC(0, 22); // mips 4000 LE +pub const L_MAGIC = _MAGIC(0, 23); // dec alpha (retired) +pub const P_MAGIC = _MAGIC(0, 24); // mips 3000 LE +pub const U_MAGIC = _MAGIC(0, 25); // sparc64 +pub const S_MAGIC = _MAGIC(HDR_MAGIC, 26); // amd64 +pub const T_MAGIC = _MAGIC(HDR_MAGIC, 27); // powerpc64 +pub const R_MAGIC = _MAGIC(HDR_MAGIC, 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, + }; +} diff --git a/src/target.zig b/src/target.zig index a61bbe7f09..06483d68ff 100644 --- a/src/target.zig +++ b/src/target.zig @@ -244,7 +244,7 @@ pub fn supportsStackProbing(target: std.Target) bool { pub fn osToLLVM(os_tag: std.Target.Os.Tag) llvm.OSType { return switch (os_tag) { - .freestanding, .other, .opencl, .glsl450, .vulkan => .UnknownOS, + .freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS, .windows, .uefi => .Win32, .ananas => .Ananas, .cloudabi => .CloudABI, diff --git a/src/type.zig b/src/type.zig index 34b7dc178b..8ded2ee906 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3290,6 +3290,7 @@ pub const CType = enum { .openbsd, .wasi, .emscripten, + .plan9, => switch (self) { .short, .ushort,