authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-06-21 00:03:52+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-06-21 00:03:52+02:00
log7b5bd3a93f1c54609cdab19952a6f4b3fc8a20fa
tree3fed06a5b7ac8802689af1656454ae0267855ca0
parent939e4d81e11e801be4eb8fcaddbd27e6b8d3491c
parenteb1050b83aecc3b681901613eeb316030f08ad12
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #16097 from ziglang/macho-faster-uuid

macho: parallelize UUID hash calculation at the expense of full compatibility with ld64

9 files changed, 149 insertions(+), 380 deletions(-)

CMakeLists.txt+2
......@@ -594,8 +594,10 @@ set(ZIG_STAGE2_SOURCES
594594 "${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig"
595595 "${CMAKE_SOURCE_DIR}/src/link/MachO/eh_frame.zig"
596596 "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig"
597 "${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig"
597598 "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig"
598599 "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig"
600 "${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig"
599601 "${CMAKE_SOURCE_DIR}/src/link/MachO/zld.zig"
600602 "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig"
601603 "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig"
src/link/MachO.zig+14-7
......@@ -13,6 +13,7 @@ const mem = std.mem;
1313const meta = std.meta;
1414
1515const aarch64 = @import("../arch/aarch64/bits.zig");
16const calcUuid = @import("MachO/uuid.zig").calcUuid;
1617const codegen = @import("../codegen.zig");
1718const dead_strip = @import("MachO/dead_strip.zig");
1819const fat = @import("MachO/fat.zig");
......@@ -756,11 +757,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
756757 });
757758 try load_commands.writeBuildVersionLC(&self.base.options, lc_writer);
758759
759 if (self.cold_start) {
760 std.crypto.random.bytes(&self.uuid_cmd.uuid);
761 Md5.hash(&self.uuid_cmd.uuid, &self.uuid_cmd.uuid, .{});
762 conformUuid(&self.uuid_cmd.uuid);
763 }
760 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @intCast(u32, lc_buffer.items.len);
764761 try lc_writer.writeStruct(self.uuid_cmd);
765762
766763 try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), lc_writer);
......@@ -769,10 +766,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
769766 try lc_writer.writeStruct(self.codesig_cmd);
770767 }
771768
772 try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
773
774769 const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
770 try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
775771 try self.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len));
772 try self.writeUuid(comp, uuid_cmd_offset, requires_codesig);
776773
777774 if (codesig) |*csig| {
778775 try self.writeCodeSignature(comp, csig); // code signing always comes last
......@@ -3510,6 +3507,16 @@ fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void {
35103507 self.dysymtab_cmd.nindirectsyms = nindirectsyms;
35113508}
35123509
3510fn writeUuid(self: *MachO, comp: *const Compilation, uuid_cmd_offset: u32, has_codesig: bool) !void {
3511 const file_size = if (!has_codesig) blk: {
3512 const seg = self.getLinkeditSegmentPtr();
3513 break :blk seg.fileoff + seg.filesize;
3514 } else self.codesig_cmd.dataoff;
3515 try calcUuid(comp, self.base.file.?, file_size, &self.uuid_cmd.uuid);
3516 const offset = uuid_cmd_offset + @sizeOf(macho.load_command);
3517 try self.base.file.?.pwriteAll(&self.uuid_cmd.uuid, offset);
3518}
3519
35133520fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
35143521 const seg = self.getLinkeditSegmentPtr();
35153522 // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file
src/link/MachO/CodeSignature.zig+6-59
......@@ -7,11 +7,10 @@ const log = std.log.scoped(.link);
77const macho = std.macho;
88const mem = std.mem;
99const testing = std.testing;
10const ThreadPool = std.Thread.Pool;
11const WaitGroup = std.Thread.WaitGroup;
1210
1311const Allocator = mem.Allocator;
1412const Compilation = @import("../../Compilation.zig");
13const Hasher = @import("hasher.zig").ParallelHasher;
1514const Sha256 = std.crypto.hash.sha2.Sha256;
1615
1716const hash_size = Sha256.digest_length;
......@@ -289,7 +288,11 @@ pub fn writeAdhocSignature(
289288 self.code_directory.inner.nCodeSlots = total_pages;
290289
291290 // Calculate hash for each page (in file) and write it to the buffer
292 try self.parallelHash(gpa, comp.thread_pool, opts.file, opts.file_size);
291 var hasher = Hasher(Sha256){ .allocator = gpa, .thread_pool = comp.thread_pool };
292 try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
293 .chunk_size = self.page_size,
294 .max_file_size = opts.file_size,
295 });
293296
294297 try blobs.append(.{ .code_directory = &self.code_directory });
295298 header.length += @sizeOf(macho.BlobIndex);
......@@ -348,62 +351,6 @@ pub fn writeAdhocSignature(
348351 }
349352}
350353
351fn parallelHash(
352 self: *CodeSignature,
353 gpa: Allocator,
354 pool: *ThreadPool,
355 file: fs.File,
356 file_size: u32,
357) !void {
358 var wg: WaitGroup = .{};
359
360 const total_num_chunks = mem.alignForward(usize, file_size, self.page_size) / self.page_size;
361 assert(self.code_directory.code_slots.items.len >= total_num_chunks);
362
363 const buffer = try gpa.alloc(u8, self.page_size * total_num_chunks);
364 defer gpa.free(buffer);
365
366 const results = try gpa.alloc(fs.File.PReadError!usize, total_num_chunks);
367 defer gpa.free(results);
368
369 {
370 wg.reset();
371 defer wg.wait();
372
373 var i: usize = 0;
374 while (i < total_num_chunks) : (i += 1) {
375 const fstart = i * self.page_size;
376 const fsize = if (fstart + self.page_size > file_size)
377 file_size - fstart
378 else
379 self.page_size;
380 wg.start();
381 try pool.spawn(worker, .{
382 file,
383 fstart,
384 buffer[fstart..][0..fsize],
385 &self.code_directory.code_slots.items[i],
386 &results[i],
387 &wg,
388 });
389 }
390 }
391 for (results) |result| _ = try result;
392}
393
394fn worker(
395 file: fs.File,
396 fstart: usize,
397 buffer: []u8,
398 out: *[hash_size]u8,
399 err: *fs.File.PReadError!usize,
400 wg: *WaitGroup,
401) void {
402 defer wg.finish();
403 err.* = file.preadAll(buffer, fstart);
404 Sha256.hash(buffer, out, .{});
405}
406
407354pub fn size(self: CodeSignature) u32 {
408355 var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
409356 if (self.requirements) |req| {
src/link/MachO/hasher.zig created+71
......@@ -0,0 +1,71 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const fs = std.fs;
4const mem = std.mem;
5
6const Allocator = mem.Allocator;
7const ThreadPool = std.Thread.Pool;
8const WaitGroup = std.Thread.WaitGroup;
9
10pub fn ParallelHasher(comptime Hasher: type) type {
11 const hash_size = Hasher.digest_length;
12
13 return struct {
14 allocator: Allocator,
15 thread_pool: *ThreadPool,
16
17 pub fn hash(self: Self, file: fs.File, out: [][hash_size]u8, opts: struct {
18 chunk_size: u64 = 0x4000,
19 max_file_size: ?u64 = null,
20 }) !void {
21 var wg: WaitGroup = .{};
22
23 const file_size = blk: {
24 const file_size = opts.max_file_size orelse try file.getEndPos();
25 break :blk std.math.cast(usize, file_size) orelse return error.Overflow;
26 };
27 const chunk_size = std.math.cast(usize, opts.chunk_size) orelse return error.Overflow;
28
29 const buffer = try self.allocator.alloc(u8, chunk_size * out.len);
30 defer self.allocator.free(buffer);
31
32 const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len);
33 defer self.allocator.free(results);
34
35 {
36 wg.reset();
37 defer wg.wait();
38
39 for (out, results, 0..) |*out_buf, *result, i| {
40 const fstart = i * chunk_size;
41 const fsize = if (fstart + chunk_size > file_size) file_size - fstart else chunk_size;
42 wg.start();
43 try self.thread_pool.spawn(worker, .{
44 file,
45 fstart,
46 buffer[fstart..][0..fsize],
47 &(out_buf.*),
48 &(result.*),
49 &wg,
50 });
51 }
52 }
53 for (results) |result| _ = try result;
54 }
55
56 fn worker(
57 file: fs.File,
58 fstart: usize,
59 buffer: []u8,
60 out: *[hash_size]u8,
61 err: *fs.File.PReadError!usize,
62 wg: *WaitGroup,
63 ) void {
64 defer wg.finish();
65 err.* = file.preadAll(buffer, fstart);
66 Hasher.hash(buffer, out, .{});
67 }
68
69 const Self = @This();
70 };
71}
src/link/MachO/uuid.zig created+45
......@@ -0,0 +1,45 @@
1const std = @import("std");
2const fs = std.fs;
3const mem = std.mem;
4
5const Allocator = mem.Allocator;
6const Compilation = @import("../../Compilation.zig");
7const Md5 = std.crypto.hash.Md5;
8const Hasher = @import("hasher.zig").ParallelHasher;
9
10/// Calculates Md5 hash of each chunk in parallel and then hashes all Md5 hashes to produce
11/// the final digest.
12/// While this is NOT a correct MD5 hash of the contents, this methodology is used by LLVM/LLD
13/// and we will use it too as it seems accepted by Apple OSes.
14/// TODO LLD also hashes the output filename to disambiguate between same builds with different
15/// output files. Should we also do that?
16pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void {
17 const num_chunks = comp.thread_pool.threads.len * 0x10;
18 const chunk_size = @divTrunc(file_size, num_chunks);
19 const actual_num_chunks = if (@rem(file_size, num_chunks) > 0) num_chunks + 1 else num_chunks;
20
21 const hashes = try comp.gpa.alloc([Md5.digest_length]u8, actual_num_chunks);
22 defer comp.gpa.free(hashes);
23
24 var hasher = Hasher(Md5){ .allocator = comp.gpa, .thread_pool = comp.thread_pool };
25 try hasher.hash(file, hashes, .{
26 .chunk_size = chunk_size,
27 .max_file_size = file_size,
28 });
29
30 const final_buffer = try comp.gpa.alloc(u8, actual_num_chunks * Md5.digest_length);
31 defer comp.gpa.free(final_buffer);
32
33 for (hashes, 0..) |hash, i| {
34 mem.copy(u8, final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash);
35 }
36
37 Md5.hash(final_buffer, out, .{});
38 conform(out);
39}
40
41inline fn conform(out: *[Md5.digest_length]u8) void {
42 // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats
43 out[6] = (out[6] & 0x0F) | (3 << 4);
44 out[8] = (out[8] & 0x3F) | 0x80;
45}
src/link/MachO/zld.zig+11-159
......@@ -9,14 +9,15 @@ const math = std.math;
99const mem = std.mem;
1010
1111const aarch64 = @import("../../arch/aarch64/bits.zig");
12const calcUuid = @import("uuid.zig").calcUuid;
1213const dead_strip = @import("dead_strip.zig");
1314const eh_frame = @import("eh_frame.zig");
1415const fat = @import("fat.zig");
1516const link = @import("../../link.zig");
1617const load_commands = @import("load_commands.zig");
18const stub_helpers = @import("stubs.zig");
1719const thunks = @import("thunks.zig");
1820const trace = @import("../../tracy.zig").trace;
19const stub_helpers = @import("stubs.zig");
2021
2122const Allocator = mem.Allocator;
2223const Archive = @import("Archive.zig");
......@@ -2575,150 +2576,14 @@ pub const Zld = struct {
25752576 self.dysymtab_cmd.nindirectsyms = nindirectsyms;
25762577 }
25772578
2578 fn writeUuid(self: *Zld, comp: *const Compilation, args: struct {
2579 linkedit_cmd_offset: u32,
2580 symtab_cmd_offset: u32,
2581 uuid_cmd_offset: u32,
2582 codesig_cmd_offset: ?u32,
2583 }) !void {
2584 _ = comp;
2585 switch (self.options.optimize_mode) {
2586 .Debug => {
2587 // In Debug we don't really care about reproducibility, so put in a random value
2588 // and be done with it.
2589 std.crypto.random.bytes(&self.uuid_cmd.uuid);
2590 Md5.hash(&self.uuid_cmd.uuid, &self.uuid_cmd.uuid, .{});
2591 conformUuid(&self.uuid_cmd.uuid);
2592 },
2593 else => {
2594 // We set the max file size to the actual strtab buffer length to exclude any strtab padding.
2595 const max_file_end = @intCast(u32, self.symtab_cmd.stroff + self.strtab.buffer.items.len);
2596
2597 const FileSubsection = struct {
2598 start: u32,
2599 end: u32,
2600 };
2601
2602 var subsections: [5]FileSubsection = undefined;
2603 var count: usize = 0;
2604
2605 // Exclude LINKEDIT segment command as it contains file size that includes stabs contribution
2606 // and code signature.
2607 subsections[count] = .{
2608 .start = 0,
2609 .end = args.linkedit_cmd_offset,
2610 };
2611 count += 1;
2612
2613 // Exclude SYMTAB and DYSYMTAB commands for the same reason.
2614 subsections[count] = .{
2615 .start = subsections[count - 1].end + @sizeOf(macho.segment_command_64),
2616 .end = args.symtab_cmd_offset,
2617 };
2618 count += 1;
2619
2620 // Exclude CODE_SIGNATURE command (if present).
2621 if (args.codesig_cmd_offset) |offset| {
2622 subsections[count] = .{
2623 .start = subsections[count - 1].end + @sizeOf(macho.symtab_command) + @sizeOf(macho.dysymtab_command),
2624 .end = offset,
2625 };
2626 count += 1;
2627 }
2628
2629 if (!self.options.strip) {
2630 // Exclude region comprising all symbol stabs.
2631 const nlocals = self.dysymtab_cmd.nlocalsym;
2632
2633 const locals = try self.gpa.alloc(macho.nlist_64, nlocals);
2634 defer self.gpa.free(locals);
2635
2636 const locals_buf = @ptrCast([*]u8, locals.ptr)[0 .. @sizeOf(macho.nlist_64) * nlocals];
2637 const amt = try self.file.preadAll(locals_buf, self.symtab_cmd.symoff);
2638 if (amt != locals_buf.len) return error.InputOutput;
2639
2640 const istab: usize = for (locals, 0..) |local, i| {
2641 if (local.stab()) break i;
2642 } else locals.len;
2643 const nstabs = locals.len - istab;
2644
2645 if (nstabs == 0) {
2646 subsections[count] = .{
2647 .start = subsections[count - 1].end + if (args.codesig_cmd_offset == null)
2648 @as(u32, @sizeOf(macho.symtab_command) + @sizeOf(macho.dysymtab_command))
2649 else
2650 @sizeOf(macho.linkedit_data_command),
2651 .end = max_file_end,
2652 };
2653 count += 1;
2654 } else {
2655 // Exclude a subsection of the strtab with names of the stabs.
2656 // We do not care about anything succeeding strtab as it is the code signature data which is
2657 // not part of the UUID calculation anyway.
2658 const stab_stroff = locals[istab].n_strx;
2659
2660 subsections[count] = .{
2661 .start = subsections[count - 1].end + if (args.codesig_cmd_offset == null)
2662 @as(u32, @sizeOf(macho.symtab_command) + @sizeOf(macho.dysymtab_command))
2663 else
2664 @sizeOf(macho.linkedit_data_command),
2665 .end = @intCast(u32, self.symtab_cmd.symoff + istab * @sizeOf(macho.nlist_64)),
2666 };
2667 count += 1;
2668
2669 subsections[count] = .{
2670 .start = subsections[count - 1].end + @intCast(u32, nstabs * @sizeOf(macho.nlist_64)),
2671 .end = self.symtab_cmd.stroff + stab_stroff,
2672 };
2673 count += 1;
2674 }
2675 } else {
2676 subsections[count] = .{
2677 .start = subsections[count - 1].end + if (args.codesig_cmd_offset == null)
2678 @as(u32, @sizeOf(macho.symtab_command) + @sizeOf(macho.dysymtab_command))
2679 else
2680 @sizeOf(macho.linkedit_data_command),
2681 .end = max_file_end,
2682 };
2683 count += 1;
2684 }
2685
2686 const chunk_size = 0x4000;
2687
2688 var hasher = Md5.init(.{});
2689 var buffer: [chunk_size]u8 = undefined;
2690
2691 for (subsections[0..count]) |cut| {
2692 const size = cut.end - cut.start;
2693 const num_chunks = mem.alignForward(usize, size, chunk_size) / chunk_size;
2694
2695 var i: usize = 0;
2696 while (i < num_chunks) : (i += 1) {
2697 const fstart = cut.start + i * chunk_size;
2698 const fsize = if (fstart + chunk_size > cut.end)
2699 cut.end - fstart
2700 else
2701 chunk_size;
2702 const amt = try self.file.preadAll(buffer[0..fsize], fstart);
2703 if (amt != fsize) return error.InputOutput;
2704
2705 hasher.update(buffer[0..fsize]);
2706 }
2707 }
2708
2709 hasher.final(&self.uuid_cmd.uuid);
2710 conformUuid(&self.uuid_cmd.uuid);
2711 },
2712 }
2713
2714 const in_file = args.uuid_cmd_offset + @sizeOf(macho.load_command);
2715 try self.file.pwriteAll(&self.uuid_cmd.uuid, in_file);
2716 }
2717
2718 inline fn conformUuid(out: *[Md5.digest_length]u8) void {
2719 // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats
2720 out[6] = (out[6] & 0x0F) | (3 << 4);
2721 out[8] = (out[8] & 0x3F) | 0x80;
2579 fn writeUuid(self: *Zld, comp: *const Compilation, uuid_cmd_offset: u32, has_codesig: bool) !void {
2580 const file_size = if (!has_codesig) blk: {
2581 const seg = self.getLinkeditSegmentPtr();
2582 break :blk seg.fileoff + seg.filesize;
2583 } else self.codesig_cmd.dataoff;
2584 try calcUuid(comp, self.file, file_size, &self.uuid_cmd.uuid);
2585 const offset = uuid_cmd_offset + @sizeOf(macho.load_command);
2586 try self.file.pwriteAll(&self.uuid_cmd.uuid, offset);
27222587 }
27232588
27242589 fn writeCodeSignaturePadding(self: *Zld, code_sig: *CodeSignature) !void {
......@@ -4041,16 +3906,11 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
40413906 const lc_writer = lc_buffer.writer();
40423907
40433908 try zld.writeSegmentHeaders(lc_writer);
4044 const linkedit_cmd_offset = @sizeOf(macho.mach_header_64) + @intCast(u32, lc_buffer.items.len - @sizeOf(macho.segment_command_64));
4045
40463909 try lc_writer.writeStruct(zld.dyld_info_cmd);
40473910 try lc_writer.writeStruct(zld.function_starts_cmd);
40483911 try lc_writer.writeStruct(zld.data_in_code_cmd);
4049
4050 const symtab_cmd_offset = @sizeOf(macho.mach_header_64) + @intCast(u32, lc_buffer.items.len);
40513912 try lc_writer.writeStruct(zld.symtab_cmd);
40523913 try lc_writer.writeStruct(zld.dysymtab_cmd);
4053
40543914 try load_commands.writeDylinkerLC(lc_writer);
40553915
40563916 if (zld.options.output_mode == .Exe) {
......@@ -4088,22 +3948,14 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
40883948
40893949 try load_commands.writeLoadDylibLCs(zld.dylibs.items, zld.referenced_dylibs.keys(), lc_writer);
40903950
4091 var codesig_cmd_offset: ?u32 = null;
40923951 if (requires_codesig) {
4093 codesig_cmd_offset = @sizeOf(macho.mach_header_64) + @intCast(u32, lc_buffer.items.len);
40943952 try lc_writer.writeStruct(zld.codesig_cmd);
40953953 }
40963954
40973955 const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
40983956 try zld.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
40993957 try zld.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len));
4100
4101 try zld.writeUuid(comp, .{
4102 .linkedit_cmd_offset = linkedit_cmd_offset,
4103 .symtab_cmd_offset = symtab_cmd_offset,
4104 .uuid_cmd_offset = uuid_cmd_offset,
4105 .codesig_cmd_offset = codesig_cmd_offset,
4106 });
3958 try zld.writeUuid(comp, uuid_cmd_offset, requires_codesig);
41073959
41083960 if (codesig) |*csig| {
41093961 try zld.writeCodeSignature(comp, csig); // code signing always comes last
test/link.zig-9
......@@ -164,15 +164,6 @@ pub const cases = [_]Case{
164164 .build_root = "test/link/macho/unwind_info",
165165 .import = @import("link/macho/unwind_info/build.zig"),
166166 },
167 // TODO: re-enable this test. It currently has some incompatibilities with
168 // the new build system API. In particular, it depends on installing the build
169 // artifacts, which should be unnecessary, and it has a custom build step that
170 // prints directly to stderr instead of failing the step with an error message.
171 //.{
172 // .build_root = "test/link/macho/uuid",
173 // .import = @import("link/macho/uuid/build.zig"),
174 //},
175
176167 .{
177168 .build_root = "test/link/macho/weak_library",
178169 .import = @import("link/macho/weak_library/build.zig"),
test/link/macho/uuid/build.zig deleted-144
......@@ -1,144 +0,0 @@
1const std = @import("std");
2const FileSource = std.Build.FileSource;
3const Step = std.Build.Step;
4
5pub const requires_symlinks = true;
6
7pub fn build(b: *std.Build) void {
8 const test_step = b.step("test", "Test");
9 b.default_step = test_step;
10
11 // We force cross-compilation to ensure we always pick a generic CPU with
12 // constant set of CPU features.
13 const aarch64_macos = std.zig.CrossTarget{
14 .cpu_arch = .aarch64,
15 .os_tag = .macos,
16 };
17
18 testUuid(b, test_step, .ReleaseSafe, aarch64_macos);
19 testUuid(b, test_step, .ReleaseFast, aarch64_macos);
20 testUuid(b, test_step, .ReleaseSmall, aarch64_macos);
21
22 const x86_64_macos = std.zig.CrossTarget{
23 .cpu_arch = .x86_64,
24 .os_tag = .macos,
25 };
26
27 testUuid(b, test_step, .ReleaseSafe, x86_64_macos);
28 testUuid(b, test_step, .ReleaseFast, x86_64_macos);
29 testUuid(b, test_step, .ReleaseSmall, x86_64_macos);
30}
31
32fn testUuid(
33 b: *std.Build,
34 test_step: *std.Build.Step,
35 optimize: std.builtin.OptimizeMode,
36 target: std.zig.CrossTarget,
37) void {
38 // The calculated UUID value is independent of debug info and so it should
39 // stay the same across builds.
40 {
41 const dylib = simpleDylib(b, optimize, target);
42 const install_step = b.addInstallArtifact(dylib);
43 install_step.dest_sub_path = "test1.dylib";
44 install_step.step.dependOn(&dylib.step);
45 }
46 {
47 const dylib = simpleDylib(b, optimize, target);
48 dylib.strip = true;
49 const install_step = b.addInstallArtifact(dylib);
50 install_step.dest_sub_path = "test2.dylib";
51 install_step.step.dependOn(&dylib.step);
52 }
53
54 const cmp_step = CompareUuid.create(b, "test1.dylib", "test2.dylib");
55 test_step.dependOn(&cmp_step.step);
56}
57
58fn simpleDylib(
59 b: *std.Build,
60 optimize: std.builtin.OptimizeMode,
61 target: std.zig.CrossTarget,
62) *std.Build.Step.Compile {
63 const dylib = b.addSharedLibrary(.{
64 .name = "test",
65 .version = .{ .major = 1, .minor = 0, .patch = 0 },
66 .optimize = optimize,
67 .target = target,
68 });
69 dylib.addCSourceFile("test.c", &.{});
70 dylib.linkLibC();
71 return dylib;
72}
73
74const CompareUuid = struct {
75 pub const base_id = .custom;
76
77 step: Step,
78 lhs: []const u8,
79 rhs: []const u8,
80
81 pub fn create(owner: *std.Build, lhs: []const u8, rhs: []const u8) *CompareUuid {
82 const self = owner.allocator.create(CompareUuid) catch @panic("OOM");
83 self.* = CompareUuid{
84 .step = Step.init(.{
85 .id = base_id,
86 .name = owner.fmt("compare uuid: {s} and {s}", .{
87 lhs,
88 rhs,
89 }),
90 .owner = owner,
91 .makeFn = make,
92 }),
93 .lhs = lhs,
94 .rhs = rhs,
95 };
96 return self;
97 }
98
99 fn make(step: *Step, prog_node: *std.Progress.Node) anyerror!void {
100 _ = prog_node;
101 const b = step.owner;
102 const self = @fieldParentPtr(CompareUuid, "step", step);
103 const gpa = b.allocator;
104
105 var lhs_uuid: [16]u8 = undefined;
106 const lhs_path = b.getInstallPath(.lib, self.lhs);
107 try parseUuid(gpa, lhs_path, &lhs_uuid);
108
109 var rhs_uuid: [16]u8 = undefined;
110 const rhs_path = b.getInstallPath(.lib, self.rhs);
111 try parseUuid(gpa, rhs_path, &rhs_uuid);
112
113 try std.testing.expectEqualStrings(&lhs_uuid, &rhs_uuid);
114 }
115
116 fn parseUuid(gpa: std.mem.Allocator, path: []const u8, uuid: *[16]u8) anyerror!void {
117 const max_bytes: usize = 20 * 1024 * 1024;
118 const data = try std.fs.cwd().readFileAllocOptions(
119 gpa,
120 path,
121 max_bytes,
122 null,
123 @alignOf(u64),
124 null,
125 );
126 var stream = std.io.fixedBufferStream(data);
127 const reader = stream.reader();
128
129 const hdr = try reader.readStruct(std.macho.mach_header_64);
130 if (hdr.magic != std.macho.MH_MAGIC_64) {
131 return error.InvalidMagicNumber;
132 }
133
134 var it = std.macho.LoadCommandIterator{
135 .ncmds = hdr.ncmds,
136 .buffer = data[@sizeOf(std.macho.mach_header_64)..][0..hdr.sizeofcmds],
137 };
138 const cmd = while (it.next()) |cmd| switch (cmd.cmd()) {
139 .UUID => break cmd.cast(std.macho.uuid_command).?,
140 else => {},
141 } else return error.UuidLoadCommandNotFound;
142 std.mem.copy(u8, uuid, &cmd.uuid);
143 }
144};
test/link/macho/uuid/test.c deleted-2
......@@ -1,2 +0,0 @@
1void test() {}
2