authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-10-08 17:52:08+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-10-08 19:53:03+02:00
log5f86505cf79a0ce75e1a02602ae0e9c845024982
tree951e93be9825ff007af21949516263f874d28b01
parentea44d12d1be8eb17a1555f6ab794621da0212171
signature Commit is signed but in an unrecognized format.

Fix ULEB128 encoding of trie

Use algorithm described in official Apple `ld64` implementation. Link: https://opensource.apple.com/source/ld64/ld64-123.2.1/src/abstraction/MachOTrie.hpp Signed-off-by: Jakub Konka <kubkon@jakubkonka.com>

2 files changed, 84 insertions(+), 62 deletions(-)

src/link/MachO.zig+1-1
...@@ -1415,7 +1415,7 @@ fn writeExportTrie(self: *MachO) !void {...@@ -1415,7 +1415,7 @@ fn writeExportTrie(self: *MachO) !void {
1415 assert(symbol.n_value >= text_segment.vmaddr);1415 assert(symbol.n_value >= text_segment.vmaddr);
1416 try trie.put(self.base.allocator, .{1416 try trie.put(self.base.allocator, .{
1417 .name = name,1417 .name = name,
1418 .offset = symbol.n_value - text_segment.vmaddr,1418 .vmaddr_offset = symbol.n_value - text_segment.vmaddr,
1419 .export_flags = 0, // TODO workout creation of export flags1419 .export_flags = 0, // TODO workout creation of export flags
1420 });1420 });
1421 }1421 }
src/link/MachO/Trie.zig+83-61
...@@ -39,7 +39,7 @@ const Allocator = mem.Allocator;...@@ -39,7 +39,7 @@ const Allocator = mem.Allocator;
3939
40pub const Symbol = struct {40pub const Symbol = struct {
41 name: []const u8,41 name: []const u8,
42 offset: u64,42 vmaddr_offset: u64,
43 export_flags: u64,43 export_flags: u64,
44};44};
4545
...@@ -58,7 +58,8 @@ const Edge = struct {...@@ -58,7 +58,8 @@ const Edge = struct {
5858
59const Node = struct {59const Node = struct {
60 export_flags: ?u64 = null,60 export_flags: ?u64 = null,
61 offset: ?u64 = null,61 vmaddr_offset: ?u64 = null,
62 trie_offset: usize = 0,
62 edges: std.ArrayListUnmanaged(Edge) = .{},63 edges: std.ArrayListUnmanaged(Edge) = .{},
6364
64 fn deinit(self: *Node, alloc: *Allocator) void {65 fn deinit(self: *Node, alloc: *Allocator) void {
...@@ -111,8 +112,8 @@ const Node = struct {...@@ -111,8 +112,8 @@ const Node = struct {
111 return node;112 return node;
112 }113 }
113114
114 fn writeULEB128Mem(self: Node, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) Trie.WriteError!void {115 fn writeULEB128Mem(self: Node, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) !void {
115 if (self.offset) |offset| {116 if (self.vmaddr_offset) |offset| {
116 // Terminal node info: encode export flags and vmaddr offset of this symbol.117 // Terminal node info: encode export flags and vmaddr offset of this symbol.
117 var info_buf_len: usize = 0;118 var info_buf_len: usize = 0;
118 var info_buf: [@sizeOf(u64) * 2]u8 = undefined;119 var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
...@@ -134,34 +135,53 @@ const Node = struct {...@@ -134,34 +135,53 @@ const Node = struct {
134 // Write number of edges (max legal number of edges is 256).135 // Write number of edges (max legal number of edges is 256).
135 try buffer.append(alloc, @intCast(u8, self.edges.items.len));136 try buffer.append(alloc, @intCast(u8, self.edges.items.len));
136137
137 var node_offset_info: [std.math.maxInt(u8)]u64 = undefined;138 for (self.edges.items) |edge| {
138 for (self.edges.items) |edge, i| {139 // Write edges labels.
139 // Write edges labels leaving out space in-between to later populate140 try buffer.ensureCapacity(alloc, buffer.items.len + edge.label.len + 1); // +1 to account for null-byte
140 // with offsets to each node.
141 try buffer.ensureCapacity(alloc, buffer.items.len + edge.label.len + 1 + @sizeOf(u64)); // +1 to account for null-byte
142 buffer.appendSliceAssumeCapacity(edge.label);141 buffer.appendSliceAssumeCapacity(edge.label);
143 buffer.appendAssumeCapacity(0);142 buffer.appendAssumeCapacity(0);
144 node_offset_info[i] = buffer.items.len;143
145 const padding = [_]u8{0} ** @sizeOf(u64);144 var buf: [@sizeOf(u64)]u8 = undefined;
146 buffer.appendSliceAssumeCapacity(padding[0..]);145 const buf_len = try leb.writeULEB128Mem(buf[0..], edge.to.trie_offset);
146 try buffer.appendSlice(alloc, buf[0..buf_len]);
147 }147 }
148 }
149
150 const UpdateResult = struct {
151 node_size: usize,
152 updated: bool,
153 };
148154
149 for (self.edges.items) |edge, i| {155 fn updateOffset(self: *Node, offset: usize) UpdateResult {
150 const offset = buffer.items.len;156 var node_size: usize = 0;
151 try edge.to.writeULEB128Mem(alloc, buffer);157 if (self.vmaddr_offset) |vmaddr| {
152 // We can now populate the offset to the node pointed by this edge.158 node_size += sizeULEB128Mem(self.export_flags.?);
153 // TODO this is not the approach taken by `ld64` which does several iterations159 node_size += sizeULEB128Mem(vmaddr);
154 // to close the gap between the space encoding the offset to the node pointed160 node_size += sizeULEB128Mem(node_size);
155 // by this edge. However, it seems that as long as we are contiguous, the padding161 } else {
156 // introduced here should not influence the performance of `dyld`. I'm leaving162 node_size += 1; // 0x0 for non-terminal nodes
157 // this TODO here though as a reminder to re-investigate in the future and especially
158 // when we start working on dylibs in case `dyld` refuses to cooperate and/or the
159 // performance is noticably sufferring.
160 // Link to official impl: https://opensource.apple.com/source/ld64/ld64-123.2.1/src/abstraction/MachOTrie.hpp
161 var offset_buf: [@sizeOf(u64)]u8 = undefined;
162 const offset_buf_len = try leb.writeULEB128Mem(offset_buf[0..], offset);
163 mem.copy(u8, buffer.items[node_offset_info[i]..], offset_buf[0..offset_buf_len]);
164 }163 }
164 node_size += 1; // 1 byte for edge count
165
166 for (self.edges.items) |edge| {
167 node_size += edge.label.len + 1 + sizeULEB128Mem(edge.to.trie_offset);
168 }
169
170 const updated = offset != self.trie_offset;
171 self.trie_offset = offset;
172
173 return .{ .node_size = node_size, .updated = updated };
174 }
175
176 fn sizeULEB128Mem(value: u64) usize {
177 var res: usize = 0;
178 var v = value;
179 while (true) {
180 v = v >> 7;
181 res += 1;
182 if (v == 0) break;
183 }
184 return res;
165 }185 }
166};186};
167187
...@@ -172,15 +192,38 @@ root: Node,...@@ -172,15 +192,38 @@ root: Node,
172/// certain circumstances.192/// certain circumstances.
173pub fn put(self: *Trie, alloc: *Allocator, symbol: Symbol) !void {193pub fn put(self: *Trie, alloc: *Allocator, symbol: Symbol) !void {
174 const node = try self.root.put(alloc, symbol.name);194 const node = try self.root.put(alloc, symbol.name);
175 node.offset = symbol.offset;195 node.vmaddr_offset = symbol.vmaddr_offset;
176 node.export_flags = symbol.export_flags;196 node.export_flags = symbol.export_flags;
177}197}
178198
179pub const WriteError = error{ OutOfMemory, NoSpaceLeft };
180
181/// Write the trie to a buffer ULEB128 encoded.199/// Write the trie to a buffer ULEB128 encoded.
182pub fn writeULEB128Mem(self: Trie, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) WriteError!void {200pub fn writeULEB128Mem(self: *Trie, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) !void {
183 return self.root.writeULEB128Mem(alloc, buffer);201 var ordered_nodes: std.ArrayListUnmanaged(*Node) = .{};
202 defer ordered_nodes.deinit(alloc);
203
204 try walkInOrder(&self.root, alloc, &ordered_nodes);
205
206 var more: bool = true;
207 while (more) {
208 var offset: usize = 0;
209 more = false;
210 for (ordered_nodes.items) |node| {
211 const res = node.updateOffset(offset);
212 offset += res.node_size;
213 if (res.updated) more = true;
214 }
215 }
216
217 for (ordered_nodes.items) |node| {
218 try node.writeULEB128Mem(alloc, buffer);
219 }
220}
221
222fn walkInOrder(node: *Node, alloc: *Allocator, list: *std.ArrayListUnmanaged(*Node)) error{OutOfMemory}!void {
223 try list.append(alloc, node);
224 for (node.edges.items) |*edge| {
225 try walkInOrder(edge.to, alloc, list);
226 }
184}227}
185228
186pub fn deinit(self: *Trie, alloc: *Allocator) void {229pub fn deinit(self: *Trie, alloc: *Allocator) void {
...@@ -200,7 +243,7 @@ test "Trie basic" {...@@ -200,7 +243,7 @@ test "Trie basic" {
200 // root --- _st ---> node243 // root --- _st ---> node
201 try trie.put(gpa, .{244 try trie.put(gpa, .{
202 .name = "_st",245 .name = "_st",
203 .offset = 0,246 .vmaddr_offset = 0,
204 .export_flags = 0,247 .export_flags = 0,
205 });248 });
206 testing.expect(trie.root.edges.items.len == 1);249 testing.expect(trie.root.edges.items.len == 1);
...@@ -210,7 +253,7 @@ test "Trie basic" {...@@ -210,7 +253,7 @@ test "Trie basic" {
210 // root --- _st ---> node --- art ---> node253 // root --- _st ---> node --- art ---> node
211 try trie.put(gpa, .{254 try trie.put(gpa, .{
212 .name = "_start",255 .name = "_start",
213 .offset = 0,256 .vmaddr_offset = 0,
214 .export_flags = 0,257 .export_flags = 0,
215 });258 });
216 testing.expect(trie.root.edges.items.len == 1);259 testing.expect(trie.root.edges.items.len == 1);
...@@ -226,7 +269,7 @@ test "Trie basic" {...@@ -226,7 +269,7 @@ test "Trie basic" {
226 // | --- main ---> node269 // | --- main ---> node
227 try trie.put(gpa, .{270 try trie.put(gpa, .{
228 .name = "_main",271 .name = "_main",
229 .offset = 0,272 .vmaddr_offset = 0,
230 .export_flags = 0,273 .export_flags = 0,
231 });274 });
232 testing.expect(trie.root.edges.items.len == 1);275 testing.expect(trie.root.edges.items.len == 1);
...@@ -251,12 +294,12 @@ test "Trie.writeULEB128Mem" {...@@ -251,12 +294,12 @@ test "Trie.writeULEB128Mem" {
251294
252 try trie.put(gpa, .{295 try trie.put(gpa, .{
253 .name = "__mh_execute_header",296 .name = "__mh_execute_header",
254 .offset = 0,297 .vmaddr_offset = 0,
255 .export_flags = 0,298 .export_flags = 0,
256 });299 });
257 try trie.put(gpa, .{300 try trie.put(gpa, .{
258 .name = "_main",301 .name = "_main",
259 .offset = 0x1000,302 .vmaddr_offset = 0x1000,
260 .export_flags = 0,303 .export_flags = 0,
261 });304 });
262305
...@@ -270,14 +313,7 @@ test "Trie.writeULEB128Mem" {...@@ -270,14 +313,7 @@ test "Trie.writeULEB128Mem" {
270 0x1,313 0x1,
271 0x5f,314 0x5f,
272 0x0,315 0x0,
273 0xc,316 0x5,
274 0x0,
275 0x0,
276 0x0,
277 0x0,
278 0x0,
279 0x0,
280 0x0,
281 0x0,317 0x0,
282 0x2,318 0x2,
283 0x5f,319 0x5f,
...@@ -299,27 +335,13 @@ test "Trie.writeULEB128Mem" {...@@ -299,27 +335,13 @@ test "Trie.writeULEB128Mem" {
299 0x65,335 0x65,
300 0x72,336 0x72,
301 0x0,337 0x0,
302 0x36,338 0x21,
303 0x0,
304 0x0,
305 0x0,
306 0x0,
307 0x0,
308 0x0,
309 0x0,
310 0x6d,339 0x6d,
311 0x61,340 0x61,
312 0x69,341 0x69,
313 0x6e,342 0x6e,
314 0x0,343 0x0,
315 0x3a,344 0x25,
316 0x0,
317 0x0,
318 0x0,
319 0x0,
320 0x0,
321 0x0,
322 0x0,
323 0x2,345 0x2,
324 0x0,346 0x0,
325 0x0,347 0x0,