diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 27f3fbc0994a..db2f7df73e99 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -528,13 +528,11 @@ pub const SectionHeader = extern struct { /// Applicable only to section headers in COFF objects. pub fn getAlignment(self: SectionHeader) ?u16 { - if (self.flags.ALIGN == 0) return null; - return std.math.powi(u16, 2, self.flags.ALIGN - 1) catch unreachable; + return self.flags.ALIGN.toByteUnits(); } pub fn setAlignment(self: *SectionHeader, new_alignment: u16) void { - assert(new_alignment > 0 and new_alignment <= 8192); - self.flags.ALIGN = @intCast(std.math.log2(new_alignment)); + self.flags.ALIGN = .fromByteUnits(new_alignment); } pub fn isCode(self: SectionHeader) bool { @@ -651,6 +649,16 @@ pub const SectionHeader = extern struct { @"4096BYTES" = 13, @"8192BYTES" = 14, _, + + pub fn toByteUnits(a: Align) ?u16 { + if (a == .NONE) return null; + return @as(u16, 1) << (@intFromEnum(a) - 1); + } + + pub fn fromByteUnits(n: u16) Align { + std.debug.assert(std.math.isPowerOfTwo(n)); + return @enumFromInt(@ctz(n) + 1); + } }; }; }; @@ -925,6 +933,10 @@ pub const WeakExternalDefinition = struct { flag: WeakExternalFlag, unused: [10]u8, + + pub fn sizeOf() usize { + return 18; + } }; // https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/km/ntimage.h @@ -1338,14 +1350,16 @@ pub const Strtab = struct { }; pub const ImportHeader = extern struct { - sig1: IMAGE.FILE.MACHINE, - sig2: u16, + /// Must be IMAGE_FILE_MACHINE_UNKNOWN + sig1: IMAGE.FILE.MACHINE = .UNKNOWN, + /// Must be 0xFFFF + sig2: u16 = 0xFFFF, version: u16, machine: IMAGE.FILE.MACHINE, time_date_stamp: u32, size_of_data: u32, hint: u16, - types: packed struct(u32) { + types: packed struct(u16) { type: ImportType, name_type: ImportNameType, reserved: u11, diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 89bdb4b3da54..2baabe99bf16 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -338,14 +338,6 @@ extern fn ZigLLVMWriteArchive( pub const ParseCommandLineOptions = ZigLLVMParseCommandLineOptions; extern fn ZigLLVMParseCommandLineOptions(argc: usize, argv: [*]const [*:0]const u8) void; -pub const WriteImportLibrary = ZigLLVMWriteImportLibrary; -extern fn ZigLLVMWriteImportLibrary( - def_path: [*:0]const u8, - coff_machine: c_uint, - output_lib_path: [*:0]const u8, - kill_at: bool, -) bool; - pub const GetHostCPUName = LLVMGetHostCPUName; extern fn LLVMGetHostCPUName() ?[*:0]u8; diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 240423336729..ce7880989208 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -10,6 +10,13 @@ const Compilation = @import("../Compilation.zig"); const build_options = @import("build_options"); const Cache = std.Build.Cache; const dev = @import("../dev.zig"); +const def = @import("mingw/def.zig"); +const implib = @import("mingw/implib.zig"); + +test { + _ = def; + _ = implib; +} pub const CrtFile = enum { crt2_o, @@ -290,11 +297,6 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { var o_dir = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); - const final_def_basename = try std.fmt.allocPrint(arena, "{s}.def", .{lib_name}); - const def_final_path = try comp.dirs.global_cache.join(arena, &[_][]const u8{ - "o", &digest, final_def_basename, - }); - const aro = @import("aro"); var diagnostics: aro.Diagnostics = .{ .output = .{ .to_list = .{ .arena = .init(gpa) } }, @@ -312,7 +314,6 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { defer std.debug.unlockStderrWriter(); nosuspend stderr.print("def file: {s}\n", .{def_file_path}) catch break :print; nosuspend stderr.print("include dir: {s}\n", .{include_dir}) catch break :print; - nosuspend stderr.print("output path: {s}\n", .{def_final_path}) catch break :print; } try aro_comp.include_dirs.append(gpa, include_dir); @@ -339,32 +340,46 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { } } - { - // new scope to ensure definition file is written before passing the path to WriteImportLibrary - const def_final_file = try o_dir.createFile(final_def_basename, .{ .truncate = true }); - defer def_final_file.close(); - var buffer: [1024]u8 = undefined; - var file_writer = def_final_file.writer(&buffer); - try pp.prettyPrintTokens(&file_writer.interface, .result_only); - try file_writer.interface.flush(); - } + const members = members: { + var aw: std.Io.Writer.Allocating = .init(gpa); + errdefer aw.deinit(); + try pp.prettyPrintTokens(&aw.writer, .result_only); + + const input = try aw.toOwnedSliceSentinel(0); + defer gpa.free(input); + + const machine_type = target.toCoffMachine(); + var def_diagnostics: def.Diagnostics = undefined; + var module_def = def.parse(gpa, input, machine_type, .mingw, &def_diagnostics) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.ParseError => { + var buffer: [64]u8 = undefined; + const w = std.debug.lockStderrWriter(&buffer); + defer std.debug.unlockStderrWriter(); + try w.writeAll("error: "); + try def_diagnostics.writeMsg(w, input); + try w.writeByte('\n'); + return error.WritingImportLibFailed; + }, + }; + defer module_def.deinit(); + + module_def.fixupForImportLibraryGeneration(machine_type); + + break :members try implib.getMembers(gpa, module_def, machine_type); + }; + defer members.deinit(); const lib_final_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename }); errdefer gpa.free(lib_final_path); - if (!build_options.have_llvm) return error.ZigCompilerNotBuiltWithLLVMExtensions; - const llvm_bindings = @import("../codegen/llvm/bindings.zig"); - const def_final_path_z = try arena.dupeZ(u8, def_final_path); - const lib_final_path_z = try comp.dirs.global_cache.joinZ(arena, &.{lib_final_path}); - if (llvm_bindings.WriteImportLibrary( - def_final_path_z.ptr, - @intFromEnum(target.toCoffMachine()), - lib_final_path_z.ptr, - true, - )) { - // TODO surface a proper error here - log.err("unable to turn {s}.def into {s}.lib", .{ lib_name, lib_name }); - return error.WritingImportLibFailed; + { + const lib_final_file = try o_dir.createFile(final_lib_basename, .{ .truncate = true }); + defer lib_final_file.close(); + var buffer: [1024]u8 = undefined; + var file_writer = lib_final_file.writer(&buffer); + try implib.writeCoffArchive(gpa, &file_writer.interface, members); + try file_writer.interface.flush(); } man.writeManifest() catch |err| { diff --git a/src/libs/mingw/def.zig b/src/libs/mingw/def.zig new file mode 100644 index 000000000000..98ea1a539678 --- /dev/null +++ b/src/libs/mingw/def.zig @@ -0,0 +1,1079 @@ +const std = @import("std"); + +pub const ModuleDefinitionType = enum { + mingw, +}; + +pub const ModuleDefinition = struct { + exports: std.ArrayList(Export) = .empty, + name: ?[]const u8 = null, + base_address: usize = 0, + arena: std.heap.ArenaAllocator, + type: ModuleDefinitionType, + + pub const Export = struct { + /// This may lack mangling, such as underscore prefixing and stdcall suffixing. + /// In a .def file, this is `foo` in `foo` or `bar` in `foo = bar`. + name: []const u8, + /// Note: This is currently only set by `fixupForImportLibraryGeneration` + mangled_symbol_name: ?[]const u8, + /// The external, exported name. + /// In a .def file, this is `foo` in `foo = bar`. + ext_name: ?[]const u8, + /// In a .def file, this is `bar` in `foo == bar`. + import_name: ?[]const u8, + /// In a .def file, this is `bar` in `foo EXPORTAS bar`. + export_as: ?[]const u8, + no_name: bool, + ordinal: u16, + type: std.coff.ImportType, + private: bool, + }; + + /// Modifies `exports` such that import library generation will + /// behave as expected. Based on LLVM's dlltool driver. + pub fn fixupForImportLibraryGeneration(self: *ModuleDefinition, machine_type: std.coff.IMAGE.FILE.MACHINE) void { + const kill_at = true; + for (self.exports.items) |*e| { + // If ExtName is set (if the "ExtName = Name" syntax was used), overwrite + // Name with ExtName and clear ExtName. When only creating an import + // library and not linking, the internal name is irrelevant. This avoids + // cases where writeImportLibrary tries to transplant decoration from + // symbol decoration onto ExtName. + if (e.ext_name) |ext_name| { + e.name = ext_name; + e.ext_name = null; + } + + if (kill_at) { + if (e.import_name != null or std.mem.startsWith(u8, e.name, "?")) + continue; + + if (machine_type == .I386) { + // By making sure E.SymbolName != E.Name for decorated symbols, + // writeImportLibrary writes these symbols with the type + // IMPORT_NAME_UNDECORATE. + e.mangled_symbol_name = e.name; + } + // Trim off the trailing decoration. Symbols will always have a + // starting prefix here (either _ for cdecl/stdcall, @ for fastcall + // or ? for C++ functions). Vectorcall functions won't have any + // fixed prefix, but the function base name will still be at least + // one char. + const name_len_without_at_suffix = std.mem.indexOfScalarPos(u8, e.name, 1, '@') orelse e.name.len; + e.name = e.name[0..name_len_without_at_suffix]; + } + } + } + + pub fn deinit(self: *const ModuleDefinition) void { + self.arena.deinit(); + } +}; + +pub const Diagnostics = struct { + err: Error, + token: Token, + extra: Extra = .{ .none = {} }, + + pub const Extra = union { + none: void, + expected: Token.Tag, + }; + + pub const Error = enum { + invalid_byte, + unfinished_quoted_identifier, + /// `expected` is populated + expected_token, + expected_integer, + unknown_statement, + unimplemented, + }; + + fn formatToken(ctx: TokenFormatContext, writer: *std.Io.Writer) std.Io.Writer.Error!void { + switch (ctx.token.tag) { + .eof, .invalid => return writer.writeAll(ctx.token.tag.nameForErrorDisplay()), + else => return writer.writeAll(ctx.token.slice(ctx.source)), + } + } + + const TokenFormatContext = struct { + token: Token, + source: []const u8, + }; + + fn fmtToken(self: Diagnostics, source: []const u8) std.fmt.Alt(TokenFormatContext, formatToken) { + return .{ .data = .{ + .token = self.token, + .source = source, + } }; + } + + pub fn writeMsg(self: Diagnostics, writer: *std.Io.Writer, source: []const u8) !void { + switch (self.err) { + .invalid_byte => { + return writer.print("invalid byte '{f}'", .{std.ascii.hexEscape(self.token.slice(source), .upper)}); + }, + .unfinished_quoted_identifier => { + return writer.print("unfinished quoted identifier at '{f}', expected closing '\"'", .{self.fmtToken(source)}); + }, + .expected_token => { + return writer.print("expected '{s}', got '{f}'", .{ self.extra.expected.nameForErrorDisplay(), self.fmtToken(source) }); + }, + .expected_integer => { + return writer.print("expected integer, got '{f}'", .{self.fmtToken(source)}); + }, + .unimplemented => { + return writer.print("support for '{f}' has not yet been implemented", .{self.fmtToken(source)}); + }, + .unknown_statement => { + return writer.print("unknown/invalid statement syntax beginning with '{f}'", .{self.fmtToken(source)}); + }, + } + } +}; + +pub fn parse( + allocator: std.mem.Allocator, + source: [:0]const u8, + machine_type: std.coff.IMAGE.FILE.MACHINE, + module_definition_type: ModuleDefinitionType, + diagnostics: *Diagnostics, +) !ModuleDefinition { + var tokenizer = Tokenizer.init(source); + var parser = Parser.init(&tokenizer, machine_type, module_definition_type, diagnostics); + + return parser.parse(allocator); +} + +const Token = struct { + tag: Tag, + start: usize, + end: usize, + + pub const keywords = std.StaticStringMap(Tag).initComptime(.{ + .{ "BASE", .keyword_base }, + .{ "CONSTANT", .keyword_constant }, + .{ "DATA", .keyword_data }, + .{ "EXPORTS", .keyword_exports }, + .{ "EXPORTAS", .keyword_exportas }, + .{ "HEAPSIZE", .keyword_heapsize }, + .{ "LIBRARY", .keyword_library }, + .{ "NAME", .keyword_name }, + .{ "NONAME", .keyword_noname }, + .{ "PRIVATE", .keyword_private }, + .{ "STACKSIZE", .keyword_stacksize }, + .{ "VERSION", .keyword_version }, + }); + + pub const Tag = enum { + invalid, + eof, + identifier, + comma, + equal, + equal_equal, + keyword_base, + keyword_constant, + keyword_data, + keyword_exports, + keyword_exportas, + keyword_heapsize, + keyword_library, + keyword_name, + keyword_noname, + keyword_private, + keyword_stacksize, + keyword_version, + + pub fn nameForErrorDisplay(self: Tag) []const u8 { + return switch (self) { + .invalid => "", + .eof => "", + .identifier => "", + .comma => ",", + .equal => "=", + .equal_equal => "==", + .keyword_base => "BASE", + .keyword_constant => "CONSTANT", + .keyword_data => "DATA", + .keyword_exports => "EXPORTS", + .keyword_exportas => "EXPORTAS", + .keyword_heapsize => "HEAPSIZE", + .keyword_library => "LIBRARY", + .keyword_name => "NAME", + .keyword_noname => "NONAME", + .keyword_private => "PRIVATE", + .keyword_stacksize => "STACKSIZE", + .keyword_version => "VERSION", + }; + } + }; + + /// Returns a useful slice of the token, e.g. for quoted identifiers, this + /// will return a slice without the quotes included. + pub fn slice(self: Token, source: []const u8) []const u8 { + return source[self.start..self.end]; + } +}; + +const Tokenizer = struct { + source: [:0]const u8, + index: usize, + error_context_token: ?Token = null, + + pub fn init(source: [:0]const u8) Tokenizer { + return .{ + .source = source, + .index = 0, + }; + } + + const State = enum { + start, + identifier_or_keyword, + quoted_identifier, + comment, + equal, + eof_or_invalid, + }; + + pub const Error = error{ + InvalidByte, + UnfinishedQuotedIdentifier, + }; + + pub fn next(self: *Tokenizer) Error!Token { + var result: Token = .{ + .tag = undefined, + .start = self.index, + .end = undefined, + }; + state: switch (State.start) { + .start => switch (self.source[self.index]) { + 0 => continue :state .eof_or_invalid, + '\r', '\n', ' ', '\t', '\x0B' => { + self.index += 1; + result.start = self.index; + continue :state .start; + }, + ';' => continue :state .comment, + '=' => continue :state .equal, + ',' => { + result.tag = .comma; + self.index += 1; + }, + '"' => continue :state .quoted_identifier, + else => continue :state .identifier_or_keyword, + }, + .comment => { + self.index += 1; + switch (self.source[self.index]) { + 0 => continue :state .eof_or_invalid, + '\n' => { + self.index += 1; + result.start = self.index; + continue :state .start; + }, + else => continue :state .comment, + } + }, + .equal => { + self.index += 1; + switch (self.source[self.index]) { + '=' => { + result.tag = .equal_equal; + self.index += 1; + }, + else => result.tag = .equal, + } + }, + .quoted_identifier => { + self.index += 1; + switch (self.source[self.index]) { + 0 => { + self.error_context_token = .{ + .tag = .eof, + .start = self.index, + .end = self.index, + }; + return error.UnfinishedQuotedIdentifier; + }, + '"' => { + result.tag = .identifier; + self.index += 1; + + // Return the token unquoted + return .{ + .tag = result.tag, + .start = result.start + 1, + .end = self.index - 1, + }; + }, + else => continue :state .quoted_identifier, + } + }, + .identifier_or_keyword => { + self.index += 1; + switch (self.source[self.index]) { + 0, '=', ',', ';', '\r', '\n', ' ', '\t', '\x0B' => { + const keyword = Token.keywords.get(self.source[result.start..self.index]); + result.tag = keyword orelse .identifier; + }, + else => continue :state .identifier_or_keyword, + } + }, + .eof_or_invalid => { + if (self.index == self.source.len) { + return .{ + .tag = .eof, + .start = self.index, + .end = self.index, + }; + } + self.error_context_token = .{ + .tag = .invalid, + .start = self.index, + .end = self.index + 1, + }; + return error.InvalidByte; + }, + } + + result.end = self.index; + return result; + } +}; + +test Tokenizer { + try testTokenizer( + \\foo + \\; hello + \\BASE + \\"bar" + \\ + , &.{ + .identifier, + .keyword_base, + .identifier, + }); +} + +fn testTokenizer(source: [:0]const u8, expected: []const Token.Tag) !void { + var tokenizer = Tokenizer.init(source); + for (expected) |expected_tag| { + const token = try tokenizer.next(); + try std.testing.expectEqual(expected_tag, token.tag); + } + const last_token = try tokenizer.next(); + try std.testing.expectEqual(.eof, last_token.tag); +} + +pub const Parser = struct { + tokenizer: *Tokenizer, + diagnostics: *Diagnostics, + lookahead_tokenizer: Tokenizer, + machine_type: std.coff.IMAGE.FILE.MACHINE, + module_definition_type: ModuleDefinitionType, + + pub fn init( + tokenizer: *Tokenizer, + machine_type: std.coff.IMAGE.FILE.MACHINE, + module_definition_type: ModuleDefinitionType, + diagnostics: *Diagnostics, + ) Parser { + return .{ + .tokenizer = tokenizer, + .machine_type = machine_type, + .module_definition_type = module_definition_type, + .diagnostics = diagnostics, + .lookahead_tokenizer = undefined, + }; + } + + pub const Error = error{ParseError} || std.mem.Allocator.Error; + + pub fn parse(self: *Parser, allocator: std.mem.Allocator) Error!ModuleDefinition { + var module: ModuleDefinition = .{ + .arena = .init(allocator), + .type = self.module_definition_type, + }; + const arena = module.arena.allocator(); + errdefer module.deinit(); + while (true) { + const tok = try self.nextToken(); + switch (tok.tag) { + .eof => break, + .keyword_library, .keyword_name => { + const is_library = tok.tag == .keyword_library; + + const name = try self.lookaheadToken(); + if (name.tag != .identifier) continue; + self.commitLookahead(); + + const base_tok = try self.lookaheadToken(); + if (base_tok.tag == .keyword_base) { + self.commitLookahead(); + + _ = try self.expectToken(.equal); + + module.base_address = try self.expectInteger(usize); + } + + // Append .dll/.exe if there's no extension + const name_slice = name.slice(self.tokenizer.source); + module.name = if (std.fs.path.extension(name_slice).len == 0) + try std.mem.concat(arena, u8, &.{ name_slice, if (is_library) ".dll" else ".exe" }) + else + try arena.dupe(u8, name_slice); + }, + .keyword_exports => { + while (true) { + var name_tok = try self.lookaheadToken(); + if (name_tok.tag != .identifier) break; + self.commitLookahead(); + + const ext_name_tok = ext_name: { + const equal = try self.lookaheadToken(); + if (equal.tag != .equal) break :ext_name null; + self.commitLookahead(); + + // The syntax is ` = `, so we need to + // swap the current name token over to ext_name and use + // this token as the name. + const ext_name_tok = name_tok; + name_tok = try self.expectToken(.identifier); + break :ext_name ext_name_tok; + }; + + var name_needs_underscore = false; + var ext_name_needs_underscore = false; + if (self.machine_type == .I386) { + const is_decorated = isDecorated(name_tok.slice(self.tokenizer.source), self.module_definition_type); + const is_forward_target = ext_name_tok != null and std.mem.indexOfScalar(u8, name_tok.slice(self.tokenizer.source), '.') != null; + name_needs_underscore = !is_decorated and !is_forward_target; + + if (ext_name_tok) |ext_name| { + ext_name_needs_underscore = !isDecorated(ext_name.slice(self.tokenizer.source), self.module_definition_type); + } + } + + var import_name_tok: ?Token = null; + var export_as_tok: ?Token = null; + var ordinal: ?u16 = null; + var import_type: std.coff.ImportType = .CODE; + var private: bool = false; + var no_name: bool = false; + while (true) { + const arg_tok = try self.lookaheadToken(); + switch (arg_tok.tag) { + .identifier => { + const slice = arg_tok.slice(self.tokenizer.source); + if (slice[0] != '@') break; + + // foo @ 10 + if (slice.len == 1) { + self.commitLookahead(); + ordinal = try self.expectInteger(u16); + continue; + } + // foo @10 + ordinal = std.fmt.parseUnsigned(u16, slice[1..], 0) catch { + // e.g. foo @bar, the @bar is presumed to be the start of a separate + // export (and there could be a newline between them) + break; + }; + // finally safe to commit to consuming the token + self.commitLookahead(); + + const noname_tok = try self.lookaheadToken(); + if (noname_tok.tag == .keyword_noname) { + self.commitLookahead(); + no_name = true; + } + }, + .equal_equal => { + self.commitLookahead(); + import_name_tok = try self.expectToken(.identifier); + }, + .keyword_data => { + self.commitLookahead(); + import_type = .DATA; + }, + .keyword_constant => { + self.commitLookahead(); + import_type = .CONST; + }, + .keyword_private => { + self.commitLookahead(); + private = true; + }, + .keyword_exportas => { + self.commitLookahead(); + export_as_tok = try self.expectToken(.identifier); + }, + else => break, + } + } + + const name = if (name_needs_underscore) + try std.mem.concat(arena, u8, &.{ "_", name_tok.slice(self.tokenizer.source) }) + else + try arena.dupe(u8, name_tok.slice(self.tokenizer.source)); + + const ext_name: ?[]const u8 = if (ext_name_tok) |ext_name| if (name_needs_underscore) + try std.mem.concat(arena, u8, &.{ "_", ext_name.slice(self.tokenizer.source) }) + else + try arena.dupe(u8, ext_name.slice(self.tokenizer.source)) else null; + + try module.exports.append(arena, .{ + .name = name, + .mangled_symbol_name = null, + .ext_name = ext_name, + .import_name = if (import_name_tok) |imp_name| try arena.dupe(u8, imp_name.slice(self.tokenizer.source)) else null, + .export_as = if (export_as_tok) |export_as| try arena.dupe(u8, export_as.slice(self.tokenizer.source)) else null, + .no_name = no_name, + .ordinal = ordinal orelse 0, + .type = import_type, + .private = private, + }); + } + }, + .keyword_heapsize, + .keyword_stacksize, + .keyword_version, + => return self.unimplemented(tok), + else => { + self.diagnostics.* = .{ + .err = .unknown_statement, + .token = tok, + }; + return error.ParseError; + }, + } + } + return module; + } + + fn isDecorated(symbol: []const u8, module_definition_type: ModuleDefinitionType) bool { + // In def files, the symbols can either be listed decorated or undecorated. + // + // - For cdecl symbols, only the undecorated form is allowed. + // - For fastcall and vectorcall symbols, both fully decorated or + // undecorated forms can be present. + // - For stdcall symbols in non-MinGW environments, the decorated form is + // fully decorated with leading underscore and trailing stack argument + // size - like "_Func@0". + // - In MinGW def files, a decorated stdcall symbol does not include the + // leading underscore though, like "Func@0". + + // This function controls whether a leading underscore should be added to + // the given symbol name or not. For MinGW, treat a stdcall symbol name such + // as "Func@0" as undecorated, i.e. a leading underscore must be added. + // For non-MinGW, look for '@' in the whole string and consider "_Func@0" + // as decorated, i.e. don't add any more leading underscores. + // We can't check for a leading underscore here, since function names + // themselves can start with an underscore, while a second one still needs + // to be added. + if (std.mem.startsWith(u8, symbol, "@")) return true; + if (std.mem.indexOf(u8, symbol, "@@") != null) return true; + if (std.mem.startsWith(u8, symbol, "?")) return true; + if (module_definition_type != .mingw and std.mem.indexOfScalar(u8, symbol, '@') != null) return true; + return false; + } + + fn expectInteger(self: *Parser, T: type) Error!T { + const tok = try self.nextToken(); + blk: { + if (tok.tag != .identifier) break :blk; + return std.fmt.parseUnsigned(T, tok.slice(self.tokenizer.source), 0) catch break :blk; + } + self.diagnostics.* = .{ + .err = .expected_integer, + .token = tok, + }; + return error.ParseError; + } + + fn unimplemented(self: *Parser, tok: Token) Error { + self.diagnostics.* = .{ + .err = .unimplemented, + .token = tok, + }; + return error.ParseError; + } + + fn expectToken(self: *Parser, tag: Token.Tag) Error!Token { + const tok = try self.nextToken(); + if (tok.tag != tag) { + self.diagnostics.* = .{ + .err = .expected_token, + .token = tok, + .extra = .{ .expected = tag }, + }; + return error.ParseError; + } + return tok; + } + + fn nextToken(self: *Parser) Error!Token { + return self.nextFromTokenizer(self.tokenizer); + } + + fn lookaheadToken(self: *Parser) Error!Token { + self.lookahead_tokenizer = self.tokenizer.*; + return self.nextFromTokenizer(&self.lookahead_tokenizer); + } + + fn commitLookahead(self: *Parser) void { + self.tokenizer.* = self.lookahead_tokenizer; + } + + fn nextFromTokenizer( + self: *Parser, + tokenizer: *Tokenizer, + ) Error!Token { + return tokenizer.next() catch |err| { + self.diagnostics.* = .{ + .err = switch (err) { + error.InvalidByte => .invalid_byte, + error.UnfinishedQuotedIdentifier => .unfinished_quoted_identifier, + }, + .token = tokenizer.error_context_token.?, + }; + return error.ParseError; + }; + } +}; + +test parse { + const source = + \\LIBRARY "foo" + \\; hello + \\EXPORTS + \\foo @ 10 + \\bar @104 + \\baz@4 + \\foo == bar + \\alias = function + \\ + \\data DATA + \\constant CONSTANT + \\ + ; + + try testParse(.AMD64, source, "foo.dll", &[_]ModuleDefinition.Export{ + .{ + .name = "foo", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 10, + .type = .CODE, + .private = false, + }, + .{ + .name = "bar", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 104, + .type = .CODE, + .private = false, + }, + .{ + .name = "baz@4", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "foo", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = "bar", + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "function", + .mangled_symbol_name = null, + .ext_name = "alias", + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "data", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .DATA, + .private = false, + }, + .{ + .name = "constant", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CONST, + .private = false, + }, + }); + + try testParse(.I386, source, "foo.dll", &[_]ModuleDefinition.Export{ + .{ + .name = "_foo", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 10, + .type = .CODE, + .private = false, + }, + .{ + .name = "_bar", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 104, + .type = .CODE, + .private = false, + }, + .{ + .name = "_baz@4", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "_foo", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = "bar", + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "_function", + .mangled_symbol_name = null, + .ext_name = "_alias", + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "_data", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .DATA, + .private = false, + }, + .{ + .name = "_constant", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CONST, + .private = false, + }, + }); + + try testParse(.ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{ + .{ + .name = "foo", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 10, + .type = .CODE, + .private = false, + }, + .{ + .name = "bar", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 104, + .type = .CODE, + .private = false, + }, + .{ + .name = "baz@4", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "foo", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = "bar", + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "function", + .mangled_symbol_name = null, + .ext_name = "alias", + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "data", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .DATA, + .private = false, + }, + .{ + .name = "constant", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CONST, + .private = false, + }, + }); + + try testParse(.ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{ + .{ + .name = "foo", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 10, + .type = .CODE, + .private = false, + }, + .{ + .name = "bar", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 104, + .type = .CODE, + .private = false, + }, + .{ + .name = "baz@4", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "foo", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = "bar", + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "function", + .mangled_symbol_name = null, + .ext_name = "alias", + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "data", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .DATA, + .private = false, + }, + .{ + .name = "constant", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CONST, + .private = false, + }, + }); +} + +test "ntdll" { + const source = + \\; + \\; Definition file of ntdll.dll + \\; Automatic generated by gendef + \\; written by Kai Tietz 2008 + \\; + \\LIBRARY "ntdll.dll" + \\EXPORTS + \\RtlDispatchAPC@12 + \\RtlActivateActivationContextUnsafeFast@0 + ; + + try testParse(.AMD64, source, "ntdll.dll", &[_]ModuleDefinition.Export{ + .{ + .name = "RtlDispatchAPC@12", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + .{ + .name = "RtlActivateActivationContextUnsafeFast@0", + .mangled_symbol_name = null, + .ext_name = null, + .import_name = null, + .export_as = null, + .no_name = false, + .ordinal = 0, + .type = .CODE, + .private = false, + }, + }); +} + +fn testParse(machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8, expected_module_name: []const u8, expected_exports: []const ModuleDefinition.Export) !void { + var diagnostics: Diagnostics = undefined; + const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.ParseError => { + const stderr = std.debug.lockStderrWriter(&.{}); + defer std.debug.unlockStderrWriter(); + try diagnostics.writeMsg(stderr, source); + try stderr.writeByte('\n'); + return err; + }, + }; + defer module.deinit(); + + try std.testing.expectEqualStrings(expected_module_name, module.name orelse ""); + try std.testing.expectEqual(expected_exports.len, module.exports.items.len); + for (expected_exports, module.exports.items) |expected, actual| { + try std.testing.expectEqualStrings(expected.name, actual.name); + try std.testing.expectEqualStrings(expected.export_as orelse "", actual.export_as orelse ""); + try std.testing.expectEqualStrings(expected.ext_name orelse "", actual.ext_name orelse ""); + try std.testing.expectEqualStrings(expected.import_name orelse "", actual.import_name orelse ""); + try std.testing.expectEqualStrings(expected.mangled_symbol_name orelse "", actual.mangled_symbol_name orelse ""); + try std.testing.expectEqual(expected.ordinal, actual.ordinal); + try std.testing.expectEqual(expected.no_name, actual.no_name); + try std.testing.expectEqual(expected.private, actual.private); + try std.testing.expectEqual(expected.type, actual.type); + } +} + +test "parse errors" { + for (&[_]std.coff.IMAGE.FILE.MACHINE{ .AMD64, .I386, .ARMNT, .ARM64 }) |machine_type| { + try testParseErrorMsg("invalid byte '\\x00'", machine_type, "LIBRARY \x00"); + try testParseErrorMsg("unfinished quoted identifier at '', expected closing '\"'", machine_type, "LIBRARY \"foo"); + try testParseErrorMsg("expected '=', got 'foo'", machine_type, "LIBRARY foo BASE foo"); + try testParseErrorMsg("expected integer, got 'foo'", machine_type, "EXPORTS foo @ foo"); + try testParseErrorMsg("support for 'HEAPSIZE' has not yet been implemented", machine_type, "HEAPSIZE"); + try testParseErrorMsg("unknown/invalid statement syntax beginning with 'LIB'", machine_type, "LIB"); + } +} + +fn testParseErrorMsg(expected_msg: []const u8, machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8) !void { + var diagnostics: Diagnostics = undefined; + _ = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.ParseError => { + var buf: [256]u8 = undefined; + var writer: std.Io.Writer = .fixed(&buf); + try diagnostics.writeMsg(&writer, source); + try std.testing.expectEqualStrings(expected_msg, writer.buffered()); + return; + }, + }; + return error.UnexpectedSuccess; +} diff --git a/src/libs/mingw/implib.zig b/src/libs/mingw/implib.zig new file mode 100644 index 000000000000..73ee79fa5537 --- /dev/null +++ b/src/libs/mingw/implib.zig @@ -0,0 +1,1088 @@ +const std = @import("std"); +const def = @import("def.zig"); +const Allocator = std.mem.Allocator; + +// LLVM has some quirks/bugs around padding/size values. +// Emulating those quirks made it much easier to test this implementation against the LLVM +// implementation since we could just check if the .lib files are byte-for-byte identical. +// This remains set to true out of an abundance of caution. +const llvm_compat = true; + +pub const WriteCoffArchiveError = error{TooManyMembers} || std.Io.Writer.Error || std.mem.Allocator.Error; + +pub fn writeCoffArchive( + allocator: std.mem.Allocator, + writer: *std.Io.Writer, + members: Members, +) WriteCoffArchiveError!void { + // The second linker member of a COFF archive uses a 32-bit integer for the number of members field, + // but only 16-bit integers for the "array of 1-based indexes that map symbol names to archive + // member offsets." This means that the maximum number of *indexable* members is maxInt(u16) - 1. + if (members.list.items.len > std.math.maxInt(u16) - 1) return error.TooManyMembers; + + try writer.writeAll(archive_start); + + const member_offsets = try allocator.alloc(usize, members.list.items.len); + defer allocator.free(member_offsets); + { + var offset: usize = 0; + for (member_offsets, 0..) |*elem, i| { + elem.* = offset; + offset += archive_header_len; + offset += members.list.items[i].byteLenWithPadding(); + } + } + + var long_names: StringTable = .{}; + defer long_names.deinit(allocator); + + var symbol_to_member_index = std.StringArrayHashMap(usize).init(allocator); + defer symbol_to_member_index.deinit(); + var string_table_len: usize = 0; + var num_symbols: usize = 0; + + for (members.list.items, 0..) |member, i| { + for (member.symbol_names_for_import_lib) |symbol_name| { + const gop_result = try symbol_to_member_index.getOrPut(symbol_name); + // When building the symbol map, ignore duplicate symbol names. + // This can happen in cases like (using .def file syntax): + // _foo + // foo == _foo + if (gop_result.found_existing) continue; + + gop_result.value_ptr.* = i; + string_table_len += symbol_name.len + 1; + num_symbols += 1; + } + + if (member.needsLongName()) { + _ = try long_names.put(allocator, member.name); + } + } + + const first_linker_member_len = 4 + (4 * num_symbols) + string_table_len; + const second_linker_member_len = 4 + (4 * members.list.items.len) + 4 + (2 * num_symbols) + string_table_len; + const long_names_len_including_header_and_padding = blk: { + if (long_names.map.count() == 0) break :blk 0; + break :blk archive_header_len + std.mem.alignForward(usize, long_names.data.items.len, 2); + }; + const first_member_offset = archive_start.len + archive_header_len + std.mem.alignForward(usize, first_linker_member_len, 2) + archive_header_len + std.mem.alignForward(usize, second_linker_member_len, 2) + long_names_len_including_header_and_padding; + + // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#first-linker-member + try writeArchiveMemberHeader(writer, .linker_member, memberHeaderLen(first_linker_member_len), "0"); + try writer.writeInt(u32, @intCast(num_symbols), .big); + for (symbol_to_member_index.values()) |member_i| { + const offset = member_offsets[member_i]; + try writer.writeInt(u32, @intCast(first_member_offset + offset), .big); + } + for (symbol_to_member_index.keys()) |symbol_name| { + try writer.writeAll(symbol_name); + try writer.writeByte(0); + } + if (first_linker_member_len % 2 != 0) try writer.writeByte(if (llvm_compat) 0 else archive_pad_byte); + + // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#second-linker-member + try writeArchiveMemberHeader(writer, .linker_member, memberHeaderLen(second_linker_member_len), "0"); + try writer.writeInt(u32, @intCast(members.list.items.len), .little); + for (member_offsets) |offset| { + try writer.writeInt(u32, @intCast(first_member_offset + offset), .little); + } + try writer.writeInt(u32, @intCast(num_symbols), .little); + + // sort lexicographically + const C = struct { + keys: []const []const u8, + + pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool { + return std.mem.lessThan(u8, ctx.keys[a_index], ctx.keys[b_index]); + } + }; + symbol_to_member_index.sortUnstable(C{ .keys = symbol_to_member_index.keys() }); + + for (symbol_to_member_index.values()) |member_i| { + try writer.writeInt(u16, @intCast(member_i + 1), .little); + } + for (symbol_to_member_index.keys()) |symbol_name| { + try writer.writeAll(symbol_name); + try writer.writeByte(0); + } + if (first_linker_member_len % 2 != 0) try writer.writeByte(if (llvm_compat) 0 else archive_pad_byte); + + // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#longnames-member + if (long_names.data.items.len != 0) { + const written_len = long_names.data.items.len; + try writeLongNamesMemberHeader(writer, memberHeaderLen(written_len)); + try writer.writeAll(long_names.data.items); + if (long_names.data.items.len % 2 != 0) try writer.writeByte(archive_pad_byte); + } + + for (members.list.items) |member| { + const name: MemberName = if (member.needsLongName()) + .{ .longname = long_names.getOffset(member.name).? } + else + .{ .name = member.name }; + try writeArchiveMemberHeader(writer, name, member.bytes.len, "644"); + try writer.writeAll(member.bytes); + if (member.bytes.len % 2 != 0) try writer.writeByte(archive_pad_byte); + } + + try writer.flush(); +} + +const archive_start = "!\n"; +const archive_header_end = "`\n"; +const archive_pad_byte = '\n'; +const archive_header_len = 60; + +fn memberHeaderLen(len: usize) usize { + return if (llvm_compat) + // LLVM writes this with the padding byte included, likely a bug/mistake + std.mem.alignForward(usize, len, 2) + else + len; +} + +const MemberName = union(enum) { + name: []const u8, + linker_member, + longnames_member, + longname: usize, + + pub fn write(self: MemberName, writer: *std.Io.Writer) !void { + switch (self) { + .name => |name| { + try writer.writeAll(name); + try writer.writeByte('/'); + try writer.splatByteAll(' ', 16 - (name.len + 1)); + }, + .linker_member => { + try writer.writeAll("/ "); + }, + .longnames_member => { + try writer.writeAll("// "); + }, + .longname => |offset| { + try writer.print("/{d: <15}", .{offset}); + }, + } + } +}; + +fn writeLongNamesMemberHeader(writer: *std.Io.Writer, size: usize) !void { + try (MemberName{ .longnames_member = {} }).write(writer); + try writer.splatByteAll(' ', archive_header_len - 16 - 10 - archive_header_end.len); + try writer.print("{d: <10}", .{size}); + try writer.writeAll(archive_header_end); +} + +fn writeArchiveMemberHeader(writer: *std.Io.Writer, name: MemberName, size: usize, mode: []const u8) !void { + try name.write(writer); + try writer.writeAll("0 "); // date + try writer.writeAll("0 "); // user id + try writer.writeAll("0 "); // group id + try writer.print("{s: <8}", .{mode}); // mode + try writer.print("{d: <10}", .{size}); + try writer.writeAll(archive_header_end); +} + +pub const Members = struct { + list: std.ArrayList(Member) = .empty, + arena: std.heap.ArenaAllocator, + + pub const Member = struct { + bytes: []const u8, + name: []const u8, + symbol_names_for_import_lib: []const []const u8, + + pub fn byteLenWithPadding(self: Member) usize { + return std.mem.alignForward(usize, self.bytes.len, 2); + } + + pub fn needsLongName(self: Member) bool { + return self.name.len >= 16; + } + }; + + pub fn deinit(self: *const Members) void { + self.arena.deinit(); + } +}; + +const GetMembersError = GetImportDescriptorError || GetShortImportError; + +pub fn getMembers( + allocator: std.mem.Allocator, + module_def: def.ModuleDefinition, + machine_type: std.coff.IMAGE.FILE.MACHINE, +) GetMembersError!Members { + var members: Members = .{ + .arena = std.heap.ArenaAllocator.init(allocator), + }; + const arena = members.arena.allocator(); + errdefer members.deinit(); + + try members.list.ensureTotalCapacity(arena, 3 + module_def.exports.items.len); + const module_import_name = try arena.dupe(u8, module_def.name orelse ""); + const library = std.fs.path.stem(module_import_name); + + const import_descriptor_symbol_name = try std.mem.concat(arena, u8, &.{ + import_descriptor_prefix, + library, + }); + const null_thunk_symbol_name = try std.mem.concat(arena, u8, &.{ + null_thunk_data_prefix, + library, + null_thunk_data_suffix, + }); + + members.list.appendAssumeCapacity(try getImportDescriptor(arena, machine_type, module_import_name, import_descriptor_symbol_name, null_thunk_symbol_name)); + members.list.appendAssumeCapacity(try getNullImportDescriptor(arena, machine_type, module_import_name)); + members.list.appendAssumeCapacity(try getNullThunk(arena, machine_type, module_import_name, null_thunk_symbol_name)); + + const DeferredExport = struct { + name: []const u8, + e: *const def.ModuleDefinition.Export, + }; + var renames: std.ArrayList(DeferredExport) = .empty; + defer renames.deinit(allocator); + var regular_imports: std.StringArrayHashMapUnmanaged([]const u8) = .empty; + defer regular_imports.deinit(allocator); + + for (module_def.exports.items) |*e| { + if (e.private) continue; + + const maybe_mangled_name = e.mangled_symbol_name orelse e.name; + const name = maybe_mangled_name; + + if (e.ext_name) |ext_name| { + _ = ext_name; + @panic("TODO"); // impossible if fixupForImportLibraryGeneration is called + } + + var import_name_type: std.coff.ImportNameType = undefined; + var export_name: ?[]const u8 = null; + if (e.no_name) { + import_name_type = .ORDINAL; + } else if (e.export_as) |export_as| { + import_name_type = .NAME_EXPORTAS; + export_name = export_as; + } else if (e.import_name) |import_name| { + if (machine_type == .I386 and std.mem.eql(u8, applyNameType(.NAME_UNDECORATE, name), import_name)) { + import_name_type = .NAME_UNDECORATE; + } else if (machine_type == .I386 and std.mem.eql(u8, applyNameType(.NAME_NOPREFIX, name), import_name)) { + import_name_type = .NAME_NOPREFIX; + } else if (isArm64EC(machine_type)) { + import_name_type = .NAME_EXPORTAS; + export_name = import_name; + } else if (std.mem.eql(u8, name, import_name)) { + import_name_type = .NAME; + } else { + try renames.append(allocator, .{ + .name = name, + .e = e, + }); + continue; + } + } else { + import_name_type = getNameType(maybe_mangled_name, e.name, machine_type, module_def.type); + } + + try regular_imports.put(allocator, applyNameType(import_name_type, name), name); + try members.list.append(arena, try getShortImport(arena, module_import_name, name, export_name, machine_type, e.ordinal, e.type, import_name_type)); + } + for (renames.items) |deferred| { + const import_name = deferred.e.import_name.?; + if (regular_imports.get(import_name)) |symbol| { + if (deferred.e.type == .CODE) { + try members.list.append(arena, try getWeakExternal(arena, module_import_name, symbol, deferred.name, .{ + .imp_prefix = false, + .machine_type = machine_type, + })); + } + try members.list.append(arena, try getWeakExternal(arena, module_import_name, symbol, deferred.name, .{ + .imp_prefix = true, + .machine_type = machine_type, + })); + } else { + try members.list.append(arena, try getShortImport( + arena, + module_import_name, + deferred.name, + deferred.e.import_name, + machine_type, + deferred.e.ordinal, + deferred.e.type, + .NAME_EXPORTAS, + )); + } + } + + return members; +} + +/// Returns a slice of `name` +fn applyNameType(name_type: std.coff.ImportNameType, name: []const u8) []const u8 { + switch (name_type) { + .NAME_NOPREFIX, .NAME_UNDECORATE => { + if (name.len == 0) return name; + const unprefixed = switch (name[0]) { + '?', '@', '_' => name[1..], + else => name, + }; + if (name_type == .NAME_UNDECORATE) { + var split = std.mem.splitScalar(u8, unprefixed, '@'); + return split.first(); + } else { + return unprefixed; + } + }, + else => return name, + } +} + +fn getNameType( + symbol: []const u8, + ext_name: []const u8, + machine_type: std.coff.IMAGE.FILE.MACHINE, + module_definition_type: def.ModuleDefinitionType, +) std.coff.ImportNameType { + // A decorated stdcall function in MSVC is exported with the + // type IMPORT_NAME, and the exported function name includes the + // the leading underscore. In MinGW on the other hand, a decorated + // stdcall function still omits the underscore (IMPORT_NAME_NOPREFIX). + if (std.mem.startsWith(u8, ext_name, "_") and + std.mem.indexOfScalar(u8, ext_name, '@') != null and + module_definition_type != .mingw) + return .NAME; + if (!std.mem.eql(u8, symbol, ext_name)) + return .NAME_UNDECORATE; + if (machine_type == .I386 and std.mem.startsWith(u8, symbol, "_")) + return .NAME_NOPREFIX; + return .NAME; +} + +fn is64Bit(machine_type: std.coff.IMAGE.FILE.MACHINE) bool { + return switch (machine_type) { + .AMD64, .ARM64, .ARM64EC, .ARM64X => true, + else => false, + }; +} + +fn isArm64EC(machine_type: std.coff.IMAGE.FILE.MACHINE) bool { + return switch (machine_type) { + .ARM64EC, .ARM64X => true, + else => false, + }; +} + +const null_import_descriptor_symbol_name = "__NULL_IMPORT_DESCRIPTOR"; +const import_descriptor_prefix = "__IMPORT_DESCRIPTOR_"; +const null_thunk_data_prefix = "\x7F"; +const null_thunk_data_suffix = "_NULL_THUNK_DATA"; + +// past the string table length field +const first_string_table_entry_offset = @sizeOf(u32); +const first_string_table_entry = getNameBytesForStringTableOffset(first_string_table_entry_offset); + +const byte_size_of_relocation = 10; + +fn getNameBytesForStringTableOffset(offset: u32) [8]u8 { + var bytes = [_]u8{0} ** 8; + std.mem.writeInt(u32, bytes[4..8], offset, .little); + return bytes; +} + +const GetImportDescriptorError = error{UnsupportedMachineType} || std.mem.Allocator.Error; + +fn getImportDescriptor( + allocator: std.mem.Allocator, + machine_type: std.coff.IMAGE.FILE.MACHINE, + module_import_name: []const u8, + import_descriptor_symbol_name: []const u8, + null_thunk_symbol_name: []const u8, +) GetImportDescriptorError!Members.Member { + const number_of_sections = 2; + const number_of_symbols = 7; + const number_of_relocations = 3; + + const pointer_to_idata2_data = @sizeOf(std.coff.Header) + + (@sizeOf(std.coff.SectionHeader) * number_of_sections); + const pointer_to_idata6_data = pointer_to_idata2_data + + @sizeOf(std.coff.ImportDirectoryEntry) + + (byte_size_of_relocation * number_of_relocations); + const pointer_to_symbol_table = pointer_to_idata6_data + + module_import_name.len + 1; + + const string_table_byte_len = 4 + + (import_descriptor_symbol_name.len + 1) + + (null_import_descriptor_symbol_name.len + 1) + + (null_thunk_symbol_name.len + 1); + const total_byte_len = pointer_to_symbol_table + + (std.coff.Symbol.sizeOf() * number_of_symbols) + + string_table_byte_len; + + const bytes = try allocator.alloc(u8, total_byte_len); + errdefer allocator.free(bytes); + var writer: std.Io.Writer = .fixed(bytes); + + writer.writeStruct(std.coff.Header{ + .machine = machine_type, + .number_of_sections = number_of_sections, + .time_date_stamp = 0, + .pointer_to_symbol_table = @intCast(pointer_to_symbol_table), + .number_of_symbols = number_of_symbols, + .size_of_optional_header = 0, + .flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) }, + }, .little) catch unreachable; + + writer.writeStruct(std.coff.SectionHeader{ + .name = ".idata$2".*, + .virtual_size = 0, + .virtual_address = 0, + .size_of_raw_data = @sizeOf(std.coff.ImportDirectoryEntry), + .pointer_to_raw_data = pointer_to_idata2_data, + .pointer_to_relocations = pointer_to_idata2_data + @sizeOf(std.coff.ImportDirectoryEntry), + .pointer_to_linenumbers = 0, + .number_of_relocations = number_of_relocations, + .number_of_linenumbers = 0, + .flags = .{ + .ALIGN = .@"4BYTES", + .CNT_INITIALIZED_DATA = true, + .MEM_WRITE = true, + .MEM_READ = true, + }, + }, .little) catch unreachable; + + writer.writeStruct(std.coff.SectionHeader{ + .name = ".idata$6".*, + .virtual_size = 0, + .virtual_address = 0, + .size_of_raw_data = @intCast(module_import_name.len + 1), + .pointer_to_raw_data = pointer_to_idata6_data, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .ALIGN = .@"2BYTES", + .CNT_INITIALIZED_DATA = true, + .MEM_WRITE = true, + .MEM_READ = true, + }, + }, .little) catch unreachable; + + // .idata$2 + writer.writeStruct(std.coff.ImportDirectoryEntry{ + .forwarder_chain = 0, + .import_address_table_rva = 0, + .import_lookup_table_rva = 0, + .name_rva = 0, + .time_date_stamp = 0, + }, .little) catch unreachable; + + const relocation_rva_type = rvaRelocationTypeIndicator(machine_type) orelse return error.UnsupportedMachineType; + writeRelocation(&writer, .{ + .virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "name_rva"), + .symbol_table_index = 2, + .type = relocation_rva_type, + }) catch unreachable; + writeRelocation(&writer, .{ + .virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "import_lookup_table_rva"), + .symbol_table_index = 3, + .type = relocation_rva_type, + }) catch unreachable; + writeRelocation(&writer, .{ + .virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "import_address_table_rva"), + .symbol_table_index = 4, + .type = relocation_rva_type, + }) catch unreachable; + + // .idata$6 + writer.writeAll(module_import_name) catch unreachable; + writer.writeByte(0) catch unreachable; + + var string_table_offset: usize = first_string_table_entry_offset; + writeSymbol(&writer, .{ + .name = first_string_table_entry, + .value = 0, + .section_number = @enumFromInt(1), + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .EXTERNAL, + .number_of_aux_symbols = 0, + }) catch unreachable; + string_table_offset += import_descriptor_symbol_name.len + 1; + writeSymbol(&writer, .{ + .name = ".idata$2".*, + .value = 0, + .section_number = @enumFromInt(1), + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .SECTION, + .number_of_aux_symbols = 0, + }) catch unreachable; + writeSymbol(&writer, .{ + .name = ".idata$6".*, + .value = 0, + .section_number = @enumFromInt(2), + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .STATIC, + .number_of_aux_symbols = 0, + }) catch unreachable; + writeSymbol(&writer, .{ + .name = ".idata$4".*, + .value = 0, + .section_number = .UNDEFINED, + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .SECTION, + .number_of_aux_symbols = 0, + }) catch unreachable; + writeSymbol(&writer, .{ + .name = ".idata$5".*, + .value = 0, + .section_number = .UNDEFINED, + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .SECTION, + .number_of_aux_symbols = 0, + }) catch unreachable; + writeSymbol(&writer, .{ + .name = getNameBytesForStringTableOffset(@intCast(string_table_offset)), + .value = 0, + .section_number = .UNDEFINED, + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .EXTERNAL, + .number_of_aux_symbols = 0, + }) catch unreachable; + string_table_offset += null_import_descriptor_symbol_name.len + 1; + writeSymbol(&writer, .{ + .name = getNameBytesForStringTableOffset(@intCast(string_table_offset)), + .value = 0, + .section_number = .UNDEFINED, + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .EXTERNAL, + .number_of_aux_symbols = 0, + }) catch unreachable; + string_table_offset += null_thunk_symbol_name.len + 1; + + // string table + writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable; + writer.writeAll(import_descriptor_symbol_name) catch unreachable; + writer.writeByte(0) catch unreachable; + writer.writeAll(null_import_descriptor_symbol_name) catch unreachable; + writer.writeByte(0) catch unreachable; + writer.writeAll(null_thunk_symbol_name) catch unreachable; + writer.writeByte(0) catch unreachable; + + var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1); + errdefer allocator.free(symbol_names_for_import_lib); + + const duped_symbol_name = try allocator.dupe(u8, import_descriptor_symbol_name); + errdefer allocator.free(duped_symbol_name); + symbol_names_for_import_lib[0] = duped_symbol_name; + + // Confirm byte length was calculated exactly correctly + std.debug.assert(writer.end == bytes.len); + return .{ + .bytes = bytes, + .name = module_import_name, + .symbol_names_for_import_lib = symbol_names_for_import_lib, + }; +} + +fn getNullImportDescriptor( + allocator: std.mem.Allocator, + machine_type: std.coff.IMAGE.FILE.MACHINE, + module_import_name: []const u8, +) error{OutOfMemory}!Members.Member { + const number_of_sections = 1; + const number_of_symbols = 1; + const pointer_to_idata3_data = @sizeOf(std.coff.Header) + + (@sizeOf(std.coff.SectionHeader) * number_of_sections); + const pointer_to_symbol_table = pointer_to_idata3_data + + @sizeOf(std.coff.ImportDirectoryEntry); + + const string_table_byte_len = 4 + null_import_descriptor_symbol_name.len + 1; + const total_byte_len = pointer_to_symbol_table + + (std.coff.Symbol.sizeOf() * number_of_symbols) + + string_table_byte_len; + + const bytes = try allocator.alloc(u8, total_byte_len); + errdefer allocator.free(bytes); + var writer: std.Io.Writer = .fixed(bytes); + + writer.writeStruct(std.coff.Header{ + .machine = machine_type, + .number_of_sections = number_of_sections, + .time_date_stamp = 0, + .pointer_to_symbol_table = @intCast(pointer_to_symbol_table), + .number_of_symbols = number_of_symbols, + .size_of_optional_header = 0, + .flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) }, + }, .little) catch unreachable; + + writer.writeStruct(std.coff.SectionHeader{ + .name = ".idata$3".*, + .virtual_size = 0, + .virtual_address = 0, + .size_of_raw_data = @sizeOf(std.coff.ImportDirectoryEntry), + .pointer_to_raw_data = pointer_to_idata3_data, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .ALIGN = .@"4BYTES", + .CNT_INITIALIZED_DATA = true, + .MEM_WRITE = true, + .MEM_READ = true, + }, + }, .little) catch unreachable; + + writer.writeStruct(std.coff.ImportDirectoryEntry{ + .forwarder_chain = 0, + .import_address_table_rva = 0, + .import_lookup_table_rva = 0, + .name_rva = 0, + .time_date_stamp = 0, + }, .little) catch unreachable; + + writeSymbol(&writer, .{ + .name = first_string_table_entry, + .value = 0, + .section_number = @enumFromInt(1), + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .EXTERNAL, + .number_of_aux_symbols = 0, + }) catch unreachable; + + // string table + writer.writeInt(u32, string_table_byte_len, .little) catch unreachable; + writer.writeAll(null_import_descriptor_symbol_name) catch unreachable; + writer.writeByte(0) catch unreachable; + + var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1); + errdefer allocator.free(symbol_names_for_import_lib); + + const duped_symbol_name = try allocator.dupe(u8, null_import_descriptor_symbol_name); + errdefer allocator.free(duped_symbol_name); + symbol_names_for_import_lib[0] = duped_symbol_name; + + // Confirm byte length was calculated exactly correctly + std.debug.assert(writer.end == bytes.len); + return .{ + .bytes = bytes, + .name = module_import_name, + .symbol_names_for_import_lib = symbol_names_for_import_lib, + }; +} + +fn getNullThunk( + allocator: std.mem.Allocator, + machine_type: std.coff.IMAGE.FILE.MACHINE, + module_import_name: []const u8, + null_thunk_symbol_name: []const u8, +) error{OutOfMemory}!Members.Member { + const number_of_sections = 2; + const number_of_symbols = 1; + const va_size: u32 = if (is64Bit(machine_type)) 8 else 4; + const pointer_to_idata5_data = @sizeOf(std.coff.Header) + + (@sizeOf(std.coff.SectionHeader) * number_of_sections); + const pointer_to_idata4_data = pointer_to_idata5_data + va_size; + const pointer_to_symbol_table = pointer_to_idata4_data + va_size; + + const string_table_byte_len = 4 + null_thunk_symbol_name.len + 1; + const total_byte_len = pointer_to_symbol_table + + (std.coff.Symbol.sizeOf() * number_of_symbols) + + string_table_byte_len; + + const bytes = try allocator.alloc(u8, total_byte_len); + errdefer allocator.free(bytes); + var writer: std.Io.Writer = .fixed(bytes); + + writer.writeStruct(std.coff.Header{ + .machine = machine_type, + .number_of_sections = number_of_sections, + .time_date_stamp = 0, + .pointer_to_symbol_table = @intCast(pointer_to_symbol_table), + .number_of_symbols = number_of_symbols, + .size_of_optional_header = 0, + .flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) }, + }, .little) catch unreachable; + + writer.writeStruct(std.coff.SectionHeader{ + .name = ".idata$5".*, + .virtual_size = 0, + .virtual_address = 0, + .size_of_raw_data = va_size, + .pointer_to_raw_data = pointer_to_idata5_data, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .ALIGN = if (is64Bit(machine_type)) + .@"8BYTES" + else + .@"4BYTES", + .CNT_INITIALIZED_DATA = true, + .MEM_WRITE = true, + .MEM_READ = true, + }, + }, .little) catch unreachable; + + writer.writeStruct(std.coff.SectionHeader{ + .name = ".idata$4".*, + .virtual_size = 0, + .virtual_address = 0, + .size_of_raw_data = va_size, + .pointer_to_raw_data = pointer_to_idata4_data, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .ALIGN = if (is64Bit(machine_type)) + .@"8BYTES" + else + .@"4BYTES", + .CNT_INITIALIZED_DATA = true, + .MEM_WRITE = true, + .MEM_READ = true, + }, + }, .little) catch unreachable; + + // .idata$5 + writer.splatByteAll(0, va_size) catch unreachable; + // .idata$4 + writer.splatByteAll(0, va_size) catch unreachable; + + writeSymbol(&writer, .{ + .name = first_string_table_entry, + .value = 0, + .section_number = @enumFromInt(1), + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .EXTERNAL, + .number_of_aux_symbols = 0, + }) catch unreachable; + + // string table + writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable; + writer.writeAll(null_thunk_symbol_name) catch unreachable; + writer.writeByte(0) catch unreachable; + + var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1); + errdefer allocator.free(symbol_names_for_import_lib); + + const duped_symbol_name = try allocator.dupe(u8, null_thunk_symbol_name); + errdefer allocator.free(duped_symbol_name); + symbol_names_for_import_lib[0] = duped_symbol_name; + + // Confirm byte length was calculated exactly correctly + std.debug.assert(writer.end == bytes.len); + return .{ + .bytes = bytes, + .name = module_import_name, + .symbol_names_for_import_lib = symbol_names_for_import_lib, + }; +} + +const WeakExternalOptions = struct { + imp_prefix: bool, + machine_type: std.coff.IMAGE.FILE.MACHINE, +}; + +fn getWeakExternal( + arena: std.mem.Allocator, + module_import_name: []const u8, + sym: []const u8, + weak: []const u8, + options: WeakExternalOptions, +) error{OutOfMemory}!Members.Member { + const number_of_sections = 1; + const number_of_symbols = 4; + const number_of_weak_external_defs = 1; + const pointer_to_symbol_table = @sizeOf(std.coff.Header) + + (@sizeOf(std.coff.SectionHeader) * number_of_sections); + + const symbol_names = try arena.alloc([]const u8, 2); + + symbol_names[0] = if (options.imp_prefix) + try std.mem.concat(arena, u8, &.{ "__imp_", sym }) + else + try arena.dupe(u8, sym); + + symbol_names[1] = if (options.imp_prefix) + try std.mem.concat(arena, u8, &.{ "__imp_", weak }) + else + try arena.dupe(u8, weak); + + const string_table_byte_len = 4 + symbol_names[0].len + 1 + symbol_names[1].len + 1; + const total_byte_len = pointer_to_symbol_table + + (std.coff.Symbol.sizeOf() * number_of_symbols) + + (std.coff.WeakExternalDefinition.sizeOf() * number_of_weak_external_defs) + + string_table_byte_len; + + const bytes = try arena.alloc(u8, total_byte_len); + errdefer arena.free(bytes); + var writer: std.Io.Writer = .fixed(bytes); + + writer.writeStruct(std.coff.Header{ + .machine = options.machine_type, + .number_of_sections = number_of_sections, + .time_date_stamp = 0, + .pointer_to_symbol_table = @intCast(pointer_to_symbol_table), + .number_of_symbols = number_of_symbols + number_of_weak_external_defs, + .size_of_optional_header = 0, + .flags = .{}, + }, .little) catch unreachable; + + writer.writeStruct(std.coff.SectionHeader{ + .name = ".drectve".*, + .virtual_size = 0, + .virtual_address = 0, + .size_of_raw_data = 0, + .pointer_to_raw_data = 0, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .LNK_INFO = true, + .LNK_REMOVE = true, + }, + }, .little) catch unreachable; + + writeSymbol(&writer, .{ + .name = "@comp.id".*, + .value = 0, + .section_number = .ABSOLUTE, + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .STATIC, + .number_of_aux_symbols = 0, + }) catch unreachable; + writeSymbol(&writer, .{ + .name = "@feat.00".*, + .value = 0, + .section_number = .ABSOLUTE, + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .STATIC, + .number_of_aux_symbols = 0, + }) catch unreachable; + var string_table_offset: usize = first_string_table_entry_offset; + writeSymbol(&writer, .{ + .name = first_string_table_entry, + .value = 0, + .section_number = @enumFromInt(0), + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .EXTERNAL, + .number_of_aux_symbols = 0, + }) catch unreachable; + string_table_offset += symbol_names[0].len + 1; + writeSymbol(&writer, .{ + .name = getNameBytesForStringTableOffset(@intCast(string_table_offset)), + .value = 0, + .section_number = @enumFromInt(0), + .type = .{ + .base_type = .NULL, + .complex_type = .NULL, + }, + .storage_class = .WEAK_EXTERNAL, + .number_of_aux_symbols = 1, + }) catch unreachable; + writeWeakExternalDefinition(&writer, .{ + .tag_index = 2, + .flag = .SEARCH_ALIAS, + .unused = @splat(0), + }) catch unreachable; + + // string table + writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable; + writer.writeAll(symbol_names[0]) catch unreachable; + writer.writeByte(0) catch unreachable; + writer.writeAll(symbol_names[1]) catch unreachable; + writer.writeByte(0) catch unreachable; + + // Confirm byte length was calculated exactly correctly + std.debug.assert(writer.end == bytes.len); + return .{ + .bytes = bytes, + .name = module_import_name, + .symbol_names_for_import_lib = symbol_names, + }; +} + +const GetShortImportError = error{UnknownImportType} || std.mem.Allocator.Error; + +fn getShortImport( + arena: std.mem.Allocator, + module_import_name: []const u8, + sym: []const u8, + export_name: ?[]const u8, + machine_type: std.coff.IMAGE.FILE.MACHINE, + ordinal_hint: u16, + import_type: std.coff.ImportType, + name_type: std.coff.ImportNameType, +) GetShortImportError!Members.Member { + var size_of_data = module_import_name.len + 1 + sym.len + 1; + if (export_name) |name| size_of_data += name.len + 1; + const total_byte_len = @sizeOf(std.coff.ImportHeader) + size_of_data; + + const bytes = try arena.alloc(u8, total_byte_len); + errdefer arena.free(bytes); + var writer = std.Io.Writer.fixed(bytes); + + writer.writeStruct(std.coff.ImportHeader{ + .version = 0, + .machine = machine_type, + .time_date_stamp = 0, + .size_of_data = @intCast(size_of_data), + .hint = ordinal_hint, + .types = .{ + .type = import_type, + .name_type = name_type, + .reserved = 0, + }, + }, .little) catch unreachable; + + writer.writeAll(sym) catch unreachable; + writer.writeByte(0) catch unreachable; + writer.writeAll(module_import_name) catch unreachable; + writer.writeByte(0) catch unreachable; + if (export_name) |name| { + writer.writeAll(name) catch unreachable; + writer.writeByte(0) catch unreachable; + } + + var symbol_names_for_import_lib: std.ArrayList([]const u8) = try .initCapacity(arena, 2); + + switch (import_type) { + .CODE, .CONST => { + symbol_names_for_import_lib.appendAssumeCapacity(try std.mem.concat(arena, u8, &.{ "__imp_", sym })); + symbol_names_for_import_lib.appendAssumeCapacity(try arena.dupe(u8, sym)); + }, + .DATA => { + symbol_names_for_import_lib.appendAssumeCapacity(try std.mem.concat(arena, u8, &.{ "__imp_", sym })); + }, + else => return error.UnknownImportType, + } + + // Confirm byte length was calculated exactly correctly + std.debug.assert(writer.end == bytes.len); + return .{ + .bytes = bytes, + .name = module_import_name, + .symbol_names_for_import_lib = try symbol_names_for_import_lib.toOwnedSlice(arena), + }; +} + +fn writeSymbol(writer: *std.Io.Writer, symbol: std.coff.Symbol) !void { + try writer.writeAll(&symbol.name); + try writer.writeInt(u32, symbol.value, .little); + try writer.writeInt(u16, @intFromEnum(symbol.section_number), .little); + try writer.writeInt(u8, @intFromEnum(symbol.type.base_type), .little); + try writer.writeInt(u8, @intFromEnum(symbol.type.complex_type), .little); + try writer.writeInt(u8, @intFromEnum(symbol.storage_class), .little); + try writer.writeInt(u8, symbol.number_of_aux_symbols, .little); +} + +fn writeWeakExternalDefinition(writer: *std.Io.Writer, weak_external: std.coff.WeakExternalDefinition) !void { + try writer.writeInt(u32, weak_external.tag_index, .little); + try writer.writeInt(u32, @intFromEnum(weak_external.flag), .little); + try writer.writeAll(&weak_external.unused); +} + +fn writeRelocation(writer: *std.Io.Writer, relocation: std.coff.Relocation) !void { + try writer.writeInt(u32, relocation.virtual_address, .little); + try writer.writeInt(u32, relocation.symbol_table_index, .little); + try writer.writeInt(u16, relocation.type, .little); +} + +// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#type-indicators +pub fn rvaRelocationTypeIndicator(target: std.coff.IMAGE.FILE.MACHINE) ?u16 { + return switch (target) { + .AMD64 => @intFromEnum(std.coff.IMAGE.REL.AMD64.ADDR32NB), + .I386 => @intFromEnum(std.coff.IMAGE.REL.I386.DIR32NB), + .ARMNT => @intFromEnum(std.coff.IMAGE.REL.ARM.ADDR32NB), + .ARM64, .ARM64EC, .ARM64X => @intFromEnum(std.coff.IMAGE.REL.ARM64.ADDR32NB), + .IA64 => @intFromEnum(std.coff.IMAGE.REL.IA64.DIR32NB), + else => null, + }; +} + +const StringTable = struct { + data: std.ArrayList(u8) = .empty, + map: std.HashMapUnmanaged(u32, void, std.hash_map.StringIndexContext, std.hash_map.default_max_load_percentage) = .empty, + + pub fn deinit(self: *StringTable, allocator: Allocator) void { + self.data.deinit(allocator); + self.map.deinit(allocator); + } + + pub fn put(self: *StringTable, allocator: Allocator, value: []const u8) !u32 { + const result = try self.map.getOrPutContextAdapted( + allocator, + value, + std.hash_map.StringIndexAdapter{ .bytes = &self.data }, + .{ .bytes = &self.data }, + ); + if (result.found_existing) { + return result.key_ptr.*; + } + + try self.data.ensureUnusedCapacity(allocator, value.len + 1); + const offset: u32 = @intCast(self.data.items.len); + + self.data.appendSliceAssumeCapacity(value); + self.data.appendAssumeCapacity(0); + + result.key_ptr.* = offset; + + return offset; + } + + pub fn get(self: StringTable, offset: u32) []const u8 { + std.debug.assert(offset < self.data.items.len); + return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(self.data.items.ptr + offset)), 0); + } + + pub fn getOffset(self: *StringTable, value: []const u8) ?u32 { + return self.map.getKeyAdapted( + value, + std.hash_map.StringIndexAdapter{ .bytes = &self.data }, + ); + } +}; diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index 63663518c6be..198f8ead154b 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -39,9 +39,6 @@ #include #include #include -#include -#include -#include #include #include #include @@ -475,62 +472,6 @@ void ZigLLVMParseCommandLineOptions(size_t argc, const char *const *argv) { cl::ParseCommandLineOptions(argc, argv); } -bool ZigLLVMWriteImportLibrary(const char *def_path, unsigned int coff_machine, - const char *output_lib_path, bool kill_at) -{ - COFF::MachineTypes machine = static_cast(coff_machine); - - auto bufOrErr = MemoryBuffer::getFile(def_path); - if (!bufOrErr) { - return false; - } - - MemoryBuffer& buf = *bufOrErr.get(); - Expected def = - object::parseCOFFModuleDefinition(buf, machine, /* MingwDef */ true); - - if (!def) { - return true; - } - - // The exports-juggling code below is ripped from LLVM's DlltoolDriver.cpp - - // If ExtName is set (if the "ExtName = Name" syntax was used), overwrite - // Name with ExtName and clear ExtName. When only creating an import - // library and not linking, the internal name is irrelevant. This avoids - // cases where writeImportLibrary tries to transplant decoration from - // symbol decoration onto ExtName. - for (object::COFFShortExport& E : def->Exports) { - if (!E.ExtName.empty()) { - E.Name = E.ExtName; - E.ExtName.clear(); - } - } - - if (kill_at) { - for (object::COFFShortExport& E : def->Exports) { - if (!E.ImportName.empty() || (!E.Name.empty() && E.Name[0] == '?')) - continue; - if (machine == COFF::IMAGE_FILE_MACHINE_I386) { - // By making sure E.SymbolName != E.Name for decorated symbols, - // writeImportLibrary writes these symbols with the type - // IMPORT_NAME_UNDECORATE. - E.SymbolName = E.Name; - } - // Trim off the trailing decoration. Symbols will always have a - // starting prefix here (either _ for cdecl/stdcall, @ for fastcall - // or ? for C++ functions). Vectorcall functions won't have any - // fixed prefix, but the function base name will still be at least - // one char. - E.Name = E.Name.substr(0, E.Name.find('@', 1)); - } - } - - return static_cast( - object::writeImportLibrary(def->OutputFile, output_lib_path, - def->Exports, machine, /* MinGW */ true)); -} - bool ZigLLVMWriteArchive(const char *archive_name, const char **file_names, size_t file_name_count, ZigLLVMArchiveKind archive_kind) { diff --git a/src/zig_llvm.h b/src/zig_llvm.h index f1940bb3408f..64da388d477b 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -124,7 +124,4 @@ ZIG_EXTERN_C bool ZigLLDLinkWasm(int argc, const char **argv, bool can_exit_earl ZIG_EXTERN_C bool ZigLLVMWriteArchive(const char *archive_name, const char **file_names, size_t file_name_count, ZigLLVMArchiveKind archive_kind); -ZIG_EXTERN_C bool ZigLLVMWriteImportLibrary(const char *def_path, unsigned int coff_machine, - const char *output_lib_path, bool kill_at); - #endif