Commit a4e6291fbd

Jakub Konka <kubkon@jakubkonka.com>
2022-01-10 16:02:07
stage2: enable zig test on x86_64-macos (#10551)
* stage2: put decls in different MachO sections Use `getDeclVAddrWithReloc` when targeting MachO backend rather than `getDeclVAddr` - this fn returns a zero vaddr and instead creates a relocation on the linker side which will get automatically updated whenever the target decl is moved in memory. This fn also records a rebase of the target pointer so that its value is correctly slid in presence of ASLR. This commit enables `zig test` on x86_64-macos. * stage2: fix output section selection for type,val pairs
1 parent 42ef95d
Changed files (3)
src
test
stage2
src/link/MachO.zig
@@ -38,6 +38,7 @@ const Module = @import("../Module.zig");
 const StringIndexAdapter = std.hash_map.StringIndexAdapter;
 const StringIndexContext = std.hash_map.StringIndexContext;
 const Trie = @import("MachO/Trie.zig");
+const Type = @import("../type.zig").Type;
 
 pub const TextBlock = Atom;
 
@@ -220,7 +221,7 @@ managed_atoms: std.ArrayListUnmanaged(*Atom) = .{},
 /// We store them here so that we can properly dispose of any allocated
 /// memory within the atom in the incremental linker.
 /// TODO consolidate this.
-decls: std.AutoArrayHashMapUnmanaged(*Module.Decl, void) = .{},
+decls: std.AutoArrayHashMapUnmanaged(*Module.Decl, ?MatchingSection) = .{},
 
 /// Currently active Module.Decl.
 /// TODO this might not be necessary if we figure out how to pass Module.Decl instance
@@ -3450,7 +3451,7 @@ pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {
     if (decl.link.macho.local_sym_index != 0) return;
 
     try self.locals.ensureUnusedCapacity(self.base.allocator, 1);
-    try self.decls.putNoClobber(self.base.allocator, decl, {});
+    try self.decls.putNoClobber(self.base.allocator, decl, null);
 
     if (self.locals_free_list.popOrNull()) |i| {
         log.debug("reusing symbol index {d} for {s}", .{ i, decl.name });
@@ -3656,19 +3657,169 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
     try self.updateDeclExports(module, decl, decl_exports);
 }
 
+fn isElemTyPointer(ty: Type) bool {
+    switch (ty.zigTypeTag()) {
+        .Fn => return false,
+        .Pointer => return true,
+        .Array => {
+            const elem_ty = ty.elemType();
+            return isElemTyPointer(elem_ty);
+        },
+        .Struct, .Union => {
+            const len = ty.structFieldCount();
+            var i: usize = 0;
+            while (i < len) : (i += 1) {
+                const field_ty = ty.structFieldType(i);
+                if (isElemTyPointer(field_ty)) return true;
+            }
+            return false;
+        },
+        else => return false,
+    }
+}
+
+fn getMatchingSectionDecl(self: *MachO, decl: *Module.Decl) !MatchingSection {
+    const code = decl.link.macho.code.items;
+    const alignment = decl.ty.abiAlignment(self.base.options.target);
+    const align_log_2 = math.log2(alignment);
+    const ty = decl.ty;
+    const zig_ty = ty.zigTypeTag();
+    const val = decl.val;
+    const mode = self.base.options.optimize_mode;
+    const match: MatchingSection = blk: {
+        // TODO finish and audit this function
+        if (val.isUndefDeep()) {
+            if (mode == .ReleaseFast or mode == .ReleaseSmall) {
+                break :blk MatchingSection{
+                    .seg = self.data_segment_cmd_index.?,
+                    .sect = self.bss_section_index.?,
+                };
+            }
+            break :blk (try self.getMatchingSection(.{
+                .segname = makeStaticString("__DATA"),
+                .sectname = makeStaticString("__data"),
+                .size = code.len,
+                .@"align" = align_log_2,
+            })).?;
+        }
+
+        switch (zig_ty) {
+            .Fn => {
+                break :blk MatchingSection{
+                    .seg = self.text_segment_cmd_index.?,
+                    .sect = self.text_section_index.?,
+                };
+            },
+            .Array => switch (val.tag()) {
+                .bytes => {
+                    switch (ty.tag()) {
+                        .array_u8_sentinel_0,
+                        .const_slice_u8_sentinel_0,
+                        .manyptr_const_u8_sentinel_0,
+                        => {
+                            break :blk (try self.getMatchingSection(.{
+                                .segname = makeStaticString("__TEXT"),
+                                .sectname = makeStaticString("__cstring"),
+                                .flags = macho.S_CSTRING_LITERALS,
+                                .size = code.len,
+                                .@"align" = align_log_2,
+                            })).?;
+                        },
+                        else => {
+                            break :blk (try self.getMatchingSection(.{
+                                .segname = makeStaticString("__TEXT"),
+                                .sectname = makeStaticString("__const"),
+                                .size = code.len,
+                                .@"align" = align_log_2,
+                            })).?;
+                        },
+                    }
+                },
+                .array => {
+                    if (isElemTyPointer(ty)) {
+                        break :blk (try self.getMatchingSection(.{
+                            .segname = makeStaticString("__DATA_CONST"),
+                            .sectname = makeStaticString("__const"),
+                            .size = code.len,
+                            .@"align" = 3, // TODO I think this should not be needed
+                        })).?;
+                    } else {
+                        break :blk (try self.getMatchingSection(.{
+                            .segname = makeStaticString("__TEXT"),
+                            .sectname = makeStaticString("__const"),
+                            .size = code.len,
+                            .@"align" = align_log_2,
+                        })).?;
+                    }
+                },
+                else => {
+                    break :blk (try self.getMatchingSection(.{
+                        .segname = makeStaticString("__TEXT"),
+                        .sectname = makeStaticString("__const"),
+                        .size = code.len,
+                        .@"align" = align_log_2,
+                    })).?;
+                },
+            },
+            .Pointer => {
+                if (val.castTag(.variable)) |_| {
+                    break :blk MatchingSection{
+                        .seg = self.data_segment_cmd_index.?,
+                        .sect = self.data_section_index.?,
+                    };
+                } else {
+                    break :blk (try self.getMatchingSection(.{
+                        .segname = makeStaticString("__DATA_CONST"),
+                        .sectname = makeStaticString("__const"),
+                        .size = code.len,
+                        .@"align" = align_log_2,
+                    })).?;
+                }
+            },
+            else => {
+                if (val.castTag(.variable)) |_| {
+                    break :blk MatchingSection{
+                        .seg = self.data_segment_cmd_index.?,
+                        .sect = self.data_section_index.?,
+                    };
+                } else {
+                    break :blk (try self.getMatchingSection(.{
+                        .segname = makeStaticString("__TEXT"),
+                        .sectname = makeStaticString("__const"),
+                        .size = code.len,
+                        .@"align" = align_log_2,
+                    })).?;
+                }
+            },
+        }
+    };
+    const seg = self.load_commands.items[match.seg].segment;
+    const sect = seg.sections.items[match.sect];
+    log.debug("  allocating atom in '{s},{s}' ({d},{d})", .{
+        sect.segName(),
+        sect.sectName(),
+        match.seg,
+        match.sect,
+    });
+    return match;
+}
+
 fn placeDecl(self: *MachO, decl: *Module.Decl, code_len: usize) !*macho.nlist_64 {
     const required_alignment = decl.ty.abiAlignment(self.base.options.target);
     assert(decl.link.macho.local_sym_index != 0); // Caller forgot to call allocateDeclIndexes()
     const symbol = &self.locals.items[decl.link.macho.local_sym_index];
 
+    const decl_ptr = self.decls.getPtr(decl).?;
+    if (decl_ptr.* == null) {
+        decl_ptr.* = try self.getMatchingSectionDecl(decl);
+    }
+    const match = decl_ptr.*.?;
+
     if (decl.link.macho.size != 0) {
         const capacity = decl.link.macho.capacity(self.*);
         const need_realloc = code_len > capacity or !mem.isAlignedGeneric(u64, symbol.n_value, required_alignment);
         if (need_realloc) {
-            const vaddr = try self.growAtom(&decl.link.macho, code_len, required_alignment, .{
-                .seg = self.text_segment_cmd_index.?,
-                .sect = self.text_section_index.?,
-            });
+            const vaddr = try self.growAtom(&decl.link.macho, code_len, required_alignment, match);
 
             log.debug("growing {s} and moving from 0x{x} to 0x{x}", .{ decl.name, symbol.n_value, vaddr });
 
@@ -3690,10 +3841,7 @@ fn placeDecl(self: *MachO, decl: *Module.Decl, code_len: usize) !*macho.nlist_64
 
             symbol.n_value = vaddr;
         } else if (code_len < decl.link.macho.size) {
-            self.shrinkAtom(&decl.link.macho, code_len, .{
-                .seg = self.text_segment_cmd_index.?,
-                .sect = self.text_section_index.?,
-            });
+            self.shrinkAtom(&decl.link.macho, code_len, match);
         }
         decl.link.macho.size = code_len;
         decl.link.macho.dirty = true;
@@ -3714,22 +3862,16 @@ fn placeDecl(self: *MachO, decl: *Module.Decl, code_len: usize) !*macho.nlist_64
         defer self.base.allocator.free(decl_name);
 
         const name_str_index = try self.makeString(decl_name);
-        const addr = try self.allocateAtom(&decl.link.macho, code_len, required_alignment, .{
-            .seg = self.text_segment_cmd_index.?,
-            .sect = self.text_section_index.?,
-        });
+        const addr = try self.allocateAtom(&decl.link.macho, code_len, required_alignment, match);
 
         log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, addr });
 
-        errdefer self.freeAtom(&decl.link.macho, .{
-            .seg = self.text_segment_cmd_index.?,
-            .sect = self.text_section_index.?,
-        });
+        errdefer self.freeAtom(&decl.link.macho, match);
 
         symbol.* = .{
             .n_strx = name_str_index,
             .n_type = macho.N_SECT,
-            .n_sect = @intCast(u8, self.text_section_index.?) + 1,
+            .n_sect = @intCast(u8, self.section_ordinals.getIndex(match).?) + 1,
             .n_desc = 0,
             .n_value = addr,
         };
@@ -3912,12 +4054,11 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {
         if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl);
     }
     log.debug("freeDecl {*}", .{decl});
-    _ = self.decls.swapRemove(decl);
+    const kv = self.decls.fetchSwapRemove(decl);
+    if (kv.?.value) |match| {
+        self.freeAtom(&decl.link.macho, match);
+    }
     // Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
-    self.freeAtom(&decl.link.macho, .{
-        .seg = self.text_segment_cmd_index.?,
-        .sect = self.text_section_index.?,
-    });
     if (decl.link.macho.local_sym_index != 0) {
         self.locals_free_list.append(self.base.allocator, decl.link.macho.local_sym_index) catch {};
 
@@ -3958,6 +4099,29 @@ pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 {
     return self.locals.items[decl.link.macho.local_sym_index].n_value;
 }
 
+pub fn getDeclVAddrWithReloc(self: *MachO, decl: *const Module.Decl, offset: u64) !u64 {
+    assert(decl.link.macho.local_sym_index != 0);
+    assert(self.active_decl != null);
+
+    const atom = &self.active_decl.?.link.macho;
+    try atom.relocs.append(self.base.allocator, .{
+        .offset = @intCast(u32, offset),
+        .target = .{ .local = decl.link.macho.local_sym_index },
+        .addend = 0,
+        .subtractor = null,
+        .pcrel = false,
+        .length = 3,
+        .@"type" = switch (self.base.options.target.cpu.arch) {
+            .aarch64 => @enumToInt(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED),
+            .x86_64 => @enumToInt(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED),
+            else => unreachable,
+        },
+    });
+    try atom.rebases.append(self.base.allocator, offset);
+
+    return 0;
+}
+
 fn populateMissingMetadata(self: *MachO) !void {
     const cpu_arch = self.base.options.target.cpu.arch;
 
src/codegen.zig
@@ -465,9 +465,15 @@ fn lowerDeclRef(
 
     if (decl.analysis != .complete) return error.AnalysisFail;
     markDeclAlive(decl);
-    // TODO handle the dependency of this symbol on the decl's vaddr.
-    // If the decl changes vaddr, then this symbol needs to get regenerated.
-    const vaddr = bin_file.getDeclVAddr(decl);
+    const vaddr = vaddr: {
+        if (bin_file.cast(link.File.MachO)) |macho_file| {
+            break :vaddr try macho_file.getDeclVAddrWithReloc(decl, code.items.len);
+        }
+        // TODO handle the dependency of this symbol on the decl's vaddr.
+        // If the decl changes vaddr, then this symbol needs to get regenerated.
+        break :vaddr bin_file.getDeclVAddr(decl);
+    };
+
     const endian = bin_file.options.target.cpu.arch.endian();
     switch (bin_file.options.target.cpu.arch.ptrBitWidth()) {
         16 => mem.writeInt(u16, try code.addManyAsArray(2), @intCast(u16, vaddr), endian),
test/stage2/x86_64.zig
@@ -1761,6 +1761,25 @@ pub fn addCases(ctx: *TestContext) !void {
                 \\}
             , "");
         }
+
+        {
+            var case = ctx.exe("access slice element by index - slice_elem_val", target);
+            case.addCompareOutput(
+                \\var array = [_]usize{ 0, 42, 123, 34 };
+                \\var slice: []const usize = &array;
+                \\
+                \\pub fn main() void {
+                \\    assert(slice[0] == 0);
+                \\    assert(slice[1] == 42);
+                \\    assert(slice[2] == 123);
+                \\    assert(slice[3] == 34);
+                \\}
+                \\
+                \\fn assert(ok: bool) void {
+                \\    if (!ok) unreachable;
+                \\}
+            , "");
+        }
     }
 }
 
@@ -2014,26 +2033,6 @@ fn addLinuxTestCases(ctx: *TestContext) !void {
             \\}
         , "");
     }
-
-    {
-        // TODO fixing this will enable zig test on macOS
-        var case = ctx.exe("access slice element by index - slice_elem_val", linux_x64);
-        case.addCompareOutput(
-            \\var array = [_]usize{ 0, 42, 123, 34 };
-            \\var slice: []const usize = &array;
-            \\
-            \\pub fn main() void {
-            \\    assert(slice[0] == 0);
-            \\    assert(slice[1] == 42);
-            \\    assert(slice[2] == 123);
-            \\    assert(slice[3] == 34);
-            \\}
-            \\
-            \\fn assert(ok: bool) void {
-            \\    if (!ok) unreachable;
-            \\}
-        , "");
-    }
 }
 
 fn addMacOsTestCases(ctx: *TestContext) !void {