elf: store pointer relocations indexed by containing atom

In `getDeclVAddr`, it may happen that the target `Decl` has not
been allocated space in virtual memory. In this case, we store a
relocation in the linker-global table which we will iterate over
when flushing the module, and fill in any missing address in the
final binary. Note that for optimisation, if the address was resolved
at the time of a call to `getDeclVAddr`, we skip relocating this
atom.

This commit also adds the glue code for lowering const slices in
the ARM backend.
This commit is contained in:
Jakub Konka 2022-02-10 14:41:07 +01:00
parent 08e2f5d083
commit b9b1ab0240
7 changed files with 163 additions and 86 deletions

View File

@ -3931,23 +3931,20 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {
switch (typed_value.ty.zigTypeTag()) {
.Pointer => switch (typed_value.ty.ptrSize()) {
.Slice => {
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const ptr_type = typed_value.ty.slicePtrFieldType(&buf);
const ptr_mcv = try self.genTypedValue(.{ .ty = ptr_type, .val = typed_value.val });
const slice_len = typed_value.val.sliceLen();
// Codegen can't handle some kinds of indirection. If the wrong union field is accessed here it may mean
// the Sema code needs to use anonymous Decls or alloca instructions to store data.
const ptr_imm = ptr_mcv.memory;
_ = slice_len;
_ = ptr_imm;
// We need more general support for const data being stored in memory to make this work.
return self.fail("TODO codegen for const slices", .{});
return self.lowerUnnamedConst(typed_value);
},
else => {
if (typed_value.val.tag() == .int_u64) {
return MCValue{ .immediate = @intCast(u32, typed_value.val.toUnsignedInt()) };
switch (typed_value.val.tag()) {
.int_u64 => {
return MCValue{ .immediate = @intCast(u32, typed_value.val.toUnsignedInt()) };
},
.slice => {
return self.lowerUnnamedConst(typed_value);
},
else => {
return self.fail("TODO codegen more kinds of const pointers", .{});
},
}
return self.fail("TODO codegen more kinds of const pointers", .{});
},
},
.Int => {

View File

@ -142,6 +142,7 @@ pub fn generateFunction(
pub fn generateSymbol(
bin_file: *link.File,
parent_atom_index: u32,
src_loc: Module.SrcLoc,
typed_value: TypedValue,
code: *std.ArrayList(u8),
@ -177,7 +178,7 @@ pub fn generateSymbol(
if (typed_value.ty.sentinel()) |sentinel| {
try code.ensureUnusedCapacity(payload.data.len + 1);
code.appendSliceAssumeCapacity(payload.data);
switch (try generateSymbol(bin_file, src_loc, .{
switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
.ty = typed_value.ty.elemType(),
.val = sentinel,
}, code, debug_output)) {
@ -197,7 +198,7 @@ pub fn generateSymbol(
const elem_vals = typed_value.val.castTag(.array).?.data;
const elem_ty = typed_value.ty.elemType();
for (elem_vals) |elem_val| {
switch (try generateSymbol(bin_file, src_loc, .{
switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
.ty = elem_ty,
.val = elem_val,
}, code, debug_output)) {
@ -223,11 +224,11 @@ pub fn generateSymbol(
.Pointer => switch (typed_value.val.tag()) {
.variable => {
const decl = typed_value.val.castTag(.variable).?.data.owner_decl;
return lowerDeclRef(bin_file, src_loc, typed_value, decl, code, debug_output);
return lowerDeclRef(bin_file, parent_atom_index, src_loc, typed_value, decl, code, debug_output);
},
.decl_ref => {
const decl = typed_value.val.castTag(.decl_ref).?.data;
return lowerDeclRef(bin_file, src_loc, typed_value, decl, code, debug_output);
return lowerDeclRef(bin_file, parent_atom_index, src_loc, typed_value, decl, code, debug_output);
},
.slice => {
const slice = typed_value.val.castTag(.slice).?.data;
@ -235,7 +236,7 @@ pub fn generateSymbol(
// generate ptr
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const slice_ptr_field_type = typed_value.ty.slicePtrFieldType(&buf);
switch (try generateSymbol(bin_file, src_loc, .{
switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
.ty = slice_ptr_field_type,
.val = slice.ptr,
}, code, debug_output)) {
@ -247,7 +248,7 @@ pub fn generateSymbol(
}
// generate length
switch (try generateSymbol(bin_file, src_loc, .{
switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
.ty = Type.initTag(.usize),
.val = slice.len,
}, code, debug_output)) {
@ -391,7 +392,7 @@ pub fn generateSymbol(
const field_ty = typed_value.ty.structFieldType(index);
if (!field_ty.hasRuntimeBits()) continue;
switch (try generateSymbol(bin_file, src_loc, .{
switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
.ty = field_ty,
.val = field_val,
}, code, debug_output)) {
@ -446,6 +447,7 @@ pub fn generateSymbol(
fn lowerDeclRef(
bin_file: *link.File,
parent_atom_index: u32,
src_loc: Module.SrcLoc,
typed_value: TypedValue,
decl: *Module.Decl,
@ -456,7 +458,7 @@ fn lowerDeclRef(
// generate ptr
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const slice_ptr_field_type = typed_value.ty.slicePtrFieldType(&buf);
switch (try generateSymbol(bin_file, src_loc, .{
switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
.ty = slice_ptr_field_type,
.val = typed_value.val,
}, code, debug_output)) {
@ -472,7 +474,7 @@ fn lowerDeclRef(
.base = .{ .tag = .int_u64 },
.data = typed_value.val.sliceLen(),
};
switch (try generateSymbol(bin_file, src_loc, .{
switch (try generateSymbol(bin_file, parent_atom_index, src_loc, .{
.ty = Type.initTag(.usize),
.val = Value.initPayload(&slice_len.base),
}, code, debug_output)) {
@ -495,15 +497,7 @@ fn lowerDeclRef(
}
decl.markAlive();
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 vaddr = try bin_file.getDeclVAddr(decl, parent_atom_index, code.items.len);
const endian = target.cpu.arch.endian();
switch (ptr_width) {
16 => mem.writeInt(u16, try code.addManyAsArray(2), @intCast(u16, vaddr), endian),

View File

@ -684,12 +684,16 @@ pub const File = struct {
}
}
pub fn getDeclVAddr(base: *File, decl: *const Module.Decl) u64 {
/// Get allocated `Decl`'s address in virtual memory.
/// The linker is passed information about the containing atom, `parent_atom_index`, and offset within it's
/// memory buffer, `offset`, so that it can make a note of potential relocation sites, should the
/// `Decl`'s address was not yet resolved, or the containing atom gets moved in virtual memory.
pub fn getDeclVAddr(base: *File, decl: *const Module.Decl, parent_atom_index: u32, offset: u64) !u64 {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).getDeclVAddr(decl),
.elf => return @fieldParentPtr(Elf, "base", base).getDeclVAddr(decl),
.macho => return @fieldParentPtr(MachO, "base", base).getDeclVAddr(decl),
.plan9 => return @fieldParentPtr(Plan9, "base", base).getDeclVAddr(decl),
.coff => return @fieldParentPtr(Coff, "base", base).getDeclVAddr(decl, parent_atom_index, offset),
.elf => return @fieldParentPtr(Elf, "base", base).getDeclVAddr(decl, parent_atom_index, offset),
.macho => return @fieldParentPtr(MachO, "base", base).getDeclVAddr(decl, parent_atom_index, offset),
.plan9 => return @fieldParentPtr(Plan9, "base", base).getDeclVAddr(decl, parent_atom_index, offset),
.c => unreachable,
.wasm => unreachable,
.spirv => unreachable,

View File

@ -726,7 +726,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void {
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
defer code_buffer.deinit();
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
const res = try codegen.generateSymbol(&self.base, 0, decl.srcLoc(), .{
.ty = decl.ty,
.val = decl.val,
}, &code_buffer, .none);
@ -751,7 +751,7 @@ fn finishUpdateDecl(self: *Coff, module: *Module, decl: *Module.Decl, code: []co
const need_realloc = code.len > capacity or
!mem.isAlignedGeneric(u32, decl.link.coff.text_offset, required_alignment);
if (need_realloc) {
const curr_vaddr = self.getDeclVAddr(decl);
const curr_vaddr = self.text_section_virtual_address + decl.link.coff.text_offset;
const vaddr = try self.growTextBlock(&decl.link.coff, code.len, required_alignment);
log.debug("growing {s} from 0x{x} to 0x{x}\n", .{ decl.name, curr_vaddr, vaddr });
if (vaddr != curr_vaddr) {
@ -1465,7 +1465,9 @@ fn findLib(self: *Coff, arena: Allocator, name: []const u8) !?[]const u8 {
return null;
}
pub fn getDeclVAddr(self: *Coff, decl: *const Module.Decl) u64 {
pub fn getDeclVAddr(self: *Coff, decl: *const Module.Decl, parent_atom_index: u32, offset: u64) !u64 {
_ = parent_atom_index;
_ = offset;
assert(self.llvm_object == null);
return self.text_section_virtual_address + decl.link.coff.text_offset;
}

View File

@ -145,6 +145,7 @@ decls: std.AutoHashMapUnmanaged(*Module.Decl, ?u16) = .{},
/// at present owned by Module.Decl.
/// TODO consolidate this.
managed_atoms: std.ArrayListUnmanaged(*TextBlock) = .{},
atom_by_index_table: std.AutoHashMapUnmanaged(u32, *TextBlock) = .{},
/// Table of unnamed constants associated with a parent `Decl`.
/// We store them here so that we can free the constants whenever the `Decl`
@ -179,6 +180,18 @@ dbg_info_decl_free_list: std.AutoHashMapUnmanaged(*TextBlock, void) = .{},
dbg_info_decl_first: ?*TextBlock = null,
dbg_info_decl_last: ?*TextBlock = null,
/// A table of relocations indexed by the owning them `TextBlock`.
/// Note that once we refactor `TextBlock`'s lifetime and ownership rules,
/// this will be a table indexed by index into the list of Atoms.
relocs: RelocTable = .{},
const Reloc = struct {
target: u32,
offset: u64,
prev_vaddr: u64,
};
const RelocTable = std.AutoHashMapUnmanaged(*TextBlock, std.ArrayListUnmanaged(Reloc));
const UnnamedConstTable = std.AutoHashMapUnmanaged(*Module.Decl, std.ArrayListUnmanaged(*TextBlock));
/// When allocating, the ideal_capacity is calculated by
@ -397,12 +410,36 @@ pub fn deinit(self: *Elf) void {
}
self.unnamed_const_atoms.deinit(self.base.allocator);
}
{
var it = self.relocs.valueIterator();
while (it.next()) |relocs| {
relocs.deinit(self.base.allocator);
}
self.relocs.deinit(self.base.allocator);
}
self.atom_by_index_table.deinit(self.base.allocator);
}
pub fn getDeclVAddr(self: *Elf, decl: *const Module.Decl) u64 {
pub fn getDeclVAddr(self: *Elf, decl: *const Module.Decl, parent_atom_index: u32, offset: u64) !u64 {
assert(self.llvm_object == null);
assert(decl.link.elf.local_sym_index != 0);
return self.local_symbols.items[decl.link.elf.local_sym_index].st_value;
const target = decl.link.elf.local_sym_index;
const vaddr = self.local_symbols.items[target].st_value;
const atom = self.atom_by_index_table.get(parent_atom_index).?;
const gop = try self.relocs.getOrPut(self.base.allocator, atom);
if (!gop.found_existing) {
gop.value_ptr.* = .{};
}
try gop.value_ptr.append(self.base.allocator, .{
.target = target,
.offset = offset,
.prev_vaddr = vaddr,
});
return vaddr;
}
fn getDebugLineProgramOff(self: Elf) u32 {
@ -991,6 +1028,41 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void {
.p64 => 12,
};
{
var it = self.relocs.iterator();
while (it.next()) |entry| {
const atom = entry.key_ptr.*;
const relocs = entry.value_ptr.*;
const source_sym = self.local_symbols.items[atom.local_sym_index];
const source_shdr = self.sections.items[source_sym.st_shndx];
log.debug("relocating '{s}'", .{self.getString(source_sym.st_name)});
for (relocs.items) |*reloc| {
const target_sym = self.local_symbols.items[reloc.target];
const target_vaddr = target_sym.st_value;
if (target_vaddr == reloc.prev_vaddr) continue;
const section_offset = (source_sym.st_value + reloc.offset) - source_shdr.sh_addr;
const file_offset = source_shdr.sh_offset + section_offset;
log.debug(" ({x}: [() => 0x{x}] ({s}))", .{
reloc.offset,
target_vaddr,
self.getString(target_sym.st_name),
});
switch (self.ptr_width) {
.p32 => try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, target_vaddr)), file_offset),
.p64 => try self.base.file.?.pwriteAll(mem.asBytes(&target_vaddr), file_offset),
}
reloc.prev_vaddr = target_vaddr;
}
}
}
// Unfortunately these have to be buffered and done at the end because ELF does not allow
// mixing local and global symbols within a symbol table.
try self.writeAllGlobalSymbols();
@ -2508,6 +2580,7 @@ pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void {
log.debug("allocating symbol indexes for {s}", .{decl.name});
decl.link.elf.local_sym_index = try self.allocateLocalSymbol();
try self.atom_by_index_table.putNoClobber(self.base.allocator, decl.link.elf.local_sym_index, &decl.link.elf);
if (self.offset_table_free_list.popOrNull()) |i| {
decl.link.elf.offset_table_index = i;
@ -2525,6 +2598,7 @@ fn freeUnnamedConsts(self: *Elf, decl: *Module.Decl) void {
self.freeTextBlock(atom, self.phdr_load_ro_index.?);
self.local_symbol_free_list.append(self.base.allocator, atom.local_sym_index) catch {};
self.local_symbols.items[atom.local_sym_index].st_info = 0;
_ = self.atom_by_index_table.remove(atom.local_sym_index);
}
unnamed_consts.clearAndFree(self.base.allocator);
}
@ -2543,11 +2617,11 @@ pub fn freeDecl(self: *Elf, decl: *Module.Decl) void {
// Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
if (decl.link.elf.local_sym_index != 0) {
self.local_symbol_free_list.append(self.base.allocator, decl.link.elf.local_sym_index) catch {};
self.offset_table_free_list.append(self.base.allocator, decl.link.elf.offset_table_index) catch {};
self.local_symbols.items[decl.link.elf.local_sym_index].st_info = 0;
_ = self.atom_by_index_table.remove(decl.link.elf.local_sym_index);
decl.link.elf.local_sym_index = 0;
self.offset_table_free_list.append(self.base.allocator, decl.link.elf.offset_table_index) catch {};
}
// TODO make this logic match freeTextBlock. Maybe abstract the logic out since the same thing
// is desired for both.
@ -2993,7 +3067,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
// TODO implement .debug_info for global variables
const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
const res = try codegen.generateSymbol(&self.base, decl.link.elf.local_sym_index, decl.srcLoc(), .{
.ty = decl.ty,
.val = decl_val,
}, &code_buffer, .{
@ -3028,19 +3102,6 @@ pub fn lowerUnnamedConst(self: *Elf, typed_value: TypedValue, decl: *Module.Decl
}
const unnamed_consts = gop.value_ptr;
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .{
.none = .{},
});
const code = switch (res) {
.externally_managed => |x| x,
.appended => code_buffer.items,
.fail => |em| {
decl.analysis = .codegen_failure;
try module.failed_decls.put(module.gpa, decl, em);
return error.AnalysisFail;
},
};
const atom = try self.base.allocator.create(TextBlock);
errdefer self.base.allocator.destroy(atom);
atom.* = TextBlock.empty;
@ -3056,6 +3117,20 @@ pub fn lowerUnnamedConst(self: *Elf, typed_value: TypedValue, decl: *Module.Decl
log.debug("allocating symbol indexes for {s}", .{name});
atom.local_sym_index = try self.allocateLocalSymbol();
try self.atom_by_index_table.putNoClobber(self.base.allocator, atom.local_sym_index, atom);
const res = try codegen.generateSymbol(&self.base, atom.local_sym_index, decl.srcLoc(), typed_value, &code_buffer, .{
.none = .{},
});
const code = switch (res) {
.externally_managed => |x| x,
.appended => code_buffer.items,
.fail => |em| {
decl.analysis = .codegen_failure;
try module.failed_decls.put(module.gpa, decl, em);
return error.AnalysisFail;
},
};
const required_alignment = typed_value.ty.abiAlignment(self.base.options.target);
const phdr_index = self.phdr_load_ro_index.?;

View File

@ -3745,19 +3745,6 @@ pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl: *Module.De
}
const unnamed_consts = gop.value_ptr;
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .{
.none = .{},
});
const code = switch (res) {
.externally_managed => |x| x,
.appended => code_buffer.items,
.fail => |em| {
decl.analysis = .codegen_failure;
try module.failed_decls.put(module.gpa, decl, em);
return error.AnalysisFail;
},
};
const name_str_index = blk: {
const index = unnamed_consts.items.len;
const name = try std.fmt.allocPrint(self.base.allocator, "__unnamed_{s}_{d}", .{ decl.name, index });
@ -3772,12 +3759,27 @@ pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl: *Module.De
const match = (try self.getMatchingSection(.{
.segname = makeStaticString("__TEXT"),
.sectname = makeStaticString("__const"),
.size = code.len,
.size = @sizeOf(u64),
.@"align" = math.log2(required_alignment),
})).?;
const local_sym_index = try self.allocateLocalSymbol();
const atom = try self.createEmptyAtom(local_sym_index, code.len, math.log2(required_alignment));
mem.copy(u8, atom.code.items, code);
const atom = try self.createEmptyAtom(local_sym_index, @sizeOf(u64), math.log2(required_alignment));
const res = try codegen.generateSymbol(&self.base, local_sym_index, decl.srcLoc(), typed_value, &code_buffer, .{
.none = .{},
});
const code = switch (res) {
.externally_managed => |x| x,
.appended => code_buffer.items,
.fail => |em| {
decl.analysis = .codegen_failure;
try module.failed_decls.put(module.gpa, decl, em);
return error.AnalysisFail;
},
};
atom.code.clearRetainingCapacity();
try atom.code.appendSlice(self.base.allocator, code);
const addr = try self.allocateAtom(atom, code.len, required_alignment, match);
log.debug("allocated atom for {s} at 0x{x}", .{ name, addr });
@ -3841,7 +3843,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
const res = if (debug_buffers) |dbg|
try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
try codegen.generateSymbol(&self.base, decl.link.elf.local_sym_index, decl.srcLoc(), .{
.ty = decl.ty,
.val = decl_val,
}, &code_buffer, .{
@ -3852,7 +3854,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
},
})
else
try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
try codegen.generateSymbol(&self.base, decl.link.elf.local_sym_index, decl.srcLoc(), .{
.ty = decl.ty,
.val = decl_val,
}, &code_buffer, .none);
@ -4341,16 +4343,17 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {
}
}
pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 {
pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl, parent_atom_index: u32, offset: u64) !u64 {
assert(self.llvm_object == null);
assert(decl.link.macho.local_sym_index != 0);
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);
// TODO cache local_sym_index => atom!!!
const atom: *Atom = blk: for (self.managed_atoms.items) |atom| {
if (atom.local_sym_index == parent_atom_index) {
break :blk atom;
}
} else unreachable;
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 },

View File

@ -302,7 +302,7 @@ pub fn updateDecl(self: *Plan9, module: *Module, decl: *Module.Decl) !void {
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
defer code_buffer.deinit();
const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
const res = try codegen.generateSymbol(&self.base, @intCast(u32, decl.link.plan9.sym_index.?), decl.srcLoc(), .{
.ty = decl.ty,
.val = decl_val,
}, &code_buffer, .{ .none = .{} });
@ -749,7 +749,9 @@ pub fn allocateDeclIndexes(self: *Plan9, decl: *Module.Decl) !void {
_ = self;
_ = decl;
}
pub fn getDeclVAddr(self: *Plan9, decl: *const Module.Decl) u64 {
pub fn getDeclVAddr(self: *Plan9, decl: *const Module.Decl, parent_atom_index: u32, offset: u64) !u64 {
_ = parent_atom_index;
_ = offset;
if (decl.ty.zigTypeTag() == .Fn) {
var start = self.bases.text;
var it_file = self.fn_decl_table.iterator();