| ... | @@ -761,7 +761,7 @@ pub const Repository = struct { | ... | @@ -761,7 +761,7 @@ pub const Repository = struct { |
| 761 | for (r.pack_content.values()) |v| nfs.munmap(v); | 761 | for (r.pack_content.values()) |v| nfs.munmap(v); |
| 762 | r.pack_content.deinit(r.gpa); | 762 | r.pack_content.deinit(r.gpa); |
| 763 | r.commits.deinit(r.gpa); | 763 | r.commits.deinit(r.gpa); |
| 764 | for (r.trees.values()) |*v| v.children.deinit(r.gpa); | 764 | for (r.trees.values()) |*v| r.gpa.free(v.children); |
| 765 | r.trees.deinit(r.gpa); | 765 | r.trees.deinit(r.gpa); |
| 766 | r.tags.deinit(r.gpa); | 766 | r.tags.deinit(r.gpa); |
| 767 | } | 767 | } |
| ... | @@ -1097,8 +1097,7 @@ pub const Repository = struct { | ... | @@ -1097,8 +1097,7 @@ pub const Repository = struct { |
| 1097 | } | 1097 | } |
| 1098 | if (try r.getObject(arena, id.id)) |obj| { | 1098 | if (try r.getObject(arena, id.id)) |obj| { |
| 1099 | if (obj.type == .tree) { | 1099 | if (obj.type == .tree) { |
| 1100 | const MAL = MultiArrayList(Tree.Object); | 1100 | var children: std.ArrayList(Tree.Object) = .empty; |
| 1101 | var children: MAL = .empty; | | |
| 1102 | errdefer children.deinit(r.gpa); | 1101 | errdefer children.deinit(r.gpa); |
| 1103 | try children.ensureUnusedCapacity(r.gpa, 33); | 1102 | try children.ensureUnusedCapacity(r.gpa, 33); |
| 1104 | var i: usize = 0; | 1103 | var i: usize = 0; |
| ... | @@ -1133,24 +1132,9 @@ pub const Repository = struct { | ... | @@ -1133,24 +1132,9 @@ pub const Repository = struct { |
| 1133 | }); | 1132 | }); |
| 1134 | } | 1133 | } |
| 1135 | | 1134 | |
| 1136 | const S = struct { | 1135 | const children_slice = try children.toOwnedSlice(r.gpa); |
| 1137 | list: *MAL, | 1136 | errdefer r.gpa.free(children_slice); |
| 1138 | | 1137 | const tree: Tree = .{ .children = children_slice }; |
| 1139 | pub fn lessThan(s: @This(), a: usize, b: usize) bool { | | |
| 1140 | const items = s.list.items.name; | | |
| 1141 | return std.mem.order(u8, items[a], items[b]) == .lt; | | |
| 1142 | } | | |
| 1143 | pub fn swap(s: @This(), a: usize, b: usize) void { | | |
| 1144 | // Remove after updating to Zig 0.17. Ref: https://codeberg.org/ziglang/zig/pulls/32016 | | |
| 1145 | inline for (@typeInfo(Tree.Object).@"struct".fields) |field| { | | |
| 1146 | const its = @field(s.list.items, field.name); | | |
| 1147 | std.mem.swap(@FieldType(Tree.Object, field.name), &its[a], &its[b]); | | |
| 1148 | } | | |
| 1149 | } | | |
| 1150 | }; | | |
| 1151 | if (children.len() > 1000) std.mem.sortContext(0, children.len(), S{ .list = &children }); | | |
| 1152 | | | |
| 1153 | const tree: Tree = .{ .children = children }; | | |
| 1154 | try r.trees.put(r.gpa, id.id, tree); | 1138 | try r.trees.put(r.gpa, id.id, tree); |
| 1155 | return .{ id, tree }; | 1139 | return .{ id, tree }; |
| 1156 | } | 1140 | } |
| ... | @@ -1263,12 +1247,12 @@ pub const Repository = struct { | ... | @@ -1263,12 +1247,12 @@ pub const Repository = struct { |
| 1263 | const base = base_idx.reify(r); | 1247 | const base = base_idx.reify(r); |
| 1264 | const base_tree_id = (try traverseTo(r, arena, base.tree, dir_path)).?; | 1248 | const base_tree_id = (try traverseTo(r, arena, base.tree, dir_path)).?; |
| 1265 | const base_tree = try r.getTreeA(arena, base_tree_id.id); | 1249 | const base_tree = try r.getTreeA(arena, base_tree_id.id); |
| 1266 | const total = base_tree.children.len(); | 1250 | const total = base_tree.children.len; |
| 1267 | | 1251 | |
| 1268 | var found: usize = 0; | 1252 | var found: usize = 0; |
| 1269 | var result: std.StringArrayHashMapUnmanaged(CommitId) = .empty; | 1253 | var result: std.StringArrayHashMapUnmanaged(CommitId) = .empty; |
| 1270 | defer result.deinit(r.gpa); | 1254 | defer result.deinit(r.gpa); |
| 1271 | for (base_tree.children.items.name) |name| try result.put(r.gpa, name, undefined); | 1255 | for (base_tree.children) |obj| try result.put(r.gpa, obj.name, undefined); |
| 1272 | | 1256 | |
| 1273 | var set: std.bit_set.DynamicBitSetUnmanaged = try .initEmpty(r.gpa, total); | 1257 | var set: std.bit_set.DynamicBitSetUnmanaged = try .initEmpty(r.gpa, total); |
| 1274 | defer set.deinit(r.gpa); | 1258 | defer set.deinit(r.gpa); |
| ... | @@ -1301,7 +1285,7 @@ pub const Repository = struct { | ... | @@ -1301,7 +1285,7 @@ pub const Repository = struct { |
| 1301 | const tree = try r.getTreeA(arena, tree_id.id); | 1285 | const tree = try r.getTreeA(arena, tree_id.id); |
| 1302 | for (result.keys(), 0..) |k, i| { | 1286 | for (result.keys(), 0..) |k, i| { |
| 1303 | if (set.isSet(i)) continue; | 1287 | if (set.isSet(i)) continue; |
| 1304 | const new = tree.get(k); | 1288 | const new = tree.get(k, base_tree.children[i].mode.type); |
| 1305 | if (new == null) { | 1289 | if (new == null) { |
| 1306 | found += 1; | 1290 | found += 1; |
| 1307 | result.putAssumeCapacity(k, commit_id_prev); | 1291 | result.putAssumeCapacity(k, commit_id_prev); |
| ... | @@ -1309,7 +1293,7 @@ pub const Repository = struct { | ... | @@ -1309,7 +1293,7 @@ pub const Repository = struct { |
| 1309 | // std.log.debug("found [{d}/{d}] objects after searching {d} commits", .{ found, total, searched }); | 1293 | // std.log.debug("found [{d}/{d}] objects after searching {d} commits", .{ found, total, searched }); |
| 1310 | continue; | 1294 | continue; |
| 1311 | } | 1295 | } |
| 1312 | if (!std.mem.eql(u8, new.?.id.erase(), base_tree.children.items.id[i].erase())) { | 1296 | if (!std.mem.eql(u8, new.?.id.erase(), base_tree.children[i].id.erase())) { |
| 1313 | found += 1; | 1297 | found += 1; |
| 1314 | result.putAssumeCapacity(k, commit_id_prev); | 1298 | result.putAssumeCapacity(k, commit_id_prev); |
| 1315 | set.set(i); | 1299 | set.set(i); |
| ... | @@ -1393,7 +1377,7 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di | ... | @@ -1393,7 +1377,7 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di |
| 1393 | var iter = std.mem.splitScalar(u8, dir_path, '/'); | 1377 | var iter = std.mem.splitScalar(u8, dir_path, '/'); |
| 1394 | while (iter.next()) |segment| { | 1378 | while (iter.next()) |segment| { |
| 1395 | const tree = try r.getTreeA(arena, id.id); | 1379 | const tree = try r.getTreeA(arena, id.id); |
| 1396 | const o = tree.get(segment) orelse return null; | 1380 | const o = tree.get(segment, .directory) orelse return null; |
| 1397 | if (o.id != .tree) return null; | 1381 | if (o.id != .tree) return null; |
| 1398 | id = o.id.tree; | 1382 | id = o.id.tree; |
| 1399 | } | 1383 | } |
| ... | @@ -1401,31 +1385,23 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di | ... | @@ -1401,31 +1385,23 @@ fn traverseTo(r: *Repository, arena: std.mem.Allocator, treestart_id: TreeId, di |
| 1401 | } | 1385 | } |
| 1402 | | 1386 | |
| 1403 | pub const Tree = struct { | 1387 | pub const Tree = struct { |
| 1404 | children: MultiArrayList(Object), | 1388 | children: []const Object, |
| 1405 | | 1389 | |
| 1406 | pub fn get(self: Tree, name: string) ?Object { | 1390 | pub fn get(self: Tree, name: string, hint: Object.Type) ?Object { |
| 1407 | if (self.children.len() <= 1000) { | 1391 | const i = std.sort.binarySearch(Object, self.children, .{ name, hint }, Object.search) orelse return null; |
| 1408 | for (self.children.items.name, 0..) |a_name, i| { | 1392 | return self.children[i]; |
| 1409 | if (std.mem.eql(u8, a_name, name)) { | | |
| 1410 | return self.children.get(i); | | |
| 1411 | } | | |
| 1412 | } | | |
| 1413 | return null; | | |
| 1414 | } | | |
| 1415 | const i = std.sort.binarySearch([]const u8, self.children.items.name, name, extras.compareFnSlice(u8)) orelse return null; | | |
| 1416 | return self.children.get(i); | | |
| 1417 | } | 1393 | } |
| 1418 | | 1394 | |
| 1419 | pub fn getBlob(self: Tree, name: string) ?Object { | 1395 | pub fn getBlob(self: Tree, name: string, hint: Object.Type) ?Object { |
| 1420 | const o = self.get(name) orelse return null; | 1396 | const o = self.get(name, hint) orelse return null; |
| 1421 | if (o.id != .blob) return null; | 1397 | if (o.id != .blob) return null; |
| 1422 | return o; | 1398 | return o; |
| 1423 | } | 1399 | } |
| 1424 | | 1400 | |
| 1425 | pub fn find(self: Tree, name: string) ?Object { | 1401 | pub fn find(self: Tree, name: string) ?Object { |
| 1426 | for (self.children.items.name, 0..) |item, i| { | 1402 | for (self.children, 0..) |item, i| { |
| 1427 | if (std.ascii.eqlIgnoreCase(item, name)) { | 1403 | if (std.ascii.eqlIgnoreCase(item.name, name)) { |
| 1428 | return self.children.get(i); | 1404 | return self.children[i]; |
| 1429 | } | 1405 | } |
| 1430 | } | 1406 | } |
| 1431 | return null; | 1407 | return null; |
| ... | @@ -1442,6 +1418,24 @@ pub const Tree = struct { | ... | @@ -1442,6 +1418,24 @@ pub const Tree = struct { |
| 1442 | id: AnyId, | 1418 | id: AnyId, |
| 1443 | name: [:0]const u8, | 1419 | name: [:0]const u8, |
| 1444 | | 1420 | |
| | 1421 | fn search(a: struct { []const u8, Type }, b: Object) std.math.Order { |
| | 1422 | if (a[0].ptr != b.name.ptr) { |
| | 1423 | const n = @min(a[0].len, b.name.len); |
| | 1424 | for (a[0][0..n], b.name[0..n]) |lhs_elem, rhs_elem| { |
| | 1425 | switch (std.math.order(lhs_elem, rhs_elem)) { |
| | 1426 | .eq => continue, |
| | 1427 | .lt => return .lt, |
| | 1428 | .gt => return .gt, |
| | 1429 | } |
| | 1430 | } |
| | 1431 | } |
| | 1432 | return switch (std.math.order(a[0].len, b.name.len)) { |
| | 1433 | .lt => if (a[1] == .directory) std.math.order('/', b.name[a[0].len]) else .lt, |
| | 1434 | .gt => if (b.mode.type == .directory) std.math.order(a[0][b.name.len], '/') else .gt, |
| | 1435 | .eq => .eq, |
| | 1436 | }; |
| | 1437 | } |
| | 1438 | |
| 1445 | pub const Mode = struct { | 1439 | pub const Mode = struct { |
| 1446 | type: Type, | 1440 | type: Type, |
| 1447 | perm_user: Perm, | 1441 | perm_user: Perm, |
| ... | @@ -1581,8 +1575,8 @@ pub const Tree = struct { | ... | @@ -1581,8 +1575,8 @@ pub const Tree = struct { |
| 1581 | var top = &self.stack.items[self.stack.items.len - 1]; | 1575 | var top = &self.stack.items[self.stack.items.len - 1]; |
| 1582 | var containing = top; | 1576 | var containing = top; |
| 1583 | var dirname_len = top.dirname_len; | 1577 | var dirname_len = top.dirname_len; |
| 1584 | if (top.idx < top.tree.children.len()) { | 1578 | if (top.idx < top.tree.children.len) { |
| 1585 | const base = top.tree.children.get(top.idx); | 1579 | const base = top.tree.children[top.idx]; |
| 1586 | top.idx += 1; | 1580 | top.idx += 1; |
| 1587 | self.name_buffer.shrinkRetainingCapacity(dirname_len); | 1581 | self.name_buffer.shrinkRetainingCapacity(dirname_len); |
| 1588 | if (self.name_buffer.items.len != 0) { | 1582 | if (self.name_buffer.items.len != 0) { |
| ... | @@ -1652,106 +1646,3 @@ pub const Ref = struct { | ... | @@ -1652,106 +1646,3 @@ pub const Ref = struct { |
| 1652 | oid: Id, | 1646 | oid: Id, |
| 1653 | label: string, | 1647 | label: string, |
| 1654 | }; | 1648 | }; |
| 1655 | | | |
| 1656 | /// Variant on std.MultiArrayList but using an allocation per-field rather than a single for the entire structure. | | |
| 1657 | fn MultiArrayList(T: type) type { | | |
| 1658 | return struct { | | |
| 1659 | items: Items, | | |
| 1660 | capacity: usize, | | |
| 1661 | | | |
| 1662 | comptime Elem: type = T, | | |
| 1663 | | | |
| 1664 | pub const empty: Self = .{ | | |
| 1665 | .items = .{}, | | |
| 1666 | .capacity = 0, | | |
| 1667 | }; | | |
| 1668 | | | |
| 1669 | pub const Self = @This(); | | |
| 1670 | | | |
| 1671 | const info = @typeInfo(T).@"struct"; | | |
| 1672 | | | |
| 1673 | pub const Items = blk: { | | |
| 1674 | var names: [info.fields.len][]const u8 = undefined; | | |
| 1675 | var types: [info.fields.len]type = undefined; | | |
| 1676 | var attrs: [info.fields.len]std.builtin.Type.StructField.Attributes = undefined; | | |
| 1677 | for (info.fields, 0..) |f, i| { | | |
| 1678 | const empty_slice: []f.type = &[_]f.type{}; | | |
| 1679 | names[i] = f.name; | | |
| 1680 | types[i] = []f.type; | | |
| 1681 | attrs[i] = .{ .default_value_ptr = @ptrCast(&empty_slice) }; | | |
| 1682 | } | | |
| 1683 | break :blk @Struct(.auto, null, &names, &types, &attrs); | | |
| 1684 | }; | | |
| 1685 | | | |
| 1686 | pub fn deinit(self: *const Self, gpa: std.mem.Allocator) void { | | |
| 1687 | inline for (info.fields) |f| { | | |
| 1688 | gpa.free(@field(self.items, f.name).ptr[0..self.capacity]); | | |
| 1689 | } | | |
| 1690 | } | | |
| 1691 | | | |
| 1692 | pub fn len(self: *const Self) usize { | | |
| 1693 | return @field(self.items, info.fields[0].name).len; | | |
| 1694 | } | | |
| 1695 | | | |
| 1696 | pub fn get(self: *const Self, index: usize) T { | | |
| 1697 | var result: T = undefined; | | |
| 1698 | inline for (info.fields) |f| { | | |
| 1699 | @field(result, f.name) = @field(self.items, f.name)[index]; | | |
| 1700 | } | | |
| 1701 | return result; | | |
| 1702 | } | | |
| 1703 | | | |
| 1704 | pub fn ensureUnusedCapacity(self: *Self, gpa: std.mem.Allocator, additional_count: usize) !void { | | |
| 1705 | return self.ensureTotalCapacity(gpa, self.len() + additional_count); | | |
| 1706 | } | | |
| 1707 | | | |
| 1708 | pub fn ensureTotalCapacity(self: *Self, gpa: std.mem.Allocator, new_capacity: usize) !void { | | |
| 1709 | if (self.capacity >= new_capacity) return; | | |
| 1710 | const actual_new_capacity = growCapacity(self.capacity, new_capacity); | | |
| 1711 | const previous_len = self.len(); | | |
| 1712 | var new_items: Items = .{}; | | |
| 1713 | inline for (info.fields, 0..) |f, i| { | | |
| 1714 | errdefer inline for (info.fields[0..i]) |g| gpa.free(@field(new_items, g.name)[0..actual_new_capacity]); | | |
| 1715 | @field(new_items, f.name) = try gpa.alloc(f.type, actual_new_capacity); | | |
| 1716 | @field(new_items, f.name).len = previous_len; | | |
| 1717 | } | | |
| 1718 | inline for (info.fields) |f| { | | |
| 1719 | @memcpy(@field(new_items, f.name)[0..previous_len], @field(self.items, f.name)[0..previous_len]); | | |
| 1720 | } | | |
| 1721 | inline for (info.fields) |f| { | | |
| 1722 | gpa.free(@field(self.items, f.name)[0..previous_len]); | | |
| 1723 | } | | |
| 1724 | self.items = new_items; | | |
| 1725 | self.capacity = actual_new_capacity; | | |
| 1726 | } | | |
| 1727 | | | |
| 1728 | /// Called when memory growth is necessary. Returns a capacity larger than minimum that grows super-linearly. | | |
| 1729 | fn growCapacity(current: usize, minimum: usize) usize { | | |
| 1730 | var new = current; | | |
| 1731 | while (true) { | | |
| 1732 | new +|= new / 2 + init_capacity; | | |
| 1733 | if (new >= minimum) return new; | | |
| 1734 | } | | |
| 1735 | } | | |
| 1736 | | | |
| 1737 | const init_capacity = init: { | | |
| 1738 | var max = 1; | | |
| 1739 | for (info.fields) |field| max = @as(comptime_int, @max(max, @sizeOf(field.type))); | | |
| 1740 | break :init @as(comptime_int, @max(1, std.atomic.cache_line / max)); | | |
| 1741 | }; | | |
| 1742 | | | |
| 1743 | pub fn append(self: *Self, gpa: std.mem.Allocator, elem: T) !void { | | |
| 1744 | try self.ensureUnusedCapacity(gpa, 1); | | |
| 1745 | self.appendAssumeCapacity(elem); | | |
| 1746 | } | | |
| 1747 | | | |
| 1748 | pub fn appendAssumeCapacity(self: *Self, elem: T) void { | | |
| 1749 | const plen = self.len(); | | |
| 1750 | std.debug.assert(plen < self.capacity); | | |
| 1751 | inline for (info.fields) |f| { | | |
| 1752 | @field(self.items, f.name).len += 1; | | |
| 1753 | @field(self.items, f.name)[plen] = @field(elem, f.name); | | |
| 1754 | } | | |
| 1755 | } | | |
| 1756 | }; | | |
| 1757 | } | | |