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
signature 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, .{