Commit e393543e63
Changed files (6)
src
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;
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 => "<invalid>",
+ .eof => "<eof>",
+ .identifier => "<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 `<ext_name> = <name>`, 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 '<eof>', 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;
+}
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 = "!<arch>\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 },
+ );
+ }
+};
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| {
src/zig_llvm.cpp
@@ -39,9 +39,6 @@
#include <llvm/Passes/StandardInstrumentations.h>
#include <llvm/Object/Archive.h>
#include <llvm/Object/ArchiveWriter.h>
-#include <llvm/Object/COFF.h>
-#include <llvm/Object/COFFImportFile.h>
-#include <llvm/Object/COFFModuleDefinition.h>
#include <llvm/PassRegistry.h>
#include <llvm/Support/CommandLine.h>
#include <llvm/Support/FileSystem.h>
@@ -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::MachineTypes>(coff_machine);
-
- auto bufOrErr = MemoryBuffer::getFile(def_path);
- if (!bufOrErr) {
- return false;
- }
-
- MemoryBuffer& buf = *bufOrErr.get();
- Expected<object::COFFModuleDefinition> 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<bool>(
- 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)
{
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