zig/src/link/Wasm/Atom.zig
Luuk de Gram 5ef8321338
wasm: make symbol indexes a non-exhaustive enum
This introduces some type safety so we cannot accidently give an atom
index as a symbol index. This also means we do not have to store any
optionals and therefore allow for memory optimizations. Lastly, we can
now always simply access the symbol index of an atom, rather than having
to call `getSymbolIndex` as it is easy to forget.
2024-02-29 15:24:07 +01:00

205 lines
8.2 KiB
Zig

/// Represents the index of the file this atom was generated from.
/// This is 'null' when the atom was generated by a synthetic linker symbol.
file: FileIndex,
/// symbol index of the symbol representing this atom
sym_index: Symbol.Index,
/// Size of the atom, used to calculate section sizes in the final binary
size: u32 = 0,
/// List of relocations belonging to this atom
relocs: std.ArrayListUnmanaged(types.Relocation) = .{},
/// Contains the binary data of an atom, which can be non-relocated
code: std.ArrayListUnmanaged(u8) = .{},
/// For code this is 1, for data this is set to the highest value of all segments
alignment: Wasm.Alignment = .@"1",
/// Offset into the section where the atom lives, this already accounts
/// for alignment.
offset: u32 = 0,
/// The original offset within the object file. This value is substracted from
/// relocation offsets to determine where in the `data` to rewrite the value
original_offset: u32 = 0,
/// Previous atom in relation to this atom.
/// is null when this atom is the first in its order
prev: Atom.Index = .null,
/// Contains atoms local to a decl, all managed by this `Atom`.
/// When the parent atom is being freed, it will also do so for all local atoms.
locals: std.ArrayListUnmanaged(Atom.Index) = .{},
/// Represents the index of an Atom where `null` is considered
/// an invalid atom.
pub const Index = enum(u32) {
null = std.math.maxInt(u32),
_,
};
/// Frees all resources owned by this `Atom`.
pub fn deinit(atom: *Atom, gpa: std.mem.Allocator) void {
atom.relocs.deinit(gpa);
atom.code.deinit(gpa);
atom.locals.deinit(gpa);
atom.* = undefined;
}
/// Sets the length of relocations and code to '0',
/// effectively resetting them and allowing them to be re-populated.
pub fn clear(atom: *Atom) void {
atom.relocs.clearRetainingCapacity();
atom.code.clearRetainingCapacity();
}
pub fn format(atom: Atom, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = fmt;
_ = options;
try writer.print("Atom{{ .sym_index = {d}, .alignment = {d}, .size = {d}, .offset = 0x{x:0>8} }}", .{
@intFromEnum(atom.sym_index),
atom.alignment,
atom.size,
atom.offset,
});
}
/// Returns the location of the symbol that represents this `Atom`
pub fn symbolLoc(atom: Atom) Wasm.SymbolLoc {
return .{ .file = atom.file, .index = atom.sym_index };
}
/// Resolves the relocations within the atom, writing the new value
/// at the calculated offset.
pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
if (atom.relocs.items.len == 0) return;
const symbol_name = atom.symbolLoc().getName(wasm_bin);
log.debug("Resolving relocs in atom '{s}' count({d})", .{
symbol_name,
atom.relocs.items.len,
});
for (atom.relocs.items) |reloc| {
const value = atom.relocationValue(reloc, wasm_bin);
log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{
(Wasm.SymbolLoc{ .file = atom.file, .index = @enumFromInt(reloc.index) }).getName(wasm_bin),
symbol_name,
reloc.offset,
value,
});
switch (reloc.relocation_type) {
.R_WASM_TABLE_INDEX_I32,
.R_WASM_FUNCTION_OFFSET_I32,
.R_WASM_GLOBAL_INDEX_I32,
.R_WASM_MEMORY_ADDR_I32,
.R_WASM_SECTION_OFFSET_I32,
=> std.mem.writeInt(u32, atom.code.items[reloc.offset - atom.original_offset ..][0..4], @as(u32, @truncate(value)), .little),
.R_WASM_TABLE_INDEX_I64,
.R_WASM_MEMORY_ADDR_I64,
=> std.mem.writeInt(u64, atom.code.items[reloc.offset - atom.original_offset ..][0..8], value, .little),
.R_WASM_GLOBAL_INDEX_LEB,
.R_WASM_EVENT_INDEX_LEB,
.R_WASM_FUNCTION_INDEX_LEB,
.R_WASM_MEMORY_ADDR_LEB,
.R_WASM_MEMORY_ADDR_SLEB,
.R_WASM_TABLE_INDEX_SLEB,
.R_WASM_TABLE_NUMBER_LEB,
.R_WASM_TYPE_INDEX_LEB,
.R_WASM_MEMORY_ADDR_TLS_SLEB,
=> leb.writeUnsignedFixed(5, atom.code.items[reloc.offset - atom.original_offset ..][0..5], @as(u32, @truncate(value))),
.R_WASM_MEMORY_ADDR_LEB64,
.R_WASM_MEMORY_ADDR_SLEB64,
.R_WASM_TABLE_INDEX_SLEB64,
.R_WASM_MEMORY_ADDR_TLS_SLEB64,
=> leb.writeUnsignedFixed(10, atom.code.items[reloc.offset - atom.original_offset ..][0..10], value),
}
}
}
/// From a given `relocation` will return the new value to be written.
/// All values will be represented as a `u64` as all values can fit within it.
/// The final value must be casted to the correct size.
fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wasm) u64 {
const target_loc = (Wasm.SymbolLoc{ .file = atom.file, .index = @enumFromInt(relocation.index) }).finalLoc(wasm_bin);
const symbol = target_loc.getSymbol(wasm_bin);
if (relocation.relocation_type != .R_WASM_TYPE_INDEX_LEB and
symbol.tag != .section and
symbol.isDead())
{
const val = atom.thombstone(wasm_bin) orelse relocation.addend;
return @bitCast(val);
}
switch (relocation.relocation_type) {
.R_WASM_FUNCTION_INDEX_LEB => return symbol.index,
.R_WASM_TABLE_NUMBER_LEB => return symbol.index,
.R_WASM_TABLE_INDEX_I32,
.R_WASM_TABLE_INDEX_I64,
.R_WASM_TABLE_INDEX_SLEB,
.R_WASM_TABLE_INDEX_SLEB64,
=> return wasm_bin.function_table.get(.{ .file = atom.file, .index = @enumFromInt(relocation.index) }) orelse 0,
.R_WASM_TYPE_INDEX_LEB => {
const obj_file = wasm_bin.file(atom.file) orelse return relocation.index;
const original_type = obj_file.funcTypes()[relocation.index];
return wasm_bin.getTypeIndex(original_type).?;
},
.R_WASM_GLOBAL_INDEX_I32,
.R_WASM_GLOBAL_INDEX_LEB,
=> return symbol.index,
.R_WASM_MEMORY_ADDR_I32,
.R_WASM_MEMORY_ADDR_I64,
.R_WASM_MEMORY_ADDR_LEB,
.R_WASM_MEMORY_ADDR_LEB64,
.R_WASM_MEMORY_ADDR_SLEB,
.R_WASM_MEMORY_ADDR_SLEB64,
=> {
std.debug.assert(symbol.tag == .data);
if (symbol.isUndefined()) {
return 0;
}
const va: i33 = @intCast(symbol.virtual_address);
return @intCast(va + relocation.addend);
},
.R_WASM_EVENT_INDEX_LEB => return symbol.index,
.R_WASM_SECTION_OFFSET_I32 => {
const target_atom_index = wasm_bin.symbol_atom.get(target_loc).?;
const target_atom = wasm_bin.getAtom(target_atom_index);
const rel_value: i33 = @intCast(target_atom.offset);
return @intCast(rel_value + relocation.addend);
},
.R_WASM_FUNCTION_OFFSET_I32 => {
if (symbol.isUndefined()) {
const val = atom.thombstone(wasm_bin) orelse relocation.addend;
return @bitCast(val);
}
const target_atom_index = wasm_bin.symbol_atom.get(target_loc).?;
const target_atom = wasm_bin.getAtom(target_atom_index);
const rel_value: i33 = @intCast(target_atom.offset);
return @intCast(rel_value + relocation.addend);
},
.R_WASM_MEMORY_ADDR_TLS_SLEB,
.R_WASM_MEMORY_ADDR_TLS_SLEB64,
=> {
const va: i33 = @intCast(symbol.virtual_address);
return @intCast(va + relocation.addend);
},
}
}
// For a given `Atom` returns whether it has a thombstone value or not.
/// This defines whether we want a specific value when a section is dead.
fn thombstone(atom: Atom, wasm: *const Wasm) ?i64 {
const atom_name = atom.symbolLoc().getName(wasm);
if (std.mem.eql(u8, atom_name, ".debug_ranges") or std.mem.eql(u8, atom_name, ".debug_loc")) {
return -2;
} else if (std.mem.startsWith(u8, atom_name, ".debug_")) {
return -1;
}
return null;
}
const leb = std.leb;
const log = std.log.scoped(.link);
const mem = std.mem;
const std = @import("std");
const types = @import("types.zig");
const Allocator = mem.Allocator;
const Atom = @This();
const FileIndex = @import("file.zig").File.Index;
const Symbol = @import("Symbol.zig");
const Wasm = @import("../Wasm.zig");