authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-06 13:38:07-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-10-06 13:38:07-07:00
log6893e7feeed8b528d0a03d3491f4254bbcf5c7ce
treed96740ee0abb3546c825906d576ab5bb7a0ef78e
parent9bfd4c3017aa8ecc5e67e6432810af7457b51c0f
parente393543e630e29cda88737c85fa0aa19cc40f096
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25414 from squeek502/mingw-def-implib

Support generating import libraries from mingw .def files without LLVM

7 files changed, 2231 insertions(+), 105 deletions(-)

lib/std/coff.zig+21-7
...@@ -528,13 +528,11 @@ pub const SectionHeader = extern struct {...@@ -528,13 +528,11 @@ pub const SectionHeader = extern struct {
528528
529 /// Applicable only to section headers in COFF objects.529 /// Applicable only to section headers in COFF objects.
530 pub fn getAlignment(self: SectionHeader) ?u16 {530 pub fn getAlignment(self: SectionHeader) ?u16 {
531 if (self.flags.ALIGN == 0) return null;531 return self.flags.ALIGN.toByteUnits();
532 return std.math.powi(u16, 2, self.flags.ALIGN - 1) catch unreachable;
533 }532 }
534533
535 pub fn setAlignment(self: *SectionHeader, new_alignment: u16) void {534 pub fn setAlignment(self: *SectionHeader, new_alignment: u16) void {
536 assert(new_alignment > 0 and new_alignment <= 8192);535 self.flags.ALIGN = .fromByteUnits(new_alignment);
537 self.flags.ALIGN = @intCast(std.math.log2(new_alignment));
538 }536 }
539537
540 pub fn isCode(self: SectionHeader) bool {538 pub fn isCode(self: SectionHeader) bool {
...@@ -651,6 +649,16 @@ pub const SectionHeader = extern struct {...@@ -651,6 +649,16 @@ pub const SectionHeader = extern struct {
651 @"4096BYTES" = 13,649 @"4096BYTES" = 13,
652 @"8192BYTES" = 14,650 @"8192BYTES" = 14,
653 _,651 _,
652
653 pub fn toByteUnits(a: Align) ?u16 {
654 if (a == .NONE) return null;
655 return @as(u16, 1) << (@intFromEnum(a) - 1);
656 }
657
658 pub fn fromByteUnits(n: u16) Align {
659 std.debug.assert(std.math.isPowerOfTwo(n));
660 return @enumFromInt(@ctz(n) + 1);
661 }
654 };662 };
655 };663 };
656};664};
...@@ -925,6 +933,10 @@ pub const WeakExternalDefinition = struct {...@@ -925,6 +933,10 @@ pub const WeakExternalDefinition = struct {
925 flag: WeakExternalFlag,933 flag: WeakExternalFlag,
926934
927 unused: [10]u8,935 unused: [10]u8,
936
937 pub fn sizeOf() usize {
938 return 18;
939 }
928};940};
929941
930// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/km/ntimage.h942// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/km/ntimage.h
...@@ -1338,14 +1350,16 @@ pub const Strtab = struct {...@@ -1338,14 +1350,16 @@ pub const Strtab = struct {
1338};1350};
13391351
1340pub const ImportHeader = extern struct {1352pub const ImportHeader = extern struct {
1341 sig1: IMAGE.FILE.MACHINE,1353 /// Must be IMAGE_FILE_MACHINE_UNKNOWN
1342 sig2: u16,1354 sig1: IMAGE.FILE.MACHINE = .UNKNOWN,
1355 /// Must be 0xFFFF
1356 sig2: u16 = 0xFFFF,
1343 version: u16,1357 version: u16,
1344 machine: IMAGE.FILE.MACHINE,1358 machine: IMAGE.FILE.MACHINE,
1345 time_date_stamp: u32,1359 time_date_stamp: u32,
1346 size_of_data: u32,1360 size_of_data: u32,
1347 hint: u16,1361 hint: u16,
1348 types: packed struct(u32) {1362 types: packed struct(u16) {
1349 type: ImportType,1363 type: ImportType,
1350 name_type: ImportNameType,1364 name_type: ImportNameType,
1351 reserved: u11,1365 reserved: u11,
src/codegen/llvm/bindings.zig-8
...@@ -338,14 +338,6 @@ extern fn ZigLLVMWriteArchive(...@@ -338,14 +338,6 @@ extern fn ZigLLVMWriteArchive(
338pub const ParseCommandLineOptions = ZigLLVMParseCommandLineOptions;338pub const ParseCommandLineOptions = ZigLLVMParseCommandLineOptions;
339extern fn ZigLLVMParseCommandLineOptions(argc: usize, argv: [*]const [*:0]const u8) void;339extern fn ZigLLVMParseCommandLineOptions(argc: usize, argv: [*]const [*:0]const u8) void;
340340
341pub const WriteImportLibrary = ZigLLVMWriteImportLibrary;
342extern fn ZigLLVMWriteImportLibrary(
343 def_path: [*:0]const u8,
344 coff_machine: c_uint,
345 output_lib_path: [*:0]const u8,
346 kill_at: bool,
347) bool;
348
349pub const GetHostCPUName = LLVMGetHostCPUName;341pub const GetHostCPUName = LLVMGetHostCPUName;
350extern fn LLVMGetHostCPUName() ?[*:0]u8;342extern fn LLVMGetHostCPUName() ?[*:0]u8;
351343
src/libs/mingw.zig+43-28
...@@ -10,6 +10,13 @@ const Compilation = @import("../Compilation.zig");...@@ -10,6 +10,13 @@ const Compilation = @import("../Compilation.zig");
10const build_options = @import("build_options");10const build_options = @import("build_options");
11const Cache = std.Build.Cache;11const Cache = std.Build.Cache;
12const dev = @import("../dev.zig");12const dev = @import("../dev.zig");
13const def = @import("mingw/def.zig");
14const implib = @import("mingw/implib.zig");
15
16test {
17 _ = def;
18 _ = implib;
19}
1320
14pub const CrtFile = enum {21pub const CrtFile = enum {
15 crt2_o,22 crt2_o,
...@@ -290,11 +297,6 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -290,11 +297,6 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
290 var o_dir = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{});297 var o_dir = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{});
291 defer o_dir.close();298 defer o_dir.close();
292299
293 const final_def_basename = try std.fmt.allocPrint(arena, "{s}.def", .{lib_name});
294 const def_final_path = try comp.dirs.global_cache.join(arena, &[_][]const u8{
295 "o", &digest, final_def_basename,
296 });
297
298 const aro = @import("aro");300 const aro = @import("aro");
299 var diagnostics: aro.Diagnostics = .{301 var diagnostics: aro.Diagnostics = .{
300 .output = .{ .to_list = .{ .arena = .init(gpa) } },302 .output = .{ .to_list = .{ .arena = .init(gpa) } },
...@@ -312,7 +314,6 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -312,7 +314,6 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
312 defer std.debug.unlockStderrWriter();314 defer std.debug.unlockStderrWriter();
313 nosuspend stderr.print("def file: {s}\n", .{def_file_path}) catch break :print;315 nosuspend stderr.print("def file: {s}\n", .{def_file_path}) catch break :print;
314 nosuspend stderr.print("include dir: {s}\n", .{include_dir}) catch break :print;316 nosuspend stderr.print("include dir: {s}\n", .{include_dir}) catch break :print;
315 nosuspend stderr.print("output path: {s}\n", .{def_final_path}) catch break :print;
316 }317 }
317318
318 try aro_comp.include_dirs.append(gpa, include_dir);319 try aro_comp.include_dirs.append(gpa, include_dir);
...@@ -339,32 +340,46 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -339,32 +340,46 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
339 }340 }
340 }341 }
341342
342 {343 const members = members: {
343 // new scope to ensure definition file is written before passing the path to WriteImportLibrary344 var aw: std.Io.Writer.Allocating = .init(gpa);
344 const def_final_file = try o_dir.createFile(final_def_basename, .{ .truncate = true });345 errdefer aw.deinit();
345 defer def_final_file.close();346 try pp.prettyPrintTokens(&aw.writer, .result_only);
346 var buffer: [1024]u8 = undefined;347
347 var file_writer = def_final_file.writer(&buffer);348 const input = try aw.toOwnedSliceSentinel(0);
348 try pp.prettyPrintTokens(&file_writer.interface, .result_only);349 defer gpa.free(input);
349 try file_writer.interface.flush();350
350 }351 const machine_type = target.toCoffMachine();
352 var def_diagnostics: def.Diagnostics = undefined;
353 var module_def = def.parse(gpa, input, machine_type, .mingw, &def_diagnostics) catch |err| switch (err) {
354 error.OutOfMemory => |e| return e,
355 error.ParseError => {
356 var buffer: [64]u8 = undefined;
357 const w = std.debug.lockStderrWriter(&buffer);
358 defer std.debug.unlockStderrWriter();
359 try w.writeAll("error: ");
360 try def_diagnostics.writeMsg(w, input);
361 try w.writeByte('\n');
362 return error.WritingImportLibFailed;
363 },
364 };
365 defer module_def.deinit();
366
367 module_def.fixupForImportLibraryGeneration(machine_type);
368
369 break :members try implib.getMembers(gpa, module_def, machine_type);
370 };
371 defer members.deinit();
351372
352 const lib_final_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename });373 const lib_final_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename });
353 errdefer gpa.free(lib_final_path);374 errdefer gpa.free(lib_final_path);
354375
355 if (!build_options.have_llvm) return error.ZigCompilerNotBuiltWithLLVMExtensions;376 {
356 const llvm_bindings = @import("../codegen/llvm/bindings.zig");377 const lib_final_file = try o_dir.createFile(final_lib_basename, .{ .truncate = true });
357 const def_final_path_z = try arena.dupeZ(u8, def_final_path);378 defer lib_final_file.close();
358 const lib_final_path_z = try comp.dirs.global_cache.joinZ(arena, &.{lib_final_path});379 var buffer: [1024]u8 = undefined;
359 if (llvm_bindings.WriteImportLibrary(380 var file_writer = lib_final_file.writer(&buffer);
360 def_final_path_z.ptr,381 try implib.writeCoffArchive(gpa, &file_writer.interface, members);
361 @intFromEnum(target.toCoffMachine()),382 try file_writer.interface.flush();
362 lib_final_path_z.ptr,
363 true,
364 )) {
365 // TODO surface a proper error here
366 log.err("unable to turn {s}.def into {s}.lib", .{ lib_name, lib_name });
367 return error.WritingImportLibFailed;
368 }383 }
369384
370 man.writeManifest() catch |err| {385 man.writeManifest() catch |err| {
src/libs/mingw/def.zig created+1079
...@@ -0,0 +1,1079 @@
1const std = @import("std");
2
3pub const ModuleDefinitionType = enum {
4 mingw,
5};
6
7pub const ModuleDefinition = struct {
8 exports: std.ArrayList(Export) = .empty,
9 name: ?[]const u8 = null,
10 base_address: usize = 0,
11 arena: std.heap.ArenaAllocator,
12 type: ModuleDefinitionType,
13
14 pub const Export = struct {
15 /// This may lack mangling, such as underscore prefixing and stdcall suffixing.
16 /// In a .def file, this is `foo` in `foo` or `bar` in `foo = bar`.
17 name: []const u8,
18 /// Note: This is currently only set by `fixupForImportLibraryGeneration`
19 mangled_symbol_name: ?[]const u8,
20 /// The external, exported name.
21 /// In a .def file, this is `foo` in `foo = bar`.
22 ext_name: ?[]const u8,
23 /// In a .def file, this is `bar` in `foo == bar`.
24 import_name: ?[]const u8,
25 /// In a .def file, this is `bar` in `foo EXPORTAS bar`.
26 export_as: ?[]const u8,
27 no_name: bool,
28 ordinal: u16,
29 type: std.coff.ImportType,
30 private: bool,
31 };
32
33 /// Modifies `exports` such that import library generation will
34 /// behave as expected. Based on LLVM's dlltool driver.
35 pub fn fixupForImportLibraryGeneration(self: *ModuleDefinition, machine_type: std.coff.IMAGE.FILE.MACHINE) void {
36 const kill_at = true;
37 for (self.exports.items) |*e| {
38 // If ExtName is set (if the "ExtName = Name" syntax was used), overwrite
39 // Name with ExtName and clear ExtName. When only creating an import
40 // library and not linking, the internal name is irrelevant. This avoids
41 // cases where writeImportLibrary tries to transplant decoration from
42 // symbol decoration onto ExtName.
43 if (e.ext_name) |ext_name| {
44 e.name = ext_name;
45 e.ext_name = null;
46 }
47
48 if (kill_at) {
49 if (e.import_name != null or std.mem.startsWith(u8, e.name, "?"))
50 continue;
51
52 if (machine_type == .I386) {
53 // By making sure E.SymbolName != E.Name for decorated symbols,
54 // writeImportLibrary writes these symbols with the type
55 // IMPORT_NAME_UNDECORATE.
56 e.mangled_symbol_name = e.name;
57 }
58 // Trim off the trailing decoration. Symbols will always have a
59 // starting prefix here (either _ for cdecl/stdcall, @ for fastcall
60 // or ? for C++ functions). Vectorcall functions won't have any
61 // fixed prefix, but the function base name will still be at least
62 // one char.
63 const name_len_without_at_suffix = std.mem.indexOfScalarPos(u8, e.name, 1, '@') orelse e.name.len;
64 e.name = e.name[0..name_len_without_at_suffix];
65 }
66 }
67 }
68
69 pub fn deinit(self: *const ModuleDefinition) void {
70 self.arena.deinit();
71 }
72};
73
74pub const Diagnostics = struct {
75 err: Error,
76 token: Token,
77 extra: Extra = .{ .none = {} },
78
79 pub const Extra = union {
80 none: void,
81 expected: Token.Tag,
82 };
83
84 pub const Error = enum {
85 invalid_byte,
86 unfinished_quoted_identifier,
87 /// `expected` is populated
88 expected_token,
89 expected_integer,
90 unknown_statement,
91 unimplemented,
92 };
93
94 fn formatToken(ctx: TokenFormatContext, writer: *std.Io.Writer) std.Io.Writer.Error!void {
95 switch (ctx.token.tag) {
96 .eof, .invalid => return writer.writeAll(ctx.token.tag.nameForErrorDisplay()),
97 else => return writer.writeAll(ctx.token.slice(ctx.source)),
98 }
99 }
100
101 const TokenFormatContext = struct {
102 token: Token,
103 source: []const u8,
104 };
105
106 fn fmtToken(self: Diagnostics, source: []const u8) std.fmt.Alt(TokenFormatContext, formatToken) {
107 return .{ .data = .{
108 .token = self.token,
109 .source = source,
110 } };
111 }
112
113 pub fn writeMsg(self: Diagnostics, writer: *std.Io.Writer, source: []const u8) !void {
114 switch (self.err) {
115 .invalid_byte => {
116 return writer.print("invalid byte '{f}'", .{std.ascii.hexEscape(self.token.slice(source), .upper)});
117 },
118 .unfinished_quoted_identifier => {
119 return writer.print("unfinished quoted identifier at '{f}', expected closing '\"'", .{self.fmtToken(source)});
120 },
121 .expected_token => {
122 return writer.print("expected '{s}', got '{f}'", .{ self.extra.expected.nameForErrorDisplay(), self.fmtToken(source) });
123 },
124 .expected_integer => {
125 return writer.print("expected integer, got '{f}'", .{self.fmtToken(source)});
126 },
127 .unimplemented => {
128 return writer.print("support for '{f}' has not yet been implemented", .{self.fmtToken(source)});
129 },
130 .unknown_statement => {
131 return writer.print("unknown/invalid statement syntax beginning with '{f}'", .{self.fmtToken(source)});
132 },
133 }
134 }
135};
136
137pub fn parse(
138 allocator: std.mem.Allocator,
139 source: [:0]const u8,
140 machine_type: std.coff.IMAGE.FILE.MACHINE,
141 module_definition_type: ModuleDefinitionType,
142 diagnostics: *Diagnostics,
143) !ModuleDefinition {
144 var tokenizer = Tokenizer.init(source);
145 var parser = Parser.init(&tokenizer, machine_type, module_definition_type, diagnostics);
146
147 return parser.parse(allocator);
148}
149
150const Token = struct {
151 tag: Tag,
152 start: usize,
153 end: usize,
154
155 pub const keywords = std.StaticStringMap(Tag).initComptime(.{
156 .{ "BASE", .keyword_base },
157 .{ "CONSTANT", .keyword_constant },
158 .{ "DATA", .keyword_data },
159 .{ "EXPORTS", .keyword_exports },
160 .{ "EXPORTAS", .keyword_exportas },
161 .{ "HEAPSIZE", .keyword_heapsize },
162 .{ "LIBRARY", .keyword_library },
163 .{ "NAME", .keyword_name },
164 .{ "NONAME", .keyword_noname },
165 .{ "PRIVATE", .keyword_private },
166 .{ "STACKSIZE", .keyword_stacksize },
167 .{ "VERSION", .keyword_version },
168 });
169
170 pub const Tag = enum {
171 invalid,
172 eof,
173 identifier,
174 comma,
175 equal,
176 equal_equal,
177 keyword_base,
178 keyword_constant,
179 keyword_data,
180 keyword_exports,
181 keyword_exportas,
182 keyword_heapsize,
183 keyword_library,
184 keyword_name,
185 keyword_noname,
186 keyword_private,
187 keyword_stacksize,
188 keyword_version,
189
190 pub fn nameForErrorDisplay(self: Tag) []const u8 {
191 return switch (self) {
192 .invalid => "<invalid>",
193 .eof => "<eof>",
194 .identifier => "<identifier>",
195 .comma => ",",
196 .equal => "=",
197 .equal_equal => "==",
198 .keyword_base => "BASE",
199 .keyword_constant => "CONSTANT",
200 .keyword_data => "DATA",
201 .keyword_exports => "EXPORTS",
202 .keyword_exportas => "EXPORTAS",
203 .keyword_heapsize => "HEAPSIZE",
204 .keyword_library => "LIBRARY",
205 .keyword_name => "NAME",
206 .keyword_noname => "NONAME",
207 .keyword_private => "PRIVATE",
208 .keyword_stacksize => "STACKSIZE",
209 .keyword_version => "VERSION",
210 };
211 }
212 };
213
214 /// Returns a useful slice of the token, e.g. for quoted identifiers, this
215 /// will return a slice without the quotes included.
216 pub fn slice(self: Token, source: []const u8) []const u8 {
217 return source[self.start..self.end];
218 }
219};
220
221const Tokenizer = struct {
222 source: [:0]const u8,
223 index: usize,
224 error_context_token: ?Token = null,
225
226 pub fn init(source: [:0]const u8) Tokenizer {
227 return .{
228 .source = source,
229 .index = 0,
230 };
231 }
232
233 const State = enum {
234 start,
235 identifier_or_keyword,
236 quoted_identifier,
237 comment,
238 equal,
239 eof_or_invalid,
240 };
241
242 pub const Error = error{
243 InvalidByte,
244 UnfinishedQuotedIdentifier,
245 };
246
247 pub fn next(self: *Tokenizer) Error!Token {
248 var result: Token = .{
249 .tag = undefined,
250 .start = self.index,
251 .end = undefined,
252 };
253 state: switch (State.start) {
254 .start => switch (self.source[self.index]) {
255 0 => continue :state .eof_or_invalid,
256 '\r', '\n', ' ', '\t', '\x0B' => {
257 self.index += 1;
258 result.start = self.index;
259 continue :state .start;
260 },
261 ';' => continue :state .comment,
262 '=' => continue :state .equal,
263 ',' => {
264 result.tag = .comma;
265 self.index += 1;
266 },
267 '"' => continue :state .quoted_identifier,
268 else => continue :state .identifier_or_keyword,
269 },
270 .comment => {
271 self.index += 1;
272 switch (self.source[self.index]) {
273 0 => continue :state .eof_or_invalid,
274 '\n' => {
275 self.index += 1;
276 result.start = self.index;
277 continue :state .start;
278 },
279 else => continue :state .comment,
280 }
281 },
282 .equal => {
283 self.index += 1;
284 switch (self.source[self.index]) {
285 '=' => {
286 result.tag = .equal_equal;
287 self.index += 1;
288 },
289 else => result.tag = .equal,
290 }
291 },
292 .quoted_identifier => {
293 self.index += 1;
294 switch (self.source[self.index]) {
295 0 => {
296 self.error_context_token = .{
297 .tag = .eof,
298 .start = self.index,
299 .end = self.index,
300 };
301 return error.UnfinishedQuotedIdentifier;
302 },
303 '"' => {
304 result.tag = .identifier;
305 self.index += 1;
306
307 // Return the token unquoted
308 return .{
309 .tag = result.tag,
310 .start = result.start + 1,
311 .end = self.index - 1,
312 };
313 },
314 else => continue :state .quoted_identifier,
315 }
316 },
317 .identifier_or_keyword => {
318 self.index += 1;
319 switch (self.source[self.index]) {
320 0, '=', ',', ';', '\r', '\n', ' ', '\t', '\x0B' => {
321 const keyword = Token.keywords.get(self.source[result.start..self.index]);
322 result.tag = keyword orelse .identifier;
323 },
324 else => continue :state .identifier_or_keyword,
325 }
326 },
327 .eof_or_invalid => {
328 if (self.index == self.source.len) {
329 return .{
330 .tag = .eof,
331 .start = self.index,
332 .end = self.index,
333 };
334 }
335 self.error_context_token = .{
336 .tag = .invalid,
337 .start = self.index,
338 .end = self.index + 1,
339 };
340 return error.InvalidByte;
341 },
342 }
343
344 result.end = self.index;
345 return result;
346 }
347};
348
349test Tokenizer {
350 try testTokenizer(
351 \\foo
352 \\; hello
353 \\BASE
354 \\"bar"
355 \\
356 , &.{
357 .identifier,
358 .keyword_base,
359 .identifier,
360 });
361}
362
363fn testTokenizer(source: [:0]const u8, expected: []const Token.Tag) !void {
364 var tokenizer = Tokenizer.init(source);
365 for (expected) |expected_tag| {
366 const token = try tokenizer.next();
367 try std.testing.expectEqual(expected_tag, token.tag);
368 }
369 const last_token = try tokenizer.next();
370 try std.testing.expectEqual(.eof, last_token.tag);
371}
372
373pub const Parser = struct {
374 tokenizer: *Tokenizer,
375 diagnostics: *Diagnostics,
376 lookahead_tokenizer: Tokenizer,
377 machine_type: std.coff.IMAGE.FILE.MACHINE,
378 module_definition_type: ModuleDefinitionType,
379
380 pub fn init(
381 tokenizer: *Tokenizer,
382 machine_type: std.coff.IMAGE.FILE.MACHINE,
383 module_definition_type: ModuleDefinitionType,
384 diagnostics: *Diagnostics,
385 ) Parser {
386 return .{
387 .tokenizer = tokenizer,
388 .machine_type = machine_type,
389 .module_definition_type = module_definition_type,
390 .diagnostics = diagnostics,
391 .lookahead_tokenizer = undefined,
392 };
393 }
394
395 pub const Error = error{ParseError} || std.mem.Allocator.Error;
396
397 pub fn parse(self: *Parser, allocator: std.mem.Allocator) Error!ModuleDefinition {
398 var module: ModuleDefinition = .{
399 .arena = .init(allocator),
400 .type = self.module_definition_type,
401 };
402 const arena = module.arena.allocator();
403 errdefer module.deinit();
404 while (true) {
405 const tok = try self.nextToken();
406 switch (tok.tag) {
407 .eof => break,
408 .keyword_library, .keyword_name => {
409 const is_library = tok.tag == .keyword_library;
410
411 const name = try self.lookaheadToken();
412 if (name.tag != .identifier) continue;
413 self.commitLookahead();
414
415 const base_tok = try self.lookaheadToken();
416 if (base_tok.tag == .keyword_base) {
417 self.commitLookahead();
418
419 _ = try self.expectToken(.equal);
420
421 module.base_address = try self.expectInteger(usize);
422 }
423
424 // Append .dll/.exe if there's no extension
425 const name_slice = name.slice(self.tokenizer.source);
426 module.name = if (std.fs.path.extension(name_slice).len == 0)
427 try std.mem.concat(arena, u8, &.{ name_slice, if (is_library) ".dll" else ".exe" })
428 else
429 try arena.dupe(u8, name_slice);
430 },
431 .keyword_exports => {
432 while (true) {
433 var name_tok = try self.lookaheadToken();
434 if (name_tok.tag != .identifier) break;
435 self.commitLookahead();
436
437 const ext_name_tok = ext_name: {
438 const equal = try self.lookaheadToken();
439 if (equal.tag != .equal) break :ext_name null;
440 self.commitLookahead();
441
442 // The syntax is `<ext_name> = <name>`, so we need to
443 // swap the current name token over to ext_name and use
444 // this token as the name.
445 const ext_name_tok = name_tok;
446 name_tok = try self.expectToken(.identifier);
447 break :ext_name ext_name_tok;
448 };
449
450 var name_needs_underscore = false;
451 var ext_name_needs_underscore = false;
452 if (self.machine_type == .I386) {
453 const is_decorated = isDecorated(name_tok.slice(self.tokenizer.source), self.module_definition_type);
454 const is_forward_target = ext_name_tok != null and std.mem.indexOfScalar(u8, name_tok.slice(self.tokenizer.source), '.') != null;
455 name_needs_underscore = !is_decorated and !is_forward_target;
456
457 if (ext_name_tok) |ext_name| {
458 ext_name_needs_underscore = !isDecorated(ext_name.slice(self.tokenizer.source), self.module_definition_type);
459 }
460 }
461
462 var import_name_tok: ?Token = null;
463 var export_as_tok: ?Token = null;
464 var ordinal: ?u16 = null;
465 var import_type: std.coff.ImportType = .CODE;
466 var private: bool = false;
467 var no_name: bool = false;
468 while (true) {
469 const arg_tok = try self.lookaheadToken();
470 switch (arg_tok.tag) {
471 .identifier => {
472 const slice = arg_tok.slice(self.tokenizer.source);
473 if (slice[0] != '@') break;
474
475 // foo @ 10
476 if (slice.len == 1) {
477 self.commitLookahead();
478 ordinal = try self.expectInteger(u16);
479 continue;
480 }
481 // foo @10
482 ordinal = std.fmt.parseUnsigned(u16, slice[1..], 0) catch {
483 // e.g. foo @bar, the @bar is presumed to be the start of a separate
484 // export (and there could be a newline between them)
485 break;
486 };
487 // finally safe to commit to consuming the token
488 self.commitLookahead();
489
490 const noname_tok = try self.lookaheadToken();
491 if (noname_tok.tag == .keyword_noname) {
492 self.commitLookahead();
493 no_name = true;
494 }
495 },
496 .equal_equal => {
497 self.commitLookahead();
498 import_name_tok = try self.expectToken(.identifier);
499 },
500 .keyword_data => {
501 self.commitLookahead();
502 import_type = .DATA;
503 },
504 .keyword_constant => {
505 self.commitLookahead();
506 import_type = .CONST;
507 },
508 .keyword_private => {
509 self.commitLookahead();
510 private = true;
511 },
512 .keyword_exportas => {
513 self.commitLookahead();
514 export_as_tok = try self.expectToken(.identifier);
515 },
516 else => break,
517 }
518 }
519
520 const name = if (name_needs_underscore)
521 try std.mem.concat(arena, u8, &.{ "_", name_tok.slice(self.tokenizer.source) })
522 else
523 try arena.dupe(u8, name_tok.slice(self.tokenizer.source));
524
525 const ext_name: ?[]const u8 = if (ext_name_tok) |ext_name| if (name_needs_underscore)
526 try std.mem.concat(arena, u8, &.{ "_", ext_name.slice(self.tokenizer.source) })
527 else
528 try arena.dupe(u8, ext_name.slice(self.tokenizer.source)) else null;
529
530 try module.exports.append(arena, .{
531 .name = name,
532 .mangled_symbol_name = null,
533 .ext_name = ext_name,
534 .import_name = if (import_name_tok) |imp_name| try arena.dupe(u8, imp_name.slice(self.tokenizer.source)) else null,
535 .export_as = if (export_as_tok) |export_as| try arena.dupe(u8, export_as.slice(self.tokenizer.source)) else null,
536 .no_name = no_name,
537 .ordinal = ordinal orelse 0,
538 .type = import_type,
539 .private = private,
540 });
541 }
542 },
543 .keyword_heapsize,
544 .keyword_stacksize,
545 .keyword_version,
546 => return self.unimplemented(tok),
547 else => {
548 self.diagnostics.* = .{
549 .err = .unknown_statement,
550 .token = tok,
551 };
552 return error.ParseError;
553 },
554 }
555 }
556 return module;
557 }
558
559 fn isDecorated(symbol: []const u8, module_definition_type: ModuleDefinitionType) bool {
560 // In def files, the symbols can either be listed decorated or undecorated.
561 //
562 // - For cdecl symbols, only the undecorated form is allowed.
563 // - For fastcall and vectorcall symbols, both fully decorated or
564 // undecorated forms can be present.
565 // - For stdcall symbols in non-MinGW environments, the decorated form is
566 // fully decorated with leading underscore and trailing stack argument
567 // size - like "_Func@0".
568 // - In MinGW def files, a decorated stdcall symbol does not include the
569 // leading underscore though, like "Func@0".
570
571 // This function controls whether a leading underscore should be added to
572 // the given symbol name or not. For MinGW, treat a stdcall symbol name such
573 // as "Func@0" as undecorated, i.e. a leading underscore must be added.
574 // For non-MinGW, look for '@' in the whole string and consider "_Func@0"
575 // as decorated, i.e. don't add any more leading underscores.
576 // We can't check for a leading underscore here, since function names
577 // themselves can start with an underscore, while a second one still needs
578 // to be added.
579 if (std.mem.startsWith(u8, symbol, "@")) return true;
580 if (std.mem.indexOf(u8, symbol, "@@") != null) return true;
581 if (std.mem.startsWith(u8, symbol, "?")) return true;
582 if (module_definition_type != .mingw and std.mem.indexOfScalar(u8, symbol, '@') != null) return true;
583 return false;
584 }
585
586 fn expectInteger(self: *Parser, T: type) Error!T {
587 const tok = try self.nextToken();
588 blk: {
589 if (tok.tag != .identifier) break :blk;
590 return std.fmt.parseUnsigned(T, tok.slice(self.tokenizer.source), 0) catch break :blk;
591 }
592 self.diagnostics.* = .{
593 .err = .expected_integer,
594 .token = tok,
595 };
596 return error.ParseError;
597 }
598
599 fn unimplemented(self: *Parser, tok: Token) Error {
600 self.diagnostics.* = .{
601 .err = .unimplemented,
602 .token = tok,
603 };
604 return error.ParseError;
605 }
606
607 fn expectToken(self: *Parser, tag: Token.Tag) Error!Token {
608 const tok = try self.nextToken();
609 if (tok.tag != tag) {
610 self.diagnostics.* = .{
611 .err = .expected_token,
612 .token = tok,
613 .extra = .{ .expected = tag },
614 };
615 return error.ParseError;
616 }
617 return tok;
618 }
619
620 fn nextToken(self: *Parser) Error!Token {
621 return self.nextFromTokenizer(self.tokenizer);
622 }
623
624 fn lookaheadToken(self: *Parser) Error!Token {
625 self.lookahead_tokenizer = self.tokenizer.*;
626 return self.nextFromTokenizer(&self.lookahead_tokenizer);
627 }
628
629 fn commitLookahead(self: *Parser) void {
630 self.tokenizer.* = self.lookahead_tokenizer;
631 }
632
633 fn nextFromTokenizer(
634 self: *Parser,
635 tokenizer: *Tokenizer,
636 ) Error!Token {
637 return tokenizer.next() catch |err| {
638 self.diagnostics.* = .{
639 .err = switch (err) {
640 error.InvalidByte => .invalid_byte,
641 error.UnfinishedQuotedIdentifier => .unfinished_quoted_identifier,
642 },
643 .token = tokenizer.error_context_token.?,
644 };
645 return error.ParseError;
646 };
647 }
648};
649
650test parse {
651 const source =
652 \\LIBRARY "foo"
653 \\; hello
654 \\EXPORTS
655 \\foo @ 10
656 \\bar @104
657 \\baz@4
658 \\foo == bar
659 \\alias = function
660 \\
661 \\data DATA
662 \\constant CONSTANT
663 \\
664 ;
665
666 try testParse(.AMD64, source, "foo.dll", &[_]ModuleDefinition.Export{
667 .{
668 .name = "foo",
669 .mangled_symbol_name = null,
670 .ext_name = null,
671 .import_name = null,
672 .export_as = null,
673 .no_name = false,
674 .ordinal = 10,
675 .type = .CODE,
676 .private = false,
677 },
678 .{
679 .name = "bar",
680 .mangled_symbol_name = null,
681 .ext_name = null,
682 .import_name = null,
683 .export_as = null,
684 .no_name = false,
685 .ordinal = 104,
686 .type = .CODE,
687 .private = false,
688 },
689 .{
690 .name = "baz@4",
691 .mangled_symbol_name = null,
692 .ext_name = null,
693 .import_name = null,
694 .export_as = null,
695 .no_name = false,
696 .ordinal = 0,
697 .type = .CODE,
698 .private = false,
699 },
700 .{
701 .name = "foo",
702 .mangled_symbol_name = null,
703 .ext_name = null,
704 .import_name = "bar",
705 .export_as = null,
706 .no_name = false,
707 .ordinal = 0,
708 .type = .CODE,
709 .private = false,
710 },
711 .{
712 .name = "function",
713 .mangled_symbol_name = null,
714 .ext_name = "alias",
715 .import_name = null,
716 .export_as = null,
717 .no_name = false,
718 .ordinal = 0,
719 .type = .CODE,
720 .private = false,
721 },
722 .{
723 .name = "data",
724 .mangled_symbol_name = null,
725 .ext_name = null,
726 .import_name = null,
727 .export_as = null,
728 .no_name = false,
729 .ordinal = 0,
730 .type = .DATA,
731 .private = false,
732 },
733 .{
734 .name = "constant",
735 .mangled_symbol_name = null,
736 .ext_name = null,
737 .import_name = null,
738 .export_as = null,
739 .no_name = false,
740 .ordinal = 0,
741 .type = .CONST,
742 .private = false,
743 },
744 });
745
746 try testParse(.I386, source, "foo.dll", &[_]ModuleDefinition.Export{
747 .{
748 .name = "_foo",
749 .mangled_symbol_name = null,
750 .ext_name = null,
751 .import_name = null,
752 .export_as = null,
753 .no_name = false,
754 .ordinal = 10,
755 .type = .CODE,
756 .private = false,
757 },
758 .{
759 .name = "_bar",
760 .mangled_symbol_name = null,
761 .ext_name = null,
762 .import_name = null,
763 .export_as = null,
764 .no_name = false,
765 .ordinal = 104,
766 .type = .CODE,
767 .private = false,
768 },
769 .{
770 .name = "_baz@4",
771 .mangled_symbol_name = null,
772 .ext_name = null,
773 .import_name = null,
774 .export_as = null,
775 .no_name = false,
776 .ordinal = 0,
777 .type = .CODE,
778 .private = false,
779 },
780 .{
781 .name = "_foo",
782 .mangled_symbol_name = null,
783 .ext_name = null,
784 .import_name = "bar",
785 .export_as = null,
786 .no_name = false,
787 .ordinal = 0,
788 .type = .CODE,
789 .private = false,
790 },
791 .{
792 .name = "_function",
793 .mangled_symbol_name = null,
794 .ext_name = "_alias",
795 .import_name = null,
796 .export_as = null,
797 .no_name = false,
798 .ordinal = 0,
799 .type = .CODE,
800 .private = false,
801 },
802 .{
803 .name = "_data",
804 .mangled_symbol_name = null,
805 .ext_name = null,
806 .import_name = null,
807 .export_as = null,
808 .no_name = false,
809 .ordinal = 0,
810 .type = .DATA,
811 .private = false,
812 },
813 .{
814 .name = "_constant",
815 .mangled_symbol_name = null,
816 .ext_name = null,
817 .import_name = null,
818 .export_as = null,
819 .no_name = false,
820 .ordinal = 0,
821 .type = .CONST,
822 .private = false,
823 },
824 });
825
826 try testParse(.ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{
827 .{
828 .name = "foo",
829 .mangled_symbol_name = null,
830 .ext_name = null,
831 .import_name = null,
832 .export_as = null,
833 .no_name = false,
834 .ordinal = 10,
835 .type = .CODE,
836 .private = false,
837 },
838 .{
839 .name = "bar",
840 .mangled_symbol_name = null,
841 .ext_name = null,
842 .import_name = null,
843 .export_as = null,
844 .no_name = false,
845 .ordinal = 104,
846 .type = .CODE,
847 .private = false,
848 },
849 .{
850 .name = "baz@4",
851 .mangled_symbol_name = null,
852 .ext_name = null,
853 .import_name = null,
854 .export_as = null,
855 .no_name = false,
856 .ordinal = 0,
857 .type = .CODE,
858 .private = false,
859 },
860 .{
861 .name = "foo",
862 .mangled_symbol_name = null,
863 .ext_name = null,
864 .import_name = "bar",
865 .export_as = null,
866 .no_name = false,
867 .ordinal = 0,
868 .type = .CODE,
869 .private = false,
870 },
871 .{
872 .name = "function",
873 .mangled_symbol_name = null,
874 .ext_name = "alias",
875 .import_name = null,
876 .export_as = null,
877 .no_name = false,
878 .ordinal = 0,
879 .type = .CODE,
880 .private = false,
881 },
882 .{
883 .name = "data",
884 .mangled_symbol_name = null,
885 .ext_name = null,
886 .import_name = null,
887 .export_as = null,
888 .no_name = false,
889 .ordinal = 0,
890 .type = .DATA,
891 .private = false,
892 },
893 .{
894 .name = "constant",
895 .mangled_symbol_name = null,
896 .ext_name = null,
897 .import_name = null,
898 .export_as = null,
899 .no_name = false,
900 .ordinal = 0,
901 .type = .CONST,
902 .private = false,
903 },
904 });
905
906 try testParse(.ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{
907 .{
908 .name = "foo",
909 .mangled_symbol_name = null,
910 .ext_name = null,
911 .import_name = null,
912 .export_as = null,
913 .no_name = false,
914 .ordinal = 10,
915 .type = .CODE,
916 .private = false,
917 },
918 .{
919 .name = "bar",
920 .mangled_symbol_name = null,
921 .ext_name = null,
922 .import_name = null,
923 .export_as = null,
924 .no_name = false,
925 .ordinal = 104,
926 .type = .CODE,
927 .private = false,
928 },
929 .{
930 .name = "baz@4",
931 .mangled_symbol_name = null,
932 .ext_name = null,
933 .import_name = null,
934 .export_as = null,
935 .no_name = false,
936 .ordinal = 0,
937 .type = .CODE,
938 .private = false,
939 },
940 .{
941 .name = "foo",
942 .mangled_symbol_name = null,
943 .ext_name = null,
944 .import_name = "bar",
945 .export_as = null,
946 .no_name = false,
947 .ordinal = 0,
948 .type = .CODE,
949 .private = false,
950 },
951 .{
952 .name = "function",
953 .mangled_symbol_name = null,
954 .ext_name = "alias",
955 .import_name = null,
956 .export_as = null,
957 .no_name = false,
958 .ordinal = 0,
959 .type = .CODE,
960 .private = false,
961 },
962 .{
963 .name = "data",
964 .mangled_symbol_name = null,
965 .ext_name = null,
966 .import_name = null,
967 .export_as = null,
968 .no_name = false,
969 .ordinal = 0,
970 .type = .DATA,
971 .private = false,
972 },
973 .{
974 .name = "constant",
975 .mangled_symbol_name = null,
976 .ext_name = null,
977 .import_name = null,
978 .export_as = null,
979 .no_name = false,
980 .ordinal = 0,
981 .type = .CONST,
982 .private = false,
983 },
984 });
985}
986
987test "ntdll" {
988 const source =
989 \\;
990 \\; Definition file of ntdll.dll
991 \\; Automatic generated by gendef
992 \\; written by Kai Tietz 2008
993 \\;
994 \\LIBRARY "ntdll.dll"
995 \\EXPORTS
996 \\RtlDispatchAPC@12
997 \\RtlActivateActivationContextUnsafeFast@0
998 ;
999
1000 try testParse(.AMD64, source, "ntdll.dll", &[_]ModuleDefinition.Export{
1001 .{
1002 .name = "RtlDispatchAPC@12",
1003 .mangled_symbol_name = null,
1004 .ext_name = null,
1005 .import_name = null,
1006 .export_as = null,
1007 .no_name = false,
1008 .ordinal = 0,
1009 .type = .CODE,
1010 .private = false,
1011 },
1012 .{
1013 .name = "RtlActivateActivationContextUnsafeFast@0",
1014 .mangled_symbol_name = null,
1015 .ext_name = null,
1016 .import_name = null,
1017 .export_as = null,
1018 .no_name = false,
1019 .ordinal = 0,
1020 .type = .CODE,
1021 .private = false,
1022 },
1023 });
1024}
1025
1026fn testParse(machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8, expected_module_name: []const u8, expected_exports: []const ModuleDefinition.Export) !void {
1027 var diagnostics: Diagnostics = undefined;
1028 const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {
1029 error.OutOfMemory => |e| return e,
1030 error.ParseError => {
1031 const stderr = std.debug.lockStderrWriter(&.{});
1032 defer std.debug.unlockStderrWriter();
1033 try diagnostics.writeMsg(stderr, source);
1034 try stderr.writeByte('\n');
1035 return err;
1036 },
1037 };
1038 defer module.deinit();
1039
1040 try std.testing.expectEqualStrings(expected_module_name, module.name orelse "");
1041 try std.testing.expectEqual(expected_exports.len, module.exports.items.len);
1042 for (expected_exports, module.exports.items) |expected, actual| {
1043 try std.testing.expectEqualStrings(expected.name, actual.name);
1044 try std.testing.expectEqualStrings(expected.export_as orelse "", actual.export_as orelse "");
1045 try std.testing.expectEqualStrings(expected.ext_name orelse "", actual.ext_name orelse "");
1046 try std.testing.expectEqualStrings(expected.import_name orelse "", actual.import_name orelse "");
1047 try std.testing.expectEqualStrings(expected.mangled_symbol_name orelse "", actual.mangled_symbol_name orelse "");
1048 try std.testing.expectEqual(expected.ordinal, actual.ordinal);
1049 try std.testing.expectEqual(expected.no_name, actual.no_name);
1050 try std.testing.expectEqual(expected.private, actual.private);
1051 try std.testing.expectEqual(expected.type, actual.type);
1052 }
1053}
1054
1055test "parse errors" {
1056 for (&[_]std.coff.IMAGE.FILE.MACHINE{ .AMD64, .I386, .ARMNT, .ARM64 }) |machine_type| {
1057 try testParseErrorMsg("invalid byte '\\x00'", machine_type, "LIBRARY \x00");
1058 try testParseErrorMsg("unfinished quoted identifier at '<eof>', expected closing '\"'", machine_type, "LIBRARY \"foo");
1059 try testParseErrorMsg("expected '=', got 'foo'", machine_type, "LIBRARY foo BASE foo");
1060 try testParseErrorMsg("expected integer, got 'foo'", machine_type, "EXPORTS foo @ foo");
1061 try testParseErrorMsg("support for 'HEAPSIZE' has not yet been implemented", machine_type, "HEAPSIZE");
1062 try testParseErrorMsg("unknown/invalid statement syntax beginning with 'LIB'", machine_type, "LIB");
1063 }
1064}
1065
1066fn testParseErrorMsg(expected_msg: []const u8, machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8) !void {
1067 var diagnostics: Diagnostics = undefined;
1068 _ = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {
1069 error.OutOfMemory => |e| return e,
1070 error.ParseError => {
1071 var buf: [256]u8 = undefined;
1072 var writer: std.Io.Writer = .fixed(&buf);
1073 try diagnostics.writeMsg(&writer, source);
1074 try std.testing.expectEqualStrings(expected_msg, writer.buffered());
1075 return;
1076 },
1077 };
1078 return error.UnexpectedSuccess;
1079}
src/libs/mingw/implib.zig created+1088
...@@ -0,0 +1,1088 @@
1const std = @import("std");
2const def = @import("def.zig");
3const Allocator = std.mem.Allocator;
4
5// LLVM has some quirks/bugs around padding/size values.
6// Emulating those quirks made it much easier to test this implementation against the LLVM
7// implementation since we could just check if the .lib files are byte-for-byte identical.
8// This remains set to true out of an abundance of caution.
9const llvm_compat = true;
10
11pub const WriteCoffArchiveError = error{TooManyMembers} || std.Io.Writer.Error || std.mem.Allocator.Error;
12
13pub fn writeCoffArchive(
14 allocator: std.mem.Allocator,
15 writer: *std.Io.Writer,
16 members: Members,
17) WriteCoffArchiveError!void {
18 // The second linker member of a COFF archive uses a 32-bit integer for the number of members field,
19 // but only 16-bit integers for the "array of 1-based indexes that map symbol names to archive
20 // member offsets." This means that the maximum number of *indexable* members is maxInt(u16) - 1.
21 if (members.list.items.len > std.math.maxInt(u16) - 1) return error.TooManyMembers;
22
23 try writer.writeAll(archive_start);
24
25 const member_offsets = try allocator.alloc(usize, members.list.items.len);
26 defer allocator.free(member_offsets);
27 {
28 var offset: usize = 0;
29 for (member_offsets, 0..) |*elem, i| {
30 elem.* = offset;
31 offset += archive_header_len;
32 offset += members.list.items[i].byteLenWithPadding();
33 }
34 }
35
36 var long_names: StringTable = .{};
37 defer long_names.deinit(allocator);
38
39 var symbol_to_member_index = std.StringArrayHashMap(usize).init(allocator);
40 defer symbol_to_member_index.deinit();
41 var string_table_len: usize = 0;
42 var num_symbols: usize = 0;
43
44 for (members.list.items, 0..) |member, i| {
45 for (member.symbol_names_for_import_lib) |symbol_name| {
46 const gop_result = try symbol_to_member_index.getOrPut(symbol_name);
47 // When building the symbol map, ignore duplicate symbol names.
48 // This can happen in cases like (using .def file syntax):
49 // _foo
50 // foo == _foo
51 if (gop_result.found_existing) continue;
52
53 gop_result.value_ptr.* = i;
54 string_table_len += symbol_name.len + 1;
55 num_symbols += 1;
56 }
57
58 if (member.needsLongName()) {
59 _ = try long_names.put(allocator, member.name);
60 }
61 }
62
63 const first_linker_member_len = 4 + (4 * num_symbols) + string_table_len;
64 const second_linker_member_len = 4 + (4 * members.list.items.len) + 4 + (2 * num_symbols) + string_table_len;
65 const long_names_len_including_header_and_padding = blk: {
66 if (long_names.map.count() == 0) break :blk 0;
67 break :blk archive_header_len + std.mem.alignForward(usize, long_names.data.items.len, 2);
68 };
69 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;
70
71 // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#first-linker-member
72 try writeArchiveMemberHeader(writer, .linker_member, memberHeaderLen(first_linker_member_len), "0");
73 try writer.writeInt(u32, @intCast(num_symbols), .big);
74 for (symbol_to_member_index.values()) |member_i| {
75 const offset = member_offsets[member_i];
76 try writer.writeInt(u32, @intCast(first_member_offset + offset), .big);
77 }
78 for (symbol_to_member_index.keys()) |symbol_name| {
79 try writer.writeAll(symbol_name);
80 try writer.writeByte(0);
81 }
82 if (first_linker_member_len % 2 != 0) try writer.writeByte(if (llvm_compat) 0 else archive_pad_byte);
83
84 // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#second-linker-member
85 try writeArchiveMemberHeader(writer, .linker_member, memberHeaderLen(second_linker_member_len), "0");
86 try writer.writeInt(u32, @intCast(members.list.items.len), .little);
87 for (member_offsets) |offset| {
88 try writer.writeInt(u32, @intCast(first_member_offset + offset), .little);
89 }
90 try writer.writeInt(u32, @intCast(num_symbols), .little);
91
92 // sort lexicographically
93 const C = struct {
94 keys: []const []const u8,
95
96 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
97 return std.mem.lessThan(u8, ctx.keys[a_index], ctx.keys[b_index]);
98 }
99 };
100 symbol_to_member_index.sortUnstable(C{ .keys = symbol_to_member_index.keys() });
101
102 for (symbol_to_member_index.values()) |member_i| {
103 try writer.writeInt(u16, @intCast(member_i + 1), .little);
104 }
105 for (symbol_to_member_index.keys()) |symbol_name| {
106 try writer.writeAll(symbol_name);
107 try writer.writeByte(0);
108 }
109 if (first_linker_member_len % 2 != 0) try writer.writeByte(if (llvm_compat) 0 else archive_pad_byte);
110
111 // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#longnames-member
112 if (long_names.data.items.len != 0) {
113 const written_len = long_names.data.items.len;
114 try writeLongNamesMemberHeader(writer, memberHeaderLen(written_len));
115 try writer.writeAll(long_names.data.items);
116 if (long_names.data.items.len % 2 != 0) try writer.writeByte(archive_pad_byte);
117 }
118
119 for (members.list.items) |member| {
120 const name: MemberName = if (member.needsLongName())
121 .{ .longname = long_names.getOffset(member.name).? }
122 else
123 .{ .name = member.name };
124 try writeArchiveMemberHeader(writer, name, member.bytes.len, "644");
125 try writer.writeAll(member.bytes);
126 if (member.bytes.len % 2 != 0) try writer.writeByte(archive_pad_byte);
127 }
128
129 try writer.flush();
130}
131
132const archive_start = "!<arch>\n";
133const archive_header_end = "`\n";
134const archive_pad_byte = '\n';
135const archive_header_len = 60;
136
137fn memberHeaderLen(len: usize) usize {
138 return if (llvm_compat)
139 // LLVM writes this with the padding byte included, likely a bug/mistake
140 std.mem.alignForward(usize, len, 2)
141 else
142 len;
143}
144
145const MemberName = union(enum) {
146 name: []const u8,
147 linker_member,
148 longnames_member,
149 longname: usize,
150
151 pub fn write(self: MemberName, writer: *std.Io.Writer) !void {
152 switch (self) {
153 .name => |name| {
154 try writer.writeAll(name);
155 try writer.writeByte('/');
156 try writer.splatByteAll(' ', 16 - (name.len + 1));
157 },
158 .linker_member => {
159 try writer.writeAll("/ ");
160 },
161 .longnames_member => {
162 try writer.writeAll("// ");
163 },
164 .longname => |offset| {
165 try writer.print("/{d: <15}", .{offset});
166 },
167 }
168 }
169};
170
171fn writeLongNamesMemberHeader(writer: *std.Io.Writer, size: usize) !void {
172 try (MemberName{ .longnames_member = {} }).write(writer);
173 try writer.splatByteAll(' ', archive_header_len - 16 - 10 - archive_header_end.len);
174 try writer.print("{d: <10}", .{size});
175 try writer.writeAll(archive_header_end);
176}
177
178fn writeArchiveMemberHeader(writer: *std.Io.Writer, name: MemberName, size: usize, mode: []const u8) !void {
179 try name.write(writer);
180 try writer.writeAll("0 "); // date
181 try writer.writeAll("0 "); // user id
182 try writer.writeAll("0 "); // group id
183 try writer.print("{s: <8}", .{mode}); // mode
184 try writer.print("{d: <10}", .{size});
185 try writer.writeAll(archive_header_end);
186}
187
188pub const Members = struct {
189 list: std.ArrayList(Member) = .empty,
190 arena: std.heap.ArenaAllocator,
191
192 pub const Member = struct {
193 bytes: []const u8,
194 name: []const u8,
195 symbol_names_for_import_lib: []const []const u8,
196
197 pub fn byteLenWithPadding(self: Member) usize {
198 return std.mem.alignForward(usize, self.bytes.len, 2);
199 }
200
201 pub fn needsLongName(self: Member) bool {
202 return self.name.len >= 16;
203 }
204 };
205
206 pub fn deinit(self: *const Members) void {
207 self.arena.deinit();
208 }
209};
210
211const GetMembersError = GetImportDescriptorError || GetShortImportError;
212
213pub fn getMembers(
214 allocator: std.mem.Allocator,
215 module_def: def.ModuleDefinition,
216 machine_type: std.coff.IMAGE.FILE.MACHINE,
217) GetMembersError!Members {
218 var members: Members = .{
219 .arena = std.heap.ArenaAllocator.init(allocator),
220 };
221 const arena = members.arena.allocator();
222 errdefer members.deinit();
223
224 try members.list.ensureTotalCapacity(arena, 3 + module_def.exports.items.len);
225 const module_import_name = try arena.dupe(u8, module_def.name orelse "");
226 const library = std.fs.path.stem(module_import_name);
227
228 const import_descriptor_symbol_name = try std.mem.concat(arena, u8, &.{
229 import_descriptor_prefix,
230 library,
231 });
232 const null_thunk_symbol_name = try std.mem.concat(arena, u8, &.{
233 null_thunk_data_prefix,
234 library,
235 null_thunk_data_suffix,
236 });
237
238 members.list.appendAssumeCapacity(try getImportDescriptor(arena, machine_type, module_import_name, import_descriptor_symbol_name, null_thunk_symbol_name));
239 members.list.appendAssumeCapacity(try getNullImportDescriptor(arena, machine_type, module_import_name));
240 members.list.appendAssumeCapacity(try getNullThunk(arena, machine_type, module_import_name, null_thunk_symbol_name));
241
242 const DeferredExport = struct {
243 name: []const u8,
244 e: *const def.ModuleDefinition.Export,
245 };
246 var renames: std.ArrayList(DeferredExport) = .empty;
247 defer renames.deinit(allocator);
248 var regular_imports: std.StringArrayHashMapUnmanaged([]const u8) = .empty;
249 defer regular_imports.deinit(allocator);
250
251 for (module_def.exports.items) |*e| {
252 if (e.private) continue;
253
254 const maybe_mangled_name = e.mangled_symbol_name orelse e.name;
255 const name = maybe_mangled_name;
256
257 if (e.ext_name) |ext_name| {
258 _ = ext_name;
259 @panic("TODO"); // impossible if fixupForImportLibraryGeneration is called
260 }
261
262 var import_name_type: std.coff.ImportNameType = undefined;
263 var export_name: ?[]const u8 = null;
264 if (e.no_name) {
265 import_name_type = .ORDINAL;
266 } else if (e.export_as) |export_as| {
267 import_name_type = .NAME_EXPORTAS;
268 export_name = export_as;
269 } else if (e.import_name) |import_name| {
270 if (machine_type == .I386 and std.mem.eql(u8, applyNameType(.NAME_UNDECORATE, name), import_name)) {
271 import_name_type = .NAME_UNDECORATE;
272 } else if (machine_type == .I386 and std.mem.eql(u8, applyNameType(.NAME_NOPREFIX, name), import_name)) {
273 import_name_type = .NAME_NOPREFIX;
274 } else if (isArm64EC(machine_type)) {
275 import_name_type = .NAME_EXPORTAS;
276 export_name = import_name;
277 } else if (std.mem.eql(u8, name, import_name)) {
278 import_name_type = .NAME;
279 } else {
280 try renames.append(allocator, .{
281 .name = name,
282 .e = e,
283 });
284 continue;
285 }
286 } else {
287 import_name_type = getNameType(maybe_mangled_name, e.name, machine_type, module_def.type);
288 }
289
290 try regular_imports.put(allocator, applyNameType(import_name_type, name), name);
291 try members.list.append(arena, try getShortImport(arena, module_import_name, name, export_name, machine_type, e.ordinal, e.type, import_name_type));
292 }
293 for (renames.items) |deferred| {
294 const import_name = deferred.e.import_name.?;
295 if (regular_imports.get(import_name)) |symbol| {
296 if (deferred.e.type == .CODE) {
297 try members.list.append(arena, try getWeakExternal(arena, module_import_name, symbol, deferred.name, .{
298 .imp_prefix = false,
299 .machine_type = machine_type,
300 }));
301 }
302 try members.list.append(arena, try getWeakExternal(arena, module_import_name, symbol, deferred.name, .{
303 .imp_prefix = true,
304 .machine_type = machine_type,
305 }));
306 } else {
307 try members.list.append(arena, try getShortImport(
308 arena,
309 module_import_name,
310 deferred.name,
311 deferred.e.import_name,
312 machine_type,
313 deferred.e.ordinal,
314 deferred.e.type,
315 .NAME_EXPORTAS,
316 ));
317 }
318 }
319
320 return members;
321}
322
323/// Returns a slice of `name`
324fn applyNameType(name_type: std.coff.ImportNameType, name: []const u8) []const u8 {
325 switch (name_type) {
326 .NAME_NOPREFIX, .NAME_UNDECORATE => {
327 if (name.len == 0) return name;
328 const unprefixed = switch (name[0]) {
329 '?', '@', '_' => name[1..],
330 else => name,
331 };
332 if (name_type == .NAME_UNDECORATE) {
333 var split = std.mem.splitScalar(u8, unprefixed, '@');
334 return split.first();
335 } else {
336 return unprefixed;
337 }
338 },
339 else => return name,
340 }
341}
342
343fn getNameType(
344 symbol: []const u8,
345 ext_name: []const u8,
346 machine_type: std.coff.IMAGE.FILE.MACHINE,
347 module_definition_type: def.ModuleDefinitionType,
348) std.coff.ImportNameType {
349 // A decorated stdcall function in MSVC is exported with the
350 // type IMPORT_NAME, and the exported function name includes the
351 // the leading underscore. In MinGW on the other hand, a decorated
352 // stdcall function still omits the underscore (IMPORT_NAME_NOPREFIX).
353 if (std.mem.startsWith(u8, ext_name, "_") and
354 std.mem.indexOfScalar(u8, ext_name, '@') != null and
355 module_definition_type != .mingw)
356 return .NAME;
357 if (!std.mem.eql(u8, symbol, ext_name))
358 return .NAME_UNDECORATE;
359 if (machine_type == .I386 and std.mem.startsWith(u8, symbol, "_"))
360 return .NAME_NOPREFIX;
361 return .NAME;
362}
363
364fn is64Bit(machine_type: std.coff.IMAGE.FILE.MACHINE) bool {
365 return switch (machine_type) {
366 .AMD64, .ARM64, .ARM64EC, .ARM64X => true,
367 else => false,
368 };
369}
370
371fn isArm64EC(machine_type: std.coff.IMAGE.FILE.MACHINE) bool {
372 return switch (machine_type) {
373 .ARM64EC, .ARM64X => true,
374 else => false,
375 };
376}
377
378const null_import_descriptor_symbol_name = "__NULL_IMPORT_DESCRIPTOR";
379const import_descriptor_prefix = "__IMPORT_DESCRIPTOR_";
380const null_thunk_data_prefix = "\x7F";
381const null_thunk_data_suffix = "_NULL_THUNK_DATA";
382
383// past the string table length field
384const first_string_table_entry_offset = @sizeOf(u32);
385const first_string_table_entry = getNameBytesForStringTableOffset(first_string_table_entry_offset);
386
387const byte_size_of_relocation = 10;
388
389fn getNameBytesForStringTableOffset(offset: u32) [8]u8 {
390 var bytes = [_]u8{0} ** 8;
391 std.mem.writeInt(u32, bytes[4..8], offset, .little);
392 return bytes;
393}
394
395const GetImportDescriptorError = error{UnsupportedMachineType} || std.mem.Allocator.Error;
396
397fn getImportDescriptor(
398 allocator: std.mem.Allocator,
399 machine_type: std.coff.IMAGE.FILE.MACHINE,
400 module_import_name: []const u8,
401 import_descriptor_symbol_name: []const u8,
402 null_thunk_symbol_name: []const u8,
403) GetImportDescriptorError!Members.Member {
404 const number_of_sections = 2;
405 const number_of_symbols = 7;
406 const number_of_relocations = 3;
407
408 const pointer_to_idata2_data = @sizeOf(std.coff.Header) +
409 (@sizeOf(std.coff.SectionHeader) * number_of_sections);
410 const pointer_to_idata6_data = pointer_to_idata2_data +
411 @sizeOf(std.coff.ImportDirectoryEntry) +
412 (byte_size_of_relocation * number_of_relocations);
413 const pointer_to_symbol_table = pointer_to_idata6_data +
414 module_import_name.len + 1;
415
416 const string_table_byte_len = 4 +
417 (import_descriptor_symbol_name.len + 1) +
418 (null_import_descriptor_symbol_name.len + 1) +
419 (null_thunk_symbol_name.len + 1);
420 const total_byte_len = pointer_to_symbol_table +
421 (std.coff.Symbol.sizeOf() * number_of_symbols) +
422 string_table_byte_len;
423
424 const bytes = try allocator.alloc(u8, total_byte_len);
425 errdefer allocator.free(bytes);
426 var writer: std.Io.Writer = .fixed(bytes);
427
428 writer.writeStruct(std.coff.Header{
429 .machine = machine_type,
430 .number_of_sections = number_of_sections,
431 .time_date_stamp = 0,
432 .pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
433 .number_of_symbols = number_of_symbols,
434 .size_of_optional_header = 0,
435 .flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) },
436 }, .little) catch unreachable;
437
438 writer.writeStruct(std.coff.SectionHeader{
439 .name = ".idata$2".*,
440 .virtual_size = 0,
441 .virtual_address = 0,
442 .size_of_raw_data = @sizeOf(std.coff.ImportDirectoryEntry),
443 .pointer_to_raw_data = pointer_to_idata2_data,
444 .pointer_to_relocations = pointer_to_idata2_data + @sizeOf(std.coff.ImportDirectoryEntry),
445 .pointer_to_linenumbers = 0,
446 .number_of_relocations = number_of_relocations,
447 .number_of_linenumbers = 0,
448 .flags = .{
449 .ALIGN = .@"4BYTES",
450 .CNT_INITIALIZED_DATA = true,
451 .MEM_WRITE = true,
452 .MEM_READ = true,
453 },
454 }, .little) catch unreachable;
455
456 writer.writeStruct(std.coff.SectionHeader{
457 .name = ".idata$6".*,
458 .virtual_size = 0,
459 .virtual_address = 0,
460 .size_of_raw_data = @intCast(module_import_name.len + 1),
461 .pointer_to_raw_data = pointer_to_idata6_data,
462 .pointer_to_relocations = 0,
463 .pointer_to_linenumbers = 0,
464 .number_of_relocations = 0,
465 .number_of_linenumbers = 0,
466 .flags = .{
467 .ALIGN = .@"2BYTES",
468 .CNT_INITIALIZED_DATA = true,
469 .MEM_WRITE = true,
470 .MEM_READ = true,
471 },
472 }, .little) catch unreachable;
473
474 // .idata$2
475 writer.writeStruct(std.coff.ImportDirectoryEntry{
476 .forwarder_chain = 0,
477 .import_address_table_rva = 0,
478 .import_lookup_table_rva = 0,
479 .name_rva = 0,
480 .time_date_stamp = 0,
481 }, .little) catch unreachable;
482
483 const relocation_rva_type = rvaRelocationTypeIndicator(machine_type) orelse return error.UnsupportedMachineType;
484 writeRelocation(&writer, .{
485 .virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "name_rva"),
486 .symbol_table_index = 2,
487 .type = relocation_rva_type,
488 }) catch unreachable;
489 writeRelocation(&writer, .{
490 .virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "import_lookup_table_rva"),
491 .symbol_table_index = 3,
492 .type = relocation_rva_type,
493 }) catch unreachable;
494 writeRelocation(&writer, .{
495 .virtual_address = @offsetOf(std.coff.ImportDirectoryEntry, "import_address_table_rva"),
496 .symbol_table_index = 4,
497 .type = relocation_rva_type,
498 }) catch unreachable;
499
500 // .idata$6
501 writer.writeAll(module_import_name) catch unreachable;
502 writer.writeByte(0) catch unreachable;
503
504 var string_table_offset: usize = first_string_table_entry_offset;
505 writeSymbol(&writer, .{
506 .name = first_string_table_entry,
507 .value = 0,
508 .section_number = @enumFromInt(1),
509 .type = .{
510 .base_type = .NULL,
511 .complex_type = .NULL,
512 },
513 .storage_class = .EXTERNAL,
514 .number_of_aux_symbols = 0,
515 }) catch unreachable;
516 string_table_offset += import_descriptor_symbol_name.len + 1;
517 writeSymbol(&writer, .{
518 .name = ".idata$2".*,
519 .value = 0,
520 .section_number = @enumFromInt(1),
521 .type = .{
522 .base_type = .NULL,
523 .complex_type = .NULL,
524 },
525 .storage_class = .SECTION,
526 .number_of_aux_symbols = 0,
527 }) catch unreachable;
528 writeSymbol(&writer, .{
529 .name = ".idata$6".*,
530 .value = 0,
531 .section_number = @enumFromInt(2),
532 .type = .{
533 .base_type = .NULL,
534 .complex_type = .NULL,
535 },
536 .storage_class = .STATIC,
537 .number_of_aux_symbols = 0,
538 }) catch unreachable;
539 writeSymbol(&writer, .{
540 .name = ".idata$4".*,
541 .value = 0,
542 .section_number = .UNDEFINED,
543 .type = .{
544 .base_type = .NULL,
545 .complex_type = .NULL,
546 },
547 .storage_class = .SECTION,
548 .number_of_aux_symbols = 0,
549 }) catch unreachable;
550 writeSymbol(&writer, .{
551 .name = ".idata$5".*,
552 .value = 0,
553 .section_number = .UNDEFINED,
554 .type = .{
555 .base_type = .NULL,
556 .complex_type = .NULL,
557 },
558 .storage_class = .SECTION,
559 .number_of_aux_symbols = 0,
560 }) catch unreachable;
561 writeSymbol(&writer, .{
562 .name = getNameBytesForStringTableOffset(@intCast(string_table_offset)),
563 .value = 0,
564 .section_number = .UNDEFINED,
565 .type = .{
566 .base_type = .NULL,
567 .complex_type = .NULL,
568 },
569 .storage_class = .EXTERNAL,
570 .number_of_aux_symbols = 0,
571 }) catch unreachable;
572 string_table_offset += null_import_descriptor_symbol_name.len + 1;
573 writeSymbol(&writer, .{
574 .name = getNameBytesForStringTableOffset(@intCast(string_table_offset)),
575 .value = 0,
576 .section_number = .UNDEFINED,
577 .type = .{
578 .base_type = .NULL,
579 .complex_type = .NULL,
580 },
581 .storage_class = .EXTERNAL,
582 .number_of_aux_symbols = 0,
583 }) catch unreachable;
584 string_table_offset += null_thunk_symbol_name.len + 1;
585
586 // string table
587 writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable;
588 writer.writeAll(import_descriptor_symbol_name) catch unreachable;
589 writer.writeByte(0) catch unreachable;
590 writer.writeAll(null_import_descriptor_symbol_name) catch unreachable;
591 writer.writeByte(0) catch unreachable;
592 writer.writeAll(null_thunk_symbol_name) catch unreachable;
593 writer.writeByte(0) catch unreachable;
594
595 var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1);
596 errdefer allocator.free(symbol_names_for_import_lib);
597
598 const duped_symbol_name = try allocator.dupe(u8, import_descriptor_symbol_name);
599 errdefer allocator.free(duped_symbol_name);
600 symbol_names_for_import_lib[0] = duped_symbol_name;
601
602 // Confirm byte length was calculated exactly correctly
603 std.debug.assert(writer.end == bytes.len);
604 return .{
605 .bytes = bytes,
606 .name = module_import_name,
607 .symbol_names_for_import_lib = symbol_names_for_import_lib,
608 };
609}
610
611fn getNullImportDescriptor(
612 allocator: std.mem.Allocator,
613 machine_type: std.coff.IMAGE.FILE.MACHINE,
614 module_import_name: []const u8,
615) error{OutOfMemory}!Members.Member {
616 const number_of_sections = 1;
617 const number_of_symbols = 1;
618 const pointer_to_idata3_data = @sizeOf(std.coff.Header) +
619 (@sizeOf(std.coff.SectionHeader) * number_of_sections);
620 const pointer_to_symbol_table = pointer_to_idata3_data +
621 @sizeOf(std.coff.ImportDirectoryEntry);
622
623 const string_table_byte_len = 4 + null_import_descriptor_symbol_name.len + 1;
624 const total_byte_len = pointer_to_symbol_table +
625 (std.coff.Symbol.sizeOf() * number_of_symbols) +
626 string_table_byte_len;
627
628 const bytes = try allocator.alloc(u8, total_byte_len);
629 errdefer allocator.free(bytes);
630 var writer: std.Io.Writer = .fixed(bytes);
631
632 writer.writeStruct(std.coff.Header{
633 .machine = machine_type,
634 .number_of_sections = number_of_sections,
635 .time_date_stamp = 0,
636 .pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
637 .number_of_symbols = number_of_symbols,
638 .size_of_optional_header = 0,
639 .flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) },
640 }, .little) catch unreachable;
641
642 writer.writeStruct(std.coff.SectionHeader{
643 .name = ".idata$3".*,
644 .virtual_size = 0,
645 .virtual_address = 0,
646 .size_of_raw_data = @sizeOf(std.coff.ImportDirectoryEntry),
647 .pointer_to_raw_data = pointer_to_idata3_data,
648 .pointer_to_relocations = 0,
649 .pointer_to_linenumbers = 0,
650 .number_of_relocations = 0,
651 .number_of_linenumbers = 0,
652 .flags = .{
653 .ALIGN = .@"4BYTES",
654 .CNT_INITIALIZED_DATA = true,
655 .MEM_WRITE = true,
656 .MEM_READ = true,
657 },
658 }, .little) catch unreachable;
659
660 writer.writeStruct(std.coff.ImportDirectoryEntry{
661 .forwarder_chain = 0,
662 .import_address_table_rva = 0,
663 .import_lookup_table_rva = 0,
664 .name_rva = 0,
665 .time_date_stamp = 0,
666 }, .little) catch unreachable;
667
668 writeSymbol(&writer, .{
669 .name = first_string_table_entry,
670 .value = 0,
671 .section_number = @enumFromInt(1),
672 .type = .{
673 .base_type = .NULL,
674 .complex_type = .NULL,
675 },
676 .storage_class = .EXTERNAL,
677 .number_of_aux_symbols = 0,
678 }) catch unreachable;
679
680 // string table
681 writer.writeInt(u32, string_table_byte_len, .little) catch unreachable;
682 writer.writeAll(null_import_descriptor_symbol_name) catch unreachable;
683 writer.writeByte(0) catch unreachable;
684
685 var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1);
686 errdefer allocator.free(symbol_names_for_import_lib);
687
688 const duped_symbol_name = try allocator.dupe(u8, null_import_descriptor_symbol_name);
689 errdefer allocator.free(duped_symbol_name);
690 symbol_names_for_import_lib[0] = duped_symbol_name;
691
692 // Confirm byte length was calculated exactly correctly
693 std.debug.assert(writer.end == bytes.len);
694 return .{
695 .bytes = bytes,
696 .name = module_import_name,
697 .symbol_names_for_import_lib = symbol_names_for_import_lib,
698 };
699}
700
701fn getNullThunk(
702 allocator: std.mem.Allocator,
703 machine_type: std.coff.IMAGE.FILE.MACHINE,
704 module_import_name: []const u8,
705 null_thunk_symbol_name: []const u8,
706) error{OutOfMemory}!Members.Member {
707 const number_of_sections = 2;
708 const number_of_symbols = 1;
709 const va_size: u32 = if (is64Bit(machine_type)) 8 else 4;
710 const pointer_to_idata5_data = @sizeOf(std.coff.Header) +
711 (@sizeOf(std.coff.SectionHeader) * number_of_sections);
712 const pointer_to_idata4_data = pointer_to_idata5_data + va_size;
713 const pointer_to_symbol_table = pointer_to_idata4_data + va_size;
714
715 const string_table_byte_len = 4 + null_thunk_symbol_name.len + 1;
716 const total_byte_len = pointer_to_symbol_table +
717 (std.coff.Symbol.sizeOf() * number_of_symbols) +
718 string_table_byte_len;
719
720 const bytes = try allocator.alloc(u8, total_byte_len);
721 errdefer allocator.free(bytes);
722 var writer: std.Io.Writer = .fixed(bytes);
723
724 writer.writeStruct(std.coff.Header{
725 .machine = machine_type,
726 .number_of_sections = number_of_sections,
727 .time_date_stamp = 0,
728 .pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
729 .number_of_symbols = number_of_symbols,
730 .size_of_optional_header = 0,
731 .flags = .{ .@"32BIT_MACHINE" = !is64Bit(machine_type) },
732 }, .little) catch unreachable;
733
734 writer.writeStruct(std.coff.SectionHeader{
735 .name = ".idata$5".*,
736 .virtual_size = 0,
737 .virtual_address = 0,
738 .size_of_raw_data = va_size,
739 .pointer_to_raw_data = pointer_to_idata5_data,
740 .pointer_to_relocations = 0,
741 .pointer_to_linenumbers = 0,
742 .number_of_relocations = 0,
743 .number_of_linenumbers = 0,
744 .flags = .{
745 .ALIGN = if (is64Bit(machine_type))
746 .@"8BYTES"
747 else
748 .@"4BYTES",
749 .CNT_INITIALIZED_DATA = true,
750 .MEM_WRITE = true,
751 .MEM_READ = true,
752 },
753 }, .little) catch unreachable;
754
755 writer.writeStruct(std.coff.SectionHeader{
756 .name = ".idata$4".*,
757 .virtual_size = 0,
758 .virtual_address = 0,
759 .size_of_raw_data = va_size,
760 .pointer_to_raw_data = pointer_to_idata4_data,
761 .pointer_to_relocations = 0,
762 .pointer_to_linenumbers = 0,
763 .number_of_relocations = 0,
764 .number_of_linenumbers = 0,
765 .flags = .{
766 .ALIGN = if (is64Bit(machine_type))
767 .@"8BYTES"
768 else
769 .@"4BYTES",
770 .CNT_INITIALIZED_DATA = true,
771 .MEM_WRITE = true,
772 .MEM_READ = true,
773 },
774 }, .little) catch unreachable;
775
776 // .idata$5
777 writer.splatByteAll(0, va_size) catch unreachable;
778 // .idata$4
779 writer.splatByteAll(0, va_size) catch unreachable;
780
781 writeSymbol(&writer, .{
782 .name = first_string_table_entry,
783 .value = 0,
784 .section_number = @enumFromInt(1),
785 .type = .{
786 .base_type = .NULL,
787 .complex_type = .NULL,
788 },
789 .storage_class = .EXTERNAL,
790 .number_of_aux_symbols = 0,
791 }) catch unreachable;
792
793 // string table
794 writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable;
795 writer.writeAll(null_thunk_symbol_name) catch unreachable;
796 writer.writeByte(0) catch unreachable;
797
798 var symbol_names_for_import_lib = try allocator.alloc([]const u8, 1);
799 errdefer allocator.free(symbol_names_for_import_lib);
800
801 const duped_symbol_name = try allocator.dupe(u8, null_thunk_symbol_name);
802 errdefer allocator.free(duped_symbol_name);
803 symbol_names_for_import_lib[0] = duped_symbol_name;
804
805 // Confirm byte length was calculated exactly correctly
806 std.debug.assert(writer.end == bytes.len);
807 return .{
808 .bytes = bytes,
809 .name = module_import_name,
810 .symbol_names_for_import_lib = symbol_names_for_import_lib,
811 };
812}
813
814const WeakExternalOptions = struct {
815 imp_prefix: bool,
816 machine_type: std.coff.IMAGE.FILE.MACHINE,
817};
818
819fn getWeakExternal(
820 arena: std.mem.Allocator,
821 module_import_name: []const u8,
822 sym: []const u8,
823 weak: []const u8,
824 options: WeakExternalOptions,
825) error{OutOfMemory}!Members.Member {
826 const number_of_sections = 1;
827 const number_of_symbols = 4;
828 const number_of_weak_external_defs = 1;
829 const pointer_to_symbol_table = @sizeOf(std.coff.Header) +
830 (@sizeOf(std.coff.SectionHeader) * number_of_sections);
831
832 const symbol_names = try arena.alloc([]const u8, 2);
833
834 symbol_names[0] = if (options.imp_prefix)
835 try std.mem.concat(arena, u8, &.{ "__imp_", sym })
836 else
837 try arena.dupe(u8, sym);
838
839 symbol_names[1] = if (options.imp_prefix)
840 try std.mem.concat(arena, u8, &.{ "__imp_", weak })
841 else
842 try arena.dupe(u8, weak);
843
844 const string_table_byte_len = 4 + symbol_names[0].len + 1 + symbol_names[1].len + 1;
845 const total_byte_len = pointer_to_symbol_table +
846 (std.coff.Symbol.sizeOf() * number_of_symbols) +
847 (std.coff.WeakExternalDefinition.sizeOf() * number_of_weak_external_defs) +
848 string_table_byte_len;
849
850 const bytes = try arena.alloc(u8, total_byte_len);
851 errdefer arena.free(bytes);
852 var writer: std.Io.Writer = .fixed(bytes);
853
854 writer.writeStruct(std.coff.Header{
855 .machine = options.machine_type,
856 .number_of_sections = number_of_sections,
857 .time_date_stamp = 0,
858 .pointer_to_symbol_table = @intCast(pointer_to_symbol_table),
859 .number_of_symbols = number_of_symbols + number_of_weak_external_defs,
860 .size_of_optional_header = 0,
861 .flags = .{},
862 }, .little) catch unreachable;
863
864 writer.writeStruct(std.coff.SectionHeader{
865 .name = ".drectve".*,
866 .virtual_size = 0,
867 .virtual_address = 0,
868 .size_of_raw_data = 0,
869 .pointer_to_raw_data = 0,
870 .pointer_to_relocations = 0,
871 .pointer_to_linenumbers = 0,
872 .number_of_relocations = 0,
873 .number_of_linenumbers = 0,
874 .flags = .{
875 .LNK_INFO = true,
876 .LNK_REMOVE = true,
877 },
878 }, .little) catch unreachable;
879
880 writeSymbol(&writer, .{
881 .name = "@comp.id".*,
882 .value = 0,
883 .section_number = .ABSOLUTE,
884 .type = .{
885 .base_type = .NULL,
886 .complex_type = .NULL,
887 },
888 .storage_class = .STATIC,
889 .number_of_aux_symbols = 0,
890 }) catch unreachable;
891 writeSymbol(&writer, .{
892 .name = "@feat.00".*,
893 .value = 0,
894 .section_number = .ABSOLUTE,
895 .type = .{
896 .base_type = .NULL,
897 .complex_type = .NULL,
898 },
899 .storage_class = .STATIC,
900 .number_of_aux_symbols = 0,
901 }) catch unreachable;
902 var string_table_offset: usize = first_string_table_entry_offset;
903 writeSymbol(&writer, .{
904 .name = first_string_table_entry,
905 .value = 0,
906 .section_number = @enumFromInt(0),
907 .type = .{
908 .base_type = .NULL,
909 .complex_type = .NULL,
910 },
911 .storage_class = .EXTERNAL,
912 .number_of_aux_symbols = 0,
913 }) catch unreachable;
914 string_table_offset += symbol_names[0].len + 1;
915 writeSymbol(&writer, .{
916 .name = getNameBytesForStringTableOffset(@intCast(string_table_offset)),
917 .value = 0,
918 .section_number = @enumFromInt(0),
919 .type = .{
920 .base_type = .NULL,
921 .complex_type = .NULL,
922 },
923 .storage_class = .WEAK_EXTERNAL,
924 .number_of_aux_symbols = 1,
925 }) catch unreachable;
926 writeWeakExternalDefinition(&writer, .{
927 .tag_index = 2,
928 .flag = .SEARCH_ALIAS,
929 .unused = @splat(0),
930 }) catch unreachable;
931
932 // string table
933 writer.writeInt(u32, @intCast(string_table_byte_len), .little) catch unreachable;
934 writer.writeAll(symbol_names[0]) catch unreachable;
935 writer.writeByte(0) catch unreachable;
936 writer.writeAll(symbol_names[1]) catch unreachable;
937 writer.writeByte(0) catch unreachable;
938
939 // Confirm byte length was calculated exactly correctly
940 std.debug.assert(writer.end == bytes.len);
941 return .{
942 .bytes = bytes,
943 .name = module_import_name,
944 .symbol_names_for_import_lib = symbol_names,
945 };
946}
947
948const GetShortImportError = error{UnknownImportType} || std.mem.Allocator.Error;
949
950fn getShortImport(
951 arena: std.mem.Allocator,
952 module_import_name: []const u8,
953 sym: []const u8,
954 export_name: ?[]const u8,
955 machine_type: std.coff.IMAGE.FILE.MACHINE,
956 ordinal_hint: u16,
957 import_type: std.coff.ImportType,
958 name_type: std.coff.ImportNameType,
959) GetShortImportError!Members.Member {
960 var size_of_data = module_import_name.len + 1 + sym.len + 1;
961 if (export_name) |name| size_of_data += name.len + 1;
962 const total_byte_len = @sizeOf(std.coff.ImportHeader) + size_of_data;
963
964 const bytes = try arena.alloc(u8, total_byte_len);
965 errdefer arena.free(bytes);
966 var writer = std.Io.Writer.fixed(bytes);
967
968 writer.writeStruct(std.coff.ImportHeader{
969 .version = 0,
970 .machine = machine_type,
971 .time_date_stamp = 0,
972 .size_of_data = @intCast(size_of_data),
973 .hint = ordinal_hint,
974 .types = .{
975 .type = import_type,
976 .name_type = name_type,
977 .reserved = 0,
978 },
979 }, .little) catch unreachable;
980
981 writer.writeAll(sym) catch unreachable;
982 writer.writeByte(0) catch unreachable;
983 writer.writeAll(module_import_name) catch unreachable;
984 writer.writeByte(0) catch unreachable;
985 if (export_name) |name| {
986 writer.writeAll(name) catch unreachable;
987 writer.writeByte(0) catch unreachable;
988 }
989
990 var symbol_names_for_import_lib: std.ArrayList([]const u8) = try .initCapacity(arena, 2);
991
992 switch (import_type) {
993 .CODE, .CONST => {
994 symbol_names_for_import_lib.appendAssumeCapacity(try std.mem.concat(arena, u8, &.{ "__imp_", sym }));
995 symbol_names_for_import_lib.appendAssumeCapacity(try arena.dupe(u8, sym));
996 },
997 .DATA => {
998 symbol_names_for_import_lib.appendAssumeCapacity(try std.mem.concat(arena, u8, &.{ "__imp_", sym }));
999 },
1000 else => return error.UnknownImportType,
1001 }
1002
1003 // Confirm byte length was calculated exactly correctly
1004 std.debug.assert(writer.end == bytes.len);
1005 return .{
1006 .bytes = bytes,
1007 .name = module_import_name,
1008 .symbol_names_for_import_lib = try symbol_names_for_import_lib.toOwnedSlice(arena),
1009 };
1010}
1011
1012fn writeSymbol(writer: *std.Io.Writer, symbol: std.coff.Symbol) !void {
1013 try writer.writeAll(&symbol.name);
1014 try writer.writeInt(u32, symbol.value, .little);
1015 try writer.writeInt(u16, @intFromEnum(symbol.section_number), .little);
1016 try writer.writeInt(u8, @intFromEnum(symbol.type.base_type), .little);
1017 try writer.writeInt(u8, @intFromEnum(symbol.type.complex_type), .little);
1018 try writer.writeInt(u8, @intFromEnum(symbol.storage_class), .little);
1019 try writer.writeInt(u8, symbol.number_of_aux_symbols, .little);
1020}
1021
1022fn writeWeakExternalDefinition(writer: *std.Io.Writer, weak_external: std.coff.WeakExternalDefinition) !void {
1023 try writer.writeInt(u32, weak_external.tag_index, .little);
1024 try writer.writeInt(u32, @intFromEnum(weak_external.flag), .little);
1025 try writer.writeAll(&weak_external.unused);
1026}
1027
1028fn writeRelocation(writer: *std.Io.Writer, relocation: std.coff.Relocation) !void {
1029 try writer.writeInt(u32, relocation.virtual_address, .little);
1030 try writer.writeInt(u32, relocation.symbol_table_index, .little);
1031 try writer.writeInt(u16, relocation.type, .little);
1032}
1033
1034// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#type-indicators
1035pub fn rvaRelocationTypeIndicator(target: std.coff.IMAGE.FILE.MACHINE) ?u16 {
1036 return switch (target) {
1037 .AMD64 => @intFromEnum(std.coff.IMAGE.REL.AMD64.ADDR32NB),
1038 .I386 => @intFromEnum(std.coff.IMAGE.REL.I386.DIR32NB),
1039 .ARMNT => @intFromEnum(std.coff.IMAGE.REL.ARM.ADDR32NB),
1040 .ARM64, .ARM64EC, .ARM64X => @intFromEnum(std.coff.IMAGE.REL.ARM64.ADDR32NB),
1041 .IA64 => @intFromEnum(std.coff.IMAGE.REL.IA64.DIR32NB),
1042 else => null,
1043 };
1044}
1045
1046const StringTable = struct {
1047 data: std.ArrayList(u8) = .empty,
1048 map: std.HashMapUnmanaged(u32, void, std.hash_map.StringIndexContext, std.hash_map.default_max_load_percentage) = .empty,
1049
1050 pub fn deinit(self: *StringTable, allocator: Allocator) void {
1051 self.data.deinit(allocator);
1052 self.map.deinit(allocator);
1053 }
1054
1055 pub fn put(self: *StringTable, allocator: Allocator, value: []const u8) !u32 {
1056 const result = try self.map.getOrPutContextAdapted(
1057 allocator,
1058 value,
1059 std.hash_map.StringIndexAdapter{ .bytes = &self.data },
1060 .{ .bytes = &self.data },
1061 );
1062 if (result.found_existing) {
1063 return result.key_ptr.*;
1064 }
1065
1066 try self.data.ensureUnusedCapacity(allocator, value.len + 1);
1067 const offset: u32 = @intCast(self.data.items.len);
1068
1069 self.data.appendSliceAssumeCapacity(value);
1070 self.data.appendAssumeCapacity(0);
1071
1072 result.key_ptr.* = offset;
1073
1074 return offset;
1075 }
1076
1077 pub fn get(self: StringTable, offset: u32) []const u8 {
1078 std.debug.assert(offset < self.data.items.len);
1079 return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(self.data.items.ptr + offset)), 0);
1080 }
1081
1082 pub fn getOffset(self: *StringTable, value: []const u8) ?u32 {
1083 return self.map.getKeyAdapted(
1084 value,
1085 std.hash_map.StringIndexAdapter{ .bytes = &self.data },
1086 );
1087 }
1088};
src/zig_llvm.cpp-59
...@@ -39,9 +39,6 @@...@@ -39,9 +39,6 @@
39#include <llvm/Passes/StandardInstrumentations.h>39#include <llvm/Passes/StandardInstrumentations.h>
40#include <llvm/Object/Archive.h>40#include <llvm/Object/Archive.h>
41#include <llvm/Object/ArchiveWriter.h>41#include <llvm/Object/ArchiveWriter.h>
42#include <llvm/Object/COFF.h>
43#include <llvm/Object/COFFImportFile.h>
44#include <llvm/Object/COFFModuleDefinition.h>
45#include <llvm/PassRegistry.h>42#include <llvm/PassRegistry.h>
46#include <llvm/Support/CommandLine.h>43#include <llvm/Support/CommandLine.h>
47#include <llvm/Support/FileSystem.h>44#include <llvm/Support/FileSystem.h>
...@@ -475,62 +472,6 @@ void ZigLLVMParseCommandLineOptions(size_t argc, const char *const *argv) {...@@ -475,62 +472,6 @@ void ZigLLVMParseCommandLineOptions(size_t argc, const char *const *argv) {
475 cl::ParseCommandLineOptions(argc, argv);472 cl::ParseCommandLineOptions(argc, argv);
476}473}
477474
478bool ZigLLVMWriteImportLibrary(const char *def_path, unsigned int coff_machine,
479 const char *output_lib_path, bool kill_at)
480{
481 COFF::MachineTypes machine = static_cast<COFF::MachineTypes>(coff_machine);
482
483 auto bufOrErr = MemoryBuffer::getFile(def_path);
484 if (!bufOrErr) {
485 return false;
486 }
487
488 MemoryBuffer& buf = *bufOrErr.get();
489 Expected<object::COFFModuleDefinition> def =
490 object::parseCOFFModuleDefinition(buf, machine, /* MingwDef */ true);
491
492 if (!def) {
493 return true;
494 }
495
496 // The exports-juggling code below is ripped from LLVM's DlltoolDriver.cpp
497
498 // If ExtName is set (if the "ExtName = Name" syntax was used), overwrite
499 // Name with ExtName and clear ExtName. When only creating an import
500 // library and not linking, the internal name is irrelevant. This avoids
501 // cases where writeImportLibrary tries to transplant decoration from
502 // symbol decoration onto ExtName.
503 for (object::COFFShortExport& E : def->Exports) {
504 if (!E.ExtName.empty()) {
505 E.Name = E.ExtName;
506 E.ExtName.clear();
507 }
508 }
509
510 if (kill_at) {
511 for (object::COFFShortExport& E : def->Exports) {
512 if (!E.ImportName.empty() || (!E.Name.empty() && E.Name[0] == '?'))
513 continue;
514 if (machine == COFF::IMAGE_FILE_MACHINE_I386) {
515 // By making sure E.SymbolName != E.Name for decorated symbols,
516 // writeImportLibrary writes these symbols with the type
517 // IMPORT_NAME_UNDECORATE.
518 E.SymbolName = E.Name;
519 }
520 // Trim off the trailing decoration. Symbols will always have a
521 // starting prefix here (either _ for cdecl/stdcall, @ for fastcall
522 // or ? for C++ functions). Vectorcall functions won't have any
523 // fixed prefix, but the function base name will still be at least
524 // one char.
525 E.Name = E.Name.substr(0, E.Name.find('@', 1));
526 }
527 }
528
529 return static_cast<bool>(
530 object::writeImportLibrary(def->OutputFile, output_lib_path,
531 def->Exports, machine, /* MinGW */ true));
532}
533
534bool ZigLLVMWriteArchive(const char *archive_name, const char **file_names, size_t file_name_count,475bool ZigLLVMWriteArchive(const char *archive_name, const char **file_names, size_t file_name_count,
535 ZigLLVMArchiveKind archive_kind)476 ZigLLVMArchiveKind archive_kind)
536{477{
src/zig_llvm.h-3
...@@ -124,7 +124,4 @@ ZIG_EXTERN_C bool ZigLLDLinkWasm(int argc, const char **argv, bool can_exit_earl...@@ -124,7 +124,4 @@ ZIG_EXTERN_C bool ZigLLDLinkWasm(int argc, const char **argv, bool can_exit_earl
124ZIG_EXTERN_C bool ZigLLVMWriteArchive(const char *archive_name, const char **file_names, size_t file_name_count,124ZIG_EXTERN_C bool ZigLLVMWriteArchive(const char *archive_name, const char **file_names, size_t file_name_count,
125 ZigLLVMArchiveKind archive_kind);125 ZigLLVMArchiveKind archive_kind);
126126
127ZIG_EXTERN_C bool ZigLLVMWriteImportLibrary(const char *def_path, unsigned int coff_machine,
128 const char *output_lib_path, bool kill_at);
129
130#endif127#endif