diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index d273712cd1..01c3cdd449 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1820,6 +1820,9 @@ fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void { try self.markOutdatedDecl(decl); decl.contents_hash = contents_hash; } else switch (self.bin_file.tag) { + .coff => { + // TODO Implement for COFF + }, .elf => if (decl.fn_link.elf.len != 0) { // TODO Look into detecting when this would be unnecessary by storing enough state // in `Decl` to notice that the line number did not change. @@ -2078,12 +2081,14 @@ fn allocateNewDecl( .deletion_flag = false, .contents_hash = contents_hash, .link = switch (self.bin_file.tag) { + .coff => .{ .coff = {} }, // @TODO .elf => .{ .elf = link.File.Elf.TextBlock.empty }, .macho => .{ .macho = link.File.MachO.TextBlock.empty }, .c => .{ .c = {} }, .wasm => .{ .wasm = {} }, }, .fn_link = switch (self.bin_file.tag) { + .coff => .{ .coff = {} }, // @TODO .elf => .{ .elf = link.File.Elf.SrcFn.empty }, .macho => .{ .macho = link.File.MachO.SrcFn.empty }, .c => .{ .c = {} }, diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index ecf3876582..4189d636b1 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -34,6 +34,7 @@ pub const File = struct { pub const LinkBlock = union { elf: Elf.TextBlock, + coff: void, // @TODO macho: MachO.TextBlock, c: void, wasm: void, @@ -41,6 +42,7 @@ pub const File = struct { pub const LinkFn = union { elf: Elf.SrcFn, + coff: void, // @TODO macho: MachO.SrcFn, c: void, wasm: ?Wasm.FnData, @@ -66,7 +68,7 @@ pub const File = struct { pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File { switch (options.object_format) { .unknown => unreachable, - .coff => return error.TODOImplementCoff, + .coff => return Coff.openPath(allocator, dir, sub_path, options), .elf => return Elf.openPath(allocator, dir, sub_path, options), .macho => return MachO.openPath(allocator, dir, sub_path, options), .wasm => return Wasm.openPath(allocator, dir, sub_path, options), @@ -85,7 +87,7 @@ pub const File = struct { pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void { switch (base.tag) { - .elf, .macho => { + .coff, .elf, .macho => { if (base.file != null) return; base.file = try dir.createFile(sub_path, .{ .truncate = false, @@ -112,6 +114,7 @@ pub const File = struct { /// after allocateDeclIndexes for any given Decl. pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void { switch (base.tag) { + .coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl), .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl), .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), @@ -121,6 +124,7 @@ pub const File = struct { pub fn updateDeclLineNumber(base: *File, module: *Module, decl: *Module.Decl) !void { switch (base.tag) { + .coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl), .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl), .c, .wasm => {}, @@ -131,6 +135,7 @@ pub const File = struct { /// any given Decl. pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void { switch (base.tag) { + .coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl), .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl), .c, .wasm => {}, @@ -140,6 +145,7 @@ pub const File = struct { pub fn deinit(base: *File) void { if (base.file) |f| f.close(); switch (base.tag) { + .coff => @fieldParentPtr(Coff, "base", base).deinit(), .elf => @fieldParentPtr(Elf, "base", base).deinit(), .macho => @fieldParentPtr(MachO, "base", base).deinit(), .c => @fieldParentPtr(C, "base", base).deinit(), @@ -149,6 +155,11 @@ pub const File = struct { pub fn destroy(base: *File) void { switch (base.tag) { + .coff => { + const parent = @fieldParentPtr(Coff, "base", base); + parent.deinit(); + base.allocator.destroy(parent); + }, .elf => { const parent = @fieldParentPtr(Elf, "base", base); parent.deinit(); @@ -177,6 +188,7 @@ pub const File = struct { defer tracy.end(); try switch (base.tag) { + .coff => @fieldParentPtr(Coff, "base", base).flush(module), .elf => @fieldParentPtr(Elf, "base", base).flush(module), .macho => @fieldParentPtr(MachO, "base", base).flush(module), .c => @fieldParentPtr(C, "base", base).flush(module), @@ -186,6 +198,7 @@ pub const File = struct { pub fn freeDecl(base: *File, decl: *Module.Decl) void { switch (base.tag) { + .coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl), .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl), .c => unreachable, @@ -195,6 +208,7 @@ pub const File = struct { pub fn errorFlags(base: *File) ErrorFlags { return switch (base.tag) { + .coff => @fieldParentPtr(Coff, "base", base).error_flags, .elf => @fieldParentPtr(Elf, "base", base).error_flags, .macho => @fieldParentPtr(MachO, "base", base).error_flags, .c => return .{ .no_entry_point_found = false }, @@ -211,6 +225,7 @@ pub const File = struct { exports: []const *Module.Export, ) !void { switch (base.tag) { + .coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports), .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports), .c => return {}, @@ -220,6 +235,7 @@ pub const File = struct { pub fn getDeclVAddr(base: *File, decl: *const Module.Decl) u64 { switch (base.tag) { + .coff => return @fieldParentPtr(Coff, "base", base).getDeclVAddr(decl), .elf => return @fieldParentPtr(Elf, "base", base).getDeclVAddr(decl), .macho => return @fieldParentPtr(MachO, "base", base).getDeclVAddr(decl), .c => unreachable, @@ -228,6 +244,7 @@ pub const File = struct { } pub const Tag = enum { + coff, elf, macho, c, @@ -239,6 +256,7 @@ pub const File = struct { }; pub const C = @import("link/C.zig"); + pub const Coff = @import("link/Coff.zig"); pub const Elf = @import("link/Elf.zig"); pub const MachO = @import("link/MachO.zig"); pub const Wasm = @import("link/Wasm.zig"); diff --git a/src-self-hosted/link/Coff.zig b/src-self-hosted/link/Coff.zig new file mode 100644 index 0000000000..eaceddb9a3 --- /dev/null +++ b/src-self-hosted/link/Coff.zig @@ -0,0 +1,231 @@ +const Coff = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const fs = std.fs; + +const Module = @import("../Module.zig"); +const codegen = @import("../codegen/wasm.zig"); +const link = @import("../link.zig"); + + +pub const base_tag: link.File.Tag = .coff; + +const msdos_stub = @embedFile("msdos-stub.bin"); +const coff_file_header_offset = msdos_stub.len + 4; +const optional_header_offset = coff_file_header_offset + 20; + +base: link.File, +ptr_width: enum { p32, p64 }, +error_flags: link.File.ErrorFlags = .{}, + +coff_file_header_dirty: bool = false, +optional_header_dirty: bool = false, + +pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File { + assert(options.object_format == .coff); + + const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options) }); + errdefer file.close(); + + var coff_file = try allocator.create(Coff); + errdefer allocator.destroy(coff_file); + + coff_file.* = openFile(allocator, file, options) catch |err| switch (err) { + error.IncrFailed => try createFile(allocator, file, options), + else => |e| return e, + }; + + return &coff_file.base; +} + +/// Returns error.IncrFailed if incremental update could not be performed. +fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !Coff { + switch (options.output_mode) { + .Exe => {}, + .Obj => return error.IncrFailed, // @TODO DO OBJ FILES + .Lib => return error.IncrFailed, + } + var self: Coff = .{ + .base = .{ + .file = file, + .tag = .coff, + .options = options, + .allocator = allocator, + }, + .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { + 32 => .p32, + 64 => .p64, + else => return error.UnsupportedELFArchitecture, + }, + }; + errdefer self.deinit(); + + // TODO implement reading the PE/COFF file + return error.IncrFailed; +} + +/// Truncates the existing file contents and overwrites the contents. +/// Returns an error if `file` is not already open with +read +write +seek abilities. +fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Coff { + switch (options.output_mode) { + .Exe => {}, + .Obj => return error.TODOImplementWritingObjFiles, // @TODO DO OBJ FILES + .Lib => return error.TODOImplementWritingLibFiles, + } + var self: Coff = .{ + .base = .{ + .tag = .coff, + .options = options, + .allocator = allocator, + .file = file, + }, + .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { + 32 => .p32, + 64 => .p64, + else => return error.UnsupportedCOFFArchitecture, + }, + .coff_file_header_dirty = true, + .optional_header_dirty = true, + }; + errdefer self.deinit(); + + var output = self.base.file.?.writer(); + + // MS-DOS stub + PE magic + try output.writeAll(msdos_stub ++ "PE\x00\x00"); + const machine_type: u16 = switch (self.base.options.target.cpu.arch) { + .x86_64 => 0x8664, + .i386 => 0x014c, + .riscv32 => 0x5032, + .riscv64 => 0x5064, + else => return error.UnsupportedCOFFArchitecture, + }; + + // Start of COFF file header + try output.writeIntLittle(u16, machine_type); + try output.writeIntLittle(u16, switch (self.ptr_width) { + .p32 => @as(u16, 98), + .p64 => 114, + }); + try output.writeAll("\x00" ** 14); + // Characteristics - IMAGE_FILE_RELOCS_STRIPPED | IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DEBUG_STRIPPED + var characteristics: u16 = 0x0001 | 0x000 | 0x02002; // @TODO Remove debug info stripped flag when necessary + switch (self.ptr_width) { + // IMAGE_FILE_32BIT_MACHINE + .p32 => characteristics |= 0x0100, + // IMAGE_FILE_LARGE_ADDRESS_AWARE + .p64 => characteristics |= 0x0020, + } + try output.writeIntLittle(u16, characteristics); + try output.writeIntLittle(u16, switch (self.ptr_width) { + .p32 => @as(u16, 0x10b), + .p64 => 0x20b, + }); + + // Start of optional header + // TODO Linker version, use 0.0 for now. + try output.writeAll("\x00" ** 2); + // Zero out every field until "BaseOfCode" + // @TODO Actually write entry point address, base of code address + try output.writeAll("\x00" ** 20); + switch (self.ptr_width) { + .p32 => { + // Zero out base of data + try output.writeAll("\x00" ** 4); + // Write image base + try output.writeIntLittle(u32, 0x40000000); + }, + .p64 => { + // Write image base + try output.writeIntLittle(u64, 0x40000000); + }, + } + + // Section alignment - default to 256 + try output.writeIntLittle(u32, 256); + // File alignment - default to 512 + try output.writeIntLittle(u32, 512); + // TODO - Minimum required windows version - use 6.0 (aka vista for now) + try output.writeIntLittle(u16, 0x6); + try output.writeIntLittle(u16, 0x0); + // TODO - Image version - use 0.0 for now + try output.writeIntLittle(u32, 0x0); + // Subsystem version + try output.writeIntLittle(u16, 0x6); + try output.writeIntLittle(u16, 0x0); + // Reserved zeroes + try output.writeIntLittle(u32, 0x0); + // Size of image - initialize to zero + try output.writeIntLittle(u32, 0x0); + // @TODO Size of headers - calculate this. + try output.writeIntLittle(u32, 0x0); + // Checksum + try output.writeIntLittle(u32, 0x0); + // Subsystem + try output.writeIntLittle(u16, 0x3); + // @TODO Dll characteristics, just using a value from a LLVM produced executable for now. + try output.writeIntLittle(u16, 0x8160); + switch (self.ptr_width) { + .p32 => { + // Stack reserve + try output.writeIntLittle(u32, 0x1000000); + // Stack commit + try output.writeIntLittle(u32, 0x1000); + // Heap reserve + try output.writeIntLittle(u32, 0x100000); + // Heap commit + try output.writeIntLittle(u32, 0x100); + }, + .p64 => { + // Stack reserve + try output.writeIntLittle(u64, 0x1000000); + // Stack commit + try output.writeIntLittle(u64, 0x1000); + // Heap reserve + try output.writeIntLittle(u64, 0x100000); + // Heap commit + try output.writeIntLittle(u64, 0x100); + }, + } + // Reserved loader flags + try output.writeIntLittle(u32, 0x0); + // Number of RVA + sizes + try output.writeIntLittle(u32, 0x0); + + return self; +} + +pub fn flush(self: *Coff, module: *Module) !void { + // @TODO Implement this +} + +pub fn freeDecl(self: *Coff, decl: *Module.Decl) void { + // @TODO Implement this +} + +pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { + // @TODO Implement this +} + +pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void { + // @TODO Implement this +} + +pub fn allocateDeclIndexes(self: *Coff, decl: *Module.Decl) !void { + // @TODO Implement this +} + +pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, exports: []const *Module.Export) !void { + // @TODO Implement this +} + +pub fn getDeclVAddr(self: *Coff, decl: *const Module.Decl) u64 { + // @TODO Implement this + return 0; +} + +pub fn deinit(self: *Coff) void { + // @TODO +} diff --git a/src-self-hosted/link/msdos-stub.bin b/src-self-hosted/link/msdos-stub.bin new file mode 100644 index 0000000000..96ad91198f Binary files /dev/null and b/src-self-hosted/link/msdos-stub.bin differ