| ... | ... | @@ -39,7 +39,7 @@ const Allocator = mem.Allocator; |
| 39 | 39 | |
| 40 | 40 | pub const Symbol = struct { |
| 41 | 41 | name: []const u8, |
| 42 | | offset: u64, |
| 42 | vmaddr_offset: u64, |
| 43 | 43 | export_flags: u64, |
| 44 | 44 | }; |
| 45 | 45 | |
| ... | ... | @@ -58,7 +58,8 @@ const Edge = struct { |
| 58 | 58 | |
| 59 | 59 | const Node = struct { |
| 60 | 60 | export_flags: ?u64 = null, |
| 61 | | offset: ?u64 = null, |
| 61 | vmaddr_offset: ?u64 = null, |
| 62 | trie_offset: usize = 0, |
| 62 | 63 | edges: std.ArrayListUnmanaged(Edge) = .{}, |
| 63 | 64 | |
| 64 | 65 | fn deinit(self: *Node, alloc: *Allocator) void { |
| ... | ... | @@ -111,8 +112,8 @@ const Node = struct { |
| 111 | 112 | return node; |
| 112 | 113 | } |
| 113 | 114 | |
| 114 | | fn writeULEB128Mem(self: Node, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) Trie.WriteError!void { |
| 115 | | if (self.offset) |offset| { |
| 115 | fn writeULEB128Mem(self: Node, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) !void { |
| 116 | if (self.vmaddr_offset) |offset| { |
| 116 | 117 | // Terminal node info: encode export flags and vmaddr offset of this symbol. |
| 117 | 118 | var info_buf_len: usize = 0; |
| 118 | 119 | var info_buf: [@sizeOf(u64) * 2]u8 = undefined; |
| ... | ... | @@ -134,34 +135,53 @@ const Node = struct { |
| 134 | 135 | // Write number of edges (max legal number of edges is 256). |
| 135 | 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, i| { |
| 139 | | // Write edges labels leaving out space in-between to later populate |
| 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 |
| 138 | for (self.edges.items) |edge| { |
| 139 | // Write edges labels. |
| 140 | try buffer.ensureCapacity(alloc, buffer.items.len + edge.label.len + 1); // +1 to account for null-byte |
| 142 | 141 | buffer.appendSliceAssumeCapacity(edge.label); |
| 143 | 142 | buffer.appendAssumeCapacity(0); |
| 144 | | node_offset_info[i] = buffer.items.len; |
| 145 | | const padding = [_]u8{0} ** @sizeOf(u64); |
| 146 | | buffer.appendSliceAssumeCapacity(padding[0..]); |
| 143 | |
| 144 | var buf: [@sizeOf(u64)]u8 = undefined; |
| 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| { |
| 150 | | const offset = buffer.items.len; |
| 151 | | try edge.to.writeULEB128Mem(alloc, buffer); |
| 152 | | // We can now populate the offset to the node pointed by this edge. |
| 153 | | // TODO this is not the approach taken by `ld64` which does several iterations |
| 154 | | // to close the gap between the space encoding the offset to the node pointed |
| 155 | | // by this edge. However, it seems that as long as we are contiguous, the padding |
| 156 | | // introduced here should not influence the performance of `dyld`. I'm leaving |
| 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]); |
| 155 | fn updateOffset(self: *Node, offset: usize) UpdateResult { |
| 156 | var node_size: usize = 0; |
| 157 | if (self.vmaddr_offset) |vmaddr| { |
| 158 | node_size += sizeULEB128Mem(self.export_flags.?); |
| 159 | node_size += sizeULEB128Mem(vmaddr); |
| 160 | node_size += sizeULEB128Mem(node_size); |
| 161 | } else { |
| 162 | node_size += 1; // 0x0 for non-terminal nodes |
| 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 | 192 | /// certain circumstances. |
| 173 | 193 | pub fn put(self: *Trie, alloc: *Allocator, symbol: Symbol) !void { |
| 174 | 194 | const node = try self.root.put(alloc, symbol.name); |
| 175 | | node.offset = symbol.offset; |
| 195 | node.vmaddr_offset = symbol.vmaddr_offset; |
| 176 | 196 | node.export_flags = symbol.export_flags; |
| 177 | 197 | } |
| 178 | 198 | |
| 179 | | pub const WriteError = error{ OutOfMemory, NoSpaceLeft }; |
| 180 | | |
| 181 | 199 | /// Write the trie to a buffer ULEB128 encoded. |
| 182 | | pub fn writeULEB128Mem(self: Trie, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) WriteError!void { |
| 183 | | return self.root.writeULEB128Mem(alloc, buffer); |
| 200 | pub fn writeULEB128Mem(self: *Trie, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) !void { |
| 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 | 229 | pub fn deinit(self: *Trie, alloc: *Allocator) void { |
| ... | ... | @@ -200,7 +243,7 @@ test "Trie basic" { |
| 200 | 243 | // root --- _st ---> node |
| 201 | 244 | try trie.put(gpa, .{ |
| 202 | 245 | .name = "_st", |
| 203 | | .offset = 0, |
| 246 | .vmaddr_offset = 0, |
| 204 | 247 | .export_flags = 0, |
| 205 | 248 | }); |
| 206 | 249 | testing.expect(trie.root.edges.items.len == 1); |
| ... | ... | @@ -210,7 +253,7 @@ test "Trie basic" { |
| 210 | 253 | // root --- _st ---> node --- art ---> node |
| 211 | 254 | try trie.put(gpa, .{ |
| 212 | 255 | .name = "_start", |
| 213 | | .offset = 0, |
| 256 | .vmaddr_offset = 0, |
| 214 | 257 | .export_flags = 0, |
| 215 | 258 | }); |
| 216 | 259 | testing.expect(trie.root.edges.items.len == 1); |
| ... | ... | @@ -226,7 +269,7 @@ test "Trie basic" { |
| 226 | 269 | // | --- main ---> node |
| 227 | 270 | try trie.put(gpa, .{ |
| 228 | 271 | .name = "_main", |
| 229 | | .offset = 0, |
| 272 | .vmaddr_offset = 0, |
| 230 | 273 | .export_flags = 0, |
| 231 | 274 | }); |
| 232 | 275 | testing.expect(trie.root.edges.items.len == 1); |
| ... | ... | @@ -251,12 +294,12 @@ test "Trie.writeULEB128Mem" { |
| 251 | 294 | |
| 252 | 295 | try trie.put(gpa, .{ |
| 253 | 296 | .name = "__mh_execute_header", |
| 254 | | .offset = 0, |
| 297 | .vmaddr_offset = 0, |
| 255 | 298 | .export_flags = 0, |
| 256 | 299 | }); |
| 257 | 300 | try trie.put(gpa, .{ |
| 258 | 301 | .name = "_main", |
| 259 | | .offset = 0x1000, |
| 302 | .vmaddr_offset = 0x1000, |
| 260 | 303 | .export_flags = 0, |
| 261 | 304 | }); |
| 262 | 305 | |
| ... | ... | @@ -270,14 +313,7 @@ test "Trie.writeULEB128Mem" { |
| 270 | 313 | 0x1, |
| 271 | 314 | 0x5f, |
| 272 | 315 | 0x0, |
| 273 | | 0xc, |
| 274 | | 0x0, |
| 275 | | 0x0, |
| 276 | | 0x0, |
| 277 | | 0x0, |
| 278 | | 0x0, |
| 279 | | 0x0, |
| 280 | | 0x0, |
| 316 | 0x5, |
| 281 | 317 | 0x0, |
| 282 | 318 | 0x2, |
| 283 | 319 | 0x5f, |
| ... | ... | @@ -299,27 +335,13 @@ test "Trie.writeULEB128Mem" { |
| 299 | 335 | 0x65, |
| 300 | 336 | 0x72, |
| 301 | 337 | 0x0, |
| 302 | | 0x36, |
| 303 | | 0x0, |
| 304 | | 0x0, |
| 305 | | 0x0, |
| 306 | | 0x0, |
| 307 | | 0x0, |
| 308 | | 0x0, |
| 309 | | 0x0, |
| 338 | 0x21, |
| 310 | 339 | 0x6d, |
| 311 | 340 | 0x61, |
| 312 | 341 | 0x69, |
| 313 | 342 | 0x6e, |
| 314 | 343 | 0x0, |
| 315 | | 0x3a, |
| 316 | | 0x0, |
| 317 | | 0x0, |
| 318 | | 0x0, |
| 319 | | 0x0, |
| 320 | | 0x0, |
| 321 | | 0x0, |
| 322 | | 0x0, |
| 344 | 0x25, |
| 323 | 345 | 0x2, |
| 324 | 346 | 0x0, |
| 325 | 347 | 0x0, |