authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-10-08 18:10:32+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-10-08 19:53:23+02:00
logba41e599bfaff2c614c4edfbe5c7ffe94b437486
tree4b3cd8d8f9bb3ac1bb52addf00e1b3c581ade710
parent5f86505cf79a0ce75e1a02602ae0e9c845024982
signaturelock-open Commit is signed but in an unrecognized format.

Clean up writing the trie into ULEB128 byte stream

Prealloc as much as possible to improve alloc performance. Signed-off-by: Jakub Konka <kubkon@jakubkonka.com>

2 files changed, 112 insertions(+), 36 deletions(-)

src/link/MachO.zig+1-3
...@@ -1403,9 +1403,7 @@ fn writeAllUndefSymbols(self: *MachO) !void {...@@ -1403,9 +1403,7 @@ fn writeAllUndefSymbols(self: *MachO) !void {
1403fn writeExportTrie(self: *MachO) !void {1403fn writeExportTrie(self: *MachO) !void {
1404 if (self.global_symbols.items.len == 0) return; // No exports, nothing to do.1404 if (self.global_symbols.items.len == 0) return; // No exports, nothing to do.
14051405
1406 var trie: Trie = .{1406 var trie: Trie = .{};
1407 .root = .{},
1408 };
1409 defer trie.deinit(self.base.allocator);1407 defer trie.deinit(self.base.allocator);
14101408
1411 const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;1409 const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
src/link/MachO/Trie.zig+111-33
...@@ -35,6 +35,7 @@ const mem = std.mem;...@@ -35,6 +35,7 @@ const mem = std.mem;
35const leb = std.debug.leb;35const leb = std.debug.leb;
36const log = std.log.scoped(.link);36const log = std.log.scoped(.link);
37const testing = std.testing;37const testing = std.testing;
38const assert = std.debug.assert;
38const Allocator = mem.Allocator;39const Allocator = mem.Allocator;
3940
40pub const Symbol = struct {41pub const Symbol = struct {
...@@ -57,9 +58,13 @@ const Edge = struct {...@@ -57,9 +58,13 @@ const Edge = struct {
57};58};
5859
59const Node = struct {60const Node = struct {
61 /// Export flags associated with this exported symbol (if any).
60 export_flags: ?u64 = null,62 export_flags: ?u64 = null,
63 /// VM address offset wrt to the section this symbol is defined against (if any).
61 vmaddr_offset: ?u64 = null,64 vmaddr_offset: ?u64 = null,
62 trie_offset: usize = 0,65 /// Offset of this node in the trie output byte stream.
66 trie_offset: ?usize = null,
67 /// List of all edges originating from this node.
63 edges: std.ArrayListUnmanaged(Edge) = .{},68 edges: std.ArrayListUnmanaged(Edge) = .{},
6469
65 fn deinit(self: *Node, alloc: *Allocator) void {70 fn deinit(self: *Node, alloc: *Allocator) void {
...@@ -69,12 +74,24 @@ const Node = struct {...@@ -69,12 +74,24 @@ const Node = struct {
69 self.edges.deinit(alloc);74 self.edges.deinit(alloc);
70 }75 }
7176
72 fn put(self: *Node, alloc: *Allocator, label: []const u8) !*Node {77 const PutResult = struct {
78 /// Node reached at this stage of `put` op.
79 node: *Node,
80 /// Count of newly inserted nodes at this stage of `put` op.
81 node_count: usize,
82 };
83
84 /// Inserts a new node starting from `self`.
85 fn put(self: *Node, alloc: *Allocator, label: []const u8, node_count: usize) !PutResult {
86 var curr_node_count = node_count;
73 // Check for match with edges from this node.87 // Check for match with edges from this node.
74 for (self.edges.items) |*edge| {88 for (self.edges.items) |*edge| {
75 const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;89 const match = mem.indexOfDiff(u8, edge.label, label) orelse return PutResult{
90 .node = edge.to,
91 .node_count = curr_node_count,
92 };
76 if (match == 0) continue;93 if (match == 0) continue;
77 if (match == edge.label.len) return edge.to.put(alloc, label[match..]);94 if (match == edge.label.len) return edge.to.put(alloc, label[match..], curr_node_count);
7895
79 // Found a match, need to splice up nodes.96 // Found a match, need to splice up nodes.
80 // From: A -> B97 // From: A -> B
...@@ -85,6 +102,7 @@ const Node = struct {...@@ -85,6 +102,7 @@ const Node = struct {
85 const to_node = edge.to;102 const to_node = edge.to;
86 edge.to = mid;103 edge.to = mid;
87 edge.label = label[0..match];104 edge.label = label[0..match];
105 curr_node_count += 1;
88106
89 try mid.edges.append(alloc, .{107 try mid.edges.append(alloc, .{
90 .from = mid,108 .from = mid,
...@@ -93,15 +111,16 @@ const Node = struct {...@@ -93,15 +111,16 @@ const Node = struct {
93 });111 });
94112
95 if (match == label.len) {113 if (match == label.len) {
96 return to_node;114 return PutResult{ .node = to_node, .node_count = curr_node_count };
97 } else {115 } else {
98 return mid.put(alloc, label[match..]);116 return mid.put(alloc, label[match..], curr_node_count);
99 }117 }
100 }118 }
101119
102 // Add a new edge.120 // Add a new node.
103 const node = try alloc.create(Node);121 const node = try alloc.create(Node);
104 node.* = .{};122 node.* = .{};
123 curr_node_count += 1;
105124
106 try self.edges.append(alloc, .{125 try self.edges.append(alloc, .{
107 .from = self,126 .from = self,
...@@ -109,10 +128,13 @@ const Node = struct {...@@ -109,10 +128,13 @@ const Node = struct {
109 .label = label,128 .label = label,
110 });129 });
111130
112 return node;131 return PutResult{ .node = node, .node_count = curr_node_count };
113 }132 }
114133
115 fn writeULEB128Mem(self: Node, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) !void {134 /// This method should only be called *after* updateOffset has been called!
135 /// In case this is not upheld, this method will panic.
136 fn writeULEB128Mem(self: Node, buffer: *std.ArrayListUnmanaged(u8)) !void {
137 assert(self.trie_offset != null); // You need to call updateOffset first.
116 if (self.vmaddr_offset) |offset| {138 if (self.vmaddr_offset) |offset| {
117 // Terminal node info: encode export flags and vmaddr offset of this symbol.139 // Terminal node info: encode export flags and vmaddr offset of this symbol.
118 var info_buf_len: usize = 0;140 var info_buf_len: usize = 0;
...@@ -125,33 +147,35 @@ const Node = struct {...@@ -125,33 +147,35 @@ const Node = struct {
125 const size_buf_len = try leb.writeULEB128Mem(size_buf[0..], info_buf_len);147 const size_buf_len = try leb.writeULEB128Mem(size_buf[0..], info_buf_len);
126148
127 // Now, write them to the output buffer.149 // Now, write them to the output buffer.
128 try buffer.ensureCapacity(alloc, buffer.items.len + info_buf_len + size_buf_len);
129 buffer.appendSliceAssumeCapacity(size_buf[0..size_buf_len]);150 buffer.appendSliceAssumeCapacity(size_buf[0..size_buf_len]);
130 buffer.appendSliceAssumeCapacity(info_buf[0..info_buf_len]);151 buffer.appendSliceAssumeCapacity(info_buf[0..info_buf_len]);
131 } else {152 } else {
132 // Non-terminal node is delimited by 0 byte.153 // Non-terminal node is delimited by 0 byte.
133 try buffer.append(alloc, 0);154 buffer.appendAssumeCapacity(0);
134 }155 }
135 // Write number of edges (max legal number of edges is 256).156 // Write number of edges (max legal number of edges is 256).
136 try buffer.append(alloc, @intCast(u8, self.edges.items.len));157 buffer.appendAssumeCapacity(@intCast(u8, self.edges.items.len));
137158
138 for (self.edges.items) |edge| {159 for (self.edges.items) |edge| {
139 // Write edges labels.160 // Write edges labels.
140 try buffer.ensureCapacity(alloc, buffer.items.len + edge.label.len + 1); // +1 to account for null-byte
141 buffer.appendSliceAssumeCapacity(edge.label);161 buffer.appendSliceAssumeCapacity(edge.label);
142 buffer.appendAssumeCapacity(0);162 buffer.appendAssumeCapacity(0);
143163
144 var buf: [@sizeOf(u64)]u8 = undefined;164 var buf: [@sizeOf(u64)]u8 = undefined;
145 const buf_len = try leb.writeULEB128Mem(buf[0..], edge.to.trie_offset);165 const buf_len = try leb.writeULEB128Mem(buf[0..], edge.to.trie_offset.?);
146 try buffer.appendSlice(alloc, buf[0..buf_len]);166 buffer.appendSliceAssumeCapacity(buf[0..buf_len]);
147 }167 }
148 }168 }
149169
150 const UpdateResult = struct {170 const UpdateResult = struct {
171 /// Current size of this node in bytes.
151 node_size: usize,172 node_size: usize,
173 /// True if the trie offset of this node in the output byte stream
174 /// would need updating; false otherwise.
152 updated: bool,175 updated: bool,
153 };176 };
154177
178 /// Updates offset of this node in the output byte stream.
155 fn updateOffset(self: *Node, offset: usize) UpdateResult {179 fn updateOffset(self: *Node, offset: usize) UpdateResult {
156 var node_size: usize = 0;180 var node_size: usize = 0;
157 if (self.vmaddr_offset) |vmaddr| {181 if (self.vmaddr_offset) |vmaddr| {
...@@ -164,15 +188,18 @@ const Node = struct {...@@ -164,15 +188,18 @@ const Node = struct {
164 node_size += 1; // 1 byte for edge count188 node_size += 1; // 1 byte for edge count
165189
166 for (self.edges.items) |edge| {190 for (self.edges.items) |edge| {
167 node_size += edge.label.len + 1 + sizeULEB128Mem(edge.to.trie_offset);191 const next_node_offset = edge.to.trie_offset orelse 0;
192 node_size += edge.label.len + 1 + sizeULEB128Mem(next_node_offset);
168 }193 }
169194
170 const updated = offset != self.trie_offset;195 const trie_offset = self.trie_offset orelse 0;
196 const updated = offset != trie_offset;
171 self.trie_offset = offset;197 self.trie_offset = offset;
172198
173 return .{ .node_size = node_size, .updated = updated };199 return .{ .node_size = node_size, .updated = updated };
174 }200 }
175201
202 /// Calculates number of bytes in ULEB128 encoding of value.
176 fn sizeULEB128Mem(value: u64) usize {203 fn sizeULEB128Mem(value: u64) usize {
177 var res: usize = 0;204 var res: usize = 0;
178 var v = value;205 var v = value;
...@@ -185,15 +212,22 @@ const Node = struct {...@@ -185,15 +212,22 @@ const Node = struct {
185 }212 }
186};213};
187214
188root: Node,215/// Count of nodes in the trie.
216/// The count is updated at every `put` call.
217/// The trie always consists of at least a root node, hence
218/// the count always starts at 1.
219node_count: usize = 1,
220/// The root node of the trie.
221root: Node = .{},
189222
190/// Insert a symbol into the trie, updating the prefixes in the process.223/// Insert a symbol into the trie, updating the prefixes in the process.
191/// This operation may change the layout of the trie by splicing edges in224/// This operation may change the layout of the trie by splicing edges in
192/// certain circumstances.225/// certain circumstances.
193pub fn put(self: *Trie, alloc: *Allocator, symbol: Symbol) !void {226pub fn put(self: *Trie, alloc: *Allocator, symbol: Symbol) !void {
194 const node = try self.root.put(alloc, symbol.name);227 const res = try self.root.put(alloc, symbol.name, 0);
195 node.vmaddr_offset = symbol.vmaddr_offset;228 self.node_count += res.node_count;
196 node.export_flags = symbol.export_flags;229 res.node.vmaddr_offset = symbol.vmaddr_offset;
230 res.node.export_flags = symbol.export_flags;
197}231}
198232
199/// Write the trie to a buffer ULEB128 encoded.233/// Write the trie to a buffer ULEB128 encoded.
...@@ -201,11 +235,13 @@ pub fn writeULEB128Mem(self: *Trie, alloc: *Allocator, buffer: *std.ArrayListUnm...@@ -201,11 +235,13 @@ pub fn writeULEB128Mem(self: *Trie, alloc: *Allocator, buffer: *std.ArrayListUnm
201 var ordered_nodes: std.ArrayListUnmanaged(*Node) = .{};235 var ordered_nodes: std.ArrayListUnmanaged(*Node) = .{};
202 defer ordered_nodes.deinit(alloc);236 defer ordered_nodes.deinit(alloc);
203237
204 try walkInOrder(&self.root, alloc, &ordered_nodes);238 try ordered_nodes.ensureCapacity(alloc, self.node_count);
239 walkInOrder(&self.root, &ordered_nodes);
205240
241 var offset: usize = 0;
206 var more: bool = true;242 var more: bool = true;
207 while (more) {243 while (more) {
208 var offset: usize = 0;244 offset = 0;
209 more = false;245 more = false;
210 for (ordered_nodes.items) |node| {246 for (ordered_nodes.items) |node| {
211 const res = node.updateOffset(offset);247 const res = node.updateOffset(offset);
...@@ -214,15 +250,17 @@ pub fn writeULEB128Mem(self: *Trie, alloc: *Allocator, buffer: *std.ArrayListUnm...@@ -214,15 +250,17 @@ pub fn writeULEB128Mem(self: *Trie, alloc: *Allocator, buffer: *std.ArrayListUnm
214 }250 }
215 }251 }
216252
253 try buffer.ensureCapacity(alloc, buffer.items.len + offset);
217 for (ordered_nodes.items) |node| {254 for (ordered_nodes.items) |node| {
218 try node.writeULEB128Mem(alloc, buffer);255 try node.writeULEB128Mem(buffer);
219 }256 }
220}257}
221258
222fn walkInOrder(node: *Node, alloc: *Allocator, list: *std.ArrayListUnmanaged(*Node)) error{OutOfMemory}!void {259/// Walks the trie in DFS order gathering all nodes into a linear stream of nodes.
223 try list.append(alloc, node);260fn walkInOrder(node: *Node, list: *std.ArrayListUnmanaged(*Node)) void {
261 list.appendAssumeCapacity(node);
224 for (node.edges.items) |*edge| {262 for (node.edges.items) |*edge| {
225 try walkInOrder(edge.to, alloc, list);263 walkInOrder(edge.to, list);
226 }264 }
227}265}
228266
...@@ -230,11 +268,53 @@ pub fn deinit(self: *Trie, alloc: *Allocator) void {...@@ -230,11 +268,53 @@ pub fn deinit(self: *Trie, alloc: *Allocator) void {
230 self.root.deinit(alloc);268 self.root.deinit(alloc);
231}269}
232270
271test "Trie node count" {
272 var gpa = testing.allocator;
273 var trie: Trie = .{};
274 defer trie.deinit(gpa);
275
276 testing.expectEqual(trie.node_count, 1);
277
278 try trie.put(gpa, .{
279 .name = "_main",
280 .vmaddr_offset = 0,
281 .export_flags = 0,
282 });
283 testing.expectEqual(trie.node_count, 2);
284
285 // Inserting the same node shouldn't update the trie.
286 try trie.put(gpa, .{
287 .name = "_main",
288 .vmaddr_offset = 0,
289 .export_flags = 0,
290 });
291 testing.expectEqual(trie.node_count, 2);
292
293 try trie.put(gpa, .{
294 .name = "__mh_execute_header",
295 .vmaddr_offset = 0x1000,
296 .export_flags = 0,
297 });
298 testing.expectEqual(trie.node_count, 4);
299
300 // Inserting the same node shouldn't update the trie.
301 try trie.put(gpa, .{
302 .name = "__mh_execute_header",
303 .vmaddr_offset = 0x1000,
304 .export_flags = 0,
305 });
306 testing.expectEqual(trie.node_count, 4);
307 try trie.put(gpa, .{
308 .name = "_main",
309 .vmaddr_offset = 0,
310 .export_flags = 0,
311 });
312 testing.expectEqual(trie.node_count, 4);
313}
314
233test "Trie basic" {315test "Trie basic" {
234 var gpa = testing.allocator;316 var gpa = testing.allocator;
235 var trie: Trie = .{317 var trie: Trie = .{};
236 .root = .{},
237 };
238 defer trie.deinit(gpa);318 defer trie.deinit(gpa);
239319
240 // root320 // root
...@@ -287,9 +367,7 @@ test "Trie basic" {...@@ -287,9 +367,7 @@ test "Trie basic" {
287367
288test "Trie.writeULEB128Mem" {368test "Trie.writeULEB128Mem" {
289 var gpa = testing.allocator;369 var gpa = testing.allocator;
290 var trie: Trie = .{370 var trie: Trie = .{};
291 .root = .{},
292 };
293 defer trie.deinit(gpa);371 defer trie.deinit(gpa);
294372
295 try trie.put(gpa, .{373 try trie.put(gpa, .{