1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
const Atom = @This();
const std = @import("std");
const types = @import("types.zig");
const Wasm = @import("../Wasm.zig");
const Symbol = @import("Symbol.zig");
const Dwarf = @import("../Dwarf.zig");
const leb = std.leb;
const log = std.log.scoped(.link);
const mem = std.mem;
const Allocator = mem.Allocator;
/// symbol index of the symbol representing this atom
sym_index: u32,
/// Size of the atom, used to calculate section sizes in the final binary
size: u32,
/// 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: u32,
/// Offset into the section where the atom lives, this already accounts
/// for alignment.
offset: u32,
/// Represents the index of the file this atom was generated from.
/// This is 'null' when the atom was generated by a Decl from Zig code.
file: ?u16,
/// Next atom in relation to this atom.
/// When null, this atom is the last atom
next: ?*Atom,
/// Previous atom in relation to this atom.
/// is null when this atom is the first in its order
prev: ?*Atom,
/// 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) = .{},
/// Represents the debug Atom that holds all debug information of this Atom.
dbg_info_atom: Dwarf.Atom,
/// Represents a default empty wasm `Atom`
pub const empty: Atom = .{
.alignment = 0,
.file = null,
.next = null,
.offset = 0,
.prev = null,
.size = 0,
.sym_index = 0,
.dbg_info_atom = undefined,
};
/// Frees all resources owned by this `Atom`.
pub fn deinit(atom: *Atom, gpa: Allocator) void {
atom.relocs.deinit(gpa);
atom.code.deinit(gpa);
for (atom.locals.items) |*local| {
local.deinit(gpa);
}
atom.locals.deinit(gpa);
}
/// 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} }}", .{
atom.sym_index,
atom.alignment,
atom.size,
atom.offset,
});
}
/// Returns the first `Atom` from a given atom
pub fn getFirst(atom: *Atom) *Atom {
var tmp = atom;
while (tmp.prev) |prev| tmp = prev;
return tmp;
}
/// Unlike `getFirst` this returns the first `*Atom` that was
/// produced from Zig code, rather than an object file.
/// This is useful for debug sections where we want to extend
/// the bytes, and don't want to overwrite existing Atoms.
pub fn getFirstZigAtom(atom: *Atom) *Atom {
if (atom.file == null) return atom;
var tmp = atom;
return while (tmp.prev) |prev| {
if (prev.file == null) break prev;
tmp = prev;
} else unreachable; // must allocate an Atom first!
}
/// 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 = 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.writeIntLittle(u32, atom.code.items[reloc.offset..][0..4], @intCast(u32, value)),
.R_WASM_TABLE_INDEX_I64,
.R_WASM_MEMORY_ADDR_I64,
=> std.mem.writeIntLittle(u64, atom.code.items[reloc.offset..][0..8], value),
.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,
=> leb.writeUnsignedFixed(5, atom.code.items[reloc.offset..][0..5], @intCast(u32, value)),
.R_WASM_MEMORY_ADDR_LEB64,
.R_WASM_MEMORY_ADDR_SLEB64,
.R_WASM_TABLE_INDEX_SLEB64,
=> leb.writeUnsignedFixed(10, atom.code.items[reloc.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 = relocation.index }).finalLoc(wasm_bin);
const symbol = target_loc.getSymbol(wasm_bin).*;
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(target_loc) orelse 0,
.R_WASM_TYPE_INDEX_LEB => return blk: {
if (symbol.isUndefined()) {
const imp = wasm_bin.imports.get(target_loc).?;
break :blk imp.kind.function;
}
break :blk wasm_bin.functions.values()[symbol.index - wasm_bin.imported_functions_count].type_index;
},
.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 and !symbol.isUndefined());
const merge_segment = wasm_bin.base.options.output_mode != .Obj;
const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
const segment_info = if (target_atom.file) |object_index| blk: {
break :blk wasm_bin.objects.items[object_index].segment_info;
} else wasm_bin.segment_info.values();
const segment_name = segment_info[symbol.index].outputName(merge_segment);
const segment_index = wasm_bin.data_segments.get(segment_name).?;
const segment = wasm_bin.segments.items[segment_index];
const rel_value = @intCast(i32, target_atom.offset + segment.offset) + relocation.addend;
return @intCast(u32, rel_value);
},
.R_WASM_EVENT_INDEX_LEB => return symbol.index,
.R_WASM_SECTION_OFFSET_I32 => {
const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
const rel_value = @intCast(i32, target_atom.offset) + relocation.addend;
return @intCast(u32, rel_value);
},
.R_WASM_FUNCTION_OFFSET_I32 => {
const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
var current_atom = target_atom.getFirst();
var offset: u32 = 0;
// TODO: Calculate this during atom allocation, rather than
// this linear calculation. For now it's done here as atoms
// are being sorted after atom allocation, as functions aren't
// merged until later.
while (true) {
offset += 5; // each atom uses 5 bytes to store its body's size
if (current_atom == target_atom) break;
current_atom = current_atom.next.?;
}
const rel_value = @intCast(i32, target_atom.offset + offset) + relocation.addend;
return @intCast(u32, rel_value);
},
}
}
|