authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-23 11:21:31+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-24 20:42:40+01:00
log96c9ff1c93532e7c2764449e0cd6f39d4dce2285
treed83924e2e67732ed0af4574299b4d7ca57534221
parentd9078dae3b6266767d66d5f2100f321b200cbd6b
signaturelock-open Commit is signed but in an unrecognized format.

link.MappedFile: rework node operations

This commit is a refactor of the public `MappedFile` API, and a near-total rewrite of its implementation (or at least, the implementation of the node moving and resizing logic). Nodes can be "header", "footer", or "floating" nodes, which dictates how they are positioned relative to their parent; "header" nodes (similar to the old "fixed" nodes) are placed at the start of the parent node, "footer" nodes are placed at the end of the parent node, and "floating" nodes may appear anywhere in the parent. There are separate functions for adding each of these types of node. Notably, when adding a floating node, the API no longer permits the caller to specify *where* these nodes are placed, because floating nodes give the implementation the freedom to make this choice for itself. Another important property is that for header and footer nodes, only their size is aligned to the node's alignment. Their offsets are not necessarily aligned, because they are required to be placed at the start/end of the parent with no additional padding: this constraint already dictates their offset. The `Node.Index.resize` function is replaced with two functions. The first, `ensureMinimumSize`, is permitted for any node, and guarantees only that the node's size is *at least* a particular value, applying exponential growth (`growth_factor`) if necessary---it is essentially equivalent to the helper function `Elf2.ensureNodeSize` which the `Elf2` linker was already making frequent use of. The other, `resizeLeaf`, sets the size of a node *exactly*, but may only be used on leaf nodes. The logic for actually placing nodes in the file, as well as becoming slightly more involved due to handling the new semantics of headers and footers, has also been made more efficient. In particular, the amount of unused "padding" space is, broadly speaking, lower with this implementation than it previously was: empirically, binaries emitted by `Elf2` are about half the size as they were before (in terms of `stat` size, not size on disk), although binaries emitted by `Coff` are around the same size as before (perhaps slightly smaller). I have spoken with Casey about some potential enhancements to `Coff` which, as well as making it more performant, could also slightly improve its file sizes. At least one alignment-related bug, wherein the Linux-specific `FALLOCATE_FL_INSERT_RANGE` path did not respect neighbors' alignment requirements, has been fixed. In order to verify correctness of this new implementation (particularly since neither linker uses footer nodes yet), I wrote a small fuzz test which performs a random sequence of node operations (add, resize, realign), while writing content into (some) leaf nodes. After all operations are complete, it validates that the node structure is valid (headers are tightly packed against the start of the parent node, no two sibling nodes overlap, etc), and ensures that all leaves contain the expected content. Even with our alpha-quality fuzzer implementation, this fuzz test was surprisingly helpful in identifying bugs during development. I suspect `MappedFile` is unusually easy to fuzz, because most code paths can be hit with relatively few nodes, so even purely random fuzzing (as opposed to coverage-guided) is likely to discover any bugs fairly quickly. This fuzz test is in `src/link/MappedFile.zig`, and is referenced by the standard compiler unit tests, so `MappedFile` can be fuzzed at any time by running `zig build test-unit --fuzz`.

4 files changed, 1977 insertions(+), 1114 deletions(-)

src/link/Coff.zig+125-165
......@@ -533,10 +533,10 @@ pub const Member = struct {
533533 errdefer _ = coff.export_table.entries.pop();
534534
535535 _, const old_size = Node.known.longnames_member.location(&coff.mf).resolve(&coff.mf);
536 const new_size = old_size + name.len + 1;
536 const new_size = Alignment.@"4".forward(old_size + name.len + 1);
537537 assert(new_size < comptime try std.math.powi(u64, 10, max_name_len - 1));
538538
539 try Node.known.longnames_member.resize(&coff.mf, gpa, new_size);
539 try Node.known.longnames_member.resizeLeaf(&coff.mf, gpa, new_size);
540540 const name_table_slice = Node.known.longnames_member.slice(&coff.mf);
541541 const name_slice = name_table_slice[@intCast(old_size)..][0 .. name.len + 1];
542542 @memcpy(name_slice[0..name.len], name);
......@@ -1840,34 +1840,20 @@ fn initHeaders(
18401840 coff.nodes.appendAssumeCapacity(.file);
18411841
18421842 const header_ni = Node.known.header;
1843 assert(header_ni == try coff.mf.addOnlyChildNode(gpa, Node.known.file, .{
1843 assert(header_ni == try Node.known.file.addOnlyHeaderChild(&coff.mf, gpa, .{
18441844 .alignment = coff.mf.flags.block_size,
1845 .fixed = true,
18461845 }));
18471846 coff.nodes.appendAssumeCapacity(.header);
18481847
1849 const signature_ni = Node.known.signature;
1850 assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image or !is_archive) header_ni else Node.known.file, .{
1851 .size = if (is_image)
1852 msdos_stub.len + std.coff.pe_signature.len
1853 else if (is_archive)
1854 std.coff.archive_signature.len
1855 else
1856 0,
1857 .alignment = .@"4",
1858 .fixed = true,
1859 }));
1860 coff.nodes.appendAssumeCapacity(.signature);
1861
1862 const signature_slice = signature_ni.slice(&coff.mf);
1863 if (is_image) {
1864 @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub);
1865 @memcpy(signature_slice[signature_slice.len - std.coff.pe_signature.len ..], std.coff.pe_signature);
1866 } else if (is_archive) {
1848 const coff_parent_ni: MappedFile.Node.Index = if (is_archive) parent: {
1849 assert(try Node.known.file.addHeaderChildAfter(&coff.mf, gpa, .wrap(header_ni), .{
1850 .size = std.coff.archive_signature.len,
1851 .alignment = .@"4",
1852 }) == Node.known.signature);
1853 coff.nodes.appendAssumeCapacity(.signature);
1854 const signature_slice = Node.known.signature.slice(&coff.mf);
18671855 @memcpy(signature_slice, std.coff.archive_signature);
1868 }
18691856
1870 const opt_coff_parent_ni = if (is_archive) parent: {
18711857 const initial_member_count = Member.Index.known_count + @intFromBool(comp.zcu != null);
18721858 try coff.members.ensureTotalCapacity(gpa, initial_member_count);
18731859
......@@ -1893,46 +1879,54 @@ fn initHeaders(
18931879 const zcu_member = zcu_mi.get(coff);
18941880 try zcu_member.initHeader(coff, zcu.main_mod.fully_qualified_name, timestamp);
18951881
1882 assert(try zcu_member.content_ni.addOnlyHeaderChild(&coff.mf, gpa, .{
1883 .size = @sizeOf(std.coff.Header),
1884 .alignment = .@"4",
1885 }) == Node.known.coff_header);
1886 coff.nodes.appendAssumeCapacity(.coff_header);
1887
18961888 break :parent zcu_member.content_ni;
18971889 }
18981890
1891 // If we're not generating any code, no more known nodes are used
1892
18991893 // These placeholder nodes are placed before the first member - if there are
19001894 // no other members then the last linker member (longnames) needs to expand
19011895 // to fill the padding at the end of the file.
1902 assert(Node.known.zcu_member_header == try coff.mf.addNodeAfter(gpa, Node.known.header, .{}));
1903 assert(Node.known.zcu_member == try coff.mf.addNodeAfter(gpa, Node.known.header, .{}));
1904 coff.nodes.appendAssumeCapacity(.placeholder);
1905 coff.nodes.appendAssumeCapacity(.placeholder);
1896 while (coff.nodes.len < Node.known_count) {
1897 _ = try Node.known.header.addHeaderChildAfter(&coff.mf, gpa, .none, .{});
1898 coff.nodes.appendAssumeCapacity(.placeholder);
1899 }
19061900
1907 break :parent null;
1901 return;
19081902 } else parent: {
1903 assert(try header_ni.addOnlyHeaderChild(&coff.mf, gpa, .{
1904 .size = if (is_image) msdos_stub.len + std.coff.pe_signature.len else 0,
1905 .alignment = .@"4",
1906 }) == Node.known.signature);
1907 coff.nodes.appendAssumeCapacity(.signature);
1908 if (is_image) {
1909 const signature_slice = Node.known.signature.slice(&coff.mf);
1910 @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub);
1911 @memcpy(signature_slice[signature_slice.len - std.coff.pe_signature.len ..], std.coff.pe_signature);
1912 }
1913
19091914 // TODO: Not ideal to have this many placeholder nodes - use two distinct `Node.known` types?
19101915 while (true) {
1911 const placeholder_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{});
1916 const placeholder_ni = try Node.known.file.addHeaderChildAfter(&coff.mf, gpa, .none, .{});
19121917 coff.nodes.appendAssumeCapacity(.placeholder);
19131918 if (placeholder_ni == Node.known.zcu_member) break;
19141919 }
19151920
1916 break :parent Node.known.header;
1917 };
1918
1919 const coff_parent_ni = opt_coff_parent_ni orelse {
1920 // If we're not generating any code, no more known nodes are used
1921 while (coff.nodes.len < Node.known_count) {
1922 _ = try coff.mf.addNodeAfter(gpa, Node.known.header, .{});
1923 coff.nodes.appendAssumeCapacity(.placeholder);
1924 }
1921 assert(try header_ni.addHeaderChildAfter(&coff.mf, gpa, .wrap(Node.known.signature), .{
1922 .size = @sizeOf(std.coff.Header),
1923 .alignment = .@"4",
1924 }) == Node.known.coff_header);
1925 coff.nodes.appendAssumeCapacity(.coff_header);
19251926
1926 return;
1927 break :parent header_ni;
19271928 };
19281929
1929 const coff_header_ni = Node.known.coff_header;
1930 assert(coff_header_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
1931 .size = @sizeOf(std.coff.Header),
1932 .alignment = .@"4",
1933 .fixed = true,
1934 }));
1935 coff.nodes.appendAssumeCapacity(.coff_header);
19361930 {
19371931 const coff_header = coff.headerPtr();
19381932 coff_header.* = .{
......@@ -1955,10 +1949,9 @@ fn initHeaders(
19551949 }
19561950
19571951 const optional_header_ni = Node.known.optional_header;
1958 assert(optional_header_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
1952 assert(optional_header_ni == try coff_parent_ni.addHeaderChildAfter(&coff.mf, gpa, .wrap(Node.known.coff_header), .{
19591953 .size = optional_header_size,
19601954 .alignment = .@"4",
1961 .fixed = true,
19621955 }));
19631956 coff.nodes.appendAssumeCapacity(.optional_header);
19641957 if (is_image) {
......@@ -2067,10 +2060,9 @@ fn initHeaders(
20672060 }
20682061
20692062 const data_directories_ni = Node.known.data_directories;
2070 assert(data_directories_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
2063 assert(data_directories_ni == try coff_parent_ni.addHeaderChildAfter(&coff.mf, gpa, .wrap(optional_header_ni), .{
20712064 .size = data_directories_size,
20722065 .alignment = .@"4",
2073 .fixed = true,
20742066 }));
20752067 coff.nodes.appendAssumeCapacity(.data_directories);
20762068 if (is_image) {
......@@ -2083,9 +2075,8 @@ fn initHeaders(
20832075 }
20842076
20852077 const section_table_ni = Node.known.section_table;
2086 assert(section_table_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
2078 assert(section_table_ni == try coff_parent_ni.addHeaderChildAfter(&coff.mf, gpa, .wrap(data_directories_ni), .{
20872079 .alignment = .@"4",
2088 .fixed = true,
20892080 }));
20902081 coff.nodes.appendAssumeCapacity(.section_table);
20912082
......@@ -2093,16 +2084,14 @@ fn initHeaders(
20932084
20942085 if (!is_image) {
20952086 // TODO: These two nodes could be inside one movable node?
2096 coff.symbol_table.ni = try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
2087 coff.symbol_table.ni = try coff_parent_ni.addHeaderChildAfter(&coff.mf, gpa, .wrap(section_table_ni), .{
20972088 .alignment = .@"2",
2098 .fixed = true,
20992089 .moved = true,
21002090 });
21012091 coff.nodes.appendAssumeCapacity(.symbol_table);
21022092
2103 coff.symbol_table.strings_ni = try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
2093 coff.symbol_table.strings_ni = try coff_parent_ni.addHeaderChildAfter(&coff.mf, gpa, .wrap(coff.symbol_table.ni), .{
21042094 .size = @sizeOf(u32),
2105 .fixed = true,
21062095 .resized = true,
21072096 });
21082097 coff.nodes.appendAssumeCapacity(.string_table);
......@@ -2149,15 +2138,14 @@ fn initHeaders(
21492138 }
21502139
21512140 // TODO: Lazily initialize this instead, avoid the extra logic for this in flushMoved / flushResized
2152 coff.import_table.ni = try coff.mf.addLastChildNode(
2153 gpa,
2154 (try coff.objectSectionMapIndex(
2155 .@".idata",
2156 coff.mf.flags.block_size,
2157 .{ .read = true, .initialized = true },
2158 )).symbol(coff).node(coff),
2159 .{ .alignment = .@"4" },
2160 );
2141 const import_table_parent_ni = (try coff.objectSectionMapIndex(
2142 .@".idata",
2143 coff.mf.flags.block_size,
2144 .{ .read = true, .initialized = true },
2145 )).symbol(coff).node(coff);
2146 coff.import_table.ni = try import_table_parent_ni.addFloatingChild(&coff.mf, gpa, .{
2147 .alignment = .@"4",
2148 });
21612149 coff.nodes.appendAssumeCapacity(.import_directory_table);
21622150
21632151 coff.export_table.ni = (try coff.pseudoSectionMapIndex(
......@@ -2166,15 +2154,10 @@ fn initHeaders(
21662154 .{ .read = true, .initialized = true },
21672155 )).symbol(coff).node(coff);
21682156
2169 coff.export_table.export_directory_table_ni = try coff.mf.addLastChildNode(
2170 gpa,
2171 coff.export_table.ni,
2172 .{
2173 .size = @sizeOf(std.coff.ExportDirectoryTable) + file_name.len + 1,
2174 .moved = true,
2175 .fixed = true,
2176 },
2177 );
2157 coff.export_table.export_directory_table_ni = try coff.export_table.ni.addHeaderChildAfter(&coff.mf, gpa, coff.export_table.ni.last(&coff.mf), .{
2158 .size = @sizeOf(std.coff.ExportDirectoryTable) + file_name.len + 1,
2159 .moved = true,
2160 });
21782161 coff.nodes.appendAssumeCapacity(.export_directory_table);
21792162
21802163 const name_index = @sizeOf(std.coff.ExportDirectoryTable);
......@@ -2182,7 +2165,7 @@ fn initHeaders(
21822165 @memcpy(table_slice[name_index..][0..file_name.len], file_name[0..file_name.len]);
21832166 @memset(table_slice[name_index + file_name.len ..], 0);
21842167
2185 const export_address_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
2168 const export_address_table_ni = try coff.export_table.ni.addFloatingChild(&coff.mf, gpa, .{
21862169 .alignment = .of(std.coff.ExportAddressTableEntry),
21872170 .moved = true,
21882171 });
......@@ -2198,19 +2181,19 @@ fn initHeaders(
21982181 export_address_table_sym.section_number =
21992182 coff.getNode(coff.export_table.ni).pseudo_section.symbol(coff).get(coff).section_number;
22002183
2201 coff.export_table.name_pointer_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
2184 coff.export_table.name_pointer_table_ni = try coff.export_table.ni.addFloatingChild(&coff.mf, gpa, .{
22022185 .alignment = .of(std.coff.ExportNamePointerTableEntry),
22032186 .moved = true,
22042187 });
22052188 coff.nodes.appendAssumeCapacity(.export_name_pointer_table);
22062189
2207 coff.export_table.ordinal_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
2190 coff.export_table.ordinal_table_ni = try coff.export_table.ni.addFloatingChild(&coff.mf, gpa, .{
22082191 .alignment = .of(std.coff.ExportOrdinalTableEntry),
22092192 .moved = true,
22102193 });
22112194 coff.nodes.appendAssumeCapacity(.export_ordinal_table);
22122195
2213 coff.export_table.name_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
2196 coff.export_table.name_table_ni = try coff.export_table.ni.addFloatingChild(&coff.mf, gpa, .{
22142197 .alignment = .of(u8),
22152198 .moved = true,
22162199 });
......@@ -2303,9 +2286,8 @@ pub fn initBuiltins(coff: *Coff) !void {
23032286 const list_len_si = try coff.globalSymbol(.{ .name = list.global, .type = .data });
23042287 const list_len_sym = list_len_si.get(coff);
23052288 list_len_sym.setExtra(.{ .size = addr_info.size });
2306 list_len_sym.ni = .wrap(try coff.mf.addFirstChildNode(gpa, start_sym.ni.unwrap().?, .{
2289 list_len_sym.ni = .wrap(try start_sym.ni.unwrap().?.addHeaderChildAfter(&coff.mf, gpa, .none, .{
23072290 .size = addr_info.size,
2308 .fixed = true,
23092291 }));
23102292 coff.nodes.appendAssumeCapacity(.{ .builtin = list_len_si });
23112293 list_len_sym.section_number = start_sym.section_number;
......@@ -2325,9 +2307,8 @@ pub fn initBuiltins(coff: *Coff) !void {
23252307 const list_end_si = coff.addSymbolAssumeCapacity();
23262308 const list_end_sym = list_end_si.get(coff);
23272309 list_end_sym.setExtra(.{ .size = addr_info.size });
2328 list_end_sym.ni = .wrap(try coff.mf.addFirstChildNode(gpa, end_sym.ni.unwrap().?, .{
2310 list_end_sym.ni = .wrap(try end_sym.ni.unwrap().?.addHeaderChildAfter(&coff.mf, gpa, .none, .{
23292311 .size = addr_info.size,
2330 .fixed = true,
23312312 }));
23322313 coff.nodes.appendAssumeCapacity(.{ .builtin = list_end_si });
23332314 list_end_sym.section_number = start_sym.section_number;
......@@ -2742,7 +2723,7 @@ fn getOrPutSymbolName(coff: *Coff, name: []const u8, opt_string: ?String) !Symbo
27422723 const string_index = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf)[1];
27432724 string_gop.value_ptr.* = @fromBackingInt(@intCast(string_index));
27442725
2745 try coff.symbol_table.strings_ni.resize(&coff.mf, gpa, string_index + name.len + 1);
2726 try coff.symbol_table.strings_ni.resizeLeaf(&coff.mf, gpa, string_index + name.len + 1);
27462727 const slice = coff.symbol_table.strings_ni.slice(&coff.mf);
27472728 @memcpy(slice[@intCast(string_index)..][0..name.len], name);
27482729 slice[@intCast(string_index + name.len)] = 0;
......@@ -2967,24 +2948,22 @@ fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind,
29672948 const comp = coff.base.comp;
29682949 const gpa = comp.gpa;
29692950
2970 // TODO: These two nodes could to be inside a movable node if kind == .coff|.import
2971 const header_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{
2951 const header_ni = try Node.known.file.addHeaderChildAfter(&coff.mf, gpa, Node.known.file.last(&coff.mf), .{
29722952 .size = @sizeOf(std.coff.ArchiveMemberHeader),
29732953 .alignment = .@"2",
2974 .fixed = true,
29752954 .moved = true,
29762955 });
29772956
2978 const content_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{
2979 // The actual alignment required by the spec is 2, but to allow aligned access to
2980 // the various COFF data structures in-place during linking we overalign
2981 .alignment = switch (kind) {
2982 .coff => .@"4",
2983 else => .@"2",
2984 },
2985 .size = size,
2957 // The actual alignment required by the spec is 2, but to allow aligned access to
2958 // the various COFF data structures in-place during linking we overalign
2959 const content_align: Alignment = switch (kind) {
2960 .first_linker, .second_linker, .longnames, .coff => .@"4",
2961 else => .@"2",
2962 };
2963 const content_ni = try Node.known.file.addHeaderChildAfter(&coff.mf, gpa, .wrap(header_ni), .{
2964 .alignment = content_align,
2965 .size = content_align.forward(size),
29862966 .resized = size > 0,
2987 .fixed = true,
29882967 });
29892968
29902969 const mi: Member.Index = @fromBackingInt(@intCast(coff.members.items.len));
......@@ -3010,7 +2989,7 @@ fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind,
30102989 const old_size = Node.known.second_linker_member.location(&coff.mf).resolve(&coff.mf)[1];
30112990 const old_header_size = new_num_members * @sizeOf(u32);
30122991 const trailing_size: usize = @intCast(old_size - old_header_size);
3013 try Node.known.second_linker_member.resize(&coff.mf, gpa, old_size + @sizeOf(u32));
2992 try Node.known.second_linker_member.resizeLeaf(&coff.mf, gpa, old_size + @sizeOf(u32));
30142993
30152994 const slice = Node.known.second_linker_member.slice(&coff.mf);
30162995 @memmove(
......@@ -3048,7 +3027,7 @@ fn appendMemberSymbolString(
30483027 name: []const u8,
30493028 offset: u64,
30503029) !void {
3051 try strings_ni.resize(&coff.mf, coff.base.comp.gpa, new_size);
3030 try strings_ni.resizeLeaf(&coff.mf, coff.base.comp.gpa, new_size);
30523031 const name_slice = strings_ni.slice(&coff.mf)[offset..][0 .. name.len + 1];
30533032 @memcpy(name_slice[0..name.len], name);
30543033 name_slice[name.len] = 0;
......@@ -3081,7 +3060,7 @@ fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void {
30813060 {
30823061 const old_header_size: usize = @intCast(@sizeOf(u32) + @backingInt(mfli) * @sizeOf(u32));
30833062 const new_header_size: usize = @intCast(old_header_size + @sizeOf(u32));
3084 try Node.known.first_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size);
3063 try Node.known.first_linker_member.resizeLeaf(&coff.mf, gpa, Alignment.@"4".forward(new_header_size + new_string_table_size));
30853064
30863065 const slice = Node.known.first_linker_member.slice(&coff.mf);
30873066 @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]);
......@@ -3095,7 +3074,7 @@ fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void {
30953074 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
30963075 const old_header_size = 2 * @sizeOf(u32) + num_members * @sizeOf(u32) + @backingInt(mfli) * @sizeOf(u16);
30973076 const new_header_size = old_header_size + @sizeOf(u16);
3098 try Node.known.second_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size);
3077 try Node.known.second_linker_member.resizeLeaf(&coff.mf, gpa, Alignment.@"4".forward(new_header_size + new_string_table_size));
30993078
31003079 const old_needs_sort = coff.pending_members.get(Member.Index.second) != null;
31013080 const needs_sort = old_needs_sort or (if (coff.lib_string_table.items.len > 0)
......@@ -3202,7 +3181,7 @@ fn flushSymbolTableEntry(coff: *Coff, index: u32, pt: Zcu.PerThread) !void {
32023181 const new_num_symbols = old_num_symbols + 1 + num_aux_symbols;
32033182 coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols);
32043183
3205 try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf());
3184 try coff.symbol_table.ni.resizeLeaf(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf());
32063185
32073186 sti.* = .wrap(old_num_symbols);
32083187 si.flushSymbolTableIndex(coff);
......@@ -3365,13 +3344,13 @@ fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !S
33653344 const section_index = coff.targetLoad(&coff_header.number_of_sections);
33663345 const section_table_len = section_index + 1;
33673346 coff.targetStore(&coff_header.number_of_sections, section_table_len);
3368 try Node.known.section_table.resize(
3347 try Node.known.section_table.resizeLeaf(
33693348 &coff.mf,
33703349 gpa,
33713350 @sizeOf(std.coff.SectionHeader) * section_table_len,
33723351 );
33733352
3374 const ni = try coff.mf.addLastChildNode(gpa, coff.sectionParent(), .{
3353 const ni = try coff.sectionParent().addFloatingChild(&coff.mf, gpa, .{
33753354 .alignment = coff.mf.flags.block_size,
33763355 .moved = true,
33773356 .bubbles_moved = false,
......@@ -3507,7 +3486,7 @@ fn pseudoSectionMapIndex(
35073486
35083487 try coff.nodes.ensureUnusedCapacity(gpa, 1);
35093488 try coff.symbols.ensureUnusedCapacity(gpa, 1);
3510 const ni = try coff.mf.addLastChildNode(gpa, parent.node(coff), .{ .alignment = alignment });
3489 const ni = try parent.node(coff).addFloatingChild(&coff.mf, gpa, .{ .alignment = alignment });
35113490 const si = coff.addSymbolAssumeCapacity();
35123491 pseudo_section_gop.value_ptr.* = si;
35133492 const sym = si.get(coff);
......@@ -3577,12 +3556,8 @@ fn objectSectionMapIndex(
35773556 .eq => unreachable,
35783557 .gt => prev_oni = .wrap(next_ni),
35793558 };
3580 const ni = if (prev_oni.unwrap()) |prev_ni| try coff.mf.addNodeAfter(gpa, prev_ni, .{
3581 .alignment = alignment,
3582 .fixed = true,
3583 }) else try coff.mf.addFirstChildNode(gpa, parent_ni, .{
3559 const ni = try parent_ni.addHeaderChildAfter(&coff.mf, gpa, prev_oni, .{
35843560 .alignment = alignment,
3585 .fixed = true,
35863561 });
35873562 const si = coff.addSymbolAssumeCapacity();
35883563 object_section_gop.value_ptr.* = si;
......@@ -3600,13 +3575,13 @@ fn objectSectionMapIndex(
36003575 const parent_alignment = parent_ni.alignment(&coff.mf);
36013576 if (alignment.compare(.gt, parent_alignment)) {
36023577 log.debug("realignParent({s}, {d}) {d}->{d}", .{ name.toSlice(coff), parent_ni, parent_alignment, alignment });
3603 try parent_ni.realign(&coff.mf, gpa, alignment, .{ .try_backwards = true });
3578 try parent_ni.realign(&coff.mf, gpa, alignment);
36043579 }
36053580
36063581 const old_alignment = sym.ni.unwrap().?.alignment(&coff.mf);
36073582 if (alignment.compare(.gt, old_alignment)) {
36083583 log.debug("realignObject({s}) {d}->{d}", .{ name.toSlice(coff), old_alignment, alignment });
3609 try sym.ni.unwrap().?.realign(&coff.mf, gpa, alignment, .{ .try_backwards = true });
3584 try sym.ni.unwrap().?.realign(&coff.mf, gpa, alignment);
36103585 }
36113586
36123587 try coff.verifyParentSectionAttributes(
......@@ -3763,18 +3738,14 @@ fn addRelocAssumeCapacity(
37633738 coff.targetStore(&aux_ptr.number_of_relocations, new_num_relocations);
37643739
37653740 if (section.relocation_table_ni.unwrap()) |relocation_table_ni| {
3766 try relocation_table_ni.resize(&coff.mf, gpa, new_size);
3741 try relocation_table_ni.resizeLeaf(&coff.mf, gpa, new_size);
37673742 } else {
3768 section.relocation_table_ni = .wrap(try coff.mf.addLastChildNode(
3769 gpa,
3770 coff.sectionParent(),
3771 .{
3772 .size = new_size,
3773 .alignment = .@"2",
3774 .moved = true,
3775 .resized = true,
3776 },
3777 ));
3743 section.relocation_table_ni = .wrap(try coff.sectionParent().addFloatingChild(&coff.mf, gpa, .{
3744 .size = new_size,
3745 .alignment = .@"2",
3746 .moved = true,
3747 .resized = true,
3748 }));
37783749 coff.nodes.appendAssumeCapacity(.{ .relocation_table = loc_sn });
37793750 }
37803751
......@@ -4677,9 +4648,10 @@ fn loadObject(
46774648 for (sections) |*section| {
46784649 if (section.parent_si == .null) continue;
46794650
4680 const ni = try coff.mf.addLastChildNode(gpa, section.parent_si.node(coff), .{
4681 .size = section.header.size_of_raw_data,
4682 .alignment = .fromByteUnits(section.header.flags.ALIGN.toByteUnits() orelse 1),
4651 const alignment: Alignment = .fromByteUnits(section.header.flags.ALIGN.toByteUnits() orelse 1);
4652 const ni = try section.parent_si.node(coff).addFloatingChild(&coff.mf, gpa, .{
4653 .size = alignment.forward(section.header.size_of_raw_data),
4654 .alignment = alignment,
46834655 .moved = true,
46844656 });
46854657 coff.nodes.appendAssumeCapacity(.{ .input_section = @fromBackingInt(@intCast(coff.input_sections.items.len)) });
......@@ -5471,7 +5443,7 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde
54715443 const sec_si = try coff.navSection(zcu, nav.resolved.?);
54725444 try coff.nodes.ensureUnusedCapacity(gpa, 1);
54735445 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
5474 const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
5446 const ni = try sec_si.node(coff).addFloatingChild(&coff.mf, gpa, .{
54755447 .alignment = .fromIp(zcu.navAlignment(nav_index)),
54765448 .moved = true,
54775449 });
......@@ -5510,21 +5482,7 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde
55105482 }
55115483
55125484 if (nav.resolved.?.@"linksection".unwrap()) |_| {
5513 try ni.resize(&coff.mf, gpa, si.get(coff).extra.size);
5514 var parent_ni = ni;
5515 while (true) {
5516 parent_ni = parent_ni.parent(&coff.mf).unwrap().?;
5517 switch (coff.getNode(parent_ni)) {
5518 else => unreachable,
5519 .image_section, .pseudo_section => break,
5520 .object_section => {
5521 var child_it = parent_ni.reverseChildren(&coff.mf);
5522 const last_offset, const last_size =
5523 child_it.next().?.location(&coff.mf).resolve(&coff.mf);
5524 try parent_ni.resize(&coff.mf, gpa, last_offset + last_size);
5525 },
5526 }
5527 }
5485 try ni.resizeLeaf(&coff.mf, gpa, si.get(coff).extra.size);
55285486 }
55295487}
55305488
......@@ -5596,7 +5554,7 @@ fn updateFuncInner(
55965554 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
55975555 const mod = zcu.navFileScope(func.owner_nav).mod.?;
55985556 const target = &mod.resolved_target.result;
5599 const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
5557 const ni = try sec_si.node(coff).addFloatingChild(&coff.mf, gpa, .{
56005558 .alignment = switch (nav.resolved.?.@"align") {
56015559 .none => switch (mod.optimize_mode) {
56025560 .debug,
......@@ -5900,15 +5858,17 @@ pub fn flush(
59005858 coff.symbol_table.pending_shrink = false;
59015859
59025860 const number_of_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols);
5903 coff.symbol_table.ni.shrink(
5861 coff.symbol_table.ni.resizeLeaf(
59045862 &coff.mf,
59055863 comp.gpa,
59065864 number_of_symbols * std.coff.Symbol.sizeOf(),
5907 true,
5908 ) catch |err| return comp.link_diags.fail(
5909 "linker failed to compact symbol table: {t}",
5910 .{err},
5911 );
5865 ) catch |err| switch (err) {
5866 else => |e| return e,
5867 error.MappedFileIo => return comp.link_diags.fail(
5868 "linker failed to compact symbol table: {t}",
5869 .{coff.mf.io_err.?},
5870 ),
5871 };
59125872 }
59135873 while (try coff.idle(tid)) {}
59145874
......@@ -6211,7 +6171,7 @@ fn flushUav(
62116171 try coff.nodes.ensureUnusedCapacity(gpa, 1);
62126172 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
62136173 const sym = si.get(coff);
6214 const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
6174 const ni = try sec_si.node(coff).addFloatingChild(&coff.mf, gpa, .{
62156175 .alignment = .fromIp(uav_align),
62166176 .moved = true,
62176177 });
......@@ -6503,7 +6463,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
65036463 const import_hint_name_align: Alignment = .@"2";
65046464 if (!gop.found_existing) {
65056465 errdefer _ = coff.import_table.entries.pop();
6506 try coff.import_table.ni.resize(
6466 try coff.import_table.ni.resizeLeaf(
65076467 &coff.mf,
65086468 gpa,
65096469 @sizeOf(std.coff.ImportDirectoryEntry) * (gop.index + 2),
......@@ -6511,12 +6471,12 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
65116471 const import_hint_name_table_len =
65126472 import_hint_name_align.forward(lib_name.len + ".dll".len + 1);
65136473 const idata_section_ni = coff.import_table.ni.parent(&coff.mf).unwrap().?;
6514 const import_lookup_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
6474 const import_lookup_table_ni = try idata_section_ni.addFloatingChild(&coff.mf, gpa, .{
65156475 .size = addr_info.size * 2,
65166476 .alignment = addr_info.alignment,
65176477 .moved = true,
65186478 });
6519 const import_address_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
6479 const import_address_table_ni = try idata_section_ni.addFloatingChild(&coff.mf, gpa, .{
65206480 .size = addr_info.size * 2,
65216481 .alignment = addr_info.alignment,
65226482 .moved = true,
......@@ -6530,7 +6490,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
65306490 import_address_table_sym.section_number =
65316491 coff.getNode(idata_section_ni).object_section.symbol(coff).get(coff).section_number;
65326492 }
6533 const import_hint_name_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
6493 const import_hint_name_table_ni = try idata_section_ni.addFloatingChild(&coff.mf, gpa, .{
65346494 .size = import_hint_name_table_len,
65356495 .alignment = import_hint_name_align,
65366496 .moved = true,
......@@ -6586,9 +6546,9 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
65866546 gop.value_ptr.len = import_symbol_index + 1;
65876547 const new_symbol_table_size = addr_info.size * (import_symbol_index + 2);
65886548
6589 try gop.value_ptr.import_lookup_table_ni.resize(&coff.mf, gpa, new_symbol_table_size);
6549 try gop.value_ptr.import_lookup_table_ni.resizeLeaf(&coff.mf, gpa, new_symbol_table_size);
65906550 const import_address_table_ni = gop.value_ptr.import_address_table_si.node(coff);
6591 try import_address_table_ni.resize(&coff.mf, gpa, new_symbol_table_size);
6551 try import_address_table_ni.resizeLeaf(&coff.mf, gpa, new_symbol_table_size);
65926552
65936553 const opt_imp_name = import.name.toSlice(coff);
65946554 const opt_import_hint_name_index = if (opt_imp_name) |imp_name| blk: {
......@@ -6596,7 +6556,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
65966556 gop.value_ptr.hint_name_len = @intCast(
65976557 import_hint_name_align.forward(import_hint_name_index + 2 + imp_name.len + 1),
65986558 );
6599 try gop.value_ptr.import_hint_name_table_ni.resize(&coff.mf, gpa, gop.value_ptr.hint_name_len);
6559 try gop.value_ptr.import_hint_name_table_ni.resizeLeaf(&coff.mf, gpa, gop.value_ptr.hint_name_len);
66006560 break :blk import_hint_name_index;
66016561 } else null;
66026562
......@@ -6671,9 +6631,9 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
66716631 else => |tag| @panic(@tagName(tag)),
66726632 .AMD64 => {
66736633 const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 };
6674 const ni = try coff.mf.addLastChildNode(gpa, parent_sym.ni.unwrap().?, .{
6634 const ni = try parent_sym.ni.unwrap().?.addFloatingChild(&coff.mf, gpa, .{
66756635 .alignment = alignment,
6676 .size = init.len,
6636 .size = alignment.forward(init.len),
66776637 });
66786638 @memcpy(ni.slice(&coff.mf)[0..init.len], &init);
66796639 sym.ni = .wrap(ni);
......@@ -6824,7 +6784,7 @@ fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void {
68246784 .code => .text,
68256785 .const_data => .rdata,
68266786 };
6827 const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{ .moved = true });
6787 const ni = try sec_si.node(coff).addFloatingChild(&coff.mf, gpa, .{ .moved = true });
68286788 coff.nodes.appendAssumeCapacity(switch (lazy.kind) {
68296789 .code => .{ .lazy_code = @fromBackingInt(@intCast(lmr.index)) },
68306790 .const_data => .{ .lazy_const_data = @fromBackingInt(@intCast(lmr.index)) },
......@@ -7480,7 +7440,7 @@ fn updateExportInner(
74807440 if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index")))
74817441 return coff.base.comp.link_diags.fail("exports name table limit reached", .{});
74827442
7483 try coff.export_table.name_table_ni.resize(&coff.mf, gpa, new_name_table_size);
7443 try coff.export_table.name_table_ni.resizeLeaf(&coff.mf, gpa, new_name_table_size);
74847444
74857445 const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
74867446 @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]);
......@@ -7503,19 +7463,19 @@ fn updateExportInner(
75037463
75047464 // TODO: These should all be resized ahead of time to fit all exports
75057465 // after https://github.com/ziglang/zig/issues/23616
7506 try coff.export_table.export_address_table_si.node(coff).resize(
7466 try coff.export_table.export_address_table_si.node(coff).resizeLeaf(
75077467 &coff.mf,
75087468 gpa,
75097469 export_count * @sizeOf(std.coff.ExportAddressTableEntry),
75107470 );
75117471
7512 try coff.export_table.name_pointer_table_ni.resize(
7472 try coff.export_table.name_pointer_table_ni.resizeLeaf(
75137473 &coff.mf,
75147474 gpa,
75157475 export_count * @sizeOf(std.coff.ExportNamePointerTableEntry),
75167476 );
75177477
7518 try coff.export_table.ordinal_table_ni.resize(
7478 try coff.export_table.ordinal_table_ni.resizeLeaf(
75197479 &coff.mf,
75207480 gpa,
75217481 export_count * @sizeOf(std.coff.ExportOrdinalTableEntry),
......@@ -7746,12 +7706,12 @@ pub fn printNode(
77467706 {
77477707 const mf_node = &coff.mf.nodes.items[@backingInt(ni)];
77487708 const off, const size = mf_node.location().resolve(&coff.mf);
7749 try w.print(" index={d} offset=0x{x} size=0x{x} align=0x{x}{s}{s}{s}{s}\n", .{
7709 try w.print(" index={d} offset=0x{x} size=0x{x} align=0x{x} {t}{s}{s}{s}\n", .{
77507710 @backingInt(ni),
77517711 off,
77527712 size,
77537713 mf_node.flags.alignment.toByteUnits(),
7754 if (mf_node.flags.fixed) " fixed" else "",
7714 mf_node.flags.position,
77557715 if (mf_node.flags.moved) " moved" else "",
77567716 if (mf_node.flags.resized) " resized" else "",
77577717 if (mf_node.flags.has_content) " has_content" else "",
src/link/Elf2.zig+72-66
......@@ -550,7 +550,7 @@ const Section = struct {
550550 }
551551 const ni = shndx.get(elf).ni;
552552 if (min_align.compare(.gt, ni.alignment(&elf.mf))) {
553 try ni.realign(&elf.mf, elf.base.comp.gpa, min_align, .{});
553 try ni.realign(&elf.mf, elf.base.comp.gpa, min_align);
554554 }
555555 switch (elf.getNode(ni.parent(&elf.mf).unwrap().?)) {
556556 .elf => {},
......@@ -583,7 +583,7 @@ const Section = struct {
583583 break :need_size cur_size + need_additional * ent_size;
584584 },
585585 };
586 try elf.ensureNodeSize(node, need_size);
586 try node.ensureMinimumSize(&elf.mf, elf.base.comp.gpa, need_size);
587587 }
588588
589589 /// Asserts that `rela_shndx` is a `SHT_RELA` section and deletes the `ElfN.Rela` entry at
......@@ -1787,6 +1787,8 @@ const SymbolReloc = struct {
17871787};
17881788
17891789fn ensureDynsymHashCapacity(elf: *Elf, max_dynsym_count: u32) Error!void {
1790 const gpa = elf.base.comp.gpa;
1791
17901792 const min_buckets = max_dynsym_count / 2;
17911793
17921794 const cur_dynsym_count: u32 = switch (elf.shdrPtr(elf.shndx.dynsym)) {
......@@ -1807,7 +1809,7 @@ fn ensureDynsymHashCapacity(elf: *Elf, max_dynsym_count: u32) Error!void {
18071809 // We don't need to add any buckets, but we still need to make sure the section is large
18081810 // enough to fit `max_dynsym_count` chains.
18091811 const need_size = @sizeOf(info.Header()) + (nbucket + max_dynsym_count) * 4;
1810 try elf.ensureNodeSize(elf.shndx.hash.get(elf).ni, need_size);
1812 try elf.shndx.hash.get(elf).ni.ensureMinimumSize(&elf.mf, gpa, need_size);
18111813 return;
18121814 }
18131815 // We need more buckets, so we'll have to rebuild the hash table.
......@@ -1819,7 +1821,7 @@ fn ensureDynsymHashCapacity(elf: *Elf, max_dynsym_count: u32) Error!void {
18191821
18201822 {
18211823 const need_size = @sizeOf(info.Header()) + (new_nbucket + max_dynsym_count) * 4;
1822 try elf.ensureNodeSize(elf.shndx.hash.get(elf).ni, need_size);
1824 try elf.shndx.hash.get(elf).ni.ensureMinimumSize(&elf.mf, gpa, need_size);
18231825 }
18241826
18251827 elf.mf.nodes_lock.lock();
......@@ -1965,7 +1967,7 @@ fn ensureUnusedSymbolCapacity(elf: *Elf, len: u32, kind: enum { all_local, maybe
19651967 const need_node_size: u64 = switch (elf.shdrPtr(.symtab)) {
19661968 inline else => |shdr, class| elf.targetLoad(&shdr.size) + len * @sizeOf(class.ElfN().Sym),
19671969 };
1968 try elf.ensureNodeSize(Section.Index.symtab.get(elf).ni, need_node_size);
1970 try Section.Index.symtab.get(elf).ni.ensureMinimumSize(&elf.mf, gpa, need_node_size);
19691971 }
19701972
19711973 switch (kind) {
......@@ -1988,7 +1990,7 @@ fn ensureUnusedSymbolCapacity(elf: *Elf, len: u32, kind: enum { all_local, maybe
19881990 const dynsym_cur_len: u32 = @intCast(@divExact(dynsym_cur_size, dynsym_ent_size));
19891991
19901992 const dynsym_need_size: u64 = (dynsym_cur_len + len) * dynsym_ent_size;
1991 try elf.ensureNodeSize(elf.shndx.dynsym.get(elf).ni, dynsym_need_size);
1993 try elf.shndx.dynsym.get(elf).ni.ensureMinimumSize(&elf.mf, gpa, dynsym_need_size);
19921994
19931995 try elf.ensureDynsymHashCapacity(dynsym_cur_len + len);
19941996
......@@ -2010,19 +2012,19 @@ fn ensureUnusedPltCapacity(elf: *Elf, len: u32) Error!void {
20102012 // Ensure the `.plt` section's node is big enough:
20112013 {
20122014 const need_size: usize = plt.entry_size * (1 + need_plt_count);
2013 try elf.ensureNodeSize(elf.shndx.plt.get(elf).ni, need_size);
2015 try elf.shndx.plt.get(elf).ni.ensureMinimumSize(&elf.mf, gpa, need_size);
20142016 }
20152017
20162018 // If there is a `.got.plt` section, ensure its node is big enough
20172019 if (plt.got_plt) |got_plt| {
20182020 const need_size: usize = elf.targetPtrSize() * (got_plt.header_entries + need_plt_count);
2019 try elf.ensureNodeSize(elf.shndx.got_plt.get(elf).ni, need_size);
2021 try elf.shndx.got_plt.get(elf).ni.ensureMinimumSize(&elf.mf, gpa, need_size);
20202022 }
20212023
20222024 // If there is a `.plt.sec` section, ensure its node is big enough
20232025 if (plt.plt_sec) |plt_sec| {
20242026 const need_size: usize = plt_sec.entry_size * need_plt_count;
2025 try elf.ensureNodeSize(elf.shndx.plt_sec.get(elf).ni, need_size);
2027 try elf.shndx.plt_sec.get(elf).ni.ensureMinimumSize(&elf.mf, gpa, need_size);
20262028 }
20272029}
20282030/// Given an index into the PLT, returns whether that PLT entry is dead, meaning it may be reused at
......@@ -2989,7 +2991,7 @@ fn lazySymbolInner(elf: *Elf, lazy: link.File.LazySymbol) Error!link.File.Symbol
29892991 .code => .{ .text, .FUNC },
29902992 .const_data => .{ .rodata, .OBJECT },
29912993 };
2992 const node = try elf.mf.addLastChildNode(gpa, shndx.get(elf).ni, .{});
2994 const node = try shndx.get(elf).ni.addFloatingChild(&elf.mf, gpa, .{});
29932995 var name_buf: [64]u8 = undefined;
29942996 const name = std.fmt.bufPrint(
29952997 &name_buf,
......@@ -3248,7 +3250,7 @@ const StringTable = struct {
32483250 break :size .{ old_size, new_size };
32493251 },
32503252 };
3251 try elf.ensureNodeSize(ni, new_size);
3253 try ni.ensureMinimumSize(&elf.mf, gpa, new_size);
32523254 const slice = ni.slice(&elf.mf)[old_size..];
32533255 @memcpy(slice[0..key.len], key);
32543256 slice[key.len] = 0;
......@@ -3611,10 +3613,11 @@ fn initHeaders(
36113613 if (is_archive) {
36123614 elf.nodes.appendAssumeCapacity(.archive);
36133615
3614 const archive_header_ni = try elf.mf.addOnlyChildNode(gpa, .root, .{
3616 const archive_ni: MappedFile.Node.Index = .root;
3617
3618 const archive_header_ni = try archive_ni.addOnlyHeaderChild(&elf.mf, gpa, .{
36153619 .size = std.elf.ARMAG.len + @sizeOf(std.elf.ar_hdr) * 2,
36163620 .alignment = .@"2",
3617 .fixed = true,
36183621 .next_moved = true,
36193622 .bubbles_moved = false,
36203623 .enable_next_moved = true,
......@@ -3633,7 +3636,7 @@ fn initHeaders(
36333636 .ar_fmag = std.elf.ARFMAG.*,
36343637 };
36353638
3636 elf.ni.elf = try elf.mf.addLastChildNode(gpa, .root, .{
3639 elf.ni.elf = try archive_ni.addFloatingChild(&elf.mf, gpa, .{
36373640 .alignment = node_block_align.max(.@"2"),
36383641 .next_moved = true,
36393642 .bubbles_moved = false,
......@@ -3657,19 +3660,18 @@ fn initHeaders(
36573660 // the rodata segment. Although to my knowledge neither ELF nor any ELF-based OS strictly
36583661 // requires this, it is highly conventional and therefore sometimes relied upon.
36593662 if (@"type" != .REL) {
3660 elf.ni.rodata = try elf.mf.addOnlyChildNode(gpa, elf.ni.elf, .{
3663 // This node will contain the ehdr, which must be at the start of the ELF file, so this
3664 // node must itself be a header of the `.elf` node.
3665 elf.ni.rodata = try elf.ni.elf.addOnlyHeaderChild(&elf.mf, gpa, .{
36613666 // Must be at least `addr_align` for `elf.ni.phdr` to be placed inside this node
36623667 .alignment = node_block_align.max(addr_align),
3663 // This node will contain the ehdr, which must be at the start of the ELF file, so this
3664 // node must itself be fixed.
3665 .fixed = true,
36663668 .moved = true,
36673669 .bubbles_moved = false,
36683670 });
36693671 elf.nodes.appendAssumeCapacity(.{ .segment = phndx.rodata });
36703672 elf.phdrs.items[phndx.rodata] = .wrap(elf.ni.rodata);
36713673
3672 elf.ni.phdr = try elf.mf.addOnlyChildNode(gpa, elf.ni.rodata, .{
3674 elf.ni.phdr = try elf.ni.rodata.addFloatingChild(&elf.mf, gpa, .{
36733675 .size = @as(u64, phnum) * entsize.ph,
36743676 .alignment = addr_align, // keep in sync with `elf.ni.rodata` alignment above
36753677 .moved = true,
......@@ -3679,7 +3681,7 @@ fn initHeaders(
36793681 elf.nodes.appendAssumeCapacity(.{ .segment = phndx.phdr });
36803682 elf.phdrs.items[phndx.phdr] = .wrap(elf.ni.phdr);
36813683
3682 elf.ni.text = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{
3684 elf.ni.text = try elf.ni.elf.addFloatingChild(&elf.mf, gpa, .{
36833685 .alignment = node_block_align,
36843686 .moved = true,
36853687 .bubbles_moved = false,
......@@ -3687,7 +3689,7 @@ fn initHeaders(
36873689 elf.nodes.appendAssumeCapacity(.{ .segment = phndx.text });
36883690 elf.phdrs.items[phndx.text] = .wrap(elf.ni.text);
36893691
3690 elf.ni.data = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{
3692 elf.ni.data = try elf.ni.elf.addFloatingChild(&elf.mf, gpa, .{
36913693 // Must be at least `addr_align` for `elf.ni.data_rel_ro` to be placed inside this node
36923694 .alignment = node_block_align.max(addr_align),
36933695 .moved = true,
......@@ -3697,7 +3699,7 @@ fn initHeaders(
36973699 elf.phdrs.items[phndx.data] = .wrap(elf.ni.data);
36983700
36993701 if (plt.got_plt == null) {
3700 const plt_ni = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{
3702 const plt_ni = try elf.ni.elf.addFloatingChild(&elf.mf, gpa, .{
37013703 .alignment = node_block_align,
37023704 .moved = true,
37033705 .bubbles_moved = false,
......@@ -3706,7 +3708,7 @@ fn initHeaders(
37063708 elf.phdrs.items[phndx.plt] = .wrap(plt_ni);
37073709 }
37083710
3709 elf.ni.data_rel_ro = try elf.mf.addOnlyChildNode(gpa, elf.ni.data, .{
3711 elf.ni.data_rel_ro = try elf.ni.data.addFloatingChild(&elf.mf, gpa, .{
37103712 // Must be at least `addr_align` for the `PT_DYNAMIC` node to be placed inside this one
37113713 // later (if `have_dynamic_section`). Keep in sync with `elf.ni.data` alignment above.
37123714 .alignment = node_block_align.max(addr_align),
......@@ -3717,7 +3719,7 @@ fn initHeaders(
37173719 elf.phdrs.items[phndx.relro] = .wrap(elf.ni.data_rel_ro);
37183720
37193721 if (comp.config.any_non_single_threaded) {
3720 elf.ni.tls = .wrap(try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{
3722 elf.ni.tls = .wrap(try elf.ni.rodata.addFloatingChild(&elf.mf, gpa, .{
37213723 .alignment = node_block_align,
37223724 .moved = true,
37233725 .bubbles_moved = false,
......@@ -3738,10 +3740,9 @@ fn initHeaders(
37383740 .REL => elf.ni.elf,
37393741 .DYN, .EXEC => elf.ni.rodata,
37403742 };
3741 elf.ni.ehdr = try elf.mf.addFirstChildNode(gpa, parent_ni, .{
3743 elf.ni.ehdr = try parent_ni.addOnlyHeaderChild(&elf.mf, gpa, .{
37423744 .size = @sizeOf(ElfN.Ehdr),
37433745 .alignment = addr_align,
3744 .fixed = true,
37453746 });
37463747 elf.nodes.appendAssumeCapacity(.ehdr);
37473748
......@@ -3793,8 +3794,8 @@ fn initHeaders(
37933794 },
37943795 }
37953796
3796 elf.ni.shdr = try elf.mf.addLastChildNode(gpa, elf.ni.elf, .{
3797 .size = 1 * entsize.sh, // as above, only the SHN_UNDEF initially
3797 elf.ni.shdr = try elf.ni.elf.addFloatingChild(&elf.mf, gpa, .{
3798 .size = node_block_align.forward(1 * entsize.sh), // as above, only the SHN_UNDEF initially
37983799 .alignment = addr_align.max(node_block_align),
37993800 .moved = true,
38003801 .resized = true,
......@@ -4109,7 +4110,7 @@ fn initHeaders(
41094110 .node_align = node_block_align,
41104111 });
41114112 if (maybe_interp) |interp| {
4112 const interp_ni = try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{
4113 const interp_ni = try elf.ni.rodata.addFloatingChild(&elf.mf, gpa, .{
41134114 .size = interp.len + 1,
41144115 .moved = true,
41154116 .resized = true,
......@@ -4130,7 +4131,7 @@ fn initHeaders(
41304131 }
41314132 if (have_dynamic_section) {
41324133 assert(elf.ni.data_rel_ro.alignment(&elf.mf).compare(.gte, addr_align));
4133 const dynamic_ni = try elf.mf.addLastChildNode(gpa, elf.ni.data_rel_ro, .{
4134 const dynamic_ni = try elf.ni.data_rel_ro.addFloatingChild(&elf.mf, gpa, .{
41344135 .alignment = addr_align,
41354136 .moved = true,
41364137 .bubbles_moved = false,
......@@ -4208,7 +4209,7 @@ fn initHeaders(
42084209 .flags = .{ .ALLOC = true, .WRITE = true },
42094210 .link = dynstr_shndx.toSection().?,
42104211 .entsize = @intCast(addr_align.toByteUnits() * 2),
4211 .node_align = addr_align,
4212 .addralign = addr_align,
42124213 });
42134214 switch (elf.targetDynsymHashInfo()) {
42144215 inline else => |info| {
......@@ -4869,7 +4870,7 @@ fn targetDynsymHashInfo(elf: *const Elf) DynsymHashInfo {
48694870 // TODO: Alpha and S390x will need to use either `."@4"` or `.@"8"` depending on `elf.identClass()`.
48704871 };
48714872}
4872fn targetLoad(elf: *const Elf, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {
4873pub fn targetLoad(elf: *const Elf, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {
48734874 const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer;
48744875 const Child = pointer_ty.child;
48754876 const alignment = pointer_ty.attrs.@"align" orelse @alignOf(Child);
......@@ -5051,7 +5052,7 @@ fn mapInputSection(elf: *Elf, opts: struct {
50515052 const name_shstrtab = try elf.string(.shstrtab, name);
50525053 const gop = try elf.section_by_name.getOrPut(gpa, name_shstrtab);
50535054 if (gop.found_existing) {
5054 break :existing @fromBackingInt(@intCast(gop.index));
5055 break :existing @fromBackingInt(@intCast(gop.index + 1)); // +1 to account for SHN_UDNEF
50555056 }
50565057 errdefer assert(elf.section_by_name.pop().?.key == name_shstrtab);
50575058 const parent_node: MappedFile.Node.Index = parent: {
......@@ -5172,7 +5173,7 @@ fn navMapIndex(elf: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index) Error!Node
51725173 },
51735174 };
51745175 try shndx.ensureAligned(elf, alignment);
5175 const node = try elf.mf.addLastChildNode(gpa, shndx.get(elf).ni, .{
5176 const node = try shndx.get(elf).ni.addFloatingChild(&elf.mf, gpa, .{
51765177 .alignment = alignment,
51775178 });
51785179 nav_gop.value_ptr.* = .{
......@@ -5216,7 +5217,7 @@ fn uavMapIndex(
52165217 if (!uav_gop.found_existing) {
52175218 const shndx: Section.Index = .data_rel_ro; // TODO: it would be better to use `.rodata` if the UAV value doesn't have relocs
52185219 try shndx.ensureAligned(elf, resolved_align);
5219 const node = try elf.mf.addLastChildNode(gpa, shndx.get(elf).ni, .{
5220 const node = try shndx.get(elf).ni.addFloatingChild(&elf.mf, gpa, .{
52205221 .moved = true, // see assert at end of `genUav`
52215222 .alignment = resolved_align,
52225223 });
......@@ -5245,7 +5246,7 @@ fn uavMapIndex(
52455246 const shndx = elf.getNode(node.parent(&elf.mf).unwrap().?).section;
52465247 try shndx.ensureAligned(elf, resolved_align);
52475248 if (resolved_align.order(node.alignment(&elf.mf)).compare(.gt)) {
5248 try node.realign(&elf.mf, gpa, resolved_align, .{});
5249 try node.realign(&elf.mf, gpa, resolved_align);
52495250 }
52505251 }
52515252 return umi;
......@@ -5462,9 +5463,10 @@ fn loadObject(
54625463 .extra = undefined,
54635464 };
54645465 if (elf.ni.elf != .root) {
5466 const archive_ni: MappedFile.Node.Index = .root;
54655467 try elf.nodes.ensureUnusedCapacity(gpa, 1);
5466 input.extra = .{ .node = try elf.mf.addLastChildNode(gpa, .root, .{
5467 .size = fl.size + @sizeOf(std.elf.ar_hdr),
5468 input.extra = .{ .node = try archive_ni.addFloatingChild(&elf.mf, gpa, .{
5469 .size = Alignment.@"2".forward(fl.size + @sizeOf(std.elf.ar_hdr)),
54685470 .alignment = .@"2",
54695471 .next_moved = true,
54705472 .bubbles_moved = false,
......@@ -5646,12 +5648,24 @@ fn loadObject(
56465648 std.math.ceilPowerOfTwoAssert(usize, @intCast(@max(section.shdr.addralign, 1))),
56475649 );
56485650 try opts.shndx.ensureAligned(elf, need_align);
5649 const ni = try elf.mf.addLastChildNode(gpa, opts.shndx.get(elf).ni, .{
5650 .size = section.shdr.size,
5651 const add_node_opts: MappedFile.Node.AddOptions = .{
5652 .size = need_align.forward(section.shdr.size),
56515653 .alignment = need_align,
56525654 .moved = true, // see assert at end of `flushInputSection`
5653 .fixed = opts.node_fixed,
5654 });
5655 };
5656 const ni = if (opts.node_fixed) ni: {
5657 const shndx_ni = opts.shndx.get(elf).ni;
5658 const after_oni: MappedFile.Node.Index.Optional = after: {
5659 const last_ni = shndx_ni.last(&elf.mf).unwrap() orelse break :after .none;
5660 break :after switch (last_ni.position(&elf.mf)) {
5661 .header => .wrap(last_ni),
5662 .footer, .floating => .none,
5663 };
5664 };
5665 break :ni try shndx_ni.addHeaderChildAfter(&elf.mf, gpa, after_oni, add_node_opts);
5666 } else ni: {
5667 break :ni try opts.shndx.get(elf).ni.addFloatingChild(&elf.mf, gpa, add_node_opts);
5668 };
56555669 elf.nodes.appendAssumeCapacity(.{
56565670 .input_section = @fromBackingInt(@intCast(elf.input_sections.items.len)),
56575671 });
......@@ -6019,8 +6033,8 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadPars
60196033 // We have a copy relocation for this global, but the amount of space we
60206034 // reserved for it could be too small or underaligned!
60216035 try Section.Index.data.ensureAligned(elf, gop.value_ptr.alignment);
6022 try copied_global.node.resize(&elf.mf, gpa, gop.value_ptr.size);
6023 try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment, .{});
6036 try copied_global.node.resizeLeaf(&elf.mf, gpa, gop.value_ptr.alignment.forward(gop.value_ptr.size));
6037 try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment);
60246038 const global_ptr = elf.globalByName(name).?;
60256039 switch (elf.symPtr(global_ptr.symtab_index)) {
60266040 inline else => |sym_ptr| elf.targetStore(&sym_ptr.size, @intCast(gop.value_ptr.size)),
......@@ -6267,7 +6281,7 @@ fn prepareDynamic(elf: *Elf) Error!void {
62676281
62686282 const dynamic_size = dynamic_len * 2 * elf.targetPtrSize();
62696283
6270 try elf.shndx.dynamic.get(elf).ni.resize(&elf.mf, comp.gpa, dynamic_size);
6284 try elf.shndx.dynamic.get(elf).ni.resizeLeaf(&elf.mf, comp.gpa, dynamic_size);
62716285 switch (elf.shdrPtr(elf.shndx.dynamic)) {
62726286 inline else => |shdr| elf.targetStore(&shdr.size, @intCast(dynamic_size)),
62736287 }
......@@ -6393,7 +6407,6 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct {
63936407 addralign: Alignment = .@"1",
63946408 entsize: std.elf.Word = 0,
63956409 node_align: Alignment = .@"1",
6396 fixed: bool = false,
63976410}) Error!Section.Index {
63986411 switch (opts.type) {
63996412 .NULL => assert(opts.size == 0),
......@@ -6437,14 +6450,15 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct {
64376450 break :shndx .{ @fromBackingInt(shndx), @as(u64, elf.targetLoad(&ehdr.shentsize)) * @as(u64, shnum) };
64386451 },
64396452 };
6440 try elf.ensureNodeSize(elf.ni.shdr, new_shdr_size);
6441 const ni = try elf.mf.addLastChildNode(gpa, switch (elf.ehdrType()) {
6453 try elf.ni.shdr.ensureMinimumSize(&elf.mf, gpa, new_shdr_size);
6454 const parent_ni = switch (elf.ehdrType()) {
64426455 .REL => elf.ni.elf,
64436456 .EXEC, .DYN => segment_ni,
6444 }, .{
6445 .size = opts.size,
6457 };
6458 assert(opts.addralign.check(opts.size));
6459 const ni = try parent_ni.addFloatingChild(&elf.mf, gpa, .{
6460 .size = opts.node_align.forward(opts.size),
64466461 .alignment = opts.addralign.max(opts.node_align),
6447 .fixed = opts.fixed,
64486462 .resized = opts.size > 0,
64496463 });
64506464 const addr = elf.computeNodeVAddr(ni);
......@@ -6530,7 +6544,7 @@ fn ensureUnusedRelocCapacity(elf: *Elf, node: MappedFile.Node.Index, len: usize)
65306544 .NONE, _ => unreachable,
65316545 inline else => |ct_class| (elf.got.count() + new_got_entries) * @sizeOf(ct_class.ElfN().Addr),
65326546 };
6533 try elf.ensureNodeSize(elf.shndx.got.get(elf).ni, need_got_size);
6547 try elf.shndx.got.get(elf).ni.ensureMinimumSize(&elf.mf, gpa, need_got_size);
65346548
65356549 if (elf.shndx.dynamic != .UNDEF) {
65366550 try elf.shndx.rela_dyn.relaEnsureAdditionalCapacity(elf, new_got_entries);
......@@ -7284,8 +7298,8 @@ fn maybeAddCopyRelocation(elf: *Elf, global_name: String(.strtab)) Error!bool {
72847298 try Section.Index.data.ensureAligned(elf, dso_global.alignment);
72857299
72867300 try elf.nodes.ensureUnusedCapacity(gpa, 1);
7287 const node = try elf.mf.addLastChildNode(gpa, Section.Index.data.get(elf).ni, .{
7288 .size = dso_global.size,
7301 const node = try Section.Index.data.get(elf).ni.addFloatingChild(&elf.mf, gpa, .{
7302 .size = dso_global.alignment.forward(dso_global.size),
72897303 .alignment = dso_global.alignment,
72907304 });
72917305 errdefer comptime unreachable;
......@@ -8868,12 +8882,12 @@ pub fn printNode(
88688882 {
88698883 const mf_node = &elf.mf.nodes.items[@backingInt(ni)];
88708884 const off, const size = mf_node.location().resolve(&elf.mf);
8871 try w.print(" index={d} offset=0x{x} size=0x{x} align=0x{x}{s}{s}{s}{s}{s}\n", .{
8885 try w.print(" index={d} offset=0x{x} size=0x{x} align=0x{x} {t}{s}{s}{s}{s}\n", .{
88728886 @backingInt(ni),
88738887 off,
88748888 size,
88758889 mf_node.flags.alignment.toByteUnits(),
8876 if (mf_node.flags.fixed) " fixed" else "",
8890 mf_node.flags.position,
88778891 if (mf_node.flags.moved) " moved" else "",
88788892 if (mf_node.flags.next_moved) " next_moved" else "",
88798893 if (mf_node.flags.resized) " resized" else "",
......@@ -8920,7 +8934,7 @@ fn ensureSegmentAligned(elf: *Elf, start_phndx: u32, min_align: Alignment) Error
89208934 // Align the actual node
89218935 const seg_ni = elf.phdrs.items[phndx].unwrap().?;
89228936 if (min_align.compare(.gt, seg_ni.alignment(&elf.mf))) {
8923 try seg_ni.realign(&elf.mf, gpa, min_align, .{});
8937 try seg_ni.realign(&elf.mf, gpa, min_align);
89248938 }
89258939 // Update the phdr `@"align"` field if necessary
89268940 switch (elf.phdrSlice()) {
......@@ -8960,15 +8974,7 @@ fn ensureElfNodeSize(elf: *Elf) MappedFile.Error!void {
89608974 const last_offset, const last_size = last_ni.location(&elf.mf).resolve(&elf.mf);
89618975 break :last_end last_offset + last_size;
89628976 } else 0;
8963 try elf.ensureNodeSize(elf.ni.elf, last_end + @sizeOf(std.elf.ar_hdr));
8964}
8965
8966fn ensureNodeSize(elf: *Elf, node: MappedFile.Node.Index, need_size: u64) MappedFile.Error!void {
8967 _, const node_size = node.location(&elf.mf).resolve(&elf.mf);
8968 if (need_size <= node_size) return;
8969 const gpa = elf.base.comp.gpa;
8970 const new_size = need_size + need_size / MappedFile.growth_factor;
8971 try node.resize(&elf.mf, gpa, new_size);
8977 try elf.ni.elf.ensureMinimumSize(&elf.mf, elf.base.comp.gpa, last_end + @sizeOf(std.elf.ar_hdr));
89728978}
89738979
89748980/// If `sym` has a PLT entry, returns the address of that entry (specifically, the address which a
src/link/MappedFile.zig+1779-883
......@@ -183,14 +183,42 @@ pub fn init(file: Io.File, gpa: Allocator, io: Io) (Allocator.Error || Io.Cancel
183183 .fallocate_insert_range_unsupported = false,
184184 .fallocate_punch_hole_unsupported = false,
185185 };
186 try mf.nodes.ensureUnusedCapacity(gpa, 1);
187 const root_ni = try mf.addNode(gpa, .{ .add_node = .{
188 .size = size,
189 .alignment = mf.flags.block_size,
190 .fixed = true,
191 } });
192 assert(root_ni == .root);
193 try mf.ensureTotalCapacityInner(@intCast(size));
186
187 const root_location: Node.Location = l: {
188 if (std.math.cast(u32, size)) |small_size| {
189 break :l .{ .small = .{ .offset = 0, .size = small_size } };
190 }
191 try mf.large.appendSlice(gpa, &.{ 0, size });
192 break :l .{ .large = .{ .index = 0 } };
193 };
194 try mf.nodes.append(gpa, .{
195 .parent = .none,
196 .prev = .none,
197 .next = .none,
198 .first = .none,
199 .last = .none,
200 .flags = .{
201 .alignment = mf.flags.block_size,
202 .position = .floating,
203 .bubbles_moved = true,
204 .enable_next_moved = false,
205 .location_tag = root_location,
206 .moved = false,
207 .resized = false,
208 .next_moved = false,
209 .has_content = false,
210 },
211 .location_payload = switch (root_location) {
212 .small => |small| .{ .small = small },
213 .large => |large| .{ .large = large },
214 },
215 });
216
217 mf.ensureTotalCapacity(@intCast(size)) catch |err| switch (err) {
218 error.MappedFileIo => return mf.io_err.?,
219 else => |e| return e,
220 };
221
194222 return mf;
195223}
196224
......@@ -213,24 +241,53 @@ pub const Node = extern struct {
213241 flags: Flags,
214242 location_payload: Location.Payload,
215243
244 /// Any non-leaf node may designate its first N children as "header" nodes. This means that its
245 /// first N children must be densely packed together and positioned at the start of the parent.
246 /// The implementation guarantees that it will never re-order these nodes, nor will it introduce
247 /// padding between them.
248 ///
249 /// Likewise, any non-leaf node may designate its *last* M children as "footer" nodes, which are
250 /// like header nodes except they are positioned at the *end* of the parent rather than the
251 /// start.
252 ///
253 /// Nodes which are neither headers nor footers are called "floating". The implementation is
254 /// always free to re-order floating nodes relative to one another, and to add or remove padding
255 /// between them.
256 pub const Position = enum(u2) {
257 header,
258 footer,
259 floating,
260 };
261
216262 pub const Flags = packed struct(u32) {
217 location_tag: Location.Tag,
263 /// While the number of header and footer nodes within a parent node is logically a part of
264 /// that parent, we actually store this information on the child nodes for efficiency: this
265 /// field indicates whether each child is a header node, a footer node, or a floating node.
266 ///
267 /// This value is meaningless for the root node, so is arbitrarily set to `.floating`.
268 position: Position,
269 /// For floating nodes, this node's offset into its parent will always be aligned to this
270 /// boundary. (This is not the case for header and footer nodes due to the requirement that
271 /// they be densely packed against the start/end of the parent node.)
272 ///
273 /// This node's size will also always be aligned to this boundary. (This applies regardless
274 /// of whether this is a floating node, a header node, or a footer node.)
218275 alignment: Alignment,
219 /// Whether this node can be moved.
220 fixed: bool,
276 /// Whether `moved` events on this node bubble down to children.
277 bubbles_moved: bool,
278 /// Whether `next_moved` events are reported in `updates`.
279 enable_next_moved: bool,
280
281 location_tag: Location.Tag,
221282 /// Whether this node has been moved.
222283 moved: bool,
223284 /// Whether this node has been resized.
224285 resized: bool,
225286 /// Whether the next sibling has moved or is a different node.
226287 next_moved: bool,
227 /// Whether this node might contain non-zero bytes.
288 /// Whether this node might contain initialized bytes.
228289 has_content: bool,
229 /// Whether `moved` events on this node bubble down to children.
230 bubbles_moved: bool,
231 /// Whether `next_moved` events are reported in `updates`.
232 enable_next_moved: bool,
233 unused: u18 = 0,
290 unused: u17 = 0,
234291 };
235292
236293 pub const Location = union(enum(u1)) {
......@@ -267,6 +324,18 @@ pub const Node = extern struct {
267324 }
268325 };
269326
327 pub const AddOptions = struct {
328 /// Must be aligned to the given `alignment`.
329 size: u64 = 0,
330 alignment: Alignment = .@"1",
331 bubbles_moved: bool = true,
332 enable_next_moved: bool = false,
333
334 moved: bool = false,
335 resized: bool = false,
336 next_moved: bool = false,
337 };
338
270339 pub const Index = enum(u32) {
271340 root,
272341 _,
......@@ -292,6 +361,70 @@ pub const Node = extern struct {
292361 return &mf.nodes.items[@backingInt(ni)];
293362 }
294363
364 /// Adds a floating child node to `parent_ni`. Returns the index of the new child.
365 pub fn addFloatingChild(parent_ni: Node.Index, mf: *MappedFile, gpa: Allocator, opts: AddOptions) Error!Node.Index {
366 return mf.addNode(gpa, .{
367 .add_options = opts,
368 .position = .floating,
369 .parent = parent_ni,
370 .prev = parent_ni.lastHeader(mf),
371 });
372 }
373 /// Adds a header child node to `parent_ni`. Returns the index of the new child.
374 ///
375 /// Asserts that `parent_ni` has no existing header children.
376 pub fn addOnlyHeaderChild(parent_ni: Node.Index, mf: *MappedFile, gpa: Allocator, opts: AddOptions) Error!Node.Index {
377 if (parent_ni.first(mf).unwrap()) |first_ni| {
378 assert(first_ni.position(mf) != .header); // `parent_ni` already has a header child
379 }
380 return parent_ni.addHeaderChildAfter(mf, gpa, .none, opts);
381 }
382 /// Adds a header child node to `parent_ni`. Returns the index of the new child.
383 ///
384 /// If `prev_oni` is `.none`, the new child is placed at the very start of the parent,
385 /// before any existing header nodes.
386 ///
387 /// Otherwise, asserts that `prev_oni` is a header node and a child of `parent_ni`, and
388 /// places the new child node immediately after `prev_oni`.
389 pub fn addHeaderChildAfter(parent_ni: Node.Index, mf: *MappedFile, gpa: Allocator, prev_oni: Node.Index.Optional, opts: AddOptions) Error!Node.Index {
390 return mf.addNode(gpa, .{
391 .add_options = opts,
392 .position = .header,
393 .parent = parent_ni,
394 .prev = prev_oni,
395 });
396 }
397 /// Adds a footer child node to `parent_ni`. Returns the index of the new child.
398 ///
399 /// Asserts that `parent_ni` has no existing footer children.
400 pub fn addOnlyFooterChild(parent_ni: Node.Index, mf: *MappedFile, gpa: Allocator, opts: AddOptions) Error!Node.Index {
401 if (parent_ni.last(mf).unwrap()) |last_ni| {
402 assert(last_ni.position(mf) != .footer); // `parent_ni` already has a footer child
403 }
404 return parent_ni.addFooterChildBefore(mf, gpa, .none, opts);
405 }
406 /// Adds a footer child node to `parent_ni`. Returns the index of the new child.
407 ///
408 /// If `next_oni` is `.none`, the new child is placed at the very end of the parent, after
409 /// any existing footer nodes.
410 ///
411 /// Otherwise, asserts that `next_oni` is a footer node and a child of `parent_ni`, and
412 /// places the new child node immediately before `next_oni`.
413 pub fn addFooterChildBefore(parent_ni: Node.Index, mf: *MappedFile, gpa: Allocator, next_oni: Node.Index.Optional, opts: AddOptions) Error!Node.Index {
414 const prev_oni: Node.Index.Optional = prev: {
415 const next_ni = next_oni.unwrap() orelse {
416 break :prev parent_ni.last(mf);
417 };
418 break :prev next_ni.prev(mf);
419 };
420 return mf.addNode(gpa, .{
421 .add_options = opts,
422 .position = .footer,
423 .parent = parent_ni,
424 .prev = prev_oni,
425 });
426 }
427
295428 /// Alias for `Optional.wrap`, provided for convenience when a result type is not available.
296429 pub const toOptional = Optional.wrap;
297430
......@@ -299,19 +432,54 @@ pub const Node = extern struct {
299432 return ni.get(mf).parent;
300433 }
301434
435 pub fn first(ni: Node.Index, mf: *const MappedFile) Node.Index.Optional {
436 return ni.get(mf).first;
437 }
438
439 pub fn last(ni: Node.Index, mf: *const MappedFile) Node.Index.Optional {
440 return ni.get(mf).last;
441 }
442
443 fn lastHeader(ni: Node.Index, mf: *const MappedFile) Node.Index.Optional {
444 var header_ni = ni.first(mf).unwrap() orelse return .none;
445 if (header_ni.position(mf) != .header) return .none;
446 while (true) {
447 const next_ni = header_ni.next(mf).unwrap() orelse break;
448 if (next_ni.position(mf) != .header) break;
449 header_ni = next_ni;
450 }
451 return .wrap(header_ni);
452 }
453 fn firstFooter(ni: Node.Index, mf: *const MappedFile) Node.Index.Optional {
454 var footer_ni = ni.last(mf).unwrap() orelse return .none;
455 if (footer_ni.position(mf) != .footer) return .none;
456 while (true) {
457 const prev_ni = footer_ni.prev(mf).unwrap() orelse break;
458 if (prev_ni.position(mf) != .footer) break;
459 footer_ni = prev_ni;
460 }
461 return .wrap(footer_ni);
462 }
463
464 /// Asserts that `ni` is not `.root`, because `Position` is meaningless for the root node.
465 pub fn position(ni: Node.Index, mf: *const MappedFile) Node.Position {
466 assert(ni != .root);
467 return ni.get(mf).flags.position;
468 }
469
302470 pub fn next(ni: Node.Index, mf: *const MappedFile) Node.Index.Optional {
303471 return ni.get(mf).next;
304472 }
305473 fn setNext(
306 prev_ni: Node.Index,
474 ni: Node.Index,
307475 gpa: Allocator,
308476 next_ni: Node.Index.Optional,
309477 mf: *MappedFile,
310478 ) Allocator.Error!void {
311 const prev_next = &prev_ni.get(mf).next;
312 if (prev_next.* == next_ni) return;
313 prev_next.* = next_ni;
314 try prev_ni.nextMoved(gpa, mf);
479 const next_ptr = &ni.get(mf).next;
480 if (next_ptr.* == next_ni) return;
481 next_ptr.* = next_ni;
482 try ni.nextMoved(gpa, mf);
315483 }
316484
317485 pub fn prev(ni: Node.Index, mf: *const MappedFile) Node.Index.Optional {
......@@ -421,8 +589,14 @@ pub const Node = extern struct {
421589 return ni.get(mf).flags.alignment;
422590 }
423591
424 fn setLocationAssumeCapacity(ni: Node.Index, mf: *MappedFile, offset: u64, size: u64) void {
592 fn setLocation(ni: Node.Index, mf: *MappedFile, gpa: Allocator, offset: u64, size: u64) Allocator.Error!void {
593 try mf.large.ensureUnusedCapacity(gpa, 2);
594 try mf.updates.ensureUnusedCapacity(gpa, 2);
425595 const node = ni.get(mf);
596 if (node.flags.position == .floating) {
597 assert(node.flags.alignment.check(offset));
598 }
599 assert(node.flags.alignment.check(size));
426600 if (size == 0) node.flags.has_content = false;
427601 switch (node.location()) {
428602 .small => |small| {
......@@ -485,62 +659,46 @@ pub const Node = extern struct {
485659 return mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)];
486660 }
487661
488 pub fn resize(ni: Node.Index, mf: *MappedFile, gpa: Allocator, size: u64) Error!void {
489 mf.resizeNode(gpa, ni, size) catch |err| switch (err) {
490 error.OutOfMemory,
491 error.Canceled,
492 => |e| return e,
493 else => |e| {
494 mf.io_err = e;
495 return error.MappedFileIo;
496 },
497 };
498 var writers_it = mf.writers.first;
499 while (writers_it) |writer_node| : (writers_it = writer_node.next) {
500 const w: *Node.Writer = @fieldParentPtr("writer_node", writer_node);
501 w.interface.buffer = w.ni.slice(mf);
502 }
662 /// Ensures that the size of `ni` is at least `min_size`. Valid for any node.
663 ///
664 /// Applies `growth_factor` if necessary (so the caller should *not* apply `growth_factor`).
665 pub fn ensureMinimumSize(ni: Node.Index, mf: *MappedFile, gpa: Allocator, min_size: u64) Error!void {
666 _, const current_size = ni.location(mf).resolve(mf);
667 if (current_size >= min_size) return;
668 const new_size = ni.alignment(mf).forward(min_size +| min_size / growth_factor);
669 try mf.growNode(gpa, ni, new_size, .minimum);
670 mf.updateWriters();
503671 }
504672
505 pub const RealignNodeOptions = struct {
506 /// Shift the node backwards if possible
507 try_backwards: bool = false,
508 };
509
510 /// Moves and expands a node such that its offset and size are aligned to `new_alignment`.
511 /// Asserts that `ni` is not `.root`.
512 pub fn realign(
513 ni: Node.Index,
514 mf: *MappedFile,
515 gpa: Allocator,
516 new_alignment: Alignment,
517 opts: RealignNodeOptions,
518 ) Error!void {
519 mf.realignNode(gpa, ni, new_alignment, opts) catch |err| switch (err) {
520 error.OutOfMemory,
521 error.Canceled,
522 => |e| return e,
523 else => |e| {
524 mf.io_err = e;
525 return error.MappedFileIo;
526 },
527 };
673 /// Sets the size of `ni` to exactly `size`.
674 ///
675 /// Asserts that `ni` is a leaf node, i.e. has no children.
676 ///
677 /// Asserts that `size` is aligned to `ni.alignment(mf)`.
678 pub fn resizeLeaf(ni: Node.Index, mf: *MappedFile, gpa: Allocator, size: u64) Error!void {
679 assert(ni.first(mf) == .none);
680 // The alignment of `size` is asserted by `shrinkLeafNode` and `growNode`.
681 _, const old_size = ni.location(mf).resolve(mf);
682 switch (std.math.order(size, old_size)) {
683 .lt => try mf.shrinkLeafNode(gpa, ni, size),
684 .eq => {}, // `old_size` must be well-aligned, so `size` is too
685 .gt => try mf.growNode(gpa, ni, size, .exact),
686 }
528687 mf.updateWriters();
529688 }
530689
531 /// Shrink a node to `size`, exactly.
532 /// Asserts that the new size can contain all the children.
533 /// If `shift_next` is set, then the following node is shifted backwards into
534 /// the free space as much as alignment allows.
535 /// Asserts that `size` is >= the end of the last child node.
536 pub fn shrink(
690 /// Updates a node's alignment to exactly `new_alignment`. Valid for any node.
691 ///
692 /// If the node's current offset or size is not sufficiently aligned, it will be moved
693 /// and/or resized to match the new alignment. The node's size may be increased by any
694 /// amount, as if `ensureMinimumSize` were used.
695 pub fn realign(
537696 ni: Node.Index,
538697 mf: *MappedFile,
539698 gpa: Allocator,
540 size: u64,
541 shift_next: bool,
699 new_alignment: Alignment,
542700 ) Error!void {
543 try mf.shrinkNode(gpa, ni, size, shift_next);
701 try mf.realignNode(gpa, ni, new_alignment);
544702 mf.updateWriters();
545703 }
546704
......@@ -644,16 +802,9 @@ pub const Node = extern struct {
644802 file_reader.pos,
645803 w.ni.fileLocation(w.mf, true).offset + interface.end,
646804 limit.minInt(interface.unusedCapacityLen()),
647 ) catch |err| switch (err) {
648 error.Canceled => |e| {
649 w.err = e;
650 return error.WriteFailed;
651 },
652 else => |e| {
653 w.mf.io_err = e;
654 w.err = error.MappedFileIo;
655 return error.WriteFailed;
656 },
805 ) catch |err| {
806 w.err = err;
807 return error.WriteFailed;
657808 });
658809 if (n == 0) return error.Unimplemented;
659810 file_reader.pos += n;
......@@ -680,10 +831,8 @@ pub const Node = extern struct {
680831 unused_capacity: usize,
681832 ) Io.Writer.Error!void {
682833 _ = preserve;
683 const total_capacity = interface.end + unused_capacity;
684 if (interface.buffer.len >= total_capacity) return;
685834 const w: *Writer = @fieldParentPtr("interface", interface);
686 w.ni.resize(w.mf, w.gpa, total_capacity +| total_capacity / growth_factor) catch |err| {
835 w.ni.ensureMinimumSize(w.mf, w.gpa, interface.end + unused_capacity) catch |err| {
687836 w.err = err;
688837 return error.WriteFailed;
689838 };
......@@ -691,624 +840,1267 @@ pub const Node = extern struct {
691840 };
692841
693842 comptime {
694 if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Node) == 32);
843 if (!std.debug.runtime_safety) assert(@sizeOf(Node) == 32);
695844 }
696845};
697846
847/// Asserts that `opts.position` is compatible with `opts.prev` (i.e. that this addition will not
848/// violate the requirement that header nodes come before floating nodes come before footer nodes).
698849fn addNode(mf: *MappedFile, gpa: Allocator, opts: struct {
699 parent: Node.Index.Optional = .none,
700 prev: Node.Index.Optional = .none,
701 next: Node.Index.Optional = .none,
702 offset: u64 = 0,
703 add_node: AddNodeOptions,
704}) (Allocator.Error || Io.Cancelable || IoError)!Node.Index {
850 add_options: Node.AddOptions,
851 position: Node.Position,
852 parent: Node.Index,
853 /// If `position == .floating`, this is just used as an initial value, and may be immediately
854 /// replaced when finding a location for this node. In this case, it is still necessary that
855 /// `prev` be compatible with `position` (so `prev` must be either a floating node or the last
856 /// header node in `parent`).
857 prev: Node.Index.Optional,
858}) Error!Node.Index {
705859 mf.nodes_lock.assertUnlocked();
706 const location_tag: Node.Location.Tag, const location_payload: Node.Location.Payload = location: {
707 if (std.math.cast(u32, opts.offset)) |small_offset| break :location .{ .small, .{
708 .small = .{ .offset = small_offset, .size = 0 },
709 } };
710 try mf.large.ensureUnusedCapacity(gpa, 2);
711 defer mf.large.appendSliceAssumeCapacity(&.{ opts.offset, 0 });
712 break :location .{ .large, .{ .large = .{ .index = mf.large.items.len } } };
713 };
714860
715 const free_ni: Node.Index, const free_node: *Node = if (mf.free_ni.unwrap()) |free_ni| free: {
716 const free_node = free_ni.get(mf);
717 mf.free_ni = free_node.next;
718 break :free .{ free_ni, free_node };
719 } else .{
720 @fromBackingInt(@intCast(mf.nodes.items.len)),
721 mf.nodes.addOneAssumeCapacity(),
861 try mf.nodes.ensureUnusedCapacity(gpa, 1);
862 try mf.large.ensureUnusedCapacity(gpa, 2);
863
864 const new_ni: Node.Index = new: {
865 if (mf.free_ni.unwrap()) |free_ni| {
866 mf.free_ni = free_ni.get(mf).next;
867 break :new free_ni;
868 }
869 const new_ni: Node.Index = @fromBackingInt(@intCast(mf.nodes.items.len));
870 _ = mf.nodes.addOneAssumeCapacity();
871 break :new new_ni;
722872 };
723873
724 if (opts.prev.unwrap()) |prev_ni| {
725 try prev_ni.setNext(gpa, .wrap(free_ni), mf);
726 } else if (opts.parent.unwrap()) |parent_ni| {
727 parent_ni.get(mf).first = .wrap(free_ni);
728 } else {
729 assert(free_ni == .root);
730 }
874 const next_oni: Node.Index.Optional = if (opts.prev.unwrap()) |prev_ni| next: {
875 assert(prev_ni.parent(mf) == opts.parent.toOptional()); // `prev` is not a child of `parent`
876 break :next prev_ni.get(mf).next;
877 } else opts.parent.first(mf);
731878
732 if (opts.next.unwrap()) |next_ni| {
733 next_ni.get(mf).prev = .wrap(free_ni);
734 } else if (opts.parent.unwrap()) |parent_ni| {
735 parent_ni.get(mf).last = .wrap(free_ni);
736 } else {
737 assert(free_ni == .root);
879 // Validate node ordering
880 switch (opts.position) {
881 .floating => {
882 if (opts.prev.unwrap()) |prev_ni| {
883 assert(prev_ni.position(mf) != .footer); // tried to add floating node after footer node
884 }
885 if (next_oni.unwrap()) |next_ni| {
886 assert(next_ni.position(mf) != .header); // tried to add floating node before header node
887 }
888 },
889 .header => if (opts.prev.unwrap()) |prev_ni| {
890 switch (prev_ni.position(mf)) {
891 .header => {},
892 .floating => unreachable, // tried to add header node after floating node
893 .footer => unreachable, // tried to add header node after footer node
894 }
895 },
896 .footer => if (next_oni.unwrap()) |next_ni| {
897 switch (next_ni.position(mf)) {
898 .header => unreachable, // tried to add footer node before header node
899 .floating => unreachable, // tried to add footer node before floating node
900 .footer => {},
901 }
902 },
738903 }
739904
740 free_node.* = .{
741 .parent = opts.parent,
742 .prev = opts.prev,
743 .next = opts.next,
905 // Initialize the node as empty with alignment 1
906 const location: Node.Location = loc: {
907 const offset: u64 = switch (opts.position) {
908 .header, .floating => offset: {
909 const prev_ni = opts.prev.unwrap() orelse break :offset 0;
910 const prev_offset, const prev_size = prev_ni.location(mf).resolve(mf);
911 break :offset prev_offset + prev_size;
912 },
913 .footer => offset: {
914 const next_ni = next_oni.unwrap() orelse {
915 _, const parent_size = opts.parent.location(mf).resolve(mf);
916 break :offset parent_size;
917 };
918 const next_offset, _ = next_ni.location(mf).resolve(mf);
919 break :offset next_offset;
920 },
921 };
922 if (std.math.cast(u32, offset)) |small_offset| {
923 break :loc .{ .small = .{ .offset = small_offset, .size = 0 } };
924 }
925 const large_index = mf.large.items.len;
926 mf.large.appendSliceAssumeCapacity(&.{ offset, 0 });
927 break :loc .{ .large = .{ .index = large_index } };
928 };
929 new_ni.get(mf).* = .{
930 .parent = .wrap(opts.parent),
931 .prev = .none,
932 .next = .none,
744933 .first = .none,
745934 .last = .none,
746935 .flags = .{
747 .location_tag = location_tag,
936 .position = opts.position,
748937 .alignment = .@"1",
749 .fixed = opts.add_node.fixed,
750 .moved = true,
751 .resized = true,
752 .next_moved = true,
938 .bubbles_moved = opts.add_options.bubbles_moved,
939 .enable_next_moved = opts.add_options.enable_next_moved,
940 .location_tag = location,
941 .moved = false,
942 .resized = false,
943 .next_moved = false,
753944 .has_content = false,
754 .bubbles_moved = opts.add_node.bubbles_moved,
755 .enable_next_moved = opts.add_node.enable_next_moved,
756945 },
757 .location_payload = location_payload,
946 .location_payload = switch (location) {
947 .small => |small| .{ .small = small },
948 .large => |large| .{ .large = large },
949 },
758950 };
759951
760 {
761 defer {
762 free_node.flags.moved = false;
763 free_node.flags.resized = false;
764 free_node.flags.next_moved = false;
765 }
766 try mf.realignNode(gpa, free_ni, opts.add_node.alignment, .{});
767 try mf.resizeNode(gpa, free_ni, opts.add_node.size);
952 try mf.addNodesToChildListBefore(gpa, next_oni, new_ni, new_ni);
953
954 try mf.realignNode(gpa, new_ni, opts.add_options.alignment);
955 if (opts.add_options.size > 0) {
956 try mf.growNode(gpa, new_ni, opts.add_options.size, .exact);
768957 }
769958 mf.updateWriters();
770 if (opts.add_node.moved) try free_ni.moved(gpa, mf);
771 if (opts.add_node.resized) try free_ni.resized(gpa, mf);
772 if (opts.add_node.next_moved) try free_ni.nextMoved(gpa, mf);
773 return free_ni;
774}
775959
776pub const AddNodeOptions = struct {
777 size: u64 = 0,
778 alignment: Alignment = .@"1",
779 fixed: bool = false,
780 moved: bool = false,
781 resized: bool = false,
782 next_moved: bool = false,
783 bubbles_moved: bool = true,
784 enable_next_moved: bool = false,
785};
960 new_ni.get(mf).flags.moved = false;
961 new_ni.get(mf).flags.resized = false;
962 new_ni.get(mf).flags.next_moved = false;
786963
787pub fn addOnlyChildNode(
788 mf: *MappedFile,
789 gpa: Allocator,
790 parent_ni: Node.Index,
791 opts: AddNodeOptions,
792) Error!Node.Index {
793 try mf.nodes.ensureUnusedCapacity(gpa, 1);
794 const parent = parent_ni.get(mf);
795 assert(parent.first == .none and parent.last == .none);
796 return mf.addNode(gpa, .{
797 .parent = .wrap(parent_ni),
798 .add_node = opts,
799 }) catch |err| switch (err) {
800 error.OutOfMemory,
801 error.Canceled,
802 => |e| return e,
803 else => |e| {
804 mf.io_err = e;
805 return error.MappedFileIo;
806 },
807 };
808}
964 if (opts.add_options.moved) try new_ni.moved(gpa, mf);
965 if (opts.add_options.resized) try new_ni.resized(gpa, mf);
966 if (opts.add_options.next_moved) try new_ni.nextMoved(gpa, mf);
809967
810pub fn addFirstChildNode(
811 mf: *MappedFile,
812 gpa: Allocator,
813 parent_ni: Node.Index,
814 opts: AddNodeOptions,
815) Error!Node.Index {
816 try mf.nodes.ensureUnusedCapacity(gpa, 1);
817 const parent = parent_ni.get(mf);
818 return mf.addNode(gpa, .{
819 .parent = .wrap(parent_ni),
820 .next = parent.first,
821 .add_node = opts,
822 }) catch |err| switch (err) {
823 error.OutOfMemory,
824 error.Canceled,
825 => |e| return e,
826 else => |e| {
827 mf.io_err = e;
828 return error.MappedFileIo;
829 },
830 };
968 return new_ni;
831969}
832970
833pub fn addLastChildNode(
971fn shrinkLeafNode(
834972 mf: *MappedFile,
835973 gpa: Allocator,
836 parent_ni: Node.Index,
837 opts: AddNodeOptions,
838) Error!Node.Index {
839 try mf.nodes.ensureUnusedCapacity(gpa, 1);
840 const parent = parent_ni.get(mf);
841 return mf.addNode(gpa, .{
842 .parent = .wrap(parent_ni),
843 .prev = parent.last,
844 .offset = offset: {
845 const last_ni = parent.last.unwrap() orelse break :offset 0;
846 const last_offset, const last_size = last_ni.location(mf).resolve(mf);
847 break :offset last_offset + last_size;
848 },
849 .add_node = opts,
850 }) catch |err| switch (err) {
851 error.OutOfMemory,
852 error.Canceled,
853 => |e| return e,
854 else => |e| {
855 mf.io_err = e;
974 ni: Node.Index,
975 new_size: u64,
976) Error!void {
977 mf.nodes_lock.assertUnlocked();
978
979 const old_offset, const old_size = ni.location(mf).resolve(mf);
980
981 assert(new_size < old_size);
982 assert(ni.alignment(mf).check(new_size));
983 assert(ni.first(mf) == .none); // `ni` must be a leaf node
984
985 const parent_ni = ni.parent(mf).unwrap() orelse {
986 assert(ni == .root);
987 mf.memory_map.write(mf.io) catch |err| {
988 mf.io_err = switch (err) {
989 error.Canceled => |e| return e,
990 error.WouldBlock => error.Unexpected, // file was not opened as non-blocking
991 error.NotOpenForWriting => error.Unexpected, // we definitely opened the file for writing
992 else => |e| e,
993 };
856994 return error.MappedFileIo;
857 },
995 };
996 mf.memory_map.file.setLength(mf.io, new_size) catch |err| switch (err) {
997 error.Canceled => |e| return e,
998 else => |e| {
999 mf.io_err = e;
1000 return error.MappedFileIo;
1001 },
1002 };
1003 try mf.ensureTotalCapacityPrecise(@intCast(new_size));
1004 try ni.setLocation(mf, gpa, old_offset, new_size);
1005 return;
8581006 };
859}
8601007
861pub fn addNodeAfter(
862 mf: *MappedFile,
863 gpa: Allocator,
864 prev_ni: Node.Index,
865 opts: AddNodeOptions,
866) Error!Node.Index {
867 try mf.nodes.ensureUnusedCapacity(gpa, 1);
868 const prev = prev_ni.get(mf);
869 const prev_offset, const prev_size = prev.location().resolve(mf);
870 return mf.addNode(gpa, .{
871 .parent = prev.parent,
872 .prev = .wrap(prev_ni),
873 .next = prev.next,
874 .offset = prev_offset + prev_size,
875 .add_node = opts,
876 }) catch |err| switch (err) {
877 error.OutOfMemory,
878 error.Canceled,
879 => |e| return e,
880 else => |e| {
881 mf.io_err = e;
882 return error.MappedFileIo;
1008 switch (ni.position(mf)) {
1009 .header => {
1010 const shift = old_size - new_size;
1011
1012 try ni.setLocation(mf, gpa, old_offset, new_size);
1013
1014 // We need to shift backwards all header nodes following us.
1015 const next_header_ni = ni.next(mf).unwrap() orelse return;
1016 if (next_header_ni.position(mf) != .header) return;
1017
1018 var header_ni = next_header_ni;
1019 while (true) {
1020 const old_header_off, const old_header_size = header_ni.location(mf).resolve(mf);
1021 try header_ni.setLocation(mf, gpa, old_header_off - shift, old_header_size);
1022
1023 const next_ni = header_ni.next(mf).unwrap() orelse break;
1024 if (next_ni.position(mf) != .header) break;
1025 header_ni = next_ni;
1026 }
1027
1028 // Now we must shift the actual header bytes of those nodes backwards.
1029 const parent_file_off = parent_ni.fileLocation(mf, false).offset;
1030 const move_src_off = old_offset + old_size;
1031 const move_dest_off = old_offset + new_size;
1032 assert(next_header_ni.location(mf).resolve(mf)[0] == move_dest_off); // `move_dest_off` because we already updated the location
1033 const move_size = size: {
1034 // `header_ni` is the last header in the parent.
1035 const last_off, const last_size = header_ni.location(mf).resolve(mf);
1036 const move_end = last_off + last_size;
1037 break :size move_end - move_dest_off; // `move_dest_off` because we already updated the location
1038 };
1039 try mf.moveRange(
1040 parent_file_off + move_src_off,
1041 parent_file_off + move_dest_off,
1042 move_size,
1043 );
8831044 },
884 };
885}
1045 .floating => {
1046 try ni.setLocation(mf, gpa, old_offset, new_size);
1047 },
1048 .footer => {
1049 const shift = old_size - new_size;
8861050
887fn shrinkNode(
888 mf: *MappedFile,
889 gpa: Allocator,
890 ni: Node.Index,
891 size: u64,
892 shift_next: bool,
893) !void {
894 mf.nodes_lock.assertUnlocked();
895 const node = ni.get(mf);
896 const old_offset, _ = node.location().resolve(mf);
1051 const new_offset = old_offset + shift;
1052 try ni.setLocation(mf, gpa, new_offset, new_size);
8971053
898 // This would require unmapping first
899 assert(ni != .root);
1054 const prev_footers_size = prev_footers_size: {
1055 // We need to shift forwards all footer nodes preceding us.
1056 const prev_footer_ni = ni.prev(mf).unwrap() orelse {
1057 break :prev_footers_size 0;
1058 };
1059 if (prev_footer_ni.position(mf) != .footer) {
1060 break :prev_footers_size 0;
1061 }
9001062
901 if (node.last.unwrap()) |last_ni| {
902 const last = last_ni.get(mf);
903 const last_offset, const last_size = last.location().resolve(mf);
904 assert(last_offset + last_size > size);
905 }
1063 var footer_ni = prev_footer_ni;
1064 while (true) {
1065 const old_footer_off, const old_footer_size = footer_ni.location(mf).resolve(mf);
1066 try footer_ni.setLocation(mf, gpa, old_footer_off + shift, old_footer_size);
9061067
907 try mf.large.ensureUnusedCapacity(gpa, 4);
908 try mf.updates.ensureUnusedCapacity(gpa, 4);
1068 const prev_ni = footer_ni.prev(mf).unwrap() orelse break;
1069 if (prev_ni.position(mf) != .footer) break;
1070 footer_ni = prev_ni;
1071 }
9091072
910 ni.setLocationAssumeCapacity(mf, old_offset, size);
911 if (!shift_next) return;
912 const next_ni = node.next.unwrap() orelse return;
1073 // `footer_ni` is the first footer in the parent. This expression gets its *new*
1074 // offset because we already did the `setLocation` calls.
1075 const first_footer_new_offset = footer_ni.location(mf).resolve(mf)[0];
9131076
914 const next = next_ni.get(mf);
915 const old_next_offset, const next_size = next.location().resolve(mf);
916 const padding = old_next_offset - (old_offset + size);
917 const new_next_offset = next.flags.alignment.forward(@intCast(old_next_offset - padding));
1077 break :prev_footers_size new_offset - first_footer_new_offset;
1078 };
9181079
919 if (next.flags.has_content and new_next_offset < old_next_offset) {
920 const old_file_offset = next_ni.fileLocation(mf, false).offset;
921 const new_file_offset = (old_file_offset - old_next_offset) + new_next_offset;
922 @memmove(
923 mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(next_size)],
924 mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(next_size)],
925 );
926 @memset(mf.memory_map.memory[@intCast(new_file_offset + next_size)..@intCast(old_file_offset + next_size)], 0);
1080 // Now we must shift the actual footer bytes forwards, including our own.
1081 const parent_file_offset = parent_ni.fileLocation(mf, false).offset;
1082 try mf.moveRange(
1083 parent_file_offset + old_offset - prev_footers_size,
1084 parent_file_offset + new_offset - prev_footers_size,
1085 prev_footers_size + new_size,
1086 );
1087 },
9271088 }
928
929 next_ni.setLocationAssumeCapacity(mf, new_next_offset, next_size);
9301089}
9311090
932fn resizeNode(
1091const GrowMode = enum { exact, minimum };
1092
1093/// Increases the size of a node. If `grow_mode` is `.exact`, the new size will be exactly `new_size`.
1094/// If `grow_mode` is `.minimum`, the new size will be greater than or equal to `new_size`.
1095///
1096/// Asserts that `new_size` is aligned to `ni.alignment(mf)` (even if `grow_mode` is `.minimum`!).
1097///
1098/// Asserts that `new_size` is greater than the current size of `ni`.
1099fn growNode(
9331100 mf: *MappedFile,
9341101 gpa: Allocator,
9351102 ni: Node.Index,
936 requested_size: u64,
937) (Allocator.Error || Io.Cancelable || IoError)!void {
1103 new_size: u64,
1104 grow_mode: GrowMode,
1105) Error!void {
9381106 mf.nodes_lock.assertUnlocked();
939 const io = mf.io;
1107
9401108 const node = ni.get(mf);
1109
9411110 const old_offset, const old_size = node.location().resolve(mf);
942 const new_size = node.flags.alignment.forward(@intCast(requested_size));
9431111
944 // Resize the entire file
1112 assert(node.flags.alignment.check(old_size));
1113 assert(node.flags.alignment.check(new_size));
1114 assert(new_size > old_size);
1115
9451116 const parent_ni = node.parent.unwrap() orelse {
9461117 assert(ni == .root);
947 try mf.ensureCapacityForSetLocation(gpa);
948 mf.memory_map.write(io) catch |err| switch (err) {
949 error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking
950 error.NotOpenForWriting => return error.Unexpected, // we definitely opened the file for writing
951 else => |e| return e,
952 };
953 try mf.memory_map.file.setLength(io, new_size);
954 try mf.ensureTotalCapacityInner(@intCast(new_size));
955 ni.setLocationAssumeCapacity(mf, old_offset, new_size);
956 return;
957 };
958 const parent = parent_ni.get(mf);
959 _, var old_parent_size = parent.location().resolve(mf);
960 const trailing_end = trailing_end: {
961 const next_ni = node.next.unwrap() orelse break :trailing_end old_parent_size;
962 const next_offset, _ = next_ni.location(mf).resolve(mf);
963 break :trailing_end next_offset;
964 };
965 assert(old_offset + old_size <= trailing_end);
966 if (old_offset + new_size <= trailing_end) {
967 // Expand the node into trailing free space
968 try mf.ensureCapacityForSetLocation(gpa);
969 ni.setLocationAssumeCapacity(mf, old_offset, new_size);
970 return;
971 }
972 insert_range: {
973 if (!is_linux) break :insert_range;
974 if (mf.flags.fallocate_insert_range_unsupported) break :insert_range;
975
976 // We need the node to be aligned to `mf.flags.block_size` in the file in order to use this
977 // fast path. It is not sufficient to check `node.flags.alignment`, because that doesn't
978 // necessarily mean that all *parent* nodes are equally aligned; instead we must compute the
979 // actual file offset.
980 const range_file_offset = ni.fileLocation(mf, false).offset + old_size;
981 const range_size = node.flags.alignment.forward(
982 @intCast(requested_size +| requested_size / growth_factor),
983 ) - old_size;
984 if (!mf.flags.block_size.check(@intCast(range_file_offset))) break :insert_range;
985 if (!mf.flags.block_size.check(@intCast(range_size))) break :insert_range;
986
987 mf.memory_map.write(io) catch |err| switch (err) {
988 error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking
989 error.NotOpenForWriting => return error.Unexpected, // we definitely opened the file for writing
990 else => |e| return e,
1118
1119 if (try mf.growNodeViaInsertRange(gpa, ni, new_size, grow_mode)) {
1120 return;
1121 }
1122
1123 mf.memory_map.write(mf.io) catch |err| {
1124 mf.io_err = switch (err) {
1125 error.Canceled => |e| return e,
1126 error.WouldBlock => error.Unexpected, // file was not opened as non-blocking
1127 error.NotOpenForWriting => error.Unexpected, // we definitely opened the file for writing
1128 else => |e| e,
1129 };
1130 return error.MappedFileIo;
9911131 };
992 // Ask the filesystem driver to insert extents into the file without copying any data
993 const last_offset, const last_size = parent.last.unwrap().?.location(mf).resolve(mf);
994 const last_end = last_offset + last_size;
995 assert(last_end <= old_parent_size);
996 _, const file_size = Node.Index.root.location(mf).resolve(mf);
997 while (true) switch (linux.errno(switch (std.math.order(range_file_offset, file_size)) {
998 .lt => linux.fallocate(
999 mf.memory_map.file.handle,
1000 linux.FALLOC.FL_INSERT_RANGE,
1001 @intCast(range_file_offset),
1002 @intCast(range_size),
1003 ),
1004 .eq => linux.ftruncate(mf.memory_map.file.handle, @intCast(range_file_offset + range_size)),
1005 .gt => unreachable,
1006 })) {
1007 .SUCCESS => {
1008 var enclosing_ni = ni;
1009 while (true) {
1010 try mf.ensureCapacityForSetLocation(gpa);
1011 const enclosing = enclosing_ni.get(mf);
1012 const enclosing_offset, const old_enclosing_size =
1013 enclosing.location().resolve(mf);
1014 const new_enclosing_size = old_enclosing_size + range_size;
1015 enclosing_ni.setLocationAssumeCapacity(mf, enclosing_offset, new_enclosing_size);
1016 if (enclosing_ni == .root) {
1017 assert(enclosing_offset == 0);
1018 try mf.ensureTotalCapacityInner(@intCast(new_enclosing_size));
1019 break;
1020 }
1021 var after_oni = enclosing.next;
1022 while (after_oni.unwrap()) |after_ni| {
1023 try mf.ensureCapacityForSetLocation(gpa);
1024 const after = after_ni.get(mf);
1025 const after_offset, const after_size = after.location().resolve(mf);
1026 after_ni.setLocationAssumeCapacity(
1027 mf,
1028 range_size + after_offset,
1029 after_size,
1030 );
1031 after_oni = after.next;
1032 }
1033 enclosing_ni = enclosing.parent.unwrap().?;
1034 }
1035 return;
1036 },
1037 .INTR => continue,
1038 .BADF, .FBIG, .INVAL => unreachable,
1039 .IO => return error.InputOutput,
1040 .NODEV => return error.NotFile,
1041 .NOSPC => return error.NoSpaceLeft,
1042 .NOSYS, .OPNOTSUPP => {
1043 mf.flags.fallocate_insert_range_unsupported = true;
1044 break :insert_range;
1132 mf.memory_map.file.setLength(mf.io, new_size) catch |err| switch (err) {
1133 error.Canceled => |e| return e,
1134 else => |e| {
1135 mf.io_err = e;
1136 return error.MappedFileIo;
10451137 },
1046 .PERM => return error.PermissionDenied,
1047 .SPIPE => return error.Unseekable,
1048 .TXTBSY => return error.FileBusy,
1049 else => |e| return std.posix.unexpectedErrno(e),
10501138 };
1051 }
1052 if (node.next == .none) {
1053 // As this is the last node, we simply need more space in the parent
1054 const new_parent_size = old_offset + new_size;
1055 try mf.resizeNode(gpa, parent_ni, new_parent_size +| new_parent_size / growth_factor);
1056 try mf.ensureCapacityForSetLocation(gpa);
1057 ni.setLocationAssumeCapacity(mf, old_offset, new_size);
1058 return;
1059 }
1060 if (!node.flags.fixed) {
1061 // Make space at the end of the parent for this floating node
1062 const last = parent.last.unwrap().?.get(mf);
1063 const last_offset, const last_size = last.location().resolve(mf);
1064 const new_offset = node.flags.alignment.forward(@intCast(last_offset + last_size));
1065 const new_parent_size = new_offset + new_size;
1066 if (new_parent_size > old_parent_size)
1067 try mf.resizeNode(gpa, parent_ni, new_parent_size +| new_parent_size / growth_factor);
1068 try mf.ensureCapacityForSetLocation(gpa);
1069 const next_ni = node.next.unwrap().?;
1070 next_ni.get(mf).prev = node.prev;
1071 if (node.prev.unwrap()) |prev_ni| {
1072 try prev_ni.setNext(gpa, .wrap(next_ni), mf);
1073 } else {
1074 parent.first = .wrap(next_ni);
1075 }
1076 try parent.last.unwrap().?.setNext(gpa, .wrap(ni), mf);
1077 node.prev = parent.last;
1078 try ni.setNext(gpa, .none, mf);
1079 parent.last = .wrap(ni);
1080 if (node.flags.has_content) {
1081 const parent_file_offset = parent_ni.fileLocation(mf, false).offset;
1139 try mf.ensureTotalCapacityPrecise(@intCast(new_size));
1140 try ni.setLocation(mf, gpa, old_offset, new_size);
1141 // We need to move any footers to be at the *new* end of the file.
1142 if (ni.firstFooter(mf).unwrap()) |first_footer_ni| {
1143 const old_footers_offset, _ = first_footer_ni.location(mf).resolve(mf);
1144 const footers_size = old_size - old_footers_offset;
10821145 try mf.moveRange(
1083 parent_file_offset + old_offset,
1084 parent_file_offset + new_offset,
1085 old_size,
1146 old_footers_offset,
1147 old_footers_offset + (new_size - old_size),
1148 footers_size,
10861149 );
1150 // Also update the footers' locations.
1151 var cur_ni = first_footer_ni;
1152 while (true) {
1153 const old_footer_offset, const footer_size = cur_ni.location(mf).resolve(mf);
1154 try cur_ni.setLocation(mf, gpa, old_footer_offset + (new_size - old_size), footer_size);
1155 cur_ni = cur_ni.next(mf).unwrap() orelse break;
1156 }
10871157 }
1088 ni.setLocationAssumeCapacity(mf, new_offset, new_size);
10891158 return;
1090 }
1091 // Search for the first floating node following this fixed node
1092 var last_fixed_ni = ni;
1093 var first_floating_oni = node.next;
1094 var shift = new_size - old_size;
1095 var max_shift_align: Alignment = .@"1";
1096 var direction: enum { forward, reverse } = .forward;
1097 while (true) {
1098 const last_fixed = last_fixed_ni.get(mf);
1099 assert(last_fixed.flags.fixed);
1100 const old_last_fixed_offset, const last_fixed_size = last_fixed.location().resolve(mf);
1101 const new_last_fixed_offset = old_last_fixed_offset + shift;
1102 if (first_floating_oni.unwrap()) |first_floating_ni| make_space: {
1103 const first_floating = first_floating_ni.get(mf);
1104 const old_first_floating_offset, const first_floating_size =
1105 first_floating.location().resolve(mf);
1106 assert(old_last_fixed_offset + last_fixed_size <= old_first_floating_offset);
1107 if (new_last_fixed_offset + last_fixed_size <= old_first_floating_offset)
1108 break :make_space;
1109 assert(direction == .forward);
1110 max_shift_align = max_shift_align.max(first_floating.flags.alignment.max(last_fixed.flags.alignment));
1111 if (first_floating.flags.fixed) {
1112 shift = max_shift_align.forward(@intCast(
1113 @max(shift, first_floating_size),
1114 ));
1115
1116 // Not enough space, try the next node
1117 last_fixed_ni = first_floating_ni;
1118 first_floating_oni = first_floating.next;
1119 continue;
1120 }
1121 // Move the found floating node to make space for preceding fixed nodes
1122 const last = parent.last.unwrap().?.get(mf);
1123 const last_offset, const last_size = last.location().resolve(mf);
1124 const new_first_floating_offset = max_shift_align.forward(
1125 @intCast(@max(new_last_fixed_offset + last_fixed_size, last_offset + last_size)),
1126 );
1127 const new_parent_size = new_first_floating_offset + first_floating_size;
1128 if (new_parent_size > old_parent_size) {
1129 try mf.resizeNode(
1130 gpa,
1131 parent_ni,
1132 new_parent_size +| new_parent_size / growth_factor,
1133 );
1134 _, old_parent_size = parent.location().resolve(mf);
1159 };
1160
1161 switch (node.flags.position) {
1162 .header => {
1163 if (try mf.growNodeViaInsertRange(gpa, ni, new_size, grow_mode)) {
1164 return;
11351165 }
1136 try mf.ensureCapacityForSetLocation(gpa);
1137 if (parent.last.unwrap().? != first_floating_ni) {
1138 const old_last = parent.last.unwrap().?;
1139 first_floating.prev = .wrap(old_last);
1140 parent.last = .wrap(first_floating_ni);
1141 try old_last.setNext(gpa, .wrap(first_floating_ni), mf);
1142 try last_fixed_ni.setNext(gpa, first_floating.next, mf);
1143 if (first_floating.next.unwrap()) |next_ni| {
1144 next_ni.get(mf).prev = .wrap(last_fixed_ni);
1166
1167 try mf.ensureAdditionalHeaderCapacity(gpa, parent_ni, new_size - old_size);
1168
1169 // `old_offset` is still valid because header nodes don't move when the parent resizes.
1170
1171 const last_header_ni: Node.Index = last_header: {
1172 var header_ni = ni;
1173 while (true) {
1174 const next_ni = header_ni.next(mf).unwrap() orelse break;
1175 if (next_ni.position(mf) != .header) break;
1176 header_ni = next_ni;
11451177 }
1146 try first_floating_ni.setNext(gpa, .none, mf);
1147 }
1148 if (first_floating.flags.has_content) {
1149 const parent_file_offset =
1150 parent_ni.fileLocation(mf, false).offset;
1151 try mf.moveRange(
1152 parent_file_offset + old_first_floating_offset,
1153 parent_file_offset + new_first_floating_offset,
1154 first_floating_size,
1155 );
1156 }
1157 first_floating_ni.setLocationAssumeCapacity(
1158 mf,
1159 new_first_floating_offset,
1160 first_floating_size,
1161 );
1162 // Continue the search after the just-moved floating node
1163 first_floating_oni = last_fixed.next;
1164 continue;
1165 } else {
1166 assert(direction == .forward);
1167 const new_parent_size = new_last_fixed_offset + last_fixed_size;
1168 if (new_parent_size > old_parent_size) {
1169 try mf.resizeNode(
1170 gpa,
1171 parent_ni,
1172 new_parent_size +| new_parent_size / growth_factor,
1173 );
1174 _, old_parent_size = parent.location().resolve(mf);
1175 }
1176 }
1177 try mf.ensureCapacityForSetLocation(gpa);
1178 if (last_fixed_ni == ni) {
1179 // The original fixed node now has enough space
1180 last_fixed_ni.setLocationAssumeCapacity(
1181 mf,
1182 old_last_fixed_offset,
1183 new_size,
1184 );
1185 return;
1186 }
1187 // Move a fixed node into trailing free space
1188 if (last_fixed.flags.has_content) {
1178 break :last_header header_ni;
1179 };
1180 const last_header_offset, const last_header_size = last_header_ni.location(mf).resolve(mf);
1181 const old_headers_size = last_header_offset + last_header_size;
1182
1183 // This is the first footer *inside* of `ni`.
1184 const first_sub_footer_oni = ni.firstFooter(mf);
1185 const sub_footers_size = size: {
1186 const first_sub_footer_ni = first_sub_footer_oni.unwrap() orelse break :size 0;
1187 const first_sub_footer_offset, _ = first_sub_footer_ni.location(mf).resolve(mf);
1188 break :size old_size - first_sub_footer_offset;
1189 };
1190
1191 // We need to shift two things forwards; any header nodes which follow us, and any
1192 // footer nodes *within* us (since they need to be at the end of our new size).
11891193 const parent_file_offset = parent_ni.fileLocation(mf, false).offset;
11901194 try mf.moveRange(
1191 parent_file_offset + old_last_fixed_offset,
1192 parent_file_offset + new_last_fixed_offset,
1193 last_fixed_size,
1195 parent_file_offset + old_offset + old_size - sub_footers_size,
1196 parent_file_offset + old_offset + new_size - sub_footers_size,
1197 old_headers_size - (old_offset + old_size - sub_footers_size),
11941198 );
1195 }
1196 last_fixed_ni.setLocationAssumeCapacity(mf, new_last_fixed_offset, last_fixed_size);
1197 // Retry the previous nodes now that there is enough space
1198 first_floating_oni = .wrap(last_fixed_ni);
1199 last_fixed_ni = last_fixed.prev.unwrap().?;
1200 direction = .reverse;
1201 }
1202}
1203
1204fn realignNode(
1205 mf: *MappedFile,
1206 gpa: Allocator,
1207 ni: Node.Index,
1208 new_alignment: Alignment,
1209 opts: Node.Index.RealignNodeOptions,
1210) (Allocator.Error || Io.Cancelable || IoError)!void {
1211 mf.nodes_lock.assertUnlocked();
12121199
1213 const node = ni.get(mf);
1214 {
1215 const prev_alignment = node.flags.alignment;
1216 node.flags.alignment = new_alignment;
1217 if (new_alignment.compare(.lte, prev_alignment)) return;
1200 // Any footers inside of us have had their offsets changed due to us growing:
1201 if (first_sub_footer_oni.unwrap()) |first_sub_footer_ni| {
1202 var cur_ni = first_sub_footer_ni;
1203 while (true) {
1204 const old_sub_footer_offset, const sub_footer_size = cur_ni.location(mf).resolve(mf);
1205 try cur_ni.setLocation(
1206 mf,
1207 gpa,
1208 old_sub_footer_offset + (new_size - old_size),
1209 sub_footer_size,
1210 );
1211 cur_ni = cur_ni.next(mf).unwrap() orelse break;
1212 }
1213 }
1214
1215 // Update the offsets of all header nodes following us:
1216 {
1217 var moved_header_ni = last_header_ni;
1218 while (moved_header_ni != ni) {
1219 assert(moved_header_ni.position(mf) == .header);
1220 const moved_header_offset, const moved_header_size = moved_header_ni.location(mf).resolve(mf);
1221 try moved_header_ni.setLocation(
1222 mf,
1223 gpa,
1224 moved_header_offset - old_size + new_size,
1225 moved_header_size,
1226 );
1227 moved_header_ni = moved_header_ni.prev(mf).unwrap().?;
1228 }
1229 }
1230
1231 // Finally, update our own size:
1232 try ni.setLocation(mf, gpa, old_offset, new_size);
1233 return;
1234 },
1235 .floating => {
1236 try mf.growFloatingNodeWithAlignment(gpa, ni, null, new_size, grow_mode);
1237 },
1238 .footer => {
1239 if (try mf.growNodeViaInsertRange(gpa, ni, new_size, grow_mode)) {
1240 return;
1241 }
1242
1243 try mf.ensureAdditionalFooterCapacity(gpa, parent_ni, new_size - old_size);
1244
1245 const first_footer_ni: Node.Index = first_footer: {
1246 var footer_ni = ni;
1247 while (true) {
1248 const prev_ni = footer_ni.prev(mf).unwrap() orelse break;
1249 if (prev_ni.position(mf) != .footer) break;
1250 footer_ni = prev_ni;
1251 }
1252 break :first_footer footer_ni;
1253 };
1254
1255 // This is the first footer *inside* of `ni` (unrelated to the fact that `ni` is itself
1256 // a footer within its parent).
1257 const first_sub_footer_oni = ni.firstFooter(mf);
1258 const sub_footers_size = size: {
1259 const first_sub_footer_ni = first_sub_footer_oni.unwrap() orelse break :size 0;
1260 const first_sub_footer_offset, _ = first_sub_footer_ni.location(mf).resolve(mf);
1261 break :size old_size - first_sub_footer_offset;
1262 };
1263
1264 _, const parent_size = parent_ni.location(mf).resolve(mf);
1265
1266 const old_footers_size = parent_size - first_footer_ni.location(mf).resolve(mf)[0];
1267 const new_footers_size = old_footers_size - old_size + new_size;
1268
1269 // Shift ourselves, and any footer before us, backwards. Unlike header nodes, this node
1270 // itself needs to shift its contents, because our offset was shifted backwards by
1271 // `new_size - old_size`, and the added bytes should go at the end of this footer node.
1272 // However, if we *contain* any footer nodes, they need to stay at the end of `ni`, so
1273 // we *shouldn't* shift *that* data.
1274 const old_footers_start = parent_size - old_footers_size;
1275 const new_footers_start = parent_size - new_footers_size;
1276 const end_offset = node.location().resolve(mf)[0] + old_size;
1277 const parent_file_offset = parent_ni.fileLocation(mf, false).offset;
1278 try mf.moveRange(
1279 parent_file_offset + old_footers_start,
1280 parent_file_offset + new_footers_start,
1281 end_offset - old_footers_start - sub_footers_size,
1282 );
1283
1284 // Update our own offset and size:
1285 try ni.setLocation(mf, gpa, end_offset - new_size, new_size);
1286
1287 // Any footers inside of us have had their offsets changed due to us growing:
1288 if (first_sub_footer_oni.unwrap()) |first_sub_footer_ni| {
1289 var cur_ni = first_sub_footer_ni;
1290 while (true) {
1291 const old_sub_footer_offset, const sub_footer_size = cur_ni.location(mf).resolve(mf);
1292 try cur_ni.setLocation(
1293 mf,
1294 gpa,
1295 old_sub_footer_offset + (new_size - old_size),
1296 sub_footer_size,
1297 );
1298 cur_ni = cur_ni.next(mf).unwrap() orelse break;
1299 }
1300 }
1301
1302 // Finally, update the offsets of every footer before us:
1303 if (node.prev.unwrap()) |prev_ni| {
1304 var maybe_footer_ni = prev_ni;
1305 while (true) {
1306 switch (maybe_footer_ni.position(mf)) {
1307 .header, .floating => break,
1308 .footer => {},
1309 }
1310 const moved_footer_offset, const moved_footer_size = maybe_footer_ni.location(mf).resolve(mf);
1311 try maybe_footer_ni.setLocation(
1312 mf,
1313 gpa,
1314 moved_footer_offset + old_size - new_size,
1315 moved_footer_size,
1316 );
1317 maybe_footer_ni = maybe_footer_ni.prev(mf).unwrap() orelse break;
1318 }
1319 }
1320
1321 return;
1322 },
12181323 }
1324}
12191325
1220 const old_offset, const size = node.location().resolve(mf);
1221 const parent_ni = node.parent.unwrap() orelse {
1222 assert(ni == .root);
1223 return mf.resizeNode(gpa, ni, size);
1224 };
1326/// Moves a floating node to an unused region with the given size, which may be greater than the
1327/// current size. If `new_alignment` is not `null`, then the offset and size of the new region will
1328/// have that alignment instead of `ni.alignment(mf)`.
1329///
1330/// Asserts that `ni` is a floating node (and not `.root`).
1331///
1332/// Asserts that `new_size` is aligned to `new_alignment orelse ni.alignment(mf)`.
1333///
1334/// Asserts that `new_size` is greater than or equal to the current size of `ni`.
1335fn growFloatingNodeWithAlignment(
1336 mf: *MappedFile,
1337 gpa: Allocator,
1338 ni: Node.Index,
1339 new_alignment: ?Alignment,
1340 new_size: u64,
1341 grow_mode: GrowMode,
1342) Error!void {
1343 mf.nodes_lock.assertUnlocked();
12251344
1226 const new_size = new_alignment.forward(@intCast(size));
1227 if (new_alignment.check(@intCast(old_offset))) return mf.resizeNode(gpa, ni, new_size);
1345 const parent_ni = ni.parent(mf).unwrap().?; // `ni` cannot be `.root`
1346 const old_offset, const old_size = ni.location(mf).resolve(mf);
12281347
1229 _, const parent_size = parent_ni.location(mf).resolve(mf);
1230 const trailing_end = trailing_end: {
1231 const next_ni = node.next.unwrap() orelse break :trailing_end parent_size;
1232 const next_offset, _ = next_ni.location(mf).resolve(mf);
1233 break :trailing_end next_offset;
1234 };
1348 const alignment = new_alignment orelse ni.alignment(mf);
12351349
1236 if (opts.try_backwards) {
1237 const backward_offset = new_alignment.backward(@intCast(old_offset));
1238 const prev_end = prev_end: {
1239 const prev_ni = node.prev.unwrap() orelse break :prev_end 0;
1240 const prev_offset, const prev_size = prev_ni.location(mf).resolve(mf);
1241 break :prev_end prev_offset + prev_size;
1350 assert(new_size >= old_size);
1351 assert(ni.position(mf) == .floating);
1352 assert(alignment.check(new_size));
1353
1354 grow_in_place: {
1355 if (!alignment.check(old_offset)) {
1356 break :grow_in_place;
1357 }
1358 const limit: u64 = limit: {
1359 const next_ni = ni.next(mf).unwrap() orelse break :limit parent_ni.location(mf).resolve(mf)[1];
1360 const next_offset, _ = next_ni.location(mf).resolve(mf);
1361 break :limit next_offset;
12421362 };
1363 if (old_offset + new_size > limit) {
1364 break :grow_in_place; // the parent is not big enough
1365 }
1366 // Great, we can grow this node without changing its offset or moving any siblings.
1367 try ni.setLocation(mf, gpa, old_offset, new_size);
1368 // If we have any footers, we need to move them to the end of our new size, and update their
1369 // offsets accordingly.
1370 if (ni.firstFooter(mf).unwrap()) |first_footer_ni| {
1371 var cur_ni = first_footer_ni;
1372 var footers_have_content = false;
1373 while (true) {
1374 footers_have_content = footers_have_content or cur_ni.get(mf).flags.has_content;
1375 const old_footer_offset, const footer_size = cur_ni.location(mf).resolve(mf);
1376 try cur_ni.setLocation(mf, gpa, old_footer_offset + (new_size - old_size), footer_size);
1377 cur_ni = cur_ni.next(mf).unwrap() orelse break;
1378 }
1379 if (footers_have_content) {
1380 const parent_file_off = parent_ni.fileLocation(mf, false).offset;
1381 // This gets the *new* offset because we already updated the offsets above.
1382 const new_footers_offset, _ = first_footer_ni.location(mf).resolve(mf);
1383 const footers_size = new_size - new_footers_offset;
1384 try mf.moveRange(
1385 parent_file_off + old_offset + old_size - footers_size,
1386 parent_file_off + old_offset + new_size - footers_size,
1387 footers_size,
1388 );
1389 }
1390 }
1391 return;
1392 }
12431393
1244 if (backward_offset >= prev_end) {
1245 try mf.ensureCapacityForSetLocation(gpa);
1394 const new_loc: struct {
1395 offset: u64,
1396 prev: Node.Index.Optional,
1397 } = new_loc: {
1398 _, const parent_size = parent_ni.location(mf).resolve(mf);
1399
1400 {
1401 // See if there's space at the start of the parent.
1402 const last_header_oni = parent_ni.lastHeader(mf);
1403 const headers_end: u64 = if (last_header_oni.unwrap()) |last_header_ni| headers_end: {
1404 const last_header_off, const last_header_size = last_header_ni.location(mf).resolve(mf);
1405 break :headers_end last_header_off + last_header_size;
1406 } else 0;
1407 const limit: u64 = limit: {
1408 const after_header_oni: Node.Index.Optional = after_header: {
1409 if (last_header_oni.unwrap()) |last_header_ni| {
1410 break :after_header last_header_ni.next(mf);
1411 }
1412 break :after_header parent_ni.first(mf);
1413 };
1414 if (after_header_oni.unwrap()) |after_header_ni| {
1415 break :limit after_header_ni.location(mf).resolve(mf)[0];
1416 } else {
1417 break :limit parent_size;
1418 }
1419 };
1420 if (alignment.forward(headers_end) + new_size <= limit) {
1421 // There's space here!
1422 break :new_loc .{
1423 // Put ourselves at the *end* of this range, so that the free space remains at the start of the parent.
1424 .offset = alignment.backward(limit - new_size),
1425 .prev = last_header_oni,
1426 };
1427 }
1428 }
12461429
1247 if (node.flags.has_content) {
1248 const old_file_offset = ni.fileLocation(mf, false).offset;
1249 const new_file_offset = (old_file_offset - old_offset) + backward_offset;
1250 @memmove(
1251 mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(size)],
1252 mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(size)],
1253 );
1254 @memset(mf.memory_map.memory[@intCast(new_file_offset + size)..@intCast(old_file_offset + size)], 0);
1430 // Otherwise, use space at the end of the parent, or make space there if necessary.
1431
1432 const first_footer_oni = parent_ni.firstFooter(mf);
1433
1434 // We know there is a node before the footer[s], because `ni` itself is such a node.
1435 const prev_ni: Node.Index = if (first_footer_oni.unwrap()) |first_footer_ni| prev: {
1436 break :prev first_footer_ni.prev(mf).unwrap().?;
1437 } else prev: {
1438 break :prev parent_ni.last(mf).unwrap().?;
1439 };
1440
1441 const result_offset: u64 = result_offset: {
1442 if (prev_ni == ni and alignment.check(old_offset)) {
1443 // We're already at the end of the parent, and our offset is already well-aligned.
1444 // The only reason we didn't simply grow in place earlier is that the parent wasn't
1445 // big enough---but now we're resizing the parent anyway, so growing in-place stops
1446 // us from unnecessarily moving!
1447 break :result_offset old_offset;
12551448 }
1449 // Otherwise, just move after the last node.
1450 const prev_offset, const prev_size = prev_ni.location(mf).resolve(mf);
1451 break :result_offset alignment.forward(prev_offset + prev_size);
1452 };
12561453
1257 if (backward_offset + new_size <= trailing_end) {
1258 ni.setLocationAssumeCapacity(mf, backward_offset, new_size);
1259 } else {
1260 ni.setLocationAssumeCapacity(mf, backward_offset, size);
1261 try mf.resizeNode(gpa, ni, new_size);
1454 const footers_size: u64 = if (first_footer_oni.unwrap()) |first_footer_ni| footers_size: {
1455 const first_footer_offset, _ = first_footer_ni.location(mf).resolve(mf);
1456 break :footers_size parent_size - first_footer_offset;
1457 } else 0;
1458
1459 const min_parent_size = result_offset + new_size + footers_size;
1460 if (parent_size < min_parent_size) {
1461 // Okay, at this point we're planning to expand the parent---so before we actually do
1462 // that, let's first try the Linux "insert range" fast path. We didn't try it before now
1463 // because it would have been more efficient to just move ourselves into existing space.
1464 //
1465 // If we were given a custom alignment, we cannot pass `grow_mode` directly into the
1466 // "insert range" path, because that function is unaware of `new_alignment`.
1467 const sub_grow_mode: GrowMode = if (new_alignment == null) grow_mode else .exact;
1468 if (alignment.check(old_offset) and
1469 try mf.growNodeViaInsertRange(gpa, ni, new_size, sub_grow_mode))
1470 {
1471 // The Linux fast path did our job for us!
1472 return;
12621473 }
12631474
1264 return;
1475 // Grow the parent and move to the end of the parent.
1476 const new_parent_size = parent_ni.alignment(mf).forward(
1477 min_parent_size +| min_parent_size / growth_factor,
1478 );
1479 try mf.growNode(gpa, parent_ni, new_parent_size, .minimum);
1480 }
1481
1482 break :new_loc .{
1483 .offset = result_offset,
1484 .prev = .wrap(prev_ni),
1485 };
1486 };
1487
1488 // We've found our new location in `parent_ni`, now to actually move ourselves there.
1489
1490 // Footers need to move to a different place than the rest of our content.
1491 const footers_size: u64, const footers_have_content: bool = footers: {
1492 const first_footer_ni = ni.firstFooter(mf).unwrap() orelse {
1493 break :footers .{ 0, false };
1494 };
1495
1496 var cur_ni = first_footer_ni;
1497 var footers_have_content = false;
1498 while (true) {
1499 footers_have_content = footers_have_content or cur_ni.get(mf).flags.has_content;
1500 const old_footer_offset, const footer_size = cur_ni.location(mf).resolve(mf);
1501 // Our footers' offsets must change to be at the end of our new size.
1502 try cur_ni.setLocation(mf, gpa, old_footer_offset + (new_size - old_size), footer_size);
1503 cur_ni = cur_ni.next(mf).unwrap() orelse break;
12651504 }
1505
1506 // This is the *new* offset because we already updated the offsets above.
1507 const new_footers_offset, _ = first_footer_ni.location(mf).resolve(mf);
1508 const footers_size = new_size - new_footers_offset;
1509
1510 break :footers .{ footers_size, footers_have_content };
1511 };
1512
1513 if (ni.get(mf).flags.has_content) {
1514 const parent_file_off = parent_ni.fileLocation(mf, false).offset;
1515 try mf.moveRange(
1516 parent_file_off + old_offset,
1517 parent_file_off + new_loc.offset,
1518 old_size - footers_size,
1519 );
1520 if (footers_have_content) try mf.moveRange(
1521 parent_file_off + old_offset + old_size - footers_size,
1522 parent_file_off + new_loc.offset + new_size - footers_size,
1523 footers_size,
1524 );
1525 } else {
1526 assert(!footers_have_content);
12661527 }
12671528
1268 const forward_offset = new_alignment.forward(@intCast(old_offset));
1269 if (forward_offset + new_size <= trailing_end) {
1270 // Shift into the free space if possible
1271 try mf.ensureCapacityForSetLocation(gpa);
1272 if (node.flags.has_content) {
1273 const old_file_offset = ni.fileLocation(mf, false).offset;
1274 const new_file_offset = (old_file_offset - old_offset) + forward_offset;
1275 if (new_file_offset < old_file_offset + size) {
1276 @memmove(
1277 mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(size)],
1278 mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(size)],
1279 );
1280 } else try mf.moveRange(old_file_offset, new_file_offset, size);
1281 @memset(mf.memory_map.memory[@intCast(new_file_offset + size)..][0..@intCast(new_size - size)], 0);
1529 try ni.setLocation(mf, gpa, new_loc.offset, new_size);
1530
1531 if (new_loc.prev != ni.toOptional()) {
1532 // We're potentially in a different place in `parent_ni`'s child list, so remove and re-add ourselves.
1533 try mf.removeNodesFromChildList(gpa, ni, ni);
1534 try mf.addNodesToChildListAfter(gpa, new_loc.prev, ni, ni);
1535 }
1536}
1537
1538/// Attempts to grow `ni` to `new_size` using `FALLOCATE_FL_INSERT_RANGE` on Linux. This strategy
1539/// has the advantage that it does not require manually moving any bytes in the file, but has the
1540/// disadvantages that it may increase the file size more than necessary, and that it changes the
1541/// offsets of all following nodes, recursively.
1542///
1543/// If this strategy is inapplicable or unsuitable for this operation, this function returns `false`
1544/// without changing any nodes' locations or invalidating any slices.
1545///
1546/// Otherwise, this function grows `ni` to `new_size`, updates the location of `ni` and every node
1547/// whose offset has changed, and returns `true`. Like in `growNode`, if `grow_mode` is `.minimum`,
1548/// the actual new size of `ni` may be greater than `new_size`.
1549fn growNodeViaInsertRange(
1550 mf: *MappedFile,
1551 gpa: Allocator,
1552 ni: Node.Index,
1553 new_size: u64,
1554 grow_mode: GrowMode,
1555) Error!bool {
1556 if (!is_linux or mf.flags.fallocate_insert_range_unsupported) {
1557 return false;
1558 }
1559
1560 _, const old_size = ni.location(mf).resolve(mf);
1561
1562 // We don't compute the size of the range yet, because depending on `grow_mode` we might want to
1563 // bump it based on our sibling and parent nodes' alignments. However, we can do an early check
1564 // for cases where we should obviously exit.
1565 const requested_range_size = new_size - old_size;
1566 if (!mf.flags.block_size.check(requested_range_size)) {
1567 // The requested size isn't exactly aligned.
1568 switch (grow_mode) {
1569 .exact => return false,
1570 .minimum => {
1571 // We can still choose to allow it by increasing the size a bit, but we shouldn't do
1572 // that if it would *significantly* increase the requested size.
1573 const block_size = mf.flags.block_size.toByteUnits();
1574 if (requested_range_size < block_size * 2) {
1575 // Bumping this size up to the next block boundary would be a quite significant
1576 // increase; let's not do it.
1577 return false;
1578 }
1579 },
1580 }
1581 }
1582 // If `grow_mode` is exact, we will use exactly this size, but if it is `.minimum`, we may bump
1583 // the size a little more.
1584 const min_range_size: u64 = s: {
1585 const exact_size = new_size - old_size;
1586 if (mf.flags.block_size.check(exact_size)) {
1587 break :s exact_size;
1588 }
1589 switch (grow_mode) {
1590 .exact => return false,
1591 .minimum => if (exact_size >= mf.flags.block_size.toByteUnits() * 2) {
1592 // We're growing by at least a few blocks, so allow ourselves to bump the size
1593 // slightly to give it the needed alignment.
1594 break :s mf.flags.block_size.forward(exact_size);
1595 } else {
1596 return false;
1597 },
1598 }
1599 };
1600 assert(min_range_size > 0);
1601 assert(mf.flags.block_size.check(min_range_size));
1602
1603 const range_file_offset: u64 = range_file_offset: {
1604 const node_file_offset = ni.fileLocation(mf, false).offset;
1605 const last_ni = ni.last(mf).unwrap() orelse {
1606 // If `ni` has no children (i.e. is a leaf node), we need to insert exactly at its end.
1607 const range_file_offset = node_file_offset + old_size;
1608 if (!mf.flags.block_size.check(range_file_offset)) {
1609 return false;
1610 }
1611 break :range_file_offset range_file_offset;
1612 };
1613 const first_footer_oni = ni.firstFooter(mf);
1614 const footers_size: u64 = if (first_footer_oni.unwrap()) |first_footer_ni| size: {
1615 const first_footer_offset, _ = first_footer_ni.location(mf).resolve(mf);
1616 break :size old_size - first_footer_offset;
1617 } else 0;
1618 const pre_footer_oni: Node.Index.Optional = if (first_footer_oni.unwrap()) |first_footer_ni| pre_footer: {
1619 break :pre_footer first_footer_ni.prev(mf);
1620 } else .wrap(last_ni);
1621 const pre_footer_end: u64 = if (pre_footer_oni.unwrap()) |pre_footer_ni| end: {
1622 const pre_footer_off, const pre_footer_size = pre_footer_ni.location(mf).resolve(mf);
1623 break :end pre_footer_off + pre_footer_size;
1624 } else 0;
1625
1626 const min_file_offset = node_file_offset + pre_footer_end;
1627 const max_file_offset = node_file_offset + old_size - footers_size;
1628 // We can go anywhere between `min_file_offset` and `max_file_offset`.
1629 const candidate_file_offset = mf.flags.block_size.forward(min_file_offset);
1630 if (candidate_file_offset > max_file_offset) {
1631 return false;
12821632 }
1633 break :range_file_offset candidate_file_offset;
1634 };
1635 assert(mf.flags.block_size.check(range_file_offset));
1636
1637 const range_size: u64 = range_size: {
1638 // For this strategy to be valid, the number of bytes we insert needs to be compatible with
1639 // the alignments of all nodes following us (and following our parents, their parents, etc).
1640 // We also probably don't want to trigger too many "node moved" events, since doing that
1641 // repeatedly could result in a lot of extra work. Therefore, while we traverse parents and
1642 // siblings to check their alignment requirements, we will also set an arbitrary limit on
1643 // the number of nodes we can move, and give up if we walk more than that.
1644 const max_moved_nodes = 32;
1645 var num_moved: u32 = 0;
1646 var cur_ni = ni;
1647 // Alignment required for `range_size`: initially the block size (required for the syscall),
1648 // then updated as we traverse based on how the operation would affect surrounding nodes.
1649 var need_range_align: Alignment = mf.flags.block_size.max(ni.alignment(mf));
1650 while (true) {
1651 // `cur_ni` will grow as a result of the range insertion. Its size must be well-aligned.
1652 need_range_align = need_range_align.max(cur_ni.alignment(mf));
1653
1654 // Siblings following `cur_ni` don't get bigger, but their offsets change.
1655 while (cur_ni.next(mf).unwrap()) |next_ni| {
1656 // Only floating children need well-aligned offsets.
1657 if (next_ni.position(mf) == .floating) {
1658 need_range_align = need_range_align.max(next_ni.alignment(mf));
1659 }
1660 num_moved += 1;
1661 if (num_moved > max_moved_nodes) return false;
1662 cur_ni = next_ni;
1663 }
1664
1665 // Move up to the parent.
1666 cur_ni = cur_ni.parent(mf).unwrap() orelse break;
1667 }
1668 // Traversal done. We didn't hit `max_moved_nodes`, so now we can use the computed alignment
1669 // requirement to figure out whether we're actually going to insert a range.
1670 if (need_range_align.check(requested_range_size)) {
1671 break :range_size requested_range_size;
1672 }
1673 // Perhaps we're allowed to grow by more than `requested_range_size`?
1674 switch (grow_mode) {
1675 .exact => return false,
1676 .minimum => {
1677 const candidate_range_size = need_range_align.forward(min_range_size);
1678 // Allow growing by up to 50% more than was requested.
1679 if (candidate_range_size <= requested_range_size +| requested_range_size / 2) {
1680 break :range_size candidate_range_size;
1681 } else {
1682 return false;
1683 }
1684 },
1685 }
1686 };
1687
1688 // This `range_size` is compatible with everyone's alignment requirements, and we won't move too
1689 // many nodes, so let's do it!
1690
1691 mf.memory_map.write(mf.io) catch |err| {
1692 mf.io_err = switch (err) {
1693 error.Canceled => |e| return e,
1694 error.WouldBlock => error.Unexpected, // file was not opened as non-blocking
1695 error.NotOpenForWriting => error.Unexpected, // we definitely opened the file for writing
1696 else => |e| e,
1697 };
1698 return error.MappedFileIo;
1699 };
12831700
1284 ni.setLocationAssumeCapacity(mf, forward_offset, new_size);
1701 // If we happen to be inserting at the very end of the file, we need to resize the file instead
1702 // of using `FALLOCATE_FL_INSERT_RANGE`.
1703 if (range_file_offset == Node.Index.root.location(mf).resolve(mf)[1]) {
1704 mf.memory_map.file.setLength(mf.io, range_file_offset + range_size) catch |err| switch (err) {
1705 error.Canceled => |e| return e,
1706 else => |e| {
1707 mf.io_err = e;
1708 return error.MappedFileIo;
1709 },
1710 };
12851711 } else {
1286 const temp_size = new_alignment.forward(@intCast(new_size + 1));
1287 try mf.resizeNode(gpa, ni, temp_size);
1288 const new_offset, _ = ni.location(mf).resolve(mf);
1289
1290 try mf.ensureCapacityForSetLocation(gpa);
1291
1292 // Non-fixed nodes may now be aligned if the resize moved them
1293 const new_forward_offset = new_alignment.forward(@intCast(new_offset));
1294 const final_offset = if (new_forward_offset != new_offset) final_offset: {
1295 if (node.flags.has_content) {
1296 const old_file_offset = ni.fileLocation(mf, false).offset;
1297 const new_file_offset = (old_file_offset - new_offset) + new_forward_offset;
1298 @memmove(
1299 mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(size)],
1300 mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(size)],
1301 );
1302 @memset(mf.memory_map.memory[@intCast(old_file_offset)..@intCast(new_file_offset)], 0);
1712 while (true) switch (linux.errno(linux.fallocate(
1713 mf.memory_map.file.handle,
1714 linux.FALLOC.FL_INSERT_RANGE,
1715 @intCast(range_file_offset),
1716 @intCast(range_size),
1717 ))) {
1718 .SUCCESS => break,
1719 .INTR => continue,
1720 .NOSYS, .OPNOTSUPP => {
1721 // After all that setup work, it turns out the operation is actually unsupported!
1722 mf.flags.fallocate_insert_range_unsupported = true;
1723 return false;
1724 },
1725 else => |e| {
1726 mf.io_err = switch (e) {
1727 .SUCCESS, .INTR, .NOSYS, .OPNOTSUPP => unreachable, // handled above
1728 .BADF => unreachable,
1729 .FBIG => unreachable,
1730 .INVAL => unreachable,
1731 .IO => error.InputOutput,
1732 .NODEV => error.NotFile,
1733 .NOSPC => error.NoSpaceLeft,
1734 .PERM => error.PermissionDenied,
1735 .SPIPE => error.Unseekable,
1736 .TXTBSY => error.FileBusy,
1737 else => std.posix.unexpectedErrno(e),
1738 };
1739 return error.MappedFileIo;
1740 },
1741 };
1742 }
1743
1744 // We did it! Now to update all the sizes and offsets. This loop is exactly the same shape as
1745 // above, except we're updating locations instead of checking alignments.
1746 var cur_ni = ni;
1747 while (true) {
1748 const this_offset, const this_old_size = cur_ni.location(mf).resolve(mf);
1749 if (cur_ni == .root) {
1750 try mf.ensureTotalCapacityPrecise(@intCast(this_old_size + range_size));
1751 }
1752 try cur_ni.setLocation(mf, gpa, this_offset, this_old_size + range_size);
1753
1754 while (cur_ni.next(mf).unwrap()) |next_ni| {
1755 const next_old_offset, const next_size = next_ni.location(mf).resolve(mf);
1756 try next_ni.setLocation(mf, gpa, next_old_offset + range_size, next_size);
1757 cur_ni = next_ni;
1758 }
1759
1760 cur_ni = cur_ni.parent(mf).unwrap() orelse break;
1761 }
1762
1763 // The only thing left is to update the offsets of any footers inside of `ni`.
1764 if (ni.firstFooter(mf).unwrap()) |first_footer_ni| {
1765 var footer_ni = first_footer_ni;
1766 while (true) {
1767 const old_footer_offset, const footer_size = footer_ni.location(mf).resolve(mf);
1768 try footer_ni.setLocation(mf, gpa, old_footer_offset + range_size, footer_size);
1769 footer_ni = footer_ni.next(mf).unwrap() orelse break;
1770 }
1771 }
1772
1773 return true;
1774}
1775
1776/// Ensures that `parent_ni` has at least `extra_capacity` padding bytes following its current
1777/// headers, so that the headers can grow into that space.
1778fn ensureAdditionalHeaderCapacity(
1779 mf: *MappedFile,
1780 gpa: Allocator,
1781 parent_ni: Node.Index,
1782 extra_capacity: u64,
1783) Error!void {
1784 _, const parent_size = parent_ni.location(mf).resolve(mf);
1785
1786 const last_header_oni = parent_ni.lastHeader(mf);
1787 const first_footer_oni = parent_ni.firstFooter(mf);
1788
1789 const headers_size: u64 = headers_size: {
1790 const last_header_ni = last_header_oni.unwrap() orelse break :headers_size 0;
1791 const last_header_off, const last_header_size = last_header_ni.location(mf).resolve(mf);
1792 break :headers_size last_header_off + last_header_size;
1793 };
1794
1795 const footers_size: u64 = footers_size: {
1796 const first_footer_ni = first_footer_oni.unwrap() orelse break :footers_size 0;
1797 const first_footer_off, _ = first_footer_ni.location(mf).resolve(mf);
1798 break :footers_size parent_size - first_footer_off;
1799 };
1800
1801 const first_floating_oni: Node.Index.Optional = if (last_header_oni.unwrap()) |last_header_ni| first_floating: {
1802 const after_header_ni = last_header_ni.next(mf).unwrap() orelse break :first_floating .none;
1803 break :first_floating switch (after_header_ni.position(mf)) {
1804 .header => unreachable,
1805 .floating => .wrap(after_header_ni),
1806 .footer => .none,
1807 };
1808 } else first_floating: {
1809 const first_ni = parent_ni.first(mf).unwrap() orelse break :first_floating .none;
1810 break :first_floating switch (first_ni.position(mf)) {
1811 .header => unreachable,
1812 .floating => .wrap(first_ni),
1813 .footer => .none,
1814 };
1815 };
1816 const first_floating_ni = first_floating_oni.unwrap() orelse {
1817 // This node has only headers and footers.
1818 const min_parent_size = headers_size + extra_capacity + footers_size;
1819 if (parent_size < min_parent_size) {
1820 const new_parent_size = parent_ni.alignment(mf).forward(
1821 min_parent_size +| min_parent_size / growth_factor,
1822 );
1823 try mf.growNode(gpa, parent_ni, new_parent_size, .minimum);
1824 }
1825 return;
1826 };
1827
1828 const last_floating_ni = if (first_footer_oni.unwrap()) |first_footer_ni| last_floating: {
1829 break :last_floating first_footer_ni.prev(mf).unwrap().?;
1830 } else last_floating: {
1831 break :last_floating parent_ni.last(mf).unwrap().?;
1832 };
1833 assert(last_floating_ni.position(mf) == .floating); // we know `parent_ni` contains at least `first_floating_ni`
1834
1835 // Find the first floating child, if any, which does not overlap the new header space.
1836 const first_good_floating_oni: Node.Index.Optional = first_good_floating: {
1837 var floating_ni = first_floating_ni;
1838 while (true) {
1839 const floating_offset, _ = floating_ni.location(mf).resolve(mf);
1840 if (floating_offset >= headers_size + extra_capacity) {
1841 break :first_good_floating .wrap(floating_ni);
1842 }
1843 const next_ni = floating_ni.next(mf).unwrap() orelse {
1844 break :first_good_floating .none;
1845 };
1846 switch (next_ni.position(mf)) {
1847 .header => unreachable, // after the last header
1848 .floating => floating_ni = next_ni,
1849 .footer => break :first_good_floating .none,
13031850 }
1851 }
1852 };
1853
1854 if (first_good_floating_oni == first_floating_ni.toOptional()) {
1855 // None of the floating children are in our way! That means there's already enough space.
1856 return;
1857 }
1858
1859 const last_moving_ni = if (first_good_floating_oni.unwrap()) |first_good_floating_ni| last_moving: {
1860 break :last_moving first_good_floating_ni.prev(mf).unwrap().?;
1861 } else last_moving: {
1862 break :last_moving last_floating_ni;
1863 };
1864
1865 // We are going to move all nodes between `first_floating_ni` and `last_moving_ni` to the end of
1866 // the parent. We'll move all the node data in one big block.
1867
1868 const moving_offset: u64 = first_floating_ni.location(mf).resolve(mf)[0];
1869 const moving_size: u64 = size: {
1870 const last_moving_off, const last_moving_size = last_moving_ni.location(mf).resolve(mf);
1871 break :size last_moving_off + last_moving_size - moving_offset;
1872 };
1873
1874 var moving_alignment: Alignment = .@"1";
1875 var moving_has_content = false; // optimization: no need to move data if it's all uninitialized
1876 {
1877 var cur_ni = first_floating_ni;
1878 while (true) {
1879 moving_alignment = moving_alignment.max(cur_ni.alignment(mf));
1880 moving_has_content = moving_has_content or cur_ni.get(mf).flags.has_content;
1881 if (cur_ni == last_moving_ni) break;
1882 cur_ni = cur_ni.next(mf).unwrap().?;
1883 }
1884 }
1885
1886 const first_free_offset = free_offset: {
1887 const last_floating_off, const last_floating_size = last_floating_ni.location(mf).resolve(mf);
1888 break :free_offset @max(last_floating_off + last_floating_size, headers_size + extra_capacity);
1889 };
1890 // Alignment is a little tricky here. We don't necessarily want the new offset to be aligned to
1891 // `moving_alignment` exactly, because if (e.g.) the first floating node is align(2) and the
1892 // second is align(4), then the overall range we're moving may not be 4-byte aligned even though
1893 // one of the nodes is. Instead, the old and new offsets must be congruent modulo the alignment.
1894 const aligned_dest_offset = moving_alignment.forward(first_free_offset);
1895 const dest_offset = aligned_dest_offset + (moving_offset - moving_alignment.backward(moving_offset));
1896 assert(dest_offset % moving_alignment.toByteUnits() == moving_offset % moving_alignment.toByteUnits());
1897
1898 // This expression is correct because `dest_offset` is after all floating nodes (except the ones
1899 // we're moving there of course).
1900 const min_parent_size = dest_offset + moving_size + footers_size;
1901 if (parent_size < min_parent_size) {
1902 const new_parent_size = parent_ni.alignment(mf).forward(
1903 min_parent_size +| min_parent_size / growth_factor,
1904 );
1905 try mf.growNode(gpa, parent_ni, new_parent_size, .minimum);
1906 }
1907
1908 if (moving_has_content) {
1909 const parent_file_off = parent_ni.fileLocation(mf, false).offset;
1910 try mf.moveRange(
1911 parent_file_off + moving_offset,
1912 parent_file_off + dest_offset,
1913 moving_size,
1914 );
1915 }
1916
1917 // Remove everything between `first_floating_ni` and `last_moving_ni` from the linked list, then
1918 // re-insert them in their new position.
1919 try mf.removeNodesFromChildList(gpa, first_floating_ni, last_moving_ni);
1920 try mf.addNodesToChildListBefore(gpa, first_footer_oni, first_floating_ni, last_moving_ni);
1921
1922 // Finally, we need to update the locations of all of those nodes.
1923 var cur_ni = first_floating_ni;
1924 while (true) {
1925 assert(cur_ni.position(mf) == .floating);
1926 const old_offset, const old_size = cur_ni.location(mf).resolve(mf);
1927 const new_offset = old_offset - moving_offset + dest_offset;
1928 assert(cur_ni.alignment(mf).check(new_offset));
1929 try cur_ni.setLocation(mf, gpa, new_offset, old_size);
1930 if (cur_ni == last_moving_ni) break;
1931 cur_ni = cur_ni.next(mf).unwrap().?;
1932 }
1933}
1934
1935/// Ensures that `parent_ni` has at least `extra_capacity` padding bytes preceding its current
1936/// footers, so that the footers can grow into that space.
1937fn ensureAdditionalFooterCapacity(
1938 mf: *MappedFile,
1939 gpa: Allocator,
1940 parent_ni: Node.Index,
1941 extra_capacity: u64,
1942) Error!void {
1943 // This is way easier than the header case, because we don't need to actually move anything; we
1944 // just need to expand the parent if there isn't space, and that will add padding after the
1945 // parent's floating children, which is exactly where we want it.
1946
1947 const first_footer_oni = parent_ni.firstFooter(mf);
1948
1949 _, const parent_size = parent_ni.location(mf).resolve(mf);
1950
1951 const footers_size: u64 = footers_size: {
1952 const first_footer_ni = first_footer_oni.unwrap() orelse break :footers_size 0;
1953 const first_footer_off, _ = first_footer_ni.location(mf).resolve(mf);
1954 break :footers_size parent_size - first_footer_off;
1955 };
1956
1957 const header_and_floating_end: u64 = end: {
1958 const before_footers_oni = if (first_footer_oni.unwrap()) |first_footer_ni| before_footers: {
1959 break :before_footers first_footer_ni.prev(mf);
1960 } else before_footers: {
1961 break :before_footers parent_ni.last(mf);
1962 };
1963 const before_footers_ni = before_footers_oni.unwrap() orelse break :end 0;
1964 const offset, const size = before_footers_ni.location(mf).resolve(mf);
1965 break :end offset + size;
1966 };
13041967
1305 break :final_offset new_forward_offset;
1306 } else new_offset;
1968 assert(header_and_floating_end + footers_size <= parent_size);
13071969
1308 ni.setLocationAssumeCapacity(mf, final_offset, new_size);
1970 const min_parent_size = header_and_floating_end + footers_size + extra_capacity;
1971 if (parent_size < min_parent_size) {
1972 const new_parent_size = parent_ni.alignment(mf).forward(
1973 min_parent_size +| min_parent_size / growth_factor,
1974 );
1975 try mf.growNode(gpa, parent_ni, new_parent_size, .minimum);
13091976 }
13101977}
13111978
1979fn removeNodesFromChildList(
1980 mf: *MappedFile,
1981 gpa: Allocator,
1982 first_remove_ni: Node.Index,
1983 last_remove_ni: Node.Index,
1984) Allocator.Error!void {
1985 const parent_ni = first_remove_ni.parent(mf).unwrap().?;
1986 assert(last_remove_ni.parent(mf).unwrap().? == parent_ni);
1987
1988 const prev_oni = first_remove_ni.prev(mf);
1989 const next_oni = last_remove_ni.next(mf);
1990
1991 if (prev_oni.unwrap()) |prev_ni| {
1992 assert(prev_ni.next(mf).unwrap().? == first_remove_ni);
1993 try prev_ni.setNext(gpa, next_oni, mf);
1994 } else {
1995 assert(parent_ni.first(mf).unwrap().? == first_remove_ni);
1996 parent_ni.get(mf).first = next_oni;
1997 }
1998
1999 if (next_oni.unwrap()) |next_ni| {
2000 assert(next_ni.prev(mf).unwrap().? == last_remove_ni);
2001 next_ni.get(mf).prev = prev_oni;
2002 } else {
2003 assert(parent_ni.last(mf).unwrap().? == last_remove_ni);
2004 parent_ni.get(mf).last = prev_oni;
2005 }
2006}
2007/// Assumes `first_add_ni` and `last_add_ni` are connected, and that all nodes in between them
2008/// already have their `parent` field correctly populated.
2009///
2010/// To add a single node, set `first_add_ni` equal to `last_add_ni`.
2011fn addNodesToChildListBefore(
2012 mf: *MappedFile,
2013 gpa: Allocator,
2014 /// `null` means to add at the end of the parent.
2015 next_oni: Node.Index.Optional,
2016 first_add_ni: Node.Index,
2017 last_add_ni: Node.Index,
2018) Allocator.Error!void {
2019 const parent_ni = first_add_ni.parent(mf).unwrap().?;
2020 assert(last_add_ni.parent(mf).unwrap().? == parent_ni);
2021 if (next_oni.unwrap()) |next_ni| {
2022 assert(next_ni.parent(mf).unwrap().? == parent_ni);
2023 }
2024
2025 const prev_oni: Node.Index.Optional = if (next_oni.unwrap()) |next_ni| prev: {
2026 break :prev next_ni.prev(mf);
2027 } else prev: {
2028 break :prev parent_ni.last(mf);
2029 };
2030
2031 first_add_ni.get(mf).prev = prev_oni;
2032 try last_add_ni.setNext(gpa, next_oni, mf);
2033
2034 if (prev_oni.unwrap()) |prev_ni| {
2035 assert(prev_ni.next(mf) == next_oni);
2036 try prev_ni.setNext(gpa, .wrap(first_add_ni), mf);
2037 } else {
2038 assert(parent_ni.first(mf) == next_oni);
2039 parent_ni.get(mf).first = .wrap(first_add_ni);
2040 }
2041
2042 if (next_oni.unwrap()) |next_ni| {
2043 assert(next_ni.prev(mf) == prev_oni);
2044 next_ni.get(mf).prev = .wrap(last_add_ni);
2045 } else {
2046 assert(parent_ni.last(mf) == prev_oni);
2047 parent_ni.get(mf).last = .wrap(last_add_ni);
2048 }
2049}
2050fn addNodesToChildListAfter(
2051 mf: *MappedFile,
2052 gpa: Allocator,
2053 /// `null` means to add at the start of the parent.
2054 prev_oni: Node.Index.Optional,
2055 first_add_ni: Node.Index,
2056 last_add_ni: Node.Index,
2057) Allocator.Error!void {
2058 const next_oni: Node.Index.Optional = next: {
2059 if (prev_oni.unwrap()) |prev_ni| break :next prev_ni.next(mf);
2060 const parent_ni = first_add_ni.parent(mf).unwrap().?;
2061 break :next parent_ni.first(mf);
2062 };
2063 return mf.addNodesToChildListBefore(gpa, next_oni, first_add_ni, last_add_ni);
2064}
2065
2066fn realignNode(
2067 mf: *MappedFile,
2068 gpa: Allocator,
2069 ni: Node.Index,
2070 new_alignment: Alignment,
2071) Error!void {
2072 mf.nodes_lock.assertUnlocked();
2073
2074 const old_offset, const old_size = ni.location(mf).resolve(mf);
2075
2076 if (ni == .root or ni.position(mf) != .floating) {
2077 // Only this node's size is aligned, not its offset.
2078 if (!new_alignment.check(old_size)) {
2079 assert(new_alignment.compare(.gt, ni.alignment(mf)));
2080 try mf.growNode(
2081 gpa,
2082 ni,
2083 new_alignment.forward(old_size),
2084 .exact, // because `growNode` is not aware that the size needs to match `new_alignment`
2085 );
2086 }
2087 } else {
2088 // This is a floating node, so its size and offset are both aligned.
2089 if (!new_alignment.check(old_offset) or !new_alignment.check(old_size)) {
2090 assert(new_alignment.compare(.gt, ni.alignment(mf)));
2091 try mf.growFloatingNodeWithAlignment(
2092 gpa,
2093 ni,
2094 new_alignment,
2095 new_alignment.forward(old_size),
2096 .minimum,
2097 );
2098 }
2099 }
2100
2101 ni.get(mf).flags.alignment = new_alignment;
2102}
2103
13122104fn updateWriters(mf: *MappedFile) void {
13132105 var writers_it = mf.writers.first;
13142106 while (writers_it) |writer_node| : (writers_it = writer_node.next) {
......@@ -1317,10 +2109,47 @@ fn updateWriters(mf: *MappedFile) void {
13172109 }
13182110}
13192111
1320fn moveRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: u64) (Io.Cancelable || IoError)!void {
1321 // make a copy of this node at the new location
1322 try mf.copyRange(old_file_offset, new_file_offset, size);
1323 // delete the copy of this node at the old location
2112fn moveRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: u64) Error!void {
2113 if (old_file_offset == new_file_offset) return;
2114
2115 if (old_file_offset >= new_file_offset + size or
2116 new_file_offset >= old_file_offset + size)
2117 {
2118 const n = try mf.copyFileRange(
2119 mf.memory_map.file,
2120 old_file_offset,
2121 new_file_offset,
2122 size,
2123 );
2124 @memcpy(
2125 mf.memory_map.memory[@intCast(new_file_offset + n)..][0..@intCast(size - n)],
2126 mf.memory_map.memory[@intCast(old_file_offset + n)..][0..@intCast(size - n)],
2127 );
2128
2129 try mf.zeroRange(old_file_offset, size);
2130
2131 return;
2132 }
2133
2134 // TODO: if the non-overlapping region is greater than or equal to a filesystem block, is it
2135 // ever worth doing multiple `copyFileRange` calls instead of a big `@memmove`?
2136
2137 @memmove(
2138 mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(size)],
2139 mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(size)],
2140 );
2141
2142 if (new_file_offset > old_file_offset) {
2143 const clear_size = new_file_offset - old_file_offset;
2144 assert(clear_size < size);
2145 try mf.zeroRange(old_file_offset, clear_size);
2146 } else {
2147 const clear_size = old_file_offset - new_file_offset;
2148 assert(clear_size < size);
2149 try mf.zeroRange(new_file_offset + size, clear_size);
2150 }
2151}
2152fn zeroRange(mf: *MappedFile, file_offset: u64, size: u64) Error!void {
13242153 if (is_linux and
13252154 !mf.flags.fallocate_punch_hole_unsupported and
13262155 size >= mf.flags.block_size.toByteUnits() * 2 - 1)
......@@ -1328,147 +2157,149 @@ fn moveRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size:
13282157 while (true) switch (linux.errno(linux.fallocate(
13292158 mf.memory_map.file.handle,
13302159 linux.FALLOC.FL_PUNCH_HOLE | linux.FALLOC.FL_KEEP_SIZE,
1331 @intCast(old_file_offset),
2160 @intCast(file_offset),
13322161 @intCast(size),
13332162 ))) {
13342163 .SUCCESS => return,
13352164 .INTR => continue,
1336 .BADF, .FBIG, .INVAL => unreachable,
1337 .IO => return error.InputOutput,
1338 .NODEV => return error.NotFile,
1339 .NOSPC => return error.NoSpaceLeft,
13402165 .NOSYS, .OPNOTSUPP => {
13412166 mf.flags.fallocate_punch_hole_unsupported = true;
13422167 break; // fall back to slow path
13432168 },
1344 .PERM => return error.PermissionDenied,
1345 .SPIPE => return error.Unseekable,
1346 .TXTBSY => return error.FileBusy,
1347 else => |e| return std.posix.unexpectedErrno(e),
2169 else => |e| {
2170 mf.io_err = switch (e) {
2171 .SUCCESS, .INTR, .NOSYS, .OPNOTSUPP => unreachable, // handled above
2172 .BADF => unreachable,
2173 .FBIG => unreachable,
2174 .INVAL => unreachable,
2175 .IO => error.InputOutput,
2176 .NODEV => error.NotFile,
2177 .NOSPC => error.NoSpaceLeft,
2178 .PERM => error.PermissionDenied,
2179 .SPIPE => error.Unseekable,
2180 .TXTBSY => error.FileBusy,
2181 else => std.posix.unexpectedErrno(e),
2182 };
2183 return error.MappedFileIo;
2184 },
13482185 };
13492186 }
1350 @memset(mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(size)], 0);
1351}
1352
1353fn copyRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: u64) (Io.Cancelable || IoError)!void {
1354 const copy_size = try mf.copyFileRange(mf.memory_map.file, old_file_offset, new_file_offset, size);
1355 if (copy_size < size) @memcpy(
1356 mf.memory_map.memory[@intCast(new_file_offset + copy_size)..][0..@intCast(size - copy_size)],
1357 mf.memory_map.memory[@intCast(old_file_offset + copy_size)..][0..@intCast(size - copy_size)],
1358 );
2187 @memset(mf.memory_map.memory[@intCast(file_offset)..][0..@intCast(size)], 0);
13592188}
1360
13612189fn copyFileRange(
13622190 mf: *MappedFile,
13632191 old_file: Io.File,
13642192 old_file_offset: u64,
13652193 new_file_offset: u64,
13662194 size: u64,
1367) (Io.Cancelable || IoError)!u64 {
2195) Error!u64 {
2196 if (!is_linux or mf.flags.copy_file_range_unsupported) {
2197 return 0;
2198 }
2199
2200 const min_size = mf.flags.block_size.toByteUnits() * 2 - 1;
2201 if (size < min_size) return 0;
2202
13682203 const io = mf.io;
1369 mf.memory_map.write(io) catch |err| switch (err) {
1370 error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking
1371 error.NotOpenForWriting => return error.Unexpected, // we definitely opened the file for writing
1372 else => |e| return e,
2204 mf.memory_map.write(io) catch |err| {
2205 mf.io_err = switch (err) {
2206 error.Canceled => |e| return e,
2207 error.WouldBlock => error.Unexpected, // file was not opened as non-blocking
2208 error.NotOpenForWriting => error.Unexpected, // we definitely opened the file for writing
2209 else => |e| e,
2210 };
2211 return error.MappedFileIo;
13732212 };
13742213 var remaining_size = size;
1375 if (is_linux and !mf.flags.copy_file_range_unsupported) {
1376 var old_file_offset_mut: i64 = @intCast(old_file_offset);
1377 var new_file_offset_mut: i64 = @intCast(new_file_offset);
1378 while (remaining_size >= mf.flags.block_size.toByteUnits() * 2 - 1) {
1379 const copy_len = linux.copy_file_range(
1380 old_file.handle,
1381 &old_file_offset_mut,
1382 mf.memory_map.file.handle,
1383 &new_file_offset_mut,
1384 @intCast(remaining_size),
1385 0,
1386 );
1387 switch (linux.errno(copy_len)) {
1388 .SUCCESS => {
1389 if (copy_len == 0) break;
1390 remaining_size -= copy_len;
1391 if (remaining_size == 0) break;
1392 },
1393 .INTR => continue,
1394 .BADF, .FBIG, .INVAL, .OVERFLOW => unreachable,
1395 .IO => return error.InputOutput,
1396 .ISDIR => return error.IsDir,
1397 .NOMEM => return error.SystemResources,
1398 .NOSPC => return error.NoSpaceLeft,
1399 .NOSYS, .OPNOTSUPP, .XDEV => {
1400 mf.flags.copy_file_range_unsupported = true;
1401 break;
1402 },
1403 .PERM => return error.PermissionDenied,
1404 .TXTBSY => return error.FileBusy,
1405 else => |e| return std.posix.unexpectedErrno(e),
1406 }
2214 var old_file_offset_mut: i64 = @intCast(old_file_offset);
2215 var new_file_offset_mut: i64 = @intCast(new_file_offset);
2216 while (remaining_size >= min_size) {
2217 const copy_len = linux.copy_file_range(
2218 old_file.handle,
2219 &old_file_offset_mut,
2220 mf.memory_map.file.handle,
2221 &new_file_offset_mut,
2222 @intCast(remaining_size),
2223 0,
2224 );
2225 switch (linux.errno(copy_len)) {
2226 .SUCCESS => {
2227 if (copy_len == 0) break;
2228 remaining_size -= copy_len;
2229 if (remaining_size == 0) break;
2230 },
2231 .INTR => continue,
2232 .NOSYS, .OPNOTSUPP, .XDEV => {
2233 mf.flags.copy_file_range_unsupported = true;
2234 break;
2235 },
2236 else => |e| {
2237 mf.io_err = switch (e) {
2238 .SUCCESS, .INTR, .NOSYS, .OPNOTSUPP, .XDEV => unreachable, // handled above
2239 .BADF => unreachable,
2240 .FBIG => unreachable,
2241 .INVAL => unreachable,
2242 .OVERFLOW => unreachable,
2243 .IO => error.InputOutput,
2244 .ISDIR => error.IsDir,
2245 .NOMEM => error.SystemResources,
2246 .NOSPC => error.NoSpaceLeft,
2247 .PERM => error.PermissionDenied,
2248 .TXTBSY => error.FileBusy,
2249 else => std.posix.unexpectedErrno(e),
2250 };
2251 return error.MappedFileIo;
2252 },
14072253 }
14082254 }
14092255 return size - remaining_size;
14102256}
14112257
1412fn ensureCapacityForSetLocation(mf: *MappedFile, gpa: Allocator) Allocator.Error!void {
1413 try mf.large.ensureUnusedCapacity(gpa, 2);
1414 try mf.updates.ensureUnusedCapacity(gpa, 2);
1415}
1416
14172258pub fn ensureTotalCapacity(mf: *MappedFile, new_capacity: usize) Error!void {
1418 mf.ensureTotalCapacityInner(new_capacity) catch |err| switch (err) {
1419 error.OutOfMemory,
1420 error.Canceled,
1421 => |e| return e,
1422
1423 else => |e| {
1424 mf.io_err = e;
1425 return error.MappedFileIo;
1426 },
1427 };
1428}
1429fn ensureTotalCapacityInner(mf: *MappedFile, new_capacity: usize) (Allocator.Error || Io.Cancelable || IoError)!void {
14302259 if (mf.memory_map.memory.len >= new_capacity) return;
1431 try mf.ensureTotalCapacityPreciseInner(new_capacity +| new_capacity / growth_factor);
2260 try mf.ensureTotalCapacityPrecise(new_capacity +| new_capacity / growth_factor);
14322261}
14332262
14342263pub fn ensureTotalCapacityPrecise(mf: *MappedFile, new_capacity: usize) Error!void {
1435 mf.ensureTotalCapacityPreciseInner(new_capacity) catch |err| switch (err) {
1436 error.OutOfMemory,
1437 error.Canceled,
1438 => |e| return e,
1439
1440 else => |e| {
1441 mf.io_err = e;
1442 return error.MappedFileIo;
1443 },
1444 };
1445}
1446fn ensureTotalCapacityPreciseInner(mf: *MappedFile, new_capacity: usize) (Allocator.Error || Io.Cancelable || IoError)!void {
14472264 if (mf.memory_map.memory.len >= new_capacity) return;
14482265 const io = mf.io;
1449 const aligned_capacity = mf.flags.block_size.forward(new_capacity);
2266 const aligned_capacity: usize = @intCast(
2267 mf.flags.block_size.forward(new_capacity),
2268 );
14502269
14512270 if (mf.memory_map.memory.len > 0) {
14522271 if (mf.memory_map.setLength(io, aligned_capacity)) |_| {
14532272 return;
14542273 } else |err| switch (err) {
14552274 error.OperationUnsupported => {},
1456 else => |e| return e,
2275 error.OutOfMemory, error.Canceled => |e| return e,
2276 else => |e| {
2277 mf.io_err = e;
2278 return error.MappedFileIo;
2279 },
14572280 }
14582281
1459 mf.memory_map.write(io) catch |err| switch (err) {
1460 error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking
1461 error.NotOpenForWriting => return error.Unexpected, // we definitely opened the file for writing
1462 else => |e| return e,
2282 mf.memory_map.write(io) catch |err| {
2283 mf.io_err = switch (err) {
2284 error.Canceled => |e| return e,
2285 error.WouldBlock => error.Unexpected, // file was not opened as non-blocking
2286 error.NotOpenForWriting => error.Unexpected, // we definitely opened the file for writing
2287 else => |e| e,
2288 };
2289 return error.MappedFileIo;
14632290 };
14642291 unmap(mf);
14652292 }
14662293
14672294 const file = mf.memory_map.file;
1468 mf.memory_map = Io.File.MemoryMap.create(io, file, .{ .len = aligned_capacity }) catch |err| switch (err) {
1469 error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking
1470 error.NotOpenForReading => return error.Unexpected, // we definitely opened the file for writing
1471 else => |e| return e,
2295 mf.memory_map = Io.File.MemoryMap.create(io, file, .{ .len = aligned_capacity }) catch |err| {
2296 mf.io_err = switch (err) {
2297 error.OutOfMemory, error.Canceled => |e| return e,
2298 error.WouldBlock => error.Unexpected, // file was not opened as non-blocking
2299 error.NotOpenForReading => error.Unexpected, // we definitely opened the file for writing
2300 else => |e| e,
2301 };
2302 return error.MappedFileIo;
14722303 };
14732304}
14742305
......@@ -1487,7 +2318,7 @@ pub fn flush(mf: *MappedFile) (Io.Cancelable || error{MappedFileIo})!void {
14872318
14882319 error.WouldBlock, // file was not opened as non-blocking
14892320 error.NotOpenForWriting, // we definitely opened the file for writing
1490 error.ReadOnlyFileSystem,
2321 error.ReadOnlyFileSystem, // again, we opened the file for writing
14912322 => {
14922323 mf.io_err = error.Unexpected;
14932324 return error.MappedFileIo;
......@@ -1512,211 +2343,276 @@ fn verify(mf: *MappedFile) void {
15122343 assert(root.next == .none);
15132344 mf.verifyNode(.root);
15142345}
1515
15162346fn verifyNode(mf: *MappedFile, parent_ni: Node.Index) void {
15172347 const parent = parent_ni.get(mf);
1518 const parent_offset, const parent_size = parent.location().resolve(mf);
1519 var prev_ni: Node.Index = .none;
2348 _, const parent_size = parent.location().resolve(mf);
2349
2350 var prev_oni: Node.Index.Optional = .none;
15202351 var prev_end: u64 = 0;
1521 var ni = parent.first;
1522 while (true) {
1523 if (ni == .none) {
1524 assert(parent.last == prev_ni);
1525 return;
1526 }
2352 var prev_pos: Node.Position = .header;
2353 var oni = parent.first;
2354 while (oni.unwrap()) |ni| {
15272355 const node = ni.get(mf);
1528 assert(node.parent == parent_ni);
2356 assert(node.parent == parent_ni.toOptional());
2357 assert(node.prev == prev_oni);
2358
15292359 const offset, const size = node.location().resolve(mf);
1530 assert(node.flags.alignment.check(@intCast(offset)));
1531 assert(node.flags.alignment.check(@intCast(size)));
15322360 const end = offset + size;
1533 assert(end <= parent_offset + parent_size);
2361
2362 assert(node.flags.alignment.check(size));
15342363 assert(offset >= prev_end);
1535 assert(node.prev == prev_ni);
2364 assert(end <= parent_size);
2365
2366 switch (node.flags.position) {
2367 .header => {
2368 assert(prev_pos == .header);
2369 assert(offset == prev_end);
2370 },
2371 .floating => {
2372 assert(prev_pos != .footer);
2373 assert(node.flags.alignment.check(offset));
2374 },
2375 .footer => {
2376 if (prev_pos == .footer) assert(offset == prev_end);
2377 },
2378 }
2379
15362380 mf.verifyNode(ni);
1537 prev_ni = ni;
2381
2382 prev_oni = .wrap(ni);
15382383 prev_end = end;
1539 ni = node.next;
2384 prev_pos = ni.position(mf);
2385
2386 oni = node.next;
2387 }
2388 assert(parent.last == prev_oni);
2389 if (prev_pos == .footer) {
2390 assert(prev_end == parent_size);
15402391 }
15412392}
15422393
1543const testing = std.testing;
1544fn testVerifyContent(mf: *@This(), ni: Node.Index, value: u8, init_len: usize) !void {
1545 // Not using std.mem.allEqual, so we can get useful output
1546 const slice = ni.slice(mf);
1547 var buf: [256]u8 = undefined;
1548 @memset(buf[0..init_len], value);
1549 @memset(buf[init_len..], 0);
1550 try testing.expectEqualSlices(u8, buf[0..slice.len], slice);
2394test "fuzz node operations" {
2395 try std.testing.fuzz({}, fuzzOneNodeOperations, .{});
15512396}
2397fn fuzzOneNodeOperations(_: void, smith: *std.testing.Smith) anyerror!void {
2398 const gpa = std.testing.allocator;
2399 const io = std.testing.io;
15522400
1553test {
1554 const gpa = testing.allocator;
1555
1556 var tmp_dir = testing.tmpDir(.{});
2401 var tmp_dir = std.testing.tmpDir(.{});
15572402 defer tmp_dir.cleanup();
15582403
1559 var file = try tmp_dir.dir.createFile(testing.io, "test.mf", .{ .read = true });
1560 defer file.close(testing.io);
2404 var tmp_file = try tmp_dir.dir.createFile(io, "test.mf", .{ .read = true });
2405 defer tmp_file.close(io);
15612406
1562 var mf = try init(file, gpa, testing.io);
2407 var mf: MappedFile = try .init(tmp_file, gpa, io);
15632408 defer mf.deinit(gpa);
15642409
1565 const a = try mf.addFirstChildNode(gpa, .root, .{ .fixed = true, .alignment = .@"4" });
1566 const c = try mf.addLastChildNode(gpa, .root, .{ .fixed = true, .alignment = .@"4" });
1567 const b = try mf.addNodeAfter(gpa, a, .{ .fixed = true, .alignment = .@"16" });
1568 const d = try mf.addNodeAfter(gpa, b, .{ .alignment = .@"4" });
2410 var nodes: std.array_hash_map.Auto(MappedFile.Node.Index, struct {
2411 parent: MappedFile.Node.Index.Optional,
2412 position: MappedFile.Node.Position,
2413 num_headers: u32,
2414 num_footers: u32,
2415 /// For leaf nodes, this value is whether we have initialized the contents of the node or
2416 /// not. For non-leaf nodes, this value is unspecified and should be ignored.
2417 initialized: bool,
2418 }) = .empty;
2419 defer nodes.deinit(gpa);
2420
2421 // When initializing a leaf node, we will place its 4-byte node index at the start of its range,
2422 // and the bitwise NOT of its node index at the end of its range (both little-endian). This is
2423 // just a simple way to put distinct values we can validate at all node boundaries.
2424
2425 try nodes.putNoClobber(gpa, .root, .{
2426 .parent = .none,
2427 .position = .floating,
2428 .num_headers = 0,
2429 .num_footers = 0,
2430 .initialized = false,
2431 });
2432
2433 // Allow a range of alignments, with most nodes having a small alignment of 1--32 bytes (most
2434 // commonly 1 byte), but with a small chance for some large alignments too.
2435 const alignment_weights: []const std.testing.Smith.Weight = comptime &.{
2436 .value(Alignment, .@"1", 20),
2437 .value(Alignment, .@"2", 5),
2438 .value(Alignment, .@"4", 5),
2439 .value(Alignment, .@"8", 5),
2440 .value(Alignment, .@"16", 5),
2441 .value(Alignment, .@"32", 5),
2442 .value(Alignment, .fromByteUnits(0x200), 1),
2443 .value(Alignment, .fromByteUnits(0x400), 1),
2444 .value(Alignment, .fromByteUnits(0x800), 1),
2445 .value(Alignment, .fromByteUnits(0x1000), 1),
2446 .value(Alignment, .fromByteUnits(0x2000), 1),
2447 .value(Alignment, .fromByteUnits(0x4000), 1),
2448 .value(Alignment, .fromByteUnits(0x8000), 1),
2449 };
15692450
1570 const a_init_size = 8;
1571 const b_init_size = 16;
1572 const c_init_size = 24;
1573 const d_init_size = 28;
2451 const min_nonzero_size = 2 * @sizeOf(MappedFile.Node.Index);
2452 const max_size = 0x10_000;
2453 const initial_size_weights: []const std.testing.Smith.Weight = comptime &.{
2454 // initially, make nodes just as likely to be empty as non-empty
2455 .value(u64, 0, max_size - min_nonzero_size + 1),
2456 .rangeAtMost(u64, min_nonzero_size, max_size, 1),
2457 };
15742458
1575 // Resize without content
1576 {
1577 // Verify size is aligned forward
1578 try d.resize(&mf, gpa, d_init_size - 1);
1579 try a.resize(&mf, gpa, a_init_size - 2);
1580 try c.resize(&mf, gpa, c_init_size);
1581 try b.resize(&mf, gpa, b_init_size);
1582 mf.verify();
1583
1584 const a_loc, const a_size = a.location(&mf).resolve(&mf);
1585 const b_loc, const b_size = b.location(&mf).resolve(&mf);
1586 const c_loc, const c_size = c.location(&mf).resolve(&mf);
1587 _, const d_size = d.location(&mf).resolve(&mf);
1588 try testing.expect(a_size >= a_init_size);
1589 try testing.expect(b_size >= b_init_size);
1590 try testing.expect(c_size >= c_init_size);
1591 try testing.expect(d_size >= d_init_size);
1592 try testing.expect(b_loc >= a_loc + a_size);
1593 try testing.expect(c_loc >= b_loc + b_size);
1594 }
2459 while (!smith.eos()) switch (smith.value(enum { add, resize, realign })) {
2460 .add => {
2461 const parent_ni = nodes.keys()[smith.index(nodes.count())];
15952462
1596 const a_exp_size = 24;
1597 const b_exp_size = 28;
1598 const c_exp_size = 48;
1599 const d_exp_size = 32;
2463 const alignment = smith.valueWeighted(Alignment, alignment_weights);
2464 const size = alignment.forward(smith.valueWeighted(u64, initial_size_weights));
16002465
1601 // Resize with content
1602 {
1603 @memset(a.slice(&mf)[0..a_init_size], 0xaa);
1604 @memset(b.slice(&mf)[0..b_init_size], 0xbb);
1605 @memset(c.slice(&mf)[0..c_init_size], 0xcc);
1606 @memset(d.slice(&mf)[0..d_init_size], 0xdd);
1607
1608 try a.resize(&mf, gpa, a_exp_size);
1609 try b.resize(&mf, gpa, b_exp_size);
1610 try c.resize(&mf, gpa, c_exp_size);
1611 try d.resize(&mf, gpa, d_exp_size);
1612 mf.verify();
1613
1614 const a_loc, const a_size = a.location(&mf).resolve(&mf);
1615 const b_loc, const b_size = b.location(&mf).resolve(&mf);
1616 const c_loc, const c_size = c.location(&mf).resolve(&mf);
1617 _, const d_size = d.location(&mf).resolve(&mf);
1618 try testing.expect(a_size >= a_exp_size);
1619 try testing.expect(b_size >= b_exp_size);
1620 try testing.expect(c_size >= c_exp_size);
1621 try testing.expect(d_size >= d_exp_size);
1622 try testing.expect(b_loc >= a_loc + a_size);
1623 try testing.expect(c_loc >= b_loc + b_size);
1624
1625 try testVerifyContent(&mf, a, 0xaa, a_init_size);
1626 try testVerifyContent(&mf, b, 0xbb, b_init_size);
1627 try testVerifyContent(&mf, c, 0xcc, c_init_size);
1628 try testVerifyContent(&mf, d, 0xdd, d_init_size);
1629 }
2466 const position = smith.valueWeighted(Node.Position, comptime &.{
2467 // make floating nodes more common than header and footer nodes
2468 .value(Node.Position, .header, 1),
2469 .value(Node.Position, .footer, 1),
2470 .value(Node.Position, .floating, 4),
2471 });
2472 const new_ni: Node.Index = switch (position) {
2473 .header => new_ni: {
2474 const parent_info = nodes.getPtr(parent_ni).?;
2475 const prev_oni: Node.Index.Optional = prev_oni: {
2476 const n = smith.valueRangeAtMost(u32, 0, parent_info.num_headers);
2477 if (n == 0) break :prev_oni .none;
2478 var cur_ni = parent_ni.first(&mf).unwrap().?;
2479 for (1..n) |_| cur_ni = cur_ni.next(&mf).unwrap().?;
2480 break :prev_oni .wrap(cur_ni);
2481 };
2482 const new_ni = try parent_ni.addHeaderChildAfter(&mf, gpa, prev_oni, .{
2483 .size = size,
2484 .alignment = alignment,
2485 });
2486 parent_info.num_headers += 1;
2487 break :new_ni new_ni;
2488 },
16302489
1631 const child_init: []const struct { Alignment, usize } = &.{
1632 .{ .@"16", 16 },
1633 .{ .@"1", 1 },
1634 .{ .@"1", 19 },
1635 .{ .@"1", 3 },
1636 .{ .@"8", 30 },
1637 .{ .@"2", 5 },
1638 .{ .@"1", 60 },
1639 .{ .@"2", 2 },
1640 .{ .@"16", 32 },
1641 };
2490 .floating => try parent_ni.addFloatingChild(&mf, gpa, .{
2491 .size = size,
2492 .alignment = alignment,
2493 }),
2494
2495 .footer => new_ni: {
2496 const parent_info = nodes.getPtr(parent_ni).?;
2497 const next_oni: Node.Index.Optional = next_oni: {
2498 const n = smith.valueRangeAtMost(u32, 0, parent_info.num_footers);
2499 if (n == 0) break :next_oni .none;
2500 var cur_ni = parent_ni.last(&mf).unwrap().?;
2501 for (1..n) |_| cur_ni = cur_ni.prev(&mf).unwrap().?;
2502 break :next_oni .wrap(cur_ni);
2503 };
2504 const new_ni = try parent_ni.addFooterChildBefore(&mf, gpa, next_oni, .{
2505 .size = size,
2506 .alignment = alignment,
2507 });
2508 parent_info.num_footers += 1;
2509 break :new_ni new_ni;
2510 },
2511 };
16422512
1643 var children: [child_init.len]Node.Index = undefined;
2513 const initialize = size > 0 and smith.value(bool);
2514 if (initialize) {
2515 const slice = new_ni.slice(&mf);
2516 std.mem.writeInt(u32, slice[0..4], @backingInt(new_ni), .little);
2517 std.mem.writeInt(u32, slice[slice.len - 4 ..][0..4], ~@backingInt(new_ni), .little);
2518 }
16442519
1645 // Differently-aligned fixed sibling nodes
1646 {
1647 for (children[0 .. children.len - 1], child_init[0 .. children.len - 1], 0..) |*ni, opts, i| {
1648 ni.* = try mf.addLastChildNode(gpa, b, .{
1649 .alignment = opts.@"0",
1650 .size = opts.@"1",
1651 .fixed = true,
2520 try nodes.putNoClobber(gpa, new_ni, .{
2521 .parent = .wrap(parent_ni),
2522 .position = position,
2523 .num_headers = 0,
2524 .num_footers = 0,
2525 .initialized = initialize,
16522526 });
2527 },
16532528
1654 @memset(ni.slice(&mf)[0..opts.@"1"], @intCast(i + 1));
1655 }
1656 // Shift differently-aligned nodes by inserting a node
1657 children[children.len - 1] = try mf.addNodeAfter(gpa, children[3], .{
1658 .alignment = child_init[children.len - 1].@"0",
1659 .size = child_init[children.len - 1].@"1",
1660 .fixed = true,
1661 });
1662 @memset(children[children.len - 1].slice(&mf), @intCast(children.len));
1663
1664 mf.verify();
1665 for (children, child_init, 0..) |ni, opts, i| {
1666 try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
1667 }
1668 }
2529 .resize => {
2530 const ni = nodes.keys()[smith.index(nodes.count())];
2531 const node_info = nodes.getPtr(ni).?;
16692532
1670 // Shifting child nodes forward due via resize of parent.prev
1671 {
1672 try testing.expect(a.location(&mf).resolve(&mf)[1] < 64);
1673 try a.resize(&mf, gpa, 64);
2533 const alignment = ni.alignment(&mf);
16742534
1675 try testVerifyContent(&mf, a, 0xaa, a_init_size);
1676 try testVerifyContent(&mf, c, 0xcc, c_init_size);
1677 try testVerifyContent(&mf, d, 0xdd, d_init_size);
1678 for (children, child_init, 0..) |ni, opts, i| {
1679 try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
1680 }
1681 }
2535 if (ni.first(&mf) == .none and smith.value(bool)) {
2536 // Since this is a leaf node, we can use `resizeLeaf`.
2537 const new_size = alignment.forward(smith.valueWeighted(u64, initial_size_weights));
2538 try ni.resizeLeaf(&mf, gpa, new_size);
2539 if (new_size == 0) {
2540 node_info.initialized = false;
2541 }
2542 } else {
2543 const min_size = alignment.forward(smith.valueWeighted(u64, initial_size_weights));
2544 try ni.ensureMinimumSize(&mf, gpa, min_size);
2545 }
16822546
1683 // Re-align last node into trailing free space within parent
1684 {
1685 try b.resize(&mf, gpa, b.location(&mf).resolve(&mf)[1] + 64);
2547 if (ni.first(&mf) == .none) {
2548 // This is a leaf node, so it can contain data.
2549 if (node_info.initialized) {
2550 // It's already initialized, so we'll write the expected footer at the new end.
2551 const slice = ni.slice(&mf);
2552 std.mem.writeInt(u32, slice[slice.len - 4 ..][0..4], ~@backingInt(ni), .little);
2553 } else if (ni.location(&mf).resolve(&mf)[1] > 0) {
2554 // It was uninitialized, but it has a non-zero size, so maybe we'd like to
2555 // initialize it now?
2556 if (smith.value(bool)) {
2557 node_info.initialized = true;
2558 const slice = ni.slice(&mf);
2559 std.mem.writeInt(u32, slice[0..4], @backingInt(ni), .little);
2560 std.mem.writeInt(u32, slice[slice.len - 4 ..][0..4], ~@backingInt(ni), .little);
2561 }
2562 }
2563 }
2564 },
2565 .realign => {
2566 const ni = nodes.keys()[smith.index(nodes.count())];
2567 const new_alignment = smith.valueWeighted(Alignment, alignment_weights);
2568 if (new_alignment.compare(.gt, ni.alignment(&mf))) {
2569 _, const old_size = ni.location(&mf).resolve(&mf);
2570 try ni.realign(&mf, gpa, new_alignment);
2571 if (ni.first(&mf) == .none and nodes.get(ni).?.initialized) {
2572 const slice = ni.slice(&mf);
2573 @memmove(slice[slice.len - 4 ..][0..4], slice[old_size - 4 ..][0..4]);
2574 }
2575 }
2576 },
2577 };
16862578
1687 const last = children[children.len - 2];
1688 try last.realign(&mf, gpa, .@"4", true);
1689 mf.verify();
2579 mf.verify();
16902580
1691 for (children, child_init, 0..) |ni, opts, i|
1692 try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
1693 try testVerifyContent(&mf, c, 0xcc, c_init_size);
1694 }
2581 for (nodes.keys(), nodes.values()) |ni, expected| {
2582 try std.testing.expectEqual(expected.parent, ni.parent(&mf));
2583 if (ni != .root) {
2584 try std.testing.expectEqual(expected.position, ni.position(&mf));
2585 }
16952586
1696 // Re-align, shifting sibling nodes
1697 {
1698 try children[1].realign(&mf, gpa, .@"8", true);
1699 mf.verify();
2587 {
2588 var num_headers: u32 = 0;
2589 var header_oni = ni.lastHeader(&mf);
2590 while (header_oni.unwrap()) |header_ni| {
2591 num_headers += 1;
2592 header_oni = header_ni.prev(&mf);
2593 }
2594 try std.testing.expectEqual(expected.num_headers, num_headers);
2595 }
17002596
1701 for (children, child_init, 0..) |ni, opts, i|
1702 try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
1703 try testVerifyContent(&mf, c, 0xcc, c_init_size);
1704 }
2597 {
2598 var num_footers: u32 = 0;
2599 var footer_oni = ni.firstFooter(&mf);
2600 while (footer_oni.unwrap()) |footer_ni| {
2601 num_footers += 1;
2602 footer_oni = footer_ni.next(&mf);
2603 }
2604 try std.testing.expectEqual(expected.num_footers, num_footers);
2605 }
17052606
1706 // Shrink and shift start of trailing node into free space
1707 {
1708 try mf.shrinkNode(gpa, a, 16, true);
1709 mf.verify();
1710
1711 const a_loc, const a_size = a.location(&mf).resolve(&mf);
1712 const b_loc, _ = b.location(&mf).resolve(&mf);
1713 try testing.expectEqual(b_loc, a_loc + a_size);
1714
1715 try testVerifyContent(&mf, a, 0xaa, a_init_size);
1716 try testVerifyContent(&mf, c, 0xcc, c_init_size);
1717 try testVerifyContent(&mf, d, 0xdd, d_init_size);
1718 for (children, child_init, 0..) |ni, opts, i| {
1719 try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
2607 if (ni.first(&mf) == .none and expected.initialized) {
2608 const slice = ni.sliceConst(&mf);
2609 if (slice.len > 0) {
2610 try std.testing.expect(slice.len >= min_nonzero_size);
2611 const header = std.mem.readInt(u32, slice[0..4], .little);
2612 const footer = std.mem.readInt(u32, slice[slice.len - 4 ..][0..4], .little);
2613 try std.testing.expectEqual(@backingInt(ni), header);
2614 try std.testing.expectEqual(~@backingInt(ni), footer);
2615 }
17202616 }
17212617 }
17222618}
src/main.zig+1
......@@ -36,6 +36,7 @@ const Module = @import("Module.zig");
3636
3737test {
3838 _ = @import("codegen.zig");
39 _ = @import("link/MappedFile.zig");
3940}
4041
4142const thread_stack_size = 60 << 20;