| ... | @@ -39,7 +39,7 @@ const Allocator = mem.Allocator; | ... | @@ -39,7 +39,7 @@ const Allocator = mem.Allocator; |
| 39 | | 39 | |
| 40 | pub const Symbol = struct { | 40 | pub 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 | }; |
| 45 | | 45 | |
| ... | @@ -58,7 +58,8 @@ const Edge = struct { | ... | @@ -58,7 +58,8 @@ const Edge = struct { |
| 58 | | 58 | |
| 59 | const Node = struct { | 59 | const 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) = .{}, |
| 63 | | 64 | |
| 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 | } |
| 113 | | 114 | |
| 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)); |
| 136 | | 137 | |
| 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 populate | 140 | 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 | }; |
| 148 | | 154 | |
| 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 iterations | 159 | node_size += sizeULEB128Mem(vmaddr); |
| 154 | // to close the gap between the space encoding the offset to the node pointed | 160 | node_size += sizeULEB128Mem(node_size); |
| 155 | // by this edge. However, it seems that as long as we are contiguous, the padding | 161 | } else { |
| 156 | // introduced here should not influence the performance of `dyld`. I'm leaving | 162 | 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 | }; |
| 167 | | 187 | |
| ... | @@ -172,15 +192,38 @@ root: Node, | ... | @@ -172,15 +192,38 @@ root: Node, |
| 172 | /// certain circumstances. | 192 | /// certain circumstances. |
| 173 | pub fn put(self: *Trie, alloc: *Allocator, symbol: Symbol) !void { | 193 | pub 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 | } |
| 178 | | 198 | |
| 179 | pub const WriteError = error{ OutOfMemory, NoSpaceLeft }; | | |
| 180 | | | |
| 181 | /// Write the trie to a buffer ULEB128 encoded. | 199 | /// Write the trie to a buffer ULEB128 encoded. |
| 182 | pub fn writeULEB128Mem(self: Trie, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) WriteError!void { | 200 | pub 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 | |
| | 222 | fn 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 | } |
| 185 | | 228 | |
| 186 | pub fn deinit(self: *Trie, alloc: *Allocator) void { | 229 | pub fn deinit(self: *Trie, alloc: *Allocator) void { |
| ... | @@ -200,7 +243,7 @@ test "Trie basic" { | ... | @@ -200,7 +243,7 @@ test "Trie basic" { |
| 200 | // root --- _st ---> node | 243 | // 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 ---> node | 253 | // 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 ---> node | 269 | // | --- 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" { |
| 251 | | 294 | |
| 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 | }); |
| 262 | | 305 | |
| ... | @@ -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, |