authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-06 22:52:19-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-06 22:52:19-07:00
logb9e1fef5628f01892cf09ade4425625a2cf34911
tree53c98c274d22bcd956ff5affa983ee80c6be9fb7
parent5667435bc4eacb36b4c33470784f1662637341b4
parentaac26f3b31ddb43e863ac7186fd19a7e251c1b8a

Merge remote-tracking branch 'origin/http' into wrangle-writer-buffering


87 files changed, 2544 insertions(+), 2345 deletions(-)

.github/workflows/ci.yaml+18
...@@ -50,6 +50,24 @@ jobs:...@@ -50,6 +50,24 @@ jobs:
50 uses: actions/checkout@v450 uses: actions/checkout@v4
51 - name: Build and Test51 - name: Build and Test
52 run: sh ci/aarch64-linux-release.sh52 run: sh ci/aarch64-linux-release.sh
53 riscv64-linux-debug:
54 if: github.event_name == 'push'
55 timeout-minutes: 420
56 runs-on: [self-hosted, Linux, riscv64]
57 steps:
58 - name: Checkout
59 uses: actions/checkout@v4
60 - name: Build and Test
61 run: sh ci/riscv64-linux-debug.sh
62 riscv64-linux-release:
63 if: github.event_name == 'push'
64 timeout-minutes: 420
65 runs-on: [self-hosted, Linux, riscv64]
66 steps:
67 - name: Checkout
68 uses: actions/checkout@v4
69 - name: Build and Test
70 run: sh ci/riscv64-linux-release.sh
53 x86_64-macos-release:71 x86_64-macos-release:
54 runs-on: "macos-13"72 runs-on: "macos-13"
55 env:73 env:
.github/workflows/riscv.yaml deleted-22
...@@ -1,22 +0,0 @@
1name: riscv
2on:
3 workflow_dispatch:
4permissions:
5 contents: read
6jobs:
7 riscv64-linux-debug:
8 timeout-minutes: 1020
9 runs-on: [self-hosted, Linux, riscv64]
10 steps:
11 - name: Checkout
12 uses: actions/checkout@v4
13 - name: Build and Test
14 run: sh ci/riscv64-linux-debug.sh
15 riscv64-linux-release:
16 timeout-minutes: 900
17 runs-on: [self-hosted, Linux, riscv64]
18 steps:
19 - name: Checkout
20 uses: actions/checkout@v4
21 - name: Build and Test
22 run: sh ci/riscv64-linux-release.sh
build.zig+2-20
...@@ -90,6 +90,7 @@ pub fn build(b: *std.Build) !void {...@@ -90,6 +90,7 @@ pub fn build(b: *std.Build) !void {
90 const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;90 const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;
91 const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false;91 const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false;
92 const skip_single_threaded = b.option(bool, "skip-single-threaded", "Main test suite skips tests that are single-threaded") orelse false;92 const skip_single_threaded = b.option(bool, "skip-single-threaded", "Main test suite skips tests that are single-threaded") orelse false;
93 const skip_compile_errors = b.option(bool, "skip-compile-errors", "Main test suite skips compile error tests") orelse false;
93 const skip_translate_c = b.option(bool, "skip-translate-c", "Main test suite skips translate-c tests") orelse false;94 const skip_translate_c = b.option(bool, "skip-translate-c", "Main test suite skips translate-c tests") orelse false;
94 const skip_run_translated_c = b.option(bool, "skip-run-translated-c", "Main test suite skips run-translated-c tests") orelse false;95 const skip_run_translated_c = b.option(bool, "skip-run-translated-c", "Main test suite skips run-translated-c tests") orelse false;
95 const skip_freebsd = b.option(bool, "skip-freebsd", "Main test suite skips targets with freebsd OS") orelse false;96 const skip_freebsd = b.option(bool, "skip-freebsd", "Main test suite skips targets with freebsd OS") orelse false;
...@@ -418,6 +419,7 @@ pub fn build(b: *std.Build) !void {...@@ -418,6 +419,7 @@ pub fn build(b: *std.Build) !void {
418 try tests.addCases(b, test_cases_step, target, .{419 try tests.addCases(b, test_cases_step, target, .{
419 .test_filters = test_filters,420 .test_filters = test_filters,
420 .test_target_filters = test_target_filters,421 .test_target_filters = test_target_filters,
422 .skip_compile_errors = skip_compile_errors,
421 .skip_non_native = skip_non_native,423 .skip_non_native = skip_non_native,
422 .skip_freebsd = skip_freebsd,424 .skip_freebsd = skip_freebsd,
423 .skip_netbsd = skip_netbsd,425 .skip_netbsd = skip_netbsd,
...@@ -450,7 +452,6 @@ pub fn build(b: *std.Build) !void {...@@ -450,7 +452,6 @@ pub fn build(b: *std.Build) !void {
450 .desc = "Run the behavior tests",452 .desc = "Run the behavior tests",
451 .optimize_modes = optimization_modes,453 .optimize_modes = optimization_modes,
452 .include_paths = &.{},454 .include_paths = &.{},
453 .windows_libs = &.{},
454 .skip_single_threaded = skip_single_threaded,455 .skip_single_threaded = skip_single_threaded,
455 .skip_non_native = skip_non_native,456 .skip_non_native = skip_non_native,
456 .skip_freebsd = skip_freebsd,457 .skip_freebsd = skip_freebsd,
...@@ -473,7 +474,6 @@ pub fn build(b: *std.Build) !void {...@@ -473,7 +474,6 @@ pub fn build(b: *std.Build) !void {
473 .desc = "Run the @cImport tests",474 .desc = "Run the @cImport tests",
474 .optimize_modes = optimization_modes,475 .optimize_modes = optimization_modes,
475 .include_paths = &.{"test/c_import"},476 .include_paths = &.{"test/c_import"},
476 .windows_libs = &.{},
477 .skip_single_threaded = true,477 .skip_single_threaded = true,
478 .skip_non_native = skip_non_native,478 .skip_non_native = skip_non_native,
479 .skip_freebsd = skip_freebsd,479 .skip_freebsd = skip_freebsd,
...@@ -494,7 +494,6 @@ pub fn build(b: *std.Build) !void {...@@ -494,7 +494,6 @@ pub fn build(b: *std.Build) !void {
494 .desc = "Run the compiler_rt tests",494 .desc = "Run the compiler_rt tests",
495 .optimize_modes = optimization_modes,495 .optimize_modes = optimization_modes,
496 .include_paths = &.{},496 .include_paths = &.{},
497 .windows_libs = &.{},
498 .skip_single_threaded = true,497 .skip_single_threaded = true,
499 .skip_non_native = skip_non_native,498 .skip_non_native = skip_non_native,
500 .skip_freebsd = skip_freebsd,499 .skip_freebsd = skip_freebsd,
...@@ -516,7 +515,6 @@ pub fn build(b: *std.Build) !void {...@@ -516,7 +515,6 @@ pub fn build(b: *std.Build) !void {
516 .desc = "Run the zigc tests",515 .desc = "Run the zigc tests",
517 .optimize_modes = optimization_modes,516 .optimize_modes = optimization_modes,
518 .include_paths = &.{},517 .include_paths = &.{},
519 .windows_libs = &.{},
520 .skip_single_threaded = true,518 .skip_single_threaded = true,
521 .skip_non_native = skip_non_native,519 .skip_non_native = skip_non_native,
522 .skip_freebsd = skip_freebsd,520 .skip_freebsd = skip_freebsd,
...@@ -538,12 +536,6 @@ pub fn build(b: *std.Build) !void {...@@ -538,12 +536,6 @@ pub fn build(b: *std.Build) !void {
538 .desc = "Run the standard library tests",536 .desc = "Run the standard library tests",
539 .optimize_modes = optimization_modes,537 .optimize_modes = optimization_modes,
540 .include_paths = &.{},538 .include_paths = &.{},
541 .windows_libs = &.{
542 "advapi32",
543 "crypt32",
544 "iphlpapi",
545 "ws2_32",
546 },
547 .skip_single_threaded = skip_single_threaded,539 .skip_single_threaded = skip_single_threaded,
548 .skip_non_native = skip_non_native,540 .skip_non_native = skip_non_native,
549 .skip_freebsd = skip_freebsd,541 .skip_freebsd = skip_freebsd,
...@@ -741,12 +733,6 @@ fn addCompilerMod(b: *std.Build, options: AddCompilerModOptions) *std.Build.Modu...@@ -741,12 +733,6 @@ fn addCompilerMod(b: *std.Build, options: AddCompilerModOptions) *std.Build.Modu
741 compiler_mod.addImport("aro", aro_mod);733 compiler_mod.addImport("aro", aro_mod);
742 compiler_mod.addImport("aro_translate_c", aro_translate_c_mod);734 compiler_mod.addImport("aro_translate_c", aro_translate_c_mod);
743735
744 if (options.target.result.os.tag == .windows) {
745 compiler_mod.linkSystemLibrary("advapi32", .{});
746 compiler_mod.linkSystemLibrary("crypt32", .{});
747 compiler_mod.linkSystemLibrary("ws2_32", .{});
748 }
749
750 return compiler_mod;736 return compiler_mod;
751}737}
752738
...@@ -1444,10 +1430,6 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {...@@ -1444,10 +1430,6 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
1444 }),1430 }),
1445 });1431 });
14461432
1447 if (b.graph.host.result.os.tag == .windows) {
1448 doctest_exe.root_module.linkSystemLibrary("advapi32", .{});
1449 }
1450
1451 var dir = b.build_root.handle.openDir("doc/langref", .{ .iterate = true }) catch |err| {1433 var dir = b.build_root.handle.openDir("doc/langref", .{ .iterate = true }) catch |err| {
1452 std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{1434 std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{
1453 b.build_root, @errorName(err),1435 b.build_root, @errorName(err),
ci/riscv64-linux-debug.sh+2-1
...@@ -49,11 +49,12 @@ unset CXX...@@ -49,11 +49,12 @@ unset CXX
49ninja install49ninja install
5050
51# No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts.51# No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts.
52stage3-debug/bin/zig build test-cases test-modules test-unit test-standalone test-c-abi test-link test-stack-traces test-asm-link test-llvm-ir \52stage3-debug/bin/zig build test-cases test-modules test-unit test-c-abi test-stack-traces test-asm-link test-llvm-ir \
53 --maxrss 68719476736 \53 --maxrss 68719476736 \
54 -Dstatic-llvm \54 -Dstatic-llvm \
55 -Dskip-non-native \55 -Dskip-non-native \
56 -Dskip-single-threaded \56 -Dskip-single-threaded \
57 -Dskip-compile-errors \
57 -Dskip-translate-c \58 -Dskip-translate-c \
58 -Dskip-run-translated-c \59 -Dskip-run-translated-c \
59 -Dtarget=native-native-musl \60 -Dtarget=native-native-musl \
ci/riscv64-linux-release.sh+2-1
...@@ -49,11 +49,12 @@ unset CXX...@@ -49,11 +49,12 @@ unset CXX
49ninja install49ninja install
5050
51# No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts.51# No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts.
52stage3-release/bin/zig build test-cases test-modules test-unit test-standalone test-c-abi test-link test-stack-traces test-asm-link test-llvm-ir \52stage3-release/bin/zig build test-cases test-modules test-unit test-c-abi test-stack-traces test-asm-link test-llvm-ir \
53 --maxrss 68719476736 \53 --maxrss 68719476736 \
54 -Dstatic-llvm \54 -Dstatic-llvm \
55 -Dskip-non-native \55 -Dskip-non-native \
56 -Dskip-single-threaded \56 -Dskip-single-threaded \
57 -Dskip-compile-errors \
57 -Dskip-translate-c \58 -Dskip-translate-c \
58 -Dskip-run-translated-c \59 -Dskip-run-translated-c \
59 -Dtarget=native-native-musl \60 -Dtarget=native-native-musl \
doc/langref/test_switch_dispatch_loop.zig+5-3
...@@ -8,20 +8,22 @@ const Instruction = enum {...@@ -8,20 +8,22 @@ const Instruction = enum {
8};8};
99
10fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 {10fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 {
11 var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack);11 var buffer: [8]i32 = undefined;
12 var stack = std.ArrayListUnmanaged(i32).initBuffer(&buffer);
13 try stack.appendSliceBounded(initial_stack);
12 var ip: usize = 0;14 var ip: usize = 0;
1315
14 return vm: switch (code[ip]) {16 return vm: switch (code[ip]) {
15 // Because all code after `continue` is unreachable, this branch does17 // Because all code after `continue` is unreachable, this branch does
16 // not provide a result.18 // not provide a result.
17 .add => {19 .add => {
18 try stack.append(stack.pop().? + stack.pop().?);20 try stack.appendBounded(stack.pop().? + stack.pop().?);
1921
20 ip += 1;22 ip += 1;
21 continue :vm code[ip];23 continue :vm code[ip];
22 },24 },
23 .mul => {25 .mul => {
24 try stack.append(stack.pop().? * stack.pop().?);26 try stack.appendBounded(stack.pop().? * stack.pop().?);
2527
26 ip += 1;28 ip += 1;
27 continue :vm code[ip];29 continue :vm code[ip];
lib/compiler/aro/aro/Attribute.zig+1-1
...@@ -708,7 +708,7 @@ pub const Arguments = blk: {...@@ -708,7 +708,7 @@ pub const Arguments = blk: {
708 field.* = .{708 field.* = .{
709 .name = decl.name,709 .name = decl.name,
710 .type = @field(attributes, decl.name),710 .type = @field(attributes, decl.name),
711 .alignment = 0,711 .alignment = @alignOf(@field(attributes, decl.name)),
712 };712 };
713 }713 }
714714
lib/compiler/build_runner.zig+4-1
...@@ -502,6 +502,9 @@ pub fn main() !void {...@@ -502,6 +502,9 @@ pub fn main() !void {
502 };502 };
503 }503 }
504504
505 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
506 if (!Watch.have_impl) unreachable;
507
505 try w.update(gpa, run.step_stack.keys());508 try w.update(gpa, run.step_stack.keys());
506509
507 // Wait until a file system notification arrives. Read all such events510 // Wait until a file system notification arrives. Read all such events
...@@ -511,7 +514,7 @@ pub fn main() !void {...@@ -511,7 +514,7 @@ pub fn main() !void {
511 // recursive dependants.514 // recursive dependants.
512 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;515 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
513 const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{516 const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{
514 w.dir_table.entries.len, countSubProcesses(run.step_stack.keys()),517 w.dir_count, countSubProcesses(run.step_stack.keys()),
515 }) catch &caption_buf;518 }) catch &caption_buf;
516 var debouncing_node = main_progress_node.start(caption, 0);519 var debouncing_node = main_progress_node.start(caption, 0);
517 var in_debounce = false;520 var in_debounce = false;
lib/compiler/resinator/cli.zig+7-5
...@@ -1141,6 +1141,8 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn...@@ -1141,6 +1141,8 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
1141 }1141 }
1142 output_format = .res;1142 output_format = .res;
1143 }1143 }
1144 } else {
1145 output_format_source = .output_format_arg;
1144 }1146 }
1145 options.output_source = .{ .filename = try filepathWithExtension(allocator, options.input_source.filename, output_format.?.extension()) };1147 options.output_source = .{ .filename = try filepathWithExtension(allocator, options.input_source.filename, output_format.?.extension()) };
1146 } else {1148 } else {
...@@ -1529,21 +1531,21 @@ fn testParseOutput(args: []const []const u8, expected_output: []const u8) !?Opti...@@ -1529,21 +1531,21 @@ fn testParseOutput(args: []const []const u8, expected_output: []const u8) !?Opti
1529 var diagnostics = Diagnostics.init(std.testing.allocator);1531 var diagnostics = Diagnostics.init(std.testing.allocator);
1530 defer diagnostics.deinit();1532 defer diagnostics.deinit();
15311533
1532 var output = std.ArrayList(u8).init(std.testing.allocator);1534 var output: std.io.Writer.Allocating = .init(std.testing.allocator);
1533 defer output.deinit();1535 defer output.deinit();
15341536
1535 var options = parse(std.testing.allocator, args, &diagnostics) catch |err| switch (err) {1537 var options = parse(std.testing.allocator, args, &diagnostics) catch |err| switch (err) {
1536 error.ParseError => {1538 error.ParseError => {
1537 try diagnostics.renderToWriter(args, output.writer(), .no_color);1539 try diagnostics.renderToWriter(args, &output.writer, .no_color);
1538 try std.testing.expectEqualStrings(expected_output, output.items);1540 try std.testing.expectEqualStrings(expected_output, output.getWritten());
1539 return null;1541 return null;
1540 },1542 },
1541 else => |e| return e,1543 else => |e| return e,
1542 };1544 };
1543 errdefer options.deinit();1545 errdefer options.deinit();
15441546
1545 try diagnostics.renderToWriter(args, output.writer(), .no_color);1547 try diagnostics.renderToWriter(args, &output.writer, .no_color);
1546 try std.testing.expectEqualStrings(expected_output, output.items);1548 try std.testing.expectEqualStrings(expected_output, output.getWritten());
1547 return options;1549 return options;
1548}1550}
15491551
lib/compiler/resinator/compile.zig+50-46
...@@ -550,7 +550,7 @@ pub const Compiler = struct {...@@ -550,7 +550,7 @@ pub const Compiler = struct {
550 // so get it here to simplify future usage.550 // so get it here to simplify future usage.
551 const filename_token = node.filename.getFirstToken();551 const filename_token = node.filename.getFirstToken();
552552
553 const file = self.searchForFile(filename_utf8) catch |err| switch (err) {553 const file_handle = self.searchForFile(filename_utf8) catch |err| switch (err) {
554 error.OutOfMemory => |e| return e,554 error.OutOfMemory => |e| return e,
555 else => |e| {555 else => |e| {
556 const filename_string_index = try self.diagnostics.putString(filename_utf8);556 const filename_string_index = try self.diagnostics.putString(filename_utf8);
...@@ -564,13 +564,15 @@ pub const Compiler = struct {...@@ -564,13 +564,15 @@ pub const Compiler = struct {
564 });564 });
565 },565 },
566 };566 };
567 defer file.close();567 defer file_handle.close();
568 var file_buffer: [2048]u8 = undefined;
569 var file_reader = file_handle.reader(&file_buffer);
568570
569 if (maybe_predefined_type) |predefined_type| {571 if (maybe_predefined_type) |predefined_type| {
570 switch (predefined_type) {572 switch (predefined_type) {
571 .GROUP_ICON, .GROUP_CURSOR => {573 .GROUP_ICON, .GROUP_CURSOR => {
572 // Check for animated icon first574 // Check for animated icon first
573 if (ani.isAnimatedIcon(file.deprecatedReader())) {575 if (ani.isAnimatedIcon(file_reader.interface.adaptToOldInterface())) {
574 // Animated icons are just put into the resource unmodified,576 // Animated icons are just put into the resource unmodified,
575 // and the resource type changes to ANIICON/ANICURSOR577 // and the resource type changes to ANIICON/ANICURSOR
576578
...@@ -582,18 +584,18 @@ pub const Compiler = struct {...@@ -582,18 +584,18 @@ pub const Compiler = struct {
582 header.type_value.ordinal = @intFromEnum(new_predefined_type);584 header.type_value.ordinal = @intFromEnum(new_predefined_type);
583 header.memory_flags = MemoryFlags.defaults(new_predefined_type);585 header.memory_flags = MemoryFlags.defaults(new_predefined_type);
584 header.applyMemoryFlags(node.common_resource_attributes, self.source);586 header.applyMemoryFlags(node.common_resource_attributes, self.source);
585 header.data_size = @intCast(try file.getEndPos());587 header.data_size = @intCast(try file_reader.getSize());
586588
587 try header.write(writer, self.errContext(node.id));589 try header.write(writer, self.errContext(node.id));
588 try file.seekTo(0);590 try file_reader.seekTo(0);
589 try writeResourceData(writer, file.deprecatedReader(), header.data_size);591 try writeResourceData(writer, &file_reader.interface, header.data_size);
590 return;592 return;
591 }593 }
592594
593 // isAnimatedIcon moved the file cursor so reset to the start595 // isAnimatedIcon moved the file cursor so reset to the start
594 try file.seekTo(0);596 try file_reader.seekTo(0);
595597
596 const icon_dir = ico.read(self.allocator, file.deprecatedReader(), try file.getEndPos()) catch |err| switch (err) {598 const icon_dir = ico.read(self.allocator, file_reader.interface.adaptToOldInterface(), try file_reader.getSize()) catch |err| switch (err) {
597 error.OutOfMemory => |e| return e,599 error.OutOfMemory => |e| return e,
598 else => |e| {600 else => |e| {
599 return self.iconReadError(601 return self.iconReadError(
...@@ -671,15 +673,15 @@ pub const Compiler = struct {...@@ -671,15 +673,15 @@ pub const Compiler = struct {
671 try writer.writeInt(u16, entry.type_specific_data.cursor.hotspot_y, .little);673 try writer.writeInt(u16, entry.type_specific_data.cursor.hotspot_y, .little);
672 }674 }
673675
674 try file.seekTo(entry.data_offset_from_start_of_file);676 try file_reader.seekTo(entry.data_offset_from_start_of_file);
675 var header_bytes = file.deprecatedReader().readBytesNoEof(16) catch {677 var header_bytes = (file_reader.interface.takeArray(16) catch {
676 return self.iconReadError(678 return self.iconReadError(
677 error.UnexpectedEOF,679 error.UnexpectedEOF,
678 filename_utf8,680 filename_utf8,
679 filename_token,681 filename_token,
680 predefined_type,682 predefined_type,
681 );683 );
682 };684 }).*;
683685
684 const image_format = ico.ImageFormat.detect(&header_bytes);686 const image_format = ico.ImageFormat.detect(&header_bytes);
685 if (!image_format.validate(&header_bytes)) {687 if (!image_format.validate(&header_bytes)) {
...@@ -802,8 +804,8 @@ pub const Compiler = struct {...@@ -802,8 +804,8 @@ pub const Compiler = struct {
802 },804 },
803 }805 }
804806
805 try file.seekTo(entry.data_offset_from_start_of_file);807 try file_reader.seekTo(entry.data_offset_from_start_of_file);
806 try writeResourceDataNoPadding(writer, file.deprecatedReader(), entry.data_size_in_bytes);808 try writeResourceDataNoPadding(writer, &file_reader.interface, entry.data_size_in_bytes);
807 try writeDataPadding(writer, full_data_size);809 try writeDataPadding(writer, full_data_size);
808810
809 if (self.state.icon_id == std.math.maxInt(u16)) {811 if (self.state.icon_id == std.math.maxInt(u16)) {
...@@ -857,9 +859,9 @@ pub const Compiler = struct {...@@ -857,9 +859,9 @@ pub const Compiler = struct {
857 },859 },
858 .BITMAP => {860 .BITMAP => {
859 header.applyMemoryFlags(node.common_resource_attributes, self.source);861 header.applyMemoryFlags(node.common_resource_attributes, self.source);
860 const file_size = try file.getEndPos();862 const file_size = try file_reader.getSize();
861863
862 const bitmap_info = bmp.read(file.deprecatedReader(), file_size) catch |err| {864 const bitmap_info = bmp.read(file_reader.interface.adaptToOldInterface(), file_size) catch |err| {
863 const filename_string_index = try self.diagnostics.putString(filename_utf8);865 const filename_string_index = try self.diagnostics.putString(filename_utf8);
864 return self.addErrorDetailsAndFail(.{866 return self.addErrorDetailsAndFail(.{
865 .err = .bmp_read_error,867 .err = .bmp_read_error,
...@@ -921,18 +923,17 @@ pub const Compiler = struct {...@@ -921,18 +923,17 @@ pub const Compiler = struct {
921923
922 header.data_size = bmp_bytes_to_write;924 header.data_size = bmp_bytes_to_write;
923 try header.write(writer, self.errContext(node.id));925 try header.write(writer, self.errContext(node.id));
924 try file.seekTo(bmp.file_header_len);926 try file_reader.seekTo(bmp.file_header_len);
925 const file_reader = file.deprecatedReader();927 try writeResourceDataNoPadding(writer, &file_reader.interface, bitmap_info.dib_header_size);
926 try writeResourceDataNoPadding(writer, file_reader, bitmap_info.dib_header_size);
927 if (bitmap_info.getBitmasksByteLen() > 0) {928 if (bitmap_info.getBitmasksByteLen() > 0) {
928 try writeResourceDataNoPadding(writer, file_reader, bitmap_info.getBitmasksByteLen());929 try writeResourceDataNoPadding(writer, &file_reader.interface, bitmap_info.getBitmasksByteLen());
929 }930 }
930 if (bitmap_info.getExpectedPaletteByteLen() > 0) {931 if (bitmap_info.getExpectedPaletteByteLen() > 0) {
931 try writeResourceDataNoPadding(writer, file_reader, @intCast(bitmap_info.getActualPaletteByteLen()));932 try writeResourceDataNoPadding(writer, &file_reader.interface, @intCast(bitmap_info.getActualPaletteByteLen()));
932 }933 }
933 try file.seekTo(bitmap_info.pixel_data_offset);934 try file_reader.seekTo(bitmap_info.pixel_data_offset);
934 const pixel_bytes: u32 = @intCast(file_size - bitmap_info.pixel_data_offset);935 const pixel_bytes: u32 = @intCast(file_size - bitmap_info.pixel_data_offset);
935 try writeResourceDataNoPadding(writer, file_reader, pixel_bytes);936 try writeResourceDataNoPadding(writer, &file_reader.interface, pixel_bytes);
936 try writeDataPadding(writer, bmp_bytes_to_write);937 try writeDataPadding(writer, bmp_bytes_to_write);
937 return;938 return;
938 },939 },
...@@ -956,7 +957,7 @@ pub const Compiler = struct {...@@ -956,7 +957,7 @@ pub const Compiler = struct {
956 return;957 return;
957 }958 }
958 header.applyMemoryFlags(node.common_resource_attributes, self.source);959 header.applyMemoryFlags(node.common_resource_attributes, self.source);
959 const file_size = try file.getEndPos();960 const file_size = try file_reader.getSize();
960 if (file_size > std.math.maxInt(u32)) {961 if (file_size > std.math.maxInt(u32)) {
961 return self.addErrorDetailsAndFail(.{962 return self.addErrorDetailsAndFail(.{
962 .err = .resource_data_size_exceeds_max,963 .err = .resource_data_size_exceeds_max,
...@@ -968,8 +969,9 @@ pub const Compiler = struct {...@@ -968,8 +969,9 @@ pub const Compiler = struct {
968 header.data_size = @intCast(file_size);969 header.data_size = @intCast(file_size);
969 try header.write(writer, self.errContext(node.id));970 try header.write(writer, self.errContext(node.id));
970971
971 var header_slurping_reader = headerSlurpingReader(148, file.deprecatedReader());972 var header_slurping_reader = headerSlurpingReader(148, file_reader.interface.adaptToOldInterface());
972 try writeResourceData(writer, header_slurping_reader.reader(), header.data_size);973 var adapter = header_slurping_reader.reader().adaptToNewApi(&.{});
974 try writeResourceData(writer, &adapter.new_interface, header.data_size);
973975
974 try self.state.font_dir.add(self.arena, FontDir.Font{976 try self.state.font_dir.add(self.arena, FontDir.Font{
975 .id = header.name_value.ordinal,977 .id = header.name_value.ordinal,
...@@ -992,7 +994,7 @@ pub const Compiler = struct {...@@ -992,7 +994,7 @@ pub const Compiler = struct {
992 }994 }
993995
994 // Fallback to just writing out the entire contents of the file996 // Fallback to just writing out the entire contents of the file
995 const data_size = try file.getEndPos();997 const data_size = try file_reader.getSize();
996 if (data_size > std.math.maxInt(u32)) {998 if (data_size > std.math.maxInt(u32)) {
997 return self.addErrorDetailsAndFail(.{999 return self.addErrorDetailsAndFail(.{
998 .err = .resource_data_size_exceeds_max,1000 .err = .resource_data_size_exceeds_max,
...@@ -1002,7 +1004,7 @@ pub const Compiler = struct {...@@ -1002,7 +1004,7 @@ pub const Compiler = struct {
1002 // We now know that the data size will fit in a u321004 // We now know that the data size will fit in a u32
1003 header.data_size = @intCast(data_size);1005 header.data_size = @intCast(data_size);
1004 try header.write(writer, self.errContext(node.id));1006 try header.write(writer, self.errContext(node.id));
1005 try writeResourceData(writer, file.deprecatedReader(), header.data_size);1007 try writeResourceData(writer, &file_reader.interface, header.data_size);
1006 }1008 }
10071009
1008 fn iconReadError(1010 fn iconReadError(
...@@ -1250,8 +1252,8 @@ pub const Compiler = struct {...@@ -1250,8 +1252,8 @@ pub const Compiler = struct {
1250 const data_len: u32 = @intCast(data_buffer.items.len);1252 const data_len: u32 = @intCast(data_buffer.items.len);
1251 try self.writeResourceHeader(writer, node.id, node.type, data_len, node.common_resource_attributes, self.state.language);1253 try self.writeResourceHeader(writer, node.id, node.type, data_len, node.common_resource_attributes, self.state.language);
12521254
1253 var data_fbs = std.io.fixedBufferStream(data_buffer.items);1255 var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
1254 try writeResourceData(writer, data_fbs.reader(), data_len);1256 try writeResourceData(writer, &data_fbs, data_len);
1255 }1257 }
12561258
1257 pub fn writeResourceHeader(self: *Compiler, writer: anytype, id_token: Token, type_token: Token, data_size: u32, common_resource_attributes: []Token, language: res.Language) !void {1259 pub fn writeResourceHeader(self: *Compiler, writer: anytype, id_token: Token, type_token: Token, data_size: u32, common_resource_attributes: []Token, language: res.Language) !void {
...@@ -1266,13 +1268,15 @@ pub const Compiler = struct {...@@ -1266,13 +1268,15 @@ pub const Compiler = struct {
1266 try header.write(writer, self.errContext(id_token));1268 try header.write(writer, self.errContext(id_token));
1267 }1269 }
12681270
1269 pub fn writeResourceDataNoPadding(writer: anytype, data_reader: anytype, data_size: u32) !void {1271 pub fn writeResourceDataNoPadding(writer: anytype, data_reader: *std.Io.Reader, data_size: u32) !void {
1270 var limited_reader = std.io.limitedReader(data_reader, data_size);1272 var adapted = writer.adaptToNewApi();
12711273 var buffer: [128]u8 = undefined;
1272 try limited_reader.reader().readRemaining(writer);1274 adapted.new_interface.buffer = &buffer;
1275 try data_reader.streamExact(&adapted.new_interface, data_size);
1276 try adapted.new_interface.flush();
1273 }1277 }
12741278
1275 pub fn writeResourceData(writer: anytype, data_reader: anytype, data_size: u32) !void {1279 pub fn writeResourceData(writer: anytype, data_reader: *std.Io.Reader, data_size: u32) !void {
1276 try writeResourceDataNoPadding(writer, data_reader, data_size);1280 try writeResourceDataNoPadding(writer, data_reader, data_size);
1277 try writeDataPadding(writer, data_size);1281 try writeDataPadding(writer, data_size);
1278 }1282 }
...@@ -1337,8 +1341,8 @@ pub const Compiler = struct {...@@ -1337,8 +1341,8 @@ pub const Compiler = struct {
13371341
1338 try header.write(writer, self.errContext(node.id));1342 try header.write(writer, self.errContext(node.id));
13391343
1340 var data_fbs = std.io.fixedBufferStream(data_buffer.items);1344 var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
1341 try writeResourceData(writer, data_fbs.reader(), data_size);1345 try writeResourceData(writer, &data_fbs, data_size);
1342 }1346 }
13431347
1344 /// Expects `data_writer` to be a LimitedWriter limited to u32, meaning all writes to1348 /// Expects `data_writer` to be a LimitedWriter limited to u32, meaning all writes to
...@@ -1730,8 +1734,8 @@ pub const Compiler = struct {...@@ -1730,8 +1734,8 @@ pub const Compiler = struct {
17301734
1731 try header.write(writer, self.errContext(node.id));1735 try header.write(writer, self.errContext(node.id));
17321736
1733 var data_fbs = std.io.fixedBufferStream(data_buffer.items);1737 var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
1734 try writeResourceData(writer, data_fbs.reader(), data_size);1738 try writeResourceData(writer, &data_fbs, data_size);
1735 }1739 }
17361740
1737 fn writeDialogHeaderAndStrings(1741 fn writeDialogHeaderAndStrings(
...@@ -2044,8 +2048,8 @@ pub const Compiler = struct {...@@ -2044,8 +2048,8 @@ pub const Compiler = struct {
20442048
2045 try header.write(writer, self.errContext(node.id));2049 try header.write(writer, self.errContext(node.id));
20462050
2047 var data_fbs = std.io.fixedBufferStream(data_buffer.items);2051 var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
2048 try writeResourceData(writer, data_fbs.reader(), data_size);2052 try writeResourceData(writer, &data_fbs, data_size);
2049 }2053 }
20502054
2051 /// Weight and italic carry over from previous FONT statements within a single resource,2055 /// Weight and italic carry over from previous FONT statements within a single resource,
...@@ -2119,8 +2123,8 @@ pub const Compiler = struct {...@@ -2119,8 +2123,8 @@ pub const Compiler = struct {
21192123
2120 try header.write(writer, self.errContext(node.id));2124 try header.write(writer, self.errContext(node.id));
21212125
2122 var data_fbs = std.io.fixedBufferStream(data_buffer.items);2126 var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
2123 try writeResourceData(writer, data_fbs.reader(), data_size);2127 try writeResourceData(writer, &data_fbs, data_size);
2124 }2128 }
21252129
2126 /// Expects `data_writer` to be a LimitedWriter limited to u32, meaning all writes to2130 /// Expects `data_writer` to be a LimitedWriter limited to u32, meaning all writes to
...@@ -2384,8 +2388,8 @@ pub const Compiler = struct {...@@ -2384,8 +2388,8 @@ pub const Compiler = struct {
23842388
2385 try header.write(writer, self.errContext(node.id));2389 try header.write(writer, self.errContext(node.id));
23862390
2387 var data_fbs = std.io.fixedBufferStream(data_buffer.items);2391 var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
2388 try writeResourceData(writer, data_fbs.reader(), data_size);2392 try writeResourceData(writer, &data_fbs, data_size);
2389 }2393 }
23902394
2391 /// Expects writer to be a LimitedWriter limited to u16, meaning all writes to2395 /// Expects writer to be a LimitedWriter limited to u16, meaning all writes to
...@@ -3319,8 +3323,8 @@ pub const StringTable = struct {...@@ -3319,8 +3323,8 @@ pub const StringTable = struct {
3319 // we fully control and know are numbers, so they have a fixed size.3323 // we fully control and know are numbers, so they have a fixed size.
3320 try header.writeAssertNoOverflow(writer);3324 try header.writeAssertNoOverflow(writer);
33213325
3322 var data_fbs = std.io.fixedBufferStream(data_buffer.items);3326 var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
3323 try Compiler.writeResourceData(writer, data_fbs.reader(), data_size);3327 try Compiler.writeResourceData(writer, &data_fbs, data_size);
3324 }3328 }
3325 };3329 };
33263330
lib/compiler/resinator/cvtres.zig+24-29
...@@ -65,7 +65,7 @@ pub const ParseResOptions = struct {...@@ -65,7 +65,7 @@ pub const ParseResOptions = struct {
65};65};
6666
67/// The returned ParsedResources should be freed by calling its `deinit` function.67/// The returned ParsedResources should be freed by calling its `deinit` function.
68pub fn parseRes(allocator: Allocator, reader: anytype, options: ParseResOptions) !ParsedResources {68pub fn parseRes(allocator: Allocator, reader: *std.Io.Reader, options: ParseResOptions) !ParsedResources {
69 var resources = ParsedResources.init(allocator);69 var resources = ParsedResources.init(allocator);
70 errdefer resources.deinit();70 errdefer resources.deinit();
7171
...@@ -74,7 +74,7 @@ pub fn parseRes(allocator: Allocator, reader: anytype, options: ParseResOptions)...@@ -74,7 +74,7 @@ pub fn parseRes(allocator: Allocator, reader: anytype, options: ParseResOptions)
74 return resources;74 return resources;
75}75}
7676
77pub fn parseResInto(resources: *ParsedResources, reader: anytype, options: ParseResOptions) !void {77pub fn parseResInto(resources: *ParsedResources, reader: *std.Io.Reader, options: ParseResOptions) !void {
78 const allocator = resources.allocator;78 const allocator = resources.allocator;
79 var bytes_remaining: u64 = options.max_size;79 var bytes_remaining: u64 = options.max_size;
80 {80 {
...@@ -103,43 +103,38 @@ pub const ResourceAndSize = struct {...@@ -103,43 +103,38 @@ pub const ResourceAndSize = struct {
103 total_size: u64,103 total_size: u64,
104};104};
105105
106pub fn parseResource(allocator: Allocator, reader: anytype, max_size: u64) !ResourceAndSize {106pub fn parseResource(allocator: Allocator, reader: *std.Io.Reader, max_size: u64) !ResourceAndSize {
107 var header_counting_reader = std.io.countingReader(reader);107 const data_size = try reader.takeInt(u32, .little);
108 const header_reader = header_counting_reader.reader();108 const header_size = try reader.takeInt(u32, .little);
109 const data_size = try header_reader.readInt(u32, .little);
110 const header_size = try header_reader.readInt(u32, .little);
111 const total_size: u64 = @as(u64, header_size) + data_size;109 const total_size: u64 = @as(u64, header_size) + data_size;
112 if (total_size > max_size) return error.ImpossibleSize;110 if (total_size > max_size) return error.ImpossibleSize;
113111
114 var header_bytes_available = header_size -| 8;112 const remaining_header_bytes = try reader.take(header_size -| 8);
115 var type_reader = std.io.limitedReader(header_reader, header_bytes_available);113 var remaining_header_reader: std.Io.Reader = .fixed(remaining_header_bytes);
116 const type_value = try parseNameOrOrdinal(allocator, type_reader.reader());114 const type_value = try parseNameOrOrdinal(allocator, &remaining_header_reader);
117 errdefer type_value.deinit(allocator);115 errdefer type_value.deinit(allocator);
118116
119 header_bytes_available -|= @intCast(type_value.byteLen());117 const name_value = try parseNameOrOrdinal(allocator, &remaining_header_reader);
120 var name_reader = std.io.limitedReader(header_reader, header_bytes_available);
121 const name_value = try parseNameOrOrdinal(allocator, name_reader.reader());
122 errdefer name_value.deinit(allocator);118 errdefer name_value.deinit(allocator);
123119
124 const padding_after_name = numPaddingBytesNeeded(@intCast(header_counting_reader.bytes_read));120 const padding_after_name = numPaddingBytesNeeded(@intCast(remaining_header_reader.seek));
125 try header_reader.skipBytes(padding_after_name, .{ .buf_size = 3 });121 try remaining_header_reader.discardAll(padding_after_name);
126122
127 std.debug.assert(header_counting_reader.bytes_read % 4 == 0);123 std.debug.assert(remaining_header_reader.seek % 4 == 0);
128 const data_version = try header_reader.readInt(u32, .little);124 const data_version = try remaining_header_reader.takeInt(u32, .little);
129 const memory_flags: MemoryFlags = @bitCast(try header_reader.readInt(u16, .little));125 const memory_flags: MemoryFlags = @bitCast(try remaining_header_reader.takeInt(u16, .little));
130 const language: Language = @bitCast(try header_reader.readInt(u16, .little));126 const language: Language = @bitCast(try remaining_header_reader.takeInt(u16, .little));
131 const version = try header_reader.readInt(u32, .little);127 const version = try remaining_header_reader.takeInt(u32, .little);
132 const characteristics = try header_reader.readInt(u32, .little);128 const characteristics = try remaining_header_reader.takeInt(u32, .little);
133129
134 const header_bytes_read = header_counting_reader.bytes_read;130 if (remaining_header_reader.seek != remaining_header_reader.end) return error.HeaderSizeMismatch;
135 if (header_size != header_bytes_read) return error.HeaderSizeMismatch;
136131
137 const data = try allocator.alloc(u8, data_size);132 const data = try allocator.alloc(u8, data_size);
138 errdefer allocator.free(data);133 errdefer allocator.free(data);
139 try reader.readNoEof(data);134 try reader.readSliceAll(data);
140135
141 const padding_after_data = numPaddingBytesNeeded(@intCast(data_size));136 const padding_after_data = numPaddingBytesNeeded(@intCast(data_size));
142 try reader.skipBytes(padding_after_data, .{ .buf_size = 3 });137 try reader.discardAll(padding_after_data);
143138
144 return .{139 return .{
145 .resource = .{140 .resource = .{
...@@ -156,10 +151,10 @@ pub fn parseResource(allocator: Allocator, reader: anytype, max_size: u64) !Reso...@@ -156,10 +151,10 @@ pub fn parseResource(allocator: Allocator, reader: anytype, max_size: u64) !Reso
156 };151 };
157}152}
158153
159pub fn parseNameOrOrdinal(allocator: Allocator, reader: anytype) !NameOrOrdinal {154pub fn parseNameOrOrdinal(allocator: Allocator, reader: *std.Io.Reader) !NameOrOrdinal {
160 const first_code_unit = try reader.readInt(u16, .little);155 const first_code_unit = try reader.takeInt(u16, .little);
161 if (first_code_unit == 0xFFFF) {156 if (first_code_unit == 0xFFFF) {
162 const ordinal_value = try reader.readInt(u16, .little);157 const ordinal_value = try reader.takeInt(u16, .little);
163 return .{ .ordinal = ordinal_value };158 return .{ .ordinal = ordinal_value };
164 }159 }
165 var name_buf = try std.ArrayListUnmanaged(u16).initCapacity(allocator, 16);160 var name_buf = try std.ArrayListUnmanaged(u16).initCapacity(allocator, 16);
...@@ -167,7 +162,7 @@ pub fn parseNameOrOrdinal(allocator: Allocator, reader: anytype) !NameOrOrdinal...@@ -167,7 +162,7 @@ pub fn parseNameOrOrdinal(allocator: Allocator, reader: anytype) !NameOrOrdinal
167 var code_unit = first_code_unit;162 var code_unit = first_code_unit;
168 while (code_unit != 0) {163 while (code_unit != 0) {
169 try name_buf.append(allocator, std.mem.nativeToLittle(u16, code_unit));164 try name_buf.append(allocator, std.mem.nativeToLittle(u16, code_unit));
170 code_unit = try reader.readInt(u16, .little);165 code_unit = try reader.takeInt(u16, .little);
171 }166 }
172 return .{ .name = try name_buf.toOwnedSliceSentinel(allocator, 0) };167 return .{ .name = try name_buf.toOwnedSliceSentinel(allocator, 0) };
173}168}
lib/compiler/resinator/errors.zig+2-2
...@@ -1112,7 +1112,7 @@ const CorrespondingLines = struct {...@@ -1112,7 +1112,7 @@ const CorrespondingLines = struct {
11121112
1113 try corresponding_lines.writeLineFromStreamVerbatim(1113 try corresponding_lines.writeLineFromStreamVerbatim(
1114 writer,1114 writer,
1115 corresponding_lines.buffered_reader.reader(),1115 corresponding_lines.buffered_reader.interface.adaptToOldInterface(),
1116 corresponding_span.start_line,1116 corresponding_span.start_line,
1117 );1117 );
11181118
...@@ -1155,7 +1155,7 @@ const CorrespondingLines = struct {...@@ -1155,7 +1155,7 @@ const CorrespondingLines = struct {
11551155
1156 try self.writeLineFromStreamVerbatim(1156 try self.writeLineFromStreamVerbatim(
1157 writer,1157 writer,
1158 self.buffered_reader.reader(),1158 self.buffered_reader.interface.adaptToOldInterface(),
1159 self.line_num,1159 self.line_num,
1160 );1160 );
11611161
lib/compiler/resinator/ico.zig+2-1
...@@ -14,8 +14,9 @@ pub fn read(allocator: std.mem.Allocator, reader: anytype, max_size: u64) ReadEr...@@ -14,8 +14,9 @@ pub fn read(allocator: std.mem.Allocator, reader: anytype, max_size: u64) ReadEr
14 // Some Reader implementations have an empty ReadError error set which would14 // Some Reader implementations have an empty ReadError error set which would
15 // cause 'unreachable else' if we tried to use an else in the switch, so we15 // cause 'unreachable else' if we tried to use an else in the switch, so we
16 // need to detect this case and not try to translate to ReadError16 // need to detect this case and not try to translate to ReadError
17 const anyerror_reader_errorset = @TypeOf(reader).Error == anyerror;
17 const empty_reader_errorset = @typeInfo(@TypeOf(reader).Error).error_set == null or @typeInfo(@TypeOf(reader).Error).error_set.?.len == 0;18 const empty_reader_errorset = @typeInfo(@TypeOf(reader).Error).error_set == null or @typeInfo(@TypeOf(reader).Error).error_set.?.len == 0;
18 if (empty_reader_errorset) {19 if (empty_reader_errorset and !anyerror_reader_errorset) {
19 return readAnyError(allocator, reader, max_size) catch |err| switch (err) {20 return readAnyError(allocator, reader, max_size) catch |err| switch (err) {
20 error.EndOfStream => error.UnexpectedEOF,21 error.EndOfStream => error.UnexpectedEOF,
21 else => |e| return e,22 else => |e| return e,
lib/compiler/resinator/main.zig+2-2
...@@ -325,8 +325,8 @@ pub fn main() !void {...@@ -325,8 +325,8 @@ pub fn main() !void {
325 std.debug.assert(options.output_format == .coff);325 std.debug.assert(options.output_format == .coff);
326326
327 // TODO: Maybe use a buffered file reader instead of reading file into memory -> fbs327 // TODO: Maybe use a buffered file reader instead of reading file into memory -> fbs
328 var fbs = std.io.fixedBufferStream(res_data.bytes);328 var res_reader: std.Io.Reader = .fixed(res_data.bytes);
329 break :resources cvtres.parseRes(allocator, fbs.reader(), .{ .max_size = res_data.bytes.len }) catch |err| {329 break :resources cvtres.parseRes(allocator, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| {
330 // TODO: Better errors330 // TODO: Better errors
331 try error_handler.emitMessage(allocator, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) });331 try error_handler.emitMessage(allocator, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
332 std.process.exit(1);332 std.process.exit(1);
lib/docs/main.js+13-3
...@@ -129,6 +129,11 @@...@@ -129,6 +129,11 @@
129 domSearch.addEventListener('input', onSearchChange, false);129 domSearch.addEventListener('input', onSearchChange, false);
130 window.addEventListener('keydown', onWindowKeyDown, false);130 window.addEventListener('keydown', onWindowKeyDown, false);
131 onHashChange(null);131 onHashChange(null);
132 if (domSearch.value) {
133 // user started typing a search query while the page was loading
134 curSearchIndex = -1;
135 startAsyncSearch();
136 }
132 });137 });
133 });138 });
134139
...@@ -643,6 +648,7 @@...@@ -643,6 +648,7 @@
643 }648 }
644649
645 function onHashChange(state) {650 function onHashChange(state) {
651 // Use a non-null state value to prevent the window scrolling if the user goes back to this history entry.
646 history.replaceState({}, "");652 history.replaceState({}, "");
647 navigate(location.hash);653 navigate(location.hash);
648 if (state == null) window.scrollTo({top: 0});654 if (state == null) window.scrollTo({top: 0});
...@@ -650,13 +656,11 @@...@@ -650,13 +656,11 @@
650656
651 function onPopState(ev) {657 function onPopState(ev) {
652 onHashChange(ev.state);658 onHashChange(ev.state);
659 syncDomSearch();
653 }660 }
654661
655 function navigate(location_hash) {662 function navigate(location_hash) {
656 updateCurNav(location_hash);663 updateCurNav(location_hash);
657 if (domSearch.value !== curNavSearch) {
658 domSearch.value = curNavSearch;
659 }
660 render();664 render();
661 if (imFeelingLucky) {665 if (imFeelingLucky) {
662 imFeelingLucky = false;666 imFeelingLucky = false;
...@@ -664,6 +668,12 @@...@@ -664,6 +668,12 @@
664 }668 }
665 }669 }
666670
671 function syncDomSearch() {
672 if (domSearch.value !== curNavSearch) {
673 domSearch.value = curNavSearch;
674 }
675 }
676
667 function activateSelectedResult() {677 function activateSelectedResult() {
668 if (domSectSearchResults.classList.contains("hidden")) {678 if (domSectSearchResults.classList.contains("hidden")) {
669 return;679 return;
lib/docs/wasm/markdown.zig+6-7
...@@ -143,13 +143,12 @@ fn mainImpl() !void {...@@ -143,13 +143,12 @@ fn mainImpl() !void {
143 var parser = try Parser.init(gpa);143 var parser = try Parser.init(gpa);
144 defer parser.deinit();144 defer parser.deinit();
145145
146 var stdin_buf = std.io.bufferedReader(std.fs.File.stdin().deprecatedReader());146 var stdin_buffer: [1024]u8 = undefined;
147 var line_buf = std.ArrayList(u8).init(gpa);147 var stdin_reader = std.fs.File.stdin().reader(&stdin_buffer);
148 defer line_buf.deinit();148
149 while (stdin_buf.reader().streamUntilDelimiter(line_buf.writer(), '\n', null)) {149 while (stdin_reader.takeDelimiterExclusive('\n')) |line| {
150 if (line_buf.getLastOrNull() == '\r') _ = line_buf.pop();150 const trimmed = std.mem.trimRight(u8, line, '\r');
151 try parser.feedLine(line_buf.items);151 try parser.feedLine(trimmed);
152 line_buf.clearRetainingCapacity();
153 } else |err| switch (err) {152 } else |err| switch (err) {
154 error.EndOfStream => {},153 error.EndOfStream => {},
155 else => |e| return e,154 else => |e| return e,
lib/docs/wasm/markdown/Parser.zig+40-26
...@@ -29,13 +29,14 @@ const Node = Document.Node;...@@ -29,13 +29,14 @@ const Node = Document.Node;
29const ExtraIndex = Document.ExtraIndex;29const ExtraIndex = Document.ExtraIndex;
30const ExtraData = Document.ExtraData;30const ExtraData = Document.ExtraData;
31const StringIndex = Document.StringIndex;31const StringIndex = Document.StringIndex;
32const ArrayList = std.ArrayListUnmanaged;
3233
33nodes: Node.List = .{},34nodes: Node.List = .{},
34extra: std.ArrayListUnmanaged(u32) = .empty,35extra: ArrayList(u32) = .empty,
35scratch_extra: std.ArrayListUnmanaged(u32) = .empty,36scratch_extra: ArrayList(u32) = .empty,
36string_bytes: std.ArrayListUnmanaged(u8) = .empty,37string_bytes: ArrayList(u8) = .empty,
37scratch_string: std.ArrayListUnmanaged(u8) = .empty,38scratch_string: ArrayList(u8) = .empty,
38pending_blocks: std.ArrayListUnmanaged(Block) = .empty,39pending_blocks: ArrayList(Block) = .empty,
39allocator: Allocator,40allocator: Allocator,
4041
41const Parser = @This();42const Parser = @This();
...@@ -86,7 +87,8 @@ const Block = struct {...@@ -86,7 +87,8 @@ const Block = struct {
86 continuation_indent: usize,87 continuation_indent: usize,
87 },88 },
88 table: struct {89 table: struct {
89 column_alignments: std.BoundedArray(Node.TableCellAlignment, max_table_columns) = .{},90 column_alignments_buffer: [max_table_columns]Node.TableCellAlignment,
91 column_alignments_len: usize,
90 },92 },
91 heading: struct {93 heading: struct {
92 /// Between 1 and 6, inclusive.94 /// Between 1 and 6, inclusive.
...@@ -354,7 +356,8 @@ const BlockStart = struct {...@@ -354,7 +356,8 @@ const BlockStart = struct {
354 continuation_indent: usize,356 continuation_indent: usize,
355 },357 },
356 table_row: struct {358 table_row: struct {
357 cells: std.BoundedArray([]const u8, max_table_columns),359 cells_buffer: [max_table_columns][]const u8,
360 cells_len: usize,
358 },361 },
359 heading: struct {362 heading: struct {
360 /// Between 1 and 6, inclusive.363 /// Between 1 and 6, inclusive.
...@@ -422,7 +425,8 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {...@@ -422,7 +425,8 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {
422 try p.pending_blocks.append(p.allocator, .{425 try p.pending_blocks.append(p.allocator, .{
423 .tag = .table,426 .tag = .table,
424 .data = .{ .table = .{427 .data = .{ .table = .{
425 .column_alignments = .{},428 .column_alignments_buffer = undefined,
429 .column_alignments_len = 0,
426 } },430 } },
427 .string_start = p.scratch_string.items.len,431 .string_start = p.scratch_string.items.len,
428 .extra_start = p.scratch_extra.items.len,432 .extra_start = p.scratch_extra.items.len,
...@@ -431,15 +435,19 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {...@@ -431,15 +435,19 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {
431435
432 const current_row = p.scratch_extra.items.len - p.pending_blocks.getLast().extra_start;436 const current_row = p.scratch_extra.items.len - p.pending_blocks.getLast().extra_start;
433 if (current_row <= 1) {437 if (current_row <= 1) {
434 if (parseTableHeaderDelimiter(block_start.data.table_row.cells)) |alignments| {438 var buffer: [max_table_columns]Node.TableCellAlignment = undefined;
435 p.pending_blocks.items[p.pending_blocks.items.len - 1].data.table.column_alignments = alignments;439 const table_row = &block_start.data.table_row;
440 if (parseTableHeaderDelimiter(table_row.cells_buffer[0..table_row.cells_len], &buffer)) |alignments| {
441 const table = &p.pending_blocks.items[p.pending_blocks.items.len - 1].data.table;
442 @memcpy(table.column_alignments_buffer[0..alignments.len], alignments);
443 table.column_alignments_len = alignments.len;
436 if (current_row == 1) {444 if (current_row == 1) {
437 // We need to go back and mark the header row and its column445 // We need to go back and mark the header row and its column
438 // alignments.446 // alignments.
439 const datas = p.nodes.items(.data);447 const datas = p.nodes.items(.data);
440 const header_data = datas[p.scratch_extra.getLast()];448 const header_data = datas[p.scratch_extra.getLast()];
441 for (p.extraChildren(header_data.container.children), 0..) |header_cell, i| {449 for (p.extraChildren(header_data.container.children), 0..) |header_cell, i| {
442 const alignment = if (i < alignments.len) alignments.buffer[i] else .unset;450 const alignment = if (i < alignments.len) alignments[i] else .unset;
443 const cell_data = &datas[@intFromEnum(header_cell)].table_cell;451 const cell_data = &datas[@intFromEnum(header_cell)].table_cell;
444 cell_data.info.alignment = alignment;452 cell_data.info.alignment = alignment;
445 cell_data.info.header = true;453 cell_data.info.header = true;
...@@ -480,8 +488,10 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {...@@ -480,8 +488,10 @@ fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {
480 // available in the BlockStart. We can immediately parse and append488 // available in the BlockStart. We can immediately parse and append
481 // these children now.489 // these children now.
482 const containing_table = p.pending_blocks.items[p.pending_blocks.items.len - 2];490 const containing_table = p.pending_blocks.items[p.pending_blocks.items.len - 2];
483 const column_alignments = containing_table.data.table.column_alignments.slice();491 const table = &containing_table.data.table;
484 for (block_start.data.table_row.cells.slice(), 0..) |cell_content, i| {492 const column_alignments = table.column_alignments_buffer[0..table.column_alignments_len];
493 const table_row = &block_start.data.table_row;
494 for (table_row.cells_buffer[0..table_row.cells_len], 0..) |cell_content, i| {
485 const cell_children = try p.parseInlines(cell_content);495 const cell_children = try p.parseInlines(cell_content);
486 const alignment = if (i < column_alignments.len) column_alignments[i] else .unset;496 const alignment = if (i < column_alignments.len) column_alignments[i] else .unset;
487 const cell = try p.addNode(.{497 const cell = try p.addNode(.{
...@@ -523,7 +533,8 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {...@@ -523,7 +533,8 @@ fn startBlock(p: *Parser, line: []const u8) !?BlockStart {
523 return .{533 return .{
524 .tag = .table_row,534 .tag = .table_row,
525 .data = .{ .table_row = .{535 .data = .{ .table_row = .{
526 .cells = table_row.cells,536 .cells_buffer = table_row.cells_buffer,
537 .cells_len = table_row.cells_len,
527 } },538 } },
528 .rest = "",539 .rest = "",
529 };540 };
...@@ -606,7 +617,8 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {...@@ -606,7 +617,8 @@ fn startListItem(unindented_line: []const u8) ?ListItemStart {
606}617}
607618
608const TableRowStart = struct {619const TableRowStart = struct {
609 cells: std.BoundedArray([]const u8, max_table_columns),620 cells_buffer: [max_table_columns][]const u8,
621 cells_len: usize,
610};622};
611623
612fn startTableRow(unindented_line: []const u8) ?TableRowStart {624fn startTableRow(unindented_line: []const u8) ?TableRowStart {
...@@ -615,7 +627,8 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {...@@ -615,7 +627,8 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {
615 mem.endsWith(u8, unindented_line, "\\|") or627 mem.endsWith(u8, unindented_line, "\\|") or
616 !mem.endsWith(u8, unindented_line, "|")) return null;628 !mem.endsWith(u8, unindented_line, "|")) return null;
617629
618 var cells: std.BoundedArray([]const u8, max_table_columns) = .{};630 var cells_buffer: [max_table_columns][]const u8 = undefined;
631 var cells: ArrayList([]const u8) = .initBuffer(&cells_buffer);
619 const table_row_content = unindented_line[1 .. unindented_line.len - 1];632 const table_row_content = unindented_line[1 .. unindented_line.len - 1];
620 var cell_start: usize = 0;633 var cell_start: usize = 0;
621 var i: usize = 0;634 var i: usize = 0;
...@@ -623,7 +636,7 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {...@@ -623,7 +636,7 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {
623 switch (table_row_content[i]) {636 switch (table_row_content[i]) {
624 '\\' => i += 1,637 '\\' => i += 1,
625 '|' => {638 '|' => {
626 cells.append(table_row_content[cell_start..i]) catch return null;639 cells.appendBounded(table_row_content[cell_start..i]) catch return null;
627 cell_start = i + 1;640 cell_start = i + 1;
628 },641 },
629 '`' => {642 '`' => {
...@@ -641,20 +654,21 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {...@@ -641,20 +654,21 @@ fn startTableRow(unindented_line: []const u8) ?TableRowStart {
641 else => {},654 else => {},
642 }655 }
643 }656 }
644 cells.append(table_row_content[cell_start..]) catch return null;657 cells.appendBounded(table_row_content[cell_start..]) catch return null;
645658
646 return .{ .cells = cells };659 return .{ .cells_buffer = cells_buffer, .cells_len = cells.items.len };
647}660}
648661
649fn parseTableHeaderDelimiter(662fn parseTableHeaderDelimiter(
650 row_cells: std.BoundedArray([]const u8, max_table_columns),663 row_cells: []const []const u8,
651) ?std.BoundedArray(Node.TableCellAlignment, max_table_columns) {664 buffer: []Node.TableCellAlignment,
652 var alignments: std.BoundedArray(Node.TableCellAlignment, max_table_columns) = .{};665) ?[]Node.TableCellAlignment {
653 for (row_cells.slice()) |content| {666 var alignments: ArrayList(Node.TableCellAlignment) = .initBuffer(buffer);
667 for (row_cells) |content| {
654 const alignment = parseTableHeaderDelimiterCell(content) orelse return null;668 const alignment = parseTableHeaderDelimiterCell(content) orelse return null;
655 alignments.appendAssumeCapacity(alignment);669 alignments.appendAssumeCapacity(alignment);
656 }670 }
657 return alignments;671 return alignments.items;
658}672}
659673
660fn parseTableHeaderDelimiterCell(content: []const u8) ?Node.TableCellAlignment {674fn parseTableHeaderDelimiterCell(content: []const u8) ?Node.TableCellAlignment {
...@@ -928,8 +942,8 @@ const InlineParser = struct {...@@ -928,8 +942,8 @@ const InlineParser = struct {
928 parent: *Parser,942 parent: *Parser,
929 content: []const u8,943 content: []const u8,
930 pos: usize = 0,944 pos: usize = 0,
931 pending_inlines: std.ArrayListUnmanaged(PendingInline) = .empty,945 pending_inlines: ArrayList(PendingInline) = .empty,
932 completed_inlines: std.ArrayListUnmanaged(CompletedInline) = .empty,946 completed_inlines: ArrayList(CompletedInline) = .empty,
933947
934 const PendingInline = struct {948 const PendingInline = struct {
935 tag: Tag,949 tag: Tag,
lib/std/Build/Fuzz.zig+16-23
...@@ -234,7 +234,7 @@ pub const Previous = struct {...@@ -234,7 +234,7 @@ pub const Previous = struct {
234};234};
235pub fn sendUpdate(235pub fn sendUpdate(
236 fuzz: *Fuzz,236 fuzz: *Fuzz,
237 socket: *std.http.WebSocket,237 socket: *std.http.Server.WebSocket,
238 prev: *Previous,238 prev: *Previous,
239) !void {239) !void {
240 fuzz.coverage_mutex.lock();240 fuzz.coverage_mutex.lock();
...@@ -263,36 +263,36 @@ pub fn sendUpdate(...@@ -263,36 +263,36 @@ pub fn sendUpdate(
263 .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len),263 .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len),
264 .start_timestamp = coverage_map.start_timestamp,264 .start_timestamp = coverage_map.start_timestamp,
265 };265 };
266 const iovecs: [5]std.posix.iovec_const = .{266 var iovecs: [5][]const u8 = .{
267 makeIov(@ptrCast(&header)),267 @ptrCast(&header),
268 makeIov(@ptrCast(coverage_map.coverage.directories.keys())),268 @ptrCast(coverage_map.coverage.directories.keys()),
269 makeIov(@ptrCast(coverage_map.coverage.files.keys())),269 @ptrCast(coverage_map.coverage.files.keys()),
270 makeIov(@ptrCast(coverage_map.source_locations)),270 @ptrCast(coverage_map.source_locations),
271 makeIov(coverage_map.coverage.string_bytes.items),271 coverage_map.coverage.string_bytes.items,
272 };272 };
273 try socket.writeMessagev(&iovecs, .binary);273 try socket.writeMessageVec(&iovecs, .binary);
274 }274 }
275275
276 const header: abi.CoverageUpdateHeader = .{276 const header: abi.CoverageUpdateHeader = .{
277 .n_runs = n_runs,277 .n_runs = n_runs,
278 .unique_runs = unique_runs,278 .unique_runs = unique_runs,
279 };279 };
280 const iovecs: [2]std.posix.iovec_const = .{280 var iovecs: [2][]const u8 = .{
281 makeIov(@ptrCast(&header)),281 @ptrCast(&header),
282 makeIov(@ptrCast(seen_pcs)),282 @ptrCast(seen_pcs),
283 };283 };
284 try socket.writeMessagev(&iovecs, .binary);284 try socket.writeMessageVec(&iovecs, .binary);
285285
286 prev.unique_runs = unique_runs;286 prev.unique_runs = unique_runs;
287 }287 }
288288
289 if (prev.entry_points != coverage_map.entry_points.items.len) {289 if (prev.entry_points != coverage_map.entry_points.items.len) {
290 const header: abi.EntryPointHeader = .init(@intCast(coverage_map.entry_points.items.len));290 const header: abi.EntryPointHeader = .init(@intCast(coverage_map.entry_points.items.len));
291 const iovecs: [2]std.posix.iovec_const = .{291 var iovecs: [2][]const u8 = .{
292 makeIov(@ptrCast(&header)),292 @ptrCast(&header),
293 makeIov(@ptrCast(coverage_map.entry_points.items)),293 @ptrCast(coverage_map.entry_points.items),
294 };294 };
295 try socket.writeMessagev(&iovecs, .binary);295 try socket.writeMessageVec(&iovecs, .binary);
296296
297 prev.entry_points = coverage_map.entry_points.items.len;297 prev.entry_points = coverage_map.entry_points.items.len;
298 }298 }
...@@ -448,10 +448,3 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte...@@ -448,10 +448,3 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
448 }448 }
449 try coverage_map.entry_points.append(fuzz.ws.gpa, @intCast(index));449 try coverage_map.entry_points.append(fuzz.ws.gpa, @intCast(index));
450}450}
451
452fn makeIov(s: []const u8) std.posix.iovec_const {
453 return .{
454 .base = s.ptr,
455 .len = s.len,
456 };
457}
lib/std/Build/Step/Compile.zig+1-1
...@@ -1851,7 +1851,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -1851,7 +1851,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
1851 const maybe_output_dir = step.evalZigProcess(1851 const maybe_output_dir = step.evalZigProcess(
1852 zig_args,1852 zig_args,
1853 options.progress_node,1853 options.progress_node,
1854 (b.graph.incremental == true) and options.watch,1854 (b.graph.incremental == true) and (options.watch or options.web_server != null),
1855 options.web_server,1855 options.web_server,
1856 options.gpa,1856 options.gpa,
1857 ) catch |err| switch (err) {1857 ) catch |err| switch (err) {
lib/std/Build/Watch.zig+36-3
...@@ -1,13 +1,18 @@...@@ -1,13 +1,18 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("../std.zig");2const std = @import("../std.zig");
3const Watch = @This();
4const Step = std.Build.Step;3const Step = std.Build.Step;
5const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
6const assert = std.debug.assert;5const assert = std.debug.assert;
7const fatal = std.process.fatal;6const fatal = std.process.fatal;
7const Watch = @This();
8const FsEvents = @import("Watch/FsEvents.zig");
89
9dir_table: DirTable,
10os: Os,10os: Os,
11/// The number to show as the number of directories being watched.
12dir_count: usize,
13// These fields are common to most implementations so are kept here for simplicity.
14// They are `undefined` on implementations which do not utilize then.
15dir_table: DirTable,
11generation: Generation,16generation: Generation,
1217
13pub const have_impl = Os != void;18pub const have_impl = Os != void;
...@@ -97,6 +102,7 @@ const Os = switch (builtin.os.tag) {...@@ -97,6 +102,7 @@ const Os = switch (builtin.os.tag) {
97 fn init() !Watch {102 fn init() !Watch {
98 return .{103 return .{
99 .dir_table = .{},104 .dir_table = .{},
105 .dir_count = 0,
100 .os = switch (builtin.os.tag) {106 .os = switch (builtin.os.tag) {
101 .linux => .{107 .linux => .{
102 .handle_table = .{},108 .handle_table = .{},
...@@ -273,6 +279,7 @@ const Os = switch (builtin.os.tag) {...@@ -273,6 +279,7 @@ const Os = switch (builtin.os.tag) {
273 }279 }
274 w.generation +%= 1;280 w.generation +%= 1;
275 }281 }
282 w.dir_count = w.dir_table.count();
276 }283 }
277284
278 fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult {285 fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult {
...@@ -408,6 +415,7 @@ const Os = switch (builtin.os.tag) {...@@ -408,6 +415,7 @@ const Os = switch (builtin.os.tag) {
408 fn init() !Watch {415 fn init() !Watch {
409 return .{416 return .{
410 .dir_table = .{},417 .dir_table = .{},
418 .dir_count = 0,
411 .os = switch (builtin.os.tag) {419 .os = switch (builtin.os.tag) {
412 .windows => .{420 .windows => .{
413 .handle_table = .{},421 .handle_table = .{},
...@@ -572,6 +580,7 @@ const Os = switch (builtin.os.tag) {...@@ -572,6 +580,7 @@ const Os = switch (builtin.os.tag) {
572 }580 }
573 w.generation +%= 1;581 w.generation +%= 1;
574 }582 }
583 w.dir_count = w.dir_table.count();
575 }584 }
576585
577 fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult {586 fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult {
...@@ -605,7 +614,7 @@ const Os = switch (builtin.os.tag) {...@@ -605,7 +614,7 @@ const Os = switch (builtin.os.tag) {
605 };614 };
606 }615 }
607 },616 },
608 .dragonfly, .freebsd, .netbsd, .openbsd, .ios, .macos, .tvos, .visionos, .watchos => struct {617 .dragonfly, .freebsd, .netbsd, .openbsd, .ios, .tvos, .visionos, .watchos => struct {
609 const posix = std.posix;618 const posix = std.posix;
610619
611 kq_fd: i32,620 kq_fd: i32,
...@@ -639,6 +648,7 @@ const Os = switch (builtin.os.tag) {...@@ -639,6 +648,7 @@ const Os = switch (builtin.os.tag) {
639 errdefer posix.close(kq_fd);648 errdefer posix.close(kq_fd);
640 return .{649 return .{
641 .dir_table = .{},650 .dir_table = .{},
651 .dir_count = 0,
642 .os = .{652 .os = .{
643 .kq_fd = kq_fd,653 .kq_fd = kq_fd,
644 .handles = .empty,654 .handles = .empty,
...@@ -769,6 +779,7 @@ const Os = switch (builtin.os.tag) {...@@ -769,6 +779,7 @@ const Os = switch (builtin.os.tag) {
769 }779 }
770 w.generation +%= 1;780 w.generation +%= 1;
771 }781 }
782 w.dir_count = w.dir_table.count();
772 }783 }
773784
774 fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult {785 fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult {
...@@ -812,6 +823,28 @@ const Os = switch (builtin.os.tag) {...@@ -812,6 +823,28 @@ const Os = switch (builtin.os.tag) {
812 return any_dirty;823 return any_dirty;
813 }824 }
814 },825 },
826 .macos => struct {
827 fse: FsEvents,
828
829 fn init() !Watch {
830 return .{
831 .os = .{ .fse = try .init() },
832 .dir_count = 0,
833 .dir_table = undefined,
834 .generation = undefined,
835 };
836 }
837 fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void {
838 try w.os.fse.setPaths(gpa, steps);
839 w.dir_count = w.os.fse.watch_roots.len;
840 }
841 fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult {
842 return w.os.fse.wait(gpa, switch (timeout) {
843 .none => null,
844 .ms => |ms| @as(u64, ms) * std.time.ns_per_ms,
845 });
846 }
847 },
815 else => void,848 else => void,
816};849};
817850
lib/std/Build/Watch/FsEvents.zig created+493
...@@ -0,0 +1,493 @@
1//! An implementation of file-system watching based on the `FSEventStream` API in macOS.
2//! While macOS supports kqueue, it does not allow detecting changes to files without
3//! placing watches on each individual file, meaning FD limits are reached incredibly
4//! quickly. The File System Events API works differently: it implements *recursive*
5//! directory watches, managed by a system service. Rather than being in libc, the API is
6//! exposed by the CoreServices framework. To avoid a compile dependency on the framework
7//! bundle, we dynamically load CoreServices with `std.DynLib`.
8//!
9//! While the logic in this file *is* specialized to `std.Build.Watch`, efforts have been
10//! made to keep that specialization to a minimum. Other use cases could be served with
11//! relatively minimal modifications to the `watch_paths` field and its usages (in
12//! particular the `setPaths` function). We avoid using the global GCD dispatch queue in
13//! favour of creating our own and synchronizing with an explicit semaphore, meaning this
14//! logic is thread-safe and does not affect process-global state.
15//!
16//! In theory, this API is quite good at avoiding filesystem race conditions. In practice,
17//! the logic that would avoid them is currently disabled, because the build system kind
18//! of relies on them at the time of writing to avoid redundant work -- see the comment at
19//! the top of `wait` for details.
20
21const enable_debug_logs = false;
22
23core_services: std.DynLib,
24resolved_symbols: ResolvedSymbols,
25
26paths_arena: std.heap.ArenaAllocator.State,
27/// The roots of the recursive watches. FSEvents has relatively small limits on the number
28/// of watched paths, so this slice must not be too long. The paths themselves are allocated
29/// into `paths_arena`, but this slice is allocated into the GPA.
30watch_roots: [][:0]const u8,
31/// All of the paths being watched. Value is the set of steps which depend on the file/directory.
32/// Keys and values are in `paths_arena`, but this map is allocated into the GPA.
33watch_paths: std.StringArrayHashMapUnmanaged([]const *std.Build.Step),
34
35/// The semaphore we use to block the thread calling `wait` until the callback determines a relevant
36/// event has occurred. This is retained across `wait` calls for simplicity and efficiency.
37waiting_semaphore: dispatch_semaphore_t,
38/// This dispatch queue is created by us and executes serially. It exists exclusively to trigger the
39/// callbacks of the FSEventStream we create. This is not in use outside of `wait`, but is retained
40/// across `wait` calls for simplicity and efficiency.
41dispatch_queue: dispatch_queue_t,
42/// In theory, this field avoids race conditions. In practice, it is essentially unused at the time
43/// of writing. See the comment at the start of `wait` for details.
44since_event: FSEventStreamEventId,
45
46/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols
47/// is not present, `init` will close the framework and return an error.
48const ResolvedSymbols = struct {
49 FSEventStreamCreate: *const fn (
50 allocator: CFAllocatorRef,
51 callback: FSEventStreamCallback,
52 ctx: ?*const FSEventStreamContext,
53 paths_to_watch: CFArrayRef,
54 since_when: FSEventStreamEventId,
55 latency: CFTimeInterval,
56 flags: FSEventStreamCreateFlags,
57 ) callconv(.c) FSEventStreamRef,
58 FSEventStreamSetDispatchQueue: *const fn (stream: FSEventStreamRef, queue: dispatch_queue_t) callconv(.c) void,
59 FSEventStreamStart: *const fn (stream: FSEventStreamRef) callconv(.c) bool,
60 FSEventStreamStop: *const fn (stream: FSEventStreamRef) callconv(.c) void,
61 FSEventStreamInvalidate: *const fn (stream: FSEventStreamRef) callconv(.c) void,
62 FSEventStreamRelease: *const fn (stream: FSEventStreamRef) callconv(.c) void,
63 FSEventStreamGetLatestEventId: *const fn (stream: ConstFSEventStreamRef) callconv(.c) FSEventStreamEventId,
64 FSEventsGetCurrentEventId: *const fn () callconv(.c) FSEventStreamEventId,
65 CFRelease: *const fn (cf: *const anyopaque) callconv(.c) void,
66 CFArrayCreate: *const fn (
67 allocator: CFAllocatorRef,
68 values: [*]const usize,
69 num_values: CFIndex,
70 call_backs: ?*const CFArrayCallBacks,
71 ) callconv(.c) CFArrayRef,
72 CFStringCreateWithCString: *const fn (
73 alloc: CFAllocatorRef,
74 c_str: [*:0]const u8,
75 encoding: CFStringEncoding,
76 ) callconv(.c) CFStringRef,
77 CFAllocatorCreate: *const fn (allocator: CFAllocatorRef, context: *const CFAllocatorContext) callconv(.c) CFAllocatorRef,
78 kCFAllocatorUseContext: *const CFAllocatorRef,
79};
80
81pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {
82 var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch
83 return error.OpenFrameworkFailed;
84 errdefer core_services.close();
85
86 var resolved_symbols: ResolvedSymbols = undefined;
87 inline for (@typeInfo(ResolvedSymbols).@"struct".fields) |f| {
88 @field(resolved_symbols, f.name) = core_services.lookup(f.type, f.name) orelse return error.MissingCoreServicesSymbol;
89 }
90
91 return .{
92 .core_services = core_services,
93 .resolved_symbols = resolved_symbols,
94 .paths_arena = .{},
95 .watch_roots = &.{},
96 .watch_paths = .empty,
97 .waiting_semaphore = dispatch_semaphore_create(0),
98 .dispatch_queue = dispatch_queue_create("zig-watch", .SERIAL),
99 // Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order
100 // to notice any changes which happened during said work.
101 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),
102 };
103}
104
105pub fn deinit(fse: *FsEvents, gpa: Allocator) void {
106 dispatch_release(fse.waiting_semaphore);
107 dispatch_release(fse.dispatch_queue);
108 fse.core_services.close();
109
110 gpa.free(fse.watch_roots);
111 fse.watch_paths.deinit(gpa);
112 {
113 var paths_arena = fse.paths_arena.promote(gpa);
114 paths_arena.deinit();
115 }
116}
117
118pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step) !void {
119 var paths_arena_instance = fse.paths_arena.promote(gpa);
120 defer fse.paths_arena = paths_arena_instance.state;
121 const paths_arena = paths_arena_instance.allocator();
122
123 const cwd_path = try std.process.getCwdAlloc(gpa);
124 defer gpa.free(cwd_path);
125
126 var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty;
127 defer need_dirs.deinit(gpa);
128
129 fse.watch_paths.clearRetainingCapacity();
130
131 // We take `step` by pointer for a slight memory optimization in a moment.
132 for (steps) |*step| {
133 for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| {
134 const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{ cwd_path, path.root_dir.path orelse ".", path.sub_path });
135 try need_dirs.put(gpa, resolved_dir, {});
136 for (files.items) |file_name| {
137 const watch_path = if (std.mem.eql(u8, file_name, "."))
138 resolved_dir
139 else
140 try std.fs.path.join(paths_arena, &.{ resolved_dir, file_name });
141 const gop = try fse.watch_paths.getOrPut(gpa, watch_path);
142 if (gop.found_existing) {
143 const old_steps = gop.value_ptr.*;
144 const new_steps = try paths_arena.alloc(*std.Build.Step, old_steps.len + 1);
145 @memcpy(new_steps[0..old_steps.len], old_steps);
146 new_steps[old_steps.len] = step.*;
147 gop.value_ptr.* = new_steps;
148 } else {
149 // This is why we captured `step` by pointer! We can avoid allocating a slice of one
150 // step in the arena in the common case where a file is referenced by only one step.
151 gop.value_ptr.* = step[0..1];
152 }
153 }
154 }
155 }
156
157 {
158 // There's no point looking at directories inside other ones (e.g. "/foo" and "/foo/bar").
159 // To eliminate these, we'll re-add directories in order of path length with a redundancy check.
160 const old_dirs = try gpa.dupe([]const u8, need_dirs.keys());
161 defer gpa.free(old_dirs);
162 std.mem.sort([]const u8, old_dirs, {}, struct {
163 fn lessThan(ctx: void, a: []const u8, b: []const u8) bool {
164 ctx;
165 return std.mem.lessThan(u8, a, b);
166 }
167 }.lessThan);
168 need_dirs.clearRetainingCapacity();
169 for (old_dirs) |dir_path| {
170 var it: std.fs.path.ComponentIterator(.posix, u8) = try .init(dir_path);
171 while (it.next()) |component| {
172 if (need_dirs.contains(component.path)) {
173 // this path is '/foo/bar/qux', but '/foo' or '/foo/bar' was already added
174 break;
175 }
176 } else {
177 need_dirs.putAssumeCapacityNoClobber(dir_path, {});
178 }
179 }
180 }
181
182 // `need_dirs` is now a set of directories to watch with no redundancy. In practice, this is very
183 // likely to have reduced it to a quite small set (e.g. it'll typically coalesce a full `src/`
184 // directory into one entry). However, the FSEventStream API has a fairly low undocumented limit
185 // on total watches (supposedly 4096), so we should handle the case where we exceed it. To be
186 // safe, because this API can be a little unpredictable, we'll cap ourselves a little *below*
187 // that known limit.
188 if (need_dirs.count() > 2048) {
189 // Fallback: watch the whole filesystem. This is excessive, but... it *works* :P
190 if (enable_debug_logs) watch_log.debug("too many dirs; recursively watching root", .{});
191 fse.watch_roots = try gpa.realloc(fse.watch_roots, 1);
192 fse.watch_roots[0] = "/";
193 } else {
194 fse.watch_roots = try gpa.realloc(fse.watch_roots, need_dirs.count());
195 for (fse.watch_roots, need_dirs.keys()) |*out, in| {
196 out.* = try paths_arena.dupeZ(u8, in);
197 }
198 }
199 if (enable_debug_logs) {
200 watch_log.debug("watching {d} paths using {d} recursive watches:", .{ fse.watch_paths.count(), fse.watch_roots.len });
201 for (fse.watch_roots) |dir_path| {
202 watch_log.debug("- '{s}'", .{dir_path});
203 }
204 }
205}
206
207pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!std.Build.Watch.WaitResult {
208 if (fse.watch_roots.len == 0) @panic("nothing to watch");
209
210 const rs = fse.resolved_symbols;
211
212 // At the time of writing, using `since_event` in the obvious way causes redundant rebuilds
213 // to occur, because one step modifies a file which is an input to another step. The solution
214 // to this problem will probably be either:
215 //
216 // a) Don't include the output of one step as a watch input of another; only mark external
217 // files as watch inputs. Or...
218 //
219 // b) Note the current event ID when a step begins, and disregard events preceding that ID
220 // when considering whether to dirty that step in `eventCallback`.
221 //
222 // For now, to avoid the redundant rebuilds, we bypass this `since_event` mechanism. This does
223 // introduce race conditions, but the other `std.Build.Watch` implementations suffer from those
224 // too at the time of writing, so this is kind of expected.
225 fse.since_event = .since_now;
226
227 const cf_allocator = rs.CFAllocatorCreate(rs.kCFAllocatorUseContext.*, &.{
228 .version = 0,
229 .info = @constCast(&gpa),
230 .retain = null,
231 .release = null,
232 .copy_description = null,
233 .allocate = &cf_alloc_callbacks.allocate,
234 .reallocate = &cf_alloc_callbacks.reallocate,
235 .deallocate = &cf_alloc_callbacks.deallocate,
236 .preferred_size = null,
237 }) orelse return error.OutOfMemory;
238 defer rs.CFRelease(cf_allocator);
239
240 const cf_paths = try gpa.alloc(?CFStringRef, fse.watch_roots.len);
241 @memset(cf_paths, null);
242 defer {
243 for (cf_paths) |o| if (o) |p| rs.CFRelease(p);
244 gpa.free(cf_paths);
245 }
246 for (fse.watch_roots, cf_paths) |raw_path, *cf_path| {
247 cf_path.* = rs.CFStringCreateWithCString(cf_allocator, raw_path, .utf8);
248 }
249 const cf_paths_array = rs.CFArrayCreate(cf_allocator, @ptrCast(cf_paths), @intCast(cf_paths.len), null);
250 defer rs.CFRelease(cf_paths_array);
251
252 const callback_ctx: EventCallbackCtx = .{
253 .fse = fse,
254 .gpa = gpa,
255 };
256 const event_stream = rs.FSEventStreamCreate(
257 null,
258 &eventCallback,
259 &.{
260 .version = 0,
261 .info = @constCast(&callback_ctx),
262 .retain = null,
263 .release = null,
264 .copy_description = null,
265 },
266 cf_paths_array,
267 fse.since_event,
268 0.05, // 0.05s latency; higher values increase efficiency by coalescing more events
269 .{ .watch_root = true, .file_events = true },
270 );
271 defer rs.FSEventStreamRelease(event_stream);
272 rs.FSEventStreamSetDispatchQueue(event_stream, fse.dispatch_queue);
273 defer rs.FSEventStreamInvalidate(event_stream);
274 if (!rs.FSEventStreamStart(event_stream)) return error.StartFailed;
275 defer rs.FSEventStreamStop(event_stream);
276 const result = dispatch_semaphore_wait(fse.waiting_semaphore, timeout: {
277 const ns = timeout_ns orelse break :timeout .forever;
278 break :timeout dispatch_time(.now, @intCast(ns));
279 });
280 return switch (result) {
281 0 => .dirty,
282 else => .timeout,
283 };
284}
285
286const cf_alloc_callbacks = struct {
287 const log = std.log.scoped(.cf_alloc);
288 fn allocate(size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque {
289 if (enable_debug_logs) log.debug("allocate {d}", .{size});
290 _ = hint;
291 const gpa: *const Allocator = @ptrCast(@alignCast(info));
292 const mem = gpa.alignedAlloc(u8, .of(usize), @intCast(size + @sizeOf(usize))) catch return null;
293 const metadata: *usize = @ptrCast(mem);
294 metadata.* = @intCast(size);
295 return mem[@sizeOf(usize)..].ptr;
296 }
297 fn reallocate(ptr: ?*anyopaque, new_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque {
298 if (enable_debug_logs) log.debug("reallocate @{*} {d}", .{ ptr, new_size });
299 _ = hint;
300 if (ptr == null or new_size == 0) return null; // not a bug: documentation explicitly states that realloc on NULL should return NULL
301 const gpa: *const Allocator = @ptrCast(@alignCast(info));
302 const old_base: [*]align(@alignOf(usize)) u8 = @alignCast(@as([*]u8, @ptrCast(ptr)) - @sizeOf(usize));
303 const old_size = @as(*const usize, @ptrCast(old_base)).*;
304 const old_mem = old_base[0 .. old_size + @sizeOf(usize)];
305 const new_mem = gpa.realloc(old_mem, @intCast(new_size + @sizeOf(usize))) catch return null;
306 const metadata: *usize = @ptrCast(new_mem);
307 metadata.* = @intCast(new_size);
308 return new_mem[@sizeOf(usize)..].ptr;
309 }
310 fn deallocate(ptr: *anyopaque, info: ?*const anyopaque) callconv(.c) void {
311 if (enable_debug_logs) log.debug("deallocate @{*}", .{ptr});
312 const gpa: *const Allocator = @ptrCast(@alignCast(info));
313 const old_base: [*]align(@alignOf(usize)) u8 = @alignCast(@as([*]u8, @ptrCast(ptr)) - @sizeOf(usize));
314 const old_size = @as(*const usize, @ptrCast(old_base)).*;
315 const old_mem = old_base[0 .. old_size + @sizeOf(usize)];
316 gpa.free(old_mem);
317 }
318};
319
320const EventCallbackCtx = struct {
321 fse: *FsEvents,
322 gpa: Allocator,
323};
324
325fn eventCallback(
326 stream: ConstFSEventStreamRef,
327 client_callback_info: ?*anyopaque,
328 num_events: usize,
329 events_paths_ptr: *anyopaque,
330 events_flags_ptr: [*]const FSEventStreamEventFlags,
331 events_ids_ptr: [*]const FSEventStreamEventId,
332) callconv(.c) void {
333 const ctx: *const EventCallbackCtx = @ptrCast(@alignCast(client_callback_info));
334 const fse = ctx.fse;
335 const gpa = ctx.gpa;
336 const rs = fse.resolved_symbols;
337 const events_paths_ptr_casted: [*]const [*:0]const u8 = @ptrCast(@alignCast(events_paths_ptr));
338 const events_paths = events_paths_ptr_casted[0..num_events];
339 const events_ids = events_ids_ptr[0..num_events];
340 const events_flags = events_flags_ptr[0..num_events];
341 var any_dirty = false;
342 for (events_paths, events_ids, events_flags) |event_path_nts, event_id, event_flags| {
343 _ = event_id;
344 if (event_flags.history_done) continue; // sentinel
345 const event_path = std.mem.span(event_path_nts);
346 switch (event_flags.must_scan_sub_dirs) {
347 false => {
348 if (fse.watch_paths.get(event_path)) |steps| {
349 assert(steps.len > 0);
350 for (steps) |s| dirtyStep(s, gpa, &any_dirty);
351 }
352 if (std.fs.path.dirname(event_path)) |event_dirname| {
353 // Modifying '/foo/bar' triggers the watch on '/foo'.
354 if (fse.watch_paths.get(event_dirname)) |steps| {
355 assert(steps.len > 0);
356 for (steps) |s| dirtyStep(s, gpa, &any_dirty);
357 }
358 }
359 },
360 true => {
361 // This is unlikely, but can occasionally happen when bottlenecked: events have been
362 // coalesced into one. We want to see if any of these events are actually relevant
363 // to us. The only way we can reasonably do that in this rare edge case is iterate
364 // the watch paths and see if any is under this directory. That's acceptable because
365 // we would otherwise kick off a rebuild which would be clearing those paths anyway.
366 const changed_path = std.fs.path.dirname(event_path) orelse event_path;
367 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {
368 if (dirStartsWith(watching_path, changed_path)) {
369 for (steps) |s| dirtyStep(s, gpa, &any_dirty);
370 }
371 }
372 },
373 }
374 }
375 if (any_dirty) {
376 fse.since_event = rs.FSEventStreamGetLatestEventId(stream);
377 _ = dispatch_semaphore_signal(fse.waiting_semaphore);
378 }
379}
380fn dirtyStep(s: *std.Build.Step, gpa: Allocator, any_dirty: *bool) void {
381 if (s.state == .precheck_done) return;
382 s.recursiveReset(gpa);
383 any_dirty.* = true;
384}
385fn dirStartsWith(path: []const u8, prefix: []const u8) bool {
386 if (std.mem.eql(u8, path, prefix)) return true;
387 if (!std.mem.startsWith(u8, path, prefix)) return false;
388 if (path[prefix.len] != '/') return false; // `path` is `/foo/barx`, `prefix` is `/foo/bar`
389 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`
390}
391
392const dispatch_time_t = enum(u64) {
393 now = 0,
394 forever = std.math.maxInt(u64),
395 _,
396};
397extern fn dispatch_time(base: dispatch_time_t, delta_ns: i64) dispatch_time_t;
398
399const dispatch_semaphore_t = *opaque {};
400extern fn dispatch_semaphore_create(value: isize) dispatch_semaphore_t;
401extern fn dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t) isize;
402extern fn dispatch_semaphore_signal(dsema: dispatch_semaphore_t) isize;
403
404const dispatch_queue_t = *opaque {};
405const dispatch_queue_attr_t = ?*opaque {
406 const SERIAL: dispatch_queue_attr_t = null;
407};
408extern fn dispatch_queue_create(label: [*:0]const u8, attr: dispatch_queue_attr_t) dispatch_queue_t;
409extern fn dispatch_release(object: *anyopaque) void;
410
411const CFAllocatorRef = ?*const opaque {};
412const CFArrayRef = *const opaque {};
413const CFStringRef = *const opaque {};
414const CFTimeInterval = f64;
415const CFIndex = i32;
416const CFOptionFlags = enum(u32) { _ };
417const CFAllocatorRetainCallBack = *const fn (info: ?*const anyopaque) callconv(.c) *const anyopaque;
418const CFAllocatorReleaseCallBack = *const fn (info: ?*const anyopaque) callconv(.c) void;
419const CFAllocatorCopyDescriptionCallBack = *const fn (info: ?*const anyopaque) callconv(.c) CFStringRef;
420const CFAllocatorAllocateCallBack = *const fn (alloc_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque;
421const CFAllocatorReallocateCallBack = *const fn (ptr: ?*anyopaque, new_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque;
422const CFAllocatorDeallocateCallBack = *const fn (ptr: *anyopaque, info: ?*const anyopaque) callconv(.c) void;
423const CFAllocatorPreferredSizeCallBack = *const fn (size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) CFIndex;
424const CFAllocatorContext = extern struct {
425 version: CFIndex,
426 info: ?*anyopaque,
427 retain: ?CFAllocatorRetainCallBack,
428 release: ?CFAllocatorReleaseCallBack,
429 copy_description: ?CFAllocatorCopyDescriptionCallBack,
430 allocate: CFAllocatorAllocateCallBack,
431 reallocate: ?CFAllocatorReallocateCallBack,
432 deallocate: ?CFAllocatorDeallocateCallBack,
433 preferred_size: ?CFAllocatorPreferredSizeCallBack,
434};
435const CFArrayCallBacks = opaque {};
436const CFStringEncoding = enum(u32) {
437 invalid_id = std.math.maxInt(u32),
438 mac_roman = 0,
439 windows_latin_1 = 0x500,
440 iso_latin_1 = 0x201,
441 next_step_latin = 0xB01,
442 ascii = 0x600,
443 unicode = 0x100,
444 utf8 = 0x8000100,
445 non_lossy_ascii = 0xBFF,
446};
447
448const FSEventStreamRef = *opaque {};
449const ConstFSEventStreamRef = *const @typeInfo(FSEventStreamRef).pointer.child;
450const FSEventStreamCallback = *const fn (
451 stream: ConstFSEventStreamRef,
452 client_callback_info: ?*anyopaque,
453 num_events: usize,
454 event_paths: *anyopaque,
455 event_flags: [*]const FSEventStreamEventFlags,
456 event_ids: [*]const FSEventStreamEventId,
457) callconv(.c) void;
458const FSEventStreamContext = extern struct {
459 version: CFIndex,
460 info: ?*anyopaque,
461 retain: ?CFAllocatorRetainCallBack,
462 release: ?CFAllocatorReleaseCallBack,
463 copy_description: ?CFAllocatorCopyDescriptionCallBack,
464};
465const FSEventStreamEventId = enum(u64) {
466 since_now = std.math.maxInt(u64),
467 _,
468};
469const FSEventStreamCreateFlags = packed struct(u32) {
470 use_cf_types: bool = false,
471 no_defer: bool = false,
472 watch_root: bool = false,
473 ignore_self: bool = false,
474 file_events: bool = false,
475 _: u27 = 0,
476};
477const FSEventStreamEventFlags = packed struct(u32) {
478 must_scan_sub_dirs: bool,
479 user_dropped: bool,
480 kernel_dropped: bool,
481 event_ids_wrapped: bool,
482 history_done: bool,
483 root_changed: bool,
484 mount: bool,
485 unmount: bool,
486 _: u24 = 0,
487};
488
489const std = @import("std");
490const assert = std.debug.assert;
491const Allocator = std.mem.Allocator;
492const watch_log = std.log.scoped(.watch);
493const FsEvents = @This();
lib/std/Build/WebServer.zig+42-53
...@@ -251,48 +251,44 @@ pub fn now(s: *const WebServer) i64 {...@@ -251,48 +251,44 @@ pub fn now(s: *const WebServer) i64 {
251fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {251fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {
252 defer connection.stream.close();252 defer connection.stream.close();
253253
254 var read_buf: [0x4000]u8 = undefined;254 var send_buffer: [4096]u8 = undefined;
255 var server: std.http.Server = .init(connection, &read_buf);255 var recv_buffer: [4096]u8 = undefined;
256 var connection_reader = connection.stream.reader(&recv_buffer);
257 var connection_writer = connection.stream.writer(&send_buffer);
258 var server: http.Server = .init(connection_reader.interface(), &connection_writer.interface);
256259
257 while (true) {260 while (true) {
258 var request = server.receiveHead() catch |err| switch (err) {261 var request = server.receiveHead() catch |err| switch (err) {
259 error.HttpConnectionClosing => return,262 error.HttpConnectionClosing => return,
260 else => {263 else => return log.err("failed to receive http request: {t}", .{err}),
261 log.err("failed to receive http request: {s}", .{@errorName(err)});
262 return;
263 },
264 };264 };
265 var ws_send_buf: [0x4000]u8 = undefined;265 switch (request.upgradeRequested()) {
266 var ws_recv_buf: [0x4000]u8 align(4) = undefined;266 .websocket => |opt_key| {
267 if (std.http.WebSocket.init(&request, &ws_send_buf, &ws_recv_buf) catch |err| {267 const key = opt_key orelse return log.err("missing websocket key", .{});
268 log.err("failed to initialize websocket connection: {s}", .{@errorName(err)});268 var web_socket = request.respondWebSocket(.{ .key = key }) catch {
269 return;269 return log.err("failed to respond web socket: {t}", .{connection_writer.err.?});
270 }) |ws_init| {270 };
271 var web_socket = ws_init;271 ws.serveWebSocket(&web_socket) catch |err| {
272 ws.serveWebSocket(&web_socket) catch |err| {272 log.err("failed to serve websocket: {t}", .{err});
273 log.err("failed to serve websocket: {s}", .{@errorName(err)});
274 return;
275 };
276 comptime unreachable;
277 } else {
278 ws.serveRequest(&request) catch |err| switch (err) {
279 error.AlreadyReported => return,
280 else => {
281 log.err("failed to serve '{s}': {s}", .{ request.head.target, @errorName(err) });
282 return;273 return;
283 },274 };
284 };275 comptime unreachable;
276 },
277 .other => |name| return log.err("unknown upgrade request: {s}", .{name}),
278 .none => {
279 ws.serveRequest(&request) catch |err| switch (err) {
280 error.AlreadyReported => return,
281 else => {
282 log.err("failed to serve '{s}': {t}", .{ request.head.target, err });
283 return;
284 },
285 };
286 },
285 }287 }
286 }288 }
287}289}
288290
289fn makeIov(s: []const u8) std.posix.iovec_const {291fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
290 return .{
291 .base = s.ptr,
292 .len = s.len,
293 };
294}
295fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn {
296 var prev_build_status = ws.build_status.load(.monotonic);292 var prev_build_status = ws.build_status.load(.monotonic);
297293
298 const prev_step_status_bits = try ws.gpa.alloc(u8, ws.step_status_bits.len);294 const prev_step_status_bits = try ws.gpa.alloc(u8, ws.step_status_bits.len);
...@@ -312,11 +308,8 @@ fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn {...@@ -312,11 +308,8 @@ fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn {
312 .timestamp = ws.now(),308 .timestamp = ws.now(),
313 .steps_len = @intCast(ws.all_steps.len),309 .steps_len = @intCast(ws.all_steps.len),
314 };310 };
315 try sock.writeMessagev(&.{311 var bufs: [3][]const u8 = .{ @ptrCast(&hello_header), ws.step_names_trailing, prev_step_status_bits };
316 makeIov(@ptrCast(&hello_header)),312 try sock.writeMessageVec(&bufs, .binary);
317 makeIov(ws.step_names_trailing),
318 makeIov(prev_step_status_bits),
319 }, .binary);
320 }313 }
321314
322 var prev_fuzz: Fuzz.Previous = .init;315 var prev_fuzz: Fuzz.Previous = .init;
...@@ -380,7 +373,7 @@ fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn {...@@ -380,7 +373,7 @@ fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn {
380 std.Thread.Futex.timedWait(&ws.update_id, start_update_id, std.time.ns_per_ms * default_update_interval_ms) catch {};373 std.Thread.Futex.timedWait(&ws.update_id, start_update_id, std.time.ns_per_ms * default_update_interval_ms) catch {};
381 }374 }
382}375}
383fn recvWebSocketMessages(ws: *WebServer, sock: *std.http.WebSocket) void {376fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void {
384 while (true) {377 while (true) {
385 const msg = sock.readSmallMessage() catch return;378 const msg = sock.readSmallMessage() catch return;
386 if (msg.opcode != .binary) continue;379 if (msg.opcode != .binary) continue;
...@@ -402,7 +395,7 @@ fn recvWebSocketMessages(ws: *WebServer, sock: *std.http.WebSocket) void {...@@ -402,7 +395,7 @@ fn recvWebSocketMessages(ws: *WebServer, sock: *std.http.WebSocket) void {
402 }395 }
403}396}
404397
405fn serveRequest(ws: *WebServer, req: *std.http.Server.Request) !void {398fn serveRequest(ws: *WebServer, req: *http.Server.Request) !void {
406 // Strip an optional leading '/debug' component from the request.399 // Strip an optional leading '/debug' component from the request.
407 const target: []const u8, const debug: bool = target: {400 const target: []const u8, const debug: bool = target: {
408 if (mem.eql(u8, req.head.target, "/debug")) break :target .{ "/", true };401 if (mem.eql(u8, req.head.target, "/debug")) break :target .{ "/", true };
...@@ -431,7 +424,7 @@ fn serveRequest(ws: *WebServer, req: *std.http.Server.Request) !void {...@@ -431,7 +424,7 @@ fn serveRequest(ws: *WebServer, req: *std.http.Server.Request) !void {
431424
432fn serveLibFile(425fn serveLibFile(
433 ws: *WebServer,426 ws: *WebServer,
434 request: *std.http.Server.Request,427 request: *http.Server.Request,
435 sub_path: []const u8,428 sub_path: []const u8,
436 content_type: []const u8,429 content_type: []const u8,
437) !void {430) !void {
...@@ -442,7 +435,7 @@ fn serveLibFile(...@@ -442,7 +435,7 @@ fn serveLibFile(
442}435}
443fn serveClientWasm(436fn serveClientWasm(
444 ws: *WebServer,437 ws: *WebServer,
445 req: *std.http.Server.Request,438 req: *http.Server.Request,
446 optimize_mode: std.builtin.OptimizeMode,439 optimize_mode: std.builtin.OptimizeMode,
447) !void {440) !void {
448 var arena_state: std.heap.ArenaAllocator = .init(ws.gpa);441 var arena_state: std.heap.ArenaAllocator = .init(ws.gpa);
...@@ -456,12 +449,12 @@ fn serveClientWasm(...@@ -456,12 +449,12 @@ fn serveClientWasm(
456449
457pub fn serveFile(450pub fn serveFile(
458 ws: *WebServer,451 ws: *WebServer,
459 request: *std.http.Server.Request,452 request: *http.Server.Request,
460 path: Cache.Path,453 path: Cache.Path,
461 content_type: []const u8,454 content_type: []const u8,
462) !void {455) !void {
463 const gpa = ws.gpa;456 const gpa = ws.gpa;
464 // The desired API is actually sendfile, which will require enhancing std.http.Server.457 // The desired API is actually sendfile, which will require enhancing http.Server.
465 // We load the file with every request so that the user can make changes to the file458 // We load the file with every request so that the user can make changes to the file
466 // and refresh the HTML page without restarting this server.459 // and refresh the HTML page without restarting this server.
467 const file_contents = path.root_dir.handle.readFileAlloc(gpa, path.sub_path, 10 * 1024 * 1024) catch |err| {460 const file_contents = path.root_dir.handle.readFileAlloc(gpa, path.sub_path, 10 * 1024 * 1024) catch |err| {
...@@ -478,14 +471,13 @@ pub fn serveFile(...@@ -478,14 +471,13 @@ pub fn serveFile(
478}471}
479pub fn serveTarFile(472pub fn serveTarFile(
480 ws: *WebServer,473 ws: *WebServer,
481 request: *std.http.Server.Request,474 request: *http.Server.Request,
482 paths: []const Cache.Path,475 paths: []const Cache.Path,
483) !void {476) !void {
484 const gpa = ws.gpa;477 const gpa = ws.gpa;
485478
486 var send_buf: [0x4000]u8 = undefined;479 var send_buffer: [0x4000]u8 = undefined;
487 var response = request.respondStreaming(.{480 var response = try request.respondStreaming(&send_buffer, .{
488 .send_buffer = &send_buf,
489 .respond_options = .{481 .respond_options = .{
490 .extra_headers = &.{482 .extra_headers = &.{
491 .{ .name = "Content-Type", .value = "application/x-tar" },483 .{ .name = "Content-Type", .value = "application/x-tar" },
...@@ -497,10 +489,7 @@ pub fn serveTarFile(...@@ -497,10 +489,7 @@ pub fn serveTarFile(
497 var cached_cwd_path: ?[]const u8 = null;489 var cached_cwd_path: ?[]const u8 = null;
498 defer if (cached_cwd_path) |p| gpa.free(p);490 defer if (cached_cwd_path) |p| gpa.free(p);
499491
500 var response_buf: [1024]u8 = undefined;492 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
501 var adapter = response.writer().adaptToNewApi();
502 adapter.new_interface.buffer = &response_buf;
503 var archiver: std.tar.Writer = .{ .underlying_writer = &adapter.new_interface };
504493
505 for (paths) |path| {494 for (paths) |path| {
506 var file = path.root_dir.handle.openFile(path.sub_path, .{}) catch |err| {495 var file = path.root_dir.handle.openFile(path.sub_path, .{}) catch |err| {
...@@ -526,7 +515,6 @@ pub fn serveTarFile(...@@ -526,7 +515,6 @@ pub fn serveTarFile(
526 }515 }
527516
528 // intentionally not calling `archiver.finishPedantically`517 // intentionally not calling `archiver.finishPedantically`
529 try adapter.new_interface.flush();
530 try response.end();518 try response.end();
531}519}
532520
...@@ -804,7 +792,7 @@ pub fn wait(ws: *WebServer) RunnerRequest {...@@ -804,7 +792,7 @@ pub fn wait(ws: *WebServer) RunnerRequest {
804 }792 }
805}793}
806794
807const cache_control_header: std.http.Header = .{795const cache_control_header: http.Header = .{
808 .name = "Cache-Control",796 .name = "Cache-Control",
809 .value = "max-age=0, must-revalidate",797 .value = "max-age=0, must-revalidate",
810};798};
...@@ -819,5 +807,6 @@ const Build = std.Build;...@@ -819,5 +807,6 @@ const Build = std.Build;
819const Cache = Build.Cache;807const Cache = Build.Cache;
820const Fuzz = Build.Fuzz;808const Fuzz = Build.Fuzz;
821const abi = Build.abi;809const abi = Build.abi;
810const http = std.http;
822811
823const WebServer = @This();812const WebServer = @This();
lib/std/Io/DeprecatedReader.zig-27
...@@ -249,33 +249,6 @@ pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes...@@ -249,33 +249,6 @@ pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes
249 return bytes;249 return bytes;
250}250}
251251
252/// Reads bytes until `bounded.len` is equal to `num_bytes`,
253/// or the stream ends.
254///
255/// * it is assumed that `num_bytes` will not exceed `bounded.capacity()`
256pub fn readIntoBoundedBytes(
257 self: Self,
258 comptime num_bytes: usize,
259 bounded: *std.BoundedArray(u8, num_bytes),
260) anyerror!void {
261 while (bounded.len < num_bytes) {
262 // get at most the number of bytes free in the bounded array
263 const bytes_read = try self.read(bounded.unusedCapacitySlice());
264 if (bytes_read == 0) return;
265
266 // bytes_read will never be larger than @TypeOf(bounded.len)
267 // due to `self.read` being bounded by `bounded.unusedCapacitySlice()`
268 bounded.len += @as(@TypeOf(bounded.len), @intCast(bytes_read));
269 }
270}
271
272/// Reads at most `num_bytes` and returns as a bounded array.
273pub fn readBoundedBytes(self: Self, comptime num_bytes: usize) anyerror!std.BoundedArray(u8, num_bytes) {
274 var result = std.BoundedArray(u8, num_bytes){};
275 try self.readIntoBoundedBytes(num_bytes, &result);
276 return result;
277}
278
279pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {252pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {
280 const bytes = try self.readBytesNoEof(@divExact(@typeInfo(T).int.bits, 8));253 const bytes = try self.readBytesNoEof(@divExact(@typeInfo(T).int.bits, 8));
281 return mem.readInt(T, &bytes, endian);254 return mem.readInt(T, &bytes, endian);
lib/std/Io/Reader.zig+105-87
...@@ -70,13 +70,14 @@ pub const VTable = struct {...@@ -70,13 +70,14 @@ pub const VTable = struct {
7070
71 /// Returns number of bytes written to `data`.71 /// Returns number of bytes written to `data`.
72 ///72 ///
73 /// `data` may not have nonzero length.73 /// `data` must have nonzero length. `data[0]` may have zero length, in
74 /// which case the implementation must write to `Reader.buffer`.
74 ///75 ///
75 /// `data` may not contain an alias to `Reader.buffer`.76 /// `data` may not contain an alias to `Reader.buffer`.
76 ///77 ///
77 /// `data` is mutable because the implementation may to temporarily modify78 /// `data` is mutable because the implementation may temporarily modify the
78 /// the fields in order to handle partial reads. Implementations must79 /// fields in order to handle partial reads. Implementations must restore
79 /// restore the original value before returning.80 /// the original value before returning.
80 ///81 ///
81 /// Implementations may ignore `data`, writing directly to `Reader.buffer`,82 /// Implementations may ignore `data`, writing directly to `Reader.buffer`,
82 /// modifying `seek` and `end` accordingly, and returning 0 from this83 /// modifying `seek` and `end` accordingly, and returning 0 from this
...@@ -366,8 +367,11 @@ pub fn appendRemainingUnlimited(...@@ -366,8 +367,11 @@ pub fn appendRemainingUnlimited(
366 const buffer_contents = r.buffer[r.seek..r.end];367 const buffer_contents = r.buffer[r.seek..r.end];
367 try list.ensureUnusedCapacity(gpa, buffer_contents.len + bump);368 try list.ensureUnusedCapacity(gpa, buffer_contents.len + bump);
368 list.appendSliceAssumeCapacity(buffer_contents);369 list.appendSliceAssumeCapacity(buffer_contents);
369 r.seek = 0;370 // If statement protects `ending`.
370 r.end = 0;371 if (r.end != 0) {
372 r.seek = 0;
373 r.end = 0;
374 }
371 // From here, we leave `buffer` empty, appending directly to `list`.375 // From here, we leave `buffer` empty, appending directly to `list`.
372 var writer: Writer = .{376 var writer: Writer = .{
373 .buffer = undefined,377 .buffer = undefined,
...@@ -421,23 +425,29 @@ pub fn readVec(r: *Reader, data: [][]u8) Error!usize {...@@ -421,23 +425,29 @@ pub fn readVec(r: *Reader, data: [][]u8) Error!usize {
421425
422/// Writes to `Reader.buffer` or `data`, whichever has larger capacity.426/// Writes to `Reader.buffer` or `data`, whichever has larger capacity.
423pub fn defaultReadVec(r: *Reader, data: [][]u8) Error!usize {427pub fn defaultReadVec(r: *Reader, data: [][]u8) Error!usize {
424 assert(r.seek == r.end);
425 r.seek = 0;
426 r.end = 0;
427 const first = data[0];428 const first = data[0];
428 const direct = first.len >= r.buffer.len;429 if (r.seek == r.end and first.len >= r.buffer.len) {
430 var writer: Writer = .{
431 .buffer = first,
432 .end = 0,
433 .vtable = &.{ .drain = Writer.fixedDrain },
434 };
435 const limit: Limit = .limited(writer.buffer.len - writer.end);
436 return r.vtable.stream(r, &writer, limit) catch |err| switch (err) {
437 error.WriteFailed => unreachable,
438 else => |e| return e,
439 };
440 }
429 var writer: Writer = .{441 var writer: Writer = .{
430 .buffer = if (direct) first else r.buffer,442 .buffer = r.buffer,
431 .end = 0,443 .end = r.end,
432 .vtable = &.{ .drain = Writer.fixedDrain },444 .vtable = &.{ .drain = Writer.fixedDrain },
433 };445 };
434 const limit: Limit = .limited(writer.buffer.len - writer.end);446 const limit: Limit = .limited(writer.buffer.len - writer.end);
435 const n = r.vtable.stream(r, &writer, limit) catch |err| switch (err) {447 r.end += r.vtable.stream(r, &writer, limit) catch |err| switch (err) {
436 error.WriteFailed => unreachable,448 error.WriteFailed => unreachable,
437 else => |e| return e,449 else => |e| return e,
438 };450 };
439 if (direct) return n;
440 r.end += n;
441 return 0;451 return 0;
442}452}
443453
...@@ -1059,17 +1069,8 @@ pub fn fill(r: *Reader, n: usize) Error!void {...@@ -1059,17 +1069,8 @@ pub fn fill(r: *Reader, n: usize) Error!void {
1059/// increasing by a factor of 5 or more.1069/// increasing by a factor of 5 or more.
1060fn fillUnbuffered(r: *Reader, n: usize) Error!void {1070fn fillUnbuffered(r: *Reader, n: usize) Error!void {
1061 try rebase(r, n);1071 try rebase(r, n);
1062 var writer: Writer = .{1072 var bufs: [1][]u8 = .{""};
1063 .buffer = r.buffer,1073 while (r.end < r.seek + n) _ = try r.vtable.readVec(r, &bufs);
1064 .vtable = &.{ .drain = Writer.fixedDrain },
1065 };
1066 while (r.end < r.seek + n) {
1067 writer.end = r.end;
1068 r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) {
1069 error.WriteFailed => unreachable,
1070 error.ReadFailed, error.EndOfStream => |e| return e,
1071 };
1072 }
1073}1074}
10741075
1075/// Without advancing the seek position, does exactly one underlying read, filling the buffer as1076/// Without advancing the seek position, does exactly one underlying read, filling the buffer as
...@@ -1079,15 +1080,8 @@ fn fillUnbuffered(r: *Reader, n: usize) Error!void {...@@ -1079,15 +1080,8 @@ fn fillUnbuffered(r: *Reader, n: usize) Error!void {
1079/// Asserts buffer capacity is at least 1.1080/// Asserts buffer capacity is at least 1.
1080pub fn fillMore(r: *Reader) Error!void {1081pub fn fillMore(r: *Reader) Error!void {
1081 try rebase(r, 1);1082 try rebase(r, 1);
1082 var writer: Writer = .{1083 var bufs: [1][]u8 = .{""};
1083 .buffer = r.buffer,1084 _ = try r.vtable.readVec(r, &bufs);
1084 .end = r.end,
1085 .vtable = &.{ .drain = Writer.fixedDrain },
1086 };
1087 r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) {
1088 error.WriteFailed => unreachable,
1089 else => |e| return e,
1090 };
1091}1085}
10921086
1093/// Returns the next byte from the stream or returns `error.EndOfStream`.1087/// Returns the next byte from the stream or returns `error.EndOfStream`.
...@@ -1315,31 +1309,6 @@ pub fn defaultRebase(r: *Reader, capacity: usize) RebaseError!void {...@@ -1315,31 +1309,6 @@ pub fn defaultRebase(r: *Reader, capacity: usize) RebaseError!void {
1315 r.end = data.len;1309 r.end = data.len;
1316}1310}
13171311
1318/// Advances the stream and decreases the size of the storage buffer by `n`,
1319/// returning the range of bytes no longer accessible by `r`.
1320///
1321/// This action can be undone by `restitute`.
1322///
1323/// Asserts there are at least `n` buffered bytes already.
1324///
1325/// Asserts that `r.seek` is zero, i.e. the buffer is in a rebased state.
1326pub fn steal(r: *Reader, n: usize) []u8 {
1327 assert(r.seek == 0);
1328 assert(n <= r.end);
1329 const stolen = r.buffer[0..n];
1330 r.buffer = r.buffer[n..];
1331 r.end -= n;
1332 return stolen;
1333}
1334
1335/// Expands the storage buffer, undoing the effects of `steal`
1336/// Assumes that `n` does not exceed the total number of stolen bytes.
1337pub fn restitute(r: *Reader, n: usize) void {
1338 r.buffer = (r.buffer.ptr - n)[0 .. r.buffer.len + n];
1339 r.end += n;
1340 r.seek += n;
1341}
1342
1343test fixed {1312test fixed {
1344 var r: Reader = .fixed("a\x02");1313 var r: Reader = .fixed("a\x02");
1345 try testing.expect((try r.takeByte()) == 'a');1314 try testing.expect((try r.takeByte()) == 'a');
...@@ -1796,18 +1765,26 @@ pub fn Hashed(comptime Hasher: type) type {...@@ -1796,18 +1765,26 @@ pub fn Hashed(comptime Hasher: type) type {
17961765
1797 fn readVec(r: *Reader, data: [][]u8) Error!usize {1766 fn readVec(r: *Reader, data: [][]u8) Error!usize {
1798 const this: *@This() = @alignCast(@fieldParentPtr("reader", r));1767 const this: *@This() = @alignCast(@fieldParentPtr("reader", r));
1799 const n = try this.in.readVec(data);1768 var vecs: [8][]u8 = undefined; // Arbitrarily chosen amount.
1769 const dest_n, const data_size = try r.writableVector(&vecs, data);
1770 const dest = vecs[0..dest_n];
1771 const n = try this.in.readVec(dest);
1800 var remaining: usize = n;1772 var remaining: usize = n;
1801 for (data) |slice| {1773 for (dest) |slice| {
1802 if (remaining < slice.len) {1774 if (remaining < slice.len) {
1803 this.hasher.update(slice[0..remaining]);1775 this.hasher.update(slice[0..remaining]);
1804 return n;1776 remaining = 0;
1777 break;
1805 } else {1778 } else {
1806 remaining -= slice.len;1779 remaining -= slice.len;
1807 this.hasher.update(slice);1780 this.hasher.update(slice);
1808 }1781 }
1809 }1782 }
1810 assert(remaining == 0);1783 assert(remaining == 0);
1784 if (n > data_size) {
1785 r.end += n - data_size;
1786 return data_size;
1787 }
1811 return n;1788 return n;
1812 }1789 }
18131790
...@@ -1824,17 +1801,24 @@ pub fn Hashed(comptime Hasher: type) type {...@@ -1824,17 +1801,24 @@ pub fn Hashed(comptime Hasher: type) type {
1824pub fn writableVectorPosix(r: *Reader, buffer: []std.posix.iovec, data: []const []u8) Error!struct { usize, usize } {1801pub fn writableVectorPosix(r: *Reader, buffer: []std.posix.iovec, data: []const []u8) Error!struct { usize, usize } {
1825 var i: usize = 0;1802 var i: usize = 0;
1826 var n: usize = 0;1803 var n: usize = 0;
1827 for (data) |buf| {1804 if (r.seek == r.end) {
1828 if (buffer.len - i == 0) return .{ i, n };1805 for (data) |buf| {
1806 if (buffer.len - i == 0) return .{ i, n };
1807 if (buf.len != 0) {
1808 buffer[i] = .{ .base = buf.ptr, .len = buf.len };
1809 i += 1;
1810 n += buf.len;
1811 }
1812 }
1813 const buf = r.buffer;
1829 if (buf.len != 0) {1814 if (buf.len != 0) {
1815 r.seek = 0;
1816 r.end = 0;
1830 buffer[i] = .{ .base = buf.ptr, .len = buf.len };1817 buffer[i] = .{ .base = buf.ptr, .len = buf.len };
1831 i += 1;1818 i += 1;
1832 n += buf.len;
1833 }1819 }
1834 }1820 } else {
1835 assert(r.seek == r.end);1821 const buf = r.buffer[r.end..];
1836 const buf = r.buffer;
1837 if (buf.len != 0) {
1838 buffer[i] = .{ .base = buf.ptr, .len = buf.len };1822 buffer[i] = .{ .base = buf.ptr, .len = buf.len };
1839 i += 1;1823 i += 1;
1840 }1824 }
...@@ -1848,28 +1832,62 @@ pub fn writableVectorWsa(...@@ -1848,28 +1832,62 @@ pub fn writableVectorWsa(
1848) Error!struct { usize, usize } {1832) Error!struct { usize, usize } {
1849 var i: usize = 0;1833 var i: usize = 0;
1850 var n: usize = 0;1834 var n: usize = 0;
1851 for (data) |buf| {1835 if (r.seek == r.end) {
1852 if (buffer.len - i == 0) return .{ i, n };1836 for (data) |buf| {
1853 if (buf.len == 0) continue;1837 if (buffer.len - i == 0) return .{ i, n };
1854 if (std.math.cast(u32, buf.len)) |len| {1838 if (buf.len == 0) continue;
1855 buffer[i] = .{ .buf = buf.ptr, .len = len };1839 if (std.math.cast(u32, buf.len)) |len| {
1840 buffer[i] = .{ .buf = buf.ptr, .len = len };
1841 i += 1;
1842 n += len;
1843 continue;
1844 }
1845 buffer[i] = .{ .buf = buf.ptr, .len = std.math.maxInt(u32) };
1856 i += 1;1846 i += 1;
1857 n += len;1847 n += std.math.maxInt(u32);
1858 continue;1848 return .{ i, n };
1859 }1849 }
1860 buffer[i] = .{ .buf = buf.ptr, .len = std.math.maxInt(u32) };1850 const buf = r.buffer;
1851 if (buf.len != 0) {
1852 r.seek = 0;
1853 r.end = 0;
1854 if (std.math.cast(u32, buf.len)) |len| {
1855 buffer[i] = .{ .buf = buf.ptr, .len = len };
1856 } else {
1857 buffer[i] = .{ .buf = buf.ptr, .len = std.math.maxInt(u32) };
1858 }
1859 i += 1;
1860 }
1861 } else {
1862 buffer[i] = .{
1863 .buf = r.buffer.ptr + r.end,
1864 .len = @min(std.math.maxInt(u32), r.buffer.len - r.end),
1865 };
1861 i += 1;1866 i += 1;
1862 n += std.math.maxInt(u32);
1863 return .{ i, n };
1864 }1867 }
1865 assert(r.seek == r.end);1868 return .{ i, n };
1866 const buf = r.buffer;1869}
1867 if (buf.len != 0) {1870
1868 if (std.math.cast(u32, buf.len)) |len| {1871pub fn writableVector(r: *Reader, buffer: [][]u8, data: []const []u8) Error!struct { usize, usize } {
1869 buffer[i] = .{ .buf = buf.ptr, .len = len };1872 var i: usize = 0;
1870 } else {1873 var n: usize = 0;
1871 buffer[i] = .{ .buf = buf.ptr, .len = std.math.maxInt(u32) };1874 if (r.seek == r.end) {
1875 for (data) |buf| {
1876 if (buffer.len - i == 0) return .{ i, n };
1877 if (buf.len != 0) {
1878 buffer[i] = buf;
1879 i += 1;
1880 n += buf.len;
1881 }
1882 }
1883 if (r.buffer.len != 0) {
1884 r.seek = 0;
1885 r.end = 0;
1886 buffer[i] = r.buffer;
1887 i += 1;
1872 }1888 }
1889 } else {
1890 buffer[i] = r.buffer[r.end..];
1873 i += 1;1891 i += 1;
1874 }1892 }
1875 return .{ i, n };1893 return .{ i, n };
lib/std/Io/Writer.zig+77-19
...@@ -191,29 +191,87 @@ pub fn writeSplatHeader(...@@ -191,29 +191,87 @@ pub fn writeSplatHeader(
191 data: []const []const u8,191 data: []const []const u8,
192 splat: usize,192 splat: usize,
193) Error!usize {193) Error!usize {
194 const new_end = w.end + header.len;194 return writeSplatHeaderLimit(w, header, data, splat, .unlimited);
195 if (new_end <= w.buffer.len) {195}
196 @memcpy(w.buffer[w.end..][0..header.len], header);196
197 w.end = new_end;197/// Equivalent to `writeSplatHeader` but writes at most `limit` bytes.
198 return header.len + try writeSplat(w, data, splat);198pub fn writeSplatHeaderLimit(
199 }199 w: *Writer,
200 var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size.200 header: []const u8,
201 var i: usize = 1;201 data: []const []const u8,
202 vecs[0] = header;202 splat: usize,
203 for (data[0 .. data.len - 1]) |buf| {203 limit: Limit,
204 if (buf.len == 0) continue;204) Error!usize {
205 vecs[i] = buf;205 var remaining = @intFromEnum(limit);
206 i += 1;206 {
207 if (vecs.len - i == 0) break;207 const copy_len = @min(header.len, w.buffer.len - w.end, remaining);
208 if (header.len - copy_len != 0) return writeSplatHeaderLimitFinish(w, header, data, splat, remaining);
209 @memcpy(w.buffer[w.end..][0..copy_len], header[0..copy_len]);
210 w.end += copy_len;
211 remaining -= copy_len;
212 }
213 for (data[0 .. data.len - 1], 0..) |buf, i| {
214 const copy_len = @min(buf.len, w.buffer.len - w.end, remaining);
215 if (buf.len - copy_len != 0) return @intFromEnum(limit) - remaining +
216 try writeSplatHeaderLimitFinish(w, &.{}, data[i..], splat, remaining);
217 @memcpy(w.buffer[w.end..][0..copy_len], buf[0..copy_len]);
218 w.end += copy_len;
219 remaining -= copy_len;
208 }220 }
209 const pattern = data[data.len - 1];221 const pattern = data[data.len - 1];
210 const new_splat = s: {222 const splat_n = pattern.len * splat;
211 if (pattern.len == 0 or vecs.len - i == 0) break :s 1;223 if (splat_n > @min(w.buffer.len - w.end, remaining)) {
224 const buffered_n = @intFromEnum(limit) - remaining;
225 const written = try writeSplatHeaderLimitFinish(w, &.{}, data[data.len - 1 ..][0..1], splat, remaining);
226 return buffered_n + written;
227 }
228
229 for (0..splat) |_| {
230 @memcpy(w.buffer[w.end..][0..pattern.len], pattern);
231 w.end += pattern.len;
232 }
233
234 remaining -= splat_n;
235 return @intFromEnum(limit) - remaining;
236}
237
238fn writeSplatHeaderLimitFinish(
239 w: *Writer,
240 header: []const u8,
241 data: []const []const u8,
242 splat: usize,
243 limit: usize,
244) Error!usize {
245 var remaining = limit;
246 var vecs: [8][]const u8 = undefined;
247 var i: usize = 0;
248 v: {
249 if (header.len != 0) {
250 const copy_len = @min(header.len, remaining);
251 vecs[i] = header[0..copy_len];
252 i += 1;
253 remaining -= copy_len;
254 if (remaining == 0) break :v;
255 }
256 for (data[0 .. data.len - 1]) |buf| if (buf.len != 0) {
257 const copy_len = @min(header.len, remaining);
258 vecs[i] = buf;
259 i += 1;
260 remaining -= copy_len;
261 if (remaining == 0) break :v;
262 if (vecs.len - i == 0) break :v;
263 };
264 const pattern = data[data.len - 1];
265 if (splat == 1) {
266 vecs[i] = pattern[0..@min(remaining, pattern.len)];
267 i += 1;
268 break :v;
269 }
212 vecs[i] = pattern;270 vecs[i] = pattern;
213 i += 1;271 i += 1;
214 break :s splat;272 return w.vtable.drain(w, (&vecs)[0..i], @min(remaining / pattern.len, splat));
215 };273 }
216 return w.vtable.drain(w, vecs[0..i], new_splat);274 return w.vtable.drain(w, (&vecs)[0..i], 1);
217}275}
218276
219test "writeSplatHeader splatting avoids buffer aliasing temptation" {277test "writeSplatHeader splatting avoids buffer aliasing temptation" {
lib/std/Io/test.zig+3-3
...@@ -45,9 +45,9 @@ test "write a file, read it, then delete it" {...@@ -45,9 +45,9 @@ test "write a file, read it, then delete it" {
45 const expected_file_size: u64 = "begin".len + data.len + "end".len;45 const expected_file_size: u64 = "begin".len + data.len + "end".len;
46 try expectEqual(expected_file_size, file_size);46 try expectEqual(expected_file_size, file_size);
4747
48 var buf_stream = io.bufferedReader(file.deprecatedReader());48 var file_buffer: [1024]u8 = undefined;
49 const st = buf_stream.reader();49 var file_reader = file.reader(&file_buffer);
50 const contents = try st.readAllAlloc(std.testing.allocator, 2 * 1024);50 const contents = try file_reader.interface.allocRemaining(std.testing.allocator, .limited(2 * 1024));
51 defer std.testing.allocator.free(contents);51 defer std.testing.allocator.free(contents);
5252
53 try expect(mem.eql(u8, contents[0.."begin".len], "begin"));53 try expect(mem.eql(u8, contents[0.."begin".len], "begin"));
lib/std/Progress.zig+1-1
...@@ -1006,7 +1006,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -1006,7 +1006,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
1006 continue;1006 continue;
1007 }1007 }
1008 const src = pipe_buf[m.remaining_read_trash_bytes..n];1008 const src = pipe_buf[m.remaining_read_trash_bytes..n];
1009 std.mem.copyForwards(u8, &pipe_buf, src);1009 @memmove(pipe_buf[0..src.len], src);
1010 m.remaining_read_trash_bytes = 0;1010 m.remaining_read_trash_bytes = 0;
1011 bytes_read = src.len;1011 bytes_read = src.len;
1012 continue;1012 continue;
lib/std/Target.zig+69-65
...@@ -405,7 +405,7 @@ pub const Os = struct {...@@ -405,7 +405,7 @@ pub const Os = struct {
405 .fuchsia => .{405 .fuchsia => .{
406 .semver = .{406 .semver = .{
407 .min = .{ .major = 1, .minor = 0, .patch = 0 },407 .min = .{ .major = 1, .minor = 0, .patch = 0 },
408 .max = .{ .major = 26, .minor = 0, .patch = 0 },408 .max = .{ .major = 27, .minor = 0, .patch = 0 },
409 },409 },
410 },410 },
411 .hermit => .{411 .hermit => .{
...@@ -446,7 +446,7 @@ pub const Os = struct {...@@ -446,7 +446,7 @@ pub const Os = struct {
446446
447 break :blk default_min;447 break :blk default_min;
448 },448 },
449 .max = .{ .major = 6, .minor = 13, .patch = 4 },449 .max = .{ .major = 6, .minor = 16, .patch = 0 },
450 },450 },
451 .glibc = blk: {451 .glibc = blk: {
452 // For 32-bit targets that traditionally used 32-bit time, we require452 // For 32-bit targets that traditionally used 32-bit time, we require
...@@ -519,7 +519,7 @@ pub const Os = struct {...@@ -519,7 +519,7 @@ pub const Os = struct {
519519
520 break :blk default_min;520 break :blk default_min;
521 },521 },
522 .max = .{ .major = 14, .minor = 2, .patch = 0 },522 .max = .{ .major = 14, .minor = 3, .patch = 0 },
523 },523 },
524 },524 },
525 .netbsd => .{525 .netbsd => .{
...@@ -549,38 +549,38 @@ pub const Os = struct {...@@ -549,38 +549,38 @@ pub const Os = struct {
549549
550 .driverkit => .{550 .driverkit => .{
551 .semver = .{551 .semver = .{
552 .min = .{ .major = 19, .minor = 0, .patch = 0 },552 .min = .{ .major = 20, .minor = 0, .patch = 0 },
553 .max = .{ .major = 24, .minor = 4, .patch = 0 },553 .max = .{ .major = 25, .minor = 0, .patch = 0 },
554 },554 },
555 },555 },
556 .macos => .{556 .macos => .{
557 .semver = .{557 .semver = .{
558 .min = .{ .major = 13, .minor = 0, .patch = 0 },558 .min = .{ .major = 13, .minor = 0, .patch = 0 },
559 .max = .{ .major = 15, .minor = 4, .patch = 1 },559 .max = .{ .major = 15, .minor = 6, .patch = 0 },
560 },560 },
561 },561 },
562 .ios => .{562 .ios => .{
563 .semver = .{563 .semver = .{
564 .min = .{ .major = 15, .minor = 0, .patch = 0 },564 .min = .{ .major = 15, .minor = 0, .patch = 0 },
565 .max = .{ .major = 18, .minor = 4, .patch = 1 },565 .max = .{ .major = 18, .minor = 6, .patch = 0 },
566 },566 },
567 },567 },
568 .tvos => .{568 .tvos => .{
569 .semver = .{569 .semver = .{
570 .min = .{ .major = 15, .minor = 0, .patch = 0 },570 .min = .{ .major = 15, .minor = 0, .patch = 0 },
571 .max = .{ .major = 18, .minor = 4, .patch = 1 },571 .max = .{ .major = 18, .minor = 5, .patch = 0 },
572 },572 },
573 },573 },
574 .visionos => .{574 .visionos => .{
575 .semver = .{575 .semver = .{
576 .min = .{ .major = 1, .minor = 0, .patch = 0 },576 .min = .{ .major = 1, .minor = 0, .patch = 0 },
577 .max = .{ .major = 2, .minor = 4, .patch = 1 },577 .max = .{ .major = 2, .minor = 5, .patch = 0 },
578 },578 },
579 },579 },
580 .watchos => .{580 .watchos => .{
581 .semver = .{581 .semver = .{
582 .min = .{ .major = 7, .minor = 0, .patch = 0 },582 .min = .{ .major = 8, .minor = 0, .patch = 0 },
583 .max = .{ .major = 11, .minor = 4, .patch = 0 },583 .max = .{ .major = 11, .minor = 6, .patch = 0 },
584 },584 },
585 },585 },
586586
...@@ -614,7 +614,7 @@ pub const Os = struct {...@@ -614,7 +614,7 @@ pub const Os = struct {
614 .amdhsa => .{614 .amdhsa => .{
615 .semver = .{615 .semver = .{
616 .min = .{ .major = 5, .minor = 0, .patch = 0 },616 .min = .{ .major = 5, .minor = 0, .patch = 0 },
617 .max = .{ .major = 6, .minor = 4, .patch = 0 },617 .max = .{ .major = 6, .minor = 4, .patch = 2 },
618 },618 },
619 },619 },
620 .amdpal => .{620 .amdpal => .{
...@@ -626,7 +626,7 @@ pub const Os = struct {...@@ -626,7 +626,7 @@ pub const Os = struct {
626 .cuda => .{626 .cuda => .{
627 .semver = .{627 .semver = .{
628 .min = .{ .major = 11, .minor = 0, .patch = 1 },628 .min = .{ .major = 11, .minor = 0, .patch = 1 },
629 .max = .{ .major = 12, .minor = 9, .patch = 0 },629 .max = .{ .major = 12, .minor = 9, .patch = 1 },
630 },630 },
631 },631 },
632 .nvcl,632 .nvcl,
...@@ -646,7 +646,7 @@ pub const Os = struct {...@@ -646,7 +646,7 @@ pub const Os = struct {
646 .vulkan => .{646 .vulkan => .{
647 .semver = .{647 .semver = .{
648 .min = .{ .major = 1, .minor = 2, .patch = 0 },648 .min = .{ .major = 1, .minor = 2, .patch = 0 },
649 .max = .{ .major = 1, .minor = 4, .patch = 313 },649 .max = .{ .major = 1, .minor = 4, .patch = 321 },
650 },650 },
651 },651 },
652 };652 };
...@@ -697,57 +697,6 @@ pub const Os = struct {...@@ -697,57 +697,6 @@ pub const Os = struct {
697 => |field| @field(os.version_range, @tagName(field)).isAtLeast(ver),697 => |field| @field(os.version_range, @tagName(field)).isAtLeast(ver),
698 };698 };
699 }699 }
700
701 /// On Darwin, we always link libSystem which contains libc.
702 /// Similarly on FreeBSD and NetBSD we always link system libc
703 /// since this is the stable syscall interface.
704 pub fn requiresLibC(os: Os) bool {
705 return switch (os.tag) {
706 .aix,
707 .driverkit,
708 .macos,
709 .ios,
710 .tvos,
711 .watchos,
712 .visionos,
713 .dragonfly,
714 .openbsd,
715 .haiku,
716 .solaris,
717 .illumos,
718 .serenity,
719 => true,
720
721 .linux,
722 .windows,
723 .freebsd,
724 .netbsd,
725 .freestanding,
726 .fuchsia,
727 .ps3,
728 .zos,
729 .rtems,
730 .cuda,
731 .nvcl,
732 .amdhsa,
733 .ps4,
734 .ps5,
735 .mesa3d,
736 .contiki,
737 .amdpal,
738 .hermit,
739 .hurd,
740 .wasi,
741 .emscripten,
742 .uefi,
743 .opencl,
744 .opengl,
745 .vulkan,
746 .plan9,
747 .other,
748 => false,
749 };
750 }
751};700};
752701
753pub const aarch64 = @import("Target/aarch64.zig");702pub const aarch64 = @import("Target/aarch64.zig");
...@@ -2055,6 +2004,61 @@ pub inline fn isWasiLibC(target: *const Target) bool {...@@ -2055,6 +2004,61 @@ pub inline fn isWasiLibC(target: *const Target) bool {
2055 return target.os.tag == .wasi and target.abi.isMusl();2004 return target.os.tag == .wasi and target.abi.isMusl();
2056}2005}
20572006
2007/// Does this target require linking libc? This may be the case if the target has an unstable
2008/// syscall interface, for example.
2009pub fn requiresLibC(target: *const Target) bool {
2010 return switch (target.os.tag) {
2011 .aix,
2012 .driverkit,
2013 .macos,
2014 .ios,
2015 .tvos,
2016 .watchos,
2017 .visionos,
2018 .dragonfly,
2019 .openbsd,
2020 .haiku,
2021 .solaris,
2022 .illumos,
2023 .serenity,
2024 => true,
2025
2026 // Android API levels prior to 29 did not have native TLS support. For these API levels, TLS
2027 // is implemented through calls to `__emutls_get_address`. We provide this function in
2028 // compiler-rt, but it's implemented by way of `pthread_key_create` et al, so linking libc
2029 // is required.
2030 .linux => target.abi.isAndroid() and target.os.version_range.linux.android < 29,
2031
2032 .windows,
2033 .freebsd,
2034 .netbsd,
2035 .freestanding,
2036 .fuchsia,
2037 .ps3,
2038 .zos,
2039 .rtems,
2040 .cuda,
2041 .nvcl,
2042 .amdhsa,
2043 .ps4,
2044 .ps5,
2045 .mesa3d,
2046 .contiki,
2047 .amdpal,
2048 .hermit,
2049 .hurd,
2050 .wasi,
2051 .emscripten,
2052 .uefi,
2053 .opencl,
2054 .opengl,
2055 .vulkan,
2056 .plan9,
2057 .other,
2058 => false,
2059 };
2060}
2061
2058pub const DynamicLinker = struct {2062pub const DynamicLinker = struct {
2059 /// Contains the memory used to store the dynamic linker path. This field2063 /// Contains the memory used to store the dynamic linker path. This field
2060 /// should not be used directly. See `get` and `set`. This field exists so2064 /// should not be used directly. See `get` and `set`. This field exists so
lib/std/Target/Query.zig+18-2
...@@ -423,7 +423,7 @@ pub fn zigTriple(self: Query, gpa: Allocator) Allocator.Error![]u8 {...@@ -423,7 +423,7 @@ pub fn zigTriple(self: Query, gpa: Allocator) Allocator.Error![]u8 {
423 try formatVersion(v, gpa, &result);423 try formatVersion(v, gpa, &result);
424 },424 },
425 .windows => |v| {425 .windows => |v| {
426 try result.print(gpa, "{d}", .{v});426 try result.print(gpa, "{f}", .{v});
427 },427 },
428 }428 }
429 }429 }
...@@ -437,7 +437,7 @@ pub fn zigTriple(self: Query, gpa: Allocator) Allocator.Error![]u8 {...@@ -437,7 +437,7 @@ pub fn zigTriple(self: Query, gpa: Allocator) Allocator.Error![]u8 {
437 .windows => |v| {437 .windows => |v| {
438 // This is counting on a custom format() function defined on `WindowsVersion`438 // This is counting on a custom format() function defined on `WindowsVersion`
439 // to add a prefix '.' and make there be a total of three dots.439 // to add a prefix '.' and make there be a total of three dots.
440 try result.print(gpa, "..{d}", .{v});440 try result.print(gpa, "..{f}", .{v});
441 },441 },
442 }442 }
443 }443 }
...@@ -729,4 +729,20 @@ test parse {...@@ -729,4 +729,20 @@ test parse {
729 defer std.testing.allocator.free(text);729 defer std.testing.allocator.free(text);
730 try std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-android.30", text);730 try std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-android.30", text);
731 }731 }
732 {
733 const query = try Query.parse(.{
734 .arch_os_abi = "x86-windows.xp...win8-msvc",
735 });
736 const target = try std.zig.system.resolveTargetQuery(query);
737
738 try std.testing.expect(target.cpu.arch == .x86);
739 try std.testing.expect(target.os.tag == .windows);
740 try std.testing.expect(target.os.version_range.windows.min == .xp);
741 try std.testing.expect(target.os.version_range.windows.max == .win8);
742 try std.testing.expect(target.abi == .msvc);
743
744 const text = try query.zigTriple(std.testing.allocator);
745 defer std.testing.allocator.free(text);
746 try std.testing.expectEqualSlices(u8, "x86-windows.xp...win8-msvc", text);
747 }
732}748}
lib/std/Uri.zig+2-1
...@@ -377,7 +377,8 @@ pub fn parse(text: []const u8) ParseError!Uri {...@@ -377,7 +377,8 @@ pub fn parse(text: []const u8) ParseError!Uri {
377377
378pub const ResolveInPlaceError = ParseError || error{NoSpaceLeft};378pub const ResolveInPlaceError = ParseError || error{NoSpaceLeft};
379379
380/// Resolves a URI against a base URI, conforming to RFC 3986, Section 5.380/// Resolves a URI against a base URI, conforming to
381/// [RFC 3986, Section 5](https://www.rfc-editor.org/rfc/rfc3986#section-5)
381///382///
382/// Assumes new location is already copied to the beginning of `aux_buf.*`.383/// Assumes new location is already copied to the beginning of `aux_buf.*`.
383/// Parses that new location as a URI, and then resolves the path in place.384/// Parses that new location as a URI, and then resolves the path in place.
lib/std/array_list.zig+174-16
...@@ -158,7 +158,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?mem.Alignment) ty...@@ -158,7 +158,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?mem.Alignment) ty
158 assert(self.items.len < self.capacity);158 assert(self.items.len < self.capacity);
159 self.items.len += 1;159 self.items.len += 1;
160160
161 mem.copyBackwards(T, self.items[i + 1 .. self.items.len], self.items[i .. self.items.len - 1]);161 @memmove(self.items[i + 1 .. self.items.len], self.items[i .. self.items.len - 1]);
162 self.items[i] = item;162 self.items[i] = item;
163 }163 }
164164
...@@ -216,7 +216,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?mem.Alignment) ty...@@ -216,7 +216,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?mem.Alignment) ty
216 assert(self.capacity >= new_len);216 assert(self.capacity >= new_len);
217 const to_move = self.items[index..];217 const to_move = self.items[index..];
218 self.items.len = new_len;218 self.items.len = new_len;
219 mem.copyBackwards(T, self.items[index + count ..], to_move);219 @memmove(self.items[index + count ..][0..to_move.len], to_move);
220 const result = self.items[index..][0..count];220 const result = self.items[index..][0..count];
221 @memset(result, undefined);221 @memset(result, undefined);
222 return result;222 return result;
...@@ -624,6 +624,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -624,6 +624,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
624624
625 /// Initialize with externally-managed memory. The buffer determines the625 /// Initialize with externally-managed memory. The buffer determines the
626 /// capacity, and the length is set to zero.626 /// capacity, and the length is set to zero.
627 ///
627 /// When initialized this way, all functions that accept an Allocator628 /// When initialized this way, all functions that accept an Allocator
628 /// argument cause illegal behavior.629 /// argument cause illegal behavior.
629 pub fn initBuffer(buffer: Slice) Self {630 pub fn initBuffer(buffer: Slice) Self {
...@@ -705,18 +706,37 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -705,18 +706,37 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
705 }706 }
706707
707 /// Insert `item` at index `i`. Moves `list[i .. list.len]` to higher indices to make room.708 /// Insert `item` at index `i`. Moves `list[i .. list.len]` to higher indices to make room.
708 /// If in` is equal to the length of the list this operation is equivalent to append.709 ///
710 /// If `i` is equal to the length of the list this operation is equivalent to append.
711 ///
709 /// This operation is O(N).712 /// This operation is O(N).
713 ///
710 /// Asserts that the list has capacity for one additional item.714 /// Asserts that the list has capacity for one additional item.
715 ///
711 /// Asserts that the index is in bounds or equal to the length.716 /// Asserts that the index is in bounds or equal to the length.
712 pub fn insertAssumeCapacity(self: *Self, i: usize, item: T) void {717 pub fn insertAssumeCapacity(self: *Self, i: usize, item: T) void {
713 assert(self.items.len < self.capacity);718 assert(self.items.len < self.capacity);
714 self.items.len += 1;719 self.items.len += 1;
715720
716 mem.copyBackwards(T, self.items[i + 1 .. self.items.len], self.items[i .. self.items.len - 1]);721 @memmove(self.items[i + 1 .. self.items.len], self.items[i .. self.items.len - 1]);
717 self.items[i] = item;722 self.items[i] = item;
718 }723 }
719724
725 /// Insert `item` at index `i`, moving `list[i .. list.len]` to higher indices to make room.
726 ///
727 /// If `i` is equal to the length of the list this operation is equivalent to append.
728 ///
729 /// This operation is O(N).
730 ///
731 /// If the list lacks unused capacity for the additional item, returns
732 /// `error.OutOfMemory`.
733 ///
734 /// Asserts that the index is in bounds or equal to the length.
735 pub fn insertBounded(self: *Self, i: usize, item: T) error{OutOfMemory}!void {
736 if (self.capacity - self.items.len == 0) return error.OutOfMemory;
737 return insertAssumeCapacity(self, i, item);
738 }
739
720 /// Add `count` new elements at position `index`, which have740 /// Add `count` new elements at position `index`, which have
721 /// `undefined` values. Returns a slice pointing to the newly allocated741 /// `undefined` values. Returns a slice pointing to the newly allocated
722 /// elements, which becomes invalid after various `ArrayList`742 /// elements, which becomes invalid after various `ArrayList`
...@@ -749,12 +769,29 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -749,12 +769,29 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
749 assert(self.capacity >= new_len);769 assert(self.capacity >= new_len);
750 const to_move = self.items[index..];770 const to_move = self.items[index..];
751 self.items.len = new_len;771 self.items.len = new_len;
752 mem.copyBackwards(T, self.items[index + count ..], to_move);772 @memmove(self.items[index + count ..][0..to_move.len], to_move);
753 const result = self.items[index..][0..count];773 const result = self.items[index..][0..count];
754 @memset(result, undefined);774 @memset(result, undefined);
755 return result;775 return result;
756 }776 }
757777
778 /// Add `count` new elements at position `index`, which have
779 /// `undefined` values, returning a slice pointing to the newly
780 /// allocated elements, which becomes invalid after various `ArrayList`
781 /// operations.
782 ///
783 /// Invalidates pre-existing pointers to elements at and after `index`, but
784 /// does not invalidate any before that.
785 ///
786 /// If the list lacks unused capacity for the additional items, returns
787 /// `error.OutOfMemory`.
788 ///
789 /// Asserts that the index is in bounds or equal to the length.
790 pub fn addManyAtBounded(self: *Self, index: usize, count: usize) error{OutOfMemory}![]T {
791 if (self.capacity - self.items.len < count) return error.OutOfMemory;
792 return addManyAtAssumeCapacity(self, index, count);
793 }
794
758 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.795 /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
759 /// This operation is O(N).796 /// This operation is O(N).
760 /// Invalidates pre-existing pointers to elements at and after `index`.797 /// Invalidates pre-existing pointers to elements at and after `index`.
...@@ -798,7 +835,9 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -798,7 +835,9 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
798 }835 }
799836
800 /// Grows or shrinks the list as necessary.837 /// Grows or shrinks the list as necessary.
838 ///
801 /// Never invalidates element pointers.839 /// Never invalidates element pointers.
840 ///
802 /// Asserts the capacity is enough for additional items.841 /// Asserts the capacity is enough for additional items.
803 pub fn replaceRangeAssumeCapacity(self: *Self, start: usize, len: usize, new_items: []const T) void {842 pub fn replaceRangeAssumeCapacity(self: *Self, start: usize, len: usize, new_items: []const T) void {
804 const after_range = start + len;843 const after_range = start + len;
...@@ -815,16 +854,24 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -815,16 +854,24 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
815 } else {854 } else {
816 const extra = range.len - new_items.len;855 const extra = range.len - new_items.len;
817 @memcpy(range[0..new_items.len], new_items);856 @memcpy(range[0..new_items.len], new_items);
818 std.mem.copyForwards(857 const src = self.items[after_range..];
819 T,858 @memmove(self.items[after_range - extra ..][0..src.len], src);
820 self.items[after_range - extra ..],
821 self.items[after_range..],
822 );
823 @memset(self.items[self.items.len - extra ..], undefined);859 @memset(self.items[self.items.len - extra ..], undefined);
824 self.items.len -= extra;860 self.items.len -= extra;
825 }861 }
826 }862 }
827863
864 /// Grows or shrinks the list as necessary.
865 ///
866 /// Never invalidates element pointers.
867 ///
868 /// If the unused capacity is insufficient for additional items,
869 /// returns `error.OutOfMemory`.
870 pub fn replaceRangeBounded(self: *Self, start: usize, len: usize, new_items: []const T) error{OutOfMemory}!void {
871 if (self.capacity - self.items.len < new_items.len -| len) return error.OutOfMemory;
872 return replaceRangeAssumeCapacity(self, start, len, new_items);
873 }
874
828 /// Extend the list by 1 element. Allocates more memory as necessary.875 /// Extend the list by 1 element. Allocates more memory as necessary.
829 /// Invalidates element pointers if additional memory is needed.876 /// Invalidates element pointers if additional memory is needed.
830 pub fn append(self: *Self, gpa: Allocator, item: T) Allocator.Error!void {877 pub fn append(self: *Self, gpa: Allocator, item: T) Allocator.Error!void {
...@@ -833,12 +880,25 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -833,12 +880,25 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
833 }880 }
834881
835 /// Extend the list by 1 element.882 /// Extend the list by 1 element.
883 ///
836 /// Never invalidates element pointers.884 /// Never invalidates element pointers.
885 ///
837 /// Asserts that the list can hold one additional item.886 /// Asserts that the list can hold one additional item.
838 pub fn appendAssumeCapacity(self: *Self, item: T) void {887 pub fn appendAssumeCapacity(self: *Self, item: T) void {
839 self.addOneAssumeCapacity().* = item;888 self.addOneAssumeCapacity().* = item;
840 }889 }
841890
891 /// Extend the list by 1 element.
892 ///
893 /// Never invalidates element pointers.
894 ///
895 /// If the list lacks unused capacity for the additional item, returns
896 /// `error.OutOfMemory`.
897 pub fn appendBounded(self: *Self, item: T) error{OutOfMemory}!void {
898 if (self.capacity - self.items.len == 0) return error.OutOfMemory;
899 return appendAssumeCapacity(self, item);
900 }
901
842 /// Remove the element at index `i` from the list and return its value.902 /// Remove the element at index `i` from the list and return its value.
843 /// Invalidates pointers to the last element.903 /// Invalidates pointers to the last element.
844 /// This operation is O(N).904 /// This operation is O(N).
...@@ -873,6 +933,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -873,6 +933,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
873 }933 }
874934
875 /// Append the slice of items to the list.935 /// Append the slice of items to the list.
936 ///
876 /// Asserts that the list can hold the additional items.937 /// Asserts that the list can hold the additional items.
877 pub fn appendSliceAssumeCapacity(self: *Self, items: []const T) void {938 pub fn appendSliceAssumeCapacity(self: *Self, items: []const T) void {
878 const old_len = self.items.len;939 const old_len = self.items.len;
...@@ -882,6 +943,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -882,6 +943,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
882 @memcpy(self.items[old_len..][0..items.len], items);943 @memcpy(self.items[old_len..][0..items.len], items);
883 }944 }
884945
946 /// Append the slice of items to the list.
947 ///
948 /// If the list lacks unused capacity for the additional items, returns `error.OutOfMemory`.
949 pub fn appendSliceBounded(self: *Self, items: []const T) error{OutOfMemory}!void {
950 if (self.capacity - self.items.len < items.len) return error.OutOfMemory;
951 return appendSliceAssumeCapacity(self, items);
952 }
953
885 /// Append the slice of items to the list. Allocates more954 /// Append the slice of items to the list. Allocates more
886 /// memory as necessary. Only call this function if a call to `appendSlice` instead would955 /// memory as necessary. Only call this function if a call to `appendSlice` instead would
887 /// be a compile error.956 /// be a compile error.
...@@ -892,8 +961,10 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -892,8 +961,10 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
892 }961 }
893962
894 /// Append an unaligned slice of items to the list.963 /// Append an unaligned slice of items to the list.
895 /// Only call this function if a call to `appendSliceAssumeCapacity`964 ///
896 /// instead would be a compile error.965 /// Intended to be used only when `appendSliceAssumeCapacity` would be
966 /// a compile error.
967 ///
897 /// Asserts that the list can hold the additional items.968 /// Asserts that the list can hold the additional items.
898 pub fn appendUnalignedSliceAssumeCapacity(self: *Self, items: []align(1) const T) void {969 pub fn appendUnalignedSliceAssumeCapacity(self: *Self, items: []align(1) const T) void {
899 const old_len = self.items.len;970 const old_len = self.items.len;
...@@ -903,6 +974,18 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -903,6 +974,18 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
903 @memcpy(self.items[old_len..][0..items.len], items);974 @memcpy(self.items[old_len..][0..items.len], items);
904 }975 }
905976
977 /// Append an unaligned slice of items to the list.
978 ///
979 /// Intended to be used only when `appendSliceAssumeCapacity` would be
980 /// a compile error.
981 ///
982 /// If the list lacks unused capacity for the additional items, returns
983 /// `error.OutOfMemory`.
984 pub fn appendUnalignedSliceBounded(self: *Self, items: []align(1) const T) error{OutOfMemory}!void {
985 if (self.capacity - self.items.len < items.len) return error.OutOfMemory;
986 return appendUnalignedSliceAssumeCapacity(self, items);
987 }
988
906 pub fn print(self: *Self, gpa: Allocator, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void {989 pub fn print(self: *Self, gpa: Allocator, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void {
907 comptime assert(T == u8);990 comptime assert(T == u8);
908 try self.ensureUnusedCapacity(gpa, fmt.len);991 try self.ensureUnusedCapacity(gpa, fmt.len);
...@@ -920,6 +1003,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -920,6 +1003,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
920 self.items.len += w.end;1003 self.items.len += w.end;
921 }1004 }
9221005
1006 pub fn printBounded(self: *Self, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void {
1007 comptime assert(T == u8);
1008 var w: std.io.Writer = .fixed(self.unusedCapacitySlice());
1009 w.print(fmt, args) catch return error.OutOfMemory;
1010 self.items.len += w.end;
1011 }
1012
923 /// Append a value to the list `n` times.1013 /// Append a value to the list `n` times.
924 /// Allocates more memory as necessary.1014 /// Allocates more memory as necessary.
925 /// Invalidates element pointers if additional memory is needed.1015 /// Invalidates element pointers if additional memory is needed.
...@@ -932,9 +1022,12 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -932,9 +1022,12 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
932 }1022 }
9331023
934 /// Append a value to the list `n` times.1024 /// Append a value to the list `n` times.
1025 ///
935 /// Never invalidates element pointers.1026 /// Never invalidates element pointers.
1027 ///
936 /// The function is inline so that a comptime-known `value` parameter will1028 /// The function is inline so that a comptime-known `value` parameter will
937 /// have better memset codegen in case it has a repeated byte pattern.1029 /// have better memset codegen in case it has a repeated byte pattern.
1030 ///
938 /// Asserts that the list can hold the additional items.1031 /// Asserts that the list can hold the additional items.
939 pub inline fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {1032 pub inline fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {
940 const new_len = self.items.len + n;1033 const new_len = self.items.len + n;
...@@ -943,6 +1036,22 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -943,6 +1036,22 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
943 self.items.len = new_len;1036 self.items.len = new_len;
944 }1037 }
9451038
1039 /// Append a value to the list `n` times.
1040 ///
1041 /// Never invalidates element pointers.
1042 ///
1043 /// The function is inline so that a comptime-known `value` parameter will
1044 /// have better memset codegen in case it has a repeated byte pattern.
1045 ///
1046 /// If the list lacks unused capacity for the additional items, returns
1047 /// `error.OutOfMemory`.
1048 pub inline fn appendNTimesBounded(self: *Self, value: T, n: usize) error{OutOfMemory}!void {
1049 const new_len = self.items.len + n;
1050 if (self.capacity < new_len) return error.OutOfMemory;
1051 @memset(self.items.ptr[self.items.len..new_len], value);
1052 self.items.len = new_len;
1053 }
1054
946 /// Adjust the list length to `new_len`.1055 /// Adjust the list length to `new_len`.
947 /// Additional elements contain the value `undefined`.1056 /// Additional elements contain the value `undefined`.
948 /// Invalidates element pointers if additional memory is needed.1057 /// Invalidates element pointers if additional memory is needed.
...@@ -1068,8 +1177,11 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -1068,8 +1177,11 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
1068 }1177 }
10691178
1070 /// Increase length by 1, returning pointer to the new item.1179 /// Increase length by 1, returning pointer to the new item.
1180 ///
1071 /// Never invalidates element pointers.1181 /// Never invalidates element pointers.
1182 ///
1072 /// The returned element pointer becomes invalid when the list is resized.1183 /// The returned element pointer becomes invalid when the list is resized.
1184 ///
1073 /// Asserts that the list can hold one additional item.1185 /// Asserts that the list can hold one additional item.
1074 pub fn addOneAssumeCapacity(self: *Self) *T {1186 pub fn addOneAssumeCapacity(self: *Self) *T {
1075 assert(self.items.len < self.capacity);1187 assert(self.items.len < self.capacity);
...@@ -1078,6 +1190,18 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -1078,6 +1190,18 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
1078 return &self.items[self.items.len - 1];1190 return &self.items[self.items.len - 1];
1079 }1191 }
10801192
1193 /// Increase length by 1, returning pointer to the new item.
1194 ///
1195 /// Never invalidates element pointers.
1196 ///
1197 /// The returned element pointer becomes invalid when the list is resized.
1198 ///
1199 /// If the list lacks unused capacity for the additional item, returns `error.OutOfMemory`.
1200 pub fn addOneBounded(self: *Self) error{OutOfMemory}!*T {
1201 if (self.capacity - self.items.len < 1) return error.OutOfMemory;
1202 return addOneAssumeCapacity(self);
1203 }
1204
1081 /// Resize the array, adding `n` new elements, which have `undefined` values.1205 /// Resize the array, adding `n` new elements, which have `undefined` values.
1082 /// The return value is an array pointing to the newly allocated elements.1206 /// The return value is an array pointing to the newly allocated elements.
1083 /// The returned pointer becomes invalid when the list is resized.1207 /// The returned pointer becomes invalid when the list is resized.
...@@ -1088,9 +1212,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -1088,9 +1212,13 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
1088 }1212 }
10891213
1090 /// Resize the array, adding `n` new elements, which have `undefined` values.1214 /// Resize the array, adding `n` new elements, which have `undefined` values.
1215 ///
1091 /// The return value is an array pointing to the newly allocated elements.1216 /// The return value is an array pointing to the newly allocated elements.
1217 ///
1092 /// Never invalidates element pointers.1218 /// Never invalidates element pointers.
1219 ///
1093 /// The returned pointer becomes invalid when the list is resized.1220 /// The returned pointer becomes invalid when the list is resized.
1221 ///
1094 /// Asserts that the list can hold the additional items.1222 /// Asserts that the list can hold the additional items.
1095 pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T {1223 pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T {
1096 assert(self.items.len + n <= self.capacity);1224 assert(self.items.len + n <= self.capacity);
...@@ -1099,6 +1227,21 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -1099,6 +1227,21 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
1099 return self.items[prev_len..][0..n];1227 return self.items[prev_len..][0..n];
1100 }1228 }
11011229
1230 /// Resize the array, adding `n` new elements, which have `undefined` values.
1231 ///
1232 /// The return value is an array pointing to the newly allocated elements.
1233 ///
1234 /// Never invalidates element pointers.
1235 ///
1236 /// The returned pointer becomes invalid when the list is resized.
1237 ///
1238 /// If the list lacks unused capacity for the additional items, returns
1239 /// `error.OutOfMemory`.
1240 pub fn addManyAsArrayBounded(self: *Self, comptime n: usize) error{OutOfMemory}!*[n]T {
1241 if (self.capacity - self.items.len < n) return error.OutOfMemory;
1242 return addManyAsArrayAssumeCapacity(self, n);
1243 }
1244
1102 /// Resize the array, adding `n` new elements, which have `undefined` values.1245 /// Resize the array, adding `n` new elements, which have `undefined` values.
1103 /// The return value is a slice pointing to the newly allocated elements.1246 /// The return value is a slice pointing to the newly allocated elements.
1104 /// The returned pointer becomes invalid when the list is resized.1247 /// The returned pointer becomes invalid when the list is resized.
...@@ -1109,10 +1252,12 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -1109,10 +1252,12 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
1109 return self.items[prev_len..][0..n];1252 return self.items[prev_len..][0..n];
1110 }1253 }
11111254
1112 /// Resize the array, adding `n` new elements, which have `undefined` values.1255 /// Resizes the array, adding `n` new elements, which have `undefined`
1113 /// The return value is a slice pointing to the newly allocated elements.1256 /// values, returning a slice pointing to the newly allocated elements.
1114 /// Never invalidates element pointers.1257 ///
1115 /// The returned pointer becomes invalid when the list is resized.1258 /// Never invalidates element pointers. The returned pointer becomes
1259 /// invalid when the list is resized.
1260 ///
1116 /// Asserts that the list can hold the additional items.1261 /// Asserts that the list can hold the additional items.
1117 pub fn addManyAsSliceAssumeCapacity(self: *Self, n: usize) []T {1262 pub fn addManyAsSliceAssumeCapacity(self: *Self, n: usize) []T {
1118 assert(self.items.len + n <= self.capacity);1263 assert(self.items.len + n <= self.capacity);
...@@ -1121,6 +1266,19 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -1121,6 +1266,19 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
1121 return self.items[prev_len..][0..n];1266 return self.items[prev_len..][0..n];
1122 }1267 }
11231268
1269 /// Resizes the array, adding `n` new elements, which have `undefined`
1270 /// values, returning a slice pointing to the newly allocated elements.
1271 ///
1272 /// Never invalidates element pointers. The returned pointer becomes
1273 /// invalid when the list is resized.
1274 ///
1275 /// If the list lacks unused capacity for the additional items, returns
1276 /// `error.OutOfMemory`.
1277 pub fn addManyAsSliceBounded(self: *Self, n: usize) error{OutOfMemory}![]T {
1278 if (self.capacity - self.items.len < n) return error.OutOfMemory;
1279 return addManyAsSliceAssumeCapacity(self, n);
1280 }
1281
1124 /// Remove and return the last element from the list.1282 /// Remove and return the last element from the list.
1125 /// If the list is empty, returns `null`.1283 /// If the list is empty, returns `null`.
1126 /// Invalidates pointers to last element.1284 /// Invalidates pointers to last element.
lib/std/base64.zig+6-26
...@@ -118,22 +118,6 @@ pub const Base64Encoder = struct {...@@ -118,22 +118,6 @@ pub const Base64Encoder = struct {
118 }118 }
119 }119 }
120120
121 // destWriter must be compatible with std.io.GenericWriter's writeAll interface
122 // sourceReader must be compatible with `std.io.GenericReader` read interface
123 pub fn encodeFromReaderToWriter(encoder: *const Base64Encoder, destWriter: anytype, sourceReader: anytype) !void {
124 while (true) {
125 var tempSource: [3]u8 = undefined;
126 const bytesRead = try sourceReader.read(&tempSource);
127 if (bytesRead == 0) {
128 break;
129 }
130
131 var temp: [5]u8 = undefined;
132 const s = encoder.encode(&temp, tempSource[0..bytesRead]);
133 try destWriter.writeAll(s);
134 }
135 }
136
137 /// dest.len must at least be what you get from ::calcSize.121 /// dest.len must at least be what you get from ::calcSize.
138 pub fn encode(encoder: *const Base64Encoder, dest: []u8, source: []const u8) []const u8 {122 pub fn encode(encoder: *const Base64Encoder, dest: []u8, source: []const u8) []const u8 {
139 const out_len = encoder.calcSize(source.len);123 const out_len = encoder.calcSize(source.len);
...@@ -517,17 +501,13 @@ fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: [...@@ -517,17 +501,13 @@ fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: [
517 var buffer: [0x100]u8 = undefined;501 var buffer: [0x100]u8 = undefined;
518 const encoded = codecs.Encoder.encode(&buffer, expected_decoded);502 const encoded = codecs.Encoder.encode(&buffer, expected_decoded);
519 try testing.expectEqualSlices(u8, expected_encoded, encoded);503 try testing.expectEqualSlices(u8, expected_encoded, encoded);
520504 }
505 {
521 // stream encode506 // stream encode
522 var list = try std.BoundedArray(u8, 0x100).init(0);507 var buffer: [0x100]u8 = undefined;
523 try codecs.Encoder.encodeWriter(list.writer(), expected_decoded);508 var writer: std.Io.Writer = .fixed(&buffer);
524 try testing.expectEqualSlices(u8, expected_encoded, list.slice());509 try codecs.Encoder.encodeWriter(&writer, expected_decoded);
525510 try testing.expectEqualSlices(u8, expected_encoded, writer.buffered());
526 // reader to writer encode
527 var stream = std.io.fixedBufferStream(expected_decoded);
528 list = try std.BoundedArray(u8, 0x100).init(0);
529 try codecs.Encoder.encodeFromReaderToWriter(list.writer(), stream.reader());
530 try testing.expectEqualSlices(u8, expected_encoded, list.slice());
531 }511 }
532512
533 // Base64Decoder513 // Base64Decoder
lib/std/bounded_array.zig deleted-412
...@@ -1,412 +0,0 @@
1const std = @import("std.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const testing = std.testing;
5const Alignment = std.mem.Alignment;
6
7/// A structure with an array and a length, that can be used as a slice.
8///
9/// Useful to pass around small arrays whose exact size is only known at
10/// runtime, but whose maximum size is known at comptime, without requiring
11/// an `Allocator`.
12///
13/// ```zig
14/// var actual_size = 32;
15/// var a = try BoundedArray(u8, 64).init(actual_size);
16/// var slice = a.slice(); // a slice of the 64-byte array
17/// var a_clone = a; // creates a copy - the structure doesn't use any internal pointers
18/// ```
19pub fn BoundedArray(comptime T: type, comptime buffer_capacity: usize) type {
20 return BoundedArrayAligned(T, .of(T), buffer_capacity);
21}
22
23/// A structure with an array, length and alignment, that can be used as a
24/// slice.
25///
26/// Useful to pass around small explicitly-aligned arrays whose exact size is
27/// only known at runtime, but whose maximum size is known at comptime, without
28/// requiring an `Allocator`.
29/// ```zig
30// var a = try BoundedArrayAligned(u8, 16, 2).init(0);
31// try a.append(255);
32// try a.append(255);
33// const b = @ptrCast(*const [1]u16, a.constSlice().ptr);
34// try testing.expectEqual(@as(u16, 65535), b[0]);
35/// ```
36pub fn BoundedArrayAligned(
37 comptime T: type,
38 comptime alignment: Alignment,
39 comptime buffer_capacity: usize,
40) type {
41 return struct {
42 const Self = @This();
43 buffer: [buffer_capacity]T align(alignment.toByteUnits()) = undefined,
44 len: usize = 0,
45
46 /// Set the actual length of the slice.
47 /// Returns error.Overflow if it exceeds the length of the backing array.
48 pub fn init(len: usize) error{Overflow}!Self {
49 if (len > buffer_capacity) return error.Overflow;
50 return Self{ .len = len };
51 }
52
53 /// View the internal array as a slice whose size was previously set.
54 pub fn slice(self: anytype) switch (@TypeOf(&self.buffer)) {
55 *align(alignment.toByteUnits()) [buffer_capacity]T => []align(alignment.toByteUnits()) T,
56 *align(alignment.toByteUnits()) const [buffer_capacity]T => []align(alignment.toByteUnits()) const T,
57 else => unreachable,
58 } {
59 return self.buffer[0..self.len];
60 }
61
62 /// View the internal array as a constant slice whose size was previously set.
63 pub fn constSlice(self: *const Self) []align(alignment.toByteUnits()) const T {
64 return self.slice();
65 }
66
67 /// Adjust the slice's length to `len`.
68 /// Does not initialize added items if any.
69 pub fn resize(self: *Self, len: usize) error{Overflow}!void {
70 if (len > buffer_capacity) return error.Overflow;
71 self.len = len;
72 }
73
74 /// Remove all elements from the slice.
75 pub fn clear(self: *Self) void {
76 self.len = 0;
77 }
78
79 /// Copy the content of an existing slice.
80 pub fn fromSlice(m: []const T) error{Overflow}!Self {
81 var list = try init(m.len);
82 @memcpy(list.slice(), m);
83 return list;
84 }
85
86 /// Return the element at index `i` of the slice.
87 pub fn get(self: Self, i: usize) T {
88 return self.constSlice()[i];
89 }
90
91 /// Set the value of the element at index `i` of the slice.
92 pub fn set(self: *Self, i: usize, item: T) void {
93 self.slice()[i] = item;
94 }
95
96 /// Return the maximum length of a slice.
97 pub fn capacity(self: Self) usize {
98 return self.buffer.len;
99 }
100
101 /// Check that the slice can hold at least `additional_count` items.
102 pub fn ensureUnusedCapacity(self: Self, additional_count: usize) error{Overflow}!void {
103 if (self.len + additional_count > buffer_capacity) {
104 return error.Overflow;
105 }
106 }
107
108 /// Increase length by 1, returning a pointer to the new item.
109 pub fn addOne(self: *Self) error{Overflow}!*T {
110 try self.ensureUnusedCapacity(1);
111 return self.addOneAssumeCapacity();
112 }
113
114 /// Increase length by 1, returning pointer to the new item.
115 /// Asserts that there is space for the new item.
116 pub fn addOneAssumeCapacity(self: *Self) *T {
117 assert(self.len < buffer_capacity);
118 self.len += 1;
119 return &self.slice()[self.len - 1];
120 }
121
122 /// Resize the slice, adding `n` new elements, which have `undefined` values.
123 /// The return value is a pointer to the array of uninitialized elements.
124 pub fn addManyAsArray(self: *Self, comptime n: usize) error{Overflow}!*align(alignment.toByteUnits()) [n]T {
125 const prev_len = self.len;
126 try self.resize(self.len + n);
127 return self.slice()[prev_len..][0..n];
128 }
129
130 /// Resize the slice, adding `n` new elements, which have `undefined` values.
131 /// The return value is a slice pointing to the uninitialized elements.
132 pub fn addManyAsSlice(self: *Self, n: usize) error{Overflow}![]align(alignment.toByteUnits()) T {
133 const prev_len = self.len;
134 try self.resize(self.len + n);
135 return self.slice()[prev_len..][0..n];
136 }
137
138 /// Remove and return the last element from the slice, or return `null` if the slice is empty.
139 pub fn pop(self: *Self) ?T {
140 if (self.len == 0) return null;
141 const item = self.get(self.len - 1);
142 self.len -= 1;
143 return item;
144 }
145
146 /// Return a slice of only the extra capacity after items.
147 /// This can be useful for writing directly into it.
148 /// Note that such an operation must be followed up with a
149 /// call to `resize()`
150 pub fn unusedCapacitySlice(self: *Self) []align(alignment.toByteUnits()) T {
151 return self.buffer[self.len..];
152 }
153
154 /// Insert `item` at index `i` by moving `slice[n .. slice.len]` to make room.
155 /// This operation is O(N).
156 pub fn insert(
157 self: *Self,
158 i: usize,
159 item: T,
160 ) error{Overflow}!void {
161 if (i > self.len) {
162 return error.Overflow;
163 }
164 _ = try self.addOne();
165 var s = self.slice();
166 mem.copyBackwards(T, s[i + 1 .. s.len], s[i .. s.len - 1]);
167 self.buffer[i] = item;
168 }
169
170 /// Insert slice `items` at index `i` by moving `slice[i .. slice.len]` to make room.
171 /// This operation is O(N).
172 pub fn insertSlice(self: *Self, i: usize, items: []const T) error{Overflow}!void {
173 try self.ensureUnusedCapacity(items.len);
174 self.len += items.len;
175 mem.copyBackwards(T, self.slice()[i + items.len .. self.len], self.constSlice()[i .. self.len - items.len]);
176 @memcpy(self.slice()[i..][0..items.len], items);
177 }
178
179 /// Replace range of elements `slice[start..][0..len]` with `new_items`.
180 /// Grows slice if `len < new_items.len`.
181 /// Shrinks slice if `len > new_items.len`.
182 pub fn replaceRange(
183 self: *Self,
184 start: usize,
185 len: usize,
186 new_items: []const T,
187 ) error{Overflow}!void {
188 const after_range = start + len;
189 var range = self.slice()[start..after_range];
190
191 if (range.len == new_items.len) {
192 @memcpy(range[0..new_items.len], new_items);
193 } else if (range.len < new_items.len) {
194 const first = new_items[0..range.len];
195 const rest = new_items[range.len..];
196 @memcpy(range[0..first.len], first);
197 try self.insertSlice(after_range, rest);
198 } else {
199 @memcpy(range[0..new_items.len], new_items);
200 const after_subrange = start + new_items.len;
201 for (self.constSlice()[after_range..], 0..) |item, i| {
202 self.slice()[after_subrange..][i] = item;
203 }
204 self.len -= len - new_items.len;
205 }
206 }
207
208 /// Extend the slice by 1 element.
209 pub fn append(self: *Self, item: T) error{Overflow}!void {
210 const new_item_ptr = try self.addOne();
211 new_item_ptr.* = item;
212 }
213
214 /// Extend the slice by 1 element, asserting the capacity is already
215 /// enough to store the new item.
216 pub fn appendAssumeCapacity(self: *Self, item: T) void {
217 const new_item_ptr = self.addOneAssumeCapacity();
218 new_item_ptr.* = item;
219 }
220
221 /// Remove the element at index `i`, shift elements after index
222 /// `i` forward, and return the removed element.
223 /// Asserts the slice has at least one item.
224 /// This operation is O(N).
225 pub fn orderedRemove(self: *Self, i: usize) T {
226 const newlen = self.len - 1;
227 if (newlen == i) return self.pop().?;
228 const old_item = self.get(i);
229 for (self.slice()[i..newlen], 0..) |*b, j| b.* = self.get(i + 1 + j);
230 self.set(newlen, undefined);
231 self.len = newlen;
232 return old_item;
233 }
234
235 /// Remove the element at the specified index and return it.
236 /// The empty slot is filled from the end of the slice.
237 /// This operation is O(1).
238 pub fn swapRemove(self: *Self, i: usize) T {
239 if (self.len - 1 == i) return self.pop().?;
240 const old_item = self.get(i);
241 self.set(i, self.pop().?);
242 return old_item;
243 }
244
245 /// Append the slice of items to the slice.
246 pub fn appendSlice(self: *Self, items: []const T) error{Overflow}!void {
247 try self.ensureUnusedCapacity(items.len);
248 self.appendSliceAssumeCapacity(items);
249 }
250
251 /// Append the slice of items to the slice, asserting the capacity is already
252 /// enough to store the new items.
253 pub fn appendSliceAssumeCapacity(self: *Self, items: []const T) void {
254 const old_len = self.len;
255 self.len += items.len;
256 @memcpy(self.slice()[old_len..][0..items.len], items);
257 }
258
259 /// Append a value to the slice `n` times.
260 /// Allocates more memory as necessary.
261 pub fn appendNTimes(self: *Self, value: T, n: usize) error{Overflow}!void {
262 const old_len = self.len;
263 try self.resize(old_len + n);
264 @memset(self.slice()[old_len..self.len], value);
265 }
266
267 /// Append a value to the slice `n` times.
268 /// Asserts the capacity is enough.
269 pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {
270 const old_len = self.len;
271 self.len += n;
272 assert(self.len <= buffer_capacity);
273 @memset(self.slice()[old_len..self.len], value);
274 }
275
276 pub const Writer = if (T != u8)
277 @compileError("The Writer interface is only defined for BoundedArray(u8, ...) " ++
278 "but the given type is BoundedArray(" ++ @typeName(T) ++ ", ...)")
279 else
280 std.io.GenericWriter(*Self, error{Overflow}, appendWrite);
281
282 /// Initializes a writer which will write into the array.
283 pub fn writer(self: *Self) Writer {
284 return .{ .context = self };
285 }
286
287 /// Same as `appendSlice` except it returns the number of bytes written, which is always the same
288 /// as `m.len`. The purpose of this function existing is to match `std.io.GenericWriter` API.
289 fn appendWrite(self: *Self, m: []const u8) error{Overflow}!usize {
290 try self.appendSlice(m);
291 return m.len;
292 }
293 };
294}
295
296test BoundedArray {
297 var a = try BoundedArray(u8, 64).init(32);
298
299 try testing.expectEqual(a.capacity(), 64);
300 try testing.expectEqual(a.slice().len, 32);
301 try testing.expectEqual(a.constSlice().len, 32);
302
303 try a.resize(48);
304 try testing.expectEqual(a.len, 48);
305
306 const x = [_]u8{1} ** 10;
307 a = try BoundedArray(u8, 64).fromSlice(&x);
308 try testing.expectEqualSlices(u8, &x, a.constSlice());
309
310 var a2 = a;
311 try testing.expectEqualSlices(u8, a.constSlice(), a2.constSlice());
312 a2.set(0, 0);
313 try testing.expect(a.get(0) != a2.get(0));
314
315 try testing.expectError(error.Overflow, a.resize(100));
316 try testing.expectError(error.Overflow, BoundedArray(u8, x.len - 1).fromSlice(&x));
317
318 try a.resize(0);
319 try a.ensureUnusedCapacity(a.capacity());
320 (try a.addOne()).* = 0;
321 try a.ensureUnusedCapacity(a.capacity() - 1);
322 try testing.expectEqual(a.len, 1);
323
324 const uninitialized = try a.addManyAsArray(4);
325 try testing.expectEqual(uninitialized.len, 4);
326 try testing.expectEqual(a.len, 5);
327
328 try a.append(0xff);
329 try testing.expectEqual(a.len, 6);
330 try testing.expectEqual(a.pop(), 0xff);
331
332 a.appendAssumeCapacity(0xff);
333 try testing.expectEqual(a.len, 6);
334 try testing.expectEqual(a.pop(), 0xff);
335
336 try a.resize(1);
337 try testing.expectEqual(a.pop(), 0);
338 try testing.expectEqual(a.pop(), null);
339 var unused = a.unusedCapacitySlice();
340 @memset(unused[0..8], 2);
341 unused[8] = 3;
342 unused[9] = 4;
343 try testing.expectEqual(unused.len, a.capacity());
344 try a.resize(10);
345
346 try a.insert(5, 0xaa);
347 try testing.expectEqual(a.len, 11);
348 try testing.expectEqual(a.get(5), 0xaa);
349 try testing.expectEqual(a.get(9), 3);
350 try testing.expectEqual(a.get(10), 4);
351
352 try a.insert(11, 0xbb);
353 try testing.expectEqual(a.len, 12);
354 try testing.expectEqual(a.pop(), 0xbb);
355
356 try a.appendSlice(&x);
357 try testing.expectEqual(a.len, 11 + x.len);
358
359 try a.appendNTimes(0xbb, 5);
360 try testing.expectEqual(a.len, 11 + x.len + 5);
361 try testing.expectEqual(a.pop(), 0xbb);
362
363 a.appendNTimesAssumeCapacity(0xcc, 5);
364 try testing.expectEqual(a.len, 11 + x.len + 5 - 1 + 5);
365 try testing.expectEqual(a.pop(), 0xcc);
366
367 try testing.expectEqual(a.len, 29);
368 try a.replaceRange(1, 20, &x);
369 try testing.expectEqual(a.len, 29 + x.len - 20);
370
371 try a.insertSlice(0, &x);
372 try testing.expectEqual(a.len, 29 + x.len - 20 + x.len);
373
374 try a.replaceRange(1, 5, &x);
375 try testing.expectEqual(a.len, 29 + x.len - 20 + x.len + x.len - 5);
376
377 try a.append(10);
378 try testing.expectEqual(a.pop(), 10);
379
380 try a.append(20);
381 const removed = a.orderedRemove(5);
382 try testing.expectEqual(removed, 1);
383 try testing.expectEqual(a.len, 34);
384
385 a.set(0, 0xdd);
386 a.set(a.len - 1, 0xee);
387 const swapped = a.swapRemove(0);
388 try testing.expectEqual(swapped, 0xdd);
389 try testing.expectEqual(a.get(0), 0xee);
390
391 const added_slice = try a.addManyAsSlice(3);
392 try testing.expectEqual(added_slice.len, 3);
393 try testing.expectEqual(a.len, 36);
394
395 while (a.pop()) |_| {}
396 const w = a.writer();
397 const s = "hello, this is a test string";
398 try w.writeAll(s);
399 try testing.expectEqualStrings(s, a.constSlice());
400}
401
402test "BoundedArrayAligned" {
403 var a = try BoundedArrayAligned(u8, .@"16", 4).init(0);
404 try a.append(0);
405 try a.append(0);
406 try a.append(255);
407 try a.append(255);
408
409 const b = @as(*const [2]u16, @ptrCast(a.constSlice().ptr));
410 try testing.expectEqual(@as(u16, 0), b[0]);
411 try testing.expectEqual(@as(u16, 65535), b[1]);
412}
lib/std/c.zig+6-6
...@@ -6970,11 +6970,11 @@ pub const utsname = switch (native_os) {...@@ -6970,11 +6970,11 @@ pub const utsname = switch (native_os) {
6970 domainname: [256:0]u8,6970 domainname: [256:0]u8,
6971 },6971 },
6972 .macos => extern struct {6972 .macos => extern struct {
6973 sysname: [256:0]u8,6973 sysname: [255:0]u8,
6974 nodename: [256:0]u8,6974 nodename: [255:0]u8,
6975 release: [256:0]u8,6975 release: [255:0]u8,
6976 version: [256:0]u8,6976 version: [255:0]u8,
6977 machine: [256:0]u8,6977 machine: [255:0]u8,
6978 },6978 },
6979 // https://github.com/SerenityOS/serenity/blob/d794ed1de7a46482272683f8dc4c858806390f29/Kernel/API/POSIX/sys/utsname.h#L17-L236979 // https://github.com/SerenityOS/serenity/blob/d794ed1de7a46482272683f8dc4c858806390f29/Kernel/API/POSIX/sys/utsname.h#L17-L23
6980 .serenity => extern struct {6980 .serenity => extern struct {
...@@ -6984,7 +6984,7 @@ pub const utsname = switch (native_os) {...@@ -6984,7 +6984,7 @@ pub const utsname = switch (native_os) {
6984 version: [UTSNAME_ENTRY_LEN:0]u8,6984 version: [UTSNAME_ENTRY_LEN:0]u8,
6985 machine: [UTSNAME_ENTRY_LEN:0]u8,6985 machine: [UTSNAME_ENTRY_LEN:0]u8,
69866986
6987 const UTSNAME_ENTRY_LEN = 65;6987 const UTSNAME_ENTRY_LEN = 64;
6988 },6988 },
6989 else => void,6989 else => void,
6990};6990};
lib/std/compress/flate/Decompress.zig+4-3
...@@ -373,7 +373,7 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader...@@ -373,7 +373,7 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader
373 d.state = .{ .stored_block = @intCast(remaining_len - n) };373 d.state = .{ .stored_block = @intCast(remaining_len - n) };
374 }374 }
375 w.advance(n);375 w.advance(n);
376 return n;376 return @intFromEnum(limit) - remaining + n;
377 },377 },
378 .fixed_block => {378 .fixed_block => {
379 while (remaining > 0) {379 while (remaining > 0) {
...@@ -603,7 +603,7 @@ fn tossBitsEnding(d: *Decompress, n: u4) !void {...@@ -603,7 +603,7 @@ fn tossBitsEnding(d: *Decompress, n: u4) !void {
603 error.EndOfStream => unreachable,603 error.EndOfStream => unreachable,
604 };604 };
605 d.next_bits = next_int >> needed_bits;605 d.next_bits = next_int >> needed_bits;
606 d.remaining_bits = @intCast(@as(usize, n) * 8 -| @as(usize, needed_bits));606 d.remaining_bits = @intCast(@as(usize, buffered_n) * 8 -| @as(usize, needed_bits));
607}607}
608608
609fn takeBitsRuntime(d: *Decompress, n: u4) !u16 {609fn takeBitsRuntime(d: *Decompress, n: u4) !u16 {
...@@ -1265,6 +1265,7 @@ fn testDecompress(container: Container, compressed: []const u8, expected_plain:...@@ -1265,6 +1265,7 @@ fn testDecompress(container: Container, compressed: []const u8, expected_plain:
1265 defer aw.deinit();1265 defer aw.deinit();
12661266
1267 var decompress: Decompress = .init(&in, container, &.{});1267 var decompress: Decompress = .init(&in, container, &.{});
1268 _ = try decompress.reader.streamRemaining(&aw.writer);1268 const decompressed_len = try decompress.reader.streamRemaining(&aw.writer);
1269 try testing.expectEqual(expected_plain.len, decompressed_len);
1269 try testing.expectEqualSlices(u8, expected_plain, aw.getWritten());1270 try testing.expectEqualSlices(u8, expected_plain, aw.getWritten());
1270}1271}
lib/std/crypto/ecdsa.zig+1-1
...@@ -58,7 +58,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -58,7 +58,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
58 pub const PublicKey = struct {58 pub const PublicKey = struct {
59 /// Length (in bytes) of a compressed sec1-encoded key.59 /// Length (in bytes) of a compressed sec1-encoded key.
60 pub const compressed_sec1_encoded_length = 1 + Curve.Fe.encoded_length;60 pub const compressed_sec1_encoded_length = 1 + Curve.Fe.encoded_length;
61 /// Length (in bytes) of a compressed sec1-encoded key.61 /// Length (in bytes) of an uncompressed sec1-encoded key.
62 pub const uncompressed_sec1_encoded_length = 1 + 2 * Curve.Fe.encoded_length;62 pub const uncompressed_sec1_encoded_length = 1 + 2 * Curve.Fe.encoded_length;
6363
64 p: Curve,64 p: Curve,
lib/std/crypto/tls/Client.zig+51-19
...@@ -8,8 +8,8 @@ const mem = std.mem;...@@ -8,8 +8,8 @@ const mem = std.mem;
8const crypto = std.crypto;8const crypto = std.crypto;
9const assert = std.debug.assert;9const assert = std.debug.assert;
10const Certificate = std.crypto.Certificate;10const Certificate = std.crypto.Certificate;
11const Reader = std.io.Reader;11const Reader = std.Io.Reader;
12const Writer = std.io.Writer;12const Writer = std.Io.Writer;
1313
14const max_ciphertext_len = tls.max_ciphertext_len;14const max_ciphertext_len = tls.max_ciphertext_len;
15const hmacExpandLabel = tls.hmacExpandLabel;15const hmacExpandLabel = tls.hmacExpandLabel;
...@@ -27,6 +27,8 @@ reader: Reader,...@@ -27,6 +27,8 @@ reader: Reader,
2727
28/// The encrypted stream from the client to the server. Bytes are pushed here28/// The encrypted stream from the client to the server. Bytes are pushed here
29/// via `writer`.29/// via `writer`.
30///
31/// The buffer is asserted to have capacity at least `min_buffer_len`.
30output: *Writer,32output: *Writer,
31/// The plaintext stream from the client to the server.33/// The plaintext stream from the client to the server.
32writer: Writer,34writer: Writer,
...@@ -122,7 +124,6 @@ pub const Options = struct {...@@ -122,7 +124,6 @@ pub const Options = struct {
122 /// the amount of data expected, such as HTTP with the Content-Length header.124 /// the amount of data expected, such as HTTP with the Content-Length header.
123 allow_truncation_attacks: bool = false,125 allow_truncation_attacks: bool = false,
124 write_buffer: []u8,126 write_buffer: []u8,
125 /// Asserted to have capacity at least `min_buffer_len`.
126 read_buffer: []u8,127 read_buffer: []u8,
127 /// Populated when `error.TlsAlert` is returned from `init`.128 /// Populated when `error.TlsAlert` is returned from `init`.
128 alert: ?*tls.Alert = null,129 alert: ?*tls.Alert = null,
...@@ -185,6 +186,7 @@ const InitError = error{...@@ -185,6 +186,7 @@ const InitError = error{
185/// `input` is asserted to have buffer capacity at least `min_buffer_len`.186/// `input` is asserted to have buffer capacity at least `min_buffer_len`.
186pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client {187pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client {
187 assert(input.buffer.len >= min_buffer_len);188 assert(input.buffer.len >= min_buffer_len);
189 assert(output.buffer.len >= min_buffer_len);
188 const host = switch (options.host) {190 const host = switch (options.host) {
189 .no_verification => "",191 .no_verification => "",
190 .explicit => |host| host,192 .explicit => |host| host,
...@@ -278,6 +280,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -278,6 +280,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
278 {280 {
279 var iovecs: [2][]const u8 = .{ cleartext_header, host };281 var iovecs: [2][]const u8 = .{ cleartext_header, host };
280 try output.writeVecAll(iovecs[0..if (host.len == 0) 1 else 2]);282 try output.writeVecAll(iovecs[0..if (host.len == 0) 1 else 2]);
283 try output.flush();
281 }284 }
282285
283 var tls_version: tls.ProtocolVersion = undefined;286 var tls_version: tls.ProtocolVersion = undefined;
...@@ -328,7 +331,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -328,7 +331,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
328 var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined;331 var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined;
329 fragment: while (true) {332 fragment: while (true) {
330 // Ensure the input buffer pointer is stable in this scope.333 // Ensure the input buffer pointer is stable in this scope.
331 input.rebaseCapacity(tls.max_ciphertext_record_len);334 input.rebase(tls.max_ciphertext_record_len) catch |err| switch (err) {
335 error.EndOfStream => {}, // We have assurance the remainder of stream can be buffered.
336 };
332 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {337 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
333 error.EndOfStream => return error.TlsConnectionTruncated,338 error.EndOfStream => return error.TlsConnectionTruncated,
334 error.ReadFailed => return error.ReadFailed,339 error.ReadFailed => return error.ReadFailed,
...@@ -761,6 +766,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -761,6 +766,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
761 &client_verify_msg,766 &client_verify_msg,
762 };767 };
763 try output.writeVecAll(&all_msgs_vec);768 try output.writeVecAll(&all_msgs_vec);
769 try output.flush();
764 },770 },
765 }771 }
766 write_seq += 1;772 write_seq += 1;
...@@ -826,6 +832,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -826,6 +832,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
826 &finished_msg,832 &finished_msg,
827 };833 };
828 try output.writeVecAll(&all_msgs_vec);834 try output.writeVecAll(&all_msgs_vec);
835 try output.flush();
829836
830 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);837 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);
831 const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length);838 const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length);
...@@ -875,7 +882,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -875,7 +882,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
875 .buffer = options.write_buffer,882 .buffer = options.write_buffer,
876 .vtable = &.{883 .vtable = &.{
877 .drain = drain,884 .drain = drain,
878 .sendFile = Writer.unimplementedSendFile,885 .flush = flush,
879 },886 },
880 },887 },
881 .tls_version = tls_version,888 .tls_version = tls_version,
...@@ -908,32 +915,57 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -908,32 +915,57 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
908}915}
909916
910fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {917fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
911 const c: *Client = @fieldParentPtr("writer", w);918 const c: *Client = @alignCast(@fieldParentPtr("writer", w));
912 if (true) @panic("update to use the buffer and flush");
913 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;
914 const output = c.output;919 const output = c.output;
915 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);920 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
916 var total_clear: usize = 0;
917 var ciphertext_end: usize = 0;921 var ciphertext_end: usize = 0;
918 for (sliced_data) |buf| {922 var total_clear: usize = 0;
919 const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data);923 done: {
920 total_clear += prepared.cleartext_len;924 {
921 ciphertext_end += prepared.ciphertext_end;925 const buf = w.buffered();
922 if (total_clear < buf.len) break;926 const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data);
927 total_clear += prepared.cleartext_len;
928 ciphertext_end += prepared.ciphertext_end;
929 if (prepared.cleartext_len < buf.len) break :done;
930 }
931 for (data[0 .. data.len - 1]) |buf| {
932 if (buf.len < min_buffer_len) break :done;
933 const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data);
934 total_clear += prepared.cleartext_len;
935 ciphertext_end += prepared.ciphertext_end;
936 if (prepared.cleartext_len < buf.len) break :done;
937 }
938 const buf = data[data.len - 1];
939 for (0..splat) |_| {
940 if (buf.len < min_buffer_len) break :done;
941 const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data);
942 total_clear += prepared.cleartext_len;
943 ciphertext_end += prepared.ciphertext_end;
944 if (prepared.cleartext_len < buf.len) break :done;
945 }
923 }946 }
924 output.advance(ciphertext_end);947 output.advance(ciphertext_end);
925 return total_clear;948 return w.consume(total_clear);
949}
950
951fn flush(w: *Writer) Writer.Error!void {
952 const c: *Client = @alignCast(@fieldParentPtr("writer", w));
953 const output = c.output;
954 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
955 const prepared = prepareCiphertextRecord(c, ciphertext_buf, w.buffered(), .application_data);
956 output.advance(prepared.ciphertext_end);
957 w.end = 0;
926}958}
927959
928/// Sends a `close_notify` alert, which is necessary for the server to960/// Sends a `close_notify` alert, which is necessary for the server to
929/// distinguish between a properly finished TLS session, or a truncation961/// distinguish between a properly finished TLS session, or a truncation
930/// attack.962/// attack.
931pub fn end(c: *Client) Writer.Error!void {963pub fn end(c: *Client) Writer.Error!void {
964 try flush(&c.writer);
932 const output = c.output;965 const output = c.output;
933 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);966 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
934 const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert);967 const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert);
935 output.advance(prepared.cleartext_len);968 output.advance(prepared.ciphertext_end);
936 return prepared.ciphertext_end;
937}969}
938970
939fn prepareCiphertextRecord(971fn prepareCiphertextRecord(
...@@ -1043,8 +1075,8 @@ pub fn eof(c: Client) bool {...@@ -1043,8 +1075,8 @@ pub fn eof(c: Client) bool {
1043 return c.received_close_notify;1075 return c.received_close_notify;
1044}1076}
10451077
1046fn stream(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!usize {1078fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize {
1047 const c: *Client = @fieldParentPtr("reader", r);1079 const c: *Client = @alignCast(@fieldParentPtr("reader", r));
1048 if (c.eof()) return error.EndOfStream;1080 if (c.eof()) return error.EndOfStream;
1049 const input = c.input;1081 const input = c.input;
1050 // If at least one full encrypted record is not buffered, read once.1082 // If at least one full encrypted record is not buffered, read once.
lib/std/elf.zig+92-38
...@@ -502,6 +502,13 @@ pub const Header = struct {...@@ -502,6 +502,13 @@ pub const Header = struct {
502 };502 };
503 }503 }
504504
505 pub fn iterateProgramHeadersBuffer(h: Header, buf: []const u8) ProgramHeaderBufferIterator {
506 return .{
507 .elf_header = h,
508 .buf = buf,
509 };
510 }
511
505 pub fn iterateSectionHeaders(h: Header, file_reader: *std.fs.File.Reader) SectionHeaderIterator {512 pub fn iterateSectionHeaders(h: Header, file_reader: *std.fs.File.Reader) SectionHeaderIterator {
506 return .{513 return .{
507 .elf_header = h,514 .elf_header = h,
...@@ -509,6 +516,13 @@ pub const Header = struct {...@@ -509,6 +516,13 @@ pub const Header = struct {
509 };516 };
510 }517 }
511518
519 pub fn iterateSectionHeadersBuffer(h: Header, buf: []const u8) SectionHeaderBufferIterator {
520 return .{
521 .elf_header = h,
522 .buf = buf,
523 };
524 }
525
512 pub const ReadError = std.Io.Reader.Error || error{526 pub const ReadError = std.Io.Reader.Error || error{
513 InvalidElfMagic,527 InvalidElfMagic,
514 InvalidElfVersion,528 InvalidElfVersion,
...@@ -570,29 +584,48 @@ pub const ProgramHeaderIterator = struct {...@@ -570,29 +584,48 @@ pub const ProgramHeaderIterator = struct {
570 if (it.index >= it.elf_header.phnum) return null;584 if (it.index >= it.elf_header.phnum) return null;
571 defer it.index += 1;585 defer it.index += 1;
572586
573 if (it.elf_header.is_64) {587 const offset = it.elf_header.phoff + if (it.elf_header.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr) * it.index;
574 const offset = it.elf_header.phoff + @sizeOf(Elf64_Phdr) * it.index;
575 try it.file_reader.seekTo(offset);
576 const phdr = try it.file_reader.interface.takeStruct(Elf64_Phdr, it.elf_header.endian);
577 return phdr;
578 }
579
580 const offset = it.elf_header.phoff + @sizeOf(Elf32_Phdr) * it.index;
581 try it.file_reader.seekTo(offset);588 try it.file_reader.seekTo(offset);
582 const phdr = try it.file_reader.interface.takeStruct(Elf32_Phdr, it.elf_header.endian);589
583 return .{590 return takePhdr(&it.file_reader.interface, it.elf_header);
584 .p_type = phdr.p_type,591 }
585 .p_offset = phdr.p_offset,592};
586 .p_vaddr = phdr.p_vaddr,593
587 .p_paddr = phdr.p_paddr,594pub const ProgramHeaderBufferIterator = struct {
588 .p_filesz = phdr.p_filesz,595 elf_header: Header,
589 .p_memsz = phdr.p_memsz,596 buf: []const u8,
590 .p_flags = phdr.p_flags,597 index: usize = 0,
591 .p_align = phdr.p_align,598
592 };599 pub fn next(it: *ProgramHeaderBufferIterator) !?Elf64_Phdr {
600 if (it.index >= it.elf_header.phnum) return null;
601 defer it.index += 1;
602
603 const offset = it.elf_header.phoff + if (it.elf_header.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr) * it.index;
604 var reader = std.Io.Reader.fixed(it.buf[offset..]);
605
606 return takePhdr(&reader, it.elf_header);
593 }607 }
594};608};
595609
610fn takePhdr(reader: *std.io.Reader, elf_header: Header) !?Elf64_Phdr {
611 if (elf_header.is_64) {
612 const phdr = try reader.takeStruct(Elf64_Phdr, elf_header.endian);
613 return phdr;
614 }
615
616 const phdr = try reader.takeStruct(Elf32_Phdr, elf_header.endian);
617 return .{
618 .p_type = phdr.p_type,
619 .p_offset = phdr.p_offset,
620 .p_vaddr = phdr.p_vaddr,
621 .p_paddr = phdr.p_paddr,
622 .p_filesz = phdr.p_filesz,
623 .p_memsz = phdr.p_memsz,
624 .p_flags = phdr.p_flags,
625 .p_align = phdr.p_align,
626 };
627}
628
596pub const SectionHeaderIterator = struct {629pub const SectionHeaderIterator = struct {
597 elf_header: Header,630 elf_header: Header,
598 file_reader: *std.fs.File.Reader,631 file_reader: *std.fs.File.Reader,
...@@ -602,29 +635,50 @@ pub const SectionHeaderIterator = struct {...@@ -602,29 +635,50 @@ pub const SectionHeaderIterator = struct {
602 if (it.index >= it.elf_header.shnum) return null;635 if (it.index >= it.elf_header.shnum) return null;
603 defer it.index += 1;636 defer it.index += 1;
604637
605 if (it.elf_header.is_64) {638 const offset = it.elf_header.shoff + if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr) * it.index;
606 try it.file_reader.seekTo(it.elf_header.shoff + @sizeOf(Elf64_Shdr) * it.index);639 try it.file_reader.seekTo(offset);
607 const shdr = try it.file_reader.interface.takeStruct(Elf64_Shdr, it.elf_header.endian);
608 return shdr;
609 }
610640
611 try it.file_reader.seekTo(it.elf_header.shoff + @sizeOf(Elf32_Shdr) * it.index);641 return takeShdr(&it.file_reader.interface, it.elf_header);
612 const shdr = try it.file_reader.interface.takeStruct(Elf32_Shdr, it.elf_header.endian);642 }
613 return .{643};
614 .sh_name = shdr.sh_name,644
615 .sh_type = shdr.sh_type,645pub const SectionHeaderBufferIterator = struct {
616 .sh_flags = shdr.sh_flags,646 elf_header: Header,
617 .sh_addr = shdr.sh_addr,647 buf: []const u8,
618 .sh_offset = shdr.sh_offset,648 index: usize = 0,
619 .sh_size = shdr.sh_size,649
620 .sh_link = shdr.sh_link,650 pub fn next(it: *SectionHeaderBufferIterator) !?Elf64_Shdr {
621 .sh_info = shdr.sh_info,651 if (it.index >= it.elf_header.shnum) return null;
622 .sh_addralign = shdr.sh_addralign,652 defer it.index += 1;
623 .sh_entsize = shdr.sh_entsize,653
624 };654 const offset = it.elf_header.shoff + if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr) * it.index;
655 var reader = std.Io.Reader.fixed(it.buf[offset..]);
656
657 return takeShdr(&reader, it.elf_header);
625 }658 }
626};659};
627660
661fn takeShdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Shdr {
662 if (elf_header.is_64) {
663 const shdr = try reader.takeStruct(Elf64_Shdr, elf_header.endian);
664 return shdr;
665 }
666
667 const shdr = try reader.takeStruct(Elf32_Shdr, elf_header.endian);
668 return .{
669 .sh_name = shdr.sh_name,
670 .sh_type = shdr.sh_type,
671 .sh_flags = shdr.sh_flags,
672 .sh_addr = shdr.sh_addr,
673 .sh_offset = shdr.sh_offset,
674 .sh_size = shdr.sh_size,
675 .sh_link = shdr.sh_link,
676 .sh_info = shdr.sh_info,
677 .sh_addralign = shdr.sh_addralign,
678 .sh_entsize = shdr.sh_entsize,
679 };
680}
681
628pub const ELFCLASSNONE = 0;682pub const ELFCLASSNONE = 0;
629pub const ELFCLASS32 = 1;683pub const ELFCLASS32 = 1;
630pub const ELFCLASS64 = 2;684pub const ELFCLASS64 = 2;
lib/std/fs/File.zig+22-6
...@@ -1111,7 +1111,16 @@ pub const Reader = struct {...@@ -1111,7 +1111,16 @@ pub const Reader = struct {
1111 if (is_windows) {1111 if (is_windows) {
1112 // Unfortunately, `ReadFileScatter` cannot be used since it1112 // Unfortunately, `ReadFileScatter` cannot be used since it
1113 // requires page alignment.1113 // requires page alignment.
1114 return readPositional(r, data[0]);1114 assert(io_reader.seek == io_reader.end);
1115 io_reader.seek = 0;
1116 io_reader.end = 0;
1117 const first = data[0];
1118 if (first.len >= io_reader.buffer.len) {
1119 return readPositional(r, first);
1120 } else {
1121 io_reader.end += try readPositional(r, io_reader.buffer);
1122 return 0;
1123 }
1115 }1124 }
1116 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;1125 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1117 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);1126 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
...@@ -1141,8 +1150,7 @@ pub const Reader = struct {...@@ -1141,8 +1150,7 @@ pub const Reader = struct {
1141 }1150 }
1142 r.pos += n;1151 r.pos += n;
1143 if (n > data_size) {1152 if (n > data_size) {
1144 io_reader.seek = 0;1153 io_reader.end += n - data_size;
1145 io_reader.end = n - data_size;
1146 return data_size;1154 return data_size;
1147 }1155 }
1148 return n;1156 return n;
...@@ -1151,7 +1159,16 @@ pub const Reader = struct {...@@ -1151,7 +1159,16 @@ pub const Reader = struct {
1151 if (is_windows) {1159 if (is_windows) {
1152 // Unfortunately, `ReadFileScatter` cannot be used since it1160 // Unfortunately, `ReadFileScatter` cannot be used since it
1153 // requires page alignment.1161 // requires page alignment.
1154 return readStreaming(r, data[0]);1162 assert(io_reader.seek == io_reader.end);
1163 io_reader.seek = 0;
1164 io_reader.end = 0;
1165 const first = data[0];
1166 if (first.len >= io_reader.buffer.len) {
1167 return readStreaming(r, first);
1168 } else {
1169 io_reader.end += try readStreaming(r, io_reader.buffer);
1170 return 0;
1171 }
1155 }1172 }
1156 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;1173 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1157 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);1174 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
...@@ -1167,8 +1184,7 @@ pub const Reader = struct {...@@ -1167,8 +1184,7 @@ pub const Reader = struct {
1167 }1184 }
1168 r.pos += n;1185 r.pos += n;
1169 if (n > data_size) {1186 if (n > data_size) {
1170 io_reader.seek = 0;1187 io_reader.end += n - data_size;
1171 io_reader.end = n - data_size;
1172 return data_size;1188 return data_size;
1173 }1189 }
1174 return n;1190 return n;
lib/std/http.zig+125-112
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("std.zig");2const std = @import("std.zig");
3const assert = std.debug.assert;3const assert = std.debug.assert;
4const Writer = std.io.Writer;4const Writer = std.Io.Writer;
5const File = std.fs.File;5const File = std.fs.File;
66
7pub const Client = @import("http/Client.zig");7pub const Client = @import("http/Client.zig");
...@@ -20,51 +20,32 @@ pub const Version = enum {...@@ -20,51 +20,32 @@ pub const Version = enum {
20/// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definition20/// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definition
21///21///
22/// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH22/// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH
23pub const Method = enum(u64) {23pub const Method = enum {
24 GET = parse("GET"),24 GET,
25 HEAD = parse("HEAD"),25 HEAD,
26 POST = parse("POST"),26 POST,
27 PUT = parse("PUT"),27 PUT,
28 DELETE = parse("DELETE"),28 DELETE,
29 CONNECT = parse("CONNECT"),29 CONNECT,
30 OPTIONS = parse("OPTIONS"),30 OPTIONS,
31 TRACE = parse("TRACE"),31 TRACE,
32 PATCH = parse("PATCH"),32 PATCH,
33
34 _,
35
36 /// Converts `s` into a type that may be used as a `Method` field.
37 /// Asserts that `s` is 24 or fewer bytes.
38 pub fn parse(s: []const u8) u64 {
39 var x: u64 = 0;
40 const len = @min(s.len, @sizeOf(@TypeOf(x)));
41 @memcpy(std.mem.asBytes(&x)[0..len], s[0..len]);
42 return x;
43 }
44
45 pub fn format(self: Method, w: *std.io.Writer) std.io.Writer.Error!void {
46 const bytes: []const u8 = @ptrCast(&@intFromEnum(self));
47 const str = std.mem.sliceTo(bytes, 0);
48 try w.writeAll(str);
49 }
5033
51 /// Returns true if a request of this method is allowed to have a body34 /// Returns true if a request of this method is allowed to have a body
52 /// Actual behavior from servers may vary and should still be checked35 /// Actual behavior from servers may vary and should still be checked
53 pub fn requestHasBody(self: Method) bool {36 pub fn requestHasBody(m: Method) bool {
54 return switch (self) {37 return switch (m) {
55 .POST, .PUT, .PATCH => true,38 .POST, .PUT, .PATCH => true,
56 .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false,39 .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false,
57 else => true,
58 };40 };
59 }41 }
6042
61 /// Returns true if a response to this method is allowed to have a body43 /// Returns true if a response to this method is allowed to have a body
62 /// Actual behavior from clients may vary and should still be checked44 /// Actual behavior from clients may vary and should still be checked
63 pub fn responseHasBody(self: Method) bool {45 pub fn responseHasBody(m: Method) bool {
64 return switch (self) {46 return switch (m) {
65 .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true,47 .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true,
66 .HEAD, .PUT, .TRACE => false,48 .HEAD, .PUT, .TRACE => false,
67 else => true,
68 };49 };
69 }50 }
7051
...@@ -73,11 +54,10 @@ pub const Method = enum(u64) {...@@ -73,11 +54,10 @@ pub const Method = enum(u64) {
73 /// https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP54 /// https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP
74 ///55 ///
75 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.156 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1
76 pub fn safe(self: Method) bool {57 pub fn safe(m: Method) bool {
77 return switch (self) {58 return switch (m) {
78 .GET, .HEAD, .OPTIONS, .TRACE => true,59 .GET, .HEAD, .OPTIONS, .TRACE => true,
79 .POST, .PUT, .DELETE, .CONNECT, .PATCH => false,60 .POST, .PUT, .DELETE, .CONNECT, .PATCH => false,
80 else => false,
81 };61 };
82 }62 }
8363
...@@ -88,11 +68,10 @@ pub const Method = enum(u64) {...@@ -88,11 +68,10 @@ pub const Method = enum(u64) {
88 /// https://developer.mozilla.org/en-US/docs/Glossary/Idempotent68 /// https://developer.mozilla.org/en-US/docs/Glossary/Idempotent
89 ///69 ///
90 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.270 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2
91 pub fn idempotent(self: Method) bool {71 pub fn idempotent(m: Method) bool {
92 return switch (self) {72 return switch (m) {
93 .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true,73 .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true,
94 .CONNECT, .POST, .PATCH => false,74 .CONNECT, .POST, .PATCH => false,
95 else => false,
96 };75 };
97 }76 }
9877
...@@ -102,11 +81,10 @@ pub const Method = enum(u64) {...@@ -102,11 +81,10 @@ pub const Method = enum(u64) {
102 /// https://developer.mozilla.org/en-US/docs/Glossary/cacheable81 /// https://developer.mozilla.org/en-US/docs/Glossary/cacheable
103 ///82 ///
104 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.383 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3
105 pub fn cacheable(self: Method) bool {84 pub fn cacheable(m: Method) bool {
106 return switch (self) {85 return switch (m) {
107 .GET, .HEAD => true,86 .GET, .HEAD => true,
108 .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false,87 .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false,
109 else => false,
110 };88 };
111 }89 }
112};90};
...@@ -327,11 +305,11 @@ pub const Header = struct {...@@ -327,11 +305,11 @@ pub const Header = struct {
327};305};
328306
329pub const Reader = struct {307pub const Reader = struct {
330 in: *std.io.Reader,308 in: *std.Io.Reader,
331 /// This is preallocated memory that might be used by `bodyReader`. That309 /// This is preallocated memory that might be used by `bodyReader`. That
332 /// function might return a pointer to this field, or a different310 /// function might return a pointer to this field, or a different
333 /// `*std.io.Reader`. Advisable to not access this field directly.311 /// `*std.Io.Reader`. Advisable to not access this field directly.
334 interface: std.io.Reader,312 interface: std.Io.Reader,
335 /// Keeps track of whether the stream is ready to accept a new request,313 /// Keeps track of whether the stream is ready to accept a new request,
336 /// making invalid API usage cause assertion failures rather than HTTP314 /// making invalid API usage cause assertion failures rather than HTTP
337 /// protocol violations.315 /// protocol violations.
...@@ -343,10 +321,6 @@ pub const Reader = struct {...@@ -343,10 +321,6 @@ pub const Reader = struct {
343 /// read from `in`.321 /// read from `in`.
344 trailers: []const u8 = &.{},322 trailers: []const u8 = &.{},
345 body_err: ?BodyError = null,323 body_err: ?BodyError = null,
346 /// Stolen from `in`.
347 head_buffer: []u8 = &.{},
348
349 pub const max_chunk_header_len = 22;
350324
351 pub const RemainingChunkLen = enum(u64) {325 pub const RemainingChunkLen = enum(u64) {
352 head = 0,326 head = 0,
...@@ -398,35 +372,34 @@ pub const Reader = struct {...@@ -398,35 +372,34 @@ pub const Reader = struct {
398 ReadFailed,372 ReadFailed,
399 };373 };
400374
401 pub fn restituteHeadBuffer(reader: *Reader) void {375 /// Buffers the entire head inside `in`.
402 reader.in.restitute(reader.head_buffer.len);376 ///
403 reader.head_buffer.len = 0;377 /// The resulting memory is invalidated by any subsequent consumption of
404 }378 /// the input stream.
405379 pub fn receiveHead(reader: *Reader) HeadError![]const u8 {
406 /// Buffers the entire head into `head_buffer`, invalidating the previous
407 /// `head_buffer`, if any.
408 pub fn receiveHead(reader: *Reader) HeadError!void {
409 reader.trailers = &.{};380 reader.trailers = &.{};
410 const in = reader.in;381 const in = reader.in;
411 in.restitute(reader.head_buffer.len);
412 reader.head_buffer.len = 0;
413 in.rebase();
414 var hp: HeadParser = .{};382 var hp: HeadParser = .{};
415 var head_end: usize = 0;383 var head_len: usize = 0;
416 while (true) {384 while (true) {
417 if (head_end >= in.buffer.len) return error.HttpHeadersOversize;385 if (in.buffer.len - head_len == 0) return error.HttpHeadersOversize;
418 in.fillMore() catch |err| switch (err) {386 const remaining = in.buffered()[head_len..];
419 error.EndOfStream => switch (head_end) {387 if (remaining.len == 0) {
420 0 => return error.HttpConnectionClosing,388 in.fillMore() catch |err| switch (err) {
421 else => return error.HttpRequestTruncated,389 error.EndOfStream => switch (head_len) {
422 },390 0 => return error.HttpConnectionClosing,
423 error.ReadFailed => return error.ReadFailed,391 else => return error.HttpRequestTruncated,
424 };392 },
425 head_end += hp.feed(in.buffered()[head_end..]);393 error.ReadFailed => return error.ReadFailed,
394 };
395 continue;
396 }
397 head_len += hp.feed(remaining);
426 if (hp.state == .finished) {398 if (hp.state == .finished) {
427 reader.head_buffer = in.steal(head_end);
428 reader.state = .received_head;399 reader.state = .received_head;
429 return;400 const head_buffer = in.buffered()[0..head_len];
401 in.toss(head_len);
402 return head_buffer;
430 }403 }
431 }404 }
432 }405 }
...@@ -442,7 +415,7 @@ pub const Reader = struct {...@@ -442,7 +415,7 @@ pub const Reader = struct {
442 buffer: []u8,415 buffer: []u8,
443 transfer_encoding: TransferEncoding,416 transfer_encoding: TransferEncoding,
444 content_length: ?u64,417 content_length: ?u64,
445 ) *std.io.Reader {418 ) *std.Io.Reader {
446 assert(reader.state == .received_head);419 assert(reader.state == .received_head);
447 switch (transfer_encoding) {420 switch (transfer_encoding) {
448 .chunked => {421 .chunked => {
...@@ -492,7 +465,7 @@ pub const Reader = struct {...@@ -492,7 +465,7 @@ pub const Reader = struct {
492 content_encoding: ContentEncoding,465 content_encoding: ContentEncoding,
493 decompressor: *Decompressor,466 decompressor: *Decompressor,
494 decompression_buffer: []u8,467 decompression_buffer: []u8,
495 ) *std.io.Reader {468 ) *std.Io.Reader {
496 if (transfer_encoding == .none and content_length == null) {469 if (transfer_encoding == .none and content_length == null) {
497 assert(reader.state == .received_head);470 assert(reader.state == .received_head);
498 reader.state = .body_none;471 reader.state = .body_none;
...@@ -501,7 +474,7 @@ pub const Reader = struct {...@@ -501,7 +474,7 @@ pub const Reader = struct {
501 return reader.in;474 return reader.in;
502 },475 },
503 .deflate => {476 .deflate => {
504 decompressor.* = .{ .flate = .init(reader.in, .raw, decompression_buffer) };477 decompressor.* = .{ .flate = .init(reader.in, .zlib, decompression_buffer) };
505 return &decompressor.flate.reader;478 return &decompressor.flate.reader;
506 },479 },
507 .gzip => {480 .gzip => {
...@@ -520,37 +493,37 @@ pub const Reader = struct {...@@ -520,37 +493,37 @@ pub const Reader = struct {
520 }493 }
521494
522 fn contentLengthStream(495 fn contentLengthStream(
523 io_r: *std.io.Reader,496 io_r: *std.Io.Reader,
524 w: *Writer,497 w: *Writer,
525 limit: std.io.Limit,498 limit: std.Io.Limit,
526 ) std.io.Reader.StreamError!usize {499 ) std.Io.Reader.StreamError!usize {
527 const reader: *Reader = @fieldParentPtr("interface", io_r);500 const reader: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
528 const remaining_content_length = &reader.state.body_remaining_content_length;501 const remaining_content_length = &reader.state.body_remaining_content_length;
529 const remaining = remaining_content_length.*;502 const remaining = remaining_content_length.*;
530 if (remaining == 0) {503 if (remaining == 0) {
531 reader.state = .ready;504 reader.state = .ready;
532 return error.EndOfStream;505 return error.EndOfStream;
533 }506 }
534 const n = try reader.in.stream(w, limit.min(.limited(remaining)));507 const n = try reader.in.stream(w, limit.min(.limited64(remaining)));
535 remaining_content_length.* = remaining - n;508 remaining_content_length.* = remaining - n;
536 return n;509 return n;
537 }510 }
538511
539 fn contentLengthDiscard(io_r: *std.io.Reader, limit: std.io.Limit) std.io.Reader.Error!usize {512 fn contentLengthDiscard(io_r: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {
540 const reader: *Reader = @fieldParentPtr("interface", io_r);513 const reader: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
541 const remaining_content_length = &reader.state.body_remaining_content_length;514 const remaining_content_length = &reader.state.body_remaining_content_length;
542 const remaining = remaining_content_length.*;515 const remaining = remaining_content_length.*;
543 if (remaining == 0) {516 if (remaining == 0) {
544 reader.state = .ready;517 reader.state = .ready;
545 return error.EndOfStream;518 return error.EndOfStream;
546 }519 }
547 const n = try reader.in.discard(limit.min(.limited(remaining)));520 const n = try reader.in.discard(limit.min(.limited64(remaining)));
548 remaining_content_length.* = remaining - n;521 remaining_content_length.* = remaining - n;
549 return n;522 return n;
550 }523 }
551524
552 fn chunkedStream(io_r: *std.io.Reader, w: *Writer, limit: std.io.Limit) std.io.Reader.StreamError!usize {525 fn chunkedStream(io_r: *std.Io.Reader, w: *Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
553 const reader: *Reader = @fieldParentPtr("interface", io_r);526 const reader: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
554 const chunk_len_ptr = switch (reader.state) {527 const chunk_len_ptr = switch (reader.state) {
555 .ready => return error.EndOfStream,528 .ready => return error.EndOfStream,
556 .body_remaining_chunk_len => |*x| x,529 .body_remaining_chunk_len => |*x| x,
...@@ -573,9 +546,9 @@ pub const Reader = struct {...@@ -573,9 +546,9 @@ pub const Reader = struct {
573 fn chunkedReadEndless(546 fn chunkedReadEndless(
574 reader: *Reader,547 reader: *Reader,
575 w: *Writer,548 w: *Writer,
576 limit: std.io.Limit,549 limit: std.Io.Limit,
577 chunk_len_ptr: *RemainingChunkLen,550 chunk_len_ptr: *RemainingChunkLen,
578 ) (BodyError || std.io.Reader.StreamError)!usize {551 ) (BodyError || std.Io.Reader.StreamError)!usize {
579 const in = reader.in;552 const in = reader.in;
580 len: switch (chunk_len_ptr.*) {553 len: switch (chunk_len_ptr.*) {
581 .head => {554 .head => {
...@@ -596,7 +569,7 @@ pub const Reader = struct {...@@ -596,7 +569,7 @@ pub const Reader = struct {
596 }569 }
597 }570 }
598 if (cp.chunk_len == 0) return parseTrailers(reader, 0);571 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
599 const n = try in.stream(w, limit.min(.limited(cp.chunk_len)));572 const n = try in.stream(w, limit.min(.limited64(cp.chunk_len)));
600 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);573 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
601 return n;574 return n;
602 },575 },
...@@ -612,15 +585,15 @@ pub const Reader = struct {...@@ -612,15 +585,15 @@ pub const Reader = struct {
612 continue :len .head;585 continue :len .head;
613 },586 },
614 else => |remaining_chunk_len| {587 else => |remaining_chunk_len| {
615 const n = try in.stream(w, limit.min(.limited(@intFromEnum(remaining_chunk_len) - 2)));588 const n = try in.stream(w, limit.min(.limited64(@intFromEnum(remaining_chunk_len) - 2)));
616 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);589 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);
617 return n;590 return n;
618 },591 },
619 }592 }
620 }593 }
621594
622 fn chunkedDiscard(io_r: *std.io.Reader, limit: std.io.Limit) std.io.Reader.Error!usize {595 fn chunkedDiscard(io_r: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {
623 const reader: *Reader = @fieldParentPtr("interface", io_r);596 const reader: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
624 const chunk_len_ptr = switch (reader.state) {597 const chunk_len_ptr = switch (reader.state) {
625 .ready => return error.EndOfStream,598 .ready => return error.EndOfStream,
626 .body_remaining_chunk_len => |*x| x,599 .body_remaining_chunk_len => |*x| x,
...@@ -641,9 +614,9 @@ pub const Reader = struct {...@@ -641,9 +614,9 @@ pub const Reader = struct {
641614
642 fn chunkedDiscardEndless(615 fn chunkedDiscardEndless(
643 reader: *Reader,616 reader: *Reader,
644 limit: std.io.Limit,617 limit: std.Io.Limit,
645 chunk_len_ptr: *RemainingChunkLen,618 chunk_len_ptr: *RemainingChunkLen,
646 ) (BodyError || std.io.Reader.Error)!usize {619 ) (BodyError || std.Io.Reader.Error)!usize {
647 const in = reader.in;620 const in = reader.in;
648 len: switch (chunk_len_ptr.*) {621 len: switch (chunk_len_ptr.*) {
649 .head => {622 .head => {
...@@ -664,7 +637,7 @@ pub const Reader = struct {...@@ -664,7 +637,7 @@ pub const Reader = struct {
664 }637 }
665 }638 }
666 if (cp.chunk_len == 0) return parseTrailers(reader, 0);639 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
667 const n = try in.discard(limit.min(.limited(cp.chunk_len)));640 const n = try in.discard(limit.min(.limited64(cp.chunk_len)));
668 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);641 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
669 return n;642 return n;
670 },643 },
...@@ -680,7 +653,7 @@ pub const Reader = struct {...@@ -680,7 +653,7 @@ pub const Reader = struct {
680 continue :len .head;653 continue :len .head;
681 },654 },
682 else => |remaining_chunk_len| {655 else => |remaining_chunk_len| {
683 const n = try in.discard(limit.min(.limited(remaining_chunk_len.int() - 2)));656 const n = try in.discard(limit.min(.limited64(remaining_chunk_len.int() - 2)));
684 chunk_len_ptr.* = .init(remaining_chunk_len.int() - n);657 chunk_len_ptr.* = .init(remaining_chunk_len.int() - n);
685 return n;658 return n;
686 },659 },
...@@ -689,7 +662,7 @@ pub const Reader = struct {...@@ -689,7 +662,7 @@ pub const Reader = struct {
689662
690 /// Called when next bytes in the stream are trailers, or "\r\n" to indicate663 /// Called when next bytes in the stream are trailers, or "\r\n" to indicate
691 /// end of chunked body.664 /// end of chunked body.
692 fn parseTrailers(reader: *Reader, amt_read: usize) (BodyError || std.io.Reader.Error)!usize {665 fn parseTrailers(reader: *Reader, amt_read: usize) (BodyError || std.Io.Reader.Error)!usize {
693 const in = reader.in;666 const in = reader.in;
694 const rn = try in.peekArray(2);667 const rn = try in.peekArray(2);
695 if (rn[0] == '\r' and rn[1] == '\n') {668 if (rn[0] == '\r' and rn[1] == '\n') {
...@@ -721,21 +694,21 @@ pub const Reader = struct {...@@ -721,21 +694,21 @@ pub const Reader = struct {
721pub const Decompressor = union(enum) {694pub const Decompressor = union(enum) {
722 flate: std.compress.flate.Decompress,695 flate: std.compress.flate.Decompress,
723 zstd: std.compress.zstd.Decompress,696 zstd: std.compress.zstd.Decompress,
724 none: *std.io.Reader,697 none: *std.Io.Reader,
725698
726 pub fn init(699 pub fn init(
727 decompressor: *Decompressor,700 decompressor: *Decompressor,
728 transfer_reader: *std.io.Reader,701 transfer_reader: *std.Io.Reader,
729 buffer: []u8,702 buffer: []u8,
730 content_encoding: ContentEncoding,703 content_encoding: ContentEncoding,
731 ) *std.io.Reader {704 ) *std.Io.Reader {
732 switch (content_encoding) {705 switch (content_encoding) {
733 .identity => {706 .identity => {
734 decompressor.* = .{ .none = transfer_reader };707 decompressor.* = .{ .none = transfer_reader };
735 return transfer_reader;708 return transfer_reader;
736 },709 },
737 .deflate => {710 .deflate => {
738 decompressor.* = .{ .flate = .init(transfer_reader, .raw, buffer) };711 decompressor.* = .{ .flate = .init(transfer_reader, .zlib, buffer) };
739 return &decompressor.flate.reader;712 return &decompressor.flate.reader;
740 },713 },
741 .gzip => {714 .gzip => {
...@@ -763,7 +736,7 @@ pub const BodyWriter = struct {...@@ -763,7 +736,7 @@ pub const BodyWriter = struct {
763736
764 /// How many zeroes to reserve for hex-encoded chunk length.737 /// How many zeroes to reserve for hex-encoded chunk length.
765 const chunk_len_digits = 8;738 const chunk_len_digits = 8;
766 const max_chunk_len: usize = std.math.pow(usize, 16, chunk_len_digits) - 1;739 const max_chunk_len: usize = std.math.pow(u64, 16, chunk_len_digits) - 1;
767 const chunk_header_template = ("0" ** chunk_len_digits) ++ "\r\n";740 const chunk_header_template = ("0" ** chunk_len_digits) ++ "\r\n";
768741
769 comptime {742 comptime {
...@@ -795,7 +768,7 @@ pub const BodyWriter = struct {...@@ -795,7 +768,7 @@ pub const BodyWriter = struct {
795 };768 };
796769
797 pub fn isEliding(w: *const BodyWriter) bool {770 pub fn isEliding(w: *const BodyWriter) bool {
798 return w.writer.vtable.drain == Writer.discardingDrain;771 return w.writer.vtable.drain == elidingDrain;
799 }772 }
800773
801 /// Sends all buffered data across `BodyWriter.http_protocol_output`.774 /// Sends all buffered data across `BodyWriter.http_protocol_output`.
...@@ -923,7 +896,7 @@ pub const BodyWriter = struct {...@@ -923,7 +896,7 @@ pub const BodyWriter = struct {
923 }896 }
924897
925 pub fn contentLengthDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {898 pub fn contentLengthDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
926 const bw: *BodyWriter = @fieldParentPtr("writer", w);899 const bw: *BodyWriter = @alignCast(@fieldParentPtr("writer", w));
927 assert(!bw.isEliding());900 assert(!bw.isEliding());
928 const out = bw.http_protocol_output;901 const out = bw.http_protocol_output;
929 const n = try out.writeSplatHeader(w.buffered(), data, splat);902 const n = try out.writeSplatHeader(w.buffered(), data, splat);
...@@ -932,24 +905,64 @@ pub const BodyWriter = struct {...@@ -932,24 +905,64 @@ pub const BodyWriter = struct {
932 }905 }
933906
934 pub fn noneDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {907 pub fn noneDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
935 const bw: *BodyWriter = @fieldParentPtr("writer", w);908 const bw: *BodyWriter = @alignCast(@fieldParentPtr("writer", w));
936 assert(!bw.isEliding());909 assert(!bw.isEliding());
937 const out = bw.http_protocol_output;910 const out = bw.http_protocol_output;
938 const n = try out.writeSplatHeader(w.buffered(), data, splat);911 const n = try out.writeSplatHeader(w.buffered(), data, splat);
939 return w.consume(n);912 return w.consume(n);
940 }913 }
941914
915 pub fn elidingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
916 const bw: *BodyWriter = @alignCast(@fieldParentPtr("writer", w));
917 const slice = data[0 .. data.len - 1];
918 const pattern = data[slice.len];
919 var written: usize = pattern.len * splat;
920 for (slice) |bytes| written += bytes.len;
921 switch (bw.state) {
922 .content_length => |*len| len.* -= written + w.end,
923 else => {},
924 }
925 w.end = 0;
926 return written;
927 }
928
929 pub fn elidingSendFile(w: *Writer, file_reader: *File.Reader, limit: std.Io.Limit) Writer.FileError!usize {
930 const bw: *BodyWriter = @alignCast(@fieldParentPtr("writer", w));
931 if (File.Handle == void) return error.Unimplemented;
932 if (builtin.zig_backend == .stage2_aarch64) return error.Unimplemented;
933 switch (bw.state) {
934 .content_length => |*len| len.* -= w.end,
935 else => {},
936 }
937 w.end = 0;
938 if (limit == .nothing) return 0;
939 if (file_reader.getSize()) |size| {
940 const n = limit.minInt64(size - file_reader.pos);
941 if (n == 0) return error.EndOfStream;
942 file_reader.seekBy(@intCast(n)) catch return error.Unimplemented;
943 switch (bw.state) {
944 .content_length => |*len| len.* -= n,
945 else => {},
946 }
947 return n;
948 } else |_| {
949 // Error is observable on `file_reader` instance, and it is better to
950 // treat the file as a pipe.
951 return error.Unimplemented;
952 }
953 }
954
942 /// Returns `null` if size cannot be computed without making any syscalls.955 /// Returns `null` if size cannot be computed without making any syscalls.
943 pub fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {956 pub fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.Io.Limit) Writer.FileError!usize {
944 const bw: *BodyWriter = @fieldParentPtr("writer", w);957 const bw: *BodyWriter = @alignCast(@fieldParentPtr("writer", w));
945 assert(!bw.isEliding());958 assert(!bw.isEliding());
946 const out = bw.http_protocol_output;959 const out = bw.http_protocol_output;
947 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);960 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);
948 return w.consume(n);961 return w.consume(n);
949 }962 }
950963
951 pub fn contentLengthSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {964 pub fn contentLengthSendFile(w: *Writer, file_reader: *File.Reader, limit: std.Io.Limit) Writer.FileError!usize {
952 const bw: *BodyWriter = @fieldParentPtr("writer", w);965 const bw: *BodyWriter = @alignCast(@fieldParentPtr("writer", w));
953 assert(!bw.isEliding());966 assert(!bw.isEliding());
954 const out = bw.http_protocol_output;967 const out = bw.http_protocol_output;
955 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);968 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);
...@@ -957,8 +970,8 @@ pub const BodyWriter = struct {...@@ -957,8 +970,8 @@ pub const BodyWriter = struct {
957 return w.consume(n);970 return w.consume(n);
958 }971 }
959972
960 pub fn chunkedSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {973 pub fn chunkedSendFile(w: *Writer, file_reader: *File.Reader, limit: std.Io.Limit) Writer.FileError!usize {
961 const bw: *BodyWriter = @fieldParentPtr("writer", w);974 const bw: *BodyWriter = @alignCast(@fieldParentPtr("writer", w));
962 assert(!bw.isEliding());975 assert(!bw.isEliding());
963 const data_len = Writer.countSendFileLowerBound(w.end, file_reader, limit) orelse {976 const data_len = Writer.countSendFileLowerBound(w.end, file_reader, limit) orelse {
964 // If the file size is unknown, we cannot lower to a `sendFile` since we would977 // If the file size is unknown, we cannot lower to a `sendFile` since we would
...@@ -1006,7 +1019,7 @@ pub const BodyWriter = struct {...@@ -1006,7 +1019,7 @@ pub const BodyWriter = struct {
1006 }1019 }
10071020
1008 pub fn chunkedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {1021 pub fn chunkedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
1009 const bw: *BodyWriter = @fieldParentPtr("writer", w);1022 const bw: *BodyWriter = @alignCast(@fieldParentPtr("writer", w));
1010 assert(!bw.isEliding());1023 assert(!bw.isEliding());
1011 const out = bw.http_protocol_output;1024 const out = bw.http_protocol_output;
1012 const data_len = w.end + Writer.countSplat(data, splat);1025 const data_len = w.end + Writer.countSplat(data, splat);
lib/std/http/Client.zig+86-44
...@@ -42,7 +42,7 @@ connection_pool: ConnectionPool = .{},...@@ -42,7 +42,7 @@ connection_pool: ConnectionPool = .{},
42///42///
43/// If the entire HTTP header cannot fit in this amount of bytes,43/// If the entire HTTP header cannot fit in this amount of bytes,
44/// `error.HttpHeadersOversize` will be returned from `Request.wait`.44/// `error.HttpHeadersOversize` will be returned from `Request.wait`.
45read_buffer_size: usize = 4096,45read_buffer_size: usize = 4096 + if (disable_tls) 0 else std.crypto.tls.Client.min_buffer_len,
46/// Each `Connection` allocates this amount for the writer buffer.46/// Each `Connection` allocates this amount for the writer buffer.
47write_buffer_size: usize = 1024,47write_buffer_size: usize = 1024,
4848
...@@ -82,7 +82,7 @@ pub const ConnectionPool = struct {...@@ -82,7 +82,7 @@ pub const ConnectionPool = struct {
8282
83 var next = pool.free.last;83 var next = pool.free.last;
84 while (next) |node| : (next = node.prev) {84 while (next) |node| : (next = node.prev) {
85 const connection: *Connection = @fieldParentPtr("pool_node", node);85 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
86 if (connection.protocol != criteria.protocol) continue;86 if (connection.protocol != criteria.protocol) continue;
87 if (connection.port != criteria.port) continue;87 if (connection.port != criteria.port) continue;
8888
...@@ -115,8 +115,6 @@ pub const ConnectionPool = struct {...@@ -115,8 +115,6 @@ pub const ConnectionPool = struct {
115 /// Tries to release a connection back to the connection pool.115 /// Tries to release a connection back to the connection pool.
116 /// If the connection is marked as closing, it will be closed instead.116 /// If the connection is marked as closing, it will be closed instead.
117 ///117 ///
118 /// `allocator` must be the same one used to create `connection`.
119 ///
120 /// Threadsafe.118 /// Threadsafe.
121 pub fn release(pool: *ConnectionPool, connection: *Connection) void {119 pub fn release(pool: *ConnectionPool, connection: *Connection) void {
122 pool.mutex.lock();120 pool.mutex.lock();
...@@ -127,7 +125,7 @@ pub const ConnectionPool = struct {...@@ -127,7 +125,7 @@ pub const ConnectionPool = struct {
127 if (connection.closing or pool.free_size == 0) return connection.destroy();125 if (connection.closing or pool.free_size == 0) return connection.destroy();
128126
129 if (pool.free_len >= pool.free_size) {127 if (pool.free_len >= pool.free_size) {
130 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);128 const popped: *Connection = @alignCast(@fieldParentPtr("pool_node", pool.free.popFirst().?));
131 pool.free_len -= 1;129 pool.free_len -= 1;
132130
133 popped.destroy();131 popped.destroy();
...@@ -183,14 +181,14 @@ pub const ConnectionPool = struct {...@@ -183,14 +181,14 @@ pub const ConnectionPool = struct {
183181
184 var next = pool.free.first;182 var next = pool.free.first;
185 while (next) |node| {183 while (next) |node| {
186 const connection: *Connection = @fieldParentPtr("pool_node", node);184 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
187 next = node.next;185 next = node.next;
188 connection.destroy();186 connection.destroy();
189 }187 }
190188
191 next = pool.used.first;189 next = pool.used.first;
192 while (next) |node| {190 while (next) |node| {
193 const connection: *Connection = @fieldParentPtr("pool_node", node);191 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
194 next = node.next;192 next = node.next;
195 connection.destroy();193 connection.destroy();
196 }194 }
...@@ -306,15 +304,16 @@ pub const Connection = struct {...@@ -306,15 +304,16 @@ pub const Connection = struct {
306 const host_buffer = base[@sizeOf(Tls)..][0..remote_host.len];304 const host_buffer = base[@sizeOf(Tls)..][0..remote_host.len];
307 const tls_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.tls_buffer_size];305 const tls_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.tls_buffer_size];
308 const tls_write_buffer = tls_read_buffer.ptr[tls_read_buffer.len..][0..client.tls_buffer_size];306 const tls_write_buffer = tls_read_buffer.ptr[tls_read_buffer.len..][0..client.tls_buffer_size];
309 const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];307 const write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];
310 assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len);308 const read_buffer = write_buffer.ptr[write_buffer.len..][0..client.read_buffer_size];
309 assert(base.ptr + alloc_len == read_buffer.ptr + read_buffer.len);
311 @memcpy(host_buffer, remote_host);310 @memcpy(host_buffer, remote_host);
312 const tls: *Tls = @ptrCast(base);311 const tls: *Tls = @ptrCast(base);
313 tls.* = .{312 tls.* = .{
314 .connection = .{313 .connection = .{
315 .client = client,314 .client = client,
316 .stream_writer = stream.writer(socket_write_buffer),315 .stream_writer = stream.writer(tls_write_buffer),
317 .stream_reader = stream.reader(&.{}),316 .stream_reader = stream.reader(tls_read_buffer),
318 .pool_node = .{},317 .pool_node = .{},
319 .port = port,318 .port = port,
320 .host_len = @intCast(remote_host.len),319 .host_len = @intCast(remote_host.len),
...@@ -330,8 +329,8 @@ pub const Connection = struct {...@@ -330,8 +329,8 @@ pub const Connection = struct {
330 .host = .{ .explicit = remote_host },329 .host = .{ .explicit = remote_host },
331 .ca = .{ .bundle = client.ca_bundle },330 .ca = .{ .bundle = client.ca_bundle },
332 .ssl_key_log = client.ssl_key_log,331 .ssl_key_log = client.ssl_key_log,
333 .read_buffer = tls_read_buffer,332 .read_buffer = read_buffer,
334 .write_buffer = tls_write_buffer,333 .write_buffer = write_buffer,
335 // This is appropriate for HTTPS because the HTTP headers contain334 // This is appropriate for HTTPS because the HTTP headers contain
336 // the content length which is used to detect truncation attacks.335 // the content length which is used to detect truncation attacks.
337 .allow_truncation_attacks = true,336 .allow_truncation_attacks = true,
...@@ -349,7 +348,8 @@ pub const Connection = struct {...@@ -349,7 +348,8 @@ pub const Connection = struct {
349 }348 }
350349
351 fn allocLen(client: *Client, host_len: usize) usize {350 fn allocLen(client: *Client, host_len: usize) usize {
352 return @sizeOf(Tls) + host_len + client.tls_buffer_size + client.tls_buffer_size + client.write_buffer_size;351 return @sizeOf(Tls) + host_len + client.tls_buffer_size + client.tls_buffer_size +
352 client.write_buffer_size + client.read_buffer_size;
353 }353 }
354354
355 fn host(tls: *Tls) []u8 {355 fn host(tls: *Tls) []u8 {
...@@ -358,6 +358,21 @@ pub const Connection = struct {...@@ -358,6 +358,21 @@ pub const Connection = struct {
358 }358 }
359 };359 };
360360
361 pub const ReadError = std.crypto.tls.Client.ReadError || std.net.Stream.ReadError;
362
363 pub fn getReadError(c: *const Connection) ?ReadError {
364 return switch (c.protocol) {
365 .tls => {
366 if (disable_tls) unreachable;
367 const tls: *const Tls = @alignCast(@fieldParentPtr("connection", c));
368 return tls.client.read_err orelse c.stream_reader.getError();
369 },
370 .plain => {
371 return c.stream_reader.getError();
372 },
373 };
374 }
375
361 fn getStream(c: *Connection) net.Stream {376 fn getStream(c: *Connection) net.Stream {
362 return c.stream_reader.getStream();377 return c.stream_reader.getStream();
363 }378 }
...@@ -366,11 +381,11 @@ pub const Connection = struct {...@@ -366,11 +381,11 @@ pub const Connection = struct {
366 return switch (c.protocol) {381 return switch (c.protocol) {
367 .tls => {382 .tls => {
368 if (disable_tls) unreachable;383 if (disable_tls) unreachable;
369 const tls: *Tls = @fieldParentPtr("connection", c);384 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
370 return tls.host();385 return tls.host();
371 },386 },
372 .plain => {387 .plain => {
373 const plain: *Plain = @fieldParentPtr("connection", c);388 const plain: *Plain = @alignCast(@fieldParentPtr("connection", c));
374 return plain.host();389 return plain.host();
375 },390 },
376 };391 };
...@@ -383,11 +398,11 @@ pub const Connection = struct {...@@ -383,11 +398,11 @@ pub const Connection = struct {
383 switch (c.protocol) {398 switch (c.protocol) {
384 .tls => {399 .tls => {
385 if (disable_tls) unreachable;400 if (disable_tls) unreachable;
386 const tls: *Tls = @fieldParentPtr("connection", c);401 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
387 tls.destroy();402 tls.destroy();
388 },403 },
389 .plain => {404 .plain => {
390 const plain: *Plain = @fieldParentPtr("connection", c);405 const plain: *Plain = @alignCast(@fieldParentPtr("connection", c));
391 plain.destroy();406 plain.destroy();
392 },407 },
393 }408 }
...@@ -399,7 +414,7 @@ pub const Connection = struct {...@@ -399,7 +414,7 @@ pub const Connection = struct {
399 return switch (c.protocol) {414 return switch (c.protocol) {
400 .tls => {415 .tls => {
401 if (disable_tls) unreachable;416 if (disable_tls) unreachable;
402 const tls: *Tls = @fieldParentPtr("connection", c);417 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
403 return &tls.client.writer;418 return &tls.client.writer;
404 },419 },
405 .plain => &c.stream_writer.interface,420 .plain => &c.stream_writer.interface,
...@@ -412,7 +427,7 @@ pub const Connection = struct {...@@ -412,7 +427,7 @@ pub const Connection = struct {
412 return switch (c.protocol) {427 return switch (c.protocol) {
413 .tls => {428 .tls => {
414 if (disable_tls) unreachable;429 if (disable_tls) unreachable;
415 const tls: *Tls = @fieldParentPtr("connection", c);430 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
416 return &tls.client.reader;431 return &tls.client.reader;
417 },432 },
418 .plain => c.stream_reader.interface(),433 .plain => c.stream_reader.interface(),
...@@ -422,7 +437,7 @@ pub const Connection = struct {...@@ -422,7 +437,7 @@ pub const Connection = struct {
422 pub fn flush(c: *Connection) Writer.Error!void {437 pub fn flush(c: *Connection) Writer.Error!void {
423 if (c.protocol == .tls) {438 if (c.protocol == .tls) {
424 if (disable_tls) unreachable;439 if (disable_tls) unreachable;
425 const tls: *Tls = @fieldParentPtr("connection", c);440 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
426 try tls.client.writer.flush();441 try tls.client.writer.flush();
427 }442 }
428 try c.stream_writer.interface.flush();443 try c.stream_writer.interface.flush();
...@@ -434,9 +449,8 @@ pub const Connection = struct {...@@ -434,9 +449,8 @@ pub const Connection = struct {
434 pub fn end(c: *Connection) Writer.Error!void {449 pub fn end(c: *Connection) Writer.Error!void {
435 if (c.protocol == .tls) {450 if (c.protocol == .tls) {
436 if (disable_tls) unreachable;451 if (disable_tls) unreachable;
437 const tls: *Tls = @fieldParentPtr("connection", c);452 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
438 try tls.client.end();453 try tls.client.end();
439 try tls.client.writer.flush();
440 }454 }
441 try c.stream_writer.interface.flush();455 try c.stream_writer.interface.flush();
442 }456 }
...@@ -444,8 +458,8 @@ pub const Connection = struct {...@@ -444,8 +458,8 @@ pub const Connection = struct {
444458
445pub const Response = struct {459pub const Response = struct {
446 request: *Request,460 request: *Request,
447 /// Pointers in this struct are invalidated with the next call to461 /// Pointers in this struct are invalidated when the response body stream
448 /// `receiveHead`.462 /// is initialized.
449 head: Head,463 head: Head,
450464
451 pub const Head = struct {465 pub const Head = struct {
...@@ -484,10 +498,8 @@ pub const Response = struct {...@@ -484,10 +498,8 @@ pub const Response = struct {
484 };498 };
485 var it = mem.splitSequence(u8, bytes, "\r\n");499 var it = mem.splitSequence(u8, bytes, "\r\n");
486500
487 const first_line = it.next().?;501 const first_line = it.first();
488 if (first_line.len < 12) {502 if (first_line.len < 12) return error.HttpHeadersInvalid;
489 return error.HttpHeadersInvalid;
490 }
491503
492 const version: http.Version = switch (int64(first_line[0..8])) {504 const version: http.Version = switch (int64(first_line[0..8])) {
493 int64("HTTP/1.0") => .@"HTTP/1.0",505 int64("HTTP/1.0") => .@"HTTP/1.0",
...@@ -671,6 +683,16 @@ pub const Response = struct {...@@ -671,6 +683,16 @@ pub const Response = struct {
671 try expectEqual(@as(u10, 418), parseInt3("418"));683 try expectEqual(@as(u10, 418), parseInt3("418"));
672 try expectEqual(@as(u10, 999), parseInt3("999"));684 try expectEqual(@as(u10, 999), parseInt3("999"));
673 }685 }
686
687 /// Help the programmer avoid bugs by calling this when the string
688 /// memory of `Head` becomes invalidated.
689 fn invalidateStrings(h: *Head) void {
690 h.bytes = undefined;
691 h.reason = undefined;
692 if (h.location) |*s| s.* = undefined;
693 if (h.content_type) |*s| s.* = undefined;
694 if (h.content_disposition) |*s| s.* = undefined;
695 }
674 };696 };
675697
676 /// If compressed body has been negotiated this will return compressed bytes.698 /// If compressed body has been negotiated this will return compressed bytes.
...@@ -683,6 +705,7 @@ pub const Response = struct {...@@ -683,6 +705,7 @@ pub const Response = struct {
683 /// See also:705 /// See also:
684 /// * `readerDecompressing`706 /// * `readerDecompressing`
685 pub fn reader(response: *Response, buffer: []u8) *Reader {707 pub fn reader(response: *Response, buffer: []u8) *Reader {
708 response.head.invalidateStrings();
686 const req = response.request;709 const req = response.request;
687 if (!req.method.responseHasBody()) return .ending;710 if (!req.method.responseHasBody()) return .ending;
688 const head = &response.head;711 const head = &response.head;
...@@ -703,6 +726,7 @@ pub const Response = struct {...@@ -703,6 +726,7 @@ pub const Response = struct {
703 decompressor: *http.Decompressor,726 decompressor: *http.Decompressor,
704 decompression_buffer: []u8,727 decompression_buffer: []u8,
705 ) *Reader {728 ) *Reader {
729 response.head.invalidateStrings();
706 const head = &response.head;730 const head = &response.head;
707 return response.request.reader.bodyReaderDecompressing(731 return response.request.reader.bodyReaderDecompressing(
708 head.transfer_encoding,732 head.transfer_encoding,
...@@ -805,6 +829,11 @@ pub const Request = struct {...@@ -805,6 +829,11 @@ pub const Request = struct {
805 unhandled = std.math.maxInt(u16),829 unhandled = std.math.maxInt(u16),
806 _,830 _,
807831
832 pub fn init(n: u16) RedirectBehavior {
833 assert(n != std.math.maxInt(u16));
834 return @enumFromInt(n);
835 }
836
808 pub fn subtractOne(rb: *RedirectBehavior) void {837 pub fn subtractOne(rb: *RedirectBehavior) void {
809 switch (rb.*) {838 switch (rb.*) {
810 .not_allowed => unreachable,839 .not_allowed => unreachable,
...@@ -821,7 +850,6 @@ pub const Request = struct {...@@ -821,7 +850,6 @@ pub const Request = struct {
821850
822 /// Returns the request's `Connection` back to the pool of the `Client`.851 /// Returns the request's `Connection` back to the pool of the `Client`.
823 pub fn deinit(r: *Request) void {852 pub fn deinit(r: *Request) void {
824 r.reader.restituteHeadBuffer();
825 if (r.connection) |connection| {853 if (r.connection) |connection| {
826 connection.closing = connection.closing or switch (r.reader.state) {854 connection.closing = connection.closing or switch (r.reader.state) {
827 .ready => false,855 .ready => false,
...@@ -856,6 +884,15 @@ pub const Request = struct {...@@ -856,6 +884,15 @@ pub const Request = struct {
856 return result;884 return result;
857 }885 }
858886
887 /// Transfers the HTTP head and body over the connection and flushes.
888 pub fn sendBodyComplete(r: *Request, body: []u8) Writer.Error!void {
889 r.transfer_encoding = .{ .content_length = body.len };
890 var bw = try sendBodyUnflushed(r, body);
891 bw.writer.end = body.len;
892 try bw.end();
893 try r.connection.?.flush();
894 }
895
859 /// Transfers the HTTP head over the connection, which is not flushed until896 /// Transfers the HTTP head over the connection, which is not flushed until
860 /// `BodyWriter.flush` or `BodyWriter.end` is called.897 /// `BodyWriter.flush` or `BodyWriter.end` is called.
861 ///898 ///
...@@ -908,13 +945,13 @@ pub const Request = struct {...@@ -908,13 +945,13 @@ pub const Request = struct {
908 const connection = r.connection.?;945 const connection = r.connection.?;
909 const w = connection.writer();946 const w = connection.writer();
910947
911 try r.method.write(w);948 try w.writeAll(@tagName(r.method));
912 try w.writeByte(' ');949 try w.writeByte(' ');
913950
914 if (r.method == .CONNECT) {951 if (r.method == .CONNECT) {
915 try uri.writeToStream(.{ .authority = true }, w);952 try uri.writeToStream(w, .{ .authority = true });
916 } else {953 } else {
917 try uri.writeToStream(.{954 try uri.writeToStream(w, .{
918 .scheme = connection.proxied,955 .scheme = connection.proxied,
919 .authentication = connection.proxied,956 .authentication = connection.proxied,
920 .authority = connection.proxied,957 .authority = connection.proxied,
...@@ -928,7 +965,7 @@ pub const Request = struct {...@@ -928,7 +965,7 @@ pub const Request = struct {
928965
929 if (try emitOverridableHeader("host: ", r.headers.host, w)) {966 if (try emitOverridableHeader("host: ", r.headers.host, w)) {
930 try w.writeAll("host: ");967 try w.writeAll("host: ");
931 try uri.writeToStream(.{ .authority = true }, w);968 try uri.writeToStream(w, .{ .authority = true });
932 try w.writeAll("\r\n");969 try w.writeAll("\r\n");
933 }970 }
934971
...@@ -1043,13 +1080,16 @@ pub const Request = struct {...@@ -1043,13 +1080,16 @@ pub const Request = struct {
1043 /// buffer capacity would be exceeded, `error.HttpRedirectLocationOversize`1080 /// buffer capacity would be exceeded, `error.HttpRedirectLocationOversize`
1044 /// is returned instead. This buffer may be empty if no redirects are to be1081 /// is returned instead. This buffer may be empty if no redirects are to be
1045 /// handled.1082 /// handled.
1083 ///
1084 /// If this fails with `error.ReadFailed` then the `Connection.getReadError`
1085 /// method of `r.connection` can be used to get more detailed information.
1046 pub fn receiveHead(r: *Request, redirect_buffer: []u8) ReceiveHeadError!Response {1086 pub fn receiveHead(r: *Request, redirect_buffer: []u8) ReceiveHeadError!Response {
1047 var aux_buf = redirect_buffer;1087 var aux_buf = redirect_buffer;
1048 while (true) {1088 while (true) {
1049 try r.reader.receiveHead();1089 const head_buffer = try r.reader.receiveHead();
1050 const response: Response = .{1090 const response: Response = .{
1051 .request = r,1091 .request = r,
1052 .head = Response.Head.parse(r.reader.head_buffer) catch return error.HttpHeadersInvalid,1092 .head = Response.Head.parse(head_buffer) catch return error.HttpHeadersInvalid,
1053 };1093 };
1054 const head = &response.head;1094 const head = &response.head;
10551095
...@@ -1121,7 +1161,6 @@ pub const Request = struct {...@@ -1121,7 +1161,6 @@ pub const Request = struct {
1121 _ = reader.discardRemaining() catch |err| switch (err) {1161 _ = reader.discardRemaining() catch |err| switch (err) {
1122 error.ReadFailed => return r.reader.body_err.?,1162 error.ReadFailed => return r.reader.body_err.?,
1123 };1163 };
1124 r.reader.restituteHeadBuffer();
1125 }1164 }
1126 const new_uri = r.uri.resolveInPlace(location.len, aux_buf) catch |err| switch (err) {1165 const new_uri = r.uri.resolveInPlace(location.len, aux_buf) catch |err| switch (err) {
1127 error.UnexpectedCharacter => return error.HttpRedirectLocationInvalid,1166 error.UnexpectedCharacter => return error.HttpRedirectLocationInvalid,
...@@ -1298,16 +1337,17 @@ pub const basic_authorization = struct {...@@ -1298,16 +1337,17 @@ pub const basic_authorization = struct {
1298 pub fn value(uri: Uri, out: []u8) []u8 {1337 pub fn value(uri: Uri, out: []u8) []u8 {
1299 var bw: Writer = .fixed(out);1338 var bw: Writer = .fixed(out);
1300 write(uri, &bw) catch unreachable;1339 write(uri, &bw) catch unreachable;
1301 return bw.getWritten();1340 return bw.buffered();
1302 }1341 }
13031342
1304 pub fn write(uri: Uri, out: *Writer) Writer.Error!void {1343 pub fn write(uri: Uri, out: *Writer) Writer.Error!void {
1305 var buf: [max_user_len + ":".len + max_password_len]u8 = undefined;1344 var buf: [max_user_len + 1 + max_password_len]u8 = undefined;
1306 var w: Writer = .fixed(&buf);1345 var w: Writer = .fixed(&buf);
1307 w.print("{fuser}:{fpassword}", .{1346 const user: Uri.Component = uri.user orelse .empty;
1308 uri.user orelse Uri.Component.empty,1347 const password: Uri.Component = uri.user orelse .empty;
1309 uri.password orelse Uri.Component.empty,1348 user.formatUser(&w) catch unreachable;
1310 }) catch unreachable;1349 w.writeByte(':') catch unreachable;
1350 password.formatPassword(&w) catch unreachable;
1311 try out.print("Basic {b64}", .{w.buffered()});1351 try out.print("Basic {b64}", .{w.buffered()});
1312 }1352 }
1313};1353};
...@@ -1697,6 +1737,7 @@ pub const FetchError = Uri.ParseError || RequestError || Request.ReceiveHeadErro...@@ -1697,6 +1737,7 @@ pub const FetchError = Uri.ParseError || RequestError || Request.ReceiveHeadErro
1697 StreamTooLong,1737 StreamTooLong,
1698 /// TODO provide optional diagnostics when this occurs or break into more error codes1738 /// TODO provide optional diagnostics when this occurs or break into more error codes
1699 WriteFailed,1739 WriteFailed,
1740 UnsupportedCompressionMethod,
1700};1741};
17011742
1702/// Perform a one-shot HTTP request with the provided options.1743/// Perform a one-shot HTTP request with the provided options.
...@@ -1748,7 +1789,8 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {...@@ -1748,7 +1789,8 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
1748 const decompress_buffer: []u8 = switch (response.head.content_encoding) {1789 const decompress_buffer: []u8 = switch (response.head.content_encoding) {
1749 .identity => &.{},1790 .identity => &.{},
1750 .zstd => options.decompress_buffer orelse try client.allocator.alloc(u8, std.compress.zstd.default_window_len),1791 .zstd => options.decompress_buffer orelse try client.allocator.alloc(u8, std.compress.zstd.default_window_len),
1751 else => options.decompress_buffer orelse try client.allocator.alloc(u8, 8 * 1024),1792 .deflate, .gzip => options.decompress_buffer orelse try client.allocator.alloc(u8, std.compress.flate.max_window_len),
1793 .compress => return error.UnsupportedCompressionMethod,
1752 };1794 };
1753 defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer);1795 defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer);
17541796
lib/std/http/Server.zig+64-47
...@@ -6,7 +6,8 @@ const mem = std.mem;...@@ -6,7 +6,8 @@ const mem = std.mem;
6const Uri = std.Uri;6const Uri = std.Uri;
7const assert = std.debug.assert;7const assert = std.debug.assert;
8const testing = std.testing;8const testing = std.testing;
9const Writer = std.io.Writer;9const Writer = std.Io.Writer;
10const Reader = std.Io.Reader;
1011
11const Server = @This();12const Server = @This();
1213
...@@ -21,7 +22,7 @@ reader: http.Reader,...@@ -21,7 +22,7 @@ reader: http.Reader,
21/// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`.22/// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`.
22///23///
23/// The returned `Server` is ready for `receiveHead` to be called.24/// The returned `Server` is ready for `receiveHead` to be called.
24pub fn init(in: *std.io.Reader, out: *Writer) Server {25pub fn init(in: *Reader, out: *Writer) Server {
25 return .{26 return .{
26 .reader = .{27 .reader = .{
27 .in = in,28 .in = in,
...@@ -33,33 +34,31 @@ pub fn init(in: *std.io.Reader, out: *Writer) Server {...@@ -33,33 +34,31 @@ pub fn init(in: *std.io.Reader, out: *Writer) Server {
33 };34 };
34}35}
3536
36pub fn deinit(s: *Server) void {
37 s.reader.restituteHeadBuffer();
38}
39
40pub const ReceiveHeadError = http.Reader.HeadError || error{37pub const ReceiveHeadError = http.Reader.HeadError || error{
41 /// Client sent headers that did not conform to the HTTP protocol.38 /// Client sent headers that did not conform to the HTTP protocol.
42 ///39 ///
43 /// To find out more detailed diagnostics, `http.Reader.head_buffer` can be40 /// To find out more detailed diagnostics, `Request.head_buffer` can be
44 /// passed directly to `Request.Head.parse`.41 /// passed directly to `Request.Head.parse`.
45 HttpHeadersInvalid,42 HttpHeadersInvalid,
46};43};
4744
48pub fn receiveHead(s: *Server) ReceiveHeadError!Request {45pub fn receiveHead(s: *Server) ReceiveHeadError!Request {
49 try s.reader.receiveHead();46 const head_buffer = try s.reader.receiveHead();
50 return .{47 return .{
51 .server = s,48 .server = s,
49 .head_buffer = head_buffer,
52 // No need to track the returned error here since users can repeat the50 // No need to track the returned error here since users can repeat the
53 // parse with the header buffer to get detailed diagnostics.51 // parse with the header buffer to get detailed diagnostics.
54 .head = Request.Head.parse(s.reader.head_buffer) catch return error.HttpHeadersInvalid,52 .head = Request.Head.parse(head_buffer) catch return error.HttpHeadersInvalid,
55 };53 };
56}54}
5755
58pub const Request = struct {56pub const Request = struct {
59 server: *Server,57 server: *Server,
60 /// Pointers in this struct are invalidated with the next call to58 /// Pointers in this struct are invalidated when the request body stream is
61 /// `receiveHead`.59 /// initialized.
62 head: Head,60 head: Head,
61 head_buffer: []const u8,
63 respond_err: ?RespondError = null,62 respond_err: ?RespondError = null,
6463
65 pub const RespondError = error{64 pub const RespondError = error{
...@@ -98,10 +97,9 @@ pub const Request = struct {...@@ -98,10 +97,9 @@ pub const Request = struct {
9897
99 const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse98 const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse
100 return error.HttpHeadersInvalid;99 return error.HttpHeadersInvalid;
101 if (method_end > 24) return error.HttpHeadersInvalid;
102100
103 const method_str = first_line[0..method_end];101 const method = std.meta.stringToEnum(http.Method, first_line[0..method_end]) orelse
104 const method: http.Method = @enumFromInt(http.Method.parse(method_str));102 return error.UnknownHttpMethod;
105103
106 const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse104 const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse
107 return error.HttpHeadersInvalid;105 return error.HttpHeadersInvalid;
...@@ -225,11 +223,19 @@ pub const Request = struct {...@@ -225,11 +223,19 @@ pub const Request = struct {
225 inline fn int64(array: *const [8]u8) u64 {223 inline fn int64(array: *const [8]u8) u64 {
226 return @bitCast(array.*);224 return @bitCast(array.*);
227 }225 }
226
227 /// Help the programmer avoid bugs by calling this when the string
228 /// memory of `Head` becomes invalidated.
229 fn invalidateStrings(h: *Head) void {
230 h.target = undefined;
231 if (h.expect) |*s| s.* = undefined;
232 if (h.content_type) |*s| s.* = undefined;
233 }
228 };234 };
229235
230 pub fn iterateHeaders(r: *Request) http.HeaderIterator {236 pub fn iterateHeaders(r: *const Request) http.HeaderIterator {
231 assert(r.server.reader.state == .received_head);237 assert(r.server.reader.state == .received_head);
232 return http.HeaderIterator.init(r.server.reader.head_buffer);238 return http.HeaderIterator.init(r.head_buffer);
233 }239 }
234240
235 test iterateHeaders {241 test iterateHeaders {
...@@ -244,7 +250,6 @@ pub const Request = struct {...@@ -244,7 +250,6 @@ pub const Request = struct {
244 .reader = .{250 .reader = .{
245 .in = undefined,251 .in = undefined,
246 .state = .received_head,252 .state = .received_head,
247 .head_buffer = @constCast(request_bytes),
248 .interface = undefined,253 .interface = undefined,
249 },254 },
250 .out = undefined,255 .out = undefined,
...@@ -253,6 +258,7 @@ pub const Request = struct {...@@ -253,6 +258,7 @@ pub const Request = struct {
253 var request: Request = .{258 var request: Request = .{
254 .server = &server,259 .server = &server,
255 .head = undefined,260 .head = undefined,
261 .head_buffer = @constCast(request_bytes),
256 };262 };
257263
258 var it = request.iterateHeaders();264 var it = request.iterateHeaders();
...@@ -435,10 +441,8 @@ pub const Request = struct {...@@ -435,10 +441,8 @@ pub const Request = struct {
435441
436 for (o.extra_headers) |header| {442 for (o.extra_headers) |header| {
437 assert(header.name.len != 0);443 assert(header.name.len != 0);
438 try out.writeAll(header.name);444 var bufs: [4][]const u8 = .{ header.name, ": ", header.value, "\r\n" };
439 try out.writeAll(": ");445 try out.writeVecAll(&bufs);
440 try out.writeAll(header.value);
441 try out.writeAll("\r\n");
442 }446 }
443447
444 try out.writeAll("\r\n");448 try out.writeAll("\r\n");
...@@ -453,7 +457,13 @@ pub const Request = struct {...@@ -453,7 +457,13 @@ pub const Request = struct {
453 return if (elide_body) .{457 return if (elide_body) .{
454 .http_protocol_output = request.server.out,458 .http_protocol_output = request.server.out,
455 .state = state,459 .state = state,
456 .writer = .discarding(buffer),460 .writer = .{
461 .buffer = buffer,
462 .vtable = &.{
463 .drain = http.BodyWriter.elidingDrain,
464 .sendFile = http.BodyWriter.elidingSendFile,
465 },
466 },
457 } else .{467 } else .{
458 .http_protocol_output = request.server.out,468 .http_protocol_output = request.server.out,
459 .state = state,469 .state = state,
...@@ -484,10 +494,11 @@ pub const Request = struct {...@@ -484,10 +494,11 @@ pub const Request = struct {
484 none,494 none,
485 };495 };
486496
497 /// Does not invalidate `request.head`.
487 pub fn upgradeRequested(request: *const Request) UpgradeRequest {498 pub fn upgradeRequested(request: *const Request) UpgradeRequest {
488 switch (request.head.version) {499 switch (request.head.version) {
489 .@"HTTP/1.0" => return null,500 .@"HTTP/1.0" => return .none,
490 .@"HTTP/1.1" => if (request.head.method != .GET) return null,501 .@"HTTP/1.1" => if (request.head.method != .GET) return .none,
491 }502 }
492503
493 var sec_websocket_key: ?[]const u8 = null;504 var sec_websocket_key: ?[]const u8 = null;
...@@ -515,7 +526,7 @@ pub const Request = struct {...@@ -515,7 +526,7 @@ pub const Request = struct {
515526
516 /// The header is not guaranteed to be sent until `WebSocket.flush` is527 /// The header is not guaranteed to be sent until `WebSocket.flush` is
517 /// called on the returned struct.528 /// called on the returned struct.
518 pub fn respondWebSocket(request: *Request, options: WebSocketOptions) Writer.Error!WebSocket {529 pub fn respondWebSocket(request: *Request, options: WebSocketOptions) ExpectContinueError!WebSocket {
519 if (request.head.expect != null) return error.HttpExpectationFailed;530 if (request.head.expect != null) return error.HttpExpectationFailed;
520531
521 const out = request.server.out;532 const out = request.server.out;
...@@ -534,16 +545,14 @@ pub const Request = struct {...@@ -534,16 +545,14 @@ pub const Request = struct {
534 try out.print("{s} {d} {s}\r\n", .{ @tagName(version), @intFromEnum(status), phrase });545 try out.print("{s} {d} {s}\r\n", .{ @tagName(version), @intFromEnum(status), phrase });
535 try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: ");546 try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: ");
536 const base64_digest = try out.writableArray(28);547 const base64_digest = try out.writableArray(28);
537 assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len);548 assert(std.base64.standard.Encoder.encode(base64_digest, &digest).len == base64_digest.len);
538 out.advance(base64_digest.len);549 out.advance(base64_digest.len);
539 try out.writeAll("\r\n");550 try out.writeAll("\r\n");
540551
541 for (options.extra_headers) |header| {552 for (options.extra_headers) |header| {
542 assert(header.name.len != 0);553 assert(header.name.len != 0);
543 try out.writeAll(header.name);554 var bufs: [4][]const u8 = .{ header.name, ": ", header.value, "\r\n" };
544 try out.writeAll(": ");555 try out.writeVecAll(&bufs);
545 try out.writeAll(header.value);
546 try out.writeAll("\r\n");
547 }556 }
548557
549 try out.writeAll("\r\n");558 try out.writeAll("\r\n");
...@@ -564,7 +573,7 @@ pub const Request = struct {...@@ -564,7 +573,7 @@ pub const Request = struct {
564 ///573 ///
565 /// See `readerExpectNone` for an infallible alternative that cannot write574 /// See `readerExpectNone` for an infallible alternative that cannot write
566 /// to the server output stream.575 /// to the server output stream.
567 pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*std.io.Reader {576 pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*Reader {
568 const flush = request.head.expect != null;577 const flush = request.head.expect != null;
569 try writeExpectContinue(request);578 try writeExpectContinue(request);
570 if (flush) try request.server.out.flush();579 if (flush) try request.server.out.flush();
...@@ -576,9 +585,12 @@ pub const Request = struct {...@@ -576,9 +585,12 @@ pub const Request = struct {
576 /// this function.585 /// this function.
577 ///586 ///
578 /// Asserts that this function is only called once.587 /// Asserts that this function is only called once.
579 pub fn readerExpectNone(request: *Request, buffer: []u8) *std.io.Reader {588 ///
589 /// Invalidates the string memory inside `Head`.
590 pub fn readerExpectNone(request: *Request, buffer: []u8) *Reader {
580 assert(request.server.reader.state == .received_head);591 assert(request.server.reader.state == .received_head);
581 assert(request.head.expect == null);592 assert(request.head.expect == null);
593 request.head.invalidateStrings();
582 if (!request.head.method.requestHasBody()) return .ending;594 if (!request.head.method.requestHasBody()) return .ending;
583 return request.server.reader.bodyReader(buffer, request.head.transfer_encoding, request.head.content_length);595 return request.server.reader.bodyReader(buffer, request.head.transfer_encoding, request.head.content_length);
584 }596 }
...@@ -640,7 +652,7 @@ pub const Request = struct {...@@ -640,7 +652,7 @@ pub const Request = struct {
640/// See https://tools.ietf.org/html/rfc6455652/// See https://tools.ietf.org/html/rfc6455
641pub const WebSocket = struct {653pub const WebSocket = struct {
642 key: []const u8,654 key: []const u8,
643 input: *std.io.Reader,655 input: *Reader,
644 output: *Writer,656 output: *Writer,
645657
646 pub const Header0 = packed struct(u8) {658 pub const Header0 = packed struct(u8) {
...@@ -677,6 +689,8 @@ pub const WebSocket = struct {...@@ -677,6 +689,8 @@ pub const WebSocket = struct {
677 UnexpectedOpCode,689 UnexpectedOpCode,
678 MessageTooBig,690 MessageTooBig,
679 MissingMaskBit,691 MissingMaskBit,
692 ReadFailed,
693 EndOfStream,
680 };694 };
681695
682 pub const SmallMessage = struct {696 pub const SmallMessage = struct {
...@@ -691,8 +705,9 @@ pub const WebSocket = struct {...@@ -691,8 +705,9 @@ pub const WebSocket = struct {
691 pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage {705 pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage {
692 const in = ws.input;706 const in = ws.input;
693 while (true) {707 while (true) {
694 const h0 = in.takeStruct(Header0);708 const header = try in.takeArray(2);
695 const h1 = in.takeStruct(Header1);709 const h0: Header0 = @bitCast(header[0]);
710 const h1: Header1 = @bitCast(header[1]);
696711
697 switch (h0.opcode) {712 switch (h0.opcode) {
698 .text, .binary, .pong, .ping => {},713 .text, .binary, .pong, .ping => {},
...@@ -732,47 +747,49 @@ pub const WebSocket = struct {...@@ -732,47 +747,49 @@ pub const WebSocket = struct {
732 }747 }
733748
734 pub fn writeMessage(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void {749 pub fn writeMessage(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void {
735 try writeMessageVecUnflushed(ws, &.{data}, op);750 var bufs: [1][]const u8 = .{data};
751 try writeMessageVecUnflushed(ws, &bufs, op);
736 try ws.output.flush();752 try ws.output.flush();
737 }753 }
738754
739 pub fn writeMessageUnflushed(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void {755 pub fn writeMessageUnflushed(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void {
740 try writeMessageVecUnflushed(ws, &.{data}, op);756 var bufs: [1][]const u8 = .{data};
757 try writeMessageVecUnflushed(ws, &bufs, op);
741 }758 }
742759
743 pub fn writeMessageVec(ws: *WebSocket, data: []const []const u8, op: Opcode) Writer.Error!void {760 pub fn writeMessageVec(ws: *WebSocket, data: [][]const u8, op: Opcode) Writer.Error!void {
744 try writeMessageVecUnflushed(ws, data, op);761 try writeMessageVecUnflushed(ws, data, op);
745 try ws.output.flush();762 try ws.output.flush();
746 }763 }
747764
748 pub fn writeMessageVecUnflushed(ws: *WebSocket, data: []const []const u8, op: Opcode) Writer.Error!void {765 pub fn writeMessageVecUnflushed(ws: *WebSocket, data: [][]const u8, op: Opcode) Writer.Error!void {
749 const total_len = l: {766 const total_len = l: {
750 var total_len: u64 = 0;767 var total_len: u64 = 0;
751 for (data) |iovec| total_len += iovec.len;768 for (data) |iovec| total_len += iovec.len;
752 break :l total_len;769 break :l total_len;
753 };770 };
754 const out = ws.output;771 const out = ws.output;
755 try out.writeStruct(@as(Header0, .{772 try out.writeByte(@bitCast(@as(Header0, .{
756 .opcode = op,773 .opcode = op,
757 .fin = true,774 .fin = true,
758 }));775 })));
759 switch (total_len) {776 switch (total_len) {
760 0...125 => try out.writeStruct(@as(Header1, .{777 0...125 => try out.writeByte(@bitCast(@as(Header1, .{
761 .payload_len = @enumFromInt(total_len),778 .payload_len = @enumFromInt(total_len),
762 .mask = false,779 .mask = false,
763 })),780 }))),
764 126...0xffff => {781 126...0xffff => {
765 try out.writeStruct(@as(Header1, .{782 try out.writeByte(@bitCast(@as(Header1, .{
766 .payload_len = .len16,783 .payload_len = .len16,
767 .mask = false,784 .mask = false,
768 }));785 })));
769 try out.writeInt(u16, @intCast(total_len), .big);786 try out.writeInt(u16, @intCast(total_len), .big);
770 },787 },
771 else => {788 else => {
772 try out.writeStruct(@as(Header1, .{789 try out.writeByte(@bitCast(@as(Header1, .{
773 .payload_len = .len64,790 .payload_len = .len64,
774 .mask = false,791 .mask = false,
775 }));792 })));
776 try out.writeInt(u64, total_len, .big);793 try out.writeInt(u64, total_len, .big);
777 },794 },
778 }795 }
lib/std/http/test.zig+70-58
...@@ -65,23 +65,22 @@ test "trailers" {...@@ -65,23 +65,22 @@ test "trailers" {
65 try req.sendBodiless();65 try req.sendBodiless();
66 var response = try req.receiveHead(&.{});66 var response = try req.receiveHead(&.{});
6767
68 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
69 defer gpa.free(body);
70
71 try expectEqualStrings("Hello, World!\n", body);
72
73 {68 {
74 var it = response.head.iterateHeaders();69 var it = response.head.iterateHeaders();
75 const header = it.next().?;70 const header = it.next().?;
76 try expect(!it.is_trailer);
77 try expectEqualStrings("transfer-encoding", header.name);71 try expectEqualStrings("transfer-encoding", header.name);
78 try expectEqualStrings("chunked", header.value);72 try expectEqualStrings("chunked", header.value);
79 try expectEqual(null, it.next());73 try expectEqual(null, it.next());
80 }74 }
75
76 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
77 defer gpa.free(body);
78
79 try expectEqualStrings("Hello, World!\n", body);
80
81 {81 {
82 var it = response.iterateTrailers();82 var it = response.iterateTrailers();
83 const header = it.next().?;83 const header = it.next().?;
84 try expect(it.is_trailer);
85 try expectEqualStrings("X-Checksum", header.name);84 try expectEqualStrings("X-Checksum", header.name);
86 try expectEqualStrings("aaaa", header.value);85 try expectEqualStrings("aaaa", header.value);
87 try expectEqual(null, it.next());86 try expectEqual(null, it.next());
...@@ -183,7 +182,11 @@ test "echo content server" {...@@ -183,7 +182,11 @@ test "echo content server" {
183 if (request.head.expect) |expect_header_value| {182 if (request.head.expect) |expect_header_value| {
184 if (mem.eql(u8, expect_header_value, "garbage")) {183 if (mem.eql(u8, expect_header_value, "garbage")) {
185 try expectError(error.HttpExpectationFailed, request.readerExpectContinue(&.{}));184 try expectError(error.HttpExpectationFailed, request.readerExpectContinue(&.{}));
186 try request.respond("", .{ .keep_alive = false });185 request.head.expect = null;
186 try request.respond("", .{
187 .keep_alive = false,
188 .status = .expectation_failed,
189 });
187 continue;190 continue;
188 }191 }
189 }192 }
...@@ -204,12 +207,14 @@ test "echo content server" {...@@ -204,12 +207,14 @@ test "echo content server" {
204 // request.head.target,207 // request.head.target,
205 //});208 //});
206209
207 const body = try (try request.readerExpectContinue(&.{})).allocRemaining(std.testing.allocator, .limited(8192));210 try expect(mem.startsWith(u8, request.head.target, "/echo-content"));
211 try expectEqualStrings("text/plain", request.head.content_type.?);
212
213 // head strings expire here
214 const body = try (try request.readerExpectContinue(&.{})).allocRemaining(std.testing.allocator, .unlimited);
208 defer std.testing.allocator.free(body);215 defer std.testing.allocator.free(body);
209216
210 try expect(mem.startsWith(u8, request.head.target, "/echo-content"));
211 try expectEqualStrings("Hello, World!\n", body);217 try expectEqualStrings("Hello, World!\n", body);
212 try expectEqualStrings("text/plain", request.head.content_type.?);
213218
214 var response = try request.respondStreaming(&.{}, .{219 var response = try request.respondStreaming(&.{}, .{
215 .content_length = switch (request.head.transfer_encoding) {220 .content_length = switch (request.head.transfer_encoding) {
...@@ -273,7 +278,6 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -273,7 +278,6 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
273 for (0..500) |i| {278 for (0..500) |i| {
274 try w.print("{d}, ah ha ha!\n", .{i});279 try w.print("{d}, ah ha ha!\n", .{i});
275 }280 }
276 try expectEqual(7390, w.count);
277 try w.flush();281 try w.flush();
278 try response.end();282 try response.end();
279 try expectEqual(.closing, server.reader.state);283 try expectEqual(.closing, server.reader.state);
...@@ -291,7 +295,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -291,7 +295,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
291295
292 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded296 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
293 var stream_reader = stream.reader(&tiny_buffer);297 var stream_reader = stream.reader(&tiny_buffer);
294 const response = try stream_reader.interface().allocRemaining(gpa, .limited(8192));298 const response = try stream_reader.interface().allocRemaining(gpa, .unlimited);
295 defer gpa.free(response);299 defer gpa.free(response);
296300
297 var expected_response = std.ArrayList(u8).init(gpa);301 var expected_response = std.ArrayList(u8).init(gpa);
...@@ -362,7 +366,7 @@ test "receiving arbitrary http headers from the client" {...@@ -362,7 +366,7 @@ test "receiving arbitrary http headers from the client" {
362366
363 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded367 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
364 var stream_reader = stream.reader(&tiny_buffer);368 var stream_reader = stream.reader(&tiny_buffer);
365 const response = try stream_reader.interface().allocRemaining(gpa, .limited(8192));369 const response = try stream_reader.interface().allocRemaining(gpa, .unlimited);
366 defer gpa.free(response);370 defer gpa.free(response);
367371
368 var expected_response = std.ArrayList(u8).init(gpa);372 var expected_response = std.ArrayList(u8).init(gpa);
...@@ -407,18 +411,19 @@ test "general client/server API coverage" {...@@ -407,18 +411,19 @@ test "general client/server API coverage" {
407411
408 fn handleRequest(request: *http.Server.Request, listen_port: u16) !void {412 fn handleRequest(request: *http.Server.Request, listen_port: u16) !void {
409 const log = std.log.scoped(.server);413 const log = std.log.scoped(.server);
414 const gpa = std.testing.allocator;
410415
411 log.info("{f} {s} {s}", .{416 log.info("{t} {t} {s}", .{ request.head.method, request.head.version, request.head.target });
412 request.head.method, @tagName(request.head.version), request.head.target,417 const target = try gpa.dupe(u8, request.head.target);
413 });418 defer gpa.free(target);
414419
415 const gpa = std.testing.allocator;420 const reader = (try request.readerExpectContinue(&.{}));
416 const body = try (try request.readerExpectContinue(&.{})).allocRemaining(gpa, .limited(8192));421 const body = try reader.allocRemaining(gpa, .unlimited);
417 defer gpa.free(body);422 defer gpa.free(body);
418423
419 if (mem.startsWith(u8, request.head.target, "/get")) {424 if (mem.startsWith(u8, target, "/get")) {
420 var response = try request.respondStreaming(&.{}, .{425 var response = try request.respondStreaming(&.{}, .{
421 .content_length = if (mem.indexOf(u8, request.head.target, "?chunked") == null)426 .content_length = if (mem.indexOf(u8, target, "?chunked") == null)
422 14427 14
423 else428 else
424 null,429 null,
...@@ -433,7 +438,7 @@ test "general client/server API coverage" {...@@ -433,7 +438,7 @@ test "general client/server API coverage" {
433 try w.writeAll("World!\n");438 try w.writeAll("World!\n");
434 try response.end();439 try response.end();
435 // Writing again would cause an assertion failure.440 // Writing again would cause an assertion failure.
436 } else if (mem.startsWith(u8, request.head.target, "/large")) {441 } else if (mem.startsWith(u8, target, "/large")) {
437 var response = try request.respondStreaming(&.{}, .{442 var response = try request.respondStreaming(&.{}, .{
438 .content_length = 14 * 1024 + 14 * 10,443 .content_length = 14 * 1024 + 14 * 10,
439 });444 });
...@@ -447,7 +452,8 @@ test "general client/server API coverage" {...@@ -447,7 +452,8 @@ test "general client/server API coverage" {
447 try w.writeAll("Hello, World!\n");452 try w.writeAll("Hello, World!\n");
448 }453 }
449454
450 try w.writeAll("Hello, World!\n" ** 1024);455 var vec: [1][]const u8 = .{"Hello, World!\n"};
456 try w.writeSplatAll(&vec, 1024);
451457
452 i = 0;458 i = 0;
453 while (i < 5) : (i += 1) {459 while (i < 5) : (i += 1) {
...@@ -455,7 +461,7 @@ test "general client/server API coverage" {...@@ -455,7 +461,7 @@ test "general client/server API coverage" {
455 }461 }
456462
457 try response.end();463 try response.end();
458 } else if (mem.eql(u8, request.head.target, "/redirect/1")) {464 } else if (mem.eql(u8, target, "/redirect/1")) {
459 var response = try request.respondStreaming(&.{}, .{465 var response = try request.respondStreaming(&.{}, .{
460 .respond_options = .{466 .respond_options = .{
461 .status = .found,467 .status = .found,
...@@ -469,14 +475,14 @@ test "general client/server API coverage" {...@@ -469,14 +475,14 @@ test "general client/server API coverage" {
469 try w.writeAll("Hello, ");475 try w.writeAll("Hello, ");
470 try w.writeAll("Redirected!\n");476 try w.writeAll("Redirected!\n");
471 try response.end();477 try response.end();
472 } else if (mem.eql(u8, request.head.target, "/redirect/2")) {478 } else if (mem.eql(u8, target, "/redirect/2")) {
473 try request.respond("Hello, Redirected!\n", .{479 try request.respond("Hello, Redirected!\n", .{
474 .status = .found,480 .status = .found,
475 .extra_headers = &.{481 .extra_headers = &.{
476 .{ .name = "location", .value = "/redirect/1" },482 .{ .name = "location", .value = "/redirect/1" },
477 },483 },
478 });484 });
479 } else if (mem.eql(u8, request.head.target, "/redirect/3")) {485 } else if (mem.eql(u8, target, "/redirect/3")) {
480 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/2", .{486 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/2", .{
481 listen_port,487 listen_port,
482 });488 });
...@@ -488,23 +494,23 @@ test "general client/server API coverage" {...@@ -488,23 +494,23 @@ test "general client/server API coverage" {
488 .{ .name = "location", .value = location },494 .{ .name = "location", .value = location },
489 },495 },
490 });496 });
491 } else if (mem.eql(u8, request.head.target, "/redirect/4")) {497 } else if (mem.eql(u8, target, "/redirect/4")) {
492 try request.respond("Hello, Redirected!\n", .{498 try request.respond("Hello, Redirected!\n", .{
493 .status = .found,499 .status = .found,
494 .extra_headers = &.{500 .extra_headers = &.{
495 .{ .name = "location", .value = "/redirect/3" },501 .{ .name = "location", .value = "/redirect/3" },
496 },502 },
497 });503 });
498 } else if (mem.eql(u8, request.head.target, "/redirect/5")) {504 } else if (mem.eql(u8, target, "/redirect/5")) {
499 try request.respond("Hello, Redirected!\n", .{505 try request.respond("Hello, Redirected!\n", .{
500 .status = .found,506 .status = .found,
501 .extra_headers = &.{507 .extra_headers = &.{
502 .{ .name = "location", .value = "/%2525" },508 .{ .name = "location", .value = "/%2525" },
503 },509 },
504 });510 });
505 } else if (mem.eql(u8, request.head.target, "/%2525")) {511 } else if (mem.eql(u8, target, "/%2525")) {
506 try request.respond("Encoded redirect successful!\n", .{});512 try request.respond("Encoded redirect successful!\n", .{});
507 } else if (mem.eql(u8, request.head.target, "/redirect/invalid")) {513 } else if (mem.eql(u8, target, "/redirect/invalid")) {
508 const invalid_port = try getUnusedTcpPort();514 const invalid_port = try getUnusedTcpPort();
509 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}", .{invalid_port});515 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}", .{invalid_port});
510 defer gpa.free(location);516 defer gpa.free(location);
...@@ -515,7 +521,7 @@ test "general client/server API coverage" {...@@ -515,7 +521,7 @@ test "general client/server API coverage" {
515 .{ .name = "location", .value = location },521 .{ .name = "location", .value = location },
516 },522 },
517 });523 });
518 } else if (mem.eql(u8, request.head.target, "/empty")) {524 } else if (mem.eql(u8, target, "/empty")) {
519 try request.respond("", .{525 try request.respond("", .{
520 .extra_headers = &.{526 .extra_headers = &.{
521 .{ .name = "empty", .value = "" },527 .{ .name = "empty", .value = "" },
...@@ -556,11 +562,12 @@ test "general client/server API coverage" {...@@ -556,11 +562,12 @@ test "general client/server API coverage" {
556 try req.sendBodiless();562 try req.sendBodiless();
557 var response = try req.receiveHead(&redirect_buffer);563 var response = try req.receiveHead(&redirect_buffer);
558564
559 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));565 try expectEqualStrings("text/plain", response.head.content_type.?);
566
567 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
560 defer gpa.free(body);568 defer gpa.free(body);
561569
562 try expectEqualStrings("Hello, World!\n", body);570 try expectEqualStrings("Hello, World!\n", body);
563 try expectEqualStrings("text/plain", response.head.content_type.?);
564 }571 }
565572
566 // connection has been kept alive573 // connection has been kept alive
...@@ -579,7 +586,7 @@ test "general client/server API coverage" {...@@ -579,7 +586,7 @@ test "general client/server API coverage" {
579 try req.sendBodiless();586 try req.sendBodiless();
580 var response = try req.receiveHead(&redirect_buffer);587 var response = try req.receiveHead(&redirect_buffer);
581588
582 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192 * 1024));589 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
583 defer gpa.free(body);590 defer gpa.free(body);
584591
585 try expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len);592 try expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len);
...@@ -601,12 +608,13 @@ test "general client/server API coverage" {...@@ -601,12 +608,13 @@ test "general client/server API coverage" {
601 try req.sendBodiless();608 try req.sendBodiless();
602 var response = try req.receiveHead(&redirect_buffer);609 var response = try req.receiveHead(&redirect_buffer);
603610
604 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));611 try expectEqualStrings("text/plain", response.head.content_type.?);
612 try expectEqual(14, response.head.content_length.?);
613
614 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
605 defer gpa.free(body);615 defer gpa.free(body);
606616
607 try expectEqualStrings("", body);617 try expectEqualStrings("", body);
608 try expectEqualStrings("text/plain", response.head.content_type.?);
609 try expectEqual(14, response.head.content_length.?);
610 }618 }
611619
612 // connection has been kept alive620 // connection has been kept alive
...@@ -625,11 +633,12 @@ test "general client/server API coverage" {...@@ -625,11 +633,12 @@ test "general client/server API coverage" {
625 try req.sendBodiless();633 try req.sendBodiless();
626 var response = try req.receiveHead(&redirect_buffer);634 var response = try req.receiveHead(&redirect_buffer);
627635
628 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));636 try expectEqualStrings("text/plain", response.head.content_type.?);
637
638 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
629 defer gpa.free(body);639 defer gpa.free(body);
630640
631 try expectEqualStrings("Hello, World!\n", body);641 try expectEqualStrings("Hello, World!\n", body);
632 try expectEqualStrings("text/plain", response.head.content_type.?);
633 }642 }
634643
635 // connection has been kept alive644 // connection has been kept alive
...@@ -648,12 +657,13 @@ test "general client/server API coverage" {...@@ -648,12 +657,13 @@ test "general client/server API coverage" {
648 try req.sendBodiless();657 try req.sendBodiless();
649 var response = try req.receiveHead(&redirect_buffer);658 var response = try req.receiveHead(&redirect_buffer);
650659
651 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));660 try expectEqualStrings("text/plain", response.head.content_type.?);
661 try expect(response.head.transfer_encoding == .chunked);
662
663 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
652 defer gpa.free(body);664 defer gpa.free(body);
653665
654 try expectEqualStrings("", body);666 try expectEqualStrings("", body);
655 try expectEqualStrings("text/plain", response.head.content_type.?);
656 try expect(response.head.transfer_encoding == .chunked);
657 }667 }
658668
659 // connection has been kept alive669 // connection has been kept alive
...@@ -674,11 +684,12 @@ test "general client/server API coverage" {...@@ -674,11 +684,12 @@ test "general client/server API coverage" {
674 try req.sendBodiless();684 try req.sendBodiless();
675 var response = try req.receiveHead(&redirect_buffer);685 var response = try req.receiveHead(&redirect_buffer);
676686
677 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));687 try expectEqualStrings("text/plain", response.head.content_type.?);
688
689 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
678 defer gpa.free(body);690 defer gpa.free(body);
679691
680 try expectEqualStrings("Hello, World!\n", body);692 try expectEqualStrings("Hello, World!\n", body);
681 try expectEqualStrings("text/plain", response.head.content_type.?);
682 }693 }
683694
684 // connection has been closed695 // connection has been closed
...@@ -703,11 +714,6 @@ test "general client/server API coverage" {...@@ -703,11 +714,6 @@ test "general client/server API coverage" {
703714
704 try std.testing.expectEqual(.ok, response.head.status);715 try std.testing.expectEqual(.ok, response.head.status);
705716
706 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
707 defer gpa.free(body);
708
709 try expectEqualStrings("", body);
710
711 var it = response.head.iterateHeaders();717 var it = response.head.iterateHeaders();
712 {718 {
713 const header = it.next().?;719 const header = it.next().?;
...@@ -715,6 +721,12 @@ test "general client/server API coverage" {...@@ -715,6 +721,12 @@ test "general client/server API coverage" {
715 try expectEqualStrings("content-length", header.name);721 try expectEqualStrings("content-length", header.name);
716 try expectEqualStrings("0", header.value);722 try expectEqualStrings("0", header.value);
717 }723 }
724
725 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
726 defer gpa.free(body);
727
728 try expectEqualStrings("", body);
729
718 {730 {
719 const header = it.next().?;731 const header = it.next().?;
720 try expect(!it.is_trailer);732 try expect(!it.is_trailer);
...@@ -740,7 +752,7 @@ test "general client/server API coverage" {...@@ -740,7 +752,7 @@ test "general client/server API coverage" {
740 try req.sendBodiless();752 try req.sendBodiless();
741 var response = try req.receiveHead(&redirect_buffer);753 var response = try req.receiveHead(&redirect_buffer);
742754
743 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));755 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
744 defer gpa.free(body);756 defer gpa.free(body);
745757
746 try expectEqualStrings("Hello, World!\n", body);758 try expectEqualStrings("Hello, World!\n", body);
...@@ -762,7 +774,7 @@ test "general client/server API coverage" {...@@ -762,7 +774,7 @@ test "general client/server API coverage" {
762 try req.sendBodiless();774 try req.sendBodiless();
763 var response = try req.receiveHead(&redirect_buffer);775 var response = try req.receiveHead(&redirect_buffer);
764776
765 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));777 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
766 defer gpa.free(body);778 defer gpa.free(body);
767779
768 try expectEqualStrings("Hello, World!\n", body);780 try expectEqualStrings("Hello, World!\n", body);
...@@ -784,7 +796,7 @@ test "general client/server API coverage" {...@@ -784,7 +796,7 @@ test "general client/server API coverage" {
784 try req.sendBodiless();796 try req.sendBodiless();
785 var response = try req.receiveHead(&redirect_buffer);797 var response = try req.receiveHead(&redirect_buffer);
786798
787 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));799 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
788 defer gpa.free(body);800 defer gpa.free(body);
789801
790 try expectEqualStrings("Hello, World!\n", body);802 try expectEqualStrings("Hello, World!\n", body);
...@@ -825,7 +837,7 @@ test "general client/server API coverage" {...@@ -825,7 +837,7 @@ test "general client/server API coverage" {
825 try req.sendBodiless();837 try req.sendBodiless();
826 var response = try req.receiveHead(&redirect_buffer);838 var response = try req.receiveHead(&redirect_buffer);
827839
828 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));840 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
829 defer gpa.free(body);841 defer gpa.free(body);
830842
831 try expectEqualStrings("Encoded redirect successful!\n", body);843 try expectEqualStrings("Encoded redirect successful!\n", body);
...@@ -915,7 +927,7 @@ test "Server streams both reading and writing" {...@@ -915,7 +927,7 @@ test "Server streams both reading and writing" {
915 try body_writer.writer.writeAll("fish");927 try body_writer.writer.writeAll("fish");
916 try body_writer.end();928 try body_writer.end();
917929
918 const body = try response.reader(&.{}).allocRemaining(std.testing.allocator, .limited(8192));930 const body = try response.reader(&.{}).allocRemaining(std.testing.allocator, .unlimited);
919 defer std.testing.allocator.free(body);931 defer std.testing.allocator.free(body);
920932
921 try expectEqualStrings("ONE FISH", body);933 try expectEqualStrings("ONE FISH", body);
...@@ -947,7 +959,7 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -947,7 +959,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
947959
948 var response = try req.receiveHead(&redirect_buffer);960 var response = try req.receiveHead(&redirect_buffer);
949961
950 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));962 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
951 defer gpa.free(body);963 defer gpa.free(body);
952964
953 try expectEqualStrings("Hello, World!\n", body);965 try expectEqualStrings("Hello, World!\n", body);
...@@ -980,7 +992,7 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -980,7 +992,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
980992
981 var response = try req.receiveHead(&redirect_buffer);993 var response = try req.receiveHead(&redirect_buffer);
982994
983 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));995 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
984 defer gpa.free(body);996 defer gpa.free(body);
985997
986 try expectEqualStrings("Hello, World!\n", body);998 try expectEqualStrings("Hello, World!\n", body);
...@@ -1034,7 +1046,7 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -1034,7 +1046,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
1034 var response = try req.receiveHead(&redirect_buffer);1046 var response = try req.receiveHead(&redirect_buffer);
1035 try expectEqual(.ok, response.head.status);1047 try expectEqual(.ok, response.head.status);
10361048
1037 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));1049 const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited);
1038 defer gpa.free(body);1050 defer gpa.free(body);
10391051
1040 try expectEqualStrings("Hello, World!\n", body);1052 try expectEqualStrings("Hello, World!\n", body);
...@@ -1175,7 +1187,7 @@ test "redirect to different connection" {...@@ -1175,7 +1187,7 @@ test "redirect to different connection" {
1175 var response = try req.receiveHead(&redirect_buffer);1187 var response = try req.receiveHead(&redirect_buffer);
1176 var reader = response.reader(&.{});1188 var reader = response.reader(&.{});
11771189
1178 const body = try reader.allocRemaining(gpa, .limited(8192));1190 const body = try reader.allocRemaining(gpa, .unlimited);
1179 defer gpa.free(body);1191 defer gpa.free(body);
11801192
1181 try expectEqualStrings("good job, you pass", body);1193 try expectEqualStrings("good job, you pass", body);
lib/std/math/big/int.zig+3-5
...@@ -1710,7 +1710,7 @@ pub const Mutable = struct {...@@ -1710,7 +1710,7 @@ pub const Mutable = struct {
17101710
1711 if (xy_trailing != 0 and r.limbs[r.len - 1] != 0) {1711 if (xy_trailing != 0 and r.limbs[r.len - 1] != 0) {
1712 // Manually shift here since we know its limb aligned.1712 // Manually shift here since we know its limb aligned.
1713 mem.copyBackwards(Limb, r.limbs[xy_trailing..], r.limbs[0..r.len]);1713 @memmove(r.limbs[xy_trailing..][0..r.len], r.limbs[0..r.len]);
1714 @memset(r.limbs[0..xy_trailing], 0);1714 @memset(r.limbs[0..xy_trailing], 0);
1715 r.len += xy_trailing;1715 r.len += xy_trailing;
1716 }1716 }
...@@ -3836,8 +3836,7 @@ fn llshl(r: []Limb, a: []const Limb, shift: usize) usize {...@@ -3836,8 +3836,7 @@ fn llshl(r: []Limb, a: []const Limb, shift: usize) usize {
3836 std.debug.assert(@intFromPtr(r.ptr) >= @intFromPtr(a.ptr));3836 std.debug.assert(@intFromPtr(r.ptr) >= @intFromPtr(a.ptr));
38373837
3838 if (shift == 0) {3838 if (shift == 0) {
3839 if (a.ptr != r.ptr)3839 if (a.ptr != r.ptr) @memmove(r[0..a.len], a);
3840 std.mem.copyBackwards(Limb, r[0..a.len], a);
3841 return a.len;3840 return a.len;
3842 }3841 }
3843 if (shift >= limb_bits) {3842 if (shift >= limb_bits) {
...@@ -3891,8 +3890,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize {...@@ -3891,8 +3890,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize {
3891 if (shift == 0) {3890 if (shift == 0) {
3892 std.debug.assert(r.len >= a.len);3891 std.debug.assert(r.len >= a.len);
38933892
3894 if (a.ptr != r.ptr)3893 if (a.ptr != r.ptr) @memmove(r[0..a.len], a);
3895 std.mem.copyForwards(Limb, r[0..a.len], a);
3896 return a.len;3894 return a.len;
3897 }3895 }
3898 if (shift >= limb_bits) {3896 if (shift >= limb_bits) {
lib/std/meta.zig+1-1
...@@ -939,7 +939,7 @@ fn CreateUniqueTuple(comptime N: comptime_int, comptime types: [N]type) type {...@@ -939,7 +939,7 @@ fn CreateUniqueTuple(comptime N: comptime_int, comptime types: [N]type) type {
939 .type = T,939 .type = T,
940 .default_value_ptr = null,940 .default_value_ptr = null,
941 .is_comptime = false,941 .is_comptime = false,
942 .alignment = 0,942 .alignment = @alignOf(T),
943 };943 };
944 }944 }
945945
lib/std/net.zig+1-1
...@@ -1944,7 +1944,7 @@ pub const Stream = struct {...@@ -1944,7 +1944,7 @@ pub const Stream = struct {
1944 pub const Error = ReadError;1944 pub const Error = ReadError;
19451945
1946 pub fn getStream(r: *const Reader) Stream {1946 pub fn getStream(r: *const Reader) Stream {
1947 return r.stream;1947 return r.net_stream;
1948 }1948 }
19491949
1950 pub fn getError(r: *const Reader) ?Error {1950 pub fn getError(r: *const Reader) ?Error {
lib/std/os/linux.zig+38
...@@ -1014,6 +1014,44 @@ pub fn munmap(address: [*]const u8, length: usize) usize {...@@ -1014,6 +1014,44 @@ pub fn munmap(address: [*]const u8, length: usize) usize {
1014 return syscall2(.munmap, @intFromPtr(address), length);1014 return syscall2(.munmap, @intFromPtr(address), length);
1015}1015}
10161016
1017pub fn mlock(address: [*]const u8, length: usize) usize {
1018 return syscall2(.mlock, @intFromPtr(address), length);
1019}
1020
1021pub fn munlock(address: [*]const u8, length: usize) usize {
1022 return syscall2(.munlock, @intFromPtr(address), length);
1023}
1024
1025pub const MLOCK = packed struct(u32) {
1026 ONFAULT: bool = false,
1027 _1: u31 = 0,
1028};
1029
1030pub fn mlock2(address: [*]const u8, length: usize, flags: MLOCK) usize {
1031 return syscall3(.mlock2, @intFromPtr(address), length, @as(u32, @bitCast(flags)));
1032}
1033
1034pub const MCL = if (native_arch.isSPARC() or native_arch.isPowerPC()) packed struct(u32) {
1035 _0: u13 = 0,
1036 CURRENT: bool = false,
1037 FUTURE: bool = false,
1038 ONFAULT: bool = false,
1039 _4: u16 = 0,
1040} else packed struct(u32) {
1041 CURRENT: bool = false,
1042 FUTURE: bool = false,
1043 ONFAULT: bool = false,
1044 _3: u29 = 0,
1045};
1046
1047pub fn mlockall(flags: MCL) usize {
1048 return syscall1(.mlockall, @as(u32, @bitCast(flags)));
1049}
1050
1051pub fn munlockall() usize {
1052 return syscall0(.munlockall);
1053}
1054
1017pub fn poll(fds: [*]pollfd, n: nfds_t, timeout: i32) usize {1055pub fn poll(fds: [*]pollfd, n: nfds_t, timeout: i32) usize {
1018 if (@hasField(SYS, "poll")) {1056 if (@hasField(SYS, "poll")) {
1019 return syscall3(.poll, @intFromPtr(fds), n, @as(u32, @bitCast(timeout)));1057 return syscall3(.poll, @intFromPtr(fds), n, @as(u32, @bitCast(timeout)));
lib/std/os/windows.zig+3-8
...@@ -1332,7 +1332,7 @@ pub fn GetFinalPathNameByHandle(...@@ -1332,7 +1332,7 @@ pub fn GetFinalPathNameByHandle(
1332 // dropping the \Device\Mup\ and making sure the path begins with \\1332 // dropping the \Device\Mup\ and making sure the path begins with \\
1333 if (mem.eql(u16, device_name_u16, std.unicode.utf8ToUtf16LeStringLiteral("Mup"))) {1333 if (mem.eql(u16, device_name_u16, std.unicode.utf8ToUtf16LeStringLiteral("Mup"))) {
1334 out_buffer[0] = '\\';1334 out_buffer[0] = '\\';
1335 mem.copyForwards(u16, out_buffer[1..][0..file_name_u16.len], file_name_u16);1335 @memmove(out_buffer[1..][0..file_name_u16.len], file_name_u16);
1336 return out_buffer[0 .. 1 + file_name_u16.len];1336 return out_buffer[0 .. 1 + file_name_u16.len];
1337 }1337 }
13381338
...@@ -1400,7 +1400,7 @@ pub fn GetFinalPathNameByHandle(...@@ -1400,7 +1400,7 @@ pub fn GetFinalPathNameByHandle(
1400 if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong;1400 if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong;
14011401
1402 @memcpy(out_buffer[0..drive_letter.len], drive_letter);1402 @memcpy(out_buffer[0..drive_letter.len], drive_letter);
1403 mem.copyForwards(u16, out_buffer[drive_letter.len..][0..file_name_u16.len], file_name_u16);1403 @memmove(out_buffer[drive_letter.len..][0..file_name_u16.len], file_name_u16);
1404 const total_len = drive_letter.len + file_name_u16.len;1404 const total_len = drive_letter.len + file_name_u16.len;
14051405
1406 // Validate that DOS does not contain any spurious nul bytes.1406 // Validate that DOS does not contain any spurious nul bytes.
...@@ -1449,12 +1449,7 @@ pub fn GetFinalPathNameByHandle(...@@ -1449,12 +1449,7 @@ pub fn GetFinalPathNameByHandle(
1449 // to copy backwards. We also need to do this before copying the volume path because1449 // to copy backwards. We also need to do this before copying the volume path because
1450 // it could overwrite the file_name_u16 memory.1450 // it could overwrite the file_name_u16 memory.
1451 const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len];1451 const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len];
1452 const file_name_byte_offset = @intFromPtr(file_name_u16.ptr) - @intFromPtr(out_buffer.ptr);1452 @memmove(file_name_dest, file_name_u16);
1453 const file_name_index = file_name_byte_offset / @sizeOf(u16);
1454 if (volume_path.len > file_name_index)
1455 mem.copyBackwards(u16, file_name_dest, file_name_u16)
1456 else
1457 mem.copyForwards(u16, file_name_dest, file_name_u16);
1458 @memcpy(out_buffer[0..volume_path.len], volume_path);1453 @memcpy(out_buffer[0..volume_path.len], volume_path);
1459 const total_len = volume_path.len + file_name_u16.len;1454 const total_len = volume_path.len + file_name_u16.len;
14601455
lib/std/process/Child.zig-9
...@@ -901,11 +901,6 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {...@@ -901,11 +901,6 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
901 if (dir_buf.items.len > 0) try dir_buf.append(self.allocator, fs.path.sep);901 if (dir_buf.items.len > 0) try dir_buf.append(self.allocator, fs.path.sep);
902 try dir_buf.appendSlice(self.allocator, app_dir);902 try dir_buf.appendSlice(self.allocator, app_dir);
903 }903 }
904 if (dir_buf.items.len > 0) {
905 // Need to normalize the path, openDirW can't handle things like double backslashes
906 const normalized_len = windows.normalizePath(u16, dir_buf.items) catch return error.BadPathName;
907 dir_buf.shrinkRetainingCapacity(normalized_len);
908 }
909904
910 windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {905 windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {
911 const original_err = switch (no_path_err) {906 const original_err = switch (no_path_err) {
...@@ -930,10 +925,6 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {...@@ -930,10 +925,6 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
930 while (it.next()) |search_path| {925 while (it.next()) |search_path| {
931 dir_buf.clearRetainingCapacity();926 dir_buf.clearRetainingCapacity();
932 try dir_buf.appendSlice(self.allocator, search_path);927 try dir_buf.appendSlice(self.allocator, search_path);
933 // Need to normalize the path, some PATH values can contain things like double
934 // backslashes which openDirW can't handle
935 const normalized_len = windows.normalizePath(u16, dir_buf.items) catch continue;
936 dir_buf.shrinkRetainingCapacity(normalized_len);
937928
938 if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {929 if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {
939 break :run;930 break :run;
lib/std/std.zig-2
...@@ -9,8 +9,6 @@ pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;...@@ -9,8 +9,6 @@ pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
9pub const AutoHashMap = hash_map.AutoHashMap;9pub const AutoHashMap = hash_map.AutoHashMap;
10pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;10pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
11pub const BitStack = @import("BitStack.zig");11pub const BitStack = @import("BitStack.zig");
12pub const BoundedArray = @import("bounded_array.zig").BoundedArray;
13pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned;
14pub const Build = @import("Build.zig");12pub const Build = @import("Build.zig");
15pub const BufMap = @import("buf_map.zig").BufMap;13pub const BufMap = @import("buf_map.zig").BufMap;
16pub const BufSet = @import("buf_set.zig").BufSet;14pub const BufSet = @import("buf_set.zig").BufSet;
lib/std/zig/AstGen.zig+3
...@@ -5386,6 +5386,9 @@ fn unionDeclInner(...@@ -5386,6 +5386,9 @@ fn unionDeclInner(
5386 return astgen.failNode(member_node, "union field missing type", .{});5386 return astgen.failNode(member_node, "union field missing type", .{});
5387 }5387 }
5388 if (member.ast.align_expr.unwrap()) |align_expr| {5388 if (member.ast.align_expr.unwrap()) |align_expr| {
5389 if (layout == .@"packed") {
5390 return astgen.failNode(align_expr, "unable to override alignment of packed union fields", .{});
5391 }
5389 const align_inst = try expr(&block_scope, &block_scope.base, coerced_align_ri, align_expr);5392 const align_inst = try expr(&block_scope, &block_scope.base, coerced_align_ri, align_expr);
5390 wip_members.appendToField(@intFromEnum(align_inst));5393 wip_members.appendToField(@intFromEnum(align_inst));
5391 any_aligned_fields = true;5394 any_aligned_fields = true;
lib/std/zig/llvm/Builder.zig+4-3
...@@ -8533,18 +8533,19 @@ pub const Metadata = enum(u32) {...@@ -8533,18 +8533,19 @@ pub const Metadata = enum(u32) {
8533 .type = []const u8,8533 .type = []const u8,
8534 .default_value_ptr = null,8534 .default_value_ptr = null,
8535 .is_comptime = false,8535 .is_comptime = false,
8536 .alignment = 0,8536 .alignment = @alignOf([]const u8),
8537 };8537 };
8538 }8538 }
8539 fmt_str = fmt_str ++ "(";8539 fmt_str = fmt_str ++ "(";
8540 inline for (fields[2..], names) |*field, name| {8540 inline for (fields[2..], names) |*field, name| {
8541 fmt_str = fmt_str ++ "{[" ++ name ++ "]f}";8541 fmt_str = fmt_str ++ "{[" ++ name ++ "]f}";
8542 const T = std.fmt.Formatter(FormatData, format);
8542 field.* = .{8543 field.* = .{
8543 .name = name,8544 .name = name,
8544 .type = std.fmt.Formatter(FormatData, format),8545 .type = T,
8545 .default_value_ptr = null,8546 .default_value_ptr = null,
8546 .is_comptime = false,8547 .is_comptime = false,
8547 .alignment = 0,8548 .alignment = @alignOf(T),
8548 };8549 };
8549 }8550 }
8550 fmt_str = fmt_str ++ ")\n";8551 fmt_str = fmt_str ++ ")\n";
src/Compilation.zig+86-52
...@@ -2103,6 +2103,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -2103,6 +2103,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
2103 .local_zir_cache = local_zir_cache,2103 .local_zir_cache = local_zir_cache,
2104 .error_limit = error_limit,2104 .error_limit = error_limit,
2105 .llvm_object = null,2105 .llvm_object = null,
2106 .analysis_roots_buffer = undefined,
2107 .analysis_roots_len = 0,
2106 };2108 };
2107 try zcu.init(options.thread_pool.getIdCount());2109 try zcu.init(options.thread_pool.getIdCount());
2108 break :blk zcu;2110 break :blk zcu;
...@@ -2183,8 +2185,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -2183,8 +2185,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
2183 .emit_docs = try options.emit_docs.resolve(arena, &options, .docs),2185 .emit_docs = try options.emit_docs.resolve(arena, &options, .docs),
2184 };2186 };
21852187
2186 comp.windows_libs = try std.StringArrayHashMapUnmanaged(void).init(gpa, options.windows_lib_names, &.{});2188 errdefer {
2187 errdefer comp.windows_libs.deinit(gpa);2189 for (comp.windows_libs.keys()) |windows_lib| gpa.free(windows_lib);
2190 comp.windows_libs.deinit(gpa);
2191 }
2192 try comp.windows_libs.ensureUnusedCapacity(gpa, options.windows_lib_names.len);
2193 for (options.windows_lib_names) |windows_lib| comp.windows_libs.putAssumeCapacity(try gpa.dupe(u8, windows_lib), {});
21882194
2189 // Prevent some footguns by making the "any" fields of config reflect2195 // Prevent some footguns by making the "any" fields of config reflect
2190 // the default Module settings.2196 // the default Module settings.
...@@ -2378,7 +2384,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -2378,7 +2384,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
2378 };2384 };
2379 comp.c_object_table.putAssumeCapacityNoClobber(c_object, {});2385 comp.c_object_table.putAssumeCapacityNoClobber(c_object, {});
2380 }2386 }
2381 comp.link_task_queue.pending_prelink_tasks += @intCast(comp.c_object_table.count());
23822387
2383 // Add a `Win32Resource` for each `rc_source_files` and one for `manifest_file`.2388 // Add a `Win32Resource` for each `rc_source_files` and one for `manifest_file`.
2384 const win32_resource_count =2389 const win32_resource_count =
...@@ -2386,10 +2391,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -2386,10 +2391,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
2386 if (win32_resource_count > 0) {2391 if (win32_resource_count > 0) {
2387 dev.check(.win32_resource);2392 dev.check(.win32_resource);
2388 try comp.win32_resource_table.ensureTotalCapacity(gpa, win32_resource_count);2393 try comp.win32_resource_table.ensureTotalCapacity(gpa, win32_resource_count);
2389 // Add this after adding logic to updateWin32Resource to pass the
2390 // result into link.loadInput. loadInput integration is not implemented
2391 // for Windows linking logic yet.
2392 //comp.link_task_queue.pending_prelink_tasks += @intCast(win32_resource_count);
2393 for (options.rc_source_files) |rc_source_file| {2394 for (options.rc_source_files) |rc_source_file| {
2394 const win32_resource = try gpa.create(Win32Resource);2395 const win32_resource = try gpa.create(Win32Resource);
2395 errdefer gpa.destroy(win32_resource);2396 errdefer gpa.destroy(win32_resource);
...@@ -2415,13 +2416,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -2415,13 +2416,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
24152416
2416 if (comp.emit_bin != null and target.ofmt != .c) {2417 if (comp.emit_bin != null and target.ofmt != .c) {
2417 if (!comp.skip_linker_dependencies) {2418 if (!comp.skip_linker_dependencies) {
2418 // These DLLs are always loaded into every Windows process.
2419 if (target.os.tag == .windows and is_exe_or_dyn_lib) {
2420 try comp.windows_libs.ensureUnusedCapacity(gpa, 2);
2421 comp.windows_libs.putAssumeCapacity("kernel32", {});
2422 comp.windows_libs.putAssumeCapacity("ntdll", {});
2423 }
2424
2425 // If we need to build libc for the target, add work items for it.2419 // If we need to build libc for the target, add work items for it.
2426 // We go through the work queue so that building can be done in parallel.2420 // We go through the work queue so that building can be done in parallel.
2427 // If linking against host libc installation, instead queue up jobs2421 // If linking against host libc installation, instead queue up jobs
...@@ -2455,62 +2449,51 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -2455,62 +2449,51 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
24552449
2456 if (musl.needsCrt0(comp.config.output_mode, comp.config.link_mode, comp.config.pie)) |f| {2450 if (musl.needsCrt0(comp.config.output_mode, comp.config.link_mode, comp.config.pie)) |f| {
2457 comp.queued_jobs.musl_crt_file[@intFromEnum(f)] = true;2451 comp.queued_jobs.musl_crt_file[@intFromEnum(f)] = true;
2458 comp.link_task_queue.pending_prelink_tasks += 1;
2459 }2452 }
2460 switch (comp.config.link_mode) {2453 switch (comp.config.link_mode) {
2461 .static => comp.queued_jobs.musl_crt_file[@intFromEnum(musl.CrtFile.libc_a)] = true,2454 .static => comp.queued_jobs.musl_crt_file[@intFromEnum(musl.CrtFile.libc_a)] = true,
2462 .dynamic => comp.queued_jobs.musl_crt_file[@intFromEnum(musl.CrtFile.libc_so)] = true,2455 .dynamic => comp.queued_jobs.musl_crt_file[@intFromEnum(musl.CrtFile.libc_so)] = true,
2463 }2456 }
2464 comp.link_task_queue.pending_prelink_tasks += 1;
2465 } else if (target.isGnuLibC()) {2457 } else if (target.isGnuLibC()) {
2466 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;2458 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
24672459
2468 if (glibc.needsCrt0(comp.config.output_mode)) |f| {2460 if (glibc.needsCrt0(comp.config.output_mode)) |f| {
2469 comp.queued_jobs.glibc_crt_file[@intFromEnum(f)] = true;2461 comp.queued_jobs.glibc_crt_file[@intFromEnum(f)] = true;
2470 comp.link_task_queue.pending_prelink_tasks += 1;
2471 }2462 }
2472 comp.queued_jobs.glibc_shared_objects = true;2463 comp.queued_jobs.glibc_shared_objects = true;
2473 comp.link_task_queue.pending_prelink_tasks += glibc.sharedObjectsCount(target);
24742464
2475 comp.queued_jobs.glibc_crt_file[@intFromEnum(glibc.CrtFile.libc_nonshared_a)] = true;2465 comp.queued_jobs.glibc_crt_file[@intFromEnum(glibc.CrtFile.libc_nonshared_a)] = true;
2476 comp.link_task_queue.pending_prelink_tasks += 1;
2477 } else if (target.isFreeBSDLibC()) {2466 } else if (target.isFreeBSDLibC()) {
2478 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;2467 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
24792468
2480 if (freebsd.needsCrt0(comp.config.output_mode)) |f| {2469 if (freebsd.needsCrt0(comp.config.output_mode)) |f| {
2481 comp.queued_jobs.freebsd_crt_file[@intFromEnum(f)] = true;2470 comp.queued_jobs.freebsd_crt_file[@intFromEnum(f)] = true;
2482 comp.link_task_queue.pending_prelink_tasks += 1;
2483 }2471 }
24842472
2485 comp.queued_jobs.freebsd_shared_objects = true;2473 comp.queued_jobs.freebsd_shared_objects = true;
2486 comp.link_task_queue.pending_prelink_tasks += freebsd.sharedObjectsCount();
2487 } else if (target.isNetBSDLibC()) {2474 } else if (target.isNetBSDLibC()) {
2488 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;2475 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
24892476
2490 if (netbsd.needsCrt0(comp.config.output_mode)) |f| {2477 if (netbsd.needsCrt0(comp.config.output_mode)) |f| {
2491 comp.queued_jobs.netbsd_crt_file[@intFromEnum(f)] = true;2478 comp.queued_jobs.netbsd_crt_file[@intFromEnum(f)] = true;
2492 comp.link_task_queue.pending_prelink_tasks += 1;
2493 }2479 }
24942480
2495 comp.queued_jobs.netbsd_shared_objects = true;2481 comp.queued_jobs.netbsd_shared_objects = true;
2496 comp.link_task_queue.pending_prelink_tasks += netbsd.sharedObjectsCount();
2497 } else if (target.isWasiLibC()) {2482 } else if (target.isWasiLibC()) {
2498 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;2483 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
24992484
2500 comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(wasi_libc.execModelCrtFile(comp.config.wasi_exec_model))] = true;2485 comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(wasi_libc.execModelCrtFile(comp.config.wasi_exec_model))] = true;
2501 comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(wasi_libc.CrtFile.libc_a)] = true;2486 comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(wasi_libc.CrtFile.libc_a)] = true;
2502 comp.link_task_queue.pending_prelink_tasks += 2;
2503 } else if (target.isMinGW()) {2487 } else if (target.isMinGW()) {
2504 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;2488 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
25052489
2506 const main_crt_file: mingw.CrtFile = if (is_dyn_lib) .dllcrt2_o else .crt2_o;2490 const main_crt_file: mingw.CrtFile = if (is_dyn_lib) .dllcrt2_o else .crt2_o;
2507 comp.queued_jobs.mingw_crt_file[@intFromEnum(main_crt_file)] = true;2491 comp.queued_jobs.mingw_crt_file[@intFromEnum(main_crt_file)] = true;
2508 comp.queued_jobs.mingw_crt_file[@intFromEnum(mingw.CrtFile.libmingw32_lib)] = true;2492 comp.queued_jobs.mingw_crt_file[@intFromEnum(mingw.CrtFile.libmingw32_lib)] = true;
2509 comp.link_task_queue.pending_prelink_tasks += 2;
25102493
2511 // When linking mingw-w64 there are some import libs we always need.2494 // When linking mingw-w64 there are some import libs we always need.
2512 try comp.windows_libs.ensureUnusedCapacity(gpa, mingw.always_link_libs.len);2495 try comp.windows_libs.ensureUnusedCapacity(gpa, mingw.always_link_libs.len);
2513 for (mingw.always_link_libs) |name| comp.windows_libs.putAssumeCapacity(name, {});2496 for (mingw.always_link_libs) |name| comp.windows_libs.putAssumeCapacity(try gpa.dupe(u8, name), {});
2514 } else {2497 } else {
2515 return error.LibCUnavailable;2498 return error.LibCUnavailable;
2516 }2499 }
...@@ -2520,7 +2503,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -2520,7 +2503,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
2520 target.isMinGW())2503 target.isMinGW())
2521 {2504 {
2522 comp.queued_jobs.zigc_lib = true;2505 comp.queued_jobs.zigc_lib = true;
2523 comp.link_task_queue.pending_prelink_tasks += 1;
2524 }2506 }
2525 }2507 }
25262508
...@@ -2537,50 +2519,41 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -2537,50 +2519,41 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
2537 }2519 }
2538 if (comp.wantBuildLibUnwindFromSource()) {2520 if (comp.wantBuildLibUnwindFromSource()) {
2539 comp.queued_jobs.libunwind = true;2521 comp.queued_jobs.libunwind = true;
2540 comp.link_task_queue.pending_prelink_tasks += 1;
2541 }2522 }
2542 if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.link_libcpp) {2523 if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.link_libcpp) {
2543 comp.queued_jobs.libcxx = true;2524 comp.queued_jobs.libcxx = true;
2544 comp.queued_jobs.libcxxabi = true;2525 comp.queued_jobs.libcxxabi = true;
2545 comp.link_task_queue.pending_prelink_tasks += 2;
2546 }2526 }
2547 if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.any_sanitize_thread) {2527 if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.any_sanitize_thread) {
2548 comp.queued_jobs.libtsan = true;2528 comp.queued_jobs.libtsan = true;
2549 comp.link_task_queue.pending_prelink_tasks += 1;
2550 }2529 }
25512530
2552 if (can_build_compiler_rt) {2531 if (can_build_compiler_rt) {
2553 if (comp.compiler_rt_strat == .lib) {2532 if (comp.compiler_rt_strat == .lib) {
2554 log.debug("queuing a job to build compiler_rt_lib", .{});2533 log.debug("queuing a job to build compiler_rt_lib", .{});
2555 comp.queued_jobs.compiler_rt_lib = true;2534 comp.queued_jobs.compiler_rt_lib = true;
2556 comp.link_task_queue.pending_prelink_tasks += 1;
2557 } else if (comp.compiler_rt_strat == .obj) {2535 } else if (comp.compiler_rt_strat == .obj) {
2558 log.debug("queuing a job to build compiler_rt_obj", .{});2536 log.debug("queuing a job to build compiler_rt_obj", .{});
2559 // In this case we are making a static library, so we ask2537 // In this case we are making a static library, so we ask
2560 // for a compiler-rt object to put in it.2538 // for a compiler-rt object to put in it.
2561 comp.queued_jobs.compiler_rt_obj = true;2539 comp.queued_jobs.compiler_rt_obj = true;
2562 comp.link_task_queue.pending_prelink_tasks += 1;
2563 } else if (comp.compiler_rt_strat == .dyn_lib) {2540 } else if (comp.compiler_rt_strat == .dyn_lib) {
2564 // hack for stage2_x86_64 + coff2541 // hack for stage2_x86_64 + coff
2565 log.debug("queuing a job to build compiler_rt_dyn_lib", .{});2542 log.debug("queuing a job to build compiler_rt_dyn_lib", .{});
2566 comp.queued_jobs.compiler_rt_dyn_lib = true;2543 comp.queued_jobs.compiler_rt_dyn_lib = true;
2567 comp.link_task_queue.pending_prelink_tasks += 1;
2568 }2544 }
25692545
2570 if (comp.ubsan_rt_strat == .lib) {2546 if (comp.ubsan_rt_strat == .lib) {
2571 log.debug("queuing a job to build ubsan_rt_lib", .{});2547 log.debug("queuing a job to build ubsan_rt_lib", .{});
2572 comp.queued_jobs.ubsan_rt_lib = true;2548 comp.queued_jobs.ubsan_rt_lib = true;
2573 comp.link_task_queue.pending_prelink_tasks += 1;
2574 } else if (comp.ubsan_rt_strat == .obj) {2549 } else if (comp.ubsan_rt_strat == .obj) {
2575 log.debug("queuing a job to build ubsan_rt_obj", .{});2550 log.debug("queuing a job to build ubsan_rt_obj", .{});
2576 comp.queued_jobs.ubsan_rt_obj = true;2551 comp.queued_jobs.ubsan_rt_obj = true;
2577 comp.link_task_queue.pending_prelink_tasks += 1;
2578 }2552 }
25792553
2580 if (is_exe_or_dyn_lib and comp.config.any_fuzz) {2554 if (is_exe_or_dyn_lib and comp.config.any_fuzz) {
2581 log.debug("queuing a job to build libfuzzer", .{});2555 log.debug("queuing a job to build libfuzzer", .{});
2582 comp.queued_jobs.fuzzer_lib = true;2556 comp.queued_jobs.fuzzer_lib = true;
2583 comp.link_task_queue.pending_prelink_tasks += 1;
2584 }2557 }
2585 }2558 }
2586 }2559 }
...@@ -2588,8 +2561,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -2588,8 +2561,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
2588 try comp.link_task_queue.queued_prelink.append(gpa, .load_explicitly_provided);2561 try comp.link_task_queue.queued_prelink.append(gpa, .load_explicitly_provided);
2589 }2562 }
2590 log.debug("queued prelink tasks: {d}", .{comp.link_task_queue.queued_prelink.items.len});2563 log.debug("queued prelink tasks: {d}", .{comp.link_task_queue.queued_prelink.items.len});
2591 log.debug("pending prelink tasks: {d}", .{comp.link_task_queue.pending_prelink_tasks});
2592
2593 return comp;2564 return comp;
2594}2565}
25952566
...@@ -2608,6 +2579,7 @@ pub fn destroy(comp: *Compilation) void {...@@ -2608,6 +2579,7 @@ pub fn destroy(comp: *Compilation) void {
2608 comp.c_object_work_queue.deinit();2579 comp.c_object_work_queue.deinit();
2609 comp.win32_resource_work_queue.deinit();2580 comp.win32_resource_work_queue.deinit();
26102581
2582 for (comp.windows_libs.keys()) |windows_lib| gpa.free(windows_lib);
2611 comp.windows_libs.deinit(gpa);2583 comp.windows_libs.deinit(gpa);
26122584
2613 {2585 {
...@@ -2933,22 +2905,26 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2933,22 +2905,26 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
2933 try comp.appendFileSystemInput(embed_file.path);2905 try comp.appendFileSystemInput(embed_file.path);
2934 }2906 }
29352907
2936 zcu.analysis_roots.clear();2908 zcu.analysis_roots_len = 0;
29372909
2938 zcu.analysis_roots.appendAssumeCapacity(zcu.std_mod);2910 zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zcu.std_mod;
2911 zcu.analysis_roots_len += 1;
29392912
2940 // Normally we rely on importing std to in turn import the root source file in the start code.2913 // Normally we rely on importing std to in turn import the root source file in the start code.
2941 // However, the main module is distinct from the root module in tests, so that won't happen there.2914 // However, the main module is distinct from the root module in tests, so that won't happen there.
2942 if (comp.config.is_test and zcu.main_mod != zcu.std_mod) {2915 if (comp.config.is_test and zcu.main_mod != zcu.std_mod) {
2943 zcu.analysis_roots.appendAssumeCapacity(zcu.main_mod);2916 zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zcu.main_mod;
2917 zcu.analysis_roots_len += 1;
2944 }2918 }
29452919
2946 if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {2920 if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {
2947 zcu.analysis_roots.appendAssumeCapacity(compiler_rt_mod);2921 zcu.analysis_roots_buffer[zcu.analysis_roots_len] = compiler_rt_mod;
2922 zcu.analysis_roots_len += 1;
2948 }2923 }
29492924
2950 if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| {2925 if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| {
2951 zcu.analysis_roots.appendAssumeCapacity(ubsan_rt_mod);2926 zcu.analysis_roots_buffer[zcu.analysis_roots_len] = ubsan_rt_mod;
2927 zcu.analysis_roots_len += 1;
2952 }2928 }
2953 }2929 }
29542930
...@@ -4404,10 +4380,8 @@ fn performAllTheWork(...@@ -4404,10 +4380,8 @@ fn performAllTheWork(
4404 comp.link_task_wait_group.reset();4380 comp.link_task_wait_group.reset();
4405 defer comp.link_task_wait_group.wait();4381 defer comp.link_task_wait_group.wait();
44064382
4407 comp.link_prog_node.increaseEstimatedTotalItems(4383 // Already-queued prelink tasks
4408 comp.link_task_queue.queued_prelink.items.len + // already queued prelink tasks4384 comp.link_prog_node.increaseEstimatedTotalItems(comp.link_task_queue.queued_prelink.items.len);
4409 comp.link_task_queue.pending_prelink_tasks, // prelink tasks which will be queued
4410 );
4411 comp.link_task_queue.start(comp);4385 comp.link_task_queue.start(comp);
44124386
4413 if (comp.emit_docs != null) {4387 if (comp.emit_docs != null) {
...@@ -4423,6 +4397,7 @@ fn performAllTheWork(...@@ -4423,6 +4397,7 @@ fn performAllTheWork(
4423 // compiler-rt due to LLD bugs as well, e.g.:4397 // compiler-rt due to LLD bugs as well, e.g.:
4424 //4398 //
4425 // https://github.com/llvm/llvm-project/issues/43698#issuecomment-25426606114399 // https://github.com/llvm/llvm-project/issues/43698#issuecomment-2542660611
4400 comp.link_task_queue.startPrelinkItem();
4426 comp.link_task_wait_group.spawnManager(buildRt, .{4401 comp.link_task_wait_group.spawnManager(buildRt, .{
4427 comp,4402 comp,
4428 "compiler_rt.zig",4403 "compiler_rt.zig",
...@@ -4440,6 +4415,7 @@ fn performAllTheWork(...@@ -4440,6 +4415,7 @@ fn performAllTheWork(
4440 }4415 }
44414416
4442 if (comp.queued_jobs.compiler_rt_obj and comp.compiler_rt_obj == null) {4417 if (comp.queued_jobs.compiler_rt_obj and comp.compiler_rt_obj == null) {
4418 comp.link_task_queue.startPrelinkItem();
4443 comp.link_task_wait_group.spawnManager(buildRt, .{4419 comp.link_task_wait_group.spawnManager(buildRt, .{
4444 comp,4420 comp,
4445 "compiler_rt.zig",4421 "compiler_rt.zig",
...@@ -4458,6 +4434,7 @@ fn performAllTheWork(...@@ -4458,6 +4434,7 @@ fn performAllTheWork(
44584434
4459 // hack for stage2_x86_64 + coff4435 // hack for stage2_x86_64 + coff
4460 if (comp.queued_jobs.compiler_rt_dyn_lib and comp.compiler_rt_dyn_lib == null) {4436 if (comp.queued_jobs.compiler_rt_dyn_lib and comp.compiler_rt_dyn_lib == null) {
4437 comp.link_task_queue.startPrelinkItem();
4461 comp.link_task_wait_group.spawnManager(buildRt, .{4438 comp.link_task_wait_group.spawnManager(buildRt, .{
4462 comp,4439 comp,
4463 "compiler_rt.zig",4440 "compiler_rt.zig",
...@@ -4475,6 +4452,7 @@ fn performAllTheWork(...@@ -4475,6 +4452,7 @@ fn performAllTheWork(
4475 }4452 }
44764453
4477 if (comp.queued_jobs.fuzzer_lib and comp.fuzzer_lib == null) {4454 if (comp.queued_jobs.fuzzer_lib and comp.fuzzer_lib == null) {
4455 comp.link_task_queue.startPrelinkItem();
4478 comp.link_task_wait_group.spawnManager(buildRt, .{4456 comp.link_task_wait_group.spawnManager(buildRt, .{
4479 comp,4457 comp,
4480 "fuzzer.zig",4458 "fuzzer.zig",
...@@ -4489,6 +4467,7 @@ fn performAllTheWork(...@@ -4489,6 +4467,7 @@ fn performAllTheWork(
4489 }4467 }
44904468
4491 if (comp.queued_jobs.ubsan_rt_lib and comp.ubsan_rt_lib == null) {4469 if (comp.queued_jobs.ubsan_rt_lib and comp.ubsan_rt_lib == null) {
4470 comp.link_task_queue.startPrelinkItem();
4492 comp.link_task_wait_group.spawnManager(buildRt, .{4471 comp.link_task_wait_group.spawnManager(buildRt, .{
4493 comp,4472 comp,
4494 "ubsan_rt.zig",4473 "ubsan_rt.zig",
...@@ -4505,6 +4484,7 @@ fn performAllTheWork(...@@ -4505,6 +4484,7 @@ fn performAllTheWork(
4505 }4484 }
45064485
4507 if (comp.queued_jobs.ubsan_rt_obj and comp.ubsan_rt_obj == null) {4486 if (comp.queued_jobs.ubsan_rt_obj and comp.ubsan_rt_obj == null) {
4487 comp.link_task_queue.startPrelinkItem();
4508 comp.link_task_wait_group.spawnManager(buildRt, .{4488 comp.link_task_wait_group.spawnManager(buildRt, .{
4509 comp,4489 comp,
4510 "ubsan_rt.zig",4490 "ubsan_rt.zig",
...@@ -4521,40 +4501,49 @@ fn performAllTheWork(...@@ -4521,40 +4501,49 @@ fn performAllTheWork(
4521 }4501 }
45224502
4523 if (comp.queued_jobs.glibc_shared_objects) {4503 if (comp.queued_jobs.glibc_shared_objects) {
4504 comp.link_task_queue.startPrelinkItem();
4524 comp.link_task_wait_group.spawnManager(buildGlibcSharedObjects, .{ comp, main_progress_node });4505 comp.link_task_wait_group.spawnManager(buildGlibcSharedObjects, .{ comp, main_progress_node });
4525 }4506 }
45264507
4527 if (comp.queued_jobs.freebsd_shared_objects) {4508 if (comp.queued_jobs.freebsd_shared_objects) {
4509 comp.link_task_queue.startPrelinkItem();
4528 comp.link_task_wait_group.spawnManager(buildFreeBSDSharedObjects, .{ comp, main_progress_node });4510 comp.link_task_wait_group.spawnManager(buildFreeBSDSharedObjects, .{ comp, main_progress_node });
4529 }4511 }
45304512
4531 if (comp.queued_jobs.netbsd_shared_objects) {4513 if (comp.queued_jobs.netbsd_shared_objects) {
4514 comp.link_task_queue.startPrelinkItem();
4532 comp.link_task_wait_group.spawnManager(buildNetBSDSharedObjects, .{ comp, main_progress_node });4515 comp.link_task_wait_group.spawnManager(buildNetBSDSharedObjects, .{ comp, main_progress_node });
4533 }4516 }
45344517
4535 if (comp.queued_jobs.libunwind) {4518 if (comp.queued_jobs.libunwind) {
4519 comp.link_task_queue.startPrelinkItem();
4536 comp.link_task_wait_group.spawnManager(buildLibUnwind, .{ comp, main_progress_node });4520 comp.link_task_wait_group.spawnManager(buildLibUnwind, .{ comp, main_progress_node });
4537 }4521 }
45384522
4539 if (comp.queued_jobs.libcxx) {4523 if (comp.queued_jobs.libcxx) {
4524 comp.link_task_queue.startPrelinkItem();
4540 comp.link_task_wait_group.spawnManager(buildLibCxx, .{ comp, main_progress_node });4525 comp.link_task_wait_group.spawnManager(buildLibCxx, .{ comp, main_progress_node });
4541 }4526 }
45424527
4543 if (comp.queued_jobs.libcxxabi) {4528 if (comp.queued_jobs.libcxxabi) {
4529 comp.link_task_queue.startPrelinkItem();
4544 comp.link_task_wait_group.spawnManager(buildLibCxxAbi, .{ comp, main_progress_node });4530 comp.link_task_wait_group.spawnManager(buildLibCxxAbi, .{ comp, main_progress_node });
4545 }4531 }
45464532
4547 if (comp.queued_jobs.libtsan) {4533 if (comp.queued_jobs.libtsan) {
4534 comp.link_task_queue.startPrelinkItem();
4548 comp.link_task_wait_group.spawnManager(buildLibTsan, .{ comp, main_progress_node });4535 comp.link_task_wait_group.spawnManager(buildLibTsan, .{ comp, main_progress_node });
4549 }4536 }
45504537
4551 if (comp.queued_jobs.zigc_lib and comp.zigc_static_lib == null) {4538 if (comp.queued_jobs.zigc_lib and comp.zigc_static_lib == null) {
4539 comp.link_task_queue.startPrelinkItem();
4552 comp.link_task_wait_group.spawnManager(buildLibZigC, .{ comp, main_progress_node });4540 comp.link_task_wait_group.spawnManager(buildLibZigC, .{ comp, main_progress_node });
4553 }4541 }
45544542
4555 for (0..@typeInfo(musl.CrtFile).@"enum".fields.len) |i| {4543 for (0..@typeInfo(musl.CrtFile).@"enum".fields.len) |i| {
4556 if (comp.queued_jobs.musl_crt_file[i]) {4544 if (comp.queued_jobs.musl_crt_file[i]) {
4557 const tag: musl.CrtFile = @enumFromInt(i);4545 const tag: musl.CrtFile = @enumFromInt(i);
4546 comp.link_task_queue.startPrelinkItem();
4558 comp.link_task_wait_group.spawnManager(buildMuslCrtFile, .{ comp, tag, main_progress_node });4547 comp.link_task_wait_group.spawnManager(buildMuslCrtFile, .{ comp, tag, main_progress_node });
4559 }4548 }
4560 }4549 }
...@@ -4562,6 +4551,7 @@ fn performAllTheWork(...@@ -4562,6 +4551,7 @@ fn performAllTheWork(
4562 for (0..@typeInfo(glibc.CrtFile).@"enum".fields.len) |i| {4551 for (0..@typeInfo(glibc.CrtFile).@"enum".fields.len) |i| {
4563 if (comp.queued_jobs.glibc_crt_file[i]) {4552 if (comp.queued_jobs.glibc_crt_file[i]) {
4564 const tag: glibc.CrtFile = @enumFromInt(i);4553 const tag: glibc.CrtFile = @enumFromInt(i);
4554 comp.link_task_queue.startPrelinkItem();
4565 comp.link_task_wait_group.spawnManager(buildGlibcCrtFile, .{ comp, tag, main_progress_node });4555 comp.link_task_wait_group.spawnManager(buildGlibcCrtFile, .{ comp, tag, main_progress_node });
4566 }4556 }
4567 }4557 }
...@@ -4569,6 +4559,7 @@ fn performAllTheWork(...@@ -4569,6 +4559,7 @@ fn performAllTheWork(
4569 for (0..@typeInfo(freebsd.CrtFile).@"enum".fields.len) |i| {4559 for (0..@typeInfo(freebsd.CrtFile).@"enum".fields.len) |i| {
4570 if (comp.queued_jobs.freebsd_crt_file[i]) {4560 if (comp.queued_jobs.freebsd_crt_file[i]) {
4571 const tag: freebsd.CrtFile = @enumFromInt(i);4561 const tag: freebsd.CrtFile = @enumFromInt(i);
4562 comp.link_task_queue.startPrelinkItem();
4572 comp.link_task_wait_group.spawnManager(buildFreeBSDCrtFile, .{ comp, tag, main_progress_node });4563 comp.link_task_wait_group.spawnManager(buildFreeBSDCrtFile, .{ comp, tag, main_progress_node });
4573 }4564 }
4574 }4565 }
...@@ -4576,6 +4567,7 @@ fn performAllTheWork(...@@ -4576,6 +4567,7 @@ fn performAllTheWork(
4576 for (0..@typeInfo(netbsd.CrtFile).@"enum".fields.len) |i| {4567 for (0..@typeInfo(netbsd.CrtFile).@"enum".fields.len) |i| {
4577 if (comp.queued_jobs.netbsd_crt_file[i]) {4568 if (comp.queued_jobs.netbsd_crt_file[i]) {
4578 const tag: netbsd.CrtFile = @enumFromInt(i);4569 const tag: netbsd.CrtFile = @enumFromInt(i);
4570 comp.link_task_queue.startPrelinkItem();
4579 comp.link_task_wait_group.spawnManager(buildNetBSDCrtFile, .{ comp, tag, main_progress_node });4571 comp.link_task_wait_group.spawnManager(buildNetBSDCrtFile, .{ comp, tag, main_progress_node });
4580 }4572 }
4581 }4573 }
...@@ -4583,6 +4575,7 @@ fn performAllTheWork(...@@ -4583,6 +4575,7 @@ fn performAllTheWork(
4583 for (0..@typeInfo(wasi_libc.CrtFile).@"enum".fields.len) |i| {4575 for (0..@typeInfo(wasi_libc.CrtFile).@"enum".fields.len) |i| {
4584 if (comp.queued_jobs.wasi_libc_crt_file[i]) {4576 if (comp.queued_jobs.wasi_libc_crt_file[i]) {
4585 const tag: wasi_libc.CrtFile = @enumFromInt(i);4577 const tag: wasi_libc.CrtFile = @enumFromInt(i);
4578 comp.link_task_queue.startPrelinkItem();
4586 comp.link_task_wait_group.spawnManager(buildWasiLibcCrtFile, .{ comp, tag, main_progress_node });4579 comp.link_task_wait_group.spawnManager(buildWasiLibcCrtFile, .{ comp, tag, main_progress_node });
4587 }4580 }
4588 }4581 }
...@@ -4590,6 +4583,7 @@ fn performAllTheWork(...@@ -4590,6 +4583,7 @@ fn performAllTheWork(
4590 for (0..@typeInfo(mingw.CrtFile).@"enum".fields.len) |i| {4583 for (0..@typeInfo(mingw.CrtFile).@"enum".fields.len) |i| {
4591 if (comp.queued_jobs.mingw_crt_file[i]) {4584 if (comp.queued_jobs.mingw_crt_file[i]) {
4592 const tag: mingw.CrtFile = @enumFromInt(i);4585 const tag: mingw.CrtFile = @enumFromInt(i);
4586 comp.link_task_queue.startPrelinkItem();
4593 comp.link_task_wait_group.spawnManager(buildMingwCrtFile, .{ comp, tag, main_progress_node });4587 comp.link_task_wait_group.spawnManager(buildMingwCrtFile, .{ comp, tag, main_progress_node });
4594 }4588 }
4595 }4589 }
...@@ -4661,12 +4655,14 @@ fn performAllTheWork(...@@ -4661,12 +4655,14 @@ fn performAllTheWork(
4661 }4655 }
46624656
4663 while (comp.c_object_work_queue.readItem()) |c_object| {4657 while (comp.c_object_work_queue.readItem()) |c_object| {
4658 comp.link_task_queue.startPrelinkItem();
4664 comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateCObject, .{4659 comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateCObject, .{
4665 comp, c_object, main_progress_node,4660 comp, c_object, main_progress_node,
4666 });4661 });
4667 }4662 }
46684663
4669 while (comp.win32_resource_work_queue.readItem()) |win32_resource| {4664 while (comp.win32_resource_work_queue.readItem()) |win32_resource| {
4665 comp.link_task_queue.startPrelinkItem();
4670 comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateWin32Resource, .{4666 comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateWin32Resource, .{
4671 comp, win32_resource, main_progress_node,4667 comp, win32_resource, main_progress_node,
4672 });4668 });
...@@ -4745,7 +4741,7 @@ fn performAllTheWork(...@@ -4745,7 +4741,7 @@ fn performAllTheWork(
4745 try zcu.flushRetryableFailures();4741 try zcu.flushRetryableFailures();
47464742
4747 // It's analysis time! Queue up our initial analysis.4743 // It's analysis time! Queue up our initial analysis.
4748 for (zcu.analysis_roots.slice()) |mod| {4744 for (zcu.analysisRoots()) |mod| {
4749 try comp.queueJob(.{ .analyze_mod = mod });4745 try comp.queueJob(.{ .analyze_mod = mod });
4750 }4746 }
47514747
...@@ -4769,15 +4765,14 @@ fn performAllTheWork(...@@ -4769,15 +4765,14 @@ fn performAllTheWork(
4769 }4765 }
4770 };4766 };
47714767
4768 // We aren't going to queue any more prelink tasks.
4769 comp.link_task_queue.finishPrelinkItem(comp);
4770
4772 if (!comp.separateCodegenThreadOk()) {4771 if (!comp.separateCodegenThreadOk()) {
4773 // Waits until all input files have been parsed.4772 // Waits until all input files have been parsed.
4774 comp.link_task_wait_group.wait();4773 comp.link_task_wait_group.wait();
4775 comp.link_task_wait_group.reset();4774 comp.link_task_wait_group.reset();
4776 std.log.scoped(.link).debug("finished waiting for link_task_wait_group", .{});4775 std.log.scoped(.link).debug("finished waiting for link_task_wait_group", .{});
4777 if (comp.link_task_queue.pending_prelink_tasks > 0) {
4778 // Indicates an error occurred preventing prelink phase from completing.
4779 return;
4780 }
4781 }4776 }
47824777
4783 if (comp.zcu != null) {4778 if (comp.zcu != null) {
...@@ -5564,6 +5559,7 @@ fn workerUpdateCObject(...@@ -5564,6 +5559,7 @@ fn workerUpdateCObject(
5564 c_object: *CObject,5559 c_object: *CObject,
5565 progress_node: std.Progress.Node,5560 progress_node: std.Progress.Node,
5566) void {5561) void {
5562 defer comp.link_task_queue.finishPrelinkItem(comp);
5567 comp.updateCObject(c_object, progress_node) catch |err| switch (err) {5563 comp.updateCObject(c_object, progress_node) catch |err| switch (err) {
5568 error.AnalysisFail => return,5564 error.AnalysisFail => return,
5569 else => {5565 else => {
...@@ -5581,6 +5577,7 @@ fn workerUpdateWin32Resource(...@@ -5581,6 +5577,7 @@ fn workerUpdateWin32Resource(
5581 win32_resource: *Win32Resource,5577 win32_resource: *Win32Resource,
5582 progress_node: std.Progress.Node,5578 progress_node: std.Progress.Node,
5583) void {5579) void {
5580 defer comp.link_task_queue.finishPrelinkItem(comp);
5584 comp.updateWin32Resource(win32_resource, progress_node) catch |err| switch (err) {5581 comp.updateWin32Resource(win32_resource, progress_node) catch |err| switch (err) {
5585 error.AnalysisFail => return,5582 error.AnalysisFail => return,
5586 else => {5583 else => {
...@@ -5624,6 +5621,7 @@ fn buildRt(...@@ -5624,6 +5621,7 @@ fn buildRt(
5624 options: RtOptions,5621 options: RtOptions,
5625 out: *?CrtFile,5622 out: *?CrtFile,
5626) void {5623) void {
5624 defer comp.link_task_queue.finishPrelinkItem(comp);
5627 comp.buildOutputFromZig(5625 comp.buildOutputFromZig(
5628 root_source_name,5626 root_source_name,
5629 root_name,5627 root_name,
...@@ -5642,6 +5640,7 @@ fn buildRt(...@@ -5642,6 +5640,7 @@ fn buildRt(
5642}5640}
56435641
5644fn buildMuslCrtFile(comp: *Compilation, crt_file: musl.CrtFile, prog_node: std.Progress.Node) void {5642fn buildMuslCrtFile(comp: *Compilation, crt_file: musl.CrtFile, prog_node: std.Progress.Node) void {
5643 defer comp.link_task_queue.finishPrelinkItem(comp);
5645 if (musl.buildCrtFile(comp, crt_file, prog_node)) |_| {5644 if (musl.buildCrtFile(comp, crt_file, prog_node)) |_| {
5646 comp.queued_jobs.musl_crt_file[@intFromEnum(crt_file)] = false;5645 comp.queued_jobs.musl_crt_file[@intFromEnum(crt_file)] = false;
5647 } else |err| switch (err) {5646 } else |err| switch (err) {
...@@ -5653,6 +5652,7 @@ fn buildMuslCrtFile(comp: *Compilation, crt_file: musl.CrtFile, prog_node: std.P...@@ -5653,6 +5652,7 @@ fn buildMuslCrtFile(comp: *Compilation, crt_file: musl.CrtFile, prog_node: std.P
5653}5652}
56545653
5655fn buildGlibcCrtFile(comp: *Compilation, crt_file: glibc.CrtFile, prog_node: std.Progress.Node) void {5654fn buildGlibcCrtFile(comp: *Compilation, crt_file: glibc.CrtFile, prog_node: std.Progress.Node) void {
5655 defer comp.link_task_queue.finishPrelinkItem(comp);
5656 if (glibc.buildCrtFile(comp, crt_file, prog_node)) |_| {5656 if (glibc.buildCrtFile(comp, crt_file, prog_node)) |_| {
5657 comp.queued_jobs.glibc_crt_file[@intFromEnum(crt_file)] = false;5657 comp.queued_jobs.glibc_crt_file[@intFromEnum(crt_file)] = false;
5658 } else |err| switch (err) {5658 } else |err| switch (err) {
...@@ -5664,6 +5664,7 @@ fn buildGlibcCrtFile(comp: *Compilation, crt_file: glibc.CrtFile, prog_node: std...@@ -5664,6 +5664,7 @@ fn buildGlibcCrtFile(comp: *Compilation, crt_file: glibc.CrtFile, prog_node: std
5664}5664}
56655665
5666fn buildGlibcSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) void {5666fn buildGlibcSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) void {
5667 defer comp.link_task_queue.finishPrelinkItem(comp);
5667 if (glibc.buildSharedObjects(comp, prog_node)) |_| {5668 if (glibc.buildSharedObjects(comp, prog_node)) |_| {
5668 // The job should no longer be queued up since it succeeded.5669 // The job should no longer be queued up since it succeeded.
5669 comp.queued_jobs.glibc_shared_objects = false;5670 comp.queued_jobs.glibc_shared_objects = false;
...@@ -5676,6 +5677,7 @@ fn buildGlibcSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) voi...@@ -5676,6 +5677,7 @@ fn buildGlibcSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) voi
5676}5677}
56775678
5678fn buildFreeBSDCrtFile(comp: *Compilation, crt_file: freebsd.CrtFile, prog_node: std.Progress.Node) void {5679fn buildFreeBSDCrtFile(comp: *Compilation, crt_file: freebsd.CrtFile, prog_node: std.Progress.Node) void {
5680 defer comp.link_task_queue.finishPrelinkItem(comp);
5679 if (freebsd.buildCrtFile(comp, crt_file, prog_node)) |_| {5681 if (freebsd.buildCrtFile(comp, crt_file, prog_node)) |_| {
5680 comp.queued_jobs.freebsd_crt_file[@intFromEnum(crt_file)] = false;5682 comp.queued_jobs.freebsd_crt_file[@intFromEnum(crt_file)] = false;
5681 } else |err| switch (err) {5683 } else |err| switch (err) {
...@@ -5687,6 +5689,7 @@ fn buildFreeBSDCrtFile(comp: *Compilation, crt_file: freebsd.CrtFile, prog_node:...@@ -5687,6 +5689,7 @@ fn buildFreeBSDCrtFile(comp: *Compilation, crt_file: freebsd.CrtFile, prog_node:
5687}5689}
56885690
5689fn buildFreeBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) void {5691fn buildFreeBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) void {
5692 defer comp.link_task_queue.finishPrelinkItem(comp);
5690 if (freebsd.buildSharedObjects(comp, prog_node)) |_| {5693 if (freebsd.buildSharedObjects(comp, prog_node)) |_| {
5691 // The job should no longer be queued up since it succeeded.5694 // The job should no longer be queued up since it succeeded.
5692 comp.queued_jobs.freebsd_shared_objects = false;5695 comp.queued_jobs.freebsd_shared_objects = false;
...@@ -5699,6 +5702,7 @@ fn buildFreeBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) v...@@ -5699,6 +5702,7 @@ fn buildFreeBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) v
5699}5702}
57005703
5701fn buildNetBSDCrtFile(comp: *Compilation, crt_file: netbsd.CrtFile, prog_node: std.Progress.Node) void {5704fn buildNetBSDCrtFile(comp: *Compilation, crt_file: netbsd.CrtFile, prog_node: std.Progress.Node) void {
5705 defer comp.link_task_queue.finishPrelinkItem(comp);
5702 if (netbsd.buildCrtFile(comp, crt_file, prog_node)) |_| {5706 if (netbsd.buildCrtFile(comp, crt_file, prog_node)) |_| {
5703 comp.queued_jobs.netbsd_crt_file[@intFromEnum(crt_file)] = false;5707 comp.queued_jobs.netbsd_crt_file[@intFromEnum(crt_file)] = false;
5704 } else |err| switch (err) {5708 } else |err| switch (err) {
...@@ -5710,6 +5714,7 @@ fn buildNetBSDCrtFile(comp: *Compilation, crt_file: netbsd.CrtFile, prog_node: s...@@ -5710,6 +5714,7 @@ fn buildNetBSDCrtFile(comp: *Compilation, crt_file: netbsd.CrtFile, prog_node: s
5710}5714}
57115715
5712fn buildNetBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) void {5716fn buildNetBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) void {
5717 defer comp.link_task_queue.finishPrelinkItem(comp);
5713 if (netbsd.buildSharedObjects(comp, prog_node)) |_| {5718 if (netbsd.buildSharedObjects(comp, prog_node)) |_| {
5714 // The job should no longer be queued up since it succeeded.5719 // The job should no longer be queued up since it succeeded.
5715 comp.queued_jobs.netbsd_shared_objects = false;5720 comp.queued_jobs.netbsd_shared_objects = false;
...@@ -5722,6 +5727,7 @@ fn buildNetBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) vo...@@ -5722,6 +5727,7 @@ fn buildNetBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) vo
5722}5727}
57235728
5724fn buildMingwCrtFile(comp: *Compilation, crt_file: mingw.CrtFile, prog_node: std.Progress.Node) void {5729fn buildMingwCrtFile(comp: *Compilation, crt_file: mingw.CrtFile, prog_node: std.Progress.Node) void {
5730 defer comp.link_task_queue.finishPrelinkItem(comp);
5725 if (mingw.buildCrtFile(comp, crt_file, prog_node)) |_| {5731 if (mingw.buildCrtFile(comp, crt_file, prog_node)) |_| {
5726 comp.queued_jobs.mingw_crt_file[@intFromEnum(crt_file)] = false;5732 comp.queued_jobs.mingw_crt_file[@intFromEnum(crt_file)] = false;
5727 } else |err| switch (err) {5733 } else |err| switch (err) {
...@@ -5733,6 +5739,7 @@ fn buildMingwCrtFile(comp: *Compilation, crt_file: mingw.CrtFile, prog_node: std...@@ -5733,6 +5739,7 @@ fn buildMingwCrtFile(comp: *Compilation, crt_file: mingw.CrtFile, prog_node: std
5733}5739}
57345740
5735fn buildWasiLibcCrtFile(comp: *Compilation, crt_file: wasi_libc.CrtFile, prog_node: std.Progress.Node) void {5741fn buildWasiLibcCrtFile(comp: *Compilation, crt_file: wasi_libc.CrtFile, prog_node: std.Progress.Node) void {
5742 defer comp.link_task_queue.finishPrelinkItem(comp);
5736 if (wasi_libc.buildCrtFile(comp, crt_file, prog_node)) |_| {5743 if (wasi_libc.buildCrtFile(comp, crt_file, prog_node)) |_| {
5737 comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(crt_file)] = false;5744 comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(crt_file)] = false;
5738 } else |err| switch (err) {5745 } else |err| switch (err) {
...@@ -5744,6 +5751,7 @@ fn buildWasiLibcCrtFile(comp: *Compilation, crt_file: wasi_libc.CrtFile, prog_no...@@ -5744,6 +5751,7 @@ fn buildWasiLibcCrtFile(comp: *Compilation, crt_file: wasi_libc.CrtFile, prog_no
5744}5751}
57455752
5746fn buildLibUnwind(comp: *Compilation, prog_node: std.Progress.Node) void {5753fn buildLibUnwind(comp: *Compilation, prog_node: std.Progress.Node) void {
5754 defer comp.link_task_queue.finishPrelinkItem(comp);
5747 if (libunwind.buildStaticLib(comp, prog_node)) |_| {5755 if (libunwind.buildStaticLib(comp, prog_node)) |_| {
5748 comp.queued_jobs.libunwind = false;5756 comp.queued_jobs.libunwind = false;
5749 } else |err| switch (err) {5757 } else |err| switch (err) {
...@@ -5753,6 +5761,7 @@ fn buildLibUnwind(comp: *Compilation, prog_node: std.Progress.Node) void {...@@ -5753,6 +5761,7 @@ fn buildLibUnwind(comp: *Compilation, prog_node: std.Progress.Node) void {
5753}5761}
57545762
5755fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) void {5763fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) void {
5764 defer comp.link_task_queue.finishPrelinkItem(comp);
5756 if (libcxx.buildLibCxx(comp, prog_node)) |_| {5765 if (libcxx.buildLibCxx(comp, prog_node)) |_| {
5757 comp.queued_jobs.libcxx = false;5766 comp.queued_jobs.libcxx = false;
5758 } else |err| switch (err) {5767 } else |err| switch (err) {
...@@ -5762,6 +5771,7 @@ fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) void {...@@ -5762,6 +5771,7 @@ fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) void {
5762}5771}
57635772
5764fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) void {5773fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) void {
5774 defer comp.link_task_queue.finishPrelinkItem(comp);
5765 if (libcxx.buildLibCxxAbi(comp, prog_node)) |_| {5775 if (libcxx.buildLibCxxAbi(comp, prog_node)) |_| {
5766 comp.queued_jobs.libcxxabi = false;5776 comp.queued_jobs.libcxxabi = false;
5767 } else |err| switch (err) {5777 } else |err| switch (err) {
...@@ -5771,6 +5781,7 @@ fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) void {...@@ -5771,6 +5781,7 @@ fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) void {
5771}5781}
57725782
5773fn buildLibTsan(comp: *Compilation, prog_node: std.Progress.Node) void {5783fn buildLibTsan(comp: *Compilation, prog_node: std.Progress.Node) void {
5784 defer comp.link_task_queue.finishPrelinkItem(comp);
5774 if (libtsan.buildTsan(comp, prog_node)) |_| {5785 if (libtsan.buildTsan(comp, prog_node)) |_| {
5775 comp.queued_jobs.libtsan = false;5786 comp.queued_jobs.libtsan = false;
5776 } else |err| switch (err) {5787 } else |err| switch (err) {
...@@ -5780,6 +5791,7 @@ fn buildLibTsan(comp: *Compilation, prog_node: std.Progress.Node) void {...@@ -5780,6 +5791,7 @@ fn buildLibTsan(comp: *Compilation, prog_node: std.Progress.Node) void {
5780}5791}
57815792
5782fn buildLibZigC(comp: *Compilation, prog_node: std.Progress.Node) void {5793fn buildLibZigC(comp: *Compilation, prog_node: std.Progress.Node) void {
5794 defer comp.link_task_queue.finishPrelinkItem(comp);
5783 comp.buildOutputFromZig(5795 comp.buildOutputFromZig(
5784 "c.zig",5796 "c.zig",
5785 "zigc",5797 "zigc",
...@@ -7717,6 +7729,7 @@ pub fn queuePrelinkTaskMode(comp: *Compilation, path: Cache.Path, config: *const...@@ -7717,6 +7729,7 @@ pub fn queuePrelinkTaskMode(comp: *Compilation, path: Cache.Path, config: *const
7717/// Only valid to call during `update`. Automatically handles queuing up a7729/// Only valid to call during `update`. Automatically handles queuing up a
7718/// linker worker task if there is not already one.7730/// linker worker task if there is not already one.
7719pub fn queuePrelinkTasks(comp: *Compilation, tasks: []const link.PrelinkTask) void {7731pub fn queuePrelinkTasks(comp: *Compilation, tasks: []const link.PrelinkTask) void {
7732 comp.link_prog_node.increaseEstimatedTotalItems(tasks.len);
7720 comp.link_task_queue.enqueuePrelink(comp, tasks) catch |err| switch (err) {7733 comp.link_task_queue.enqueuePrelink(comp, tasks) catch |err| switch (err) {
7721 error.OutOfMemory => return comp.setAllocFailure(),7734 error.OutOfMemory => return comp.setAllocFailure(),
7722 };7735 };
...@@ -7789,6 +7802,27 @@ fn getCrtPathsInner(...@@ -7789,6 +7802,27 @@ fn getCrtPathsInner(
7789 };7802 };
7790}7803}
77917804
7805pub fn addLinkLib(comp: *Compilation, lib_name: []const u8) !void {
7806 // Avoid deadlocking on building import libs such as kernel32.lib
7807 // This can happen when the user uses `build-exe foo.obj -lkernel32` and
7808 // then when we create a sub-Compilation for zig libc, it also tries to
7809 // build kernel32.lib.
7810 if (comp.skip_linker_dependencies) return;
7811 const target = &comp.root_mod.resolved_target.result;
7812 if (target.os.tag != .windows or target.ofmt == .c) return;
7813
7814 // This happens when an `extern "foo"` function is referenced.
7815 // If we haven't seen this library yet and we're targeting Windows, we need
7816 // to queue up a work item to produce the DLL import library for this.
7817 const gop = try comp.windows_libs.getOrPut(comp.gpa, lib_name);
7818 if (gop.found_existing) return;
7819 {
7820 errdefer _ = comp.windows_libs.pop();
7821 gop.key_ptr.* = try comp.gpa.dupe(u8, lib_name);
7822 }
7823 try comp.queueJob(.{ .windows_import_lib = gop.index });
7824}
7825
7792/// This decides the optimization mode for all zig-provided libraries, including7826/// This decides the optimization mode for all zig-provided libraries, including
7793/// compiler-rt, libcxx, libc, libunwind, etc.7827/// compiler-rt, libcxx, libc, libunwind, etc.
7794pub fn compilerRtOptMode(comp: Compilation) std.builtin.OptimizeMode {7828pub fn compilerRtOptMode(comp: Compilation) std.builtin.OptimizeMode {
src/InternPool.zig+22-16
...@@ -1137,13 +1137,16 @@ const Local = struct {...@@ -1137,13 +1137,16 @@ const Local = struct {
1137 const elem_info = @typeInfo(Elem).@"struct";1137 const elem_info = @typeInfo(Elem).@"struct";
1138 const elem_fields = elem_info.fields;1138 const elem_fields = elem_info.fields;
1139 var new_fields: [elem_fields.len]std.builtin.Type.StructField = undefined;1139 var new_fields: [elem_fields.len]std.builtin.Type.StructField = undefined;
1140 for (&new_fields, elem_fields) |*new_field, elem_field| new_field.* = .{1140 for (&new_fields, elem_fields) |*new_field, elem_field| {
1141 .name = elem_field.name,1141 const T = *[len]elem_field.type;
1142 .type = *[len]elem_field.type,1142 new_field.* = .{
1143 .default_value_ptr = null,1143 .name = elem_field.name,
1144 .is_comptime = false,1144 .type = T,
1145 .alignment = 0,1145 .default_value_ptr = null,
1146 };1146 .is_comptime = false,
1147 .alignment = @alignOf(T),
1148 };
1149 }
1147 return @Type(.{ .@"struct" = .{1150 return @Type(.{ .@"struct" = .{
1148 .layout = .auto,1151 .layout = .auto,
1149 .fields = &new_fields,1152 .fields = &new_fields,
...@@ -1158,22 +1161,25 @@ const Local = struct {...@@ -1158,22 +1161,25 @@ const Local = struct {
1158 const elem_info = @typeInfo(Elem).@"struct";1161 const elem_info = @typeInfo(Elem).@"struct";
1159 const elem_fields = elem_info.fields;1162 const elem_fields = elem_info.fields;
1160 var new_fields: [elem_fields.len]std.builtin.Type.StructField = undefined;1163 var new_fields: [elem_fields.len]std.builtin.Type.StructField = undefined;
1161 for (&new_fields, elem_fields) |*new_field, elem_field| new_field.* = .{1164 for (&new_fields, elem_fields) |*new_field, elem_field| {
1162 .name = elem_field.name,1165 const T = @Type(.{ .pointer = .{
1163 .type = @Type(.{ .pointer = .{
1164 .size = opts.size,1166 .size = opts.size,
1165 .is_const = opts.is_const,1167 .is_const = opts.is_const,
1166 .is_volatile = false,1168 .is_volatile = false,
1167 .alignment = 0,1169 .alignment = @alignOf(elem_field.type),
1168 .address_space = .generic,1170 .address_space = .generic,
1169 .child = elem_field.type,1171 .child = elem_field.type,
1170 .is_allowzero = false,1172 .is_allowzero = false,
1171 .sentinel_ptr = null,1173 .sentinel_ptr = null,
1172 } }),1174 } });
1173 .default_value_ptr = null,1175 new_field.* = .{
1174 .is_comptime = false,1176 .name = elem_field.name,
1175 .alignment = 0,1177 .type = T,
1176 };1178 .default_value_ptr = null,
1179 .is_comptime = false,
1180 .alignment = @alignOf(T),
1181 };
1182 }
1177 return @Type(.{ .@"struct" = .{1183 return @Type(.{ .@"struct" = .{
1178 .layout = .auto,1184 .layout = .auto,
1179 .fields = &new_fields,1185 .fields = &new_fields,
src/Package/Fetch.zig+148-193
...@@ -385,21 +385,23 @@ pub fn run(f: *Fetch) RunError!void {...@@ -385,21 +385,23 @@ pub fn run(f: *Fetch) RunError!void {
385 var resource: Resource = .{ .dir = dir };385 var resource: Resource = .{ .dir = dir };
386 return f.runResource(path_or_url, &resource, null);386 return f.runResource(path_or_url, &resource, null);
387 } else |dir_err| {387 } else |dir_err| {
388 var server_header_buffer: [init_resource_buffer_size]u8 = undefined;
389
388 const file_err = if (dir_err == error.NotDir) e: {390 const file_err = if (dir_err == error.NotDir) e: {
389 if (fs.cwd().openFile(path_or_url, .{})) |file| {391 if (fs.cwd().openFile(path_or_url, .{})) |file| {
390 var resource: Resource = .{ .file = file };392 var resource: Resource = .{ .file = file.reader(&server_header_buffer) };
391 return f.runResource(path_or_url, &resource, null);393 return f.runResource(path_or_url, &resource, null);
392 } else |err| break :e err;394 } else |err| break :e err;
393 } else dir_err;395 } else dir_err;
394396
395 const uri = std.Uri.parse(path_or_url) catch |uri_err| {397 const uri = std.Uri.parse(path_or_url) catch |uri_err| {
396 return f.fail(0, try eb.printString(398 return f.fail(0, try eb.printString(
397 "'{s}' could not be recognized as a file path ({s}) or an URL ({s})",399 "'{s}' could not be recognized as a file path ({t}) or an URL ({t})",
398 .{ path_or_url, @errorName(file_err), @errorName(uri_err) },400 .{ path_or_url, file_err, uri_err },
399 ));401 ));
400 };402 };
401 var server_header_buffer: [header_buffer_size]u8 = undefined;403 var resource: Resource = undefined;
402 var resource = try f.initResource(uri, &server_header_buffer);404 try f.initResource(uri, &resource, &server_header_buffer);
403 return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, null);405 return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, null);
404 }406 }
405 },407 },
...@@ -464,8 +466,9 @@ pub fn run(f: *Fetch) RunError!void {...@@ -464,8 +466,9 @@ pub fn run(f: *Fetch) RunError!void {
464 f.location_tok,466 f.location_tok,
465 try eb.printString("invalid URI: {s}", .{@errorName(err)}),467 try eb.printString("invalid URI: {s}", .{@errorName(err)}),
466 );468 );
467 var server_header_buffer: [header_buffer_size]u8 = undefined;469 var buffer: [init_resource_buffer_size]u8 = undefined;
468 var resource = try f.initResource(uri, &server_header_buffer);470 var resource: Resource = undefined;
471 try f.initResource(uri, &resource, &buffer);
469 return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, remote.hash);472 return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, remote.hash);
470}473}
471474
...@@ -866,8 +869,8 @@ fn fail(f: *Fetch, msg_tok: std.zig.Ast.TokenIndex, msg_str: u32) RunError {...@@ -866,8 +869,8 @@ fn fail(f: *Fetch, msg_tok: std.zig.Ast.TokenIndex, msg_str: u32) RunError {
866}869}
867870
868const Resource = union(enum) {871const Resource = union(enum) {
869 file: fs.File,872 file: fs.File.Reader,
870 http_request: std.http.Client.Request,873 http_request: HttpRequest,
871 git: Git,874 git: Git,
872 dir: fs.Dir,875 dir: fs.Dir,
873876
...@@ -877,10 +880,16 @@ const Resource = union(enum) {...@@ -877,10 +880,16 @@ const Resource = union(enum) {
877 want_oid: git.Oid,880 want_oid: git.Oid,
878 };881 };
879882
883 const HttpRequest = struct {
884 request: std.http.Client.Request,
885 response: std.http.Client.Response,
886 buffer: []u8,
887 };
888
880 fn deinit(resource: *Resource) void {889 fn deinit(resource: *Resource) void {
881 switch (resource.*) {890 switch (resource.*) {
882 .file => |*file| file.close(),891 .file => |*file_reader| file_reader.file.close(),
883 .http_request => |*req| req.deinit(),892 .http_request => |*http_request| http_request.request.deinit(),
884 .git => |*git_resource| {893 .git => |*git_resource| {
885 git_resource.fetch_stream.deinit();894 git_resource.fetch_stream.deinit();
886 git_resource.session.deinit();895 git_resource.session.deinit();
...@@ -890,21 +899,13 @@ const Resource = union(enum) {...@@ -890,21 +899,13 @@ const Resource = union(enum) {
890 resource.* = undefined;899 resource.* = undefined;
891 }900 }
892901
893 fn reader(resource: *Resource) std.io.AnyReader {902 fn reader(resource: *Resource) *std.Io.Reader {
894 return .{903 return switch (resource.*) {
895 .context = resource,904 .file => |*file_reader| return &file_reader.interface,
896 .readFn = read,905 .http_request => |*http_request| return http_request.response.reader(http_request.buffer),
897 };906 .git => |*g| return &g.fetch_stream.reader,
898 }
899
900 fn read(context: *const anyopaque, buffer: []u8) anyerror!usize {
901 const resource: *Resource = @constCast(@ptrCast(@alignCast(context)));
902 switch (resource.*) {
903 .file => |*f| return f.read(buffer),
904 .http_request => |*r| return r.read(buffer),
905 .git => |*g| return g.fetch_stream.read(buffer),
906 .dir => unreachable,907 .dir => unreachable,
907 }908 };
908 }909 }
909};910};
910911
...@@ -967,20 +968,22 @@ const FileType = enum {...@@ -967,20 +968,22 @@ const FileType = enum {
967 }968 }
968};969};
969970
970const header_buffer_size = 16 * 1024;971const init_resource_buffer_size = git.Packet.max_data_length;
971972
972fn initResource(f: *Fetch, uri: std.Uri, server_header_buffer: []u8) RunError!Resource {973fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u8) RunError!void {
973 const gpa = f.arena.child_allocator;974 const gpa = f.arena.child_allocator;
974 const arena = f.arena.allocator();975 const arena = f.arena.allocator();
975 const eb = &f.error_bundle;976 const eb = &f.error_bundle;
976977
977 if (ascii.eqlIgnoreCase(uri.scheme, "file")) {978 if (ascii.eqlIgnoreCase(uri.scheme, "file")) {
978 const path = try uri.path.toRawMaybeAlloc(arena);979 const path = try uri.path.toRawMaybeAlloc(arena);
979 return .{ .file = f.parent_package_root.openFile(path, .{}) catch |err| {980 const file = f.parent_package_root.openFile(path, .{}) catch |err| {
980 return f.fail(f.location_tok, try eb.printString("unable to open '{f}{s}': {s}", .{981 return f.fail(f.location_tok, try eb.printString("unable to open '{f}{s}': {t}", .{
981 f.parent_package_root, path, @errorName(err),982 f.parent_package_root, path, err,
982 }));983 }));
983 } };984 };
985 resource.* = .{ .file = file.reader(reader_buffer) };
986 return;
984 }987 }
985988
986 const http_client = f.job_queue.http_client;989 const http_client = f.job_queue.http_client;
...@@ -988,37 +991,35 @@ fn initResource(f: *Fetch, uri: std.Uri, server_header_buffer: []u8) RunError!Re...@@ -988,37 +991,35 @@ fn initResource(f: *Fetch, uri: std.Uri, server_header_buffer: []u8) RunError!Re
988 if (ascii.eqlIgnoreCase(uri.scheme, "http") or991 if (ascii.eqlIgnoreCase(uri.scheme, "http") or
989 ascii.eqlIgnoreCase(uri.scheme, "https"))992 ascii.eqlIgnoreCase(uri.scheme, "https"))
990 {993 {
991 var req = http_client.open(.GET, uri, .{994 resource.* = .{ .http_request = .{
992 .server_header_buffer = server_header_buffer,995 .request = http_client.request(.GET, uri, .{}) catch |err|
993 }) catch |err| {996 return f.fail(f.location_tok, try eb.printString("unable to connect to server: {t}", .{err})),
994 return f.fail(f.location_tok, try eb.printString(997 .response = undefined,
995 "unable to connect to server: {s}",998 .buffer = reader_buffer,
996 .{@errorName(err)},999 } };
997 ));1000 const request = &resource.http_request.request;
998 };1001 errdefer request.deinit();
999 errdefer req.deinit(); // releases more than memory1002
10001003 request.sendBodiless() catch |err|
1001 req.send() catch |err| {1004 return f.fail(f.location_tok, try eb.printString("HTTP request failed: {t}", .{err}));
1002 return f.fail(f.location_tok, try eb.printString(1005
1003 "HTTP request failed: {s}",1006 var redirect_buffer: [1024]u8 = undefined;
1004 .{@errorName(err)},1007 const response = &resource.http_request.response;
1005 ));1008 response.* = request.receiveHead(&redirect_buffer) catch |err| switch (err) {
1006 };1009 error.ReadFailed => {
1007 req.wait() catch |err| {1010 return f.fail(f.location_tok, try eb.printString("HTTP response read failure: {t}", .{
1008 return f.fail(f.location_tok, try eb.printString(1011 request.connection.?.getReadError().?,
1009 "invalid HTTP response: {s}",1012 }));
1010 .{@errorName(err)},1013 },
1011 ));1014 else => |e| return f.fail(f.location_tok, try eb.printString("invalid HTTP response: {t}", .{e})),
1012 };1015 };
10131016
1014 if (req.response.status != .ok) {1017 if (response.head.status != .ok) return f.fail(f.location_tok, try eb.printString(
1015 return f.fail(f.location_tok, try eb.printString(1018 "bad HTTP response code: '{d} {s}'",
1016 "bad HTTP response code: '{d} {s}'",1019 .{ response.head.status, response.head.status.phrase() orelse "" },
1017 .{ @intFromEnum(req.response.status), req.response.status.phrase() orelse "" },1020 ));
1018 ));
1019 }
10201021
1021 return .{ .http_request = req };1022 return;
1022 }1023 }
10231024
1024 if (ascii.eqlIgnoreCase(uri.scheme, "git+http") or1025 if (ascii.eqlIgnoreCase(uri.scheme, "git+http") or
...@@ -1026,7 +1027,7 @@ fn initResource(f: *Fetch, uri: std.Uri, server_header_buffer: []u8) RunError!Re...@@ -1026,7 +1027,7 @@ fn initResource(f: *Fetch, uri: std.Uri, server_header_buffer: []u8) RunError!Re
1026 {1027 {
1027 var transport_uri = uri;1028 var transport_uri = uri;
1028 transport_uri.scheme = uri.scheme["git+".len..];1029 transport_uri.scheme = uri.scheme["git+".len..];
1029 var session = git.Session.init(gpa, http_client, transport_uri, server_header_buffer) catch |err| {1030 var session = git.Session.init(gpa, http_client, transport_uri, reader_buffer) catch |err| {
1030 return f.fail(f.location_tok, try eb.printString(1031 return f.fail(f.location_tok, try eb.printString(
1031 "unable to discover remote git server capabilities: {s}",1032 "unable to discover remote git server capabilities: {s}",
1032 .{@errorName(err)},1033 .{@errorName(err)},
...@@ -1042,16 +1043,12 @@ fn initResource(f: *Fetch, uri: std.Uri, server_header_buffer: []u8) RunError!Re...@@ -1042,16 +1043,12 @@ fn initResource(f: *Fetch, uri: std.Uri, server_header_buffer: []u8) RunError!Re
1042 const want_ref_head = try std.fmt.allocPrint(arena, "refs/heads/{s}", .{want_ref});1043 const want_ref_head = try std.fmt.allocPrint(arena, "refs/heads/{s}", .{want_ref});
1043 const want_ref_tag = try std.fmt.allocPrint(arena, "refs/tags/{s}", .{want_ref});1044 const want_ref_tag = try std.fmt.allocPrint(arena, "refs/tags/{s}", .{want_ref});
10441045
1045 var ref_iterator = session.listRefs(.{1046 var ref_iterator: git.Session.RefIterator = undefined;
1047 session.listRefs(&ref_iterator, .{
1046 .ref_prefixes = &.{ want_ref, want_ref_head, want_ref_tag },1048 .ref_prefixes = &.{ want_ref, want_ref_head, want_ref_tag },
1047 .include_peeled = true,1049 .include_peeled = true,
1048 .server_header_buffer = server_header_buffer,1050 .buffer = reader_buffer,
1049 }) catch |err| {1051 }) catch |err| return f.fail(f.location_tok, try eb.printString("unable to list refs: {t}", .{err}));
1050 return f.fail(f.location_tok, try eb.printString(
1051 "unable to list refs: {s}",
1052 .{@errorName(err)},
1053 ));
1054 };
1055 defer ref_iterator.deinit();1052 defer ref_iterator.deinit();
1056 while (ref_iterator.next() catch |err| {1053 while (ref_iterator.next() catch |err| {
1057 return f.fail(f.location_tok, try eb.printString(1054 return f.fail(f.location_tok, try eb.printString(
...@@ -1089,25 +1086,21 @@ fn initResource(f: *Fetch, uri: std.Uri, server_header_buffer: []u8) RunError!Re...@@ -1089,25 +1086,21 @@ fn initResource(f: *Fetch, uri: std.Uri, server_header_buffer: []u8) RunError!Re
10891086
1090 var want_oid_buf: [git.Oid.max_formatted_length]u8 = undefined;1087 var want_oid_buf: [git.Oid.max_formatted_length]u8 = undefined;
1091 _ = std.fmt.bufPrint(&want_oid_buf, "{f}", .{want_oid}) catch unreachable;1088 _ = std.fmt.bufPrint(&want_oid_buf, "{f}", .{want_oid}) catch unreachable;
1092 var fetch_stream = session.fetch(&.{&want_oid_buf}, server_header_buffer) catch |err| {1089 var fetch_stream: git.Session.FetchStream = undefined;
1093 return f.fail(f.location_tok, try eb.printString(1090 session.fetch(&fetch_stream, &.{&want_oid_buf}, reader_buffer) catch |err| {
1094 "unable to create fetch stream: {s}",1091 return f.fail(f.location_tok, try eb.printString("unable to create fetch stream: {t}", .{err}));
1095 .{@errorName(err)},
1096 ));
1097 };1092 };
1098 errdefer fetch_stream.deinit();1093 errdefer fetch_stream.deinit();
10991094
1100 return .{ .git = .{1095 resource.* = .{ .git = .{
1101 .session = session,1096 .session = session,
1102 .fetch_stream = fetch_stream,1097 .fetch_stream = fetch_stream,
1103 .want_oid = want_oid,1098 .want_oid = want_oid,
1104 } };1099 } };
1100 return;
1105 }1101 }
11061102
1107 return f.fail(f.location_tok, try eb.printString(1103 return f.fail(f.location_tok, try eb.printString("unsupported URL scheme: {s}", .{uri.scheme}));
1108 "unsupported URL scheme: {s}",
1109 .{uri.scheme},
1110 ));
1111}1104}
11121105
1113fn unpackResource(1106fn unpackResource(
...@@ -1121,9 +1114,11 @@ fn unpackResource(...@@ -1121,9 +1114,11 @@ fn unpackResource(
1121 .file => FileType.fromPath(uri_path) orelse1114 .file => FileType.fromPath(uri_path) orelse
1122 return f.fail(f.location_tok, try eb.printString("unknown file type: '{s}'", .{uri_path})),1115 return f.fail(f.location_tok, try eb.printString("unknown file type: '{s}'", .{uri_path})),
11231116
1124 .http_request => |req| ft: {1117 .http_request => |*http_request| ft: {
1118 const head = &http_request.response.head;
1119
1125 // Content-Type takes first precedence.1120 // Content-Type takes first precedence.
1126 const content_type = req.response.content_type orelse1121 const content_type = head.content_type orelse
1127 return f.fail(f.location_tok, try eb.addString("missing 'Content-Type' header"));1122 return f.fail(f.location_tok, try eb.addString("missing 'Content-Type' header"));
11281123
1129 // Extract the MIME type, ignoring charset and boundary directives1124 // Extract the MIME type, ignoring charset and boundary directives
...@@ -1165,7 +1160,7 @@ fn unpackResource(...@@ -1165,7 +1160,7 @@ fn unpackResource(
1165 }1160 }
11661161
1167 // Next, the filename from 'content-disposition: attachment' takes precedence.1162 // Next, the filename from 'content-disposition: attachment' takes precedence.
1168 if (req.response.content_disposition) |cd_header| {1163 if (head.content_disposition) |cd_header| {
1169 break :ft FileType.fromContentDisposition(cd_header) orelse {1164 break :ft FileType.fromContentDisposition(cd_header) orelse {
1170 return f.fail(f.location_tok, try eb.printString(1165 return f.fail(f.location_tok, try eb.printString(
1171 "unsupported Content-Disposition header value: '{s}' for Content-Type=application/octet-stream",1166 "unsupported Content-Disposition header value: '{s}' for Content-Type=application/octet-stream",
...@@ -1176,10 +1171,7 @@ fn unpackResource(...@@ -1176,10 +1171,7 @@ fn unpackResource(
11761171
1177 // Finally, the path from the URI is used.1172 // Finally, the path from the URI is used.
1178 break :ft FileType.fromPath(uri_path) orelse {1173 break :ft FileType.fromPath(uri_path) orelse {
1179 return f.fail(f.location_tok, try eb.printString(1174 return f.fail(f.location_tok, try eb.printString("unknown file type: '{s}'", .{uri_path}));
1180 "unknown file type: '{s}'",
1181 .{uri_path},
1182 ));
1183 };1175 };
1184 },1176 },
11851177
...@@ -1187,10 +1179,9 @@ fn unpackResource(...@@ -1187,10 +1179,9 @@ fn unpackResource(
11871179
1188 .dir => |dir| {1180 .dir => |dir| {
1189 f.recursiveDirectoryCopy(dir, tmp_directory.handle) catch |err| {1181 f.recursiveDirectoryCopy(dir, tmp_directory.handle) catch |err| {
1190 return f.fail(f.location_tok, try eb.printString(1182 return f.fail(f.location_tok, try eb.printString("unable to copy directory '{s}': {t}", .{
1191 "unable to copy directory '{s}': {s}",1183 uri_path, err,
1192 .{ uri_path, @errorName(err) },1184 }));
1193 ));
1194 };1185 };
1195 return .{};1186 return .{};
1196 },1187 },
...@@ -1198,27 +1189,17 @@ fn unpackResource(...@@ -1198,27 +1189,17 @@ fn unpackResource(
11981189
1199 switch (file_type) {1190 switch (file_type) {
1200 .tar => {1191 .tar => {
1201 var adapter_buffer: [1024]u8 = undefined;1192 return unpackTarball(f, tmp_directory.handle, resource.reader());
1202 var adapter = resource.reader().adaptToNewApi(&adapter_buffer);
1203 return unpackTarball(f, tmp_directory.handle, &adapter.new_interface);
1204 },1193 },
1205 .@"tar.gz" => {1194 .@"tar.gz" => {
1206 var adapter_buffer: [std.crypto.tls.max_ciphertext_record_len]u8 = undefined;
1207 var adapter = resource.reader().adaptToNewApi(&adapter_buffer);
1208 var flate_buffer: [std.compress.flate.max_window_len]u8 = undefined;1195 var flate_buffer: [std.compress.flate.max_window_len]u8 = undefined;
1209 var decompress: std.compress.flate.Decompress = .init(&adapter.new_interface, .gzip, &flate_buffer);1196 var decompress: std.compress.flate.Decompress = .init(resource.reader(), .gzip, &flate_buffer);
1210 return try unpackTarball(f, tmp_directory.handle, &decompress.reader);1197 return try unpackTarball(f, tmp_directory.handle, &decompress.reader);
1211 },1198 },
1212 .@"tar.xz" => {1199 .@"tar.xz" => {
1213 const gpa = f.arena.child_allocator;1200 const gpa = f.arena.child_allocator;
1214 const reader = resource.reader();1201 var dcp = std.compress.xz.decompress(gpa, resource.reader().adaptToOldInterface()) catch |err|
1215 var br = std.io.bufferedReaderSize(std.crypto.tls.max_ciphertext_record_len, reader);1202 return f.fail(f.location_tok, try eb.printString("unable to decompress tarball: {t}", .{err}));
1216 var dcp = std.compress.xz.decompress(gpa, br.reader()) catch |err| {
1217 return f.fail(f.location_tok, try eb.printString(
1218 "unable to decompress tarball: {s}",
1219 .{@errorName(err)},
1220 ));
1221 };
1222 defer dcp.deinit();1203 defer dcp.deinit();
1223 var adapter_buffer: [1024]u8 = undefined;1204 var adapter_buffer: [1024]u8 = undefined;
1224 var adapter = dcp.reader().adaptToNewApi(&adapter_buffer);1205 var adapter = dcp.reader().adaptToNewApi(&adapter_buffer);
...@@ -1227,9 +1208,7 @@ fn unpackResource(...@@ -1227,9 +1208,7 @@ fn unpackResource(
1227 .@"tar.zst" => {1208 .@"tar.zst" => {
1228 const window_size = std.compress.zstd.default_window_len;1209 const window_size = std.compress.zstd.default_window_len;
1229 const window_buffer = try f.arena.allocator().create([window_size]u8);1210 const window_buffer = try f.arena.allocator().create([window_size]u8);
1230 var adapter_buffer: [std.crypto.tls.max_ciphertext_record_len]u8 = undefined;1211 var decompress: std.compress.zstd.Decompress = .init(resource.reader(), window_buffer, .{
1231 var adapter = resource.reader().adaptToNewApi(&adapter_buffer);
1232 var decompress: std.compress.zstd.Decompress = .init(&adapter.new_interface, window_buffer, .{
1233 .verify_checksum = false,1212 .verify_checksum = false,
1234 });1213 });
1235 return try unpackTarball(f, tmp_directory.handle, &decompress.reader);1214 return try unpackTarball(f, tmp_directory.handle, &decompress.reader);
...@@ -1237,12 +1216,15 @@ fn unpackResource(...@@ -1237,12 +1216,15 @@ fn unpackResource(
1237 .git_pack => return unpackGitPack(f, tmp_directory.handle, &resource.git) catch |err| switch (err) {1216 .git_pack => return unpackGitPack(f, tmp_directory.handle, &resource.git) catch |err| switch (err) {
1238 error.FetchFailed => return error.FetchFailed,1217 error.FetchFailed => return error.FetchFailed,
1239 error.OutOfMemory => return error.OutOfMemory,1218 error.OutOfMemory => return error.OutOfMemory,
1240 else => |e| return f.fail(f.location_tok, try eb.printString(1219 else => |e| return f.fail(f.location_tok, try eb.printString("unable to unpack git files: {t}", .{e})),
1241 "unable to unpack git files: {s}",1220 },
1242 .{@errorName(e)},1221 .zip => return unzip(f, tmp_directory.handle, resource.reader()) catch |err| switch (err) {
1222 error.ReadFailed => return f.fail(f.location_tok, try eb.printString(
1223 "failed reading resource: {t}",
1224 .{err},
1243 )),1225 )),
1226 else => |e| return e,
1244 },1227 },
1245 .zip => return try unzip(f, tmp_directory.handle, resource.reader()),
1246 }1228 }
1247}1229}
12481230
...@@ -1277,99 +1259,69 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) RunError!Un...@@ -1277,99 +1259,69 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) RunError!Un
1277 return res;1259 return res;
1278}1260}
12791261
1280fn unzip(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackResult {1262fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) error{ ReadFailed, OutOfMemory, FetchFailed }!UnpackResult {
1281 // We write the entire contents to a file first because zip files1263 // We write the entire contents to a file first because zip files
1282 // must be processed back to front and they could be too large to1264 // must be processed back to front and they could be too large to
1283 // load into memory.1265 // load into memory.
12841266
1285 const cache_root = f.job_queue.global_cache;1267 const cache_root = f.job_queue.global_cache;
1286
1287 // TODO: the downside of this solution is if we get a failure/crash/oom/power out
1288 // during this process, we leave behind a zip file that would be
1289 // difficult to know if/when it can be cleaned up.
1290 // Might be worth it to use a mechanism that enables other processes
1291 // to see if the owning process of a file is still alive (on linux this
1292 // can be done with file locks).
1293 // Coupled with this mechansism, we could also use slots (i.e. zig-cache/tmp/0,
1294 // zig-cache/tmp/1, etc) which would mean that subsequent runs would
1295 // automatically clean up old dead files.
1296 // This could all be done with a simple TmpFile abstraction.
1297 const prefix = "tmp/";1268 const prefix = "tmp/";
1298 const suffix = ".zip";1269 const suffix = ".zip";
1299
1300 const random_bytes_count = 20;
1301 const random_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
1302 var zip_path: [prefix.len + random_path_len + suffix.len]u8 = undefined;
1303 @memcpy(zip_path[0..prefix.len], prefix);
1304 @memcpy(zip_path[prefix.len + random_path_len ..], suffix);
1305 {
1306 var random_bytes: [random_bytes_count]u8 = undefined;
1307 std.crypto.random.bytes(&random_bytes);
1308 _ = std.fs.base64_encoder.encode(
1309 zip_path[prefix.len..][0..random_path_len],
1310 &random_bytes,
1311 );
1312 }
1313
1314 defer cache_root.handle.deleteFile(&zip_path) catch {};
1315
1316 const eb = &f.error_bundle;1270 const eb = &f.error_bundle;
13171271 const random_len = @sizeOf(u64) * 2;
1318 {1272
1319 var zip_file = cache_root.handle.createFile(1273 var zip_path: [prefix.len + random_len + suffix.len]u8 = undefined;
1320 &zip_path,1274 zip_path[0..prefix.len].* = prefix.*;
1321 .{},1275 zip_path[prefix.len + random_len ..].* = suffix.*;
1322 ) catch |err| return f.fail(f.location_tok, try eb.printString(1276
1323 "failed to create tmp zip file: {s}",1277 var zip_file = while (true) {
1324 .{@errorName(err)},1278 const random_integer = std.crypto.random.int(u64);
1325 ));1279 zip_path[prefix.len..][0..random_len].* = std.fmt.hex(random_integer);
1326 defer zip_file.close();1280
1327 var buf: [4096]u8 = undefined;1281 break cache_root.handle.createFile(&zip_path, .{
1328 while (true) {1282 .exclusive = true,
1329 const len = reader.readAll(&buf) catch |err| return f.fail(f.location_tok, try eb.printString(1283 .read = true,
1330 "read zip stream failed: {s}",1284 }) catch |err| switch (err) {
1331 .{@errorName(err)},1285 error.PathAlreadyExists => continue,
1332 ));1286 else => |e| return f.fail(
1333 if (len == 0) break;1287 f.location_tok,
1334 zip_file.deprecatedWriter().writeAll(buf[0..len]) catch |err| return f.fail(f.location_tok, try eb.printString(1288 try eb.printString("failed to create temporary zip file: {t}", .{e}),
1335 "write temporary zip file failed: {s}",1289 ),
1336 .{@errorName(err)},1290 };
1337 ));1291 };
1338 }1292 defer zip_file.close();
1339 }1293 var zip_file_buffer: [4096]u8 = undefined;
1294 var zip_file_reader = b: {
1295 var zip_file_writer = zip_file.writer(&zip_file_buffer);
1296
1297 _ = reader.streamRemaining(&zip_file_writer.interface) catch |err| switch (err) {
1298 error.ReadFailed => return error.ReadFailed,
1299 error.WriteFailed => return f.fail(
1300 f.location_tok,
1301 try eb.printString("failed writing temporary zip file: {t}", .{err}),
1302 ),
1303 };
1304 zip_file_writer.interface.flush() catch |err| return f.fail(
1305 f.location_tok,
1306 try eb.printString("failed writing temporary zip file: {t}", .{err}),
1307 );
1308 break :b zip_file_writer.moveToReader();
1309 };
13401310
1341 var diagnostics: std.zip.Diagnostics = .{ .allocator = f.arena.allocator() };1311 var diagnostics: std.zip.Diagnostics = .{ .allocator = f.arena.allocator() };
1342 // no need to deinit since we are using an arena allocator1312 // no need to deinit since we are using an arena allocator
13431313
1344 {1314 zip_file_reader.seekTo(0) catch |err|
1345 var zip_file = cache_root.handle.openFile(1315 return f.fail(f.location_tok, try eb.printString("failed to seek temporary zip file: {t}", .{err}));
1346 &zip_path,1316 std.zip.extract(out_dir, &zip_file_reader, .{
1347 .{},1317 .allow_backslashes = true,
1348 ) catch |err| return f.fail(f.location_tok, try eb.printString(1318 .diagnostics = &diagnostics,
1349 "failed to open temporary zip file: {s}",1319 }) catch |err| return f.fail(f.location_tok, try eb.printString("zip extract failed: {t}", .{err}));
1350 .{@errorName(err)},
1351 ));
1352 defer zip_file.close();
1353
1354 var zip_file_buffer: [1024]u8 = undefined;
1355 var zip_file_reader = zip_file.reader(&zip_file_buffer);
1356
1357 std.zip.extract(out_dir, &zip_file_reader, .{
1358 .allow_backslashes = true,
1359 .diagnostics = &diagnostics,
1360 }) catch |err| return f.fail(f.location_tok, try eb.printString(
1361 "zip extract failed: {s}",
1362 .{@errorName(err)},
1363 ));
1364 }
13651320
1366 cache_root.handle.deleteFile(&zip_path) catch |err| return f.fail(f.location_tok, try eb.printString(1321 cache_root.handle.deleteFile(&zip_path) catch |err|
1367 "delete temporary zip failed: {s}",1322 return f.fail(f.location_tok, try eb.printString("delete temporary zip failed: {t}", .{err}));
1368 .{@errorName(err)},
1369 ));
13701323
1371 const res: UnpackResult = .{ .root_dir = diagnostics.root_dir };1324 return .{ .root_dir = diagnostics.root_dir };
1372 return res;
1373}1325}
13741326
1375fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!UnpackResult {1327fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!UnpackResult {
...@@ -1387,10 +1339,13 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U...@@ -1387,10 +1339,13 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
1387 var pack_file = try pack_dir.createFile("pkg.pack", .{ .read = true });1339 var pack_file = try pack_dir.createFile("pkg.pack", .{ .read = true });
1388 defer pack_file.close();1340 defer pack_file.close();
1389 var pack_file_buffer: [4096]u8 = undefined;1341 var pack_file_buffer: [4096]u8 = undefined;
1390 var fifo = std.fifo.LinearFifo(u8, .{ .Slice = {} }).init(&pack_file_buffer);1342 var pack_file_reader = b: {
1391 try fifo.pump(resource.fetch_stream.reader(), pack_file.deprecatedWriter());1343 var pack_file_writer = pack_file.writer(&pack_file_buffer);
13921344 const fetch_reader = &resource.fetch_stream.reader;
1393 var pack_file_reader = pack_file.reader(&pack_file_buffer);1345 _ = try fetch_reader.streamRemaining(&pack_file_writer.interface);
1346 try pack_file_writer.interface.flush();
1347 break :b pack_file_writer.moveToReader();
1348 };
13941349
1395 var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true });1350 var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true });
1396 defer index_file.close();1351 defer index_file.close();
src/Package/Fetch/git.zig+166-590
...@@ -11,8 +11,6 @@ const Allocator = mem.Allocator;...@@ -11,8 +11,6 @@ const Allocator = mem.Allocator;
11const Sha1 = std.crypto.hash.Sha1;11const Sha1 = std.crypto.hash.Sha1;
12const Sha256 = std.crypto.hash.sha2.Sha256;12const Sha256 = std.crypto.hash.sha2.Sha256;
13const assert = std.debug.assert;13const assert = std.debug.assert;
14const zlib = std.compress.zlib;
15const Writer = std.io.Writer;
1614
17/// The ID of a Git object.15/// The ID of a Git object.
18pub const Oid = union(Format) {16pub const Oid = union(Format) {
...@@ -54,6 +52,7 @@ pub const Oid = union(Format) {...@@ -54,6 +52,7 @@ pub const Oid = union(Format) {
54 };52 };
55 }53 }
5654
55 // Must be public for use from HashedReader and HashedWriter.
57 pub fn update(hasher: *Hasher, b: []const u8) void {56 pub fn update(hasher: *Hasher, b: []const u8) void {
58 switch (hasher.*) {57 switch (hasher.*) {
59 inline else => |*inner| inner.update(b),58 inline else => |*inner| inner.update(b),
...@@ -65,12 +64,6 @@ pub const Oid = union(Format) {...@@ -65,12 +64,6 @@ pub const Oid = union(Format) {
65 inline else => |*inner, tag| @unionInit(Oid, @tagName(tag), inner.finalResult()),64 inline else => |*inner, tag| @unionInit(Oid, @tagName(tag), inner.finalResult()),
66 };65 };
67 }66 }
68
69 pub fn writer(hasher: *Hasher, buffer: []u8) Writer {
70 return switch (hasher.*) {
71 inline else => |*inner| inner.writer(buffer),
72 };
73 }
74 };67 };
7568
76 const Hashing = union(Format) {69 const Hashing = union(Format) {
...@@ -107,30 +100,9 @@ pub const Oid = union(Format) {...@@ -107,30 +100,9 @@ pub const Oid = union(Format) {
107 };100 };
108 }101 }
109102
110<<<<<<< HEAD
111 pub fn readBytes(oid_format: Format, reader: *std.io.Reader) std.io.Reader.Error!Oid {
112||||||| 733b208256
113 pub fn readBytes(oid_format: Format, reader: anytype) @TypeOf(reader).NoEofError!Oid {
114=======
115 pub fn readBytes(oid_format: Format, reader: *std.Io.Reader) !Oid {103 pub fn readBytes(oid_format: Format, reader: *std.Io.Reader) !Oid {
116>>>>>>> origin/flate
117 return switch (oid_format) {104 return switch (oid_format) {
118<<<<<<< HEAD
119 .sha1 => {
120 var result: Oid = .{ .sha1 = undefined };
121 try reader.readSlice(&result.sha1);
122 return result;
123 },
124 .sha256 => {
125 var result: Oid = .{ .sha256 = undefined };
126 try reader.readSlice(&result.sha256);
127 return result;
128 },
129||||||| 733b208256
130 inline else => |tag| @unionInit(Oid, @tagName(tag), try reader.readBytesNoEof(tag.byteLength())),
131=======
132 inline else => |tag| @unionInit(Oid, @tagName(tag), (try reader.takeArray(tag.byteLength())).*),105 inline else => |tag| @unionInit(Oid, @tagName(tag), (try reader.takeArray(tag.byteLength())).*),
133>>>>>>> origin/flate
134 };106 };
135 }107 }
136108
...@@ -430,27 +402,14 @@ const Odb = struct {...@@ -430,27 +402,14 @@ const Odb = struct {
430402
431 /// Reads the object at the current position in the database.403 /// Reads the object at the current position in the database.
432 fn readObject(odb: *Odb) !Object {404 fn readObject(odb: *Odb) !Object {
433<<<<<<< HEAD
434 var base_offset = odb.pack_file.pos;
435 const pack_br = &odb.pack_file.interface;
436||||||| 733b208256
437 var base_offset = try odb.pack_file.getPos();
438=======
439 var base_offset = odb.pack_file.logicalPos();405 var base_offset = odb.pack_file.logicalPos();
440>>>>>>> origin/flate
441 var base_header: EntryHeader = undefined;406 var base_header: EntryHeader = undefined;
442 var delta_offsets: std.ArrayListUnmanaged(u64) = .empty;407 var delta_offsets: std.ArrayListUnmanaged(u64) = .empty;
443 defer delta_offsets.deinit(odb.allocator);408 defer delta_offsets.deinit(odb.allocator);
444 const base_object = while (true) {409 const base_object = while (true) {
445 if (odb.cache.get(base_offset)) |base_object| break base_object;410 if (odb.cache.get(base_offset)) |base_object| break base_object;
446411
447<<<<<<< HEAD
448 base_header = try EntryHeader.read(odb.format, pack_br);
449||||||| 733b208256
450 base_header = try EntryHeader.read(odb.format, odb.pack_file.deprecatedReader());
451=======
452 base_header = try EntryHeader.read(odb.format, &odb.pack_file.interface);412 base_header = try EntryHeader.read(odb.format, &odb.pack_file.interface);
453>>>>>>> origin/flate
454 switch (base_header) {413 switch (base_header) {
455 .ofs_delta => |ofs_delta| {414 .ofs_delta => |ofs_delta| {
456 try delta_offsets.append(odb.allocator, base_offset);415 try delta_offsets.append(odb.allocator, base_offset);
...@@ -460,22 +419,10 @@ const Odb = struct {...@@ -460,22 +419,10 @@ const Odb = struct {
460 .ref_delta => |ref_delta| {419 .ref_delta => |ref_delta| {
461 try delta_offsets.append(odb.allocator, base_offset);420 try delta_offsets.append(odb.allocator, base_offset);
462 try odb.seekOid(ref_delta.base_object);421 try odb.seekOid(ref_delta.base_object);
463<<<<<<< HEAD
464 base_offset = odb.pack_file.pos - pack_br.bufferedLen();
465||||||| 733b208256
466 base_offset = try odb.pack_file.getPos();
467=======
468 base_offset = odb.pack_file.logicalPos();422 base_offset = odb.pack_file.logicalPos();
469>>>>>>> origin/flate
470 },423 },
471 else => {424 else => {
472<<<<<<< HEAD
473 const base_data = try readObjectRaw(odb.allocator, pack_br, base_header.uncompressedLength());
474||||||| 733b208256
475 const base_data = try readObjectRaw(odb.allocator, odb.pack_file.deprecatedReader(), base_header.uncompressedLength());
476=======
477 const base_data = try readObjectRaw(odb.allocator, &odb.pack_file.interface, base_header.uncompressedLength());425 const base_data = try readObjectRaw(odb.allocator, &odb.pack_file.interface, base_header.uncompressedLength());
478>>>>>>> origin/flate
479 errdefer odb.allocator.free(base_data);426 errdefer odb.allocator.free(base_data);
480 const base_object: Object = .{ .type = base_header.objectType(), .data = base_data };427 const base_object: Object = .{ .type = base_header.objectType(), .data = base_data };
481 try odb.cache.put(odb.allocator, base_offset, base_object);428 try odb.cache.put(odb.allocator, base_offset, base_object);
...@@ -505,14 +452,7 @@ const Odb = struct {...@@ -505,14 +452,7 @@ const Odb = struct {
505 const found_index = while (start_index < end_index) {452 const found_index = while (start_index < end_index) {
506 const mid_index = start_index + (end_index - start_index) / 2;453 const mid_index = start_index + (end_index - start_index) / 2;
507 try odb.index_file.seekTo(IndexHeader.size + mid_index * oid_length);454 try odb.index_file.seekTo(IndexHeader.size + mid_index * oid_length);
508<<<<<<< HEAD
509 var br = odb.index_file.interface().unbuffered();
510 const mid_oid = try Oid.readBytes(odb.format, &br);
511||||||| 733b208256
512 const mid_oid = try Oid.readBytes(odb.format, odb.index_file.deprecatedReader());
513=======
514 const mid_oid = try Oid.readBytes(odb.format, &odb.index_file.interface);455 const mid_oid = try Oid.readBytes(odb.format, &odb.index_file.interface);
515>>>>>>> origin/flate
516 switch (mem.order(u8, mid_oid.slice(), oid.slice())) {456 switch (mem.order(u8, mid_oid.slice(), oid.slice())) {
517 .lt => start_index = mid_index + 1,457 .lt => start_index = mid_index + 1,
518 .gt => end_index = mid_index,458 .gt => end_index = mid_index,
...@@ -522,28 +462,13 @@ const Odb = struct {...@@ -522,28 +462,13 @@ const Odb = struct {
522462
523 const n_objects = odb.index_header.fan_out_table[255];463 const n_objects = odb.index_header.fan_out_table[255];
524 const offset_values_start = IndexHeader.size + n_objects * (oid_length + 4);464 const offset_values_start = IndexHeader.size + n_objects * (oid_length + 4);
525 var buffer: [8]u8 = undefined;
526 try odb.index_file.seekTo(offset_values_start + found_index * 4);465 try odb.index_file.seekTo(offset_values_start + found_index * 4);
527<<<<<<< HEAD
528 var br = odb.index_file.interface().buffered(&buffer);
529 const l1_offset: packed struct { value: u31, big: bool } = @bitCast(try br.takeInt(u32, .big));
530||||||| 733b208256
531 const l1_offset: packed struct { value: u31, big: bool } = @bitCast(try odb.index_file.deprecatedReader().readInt(u32, .big));
532=======
533 const l1_offset: packed struct { value: u31, big: bool } = @bitCast(try odb.index_file.interface.takeInt(u32, .big));466 const l1_offset: packed struct { value: u31, big: bool } = @bitCast(try odb.index_file.interface.takeInt(u32, .big));
534>>>>>>> origin/flate
535 const pack_offset = pack_offset: {467 const pack_offset = pack_offset: {
536 if (l1_offset.big) {468 if (l1_offset.big) {
537 const l2_offset_values_start = offset_values_start + n_objects * 4;469 const l2_offset_values_start = offset_values_start + n_objects * 4;
538 try odb.index_file.seekTo(l2_offset_values_start + l1_offset.value * 4);470 try odb.index_file.seekTo(l2_offset_values_start + l1_offset.value * 4);
539<<<<<<< HEAD
540 br = odb.index_file.interface().buffered(&buffer);
541 break :pack_offset try br.takeInt(u64, .big);
542||||||| 733b208256
543 break :pack_offset try odb.index_file.deprecatedReader().readInt(u64, .big);
544=======
545 break :pack_offset try odb.index_file.interface.takeInt(u64, .big);471 break :pack_offset try odb.index_file.interface.takeInt(u64, .big);
546>>>>>>> origin/flate
547 } else {472 } else {
548 break :pack_offset l1_offset.value;473 break :pack_offset l1_offset.value;
549 }474 }
...@@ -660,17 +585,17 @@ const ObjectCache = struct {...@@ -660,17 +585,17 @@ const ObjectCache = struct {
660/// [protocol-common](https://git-scm.com/docs/protocol-common). The special585/// [protocol-common](https://git-scm.com/docs/protocol-common). The special
661/// meanings of the delimiter and response-end packets are documented in586/// meanings of the delimiter and response-end packets are documented in
662/// [protocol-v2](https://git-scm.com/docs/protocol-v2).587/// [protocol-v2](https://git-scm.com/docs/protocol-v2).
663const Packet = union(enum) {588pub const Packet = union(enum) {
664 flush,589 flush,
665 delimiter,590 delimiter,
666 response_end,591 response_end,
667 data: []const u8,592 data: []const u8,
668593
669 const max_data_length = 65516;594 pub const max_data_length = 65516;
670595
671 /// Reads a packet in pkt-line format.596 /// Reads a packet in pkt-line format.
672 fn read(reader: *std.io.Reader, buf: *[max_data_length]u8) !Packet {597 fn read(reader: *std.Io.Reader) !Packet {
673 const length = std.fmt.parseUnsigned(u16, &try reader.readBytesNoEof(4), 16) catch return error.InvalidPacket;598 const length = std.fmt.parseUnsigned(u16, try reader.take(4), 16) catch return error.InvalidPacket;
674 switch (length) {599 switch (length) {
675 0 => return .flush,600 0 => return .flush,
676 1 => return .delimiter,601 1 => return .delimiter,
...@@ -678,13 +603,11 @@ const Packet = union(enum) {...@@ -678,13 +603,11 @@ const Packet = union(enum) {
678 3 => return error.InvalidPacket,603 3 => return error.InvalidPacket,
679 else => if (length - 4 > max_data_length) return error.InvalidPacket,604 else => if (length - 4 > max_data_length) return error.InvalidPacket,
680 }605 }
681 const data = buf[0 .. length - 4];606 return .{ .data = try reader.take(length - 4) };
682 try reader.readNoEof(data);
683 return .{ .data = data };
684 }607 }
685608
686 /// Writes a packet in pkt-line format.609 /// Writes a packet in pkt-line format.
687 fn write(packet: Packet, writer: *Writer) !void {610 fn write(packet: Packet, writer: *std.Io.Writer) !void {
688 switch (packet) {611 switch (packet) {
689 .flush => try writer.writeAll("0000"),612 .flush => try writer.writeAll("0000"),
690 .delimiter => try writer.writeAll("0001"),613 .delimiter => try writer.writeAll("0001"),
...@@ -732,8 +655,10 @@ pub const Session = struct {...@@ -732,8 +655,10 @@ pub const Session = struct {
732 allocator: Allocator,655 allocator: Allocator,
733 transport: *std.http.Client,656 transport: *std.http.Client,
734 uri: std.Uri,657 uri: std.Uri,
735 http_headers_buffer: []u8,658 /// Asserted to be at least `Packet.max_data_length`
659 response_buffer: []u8,
736 ) !Session {660 ) !Session {
661 assert(response_buffer.len >= Packet.max_data_length);
737 var session: Session = .{662 var session: Session = .{
738 .transport = transport,663 .transport = transport,
739 .location = try .init(allocator, uri),664 .location = try .init(allocator, uri),
...@@ -743,7 +668,8 @@ pub const Session = struct {...@@ -743,7 +668,8 @@ pub const Session = struct {
743 .allocator = allocator,668 .allocator = allocator,
744 };669 };
745 errdefer session.deinit();670 errdefer session.deinit();
746 var capability_iterator = try session.getCapabilities(http_headers_buffer);671 var capability_iterator: CapabilityIterator = undefined;
672 try session.getCapabilities(&capability_iterator, response_buffer);
747 defer capability_iterator.deinit();673 defer capability_iterator.deinit();
748 while (try capability_iterator.next()) |capability| {674 while (try capability_iterator.next()) |capability| {
749 if (mem.eql(u8, capability.key, "agent")) {675 if (mem.eql(u8, capability.key, "agent")) {
...@@ -818,7 +744,8 @@ pub const Session = struct {...@@ -818,7 +744,8 @@ pub const Session = struct {
818 ///744 ///
819 /// The `session.location` is updated if the server returns a redirect, so745 /// The `session.location` is updated if the server returns a redirect, so
820 /// that subsequent session functions do not need to handle redirects.746 /// that subsequent session functions do not need to handle redirects.
821 fn getCapabilities(session: *Session, http_headers_buffer: []u8) !CapabilityIterator {747 fn getCapabilities(session: *Session, it: *CapabilityIterator, response_buffer: []u8) !void {
748 assert(response_buffer.len >= Packet.max_data_length);
822 var info_refs_uri = session.location.uri;749 var info_refs_uri = session.location.uri;
823 {750 {
824 const session_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{751 const session_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{
...@@ -832,19 +759,22 @@ pub const Session = struct {...@@ -832,19 +759,22 @@ pub const Session = struct {
832 info_refs_uri.fragment = null;759 info_refs_uri.fragment = null;
833760
834 const max_redirects = 3;761 const max_redirects = 3;
835 var request = try session.transport.open(.GET, info_refs_uri, .{762 it.* = .{
836 .redirect_behavior = @enumFromInt(max_redirects),763 .request = try session.transport.request(.GET, info_refs_uri, .{
837 .server_header_buffer = http_headers_buffer,764 .redirect_behavior = .init(max_redirects),
838 .extra_headers = &.{765 .extra_headers = &.{
839 .{ .name = "Git-Protocol", .value = "version=2" },766 .{ .name = "Git-Protocol", .value = "version=2" },
840 },767 },
841 });768 }),
842 errdefer request.deinit();769 .reader = undefined,
843 try request.send();770 };
844 try request.finish();771 errdefer it.deinit();
772 const request = &it.request;
773 try request.sendBodiless();
845774
846 try request.wait();775 var redirect_buffer: [1024]u8 = undefined;
847 if (request.response.status != .ok) return error.ProtocolError;776 var response = try request.receiveHead(&redirect_buffer);
777 if (response.head.status != .ok) return error.ProtocolError;
848 const any_redirects_occurred = request.redirect_behavior.remaining() < max_redirects;778 const any_redirects_occurred = request.redirect_behavior.remaining() < max_redirects;
849 if (any_redirects_occurred) {779 if (any_redirects_occurred) {
850 const request_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{780 const request_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{
...@@ -859,8 +789,7 @@ pub const Session = struct {...@@ -859,8 +789,7 @@ pub const Session = struct {
859 session.location = new_location;789 session.location = new_location;
860 }790 }
861791
862 const reader = request.reader();792 it.reader = response.reader(response_buffer);
863 var buf: [Packet.max_data_length]u8 = undefined;
864 var state: enum { response_start, response_content } = .response_start;793 var state: enum { response_start, response_content } = .response_start;
865 while (true) {794 while (true) {
866 // Some Git servers (at least GitHub) include an additional795 // Some Git servers (at least GitHub) include an additional
...@@ -870,15 +799,15 @@ pub const Session = struct {...@@ -870,15 +799,15 @@ pub const Session = struct {
870 // Thus, we need to skip any such useless additional responses799 // Thus, we need to skip any such useless additional responses
871 // before we get the one we're actually looking for. The responses800 // before we get the one we're actually looking for. The responses
872 // will be delimited by flush packets.801 // will be delimited by flush packets.
873 const packet = Packet.read(reader, &buf) catch |e| switch (e) {802 const packet = Packet.read(it.reader) catch |err| switch (err) {
874 error.EndOfStream => return error.UnsupportedProtocol, // 'version 2' packet not found803 error.EndOfStream => return error.UnsupportedProtocol, // 'version 2' packet not found
875 else => |other| return other,804 else => |e| return e,
876 };805 };
877 switch (packet) {806 switch (packet) {
878 .flush => state = .response_start,807 .flush => state = .response_start,
879 .data => |data| switch (state) {808 .data => |data| switch (state) {
880 .response_start => if (mem.eql(u8, Packet.normalizeText(data), "version 2")) {809 .response_start => if (mem.eql(u8, Packet.normalizeText(data), "version 2")) {
881 return .{ .request = request };810 return;
882 } else {811 } else {
883 state = .response_content;812 state = .response_content;
884 },813 },
...@@ -891,7 +820,7 @@ pub const Session = struct {...@@ -891,7 +820,7 @@ pub const Session = struct {
891820
892 const CapabilityIterator = struct {821 const CapabilityIterator = struct {
893 request: std.http.Client.Request,822 request: std.http.Client.Request,
894 buf: [Packet.max_data_length]u8 = undefined,823 reader: *std.Io.Reader,
895824
896 const Capability = struct {825 const Capability = struct {
897 key: []const u8,826 key: []const u8,
...@@ -905,13 +834,13 @@ pub const Session = struct {...@@ -905,13 +834,13 @@ pub const Session = struct {
905 }834 }
906 };835 };
907836
908 fn deinit(iterator: *CapabilityIterator) void {837 fn deinit(it: *CapabilityIterator) void {
909 iterator.request.deinit();838 it.request.deinit();
910 iterator.* = undefined;839 it.* = undefined;
911 }840 }
912841
913 fn next(iterator: *CapabilityIterator) !?Capability {842 fn next(it: *CapabilityIterator) !?Capability {
914 switch (try Packet.read(iterator.request.reader(), &iterator.buf)) {843 switch (try Packet.read(it.reader)) {
915 .flush => return null,844 .flush => return null,
916 .data => |data| return Capability.parse(Packet.normalizeText(data)),845 .data => |data| return Capability.parse(Packet.normalizeText(data)),
917 else => return error.UnexpectedPacket,846 else => return error.UnexpectedPacket,
...@@ -929,11 +858,13 @@ pub const Session = struct {...@@ -929,11 +858,13 @@ pub const Session = struct {
929 include_symrefs: bool = false,858 include_symrefs: bool = false,
930 /// Whether to include the peeled object ID for returned tag refs.859 /// Whether to include the peeled object ID for returned tag refs.
931 include_peeled: bool = false,860 include_peeled: bool = false,
932 server_header_buffer: []u8,861 /// Asserted to be at least `Packet.max_data_length`.
862 buffer: []u8,
933 };863 };
934864
935 /// Returns an iterator over refs known to the server.865 /// Returns an iterator over refs known to the server.
936 pub fn listRefs(session: Session, options: ListRefsOptions) !RefIterator {866 pub fn listRefs(session: Session, it: *RefIterator, options: ListRefsOptions) !void {
867 assert(options.buffer.len >= Packet.max_data_length);
937 var upload_pack_uri = session.location.uri;868 var upload_pack_uri = session.location.uri;
938 {869 {
939 const session_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{870 const session_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{
...@@ -946,61 +877,56 @@ pub const Session = struct {...@@ -946,61 +877,56 @@ pub const Session = struct {
946 upload_pack_uri.query = null;877 upload_pack_uri.query = null;
947 upload_pack_uri.fragment = null;878 upload_pack_uri.fragment = null;
948879
949 var body: std.io.Writer.Allocating = .init(session.allocator);880 var body: std.Io.Writer = .fixed(options.buffer);
950 const body_writer = &body.interface;881 try Packet.write(.{ .data = "command=ls-refs\n" }, &body);
951 defer body.deinit();
952 try Packet.write(.{ .data = "command=ls-refs\n" }, body_writer);
953 if (session.supports_agent) {882 if (session.supports_agent) {
954 try Packet.write(.{ .data = agent_capability }, body_writer);883 try Packet.write(.{ .data = agent_capability }, &body);
955 }884 }
956 {885 {
957 const object_format_packet = try std.fmt.allocPrint(session.allocator, "object-format={s}\n", .{@tagName(session.object_format)});886 const object_format_packet = try std.fmt.allocPrint(session.allocator, "object-format={t}\n", .{
887 session.object_format,
888 });
958 defer session.allocator.free(object_format_packet);889 defer session.allocator.free(object_format_packet);
959 try Packet.write(.{ .data = object_format_packet }, body_writer);890 try Packet.write(.{ .data = object_format_packet }, &body);
960 }891 }
961 try Packet.write(.delimiter, body_writer);892 try Packet.write(.delimiter, &body);
962 for (options.ref_prefixes) |ref_prefix| {893 for (options.ref_prefixes) |ref_prefix| {
963 const ref_prefix_packet = try std.fmt.allocPrint(session.allocator, "ref-prefix {s}\n", .{ref_prefix});894 const ref_prefix_packet = try std.fmt.allocPrint(session.allocator, "ref-prefix {s}\n", .{ref_prefix});
964 defer session.allocator.free(ref_prefix_packet);895 defer session.allocator.free(ref_prefix_packet);
965 try Packet.write(.{ .data = ref_prefix_packet }, body_writer);896 try Packet.write(.{ .data = ref_prefix_packet }, &body);
966 }897 }
967 if (options.include_symrefs) {898 if (options.include_symrefs) {
968 try Packet.write(.{ .data = "symrefs\n" }, body_writer);899 try Packet.write(.{ .data = "symrefs\n" }, &body);
969 }900 }
970 if (options.include_peeled) {901 if (options.include_peeled) {
971 try Packet.write(.{ .data = "peel\n" }, body_writer);902 try Packet.write(.{ .data = "peel\n" }, &body);
972 }903 }
973 try Packet.write(.flush, body_writer);904 try Packet.write(.flush, &body);
974905
975 var request = try session.transport.open(.POST, upload_pack_uri, .{906 it.* = .{
976 .redirect_behavior = .unhandled,907 .request = try session.transport.request(.POST, upload_pack_uri, .{
977 .server_header_buffer = options.server_header_buffer,908 .redirect_behavior = .unhandled,
978 .extra_headers = &.{909 .extra_headers = &.{
979 .{ .name = "Content-Type", .value = "application/x-git-upload-pack-request" },910 .{ .name = "Content-Type", .value = "application/x-git-upload-pack-request" },
980 .{ .name = "Git-Protocol", .value = "version=2" },911 .{ .name = "Git-Protocol", .value = "version=2" },
981 },912 },
982 });913 }),
983 errdefer request.deinit();914 .reader = undefined,
984 const written = body.getWritten();
985 request.transfer_encoding = .{ .content_length = written.len };
986 try request.send();
987 var w = request.writer().unbuffered();
988 try w.writeAll(written);
989 try request.finish();
990
991 try request.wait();
992 if (request.response.status != .ok) return error.ProtocolError;
993
994 return .{
995 .format = session.object_format,915 .format = session.object_format,
996 .request = request,
997 };916 };
917 const request = &it.request;
918 errdefer request.deinit();
919 try request.sendBodyComplete(body.buffered());
920
921 var response = try request.receiveHead(options.buffer);
922 if (response.head.status != .ok) return error.ProtocolError;
923 it.reader = response.reader(options.buffer);
998 }924 }
999925
1000 pub const RefIterator = struct {926 pub const RefIterator = struct {
1001 format: Oid.Format,927 format: Oid.Format,
1002 request: std.http.Client.Request,928 request: std.http.Client.Request,
1003 buf: [Packet.max_data_length]u8 = undefined,929 reader: *std.Io.Reader,
1004930
1005 pub const Ref = struct {931 pub const Ref = struct {
1006 oid: Oid,932 oid: Oid,
...@@ -1014,13 +940,13 @@ pub const Session = struct {...@@ -1014,13 +940,13 @@ pub const Session = struct {
1014 iterator.* = undefined;940 iterator.* = undefined;
1015 }941 }
1016942
1017 pub fn next(iterator: *RefIterator) !?Ref {943 pub fn next(it: *RefIterator) !?Ref {
1018 switch (try Packet.read(iterator.request.reader(), &iterator.buf)) {944 switch (try Packet.read(it.reader)) {
1019 .flush => return null,945 .flush => return null,
1020 .data => |data| {946 .data => |data| {
1021 const ref_data = Packet.normalizeText(data);947 const ref_data = Packet.normalizeText(data);
1022 const oid_sep_pos = mem.indexOfScalar(u8, ref_data, ' ') orelse return error.InvalidRefPacket;948 const oid_sep_pos = mem.indexOfScalar(u8, ref_data, ' ') orelse return error.InvalidRefPacket;
1023 const oid = Oid.parse(iterator.format, data[0..oid_sep_pos]) catch return error.InvalidRefPacket;949 const oid = Oid.parse(it.format, data[0..oid_sep_pos]) catch return error.InvalidRefPacket;
1024950
1025 const name_sep_pos = mem.indexOfScalarPos(u8, ref_data, oid_sep_pos + 1, ' ') orelse ref_data.len;951 const name_sep_pos = mem.indexOfScalarPos(u8, ref_data, oid_sep_pos + 1, ' ') orelse ref_data.len;
1026 const name = ref_data[oid_sep_pos + 1 .. name_sep_pos];952 const name = ref_data[oid_sep_pos + 1 .. name_sep_pos];
...@@ -1034,7 +960,7 @@ pub const Session = struct {...@@ -1034,7 +960,7 @@ pub const Session = struct {
1034 if (mem.startsWith(u8, attribute, "symref-target:")) {960 if (mem.startsWith(u8, attribute, "symref-target:")) {
1035 symref_target = attribute["symref-target:".len..];961 symref_target = attribute["symref-target:".len..];
1036 } else if (mem.startsWith(u8, attribute, "peeled:")) {962 } else if (mem.startsWith(u8, attribute, "peeled:")) {
1037 peeled = Oid.parse(iterator.format, attribute["peeled:".len..]) catch return error.InvalidRefPacket;963 peeled = Oid.parse(it.format, attribute["peeled:".len..]) catch return error.InvalidRefPacket;
1038 }964 }
1039 last_sep_pos = next_sep_pos;965 last_sep_pos = next_sep_pos;
1040 }966 }
...@@ -1050,9 +976,12 @@ pub const Session = struct {...@@ -1050,9 +976,12 @@ pub const Session = struct {
1050 /// performed if the server supports it.976 /// performed if the server supports it.
1051 pub fn fetch(977 pub fn fetch(
1052 session: Session,978 session: Session,
979 fs: *FetchStream,
1053 wants: []const []const u8,980 wants: []const []const u8,
1054 http_headers_buffer: []u8,981 /// Asserted to be at least `Packet.max_data_length`.
1055 ) !FetchStream {982 response_buffer: []u8,
983 ) !void {
984 assert(response_buffer.len >= Packet.max_data_length);
1056 var upload_pack_uri = session.location.uri;985 var upload_pack_uri = session.location.uri;
1057 {986 {
1058 const session_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{987 const session_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{
...@@ -1065,65 +994,71 @@ pub const Session = struct {...@@ -1065,65 +994,71 @@ pub const Session = struct {
1065 upload_pack_uri.query = null;994 upload_pack_uri.query = null;
1066 upload_pack_uri.fragment = null;995 upload_pack_uri.fragment = null;
1067996
1068 var body: std.io.Writer.Allocating = .init(session.allocator);997 var body: std.Io.Writer = .fixed(response_buffer);
1069 defer body.deinit();998 try Packet.write(.{ .data = "command=fetch\n" }, &body);
1070 const body_writer = &body.interface;
1071 try Packet.write(.{ .data = "command=fetch\n" }, body_writer);
1072 if (session.supports_agent) {999 if (session.supports_agent) {
1073 try Packet.write(.{ .data = agent_capability }, body_writer);1000 try Packet.write(.{ .data = agent_capability }, &body);
1074 }1001 }
1075 {1002 {
1076 const object_format_packet = try std.fmt.allocPrint(session.allocator, "object-format={s}\n", .{@tagName(session.object_format)});1003 const object_format_packet = try std.fmt.allocPrint(session.allocator, "object-format={s}\n", .{@tagName(session.object_format)});
1077 defer session.allocator.free(object_format_packet);1004 defer session.allocator.free(object_format_packet);
1078 try Packet.write(.{ .data = object_format_packet }, body_writer);1005 try Packet.write(.{ .data = object_format_packet }, &body);
1079 }1006 }
1080 try Packet.write(.delimiter, body_writer);1007 try Packet.write(.delimiter, &body);
1081 // Our packfile parser supports the OFS_DELTA object type1008 // Our packfile parser supports the OFS_DELTA object type
1082 try Packet.write(.{ .data = "ofs-delta\n" }, body_writer);1009 try Packet.write(.{ .data = "ofs-delta\n" }, &body);
1083 // We do not currently convey server progress information to the user1010 // We do not currently convey server progress information to the user
1084 try Packet.write(.{ .data = "no-progress\n" }, body_writer);1011 try Packet.write(.{ .data = "no-progress\n" }, &body);
1085 if (session.supports_shallow) {1012 if (session.supports_shallow) {
1086 try Packet.write(.{ .data = "deepen 1\n" }, body_writer);1013 try Packet.write(.{ .data = "deepen 1\n" }, &body);
1087 }1014 }
1088 for (wants) |want| {1015 for (wants) |want| {
1089 var buf: [Packet.max_data_length]u8 = undefined;1016 var buf: [Packet.max_data_length]u8 = undefined;
1090 const arg = std.fmt.bufPrint(&buf, "want {s}\n", .{want}) catch unreachable;1017 const arg = std.fmt.bufPrint(&buf, "want {s}\n", .{want}) catch unreachable;
1091 try Packet.write(.{ .data = arg }, body_writer);1018 try Packet.write(.{ .data = arg }, &body);
1092 }1019 }
1093 try Packet.write(.{ .data = "done\n" }, body_writer);1020 try Packet.write(.{ .data = "done\n" }, &body);
1094 try Packet.write(.flush, body_writer);1021 try Packet.write(.flush, &body);
10951022
1096 var request = try session.transport.open(.POST, upload_pack_uri, .{1023 fs.* = .{
1097 .redirect_behavior = .not_allowed,1024 .request = try session.transport.request(.POST, upload_pack_uri, .{
1098 .server_header_buffer = http_headers_buffer,1025 .redirect_behavior = .not_allowed,
1099 .extra_headers = &.{1026 .extra_headers = &.{
1100 .{ .name = "Content-Type", .value = "application/x-git-upload-pack-request" },1027 .{ .name = "Content-Type", .value = "application/x-git-upload-pack-request" },
1101 .{ .name = "Git-Protocol", .value = "version=2" },1028 .{ .name = "Git-Protocol", .value = "version=2" },
1102 },1029 },
1103 });1030 }),
1031 .input = undefined,
1032 .reader = undefined,
1033 .remaining_len = undefined,
1034 };
1035 const request = &fs.request;
1104 errdefer request.deinit();1036 errdefer request.deinit();
1105 const written = body.getWritten();
1106 request.transfer_encoding = .{ .content_length = written.len };
1107 try request.send();
1108 var w = request.writer().unbuffered();
1109 try w.writeAll(written);
1110 try request.finish();
11111037
1112 try request.wait();1038 try request.sendBodyComplete(body.buffered());
1113 if (request.response.status != .ok) return error.ProtocolError;
11141039
1115 const reader = request.reader();1040 var response = try request.receiveHead(&.{});
1041 if (response.head.status != .ok) return error.ProtocolError;
1042
1043 const reader = response.reader(response_buffer);
1116 // We are not interested in any of the sections of the returned fetch1044 // We are not interested in any of the sections of the returned fetch
1117 // data other than the packfile section, since we aren't doing anything1045 // data other than the packfile section, since we aren't doing anything
1118 // complex like ref negotiation (this is a fresh clone).1046 // complex like ref negotiation (this is a fresh clone).
1119 var state: enum { section_start, section_content } = .section_start;1047 var state: enum { section_start, section_content } = .section_start;
1120 while (true) {1048 while (true) {
1121 var buf: [Packet.max_data_length]u8 = undefined;1049 const packet = try Packet.read(reader);
1122 const packet = try Packet.read(reader, &buf);
1123 switch (state) {1050 switch (state) {
1124 .section_start => switch (packet) {1051 .section_start => switch (packet) {
1125 .data => |data| if (mem.eql(u8, Packet.normalizeText(data), "packfile")) {1052 .data => |data| if (mem.eql(u8, Packet.normalizeText(data), "packfile")) {
1126 return .{ .request = request };1053 fs.input = reader;
1054 fs.reader = .{
1055 .buffer = &.{},
1056 .vtable = &.{ .stream = FetchStream.stream },
1057 .seek = 0,
1058 .end = 0,
1059 };
1060 fs.remaining_len = 0;
1061 return;
1127 } else {1062 } else {
1128 state = .section_content;1063 state = .section_content;
1129 },1064 },
...@@ -1140,20 +1075,23 @@ pub const Session = struct {...@@ -1140,20 +1075,23 @@ pub const Session = struct {
11401075
1141 pub const FetchStream = struct {1076 pub const FetchStream = struct {
1142 request: std.http.Client.Request,1077 request: std.http.Client.Request,
1143 buf: [Packet.max_data_length]u8 = undefined,1078 input: *std.Io.Reader,
1144 pos: usize = 0,1079 reader: std.Io.Reader,
1145 len: usize = 0,1080 err: ?Error = null,
1081 remaining_len: usize,
11461082
1147 pub fn deinit(stream: *FetchStream) void {1083 pub fn deinit(fs: *FetchStream) void {
1148 stream.request.deinit();1084 fs.request.deinit();
1149 }1085 }
11501086
1151 pub const ReadError = std.http.Client.Request.ReadError || error{1087 pub const Error = error{
1152 InvalidPacket,1088 InvalidPacket,
1153 ProtocolError,1089 ProtocolError,
1154 UnexpectedPacket,1090 UnexpectedPacket,
1091 WriteFailed,
1092 ReadFailed,
1093 EndOfStream,
1155 };1094 };
1156 pub const Reader = std.io.GenericReader(*FetchStream, ReadError, read);
11571095
1158 const StreamCode = enum(u8) {1096 const StreamCode = enum(u8) {
1159 pack_data = 1,1097 pack_data = 1,
...@@ -1162,33 +1100,41 @@ pub const Session = struct {...@@ -1162,33 +1100,41 @@ pub const Session = struct {
1162 _,1100 _,
1163 };1101 };
11641102
1165 pub fn reader(stream: *FetchStream) Reader {1103 pub fn stream(r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
1166 return .{ .context = stream };1104 const fs: *FetchStream = @alignCast(@fieldParentPtr("reader", r));
1167 }1105 const input = fs.input;
11681106 if (fs.remaining_len == 0) {
1169 pub fn read(stream: *FetchStream, buf: []u8) !usize {
1170 if (stream.pos == stream.len) {
1171 while (true) {1107 while (true) {
1172 switch (try Packet.read(stream.request.reader(), &stream.buf)) {1108 switch (Packet.read(input) catch |err| {
1173 .flush => return 0,1109 fs.err = err;
1110 return error.ReadFailed;
1111 }) {
1112 .flush => return error.EndOfStream,
1174 .data => |data| if (data.len > 1) switch (@as(StreamCode, @enumFromInt(data[0]))) {1113 .data => |data| if (data.len > 1) switch (@as(StreamCode, @enumFromInt(data[0]))) {
1175 .pack_data => {1114 .pack_data => {
1176 stream.pos = 1;1115 input.toss(1);
1177 stream.len = data.len;1116 fs.remaining_len = data.len;
1178 break;1117 break;
1179 },1118 },
1180 .fatal_error => return error.ProtocolError,1119 .fatal_error => {
1120 fs.err = error.ProtocolError;
1121 return error.ReadFailed;
1122 },
1181 else => {},1123 else => {},
1182 },1124 },
1183 else => return error.UnexpectedPacket,1125 else => {
1126 fs.err = error.UnexpectedPacket;
1127 return error.ReadFailed;
1128 },
1184 }1129 }
1185 }1130 }
1186 }1131 }
11871132 const buf = limit.slice(try w.writableSliceGreedy(1));
1188 const size = @min(buf.len, stream.len - stream.pos);1133 const n = @min(buf.len, fs.remaining_len);
1189 @memcpy(buf[0..size], stream.buf[stream.pos .. stream.pos + size]);1134 @memcpy(buf[0..n], input.buffered()[0..n]);
1190 stream.pos += size;1135 input.toss(n);
1191 return size;1136 fs.remaining_len -= n;
1137 return n;
1192 }1138 }
1193 };1139 };
1194};1140};
...@@ -1199,23 +1145,6 @@ const PackHeader = struct {...@@ -1199,23 +1145,6 @@ const PackHeader = struct {
1199 const signature = "PACK";1145 const signature = "PACK";
1200 const supported_version = 2;1146 const supported_version = 2;
12011147
1202<<<<<<< HEAD
1203 fn read(reader: *std.io.Reader) !PackHeader {
1204 const actual_signature = try reader.take(4);
1205 if (!mem.eql(u8, actual_signature, signature)) return error.InvalidHeader;
1206 const version = try reader.takeInt(u32, .big);
1207||||||| 733b208256
1208 fn read(reader: anytype) !PackHeader {
1209 const actual_signature = reader.readBytesNoEof(4) catch |e| switch (e) {
1210 error.EndOfStream => return error.InvalidHeader,
1211 else => |other| return other,
1212 };
1213 if (!mem.eql(u8, &actual_signature, signature)) return error.InvalidHeader;
1214 const version = reader.readInt(u32, .big) catch |e| switch (e) {
1215 error.EndOfStream => return error.InvalidHeader,
1216 else => |other| return other,
1217 };
1218=======
1219 fn read(reader: *std.Io.Reader) !PackHeader {1148 fn read(reader: *std.Io.Reader) !PackHeader {
1220 const actual_signature = reader.take(4) catch |e| switch (e) {1149 const actual_signature = reader.take(4) catch |e| switch (e) {
1221 error.EndOfStream => return error.InvalidHeader,1150 error.EndOfStream => return error.InvalidHeader,
...@@ -1226,21 +1155,11 @@ const PackHeader = struct {...@@ -1226,21 +1155,11 @@ const PackHeader = struct {
1226 error.EndOfStream => return error.InvalidHeader,1155 error.EndOfStream => return error.InvalidHeader,
1227 else => |other| return other,1156 else => |other| return other,
1228 };1157 };
1229>>>>>>> origin/flate
1230 if (version != supported_version) return error.UnsupportedVersion;1158 if (version != supported_version) return error.UnsupportedVersion;
1231<<<<<<< HEAD
1232 const total_objects = try reader.takeInt(u32, .big);
1233||||||| 733b208256
1234 const total_objects = reader.readInt(u32, .big) catch |e| switch (e) {
1235 error.EndOfStream => return error.InvalidHeader,
1236 else => |other| return other,
1237 };
1238=======
1239 const total_objects = reader.takeInt(u32, .big) catch |e| switch (e) {1159 const total_objects = reader.takeInt(u32, .big) catch |e| switch (e) {
1240 error.EndOfStream => return error.InvalidHeader,1160 error.EndOfStream => return error.InvalidHeader,
1241 else => |other| return other,1161 else => |other| return other,
1242 };1162 };
1243>>>>>>> origin/flate
1244 return .{ .total_objects = total_objects };1163 return .{ .total_objects = total_objects };
1245 }1164 }
1246};1165};
...@@ -1289,30 +1208,13 @@ const EntryHeader = union(Type) {...@@ -1289,30 +1208,13 @@ const EntryHeader = union(Type) {
1289 };1208 };
1290 }1209 }
12911210
1292<<<<<<< HEAD
1293 fn read(format: Oid.Format, reader: *std.io.Reader) !EntryHeader {
1294||||||| 733b208256
1295 fn read(format: Oid.Format, reader: anytype) !EntryHeader {
1296=======
1297 fn read(format: Oid.Format, reader: *std.Io.Reader) !EntryHeader {1211 fn read(format: Oid.Format, reader: *std.Io.Reader) !EntryHeader {
1298>>>>>>> origin/flate
1299 const InitialByte = packed struct { len: u4, type: u3, has_next: bool };1212 const InitialByte = packed struct { len: u4, type: u3, has_next: bool };
1300<<<<<<< HEAD
1301 const initial: InitialByte = @bitCast(try reader.takeByte());
1302 const rest_len = if (initial.has_next) try readSizeVarInt(reader) else 0;
1303||||||| 733b208256
1304 const initial: InitialByte = @bitCast(reader.readByte() catch |e| switch (e) {
1305 error.EndOfStream => return error.InvalidFormat,
1306 else => |other| return other,
1307 });
1308 const rest_len = if (initial.has_next) try readSizeVarInt(reader) else 0;
1309=======
1310 const initial: InitialByte = @bitCast(reader.takeByte() catch |e| switch (e) {1213 const initial: InitialByte = @bitCast(reader.takeByte() catch |e| switch (e) {
1311 error.EndOfStream => return error.InvalidFormat,1214 error.EndOfStream => return error.InvalidFormat,
1312 else => |other| return other,1215 else => |other| return other,
1313 });1216 });
1314 const rest_len = if (initial.has_next) try reader.takeLeb128(u64) else 0;1217 const rest_len = if (initial.has_next) try reader.takeLeb128(u64) else 0;
1315>>>>>>> origin/flate
1316 var uncompressed_length: u64 = initial.len;1218 var uncompressed_length: u64 = initial.len;
1317 uncompressed_length |= std.math.shlExact(u64, rest_len, 4) catch return error.InvalidFormat;1219 uncompressed_length |= std.math.shlExact(u64, rest_len, 4) catch return error.InvalidFormat;
1318 const @"type" = std.enums.fromInt(EntryHeader.Type, initial.type) orelse return error.InvalidFormat;1220 const @"type" = std.enums.fromInt(EntryHeader.Type, initial.type) orelse return error.InvalidFormat;
...@@ -1335,47 +1237,9 @@ const EntryHeader = union(Type) {...@@ -1335,47 +1237,9 @@ const EntryHeader = union(Type) {
1335 }1237 }
1336};1238};
13371239
1338<<<<<<< HEAD
1339fn readSizeVarInt(r: *std.io.Reader) !u64 {
1340||||||| 733b208256
1341fn readSizeVarInt(r: anytype) !u64 {
1342=======
1343fn readOffsetVarInt(r: *std.Io.Reader) !u64 {1240fn readOffsetVarInt(r: *std.Io.Reader) !u64 {
1344>>>>>>> origin/flate
1345 const Byte = packed struct { value: u7, has_next: bool };1241 const Byte = packed struct { value: u7, has_next: bool };
1346<<<<<<< HEAD
1347 var b: Byte = @bitCast(try r.takeByte());1242 var b: Byte = @bitCast(try r.takeByte());
1348 var value: u64 = b.value;
1349 var shift: u6 = 0;
1350 while (b.has_next) {
1351 b = @bitCast(try r.takeByte());
1352 shift = std.math.add(u6, shift, 7) catch return error.InvalidFormat;
1353 value |= @as(u64, b.value) << shift;
1354 }
1355 return value;
1356}
1357
1358fn readOffsetVarInt(r: *std.io.Reader) !u64 {
1359 const Byte = packed struct { value: u7, has_next: bool };
1360 var b: Byte = @bitCast(try r.takeByte());
1361||||||| 733b208256
1362 var b: Byte = @bitCast(try r.readByte());
1363 var value: u64 = b.value;
1364 var shift: u6 = 0;
1365 while (b.has_next) {
1366 b = @bitCast(try r.readByte());
1367 shift = std.math.add(u6, shift, 7) catch return error.InvalidFormat;
1368 value |= @as(u64, b.value) << shift;
1369 }
1370 return value;
1371}
1372
1373fn readOffsetVarInt(r: anytype) !u64 {
1374 const Byte = packed struct { value: u7, has_next: bool };
1375 var b: Byte = @bitCast(try r.readByte());
1376=======
1377 var b: Byte = @bitCast(try r.takeByte());
1378>>>>>>> origin/flate
1379 var value: u64 = b.value;1243 var value: u64 = b.value;
1380 while (b.has_next) {1244 while (b.has_next) {
1381 b = @bitCast(try r.takeByte());1245 b = @bitCast(try r.takeByte());
...@@ -1392,37 +1256,12 @@ const IndexHeader = struct {...@@ -1392,37 +1256,12 @@ const IndexHeader = struct {
1392 const supported_version = 2;1256 const supported_version = 2;
1393 const size = 4 + 4 + @sizeOf([256]u32);1257 const size = 4 + 4 + @sizeOf([256]u32);
13941258
1395<<<<<<< HEAD
1396 fn read(index_header: *IndexHeader, br: *std.io.Reader) !void {
1397 const sig = try br.take(4);
1398 if (!mem.eql(u8, sig, signature)) return error.InvalidHeader;
1399 const version = try br.takeInt(u32, .big);
1400||||||| 733b208256
1401 fn read(reader: anytype) !IndexHeader {
1402 var header_bytes = try reader.readBytesNoEof(size);
1403 if (!mem.eql(u8, header_bytes[0..4], signature)) return error.InvalidHeader;
1404 const version = mem.readInt(u32, header_bytes[4..8], .big);
1405=======
1406 fn read(index_header: *IndexHeader, reader: *std.Io.Reader) !void {1259 fn read(index_header: *IndexHeader, reader: *std.Io.Reader) !void {
1407 const sig = try reader.take(4);1260 const sig = try reader.take(4);
1408 if (!mem.eql(u8, sig, signature)) return error.InvalidHeader;1261 if (!mem.eql(u8, sig, signature)) return error.InvalidHeader;
1409 const version = try reader.takeInt(u32, .big);1262 const version = try reader.takeInt(u32, .big);
1410>>>>>>> origin/flate
1411 if (version != supported_version) return error.UnsupportedVersion;1263 if (version != supported_version) return error.UnsupportedVersion;
1412<<<<<<< HEAD
1413 try br.readSliceEndian(u32, &index_header.fan_out_table, .big);
1414||||||| 733b208256
1415
1416 var fan_out_table: [256]u32 = undefined;
1417 var fan_out_table_stream = std.io.fixedBufferStream(header_bytes[8..]);
1418 const fan_out_table_reader = fan_out_table_stream.reader();
1419 for (&fan_out_table) |*entry| {
1420 entry.* = fan_out_table_reader.readInt(u32, .big) catch unreachable;
1421 }
1422 return .{ .fan_out_table = fan_out_table };
1423=======
1424 try reader.readSliceEndian(u32, &index_header.fan_out_table, .big);1264 try reader.readSliceEndian(u32, &index_header.fan_out_table, .big);
1425>>>>>>> origin/flate
1426 }1265 }
1427};1266};
14281267
...@@ -1491,18 +1330,8 @@ pub fn indexPack(...@@ -1491,18 +1330,8 @@ pub fn indexPack(
1491 }1330 }
1492 @memset(fan_out_table[fan_out_index..], count);1331 @memset(fan_out_table[fan_out_index..], count);
14931332
1494<<<<<<< HEAD
1495 var index_writer_bw = index_writer.writer(&.{});
1496 var index_hashed_writer = index_writer_bw.hashed(Oid.Hasher.init(format));
1497 var write_buffer: [256]u8 = undefined;
1498 var writer = index_hashed_writer.writer(&write_buffer);
1499||||||| 733b208256
1500 var index_hashed_writer = hashedWriter(index_writer, Oid.Hasher.init(format));
1501 const writer = index_hashed_writer.writer();
1502=======
1503 var index_hashed_writer = std.Io.Writer.hashed(&index_writer.interface, Oid.Hasher.init(format), &.{});1333 var index_hashed_writer = std.Io.Writer.hashed(&index_writer.interface, Oid.Hasher.init(format), &.{});
1504 const writer = &index_hashed_writer.writer;1334 const writer = &index_hashed_writer.writer;
1505>>>>>>> origin/flate
1506 try writer.writeAll(IndexHeader.signature);1335 try writer.writeAll(IndexHeader.signature);
1507 try writer.writeInt(u32, IndexHeader.supported_version, .big);1336 try writer.writeInt(u32, IndexHeader.supported_version, .big);
1508 for (fan_out_table) |fan_out_entry| {1337 for (fan_out_table) |fan_out_entry| {
...@@ -1534,16 +1363,9 @@ pub fn indexPack(...@@ -1534,16 +1363,9 @@ pub fn indexPack(
1534 }1363 }
15351364
1536 try writer.writeAll(pack_checksum.slice());1365 try writer.writeAll(pack_checksum.slice());
1537 try writer.flush();
1538 const index_checksum = index_hashed_writer.hasher.finalResult();1366 const index_checksum = index_hashed_writer.hasher.finalResult();
1539<<<<<<< HEAD
1540 try index_writer_bw.writeAll(index_checksum.slice());
1541||||||| 733b208256
1542 try index_writer.writeAll(index_checksum.slice());
1543=======
1544 try index_writer.interface.writeAll(index_checksum.slice());1367 try index_writer.interface.writeAll(index_checksum.slice());
1545 try index_writer.end();1368 try index_writer.end();
1546>>>>>>> origin/flate
1547}1369}
15481370
1549/// Performs the first pass over the packfile data for index construction.1371/// Performs the first pass over the packfile data for index construction.
...@@ -1557,113 +1379,37 @@ fn indexPackFirstPass(...@@ -1557,113 +1379,37 @@ fn indexPackFirstPass(
1557 index_entries: *std.AutoHashMapUnmanaged(Oid, IndexEntry),1379 index_entries: *std.AutoHashMapUnmanaged(Oid, IndexEntry),
1558 pending_deltas: *std.ArrayListUnmanaged(IndexEntry),1380 pending_deltas: *std.ArrayListUnmanaged(IndexEntry),
1559) !Oid {1381) !Oid {
1560<<<<<<< HEAD
1561 var pack_buffer: [2048]u8 = undefined; // Reasonably large buffer for file system.
1562 var pack_hashed = pack.interface.hashed(Oid.Hasher.init(format), &pack_buffer);
1563||||||| 733b208256
1564 var pack_buffered_reader = std.io.bufferedReader(pack.deprecatedReader());
1565 var pack_counting_reader = std.io.countingReader(pack_buffered_reader.reader());
1566 var pack_hashed_reader = hashedReader(pack_counting_reader.reader(), Oid.Hasher.init(format));
1567 const pack_reader = pack_hashed_reader.reader();
1568=======
1569 var flate_buffer: [std.compress.flate.max_window_len]u8 = undefined;1382 var flate_buffer: [std.compress.flate.max_window_len]u8 = undefined;
1570 var pack_buffer: [2048]u8 = undefined; // Reasonably large buffer for file system.1383 var pack_buffer: [2048]u8 = undefined; // Reasonably large buffer for file system.
1571 var pack_hashed = pack.interface.hashed(Oid.Hasher.init(format), &pack_buffer);1384 var pack_hashed = pack.interface.hashed(Oid.Hasher.init(format), &pack_buffer);
1572>>>>>>> origin/flate
15731385
1574<<<<<<< HEAD
1575 const pack_header = try PackHeader.read(&pack_hashed.interface);
1576||||||| 733b208256
1577 const pack_header = try PackHeader.read(pack_reader);
1578=======
1579 const pack_header = try PackHeader.read(&pack_hashed.reader);1386 const pack_header = try PackHeader.read(&pack_hashed.reader);
1580>>>>>>> origin/flate
15811387
1582<<<<<<< HEAD
1583 for (0..pack_header.total_objects) |_| {
1584 const entry_offset = pack.pos - pack_hashed.interface.buffered().len;
1585 var entry_buffer: [64]u8 = undefined; // Buffer only needed for loading EntryHeader.
1586 var entry_crc32_reader = pack_hashed.interface.hashed(std.hash.Crc32.init(), &entry_buffer);
1587 const entry_crc32_br = &entry_crc32_reader.interface;
1588 const entry_header = try EntryHeader.read(format, &entry_crc32_br);
1589 var entry_decompress_stream: zlib.Decompressor = .init(&entry_crc32_br);
1590 // Decompress uses large output buffer; no input buffer needed.
1591 var entry_decompress_br = entry_decompress_stream.reader(&.{});
1592||||||| 733b208256
1593 var current_entry: u32 = 0;
1594 while (current_entry < pack_header.total_objects) : (current_entry += 1) {
1595 const entry_offset = pack_counting_reader.bytes_read;
1596 var entry_crc32_reader = hashedReader(pack_reader, std.hash.Crc32.init());
1597 const entry_header = try EntryHeader.read(format, entry_crc32_reader.reader());
1598=======
1599 for (0..pack_header.total_objects) |_| {1388 for (0..pack_header.total_objects) |_| {
1600 const entry_offset = pack.logicalPos() - pack_hashed.reader.bufferedLen();1389 const entry_offset = pack.logicalPos() - pack_hashed.reader.bufferedLen();
1601 const entry_header = try EntryHeader.read(format, &pack_hashed.reader);1390 const entry_header = try EntryHeader.read(format, &pack_hashed.reader);
1602>>>>>>> origin/flate
1603 switch (entry_header) {1391 switch (entry_header) {
1604 .commit, .tree, .blob, .tag => |object| {1392 .commit, .tree, .blob, .tag => |object| {
1605<<<<<<< HEAD
1606 var oid_hasher = Oid.Hasher.init(format);
1607 var oid_hasher_buffer: [zlib.max_window_len]u8 = undefined;
1608 var oid_hasher_bw = oid_hasher.writer(&oid_hasher_buffer);
1609||||||| 733b208256
1610 var entry_decompress_stream = std.compress.zlib.decompressor(entry_crc32_reader.reader());
1611 var entry_counting_reader = std.io.countingReader(entry_decompress_stream.reader());
1612 var entry_hashed_writer = hashedWriter(std.io.null_writer, Oid.Hasher.init(format));
1613 const entry_writer = entry_hashed_writer.writer();
1614=======
1615 var entry_decompress: std.compress.flate.Decompress = .init(&pack_hashed.reader, .zlib, &.{});1393 var entry_decompress: std.compress.flate.Decompress = .init(&pack_hashed.reader, .zlib, &.{});
1616 var oid_hasher: Oid.Hashing = .init(format, &flate_buffer);1394 var oid_hasher: Oid.Hashing = .init(format, &flate_buffer);
1617 const oid_hasher_w = oid_hasher.writer();1395 const oid_hasher_w = oid_hasher.writer();
1618>>>>>>> origin/flate
1619 // The object header is not included in the pack data but is1396 // The object header is not included in the pack data but is
1620<<<<<<< HEAD
1621 // part of the object's ID.
1622 try oid_hasher_bw.print("{s} {d}\x00", .{ @tagName(entry_header), object.uncompressed_length });
1623 const n = try entry_decompress_br.readRemaining(&oid_hasher_bw);
1624 if (n != object.uncompressed_length) return error.InvalidObject;
1625 try oid_hasher_bw.flush();
1626 const oid = oid_hasher.finalResult();
1627||||||| 733b208256
1628 // part of the object's ID
1629 try entry_writer.print("{s} {}\x00", .{ @tagName(entry_header), object.uncompressed_length });
1630 var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
1631 try fifo.pump(entry_counting_reader.reader(), entry_writer);
1632 if (entry_counting_reader.bytes_read != object.uncompressed_length) {
1633 return error.InvalidObject;
1634 }
1635 const oid = entry_hashed_writer.hasher.finalResult();
1636=======
1637 // part of the object's ID1397 // part of the object's ID
1638 try oid_hasher_w.print("{t} {d}\x00", .{ entry_header, object.uncompressed_length });1398 try oid_hasher_w.print("{t} {d}\x00", .{ entry_header, object.uncompressed_length });
1639 const n = try entry_decompress.reader.streamRemaining(oid_hasher_w);1399 const n = try entry_decompress.reader.streamRemaining(oid_hasher_w);
1640 if (n != object.uncompressed_length) return error.InvalidObject;1400 if (n != object.uncompressed_length) return error.InvalidObject;
1641 const oid = oid_hasher.final();1401 const oid = oid_hasher.final();
1642 if (!skip_checksums) @compileError("TODO");1402 if (!skip_checksums) @compileError("TODO");
1643>>>>>>> origin/flate
1644 try index_entries.put(allocator, oid, .{1403 try index_entries.put(allocator, oid, .{
1645 .offset = entry_offset,1404 .offset = entry_offset,
1646 .crc32 = 0,1405 .crc32 = 0,
1647 });1406 });
1648 },1407 },
1649 inline .ofs_delta, .ref_delta => |delta| {1408 inline .ofs_delta, .ref_delta => |delta| {
1650<<<<<<< HEAD
1651 const n = try entry_decompress_br.discardRemaining();
1652 if (n != delta.uncompressed_length) return error.InvalidObject;
1653||||||| 733b208256
1654 var entry_decompress_stream = std.compress.zlib.decompressor(entry_crc32_reader.reader());
1655 var entry_counting_reader = std.io.countingReader(entry_decompress_stream.reader());
1656 var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
1657 try fifo.pump(entry_counting_reader.reader(), std.io.null_writer);
1658 if (entry_counting_reader.bytes_read != delta.uncompressed_length) {
1659 return error.InvalidObject;
1660 }
1661=======
1662 var entry_decompress: std.compress.flate.Decompress = .init(&pack_hashed.reader, .zlib, &flate_buffer);1409 var entry_decompress: std.compress.flate.Decompress = .init(&pack_hashed.reader, .zlib, &flate_buffer);
1663 const n = try entry_decompress.reader.discardRemaining();1410 const n = try entry_decompress.reader.discardRemaining();
1664 if (n != delta.uncompressed_length) return error.InvalidObject;1411 if (n != delta.uncompressed_length) return error.InvalidObject;
1665 if (!skip_checksums) @compileError("TODO");1412 if (!skip_checksums) @compileError("TODO");
1666>>>>>>> origin/flate
1667 try pending_deltas.append(allocator, .{1413 try pending_deltas.append(allocator, .{
1668 .offset = entry_offset,1414 .offset = entry_offset,
1669 .crc32 = 0,1415 .crc32 = 0,
...@@ -1672,28 +1418,8 @@ fn indexPackFirstPass(...@@ -1672,28 +1418,8 @@ fn indexPackFirstPass(
1672 }1418 }
1673 }1419 }
16741420
1675<<<<<<< HEAD
1676 const pack_checksum = pack_hashed.hasher.finalResult();
1677 const recorded_checksum = try Oid.readBytes(format, &pack.interface);
1678 if (!mem.eql(u8, pack_checksum.slice(), recorded_checksum.slice())) {
1679 return error.CorruptedPack;
1680 }
1681 return pack_checksum;
1682||||||| 733b208256
1683 const pack_checksum = pack_hashed_reader.hasher.finalResult();
1684 const recorded_checksum = try Oid.readBytes(format, pack_buffered_reader.reader());
1685 if (!mem.eql(u8, pack_checksum.slice(), recorded_checksum.slice())) {
1686 return error.CorruptedPack;
1687 }
1688 _ = pack_reader.readByte() catch |e| switch (e) {
1689 error.EndOfStream => return pack_checksum,
1690 else => |other| return other,
1691 };
1692 return error.InvalidFormat;
1693=======
1694 if (!skip_checksums) @compileError("TODO");1421 if (!skip_checksums) @compileError("TODO");
1695 return pack_hashed.hasher.finalResult();1422 return pack_hashed.hasher.finalResult();
1696>>>>>>> origin/flate
1697}1423}
16981424
1699/// Attempts to determine the final object ID of the given deltified object.1425/// Attempts to determine the final object ID of the given deltified object.
...@@ -1738,22 +1464,6 @@ fn indexPackHashDelta(...@@ -1738,22 +1464,6 @@ fn indexPackHashDelta(
17381464
1739 const base_data = try resolveDeltaChain(allocator, format, pack, base_object, delta_offsets.items, cache);1465 const base_data = try resolveDeltaChain(allocator, format, pack, base_object, delta_offsets.items, cache);
17401466
1741<<<<<<< HEAD
1742 var entry_hasher: Oid.Hasher = .init(format);
1743 var entry_hasher_buffer: [64]u8 = undefined;
1744 var entry_hasher_bw = entry_hasher.writer(&entry_hasher_buffer);
1745 // Writes to hashers cannot fail.
1746 entry_hasher_bw.print("{s} {d}\x00", .{ @tagName(base_object.type), base_data.len }) catch unreachable;
1747 entry_hasher_bw.writeAll(base_data) catch unreachable;
1748 entry_hasher_bw.flush() catch unreachable;
1749 return entry_hasher.finalResult();
1750||||||| 733b208256
1751 var entry_hasher: Oid.Hasher = .init(format);
1752 var entry_hashed_writer = hashedWriter(std.io.null_writer, &entry_hasher);
1753 try entry_hashed_writer.writer().print("{s} {}\x00", .{ @tagName(base_object.type), base_data.len });
1754 entry_hasher.update(base_data);
1755 return entry_hasher.finalResult();
1756=======
1757 var entry_hasher_buffer: [64]u8 = undefined;1467 var entry_hasher_buffer: [64]u8 = undefined;
1758 var entry_hasher: Oid.Hashing = .init(format, &entry_hasher_buffer);1468 var entry_hasher: Oid.Hashing = .init(format, &entry_hasher_buffer);
1759 const entry_hasher_w = entry_hasher.writer();1469 const entry_hasher_w = entry_hasher.writer();
...@@ -1761,7 +1471,6 @@ fn indexPackHashDelta(...@@ -1761,7 +1471,6 @@ fn indexPackHashDelta(
1761 entry_hasher_w.print("{t} {d}\x00", .{ base_object.type, base_data.len }) catch unreachable;1471 entry_hasher_w.print("{t} {d}\x00", .{ base_object.type, base_data.len }) catch unreachable;
1762 entry_hasher_w.writeAll(base_data) catch unreachable;1472 entry_hasher_w.writeAll(base_data) catch unreachable;
1763 return entry_hasher.final();1473 return entry_hasher.final();
1764>>>>>>> origin/flate
1765}1474}
17661475
1767/// Resolves a chain of deltas, returning the final base object data. `pack` is1476/// Resolves a chain of deltas, returning the final base object data. `pack` is
...@@ -1783,24 +1492,6 @@ fn resolveDeltaChain(...@@ -1783,24 +1492,6 @@ fn resolveDeltaChain(
17831492
1784 const delta_offset = delta_offsets[i];1493 const delta_offset = delta_offsets[i];
1785 try pack.seekTo(delta_offset);1494 try pack.seekTo(delta_offset);
1786<<<<<<< HEAD
1787 const delta_header = try EntryHeader.read(format, &pack.interface);
1788 _ = delta_header;
1789 var delta_decompress: zlib.Decompressor = .init(&pack.interface);
1790 var delta_decompress_buffer: [zlib.max_window_len]u8 = undefined;
1791 var delta_reader = delta_decompress.reader(&delta_decompress_buffer);
1792 _ = try readSizeVarInt(&delta_reader); // base object size
1793 const expanded_size = try readSizeVarInt(&delta_reader);
1794||||||| 733b208256
1795 const delta_header = try EntryHeader.read(format, pack.deprecatedReader());
1796 const delta_data = try readObjectRaw(allocator, pack.deprecatedReader(), delta_header.uncompressedLength());
1797 defer allocator.free(delta_data);
1798 var delta_stream = std.io.fixedBufferStream(delta_data);
1799 const delta_reader = delta_stream.reader();
1800 _ = try readSizeVarInt(delta_reader); // base object size
1801 const expanded_size = try readSizeVarInt(delta_reader);
1802
1803=======
1804 const delta_header = try EntryHeader.read(format, &pack.interface);1495 const delta_header = try EntryHeader.read(format, &pack.interface);
1805 const delta_data = try readObjectRaw(allocator, &pack.interface, delta_header.uncompressedLength());1496 const delta_data = try readObjectRaw(allocator, &pack.interface, delta_header.uncompressedLength());
1806 defer allocator.free(delta_data);1497 defer allocator.free(delta_data);
...@@ -1808,24 +1499,12 @@ fn resolveDeltaChain(...@@ -1808,24 +1499,12 @@ fn resolveDeltaChain(
1808 _ = try delta_reader.takeLeb128(u64); // base object size1499 _ = try delta_reader.takeLeb128(u64); // base object size
1809 const expanded_size = try delta_reader.takeLeb128(u64);1500 const expanded_size = try delta_reader.takeLeb128(u64);
18101501
1811>>>>>>> origin/flate
1812 const expanded_alloc_size = std.math.cast(usize, expanded_size) orelse return error.ObjectTooLarge;1502 const expanded_alloc_size = std.math.cast(usize, expanded_size) orelse return error.ObjectTooLarge;
1813 const expanded_data = try allocator.alloc(u8, expanded_alloc_size);1503 const expanded_data = try allocator.alloc(u8, expanded_alloc_size);
1814 errdefer allocator.free(expanded_data);1504 errdefer allocator.free(expanded_data);
1815<<<<<<< HEAD
1816 var expanded_delta_stream: Writer = .fixed(expanded_data);
1817 try expandDelta(base_data, &delta_reader, &expanded_delta_stream);
1818 if (expanded_delta_stream.end != expanded_size) return error.InvalidObject;
1819||||||| 733b208256
1820 var expanded_delta_stream = std.io.fixedBufferStream(expanded_data);
1821 var base_stream = std.io.fixedBufferStream(base_data);
1822 try expandDelta(&base_stream, delta_reader, expanded_delta_stream.writer());
1823 if (expanded_delta_stream.pos != expanded_size) return error.InvalidObject;
1824=======
1825 var expanded_delta_stream: std.Io.Writer = .fixed(expanded_data);1505 var expanded_delta_stream: std.Io.Writer = .fixed(expanded_data);
1826 try expandDelta(base_data, &delta_reader, &expanded_delta_stream);1506 try expandDelta(base_data, &delta_reader, &expanded_delta_stream);
1827 if (expanded_delta_stream.end != expanded_size) return error.InvalidObject;1507 if (expanded_delta_stream.end != expanded_size) return error.InvalidObject;
1828>>>>>>> origin/flate
18291508
1830 try cache.put(allocator, delta_offset, .{ .type = base_object.type, .data = expanded_data });1509 try cache.put(allocator, delta_offset, .{ .type = base_object.type, .data = expanded_data });
1831 base_data = expanded_data;1510 base_data = expanded_data;
...@@ -1833,68 +1512,24 @@ fn resolveDeltaChain(...@@ -1833,68 +1512,24 @@ fn resolveDeltaChain(
1833 return base_data;1512 return base_data;
1834}1513}
18351514
1836<<<<<<< HEAD
1837/// Reads the complete contents of an object from `reader`.
1838fn readObjectRaw(gpa: Allocator, reader: *std.io.Reader, size: u64) ![]u8 {
1839||||||| 733b208256
1840/// Reads the complete contents of an object from `reader`. This function may
1841/// read more bytes than required from `reader`, so the reader position after
1842/// returning is not reliable.
1843fn readObjectRaw(allocator: Allocator, reader: anytype, size: u64) ![]u8 {
1844=======
1845/// Reads the complete contents of an object from `reader`. This function may1515/// Reads the complete contents of an object from `reader`. This function may
1846/// read more bytes than required from `reader`, so the reader position after1516/// read more bytes than required from `reader`, so the reader position after
1847/// returning is not reliable.1517/// returning is not reliable.
1848fn readObjectRaw(allocator: Allocator, reader: *std.Io.Reader, size: u64) ![]u8 {1518fn readObjectRaw(allocator: Allocator, reader: *std.Io.Reader, size: u64) ![]u8 {
1849>>>>>>> origin/flate
1850 const alloc_size = std.math.cast(usize, size) orelse return error.ObjectTooLarge;1519 const alloc_size = std.math.cast(usize, size) orelse return error.ObjectTooLarge;
1851<<<<<<< HEAD
1852 var decompress: zlib.Decompressor = .init(reader);
1853 var buffer: std.ArrayListUnmanaged(u8) = .empty;
1854 defer buffer.deinit(gpa);
1855 try decompress.reader().readRemainingArrayList(gpa, null, &buffer, .limited(alloc_size), zlib.max_window_len);
1856 if (buffer.items.len < size) return error.EndOfStream;
1857 return buffer.toOwnedSlice(gpa);
1858||||||| 733b208256
1859 var buffered_reader = std.io.bufferedReader(reader);
1860 var decompress_stream = std.compress.zlib.decompressor(buffered_reader.reader());
1861 const data = try allocator.alloc(u8, alloc_size);
1862 errdefer allocator.free(data);
1863 try decompress_stream.reader().readNoEof(data);
1864 _ = decompress_stream.reader().readByte() catch |e| switch (e) {
1865 error.EndOfStream => return data,
1866 else => |other| return other,
1867 };
1868 return error.InvalidFormat;
1869=======
1870 var aw: std.Io.Writer.Allocating = .init(allocator);1520 var aw: std.Io.Writer.Allocating = .init(allocator);
1871 try aw.ensureTotalCapacity(alloc_size + std.compress.flate.max_window_len);1521 try aw.ensureTotalCapacity(alloc_size + std.compress.flate.max_window_len);
1872 defer aw.deinit();1522 defer aw.deinit();
1873 var decompress: std.compress.flate.Decompress = .init(reader, .zlib, &.{});1523 var decompress: std.compress.flate.Decompress = .init(reader, .zlib, &.{});
1874 try decompress.reader.streamExact(&aw.writer, alloc_size);1524 try decompress.reader.streamExact(&aw.writer, alloc_size);
1875 return aw.toOwnedSlice();1525 return aw.toOwnedSlice();
1876>>>>>>> origin/flate
1877}1526}
18781527
1879<<<<<<< HEAD
1880||||||| 733b208256
1881/// Expands delta data from `delta_reader` to `writer`. `base_object` must
1882/// support `reader` and `seekTo` (such as a `std.io.FixedBufferStream`).
1883///
1884=======
1885/// Expands delta data from `delta_reader` to `writer`.1528/// Expands delta data from `delta_reader` to `writer`.
1886///1529///
1887>>>>>>> origin/flate
1888/// The format of the delta data is documented in1530/// The format of the delta data is documented in
1889/// [pack-format](https://git-scm.com/docs/pack-format).1531/// [pack-format](https://git-scm.com/docs/pack-format).
1890<<<<<<< HEAD
1891fn expandDelta(base_object: []const u8, delta_reader: *std.io.Reader, writer: *Writer) !void {
1892 var base_offset: u32 = 0;
1893||||||| 733b208256
1894fn expandDelta(base_object: anytype, delta_reader: anytype, writer: anytype) !void {
1895=======
1896fn expandDelta(base_object: []const u8, delta_reader: *std.Io.Reader, writer: *std.Io.Writer) !void {1532fn expandDelta(base_object: []const u8, delta_reader: *std.Io.Reader, writer: *std.Io.Writer) !void {
1897>>>>>>> origin/flate
1898 while (true) {1533 while (true) {
1899 const inst: packed struct { value: u7, copy: bool } = @bitCast(delta_reader.takeByte() catch |e| switch (e) {1534 const inst: packed struct { value: u7, copy: bool } = @bitCast(delta_reader.takeByte() catch |e| switch (e) {
1900 error.EndOfStream => return,1535 error.EndOfStream => return,
...@@ -1916,13 +1551,7 @@ fn expandDelta(base_object: []const u8, delta_reader: *std.Io.Reader, writer: *s...@@ -1916,13 +1551,7 @@ fn expandDelta(base_object: []const u8, delta_reader: *std.Io.Reader, writer: *s
1916 .offset3 = if (available.offset3) try delta_reader.takeByte() else 0,1551 .offset3 = if (available.offset3) try delta_reader.takeByte() else 0,
1917 .offset4 = if (available.offset4) try delta_reader.takeByte() else 0,1552 .offset4 = if (available.offset4) try delta_reader.takeByte() else 0,
1918 };1553 };
1919<<<<<<< HEAD
1920 base_offset = @bitCast(offset_parts);
1921||||||| 733b208256
1922 const offset: u32 = @bitCast(offset_parts);
1923=======
1924 const base_offset: u32 = @bitCast(offset_parts);1554 const base_offset: u32 = @bitCast(offset_parts);
1925>>>>>>> origin/flate
1926 const size_parts: packed struct { size1: u8, size2: u8, size3: u8 } = .{1555 const size_parts: packed struct { size1: u8, size2: u8, size3: u8 } = .{
1927 .size1 = if (available.size1) try delta_reader.takeByte() else 0,1556 .size1 = if (available.size1) try delta_reader.takeByte() else 0,
1928 .size2 = if (available.size2) try delta_reader.takeByte() else 0,1557 .size2 = if (available.size2) try delta_reader.takeByte() else 0,
...@@ -1930,28 +1559,9 @@ fn expandDelta(base_object: []const u8, delta_reader: *std.Io.Reader, writer: *s...@@ -1930,28 +1559,9 @@ fn expandDelta(base_object: []const u8, delta_reader: *std.Io.Reader, writer: *s
1930 };1559 };
1931 var size: u24 = @bitCast(size_parts);1560 var size: u24 = @bitCast(size_parts);
1932 if (size == 0) size = 0x10000;1561 if (size == 0) size = 0x10000;
1933<<<<<<< HEAD
1934
1935 try writer.writeAll(base_object[base_offset..][0..size]);1562 try writer.writeAll(base_object[base_offset..][0..size]);
1936 base_offset += size;
1937||||||| 733b208256
1938 try base_object.seekTo(offset);
1939 var copy_reader = std.io.limitedReader(base_object.reader(), size);
1940 var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
1941 try fifo.pump(copy_reader.reader(), writer);
1942=======
1943 try writer.writeAll(base_object[base_offset..][0..size]);
1944>>>>>>> origin/flate
1945 } else if (inst.value != 0) {1563 } else if (inst.value != 0) {
1946<<<<<<< HEAD
1947 try delta_reader.readAll(writer, .limited(inst.value));
1948||||||| 733b208256
1949 var data_reader = std.io.limitedReader(delta_reader, inst.value);
1950 var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
1951 try fifo.pump(data_reader.reader(), writer);
1952=======
1953 try delta_reader.streamExact(writer, inst.value);1564 try delta_reader.streamExact(writer, inst.value);
1954>>>>>>> origin/flate
1955 } else {1565 } else {
1956 return error.InvalidDeltaInstruction;1566 return error.InvalidDeltaInstruction;
1957 }1567 }
...@@ -1986,16 +1596,9 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void...@@ -1986,16 +1596,9 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void
19861596
1987 var index_file = try git_dir.dir.createFile("testrepo.idx", .{ .read = true });1597 var index_file = try git_dir.dir.createFile("testrepo.idx", .{ .read = true });
1988 defer index_file.close();1598 defer index_file.close();
1989<<<<<<< HEAD
1990 var index_file_writer = index_file.writer();
1991 try indexPack(testing.allocator, format, pack_file, &index_file_writer);
1992||||||| 733b208256
1993 try indexPack(testing.allocator, format, pack_file, index_file.deprecatedWriter());
1994=======
1995 var index_file_buffer: [2000]u8 = undefined;1599 var index_file_buffer: [2000]u8 = undefined;
1996 var index_file_writer = index_file.writer(&index_file_buffer);1600 var index_file_writer = index_file.writer(&index_file_buffer);
1997 try indexPack(testing.allocator, format, &pack_file_reader, &index_file_writer);1601 try indexPack(testing.allocator, format, &pack_file_reader, &index_file_writer);
1998>>>>>>> origin/flate
19991602
2000 // Arbitrary size limit on files read while checking the repository contents1603 // Arbitrary size limit on files read while checking the repository contents
2001 // (all files in the test repo are known to be smaller than this)1604 // (all files in the test repo are known to be smaller than this)
...@@ -2011,16 +1614,9 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void...@@ -2011,16 +1614,9 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void
2011 try testing.expectEqualSlices(u8, testrepo_idx, index_file_data);1614 try testing.expectEqualSlices(u8, testrepo_idx, index_file_data);
2012 }1615 }
20131616
2014<<<<<<< HEAD
2015 var index_file_reader = index_file_writer.moveToReader();
2016 var repository = try Repository.init(testing.allocator, format, pack_file, &index_file_reader);
2017||||||| 733b208256
2018 var repository = try Repository.init(testing.allocator, format, pack_file, index_file);
2019=======
2020 var index_file_reader = index_file.reader(&index_file_buffer);1617 var index_file_reader = index_file.reader(&index_file_buffer);
2021 var repository: Repository = undefined;1618 var repository: Repository = undefined;
2022 try repository.init(testing.allocator, format, &pack_file_reader, &index_file_reader);1619 try repository.init(testing.allocator, format, &pack_file_reader, &index_file_reader);
2023>>>>>>> origin/flate
2024 defer repository.deinit();1620 defer repository.deinit();
20251621
2026 var worktree = testing.tmpDir(.{ .iterate = true });1622 var worktree = testing.tmpDir(.{ .iterate = true });
...@@ -2107,12 +1703,10 @@ test "SHA-256 packfile indexing and checkout" {...@@ -2107,12 +1703,10 @@ test "SHA-256 packfile indexing and checkout" {
2107/// Checks out a commit of a packfile. Intended for experimenting with and1703/// Checks out a commit of a packfile. Intended for experimenting with and
2108/// benchmarking possible optimizations to the indexing and checkout behavior.1704/// benchmarking possible optimizations to the indexing and checkout behavior.
2109pub fn main() !void {1705pub fn main() !void {
2110 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;1706 const allocator = std.heap.smp_allocator;
2111 defer _ = debug_allocator.deinit();
2112 const gpa = if (std.debug.runtime_safety) debug_allocator.allocator() else std.heap.smp_allocator;
21131707
2114 const args = try std.process.argsAlloc(gpa);1708 const args = try std.process.argsAlloc(allocator);
2115 defer std.process.argsFree(gpa, args);1709 defer std.process.argsFree(allocator, args);
2116 if (args.len != 5) {1710 if (args.len != 5) {
2117 return error.InvalidArguments; // Arguments: format packfile commit worktree1711 return error.InvalidArguments; // Arguments: format packfile commit worktree
2118 }1712 }
...@@ -2134,34 +1728,16 @@ pub fn main() !void {...@@ -2134,34 +1728,16 @@ pub fn main() !void {
2134 std.debug.print("Starting index...\n", .{});1728 std.debug.print("Starting index...\n", .{});
2135 var index_file = try git_dir.createFile("idx", .{ .read = true });1729 var index_file = try git_dir.createFile("idx", .{ .read = true });
2136 defer index_file.close();1730 defer index_file.close();
2137<<<<<<< HEAD1731 var index_file_buffer: [4096]u8 = undefined;
2138 var index_file_writer = index_file.writer();1732 var index_file_writer = index_file.writer(&index_file_buffer);
2139 var pack_file_reader = pack_file.reader();1733 try indexPack(allocator, format, &pack_file_reader, &index_file_writer);
2140 try indexPack(gpa, format, &pack_file_reader, &index_file_writer);
2141 try index_file.sync();
2142||||||| 733b208256
2143 var index_buffered_writer = std.io.bufferedWriter(index_file.deprecatedWriter());
2144 try indexPack(allocator, format, pack_file, index_buffered_writer.writer());
2145 try index_buffered_writer.flush();
2146 try index_file.sync();
2147=======
2148 var index_buffered_writer = std.io.bufferedWriter(index_file.deprecatedWriter());
2149 try indexPack(allocator, format, &pack_file_reader, index_buffered_writer.writer());
2150 try index_buffered_writer.flush();
2151>>>>>>> origin/flate
21521734
2153 std.debug.print("Starting checkout...\n", .{});1735 std.debug.print("Starting checkout...\n", .{});
2154<<<<<<< HEAD1736 var index_file_reader = index_file.reader(&index_file_buffer);
2155 var index_file_reader = index_file_writer.moveToReader();
2156 var repository: Repository = undefined;1737 var repository: Repository = undefined;
2157 try repository.init(gpa, format, &pack_file_reader, &index_file_reader);1738 try repository.init(allocator, format, &pack_file_reader, &index_file_reader);
2158||||||| 733b208256
2159 var repository = try Repository.init(allocator, format, pack_file, index_file);
2160=======
2161 var repository = try Repository.init(allocator, format, &pack_file_reader, index_file);
2162>>>>>>> origin/flate
2163 defer repository.deinit();1739 defer repository.deinit();
2164 var diagnostics: Diagnostics = .{ .allocator = gpa };1740 var diagnostics: Diagnostics = .{ .allocator = allocator };
2165 defer diagnostics.deinit();1741 defer diagnostics.deinit();
2166 try repository.checkout(worktree, commit, &diagnostics);1742 try repository.checkout(worktree, commit, &diagnostics);
21671743
src/Sema.zig+60-73
...@@ -2631,7 +2631,7 @@ fn reparentOwnedErrorMsg(...@@ -2631,7 +2631,7 @@ fn reparentOwnedErrorMsg(
26312631
2632 const orig_notes = msg.notes.len;2632 const orig_notes = msg.notes.len;
2633 msg.notes = try sema.gpa.realloc(msg.notes, orig_notes + 1);2633 msg.notes = try sema.gpa.realloc(msg.notes, orig_notes + 1);
2634 std.mem.copyBackwards(Zcu.ErrorMsg, msg.notes[1..], msg.notes[0..orig_notes]);2634 @memmove(msg.notes[1..][0..orig_notes], msg.notes[0..orig_notes]);
2635 msg.notes[0] = .{2635 msg.notes[0] = .{
2636 .src_loc = msg.src_loc,2636 .src_loc = msg.src_loc,
2637 .msg = msg.msg,2637 .msg = msg.msg,
...@@ -2649,7 +2649,13 @@ pub fn analyzeAsAlign(...@@ -2649,7 +2649,13 @@ pub fn analyzeAsAlign(
2649 src: LazySrcLoc,2649 src: LazySrcLoc,
2650 air_ref: Air.Inst.Ref,2650 air_ref: Air.Inst.Ref,
2651) !Alignment {2651) !Alignment {
2652 const alignment_big = try sema.analyzeAsInt(block, src, air_ref, align_ty, .{ .simple = .@"align" });2652 const alignment_big = try sema.analyzeAsInt(
2653 block,
2654 src,
2655 air_ref,
2656 align_ty,
2657 .{ .simple = .@"align" },
2658 );
2653 return sema.validateAlign(block, src, alignment_big);2659 return sema.validateAlign(block, src, alignment_big);
2654}2660}
26552661
...@@ -3926,11 +3932,12 @@ fn resolveComptimeKnownAllocPtr(sema: *Sema, block: *Block, alloc: Air.Inst.Ref,...@@ -3926,11 +3932,12 @@ fn resolveComptimeKnownAllocPtr(sema: *Sema, block: *Block, alloc: Air.Inst.Ref,
3926 // Whilst constructing our mapping, we will also initialize optional and error union payloads when3932 // Whilst constructing our mapping, we will also initialize optional and error union payloads when
3927 // we encounter the corresponding pointers. For this reason, the ordering of `to_map` matters.3933 // we encounter the corresponding pointers. For this reason, the ordering of `to_map` matters.
3928 var to_map = try std.ArrayList(Air.Inst.Index).initCapacity(sema.arena, stores.len);3934 var to_map = try std.ArrayList(Air.Inst.Index).initCapacity(sema.arena, stores.len);
3935
3929 for (stores) |store_inst_idx| {3936 for (stores) |store_inst_idx| {
3930 const store_inst = sema.air_instructions.get(@intFromEnum(store_inst_idx));3937 const store_inst = sema.air_instructions.get(@intFromEnum(store_inst_idx));
3931 const ptr_to_map = switch (store_inst.tag) {3938 const ptr_to_map = switch (store_inst.tag) {
3932 .store, .store_safe => store_inst.data.bin_op.lhs.toIndex().?, // Map the pointer being stored to.3939 .store, .store_safe => store_inst.data.bin_op.lhs.toIndex().?, // Map the pointer being stored to.
3933 .set_union_tag => continue, // Ignore for now; handled after we map pointers3940 .set_union_tag => store_inst.data.bin_op.lhs.toIndex().?, // Map the union pointer.
3934 .optional_payload_ptr_set, .errunion_payload_ptr_set => store_inst_idx, // Map the generated pointer itself.3941 .optional_payload_ptr_set, .errunion_payload_ptr_set => store_inst_idx, // Map the generated pointer itself.
3935 else => unreachable,3942 else => unreachable,
3936 };3943 };
...@@ -4047,13 +4054,12 @@ fn resolveComptimeKnownAllocPtr(sema: *Sema, block: *Block, alloc: Air.Inst.Ref,...@@ -4047,13 +4054,12 @@ fn resolveComptimeKnownAllocPtr(sema: *Sema, block: *Block, alloc: Air.Inst.Ref,
4047 const maybe_union_ty = Value.fromInterned(decl_parent_ptr).typeOf(zcu).childType(zcu);4054 const maybe_union_ty = Value.fromInterned(decl_parent_ptr).typeOf(zcu).childType(zcu);
4048 if (zcu.typeToUnion(maybe_union_ty)) |union_obj| {4055 if (zcu.typeToUnion(maybe_union_ty)) |union_obj| {
4049 // As this is a union field, we must store to the pointer now to set the tag.4056 // As this is a union field, we must store to the pointer now to set the tag.
4050 // If the payload is OPV, there will not be a payload store, so we store that value.4057 // The payload value will be stored later, so undef is a sufficent payload for now.
4051 // Otherwise, there will be a payload store to process later, so undef will suffice.
4052 const payload_ty: Type = .fromInterned(union_obj.field_types.get(&zcu.intern_pool)[idx]);4058 const payload_ty: Type = .fromInterned(union_obj.field_types.get(&zcu.intern_pool)[idx]);
4053 const payload_val = try sema.typeHasOnePossibleValue(payload_ty) orelse try pt.undefValue(payload_ty);4059 const payload_val = try pt.undefValue(payload_ty);
4054 const tag_val = try pt.enumValueFieldIndex(.fromInterned(union_obj.enum_tag_ty), idx);4060 const tag_val = try pt.enumValueFieldIndex(.fromInterned(union_obj.enum_tag_ty), idx);
4055 const store_val = try pt.unionValue(maybe_union_ty, tag_val, payload_val);4061 const store_val = try pt.unionValue(maybe_union_ty, tag_val, payload_val);
4056 try sema.storePtrVal(block, LazySrcLoc.unneeded, Value.fromInterned(decl_parent_ptr), store_val, maybe_union_ty);4062 try sema.storePtrVal(block, .unneeded, .fromInterned(decl_parent_ptr), store_val, maybe_union_ty);
4057 }4063 }
4058 break :ptr (try Value.fromInterned(decl_parent_ptr).ptrField(idx, pt)).toIntern();4064 break :ptr (try Value.fromInterned(decl_parent_ptr).ptrField(idx, pt)).toIntern();
4059 },4065 },
...@@ -8900,6 +8906,14 @@ fn resolveGenericBody(...@@ -8900,6 +8906,14 @@ fn resolveGenericBody(
8900 return sema.resolveConstDefinedValue(block, src, result, reason);8906 return sema.resolveConstDefinedValue(block, src, result, reason);
8901}8907}
89028908
8909/// Given a library name, examines if the library name should end up in
8910/// `link.File.Options.windows_libs` table (for example, libc is always
8911/// specified via dedicated flag `link_libc` instead),
8912/// and puts it there if it doesn't exist.
8913/// It also dupes the library name which can then be saved as part of the
8914/// respective `Decl` (either `ExternFn` or `Var`).
8915/// The liveness of the duped library name is tied to liveness of `Zcu`.
8916/// To deallocate, call `deinit` on the respective `Decl` (`ExternFn` or `Var`).
8903pub fn handleExternLibName(8917pub fn handleExternLibName(
8904 sema: *Sema,8918 sema: *Sema,
8905 block: *Block,8919 block: *Block,
...@@ -8949,6 +8963,11 @@ pub fn handleExternLibName(...@@ -8949,6 +8963,11 @@ pub fn handleExternLibName(
8949 .{ lib_name, lib_name },8963 .{ lib_name, lib_name },
8950 );8964 );
8951 }8965 }
8966 comp.addLinkLib(lib_name) catch |err| {
8967 return sema.fail(block, src_loc, "unable to add link lib '{s}': {s}", .{
8968 lib_name, @errorName(err),
8969 });
8970 };
8952 }8971 }
8953}8972}
89548973
...@@ -14458,8 +14477,8 @@ fn analyzeTupleMul(...@@ -14458,8 +14477,8 @@ fn analyzeTupleMul(
14458 }14477 }
14459 }14478 }
14460 for (0..factor) |i| {14479 for (0..factor) |i| {
14461 mem.copyForwards(InternPool.Index, types[tuple_len * i ..], types[0..tuple_len]);14480 @memmove(types[tuple_len * i ..][0..tuple_len], types[0..tuple_len]);
14462 mem.copyForwards(InternPool.Index, values[tuple_len * i ..], values[0..tuple_len]);14481 @memmove(values[tuple_len * i ..][0..tuple_len], values[0..tuple_len]);
14463 }14482 }
14464 break :rs runtime_src;14483 break :rs runtime_src;
14465 };14484 };
...@@ -18817,7 +18836,7 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air...@@ -18817,7 +18836,7 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
18817 const abi_align: Alignment = if (inst_data.flags.has_align) blk: {18836 const abi_align: Alignment = if (inst_data.flags.has_align) blk: {
18818 const ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_i]);18837 const ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_i]);
18819 extra_i += 1;18838 extra_i += 1;
18820 const coerced = try sema.coerce(block, .u32, try sema.resolveInst(ref), align_src);18839 const coerced = try sema.coerce(block, align_ty, try sema.resolveInst(ref), align_src);
18821 const val = try sema.resolveConstDefinedValue(block, align_src, coerced, .{ .simple = .@"align" });18840 const val = try sema.resolveConstDefinedValue(block, align_src, coerced, .{ .simple = .@"align" });
18822 // Check if this happens to be the lazy alignment of our element type, in18841 // Check if this happens to be the lazy alignment of our element type, in
18823 // which case we can make this 0 without resolving it.18842 // which case we can make this 0 without resolving it.
...@@ -20335,15 +20354,11 @@ fn zirReify(...@@ -20335,15 +20354,11 @@ fn zirReify(
20335 try ip.getOrPutString(gpa, pt.tid, "sentinel_ptr", .no_embedded_nulls),20354 try ip.getOrPutString(gpa, pt.tid, "sentinel_ptr", .no_embedded_nulls),
20336 ).?);20355 ).?);
2033720356
20338 if (!try sema.intFitsInType(alignment_val, .u32, null)) {20357 if (!try sema.intFitsInType(alignment_val, align_ty, null)) {
20339 return sema.fail(block, src, "alignment must fit in 'u32'", .{});20358 return sema.fail(block, src, "alignment must fit in '{f}'", .{align_ty.fmt(pt)});
20340 }20359 }
20341
20342 const alignment_val_int = try alignment_val.toUnsignedIntSema(pt);20360 const alignment_val_int = try alignment_val.toUnsignedIntSema(pt);
20343 if (alignment_val_int > 0 and !math.isPowerOfTwo(alignment_val_int)) {20361 const abi_align = try sema.validateAlign(block, src, alignment_val_int);
20344 return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{alignment_val_int});
20345 }
20346 const abi_align = Alignment.fromByteUnits(alignment_val_int);
2034720362
20348 const elem_ty = child_val.toType();20363 const elem_ty = child_val.toType();
20349 if (abi_align != .none) {20364 if (abi_align != .none) {
...@@ -20920,8 +20935,6 @@ fn reifyUnion(...@@ -20920,8 +20935,6 @@ fn reifyUnion(
20920 std.hash.autoHash(&hasher, opt_tag_type_val.toIntern());20935 std.hash.autoHash(&hasher, opt_tag_type_val.toIntern());
20921 std.hash.autoHash(&hasher, fields_len);20936 std.hash.autoHash(&hasher, fields_len);
2092220937
20923 var any_aligns = false;
20924
20925 for (0..fields_len) |field_idx| {20938 for (0..fields_len) |field_idx| {
20926 const field_info = try fields_val.elemValue(pt, field_idx);20939 const field_info = try fields_val.elemValue(pt, field_idx);
2092720940
...@@ -20930,16 +20943,11 @@ fn reifyUnion(...@@ -20930,16 +20943,11 @@ fn reifyUnion(
20930 const field_align_val = try sema.resolveLazyValue(try field_info.fieldValue(pt, 2));20943 const field_align_val = try sema.resolveLazyValue(try field_info.fieldValue(pt, 2));
2093120944
20932 const field_name = try sema.sliceToIpString(block, src, field_name_val, .{ .simple = .union_field_name });20945 const field_name = try sema.sliceToIpString(block, src, field_name_val, .{ .simple = .union_field_name });
20933
20934 std.hash.autoHash(&hasher, .{20946 std.hash.autoHash(&hasher, .{
20935 field_name,20947 field_name,
20936 field_type_val.toIntern(),20948 field_type_val.toIntern(),
20937 field_align_val.toIntern(),20949 field_align_val.toIntern(),
20938 });20950 });
20939
20940 if (field_align_val.toUnsignedInt(zcu) != 0) {
20941 any_aligns = true;
20942 }
20943 }20951 }
2094420952
20945 const tracked_inst = try block.trackZir(inst);20953 const tracked_inst = try block.trackZir(inst);
...@@ -20956,7 +20964,7 @@ fn reifyUnion(...@@ -20956,7 +20964,7 @@ fn reifyUnion(
20956 true => .safety,20964 true => .safety,
20957 false => .none,20965 false => .none,
20958 },20966 },
20959 .any_aligned_fields = any_aligns,20967 .any_aligned_fields = layout != .@"packed",
20960 .requires_comptime = .unknown,20968 .requires_comptime = .unknown,
20961 .assumed_runtime_bits = false,20969 .assumed_runtime_bits = false,
20962 .assumed_pointer_aligned = false,20970 .assumed_pointer_aligned = false,
...@@ -20989,8 +20997,7 @@ fn reifyUnion(...@@ -20989,8 +20997,7 @@ fn reifyUnion(
20989 );20997 );
20990 wip_ty.setName(ip, type_name.name, type_name.nav);20998 wip_ty.setName(ip, type_name.name, type_name.nav);
2099120999
20992 const field_types = try sema.arena.alloc(InternPool.Index, fields_len);21000 const loaded_union = ip.loadUnionType(wip_ty.index);
20993 const field_aligns = if (any_aligns) try sema.arena.alloc(InternPool.Alignment, fields_len) else undefined;
2099421001
20995 const enum_tag_ty, const has_explicit_tag = if (opt_tag_type_val.optionalValue(zcu)) |tag_type_val| tag_ty: {21002 const enum_tag_ty, const has_explicit_tag = if (opt_tag_type_val.optionalValue(zcu)) |tag_type_val| tag_ty: {
20996 switch (ip.indexToKey(tag_type_val.toIntern())) {21003 switch (ip.indexToKey(tag_type_val.toIntern())) {
...@@ -21003,11 +21010,12 @@ fn reifyUnion(...@@ -21003,11 +21010,12 @@ fn reifyUnion(
21003 const tag_ty_fields_len = enum_tag_ty.enumFieldCount(zcu);21010 const tag_ty_fields_len = enum_tag_ty.enumFieldCount(zcu);
21004 var seen_tags = try std.DynamicBitSetUnmanaged.initEmpty(sema.arena, tag_ty_fields_len);21011 var seen_tags = try std.DynamicBitSetUnmanaged.initEmpty(sema.arena, tag_ty_fields_len);
2100521012
21006 for (field_types, 0..) |*field_ty, field_idx| {21013 for (0..fields_len) |field_idx| {
21007 const field_info = try fields_val.elemValue(pt, field_idx);21014 const field_info = try fields_val.elemValue(pt, field_idx);
2100821015
21009 const field_name_val = try field_info.fieldValue(pt, 0);21016 const field_name_val = try field_info.fieldValue(pt, 0);
21010 const field_type_val = try field_info.fieldValue(pt, 1);21017 const field_type_val = try field_info.fieldValue(pt, 1);
21018 const field_alignment_val = try field_info.fieldValue(pt, 2);
2101121019
21012 // Don't pass a reason; first loop acts as an assertion that this is valid.21020 // Don't pass a reason; first loop acts as an assertion that this is valid.
21013 const field_name = try sema.sliceToIpString(block, src, field_name_val, undefined);21021 const field_name = try sema.sliceToIpString(block, src, field_name_val, undefined);
...@@ -21024,14 +21032,12 @@ fn reifyUnion(...@@ -21024,14 +21032,12 @@ fn reifyUnion(
21024 }21032 }
21025 seen_tags.set(enum_index);21033 seen_tags.set(enum_index);
2102621034
21027 field_ty.* = field_type_val.toIntern();21035 loaded_union.field_types.get(ip)[field_idx] = field_type_val.toIntern();
21028 if (any_aligns) {21036 const byte_align = try field_alignment_val.toUnsignedIntSema(pt);
21029 const byte_align = try (try field_info.fieldValue(pt, 2)).toUnsignedIntSema(pt);21037 if (layout == .@"packed") {
21030 if (byte_align > 0 and !math.isPowerOfTwo(byte_align)) {21038 if (byte_align != 0) return sema.fail(block, src, "alignment of a packed union field must be set to 0", .{});
21031 // TODO: better source location21039 } else {
21032 return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{byte_align});21040 loaded_union.field_aligns.get(ip)[field_idx] = try sema.validateAlign(block, src, byte_align);
21033 }
21034 field_aligns[field_idx] = Alignment.fromByteUnits(byte_align);
21035 }21041 }
21036 }21042 }
2103721043
...@@ -21055,11 +21061,12 @@ fn reifyUnion(...@@ -21055,11 +21061,12 @@ fn reifyUnion(
21055 var field_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .empty;21061 var field_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .empty;
21056 try field_names.ensureTotalCapacity(sema.arena, fields_len);21062 try field_names.ensureTotalCapacity(sema.arena, fields_len);
2105721063
21058 for (field_types, 0..) |*field_ty, field_idx| {21064 for (0..fields_len) |field_idx| {
21059 const field_info = try fields_val.elemValue(pt, field_idx);21065 const field_info = try fields_val.elemValue(pt, field_idx);
2106021066
21061 const field_name_val = try field_info.fieldValue(pt, 0);21067 const field_name_val = try field_info.fieldValue(pt, 0);
21062 const field_type_val = try field_info.fieldValue(pt, 1);21068 const field_type_val = try field_info.fieldValue(pt, 1);
21069 const field_alignment_val = try field_info.fieldValue(pt, 2);
2106321070
21064 // Don't pass a reason; first loop acts as an assertion that this is valid.21071 // Don't pass a reason; first loop acts as an assertion that this is valid.
21065 const field_name = try sema.sliceToIpString(block, src, field_name_val, undefined);21072 const field_name = try sema.sliceToIpString(block, src, field_name_val, undefined);
...@@ -21069,14 +21076,12 @@ fn reifyUnion(...@@ -21069,14 +21076,12 @@ fn reifyUnion(
21069 return sema.fail(block, src, "duplicate union field {f}", .{field_name.fmt(ip)});21076 return sema.fail(block, src, "duplicate union field {f}", .{field_name.fmt(ip)});
21070 }21077 }
2107121078
21072 field_ty.* = field_type_val.toIntern();21079 loaded_union.field_types.get(ip)[field_idx] = field_type_val.toIntern();
21073 if (any_aligns) {21080 const byte_align = try field_alignment_val.toUnsignedIntSema(pt);
21074 const byte_align = try (try field_info.fieldValue(pt, 2)).toUnsignedIntSema(pt);21081 if (layout == .@"packed") {
21075 if (byte_align > 0 and !math.isPowerOfTwo(byte_align)) {21082 if (byte_align != 0) return sema.fail(block, src, "alignment of a packed union field must be set to 0", .{});
21076 // TODO: better source location21083 } else {
21077 return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{byte_align});21084 loaded_union.field_aligns.get(ip)[field_idx] = try sema.validateAlign(block, src, byte_align);
21078 }
21079 field_aligns[field_idx] = Alignment.fromByteUnits(byte_align);
21080 }21085 }
21081 }21086 }
2108221087
...@@ -21085,7 +21090,7 @@ fn reifyUnion(...@@ -21085,7 +21090,7 @@ fn reifyUnion(
21085 };21090 };
21086 errdefer if (!has_explicit_tag) ip.remove(pt.tid, enum_tag_ty); // remove generated tag type on error21091 errdefer if (!has_explicit_tag) ip.remove(pt.tid, enum_tag_ty); // remove generated tag type on error
2108721092
21088 for (field_types) |field_ty_ip| {21093 for (loaded_union.field_types.get(ip)) |field_ty_ip| {
21089 const field_ty: Type = .fromInterned(field_ty_ip);21094 const field_ty: Type = .fromInterned(field_ty_ip);
21090 if (field_ty.zigTypeTag(zcu) == .@"opaque") {21095 if (field_ty.zigTypeTag(zcu) == .@"opaque") {
21091 return sema.failWithOwnedErrorMsg(block, msg: {21096 return sema.failWithOwnedErrorMsg(block, msg: {
...@@ -21119,11 +21124,6 @@ fn reifyUnion(...@@ -21119,11 +21124,6 @@ fn reifyUnion(
21119 }21124 }
21120 }21125 }
2112121126
21122 const loaded_union = ip.loadUnionType(wip_ty.index);
21123 loaded_union.setFieldTypes(ip, field_types);
21124 if (any_aligns) {
21125 loaded_union.setFieldAligns(ip, field_aligns);
21126 }
21127 loaded_union.setTagType(ip, enum_tag_ty);21127 loaded_union.setTagType(ip, enum_tag_ty);
21128 loaded_union.setStatus(ip, .have_field_types);21128 loaded_union.setStatus(ip, .have_field_types);
2112921129
...@@ -21276,7 +21276,6 @@ fn reifyStruct(...@@ -21276,7 +21276,6 @@ fn reifyStruct(
2127621276
21277 var any_comptime_fields = false;21277 var any_comptime_fields = false;
21278 var any_default_inits = false;21278 var any_default_inits = false;
21279 var any_aligned_fields = false;
2128021279
21281 for (0..fields_len) |field_idx| {21280 for (0..fields_len) |field_idx| {
21282 const field_info = try fields_val.elemValue(pt, field_idx);21281 const field_info = try fields_val.elemValue(pt, field_idx);
...@@ -21311,11 +21310,6 @@ fn reifyStruct(...@@ -21311,11 +21310,6 @@ fn reifyStruct(
2131121310
21312 if (field_is_comptime) any_comptime_fields = true;21311 if (field_is_comptime) any_comptime_fields = true;
21313 if (field_default_value != .none) any_default_inits = true;21312 if (field_default_value != .none) any_default_inits = true;
21314 switch (try field_alignment_val.orderAgainstZeroSema(pt)) {
21315 .eq => {},
21316 .gt => any_aligned_fields = true,
21317 .lt => unreachable,
21318 }
21319 }21313 }
2132021314
21321 const tracked_inst = try block.trackZir(inst);21315 const tracked_inst = try block.trackZir(inst);
...@@ -21327,7 +21321,7 @@ fn reifyStruct(...@@ -21327,7 +21321,7 @@ fn reifyStruct(
21327 .requires_comptime = .unknown,21321 .requires_comptime = .unknown,
21328 .any_comptime_fields = any_comptime_fields,21322 .any_comptime_fields = any_comptime_fields,
21329 .any_default_inits = any_default_inits,21323 .any_default_inits = any_default_inits,
21330 .any_aligned_fields = any_aligned_fields,21324 .any_aligned_fields = layout != .@"packed",
21331 .inits_resolved = true,21325 .inits_resolved = true,
21332 .key = .{ .reified = .{21326 .key = .{ .reified = .{
21333 .zir_index = tracked_inst,21327 .zir_index = tracked_inst,
...@@ -21371,21 +21365,14 @@ fn reifyStruct(...@@ -21371,21 +21365,14 @@ fn reifyStruct(
21371 return sema.fail(block, src, "duplicate struct field name {f}", .{field_name.fmt(ip)});21365 return sema.fail(block, src, "duplicate struct field name {f}", .{field_name.fmt(ip)});
21372 }21366 }
2137321367
21374 if (any_aligned_fields) {21368 if (!try sema.intFitsInType(field_alignment_val, align_ty, null)) {
21375 if (!try sema.intFitsInType(field_alignment_val, .u32, null)) {21369 return sema.fail(block, src, "alignment must fit in '{f}'", .{align_ty.fmt(pt)});
21376 return sema.fail(block, src, "alignment must fit in 'u32'", .{});21370 }
21377 }21371 const byte_align = try field_alignment_val.toUnsignedIntSema(pt);
2137821372 if (layout == .@"packed") {
21379 const byte_align = try field_alignment_val.toUnsignedIntSema(pt);21373 if (byte_align != 0) return sema.fail(block, src, "alignment of a packed struct field must be set to 0", .{});
21380 if (byte_align == 0) {21374 } else {
21381 if (layout != .@"packed") {21375 struct_type.field_aligns.get(ip)[field_idx] = try sema.validateAlign(block, src, byte_align);
21382 struct_type.field_aligns.get(ip)[field_idx] = .none;
21383 }
21384 } else {
21385 if (layout == .@"packed") return sema.fail(block, src, "alignment in a packed struct field must be set to 0", .{});
21386 if (!math.isPowerOfTwo(byte_align)) return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{byte_align});
21387 struct_type.field_aligns.get(ip)[field_idx] = Alignment.fromNonzeroByteUnits(byte_align);
21388 }
21389 }21376 }
2139021377
21391 const field_is_comptime = field_is_comptime_val.toBool();21378 const field_is_comptime = field_is_comptime_val.toBool();
src/Zcu.zig+9-4
...@@ -268,7 +268,8 @@ nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, voi...@@ -268,7 +268,8 @@ nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, voi
268268
269/// These are the modules which we initially queue for analysis in `Compilation.update`.269/// These are the modules which we initially queue for analysis in `Compilation.update`.
270/// `resolveReferences` will use these as the root of its reachability traversal.270/// `resolveReferences` will use these as the root of its reachability traversal.
271analysis_roots: std.BoundedArray(*Package.Module, 4) = .{},271analysis_roots_buffer: [4]*Package.Module,
272analysis_roots_len: usize = 0,
272/// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and273/// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and
273/// reset to `null` when any semantic analysis occurs (since this invalidates the data).274/// reset to `null` when any semantic analysis occurs (since this invalidates the data).
274/// Allocated into `gpa`.275/// Allocated into `gpa`.
...@@ -4013,8 +4014,8 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv...@@ -4013,8 +4014,8 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
4013 // This is not a sufficient size, but a lower bound.4014 // This is not a sufficient size, but a lower bound.
4014 try result.ensureTotalCapacity(gpa, @intCast(zcu.reference_table.count()));4015 try result.ensureTotalCapacity(gpa, @intCast(zcu.reference_table.count()));
40154016
4016 try type_queue.ensureTotalCapacity(gpa, zcu.analysis_roots.len);4017 try type_queue.ensureTotalCapacity(gpa, zcu.analysis_roots_len);
4017 for (zcu.analysis_roots.slice()) |mod| {4018 for (zcu.analysisRoots()) |mod| {
4018 const file = zcu.module_roots.get(mod).?.unwrap() orelse continue;4019 const file = zcu.module_roots.get(mod).?.unwrap() orelse continue;
4019 const root_ty = zcu.fileRootType(file);4020 const root_ty = zcu.fileRootType(file);
4020 if (root_ty == .none) continue;4021 if (root_ty == .none) continue;
...@@ -4202,6 +4203,10 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv...@@ -4202,6 +4203,10 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
4202 return result;4203 return result;
4203}4204}
42044205
4206pub fn analysisRoots(zcu: *Zcu) []*Package.Module {
4207 return zcu.analysis_roots_buffer[0..zcu.analysis_roots_len];
4208}
4209
4205pub fn fileByIndex(zcu: *const Zcu, file_index: File.Index) *File {4210pub fn fileByIndex(zcu: *const Zcu, file_index: File.Index) *File {
4206 return zcu.intern_pool.filePtr(file_index);4211 return zcu.intern_pool.filePtr(file_index);
4207}4212}
...@@ -4510,7 +4515,7 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.builtin.CallingConvention) union(enu...@@ -4510,7 +4515,7 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.builtin.CallingConvention) union(enu
4510 },4515 },
4511 .stage2_spirv => switch (cc) {4516 .stage2_spirv => switch (cc) {
4512 .spirv_device, .spirv_kernel => true,4517 .spirv_device, .spirv_kernel => true,
4513 .spirv_fragment, .spirv_vertex => target.os.tag == .vulkan,4518 .spirv_fragment, .spirv_vertex => target.os.tag == .vulkan or target.os.tag == .opengl,
4514 else => false,4519 else => false,
4515 },4520 },
4516 };4521 };
src/Zcu/PerThread.zig+3-2
...@@ -2116,8 +2116,9 @@ pub fn computeAliveFiles(pt: Zcu.PerThread) Allocator.Error!bool {...@@ -2116,8 +2116,9 @@ pub fn computeAliveFiles(pt: Zcu.PerThread) Allocator.Error!bool {
2116 // multi-threaded environment (where things like file indices could differ between compiler runs).2116 // multi-threaded environment (where things like file indices could differ between compiler runs).
21172117
2118 // The roots of our file liveness analysis will be the analysis roots.2118 // The roots of our file liveness analysis will be the analysis roots.
2119 try zcu.alive_files.ensureTotalCapacity(gpa, zcu.analysis_roots.len);2119 const analysis_roots = zcu.analysisRoots();
2120 for (zcu.analysis_roots.slice()) |mod| {2120 try zcu.alive_files.ensureTotalCapacity(gpa, analysis_roots.len);
2121 for (analysis_roots) |mod| {
2121 const file_index = zcu.module_roots.get(mod).?.unwrap() orelse continue;2122 const file_index = zcu.module_roots.get(mod).?.unwrap() orelse continue;
2122 const file = zcu.fileByIndex(file_index);2123 const file = zcu.fileByIndex(file_index);
21232124
src/codegen/aarch64/Assemble.zig+10-7
...@@ -33,13 +33,16 @@ pub fn nextInstruction(as: *Assemble) !?Instruction {...@@ -33,13 +33,16 @@ pub fn nextInstruction(as: *Assemble) !?Instruction {
33 var symbols: Symbols: {33 var symbols: Symbols: {
34 const symbols = @typeInfo(@TypeOf(instruction.symbols)).@"struct".fields;34 const symbols = @typeInfo(@TypeOf(instruction.symbols)).@"struct".fields;
35 var symbol_fields: [symbols.len]std.builtin.Type.StructField = undefined;35 var symbol_fields: [symbols.len]std.builtin.Type.StructField = undefined;
36 for (&symbol_fields, symbols) |*symbol_field, symbol| symbol_field.* = .{36 for (&symbol_fields, symbols) |*symbol_field, symbol| {
37 .name = symbol.name,37 const Storage = zonCast(SymbolSpec, @field(instruction.symbols, symbol.name), .{}).Storage();
38 .type = zonCast(SymbolSpec, @field(instruction.symbols, symbol.name), .{}).Storage(),38 symbol_field.* = .{
39 .default_value_ptr = null,39 .name = symbol.name,
40 .is_comptime = false,40 .type = Storage,
41 .alignment = 0,41 .default_value_ptr = null,
42 };42 .is_comptime = false,
43 .alignment = @alignOf(Storage),
44 };
45 }
43 break :Symbols @Type(.{ .@"struct" = .{46 break :Symbols @Type(.{ .@"struct" = .{
44 .layout = .auto,47 .layout = .auto,
45 .fields = &symbol_fields,48 .fields = &symbol_fields,
src/codegen/llvm.zig+7-4
...@@ -12210,11 +12210,14 @@ fn lowerFnRetTy(o: *Object, pt: Zcu.PerThread, fn_info: InternPool.Key.FuncType)...@@ -12210,11 +12210,14 @@ fn lowerFnRetTy(o: *Object, pt: Zcu.PerThread, fn_info: InternPool.Key.FuncType)
12210 },12210 },
12211 .riscv64_lp64, .riscv32_ilp32 => switch (riscv_c_abi.classifyType(return_type, zcu)) {12211 .riscv64_lp64, .riscv32_ilp32 => switch (riscv_c_abi.classifyType(return_type, zcu)) {
12212 .memory => return .void,12212 .memory => return .void,
12213 .integer => {12213 .integer => return o.builder.intType(@intCast(return_type.bitSize(zcu))),
12214 return o.builder.intType(@intCast(return_type.bitSize(zcu)));
12215 },
12216 .double_integer => {12214 .double_integer => {
12217 return o.builder.structType(.normal, &.{ .i64, .i64 });12215 const integer: Builder.Type = switch (zcu.getTarget().cpu.arch) {
12216 .riscv64 => .i64,
12217 .riscv32 => .i32,
12218 else => unreachable,
12219 };
12220 return o.builder.structType(.normal, &.{ integer, integer });
12218 },12221 },
12219 .byval => return o.lowerType(pt, return_type),12222 .byval => return o.lowerType(pt, return_type),
12220 .fields => {12223 .fields => {
src/libs/freebsd.zig-4
...@@ -977,10 +977,6 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -977,10 +977,6 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
977 });977 });
978}978}
979979
980pub fn sharedObjectsCount() u8 {
981 return libs.len;
982}
983
984fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void {980fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void {
985 assert(comp.freebsd_so_files == null);981 assert(comp.freebsd_so_files == null);
986 comp.freebsd_so_files = so_files;982 comp.freebsd_so_files = so_files;
src/libs/glibc.zig-12
...@@ -1120,18 +1120,6 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -1120,18 +1120,6 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
1120 });1120 });
1121}1121}
11221122
1123pub fn sharedObjectsCount(target: *const std.Target) u8 {
1124 const target_version = target.os.versionRange().gnuLibCVersion() orelse return 0;
1125 var count: u8 = 0;
1126 for (libs) |lib| {
1127 if (lib.removed_in) |rem_in| {
1128 if (target_version.order(rem_in) != .lt) continue;
1129 }
1130 count += 1;
1131 }
1132 return count;
1133}
1134
1135fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void {1123fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void {
1136 const target_version = comp.getTarget().os.versionRange().gnuLibCVersion().?;1124 const target_version = comp.getTarget().os.versionRange().gnuLibCVersion().?;
11371125
src/libs/mingw.zig+2-1
...@@ -1011,7 +1011,6 @@ const mingw32_winpthreads_src = [_][]const u8{...@@ -1011,7 +1011,6 @@ const mingw32_winpthreads_src = [_][]const u8{
1011 "winpthreads" ++ path.sep_str ++ "thread.c",1011 "winpthreads" ++ path.sep_str ++ "thread.c",
1012};1012};
10131013
1014// Note: kernel32 and ntdll are always linked even without targeting MinGW-w64.
1015pub const always_link_libs = [_][]const u8{1014pub const always_link_libs = [_][]const u8{
1016 "api-ms-win-crt-conio-l1-1-0",1015 "api-ms-win-crt-conio-l1-1-0",
1017 "api-ms-win-crt-convert-l1-1-0",1016 "api-ms-win-crt-convert-l1-1-0",
...@@ -1029,6 +1028,8 @@ pub const always_link_libs = [_][]const u8{...@@ -1029,6 +1028,8 @@ pub const always_link_libs = [_][]const u8{
1029 "api-ms-win-crt-time-l1-1-0",1028 "api-ms-win-crt-time-l1-1-0",
1030 "api-ms-win-crt-utility-l1-1-0",1029 "api-ms-win-crt-utility-l1-1-0",
1031 "advapi32",1030 "advapi32",
1031 "kernel32",
1032 "ntdll",
1032 "shell32",1033 "shell32",
1033 "user32",1034 "user32",
1034};1035};
src/libs/netbsd.zig-4
...@@ -642,10 +642,6 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -642,10 +642,6 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
642 });642 });
643}643}
644644
645pub fn sharedObjectsCount() u8 {
646 return libs.len;
647}
648
649fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void {645fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void {
650 assert(comp.netbsd_so_files == null);646 assert(comp.netbsd_so_files == null);
651 comp.netbsd_so_files = so_files;647 comp.netbsd_so_files = so_files;
src/link/MachO/dyld_info/Trie.zig+16-11
...@@ -138,18 +138,23 @@ fn finalize(self: *Trie, allocator: Allocator) !void {...@@ -138,18 +138,23 @@ fn finalize(self: *Trie, allocator: Allocator) !void {
138 defer ordered_nodes.deinit();138 defer ordered_nodes.deinit();
139 try ordered_nodes.ensureTotalCapacityPrecise(self.nodes.items(.is_terminal).len);139 try ordered_nodes.ensureTotalCapacityPrecise(self.nodes.items(.is_terminal).len);
140140
141 var fifo = DeprecatedLinearFifo(Node.Index).init(allocator);141 {
142 defer fifo.deinit();142 var fifo: std.ArrayListUnmanaged(Node.Index) = .empty;
143143 defer fifo.deinit(allocator);
144 try fifo.writeItem(self.root.?);144
145145 try fifo.append(allocator, self.root.?);
146 while (fifo.readItem()) |next_index| {146
147 const edges = &self.nodes.items(.edges)[next_index];147 var i: usize = 0;
148 for (edges.items) |edge_index| {148 while (i < fifo.items.len) {
149 const edge = self.edges.items[edge_index];149 const next_index = fifo.items[i];
150 try fifo.writeItem(edge.node);150 i += 1;
151 const edges = &self.nodes.items(.edges)[next_index];
152 for (edges.items) |edge_index| {
153 const edge = self.edges.items[edge_index];
154 try fifo.append(allocator, edge.node);
155 }
156 ordered_nodes.appendAssumeCapacity(next_index);
151 }157 }
152 ordered_nodes.appendAssumeCapacity(next_index);
153 }158 }
154159
155 var more: bool = true;160 var more: bool = true;
src/link/Queue.zig+45-13
...@@ -16,9 +16,9 @@ mutex: std.Thread.Mutex,...@@ -16,9 +16,9 @@ mutex: std.Thread.Mutex,
16/// Validates that only one `flushTaskQueue` thread is running at a time.16/// Validates that only one `flushTaskQueue` thread is running at a time.
17flush_safety: std.debug.SafetyLock,17flush_safety: std.debug.SafetyLock,
1818
19/// This is the number of prelink tasks which are expected but have not yet been enqueued.19/// This value is positive while there are still prelink tasks yet to be queued. Once they are
20/// Guarded by `mutex`.20/// all queued, this value becomes 0, and ZCU tasks can be run. Guarded by `mutex`.
21pending_prelink_tasks: u32,21prelink_wait_count: u32,
2222
23/// Prelink tasks which have been enqueued and are not yet owned by the worker thread.23/// Prelink tasks which have been enqueued and are not yet owned by the worker thread.
24/// Allocated into `gpa`, guarded by `mutex`.24/// Allocated into `gpa`, guarded by `mutex`.
...@@ -59,7 +59,7 @@ state: union(enum) {...@@ -59,7 +59,7 @@ state: union(enum) {
59 /// The link thread is currently running or queued to run.59 /// The link thread is currently running or queued to run.
60 running,60 running,
61 /// The link thread is not running or queued, because it has exhausted all immediately available61 /// The link thread is not running or queued, because it has exhausted all immediately available
62 /// tasks. It should be spawned when more tasks are enqueued. If `pending_prelink_tasks` is not62 /// tasks. It should be spawned when more tasks are enqueued. If `prelink_wait_count` is not
63 /// zero, we are specifically waiting for prelink tasks.63 /// zero, we are specifically waiting for prelink tasks.
64 finished,64 finished,
65 /// The link thread is not running or queued, because it is waiting for this MIR to be populated.65 /// The link thread is not running or queued, because it is waiting for this MIR to be populated.
...@@ -73,11 +73,11 @@ state: union(enum) {...@@ -73,11 +73,11 @@ state: union(enum) {
73const max_air_bytes_in_flight = 10 * 1024 * 1024;73const max_air_bytes_in_flight = 10 * 1024 * 1024;
7474
75/// The initial `Queue` state, containing no tasks, expecting no prelink tasks, and with no running worker thread.75/// The initial `Queue` state, containing no tasks, expecting no prelink tasks, and with no running worker thread.
76/// The `pending_prelink_tasks` and `queued_prelink` fields may be modified as needed before calling `start`.76/// The `queued_prelink` field may be appended to before calling `start`.
77pub const empty: Queue = .{77pub const empty: Queue = .{
78 .mutex = .{},78 .mutex = .{},
79 .flush_safety = .{},79 .flush_safety = .{},
80 .pending_prelink_tasks = 0,80 .prelink_wait_count = undefined, // set in `start`
81 .queued_prelink = .empty,81 .queued_prelink = .empty,
82 .wip_prelink = .empty,82 .wip_prelink = .empty,
83 .queued_zcu = .empty,83 .queued_zcu = .empty,
...@@ -100,17 +100,49 @@ pub fn deinit(q: *Queue, comp: *Compilation) void {...@@ -100,17 +100,49 @@ pub fn deinit(q: *Queue, comp: *Compilation) void {
100}100}
101101
102/// This is expected to be called exactly once, after which the caller must not directly access102/// This is expected to be called exactly once, after which the caller must not directly access
103/// `queued_prelink` or `pending_prelink_tasks` any longer. This will spawn the link thread if103/// `queued_prelink` any longer. This will spawn the link thread if necessary.
104/// necessary.
105pub fn start(q: *Queue, comp: *Compilation) void {104pub fn start(q: *Queue, comp: *Compilation) void {
106 assert(q.state == .finished);105 assert(q.state == .finished);
107 assert(q.queued_zcu.items.len == 0);106 assert(q.queued_zcu.items.len == 0);
107 // Reset this to 1. We can't init it to 1 in `empty`, because it would fall to 0 on successive
108 // incremental updates, but we still need the initial 1.
109 q.prelink_wait_count = 1;
108 if (q.queued_prelink.items.len != 0) {110 if (q.queued_prelink.items.len != 0) {
109 q.state = .running;111 q.state = .running;
110 comp.thread_pool.spawnWgId(&comp.link_task_wait_group, flushTaskQueue, .{ q, comp });112 comp.thread_pool.spawnWgId(&comp.link_task_wait_group, flushTaskQueue, .{ q, comp });
111 }113 }
112}114}
113115
116/// Every call to this must be paired with a call to `finishPrelinkItem`.
117pub fn startPrelinkItem(q: *Queue) void {
118 q.mutex.lock();
119 defer q.mutex.unlock();
120 assert(q.prelink_wait_count > 0); // must not have finished everything already
121 q.prelink_wait_count += 1;
122}
123/// This function must be called exactly one more time than `startPrelinkItem` is. The final call
124/// indicates that we have finished calling `startPrelinkItem`, so once all pending items finish,
125/// we are ready to move on to ZCU tasks.
126pub fn finishPrelinkItem(q: *Queue, comp: *Compilation) void {
127 {
128 q.mutex.lock();
129 defer q.mutex.unlock();
130 q.prelink_wait_count -= 1;
131 if (q.prelink_wait_count != 0) return;
132 // The prelink task count dropped to 0; restart the linker thread if necessary.
133 switch (q.state) {
134 .wait_for_mir => unreachable, // we've not started zcu tasks yet
135 .running => return,
136 .finished => {},
137 }
138 assert(q.queued_prelink.items.len == 0);
139 // Even if there are no ZCU tasks, we must restart the linker thread to make sure
140 // that `link.File.prelink()` is called.
141 q.state = .running;
142 }
143 comp.thread_pool.spawnWgId(&comp.link_task_wait_group, flushTaskQueue, .{ q, comp });
144}
145
114/// Called by codegen workers after they have populated a `ZcuTask.LinkFunc.SharedMir`. If the link146/// Called by codegen workers after they have populated a `ZcuTask.LinkFunc.SharedMir`. If the link
115/// thread was waiting for this MIR, it can resume.147/// thread was waiting for this MIR, it can resume.
116pub fn mirReady(q: *Queue, comp: *Compilation, func_index: InternPool.Index, mir: *ZcuTask.LinkFunc.SharedMir) void {148pub fn mirReady(q: *Queue, comp: *Compilation, func_index: InternPool.Index, mir: *ZcuTask.LinkFunc.SharedMir) void {
...@@ -130,14 +162,14 @@ pub fn mirReady(q: *Queue, comp: *Compilation, func_index: InternPool.Index, mir...@@ -130,14 +162,14 @@ pub fn mirReady(q: *Queue, comp: *Compilation, func_index: InternPool.Index, mir
130 comp.thread_pool.spawnWgId(&comp.link_task_wait_group, flushTaskQueue, .{ q, comp });162 comp.thread_pool.spawnWgId(&comp.link_task_wait_group, flushTaskQueue, .{ q, comp });
131}163}
132164
133/// Enqueues all prelink tasks in `tasks`. Asserts that they were expected, i.e. that `tasks.len` is165/// Enqueues all prelink tasks in `tasks`. Asserts that they were expected, i.e. that
134/// less than or equal to `q.pending_prelink_tasks`. Also asserts that `tasks.len` is not 0.166/// `prelink_wait_count` is not yet 0. Also asserts that `tasks.len` is not 0.
135pub fn enqueuePrelink(q: *Queue, comp: *Compilation, tasks: []const PrelinkTask) Allocator.Error!void {167pub fn enqueuePrelink(q: *Queue, comp: *Compilation, tasks: []const PrelinkTask) Allocator.Error!void {
136 {168 {
137 q.mutex.lock();169 q.mutex.lock();
138 defer q.mutex.unlock();170 defer q.mutex.unlock();
171 assert(q.prelink_wait_count > 0);
139 try q.queued_prelink.appendSlice(comp.gpa, tasks);172 try q.queued_prelink.appendSlice(comp.gpa, tasks);
140 q.pending_prelink_tasks -= @intCast(tasks.len);
141 switch (q.state) {173 switch (q.state) {
142 .wait_for_mir => unreachable, // we've not started zcu tasks yet174 .wait_for_mir => unreachable, // we've not started zcu tasks yet
143 .running => return,175 .running => return,
...@@ -167,7 +199,7 @@ pub fn enqueueZcu(q: *Queue, comp: *Compilation, task: ZcuTask) Allocator.Error!...@@ -167,7 +199,7 @@ pub fn enqueueZcu(q: *Queue, comp: *Compilation, task: ZcuTask) Allocator.Error!
167 try q.queued_zcu.append(comp.gpa, task);199 try q.queued_zcu.append(comp.gpa, task);
168 switch (q.state) {200 switch (q.state) {
169 .running, .wait_for_mir => return,201 .running, .wait_for_mir => return,
170 .finished => if (q.pending_prelink_tasks != 0) return,202 .finished => if (q.prelink_wait_count > 0) return,
171 }203 }
172 // Restart the linker thread, unless it would immediately be blocked204 // Restart the linker thread, unless it would immediately be blocked
173 if (task == .link_func and task.link_func.mir.status.load(.acquire) == .pending) {205 if (task == .link_func and task.link_func.mir.status.load(.acquire) == .pending) {
...@@ -194,7 +226,7 @@ fn flushTaskQueue(tid: usize, q: *Queue, comp: *Compilation) void {...@@ -194,7 +226,7 @@ fn flushTaskQueue(tid: usize, q: *Queue, comp: *Compilation) void {
194 defer q.mutex.unlock();226 defer q.mutex.unlock();
195 std.mem.swap(std.ArrayListUnmanaged(PrelinkTask), &q.queued_prelink, &q.wip_prelink);227 std.mem.swap(std.ArrayListUnmanaged(PrelinkTask), &q.queued_prelink, &q.wip_prelink);
196 if (q.wip_prelink.items.len == 0) {228 if (q.wip_prelink.items.len == 0) {
197 if (q.pending_prelink_tasks == 0) {229 if (q.prelink_wait_count == 0) {
198 break :prelink; // prelink is done230 break :prelink; // prelink is done
199 } else {231 } else {
200 // We're expecting more prelink tasks so can't move on to ZCU tasks.232 // We're expecting more prelink tasks so can't move on to ZCU tasks.
src/main.zig+23-23
...@@ -312,7 +312,6 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -312,7 +312,6 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
312 return jitCmd(gpa, arena, cmd_args, .{312 return jitCmd(gpa, arena, cmd_args, .{
313 .cmd_name = "resinator",313 .cmd_name = "resinator",
314 .root_src_path = "resinator/main.zig",314 .root_src_path = "resinator/main.zig",
315 .windows_libs = &.{"advapi32"},
316 .depend_on_aro = true,315 .depend_on_aro = true,
317 .prepend_zig_lib_dir_path = true,316 .prepend_zig_lib_dir_path = true,
318 .server = use_server,317 .server = use_server,
...@@ -337,7 +336,6 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -337,7 +336,6 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
337 return jitCmd(gpa, arena, cmd_args, .{336 return jitCmd(gpa, arena, cmd_args, .{
338 .cmd_name = "std",337 .cmd_name = "std",
339 .root_src_path = "std-docs.zig",338 .root_src_path = "std-docs.zig",
340 .windows_libs = &.{"ws2_32"},
341 .prepend_zig_lib_dir_path = true,339 .prepend_zig_lib_dir_path = true,
342 .prepend_zig_exe_path = true,340 .prepend_zig_exe_path = true,
343 .prepend_global_cache_path = true,341 .prepend_global_cache_path = true,
...@@ -3659,6 +3657,7 @@ fn buildOutputType(...@@ -3659,6 +3657,7 @@ fn buildOutputType(
3659 } else if (target.os.tag == .windows) {3657 } else if (target.os.tag == .windows) {
3660 try test_exec_args.appendSlice(arena, &.{3658 try test_exec_args.appendSlice(arena, &.{
3661 "--subsystem", "console",3659 "--subsystem", "console",
3660 "-lkernel32", "-lntdll",
3662 });3661 });
3663 }3662 }
36643663
...@@ -3862,8 +3861,7 @@ fn createModule(...@@ -3862,8 +3861,7 @@ fn createModule(
3862 .only_compiler_rt => continue,3861 .only_compiler_rt => continue,
3863 }3862 }
38643863
3865 // We currently prefer import libraries provided by MinGW-w64 even for MSVC.3864 if (target.isMinGW()) {
3866 if (target.os.tag == .windows) {
3867 const exists = mingw.libExists(arena, target, create_module.dirs.zig_lib, lib_name) catch |err| {3865 const exists = mingw.libExists(arena, target, create_module.dirs.zig_lib, lib_name) catch |err| {
3868 fatal("failed to check zig installation for DLL import libs: {s}", .{3866 fatal("failed to check zig installation for DLL import libs: {s}", .{
3869 @errorName(err),3867 @errorName(err),
...@@ -4796,7 +4794,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4796,7 +4794,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4796 writeSimpleTemplateFile(Package.Manifest.basename,4794 writeSimpleTemplateFile(Package.Manifest.basename,
4797 \\.{{4795 \\.{{
4798 \\ .name = .{s},4796 \\ .name = .{s},
4799 \\ .version = "{s}",4797 \\ .version = "0.0.1",
4798 \\ .minimum_zig_version = "{s}",
4800 \\ .paths = .{{""}},4799 \\ .paths = .{{""}},
4801 \\ .fingerprint = 0x{x},4800 \\ .fingerprint = 0x{x},
4802 \\}}4801 \\}}
...@@ -4811,6 +4810,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4811,6 +4810,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4811 };4810 };
4812 writeSimpleTemplateFile(Package.build_zig_basename,4811 writeSimpleTemplateFile(Package.build_zig_basename,
4813 \\const std = @import("std");4812 \\const std = @import("std");
4813 \\
4814 \\pub fn build(b: *std.Build) void {{4814 \\pub fn build(b: *std.Build) void {{
4815 \\ _ = b; // stub4815 \\ _ = b; // stub
4816 \\}}4816 \\}}
...@@ -4891,6 +4891,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4891,6 +4891,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4891 var fetch_mode: Package.Fetch.JobQueue.Mode = .needed;4891 var fetch_mode: Package.Fetch.JobQueue.Mode = .needed;
4892 var system_pkg_dir_path: ?[]const u8 = null;4892 var system_pkg_dir_path: ?[]const u8 = null;
4893 var debug_target: ?[]const u8 = null;4893 var debug_target: ?[]const u8 = null;
4894 var debug_libc_paths_file: ?[]const u8 = null;
48944895
4895 const argv_index_exe = child_argv.items.len;4896 const argv_index_exe = child_argv.items.len;
4896 _ = try child_argv.addOne();4897 _ = try child_argv.addOne();
...@@ -5014,6 +5015,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5014,6 +5015,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5014 } else {5015 } else {
5015 warn("Zig was compiled without debug extensions. --debug-target has no effect.", .{});5016 warn("Zig was compiled without debug extensions. --debug-target has no effect.", .{});
5016 }5017 }
5018 } else if (mem.eql(u8, arg, "--debug-libc")) {
5019 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
5020 i += 1;
5021 if (build_options.enable_debug_extensions) {
5022 debug_libc_paths_file = args[i];
5023 } else {
5024 warn("Zig was compiled without debug extensions. --debug-libc has no effect.", .{});
5025 }
5017 } else if (mem.eql(u8, arg, "--verbose-link")) {5026 } else if (mem.eql(u8, arg, "--verbose-link")) {
5018 verbose_link = true;5027 verbose_link = true;
5019 } else if (mem.eql(u8, arg, "--verbose-cc")) {5028 } else if (mem.eql(u8, arg, "--verbose-cc")) {
...@@ -5101,6 +5110,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5101,6 +5110,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5101 .is_explicit_dynamic_linker = false,5110 .is_explicit_dynamic_linker = false,
5102 };5111 };
5103 };5112 };
5113 // Likewise, `--debug-libc` allows overriding the libc installation.
5114 const libc_installation: ?*const LibCInstallation = lci: {
5115 const paths_file = debug_libc_paths_file orelse break :lci null;
5116 if (!build_options.enable_debug_extensions) unreachable;
5117 const lci = try arena.create(LibCInstallation);
5118 lci.* = try .parse(arena, paths_file, &resolved_target.result);
5119 break :lci lci;
5120 };
51045121
5105 process.raiseFileDescriptorLimit();5122 process.raiseFileDescriptorLimit();
51065123
...@@ -5356,15 +5373,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5356,15 +5373,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
53565373
5357 try root_mod.deps.put(arena, "@build", build_mod);5374 try root_mod.deps.put(arena, "@build", build_mod);
53585375
5359 var windows_libs: std.StringArrayHashMapUnmanaged(void) = .empty;
5360
5361 if (resolved_target.result.os.tag == .windows) {
5362 try windows_libs.ensureUnusedCapacity(arena, 2);
5363 windows_libs.putAssumeCapacity("advapi32", {});
5364 windows_libs.putAssumeCapacity("ws2_32", {}); // for `--listen` (web interface)
5365 }
5366
5367 const comp = Compilation.create(gpa, arena, .{5376 const comp = Compilation.create(gpa, arena, .{
5377 .libc_installation = libc_installation,
5368 .dirs = dirs,5378 .dirs = dirs,
5369 .root_name = "build",5379 .root_name = "build",
5370 .config = config,5380 .config = config,
...@@ -5385,7 +5395,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5385,7 +5395,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5385 .cache_mode = .whole,5395 .cache_mode = .whole,
5386 .reference_trace = reference_trace,5396 .reference_trace = reference_trace,
5387 .debug_compile_errors = debug_compile_errors,5397 .debug_compile_errors = debug_compile_errors,
5388 .windows_lib_names = windows_libs.keys(),
5389 }) catch |err| {5398 }) catch |err| {
5390 fatal("unable to create compilation: {s}", .{@errorName(err)});5399 fatal("unable to create compilation: {s}", .{@errorName(err)});
5391 };5400 };
...@@ -5489,7 +5498,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5489,7 +5498,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5489const JitCmdOptions = struct {5498const JitCmdOptions = struct {
5490 cmd_name: []const u8,5499 cmd_name: []const u8,
5491 root_src_path: []const u8,5500 root_src_path: []const u8,
5492 windows_libs: []const []const u8 = &.{},
5493 prepend_zig_lib_dir_path: bool = false,5501 prepend_zig_lib_dir_path: bool = false,
5494 prepend_global_cache_path: bool = false,5502 prepend_global_cache_path: bool = false,
5495 prepend_zig_exe_path: bool = false,5503 prepend_zig_exe_path: bool = false,
...@@ -5606,13 +5614,6 @@ fn jitCmd(...@@ -5606,13 +5614,6 @@ fn jitCmd(
5606 try root_mod.deps.put(arena, "aro", aro_mod);5614 try root_mod.deps.put(arena, "aro", aro_mod);
5607 }5615 }
56085616
5609 var windows_libs: std.StringArrayHashMapUnmanaged(void) = .empty;
5610
5611 if (resolved_target.result.os.tag == .windows) {
5612 try windows_libs.ensureUnusedCapacity(arena, options.windows_libs.len);
5613 for (options.windows_libs) |lib| windows_libs.putAssumeCapacity(lib, {});
5614 }
5615
5616 const comp = Compilation.create(gpa, arena, .{5617 const comp = Compilation.create(gpa, arena, .{
5617 .dirs = dirs,5618 .dirs = dirs,
5618 .root_name = options.cmd_name,5619 .root_name = options.cmd_name,
...@@ -5623,7 +5624,6 @@ fn jitCmd(...@@ -5623,7 +5624,6 @@ fn jitCmd(
5623 .self_exe_path = self_exe_path,5624 .self_exe_path = self_exe_path,
5624 .thread_pool = &thread_pool,5625 .thread_pool = &thread_pool,
5625 .cache_mode = .whole,5626 .cache_mode = .whole,
5626 .windows_lib_names = windows_libs.keys(),
5627 }) catch |err| {5627 }) catch |err| {
5628 fatal("unable to create compilation: {s}", .{@errorName(err)});5628 fatal("unable to create compilation: {s}", .{@errorName(err)});
5629 };5629 };
src/target.zig+1-1
...@@ -20,7 +20,7 @@ pub fn cannotDynamicLink(target: *const std.Target) bool {...@@ -20,7 +20,7 @@ pub fn cannotDynamicLink(target: *const std.Target) bool {
20/// Similarly on FreeBSD and NetBSD we always link system libc20/// Similarly on FreeBSD and NetBSD we always link system libc
21/// since this is the stable syscall interface.21/// since this is the stable syscall interface.
22pub fn osRequiresLibC(target: *const std.Target) bool {22pub fn osRequiresLibC(target: *const std.Target) bool {
23 return target.os.requiresLibC();23 return target.requiresLibC();
24}24}
2525
26pub fn libCNeedsLibUnwind(target: *const std.Target, link_mode: std.builtin.LinkMode) bool {26pub fn libCNeedsLibUnwind(target: *const std.Target, link_mode: std.builtin.LinkMode) bool {
test/behavior/tuple.zig+4-2
...@@ -318,6 +318,8 @@ test "tuple type with void field" {...@@ -318,6 +318,8 @@ test "tuple type with void field" {
318test "zero sized struct in tuple handled correctly" {318test "zero sized struct in tuple handled correctly" {
319 const State = struct {319 const State = struct {
320 const Self = @This();320 const Self = @This();
321 const Inner = struct {};
322
321 data: @Type(.{323 data: @Type(.{
322 .@"struct" = .{324 .@"struct" = .{
323 .is_tuple = true,325 .is_tuple = true,
...@@ -325,10 +327,10 @@ test "zero sized struct in tuple handled correctly" {...@@ -325,10 +327,10 @@ test "zero sized struct in tuple handled correctly" {
325 .decls = &.{},327 .decls = &.{},
326 .fields = &.{.{328 .fields = &.{.{
327 .name = "0",329 .name = "0",
328 .type = struct {},330 .type = Inner,
329 .default_value_ptr = null,331 .default_value_ptr = null,
330 .is_comptime = false,332 .is_comptime = false,
331 .alignment = 0,333 .alignment = @alignOf(Inner),
332 }},334 }},
333 },335 },
334 }),336 }),
test/behavior/type.zig+3-3
...@@ -433,8 +433,8 @@ test "Type.Union" {...@@ -433,8 +433,8 @@ test "Type.Union" {
433 .layout = .@"packed",433 .layout = .@"packed",
434 .tag_type = null,434 .tag_type = null,
435 .fields = &.{435 .fields = &.{
436 .{ .name = "signed", .type = i32, .alignment = @alignOf(i32) },436 .{ .name = "signed", .type = i32, .alignment = 0 },
437 .{ .name = "unsigned", .type = u32, .alignment = @alignOf(u32) },437 .{ .name = "unsigned", .type = u32, .alignment = 0 },
438 },438 },
439 .decls = &.{},439 .decls = &.{},
440 },440 },
...@@ -735,7 +735,7 @@ test "struct field names sliced at comptime from larger string" {...@@ -735,7 +735,7 @@ test "struct field names sliced at comptime from larger string" {
735 var it = std.mem.tokenizeScalar(u8, text, '\n');735 var it = std.mem.tokenizeScalar(u8, text, '\n');
736 while (it.next()) |name| {736 while (it.next()) |name| {
737 fields = fields ++ &[_]Type.StructField{.{737 fields = fields ++ &[_]Type.StructField{.{
738 .alignment = 0,738 .alignment = @alignOf(usize),
739 .name = name ++ "",739 .name = name ++ "",
740 .type = usize,740 .type = usize,
741 .default_value_ptr = null,741 .default_value_ptr = null,
test/behavior/union.zig+8
...@@ -2311,3 +2311,11 @@ test "set mutable union by switching on same union" {...@@ -2311,3 +2311,11 @@ test "set mutable union by switching on same union" {
2311 try expect(val == .bar);2311 try expect(val == .bar);
2312 try expect(val.bar == 2);2312 try expect(val.bar == 2);
2313}2313}
2314
2315test "initialize empty field of union inside comptime-known struct constant" {
2316 const Inner = union { none: void, some: u8 };
2317 const Wrapper = struct { inner: Inner };
2318
2319 const val: Wrapper = .{ .inner = .{ .none = {} } };
2320 comptime assert(val.inner.none == {});
2321}
test/cases/compile_errors/align_zero.zig+44-16
...@@ -1,52 +1,80 @@...@@ -1,52 +1,80 @@
1pub var global_var: i32 align(0) = undefined;1var global_var: i32 align(0) = undefined;
22
3pub export fn a() void {3export fn a() void {
4 _ = &global_var;4 _ = &global_var;
5}5}
66
7pub extern var extern_var: i32 align(0);7extern var extern_var: i32 align(0);
88
9pub export fn b() void {9export fn b() void {
10 _ = &extern_var;10 _ = &extern_var;
11}11}
1212
13pub export fn c() align(0) void {}13export fn c() align(0) void {}
1414
15pub export fn d() void {15export fn d() void {
16 _ = *align(0) fn () i32;16 _ = *align(0) fn () i32;
17}17}
1818
19pub export fn e() void {19export fn e() void {
20 var local_var: i32 align(0) = undefined;20 var local_var: i32 align(0) = undefined;
21 _ = &local_var;21 _ = &local_var;
22}22}
2323
24pub export fn f() void {24export fn f() void {
25 _ = *align(0) i32;25 _ = *align(0) i32;
26}26}
2727
28pub export fn g() void {28export fn g() void {
29 _ = []align(0) i32;29 _ = []align(0) i32;
30}30}
3131
32pub export fn h() void {32export fn h() void {
33 _ = struct { field: i32 align(0) };33 _ = struct { field: i32 align(0) };
34}34}
3535
36pub export fn i() void {36export fn i() void {
37 _ = union { field: i32 align(0) };37 _ = union { field: i32 align(0) };
38}38}
3939
40export fn j() void {
41 _ = @Type(.{ .@"struct" = .{
42 .layout = .auto,
43 .fields = &.{.{
44 .name = "test",
45 .type = u32,
46 .default_value_ptr = null,
47 .is_comptime = false,
48 .alignment = 0,
49 }},
50 .decls = &.{},
51 .is_tuple = false,
52 } });
53}
54
55export fn k() void {
56 _ = @Type(.{ .pointer = .{
57 .size = .one,
58 .is_const = false,
59 .is_volatile = false,
60 .alignment = 0,
61 .address_space = .generic,
62 .child = u32,
63 .is_allowzero = false,
64 .sentinel_ptr = null,
65 } });
66}
67
40// error68// error
41// backend=stage2
42// target=native
43//69//
44// :1:31: error: alignment must be >= 170// :1:27: error: alignment must be >= 1
45// :7:38: error: alignment must be >= 171// :7:34: error: alignment must be >= 1
46// :13:25: error: alignment must be >= 172// :13:21: error: alignment must be >= 1
47// :16:16: error: alignment must be >= 173// :16:16: error: alignment must be >= 1
48// :20:30: error: alignment must be >= 174// :20:30: error: alignment must be >= 1
49// :25:16: error: alignment must be >= 175// :25:16: error: alignment must be >= 1
50// :29:17: error: alignment must be >= 176// :29:17: error: alignment must be >= 1
51// :33:35: error: alignment must be >= 177// :33:35: error: alignment must be >= 1
52// :37:34: error: alignment must be >= 178// :37:34: error: alignment must be >= 1
79// :41:9: error: alignment must be >= 1
80// :56:9: error: alignment must be >= 1
test/cases/compile_errors/bad_alignment_type.zig+2-2
...@@ -11,5 +11,5 @@ export fn entry2() void {...@@ -11,5 +11,5 @@ export fn entry2() void {
11// backend=stage211// backend=stage2
12// target=native12// target=native
13//13//
14// :2:22: error: expected type 'u32', found 'bool'14// :2:22: error: expected type 'u29', found 'bool'
15// :6:21: error: fractional component prevents float value '12.34' from coercion to type 'u32'15// :6:21: error: fractional component prevents float value '12.34' from coercion to type 'u29'
test/cases/compile_errors/packed_struct_field_alignment_unavailable_for_reify_type.zig deleted-9
...@@ -1,9 +0,0 @@
1export fn entry() void {
2 _ = @Type(.{ .@"struct" = .{ .layout = .@"packed", .fields = &.{
3 .{ .name = "one", .type = u4, .default_value_ptr = null, .is_comptime = false, .alignment = 2 },
4 }, .decls = &.{}, .is_tuple = false } });
5}
6
7// error
8//
9// :2:9: error: alignment in a packed struct field must be set to 0
test/cases/compile_errors/packed_union_alignment_override.zig created+9
...@@ -0,0 +1,9 @@
1const U = packed union {
2 x: f32,
3 y: u8 align(10),
4 z: u32,
5};
6
7// error
8//
9// :3:17: error: unable to override alignment of packed union fields
test/cases/compile_errors/reify_struct.zig+2-1
...@@ -75,4 +75,5 @@ comptime {...@@ -75,4 +75,5 @@ comptime {
75// :16:5: error: tuple field name '3' does not match field index 075// :16:5: error: tuple field name '3' does not match field index 0
76// :30:5: error: comptime field without default initialization value76// :30:5: error: comptime field without default initialization value
77// :44:5: error: extern struct fields cannot be marked comptime77// :44:5: error: extern struct fields cannot be marked comptime
78// :58:5: error: alignment in a packed struct field must be set to 078// :58:5: error: alignment of a packed struct field must be set to 0
79
test/cases/compile_errors/reify_type_with_invalid_field_alignment.zig+3-3
...@@ -43,6 +43,6 @@ comptime {...@@ -43,6 +43,6 @@ comptime {
4343
44// error44// error
45//45//
46// :2:9: error: alignment value '3' is not a power of two or zero46// :2:9: error: alignment value '3' is not a power of two
47// :14:9: error: alignment value '5' is not a power of two or zero47// :14:9: error: alignment value '5' is not a power of two
48// :30:9: error: alignment value '7' is not a power of two or zero48// :30:9: error: alignment value '7' is not a power of two
test/src/Cases.zig+3
...@@ -593,6 +593,7 @@ pub fn lowerToTranslateCSteps(...@@ -593,6 +593,7 @@ pub fn lowerToTranslateCSteps(
593pub const CaseTestOptions = struct {593pub const CaseTestOptions = struct {
594 test_filters: []const []const u8,594 test_filters: []const []const u8,
595 test_target_filters: []const []const u8,595 test_target_filters: []const []const u8,
596 skip_compile_errors: bool,
596 skip_non_native: bool,597 skip_non_native: bool,
597 skip_freebsd: bool,598 skip_freebsd: bool,
598 skip_netbsd: bool,599 skip_netbsd: bool,
...@@ -618,6 +619,8 @@ pub fn lowerToBuildSteps(...@@ -618,6 +619,8 @@ pub fn lowerToBuildSteps(
618 if (std.mem.indexOf(u8, case.name, test_filter)) |_| break;619 if (std.mem.indexOf(u8, case.name, test_filter)) |_| break;
619 } else if (options.test_filters.len > 0) continue;620 } else if (options.test_filters.len > 0) continue;
620621
622 if (case.case.? == .Error and options.skip_compile_errors) continue;
623
621 if (options.skip_non_native and !case.target.query.isNative())624 if (options.skip_non_native and !case.target.query.isNative())
622 continue;625 continue;
623626
test/standalone/child_process/build.zig+11
...@@ -31,5 +31,16 @@ pub fn build(b: *std.Build) void {...@@ -31,5 +31,16 @@ pub fn build(b: *std.Build) void {
31 run.addArtifactArg(child);31 run.addArtifactArg(child);
32 run.expectExitCode(0);32 run.expectExitCode(0);
3333
34 // Use a temporary directory within the cache as the CWD to test
35 // spawning the child using a path that contains a leading `..` component.
36 const run_relative = b.addRunArtifact(main);
37 run_relative.addArtifactArg(child);
38 const write_tmp_dir = b.addWriteFiles();
39 const tmp_cwd = write_tmp_dir.getDirectory();
40 run_relative.addDirectoryArg(tmp_cwd);
41 run_relative.setCwd(tmp_cwd);
42 run_relative.expectExitCode(0);
43
34 test_step.dependOn(&run.step);44 test_step.dependOn(&run.step);
45 test_step.dependOn(&run_relative.step);
35}46}
test/standalone/child_process/main.zig+14-2
...@@ -11,7 +11,14 @@ pub fn main() !void {...@@ -11,7 +11,14 @@ pub fn main() !void {
11 var it = try std.process.argsWithAllocator(gpa);11 var it = try std.process.argsWithAllocator(gpa);
12 defer it.deinit();12 defer it.deinit();
13 _ = it.next() orelse unreachable; // skip binary name13 _ = it.next() orelse unreachable; // skip binary name
14 const child_path = it.next() orelse unreachable;14 const child_path, const needs_free = child_path: {
15 const child_path = it.next() orelse unreachable;
16 const cwd_path = it.next() orelse break :child_path .{ child_path, false };
17 // If there is a third argument, it is the current CWD somewhere within the cache directory.
18 // In that case, modify the child path in order to test spawning a path with a leading `..` component.
19 break :child_path .{ try std.fs.path.relative(gpa, cwd_path, child_path), true };
20 };
21 defer if (needs_free) gpa.free(child_path);
1522
16 var child = std.process.Child.init(&.{ child_path, "hello arg" }, gpa);23 var child = std.process.Child.init(&.{ child_path, "hello arg" }, gpa);
17 child.stdin_behavior = .Pipe;24 child.stdin_behavior = .Pipe;
...@@ -39,7 +46,12 @@ pub fn main() !void {...@@ -39,7 +46,12 @@ pub fn main() !void {
39 },46 },
40 else => |term| testError("abnormal child exit: {}", .{term}),47 else => |term| testError("abnormal child exit: {}", .{term}),
41 }48 }
42 return if (parent_test_error) error.ParentTestError else {};49 if (parent_test_error) return error.ParentTestError;
50
51 // Check that FileNotFound is consistent across platforms when trying to spawn an executable that doesn't exist
52 const missing_child_path = try std.mem.concat(gpa, u8, &.{ child_path, "_intentionally_missing" });
53 defer gpa.free(missing_child_path);
54 try std.testing.expectError(error.FileNotFound, std.process.Child.run(.{ .allocator = gpa, .argv = &.{missing_child_path} }));
43}55}
4456
45var parent_test_error = false;57var parent_test_error = false;
test/standalone/simple/build.zig-8
...@@ -50,10 +50,6 @@ pub fn build(b: *std.Build) void {...@@ -50,10 +50,6 @@ pub fn build(b: *std.Build) void {
50 });50 });
51 if (case.link_libc) exe.root_module.link_libc = true;51 if (case.link_libc) exe.root_module.link_libc = true;
5252
53 if (resolved_target.result.os.tag == .windows) {
54 exe.root_module.linkSystemLibrary("advapi32", .{});
55 }
56
57 _ = exe.getEmittedBin();53 _ = exe.getEmittedBin();
5854
59 step.dependOn(&exe.step);55 step.dependOn(&exe.step);
...@@ -70,10 +66,6 @@ pub fn build(b: *std.Build) void {...@@ -70,10 +66,6 @@ pub fn build(b: *std.Build) void {
70 });66 });
71 if (case.link_libc) exe.root_module.link_libc = true;67 if (case.link_libc) exe.root_module.link_libc = true;
7268
73 if (resolved_target.result.os.tag == .windows) {
74 exe.root_module.linkSystemLibrary("advapi32", .{});
75 }
76
77 const run = b.addRunArtifact(exe);69 const run = b.addRunArtifact(exe);
78 step.dependOn(&run.step);70 step.dependOn(&run.step);
79 }71 }
test/standalone/windows_argv/build.zig-2
...@@ -47,8 +47,6 @@ pub fn build(b: *std.Build) !void {...@@ -47,8 +47,6 @@ pub fn build(b: *std.Build) !void {
47 }),47 }),
48 });48 });
4949
50 fuzz.root_module.linkSystemLibrary("advapi32", .{});
51
52 const fuzz_max_iterations = b.option(u64, "iterations", "The max fuzz iterations (default: 100)") orelse 100;50 const fuzz_max_iterations = b.option(u64, "iterations", "The max fuzz iterations (default: 100)") orelse 100;
53 const fuzz_iterations_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_max_iterations}) catch @panic("oom");51 const fuzz_iterations_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_max_iterations}) catch @panic("oom");
5452
test/standalone/windows_bat_args/build.zig-4
...@@ -28,8 +28,6 @@ pub fn build(b: *std.Build) !void {...@@ -28,8 +28,6 @@ pub fn build(b: *std.Build) !void {
28 }),28 }),
29 });29 });
3030
31 test_exe.root_module.linkSystemLibrary("advapi32", .{});
32
33 const run = b.addRunArtifact(test_exe);31 const run = b.addRunArtifact(test_exe);
34 run.addArtifactArg(echo_args);32 run.addArtifactArg(echo_args);
35 run.expectExitCode(0);33 run.expectExitCode(0);
...@@ -46,8 +44,6 @@ pub fn build(b: *std.Build) !void {...@@ -46,8 +44,6 @@ pub fn build(b: *std.Build) !void {
46 }),44 }),
47 });45 });
4846
49 fuzz.root_module.linkSystemLibrary("advapi32", .{});
50
51 const fuzz_max_iterations = b.option(u64, "iterations", "The max fuzz iterations (default: 100)") orelse 100;47 const fuzz_max_iterations = b.option(u64, "iterations", "The max fuzz iterations (default: 100)") orelse 100;
52 const fuzz_iterations_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_max_iterations}) catch @panic("oom");48 const fuzz_iterations_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_max_iterations}) catch @panic("oom");
5349
test/standalone/windows_spawn/build.zig-2
...@@ -28,8 +28,6 @@ pub fn build(b: *std.Build) void {...@@ -28,8 +28,6 @@ pub fn build(b: *std.Build) void {
28 }),28 }),
29 });29 });
3030
31 main.root_module.linkSystemLibrary("advapi32", .{});
32
33 const run = b.addRunArtifact(main);31 const run = b.addRunArtifact(main);
34 run.addArtifactArg(hello);32 run.addArtifactArg(hello);
35 run.expectExitCode(0);33 run.expectExitCode(0);
test/standalone/windows_spawn/main.zig+46
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2
2const windows = std.os.windows;3const windows = std.os.windows;
3const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral;4const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral;
45
...@@ -39,6 +40,9 @@ pub fn main() anyerror!void {...@@ -39,6 +40,9 @@ pub fn main() anyerror!void {
39 // No PATH, so it should fail to find anything not in the cwd40 // No PATH, so it should fail to find anything not in the cwd
40 try testExecError(error.FileNotFound, allocator, "something_missing");41 try testExecError(error.FileNotFound, allocator, "something_missing");
4142
43 // make sure we don't get error.BadPath traversing out of cwd with a relative path
44 try testExecError(error.FileNotFound, allocator, "..\\.\\.\\.\\\\..\\more_missing");
45
42 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(46 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
43 utf16Literal("PATH"),47 utf16Literal("PATH"),
44 tmp_absolute_path_w,48 tmp_absolute_path_w,
...@@ -149,6 +153,48 @@ pub fn main() anyerror!void {...@@ -149,6 +153,48 @@ pub fn main() anyerror!void {
149 // If we try to exec but provide a cwd that is an absolute path, the PATH153 // If we try to exec but provide a cwd that is an absolute path, the PATH
150 // should still be searched and the goodbye.exe in something should be found.154 // should still be searched and the goodbye.exe in something should be found.
151 try testExecWithCwd(allocator, "goodbye", tmp_absolute_path, "hello from exe\n");155 try testExecWithCwd(allocator, "goodbye", tmp_absolute_path, "hello from exe\n");
156
157 // introduce some extra path separators into the path which is dealt with inside the spawn call.
158 const denormed_something_subdir_size = std.mem.replacementSize(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"));
159
160 const denormed_something_subdir_abs_path = try allocator.allocSentinel(u16, denormed_something_subdir_size, 0);
161 defer allocator.free(denormed_something_subdir_abs_path);
162
163 _ = std.mem.replace(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"), denormed_something_subdir_abs_path);
164
165 const denormed_something_subdir_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, denormed_something_subdir_abs_path);
166 defer allocator.free(denormed_something_subdir_wtf8);
167
168 // clear the path to ensure that the match comes from the cwd
169 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
170 utf16Literal("PATH"),
171 null,
172 ) == windows.TRUE);
173
174 try testExecWithCwd(allocator, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n");
175
176 // normalization should also work if the non-normalized path is found in the PATH var.
177 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
178 utf16Literal("PATH"),
179 denormed_something_subdir_abs_path,
180 ) == windows.TRUE);
181 try testExec(allocator, "goodbye", "hello from exe\n");
182
183 // now make sure we can launch executables "outside" of the cwd
184 var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{});
185 defer subdir_cwd.close();
186
187 try tmp.dir.rename("something/goodbye.exe", "hello.exe");
188 try subdir_cwd.setAsCwd();
189
190 // clear the PATH again
191 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
192 utf16Literal("PATH"),
193 null,
194 ) == windows.TRUE);
195
196 // while we're at it make sure non-windows separators work fine
197 try testExec(allocator, "../hello", "hello from exe\n");
152}198}
153199
154fn testExecError(err: anyerror, allocator: std.mem.Allocator, command: []const u8) !void {200fn testExecError(err: anyerror, allocator: std.mem.Allocator, command: []const u8) !void {
test/tests.zig-9
...@@ -2238,7 +2238,6 @@ const ModuleTestOptions = struct {...@@ -2238,7 +2238,6 @@ const ModuleTestOptions = struct {
2238 desc: []const u8,2238 desc: []const u8,
2239 optimize_modes: []const OptimizeMode,2239 optimize_modes: []const OptimizeMode,
2240 include_paths: []const []const u8,2240 include_paths: []const []const u8,
2241 windows_libs: []const []const u8,
2242 skip_single_threaded: bool,2241 skip_single_threaded: bool,
2243 skip_non_native: bool,2242 skip_non_native: bool,
2244 skip_freebsd: bool,2243 skip_freebsd: bool,
...@@ -2373,10 +2372,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {...@@ -2373,10 +2372,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
23732372
2374 for (options.include_paths) |include_path| these_tests.root_module.addIncludePath(b.path(include_path));2373 for (options.include_paths) |include_path| these_tests.root_module.addIncludePath(b.path(include_path));
23752374
2376 if (target.os.tag == .windows) {
2377 for (options.windows_libs) |lib| these_tests.root_module.linkSystemLibrary(lib, .{});
2378 }
2379
2380 const qualified_name = b.fmt("{s}-{s}-{s}-{s}{s}{s}{s}{s}{s}{s}", .{2375 const qualified_name = b.fmt("{s}-{s}-{s}-{s}{s}{s}{s}{s}{s}{s}", .{
2381 options.name,2376 options.name,
2382 triple_txt,2377 triple_txt,
...@@ -2672,10 +2667,6 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {...@@ -2672,10 +2667,6 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {
2672 }),2667 }),
2673 });2668 });
26742669
2675 if (b.graph.host.result.os.tag == .windows) {
2676 incr_check.root_module.linkSystemLibrary("advapi32", .{});
2677 }
2678
2679 var dir = try b.build_root.handle.openDir("test/incremental", .{ .iterate = true });2670 var dir = try b.build_root.handle.openDir("test/incremental", .{ .iterate = true });
2680 defer dir.close();2671 defer dir.close();
26812672