Support generating import libraries from mingw .def files without LLVM

For the supported COFF machine types of X64 (x86_64), I386 (x86), ARMNT (thumb), and ARM64 (aarch64), this new Zig implementation results in byte-for-byte identical .lib files when compared to the previous LLVM-backed implementation.
This commit is contained in:
Ryan Liptak 2025-09-30 22:06:36 -07:00
parent 900315a3f3
commit e393543e63
6 changed files with 2210 additions and 98 deletions

View file

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

View file

@ -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| {

1079
src/libs/mingw/def.zig Normal file

File diff suppressed because it is too large Load diff

1088
src/libs/mingw/implib.zig Normal file

File diff suppressed because it is too large Load diff

View file

@ -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)
{

View file

@ -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