Merge remote-tracking branch 'origin/master' into llvm16

This commit is contained in:
Andrew Kelley 2023-03-16 17:33:24 -07:00
commit 1ed569e0b2
392 changed files with 26277 additions and 18328 deletions

View File

@ -296,14 +296,12 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig"
"${CMAKE_SOURCE_DIR}/lib/std/multi_array_list.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/darwin.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/linux/errno/generic.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/linux/io_uring.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/posix_spawn.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/windows.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig"
"${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig"
@ -507,7 +505,9 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/lib/std/Thread.zig"
"${CMAKE_SOURCE_DIR}/lib/std/Thread/Futex.zig"
"${CMAKE_SOURCE_DIR}/lib/std/Thread/Mutex.zig"
"${CMAKE_SOURCE_DIR}/lib/std/Thread/Pool.zig"
"${CMAKE_SOURCE_DIR}/lib/std/Thread/ResetEvent.zig"
"${CMAKE_SOURCE_DIR}/lib/std/Thread/WaitGroup.zig"
"${CMAKE_SOURCE_DIR}/lib/std/time.zig"
"${CMAKE_SOURCE_DIR}/lib/std/treap.zig"
"${CMAKE_SOURCE_DIR}/lib/std/unicode.zig"
@ -517,6 +517,7 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig"
"${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig"
"${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"
"${CMAKE_SOURCE_DIR}/lib/std/zig/Server.zig"
"${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"
"${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
"${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"
@ -531,9 +532,7 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/Package.zig"
"${CMAKE_SOURCE_DIR}/src/RangeSet.zig"
"${CMAKE_SOURCE_DIR}/src/Sema.zig"
"${CMAKE_SOURCE_DIR}/src/ThreadPool.zig"
"${CMAKE_SOURCE_DIR}/src/TypedValue.zig"
"${CMAKE_SOURCE_DIR}/src/WaitGroup.zig"
"${CMAKE_SOURCE_DIR}/src/Zir.zig"
"${CMAKE_SOURCE_DIR}/src/arch/aarch64/CodeGen.zig"
"${CMAKE_SOURCE_DIR}/src/arch/aarch64/Emit.zig"
@ -560,9 +559,12 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/arch/wasm/Mir.zig"
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/CodeGen.zig"
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/Emit.zig"
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/Encoding.zig"
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/Mir.zig"
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig"
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/abi.zig"
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig"
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/encoder.zig"
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/encodings.zig"
"${CMAKE_SOURCE_DIR}/src/clang.zig"
"${CMAKE_SOURCE_DIR}/src/clang_options.zig"
"${CMAKE_SOURCE_DIR}/src/clang_options_data.zig"
@ -827,6 +829,12 @@ else()
set(ZIG_STATIC_ARG "")
endif()
if(CMAKE_POSITION_INDEPENDENT_CODE)
set(ZIG_PIE_ARG="-Dpie")
else()
set(ZIG_PIE_ARG="")
endif()
set(ZIG_BUILD_ARGS
--zig-lib-dir "${CMAKE_SOURCE_DIR}/lib"
"-Dconfig_h=${ZIG_CONFIG_H_OUT}"
@ -835,6 +843,7 @@ set(ZIG_BUILD_ARGS
${ZIG_STATIC_ARG}
${ZIG_NO_LIB_ARG}
${ZIG_SINGLE_THREADED_ARG}
${ZIG_PIE_ARG}
"-Dtarget=${ZIG_TARGET_TRIPLE}"
"-Dcpu=${ZIG_TARGET_MCPU}"
"-Dversion-string=${RESOLVED_ZIG_VERSION}"

220
build.zig
View File

@ -31,6 +31,11 @@ pub fn build(b: *std.Build) !void {
const use_zig_libcxx = b.option(bool, "use-zig-libcxx", "If libc++ is needed, use zig's bundled version, don't try to integrate with the system") orelse false;
const test_step = b.step("test", "Run all the tests");
const deprecated_skip_install_lib_files = b.option(bool, "skip-install-lib-files", "deprecated. see no-lib") orelse false;
if (deprecated_skip_install_lib_files) {
std.log.warn("-Dskip-install-lib-files is deprecated in favor of -Dno-lib", .{});
}
const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files and langref to installation prefix. Useful for development") orelse deprecated_skip_install_lib_files;
const docgen_exe = b.addExecutable(.{
.name = "docgen",
@ -40,28 +45,35 @@ pub fn build(b: *std.Build) !void {
});
docgen_exe.single_threaded = single_threaded;
const langref_out_path = try b.cache_root.join(b.allocator, &.{"langref.html"});
const docgen_cmd = docgen_exe.run();
docgen_cmd.addArgs(&[_][]const u8{
"--zig",
b.zig_exe,
"doc" ++ fs.path.sep_str ++ "langref.html.in",
langref_out_path,
});
docgen_cmd.step.dependOn(&docgen_exe.step);
const docgen_cmd = b.addRunArtifact(docgen_exe);
docgen_cmd.addArgs(&.{ "--zig", b.zig_exe });
if (b.zig_lib_dir) |p| {
docgen_cmd.addArgs(&.{ "--zig-lib-dir", p });
}
docgen_cmd.addFileSourceArg(.{ .path = "doc/langref.html.in" });
const langref_file = docgen_cmd.addOutputFileArg("langref.html");
const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "doc/langref.html");
if (!skip_install_lib_files) {
b.getInstallStep().dependOn(&install_langref.step);
}
const docs_step = b.step("docs", "Build documentation");
docs_step.dependOn(&docgen_cmd.step);
const test_cases = b.addTest(.{
.root_source_file = .{ .path = "src/test.zig" },
// This is for legacy reasons, to be removed after our CI scripts are upgraded to use
// the file from the install prefix instead.
const legacy_write_to_cache = b.addWriteFiles();
legacy_write_to_cache.addCopyFileToSource(langref_file, "zig-cache/langref.html");
docs_step.dependOn(&legacy_write_to_cache.step);
const check_case_exe = b.addExecutable(.{
.name = "check-case",
.root_source_file = .{ .path = "test/src/Cases.zig" },
.optimize = optimize,
});
test_cases.main_pkg_path = ".";
test_cases.stack_size = stack_size;
test_cases.single_threaded = single_threaded;
const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"});
check_case_exe.main_pkg_path = ".";
check_case_exe.stack_size = stack_size;
check_case_exe.single_threaded = single_threaded;
const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false;
const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false;
@ -74,11 +86,6 @@ pub fn build(b: *std.Build) !void {
const skip_stage1 = b.option(bool, "skip-stage1", "Main test suite skips stage1 compile error tests") orelse false;
const skip_run_translated_c = b.option(bool, "skip-run-translated-c", "Main test suite skips run-translated-c tests") orelse false;
const skip_stage2_tests = b.option(bool, "skip-stage2-tests", "Main test suite skips self-hosted compiler tests") orelse false;
const deprecated_skip_install_lib_files = b.option(bool, "skip-install-lib-files", "deprecated. see no-lib") orelse false;
if (deprecated_skip_install_lib_files) {
std.log.warn("-Dskip-install-lib-files is deprecated in favor of -Dno-lib", .{});
}
const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files to installation prefix. Useful for development") orelse deprecated_skip_install_lib_files;
const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;
@ -157,6 +164,7 @@ pub fn build(b: *std.Build) !void {
const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse (enable_llvm or only_c);
const sanitize_thread = b.option(bool, "sanitize-thread", "Enable thread-sanitization") orelse false;
const strip = b.option(bool, "strip", "Omit debug information");
const pie = b.option(bool, "pie", "Produce a Position Independent Executable");
const value_tracing = b.option(bool, "value-tracing", "Enable extra state tracking to help troubleshoot bugs in the compiler (using the std.debug.Trace API)") orelse false;
const mem_leak_frames: u32 = b.option(u32, "mem-leak-frames", "How many stack frames to print when a memory leak occurs. Tests get 2x this amount.") orelse blk: {
@ -167,6 +175,7 @@ pub fn build(b: *std.Build) !void {
const exe = addCompilerStep(b, optimize, target);
exe.strip = strip;
exe.pie = pie;
exe.sanitize_thread = sanitize_thread;
exe.build_id = b.option(bool, "build-id", "Include a build id note") orelse false;
exe.install();
@ -178,13 +187,12 @@ pub fn build(b: *std.Build) !void {
test_step.dependOn(&exe.step);
}
b.default_step.dependOn(&exe.step);
exe.single_threaded = single_threaded;
if (target.isWindows() and target.getAbi() == .gnu) {
// LTO is currently broken on mingw, this can be removed when it's fixed.
exe.want_lto = false;
test_cases.want_lto = false;
check_case_exe.want_lto = false;
}
const exe_options = b.addOptions();
@ -199,11 +207,11 @@ pub fn build(b: *std.Build) !void {
exe_options.addOption(bool, "llvm_has_xtensa", llvm_has_xtensa);
exe_options.addOption(bool, "force_gpa", force_gpa);
exe_options.addOption(bool, "only_c", only_c);
exe_options.addOption(bool, "omit_pkg_fetching_code", false);
exe_options.addOption(bool, "omit_pkg_fetching_code", only_c);
if (link_libc) {
exe.linkLibC();
test_cases.linkLibC();
check_case_exe.linkLibC();
}
const is_debug = optimize == .Debug;
@ -289,14 +297,14 @@ pub fn build(b: *std.Build) !void {
}
try addCmakeCfgOptionsToExe(b, cfg, exe, use_zig_libcxx);
try addCmakeCfgOptionsToExe(b, cfg, test_cases, use_zig_libcxx);
try addCmakeCfgOptionsToExe(b, cfg, check_case_exe, use_zig_libcxx);
} else {
// Here we are -Denable-llvm but no cmake integration.
try addStaticLlvmOptionsToExe(exe);
try addStaticLlvmOptionsToExe(test_cases);
try addStaticLlvmOptionsToExe(check_case_exe);
}
if (target.isWindows()) {
inline for (.{ exe, test_cases }) |artifact| {
inline for (.{ exe, check_case_exe }) |artifact| {
artifact.linkSystemLibrary("version");
artifact.linkSystemLibrary("uuid");
artifact.linkSystemLibrary("ole32");
@ -341,8 +349,9 @@ pub fn build(b: *std.Build) !void {
const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
const test_cases_options = b.addOptions();
test_cases.addOptions("build_options", test_cases_options);
check_case_exe.addOptions("build_options", test_cases_options);
test_cases_options.addOption(bool, "enable_tracy", false);
test_cases_options.addOption(bool, "enable_logging", enable_logging);
test_cases_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots);
test_cases_options.addOption(bool, "skip_non_native", skip_non_native);
@ -366,12 +375,6 @@ pub fn build(b: *std.Build) !void {
test_cases_options.addOption(std.SemanticVersion, "semver", semver);
test_cases_options.addOption(?[]const u8, "test_filter", test_filter);
const test_cases_step = b.step("test-cases", "Run the main compiler test cases");
test_cases_step.dependOn(&test_cases.step);
if (!skip_stage2_tests) {
test_step.dependOn(test_cases_step);
}
var chosen_opt_modes_buf: [4]builtin.Mode = undefined;
var chosen_mode_index: usize = 0;
if (!skip_debug) {
@ -392,96 +395,101 @@ pub fn build(b: *std.Build) !void {
}
const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index];
// run stage1 `zig fmt` on this build.zig file just to make sure it works
test_step.dependOn(&fmt_build_zig.step);
const fmt_step = b.step("test-fmt", "Run zig fmt against build.zig to make sure it works");
fmt_step.dependOn(&fmt_build_zig.step);
const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" };
const fmt_exclude_paths = &.{"test/cases"};
const do_fmt = b.addFmt(.{
.paths = fmt_include_paths,
.exclude_paths = fmt_exclude_paths,
});
test_step.dependOn(tests.addPkgTests(
b,
test_filter,
"test/behavior.zig",
"behavior",
"Run the behavior tests",
optimization_modes,
skip_single_threaded,
skip_non_native,
skip_libc,
skip_stage1,
skip_stage2_tests,
));
b.step("test-fmt", "Check source files having conforming formatting").dependOn(&b.addFmt(.{
.paths = fmt_include_paths,
.exclude_paths = fmt_exclude_paths,
.check = true,
}).step);
test_step.dependOn(tests.addPkgTests(
b,
test_filter,
"lib/compiler_rt.zig",
"compiler-rt",
"Run the compiler_rt tests",
optimization_modes,
true, // skip_single_threaded
skip_non_native,
true, // skip_libc
skip_stage1,
skip_stage2_tests or true, // TODO get these all passing
));
const test_cases_step = b.step("test-cases", "Run the main compiler test cases");
try tests.addCases(b, test_cases_step, test_filter, check_case_exe);
if (!skip_stage2_tests) test_step.dependOn(test_cases_step);
test_step.dependOn(tests.addPkgTests(
b,
test_filter,
"lib/c.zig",
"universal-libc",
"Run the universal libc tests",
optimization_modes,
true, // skip_single_threaded
skip_non_native,
true, // skip_libc
skip_stage1,
skip_stage2_tests or true, // TODO get these all passing
));
test_step.dependOn(tests.addModuleTests(b, .{
.test_filter = test_filter,
.root_src = "test/behavior.zig",
.name = "behavior",
.desc = "Run the behavior tests",
.optimize_modes = optimization_modes,
.skip_single_threaded = skip_single_threaded,
.skip_non_native = skip_non_native,
.skip_libc = skip_libc,
.skip_stage1 = skip_stage1,
.skip_stage2 = skip_stage2_tests,
.max_rss = 1 * 1024 * 1024 * 1024,
}));
test_step.dependOn(tests.addModuleTests(b, .{
.test_filter = test_filter,
.root_src = "lib/compiler_rt.zig",
.name = "compiler-rt",
.desc = "Run the compiler_rt tests",
.optimize_modes = optimization_modes,
.skip_single_threaded = true,
.skip_non_native = skip_non_native,
.skip_libc = true,
.skip_stage1 = skip_stage1,
.skip_stage2 = true, // TODO get all these passing
}));
test_step.dependOn(tests.addModuleTests(b, .{
.test_filter = test_filter,
.root_src = "lib/c.zig",
.name = "universal-libc",
.desc = "Run the universal libc tests",
.optimize_modes = optimization_modes,
.skip_single_threaded = true,
.skip_non_native = skip_non_native,
.skip_libc = true,
.skip_stage1 = skip_stage1,
.skip_stage2 = true, // TODO get all these passing
}));
test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes));
test_step.dependOn(tests.addStandaloneTests(
b,
test_filter,
optimization_modes,
skip_non_native,
enable_macos_sdk,
target,
skip_stage2_tests,
b.enable_darling,
b.enable_qemu,
b.enable_rosetta,
b.enable_wasmtime,
b.enable_wine,
enable_symlinks_windows,
));
test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release));
test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows));
test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows));
test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes));
test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes));
test_step.dependOn(tests.addCliTests(b));
test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes));
test_step.dependOn(tests.addTranslateCTests(b, test_filter));
if (!skip_run_translated_c) {
test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter, target));
}
// tests for this feature are disabled until we have the self-hosted compiler available
// test_step.dependOn(tests.addGenHTests(b, test_filter));
test_step.dependOn(tests.addPkgTests(
b,
test_filter,
"lib/std/std.zig",
"std",
"Run the standard library tests",
optimization_modes,
skip_single_threaded,
skip_non_native,
skip_libc,
skip_stage1,
true, // TODO get these all passing
));
test_step.dependOn(tests.addModuleTests(b, .{
.test_filter = test_filter,
.root_src = "lib/std/std.zig",
.name = "std",
.desc = "Run the standard library tests",
.optimize_modes = optimization_modes,
.skip_single_threaded = skip_single_threaded,
.skip_non_native = skip_non_native,
.skip_libc = skip_libc,
.skip_stage1 = skip_stage1,
.skip_stage2 = true, // TODO get all these passing
// I observed a value of 3398275072 on my M1, and multiplied by 1.1 to
// get this amount:
.max_rss = 3738102579,
}));
try addWasiUpdateStep(b, version);
b.step("fmt", "Modify source files in place to have conforming formatting")
.dependOn(&do_fmt.step);
}
fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
@ -510,6 +518,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
exe_options.addOption(bool, "enable_tracy_callstack", false);
exe_options.addOption(bool, "enable_tracy_allocation", false);
exe_options.addOption(bool, "value_tracing", false);
exe_options.addOption(bool, "omit_pkg_fetching_code", true);
const run_opt = b.addSystemCommand(&.{ "wasm-opt", "-Oz", "--enable-bulk-memory" });
run_opt.addArtifactArg(exe);
@ -681,10 +690,7 @@ fn addCxxKnownPath(
) !void {
if (!std.process.can_spawn)
return error.RequiredLibraryNotFound;
const path_padded = try b.exec(&[_][]const u8{
ctx.cxx_compiler,
b.fmt("-print-file-name={s}", .{objname}),
});
const path_padded = b.exec(&.{ ctx.cxx_compiler, b.fmt("-print-file-name={s}", .{objname}) });
var tokenizer = mem.tokenize(u8, path_padded, "\r\n");
const path_unpadded = tokenizer.next().?;
if (mem.eql(u8, path_unpadded, objname)) {

View File

@ -67,7 +67,7 @@ stage3-debug/bin/zig build test docs \
--zig-lib-dir "$(pwd)/../lib"
# Look for HTML errors.
tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html"
tidy --drop-empty-elements no -qe "stage3-debug/doc/langref.html"
# Produce the experimental std lib documentation.
stage3-debug/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
@ -98,3 +98,9 @@ unset CXX
ninja install
stage3/bin/zig test ../test/behavior.zig -I../test
stage3/bin/zig build -p stage4 \
-Dstatic-llvm \
-Dtarget=native-native-musl \
--search-prefix "$PREFIX" \
--zig-lib-dir "$(pwd)/../lib"
stage4/bin/zig test ../test/behavior.zig -I../test

View File

@ -67,7 +67,7 @@ stage3-release/bin/zig build test docs \
--zig-lib-dir "$(pwd)/../lib"
# Look for HTML errors.
tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html"
tidy --drop-empty-elements no -qe "stage3-release/doc/langref.html"
# Produce the experimental std lib documentation.
stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
@ -98,3 +98,9 @@ unset CXX
ninja install
stage3/bin/zig test ../test/behavior.zig -I../test
stage3/bin/zig build -p stage4 \
-Dstatic-llvm \
-Dtarget=native-native-musl \
--search-prefix "$PREFIX" \
--zig-lib-dir "$(pwd)/../lib"
stage4/bin/zig test ../test/behavior.zig -I../test

View File

@ -66,7 +66,7 @@ stage3-debug/bin/zig build test docs \
--zig-lib-dir "$(pwd)/../lib"
# Look for HTML errors.
tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html"
tidy --drop-empty-elements no -qe "stage3-debug/doc/langref.html"
# Produce the experimental std lib documentation.
stage3-debug/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
@ -97,3 +97,9 @@ unset CXX
ninja install
stage3/bin/zig test ../test/behavior.zig -I../test
stage3/bin/zig build -p stage4 \
-Dstatic-llvm \
-Dtarget=native-native-musl \
--search-prefix "$PREFIX" \
--zig-lib-dir "$(pwd)/../lib"
stage4/bin/zig test ../test/behavior.zig -I../test

View File

@ -67,7 +67,7 @@ stage3-release/bin/zig build test docs \
--zig-lib-dir "$(pwd)/../lib"
# Look for HTML errors.
tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html"
tidy --drop-empty-elements no -qe "stage3-release/doc/langref.html"
# Produce the experimental std lib documentation.
stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
@ -114,3 +114,9 @@ unset CXX
ninja install
stage3/bin/zig test ../test/behavior.zig -I../test
stage3/bin/zig build -p stage4 \
-Dstatic-llvm \
-Dtarget=native-native-musl \
--search-prefix "$PREFIX" \
--zig-lib-dir "$(pwd)/../lib"
stage4/bin/zig test ../test/behavior.zig -I../test

View File

@ -28,10 +28,10 @@ const usage =
\\
;
fn errorf(comptime format: []const u8, args: anytype) noreturn {
fn fatal(comptime format: []const u8, args: anytype) noreturn {
const stderr = io.getStdErr().writer();
stderr.print("error: " ++ format, args) catch {};
stderr.print("error: " ++ format ++ "\n", args) catch {};
process.exit(1);
}
@ -45,6 +45,7 @@ pub fn main() !void {
if (!args_it.skip()) @panic("expected self arg");
var zig_exe: []const u8 = "zig";
var opt_zig_lib_dir: ?[]const u8 = null;
var do_code_tests = true;
var files = [_][]const u8{ "", "" };
@ -59,24 +60,29 @@ pub fn main() !void {
if (args_it.next()) |param| {
zig_exe = param;
} else {
errorf("expected parameter after --zig\n", .{});
fatal("expected parameter after --zig", .{});
}
} else if (mem.eql(u8, arg, "--zig-lib-dir")) {
if (args_it.next()) |param| {
opt_zig_lib_dir = param;
} else {
fatal("expected parameter after --zig-lib-dir", .{});
}
} else if (mem.eql(u8, arg, "--skip-code-tests")) {
do_code_tests = false;
} else {
errorf("unrecognized option: '{s}'\n", .{arg});
fatal("unrecognized option: '{s}'", .{arg});
}
} else {
if (i > 1) {
errorf("too many arguments\n", .{});
fatal("too many arguments", .{});
}
files[i] = arg;
i += 1;
}
}
if (i < 2) {
errorf("not enough arguments\n", .{});
process.exit(1);
fatal("not enough arguments", .{});
}
var in_file = try fs.cwd().openFile(files[0], .{ .mode = .read_only });
@ -95,7 +101,7 @@ pub fn main() !void {
try fs.cwd().makePath(tmp_dir_name);
defer fs.cwd().deleteTree(tmp_dir_name) catch {};
try genHtml(allocator, &tokenizer, &toc, buffered_writer.writer(), zig_exe, do_code_tests);
try genHtml(allocator, &tokenizer, &toc, buffered_writer.writer(), zig_exe, opt_zig_lib_dir, do_code_tests);
try buffered_writer.flush();
}
@ -1268,9 +1274,10 @@ fn genHtml(
toc: *Toc,
out: anytype,
zig_exe: []const u8,
opt_zig_lib_dir: ?[]const u8,
do_code_tests: bool,
) !void {
var progress = Progress{};
var progress = Progress{ .dont_print_on_dumb = true };
const root_node = progress.start("Generating docgen examples", toc.nodes.len);
defer root_node.end();
@ -1278,7 +1285,7 @@ fn genHtml(
try env_map.put("ZIG_DEBUG_COLOR", "1");
const host = try std.zig.system.NativeTargetInfo.detect(.{});
const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe);
const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe, opt_zig_lib_dir);
for (toc.nodes) |node| {
defer root_node.completeOne();
@ -1370,6 +1377,9 @@ fn genHtml(
"--color", "on",
"--enable-cache", tmp_source_file_name,
});
if (opt_zig_lib_dir) |zig_lib_dir| {
try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
}
try shell_out.print("$ zig build-exe {s} ", .{name_plus_ext});
@ -1512,8 +1522,12 @@ fn genHtml(
defer test_args.deinit();
try test_args.appendSlice(&[_][]const u8{
zig_exe, "test", tmp_source_file_name,
zig_exe, "test",
tmp_source_file_name,
});
if (opt_zig_lib_dir) |zig_lib_dir| {
try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
}
try shell_out.print("$ zig test {s}.zig ", .{code.name});
switch (code.mode) {
@ -1564,12 +1578,13 @@ fn genHtml(
defer test_args.deinit();
try test_args.appendSlice(&[_][]const u8{
zig_exe,
"test",
"--color",
"on",
zig_exe, "test",
"--color", "on",
tmp_source_file_name,
});
if (opt_zig_lib_dir) |zig_lib_dir| {
try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
}
try shell_out.print("$ zig test {s}.zig ", .{code.name});
switch (code.mode) {
@ -1624,8 +1639,12 @@ fn genHtml(
defer test_args.deinit();
try test_args.appendSlice(&[_][]const u8{
zig_exe, "test", tmp_source_file_name,
zig_exe, "test",
tmp_source_file_name,
});
if (opt_zig_lib_dir) |zig_lib_dir| {
try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
}
var mode_arg: []const u8 = "";
switch (code.mode) {
.Debug => {},
@ -1684,17 +1703,17 @@ fn genHtml(
defer build_args.deinit();
try build_args.appendSlice(&[_][]const u8{
zig_exe,
"build-obj",
zig_exe, "build-obj",
"--color", "on",
"--name", code.name,
tmp_source_file_name,
"--color",
"on",
"--name",
code.name,
try std.fmt.allocPrint(allocator, "-femit-bin={s}{c}{s}", .{
tmp_dir_name, fs.path.sep, name_plus_obj_ext,
}),
});
if (opt_zig_lib_dir) |zig_lib_dir| {
try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
}
try shell_out.print("$ zig build-obj {s}.zig ", .{code.name});
@ -1758,13 +1777,15 @@ fn genHtml(
defer test_args.deinit();
try test_args.appendSlice(&[_][]const u8{
zig_exe,
"build-lib",
zig_exe, "build-lib",
tmp_source_file_name,
try std.fmt.allocPrint(allocator, "-femit-bin={s}{s}{s}", .{
tmp_dir_name, fs.path.sep_str, bin_basename,
}),
});
if (opt_zig_lib_dir) |zig_lib_dir| {
try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
}
try shell_out.print("$ zig build-lib {s}.zig ", .{code.name});
switch (code.mode) {
@ -1829,9 +1850,23 @@ fn exec(allocator: Allocator, env_map: *process.EnvMap, args: []const []const u8
return result;
}
fn getBuiltinCode(allocator: Allocator, env_map: *process.EnvMap, zig_exe: []const u8) ![]const u8 {
const result = try exec(allocator, env_map, &[_][]const u8{ zig_exe, "build-obj", "--show-builtin" });
return result.stdout;
fn getBuiltinCode(
allocator: Allocator,
env_map: *process.EnvMap,
zig_exe: []const u8,
opt_zig_lib_dir: ?[]const u8,
) ![]const u8 {
if (opt_zig_lib_dir) |zig_lib_dir| {
const result = try exec(allocator, env_map, &.{
zig_exe, "build-obj", "--show-builtin", "--zig-lib-dir", zig_lib_dir,
});
return result.stdout;
} else {
const result = try exec(allocator, env_map, &.{
zig_exe, "build-obj", "--show-builtin",
});
return result.stdout;
}
}
fn dumpArgs(args: []const []const u8) void {

View File

@ -7457,20 +7457,20 @@ pub const STDOUT_FILENO = 1;
pub fn syscall1(number: usize, arg1: usize) usize {
return asm volatile ("syscall"
: [ret] "={rax}" (-> usize)
: [ret] "={rax}" (-> usize),
: [number] "{rax}" (number),
[arg1] "{rdi}" (arg1)
[arg1] "{rdi}" (arg1),
: "rcx", "r11"
);
}
pub fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) usize {
return asm volatile ("syscall"
: [ret] "={rax}" (-> usize)
: [ret] "={rax}" (-> usize),
: [number] "{rax}" (number),
[arg1] "{rdi}" (arg1),
[arg2] "{rsi}" (arg2),
[arg3] "{rdx}" (arg3)
[arg3] "{rdx}" (arg3),
: "rcx", "r11"
);
}
@ -7519,14 +7519,14 @@ pub fn syscall1(number: usize, arg1: usize) usize {
// type is the result type of the inline assembly expression.
// If it is a value binding, then `%[ret]` syntax would be used
// to refer to the register bound to the value.
(-> usize)
(-> usize),
// Next is the list of inputs.
// The constraint for these inputs means, "when the assembly code is
// executed, $rax shall have the value of `number` and $rdi shall have
// the value of `arg1`". Any number of input parameters is allowed,
// including none.
: [number] "{rax}" (number),
[arg1] "{rdi}" (arg1)
[arg1] "{rdi}" (arg1),
// Next is the list of clobbers. These declare a set of registers whose
// values will not be preserved by the execution of this assembly code.
// These do not include output or input registers. The special clobber
@ -7818,12 +7818,14 @@ comptime {
<p>
This function inserts a platform-specific debug trap instruction which causes
debuggers to break there.
Unlike for {#syntax#}@trap(){#endsyntax#}, execution may continue after this point if the program is resumed.
</p>
<p>
This function is only valid within function scope.
</p>
{#see_also|@trap#}
{#header_close#}
{#header_open|@mulAdd#}
<pre>{#syntax#}@mulAdd(comptime T: type, a: T, b: T, c: T) T{#endsyntax#}</pre>
<p>
@ -9393,6 +9395,19 @@ fn List(comptime T: type) type {
</p>
{#header_close#}
{#header_open|@trap#}
<pre>{#syntax#}@trap() noreturn{#endsyntax#}</pre>
<p>
This function inserts a platform-specific trap/jam instruction which can be used to exit the program abnormally.
This may be implemented by explicitly emitting an invalid instruction which may cause an illegal instruction exception of some sort.
Unlike for {#syntax#}@breakpoint(){#endsyntax#}, execution does not continue after this point.
</p>
<p>
Outside function scope, this builtin causes a compile error.
</p>
{#see_also|@breakpoint#}
{#header_close#}
{#header_open|@truncate#}
<pre>{#syntax#}@truncate(comptime T: type, integer: anytype) T{#endsyntax#}</pre>
<p>
@ -9565,9 +9580,10 @@ pub fn build(b: *std.Build) void {
This causes these options to be available:
</p>
<dl>
<dt><kbd>-Drelease-safe=[bool]</kbd></dt><dd>Optimizations on and safety on</dd>
<dt><kbd>-Drelease-fast=[bool]</kbd></dt><dd>Optimizations on and safety off</dd>
<dt><kbd>-Drelease-small=[bool]</kbd></dt><dd>Size optimizations on and safety off</dd>
<dt><kbd>-Doptimize=Debug</kbd></dt><dd>Optimizations off and safety on (default)</dd>
<dt><kbd>-Doptimize=ReleaseSafe</kbd></dt><dd>Optimizations on and safety on</dd>
<dt><kbd>-Doptimize=ReleaseFast</kbd></dt><dd>Optimizations on and safety off</dd>
<dt><kbd>-Doptimize=ReleaseSmall</kbd></dt><dd>Size optimizations on and safety off</dd>
</dl>
{#header_open|Debug#}
{#shell_samp#}$ zig build-exe example.zig{#end_shell_samp#}
@ -9788,8 +9804,8 @@ pub fn main() !void {
{#header_close#}
{#header_open|Builtin Overflow Functions#}
<p>
These builtins return a {#syntax#}bool{#endsyntax#} of whether or not overflow
occurred, as well as returning the overflowed bits:
These builtins return a tuple containing whether there was an overflow
(as a {#syntax#}u1{#endsyntax#}) and the possibly overflowed bits of the operation:
</p>
<ul>
<li>{#link|@addWithOverflow#}</li>

View File

@ -1,12 +1,14 @@
const root = @import("@build");
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const io = std.io;
const fmt = std.fmt;
const mem = std.mem;
const process = std.process;
const ArrayList = std.ArrayList;
const File = std.fs.File;
const Step = std.Build.Step;
pub const dependencies = @import("@dependencies");
@ -14,12 +16,15 @@ pub fn main() !void {
// Here we use an ArenaAllocator backed by a DirectAllocator because a build is a short-lived,
// one shot program. We don't need to waste time freeing memory and finding places to squish
// bytes into. So we free everything all at once at the very end.
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
var single_threaded_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer single_threaded_arena.deinit();
const allocator = arena.allocator();
var args = try process.argsAlloc(allocator);
defer process.argsFree(allocator, args);
var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
.child_allocator = single_threaded_arena.allocator(),
};
const arena = thread_safe_arena.allocator();
var args = try process.argsAlloc(arena);
// skip my own exe name
var arg_idx: usize = 1;
@ -59,18 +64,17 @@ pub fn main() !void {
};
var cache: std.Build.Cache = .{
.gpa = allocator,
.gpa = arena,
.manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
};
cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
cache.addPrefix(build_root_directory);
cache.addPrefix(local_cache_directory);
cache.addPrefix(global_cache_directory);
//cache.hash.addBytes(builtin.zig_version);
cache.hash.addBytes(builtin.zig_version_string);
const builder = try std.Build.create(
allocator,
arena,
zig_exe,
build_root_directory,
local_cache_directory,
@ -80,35 +84,34 @@ pub fn main() !void {
);
defer builder.destroy();
var targets = ArrayList([]const u8).init(allocator);
var debug_log_scopes = ArrayList([]const u8).init(allocator);
const stderr_stream = io.getStdErr().writer();
const stdout_stream = io.getStdOut().writer();
var targets = ArrayList([]const u8).init(arena);
var debug_log_scopes = ArrayList([]const u8).init(arena);
var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena };
var install_prefix: ?[]const u8 = null;
var dir_list = std.Build.DirList{};
var enable_summary: ?bool = null;
var max_rss: usize = 0;
var color: Color = .auto;
// before arg parsing, check for the NO_COLOR environment variable
// if it exists, default the color setting to .off
// explicit --color arguments will still override this setting.
builder.color = if (std.process.hasEnvVarConstant("NO_COLOR")) .off else .auto;
const stderr_stream = io.getStdErr().writer();
const stdout_stream = io.getStdOut().writer();
while (nextArg(args, &arg_idx)) |arg| {
if (mem.startsWith(u8, arg, "-D")) {
const option_contents = arg[2..];
if (option_contents.len == 0) {
std.debug.print("Expected option name after '-D'\n\n", .{});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
}
if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
const option_name = option_contents[0..name_end];
const option_value = option_contents[name_end + 1 ..];
if (try builder.addUserInputOption(option_name, option_value))
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
} else {
if (try builder.addUserInputFlag(option_contents))
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
}
} else if (mem.startsWith(u8, arg, "-")) {
if (mem.eql(u8, arg, "--verbose")) {
@ -118,69 +121,83 @@ pub fn main() !void {
} else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
install_prefix = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
} else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
return steps(builder, false, stdout_stream);
} else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
dir_list.lib_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
} else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
dir_list.exe_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
} else if (mem.eql(u8, arg, "--prefix-include-dir")) {
dir_list.include_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
} else if (mem.eql(u8, arg, "--sysroot")) {
const sysroot = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after --sysroot\n\n", .{});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
builder.sysroot = sysroot;
} else if (mem.eql(u8, arg, "--maxrss")) {
const max_rss_text = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after --sysroot\n\n", .{});
usageAndErr(builder, false, stderr_stream);
};
// TODO: support shorthand such as "2GiB", "2GB", or "2G"
max_rss = std.fmt.parseInt(usize, max_rss_text, 10) catch |err| {
std.debug.print("invalid byte size: '{s}': {s}\n", .{
max_rss_text, @errorName(err),
});
process.exit(1);
};
} else if (mem.eql(u8, arg, "--search-prefix")) {
const search_prefix = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after --search-prefix\n\n", .{});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
builder.addSearchPrefix(search_prefix);
} else if (mem.eql(u8, arg, "--libc")) {
const libc_file = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after --libc\n\n", .{});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
builder.libc_file = libc_file;
} else if (mem.eql(u8, arg, "--color")) {
const next_arg = nextArg(args, &arg_idx) orelse {
std.debug.print("expected [auto|on|off] after --color", .{});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
builder.color = std.meta.stringToEnum(@TypeOf(builder.color), next_arg) orelse {
color = std.meta.stringToEnum(Color, next_arg) orelse {
std.debug.print("expected [auto|on|off] after --color, found '{s}'", .{next_arg});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
} else if (mem.eql(u8, arg, "--zig-lib-dir")) {
builder.zig_lib_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after --zig-lib-dir\n\n", .{});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
} else if (mem.eql(u8, arg, "--debug-log")) {
const next_arg = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
try debug_log_scopes.append(next_arg);
} else if (mem.eql(u8, arg, "--debug-pkg-config")) {
builder.debug_pkg_config = true;
} else if (mem.eql(u8, arg, "--debug-compile-errors")) {
builder.debug_compile_errors = true;
} else if (mem.eql(u8, arg, "--glibc-runtimes")) {
builder.glibc_runtimes_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after --glibc-runtimes\n\n", .{});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
};
} else if (mem.eql(u8, arg, "--verbose-link")) {
builder.verbose_link = true;
@ -194,8 +211,6 @@ pub fn main() !void {
builder.verbose_cc = true;
} else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
builder.verbose_llvm_cpu_features = true;
} else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
builder.prominent_compile_errors = true;
} else if (mem.eql(u8, arg, "-fwine")) {
builder.enable_wine = true;
} else if (mem.eql(u8, arg, "-fno-wine")) {
@ -216,6 +231,10 @@ pub fn main() !void {
builder.enable_darling = true;
} else if (mem.eql(u8, arg, "-fno-darling")) {
builder.enable_darling = false;
} else if (mem.eql(u8, arg, "-fsummary")) {
enable_summary = true;
} else if (mem.eql(u8, arg, "-fno-summary")) {
enable_summary = false;
} else if (mem.eql(u8, arg, "-freference-trace")) {
builder.reference_trace = 256;
} else if (mem.startsWith(u8, arg, "-freference-trace=")) {
@ -226,39 +245,639 @@ pub fn main() !void {
};
} else if (mem.eql(u8, arg, "-fno-reference-trace")) {
builder.reference_trace = null;
} else if (mem.startsWith(u8, arg, "-j")) {
const num = arg["-j".len..];
const n_jobs = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
std.debug.print("unable to parse jobs count '{s}': {s}", .{
num, @errorName(err),
});
process.exit(1);
};
if (n_jobs < 1) {
std.debug.print("number of jobs must be at least 1\n", .{});
process.exit(1);
}
thread_pool_options.n_jobs = n_jobs;
} else if (mem.eql(u8, arg, "--")) {
builder.args = argsRest(args, arg_idx);
break;
} else {
std.debug.print("Unrecognized argument: {s}\n\n", .{arg});
return usageAndErr(builder, false, stderr_stream);
usageAndErr(builder, false, stderr_stream);
}
} else {
try targets.append(arg);
}
}
const stderr = std.io.getStdErr();
const ttyconf = get_tty_conf(color, stderr);
switch (ttyconf) {
.no_color => try builder.env_map.put("NO_COLOR", "1"),
.escape_codes => try builder.env_map.put("ZIG_DEBUG_COLOR", "1"),
.windows_api => {},
}
var progress: std.Progress = .{ .dont_print_on_dumb = true };
const main_progress_node = progress.start("", 0);
builder.debug_log_scopes = debug_log_scopes.items;
builder.resolveInstallPrefix(install_prefix, dir_list);
try builder.runBuild(root);
{
var prog_node = main_progress_node.start("user build.zig logic", 0);
defer prog_node.end();
try builder.runBuild(root);
}
if (builder.validateUserInputDidItFail())
return usageAndErr(builder, true, stderr_stream);
usageAndErr(builder, true, stderr_stream);
builder.make(targets.items) catch |err| {
switch (err) {
error.InvalidStepName => {
return usageAndErr(builder, true, stderr_stream);
},
error.UncleanExit => process.exit(1),
// This error is intended to indicate that the step has already
// logged an error message and so printing the error return trace
// here would be unwanted extra information, unless the user opts
// into it with a debug flag.
error.StepFailed => process.exit(1),
else => return err,
}
var run: Run = .{
.max_rss = max_rss,
.max_rss_is_default = false,
.max_rss_mutex = .{},
.memory_blocked_steps = std.ArrayList(*Step).init(arena),
.claimed_rss = 0,
.enable_summary = enable_summary,
.ttyconf = ttyconf,
.stderr = stderr,
};
if (run.max_rss == 0) {
run.max_rss = process.totalSystemMemory() catch std.math.maxInt(usize);
run.max_rss_is_default = true;
}
runStepNames(
arena,
builder,
targets.items,
main_progress_node,
thread_pool_options,
&run,
) catch |err| switch (err) {
error.UncleanExit => process.exit(1),
else => return err,
};
}
const Run = struct {
max_rss: usize,
max_rss_is_default: bool,
max_rss_mutex: std.Thread.Mutex,
memory_blocked_steps: std.ArrayList(*Step),
claimed_rss: usize,
enable_summary: ?bool,
ttyconf: std.debug.TTY.Config,
stderr: std.fs.File,
};
fn runStepNames(
arena: std.mem.Allocator,
b: *std.Build,
step_names: []const []const u8,
parent_prog_node: *std.Progress.Node,
thread_pool_options: std.Thread.Pool.Options,
run: *Run,
) !void {
const gpa = b.allocator;
var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{};
defer step_stack.deinit(gpa);
if (step_names.len == 0) {
try step_stack.put(gpa, b.default_step, {});
} else {
try step_stack.ensureUnusedCapacity(gpa, step_names.len);
for (0..step_names.len) |i| {
const step_name = step_names[step_names.len - i - 1];
const s = b.top_level_steps.get(step_name) orelse {
std.debug.print("no step named '{s}'. Access the help menu with 'zig build -h'\n", .{step_name});
process.exit(1);
};
step_stack.putAssumeCapacity(&s.step, {});
}
}
const starting_steps = try arena.dupe(*Step, step_stack.keys());
for (starting_steps) |s| {
checkForDependencyLoop(b, s, &step_stack) catch |err| switch (err) {
error.DependencyLoopDetected => return error.UncleanExit,
else => |e| return e,
};
}
{
// Check that we have enough memory to complete the build.
var any_problems = false;
for (step_stack.keys()) |s| {
if (s.max_rss == 0) continue;
if (s.max_rss > run.max_rss) {
std.debug.print("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory\n", .{
s.owner.dep_prefix, s.name, s.max_rss, run.max_rss,
});
any_problems = true;
}
}
if (any_problems) {
if (run.max_rss_is_default) {
std.debug.print("note: use --maxrss to override the default", .{});
}
return error.UncleanExit;
}
}
var thread_pool: std.Thread.Pool = undefined;
try thread_pool.init(thread_pool_options);
defer thread_pool.deinit();
{
defer parent_prog_node.end();
var step_prog = parent_prog_node.start("steps", step_stack.count());
defer step_prog.end();
var wait_group: std.Thread.WaitGroup = .{};
defer wait_group.wait();
// Here we spawn the initial set of tasks with a nice heuristic -
// dependency order. Each worker when it finishes a step will then
// check whether it should run any dependants.
const steps_slice = step_stack.keys();
for (0..steps_slice.len) |i| {
const step = steps_slice[steps_slice.len - i - 1];
wait_group.start();
thread_pool.spawn(workerMakeOneStep, .{
&wait_group, &thread_pool, b, step, &step_prog, run,
}) catch @panic("OOM");
}
}
assert(run.memory_blocked_steps.items.len == 0);
var test_skip_count: usize = 0;
var test_fail_count: usize = 0;
var test_pass_count: usize = 0;
var test_leak_count: usize = 0;
var test_count: usize = 0;
var success_count: usize = 0;
var skipped_count: usize = 0;
var failure_count: usize = 0;
var pending_count: usize = 0;
var total_compile_errors: usize = 0;
var compile_error_steps: std.ArrayListUnmanaged(*Step) = .{};
defer compile_error_steps.deinit(gpa);
for (step_stack.keys()) |s| {
test_fail_count += s.test_results.fail_count;
test_skip_count += s.test_results.skip_count;
test_leak_count += s.test_results.leak_count;
test_pass_count += s.test_results.passCount();
test_count += s.test_results.test_count;
switch (s.state) {
.precheck_unstarted => unreachable,
.precheck_started => unreachable,
.running => unreachable,
.precheck_done => {
// precheck_done is equivalent to dependency_failure in the case of
// transitive dependencies. For example:
// A -> B -> C (failure)
// B will be marked as dependency_failure, while A may never be queued, and thus
// remain in the initial state of precheck_done.
s.state = .dependency_failure;
pending_count += 1;
},
.dependency_failure => pending_count += 1,
.success => success_count += 1,
.skipped => skipped_count += 1,
.failure => {
failure_count += 1;
const compile_errors_len = s.result_error_bundle.errorMessageCount();
if (compile_errors_len > 0) {
total_compile_errors += compile_errors_len;
try compile_error_steps.append(gpa, s);
}
},
}
}
// A proper command line application defaults to silently succeeding.
// The user may request verbose mode if they have a different preference.
if (failure_count == 0 and run.enable_summary != true) return cleanExit();
const ttyconf = run.ttyconf;
const stderr = run.stderr;
if (run.enable_summary != false) {
const total_count = success_count + failure_count + pending_count + skipped_count;
ttyconf.setColor(stderr, .Cyan) catch {};
stderr.writeAll("Build Summary:") catch {};
ttyconf.setColor(stderr, .Reset) catch {};
stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {};
if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
if (run.enable_summary == null) {
ttyconf.setColor(stderr, .Dim) catch {};
stderr.writeAll(" (disable with -fno-summary)") catch {};
ttyconf.setColor(stderr, .Reset) catch {};
}
stderr.writeAll("\n") catch {};
// Print a fancy tree with build results.
var print_node: PrintNode = .{ .parent = null };
if (step_names.len == 0) {
print_node.last = true;
printTreeStep(b, b.default_step, stderr, ttyconf, &print_node, &step_stack) catch {};
} else {
for (step_names, 0..) |step_name, i| {
const tls = b.top_level_steps.get(step_name).?;
print_node.last = i + 1 == b.top_level_steps.count();
printTreeStep(b, &tls.step, stderr, ttyconf, &print_node, &step_stack) catch {};
}
}
}
if (failure_count == 0) return cleanExit();
// Finally, render compile errors at the bottom of the terminal.
// We use a separate compile_error_steps array list because step_stack is destructively
// mutated in printTreeStep above.
if (total_compile_errors > 0) {
for (compile_error_steps.items) |s| {
if (s.result_error_bundle.errorMessageCount() > 0) {
s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));
}
}
// Signal to parent process that we have printed compile errors. The
// parent process may choose to omit the "following command failed"
// line in this case.
process.exit(2);
}
process.exit(1);
}
const PrintNode = struct {
parent: ?*PrintNode,
last: bool = false,
};
fn printPrefix(node: *PrintNode, stderr: std.fs.File, ttyconf: std.debug.TTY.Config) !void {
const parent = node.parent orelse return;
if (parent.parent == null) return;
try printPrefix(parent, stderr, ttyconf);
if (parent.last) {
try stderr.writeAll(" ");
} else {
try stderr.writeAll(switch (ttyconf) {
.no_color, .windows_api => "| ",
.escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", //
});
}
}
fn printTreeStep(
b: *std.Build,
s: *Step,
stderr: std.fs.File,
ttyconf: std.debug.TTY.Config,
parent_node: *PrintNode,
step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
) !void {
const first = step_stack.swapRemove(s);
try printPrefix(parent_node, stderr, ttyconf);
if (!first) try ttyconf.setColor(stderr, .Dim);
if (parent_node.parent != null) {
if (parent_node.last) {
try stderr.writeAll(switch (ttyconf) {
.no_color, .windows_api => "+- ",
.escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", //
});
} else {
try stderr.writeAll(switch (ttyconf) {
.no_color, .windows_api => "+- ",
.escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", //
});
}
}
// dep_prefix omitted here because it is redundant with the tree.
try stderr.writeAll(s.name);
if (first) {
switch (s.state) {
.precheck_unstarted => unreachable,
.precheck_started => unreachable,
.precheck_done => unreachable,
.running => unreachable,
.dependency_failure => {
try ttyconf.setColor(stderr, .Dim);
try stderr.writeAll(" transitive failure\n");
try ttyconf.setColor(stderr, .Reset);
},
.success => {
try ttyconf.setColor(stderr, .Green);
if (s.result_cached) {
try stderr.writeAll(" cached");
} else if (s.test_results.test_count > 0) {
const pass_count = s.test_results.passCount();
try stderr.writer().print(" {d} passed", .{pass_count});
if (s.test_results.skip_count > 0) {
try ttyconf.setColor(stderr, .Yellow);
try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count});
}
} else {
try stderr.writeAll(" success");
}
try ttyconf.setColor(stderr, .Reset);
if (s.result_duration_ns) |ns| {
try ttyconf.setColor(stderr, .Dim);
if (ns >= std.time.ns_per_min) {
try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min});
} else if (ns >= std.time.ns_per_s) {
try stderr.writer().print(" {d}s", .{ns / std.time.ns_per_s});
} else if (ns >= std.time.ns_per_ms) {
try stderr.writer().print(" {d}ms", .{ns / std.time.ns_per_ms});
} else if (ns >= std.time.ns_per_us) {
try stderr.writer().print(" {d}us", .{ns / std.time.ns_per_us});
} else {
try stderr.writer().print(" {d}ns", .{ns});
}
try ttyconf.setColor(stderr, .Reset);
}
if (s.result_peak_rss != 0) {
const rss = s.result_peak_rss;
try ttyconf.setColor(stderr, .Dim);
if (rss >= 1000_000_000) {
try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000});
} else if (rss >= 1000_000) {
try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000});
} else if (rss >= 1000) {
try stderr.writer().print(" MaxRSS:{d}K", .{rss / 1000});
} else {
try stderr.writer().print(" MaxRSS:{d}B", .{rss});
}
try ttyconf.setColor(stderr, .Reset);
}
try stderr.writeAll("\n");
},
.skipped => {
try ttyconf.setColor(stderr, .Yellow);
try stderr.writeAll(" skipped\n");
try ttyconf.setColor(stderr, .Reset);
},
.failure => {
if (s.result_error_bundle.errorMessageCount() > 0) {
try ttyconf.setColor(stderr, .Red);
try stderr.writer().print(" {d} errors\n", .{
s.result_error_bundle.errorMessageCount(),
});
try ttyconf.setColor(stderr, .Reset);
} else if (!s.test_results.isSuccess()) {
try stderr.writer().print(" {d}/{d} passed", .{
s.test_results.passCount(), s.test_results.test_count,
});
if (s.test_results.fail_count > 0) {
try stderr.writeAll(", ");
try ttyconf.setColor(stderr, .Red);
try stderr.writer().print("{d} failed", .{
s.test_results.fail_count,
});
try ttyconf.setColor(stderr, .Reset);
}
if (s.test_results.skip_count > 0) {
try stderr.writeAll(", ");
try ttyconf.setColor(stderr, .Yellow);
try stderr.writer().print("{d} skipped", .{
s.test_results.skip_count,
});
try ttyconf.setColor(stderr, .Reset);
}
if (s.test_results.leak_count > 0) {
try stderr.writeAll(", ");
try ttyconf.setColor(stderr, .Red);
try stderr.writer().print("{d} leaked", .{
s.test_results.leak_count,
});
try ttyconf.setColor(stderr, .Reset);
}
try stderr.writeAll("\n");
} else {
try ttyconf.setColor(stderr, .Red);
try stderr.writeAll(" failure\n");
try ttyconf.setColor(stderr, .Reset);
}
},
}
for (s.dependencies.items, 0..) |dep, i| {
var print_node: PrintNode = .{
.parent = parent_node,
.last = i == s.dependencies.items.len - 1,
};
try printTreeStep(b, dep, stderr, ttyconf, &print_node, step_stack);
}
} else {
if (s.dependencies.items.len == 0) {
try stderr.writeAll(" (reused)\n");
} else {
try stderr.writer().print(" (+{d} more reused dependencies)\n", .{
s.dependencies.items.len,
});
}
try ttyconf.setColor(stderr, .Reset);
}
}
fn checkForDependencyLoop(
b: *std.Build,
s: *Step,
step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
) !void {
switch (s.state) {
.precheck_started => {
std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
return error.DependencyLoopDetected;
},
.precheck_unstarted => {
s.state = .precheck_started;
try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len);
for (s.dependencies.items) |dep| {
try step_stack.put(b.allocator, dep, {});
try dep.dependants.append(b.allocator, s);
checkForDependencyLoop(b, dep, step_stack) catch |err| {
if (err == error.DependencyLoopDetected) {
std.debug.print(" {s}\n", .{s.name});
}
return err;
};
}
s.state = .precheck_done;
},
.precheck_done => {},
// These don't happen until we actually run the step graph.
.dependency_failure => unreachable,
.running => unreachable,
.success => unreachable,
.failure => unreachable,
.skipped => unreachable,
}
}
fn workerMakeOneStep(
wg: *std.Thread.WaitGroup,
thread_pool: *std.Thread.Pool,
b: *std.Build,
s: *Step,
prog_node: *std.Progress.Node,
run: *Run,
) void {
defer wg.finish();
// First, check the conditions for running this step. If they are not met,
// then we return without doing the step, relying on another worker to
// queue this step up again when dependencies are met.
for (s.dependencies.items) |dep| {
switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) {
.success, .skipped => continue,
.failure, .dependency_failure => {
@atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst);
return;
},
.precheck_done, .running => {
// dependency is not finished yet.
return;
},
.precheck_unstarted => unreachable,
.precheck_started => unreachable,
}
}
if (s.max_rss != 0) {
run.max_rss_mutex.lock();
defer run.max_rss_mutex.unlock();
// Avoid running steps twice.
if (s.state != .precheck_done) {
// Another worker got the job.
return;
}
const new_claimed_rss = run.claimed_rss + s.max_rss;
if (new_claimed_rss > run.max_rss) {
// Running this step right now could possibly exceed the allotted RSS.
// Add this step to the queue of memory-blocked steps.
run.memory_blocked_steps.append(s) catch @panic("OOM");
return;
}
run.claimed_rss = new_claimed_rss;
s.state = .running;
} else {
// Avoid running steps twice.
if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .SeqCst, .SeqCst) != null) {
// Another worker got the job.
return;
}
}
var sub_prog_node = prog_node.start(s.name, 0);
sub_prog_node.activate();
defer sub_prog_node.end();
const make_result = s.make(&sub_prog_node);
// No matter the result, we want to display error/warning messages.
if (s.result_error_msgs.items.len > 0) {
sub_prog_node.context.lock_stderr();
defer sub_prog_node.context.unlock_stderr();
const stderr = run.stderr;
const ttyconf = run.ttyconf;
for (s.result_error_msgs.items) |msg| {
// Sometimes it feels like you just can't catch a break. Finally,
// with Zig, you can.
ttyconf.setColor(stderr, .Bold) catch break;
stderr.writeAll(s.owner.dep_prefix) catch break;
stderr.writeAll(s.name) catch break;
stderr.writeAll(": ") catch break;
ttyconf.setColor(stderr, .Red) catch break;
stderr.writeAll("error: ") catch break;
ttyconf.setColor(stderr, .Reset) catch break;
stderr.writeAll(msg) catch break;
stderr.writeAll("\n") catch break;
}
}
handle_result: {
if (make_result) |_| {
@atomicStore(Step.State, &s.state, .success, .SeqCst);
} else |err| switch (err) {
error.MakeFailed => {
@atomicStore(Step.State, &s.state, .failure, .SeqCst);
break :handle_result;
},
error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .SeqCst),
}
// Successful completion of a step, so we queue up its dependants as well.
for (s.dependants.items) |dep| {
wg.start();
thread_pool.spawn(workerMakeOneStep, .{
wg, thread_pool, b, dep, prog_node, run,
}) catch @panic("OOM");
}
}
// If this is a step that claims resources, we must now queue up other
// steps that are waiting for resources.
if (s.max_rss != 0) {
run.max_rss_mutex.lock();
defer run.max_rss_mutex.unlock();
// Give the memory back to the scheduler.
run.claimed_rss -= s.max_rss;
// Avoid kicking off too many tasks that we already know will not have
// enough resources.
var remaining = run.max_rss - run.claimed_rss;
var i: usize = 0;
var j: usize = 0;
while (j < run.memory_blocked_steps.items.len) : (j += 1) {
const dep = run.memory_blocked_steps.items[j];
assert(dep.max_rss != 0);
if (dep.max_rss <= remaining) {
remaining -= dep.max_rss;
wg.start();
thread_pool.spawn(workerMakeOneStep, .{
wg, thread_pool, b, dep, prog_node, run,
}) catch @panic("OOM");
} else {
run.memory_blocked_steps.items[i] = dep;
i += 1;
}
}
run.memory_blocked_steps.shrinkRetainingCapacity(i);
}
}
fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void {
@ -269,7 +888,7 @@ fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
}
const allocator = builder.allocator;
for (builder.top_level_steps.items) |top_level_step| {
for (builder.top_level_steps.values()) |top_level_step| {
const name = if (&top_level_step.step == builder.default_step)
try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
else
@ -327,6 +946,10 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
\\ --verbose Print commands before executing them
\\ --color [auto|off|on] Enable or disable colored error messages
\\ --prominent-compile-errors Output compile errors formatted for a human to read
\\ -fsummary Print the build summary, even on success
\\ -fno-summary Omit the build summary, even on failure
\\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
\\ --maxrss <bytes> Limit memory usage (default is to use available memory)
\\
\\Project-Specific Options:
\\
@ -364,6 +987,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
\\ --zig-lib-dir [arg] Override path to Zig lib directory
\\ --build-runner [file] Override path to build runner
\\ --debug-log [scope] Enable debugging the compiler
\\ --debug-pkg-config Fail if unknown pkg-config flags encountered
\\ --verbose-link Enable compiler debug output for linking
\\ --verbose-air Enable compiler debug output for Zig AIR
\\ --verbose-llvm-ir Enable compiler debug output for LLVM IR
@ -374,7 +998,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
);
}
fn usageAndErr(builder: *std.Build, already_ran_build: bool, out_stream: anytype) void {
fn usageAndErr(builder: *std.Build, already_ran_build: bool, out_stream: anytype) noreturn {
usage(builder, already_ran_build, out_stream) catch {};
process.exit(1);
}
@ -389,3 +1013,28 @@ fn argsRest(args: [][]const u8, idx: usize) ?[][]const u8 {
if (idx >= args.len) return null;
return args[idx..];
}
fn cleanExit() void {
// Perhaps in the future there could be an Advanced Options flag such as
// --debug-build-runner-leaks which would make this function return instead
// of calling exit.
process.exit(0);
}
const Color = enum { auto, off, on };
fn get_tty_conf(color: Color, stderr: std.fs.File) std.debug.TTY.Config {
return switch (color) {
.auto => std.debug.detectTTYConfig(stderr),
.on => .escape_codes,
.off => .no_color,
};
}
fn renderOptions(ttyconf: std.debug.TTY.Config) std.zig.ErrorBundle.RenderOptions {
return .{
.ttyconf = ttyconf,
.include_source_line = ttyconf != .no_color,
.include_reference_trace = ttyconf != .no_color,
};
}

View File

@ -79,16 +79,16 @@ fn divmod(q: ?[]u32, r: ?[]u32, u: []const u32, v: []const u32) !void {
}
break;
}
var carry: u64 = 0;
var carry: i64 = 0;
i = 0;
while (i <= n) : (i += 1) {
const p = qhat * limb(&vn, i);
const t = limb(&un, i + j) - carry - @truncate(u32, p);
limb_set(&un, i + j, @truncate(u32, t));
carry = @intCast(u64, p >> 32) - @intCast(u64, t >> 32);
limb_set(&un, i + j, @truncate(u32, @bitCast(u64, t)));
carry = @intCast(i64, p >> 32) - @intCast(i64, t >> 32);
}
const t = limb(&un, j + n + 1) - carry;
limb_set(&un, j + n + 1, @truncate(u32, t));
const t = limb(&un, j + n + 1) -% carry;
limb_set(&un, j + n + 1, @truncate(u32, @bitCast(u64, t)));
if (q) |q_| limb_set(q_, j, @truncate(u32, qhat));
if (t < 0) {
if (q) |q_| limb_set(q_, j, limb(q_, j) - 1);
@ -99,7 +99,7 @@ fn divmod(q: ?[]u32, r: ?[]u32, u: []const u32, v: []const u32) !void {
limb_set(&un, i + j, @truncate(u32, t2));
carry2 = t2 >> 32;
}
limb_set(un, j + n + 1, @truncate(u32, limb(&un, j + n + 1) + carry2));
limb_set(&un, j + n + 1, @truncate(u32, limb(&un, j + n + 1) + carry2));
}
if (j == 0) break;
}

View File

@ -1187,10 +1187,6 @@ const NAV_MODES = {
payloadHtml += "panic";
break;
}
case "set_cold": {
payloadHtml += "setCold";
break;
}
case "set_runtime_safety": {
payloadHtml += "setRuntimeSafety";
break;

View File

@ -32,14 +32,12 @@ pub const Step = @import("Build/Step.zig");
pub const CheckFileStep = @import("Build/CheckFileStep.zig");
pub const CheckObjectStep = @import("Build/CheckObjectStep.zig");
pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig");
pub const EmulatableRunStep = @import("Build/EmulatableRunStep.zig");
pub const FmtStep = @import("Build/FmtStep.zig");
pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig");
pub const InstallDirStep = @import("Build/InstallDirStep.zig");
pub const InstallFileStep = @import("Build/InstallFileStep.zig");
pub const ObjCopyStep = @import("Build/ObjCopyStep.zig");
pub const CompileStep = @import("Build/CompileStep.zig");
pub const LogStep = @import("Build/LogStep.zig");
pub const OptionsStep = @import("Build/OptionsStep.zig");
pub const RemoveDirStep = @import("Build/RemoveDirStep.zig");
pub const RunStep = @import("Build/RunStep.zig");
@ -59,15 +57,12 @@ verbose_air: bool,
verbose_llvm_ir: bool,
verbose_cimport: bool,
verbose_llvm_cpu_features: bool,
/// The purpose of executing the command is for a human to read compile errors from the terminal
prominent_compile_errors: bool,
color: enum { auto, on, off } = .auto,
reference_trace: ?u32 = null,
invalid_user_input: bool,
zig_exe: []const u8,
default_step: *Step,
env_map: *EnvMap,
top_level_steps: ArrayList(*TopLevelStep),
top_level_steps: std.StringArrayHashMapUnmanaged(*TopLevelStep),
install_prefix: []const u8,
dest_dir: ?[]const u8,
lib_dir: []const u8,
@ -90,6 +85,7 @@ pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
args: ?[][]const u8 = null,
debug_log_scopes: []const []const u8 = &.{},
debug_compile_errors: bool = false,
debug_pkg_config: bool = false,
/// Experimental. Use system Darling installation to run cross compiled macOS build artifacts.
enable_darling: bool = false,
@ -198,7 +194,7 @@ pub fn create(
env_map.* = try process.getEnvMap(allocator);
const self = try allocator.create(Build);
self.* = Build{
self.* = .{
.zig_exe = zig_exe,
.build_root = build_root,
.cache_root = cache_root,
@ -211,13 +207,12 @@ pub fn create(
.verbose_llvm_ir = false,
.verbose_cimport = false,
.verbose_llvm_cpu_features = false,
.prominent_compile_errors = false,
.invalid_user_input = false,
.allocator = allocator,
.user_input_options = UserInputOptionsMap.init(allocator),
.available_options_map = AvailableOptionsMap.init(allocator),
.available_options_list = ArrayList(AvailableOption).init(allocator),
.top_level_steps = ArrayList(*TopLevelStep).init(allocator),
.top_level_steps = .{},
.default_step = undefined,
.env_map = env_map,
.search_prefixes = ArrayList([]const u8).init(allocator),
@ -227,12 +222,21 @@ pub fn create(
.h_dir = undefined,
.dest_dir = env_map.get("DESTDIR"),
.installed_files = ArrayList(InstalledFile).init(allocator),
.install_tls = TopLevelStep{
.step = Step.initNoOp(.top_level, "install", allocator),
.install_tls = .{
.step = Step.init(.{
.id = .top_level,
.name = "install",
.owner = self,
}),
.description = "Copy build artifacts to prefix path",
},
.uninstall_tls = TopLevelStep{
.step = Step.init(.top_level, "uninstall", allocator, makeUninstall),
.uninstall_tls = .{
.step = Step.init(.{
.id = .top_level,
.name = "uninstall",
.owner = self,
.makeFn = makeUninstall,
}),
.description = "Remove build artifacts from prefix path",
},
.zig_lib_dir = null,
@ -241,8 +245,8 @@ pub fn create(
.host = host,
.modules = std.StringArrayHashMap(*Module).init(allocator),
};
try self.top_level_steps.append(&self.install_tls);
try self.top_level_steps.append(&self.uninstall_tls);
try self.top_level_steps.put(allocator, self.install_tls.step.name, &self.install_tls);
try self.top_level_steps.put(allocator, self.uninstall_tls.step.name, &self.uninstall_tls);
self.default_step = &self.install_tls.step;
return self;
}
@ -264,11 +268,20 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
child.* = .{
.allocator = allocator,
.install_tls = .{
.step = Step.initNoOp(.top_level, "install", allocator),
.step = Step.init(.{
.id = .top_level,
.name = "install",
.owner = child,
}),
.description = "Copy build artifacts to prefix path",
},
.uninstall_tls = .{
.step = Step.init(.top_level, "uninstall", allocator, makeUninstall),
.step = Step.init(.{
.id = .top_level,
.name = "uninstall",
.owner = child,
.makeFn = makeUninstall,
}),
.description = "Remove build artifacts from prefix path",
},
.user_input_options = UserInputOptionsMap.init(allocator),
@ -281,14 +294,12 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
.verbose_llvm_ir = parent.verbose_llvm_ir,
.verbose_cimport = parent.verbose_cimport,
.verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features,
.prominent_compile_errors = parent.prominent_compile_errors,
.color = parent.color,
.reference_trace = parent.reference_trace,
.invalid_user_input = false,
.zig_exe = parent.zig_exe,
.default_step = undefined,
.env_map = parent.env_map,
.top_level_steps = ArrayList(*TopLevelStep).init(allocator),
.top_level_steps = .{},
.install_prefix = undefined,
.dest_dir = parent.dest_dir,
.lib_dir = parent.lib_dir,
@ -306,6 +317,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
.zig_lib_dir = parent.zig_lib_dir,
.debug_log_scopes = parent.debug_log_scopes,
.debug_compile_errors = parent.debug_compile_errors,
.debug_pkg_config = parent.debug_pkg_config,
.enable_darling = parent.enable_darling,
.enable_qemu = parent.enable_qemu,
.enable_rosetta = parent.enable_rosetta,
@ -316,8 +328,8 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
.dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }),
.modules = std.StringArrayHashMap(*Module).init(allocator),
};
try child.top_level_steps.append(&child.install_tls);
try child.top_level_steps.append(&child.uninstall_tls);
try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls);
try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls);
child.default_step = &child.install_tls.step;
return child;
}
@ -372,27 +384,24 @@ fn applyArgs(b: *Build, args: anytype) !void {
},
}
}
const Hasher = std.crypto.auth.siphash.SipHash128(1, 3);
// Create an installation directory local to this package. This will be used when
// dependant packages require a standard prefix, such as include directories for C headers.
var hash = b.cache.hash;
// Random bytes to make unique. Refresh this with new random bytes when
// implementation is modified in a non-backwards-compatible way.
var hash = Hasher.init("ZaEsvQ5ClaA2IdH9");
hash.update(b.dep_prefix);
hash.add(@as(u32, 0xd8cb0055));
hash.addBytes(b.dep_prefix);
// TODO additionally update the hash with `args`.
var digest: [16]u8 = undefined;
hash.final(&digest);
var hash_basename: [digest.len * 2]u8 = undefined;
_ = std.fmt.bufPrint(&hash_basename, "{s}", .{std.fmt.fmtSliceHexLower(&digest)}) catch
unreachable;
const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &hash_basename });
const digest = hash.final();
const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest });
b.resolveInstallPrefix(install_prefix, .{});
}
pub fn destroy(self: *Build) void {
self.env_map.deinit();
self.top_level_steps.deinit();
self.allocator.destroy(self);
pub fn destroy(b: *Build) void {
b.env_map.deinit();
b.top_level_steps.deinit(b.allocator);
b.allocator.destroy(b);
}
/// This function is intended to be called by lib/build_runner.zig, not a build.zig file.
@ -441,6 +450,7 @@ pub const ExecutableOptions = struct {
target: CrossTarget = .{},
optimize: std.builtin.Mode = .Debug,
linkage: ?CompileStep.Linkage = null,
max_rss: usize = 0,
};
pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {
@ -452,6 +462,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {
.optimize = options.optimize,
.kind = .exe,
.linkage = options.linkage,
.max_rss = options.max_rss,
});
}
@ -460,6 +471,7 @@ pub const ObjectOptions = struct {
root_source_file: ?FileSource = null,
target: CrossTarget,
optimize: std.builtin.Mode,
max_rss: usize = 0,
};
pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {
@ -469,6 +481,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {
.target = options.target,
.optimize = options.optimize,
.kind = .obj,
.max_rss = options.max_rss,
});
}
@ -478,6 +491,7 @@ pub const SharedLibraryOptions = struct {
version: ?std.builtin.Version = null,
target: CrossTarget,
optimize: std.builtin.Mode,
max_rss: usize = 0,
};
pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {
@ -489,6 +503,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {
.version = options.version,
.target = options.target,
.optimize = options.optimize,
.max_rss = options.max_rss,
});
}
@ -498,6 +513,7 @@ pub const StaticLibraryOptions = struct {
target: CrossTarget,
optimize: std.builtin.Mode,
version: ?std.builtin.Version = null,
max_rss: usize = 0,
};
pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
@ -509,25 +525,27 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
.version = options.version,
.target = options.target,
.optimize = options.optimize,
.max_rss = options.max_rss,
});
}
pub const TestOptions = struct {
name: []const u8 = "test",
kind: CompileStep.Kind = .@"test",
root_source_file: FileSource,
target: CrossTarget = .{},
optimize: std.builtin.Mode = .Debug,
version: ?std.builtin.Version = null,
max_rss: usize = 0,
};
pub fn addTest(b: *Build, options: TestOptions) *CompileStep {
return CompileStep.create(b, .{
.name = options.name,
.kind = options.kind,
.kind = .@"test",
.root_source_file = options.root_source_file,
.target = options.target,
.optimize = options.optimize,
.max_rss = options.max_rss,
});
}
@ -536,6 +554,7 @@ pub const AssemblyOptions = struct {
source_file: FileSource,
target: CrossTarget,
optimize: std.builtin.Mode,
max_rss: usize = 0,
};
pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {
@ -545,22 +564,19 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {
.root_source_file = null,
.target = options.target,
.optimize = options.optimize,
.max_rss = options.max_rss,
});
obj_step.addAssemblyFileSource(options.source_file.dupe(b));
return obj_step;
}
pub const AddModuleOptions = struct {
name: []const u8,
source_file: FileSource,
dependencies: []const ModuleDependency = &.{},
};
pub fn addModule(b: *Build, options: AddModuleOptions) void {
b.modules.put(b.dupe(options.name), b.createModule(.{
.source_file = options.source_file,
.dependencies = options.dependencies,
})) catch @panic("OOM");
/// This function creates a module and adds it to the package's module set, making
/// it available to other packages which depend on this one.
/// `createModule` can be used instead to create a private module.
pub fn addModule(b: *Build, name: []const u8, options: CreateModuleOptions) *Module {
const module = b.createModule(options);
b.modules.put(b.dupe(name), module) catch @panic("OOM");
return module;
}
pub const ModuleDependency = struct {
@ -573,8 +589,9 @@ pub const CreateModuleOptions = struct {
dependencies: []const ModuleDependency = &.{},
};
/// Prefer to use `addModule` which will make the module available to other
/// packages which depend on this package.
/// This function creates a private module, to be used by the current package,
/// but not exposed to other packages depending on this one.
/// `addModule` can be used instead to create a public module.
pub fn createModule(b: *Build, options: CreateModuleOptions) *Module {
const module = b.allocator.create(Module) catch @panic("OOM");
module.* = .{
@ -608,16 +625,15 @@ pub fn addSystemCommand(self: *Build, argv: []const []const u8) *RunStep {
/// Creates a `RunStep` with an executable built with `addExecutable`.
/// Add command line arguments with methods of `RunStep`.
pub fn addRunArtifact(b: *Build, exe: *CompileStep) *RunStep {
assert(exe.kind == .exe or exe.kind == .test_exe);
// It doesn't have to be native. We catch that if you actually try to run it.
// Consider that this is declarative; the run step may not be run unless a user
// option is supplied.
const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.step.name}));
const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.name}));
run_step.addArtifactArg(exe);
if (exe.kind == .test_exe) {
run_step.addArg(b.zig_exe);
if (exe.kind == .@"test") {
run_step.stdio = .zig_test;
run_step.addArgs(&.{"--listen=-"});
}
if (exe.vcpkg_bin_path) |path| {
@ -637,7 +653,11 @@ pub fn addConfigHeader(
options: ConfigHeaderStep.Options,
values: anytype,
) *ConfigHeaderStep {
const config_header_step = ConfigHeaderStep.create(b, options);
var options_copy = options;
if (options_copy.first_ret_addr == null)
options_copy.first_ret_addr = @returnAddress();
const config_header_step = ConfigHeaderStep.create(b, options_copy);
config_header_step.addValues(values);
return config_header_step;
}
@ -674,17 +694,8 @@ pub fn addWriteFile(self: *Build, file_path: []const u8, data: []const u8) *Writ
return write_file_step;
}
pub fn addWriteFiles(self: *Build) *WriteFileStep {
const write_file_step = self.allocator.create(WriteFileStep) catch @panic("OOM");
write_file_step.* = WriteFileStep.init(self);
return write_file_step;
}
pub fn addLog(self: *Build, comptime format: []const u8, args: anytype) *LogStep {
const data = self.fmt(format, args);
const log_step = self.allocator.create(LogStep) catch @panic("OOM");
log_step.* = LogStep.init(self, data);
return log_step;
pub fn addWriteFiles(b: *Build) *WriteFileStep {
return WriteFileStep.create(b);
}
pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {
@ -693,32 +704,14 @@ pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {
return remove_dir_step;
}
pub fn addFmt(self: *Build, paths: []const []const u8) *FmtStep {
return FmtStep.create(self, paths);
pub fn addFmt(b: *Build, options: FmtStep.Options) *FmtStep {
return FmtStep.create(b, options);
}
pub fn addTranslateC(self: *Build, options: TranslateCStep.Options) *TranslateCStep {
return TranslateCStep.create(self, options);
}
pub fn make(self: *Build, step_names: []const []const u8) !void {
var wanted_steps = ArrayList(*Step).init(self.allocator);
defer wanted_steps.deinit();
if (step_names.len == 0) {
try wanted_steps.append(self.default_step);
} else {
for (step_names) |step_name| {
const s = try self.getTopLevelStepByName(step_name);
try wanted_steps.append(s);
}
}
for (wanted_steps.items) |s| {
try self.makeOneStep(s);
}
}
pub fn getInstallStep(self: *Build) *Step {
return &self.install_tls.step;
}
@ -727,7 +720,8 @@ pub fn getUninstallStep(self: *Build) *Step {
return &self.uninstall_tls.step;
}
fn makeUninstall(uninstall_step: *Step) anyerror!void {
fn makeUninstall(uninstall_step: *Step, prog_node: *std.Progress.Node) anyerror!void {
_ = prog_node;
const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step);
const self = @fieldParentPtr(Build, "uninstall_tls", uninstall_tls);
@ -742,37 +736,6 @@ fn makeUninstall(uninstall_step: *Step) anyerror!void {
// TODO remove empty directories
}
fn makeOneStep(self: *Build, s: *Step) anyerror!void {
if (s.loop_flag) {
log.err("Dependency loop detected:\n {s}", .{s.name});
return error.DependencyLoopDetected;
}
s.loop_flag = true;
for (s.dependencies.items) |dep| {
self.makeOneStep(dep) catch |err| {
if (err == error.DependencyLoopDetected) {
log.err(" {s}", .{s.name});
}
return err;
};
}
s.loop_flag = false;
try s.make();
}
fn getTopLevelStepByName(self: *Build, name: []const u8) !*Step {
for (self.top_level_steps.items) |top_level_step| {
if (mem.eql(u8, top_level_step.step.name, name)) {
return &top_level_step.step;
}
}
log.err("Cannot run step '{s}' because it does not exist", .{name});
return error.InvalidStepName;
}
pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T {
const name = self.dupe(name_raw);
const description = self.dupe(description_raw);
@ -909,11 +872,15 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_
pub fn step(self: *Build, name: []const u8, description: []const u8) *Step {
const step_info = self.allocator.create(TopLevelStep) catch @panic("OOM");
step_info.* = TopLevelStep{
.step = Step.initNoOp(.top_level, name, self.allocator),
step_info.* = .{
.step = Step.init(.{
.id = .top_level,
.name = name,
.owner = self,
}),
.description = self.dupe(description),
};
self.top_level_steps.append(step_info) catch @panic("OOM");
self.top_level_steps.put(self.allocator, step_info.step.name, step_info) catch @panic("OOM");
return &step_info.step;
}
@ -1181,50 +1148,18 @@ pub fn validateUserInputDidItFail(self: *Build) bool {
return self.invalid_user_input;
}
pub fn spawnChild(self: *Build, argv: []const []const u8) !void {
return self.spawnChildEnvMap(null, self.env_map, argv);
}
fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void {
if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd});
fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 {
var buf = ArrayList(u8).init(ally);
if (opt_cwd) |cwd| try buf.writer().print("cd {s} && ", .{cwd});
for (argv) |arg| {
std.debug.print("{s} ", .{arg});
try buf.writer().print("{s} ", .{arg});
}
std.debug.print("\n", .{});
return buf.toOwnedSlice();
}
pub fn spawnChildEnvMap(self: *Build, cwd: ?[]const u8, env_map: *const EnvMap, argv: []const []const u8) !void {
if (self.verbose) {
printCmd(cwd, argv);
}
if (!std.process.can_spawn)
return error.ExecNotSupported;
var child = std.ChildProcess.init(argv, self.allocator);
child.cwd = cwd;
child.env_map = env_map;
const term = child.spawnAndWait() catch |err| {
log.err("Unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });
return err;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
log.err("The following command exited with error code {}:", .{code});
printCmd(cwd, argv);
return error.UncleanExit;
}
},
else => {
log.err("The following command terminated unexpectedly:", .{});
printCmd(cwd, argv);
return error.UncleanExit;
},
}
fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void {
const text = allocPrintCmd(ally, cwd, argv) catch @panic("OOM");
std.debug.print("{s}\n", .{text});
}
pub fn installArtifact(self: *Build, artifact: *CompileStep) void {
@ -1283,12 +1218,7 @@ pub fn addInstallFileWithDir(
install_dir: InstallDir,
dest_rel_path: []const u8,
) *InstallFileStep {
if (dest_rel_path.len == 0) {
panic("dest_rel_path must be non-empty", .{});
}
const install_step = self.allocator.create(InstallFileStep) catch @panic("OOM");
install_step.* = InstallFileStep.init(self, source.dupe(self), install_dir, dest_rel_path);
return install_step;
return InstallFileStep.create(self, source.dupe(self), install_dir, dest_rel_path);
}
pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *InstallDirStep {
@ -1297,6 +1227,14 @@ pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Inst
return install_step;
}
pub fn addCheckFile(
b: *Build,
file_source: FileSource,
options: CheckFileStep.Options,
) *CheckFileStep {
return CheckFileStep.create(b, file_source, options);
}
pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void {
const file = InstalledFile{
.dir = dir,
@ -1305,18 +1243,6 @@ pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u
self.installed_files.append(file.dupe(self)) catch @panic("OOM");
}
pub fn updateFile(self: *Build, source_path: []const u8, dest_path: []const u8) !void {
if (self.verbose) {
log.info("cp {s} {s} ", .{ source_path, dest_path });
}
const cwd = fs.cwd();
const prev_status = try fs.Dir.updateFile(cwd, source_path, cwd, dest_path, .{});
if (self.verbose) switch (prev_status) {
.stale => log.info("# installed", .{}),
.fresh => log.info("# up-to-date", .{}),
};
}
pub fn truncateFile(self: *Build, dest_path: []const u8) !void {
if (self.verbose) {
log.info("truncate {s}", .{dest_path});
@ -1400,7 +1326,7 @@ pub fn execAllowFail(
) ExecError![]u8 {
assert(argv.len != 0);
if (!std.process.can_spawn)
if (!process.can_spawn)
return error.ExecNotSupported;
const max_output_size = 400 * 1024;
@ -1433,59 +1359,27 @@ pub fn execAllowFail(
}
}
pub fn execFromStep(self: *Build, argv: []const []const u8, src_step: ?*Step) ![]u8 {
assert(argv.len != 0);
if (self.verbose) {
printCmd(null, argv);
}
if (!std.process.can_spawn) {
if (src_step) |s| log.err("{s}...", .{s.name});
log.err("Unable to spawn the following command: cannot spawn child process", .{});
printCmd(null, argv);
std.os.abort();
/// This is a helper function to be called from build.zig scripts, *not* from
/// inside step make() functions. If any errors occur, it fails the build with
/// a helpful message.
pub fn exec(b: *Build, argv: []const []const u8) []u8 {
if (!process.can_spawn) {
std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{
try allocPrintCmd(b.allocator, null, argv),
});
process.exit(1);
}
var code: u8 = undefined;
return self.execAllowFail(argv, &code, .Inherit) catch |err| switch (err) {
error.ExecNotSupported => {
if (src_step) |s| log.err("{s}...", .{s.name});
log.err("Unable to spawn the following command: cannot spawn child process", .{});
printCmd(null, argv);
std.os.abort();
},
error.FileNotFound => {
if (src_step) |s| log.err("{s}...", .{s.name});
log.err("Unable to spawn the following command: file not found", .{});
printCmd(null, argv);
std.os.exit(@truncate(u8, code));
},
error.ExitCodeFailure => {
if (src_step) |s| log.err("{s}...", .{s.name});
if (self.prominent_compile_errors) {
log.err("The step exited with error code {d}", .{code});
} else {
log.err("The following command exited with error code {d}:", .{code});
printCmd(null, argv);
}
std.os.exit(@truncate(u8, code));
},
error.ProcessTerminated => {
if (src_step) |s| log.err("{s}...", .{s.name});
log.err("The following command terminated unexpectedly:", .{});
printCmd(null, argv);
std.os.exit(@truncate(u8, code));
},
else => |e| return e,
return b.execAllowFail(argv, &code, .Inherit) catch |err| {
const printed_cmd = allocPrintCmd(b.allocator, null, argv) catch @panic("OOM");
std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{
@errorName(err), printed_cmd,
});
process.exit(1);
};
}
pub fn exec(self: *Build, argv: []const []const u8) ![]u8 {
return self.execFromStep(argv, null);
}
pub fn addSearchPrefix(self: *Build, search_prefix: []const u8) void {
self.search_prefixes.append(self.dupePath(search_prefix)) catch @panic("OOM");
}
@ -1550,10 +1444,29 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
const full_path = b.pathFromRoot("build.zig.zon");
std.debug.print("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file.\n", .{ name, full_path });
std.process.exit(1);
process.exit(1);
}
fn dependencyInner(
pub fn anonymousDependency(
b: *Build,
/// The path to the directory containing the dependency's build.zig file,
/// relative to the current package's build.zig.
relative_build_root: []const u8,
/// A direct `@import` of the build.zig of the dependency.
comptime build_zig: type,
args: anytype,
) *Dependency {
const arena = b.allocator;
const build_root = b.build_root.join(arena, &.{relative_build_root}) catch @panic("OOM");
const name = arena.dupe(u8, relative_build_root) catch @panic("OOM");
for (name) |*byte| switch (byte.*) {
'/', '\\' => byte.* = '.',
else => continue,
};
return dependencyInner(b, name, build_root, build_zig, args);
}
pub fn dependencyInner(
b: *Build,
name: []const u8,
build_root_string: []const u8,
@ -1566,7 +1479,7 @@ fn dependencyInner(
std.debug.print("unable to open '{s}': {s}\n", .{
build_root_string, @errorName(err),
});
std.process.exit(1);
process.exit(1);
},
};
const sub_builder = b.createChild(name, build_root, args) catch @panic("unhandled error");
@ -1610,7 +1523,7 @@ pub const GeneratedFile = struct {
pub fn getPath(self: GeneratedFile) []const u8 {
return self.path orelse std.debug.panic(
"getPath() was called on a GeneratedFile that wasn't build yet. Is there a missing Step dependency on step '{s}'?",
"getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?",
.{self.step.name},
);
}
@ -1650,12 +1563,23 @@ pub const FileSource = union(enum) {
}
/// Should only be called during make(), returns a path relative to the build root or absolute.
pub fn getPath(self: FileSource, builder: *Build) []const u8 {
const path = switch (self) {
.path => |p| builder.pathFromRoot(p),
.generated => |gen| gen.getPath(),
};
return path;
pub fn getPath(self: FileSource, src_builder: *Build) []const u8 {
return getPath2(self, src_builder, null);
}
/// Should only be called during make(), returns a path relative to the build root or absolute.
/// asking_step is only used for debugging purposes; it's the step being run that is asking for
/// the path.
pub fn getPath2(self: FileSource, src_builder: *Build, asking_step: ?*Step) []const u8 {
switch (self) {
.path => |p| return src_builder.pathFromRoot(p),
.generated => |gen| return gen.path orelse {
std.debug.getStderrMutex().lock();
const stderr = std.io.getStdErr();
dumpBadGetPathHelp(gen.step, stderr, src_builder, asking_step) catch {};
@panic("misconfigured build script");
},
}
}
/// Duplicates the file source for a given builder.
@ -1667,6 +1591,54 @@ pub const FileSource = union(enum) {
}
};
/// In this function the stderr mutex has already been locked.
fn dumpBadGetPathHelp(
s: *Step,
stderr: fs.File,
src_builder: *Build,
asking_step: ?*Step,
) anyerror!void {
const w = stderr.writer();
try w.print(
\\getPath() was called on a GeneratedFile that wasn't built yet.
\\ source package path: {s}
\\ Is there a missing Step dependency on step '{s}'?
\\
, .{
src_builder.build_root.path orelse ".",
s.name,
});
const tty_config = std.debug.detectTTYConfig(stderr);
tty_config.setColor(w, .Red) catch {};
try stderr.writeAll(" The step was created by this stack trace:\n");
tty_config.setColor(w, .Reset) catch {};
const debug_info = std.debug.getSelfDebugInfo() catch |err| {
try w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)});
return;
};
const ally = debug_info.allocator;
std.debug.writeStackTrace(s.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
return;
};
if (asking_step) |as| {
tty_config.setColor(w, .Red) catch {};
try stderr.writeAll(" The step that is missing a dependency on the above step was created by this stack trace:\n");
tty_config.setColor(w, .Reset) catch {};
std.debug.writeStackTrace(as.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
return;
};
}
tty_config.setColor(w, .Red) catch {};
try stderr.writeAll(" Hope that helps. Proceeding to panic.\n");
tty_config.setColor(w, .Reset) catch {};
}
/// Allocates a new string for assigning a value to a named macro.
/// If the value is omitted, it is set to 1.
/// `name` and `value` need not live longer than the function call.
@ -1706,9 +1678,7 @@ pub const InstallDir = union(enum) {
/// Duplicates the install directory including the path if set to custom.
pub fn dupe(self: InstallDir, builder: *Build) InstallDir {
if (self == .custom) {
// Written with this temporary to avoid RLS problems
const duped_path = builder.dupe(self.custom);
return .{ .custom = duped_path };
return .{ .custom = builder.dupe(self.custom) };
} else {
return self;
}
@ -1756,17 +1726,45 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 {
}
}
/// This function is intended to be called in the `configure` phase only.
/// It returns an absolute directory path, which is potentially going to be a
/// source of API breakage in the future, so keep that in mind when using this
/// function.
pub fn makeTempPath(b: *Build) []const u8 {
const rand_int = std.crypto.random.int(u64);
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ hex64(rand_int);
const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM");
fs.cwd().makePath(result_path) catch |err| {
std.debug.print("unable to make tmp path '{s}': {s}\n", .{
result_path, @errorName(err),
});
};
return result_path;
}
/// There are a few copies of this function in miscellaneous places. Would be nice to find
/// a home for them.
fn hex64(x: u64) [16]u8 {
const hex_charset = "0123456789abcdef";
var result: [16]u8 = undefined;
var i: usize = 0;
while (i < 8) : (i += 1) {
const byte = @truncate(u8, x >> @intCast(u6, 8 * i));
result[i * 2 + 0] = hex_charset[byte >> 4];
result[i * 2 + 1] = hex_charset[byte & 15];
}
return result;
}
test {
_ = CheckFileStep;
_ = CheckObjectStep;
_ = EmulatableRunStep;
_ = FmtStep;
_ = InstallArtifactStep;
_ = InstallDirStep;
_ = InstallFileStep;
_ = ObjCopyStep;
_ = CompileStep;
_ = LogStep;
_ = OptionsStep;
_ = RemoveDirStep;
_ = RunStep;

View File

@ -7,27 +7,27 @@ pub const Directory = struct {
/// directly, but it is needed when passing the directory to a child process.
/// `null` means cwd.
path: ?[]const u8,
handle: std.fs.Dir,
handle: fs.Dir,
pub fn join(self: Directory, allocator: Allocator, paths: []const []const u8) ![]u8 {
if (self.path) |p| {
// TODO clean way to do this with only 1 allocation
const part2 = try std.fs.path.join(allocator, paths);
const part2 = try fs.path.join(allocator, paths);
defer allocator.free(part2);
return std.fs.path.join(allocator, &[_][]const u8{ p, part2 });
return fs.path.join(allocator, &[_][]const u8{ p, part2 });
} else {
return std.fs.path.join(allocator, paths);
return fs.path.join(allocator, paths);
}
}
pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) ![:0]u8 {
if (self.path) |p| {
// TODO clean way to do this with only 1 allocation
const part2 = try std.fs.path.join(allocator, paths);
const part2 = try fs.path.join(allocator, paths);
defer allocator.free(part2);
return std.fs.path.joinZ(allocator, &[_][]const u8{ p, part2 });
return fs.path.joinZ(allocator, &[_][]const u8{ p, part2 });
} else {
return std.fs.path.joinZ(allocator, paths);
return fs.path.joinZ(allocator, paths);
}
}
@ -39,6 +39,20 @@ pub const Directory = struct {
if (self.path) |p| gpa.free(p);
self.* = undefined;
}
pub fn format(
self: Directory,
comptime fmt_string: []const u8,
options: fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
if (fmt_string.len != 0) fmt.invalidFmtError(fmt, self);
if (self.path) |p| {
try writer.writeAll(p);
try writer.writeAll(fs.path.sep_str);
}
}
};
gpa: Allocator,
@ -243,10 +257,10 @@ pub const HashHelper = struct {
hh.hasher.final(&bin_digest);
var out_digest: [hex_digest_len]u8 = undefined;
_ = std.fmt.bufPrint(
_ = fmt.bufPrint(
&out_digest,
"{s}",
.{std.fmt.fmtSliceHexLower(&bin_digest)},
.{fmt.fmtSliceHexLower(&bin_digest)},
) catch unreachable;
return out_digest;
}
@ -365,10 +379,10 @@ pub const Manifest = struct {
var bin_digest: BinDigest = undefined;
self.hash.hasher.final(&bin_digest);
_ = std.fmt.bufPrint(
_ = fmt.bufPrint(
&self.hex_digest,
"{s}",
.{std.fmt.fmtSliceHexLower(&bin_digest)},
.{fmt.fmtSliceHexLower(&bin_digest)},
) catch unreachable;
self.hash.hasher = hasher_init;
@ -408,7 +422,11 @@ pub const Manifest = struct {
self.have_exclusive_lock = true;
return false; // cache miss; exclusive lock already held
} else |err| switch (err) {
error.WouldBlock => continue,
// There are no dir components, so you would think
// that this was unreachable, however we have
// observed on macOS two processes racing to do
// openat() with O_CREAT manifest in ENOENT.
error.WouldBlock, error.FileNotFound => continue,
else => |e| return e,
}
},
@ -425,7 +443,10 @@ pub const Manifest = struct {
self.manifest_file = manifest_file;
self.have_exclusive_lock = true;
} else |err| switch (err) {
error.WouldBlock => {
// There are no dir components, so you would think that this was
// unreachable, however we have observed on macOS two processes racing
// to do openat() with O_CREAT manifest in ENOENT.
error.WouldBlock, error.FileNotFound => {
self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{
.lock = .Shared,
});
@ -469,7 +490,7 @@ pub const Manifest = struct {
cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat;
cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat;
cache_hash_file.stat.mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat;
_ = std.fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat;
_ = fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat;
const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat;
if (prefix >= self.cache.prefixes_len) return error.InvalidFormat;
@ -806,10 +827,10 @@ pub const Manifest = struct {
self.hash.hasher.final(&bin_digest);
var out_digest: [hex_digest_len]u8 = undefined;
_ = std.fmt.bufPrint(
_ = fmt.bufPrint(
&out_digest,
"{s}",
.{std.fmt.fmtSliceHexLower(&bin_digest)},
.{fmt.fmtSliceHexLower(&bin_digest)},
) catch unreachable;
return out_digest;
@ -831,10 +852,10 @@ pub const Manifest = struct {
var encoded_digest: [hex_digest_len]u8 = undefined;
for (self.files.items) |file| {
_ = std.fmt.bufPrint(
_ = fmt.bufPrint(
&encoded_digest,
"{s}",
.{std.fmt.fmtSliceHexLower(&file.bin_digest)},
.{fmt.fmtSliceHexLower(&file.bin_digest)},
) catch unreachable;
try writer.print("{d} {d} {d} {s} {d} {s}\n", .{
file.stat.size,
@ -955,16 +976,16 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void {
}
// Create/Write a file, close it, then grab its stat.mtime timestamp.
fn testGetCurrentFileTimestamp() !i128 {
fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 {
const test_out_file = "test-filetimestamp.tmp";
var file = try fs.cwd().createFile(test_out_file, .{
var file = try dir.createFile(test_out_file, .{
.read = true,
.truncate = true,
});
defer {
file.close();
fs.cwd().deleteFile(test_out_file) catch {};
dir.deleteFile(test_out_file) catch {};
}
return (try file.stat()).mtime;
@ -976,16 +997,17 @@ test "cache file and then recall it" {
return error.SkipZigTest;
}
const cwd = fs.cwd();
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
const temp_file = "test.txt";
const temp_manifest_dir = "temp_manifest_dir";
try cwd.writeFile(temp_file, "Hello, world!\n");
try tmp.dir.writeFile(temp_file, "Hello, world!\n");
// Wait for file timestamps to tick
const initial_time = try testGetCurrentFileTimestamp();
while ((try testGetCurrentFileTimestamp()) == initial_time) {
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
std.time.sleep(1);
}
@ -995,9 +1017,9 @@ test "cache file and then recall it" {
{
var cache = Cache{
.gpa = testing.allocator,
.manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
};
cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
defer cache.manifest_dir.close();
{
@ -1033,9 +1055,6 @@ test "cache file and then recall it" {
try testing.expectEqual(digest1, digest2);
}
try cwd.deleteTree(temp_manifest_dir);
try cwd.deleteFile(temp_file);
}
test "check that changing a file makes cache fail" {
@ -1043,21 +1062,19 @@ test "check that changing a file makes cache fail" {
// https://github.com/ziglang/zig/issues/5437
return error.SkipZigTest;
}
const cwd = fs.cwd();
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
const temp_file = "cache_hash_change_file_test.txt";
const temp_manifest_dir = "cache_hash_change_file_manifest_dir";
const original_temp_file_contents = "Hello, world!\n";
const updated_temp_file_contents = "Hello, world; but updated!\n";
try cwd.deleteTree(temp_manifest_dir);
try cwd.deleteTree(temp_file);
try cwd.writeFile(temp_file, original_temp_file_contents);
try tmp.dir.writeFile(temp_file, original_temp_file_contents);
// Wait for file timestamps to tick
const initial_time = try testGetCurrentFileTimestamp();
while ((try testGetCurrentFileTimestamp()) == initial_time) {
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
std.time.sleep(1);
}
@ -1067,9 +1084,9 @@ test "check that changing a file makes cache fail" {
{
var cache = Cache{
.gpa = testing.allocator,
.manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
};
cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
defer cache.manifest_dir.close();
{
@ -1089,7 +1106,7 @@ test "check that changing a file makes cache fail" {
try ch.writeManifest();
}
try cwd.writeFile(temp_file, updated_temp_file_contents);
try tmp.dir.writeFile(temp_file, updated_temp_file_contents);
{
var ch = cache.obtain();
@ -1111,9 +1128,6 @@ test "check that changing a file makes cache fail" {
try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));
}
try cwd.deleteTree(temp_manifest_dir);
try cwd.deleteTree(temp_file);
}
test "no file inputs" {
@ -1121,18 +1135,20 @@ test "no file inputs" {
// https://github.com/ziglang/zig/issues/5437
return error.SkipZigTest;
}
const cwd = fs.cwd();
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
const temp_manifest_dir = "no_file_inputs_manifest_dir";
defer cwd.deleteTree(temp_manifest_dir) catch {};
var digest1: [hex_digest_len]u8 = undefined;
var digest2: [hex_digest_len]u8 = undefined;
var cache = Cache{
.gpa = testing.allocator,
.manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
};
cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
defer cache.manifest_dir.close();
{
@ -1167,18 +1183,19 @@ test "Manifest with files added after initial hash work" {
// https://github.com/ziglang/zig/issues/5437
return error.SkipZigTest;
}
const cwd = fs.cwd();
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
const temp_file1 = "cache_hash_post_file_test1.txt";
const temp_file2 = "cache_hash_post_file_test2.txt";
const temp_manifest_dir = "cache_hash_post_file_manifest_dir";
try cwd.writeFile(temp_file1, "Hello, world!\n");
try cwd.writeFile(temp_file2, "Hello world the second!\n");
try tmp.dir.writeFile(temp_file1, "Hello, world!\n");
try tmp.dir.writeFile(temp_file2, "Hello world the second!\n");
// Wait for file timestamps to tick
const initial_time = try testGetCurrentFileTimestamp();
while ((try testGetCurrentFileTimestamp()) == initial_time) {
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
std.time.sleep(1);
}
@ -1189,9 +1206,9 @@ test "Manifest with files added after initial hash work" {
{
var cache = Cache{
.gpa = testing.allocator,
.manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
};
cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
defer cache.manifest_dir.close();
{
@ -1224,11 +1241,11 @@ test "Manifest with files added after initial hash work" {
try testing.expect(mem.eql(u8, &digest1, &digest2));
// Modify the file added after initial hash
try cwd.writeFile(temp_file2, "Hello world the second, updated\n");
try tmp.dir.writeFile(temp_file2, "Hello world the second, updated\n");
// Wait for file timestamps to tick
const initial_time2 = try testGetCurrentFileTimestamp();
while ((try testGetCurrentFileTimestamp()) == initial_time2) {
const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) {
std.time.sleep(1);
}
@ -1251,8 +1268,4 @@ test "Manifest with files added after initial hash work" {
try testing.expect(!mem.eql(u8, &digest1, &digest3));
}
try cwd.deleteTree(temp_manifest_dir);
try cwd.deleteFile(temp_file1);
try cwd.deleteFile(temp_file2);
}

View File

@ -1,51 +1,88 @@
const std = @import("../std.zig");
const Step = std.Build.Step;
const fs = std.fs;
const mem = std.mem;
const CheckFileStep = @This();
pub const base_id = .check_file;
//! Fail the build step if a file does not match certain checks.
//! TODO: make this more flexible, supporting more kinds of checks.
//! TODO: generalize the code in std.testing.expectEqualStrings and make this
//! CheckFileStep produce those helpful diagnostics when there is not a match.
step: Step,
builder: *std.Build,
expected_matches: []const []const u8,
expected_exact: ?[]const u8,
source: std.Build.FileSource,
max_bytes: usize = 20 * 1024 * 1024,
pub const base_id = .check_file;
pub const Options = struct {
expected_matches: []const []const u8 = &.{},
expected_exact: ?[]const u8 = null,
};
pub fn create(
builder: *std.Build,
owner: *std.Build,
source: std.Build.FileSource,
expected_matches: []const []const u8,
options: Options,
) *CheckFileStep {
const self = builder.allocator.create(CheckFileStep) catch @panic("OOM");
self.* = CheckFileStep{
.builder = builder,
.step = Step.init(.check_file, "CheckFile", builder.allocator, make),
.source = source.dupe(builder),
.expected_matches = builder.dupeStrings(expected_matches),
const self = owner.allocator.create(CheckFileStep) catch @panic("OOM");
self.* = .{
.step = Step.init(.{
.id = .check_file,
.name = "CheckFile",
.owner = owner,
.makeFn = make,
}),
.source = source.dupe(owner),
.expected_matches = owner.dupeStrings(options.expected_matches),
.expected_exact = options.expected_exact,
};
self.source.addStepDependencies(&self.step);
return self;
}
fn make(step: *Step) !void {
pub fn setName(self: *CheckFileStep, name: []const u8) void {
self.step.name = name;
}
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
_ = prog_node;
const b = step.owner;
const self = @fieldParentPtr(CheckFileStep, "step", step);
const src_path = self.source.getPath(self.builder);
const contents = try fs.cwd().readFileAlloc(self.builder.allocator, src_path, self.max_bytes);
const src_path = self.source.getPath(b);
const contents = fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes) catch |err| {
return step.fail("unable to read '{s}': {s}", .{
src_path, @errorName(err),
});
};
for (self.expected_matches) |expected_match| {
if (mem.indexOf(u8, contents, expected_match) == null) {
std.debug.print(
return step.fail(
\\
\\========= Expected to find: ===================
\\========= expected to find: ===================
\\{s}
\\========= But file does not contain it: =======
\\========= but file does not contain it: =======
\\{s}
\\
\\===============================================
, .{ expected_match, contents });
return error.TestFailed;
}
}
if (self.expected_exact) |expected_exact| {
if (!mem.eql(u8, expected_exact, contents)) {
return step.fail(
\\
\\========= expected: =====================
\\{s}
\\========= but found: ====================
\\{s}
\\========= from the following file: ======
\\{s}
, .{ expected_exact, contents, src_path });
}
}
}
const CheckFileStep = @This();
const std = @import("../std.zig");
const Step = std.Build.Step;
const fs = std.fs;
const mem = std.mem;

View File

@ -10,25 +10,31 @@ const CheckObjectStep = @This();
const Allocator = mem.Allocator;
const Step = std.Build.Step;
const EmulatableRunStep = std.Build.EmulatableRunStep;
pub const base_id = .check_object;
step: Step,
builder: *std.Build,
source: std.Build.FileSource,
max_bytes: usize = 20 * 1024 * 1024,
checks: std.ArrayList(Check),
dump_symtab: bool = false,
obj_format: std.Target.ObjectFormat,
pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep {
const gpa = builder.allocator;
pub fn create(
owner: *std.Build,
source: std.Build.FileSource,
obj_format: std.Target.ObjectFormat,
) *CheckObjectStep {
const gpa = owner.allocator;
const self = gpa.create(CheckObjectStep) catch @panic("OOM");
self.* = .{
.builder = builder,
.step = Step.init(.check_file, "CheckObject", gpa, make),
.source = source.dupe(builder),
.step = Step.init(.{
.id = .check_file,
.name = "CheckObject",
.owner = owner,
.makeFn = make,
}),
.source = source.dupe(owner),
.checks = std.ArrayList(Check).init(gpa),
.obj_format = obj_format,
};
@ -38,14 +44,18 @@ pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std
/// Runs and (optionally) compares the output of a binary.
/// Asserts `self` was generated from an executable step.
pub fn runAndCompare(self: *CheckObjectStep) *EmulatableRunStep {
/// TODO this doesn't actually compare, and there's no apparent reason for it
/// to depend on the check object step. I don't see why this function should exist,
/// the caller could just add the run step directly.
pub fn runAndCompare(self: *CheckObjectStep) *std.Build.RunStep {
const dependencies_len = self.step.dependencies.items.len;
assert(dependencies_len > 0);
const exe_step = self.step.dependencies.items[dependencies_len - 1];
const exe = exe_step.cast(std.Build.CompileStep).?;
const emulatable_step = EmulatableRunStep.create(self.builder, "EmulatableRun", exe);
emulatable_step.step.dependOn(&self.step);
return emulatable_step;
const run = self.step.owner.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.step.dependOn(&self.step);
return run;
}
/// There two types of actions currently suported:
@ -123,7 +133,8 @@ const Action = struct {
/// Will return true if the `phrase` is correctly parsed into an RPN program and
/// its reduced, computed value compares using `op` with the expected value, either
/// a literal or another extracted variable.
fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool {
fn computeCmp(act: Action, step: *Step, global_vars: anytype) !bool {
const gpa = step.owner.allocator;
var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa);
var values = std.ArrayList(u64).init(gpa);
@ -140,11 +151,11 @@ const Action = struct {
} else {
const val = std.fmt.parseInt(u64, next, 0) catch blk: {
break :blk global_vars.get(next) orelse {
std.debug.print(
try step.addError(
\\
\\========= Variable was not extracted: ===========
\\========= variable was not extracted: ===========
\\{s}
\\
\\=================================================
, .{next});
return error.UnknownVariable;
};
@ -176,11 +187,11 @@ const Action = struct {
const exp_value = switch (act.expected.?.value) {
.variable => |name| global_vars.get(name) orelse {
std.debug.print(
try step.addError(
\\
\\========= Variable was not extracted: ===========
\\========= variable was not extracted: ===========
\\{s}
\\
\\=================================================
, .{name});
return error.UnknownVariable;
},
@ -249,7 +260,7 @@ const Check = struct {
/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase.
pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void {
var new_check = Check.create(self.builder);
var new_check = Check.create(self.step.owner);
new_check.match(phrase);
self.checks.append(new_check) catch @panic("OOM");
}
@ -291,34 +302,34 @@ pub fn checkComputeCompare(
program: []const u8,
expected: ComputeCompareExpected,
) void {
var new_check = Check.create(self.builder);
var new_check = Check.create(self.step.owner);
new_check.computeCmp(program, expected);
self.checks.append(new_check) catch @panic("OOM");
}
fn make(step: *Step) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
_ = prog_node;
const b = step.owner;
const gpa = b.allocator;
const self = @fieldParentPtr(CheckObjectStep, "step", step);
const gpa = self.builder.allocator;
const src_path = self.source.getPath(self.builder);
const contents = try fs.cwd().readFileAllocOptions(
const src_path = self.source.getPath(b);
const contents = fs.cwd().readFileAllocOptions(
gpa,
src_path,
self.max_bytes,
null,
@alignOf(u64),
null,
);
) catch |err| return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err) });
const output = switch (self.obj_format) {
.macho => try MachODumper.parseAndDump(contents, .{
.gpa = gpa,
.macho => try MachODumper.parseAndDump(step, contents, .{
.dump_symtab = self.dump_symtab,
}),
.elf => @panic("TODO elf parser"),
.coff => @panic("TODO coff parser"),
.wasm => try WasmDumper.parseAndDump(contents, .{
.gpa = gpa,
.wasm => try WasmDumper.parseAndDump(step, contents, .{
.dump_symtab = self.dump_symtab,
}),
else => unreachable,
@ -334,54 +345,50 @@ fn make(step: *Step) !void {
while (it.next()) |line| {
if (try act.match(line, &vars)) break;
} else {
std.debug.print(
return step.fail(
\\
\\========= Expected to find: ==========================
\\========= expected to find: ==========================
\\{s}
\\========= But parsed file does not contain it: =======
\\========= but parsed file does not contain it: =======
\\{s}
\\
\\======================================================
, .{ act.phrase, output });
return error.TestFailed;
}
},
.not_present => {
while (it.next()) |line| {
if (try act.match(line, &vars)) {
std.debug.print(
return step.fail(
\\
\\========= Expected not to find: ===================
\\========= expected not to find: ===================
\\{s}
\\========= But parsed file does contain it: ========
\\========= but parsed file does contain it: ========
\\{s}
\\
\\===================================================
, .{ act.phrase, output });
return error.TestFailed;
}
}
},
.compute_cmp => {
const res = act.computeCmp(gpa, vars) catch |err| switch (err) {
const res = act.computeCmp(step, vars) catch |err| switch (err) {
error.UnknownVariable => {
std.debug.print(
\\========= From parsed file: =====================
return step.fail(
\\========= from parsed file: =====================
\\{s}
\\
\\=================================================
, .{output});
return error.TestFailed;
},
else => |e| return e,
};
if (!res) {
std.debug.print(
return step.fail(
\\
\\========= Comparison failed for action: ===========
\\========= comparison failed for action: ===========
\\{s} {}
\\========= From parsed file: =======================
\\========= from parsed file: =======================
\\{s}
\\
\\===================================================
, .{ act.phrase, act.expected.?, output });
return error.TestFailed;
}
},
}
@ -390,7 +397,6 @@ fn make(step: *Step) !void {
}
const Opts = struct {
gpa: ?Allocator = null,
dump_symtab: bool = false,
};
@ -398,8 +404,8 @@ const MachODumper = struct {
const LoadCommandIterator = macho.LoadCommandIterator;
const symtab_label = "symtab";
fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {
const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator
fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {
const gpa = step.owner.allocator;
var stream = std.io.fixedBufferStream(bytes);
const reader = stream.reader();
@ -681,8 +687,8 @@ const MachODumper = struct {
const WasmDumper = struct {
const symtab_label = "symbols";
fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 {
const gpa = opts.gpa orelse unreachable; // Wasm dumper requires an allocator
fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 {
const gpa = step.owner.allocator;
if (opts.dump_symtab) {
@panic("TODO: Implement symbol table parsing and dumping");
}
@ -703,20 +709,24 @@ const WasmDumper = struct {
const writer = output.writer();
while (reader.readByte()) |current_byte| {
const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch |err| {
std.debug.print("Found invalid section id '{d}'\n", .{current_byte});
return err;
const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch {
return step.fail("Found invalid section id '{d}'", .{current_byte});
};
const section_length = try std.leb.readULEB128(u32, reader);
try parseAndDumpSection(section, bytes[fbs.pos..][0..section_length], writer);
try parseAndDumpSection(step, section, bytes[fbs.pos..][0..section_length], writer);
fbs.pos += section_length;
} else |_| {} // reached end of stream
return output.toOwnedSlice();
}
fn parseAndDumpSection(section: std.wasm.Section, data: []const u8, writer: anytype) !void {
fn parseAndDumpSection(
step: *Step,
section: std.wasm.Section,
data: []const u8,
writer: anytype,
) !void {
var fbs = std.io.fixedBufferStream(data);
const reader = fbs.reader();
@ -739,7 +749,7 @@ const WasmDumper = struct {
=> {
const entries = try std.leb.readULEB128(u32, reader);
try writer.print("\nentries {d}\n", .{entries});
try dumpSection(section, data[fbs.pos..], entries, writer);
try dumpSection(step, section, data[fbs.pos..], entries, writer);
},
.custom => {
const name_length = try std.leb.readULEB128(u32, reader);
@ -748,7 +758,7 @@ const WasmDumper = struct {
try writer.print("\nname {s}\n", .{name});
if (mem.eql(u8, name, "name")) {
try parseDumpNames(reader, writer, data);
try parseDumpNames(step, reader, writer, data);
} else if (mem.eql(u8, name, "producers")) {
try parseDumpProducers(reader, writer, data);
} else if (mem.eql(u8, name, "target_features")) {
@ -764,7 +774,7 @@ const WasmDumper = struct {
}
}
fn dumpSection(section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void {
fn dumpSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void {
var fbs = std.io.fixedBufferStream(data);
const reader = fbs.reader();
@ -774,19 +784,18 @@ const WasmDumper = struct {
while (i < entries) : (i += 1) {
const func_type = try reader.readByte();
if (func_type != std.wasm.function_type) {
std.debug.print("Expected function type, found byte '{d}'\n", .{func_type});
return error.UnexpectedByte;
return step.fail("expected function type, found byte '{d}'", .{func_type});
}
const params = try std.leb.readULEB128(u32, reader);
try writer.print("params {d}\n", .{params});
var index: u32 = 0;
while (index < params) : (index += 1) {
try parseDumpType(std.wasm.Valtype, reader, writer);
try parseDumpType(step, std.wasm.Valtype, reader, writer);
} else index = 0;
const returns = try std.leb.readULEB128(u32, reader);
try writer.print("returns {d}\n", .{returns});
while (index < returns) : (index += 1) {
try parseDumpType(std.wasm.Valtype, reader, writer);
try parseDumpType(step, std.wasm.Valtype, reader, writer);
}
}
},
@ -800,9 +809,8 @@ const WasmDumper = struct {
const name = data[fbs.pos..][0..name_len];
fbs.pos += name_len;
const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch |err| {
std.debug.print("Invalid import kind\n", .{});
return err;
const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch {
return step.fail("invalid import kind", .{});
};
try writer.print(
@ -819,11 +827,11 @@ const WasmDumper = struct {
try parseDumpLimits(reader, writer);
},
.global => {
try parseDumpType(std.wasm.Valtype, reader, writer);
try parseDumpType(step, std.wasm.Valtype, reader, writer);
try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)});
},
.table => {
try parseDumpType(std.wasm.RefType, reader, writer);
try parseDumpType(step, std.wasm.RefType, reader, writer);
try parseDumpLimits(reader, writer);
},
}
@ -838,7 +846,7 @@ const WasmDumper = struct {
.table => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
try parseDumpType(std.wasm.RefType, reader, writer);
try parseDumpType(step, std.wasm.RefType, reader, writer);
try parseDumpLimits(reader, writer);
}
},
@ -851,9 +859,9 @@ const WasmDumper = struct {
.global => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
try parseDumpType(std.wasm.Valtype, reader, writer);
try parseDumpType(step, std.wasm.Valtype, reader, writer);
try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)});
try parseDumpInit(reader, writer);
try parseDumpInit(step, reader, writer);
}
},
.@"export" => {
@ -863,9 +871,8 @@ const WasmDumper = struct {
const name = data[fbs.pos..][0..name_len];
fbs.pos += name_len;
const kind_byte = try std.leb.readULEB128(u8, reader);
const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch |err| {
std.debug.print("invalid export kind value '{d}'\n", .{kind_byte});
return err;
const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch {
return step.fail("invalid export kind value '{d}'", .{kind_byte});
};
const index = try std.leb.readULEB128(u32, reader);
try writer.print(
@ -880,7 +887,7 @@ const WasmDumper = struct {
var i: u32 = 0;
while (i < entries) : (i += 1) {
try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)});
try parseDumpInit(reader, writer);
try parseDumpInit(step, reader, writer);
const function_indexes = try std.leb.readULEB128(u32, reader);
var function_index: u32 = 0;
@ -896,7 +903,7 @@ const WasmDumper = struct {
while (i < entries) : (i += 1) {
const index = try std.leb.readULEB128(u32, reader);
try writer.print("memory index 0x{x}\n", .{index});
try parseDumpInit(reader, writer);
try parseDumpInit(step, reader, writer);
const size = try std.leb.readULEB128(u32, reader);
try writer.print("size {d}\n", .{size});
try reader.skipBytes(size, .{}); // we do not care about the content of the segments
@ -906,11 +913,10 @@ const WasmDumper = struct {
}
}
fn parseDumpType(comptime WasmType: type, reader: anytype, writer: anytype) !void {
fn parseDumpType(step: *Step, comptime WasmType: type, reader: anytype, writer: anytype) !void {
const type_byte = try reader.readByte();
const valtype = std.meta.intToEnum(WasmType, type_byte) catch |err| {
std.debug.print("Invalid wasm type value '{d}'\n", .{type_byte});
return err;
const valtype = std.meta.intToEnum(WasmType, type_byte) catch {
return step.fail("Invalid wasm type value '{d}'", .{type_byte});
};
try writer.print("type {s}\n", .{@tagName(valtype)});
}
@ -925,11 +931,10 @@ const WasmDumper = struct {
}
}
fn parseDumpInit(reader: anytype, writer: anytype) !void {
fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void {
const byte = try std.leb.readULEB128(u8, reader);
const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch |err| {
std.debug.print("invalid wasm opcode '{d}'\n", .{byte});
return err;
const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch {
return step.fail("invalid wasm opcode '{d}'", .{byte});
};
switch (opcode) {
.i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}),
@ -941,14 +946,13 @@ const WasmDumper = struct {
}
const end_opcode = try std.leb.readULEB128(u8, reader);
if (end_opcode != std.wasm.opcode(.end)) {
std.debug.print("expected 'end' opcode in init expression\n", .{});
return error.MissingEndOpcode;
return step.fail("expected 'end' opcode in init expression", .{});
}
}
fn parseDumpNames(reader: anytype, writer: anytype, data: []const u8) !void {
fn parseDumpNames(step: *Step, reader: anytype, writer: anytype, data: []const u8) !void {
while (reader.context.pos < data.len) {
try parseDumpType(std.wasm.NameSubsection, reader, writer);
try parseDumpType(step, std.wasm.NameSubsection, reader, writer);
const size = try std.leb.readULEB128(u32, reader);
const entries = try std.leb.readULEB128(u32, reader);
try writer.print(

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,3 @@
const std = @import("../std.zig");
const ConfigHeaderStep = @This();
const Step = std.Build.Step;
pub const base_id: Step.Id = .config_header;
pub const Style = union(enum) {
/// The configure format supported by autotools. It uses `#undef foo` to
/// mark lines that can be substituted with different values.
@ -34,7 +28,6 @@ pub const Value = union(enum) {
};
step: Step,
builder: *std.Build,
values: std.StringArrayHashMap(Value),
output_file: std.Build.GeneratedFile,
@ -42,43 +35,57 @@ style: Style,
max_bytes: usize,
include_path: []const u8,
pub const base_id: Step.Id = .config_header;
pub const Options = struct {
style: Style = .blank,
max_bytes: usize = 2 * 1024 * 1024,
include_path: ?[]const u8 = null,
first_ret_addr: ?usize = null,
};
pub fn create(builder: *std.Build, options: Options) *ConfigHeaderStep {
const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM");
const name = if (options.style.getFileSource()) |s|
builder.fmt("configure {s} header {s}", .{ @tagName(options.style), s.getDisplayName() })
else
builder.fmt("configure {s} header", .{@tagName(options.style)});
self.* = .{
.builder = builder,
.step = Step.init(base_id, name, builder.allocator, make),
.style = options.style,
.values = std.StringArrayHashMap(Value).init(builder.allocator),
pub fn create(owner: *std.Build, options: Options) *ConfigHeaderStep {
const self = owner.allocator.create(ConfigHeaderStep) catch @panic("OOM");
.max_bytes = options.max_bytes,
.include_path = "config.h",
.output_file = .{ .step = &self.step },
};
var include_path: []const u8 = "config.h";
if (options.style.getFileSource()) |s| switch (s) {
.path => |p| {
const basename = std.fs.path.basename(p);
if (std.mem.endsWith(u8, basename, ".h.in")) {
self.include_path = basename[0 .. basename.len - 3];
include_path = basename[0 .. basename.len - 3];
}
},
else => {},
};
if (options.include_path) |include_path| {
self.include_path = include_path;
if (options.include_path) |p| {
include_path = p;
}
const name = if (options.style.getFileSource()) |s|
owner.fmt("configure {s} header {s} to {s}", .{
@tagName(options.style), s.getDisplayName(), include_path,
})
else
owner.fmt("configure {s} header to {s}", .{ @tagName(options.style), include_path });
self.* = .{
.step = Step.init(.{
.id = base_id,
.name = name,
.owner = owner,
.makeFn = make,
.first_ret_addr = options.first_ret_addr orelse @returnAddress(),
}),
.style = options.style,
.values = std.StringArrayHashMap(Value).init(owner.allocator),
.max_bytes = options.max_bytes,
.include_path = include_path,
.output_file = .{ .step = &self.step },
};
return self;
}
@ -146,26 +153,20 @@ fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v
}
}
fn make(step: *Step) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
_ = prog_node;
const b = step.owner;
const self = @fieldParentPtr(ConfigHeaderStep, "step", step);
const gpa = self.builder.allocator;
const gpa = b.allocator;
const arena = b.allocator;
// The cache is used here not really as a way to speed things up - because writing
// the data to a file would probably be very fast - but as a way to find a canonical
// location to put build artifacts.
var man = b.cache.obtain();
defer man.deinit();
// If, for example, a hard-coded path was used as the location to put ConfigHeaderStep
// files, then two ConfigHeaderStep executing in parallel might clobber each other.
// TODO port the cache system from the compiler to zig std lib. Until then
// we construct the path directly, and no "cache hit" detection happens;
// the files are always written.
// Note there is very similar code over in WriteFileStep
const Hasher = std.crypto.auth.siphash.SipHash128(1, 3);
// Random bytes to make ConfigHeaderStep unique. Refresh this with new
// random bytes when ConfigHeaderStep implementation is modified in a
// non-backwards-compatible way.
var hash = Hasher.init("PGuDTpidxyMqnkGM");
man.hash.add(@as(u32, 0xdef08d23));
var output = std.ArrayList(u8).init(gpa);
defer output.deinit();
@ -177,15 +178,15 @@ fn make(step: *Step) !void {
switch (self.style) {
.autoconf => |file_source| {
try output.appendSlice(c_generated_line);
const src_path = file_source.getPath(self.builder);
const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
try render_autoconf(contents, &output, self.values, src_path);
const src_path = file_source.getPath(b);
const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes);
try render_autoconf(step, contents, &output, self.values, src_path);
},
.cmake => |file_source| {
try output.appendSlice(c_generated_line);
const src_path = file_source.getPath(self.builder);
const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
try render_cmake(contents, &output, self.values, src_path);
const src_path = file_source.getPath(b);
const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes);
try render_cmake(step, contents, &output, self.values, src_path);
},
.blank => {
try output.appendSlice(c_generated_line);
@ -197,43 +198,44 @@ fn make(step: *Step) !void {
},
}
hash.update(output.items);
man.hash.addBytes(output.items);
var digest: [16]u8 = undefined;
hash.final(&digest);
var hash_basename: [digest.len * 2]u8 = undefined;
_ = std.fmt.bufPrint(
&hash_basename,
"{s}",
.{std.fmt.fmtSliceHexLower(&digest)},
) catch unreachable;
if (try step.cacheHit(&man)) {
const digest = man.final();
self.output_file.path = try b.cache_root.join(arena, &.{
"o", &digest, self.include_path,
});
return;
}
const output_dir = try self.builder.cache_root.join(gpa, &.{ "o", &hash_basename });
const digest = man.final();
// If output_path has directory parts, deal with them. Example:
// output_dir is zig-cache/o/HASH
// output_path is libavutil/avconfig.h
// We want to open directory zig-cache/o/HASH/libavutil/
// but keep output_dir as zig-cache/o/HASH for -I include
const sub_dir_path = if (std.fs.path.dirname(self.include_path)) |d|
try std.fs.path.join(gpa, &.{ output_dir, d })
else
output_dir;
const sub_path = try std.fs.path.join(arena, &.{ "o", &digest, self.include_path });
const sub_path_dirname = std.fs.path.dirname(sub_path).?;
var dir = std.fs.cwd().makeOpenPath(sub_dir_path, .{}) catch |err| {
std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) });
return err;
b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
return step.fail("unable to make path '{}{s}': {s}", .{
b.cache_root, sub_path_dirname, @errorName(err),
});
};
defer dir.close();
try dir.writeFile(std.fs.path.basename(self.include_path), output.items);
b.cache_root.handle.writeFile(sub_path, output.items) catch |err| {
return step.fail("unable to write file '{}{s}': {s}", .{
b.cache_root, sub_path, @errorName(err),
});
};
self.output_file.path = try std.fs.path.join(self.builder.allocator, &.{
output_dir, self.include_path,
});
self.output_file.path = try b.cache_root.join(arena, &.{sub_path});
try man.writeManifest();
}
fn render_autoconf(
step: *Step,
contents: []const u8,
output: *std.ArrayList(u8),
values: std.StringArrayHashMap(Value),
@ -260,7 +262,7 @@ fn render_autoconf(
}
const name = it.rest();
const kv = values_copy.fetchSwapRemove(name) orelse {
std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{
try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
src_path, line_index + 1, name,
});
any_errors = true;
@ -270,15 +272,17 @@ fn render_autoconf(
}
for (values_copy.keys()) |name| {
std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name });
try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
any_errors = true;
}
if (any_errors) {
return error.HeaderConfigFailed;
return error.MakeFailed;
}
}
fn render_cmake(
step: *Step,
contents: []const u8,
output: *std.ArrayList(u8),
values: std.StringArrayHashMap(Value),
@ -304,14 +308,14 @@ fn render_cmake(
continue;
}
const name = it.next() orelse {
std.debug.print("{s}:{d}: error: missing define name\n", .{
try step.addError("{s}:{d}: error: missing define name", .{
src_path, line_index + 1,
});
any_errors = true;
continue;
};
const kv = values_copy.fetchSwapRemove(name) orelse {
std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{
try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
src_path, line_index + 1, name,
});
any_errors = true;
@ -321,7 +325,8 @@ fn render_cmake(
}
for (values_copy.keys()) |name| {
std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name });
try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
any_errors = true;
}
if (any_errors) {
@ -426,3 +431,7 @@ fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !
},
}
}
const std = @import("../std.zig");
const ConfigHeaderStep = @This();
const Step = std.Build.Step;

View File

@ -1,213 +0,0 @@
//! Unlike `RunStep` this step will provide emulation, when enabled, to run foreign binaries.
//! When a binary is foreign, but emulation for the target is disabled, the specified binary
//! will not be run and therefore also not validated against its output.
//! This step can be useful when wishing to run a built binary on multiple platforms,
//! without having to verify if it's possible to be ran against.
const std = @import("../std.zig");
const Step = std.Build.Step;
const CompileStep = std.Build.CompileStep;
const RunStep = std.Build.RunStep;
const fs = std.fs;
const process = std.process;
const EnvMap = process.EnvMap;
const EmulatableRunStep = @This();
pub const base_id = .emulatable_run;
const max_stdout_size = 1 * 1024 * 1024; // 1 MiB
step: Step,
builder: *std.Build,
/// The artifact (executable) to be run by this step
exe: *CompileStep,
/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 },
/// Override this field to modify the environment
env_map: ?*EnvMap,
/// Set this to modify the current working directory
cwd: ?[]const u8,
stdout_action: RunStep.StdIoAction = .inherit,
stderr_action: RunStep.StdIoAction = .inherit,
/// When set to true, hides the warning of skipping a foreign binary which cannot be run on the host
/// or through emulation.
hide_foreign_binaries_warning: bool,
/// Creates a step that will execute the given artifact. This step will allow running the
/// binary through emulation when any of the emulation options such as `enable_rosetta` are set to true.
/// When set to false, and the binary is foreign, running the executable is skipped.
/// Asserts given artifact is an executable.
pub fn create(builder: *std.Build, name: []const u8, artifact: *CompileStep) *EmulatableRunStep {
std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe);
const self = builder.allocator.create(EmulatableRunStep) catch @panic("OOM");
const option_name = "hide-foreign-warnings";
const hide_warnings = if (builder.available_options_map.get(option_name) == null) warn: {
break :warn builder.option(bool, option_name, "Hide the warning when a foreign binary which is incompatible is skipped") orelse false;
} else false;
self.* = .{
.builder = builder,
.step = Step.init(.emulatable_run, name, builder.allocator, make),
.exe = artifact,
.env_map = null,
.cwd = null,
.hide_foreign_binaries_warning = hide_warnings,
};
self.step.dependOn(&artifact.step);
return self;
}
fn make(step: *Step) !void {
const self = @fieldParentPtr(EmulatableRunStep, "step", step);
const host_info = self.builder.host;
var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
defer argv_list.deinit();
const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc;
switch (host_info.getExternalExecutor(self.exe.target_info, .{
.qemu_fixes_dl = need_cross_glibc and self.builder.glibc_runtimes_dir != null,
.link_libc = self.exe.is_linking_libc,
})) {
.native => {},
.rosetta => if (!self.builder.enable_rosetta) return warnAboutForeignBinaries(self),
.wine => |bin_name| if (self.builder.enable_wine) {
try argv_list.append(bin_name);
} else return,
.qemu => |bin_name| if (self.builder.enable_qemu) {
const glibc_dir_arg = if (need_cross_glibc)
self.builder.glibc_runtimes_dir orelse return
else
null;
try argv_list.append(bin_name);
if (glibc_dir_arg) |dir| {
// TODO look into making this a call to `linuxTriple`. This
// needs the directory to be called "i686" rather than
// "x86" which is why we do it manually here.
const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
const cpu_arch = self.exe.target.getCpuArch();
const os_tag = self.exe.target.getOsTag();
const abi = self.exe.target.getAbi();
const cpu_arch_name: []const u8 = if (cpu_arch == .x86)
"i686"
else
@tagName(cpu_arch);
const full_dir = try std.fmt.allocPrint(self.builder.allocator, fmt_str, .{
dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
});
try argv_list.append("-L");
try argv_list.append(full_dir);
}
} else return warnAboutForeignBinaries(self),
.darling => |bin_name| if (self.builder.enable_darling) {
try argv_list.append(bin_name);
} else return warnAboutForeignBinaries(self),
.wasmtime => |bin_name| if (self.builder.enable_wasmtime) {
try argv_list.append(bin_name);
try argv_list.append("--dir=.");
} else return warnAboutForeignBinaries(self),
else => return warnAboutForeignBinaries(self),
}
if (self.exe.target.isWindows()) {
// On Windows we don't have rpaths so we have to add .dll search paths to PATH
RunStep.addPathForDynLibsInternal(&self.step, self.builder, self.exe);
}
const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder);
try argv_list.append(executable_path);
try RunStep.runCommand(
argv_list.items,
self.builder,
self.expected_term,
self.stdout_action,
self.stderr_action,
.Inherit,
self.env_map,
self.cwd,
false,
);
}
pub fn expectStdErrEqual(self: *EmulatableRunStep, bytes: []const u8) void {
self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) };
}
pub fn expectStdOutEqual(self: *EmulatableRunStep, bytes: []const u8) void {
self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) };
}
fn warnAboutForeignBinaries(step: *EmulatableRunStep) void {
if (step.hide_foreign_binaries_warning) return;
const builder = step.builder;
const artifact = step.exe;
const host_name = builder.host.target.zigTriple(builder.allocator) catch @panic("unhandled error");
const foreign_name = artifact.target.zigTriple(builder.allocator) catch @panic("unhandled error");
const target_info = std.zig.system.NativeTargetInfo.detect(artifact.target) catch @panic("unhandled error");
const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc;
switch (builder.host.getExternalExecutor(target_info, .{
.qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null,
.link_libc = artifact.is_linking_libc,
})) {
.native => unreachable,
.bad_dl => |foreign_dl| {
const host_dl = builder.host.dynamic_linker.get() orelse "(none)";
std.debug.print("the host system does not appear to be capable of executing binaries from the target because the host dynamic linker is '{s}', while the target dynamic linker is '{s}'. Consider setting the dynamic linker as '{s}'.\n", .{
host_dl, foreign_dl, host_dl,
});
},
.bad_os_or_cpu => {
std.debug.print("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}).\n", .{
host_name, foreign_name,
});
},
.darling => if (!builder.enable_darling) {
std.debug.print(
"the host system ({s}) does not appear to be capable of executing binaries " ++
"from the target ({s}). Consider enabling darling.\n",
.{ host_name, foreign_name },
);
},
.rosetta => if (!builder.enable_rosetta) {
std.debug.print(
"the host system ({s}) does not appear to be capable of executing binaries " ++
"from the target ({s}). Consider enabling rosetta.\n",
.{ host_name, foreign_name },
);
},
.wine => if (!builder.enable_wine) {
std.debug.print(
"the host system ({s}) does not appear to be capable of executing binaries " ++
"from the target ({s}). Consider enabling wine.\n",
.{ host_name, foreign_name },
);
},
.qemu => if (!builder.enable_qemu) {
std.debug.print(
"the host system ({s}) does not appear to be capable of executing binaries " ++
"from the target ({s}). Consider enabling qemu.\n",
.{ host_name, foreign_name },
);
},
.wasmtime => {
std.debug.print(
"the host system ({s}) does not appear to be capable of executing binaries " ++
"from the target ({s}). Consider enabling wasmtime.\n",
.{ host_name, foreign_name },
);
},
}
}

View File

@ -1,32 +1,73 @@
const std = @import("../std.zig");
const Step = std.Build.Step;
const FmtStep = @This();
//! This step has two modes:
//! * Modify mode: directly modify source files, formatting them in place.
//! * Check mode: fail the step if a non-conforming file is found.
step: Step,
paths: []const []const u8,
exclude_paths: []const []const u8,
check: bool,
pub const base_id = .fmt;
step: Step,
builder: *std.Build,
argv: [][]const u8,
pub const Options = struct {
paths: []const []const u8 = &.{},
exclude_paths: []const []const u8 = &.{},
/// If true, fails the build step when any non-conforming files are encountered.
check: bool = false,
};
pub fn create(builder: *std.Build, paths: []const []const u8) *FmtStep {
const self = builder.allocator.create(FmtStep) catch @panic("OOM");
const name = "zig fmt";
self.* = FmtStep{
.step = Step.init(.fmt, name, builder.allocator, make),
.builder = builder,
.argv = builder.allocator.alloc([]u8, paths.len + 2) catch @panic("OOM"),
pub fn create(owner: *std.Build, options: Options) *FmtStep {
const self = owner.allocator.create(FmtStep) catch @panic("OOM");
const name = if (options.check) "zig fmt --check" else "zig fmt";
self.* = .{
.step = Step.init(.{
.id = base_id,
.name = name,
.owner = owner,
.makeFn = make,
}),
.paths = options.paths,
.exclude_paths = options.exclude_paths,
.check = options.check,
};
self.argv[0] = builder.zig_exe;
self.argv[1] = "fmt";
for (paths, 0..) |path, i| {
self.argv[2 + i] = builder.pathFromRoot(path);
}
return self;
}
fn make(step: *Step) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
// zig fmt is fast enough that no progress is needed.
_ = prog_node;
// TODO: if check=false, this means we are modifying source files in place, which
// is an operation that could race against other operations also modifying source files
// in place. In this case, this step should obtain a write lock while making those
// modifications.
const b = step.owner;
const arena = b.allocator;
const self = @fieldParentPtr(FmtStep, "step", step);
return self.builder.spawnChild(self.argv);
var argv: std.ArrayListUnmanaged([]const u8) = .{};
try argv.ensureUnusedCapacity(arena, 2 + 1 + self.paths.len + 2 * self.exclude_paths.len);
argv.appendAssumeCapacity(b.zig_exe);
argv.appendAssumeCapacity("fmt");
if (self.check) {
argv.appendAssumeCapacity("--check");
}
for (self.paths) |p| {
argv.appendAssumeCapacity(b.pathFromRoot(p));
}
for (self.exclude_paths) |p| {
argv.appendAssumeCapacity("--exclude");
argv.appendAssumeCapacity(b.pathFromRoot(p));
}
return step.evalChildProcess(argv.items);
}
const std = @import("../std.zig");
const Step = std.Build.Step;
const FmtStep = @This();

View File

@ -3,83 +3,133 @@ const Step = std.Build.Step;
const CompileStep = std.Build.CompileStep;
const InstallDir = std.Build.InstallDir;
const InstallArtifactStep = @This();
const fs = std.fs;
pub const base_id = .install_artifact;
step: Step,
builder: *std.Build,
dest_builder: *std.Build,
artifact: *CompileStep,
dest_dir: InstallDir,
pdb_dir: ?InstallDir,
h_dir: ?InstallDir,
/// If non-null, adds additional path components relative to dest_dir, and
/// overrides the basename of the CompileStep.
dest_sub_path: ?[]const u8,
pub fn create(builder: *std.Build, artifact: *CompileStep) *InstallArtifactStep {
pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep {
if (artifact.install_step) |s| return s;
const self = builder.allocator.create(InstallArtifactStep) catch @panic("OOM");
const self = owner.allocator.create(InstallArtifactStep) catch @panic("OOM");
self.* = InstallArtifactStep{
.builder = builder,
.step = Step.init(.install_artifact, builder.fmt("install {s}", .{artifact.step.name}), builder.allocator, make),
.step = Step.init(.{
.id = base_id,
.name = owner.fmt("install {s}", .{artifact.name}),
.owner = owner,
.makeFn = make,
}),
.dest_builder = owner,
.artifact = artifact,
.dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) {
.obj => @panic("Cannot install a .obj build artifact."),
.@"test" => @panic("Cannot install a .test build artifact, use .test_exe instead."),
.exe, .test_exe => InstallDir{ .bin = {} },
.exe, .@"test" => InstallDir{ .bin = {} },
.lib => InstallDir{ .lib = {} },
},
.pdb_dir = if (artifact.producesPdbFile()) blk: {
if (artifact.kind == .exe or artifact.kind == .test_exe) {
if (artifact.kind == .exe or artifact.kind == .@"test") {
break :blk InstallDir{ .bin = {} };
} else {
break :blk InstallDir{ .lib = {} };
}
} else null,
.h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null,
.dest_sub_path = null,
};
self.step.dependOn(&artifact.step);
artifact.install_step = self;
builder.pushInstalledFile(self.dest_dir, artifact.out_filename);
owner.pushInstalledFile(self.dest_dir, artifact.out_filename);
if (self.artifact.isDynamicLibrary()) {
if (artifact.major_only_filename) |name| {
builder.pushInstalledFile(.lib, name);
owner.pushInstalledFile(.lib, name);
}
if (artifact.name_only_filename) |name| {
builder.pushInstalledFile(.lib, name);
owner.pushInstalledFile(.lib, name);
}
if (self.artifact.target.isWindows()) {
builder.pushInstalledFile(.lib, artifact.out_lib_filename);
owner.pushInstalledFile(.lib, artifact.out_lib_filename);
}
}
if (self.pdb_dir) |pdb_dir| {
builder.pushInstalledFile(pdb_dir, artifact.out_pdb_filename);
owner.pushInstalledFile(pdb_dir, artifact.out_pdb_filename);
}
if (self.h_dir) |h_dir| {
builder.pushInstalledFile(h_dir, artifact.out_h_filename);
owner.pushInstalledFile(h_dir, artifact.out_h_filename);
}
return self;
}
fn make(step: *Step) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
_ = prog_node;
const src_builder = step.owner;
const self = @fieldParentPtr(InstallArtifactStep, "step", step);
const builder = self.builder;
const dest_builder = self.dest_builder;
const full_dest_path = builder.getInstallPath(self.dest_dir, self.artifact.out_filename);
try builder.updateFile(self.artifact.getOutputSource().getPath(builder), full_dest_path);
if (self.artifact.isDynamicLibrary() and self.artifact.version != null and self.artifact.target.wantSharedLibSymLinks()) {
try CompileStep.doAtomicSymLinks(builder.allocator, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?);
const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename;
const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path);
const cwd = fs.cwd();
var all_cached = true;
{
const full_src_path = self.artifact.getOutputSource().getPath(src_builder);
const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| {
return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
full_src_path, full_dest_path, @errorName(err),
});
};
all_cached = all_cached and p == .fresh;
}
if (self.artifact.isDynamicLibrary() and self.artifact.target.isWindows() and self.artifact.emit_implib != .no_emit) {
const full_implib_path = builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename);
try builder.updateFile(self.artifact.getOutputLibSource().getPath(builder), full_implib_path);
if (self.artifact.isDynamicLibrary() and
self.artifact.version != null and
self.artifact.target.wantSharedLibSymLinks())
{
try CompileStep.doAtomicSymLinks(step, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?);
}
if (self.artifact.isDynamicLibrary() and
self.artifact.target.isWindows() and
self.artifact.emit_implib != .no_emit)
{
const full_src_path = self.artifact.getOutputLibSource().getPath(src_builder);
const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename);
const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| {
return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
full_src_path, full_implib_path, @errorName(err),
});
};
all_cached = all_cached and p == .fresh;
}
if (self.pdb_dir) |pdb_dir| {
const full_pdb_path = builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename);
try builder.updateFile(self.artifact.getOutputPdbSource().getPath(builder), full_pdb_path);
const full_src_path = self.artifact.getOutputPdbSource().getPath(src_builder);
const full_pdb_path = dest_builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename);
const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| {
return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
full_src_path, full_pdb_path, @errorName(err),
});
};
all_cached = all_cached and p == .fresh;
}
if (self.h_dir) |h_dir| {
const full_h_path = builder.getInstallPath(h_dir, self.artifact.out_h_filename);
try builder.updateFile(self.artifact.getOutputHSource().getPath(builder), full_h_path);
const full_src_path = self.artifact.getOutputHSource().getPath(src_builder);
const full_h_path = dest_builder.getInstallPath(h_dir, self.artifact.out_h_filename);
const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| {
return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
full_src_path, full_h_path, @errorName(err),
});
};
all_cached = all_cached and p == .fresh;
}
self.artifact.installed_path = full_dest_path;
step.result_cached = all_cached;
}

View File

@ -4,14 +4,12 @@ const fs = std.fs;
const Step = std.Build.Step;
const InstallDir = std.Build.InstallDir;
const InstallDirStep = @This();
const log = std.log;
step: Step,
builder: *std.Build,
options: Options,
/// This is used by the build system when a file being installed comes from one
/// package but is being installed by another.
override_source_builder: ?*std.Build = null,
dest_builder: *std.Build,
pub const base_id = .install_dir;
@ -40,31 +38,35 @@ pub const Options = struct {
}
};
pub fn init(
builder: *std.Build,
options: Options,
) InstallDirStep {
builder.pushInstalledFile(options.install_dir, options.install_subdir);
return InstallDirStep{
.builder = builder,
.step = Step.init(.install_dir, builder.fmt("install {s}/", .{options.source_dir}), builder.allocator, make),
.options = options.dupe(builder),
pub fn init(owner: *std.Build, options: Options) InstallDirStep {
owner.pushInstalledFile(options.install_dir, options.install_subdir);
return .{
.step = Step.init(.{
.id = .install_dir,
.name = owner.fmt("install {s}/", .{options.source_dir}),
.owner = owner,
.makeFn = make,
}),
.options = options.dupe(owner),
.dest_builder = owner,
};
}
fn make(step: *Step) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
_ = prog_node;
const self = @fieldParentPtr(InstallDirStep, "step", step);
const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
const src_builder = self.override_source_builder orelse self.builder;
const full_src_dir = src_builder.pathFromRoot(self.options.source_dir);
var src_dir = std.fs.cwd().openIterableDir(full_src_dir, .{}) catch |err| {
log.err("InstallDirStep: unable to open source directory '{s}': {s}", .{
full_src_dir, @errorName(err),
const dest_builder = self.dest_builder;
const arena = dest_builder.allocator;
const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
const src_builder = self.step.owner;
var src_dir = src_builder.build_root.handle.openIterableDir(self.options.source_dir, .{}) catch |err| {
return step.fail("unable to open source directory '{}{s}': {s}", .{
src_builder.build_root, self.options.source_dir, @errorName(err),
});
return error.StepFailed;
};
defer src_dir.close();
var it = try src_dir.walk(self.builder.allocator);
var it = try src_dir.walk(arena);
var all_cached = true;
next_entry: while (try it.next()) |entry| {
for (self.options.exclude_extensions) |ext| {
if (mem.endsWith(u8, entry.path, ext)) {
@ -72,22 +74,37 @@ fn make(step: *Step) !void {
}
}
const full_path = self.builder.pathJoin(&.{ full_src_dir, entry.path });
const dest_path = self.builder.pathJoin(&.{ dest_prefix, entry.path });
// relative to src build root
const src_sub_path = try fs.path.join(arena, &.{ self.options.source_dir, entry.path });
const dest_path = try fs.path.join(arena, &.{ dest_prefix, entry.path });
const cwd = fs.cwd();
switch (entry.kind) {
.Directory => try fs.cwd().makePath(dest_path),
.Directory => try cwd.makePath(dest_path),
.File => {
for (self.options.blank_extensions) |ext| {
if (mem.endsWith(u8, entry.path, ext)) {
try self.builder.truncateFile(dest_path);
try dest_builder.truncateFile(dest_path);
continue :next_entry;
}
}
try self.builder.updateFile(full_path, dest_path);
const prev_status = fs.Dir.updateFile(
src_builder.build_root.handle,
src_sub_path,
cwd,
dest_path,
.{},
) catch |err| {
return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{
src_builder.build_root, src_sub_path, dest_path, @errorName(err),
});
};
all_cached = all_cached and prev_status == .fresh;
},
else => continue,
}
}
step.result_cached = all_cached;
}

View File

@ -3,38 +3,55 @@ const Step = std.Build.Step;
const FileSource = std.Build.FileSource;
const InstallDir = std.Build.InstallDir;
const InstallFileStep = @This();
const assert = std.debug.assert;
pub const base_id = .install_file;
step: Step,
builder: *std.Build,
source: FileSource,
dir: InstallDir,
dest_rel_path: []const u8,
/// This is used by the build system when a file being installed comes from one
/// package but is being installed by another.
override_source_builder: ?*std.Build = null,
dest_builder: *std.Build,
pub fn init(
builder: *std.Build,
pub fn create(
owner: *std.Build,
source: FileSource,
dir: InstallDir,
dest_rel_path: []const u8,
) InstallFileStep {
builder.pushInstalledFile(dir, dest_rel_path);
return InstallFileStep{
.builder = builder,
.step = Step.init(.install_file, builder.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), builder.allocator, make),
.source = source.dupe(builder),
.dir = dir.dupe(builder),
.dest_rel_path = builder.dupePath(dest_rel_path),
) *InstallFileStep {
assert(dest_rel_path.len != 0);
owner.pushInstalledFile(dir, dest_rel_path);
const self = owner.allocator.create(InstallFileStep) catch @panic("OOM");
self.* = .{
.step = Step.init(.{
.id = base_id,
.name = owner.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }),
.owner = owner,
.makeFn = make,
}),
.source = source.dupe(owner),
.dir = dir.dupe(owner),
.dest_rel_path = owner.dupePath(dest_rel_path),
.dest_builder = owner,
};
source.addStepDependencies(&self.step);
return self;
}
fn make(step: *Step) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
_ = prog_node;
const src_builder = step.owner;
const self = @fieldParentPtr(InstallFileStep, "step", step);
const src_builder = self.override_source_builder orelse self.builder;
const full_src_path = self.source.getPath(src_builder);
const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path);
try self.builder.updateFile(full_src_path, full_dest_path);
const dest_builder = self.dest_builder;
const full_src_path = self.source.getPath2(src_builder, step);
const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path);
const cwd = std.fs.cwd();
const prev = std.fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| {
return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
full_src_path, full_dest_path, @errorName(err),
});
};
step.result_cached = prev == .fresh;
}

View File

@ -1,23 +0,0 @@
const std = @import("../std.zig");
const log = std.log;
const Step = std.Build.Step;
const LogStep = @This();
pub const base_id = .log;
step: Step,
builder: *std.Build,
data: []const u8,
pub fn init(builder: *std.Build, data: []const u8) LogStep {
return LogStep{
.builder = builder,
.step = Step.init(.log, builder.fmt("log {s}", .{data}), builder.allocator, make),
.data = builder.dupe(data),
};
}
fn make(step: *Step) anyerror!void {
const self = @fieldParentPtr(LogStep, "step", step);
log.info("{s}", .{self.data});
}

View File

@ -21,7 +21,6 @@ pub const RawFormat = enum {
};
step: Step,
builder: *std.Build,
file_source: std.Build.FileSource,
basename: []const u8,
output_file: std.Build.GeneratedFile,
@ -38,19 +37,18 @@ pub const Options = struct {
};
pub fn create(
builder: *std.Build,
owner: *std.Build,
file_source: std.Build.FileSource,
options: Options,
) *ObjCopyStep {
const self = builder.allocator.create(ObjCopyStep) catch @panic("OOM");
const self = owner.allocator.create(ObjCopyStep) catch @panic("OOM");
self.* = ObjCopyStep{
.step = Step.init(
base_id,
builder.fmt("objcopy {s}", .{file_source.getDisplayName()}),
builder.allocator,
make,
),
.builder = builder,
.step = Step.init(.{
.id = base_id,
.name = owner.fmt("objcopy {s}", .{file_source.getDisplayName()}),
.owner = owner,
.makeFn = make,
}),
.file_source = file_source,
.basename = options.basename orelse file_source.getDisplayName(),
.output_file = std.Build.GeneratedFile{ .step = &self.step },
@ -67,9 +65,9 @@ pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource {
return .{ .generated = &self.output_file };
}
fn make(step: *Step) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const b = step.owner;
const self = @fieldParentPtr(ObjCopyStep, "step", step);
const b = self.builder;
var man = b.cache.obtain();
defer man.deinit();
@ -84,7 +82,7 @@ fn make(step: *Step) !void {
man.hash.addOptional(self.pad_to);
man.hash.addOptional(self.format);
if (man.hit() catch |err| failWithCacheError(man, err)) {
if (try step.cacheHit(&man)) {
// Cache hit, skip subprocess execution.
const digest = man.final();
self.output_file.path = try b.cache_root.join(b.allocator, &.{
@ -97,8 +95,7 @@ fn make(step: *Step) !void {
const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename });
const cache_path = "o" ++ fs.path.sep_str ++ digest;
b.cache_root.handle.makePath(cache_path) catch |err| {
std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) });
return err;
return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });
};
var argv = std.ArrayList([]const u8).init(b.allocator);
@ -116,23 +113,10 @@ fn make(step: *Step) !void {
};
try argv.appendSlice(&.{ full_src_path, full_dest_path });
_ = try self.builder.execFromStep(argv.items, &self.step);
try argv.append("--listen=-");
_ = try step.evalZigProcess(argv.items, prog_node);
self.output_file.path = full_dest_path;
try man.writeManifest();
}
/// TODO consolidate this with the same function in RunStep?
/// Also properly deal with concurrency (see open PR)
fn failWithCacheError(man: std.Build.Cache.Manifest, err: anyerror) noreturn {
const i = man.failed_file_index orelse failWithSimpleError(err);
const pp = man.files.items[i].prefixed_path orelse failWithSimpleError(err);
const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
std.debug.print("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path });
std.process.exit(1);
}
fn failWithSimpleError(err: anyerror) noreturn {
std.debug.print("{s}\n", .{@errorName(err)});
std.process.exit(1);
}

View File

@ -12,21 +12,24 @@ pub const base_id = .options;
step: Step,
generated_file: GeneratedFile,
builder: *std.Build,
contents: std.ArrayList(u8),
artifact_args: std.ArrayList(OptionArtifactArg),
file_source_args: std.ArrayList(OptionFileSourceArg),
pub fn create(builder: *std.Build) *OptionsStep {
const self = builder.allocator.create(OptionsStep) catch @panic("OOM");
pub fn create(owner: *std.Build) *OptionsStep {
const self = owner.allocator.create(OptionsStep) catch @panic("OOM");
self.* = .{
.builder = builder,
.step = Step.init(.options, "options", builder.allocator, make),
.step = Step.init(.{
.id = base_id,
.name = "options",
.owner = owner,
.makeFn = make,
}),
.generated_file = undefined,
.contents = std.ArrayList(u8).init(builder.allocator),
.artifact_args = std.ArrayList(OptionArtifactArg).init(builder.allocator),
.file_source_args = std.ArrayList(OptionFileSourceArg).init(builder.allocator),
.contents = std.ArrayList(u8).init(owner.allocator),
.artifact_args = std.ArrayList(OptionArtifactArg).init(owner.allocator),
.file_source_args = std.ArrayList(OptionFileSourceArg).init(owner.allocator),
};
self.generated_file = .{ .step = &self.step };
@ -192,7 +195,7 @@ pub fn addOptionFileSource(
) void {
self.file_source_args.append(.{
.name = name,
.source = source.dupe(self.builder),
.source = source.dupe(self.step.owner),
}) catch @panic("OOM");
source.addStepDependencies(&self.step);
}
@ -200,12 +203,12 @@ pub fn addOptionFileSource(
/// The value is the path in the cache dir.
/// Adds a dependency automatically.
pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *CompileStep) void {
self.artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch @panic("OOM");
self.artifact_args.append(.{ .name = self.step.owner.dupe(name), .artifact = artifact }) catch @panic("OOM");
self.step.dependOn(&artifact.step);
}
pub fn createModule(self: *OptionsStep) *std.Build.Module {
return self.builder.createModule(.{
return self.step.owner.createModule(.{
.source_file = self.getSource(),
.dependencies = &.{},
});
@ -215,14 +218,18 @@ pub fn getSource(self: *OptionsStep) FileSource {
return .{ .generated = &self.generated_file };
}
fn make(step: *Step) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
// This step completes so quickly that no progress is necessary.
_ = prog_node;
const b = step.owner;
const self = @fieldParentPtr(OptionsStep, "step", step);
for (self.artifact_args.items) |item| {
self.addOption(
[]const u8,
item.name,
self.builder.pathFromRoot(item.artifact.getOutputSource().getPath(self.builder)),
b.pathFromRoot(item.artifact.getOutputSource().getPath(b)),
);
}
@ -230,20 +237,18 @@ fn make(step: *Step) !void {
self.addOption(
[]const u8,
item.name,
item.source.getPath(self.builder),
item.source.getPath(b),
);
}
var options_dir = try self.builder.cache_root.handle.makeOpenPath("options", .{});
var options_dir = try b.cache_root.handle.makeOpenPath("options", .{});
defer options_dir.close();
const basename = self.hashContentsToFileName();
try options_dir.writeFile(&basename, self.contents.items);
self.generated_file.path = try self.builder.cache_root.join(self.builder.allocator, &.{
"options", &basename,
});
self.generated_file.path = try b.cache_root.join(b.allocator, &.{ "options", &basename });
}
fn hashContentsToFileName(self: *OptionsStep) [64]u8 {

View File

@ -1,5 +1,4 @@
const std = @import("../std.zig");
const log = std.log;
const fs = std.fs;
const Step = std.Build.Step;
const RemoveDirStep = @This();
@ -7,23 +6,37 @@ const RemoveDirStep = @This();
pub const base_id = .remove_dir;
step: Step,
builder: *std.Build,
dir_path: []const u8,
pub fn init(builder: *std.Build, dir_path: []const u8) RemoveDirStep {
pub fn init(owner: *std.Build, dir_path: []const u8) RemoveDirStep {
return RemoveDirStep{
.builder = builder,
.step = Step.init(.remove_dir, builder.fmt("RemoveDir {s}", .{dir_path}), builder.allocator, make),
.dir_path = builder.dupePath(dir_path),
.step = Step.init(.{
.id = .remove_dir,
.name = owner.fmt("RemoveDir {s}", .{dir_path}),
.owner = owner,
.makeFn = make,
}),
.dir_path = owner.dupePath(dir_path),
};
}
fn make(step: *Step) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
// TODO update progress node while walking file system.
// Should the standard library support this use case??
_ = prog_node;
const b = step.owner;
const self = @fieldParentPtr(RemoveDirStep, "step", step);
const full_path = self.builder.pathFromRoot(self.dir_path);
fs.cwd().deleteTree(full_path) catch |err| {
log.err("Unable to remove {s}: {s}", .{ full_path, @errorName(err) });
return err;
b.build_root.handle.deleteTree(self.dir_path) catch |err| {
if (b.build_root.path) |base| {
return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{
base, self.dir_path, @errorName(err),
});
} else {
return step.fail("unable to recursively delete path '{s}': {s}", .{
self.dir_path, @errorName(err),
});
}
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,77 @@
id: Id,
name: []const u8,
makeFn: *const fn (self: *Step) anyerror!void,
owner: *Build,
makeFn: MakeFn,
dependencies: std.ArrayList(*Step),
loop_flag: bool,
done_flag: bool,
/// This field is empty during execution of the user's build script, and
/// then populated during dependency loop checking in the build runner.
dependants: std.ArrayListUnmanaged(*Step),
state: State,
/// Set this field to declare an upper bound on the amount of bytes of memory it will
/// take to run the step. Zero means no limit.
///
/// The idea to annotate steps that might use a high amount of RAM with an
/// upper bound. For example, perhaps a particular set of unit tests require 4
/// GiB of RAM, and those tests will be run under 4 different build
/// configurations at once. This would potentially require 16 GiB of memory on
/// the system if all 4 steps executed simultaneously, which could easily be
/// greater than what is actually available, potentially causing the system to
/// crash when using `zig build` at the default concurrency level.
///
/// This field causes the build runner to do two things:
/// 1. ulimit child processes, so that they will fail if it would exceed this
/// memory limit. This serves to enforce that this upper bound value is
/// correct.
/// 2. Ensure that the set of concurrent steps at any given time have a total
/// max_rss value that does not exceed the `max_total_rss` value of the build
/// runner. This value is configurable on the command line, and defaults to the
/// total system memory available.
max_rss: usize,
result_error_msgs: std.ArrayListUnmanaged([]const u8),
result_error_bundle: std.zig.ErrorBundle,
result_cached: bool,
result_duration_ns: ?u64,
/// 0 means unavailable or not reported.
result_peak_rss: usize,
test_results: TestResults,
/// The return addresss associated with creation of this step that can be useful
/// to print along with debugging messages.
debug_stack_trace: [n_debug_stack_frames]usize,
pub const TestResults = struct {
fail_count: u32 = 0,
skip_count: u32 = 0,
leak_count: u32 = 0,
test_count: u32 = 0,
pub fn isSuccess(tr: TestResults) bool {
return tr.fail_count == 0 and tr.leak_count == 0;
}
pub fn passCount(tr: TestResults) u32 {
return tr.test_count - tr.fail_count - tr.skip_count;
}
};
pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void;
const n_debug_stack_frames = 4;
pub const State = enum {
precheck_unstarted,
precheck_started,
precheck_done,
running,
dependency_failure,
success,
failure,
/// This state indicates that the step did not complete, however, it also did not fail,
/// and it is safe to continue executing its dependencies.
skipped,
};
pub const Id = enum {
top_level,
@ -17,7 +85,6 @@ pub const Id = enum {
translate_c,
write_file,
run,
emulatable_run,
check_file,
check_object,
config_header,
@ -38,7 +105,6 @@ pub const Id = enum {
.translate_c => Build.TranslateCStep,
.write_file => Build.WriteFileStep,
.run => Build.RunStep,
.emulatable_run => Build.EmulatableRunStep,
.check_file => Build.CheckFileStep,
.check_object => Build.CheckObjectStep,
.config_header => Build.ConfigHeaderStep,
@ -49,39 +115,99 @@ pub const Id = enum {
}
};
pub fn init(
pub const Options = struct {
id: Id,
name: []const u8,
allocator: Allocator,
makeFn: *const fn (self: *Step) anyerror!void,
) Step {
return Step{
.id = id,
.name = allocator.dupe(u8, name) catch @panic("OOM"),
.makeFn = makeFn,
.dependencies = std.ArrayList(*Step).init(allocator),
.loop_flag = false,
.done_flag = false,
owner: *Build,
makeFn: MakeFn = makeNoOp,
first_ret_addr: ?usize = null,
max_rss: usize = 0,
};
pub fn init(options: Options) Step {
const arena = options.owner.allocator;
var addresses = [1]usize{0} ** n_debug_stack_frames;
const first_ret_addr = options.first_ret_addr orelse @returnAddress();
var stack_trace = std.builtin.StackTrace{
.instruction_addresses = &addresses,
.index = 0,
};
std.debug.captureStackTrace(first_ret_addr, &stack_trace);
return .{
.id = options.id,
.name = arena.dupe(u8, options.name) catch @panic("OOM"),
.owner = options.owner,
.makeFn = options.makeFn,
.dependencies = std.ArrayList(*Step).init(arena),
.dependants = .{},
.state = .precheck_unstarted,
.max_rss = options.max_rss,
.debug_stack_trace = addresses,
.result_error_msgs = .{},
.result_error_bundle = std.zig.ErrorBundle.empty,
.result_cached = false,
.result_duration_ns = null,
.result_peak_rss = 0,
.test_results = .{},
};
}
pub fn initNoOp(id: Id, name: []const u8, allocator: Allocator) Step {
return init(id, name, allocator, makeNoOp);
}
/// If the Step's `make` function reports `error.MakeFailed`, it indicates they
/// have already reported the error. Otherwise, we add a simple error report
/// here.
pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkipped }!void {
const arena = s.owner.allocator;
pub fn make(self: *Step) !void {
if (self.done_flag) return;
s.makeFn(s, prog_node) catch |err| switch (err) {
error.MakeFailed => return error.MakeFailed,
error.MakeSkipped => return error.MakeSkipped,
else => {
s.result_error_msgs.append(arena, @errorName(err)) catch @panic("OOM");
return error.MakeFailed;
},
};
try self.makeFn(self);
self.done_flag = true;
if (!s.test_results.isSuccess()) {
return error.MakeFailed;
}
if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) {
const msg = std.fmt.allocPrint(arena, "memory usage peaked at {d} bytes, exceeding the declared upper bound of {d}", .{
s.result_peak_rss, s.max_rss,
}) catch @panic("OOM");
s.result_error_msgs.append(arena, msg) catch @panic("OOM");
return error.MakeFailed;
}
}
pub fn dependOn(self: *Step, other: *Step) void {
self.dependencies.append(other) catch @panic("OOM");
}
fn makeNoOp(self: *Step) anyerror!void {
_ = self;
pub fn getStackTrace(s: *Step) std.builtin.StackTrace {
const stack_addresses = &s.debug_stack_trace;
var len: usize = 0;
while (len < n_debug_stack_frames and stack_addresses[len] != 0) {
len += 1;
}
return .{
.instruction_addresses = stack_addresses,
.index = len,
};
}
fn makeNoOp(step: *Step, prog_node: *std.Progress.Node) anyerror!void {
_ = prog_node;
var all_cached = true;
for (step.dependencies.items) |dep| {
all_cached = all_cached and dep.result_cached;
}
step.result_cached = all_cached;
}
pub fn cast(step: *Step, comptime T: type) ?*T {
@ -91,7 +217,323 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
return null;
}
/// For debugging purposes, prints identifying information about this Step.
pub fn dump(step: *Step) void {
std.debug.getStderrMutex().lock();
defer std.debug.getStderrMutex().unlock();
const stderr = std.io.getStdErr();
const w = stderr.writer();
const tty_config = std.debug.detectTTYConfig(stderr);
const debug_info = std.debug.getSelfDebugInfo() catch |err| {
w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{
@errorName(err),
}) catch {};
return;
};
const ally = debug_info.allocator;
w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
std.debug.writeStackTrace(step.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch {};
return;
};
}
const Step = @This();
const std = @import("../std.zig");
const Build = std.Build;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const builtin = @import("builtin");
pub fn evalChildProcess(s: *Step, argv: []const []const u8) !void {
const arena = s.owner.allocator;
try handleChildProcUnsupported(s, null, argv);
try handleVerbose(s.owner, null, argv);
const result = std.ChildProcess.exec(.{
.allocator = arena,
.argv = argv,
}) catch |err| return s.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });
if (result.stderr.len > 0) {
try s.result_error_msgs.append(arena, result.stderr);
}
try handleChildProcessTerm(s, result.term, null, argv);
}
pub fn fail(step: *Step, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, MakeFailed } {
try step.addError(fmt, args);
return error.MakeFailed;
}
pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void {
const arena = step.owner.allocator;
const msg = try std.fmt.allocPrint(arena, fmt, args);
try step.result_error_msgs.append(arena, msg);
}
/// Assumes that argv contains `--listen=-` and that the process being spawned
/// is the zig compiler - the same version that compiled the build runner.
pub fn evalZigProcess(
s: *Step,
argv: []const []const u8,
prog_node: *std.Progress.Node,
) ![]const u8 {
assert(argv.len != 0);
const b = s.owner;
const arena = b.allocator;
const gpa = arena;
try handleChildProcUnsupported(s, null, argv);
try handleVerbose(s.owner, null, argv);
var child = std.ChildProcess.init(argv, arena);
child.env_map = b.env_map;
child.stdin_behavior = .Pipe;
child.stdout_behavior = .Pipe;
child.stderr_behavior = .Pipe;
child.request_resource_usage_statistics = true;
child.spawn() catch |err| return s.fail("unable to spawn {s}: {s}", .{
argv[0], @errorName(err),
});
var timer = try std.time.Timer.start();
var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
.stdout = child.stdout.?,
.stderr = child.stderr.?,
});
defer poller.deinit();
try sendMessage(child.stdin.?, .update);
try sendMessage(child.stdin.?, .exit);
const Header = std.zig.Server.Message.Header;
var result: ?[]const u8 = null;
var node_name: std.ArrayListUnmanaged(u8) = .{};
defer node_name.deinit(gpa);
var sub_prog_node = prog_node.start("", 0);
defer sub_prog_node.end();
const stdout = poller.fifo(.stdout);
poll: while (true) {
while (stdout.readableLength() < @sizeOf(Header)) {
if (!(try poller.poll())) break :poll;
}
const header = stdout.reader().readStruct(Header) catch unreachable;
while (stdout.readableLength() < header.bytes_len) {
if (!(try poller.poll())) break :poll;
}
const body = stdout.readableSliceOfLen(header.bytes_len);
switch (header.tag) {
.zig_version => {
if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
return s.fail(
"zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
.{ builtin.zig_version_string, body },
);
}
},
.error_bundle => {
const EbHdr = std.zig.Server.Message.ErrorBundle;
const eb_hdr = @ptrCast(*align(1) const EbHdr, body);
const extra_bytes =
body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
const string_bytes =
body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
// TODO: use @ptrCast when the compiler supports it
const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
const extra_array = try arena.alloc(u32, unaligned_extra.len);
// TODO: use @memcpy when it supports slices
for (extra_array, unaligned_extra) |*dst, src| dst.* = src;
s.result_error_bundle = .{
.string_bytes = try arena.dupe(u8, string_bytes),
.extra = extra_array,
};
},
.progress => {
node_name.clearRetainingCapacity();
try node_name.appendSlice(gpa, body);
sub_prog_node.setName(node_name.items);
},
.emit_bin_path => {
const EbpHdr = std.zig.Server.Message.EmitBinPath;
const ebp_hdr = @ptrCast(*align(1) const EbpHdr, body);
s.result_cached = ebp_hdr.flags.cache_hit;
result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
},
else => {}, // ignore other messages
}
stdout.discard(body.len);
}
const stderr = poller.fifo(.stderr);
if (stderr.readableLength() > 0) {
try s.result_error_msgs.append(arena, try stderr.toOwnedSlice());
}
// Send EOF to stdin.
child.stdin.?.close();
child.stdin = null;
const term = child.wait() catch |err| {
return s.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) });
};
s.result_duration_ns = timer.read();
s.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0;
// Special handling for CompileStep that is expecting compile errors.
if (s.cast(Build.CompileStep)) |compile| switch (term) {
.Exited => {
// Note that the exit code may be 0 in this case due to the
// compiler server protocol.
if (compile.expect_errors.len != 0 and s.result_error_bundle.errorMessageCount() > 0) {
return error.NeedCompileErrorCheck;
}
},
else => {},
};
try handleChildProcessTerm(s, term, null, argv);
if (s.result_error_bundle.errorMessageCount() > 0) {
return s.fail("the following command failed with {d} compilation errors:\n{s}", .{
s.result_error_bundle.errorMessageCount(),
try allocPrintCmd(arena, null, argv),
});
}
return result orelse return s.fail(
"the following command failed to communicate the compilation result:\n{s}",
.{try allocPrintCmd(arena, null, argv)},
);
}
fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
const header: std.zig.Client.Message.Header = .{
.tag = tag,
.bytes_len = 0,
};
try file.writeAll(std.mem.asBytes(&header));
}
pub fn handleVerbose(
b: *Build,
opt_cwd: ?[]const u8,
argv: []const []const u8,
) error{OutOfMemory}!void {
return handleVerbose2(b, opt_cwd, null, argv);
}
pub fn handleVerbose2(
b: *Build,
opt_cwd: ?[]const u8,
opt_env: ?*const std.process.EnvMap,
argv: []const []const u8,
) error{OutOfMemory}!void {
if (b.verbose) {
// Intention of verbose is to print all sub-process command lines to
// stderr before spawning them.
const text = try allocPrintCmd2(b.allocator, opt_cwd, opt_env, argv);
std.debug.print("{s}\n", .{text});
}
}
pub inline fn handleChildProcUnsupported(
s: *Step,
opt_cwd: ?[]const u8,
argv: []const []const u8,
) error{ OutOfMemory, MakeFailed }!void {
if (!std.process.can_spawn) {
return s.fail(
"unable to execute the following command: host cannot spawn child processes\n{s}",
.{try allocPrintCmd(s.owner.allocator, opt_cwd, argv)},
);
}
}
pub fn handleChildProcessTerm(
s: *Step,
term: std.ChildProcess.Term,
opt_cwd: ?[]const u8,
argv: []const []const u8,
) error{ MakeFailed, OutOfMemory }!void {
const arena = s.owner.allocator;
switch (term) {
.Exited => |code| {
if (code != 0) {
return s.fail(
"the following command exited with error code {d}:\n{s}",
.{ code, try allocPrintCmd(arena, opt_cwd, argv) },
);
}
},
.Signal, .Stopped, .Unknown => {
return s.fail(
"the following command terminated unexpectedly:\n{s}",
.{try allocPrintCmd(arena, opt_cwd, argv)},
);
},
}
}
pub fn allocPrintCmd(
arena: Allocator,
opt_cwd: ?[]const u8,
argv: []const []const u8,
) Allocator.Error![]u8 {
return allocPrintCmd2(arena, opt_cwd, null, argv);
}
pub fn allocPrintCmd2(
arena: Allocator,
opt_cwd: ?[]const u8,
opt_env: ?*const std.process.EnvMap,
argv: []const []const u8,
) Allocator.Error![]u8 {
var buf: std.ArrayListUnmanaged(u8) = .{};
if (opt_cwd) |cwd| try buf.writer(arena).print("cd {s} && ", .{cwd});
if (opt_env) |env| {
const process_env_map = std.process.getEnvMap(arena) catch std.process.EnvMap.init(arena);
var it = env.iterator();
while (it.next()) |entry| {
const key = entry.key_ptr.*;
const value = entry.value_ptr.*;
if (process_env_map.get(key)) |process_value| {
if (std.mem.eql(u8, value, process_value)) continue;
}
try buf.writer(arena).print("{s}={s} ", .{ key, value });
}
}
for (argv) |arg| {
try buf.writer(arena).print("{s} ", .{arg});
}
return buf.toOwnedSlice(arena);
}
pub fn cacheHit(s: *Step, man: *std.Build.Cache.Manifest) !bool {
s.result_cached = man.hit() catch |err| return failWithCacheError(s, man, err);
return s.result_cached;
}
fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyerror) anyerror {
const i = man.failed_file_index orelse return err;
const pp = man.files.items[i].prefixed_path orelse return err;
const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path });
}
pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void {
if (s.test_results.isSuccess()) {
man.writeManifest() catch |err| {
try s.addError("unable to write cache manifest: {s}", .{@errorName(err)});
};
}
}

View File

@ -11,7 +11,6 @@ const TranslateCStep = @This();
pub const base_id = .translate_c;
step: Step,
builder: *std.Build,
source: std.Build.FileSource,
include_dirs: std.ArrayList([]const u8),
c_macros: std.ArrayList([]const u8),
@ -26,15 +25,19 @@ pub const Options = struct {
optimize: std.builtin.OptimizeMode,
};
pub fn create(builder: *std.Build, options: Options) *TranslateCStep {
const self = builder.allocator.create(TranslateCStep) catch @panic("OOM");
const source = options.source_file.dupe(builder);
pub fn create(owner: *std.Build, options: Options) *TranslateCStep {
const self = owner.allocator.create(TranslateCStep) catch @panic("OOM");
const source = options.source_file.dupe(owner);
self.* = TranslateCStep{
.step = Step.init(.translate_c, "translate-c", builder.allocator, make),
.builder = builder,
.step = Step.init(.{
.id = .translate_c,
.name = "translate-c",
.owner = owner,
.makeFn = make,
}),
.source = source,
.include_dirs = std.ArrayList([]const u8).init(builder.allocator),
.c_macros = std.ArrayList([]const u8).init(builder.allocator),
.include_dirs = std.ArrayList([]const u8).init(owner.allocator),
.c_macros = std.ArrayList([]const u8).init(owner.allocator),
.out_basename = undefined,
.target = options.target,
.optimize = options.optimize,
@ -54,7 +57,7 @@ pub const AddExecutableOptions = struct {
/// Creates a step to build an executable from the translated source.
pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep {
return self.builder.addExecutable(.{
return self.step.owner.addExecutable(.{
.root_source_file = .{ .generated = &self.output_file },
.name = options.name orelse "translated_c",
.version = options.version,
@ -65,43 +68,49 @@ pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *Comp
}
pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void {
self.include_dirs.append(self.builder.dupePath(include_dir)) catch @panic("OOM");
self.include_dirs.append(self.step.owner.dupePath(include_dir)) catch @panic("OOM");
}
pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep {
return CheckFileStep.create(self.builder, .{ .generated = &self.output_file }, self.builder.dupeStrings(expected_matches));
return CheckFileStep.create(
self.step.owner,
.{ .generated = &self.output_file },
.{ .expected_matches = expected_matches },
);
}
/// If the value is omitted, it is set to 1.
/// `name` and `value` need not live longer than the function call.
pub fn defineCMacro(self: *TranslateCStep, name: []const u8, value: ?[]const u8) void {
const macro = std.Build.constructCMacro(self.builder.allocator, name, value);
const macro = std.Build.constructCMacro(self.step.owner.allocator, name, value);
self.c_macros.append(macro) catch @panic("OOM");
}
/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1.
pub fn defineCMacroRaw(self: *TranslateCStep, name_and_value: []const u8) void {
self.c_macros.append(self.builder.dupe(name_and_value)) catch @panic("OOM");
self.c_macros.append(self.step.owner.dupe(name_and_value)) catch @panic("OOM");
}
fn make(step: *Step) !void {
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const b = step.owner;
const self = @fieldParentPtr(TranslateCStep, "step", step);
var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
try argv_list.append(self.builder.zig_exe);
var argv_list = std.ArrayList([]const u8).init(b.allocator);
try argv_list.append(b.zig_exe);
try argv_list.append("translate-c");
try argv_list.append("-lc");
try argv_list.append("--enable-cache");
try argv_list.append("--listen=-");
if (!self.target.isNative()) {
try argv_list.append("-target");
try argv_list.append(try self.target.zigTriple(self.builder.allocator));
try argv_list.append(try self.target.zigTriple(b.allocator));
}
switch (self.optimize) {
.Debug => {}, // Skip since it's the default.
else => try argv_list.append(self.builder.fmt("-O{s}", .{@tagName(self.optimize)})),
else => try argv_list.append(b.fmt("-O{s}", .{@tagName(self.optimize)})),
}
for (self.include_dirs.items) |include_dir| {
@ -114,16 +123,15 @@ fn make(step: *Step) !void {
try argv_list.append(c_macro);
}
try argv_list.append(self.source.getPath(self.builder));
try argv_list.append(self.source.getPath(b));
const output_path_nl = try self.builder.execFromStep(argv_list.items, &self.step);
const output_path = mem.trimRight(u8, output_path_nl, "\r\n");
const output_path = try step.evalZigProcess(argv_list.items, prog_node);
self.out_basename = fs.path.basename(output_path);
const output_dir = fs.path.dirname(output_path).?;
self.output_file.path = try fs.path.join(
self.builder.allocator,
b.allocator,
&[_][]const u8{ output_dir, self.out_basename },
);
}

View File

@ -10,11 +10,11 @@
//! control.
step: Step,
builder: *std.Build,
/// The elements here are pointers because we need stable pointers for the
/// GeneratedFile field.
files: std.ArrayListUnmanaged(*File),
output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
generated_directory: std.Build.GeneratedFile,
pub const base_id = .write_file;
@ -34,24 +34,34 @@ pub const Contents = union(enum) {
copy: std.Build.FileSource,
};
pub fn init(builder: *std.Build) WriteFileStep {
return .{
.builder = builder,
.step = Step.init(.write_file, "writefile", builder.allocator, make),
pub fn create(owner: *std.Build) *WriteFileStep {
const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM");
wf.* = .{
.step = Step.init(.{
.id = .write_file,
.name = "WriteFile",
.owner = owner,
.makeFn = make,
}),
.files = .{},
.output_source_files = .{},
.generated_directory = .{ .step = &wf.step },
};
return wf;
}
pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {
const gpa = wf.builder.allocator;
const b = wf.step.owner;
const gpa = b.allocator;
const file = gpa.create(File) catch @panic("OOM");
file.* = .{
.generated_file = .{ .step = &wf.step },
.sub_path = wf.builder.dupePath(sub_path),
.contents = .{ .bytes = wf.builder.dupe(bytes) },
.sub_path = b.dupePath(sub_path),
.contents = .{ .bytes = b.dupe(bytes) },
};
wf.files.append(gpa, file) catch @panic("OOM");
wf.maybeUpdateName();
}
/// Place the file into the generated directory within the local cache,
@ -62,14 +72,18 @@ pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {
/// required sub-path exists.
/// This is the option expected to be used most commonly with `addCopyFile`.
pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void {
const gpa = wf.builder.allocator;
const b = wf.step.owner;
const gpa = b.allocator;
const file = gpa.create(File) catch @panic("OOM");
file.* = .{
.generated_file = .{ .step = &wf.step },
.sub_path = wf.builder.dupePath(sub_path),
.sub_path = b.dupePath(sub_path),
.contents = .{ .copy = source },
};
wf.files.append(gpa, file) catch @panic("OOM");
wf.maybeUpdateName();
source.addStepDependencies(&wf.step);
}
/// A path relative to the package root.
@ -79,10 +93,26 @@ pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: [
/// those changes to version control.
/// A file added this way is not available with `getFileSource`.
pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void {
wf.output_source_files.append(wf.builder.allocator, .{
const b = wf.step.owner;
wf.output_source_files.append(b.allocator, .{
.contents = .{ .copy = source },
.sub_path = sub_path,
}) catch @panic("OOM");
source.addStepDependencies(&wf.step);
}
/// A path relative to the package root.
/// Be careful with this because it updates source files. This should not be
/// used as part of the normal build process, but as a utility occasionally
/// run by a developer with intent to modify source files and then commit
/// those changes to version control.
/// A file added this way is not available with `getFileSource`.
pub fn addBytesToSource(wf: *WriteFileStep, bytes: []const u8, sub_path: []const u8) void {
const b = wf.step.owner;
wf.output_source_files.append(b.allocator, .{
.contents = .{ .bytes = bytes },
.sub_path = sub_path,
}) catch @panic("OOM");
}
/// Gets a file source for the given sub_path. If the file does not exist, returns `null`.
@ -95,21 +125,63 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo
return null;
}
fn make(step: *Step) !void {
/// Returns a `FileSource` representing the base directory that contains all the
/// files from this `WriteFileStep`.
pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource {
return .{ .generated = &wf.generated_directory };
}
fn maybeUpdateName(wf: *WriteFileStep) void {
if (wf.files.items.len == 1) {
// First time adding a file; update name.
if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path});
}
}
}
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
_ = prog_node;
const b = step.owner;
const wf = @fieldParentPtr(WriteFileStep, "step", step);
// Writing to source files is kind of an extra capability of this
// WriteFileStep - arguably it should be a different step. But anyway here
// it is, it happens unconditionally and does not interact with the other
// files here.
var any_miss = false;
for (wf.output_source_files.items) |output_source_file| {
const basename = fs.path.basename(output_source_file.sub_path);
if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
var dir = try wf.builder.build_root.handle.makeOpenPath(dirname, .{});
defer dir.close();
try writeFile(wf, dir, output_source_file.contents, basename);
} else {
try writeFile(wf, wf.builder.build_root.handle, output_source_file.contents, basename);
b.build_root.handle.makePath(dirname) catch |err| {
return step.fail("unable to make path '{}{s}': {s}", .{
b.build_root, dirname, @errorName(err),
});
};
}
switch (output_source_file.contents) {
.bytes => |bytes| {
b.build_root.handle.writeFile(output_source_file.sub_path, bytes) catch |err| {
return step.fail("unable to write file '{}{s}': {s}", .{
b.build_root, output_source_file.sub_path, @errorName(err),
});
};
any_miss = true;
},
.copy => |file_source| {
const source_path = file_source.getPath(b);
const prev_status = fs.Dir.updateFile(
fs.cwd(),
source_path,
b.build_root.handle,
output_source_file.sub_path,
.{},
) catch |err| {
return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{
source_path, b.build_root, output_source_file.sub_path, @errorName(err),
});
};
any_miss = any_miss or prev_status == .stale;
},
}
}
@ -120,7 +192,7 @@ fn make(step: *Step) !void {
// If, for example, a hard-coded path was used as the location to put WriteFileStep
// files, then two WriteFileSteps executing in parallel might clobber each other.
var man = wf.builder.cache.obtain();
var man = b.cache.obtain();
defer man.deinit();
// Random bytes to make WriteFileStep unique. Refresh this with
@ -135,76 +207,82 @@ fn make(step: *Step) !void {
man.hash.addBytes(bytes);
},
.copy => |file_source| {
_ = try man.addFile(file_source.getPath(wf.builder), null);
_ = try man.addFile(file_source.getPath(b), null);
},
}
}
if (man.hit() catch |err| failWithCacheError(man, err)) {
// Cache hit, skip writing file data.
if (try step.cacheHit(&man)) {
const digest = man.final();
for (wf.files.items) |file| {
file.generated_file.path = try wf.builder.cache_root.join(
wf.builder.allocator,
&.{ "o", &digest, file.sub_path },
);
file.generated_file.path = try b.cache_root.join(b.allocator, &.{
"o", &digest, file.sub_path,
});
}
wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
return;
}
const digest = man.final();
const cache_path = "o" ++ fs.path.sep_str ++ digest;
var cache_dir = wf.builder.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {
std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) });
return err;
wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {
return step.fail("unable to make path '{}{s}': {s}", .{
b.cache_root, cache_path, @errorName(err),
});
};
defer cache_dir.close();
for (wf.files.items) |file| {
const basename = fs.path.basename(file.sub_path);
if (fs.path.dirname(file.sub_path)) |dirname| {
var dir = try wf.builder.cache_root.handle.makeOpenPath(dirname, .{});
defer dir.close();
try writeFile(wf, dir, file.contents, basename);
} else {
try writeFile(wf, cache_dir, file.contents, basename);
cache_dir.makePath(dirname) catch |err| {
return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{
b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err),
});
};
}
switch (file.contents) {
.bytes => |bytes| {
cache_dir.writeFile(file.sub_path, bytes) catch |err| {
return step.fail("unable to write file '{}{s}{c}{s}': {s}", .{
b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err),
});
};
},
.copy => |file_source| {
const source_path = file_source.getPath(b);
const prev_status = fs.Dir.updateFile(
fs.cwd(),
source_path,
cache_dir,
file.sub_path,
.{},
) catch |err| {
return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{
source_path,
b.cache_root,
cache_path,
fs.path.sep,
file.sub_path,
@errorName(err),
});
};
// At this point we already will mark the step as a cache miss.
// But this is kind of a partial cache hit since individual
// file copies may be avoided. Oh well, this information is
// discarded.
_ = prev_status;
},
}
file.generated_file.path = try wf.builder.cache_root.join(
wf.builder.allocator,
&.{ cache_path, file.sub_path },
);
file.generated_file.path = try b.cache_root.join(b.allocator, &.{
cache_path, file.sub_path,
});
}
try man.writeManifest();
}
fn writeFile(wf: *WriteFileStep, dir: fs.Dir, contents: Contents, basename: []const u8) !void {
// TODO after landing concurrency PR, improve error reporting here
switch (contents) {
.bytes => |bytes| return dir.writeFile(basename, bytes),
.copy => |file_source| {
const source_path = file_source.getPath(wf.builder);
const prev_status = try fs.Dir.updateFile(fs.cwd(), source_path, dir, basename, .{});
_ = prev_status; // TODO logging (affected by open PR regarding concurrency)
},
}
}
/// TODO consolidate this with the same function in RunStep?
/// Also properly deal with concurrency (see open PR)
fn failWithCacheError(man: std.Build.Cache.Manifest, err: anyerror) noreturn {
const i = man.failed_file_index orelse failWithSimpleError(err);
const pp = man.files.items[i].prefixed_path orelse failWithSimpleError(err);
const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
std.debug.print("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path });
std.process.exit(1);
}
fn failWithSimpleError(err: anyerror) noreturn {
std.debug.print("{s}\n", .{@errorName(err)});
std.process.exit(1);
try step.writeManifest(&man);
}
const std = @import("../std.zig");

View File

@ -126,6 +126,21 @@ pub const Node = struct {
}
}
/// Thread-safe.
pub fn setName(self: *Node, name: []const u8) void {
const progress = self.context;
progress.update_mutex.lock();
defer progress.update_mutex.unlock();
self.name = name;
if (self.parent) |parent| {
@atomicStore(?*Node, &parent.recently_updated_child, self, .Release);
if (parent.parent) |grand_parent| {
@atomicStore(?*Node, &grand_parent.recently_updated_child, parent, .Release);
}
if (progress.timer) |*timer| progress.maybeRefreshWithHeldLock(timer);
}
}
/// Thread-safe. 0 means unknown.
pub fn setEstimatedTotalItems(self: *Node, count: usize) void {
@atomicStore(usize, &self.unprotected_estimated_total_items, count, .Monotonic);
@ -174,16 +189,20 @@ pub fn maybeRefresh(self: *Progress) void {
if (self.timer) |*timer| {
if (!self.update_mutex.tryLock()) return;
defer self.update_mutex.unlock();
const now = timer.read();
if (now < self.initial_delay_ns) return;
// TODO I have observed this to happen sometimes. I think we need to follow Rust's
// lead and guarantee monotonically increasing times in the std lib itself.
if (now < self.prev_refresh_timestamp) return;
if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
return self.refreshWithHeldLock();
maybeRefreshWithHeldLock(self, timer);
}
}
fn maybeRefreshWithHeldLock(self: *Progress, timer: *std.time.Timer) void {
const now = timer.read();
if (now < self.initial_delay_ns) return;
// TODO I have observed this to happen sometimes. I think we need to follow Rust's
// lead and guarantee monotonically increasing times in the std lib itself.
if (now < self.prev_refresh_timestamp) return;
if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
return self.refreshWithHeldLock();
}
/// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe.
pub fn refresh(self: *Progress) void {
if (!self.update_mutex.tryLock()) return;
@ -192,32 +211,28 @@ pub fn refresh(self: *Progress) void {
return self.refreshWithHeldLock();
}
fn refreshWithHeldLock(self: *Progress) void {
const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal;
if (is_dumb and self.dont_print_on_dumb) return;
const file = self.terminal orelse return;
var end: usize = 0;
if (self.columns_written > 0) {
fn clearWithHeldLock(p: *Progress, end_ptr: *usize) void {
const file = p.terminal orelse return;
var end = end_ptr.*;
if (p.columns_written > 0) {
// restore the cursor position by moving the cursor
// `columns_written` cells to the left, then clear the rest of the
// line
if (self.supports_ansi_escape_codes) {
end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len;
end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
if (p.supports_ansi_escape_codes) {
end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[{d}D", .{p.columns_written}) catch unreachable).len;
end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
} else if (builtin.os.tag == .windows) winapi: {
std.debug.assert(self.is_windows_terminal);
std.debug.assert(p.is_windows_terminal);
var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {
// stop trying to write to this file
self.terminal = null;
p.terminal = null;
break :winapi;
}
var cursor_pos = windows.COORD{
.X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
.X = info.dwCursorPosition.X - @intCast(windows.SHORT, p.columns_written),
.Y = info.dwCursorPosition.Y,
};
@ -235,7 +250,7 @@ fn refreshWithHeldLock(self: *Progress) void {
&written,
) != windows.TRUE) {
// stop trying to write to this file
self.terminal = null;
p.terminal = null;
break :winapi;
}
if (windows.kernel32.FillConsoleOutputCharacterW(
@ -246,22 +261,33 @@ fn refreshWithHeldLock(self: *Progress) void {
&written,
) != windows.TRUE) {
// stop trying to write to this file
self.terminal = null;
p.terminal = null;
break :winapi;
}
if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) {
// stop trying to write to this file
self.terminal = null;
p.terminal = null;
break :winapi;
}
} else {
// we are in a "dumb" terminal like in acme or writing to a file
self.output_buffer[end] = '\n';
p.output_buffer[end] = '\n';
end += 1;
}
self.columns_written = 0;
p.columns_written = 0;
}
end_ptr.* = end;
}
fn refreshWithHeldLock(self: *Progress) void {
const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal;
if (is_dumb and self.dont_print_on_dumb) return;
const file = self.terminal orelse return;
var end: usize = 0;
clearWithHeldLock(self, &end);
if (!self.done) {
var need_ellipse = false;
@ -318,6 +344,26 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
self.columns_written = 0;
}
/// Allows the caller to freely write to stderr until unlock_stderr() is called.
/// During the lock, the progress information is cleared from the terminal.
pub fn lock_stderr(p: *Progress) void {
p.update_mutex.lock();
if (p.terminal) |file| {
var end: usize = 0;
clearWithHeldLock(p, &end);
_ = file.write(p.output_buffer[0..end]) catch {
// stop trying to write to this file
p.terminal = null;
};
}
std.debug.getStderrMutex().lock();
}
pub fn unlock_stderr(p: *Progress) void {
std.debug.getStderrMutex().unlock();
p.update_mutex.unlock();
}
fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
const amt = written.len;

View File

@ -16,6 +16,8 @@ pub const Mutex = @import("Thread/Mutex.zig");
pub const Semaphore = @import("Thread/Semaphore.zig");
pub const Condition = @import("Thread/Condition.zig");
pub const RwLock = @import("Thread/RwLock.zig");
pub const Pool = @import("Thread/Pool.zig");
pub const WaitGroup = @import("Thread/WaitGroup.zig");
pub const use_pthreads = target.os.tag != .windows and target.os.tag != .wasi and builtin.link_libc;
const is_gnu = target.abi.isGnu();
@ -945,6 +947,7 @@ const LinuxThreadImpl = struct {
// map all memory needed without read/write permissions
// to avoid committing the whole region right away
// anonymous mapping ensures file descriptor limits are not exceeded
const mapped = os.mmap(
null,
map_bytes,
@ -956,6 +959,8 @@ const LinuxThreadImpl = struct {
error.MemoryMappingNotSupported => unreachable,
error.AccessDenied => unreachable,
error.PermissionDenied => unreachable,
error.ProcessFdQuotaExceeded => unreachable,
error.SystemFdQuotaExceeded => unreachable,
else => |e| return e,
};
assert(mapped.len >= map_bytes);

View File

@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const ThreadPool = @This();
const Pool = @This();
const WaitGroup = @import("WaitGroup.zig");
mutex: std.Thread.Mutex = .{},
@ -17,7 +17,14 @@ const Runnable = struct {
const RunProto = *const fn (*Runnable) void;
pub fn init(pool: *ThreadPool, allocator: std.mem.Allocator) !void {
pub const Options = struct {
allocator: std.mem.Allocator,
n_jobs: ?u32 = null,
};
pub fn init(pool: *Pool, options: Options) !void {
const allocator = options.allocator;
pool.* = .{
.allocator = allocator,
.threads = &[_]std.Thread{},
@ -27,7 +34,7 @@ pub fn init(pool: *ThreadPool, allocator: std.mem.Allocator) !void {
return;
}
const thread_count = std.math.max(1, std.Thread.getCpuCount() catch 1);
const thread_count = options.n_jobs orelse @max(1, std.Thread.getCpuCount() catch 1);
pool.threads = try allocator.alloc(std.Thread, thread_count);
errdefer allocator.free(pool.threads);
@ -41,12 +48,12 @@ pub fn init(pool: *ThreadPool, allocator: std.mem.Allocator) !void {
}
}
pub fn deinit(pool: *ThreadPool) void {
pub fn deinit(pool: *Pool) void {
pool.join(pool.threads.len); // kill and join all threads.
pool.* = undefined;
}
fn join(pool: *ThreadPool, spawned: usize) void {
fn join(pool: *Pool, spawned: usize) void {
if (builtin.single_threaded) {
return;
}
@ -69,7 +76,7 @@ fn join(pool: *ThreadPool, spawned: usize) void {
pool.allocator.free(pool.threads);
}
pub fn spawn(pool: *ThreadPool, comptime func: anytype, args: anytype) !void {
pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {
if (builtin.single_threaded) {
@call(.auto, func, args);
return;
@ -78,7 +85,7 @@ pub fn spawn(pool: *ThreadPool, comptime func: anytype, args: anytype) !void {
const Args = @TypeOf(args);
const Closure = struct {
arguments: Args,
pool: *ThreadPool,
pool: *Pool,
run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
fn runFn(runnable: *Runnable) void {
@ -112,7 +119,7 @@ pub fn spawn(pool: *ThreadPool, comptime func: anytype, args: anytype) !void {
pool.cond.signal();
}
fn worker(pool: *ThreadPool) void {
fn worker(pool: *Pool) void {
pool.mutex.lock();
defer pool.mutex.unlock();
@ -135,7 +142,7 @@ fn worker(pool: *ThreadPool) void {
}
}
pub fn waitAndWork(pool: *ThreadPool, wait_group: *WaitGroup) void {
pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void {
while (!wait_group.isDone()) {
if (blk: {
pool.mutex.lock();

View File

@ -16,15 +16,27 @@ fragment: ?[]const u8,
/// Applies URI encoding and replaces all reserved characters with their respective %XX code.
pub fn escapeString(allocator: std.mem.Allocator, input: []const u8) error{OutOfMemory}![]const u8 {
return escapeStringWithFn(allocator, input, isUnreserved);
}
pub fn escapePath(allocator: std.mem.Allocator, input: []const u8) error{OutOfMemory}![]const u8 {
return escapeStringWithFn(allocator, input, isPathChar);
}
pub fn escapeQuery(allocator: std.mem.Allocator, input: []const u8) error{OutOfMemory}![]const u8 {
return escapeStringWithFn(allocator, input, isQueryChar);
}
pub fn escapeStringWithFn(allocator: std.mem.Allocator, input: []const u8, comptime keepUnescaped: fn (c: u8) bool) std.mem.Allocator.Error![]const u8 {
var outsize: usize = 0;
for (input) |c| {
outsize += if (isUnreserved(c)) @as(usize, 1) else 3;
outsize += if (keepUnescaped(c)) @as(usize, 1) else 3;
}
var output = try allocator.alloc(u8, outsize);
var outptr: usize = 0;
for (input) |c| {
if (isUnreserved(c)) {
if (keepUnescaped(c)) {
output[outptr] = c;
outptr += 1;
} else {
@ -94,13 +106,14 @@ pub fn unescapeString(allocator: std.mem.Allocator, input: []const u8) error{Out
pub const ParseError = error{ UnexpectedCharacter, InvalidFormat, InvalidPort };
/// Parses the URI or returns an error.
/// Parses the URI or returns an error. This function is not compliant, but is required to parse
/// some forms of URIs in the wild. Such as HTTP Location headers.
/// The return value will contain unescaped strings pointing into the
/// original `text`. Each component that is provided, will be non-`null`.
pub fn parse(text: []const u8) ParseError!Uri {
pub fn parseWithoutScheme(text: []const u8) ParseError!Uri {
var reader = SliceReader{ .slice = text };
var uri = Uri{
.scheme = reader.readWhile(isSchemeChar),
.scheme = "",
.user = null,
.password = null,
.host = null,
@ -110,14 +123,6 @@ pub fn parse(text: []const u8) ParseError!Uri {
.fragment = null,
};
// after the scheme, a ':' must appear
if (reader.get()) |c| {
if (c != ':')
return error.UnexpectedCharacter;
} else {
return error.InvalidFormat;
}
if (reader.peekPrefix("//")) { // authority part
std.debug.assert(reader.get().? == '/');
std.debug.assert(reader.get().? == '/');
@ -179,6 +184,76 @@ pub fn parse(text: []const u8) ParseError!Uri {
return uri;
}
/// Parses the URI or returns an error.
/// The return value will contain unescaped strings pointing into the
/// original `text`. Each component that is provided, will be non-`null`.
pub fn parse(text: []const u8) ParseError!Uri {
var reader = SliceReader{ .slice = text };
const scheme = reader.readWhile(isSchemeChar);
// after the scheme, a ':' must appear
if (reader.get()) |c| {
if (c != ':')
return error.UnexpectedCharacter;
} else {
return error.InvalidFormat;
}
var uri = try parseWithoutScheme(reader.readUntilEof());
uri.scheme = scheme;
return uri;
}
/// Resolves a URI against a base URI, conforming to RFC 3986, Section 5.
/// arena owns any memory allocated by this function.
pub fn resolve(Base: Uri, R: Uri, strict: bool, arena: std.mem.Allocator) !Uri {
var T: Uri = undefined;
if (R.scheme.len > 0 and !((!strict) and (std.mem.eql(u8, R.scheme, Base.scheme)))) {
T.scheme = R.scheme;
T.user = R.user;
T.host = R.host;
T.port = R.port;
T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path });
T.query = R.query;
} else {
if (R.host) |host| {
T.user = R.user;
T.host = host;
T.port = R.port;
T.path = R.path;
T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path });
T.query = R.query;
} else {
if (R.path.len == 0) {
T.path = Base.path;
if (R.query) |query| {
T.query = query;
} else {
T.query = Base.query;
}
} else {
if (R.path[0] == '/') {
T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path });
} else {
T.path = try std.fs.path.resolvePosix(arena, &.{ "/", Base.path, R.path });
}
T.query = R.query;
}
T.user = Base.user;
T.host = Base.host;
T.port = Base.port;
}
T.scheme = Base.scheme;
}
T.fragment = R.fragment;
return T;
}
const SliceReader = struct {
const Self = @This();
@ -284,6 +359,14 @@ fn isPathSeparator(c: u8) bool {
};
}
fn isPathChar(c: u8) bool {
return isUnreserved(c) or isSubLimit(c) or c == '/' or c == ':' or c == '@';
}
fn isQueryChar(c: u8) bool {
return isPathChar(c) or c == '?';
}
fn isQuerySeparator(c: u8) bool {
return switch (c) {
'#' => true,

View File

@ -141,11 +141,21 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
return cloned;
}
/// Insert `item` at index `n` by moving `list[n .. list.len]` to make room.
/// Insert `item` at index `n`. Moves `list[n .. list.len]` to higher indices to make room.
/// If `n` is equal to the length of the list this operation is equivalent to append.
/// This operation is O(N).
/// Invalidates pointers if additional memory is needed.
pub fn insert(self: *Self, n: usize, item: T) Allocator.Error!void {
try self.ensureUnusedCapacity(1);
self.insertAssumeCapacity(n, item);
}
/// Insert `item` at index `n`. Moves `list[n .. list.len]` to higher indices to make room.
/// If `n` is equal to the length of the list this operation is equivalent to append.
/// This operation is O(N).
/// Asserts that there is enough capacity for the new item.
pub fn insertAssumeCapacity(self: *Self, n: usize, item: T) void {
assert(self.items.len < self.capacity);
self.items.len += 1;
mem.copyBackwards(T, self.items[n + 1 .. self.items.len], self.items[n .. self.items.len - 1]);
@ -609,12 +619,21 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
return cloned;
}
/// Insert `item` at index `n`. Moves `list[n .. list.len]`
/// to higher indices to make room.
/// Insert `item` at index `n`. Moves `list[n .. list.len]` to higher indices to make room.
/// If `n` is equal to the length of the list this operation is equivalent to append.
/// This operation is O(N).
/// Invalidates pointers if additional memory is needed.
pub fn insert(self: *Self, allocator: Allocator, n: usize, item: T) Allocator.Error!void {
try self.ensureUnusedCapacity(allocator, 1);
self.insertAssumeCapacity(n, item);
}
/// Insert `item` at index `n`. Moves `list[n .. list.len]` to higher indices to make room.
/// If `n` is equal to the length of the list this operation is equivalent to append.
/// This operation is O(N).
/// Asserts that there is enough capacity for the new item.
pub fn insertAssumeCapacity(self: *Self, n: usize, item: T) void {
assert(self.items.len < self.capacity);
self.items.len += 1;
mem.copyBackwards(T, self.items[n + 1 .. self.items.len], self.items[n .. self.items.len - 1]);
@ -1309,9 +1328,9 @@ test "std.ArrayList/ArrayListUnmanaged.insert" {
var list = ArrayList(i32).init(a);
defer list.deinit();
try list.append(1);
try list.insert(0, 1);
try list.append(2);
try list.append(3);
try list.insert(2, 3);
try list.insert(0, 5);
try testing.expect(list.items[0] == 5);
try testing.expect(list.items[1] == 1);
@ -1322,9 +1341,9 @@ test "std.ArrayList/ArrayListUnmanaged.insert" {
var list = ArrayListUnmanaged(i32){};
defer list.deinit(a);
try list.append(a, 1);
try list.insert(a, 0, 1);
try list.append(a, 2);
try list.append(a, 3);
try list.insert(a, 2, 3);
try list.insert(a, 0, 5);
try testing.expect(list.items[0] == 5);
try testing.expect(list.items[1] == 1);

View File

@ -153,7 +153,8 @@ pub extern "c" fn linkat(oldfd: c.fd_t, oldpath: [*:0]const u8, newfd: c.fd_t, n
pub extern "c" fn unlink(path: [*:0]const u8) c_int;
pub extern "c" fn unlinkat(dirfd: c.fd_t, path: [*:0]const u8, flags: c_uint) c_int;
pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8;
pub extern "c" fn waitpid(pid: c.pid_t, stat_loc: ?*c_int, options: c_int) c.pid_t;
pub extern "c" fn waitpid(pid: c.pid_t, status: ?*c_int, options: c_int) c.pid_t;
pub extern "c" fn wait4(pid: c.pid_t, status: ?*c_int, options: c_int, ru: ?*c.rusage) c.pid_t;
pub extern "c" fn fork() c_int;
pub extern "c" fn access(path: [*:0]const u8, mode: c_uint) c_int;
pub extern "c" fn faccessat(dirfd: c.fd_t, path: [*:0]const u8, mode: c_uint, flags: c_uint) c_int;

View File

@ -8,6 +8,7 @@ const iovec_const = std.os.iovec_const;
pub const aarch64 = @import("darwin/aarch64.zig");
pub const x86_64 = @import("darwin/x86_64.zig");
pub const cssm = @import("darwin/cssm.zig");
const arch_bits = switch (native_arch) {
.aarch64 => @import("darwin/aarch64.zig"),
@ -2179,6 +2180,14 @@ pub fn getKernError(err: kern_return_t) KernE {
return @intToEnum(KernE, @truncate(u32, @intCast(usize, err)));
}
pub fn unexpectedKernError(err: KernE) std.os.UnexpectedError {
if (std.os.unexpected_error_tracing) {
std.debug.print("unexpected errno: {d}\n", .{@enumToInt(err)});
std.debug.dumpCurrentStackTrace(null);
}
return error.Unexpected;
}
/// Kernel return values
pub const KernE = enum(u32) {
SUCCESS = 0,
@ -3085,3 +3094,471 @@ pub const PT_DENY_ATTACH = 31;
pub const caddr_t = ?[*]u8;
pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: caddr_t, data: c_int) c_int;
pub const MachError = error{
/// Not enough permissions held to perform the requested kernel
/// call.
PermissionDenied,
} || std.os.UnexpectedError;
pub const MachTask = extern struct {
port: mach_port_name_t,
pub fn isValid(self: MachTask) bool {
return self.port != TASK_NULL;
}
pub fn pidForTask(self: MachTask) MachError!std.os.pid_t {
var pid: std.os.pid_t = undefined;
switch (getKernError(pid_for_task(self.port, &pid))) {
.SUCCESS => return pid,
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
}
pub fn allocatePort(self: MachTask, right: MACH_PORT_RIGHT) MachError!MachTask {
var out_port: mach_port_name_t = undefined;
switch (getKernError(mach_port_allocate(
self.port,
@enumToInt(right),
&out_port,
))) {
.SUCCESS => return .{ .port = out_port },
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
}
pub fn deallocatePort(self: MachTask, port: MachTask) void {
_ = getKernError(mach_port_deallocate(self.port, port.port));
}
pub fn insertRight(self: MachTask, port: MachTask, msg: MACH_MSG_TYPE) !void {
switch (getKernError(mach_port_insert_right(
self.port,
port.port,
port.port,
@enumToInt(msg),
))) {
.SUCCESS => return,
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
}
pub const PortInfo = struct {
mask: exception_mask_t,
masks: [EXC_TYPES_COUNT]exception_mask_t,
ports: [EXC_TYPES_COUNT]mach_port_t,
behaviors: [EXC_TYPES_COUNT]exception_behavior_t,
flavors: [EXC_TYPES_COUNT]thread_state_flavor_t,
count: mach_msg_type_number_t,
};
pub fn getExceptionPorts(self: MachTask, mask: exception_mask_t) !PortInfo {
var info = PortInfo{
.mask = mask,
.masks = undefined,
.ports = undefined,
.behaviors = undefined,
.flavors = undefined,
.count = 0,
};
info.count = info.ports.len / @sizeOf(mach_port_t);
switch (getKernError(task_get_exception_ports(
self.port,
info.mask,
&info.masks,
&info.count,
&info.ports,
&info.behaviors,
&info.flavors,
))) {
.SUCCESS => return info,
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
}
pub fn setExceptionPorts(
self: MachTask,
mask: exception_mask_t,
new_port: MachTask,
behavior: exception_behavior_t,
new_flavor: thread_state_flavor_t,
) !void {
switch (getKernError(task_set_exception_ports(
self.port,
mask,
new_port.port,
behavior,
new_flavor,
))) {
.SUCCESS => return,
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
}
pub const RegionInfo = struct {
pub const Tag = enum {
basic,
extended,
top,
};
base_addr: u64,
tag: Tag,
info: union {
basic: vm_region_basic_info_64,
extended: vm_region_extended_info,
top: vm_region_top_info,
},
};
pub fn getRegionInfo(
task: MachTask,
address: u64,
len: usize,
tag: RegionInfo.Tag,
) MachError!RegionInfo {
var info: RegionInfo = .{
.base_addr = address,
.tag = tag,
.info = undefined,
};
switch (tag) {
.basic => info.info = .{ .basic = undefined },
.extended => info.info = .{ .extended = undefined },
.top => info.info = .{ .top = undefined },
}
var base_len: mach_vm_size_t = if (len == 1) 2 else len;
var objname: mach_port_t = undefined;
var count: mach_msg_type_number_t = switch (tag) {
.basic => VM_REGION_BASIC_INFO_COUNT,
.extended => VM_REGION_EXTENDED_INFO_COUNT,
.top => VM_REGION_TOP_INFO_COUNT,
};
switch (getKernError(mach_vm_region(
task.port,
&info.base_addr,
&base_len,
switch (tag) {
.basic => VM_REGION_BASIC_INFO_64,
.extended => VM_REGION_EXTENDED_INFO,
.top => VM_REGION_TOP_INFO,
},
switch (tag) {
.basic => @ptrCast(vm_region_info_t, &info.info.basic),
.extended => @ptrCast(vm_region_info_t, &info.info.extended),
.top => @ptrCast(vm_region_info_t, &info.info.top),
},
&count,
&objname,
))) {
.SUCCESS => return info,
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
}
pub const RegionSubmapInfo = struct {
pub const Tag = enum {
short,
full,
};
tag: Tag,
base_addr: u64,
info: union {
short: vm_region_submap_short_info_64,
full: vm_region_submap_info_64,
},
};
pub fn getRegionSubmapInfo(
task: MachTask,
address: u64,
len: usize,
nesting_depth: u32,
tag: RegionSubmapInfo.Tag,
) MachError!RegionSubmapInfo {
var info: RegionSubmapInfo = .{
.base_addr = address,
.tag = tag,
.info = undefined,
};
switch (tag) {
.short => info.info = .{ .short = undefined },
.full => info.info = .{ .full = undefined },
}
var nesting = nesting_depth;
var base_len: mach_vm_size_t = if (len == 1) 2 else len;
var count: mach_msg_type_number_t = switch (tag) {
.short => VM_REGION_SUBMAP_SHORT_INFO_COUNT_64,
.full => VM_REGION_SUBMAP_INFO_COUNT_64,
};
switch (getKernError(mach_vm_region_recurse(
task.port,
&info.base_addr,
&base_len,
&nesting,
switch (tag) {
.short => @ptrCast(vm_region_recurse_info_t, &info.info.short),
.full => @ptrCast(vm_region_recurse_info_t, &info.info.full),
},
&count,
))) {
.SUCCESS => return info,
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
}
pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!vm_prot_t {
const info = try task.getRegionSubmapInfo(address, len, 0, .short);
return info.info.short.protection;
}
pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: vm_prot_t) MachError!void {
return task.setProtectionImpl(address, len, true, prot);
}
pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: vm_prot_t) MachError!void {
return task.setProtectionImpl(address, len, false, prot);
}
fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: vm_prot_t) MachError!void {
switch (getKernError(mach_vm_protect(task.port, address, len, @boolToInt(set_max), prot))) {
.SUCCESS => return,
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
}
/// Will write to VM even if current protection attributes specifically prohibit
/// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY
/// variant, and resetting after a successful or unsuccessful write.
pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize {
const curr_prot = try task.getCurrProtection(address, buf.len);
try task.setCurrProtection(
address,
buf.len,
PROT.READ | PROT.WRITE | PROT.COPY,
);
defer {
task.setCurrProtection(address, buf.len, curr_prot) catch {};
}
return task.writeMem(address, buf, arch);
}
pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize {
const count = buf.len;
var total_written: usize = 0;
var curr_addr = address;
const page_size = try getPageSize(task); // TODO we probably can assume value here
var out_buf = buf[0..];
while (total_written < count) {
const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written);
switch (getKernError(mach_vm_write(
task.port,
curr_addr,
@ptrToInt(out_buf.ptr),
@intCast(mach_msg_type_number_t, curr_size),
))) {
.SUCCESS => {},
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
switch (arch) {
.aarch64 => {
var mattr_value: vm_machine_attribute_val_t = MATTR_VAL_CACHE_FLUSH;
switch (getKernError(vm_machine_attribute(
task.port,
curr_addr,
curr_size,
MATTR_CACHE,
&mattr_value,
))) {
.SUCCESS => {},
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
},
.x86_64 => {},
else => unreachable,
}
out_buf = out_buf[curr_size..];
total_written += curr_size;
curr_addr += curr_size;
}
return total_written;
}
pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize {
const count = buf.len;
var total_read: usize = 0;
var curr_addr = address;
const page_size = try getPageSize(task); // TODO we probably can assume value here
var out_buf = buf[0..];
while (total_read < count) {
const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read);
var curr_bytes_read: mach_msg_type_number_t = 0;
var vm_memory: vm_offset_t = undefined;
switch (getKernError(mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) {
.SUCCESS => {},
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
@memcpy(out_buf[0..].ptr, @intToPtr([*]const u8, vm_memory), curr_bytes_read);
_ = vm_deallocate(mach_task_self(), vm_memory, curr_bytes_read);
out_buf = out_buf[curr_bytes_read..];
curr_addr += curr_bytes_read;
total_read += curr_bytes_read;
}
return total_read;
}
fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize {
var left = count;
if (page_size > 0) {
const page_offset = address % page_size;
const bytes_left_in_page = page_size - page_offset;
if (count > bytes_left_in_page) {
left = bytes_left_in_page;
}
}
return left;
}
fn getPageSize(task: MachTask) MachError!usize {
if (task.isValid()) {
var info_count = TASK_VM_INFO_COUNT;
var vm_info: task_vm_info_data_t = undefined;
switch (getKernError(task_info(
task.port,
TASK_VM_INFO,
@ptrCast(task_info_t, &vm_info),
&info_count,
))) {
.SUCCESS => return @intCast(usize, vm_info.page_size),
else => {},
}
}
var page_size: vm_size_t = undefined;
switch (getKernError(_host_page_size(mach_host_self(), &page_size))) {
.SUCCESS => return page_size,
else => |err| return unexpectedKernError(err),
}
}
pub fn basicTaskInfo(task: MachTask) MachError!mach_task_basic_info {
var info: mach_task_basic_info = undefined;
var count = MACH_TASK_BASIC_INFO_COUNT;
switch (getKernError(task_info(
task.port,
MACH_TASK_BASIC_INFO,
@ptrCast(task_info_t, &info),
&count,
))) {
.SUCCESS => return info,
else => |err| return unexpectedKernError(err),
}
}
pub fn @"resume"(task: MachTask) MachError!void {
switch (getKernError(task_resume(task.port))) {
.SUCCESS => {},
else => |err| return unexpectedKernError(err),
}
}
pub fn @"suspend"(task: MachTask) MachError!void {
switch (getKernError(task_suspend(task.port))) {
.SUCCESS => {},
else => |err| return unexpectedKernError(err),
}
}
const ThreadList = struct {
buf: []MachThread,
pub fn deinit(list: ThreadList) void {
const self_task = machTaskForSelf();
_ = vm_deallocate(
self_task.port,
@ptrToInt(list.buf.ptr),
@intCast(vm_size_t, list.buf.len * @sizeOf(mach_port_t)),
);
}
};
pub fn getThreads(task: MachTask) MachError!ThreadList {
var thread_list: mach_port_array_t = undefined;
var thread_count: mach_msg_type_number_t = undefined;
switch (getKernError(task_threads(task.port, &thread_list, &thread_count))) {
.SUCCESS => return ThreadList{ .buf = @ptrCast([*]MachThread, thread_list)[0..thread_count] },
else => |err| return unexpectedKernError(err),
}
}
};
pub const MachThread = extern struct {
port: mach_port_t,
pub fn isValid(thread: MachThread) bool {
return thread.port != THREAD_NULL;
}
pub fn getBasicInfo(thread: MachThread) MachError!thread_basic_info {
var info: thread_basic_info = undefined;
var count = THREAD_BASIC_INFO_COUNT;
switch (getKernError(thread_info(
thread.port,
THREAD_BASIC_INFO,
@ptrCast(thread_info_t, &info),
&count,
))) {
.SUCCESS => return info,
else => |err| return unexpectedKernError(err),
}
}
pub fn getIdentifierInfo(thread: MachThread) MachError!thread_identifier_info {
var info: thread_identifier_info = undefined;
var count = THREAD_IDENTIFIER_INFO_COUNT;
switch (getKernError(thread_info(
thread.port,
THREAD_IDENTIFIER_INFO,
@ptrCast(thread_info_t, &info),
&count,
))) {
.SUCCESS => return info,
else => |err| return unexpectedKernError(err),
}
}
};
pub fn machTaskForPid(pid: std.os.pid_t) MachError!MachTask {
var port: mach_port_name_t = undefined;
switch (getKernError(task_for_pid(mach_task_self(), pid, &port))) {
.SUCCESS => {},
.FAILURE => return error.PermissionDenied,
else => |err| return unexpectedKernError(err),
}
return MachTask{ .port = port };
}
pub fn machTaskForSelf() MachTask {
return .{ .port = mach_task_self() };
}

View File

@ -17,10 +17,12 @@ const Os = std.builtin.Os;
const TailQueue = std.TailQueue;
const maxInt = std.math.maxInt;
const assert = std.debug.assert;
const is_darwin = builtin.target.isDarwin();
pub const ChildProcess = struct {
pub const Id = switch (builtin.os.tag) {
.windows => windows.HANDLE,
.wasi => void,
else => os.pid_t,
};
@ -70,6 +72,43 @@ pub const ChildProcess = struct {
/// Darwin-only. Start child process in suspended state as if SIGSTOP was sent.
start_suspended: bool = false,
/// Set to true to obtain rusage information for the child process.
/// Depending on the target platform and implementation status, the
/// requested statistics may or may not be available. If they are
/// available, then the `resource_usage_statistics` field will be populated
/// after calling `wait`.
/// On Linux, this obtains rusage statistics from wait4().
request_resource_usage_statistics: bool = false,
/// This is available after calling wait if
/// `request_resource_usage_statistics` was set to `true` before calling
/// `spawn`.
resource_usage_statistics: ResourceUsageStatistics = .{},
pub const ResourceUsageStatistics = struct {
rusage: @TypeOf(rusage_init) = rusage_init,
/// Returns the peak resident set size of the child process, in bytes,
/// if available.
pub inline fn getMaxRss(rus: ResourceUsageStatistics) ?usize {
switch (builtin.os.tag) {
.linux => {
if (rus.rusage) |ru| {
return @intCast(usize, ru.maxrss) * 1024;
} else {
return null;
}
},
else => return null,
}
}
const rusage_init = switch (builtin.os.tag) {
.linux => @as(?std.os.rusage, null),
else => {},
};
};
pub const Arg0Expand = os.Arg0Expand;
pub const SpawnError = error{
@ -90,8 +129,7 @@ pub const ChildProcess = struct {
os.SetIdError ||
os.ChangeCurDirError ||
windows.CreateProcessError ||
windows.WaitForSingleObjectError ||
os.posix_spawn.Error;
windows.WaitForSingleObjectError;
pub const Term = union(enum) {
Exited: u8,
@ -143,10 +181,6 @@ pub const ChildProcess = struct {
@compileError("the target operating system cannot spawn processes");
}
if (comptime builtin.target.isDarwin()) {
return self.spawnMacos();
}
if (builtin.os.tag == .windows) {
return self.spawnWindows();
} else {
@ -337,10 +371,16 @@ pub const ChildProcess = struct {
}
fn waitUnwrapped(self: *ChildProcess) !void {
const res: os.WaitPidResult = if (comptime builtin.target.isDarwin())
try os.posix_spawn.waitpid(self.id, 0)
else
os.waitpid(self.id, 0);
const res: os.WaitPidResult = res: {
if (builtin.os.tag == .linux and self.request_resource_usage_statistics) {
var ru: std.os.rusage = undefined;
const res = os.wait4(self.id, 0, &ru);
self.resource_usage_statistics.rusage = ru;
break :res res;
}
break :res os.waitpid(self.id, 0);
};
const status = res.status;
self.cleanupStreams();
self.handleWaitResult(status);
@ -416,121 +456,6 @@ pub const ChildProcess = struct {
Term{ .Unknown = status };
}
fn spawnMacos(self: *ChildProcess) SpawnError!void {
const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0;
const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
errdefer if (self.stdin_behavior == StdIo.Pipe) destroyPipe(stdin_pipe);
const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
errdefer if (self.stdout_behavior == StdIo.Pipe) destroyPipe(stdout_pipe);
const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
errdefer if (self.stderr_behavior == StdIo.Pipe) destroyPipe(stderr_pipe);
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
const dev_null_fd = if (any_ignore)
os.openZ("/dev/null", os.O.RDWR, 0) catch |err| switch (err) {
error.PathAlreadyExists => unreachable,
error.NoSpaceLeft => unreachable,
error.FileTooBig => unreachable,
error.DeviceBusy => unreachable,
error.FileLocksNotSupported => unreachable,
error.BadPathName => unreachable, // Windows-only
error.InvalidHandle => unreachable, // WASI-only
error.WouldBlock => unreachable,
else => |e| return e,
}
else
undefined;
defer if (any_ignore) os.close(dev_null_fd);
var attr = try os.posix_spawn.Attr.init();
defer attr.deinit();
var flags: u16 = os.darwin.POSIX_SPAWN_SETSIGDEF | os.darwin.POSIX_SPAWN_SETSIGMASK;
if (self.disable_aslr) {
flags |= os.darwin._POSIX_SPAWN_DISABLE_ASLR;
}
if (self.start_suspended) {
flags |= os.darwin.POSIX_SPAWN_START_SUSPENDED;
}
try attr.set(flags);
var actions = try os.posix_spawn.Actions.init();
defer actions.deinit();
try setUpChildIoPosixSpawn(self.stdin_behavior, &actions, stdin_pipe, os.STDIN_FILENO, dev_null_fd);
try setUpChildIoPosixSpawn(self.stdout_behavior, &actions, stdout_pipe, os.STDOUT_FILENO, dev_null_fd);
try setUpChildIoPosixSpawn(self.stderr_behavior, &actions, stderr_pipe, os.STDERR_FILENO, dev_null_fd);
if (self.cwd_dir) |cwd| {
try actions.fchdir(cwd.fd);
} else if (self.cwd) |cwd| {
try actions.chdir(cwd);
}
var arena_allocator = std.heap.ArenaAllocator.init(self.allocator);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const argv_buf = try arena.allocSentinel(?[*:0]u8, self.argv.len, null);
for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
const envp = if (self.env_map) |env_map| m: {
const envp_buf = try createNullDelimitedEnvMap(arena, env_map);
break :m envp_buf.ptr;
} else std.c.environ;
const pid = try os.posix_spawn.spawnp(self.argv[0], actions, attr, argv_buf, envp);
if (self.stdin_behavior == StdIo.Pipe) {
self.stdin = File{ .handle = stdin_pipe[1] };
} else {
self.stdin = null;
}
if (self.stdout_behavior == StdIo.Pipe) {
self.stdout = File{ .handle = stdout_pipe[0] };
} else {
self.stdout = null;
}
if (self.stderr_behavior == StdIo.Pipe) {
self.stderr = File{ .handle = stderr_pipe[0] };
} else {
self.stderr = null;
}
self.id = pid;
self.term = null;
if (self.stdin_behavior == StdIo.Pipe) {
os.close(stdin_pipe[0]);
}
if (self.stdout_behavior == StdIo.Pipe) {
os.close(stdout_pipe[1]);
}
if (self.stderr_behavior == StdIo.Pipe) {
os.close(stderr_pipe[1]);
}
}
fn setUpChildIoPosixSpawn(
stdio: StdIo,
actions: *os.posix_spawn.Actions,
pipe_fd: [2]i32,
std_fileno: i32,
dev_null_fd: i32,
) !void {
switch (stdio) {
.Pipe => {
const idx: usize = if (std_fileno == 0) 0 else 1;
try actions.dup2(pipe_fd[idx], std_fileno);
try actions.close(pipe_fd[1 - idx]);
},
.Close => try actions.close(std_fileno),
.Inherit => {},
.Ignore => try actions.dup2(dev_null_fd, std_fileno),
}
}
fn spawnPosix(self: *ChildProcess) SpawnError!void {
const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0;
const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;

View File

@ -1,3 +1,5 @@
const root = @import("root");
/// Authenticated Encryption with Associated Data
pub const aead = struct {
pub const aegis = struct {
@ -66,6 +68,11 @@ pub const dh = struct {
pub const X25519 = @import("crypto/25519/x25519.zig").X25519;
};
/// Key Encapsulation Mechanisms.
pub const kem = struct {
pub const kyber_d00 = @import("crypto/kyber_d00.zig");
};
/// Elliptic-curve arithmetic.
pub const ecc = struct {
pub const Curve25519 = @import("crypto/25519/curve25519.zig").Curve25519;
@ -183,6 +190,27 @@ pub const errors = @import("crypto/errors.zig");
pub const tls = @import("crypto/tls.zig");
pub const Certificate = @import("crypto/Certificate.zig");
/// Side-channels mitigations.
pub const SideChannelsMitigations = enum {
/// No additional side-channel mitigations are applied.
/// This is the fastest mode.
none,
/// The `basic` mode protects against most practical attacks, provided that the
/// application or implements proper defenses against brute-force attacks.
/// It offers a good balance between performance and security.
basic,
/// The `medium` mode offers increased resilience against side-channel attacks,
/// making most attacks unpractical even on shared/low latency environements.
/// This is the default mode.
medium,
/// The `full` mode offers the highest level of protection against side-channel attacks.
/// Note that this doesn't cover all possible attacks (especially power analysis or
/// thread-local attacks such as cachebleed), and that the performance impact is significant.
full,
};
pub const default_side_channels_mitigations = .medium;
test {
_ = aead.aegis.Aegis128L;
_ = aead.aegis.Aegis256;
@ -217,6 +245,8 @@ test {
_ = dh.X25519;
_ = kem.kyber_d00;
_ = ecc.Curve25519;
_ = ecc.Edwards25519;
_ = ecc.P256;

View File

@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const crypto = std.crypto;
const readIntLittle = std.mem.readIntLittle;
const writeIntLittle = std.mem.writeIntLittle;
@ -6,6 +7,12 @@ const writeIntLittle = std.mem.writeIntLittle;
const NonCanonicalError = crypto.errors.NonCanonicalError;
const NotSquareError = crypto.errors.NotSquareError;
// Inline conditionally, when it can result in large code generation.
const bloaty_inline = switch (builtin.mode) {
.ReleaseSafe, .ReleaseFast => .Inline,
.Debug, .ReleaseSmall => .Unspecified,
};
pub const Fe = struct {
limbs: [5]u64,
@ -264,7 +271,7 @@ pub const Fe = struct {
}
/// Multiply two field elements
pub inline fn mul(a: Fe, b: Fe) Fe {
pub fn mul(a: Fe, b: Fe) callconv(bloaty_inline) Fe {
var ax: [5]u128 = undefined;
var bx: [5]u128 = undefined;
var a19: [5]u128 = undefined;

View File

@ -32,62 +32,54 @@ pub const Block = struct {
/// Encrypt a block with a round key.
pub inline fn encrypt(block: Block, round_key: Block) Block {
return Block{
.repr = asm (
.repr = (asm (
\\ mov %[out].16b, %[in].16b
\\ aese %[out].16b, %[zero].16b
\\ aesmc %[out].16b, %[out].16b
\\ eor %[out].16b, %[out].16b, %[rk].16b
: [out] "=&x" (-> BlockVec),
: [in] "x" (block.repr),
[rk] "x" (round_key.repr),
[zero] "x" (zero),
),
)) ^ round_key.repr,
};
}
/// Encrypt a block with the last round key.
pub inline fn encryptLast(block: Block, round_key: Block) Block {
return Block{
.repr = asm (
.repr = (asm (
\\ mov %[out].16b, %[in].16b
\\ aese %[out].16b, %[zero].16b
\\ eor %[out].16b, %[out].16b, %[rk].16b
: [out] "=&x" (-> BlockVec),
: [in] "x" (block.repr),
[rk] "x" (round_key.repr),
[zero] "x" (zero),
),
)) ^ round_key.repr,
};
}
/// Decrypt a block with a round key.
pub inline fn decrypt(block: Block, inv_round_key: Block) Block {
return Block{
.repr = asm (
.repr = (asm (
\\ mov %[out].16b, %[in].16b
\\ aesd %[out].16b, %[zero].16b
\\ aesimc %[out].16b, %[out].16b
\\ eor %[out].16b, %[out].16b, %[rk].16b
: [out] "=&x" (-> BlockVec),
: [in] "x" (block.repr),
[rk] "x" (inv_round_key.repr),
[zero] "x" (zero),
),
)) ^ inv_round_key.repr,
};
}
/// Decrypt a block with the last round key.
pub inline fn decryptLast(block: Block, inv_round_key: Block) Block {
return Block{
.repr = asm (
.repr = (asm (
\\ mov %[out].16b, %[in].16b
\\ aesd %[out].16b, %[zero].16b
\\ eor %[out].16b, %[out].16b, %[rk].16b
: [out] "=&x" (-> BlockVec),
: [in] "x" (block.repr),
[rk] "x" (inv_round_key.repr),
[zero] "x" (zero),
),
)) ^ inv_round_key.repr,
};
}

View File

@ -1,11 +1,11 @@
// Based on Go stdlib implementation
const std = @import("../../std.zig");
const math = std.math;
const mem = std.mem;
const BlockVec = [4]u32;
const side_channels_mitigations = std.options.side_channels_mitigations;
/// A single AES block.
pub const Block = struct {
pub const block_length: usize = 16;
@ -15,20 +15,20 @@ pub const Block = struct {
/// Convert a byte sequence into an internal representation.
pub inline fn fromBytes(bytes: *const [16]u8) Block {
const s0 = mem.readIntBig(u32, bytes[0..4]);
const s1 = mem.readIntBig(u32, bytes[4..8]);
const s2 = mem.readIntBig(u32, bytes[8..12]);
const s3 = mem.readIntBig(u32, bytes[12..16]);
const s0 = mem.readIntLittle(u32, bytes[0..4]);
const s1 = mem.readIntLittle(u32, bytes[4..8]);
const s2 = mem.readIntLittle(u32, bytes[8..12]);
const s3 = mem.readIntLittle(u32, bytes[12..16]);
return Block{ .repr = BlockVec{ s0, s1, s2, s3 } };
}
/// Convert the internal representation of a block into a byte sequence.
pub inline fn toBytes(block: Block) [16]u8 {
var bytes: [16]u8 = undefined;
mem.writeIntBig(u32, bytes[0..4], block.repr[0]);
mem.writeIntBig(u32, bytes[4..8], block.repr[1]);
mem.writeIntBig(u32, bytes[8..12], block.repr[2]);
mem.writeIntBig(u32, bytes[12..16], block.repr[3]);
mem.writeIntLittle(u32, bytes[0..4], block.repr[0]);
mem.writeIntLittle(u32, bytes[4..8], block.repr[1]);
mem.writeIntLittle(u32, bytes[8..12], block.repr[2]);
mem.writeIntLittle(u32, bytes[12..16], block.repr[3]);
return bytes;
}
@ -50,32 +50,93 @@ pub const Block = struct {
const s2 = block.repr[2];
const s3 = block.repr[3];
const t0 = round_key.repr[0] ^ table_encrypt[0][@truncate(u8, s0 >> 24)] ^ table_encrypt[1][@truncate(u8, s1 >> 16)] ^ table_encrypt[2][@truncate(u8, s2 >> 8)] ^ table_encrypt[3][@truncate(u8, s3)];
const t1 = round_key.repr[1] ^ table_encrypt[0][@truncate(u8, s1 >> 24)] ^ table_encrypt[1][@truncate(u8, s2 >> 16)] ^ table_encrypt[2][@truncate(u8, s3 >> 8)] ^ table_encrypt[3][@truncate(u8, s0)];
const t2 = round_key.repr[2] ^ table_encrypt[0][@truncate(u8, s2 >> 24)] ^ table_encrypt[1][@truncate(u8, s3 >> 16)] ^ table_encrypt[2][@truncate(u8, s0 >> 8)] ^ table_encrypt[3][@truncate(u8, s1)];
const t3 = round_key.repr[3] ^ table_encrypt[0][@truncate(u8, s3 >> 24)] ^ table_encrypt[1][@truncate(u8, s0 >> 16)] ^ table_encrypt[2][@truncate(u8, s1 >> 8)] ^ table_encrypt[3][@truncate(u8, s2)];
var x: [4]u32 = undefined;
x = table_lookup(&table_encrypt, @truncate(u8, s0), @truncate(u8, s1 >> 8), @truncate(u8, s2 >> 16), @truncate(u8, s3 >> 24));
var t0 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_encrypt, @truncate(u8, s1), @truncate(u8, s2 >> 8), @truncate(u8, s3 >> 16), @truncate(u8, s0 >> 24));
var t1 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_encrypt, @truncate(u8, s2), @truncate(u8, s3 >> 8), @truncate(u8, s0 >> 16), @truncate(u8, s1 >> 24));
var t2 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_encrypt, @truncate(u8, s3), @truncate(u8, s0 >> 8), @truncate(u8, s1 >> 16), @truncate(u8, s2 >> 24));
var t3 = x[0] ^ x[1] ^ x[2] ^ x[3];
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = BlockVec{ t0, t1, t2, t3 } };
}
/// Encrypt a block with a round key *WITHOUT ANY PROTECTION AGAINST SIDE CHANNELS*
pub inline fn encryptUnprotected(block: Block, round_key: Block) Block {
const s0 = block.repr[0];
const s1 = block.repr[1];
const s2 = block.repr[2];
const s3 = block.repr[3];
var x: [4]u32 = undefined;
x = .{
table_encrypt[0][@truncate(u8, s0)],
table_encrypt[1][@truncate(u8, s1 >> 8)],
table_encrypt[2][@truncate(u8, s2 >> 16)],
table_encrypt[3][@truncate(u8, s3 >> 24)],
};
var t0 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_encrypt[0][@truncate(u8, s1)],
table_encrypt[1][@truncate(u8, s2 >> 8)],
table_encrypt[2][@truncate(u8, s3 >> 16)],
table_encrypt[3][@truncate(u8, s0 >> 24)],
};
var t1 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_encrypt[0][@truncate(u8, s2)],
table_encrypt[1][@truncate(u8, s3 >> 8)],
table_encrypt[2][@truncate(u8, s0 >> 16)],
table_encrypt[3][@truncate(u8, s1 >> 24)],
};
var t2 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_encrypt[0][@truncate(u8, s3)],
table_encrypt[1][@truncate(u8, s0 >> 8)],
table_encrypt[2][@truncate(u8, s1 >> 16)],
table_encrypt[3][@truncate(u8, s2 >> 24)],
};
var t3 = x[0] ^ x[1] ^ x[2] ^ x[3];
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = BlockVec{ t0, t1, t2, t3 } };
}
/// Encrypt a block with the last round key.
pub inline fn encryptLast(block: Block, round_key: Block) Block {
const t0 = block.repr[0];
const t1 = block.repr[1];
const t2 = block.repr[2];
const t3 = block.repr[3];
const s0 = block.repr[0];
const s1 = block.repr[1];
const s2 = block.repr[2];
const s3 = block.repr[3];
// Last round uses s-box directly and XORs to produce output.
var s0 = @as(u32, sbox_encrypt[t0 >> 24]) << 24 | @as(u32, sbox_encrypt[t1 >> 16 & 0xff]) << 16 | @as(u32, sbox_encrypt[t2 >> 8 & 0xff]) << 8 | @as(u32, sbox_encrypt[t3 & 0xff]);
var s1 = @as(u32, sbox_encrypt[t1 >> 24]) << 24 | @as(u32, sbox_encrypt[t2 >> 16 & 0xff]) << 16 | @as(u32, sbox_encrypt[t3 >> 8 & 0xff]) << 8 | @as(u32, sbox_encrypt[t0 & 0xff]);
var s2 = @as(u32, sbox_encrypt[t2 >> 24]) << 24 | @as(u32, sbox_encrypt[t3 >> 16 & 0xff]) << 16 | @as(u32, sbox_encrypt[t0 >> 8 & 0xff]) << 8 | @as(u32, sbox_encrypt[t1 & 0xff]);
var s3 = @as(u32, sbox_encrypt[t3 >> 24]) << 24 | @as(u32, sbox_encrypt[t0 >> 16 & 0xff]) << 16 | @as(u32, sbox_encrypt[t1 >> 8 & 0xff]) << 8 | @as(u32, sbox_encrypt[t2 & 0xff]);
s0 ^= round_key.repr[0];
s1 ^= round_key.repr[1];
s2 ^= round_key.repr[2];
s3 ^= round_key.repr[3];
var x: [4]u8 = undefined;
x = sbox_lookup(&sbox_encrypt, @truncate(u8, s3 >> 24), @truncate(u8, s2 >> 16), @truncate(u8, s1 >> 8), @truncate(u8, s0));
var t0 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
x = sbox_lookup(&sbox_encrypt, @truncate(u8, s0 >> 24), @truncate(u8, s3 >> 16), @truncate(u8, s2 >> 8), @truncate(u8, s1));
var t1 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
x = sbox_lookup(&sbox_encrypt, @truncate(u8, s1 >> 24), @truncate(u8, s0 >> 16), @truncate(u8, s3 >> 8), @truncate(u8, s2));
var t2 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
x = sbox_lookup(&sbox_encrypt, @truncate(u8, s2 >> 24), @truncate(u8, s1 >> 16), @truncate(u8, s0 >> 8), @truncate(u8, s3));
var t3 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
return Block{ .repr = BlockVec{ s0, s1, s2, s3 } };
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = BlockVec{ t0, t1, t2, t3 } };
}
/// Decrypt a block with a round key.
@ -85,32 +146,93 @@ pub const Block = struct {
const s2 = block.repr[2];
const s3 = block.repr[3];
const t0 = round_key.repr[0] ^ table_decrypt[0][@truncate(u8, s0 >> 24)] ^ table_decrypt[1][@truncate(u8, s3 >> 16)] ^ table_decrypt[2][@truncate(u8, s2 >> 8)] ^ table_decrypt[3][@truncate(u8, s1)];
const t1 = round_key.repr[1] ^ table_decrypt[0][@truncate(u8, s1 >> 24)] ^ table_decrypt[1][@truncate(u8, s0 >> 16)] ^ table_decrypt[2][@truncate(u8, s3 >> 8)] ^ table_decrypt[3][@truncate(u8, s2)];
const t2 = round_key.repr[2] ^ table_decrypt[0][@truncate(u8, s2 >> 24)] ^ table_decrypt[1][@truncate(u8, s1 >> 16)] ^ table_decrypt[2][@truncate(u8, s0 >> 8)] ^ table_decrypt[3][@truncate(u8, s3)];
const t3 = round_key.repr[3] ^ table_decrypt[0][@truncate(u8, s3 >> 24)] ^ table_decrypt[1][@truncate(u8, s2 >> 16)] ^ table_decrypt[2][@truncate(u8, s1 >> 8)] ^ table_decrypt[3][@truncate(u8, s0)];
var x: [4]u32 = undefined;
x = table_lookup(&table_decrypt, @truncate(u8, s0), @truncate(u8, s3 >> 8), @truncate(u8, s2 >> 16), @truncate(u8, s1 >> 24));
var t0 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_decrypt, @truncate(u8, s1), @truncate(u8, s0 >> 8), @truncate(u8, s3 >> 16), @truncate(u8, s2 >> 24));
var t1 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_decrypt, @truncate(u8, s2), @truncate(u8, s1 >> 8), @truncate(u8, s0 >> 16), @truncate(u8, s3 >> 24));
var t2 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = table_lookup(&table_decrypt, @truncate(u8, s3), @truncate(u8, s2 >> 8), @truncate(u8, s1 >> 16), @truncate(u8, s0 >> 24));
var t3 = x[0] ^ x[1] ^ x[2] ^ x[3];
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = BlockVec{ t0, t1, t2, t3 } };
}
/// Decrypt a block with a round key *WITHOUT ANY PROTECTION AGAINST SIDE CHANNELS*
pub inline fn decryptUnprotected(block: Block, round_key: Block) Block {
const s0 = block.repr[0];
const s1 = block.repr[1];
const s2 = block.repr[2];
const s3 = block.repr[3];
var x: [4]u32 = undefined;
x = .{
table_decrypt[0][@truncate(u8, s0)],
table_decrypt[1][@truncate(u8, s3 >> 8)],
table_decrypt[2][@truncate(u8, s2 >> 16)],
table_decrypt[3][@truncate(u8, s1 >> 24)],
};
var t0 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_decrypt[0][@truncate(u8, s1)],
table_decrypt[1][@truncate(u8, s0 >> 8)],
table_decrypt[2][@truncate(u8, s3 >> 16)],
table_decrypt[3][@truncate(u8, s2 >> 24)],
};
var t1 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_decrypt[0][@truncate(u8, s2)],
table_decrypt[1][@truncate(u8, s1 >> 8)],
table_decrypt[2][@truncate(u8, s0 >> 16)],
table_decrypt[3][@truncate(u8, s3 >> 24)],
};
var t2 = x[0] ^ x[1] ^ x[2] ^ x[3];
x = .{
table_decrypt[0][@truncate(u8, s3)],
table_decrypt[1][@truncate(u8, s2 >> 8)],
table_decrypt[2][@truncate(u8, s1 >> 16)],
table_decrypt[3][@truncate(u8, s0 >> 24)],
};
var t3 = x[0] ^ x[1] ^ x[2] ^ x[3];
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = BlockVec{ t0, t1, t2, t3 } };
}
/// Decrypt a block with the last round key.
pub inline fn decryptLast(block: Block, round_key: Block) Block {
const t0 = block.repr[0];
const t1 = block.repr[1];
const t2 = block.repr[2];
const t3 = block.repr[3];
const s0 = block.repr[0];
const s1 = block.repr[1];
const s2 = block.repr[2];
const s3 = block.repr[3];
// Last round uses s-box directly and XORs to produce output.
var s0 = @as(u32, sbox_decrypt[t0 >> 24]) << 24 | @as(u32, sbox_decrypt[t3 >> 16 & 0xff]) << 16 | @as(u32, sbox_decrypt[t2 >> 8 & 0xff]) << 8 | @as(u32, sbox_decrypt[t1 & 0xff]);
var s1 = @as(u32, sbox_decrypt[t1 >> 24]) << 24 | @as(u32, sbox_decrypt[t0 >> 16 & 0xff]) << 16 | @as(u32, sbox_decrypt[t3 >> 8 & 0xff]) << 8 | @as(u32, sbox_decrypt[t2 & 0xff]);
var s2 = @as(u32, sbox_decrypt[t2 >> 24]) << 24 | @as(u32, sbox_decrypt[t1 >> 16 & 0xff]) << 16 | @as(u32, sbox_decrypt[t0 >> 8 & 0xff]) << 8 | @as(u32, sbox_decrypt[t3 & 0xff]);
var s3 = @as(u32, sbox_decrypt[t3 >> 24]) << 24 | @as(u32, sbox_decrypt[t2 >> 16 & 0xff]) << 16 | @as(u32, sbox_decrypt[t1 >> 8 & 0xff]) << 8 | @as(u32, sbox_decrypt[t0 & 0xff]);
s0 ^= round_key.repr[0];
s1 ^= round_key.repr[1];
s2 ^= round_key.repr[2];
s3 ^= round_key.repr[3];
var x: [4]u8 = undefined;
x = sbox_lookup(&sbox_decrypt, @truncate(u8, s1 >> 24), @truncate(u8, s2 >> 16), @truncate(u8, s3 >> 8), @truncate(u8, s0));
var t0 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
x = sbox_lookup(&sbox_decrypt, @truncate(u8, s2 >> 24), @truncate(u8, s3 >> 16), @truncate(u8, s0 >> 8), @truncate(u8, s1));
var t1 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
x = sbox_lookup(&sbox_decrypt, @truncate(u8, s3 >> 24), @truncate(u8, s0 >> 16), @truncate(u8, s1 >> 8), @truncate(u8, s2));
var t2 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
x = sbox_lookup(&sbox_decrypt, @truncate(u8, s0 >> 24), @truncate(u8, s1 >> 16), @truncate(u8, s2 >> 8), @truncate(u8, s3));
var t3 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
return Block{ .repr = BlockVec{ s0, s1, s2, s3 } };
t0 ^= round_key.repr[0];
t1 ^= round_key.repr[1];
t2 ^= round_key.repr[2];
t3 ^= round_key.repr[3];
return Block{ .repr = BlockVec{ t0, t1, t2, t3 } };
}
/// Apply the bitwise XOR operation to the content of two blocks.
@ -226,7 +348,8 @@ fn KeySchedule(comptime Aes: type) type {
const subw = struct {
// Apply sbox_encrypt to each byte in w.
fn func(w: u32) u32 {
return @as(u32, sbox_encrypt[w >> 24]) << 24 | @as(u32, sbox_encrypt[w >> 16 & 0xff]) << 16 | @as(u32, sbox_encrypt[w >> 8 & 0xff]) << 8 | @as(u32, sbox_encrypt[w & 0xff]);
const x = sbox_lookup(&sbox_key_schedule, @truncate(u8, w), @truncate(u8, w >> 8), @truncate(u8, w >> 16), @truncate(u8, w >> 24));
return @as(u32, x[3]) << 24 | @as(u32, x[2]) << 16 | @as(u32, x[1]) << 8 | @as(u32, x[0]);
}
}.func;
@ -244,6 +367,10 @@ fn KeySchedule(comptime Aes: type) type {
}
round_keys[i / 4].repr[i % 4] = round_keys[(i - words_in_key) / 4].repr[(i - words_in_key) % 4] ^ t;
}
i = 0;
inline while (i < round_keys.len * 4) : (i += 1) {
round_keys[i / 4].repr[i % 4] = @byteSwap(round_keys[i / 4].repr[i % 4]);
}
return Self{ .round_keys = round_keys };
}
@ -257,11 +384,13 @@ fn KeySchedule(comptime Aes: type) type {
const ei = total_words - i - 4;
comptime var j: usize = 0;
inline while (j < 4) : (j += 1) {
var x = round_keys[(ei + j) / 4].repr[(ei + j) % 4];
var rk = round_keys[(ei + j) / 4].repr[(ei + j) % 4];
if (i > 0 and i + 4 < total_words) {
x = table_decrypt[0][sbox_encrypt[x >> 24]] ^ table_decrypt[1][sbox_encrypt[x >> 16 & 0xff]] ^ table_decrypt[2][sbox_encrypt[x >> 8 & 0xff]] ^ table_decrypt[3][sbox_encrypt[x & 0xff]];
const x = sbox_lookup(&sbox_key_schedule, @truncate(u8, rk >> 24), @truncate(u8, rk >> 16), @truncate(u8, rk >> 8), @truncate(u8, rk));
const y = table_lookup(&table_decrypt, x[3], x[2], x[1], x[0]);
rk = y[0] ^ y[1] ^ y[2] ^ y[3];
}
inv_round_keys[(i + j) / 4].repr[(i + j) % 4] = x;
inv_round_keys[(i + j) / 4].repr[(i + j) % 4] = rk;
}
}
return Self{ .round_keys = inv_round_keys };
@ -293,7 +422,17 @@ pub fn AesEncryptCtx(comptime Aes: type) type {
const round_keys = ctx.key_schedule.round_keys;
var t = Block.fromBytes(src).xorBlocks(round_keys[0]);
comptime var i = 1;
inline while (i < rounds) : (i += 1) {
if (side_channels_mitigations == .full) {
inline while (i < rounds) : (i += 1) {
t = t.encrypt(round_keys[i]);
}
} else {
inline while (i < 5) : (i += 1) {
t = t.encrypt(round_keys[i]);
}
inline while (i < rounds - 1) : (i += 1) {
t = t.encryptUnprotected(round_keys[i]);
}
t = t.encrypt(round_keys[i]);
}
t = t.encryptLast(round_keys[rounds]);
@ -305,7 +444,17 @@ pub fn AesEncryptCtx(comptime Aes: type) type {
const round_keys = ctx.key_schedule.round_keys;
var t = Block.fromBytes(&counter).xorBlocks(round_keys[0]);
comptime var i = 1;
inline while (i < rounds) : (i += 1) {
if (side_channels_mitigations == .full) {
inline while (i < rounds) : (i += 1) {
t = t.encrypt(round_keys[i]);
}
} else {
inline while (i < 5) : (i += 1) {
t = t.encrypt(round_keys[i]);
}
inline while (i < rounds - 1) : (i += 1) {
t = t.encryptUnprotected(round_keys[i]);
}
t = t.encrypt(round_keys[i]);
}
t = t.encryptLast(round_keys[rounds]);
@ -359,7 +508,17 @@ pub fn AesDecryptCtx(comptime Aes: type) type {
const inv_round_keys = ctx.key_schedule.round_keys;
var t = Block.fromBytes(src).xorBlocks(inv_round_keys[0]);
comptime var i = 1;
inline while (i < rounds) : (i += 1) {
if (side_channels_mitigations == .full) {
inline while (i < rounds) : (i += 1) {
t = t.decrypt(inv_round_keys[i]);
}
} else {
inline while (i < 5) : (i += 1) {
t = t.decrypt(inv_round_keys[i]);
}
inline while (i < rounds - 1) : (i += 1) {
t = t.decryptUnprotected(inv_round_keys[i]);
}
t = t.decrypt(inv_round_keys[i]);
}
t = t.decryptLast(inv_round_keys[rounds]);
@ -428,10 +587,11 @@ const powx = init: {
break :init array;
};
const sbox_encrypt align(64) = generateSbox(false);
const sbox_decrypt align(64) = generateSbox(true);
const table_encrypt align(64) = generateTable(false);
const table_decrypt align(64) = generateTable(true);
const sbox_encrypt align(64) = generateSbox(false); // S-box for encryption
const sbox_key_schedule align(64) = generateSbox(false); // S-box only for key schedule, so that it uses distinct L1 cache entries than the S-box used for encryption
const sbox_decrypt align(64) = generateSbox(true); // S-box for decryption
const table_encrypt align(64) = generateTable(false); // 4-byte LUTs for encryption
const table_decrypt align(64) = generateTable(true); // 4-byte LUTs for decryption
// Generate S-box substitution values.
fn generateSbox(invert: bool) [256]u8 {
@ -472,14 +632,14 @@ fn generateTable(invert: bool) [4][256]u32 {
var table: [4][256]u32 = undefined;
for (generateSbox(invert), 0..) |value, index| {
table[0][index] = mul(value, if (invert) 0xb else 0x3);
table[0][index] |= math.shl(u32, mul(value, if (invert) 0xd else 0x1), 8);
table[0][index] |= math.shl(u32, mul(value, if (invert) 0x9 else 0x1), 16);
table[0][index] |= math.shl(u32, mul(value, if (invert) 0xe else 0x2), 24);
table[0][index] = math.shl(u32, mul(value, if (invert) 0xb else 0x3), 24);
table[0][index] |= math.shl(u32, mul(value, if (invert) 0xd else 0x1), 16);
table[0][index] |= math.shl(u32, mul(value, if (invert) 0x9 else 0x1), 8);
table[0][index] |= mul(value, if (invert) 0xe else 0x2);
table[1][index] = math.rotr(u32, table[0][index], 8);
table[2][index] = math.rotr(u32, table[0][index], 16);
table[3][index] = math.rotr(u32, table[0][index], 24);
table[1][index] = math.rotl(u32, table[0][index], 8);
table[2][index] = math.rotl(u32, table[0][index], 16);
table[3][index] = math.rotl(u32, table[0][index], 24);
}
return table;
@ -506,3 +666,82 @@ fn mul(a: u8, b: u8) u8 {
return @truncate(u8, s);
}
const cache_line_bytes = 64;
inline fn sbox_lookup(sbox: *align(64) const [256]u8, idx0: u8, idx1: u8, idx2: u8, idx3: u8) [4]u8 {
if (side_channels_mitigations == .none) {
return [4]u8{
sbox[idx0],
sbox[idx1],
sbox[idx2],
sbox[idx3],
};
} else {
const stride = switch (side_channels_mitigations) {
.none => unreachable,
.basic => sbox.len / 4,
.medium => sbox.len / (sbox.len / cache_line_bytes) * 2,
.full => sbox.len / (sbox.len / cache_line_bytes),
};
const of0 = idx0 % stride;
const of1 = idx1 % stride;
const of2 = idx2 % stride;
const of3 = idx3 % stride;
var t: [4][sbox.len / stride]u8 align(64) = undefined;
var i: usize = 0;
while (i < t[0].len) : (i += 1) {
const tx = sbox[i * stride ..];
t[0][i] = tx[of0];
t[1][i] = tx[of1];
t[2][i] = tx[of2];
t[3][i] = tx[of3];
}
std.mem.doNotOptimizeAway(t);
return [4]u8{
t[0][idx0 / stride],
t[1][idx1 / stride],
t[2][idx2 / stride],
t[3][idx3 / stride],
};
}
}
inline fn table_lookup(table: *align(64) const [4][256]u32, idx0: u8, idx1: u8, idx2: u8, idx3: u8) [4]u32 {
if (side_channels_mitigations == .none) {
return [4]u32{
table[0][idx0],
table[1][idx1],
table[2][idx2],
table[3][idx3],
};
} else {
const table_bytes = @sizeOf(@TypeOf(table[0]));
const stride = switch (side_channels_mitigations) {
.none => unreachable,
.basic => table[0].len / 4,
.medium => table[0].len / (table_bytes / cache_line_bytes) * 2,
.full => table[0].len / (table_bytes / cache_line_bytes),
};
const of0 = idx0 % stride;
const of1 = idx1 % stride;
const of2 = idx2 % stride;
const of3 = idx3 % stride;
var t: [4][table[0].len / stride]u32 align(64) = undefined;
var i: usize = 0;
while (i < t[0].len) : (i += 1) {
const tx = table[0][i * stride ..];
t[0][i] = tx[of0];
t[1][i] = tx[of1];
t[2][i] = tx[of2];
t[3][i] = tx[of3];
}
std.mem.doNotOptimizeAway(t);
return [4]u32{
t[0][idx0 / stride],
math.rotl(u32, t[1][idx1 / stride], 8),
math.rotl(u32, t[2][idx2 / stride], 16),
math.rotl(u32, t[3][idx3 / stride], 24),
};
}
}

View File

@ -138,40 +138,39 @@ fn initHash(
}
fn blake2bLong(out: []u8, in: []const u8) void {
var b2 = Blake2b512.init(.{ .expected_out_bits = math.min(512, out.len * 8) });
const H = Blake2b512;
var outlen_bytes: [4]u8 = undefined;
mem.writeIntLittle(u32, &outlen_bytes, @intCast(u32, out.len));
var buffer: [Blake2b512.digest_length]u8 = undefined;
mem.writeIntLittle(u32, buffer[0..4], @intCast(u32, out.len));
b2.update(buffer[0..4]);
b2.update(in);
b2.final(&buffer);
var out_buf: [H.digest_length]u8 = undefined;
if (out.len <= Blake2b512.digest_length) {
mem.copy(u8, out, buffer[0..out.len]);
if (out.len <= H.digest_length) {
var h = H.init(.{ .expected_out_bits = out.len * 8 });
h.update(&outlen_bytes);
h.update(in);
h.final(&out_buf);
mem.copy(u8, out, out_buf[0..out.len]);
return;
}
b2 = Blake2b512.init(.{});
mem.copy(u8, out, buffer[0..32]);
var out_slice = out[32..];
while (out_slice.len > Blake2b512.digest_length) : ({
out_slice = out_slice[32..];
b2 = Blake2b512.init(.{});
}) {
b2.update(&buffer);
b2.final(&buffer);
mem.copy(u8, out_slice, buffer[0..32]);
}
var h = H.init(.{});
h.update(&outlen_bytes);
h.update(in);
h.final(&out_buf);
var out_slice = out;
mem.copy(u8, out_slice, out_buf[0 .. H.digest_length / 2]);
out_slice = out_slice[H.digest_length / 2 ..];
var r = Blake2b512.digest_length;
if (out.len % Blake2b512.digest_length > 0) {
r = ((out.len + 31) / 32) - 2;
b2 = Blake2b512.init(.{ .expected_out_bits = r * 8 });
var in_buf: [H.digest_length]u8 = undefined;
while (out_slice.len > H.digest_length) {
mem.copy(u8, &in_buf, &out_buf);
H.hash(&in_buf, &out_buf, .{});
mem.copy(u8, out_slice, out_buf[0 .. H.digest_length / 2]);
out_slice = out_slice[H.digest_length / 2 ..];
}
b2.update(&buffer);
b2.final(&buffer);
mem.copy(u8, out_slice, buffer[0..r]);
mem.copy(u8, &in_buf, &out_buf);
H.hash(&in_buf, &out_buf, .{ .expected_out_bits = out_slice.len * 8 });
mem.copy(u8, out_slice, out_buf[0..out_slice.len]);
}
fn initBlocks(

View File

@ -27,6 +27,8 @@ const hashes = [_]Crypto{
Crypto{ .ty = crypto.hash.sha3.Sha3_512, .name = "sha3-512" },
Crypto{ .ty = crypto.hash.sha3.Shake128, .name = "shake-128" },
Crypto{ .ty = crypto.hash.sha3.Shake256, .name = "shake-256" },
Crypto{ .ty = crypto.hash.sha3.TurboShake128(null), .name = "turboshake-128" },
Crypto{ .ty = crypto.hash.sha3.TurboShake256(null), .name = "turboshake-256" },
Crypto{ .ty = crypto.hash.Gimli, .name = "gimli-hash" },
Crypto{ .ty = crypto.hash.blake2.Blake2s256, .name = "blake2s" },
Crypto{ .ty = crypto.hash.blake2.Blake2b512, .name = "blake2b" },
@ -201,6 +203,72 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime
return throughput;
}
const kems = [_]Crypto{
Crypto{ .ty = crypto.kem.kyber_d00.Kyber512, .name = "kyber512d00" },
Crypto{ .ty = crypto.kem.kyber_d00.Kyber768, .name = "kyber768d00" },
Crypto{ .ty = crypto.kem.kyber_d00.Kyber1024, .name = "kyber1024d00" },
};
pub fn benchmarkKem(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 {
const key_pair = try Kem.KeyPair.create(null);
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < kems_count) : (i += 1) {
const e = key_pair.public_key.encaps(null);
mem.doNotOptimizeAway(&e);
}
}
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, kems_count / elapsed_s);
return throughput;
}
pub fn benchmarkKemDecaps(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 {
const key_pair = try Kem.KeyPair.create(null);
const e = key_pair.public_key.encaps(null);
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < kems_count) : (i += 1) {
const ss2 = try key_pair.secret_key.decaps(&e.ciphertext);
mem.doNotOptimizeAway(&ss2);
}
}
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, kems_count / elapsed_s);
return throughput;
}
pub fn benchmarkKemKeyGen(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 {
var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < kems_count) : (i += 1) {
const key_pair = try Kem.KeyPair.create(null);
mem.doNotOptimizeAway(&key_pair);
}
}
const end = timer.read();
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
const throughput = @floatToInt(u64, kems_count / elapsed_s);
return throughput;
}
const aeads = [_]Crypto{
Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },
Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
@ -483,4 +551,25 @@ pub fn main() !void {
try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput });
}
}
inline for (kems) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkKem(E.ty, mode(1000));
try stdout.print("{s:>17}: {:10} encaps/s\n", .{ E.name, throughput });
}
}
inline for (kems) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkKemDecaps(E.ty, mode(25000));
try stdout.print("{s:>17}: {:10} decaps/s\n", .{ E.name, throughput });
}
}
inline for (kems) |E| {
if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) {
const throughput = try benchmarkKemKeyGen(E.ty, mode(25000));
try stdout.print("{s:>17}: {:10} keygen/s\n", .{ E.name, throughput });
}
}
}

View File

@ -200,7 +200,7 @@ const CompressGeneric = struct {
}
};
const compress = if (builtin.cpu.arch == .x86_64 and builtin.zig_backend != .stage2_c)
const compress = if (builtin.cpu.arch == .x86_64)
CompressVectorized.compress
else
CompressGeneric.compress;

View File

@ -152,7 +152,7 @@ pub const State = struct {
self.endianSwap();
}
pub const permute = if (builtin.cpu.arch == .x86_64 and builtin.zig_backend != .stage2_c) impl: {
pub const permute = if (builtin.cpu.arch == .x86_64) impl: {
break :impl permute_vectorized;
} else if (builtin.mode == .ReleaseSmall) impl: {
break :impl permute_small;

View File

@ -175,12 +175,12 @@ pub fn KeccakF(comptime f: u11) type {
/// Apply a (possibly) reduced-round permutation to the state.
pub fn permuteR(self: *Self, comptime rounds: u5) void {
var i = RC.len - rounds;
while (i < rounds - rounds % 3) : (i += 3) {
while (i < RC.len - RC.len % 3) : (i += 3) {
self.round(RC[i]);
self.round(RC[i + 1]);
self.round(RC[i + 2]);
}
while (i < rounds) : (i += 1) {
while (i < RC.len) : (i += 1) {
self.round(RC[i]);
}
}
@ -231,7 +231,7 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime delim: u8, compti
bytes = bytes[rate..];
}
if (bytes.len > 0) {
self.st.addBytes(bytes[0..]);
mem.copy(u8, &self.buf, bytes);
self.offset = bytes.len;
}
}

1780
lib/std/crypto/kyber_d00.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,20 @@ pub const Keccak_512 = @compileError("Deprecated: use `Keccak512` instead");
pub const Shake128 = Shake(128);
pub const Shake256 = Shake(256);
/// TurboSHAKE128 is a XOF (a secure hash function with a variable output length), with a 128 bit security level.
/// It is based on the same permutation as SHA3 and SHAKE128, but which much higher performance.
/// The delimiter is 0x1f by default, but can be changed for context-separation.
pub fn TurboShake128(comptime delim: ?u8) type {
return TurboShake(128, delim);
}
/// TurboSHAKE256 is a XOF (a secure hash function with a variable output length), with a 256 bit security level.
/// It is based on the same permutation as SHA3 and SHAKE256, but which much higher performance.
/// The delimiter is 0x01 by default, but can be changed for context-separation.
pub fn TurboShake256(comptime delim: ?u8) type {
return TurboShake(256, delim);
}
/// A generic Keccak hash function.
pub fn Keccak(comptime f: u11, comptime output_bits: u11, comptime delim: u8, comptime rounds: u5) type {
comptime assert(output_bits > 0 and output_bits * 2 < f and output_bits % 8 == 0); // invalid output length
@ -76,9 +90,18 @@ pub fn Keccak(comptime f: u11, comptime output_bits: u11, comptime delim: u8, co
/// The SHAKE extendable output hash function.
pub fn Shake(comptime security_level: u11) type {
return ShakeLike(security_level, 0x1f, 24);
}
/// The TurboSHAKE extendable output hash function.
/// https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/
pub fn TurboShake(comptime security_level: u11, comptime delim: ?u8) type {
return ShakeLike(security_level, delim orelse 0x1f, 12);
}
fn ShakeLike(comptime security_level: u11, comptime delim: u8, comptime rounds: u5) type {
const f = 1600;
const rounds = 24;
const State = KeccakState(f, security_level * 2, 0x1f, rounds);
const State = KeccakState(f, security_level * 2, delim, rounds);
return struct {
const Self = @This();
@ -348,3 +371,23 @@ test "SHAKE-256 single" {
Shake256.hash("hello123", &out, .{});
try htest.assertEqual("ade612ba265f92de4a37", &out);
}
test "TurboSHAKE-128" {
var out: [32]u8 = undefined;
TurboShake(128, 0x06).hash("\xff", &out, .{});
try htest.assertEqual("8ec9c66465ed0d4a6c35d13506718d687a25cb05c74cca1e42501abd83874a67", &out);
}
test "SHA-3 with streaming" {
var msg: [613]u8 = [613]u8{ 0x97, 0xd1, 0x2d, 0x1a, 0x16, 0x2d, 0x36, 0x4d, 0x20, 0x62, 0x19, 0x0b, 0x14, 0x93, 0xbb, 0xf8, 0x5b, 0xea, 0x04, 0xc2, 0x61, 0x8e, 0xd6, 0x08, 0x81, 0xa1, 0x1d, 0x73, 0x27, 0x48, 0xbf, 0xa4, 0xba, 0xb1, 0x9a, 0x48, 0x9c, 0xf9, 0x9b, 0xff, 0x34, 0x48, 0xa9, 0x75, 0xea, 0xc8, 0xa3, 0x48, 0x24, 0x9d, 0x75, 0x27, 0x48, 0xec, 0x03, 0xb0, 0xbb, 0xdf, 0x33, 0x90, 0xe3, 0x93, 0xed, 0x68, 0x24, 0x39, 0x12, 0xdf, 0xea, 0xee, 0x8c, 0x9f, 0x96, 0xde, 0x42, 0x46, 0x8c, 0x2b, 0x17, 0x83, 0x36, 0xfb, 0xf4, 0xf7, 0xff, 0x79, 0xb9, 0x45, 0x41, 0xc9, 0x56, 0x1a, 0x6b, 0x0c, 0xa4, 0x1a, 0xdd, 0x6b, 0x95, 0xe8, 0x03, 0x0f, 0x09, 0x29, 0x40, 0x1b, 0xea, 0x87, 0xfa, 0xb9, 0x18, 0xa9, 0x95, 0x07, 0x7c, 0x2f, 0x7c, 0x33, 0xfb, 0xc5, 0x11, 0x5e, 0x81, 0x0e, 0xbc, 0xae, 0xec, 0xb3, 0xe1, 0x4a, 0x26, 0x56, 0xe8, 0x5b, 0x11, 0x9d, 0x37, 0x06, 0x9b, 0x34, 0x31, 0x6e, 0xa3, 0xba, 0x41, 0xbc, 0x11, 0xd8, 0xc5, 0x15, 0xc9, 0x30, 0x2c, 0x9b, 0xb6, 0x71, 0xd8, 0x7c, 0xbc, 0x38, 0x2f, 0xd5, 0xbd, 0x30, 0x96, 0xd4, 0xa3, 0x00, 0x77, 0x9d, 0x55, 0x4a, 0x33, 0x53, 0xb6, 0xb3, 0x35, 0x1b, 0xae, 0xe5, 0xdc, 0x22, 0x23, 0x85, 0x95, 0x88, 0xf9, 0x3b, 0xbf, 0x74, 0x13, 0xaa, 0xcb, 0x0a, 0x60, 0x79, 0x13, 0x79, 0xc0, 0x4a, 0x02, 0xdb, 0x1c, 0xc9, 0xff, 0x60, 0x57, 0x9a, 0x70, 0x28, 0x58, 0x60, 0xbc, 0x57, 0x07, 0xc7, 0x47, 0x1a, 0x45, 0x71, 0x76, 0x94, 0xfb, 0x05, 0xad, 0xec, 0x12, 0x29, 0x5a, 0x44, 0x6a, 0x81, 0xd9, 0xc6, 0xf0, 0xb6, 0x9b, 0x97, 0x83, 0x69, 0xfb, 0xdc, 0x0d, 0x4a, 0x67, 0xbc, 0x72, 0xf5, 0x43, 0x5e, 0x9b, 0x13, 0xf2, 0xe4, 0x6d, 0x49, 0xdb, 0x76, 0xcb, 0x42, 0x6a, 0x3c, 0x9f, 0xa1, 0xfe, 0x5e, 0xca, 0x0a, 0xfc, 0xfa, 0x39, 0x27, 0xd1, 0x3c, 0xcb, 0x9a, 0xde, 0x4c, 0x6b, 0x09, 0x8b, 0x49, 0xfd, 0x1e, 0x3d, 0x5e, 0x67, 0x7c, 0x57, 0xad, 0x90, 0xcc, 0x46, 0x5f, 0x5c, 0xae, 0x6a, 0x9c, 0xb2, 0xcd, 0x2c, 0x89, 0x78, 0xcf, 0xf1, 0x49, 0x96, 0x55, 0x1e, 0x04, 0xef, 0x0e, 0x1c, 0xde, 0x6c, 0x96, 0x51, 0x00, 0xee, 0x9a, 0x1f, 0x8d, 0x61, 0xbc, 0xeb, 0xb1, 0xa6, 0xa5, 0x21, 0x8b, 0xa7, 0xf8, 0x25, 0x41, 0x48, 0x62, 0x5b, 0x01, 0x6c, 0x7c, 0x2a, 0xe8, 0xff, 0xf9, 0xf9, 0x1f, 0xe2, 0x79, 0x2e, 0xd1, 0xff, 0xa3, 0x2e, 0x1c, 0x3a, 0x1a, 0x5d, 0x2b, 0x7b, 0x87, 0x25, 0x22, 0xa4, 0x90, 0xea, 0x26, 0x9d, 0xdd, 0x13, 0x60, 0x4c, 0x10, 0x03, 0xf6, 0x99, 0xd3, 0x21, 0x0c, 0x69, 0xc6, 0xd8, 0xc8, 0x9e, 0x94, 0x89, 0x51, 0x21, 0xe3, 0x9a, 0xcd, 0xda, 0x54, 0x72, 0x64, 0xae, 0x94, 0x79, 0x36, 0x81, 0x44, 0x14, 0x6d, 0x3a, 0x0e, 0xa6, 0x30, 0xbf, 0x95, 0x99, 0xa6, 0xf5, 0x7f, 0x4f, 0xef, 0xc6, 0x71, 0x2f, 0x36, 0x13, 0x14, 0xa2, 0x9d, 0xc2, 0x0c, 0x0d, 0x4e, 0xc0, 0x02, 0xd3, 0x6f, 0xee, 0x98, 0x5e, 0x24, 0x31, 0x74, 0x11, 0x96, 0x6e, 0x43, 0x57, 0xe8, 0x8e, 0xa0, 0x8d, 0x3d, 0x79, 0x38, 0x20, 0xc2, 0x0f, 0xb4, 0x75, 0x99, 0x3b, 0xb1, 0xf0, 0xe8, 0xe1, 0xda, 0xf9, 0xd4, 0xe6, 0xd6, 0xf4, 0x8a, 0x32, 0x4a, 0x4a, 0x25, 0xa8, 0xd9, 0x60, 0xd6, 0x33, 0x31, 0x97, 0xb9, 0xb6, 0xed, 0x5f, 0xfc, 0x15, 0xbd, 0x13, 0xc0, 0x3a, 0x3f, 0x1f, 0x2d, 0x09, 0x1d, 0xeb, 0x69, 0x6a, 0xfe, 0xd7, 0x95, 0x3e, 0x8a, 0x4e, 0xe1, 0x6e, 0x61, 0xb2, 0x6c, 0xe3, 0x2b, 0x70, 0x60, 0x7e, 0x8c, 0xe4, 0xdd, 0x27, 0x30, 0x7e, 0x0d, 0xc7, 0xb7, 0x9a, 0x1a, 0x3c, 0xcc, 0xa7, 0x22, 0x77, 0x14, 0x05, 0x50, 0x57, 0x31, 0x1b, 0xc8, 0xbf, 0xce, 0x52, 0xaf, 0x9c, 0x8e, 0x10, 0x2e, 0xd2, 0x16, 0xb6, 0x6e, 0x43, 0x10, 0xaf, 0x8b, 0xde, 0x1d, 0x60, 0xb2, 0x7d, 0xe6, 0x2f, 0x08, 0x10, 0x12, 0x7e, 0xb4, 0x76, 0x45, 0xb6, 0xd8, 0x9b, 0x26, 0x40, 0xa1, 0x63, 0x5c, 0x7a, 0x2a, 0xb1, 0x8c, 0xd6, 0xa4, 0x6f, 0x5a, 0xae, 0x33, 0x7e, 0x6d, 0x71, 0xf5, 0xc8, 0x6d, 0x80, 0x1c, 0x35, 0xfc, 0x3f, 0xc1, 0xa6, 0xc6, 0x1a, 0x15, 0x04, 0x6d, 0x76, 0x38, 0x32, 0x95, 0xb2, 0x51, 0x1a, 0xe9, 0x3e, 0x89, 0x9f, 0x0c, 0x79 };
var out: [Sha3_256.digest_length]u8 = undefined;
Sha3_256.hash(&msg, &out, .{});
try htest.assertEqual("5780048dfa381a1d01c747906e4a08711dd34fd712ecd7c6801dd2b38fd81a89", &out);
var h = Sha3_256.init(.{});
h.update(msg[0..64]);
h.update(msg[64..613]);
h.final(&out);
try htest.assertEqual("5780048dfa381a1d01c747906e4a08711dd34fd712ecd7c6801dd2b38fd81a89", &out);
}

View File

@ -88,11 +88,59 @@ pub const StreamInterface = struct {
}
};
pub fn InitError(comptime Stream: type) type {
return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || error{
InsufficientEntropy,
DiskQuota,
LockViolation,
NotOpenForWriting,
TlsAlert,
TlsUnexpectedMessage,
TlsIllegalParameter,
TlsDecryptFailure,
TlsRecordOverflow,
TlsBadRecordMac,
CertificateFieldHasInvalidLength,
CertificateHostMismatch,
CertificatePublicKeyInvalid,
CertificateExpired,
CertificateFieldHasWrongDataType,
CertificateIssuerMismatch,
CertificateNotYetValid,
CertificateSignatureAlgorithmMismatch,
CertificateSignatureAlgorithmUnsupported,
CertificateSignatureInvalid,
CertificateSignatureInvalidLength,
CertificateSignatureNamedCurveUnsupported,
CertificateSignatureUnsupportedBitCount,
TlsCertificateNotVerified,
TlsBadSignatureScheme,
TlsBadRsaSignatureBitCount,
InvalidEncoding,
IdentityElement,
SignatureVerificationFailed,
TlsDecryptError,
TlsConnectionTruncated,
TlsDecodeError,
UnsupportedCertificateVersion,
CertificateTimeInvalid,
CertificateHasUnrecognizedObjectId,
CertificateHasInvalidBitString,
MessageTooLong,
NegativeIntoUnsigned,
TargetTooSmall,
BufferTooSmall,
InvalidSignature,
NotSquare,
NonCanonical,
};
}
/// Initiates a TLS handshake and establishes a TLSv1.3 session with `stream`, which
/// must conform to `StreamInterface`.
///
/// `host` is only borrowed during this function call.
pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) !Client {
pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) InitError(@TypeOf(stream))!Client {
const host_len = @intCast(u16, host.len);
var random_buffer: [128]u8 = undefined;

View File

@ -635,6 +635,7 @@ pub const TTY = struct {
pub const Color = enum {
Red,
Green,
Yellow,
Cyan,
White,
Dim,
@ -659,6 +660,7 @@ pub const TTY = struct {
const color_string = switch (color) {
.Red => "\x1b[31;1m",
.Green => "\x1b[32;1m",
.Yellow => "\x1b[33;1m",
.Cyan => "\x1b[36;1m",
.White => "\x1b[37;1m",
.Bold => "\x1b[1m",
@ -671,6 +673,7 @@ pub const TTY = struct {
const attributes = switch (color) {
.Red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
.Green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
.Yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
.Cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
.White, .Bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
.Dim => windows.FOREGROUND_INTENSITY,
@ -682,6 +685,36 @@ pub const TTY = struct {
},
};
}
pub fn writeDEC(conf: Config, writer: anytype, codepoint: u8) !void {
const bytes = switch (conf) {
.no_color, .windows_api => switch (codepoint) {
0x50...0x5e => @as(*const [1]u8, &codepoint),
0x6a => "+", //
0x6b => "+", //
0x6c => "+", //
0x6d => "+", //
0x6e => "+", //
0x71 => "-", //
0x74 => "+", //
0x75 => "+", //
0x76 => "+", //
0x77 => "+", //
0x78 => "|", //
else => " ", // TODO
},
.escape_codes => switch (codepoint) {
// Here we avoid writing the DEC beginning sequence and
// ending sequence in separate syscalls by putting the
// beginning and ending sequence into the same string
// literals, to prevent terminals ending up in bad states
// in case a crash happens between syscalls.
inline 0x50...0x7f => |x| "\x1B\x28\x30" ++ [1]u8{x} ++ "\x1B\x28\x42",
else => unreachable,
},
};
return writer.writeAll(bytes);
}
};
};

View File

@ -164,6 +164,17 @@ pub fn LinearFifo(
return self.readableSliceMut(offset);
}
pub fn readableSliceOfLen(self: *Self, len: usize) []const T {
assert(len <= self.count);
const buf = self.readableSlice(0);
if (buf.len >= len) {
return buf[0..len];
} else {
self.realign();
return self.readableSlice(0)[0..len];
}
}
/// Discard first `count` items in the fifo
pub fn discard(self: *Self, count: usize) void {
assert(count <= self.count);
@ -383,6 +394,22 @@ pub fn LinearFifo(
self.discard(try dest_writer.write(self.readableSlice(0)));
}
}
pub fn toOwnedSlice(self: *Self) Allocator.Error![]T {
if (self.head != 0) self.realign();
assert(self.head == 0);
assert(self.count <= self.buf.len);
const allocator = self.allocator;
if (allocator.resize(self.buf, self.count)) {
const result = self.buf[0..self.count];
self.* = Self.init(allocator);
return result;
}
const new_memory = try allocator.dupe(T, self.buf[0..self.count]);
allocator.free(self.buf);
self.* = Self.init(allocator);
return new_memory;
}
};
}

View File

@ -2555,6 +2555,21 @@ test "bytes.hex" {
try expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{fmtSliceHexLower(bytes_with_zeros)});
}
/// Encodes a sequence of bytes as hexadecimal digits.
/// Returns an array containing the encoded bytes.
pub fn bytesToHex(input: anytype, case: Case) [input.len * 2]u8 {
if (input.len == 0) return [_]u8{};
comptime assert(@TypeOf(input[0]) == u8); // elements to encode must be unsigned bytes
const charset = "0123456789" ++ if (case == .upper) "ABCDEF" else "abcdef";
var result: [input.len * 2]u8 = undefined;
for (input, 0..) |b, i| {
result[i * 2 + 0] = charset[b >> 4];
result[i * 2 + 1] = charset[b & 15];
}
return result;
}
/// Decodes the sequence of bytes represented by the specified string of
/// hexadecimal characters.
/// Returns a slice of the output buffer containing the decoded bytes.
@ -2575,6 +2590,13 @@ pub fn hexToBytes(out: []u8, input: []const u8) ![]u8 {
return out[0 .. in_i / 2];
}
test "bytesToHex" {
const input = "input slice";
const encoded = bytesToHex(input, .lower);
var decoded: [input.len]u8 = undefined;
try std.testing.expectEqualSlices(u8, input, try hexToBytes(&decoded, &encoded));
}
test "hexToBytes" {
var buf: [32]u8 = undefined;
try expectFmt("90" ** 32, "{s}", .{fmtSliceHexUpper(try hexToBytes(&buf, "90" ** 32))});

View File

@ -119,6 +119,7 @@ test "fmt.parseFloat hex.f16" {
}
test "fmt.parseFloat hex.f32" {
try testing.expectError(error.InvalidCharacter, parseFloat(f32, "0x"));
try testing.expectEqual(try parseFloat(f32, "0x1p0"), 1.0);
try testing.expectEqual(try parseFloat(f32, "-0x1p-1"), -0.5);
try testing.expectEqual(try parseFloat(f32, "0x10p+10"), 16384.0);

View File

@ -107,6 +107,8 @@ fn parsePartialNumberBase(comptime T: type, stream: *FloatStream, negative: bool
tryParseDigits(MantissaT, stream, &mantissa, info.base);
var int_end = stream.offsetTrue();
var n_digits = @intCast(isize, stream.offsetTrue());
// the base being 16 implies a 0x prefix, which shouldn't be included in the digit count
if (info.base == 16) n_digits -= 2;
// handle dot with the following digits
var exponent: i64 = 0;

View File

@ -1048,12 +1048,27 @@ pub const File = struct {
/// Returns the number of bytes read. If the number read is smaller than the total bytes
/// from all the buffers, it means the file reached the end. Reaching the end of a file
/// is not an error condition.
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial reads from the underlying OS layer.
/// See https://github.com/ziglang/zig/issues/7699
///
/// The `iovecs` parameter is mutable because:
/// * This function needs to mutate the fields in order to handle partial
/// reads from the underlying OS layer.
/// * The OS layer expects pointer addresses to be inside the application's address space
/// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
/// addresses when the length is zero. So this function modifies the iov_base fields
/// when the length is zero.
///
/// Related open issue: https://github.com/ziglang/zig/issues/7699
pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize {
if (iovecs.len == 0) return 0;
// We use the address of this local variable for all zero-length
// vectors so that the OS does not complain that we are giving it
// addresses outside the application's address space.
var garbage: [1]u8 = undefined;
for (iovecs) |*v| {
if (v.iov_len == 0) v.iov_base = &garbage;
}
var i: usize = 0;
var off: usize = 0;
while (true) {
@ -1181,13 +1196,26 @@ pub const File = struct {
}
}
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial writes from the underlying OS layer.
/// The `iovecs` parameter is mutable because:
/// * This function needs to mutate the fields in order to handle partial
/// writes from the underlying OS layer.
/// * The OS layer expects pointer addresses to be inside the application's address space
/// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
/// addresses when the length is zero. So this function modifies the iov_base fields
/// when the length is zero.
/// See https://github.com/ziglang/zig/issues/7699
/// See equivalent function: `std.net.Stream.writevAll`.
pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
if (iovecs.len == 0) return;
// We use the address of this local variable for all zero-length
// vectors so that the OS does not complain that we are giving it
// addresses outside the application's address space.
var garbage: [1]u8 = undefined;
for (iovecs) |*v| {
if (v.iov_len == 0) v.iov_base = &garbage;
}
var i: usize = 0;
while (true) {
var amt = try self.writev(iovecs[i..]);

View File

@ -1124,17 +1124,31 @@ test "open file with exclusive lock twice, make sure second lock waits" {
test "open file with exclusive nonblocking lock twice (absolute paths)" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
const allocator = testing.allocator;
var random_bytes: [12]u8 = undefined;
std.crypto.random.bytes(&random_bytes);
const cwd = try std.process.getCwdAlloc(allocator);
defer allocator.free(cwd);
const file_paths: [2][]const u8 = .{ cwd, "zig-test-absolute-paths.txt" };
const filename = try fs.path.resolve(allocator, &file_paths);
defer allocator.free(filename);
var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
_ = fs.base64_encoder.encode(&random_b64, &random_bytes);
const file1 = try fs.createFileAbsolute(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt";
const file2 = fs.createFileAbsolute(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
const gpa = testing.allocator;
const cwd = try std.process.getCwdAlloc(gpa);
defer gpa.free(cwd);
const filename = try fs.path.resolve(gpa, &[_][]const u8{ cwd, sub_path });
defer gpa.free(filename);
const file1 = try fs.createFileAbsolute(filename, .{
.lock = .Exclusive,
.lock_nonblocking = true,
});
const file2 = fs.createFileAbsolute(filename, .{
.lock = .Exclusive,
.lock_nonblocking = true,
});
file1.close();
try testing.expectError(error.WouldBlock, file2);

View File

@ -19,6 +19,7 @@ pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig"
pub const WasmAllocator = @import("heap/WasmAllocator.zig");
pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig");
pub const PageAllocator = @import("heap/PageAllocator.zig");
pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig");
const memory_pool = @import("heap/memory_pool.zig");
pub const MemoryPool = memory_pool.MemoryPool;

View File

@ -0,0 +1,45 @@
//! Wraps a non-thread-safe allocator and makes it thread-safe.
child_allocator: Allocator,
mutex: std.Thread.Mutex = .{},
pub fn allocator(self: *ThreadSafeAllocator) Allocator {
return .{
.ptr = self,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.free = free,
},
};
}
fn alloc(ctx: *anyopaque, n: usize, log2_ptr_align: u8, ra: usize) ?[*]u8 {
const self = @ptrCast(*ThreadSafeAllocator, @alignCast(@alignOf(ThreadSafeAllocator), ctx));
self.mutex.lock();
defer self.mutex.unlock();
return self.child_allocator.rawAlloc(n, log2_ptr_align, ra);
}
fn resize(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, new_len: usize, ret_addr: usize) bool {
const self = @ptrCast(*ThreadSafeAllocator, @alignCast(@alignOf(ThreadSafeAllocator), ctx));
self.mutex.lock();
defer self.mutex.unlock();
return self.child_allocator.rawResize(buf, log2_buf_align, new_len, ret_addr);
}
fn free(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, ret_addr: usize) void {
const self = @ptrCast(*ThreadSafeAllocator, @alignCast(@alignOf(ThreadSafeAllocator), ctx));
self.mutex.lock();
defer self.mutex.unlock();
return self.child_allocator.rawFree(buf, log2_buf_align, ret_addr);
}
const std = @import("../std.zig");
const ThreadSafeAllocator = @This();
const Allocator = std.mem.Allocator;

View File

@ -248,9 +248,24 @@ pub const Status = enum(u10) {
pub const TransferEncoding = enum {
chunked,
// compression is intentionally omitted here, as std.http.Client stores it as content-encoding
};
pub const ContentEncoding = enum {
compress,
deflate,
gzip,
zstd,
};
pub const Connection = enum {
keep_alive,
close,
};
pub const CustomHeader = struct {
name: []const u8,
value: []const u8,
};
const std = @import("std.zig");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,482 @@
const std = @import("std");
const http = std.http;
const Uri = std.Uri;
const mem = std.mem;
const assert = std.debug.assert;
const Client = @import("../Client.zig");
const Connection = Client.Connection;
const ConnectionNode = Client.ConnectionPool.Node;
const Response = @import("Response.zig");
const Request = @This();
const read_buffer_size = 8192;
const ReadBufferIndex = std.math.IntFittingRange(0, read_buffer_size);
uri: Uri,
client: *Client,
connection: *ConnectionNode,
response: Response,
/// These are stored in Request so that they are available when following
/// redirects.
headers: Headers,
redirects_left: u32,
handle_redirects: bool,
compression_init: bool,
/// Used as a allocator for resolving redirects locations.
arena: std.heap.ArenaAllocator,
/// Read buffer for the connection. This is used to pull in large amounts of data from the connection even if the user asks for a small amount. This can probably be removed with careful planning.
read_buffer: [read_buffer_size]u8 = undefined,
read_buffer_start: ReadBufferIndex = 0,
read_buffer_len: ReadBufferIndex = 0,
pub const RequestTransfer = union(enum) {
content_length: u64,
chunked: void,
none: void,
};
pub const Headers = struct {
version: http.Version = .@"HTTP/1.1",
method: http.Method = .GET,
user_agent: []const u8 = "zig (std.http)",
connection: http.Connection = .keep_alive,
transfer_encoding: RequestTransfer = .none,
custom: []const http.CustomHeader = &[_]http.CustomHeader{},
};
pub const Options = struct {
handle_redirects: bool = true,
max_redirects: u32 = 3,
header_strategy: HeaderStrategy = .{ .dynamic = 16 * 1024 },
pub const HeaderStrategy = union(enum) {
/// In this case, the client's Allocator will be used to store the
/// entire HTTP header. This value is the maximum total size of
/// HTTP headers allowed, otherwise
/// error.HttpHeadersExceededSizeLimit is returned from read().
dynamic: usize,
/// This is used to store the entire HTTP header. If the HTTP
/// header is too big to fit, `error.HttpHeadersExceededSizeLimit`
/// is returned from read(). When this is used, `error.OutOfMemory`
/// cannot be returned from `read()`.
static: []u8,
};
};
/// Frees all resources associated with the request.
pub fn deinit(req: *Request) void {
switch (req.response.compression) {
.none => {},
.deflate => |*deflate| deflate.deinit(),
.gzip => |*gzip| gzip.deinit(),
.zstd => |*zstd| zstd.deinit(),
}
if (req.response.header_bytes_owned) {
req.response.header_bytes.deinit(req.client.allocator);
}
if (!req.response.done) {
// If the response wasn't fully read, then we need to close the connection.
req.connection.data.closing = true;
req.client.connection_pool.release(req.client, req.connection);
}
req.arena.deinit();
req.* = undefined;
}
pub const ReadRawError = Connection.ReadError || Uri.ParseError || Client.RequestError || error{
UnexpectedEndOfStream,
TooManyHttpRedirects,
HttpRedirectMissingLocation,
HttpHeadersInvalid,
};
pub const ReaderRaw = std.io.Reader(*Request, ReadRawError, readRaw);
/// Read from the underlying stream, without decompressing or parsing the headers. Must be called
/// after waitForCompleteHead() has returned successfully.
pub fn readRaw(req: *Request, buffer: []u8) ReadRawError!usize {
assert(req.response.state.isContent());
var index: usize = 0;
while (index == 0) {
const amt = try req.readRawAdvanced(buffer[index..]);
if (amt == 0 and req.response.done) break;
index += amt;
}
return index;
}
fn checkForCompleteHead(req: *Request, buffer: []u8) !usize {
switch (req.response.state) {
.invalid => unreachable,
.start, .seen_r, .seen_rn, .seen_rnr => {},
else => return 0, // No more headers to read.
}
const i = req.response.findHeadersEnd(buffer[0..]);
if (req.response.state == .invalid) return error.HttpHeadersInvalid;
const headers_data = buffer[0..i];
if (req.response.header_bytes.items.len + headers_data.len > req.response.max_header_bytes) {
return error.HttpHeadersExceededSizeLimit;
}
try req.response.header_bytes.appendSlice(req.client.allocator, headers_data);
if (req.response.state == .finished) {
req.response.headers = try Response.Headers.parse(req.response.header_bytes.items);
if (req.response.headers.upgrade) |_| {
req.connection.data.closing = false;
req.response.done = true;
return i;
}
if (req.response.headers.connection == .keep_alive) {
req.connection.data.closing = false;
} else {
req.connection.data.closing = true;
}
if (req.response.headers.transfer_encoding) |transfer_encoding| {
switch (transfer_encoding) {
.chunked => {
req.response.next_chunk_length = 0;
req.response.state = .chunk_size;
},
}
} else if (req.response.headers.content_length) |content_length| {
req.response.next_chunk_length = content_length;
if (content_length == 0) req.response.done = true;
} else {
req.response.done = true;
}
return i;
}
return 0;
}
pub const WaitForCompleteHeadError = ReadRawError || error{
UnexpectedEndOfStream,
HttpHeadersExceededSizeLimit,
ShortHttpStatusLine,
BadHttpVersion,
HttpHeaderContinuationsUnsupported,
HttpTransferEncodingUnsupported,
HttpConnectionHeaderUnsupported,
};
/// Reads a complete response head. Any leftover data is stored in the request. This function is idempotent.
pub fn waitForCompleteHead(req: *Request) WaitForCompleteHeadError!void {
if (req.response.state.isContent()) return;
while (true) {
const nread = try req.connection.data.read(req.read_buffer[0..]);
const amt = try checkForCompleteHead(req, req.read_buffer[0..nread]);
if (amt != 0) {
req.read_buffer_start = @intCast(ReadBufferIndex, amt);
req.read_buffer_len = @intCast(ReadBufferIndex, nread);
return;
} else if (nread == 0) {
return error.UnexpectedEndOfStream;
}
}
}
/// This one can return 0 without meaning EOF.
fn readRawAdvanced(req: *Request, buffer: []u8) !usize {
assert(req.response.state.isContent());
if (req.response.done) return 0;
// var in: []const u8 = undefined;
if (req.read_buffer_start == req.read_buffer_len) {
const nread = try req.connection.data.read(req.read_buffer[0..]);
if (nread == 0) return error.UnexpectedEndOfStream;
req.read_buffer_start = 0;
req.read_buffer_len = @intCast(ReadBufferIndex, nread);
}
var out_index: usize = 0;
while (true) {
switch (req.response.state) {
.invalid, .start, .seen_r, .seen_rn, .seen_rnr => unreachable,
.finished => {
// TODO https://github.com/ziglang/zig/issues/14039
const buf_avail = req.read_buffer_len - req.read_buffer_start;
const data_avail = req.response.next_chunk_length;
const out_avail = buffer.len;
if (req.handle_redirects and req.response.headers.status.class() == .redirect) {
const can_read = @intCast(usize, @min(buf_avail, data_avail));
req.response.next_chunk_length -= can_read;
if (req.response.next_chunk_length == 0) {
req.client.connection_pool.release(req.client, req.connection);
req.connection = undefined;
req.response.done = true;
}
return 0; // skip over as much data as possible
}
const can_read = @intCast(usize, @min(@min(buf_avail, data_avail), out_avail));
req.response.next_chunk_length -= can_read;
mem.copy(u8, buffer[0..], req.read_buffer[req.read_buffer_start..][0..can_read]);
req.read_buffer_start += @intCast(ReadBufferIndex, can_read);
if (req.response.next_chunk_length == 0) {
req.client.connection_pool.release(req.client, req.connection);
req.connection = undefined;
req.response.done = true;
}
return can_read;
},
.chunk_size_prefix_r => switch (req.read_buffer_len - req.read_buffer_start) {
0 => return out_index,
1 => switch (req.read_buffer[req.read_buffer_start]) {
'\r' => {
req.response.state = .chunk_size_prefix_n;
return out_index;
},
else => {
req.response.state = .invalid;
return error.HttpHeadersInvalid;
},
},
else => switch (int16(req.read_buffer[req.read_buffer_start..][0..2])) {
int16("\r\n") => {
req.read_buffer_start += 2;
req.response.state = .chunk_size;
continue;
},
else => {
req.response.state = .invalid;
return error.HttpHeadersInvalid;
},
},
},
.chunk_size_prefix_n => switch (req.read_buffer_len - req.read_buffer_start) {
0 => return out_index,
else => switch (req.read_buffer[req.read_buffer_start]) {
'\n' => {
req.read_buffer_start += 1;
req.response.state = .chunk_size;
continue;
},
else => {
req.response.state = .invalid;
return error.HttpHeadersInvalid;
},
},
},
.chunk_size, .chunk_r => {
const i = req.response.findChunkedLen(req.read_buffer[req.read_buffer_start..req.read_buffer_len]);
switch (req.response.state) {
.invalid => return error.HttpHeadersInvalid,
.chunk_data => {
if (req.response.next_chunk_length == 0) {
req.response.done = true;
req.client.connection_pool.release(req.client, req.connection);
req.connection = undefined;
return out_index;
}
req.read_buffer_start += @intCast(ReadBufferIndex, i);
continue;
},
.chunk_size => return out_index,
else => unreachable,
}
},
.chunk_data => {
// TODO https://github.com/ziglang/zig/issues/14039
const buf_avail = req.read_buffer_len - req.read_buffer_start;
const data_avail = req.response.next_chunk_length;
const out_avail = buffer.len - out_index;
if (req.handle_redirects and req.response.headers.status.class() == .redirect) {
const can_read = @intCast(usize, @min(buf_avail, data_avail));
req.response.next_chunk_length -= can_read;
if (req.response.next_chunk_length == 0) {
req.client.connection_pool.release(req.client, req.connection);
req.connection = undefined;
req.response.done = true;
continue;
}
return 0; // skip over as much data as possible
}
const can_read = @intCast(usize, @min(@min(buf_avail, data_avail), out_avail));
req.response.next_chunk_length -= can_read;
mem.copy(u8, buffer[out_index..], req.read_buffer[req.read_buffer_start..][0..can_read]);
req.read_buffer_start += @intCast(ReadBufferIndex, can_read);
out_index += can_read;
if (req.response.next_chunk_length == 0) {
req.response.state = .chunk_size_prefix_r;
continue;
}
return out_index;
},
}
}
}
pub const ReadError = Client.DeflateDecompressor.Error || Client.GzipDecompressor.Error || Client.ZstdDecompressor.Error || WaitForCompleteHeadError || error{ BadHeader, InvalidCompression, StreamTooLong, InvalidWindowSize, CompressionNotSupported };
pub const Reader = std.io.Reader(*Request, ReadError, read);
pub fn reader(req: *Request) Reader {
return .{ .context = req };
}
pub fn read(req: *Request, buffer: []u8) ReadError!usize {
while (true) {
if (!req.response.state.isContent()) try req.waitForCompleteHead();
if (req.handle_redirects and req.response.headers.status.class() == .redirect) {
assert(try req.readRaw(buffer) == 0);
if (req.redirects_left == 0) return error.TooManyHttpRedirects;
const location = req.response.headers.location orelse
return error.HttpRedirectMissingLocation;
const new_url = Uri.parse(location) catch try Uri.parseWithoutScheme(location);
var new_arena = std.heap.ArenaAllocator.init(req.client.allocator);
const resolved_url = try req.uri.resolve(new_url, false, new_arena.allocator());
errdefer new_arena.deinit();
req.arena.deinit();
req.arena = new_arena;
const new_req = try req.client.request(resolved_url, req.headers, .{
.max_redirects = req.redirects_left - 1,
.header_strategy = if (req.response.header_bytes_owned) .{
.dynamic = req.response.max_header_bytes,
} else .{
.static = req.response.header_bytes.unusedCapacitySlice(),
},
});
req.deinit();
req.* = new_req;
} else {
break;
}
}
if (req.response.compression == .none) {
if (req.response.headers.transfer_compression) |compression| {
switch (compression) {
.compress => return error.CompressionNotSupported,
.deflate => req.response.compression = .{
.deflate = try std.compress.zlib.zlibStream(req.client.allocator, ReaderRaw{ .context = req }),
},
.gzip => req.response.compression = .{
.gzip = try std.compress.gzip.decompress(req.client.allocator, ReaderRaw{ .context = req }),
},
.zstd => req.response.compression = .{
.zstd = std.compress.zstd.decompressStream(req.client.allocator, ReaderRaw{ .context = req }),
},
}
}
}
return switch (req.response.compression) {
.deflate => |*deflate| try deflate.read(buffer),
.gzip => |*gzip| try gzip.read(buffer),
.zstd => |*zstd| try zstd.read(buffer),
else => try req.readRaw(buffer),
};
}
pub fn readAll(req: *Request, buffer: []u8) !usize {
var index: usize = 0;
while (index < buffer.len) {
const amt = try read(req, buffer[index..]);
if (amt == 0) break;
index += amt;
}
return index;
}
pub const WriteError = Connection.WriteError || error{MessageTooLong};
pub const Writer = std.io.Writer(*Request, WriteError, write);
pub fn writer(req: *Request) Writer {
return .{ .context = req };
}
/// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
pub fn write(req: *Request, bytes: []const u8) !usize {
switch (req.headers.transfer_encoding) {
.chunked => {
try req.connection.data.writer().print("{x}\r\n", .{bytes.len});
try req.connection.data.writeAll(bytes);
try req.connection.data.writeAll("\r\n");
return bytes.len;
},
.content_length => |*len| {
if (len.* < bytes.len) return error.MessageTooLong;
const amt = try req.connection.data.write(bytes);
len.* -= amt;
return amt;
},
.none => return error.NotWriteable,
}
}
/// Finish the body of a request. This notifies the server that you have no more data to send.
pub fn finish(req: *Request) !void {
switch (req.headers.transfer_encoding) {
.chunked => try req.connection.data.writeAll("0\r\n"),
.content_length => |len| if (len != 0) return error.MessageNotCompleted,
.none => {},
}
}
inline fn int16(array: *const [2]u8) u16 {
return @bitCast(u16, array.*);
}
inline fn int32(array: *const [4]u8) u32 {
return @bitCast(u32, array.*);
}
inline fn int64(array: *const [8]u8) u64 {
return @bitCast(u64, array.*);
}
test {
const builtin = @import("builtin");
if (builtin.os.tag == .wasi) return error.SkipZigTest;
_ = Response;
}

View File

@ -0,0 +1,509 @@
const std = @import("std");
const http = std.http;
const mem = std.mem;
const testing = std.testing;
const assert = std.debug.assert;
const Client = @import("../Client.zig");
const Response = @This();
headers: Headers,
state: State,
header_bytes_owned: bool,
/// This could either be a fixed buffer provided by the API user or it
/// could be our own array list.
header_bytes: std.ArrayListUnmanaged(u8),
max_header_bytes: usize,
next_chunk_length: u64,
done: bool = false,
compression: union(enum) {
deflate: Client.DeflateDecompressor,
gzip: Client.GzipDecompressor,
zstd: Client.ZstdDecompressor,
none: void,
} = .none,
pub const Headers = struct {
status: http.Status,
version: http.Version,
location: ?[]const u8 = null,
content_length: ?u64 = null,
transfer_encoding: ?http.TransferEncoding = null,
transfer_compression: ?http.ContentEncoding = null,
connection: http.Connection = .close,
upgrade: ?[]const u8 = null,
number_of_headers: usize = 0,
pub fn parse(bytes: []const u8) !Headers {
var it = mem.split(u8, bytes[0 .. bytes.len - 4], "\r\n");
const first_line = it.first();
if (first_line.len < 12)
return error.ShortHttpStatusLine;
const version: http.Version = switch (int64(first_line[0..8])) {
int64("HTTP/1.0") => .@"HTTP/1.0",
int64("HTTP/1.1") => .@"HTTP/1.1",
else => return error.BadHttpVersion,
};
if (first_line[8] != ' ') return error.HttpHeadersInvalid;
const status = @intToEnum(http.Status, parseInt3(first_line[9..12].*));
var headers: Headers = .{
.version = version,
.status = status,
};
while (it.next()) |line| {
headers.number_of_headers += 1;
if (line.len == 0) return error.HttpHeadersInvalid;
switch (line[0]) {
' ', '\t' => return error.HttpHeaderContinuationsUnsupported,
else => {},
}
var line_it = mem.split(u8, line, ": ");
const header_name = line_it.first();
const header_value = line_it.rest();
if (std.ascii.eqlIgnoreCase(header_name, "location")) {
if (headers.location != null) return error.HttpHeadersInvalid;
headers.location = header_value;
} else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
if (headers.content_length != null) return error.HttpHeadersInvalid;
headers.content_length = try std.fmt.parseInt(u64, header_value, 10);
} else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {
if (headers.transfer_encoding != null or headers.transfer_compression != null) return error.HttpHeadersInvalid;
// Transfer-Encoding: second, first
// Transfer-Encoding: deflate, chunked
var iter = std.mem.splitBackwards(u8, header_value, ",");
if (iter.next()) |first| {
const trimmed = std.mem.trim(u8, first, " ");
if (std.meta.stringToEnum(http.TransferEncoding, trimmed)) |te| {
headers.transfer_encoding = te;
} else if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
headers.transfer_compression = ce;
} else {
return error.HttpTransferEncodingUnsupported;
}
}
if (iter.next()) |second| {
if (headers.transfer_compression != null) return error.HttpTransferEncodingUnsupported;
const trimmed = std.mem.trim(u8, second, " ");
if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
headers.transfer_compression = ce;
} else {
return error.HttpTransferEncodingUnsupported;
}
}
if (iter.next()) |_| return error.HttpTransferEncodingUnsupported;
} else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
if (headers.transfer_compression != null) return error.HttpHeadersInvalid;
const trimmed = std.mem.trim(u8, header_value, " ");
if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
headers.transfer_compression = ce;
} else {
return error.HttpTransferEncodingUnsupported;
}
} else if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
if (std.ascii.eqlIgnoreCase(header_value, "keep-alive")) {
headers.connection = .keep_alive;
} else if (std.ascii.eqlIgnoreCase(header_value, "close")) {
headers.connection = .close;
} else {
return error.HttpConnectionHeaderUnsupported;
}
} else if (std.ascii.eqlIgnoreCase(header_name, "upgrade")) {
headers.upgrade = header_value;
}
}
return headers;
}
test "parse headers" {
const example =
"HTTP/1.1 301 Moved Permanently\r\n" ++
"Location: https://www.example.com/\r\n" ++
"Content-Type: text/html; charset=UTF-8\r\n" ++
"Content-Length: 220\r\n\r\n";
const parsed = try Headers.parse(example);
try testing.expectEqual(http.Version.@"HTTP/1.1", parsed.version);
try testing.expectEqual(http.Status.moved_permanently, parsed.status);
try testing.expectEqualStrings("https://www.example.com/", parsed.location orelse
return error.TestFailed);
try testing.expectEqual(@as(?u64, 220), parsed.content_length);
}
test "header continuation" {
const example =
"HTTP/1.0 200 OK\r\n" ++
"Content-Type: text/html;\r\n charset=UTF-8\r\n" ++
"Content-Length: 220\r\n\r\n";
try testing.expectError(
error.HttpHeaderContinuationsUnsupported,
Headers.parse(example),
);
}
test "extra content length" {
const example =
"HTTP/1.0 200 OK\r\n" ++
"Content-Length: 220\r\n" ++
"Content-Type: text/html; charset=UTF-8\r\n" ++
"content-length: 220\r\n\r\n";
try testing.expectError(
error.HttpHeadersInvalid,
Headers.parse(example),
);
}
};
inline fn int16(array: *const [2]u8) u16 {
return @bitCast(u16, array.*);
}
inline fn int32(array: *const [4]u8) u32 {
return @bitCast(u32, array.*);
}
inline fn int64(array: *const [8]u8) u64 {
return @bitCast(u64, array.*);
}
pub const State = enum {
/// Begin header parsing states.
invalid,
start,
seen_r,
seen_rn,
seen_rnr,
finished,
/// Begin transfer-encoding: chunked parsing states.
chunk_size_prefix_r,
chunk_size_prefix_n,
chunk_size,
chunk_r,
chunk_data,
pub fn isContent(self: State) bool {
return switch (self) {
.invalid, .start, .seen_r, .seen_rn, .seen_rnr => false,
.finished, .chunk_size_prefix_r, .chunk_size_prefix_n, .chunk_size, .chunk_r, .chunk_data => true,
};
}
};
pub fn initDynamic(max: usize) Response {
return .{
.state = .start,
.headers = undefined,
.header_bytes = .{},
.max_header_bytes = max,
.header_bytes_owned = true,
.next_chunk_length = undefined,
};
}
pub fn initStatic(buf: []u8) Response {
return .{
.state = .start,
.headers = undefined,
.header_bytes = .{ .items = buf[0..0], .capacity = buf.len },
.max_header_bytes = buf.len,
.header_bytes_owned = false,
.next_chunk_length = undefined,
};
}
/// Returns how many bytes are part of HTTP headers. Always less than or
/// equal to bytes.len. If the amount returned is less than bytes.len, it
/// means the headers ended and the first byte after the double \r\n\r\n is
/// located at `bytes[result]`.
pub fn findHeadersEnd(r: *Response, bytes: []const u8) usize {
var index: usize = 0;
// TODO: https://github.com/ziglang/zig/issues/8220
state: while (true) {
switch (r.state) {
.invalid => unreachable,
.finished => unreachable,
.start => while (true) {
switch (bytes.len - index) {
0 => return index,
1 => {
if (bytes[index] == '\r')
r.state = .seen_r;
return index + 1;
},
2 => {
if (int16(bytes[index..][0..2]) == int16("\r\n")) {
r.state = .seen_rn;
} else if (bytes[index + 1] == '\r') {
r.state = .seen_r;
}
return index + 2;
},
3 => {
if (int16(bytes[index..][0..2]) == int16("\r\n") and
bytes[index + 2] == '\r')
{
r.state = .seen_rnr;
} else if (int16(bytes[index + 1 ..][0..2]) == int16("\r\n")) {
r.state = .seen_rn;
} else if (bytes[index + 2] == '\r') {
r.state = .seen_r;
}
return index + 3;
},
4...15 => {
if (int32(bytes[index..][0..4]) == int32("\r\n\r\n")) {
r.state = .finished;
return index + 4;
} else if (int16(bytes[index + 1 ..][0..2]) == int16("\r\n") and
bytes[index + 3] == '\r')
{
r.state = .seen_rnr;
index += 4;
continue :state;
} else if (int16(bytes[index + 2 ..][0..2]) == int16("\r\n")) {
r.state = .seen_rn;
index += 4;
continue :state;
} else if (bytes[index + 3] == '\r') {
r.state = .seen_r;
index += 4;
continue :state;
}
index += 4;
continue;
},
else => {
const chunk = bytes[index..][0..16];
const v: @Vector(16, u8) = chunk.*;
const matches_r = v == @splat(16, @as(u8, '\r'));
const iota = std.simd.iota(u8, 16);
const default = @splat(16, @as(u8, 16));
const sub_index = @reduce(.Min, @select(u8, matches_r, iota, default));
switch (sub_index) {
0...12 => {
index += sub_index + 4;
if (int32(chunk[sub_index..][0..4]) == int32("\r\n\r\n")) {
r.state = .finished;
return index;
}
continue;
},
13 => {
index += 16;
if (int16(chunk[14..][0..2]) == int16("\n\r")) {
r.state = .seen_rnr;
continue :state;
}
continue;
},
14 => {
index += 16;
if (chunk[15] == '\n') {
r.state = .seen_rn;
continue :state;
}
continue;
},
15 => {
r.state = .seen_r;
index += 16;
continue :state;
},
16 => {
index += 16;
continue;
},
else => unreachable,
}
},
}
},
.seen_r => switch (bytes.len - index) {
0 => return index,
1 => {
switch (bytes[index]) {
'\n' => r.state = .seen_rn,
'\r' => r.state = .seen_r,
else => r.state = .start,
}
return index + 1;
},
2 => {
if (int16(bytes[index..][0..2]) == int16("\n\r")) {
r.state = .seen_rnr;
return index + 2;
}
r.state = .start;
return index + 2;
},
else => {
if (int16(bytes[index..][0..2]) == int16("\n\r") and
bytes[index + 2] == '\n')
{
r.state = .finished;
return index + 3;
}
index += 3;
r.state = .start;
continue :state;
},
},
.seen_rn => switch (bytes.len - index) {
0 => return index,
1 => {
switch (bytes[index]) {
'\r' => r.state = .seen_rnr,
else => r.state = .start,
}
return index + 1;
},
else => {
if (int16(bytes[index..][0..2]) == int16("\r\n")) {
r.state = .finished;
return index + 2;
}
index += 2;
r.state = .start;
continue :state;
},
},
.seen_rnr => switch (bytes.len - index) {
0 => return index,
else => {
if (bytes[index] == '\n') {
r.state = .finished;
return index + 1;
}
index += 1;
r.state = .start;
continue :state;
},
},
.chunk_size_prefix_r => unreachable,
.chunk_size_prefix_n => unreachable,
.chunk_size => unreachable,
.chunk_r => unreachable,
.chunk_data => unreachable,
}
return index;
}
}
pub fn findChunkedLen(r: *Response, bytes: []const u8) usize {
var i: usize = 0;
if (r.state == .chunk_size) {
while (i < bytes.len) : (i += 1) {
const digit = switch (bytes[i]) {
'0'...'9' => |b| b - '0',
'A'...'Z' => |b| b - 'A' + 10,
'a'...'z' => |b| b - 'a' + 10,
'\r' => {
r.state = .chunk_r;
i += 1;
break;
},
else => {
r.state = .invalid;
return i;
},
};
const mul = @mulWithOverflow(r.next_chunk_length, 16);
if (mul[1] != 0) {
r.state = .invalid;
return i;
}
const add = @addWithOverflow(mul[0], digit);
if (add[1] != 0) {
r.state = .invalid;
return i;
}
r.next_chunk_length = add[0];
} else {
return i;
}
}
assert(r.state == .chunk_r);
if (i == bytes.len) return i;
if (bytes[i] == '\n') {
r.state = .chunk_data;
return i + 1;
} else {
r.state = .invalid;
return i;
}
}
fn parseInt3(nnn: @Vector(3, u8)) u10 {
const zero: @Vector(3, u8) = .{ '0', '0', '0' };
const mmm: @Vector(3, u10) = .{ 100, 10, 1 };
return @reduce(.Add, @as(@Vector(3, u10), nnn -% zero) *% mmm);
}
test parseInt3 {
const expectEqual = std.testing.expectEqual;
try expectEqual(@as(u10, 0), parseInt3("000".*));
try expectEqual(@as(u10, 418), parseInt3("418".*));
try expectEqual(@as(u10, 999), parseInt3("999".*));
}
test "find headers end basic" {
var buffer: [1]u8 = undefined;
var r = Response.initStatic(&buffer);
try testing.expectEqual(@as(usize, 10), r.findHeadersEnd("HTTP/1.1 4"));
try testing.expectEqual(@as(usize, 2), r.findHeadersEnd("18"));
try testing.expectEqual(@as(usize, 8), r.findHeadersEnd(" lol\r\n\r\nblah blah"));
}
test "find headers end vectorized" {
var buffer: [1]u8 = undefined;
var r = Response.initStatic(&buffer);
const example =
"HTTP/1.1 301 Moved Permanently\r\n" ++
"Location: https://www.example.com/\r\n" ++
"Content-Type: text/html; charset=UTF-8\r\n" ++
"Content-Length: 220\r\n" ++
"\r\ncontent";
try testing.expectEqual(@as(usize, 131), r.findHeadersEnd(example));
}
test "find headers end bug" {
var buffer: [1]u8 = undefined;
var r = Response.initStatic(&buffer);
const trail = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const example =
"HTTP/1.1 200 OK\r\n" ++
"Access-Control-Allow-Origin: https://render.githubusercontent.com\r\n" ++
"content-disposition: attachment; filename=zig-0.10.0.tar.gz\r\n" ++
"Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox\r\n" ++
"Content-Type: application/x-gzip\r\n" ++
"ETag: \"bfae0af6b01c7c0d89eb667cb5f0e65265968aeebda2689177e6b26acd3155ca\"\r\n" ++
"Strict-Transport-Security: max-age=31536000\r\n" ++
"Vary: Authorization,Accept-Encoding,Origin\r\n" ++
"X-Content-Type-Options: nosniff\r\n" ++
"X-Frame-Options: deny\r\n" ++
"X-XSS-Protection: 1; mode=block\r\n" ++
"Date: Fri, 06 Jan 2023 22:26:22 GMT\r\n" ++
"Transfer-Encoding: chunked\r\n" ++
"X-GitHub-Request-Id: 89C6:17E9:A7C9E:124B51:63B8A00E\r\n" ++
"connection: close\r\n\r\n" ++ trail;
try testing.expectEqual(@as(usize, example.len - trail.len), r.findHeadersEnd(example));
}

View File

@ -1674,6 +1674,7 @@ pub const Mutable = struct {
/// If a is positive, this passes through to truncate.
/// If a is negative, then r is set to positive with the bit pattern ~(a - 1).
/// r may alias a.
///
/// Asserts `r` has enough storage to store the result.
/// The upper bound is `calcTwosCompLimbCount(a.len)`.

View File

@ -196,13 +196,8 @@ test "Allocator.resize" {
/// dest.len must be >= source.len.
/// If the slices overlap, dest.ptr must be <= src.ptr.
pub fn copy(comptime T: type, dest: []T, source: []const T) void {
// TODO instead of manually doing this check for the whole array
// and turning off runtime safety, the compiler should detect loops like
// this and automatically omit safety checks for loops
@setRuntimeSafety(false);
assert(dest.len >= source.len);
for (source, 0..) |s, i|
dest[i] = s;
for (dest[0..source.len], source) |*d, s|
d.* = s;
}
/// Copy all of source into dest at position 0.
@ -611,8 +606,8 @@ test "lessThan" {
pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
if (a.len != b.len) return false;
if (a.ptr == b.ptr) return true;
for (a, 0..) |item, index| {
if (b[index] != item) return false;
for (a, b) |a_elem, b_elem| {
if (a_elem != b_elem) return false;
}
return true;
}

View File

@ -68,6 +68,15 @@ pub fn MultiArrayList(comptime S: type) type {
other.deinit(gpa);
self.* = undefined;
}
/// This function is used in the debugger pretty formatters in tools/ to fetch the
/// child field order and entry type to facilitate fancy debug printing for this type.
fn dbHelper(self: *Slice, child: *S, field: *Field, entry: *Entry) void {
_ = self;
_ = child;
_ = field;
_ = entry;
}
};
const Self = @This();
@ -463,16 +472,18 @@ pub fn MultiArrayList(comptime S: type) type {
} });
};
/// This function is used in the debugger pretty formatters in tools/ to fetch the
/// child type to facilitate fancy debug printing for this type.
fn dbHelper(self: *Self, child: *S, entry: *Entry) void {
/// child field order and entry type to facilitate fancy debug printing for this type.
fn dbHelper(self: *Self, child: *S, field: *Field, entry: *Entry) void {
_ = self;
_ = child;
_ = field;
_ = entry;
}
comptime {
if (builtin.mode == .Debug) {
_ = dbHelper;
_ = Slice.dbHelper;
}
}
};

View File

@ -702,8 +702,10 @@ pub const AddressList = struct {
}
};
pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;
/// All memory allocated with `allocator` will be freed before this function returns.
pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) !Stream {
pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream {
const list = try getAddressList(allocator, name, port);
defer list.deinit();
@ -720,7 +722,9 @@ pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) !
return std.os.ConnectError.ConnectionRefused;
}
pub fn tcpConnectToAddress(address: Address) !Stream {
pub const TcpConnectToAddressError = std.os.SocketError || std.os.ConnectError;
pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
const nonblock = if (std.io.is_async) os.SOCK.NONBLOCK else 0;
const sock_flags = os.SOCK.STREAM | nonblock |
(if (builtin.target.os.tag == .windows) 0 else os.SOCK.CLOEXEC);
@ -737,8 +741,32 @@ pub fn tcpConnectToAddress(address: Address) !Stream {
return Stream{ .handle = sockfd };
}
const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.os.SocketError || std.os.BindError || error{
// TODO: break this up into error sets from the various underlying functions
TemporaryNameServerFailure,
NameServerFailure,
AddressFamilyNotSupported,
UnknownHostName,
ServiceUnavailable,
Unexpected,
HostLacksNetworkAddresses,
InvalidCharacter,
InvalidEnd,
NonCanonical,
Overflow,
Incomplete,
InvalidIpv4Mapping,
InvalidIPAddressFormat,
InterfaceNotFound,
FileSystem,
};
/// Call `AddressList.deinit` on the result.
pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) !*AddressList {
pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList {
const result = blk: {
var arena = std.heap.ArenaAllocator.init(allocator);
errdefer arena.deinit();

View File

@ -29,7 +29,7 @@ const Allocator = std.mem.Allocator;
const Preopen = std.fs.wasi.Preopen;
const PreopenList = std.fs.wasi.PreopenList;
pub const darwin = @import("os/darwin.zig");
pub const darwin = std.c;
pub const dragonfly = std.c;
pub const freebsd = std.c;
pub const haiku = std.c;
@ -41,8 +41,6 @@ pub const plan9 = @import("os/plan9.zig");
pub const uefi = @import("os/uefi.zig");
pub const wasi = @import("os/wasi.zig");
pub const windows = @import("os/windows.zig");
pub const posix_spawn = @import("os/posix_spawn.zig");
pub const ptrace = @import("os/ptrace.zig");
comptime {
assert(@import("std") == std); // std lib tests require --zig-lib-dir
@ -56,7 +54,6 @@ test {
}
_ = wasi;
_ = windows;
_ = posix_spawn;
_ = @import("os/test.zig");
}
@ -253,6 +250,25 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (builtin.
else => undefined,
};
pub const have_sigpipe_support = @hasDecl(@This(), "SIG") and @hasDecl(SIG, "PIPE");
fn noopSigHandler(_: c_int) callconv(.C) void {}
/// On default executed by posix startup code before main(), if SIGPIPE is supported.
pub fn maybeIgnoreSigpipe() void {
if (have_sigpipe_support and !std.options.keep_sigpipe) {
const act = Sigaction{
// We set handler to a noop function instead of SIG.IGN so we don't leak our
// signal disposition to a child process
.handler = .{ .handler = noopSigHandler },
.mask = empty_sigset,
.flags = 0,
};
sigaction(SIG.PIPE, &act, null) catch |err|
std.debug.panic("failed to install noop SIGPIPE handler with '{s}'", .{@errorName(err)});
}
}
/// To obtain errno, call this function with the return value of the
/// system function call. For some systems this will obtain the value directly
/// from the return code; for others it will use a thread-local errno variable.
@ -575,22 +591,12 @@ pub fn abort() noreturn {
raise(SIG.KILL) catch {};
exit(127); // Pid 1 might not be signalled in some containers.
}
if (builtin.os.tag == .uefi) {
exit(0); // TODO choose appropriate exit code
switch (builtin.os.tag) {
.uefi, .wasi, .cuda => @trap(),
else => system.abort(),
}
if (builtin.os.tag == .wasi) {
exit(1);
}
if (builtin.os.tag == .cuda) {
// TODO: introduce `@trap` instead of abusing https://github.com/ziglang/zig/issues/2291
@"llvm.trap"();
}
system.abort();
}
extern fn @"llvm.trap"() noreturn;
pub const RaiseError = UnexpectedError;
pub fn raise(sig: u8) RaiseError!void {
@ -759,6 +765,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
/// This operation is non-atomic on the following systems:
/// * Windows
/// On these systems, the read races with concurrent writes to the same file descriptor.
///
/// This function assumes that all vectors, including zero-length vectors, have
/// a pointer within the address space of the application.
pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
if (builtin.os.tag == .windows) {
// TODO improve this to use ReadFileScatter
@ -1036,6 +1045,8 @@ pub const WriteError = error{
FileTooBig,
InputOutput,
NoSpaceLeft,
DeviceBusy,
InvalidArgument,
/// In WASI, this error may occur when the file descriptor does
/// not hold the required rights to write to it.
@ -1122,7 +1133,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
switch (errno(rc)) {
.SUCCESS => return @intCast(usize, rc),
.INTR => continue,
.INVAL => unreachable,
.INVAL => return error.InvalidArgument,
.FAULT => unreachable,
.AGAIN => return error.WouldBlock,
.BADF => return error.NotOpenForWriting, // can be a race condition.
@ -1134,6 +1145,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
.PERM => return error.AccessDenied,
.PIPE => return error.BrokenPipe,
.CONNRESET => return error.ConnectionResetByPeer,
.BUSY => return error.DeviceBusy,
else => |err| return unexpectedErrno(err),
}
}
@ -1157,6 +1169,9 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
///
/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur.
///
/// This function assumes that all vectors, including zero-length vectors, have
/// a pointer within the address space of the application.
pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
if (builtin.os.tag == .windows) {
// TODO improve this to use WriteFileScatter
@ -1191,7 +1206,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
switch (errno(rc)) {
.SUCCESS => return @intCast(usize, rc),
.INTR => continue,
.INVAL => unreachable,
.INVAL => return error.InvalidArgument,
.FAULT => unreachable,
.AGAIN => return error.WouldBlock,
.BADF => return error.NotOpenForWriting, // Can be a race condition.
@ -1203,6 +1218,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
.PERM => return error.AccessDenied,
.PIPE => return error.BrokenPipe,
.CONNRESET => return error.ConnectionResetByPeer,
.BUSY => return error.DeviceBusy,
else => |err| return unexpectedErrno(err),
}
}
@ -1285,7 +1301,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
switch (errno(rc)) {
.SUCCESS => return @intCast(usize, rc),
.INTR => continue,
.INVAL => unreachable,
.INVAL => return error.InvalidArgument,
.FAULT => unreachable,
.AGAIN => return error.WouldBlock,
.BADF => return error.NotOpenForWriting, // Can be a race condition.
@ -1299,6 +1315,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
.NXIO => return error.Unseekable,
.SPIPE => return error.Unseekable,
.OVERFLOW => return error.Unseekable,
.BUSY => return error.DeviceBusy,
else => |err| return unexpectedErrno(err),
}
}
@ -1374,7 +1391,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
switch (errno(rc)) {
.SUCCESS => return @intCast(usize, rc),
.INTR => continue,
.INVAL => unreachable,
.INVAL => return error.InvalidArgument,
.FAULT => unreachable,
.AGAIN => return error.WouldBlock,
.BADF => return error.NotOpenForWriting, // Can be a race condition.
@ -1388,6 +1405,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
.NXIO => return error.Unseekable,
.SPIPE => return error.Unseekable,
.OVERFLOW => return error.Unseekable,
.BUSY => return error.DeviceBusy,
else => |err| return unexpectedErrno(err),
}
}
@ -3456,7 +3474,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi
const rc = system.bind(sock, addr, len);
switch (errno(rc)) {
.SUCCESS => return,
.ACCES => return error.AccessDenied,
.ACCES, .PERM => return error.AccessDenied,
.ADDRINUSE => return error.AddressInUse,
.BADF => unreachable, // always a race condition if this error is returned
.INVAL => unreachable, // invalid parameters
@ -3983,13 +4001,32 @@ pub const WaitPidResult = struct {
};
/// Use this version of the `waitpid` wrapper if you spawned your child process using explicit
/// `fork` and `execve` method. If you spawned your child process using `posix_spawn` method,
/// use `std.os.posix_spawn.waitpid` instead.
/// `fork` and `execve` method.
pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {
const Status = if (builtin.link_libc) c_int else u32;
var status: Status = undefined;
const coerced_flags = if (builtin.link_libc) @intCast(c_int, flags) else flags;
while (true) {
const rc = system.waitpid(pid, &status, if (builtin.link_libc) @intCast(c_int, flags) else flags);
const rc = system.waitpid(pid, &status, coerced_flags);
switch (errno(rc)) {
.SUCCESS => return .{
.pid = @intCast(pid_t, rc),
.status = @bitCast(u32, status),
},
.INTR => continue,
.CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
.INVAL => unreachable, // Invalid flags.
else => unreachable,
}
}
}
pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult {
const Status = if (builtin.link_libc) c_int else u32;
var status: Status = undefined;
const coerced_flags = if (builtin.link_libc) @intCast(c_int, flags) else flags;
while (true) {
const rc = system.wait4(pid, &status, coerced_flags, ru);
switch (errno(rc)) {
.SUCCESS => return .{
.pid = @intCast(pid_t, rc),
@ -4310,6 +4347,8 @@ pub const MMapError = error{
/// a filesystem that was mounted no-exec.
PermissionDenied,
LockedMemoryLimitExceeded,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
OutOfMemory,
} || UnexpectedError;
@ -4351,6 +4390,8 @@ pub fn mmap(
.OVERFLOW => unreachable, // The number of pages used for length + offset would overflow.
.NODEV => return error.MemoryMappingNotSupported,
.INVAL => unreachable, // Invalid parameters to mmap()
.MFILE => return error.ProcessFdQuotaExceeded,
.NFILE => return error.SystemFdQuotaExceeded,
.NOMEM => return error.OutOfMemory,
else => return unexpectedErrno(err),
}
@ -7086,20 +7127,24 @@ pub fn timerfd_gettime(fd: i32) TimerFdGetError!linux.itimerspec {
};
}
pub const have_sigpipe_support = @hasDecl(@This(), "SIG") and @hasDecl(SIG, "PIPE");
pub const PtraceError = error{
DeviceBusy,
ProcessNotFound,
PermissionDenied,
} || UnexpectedError;
fn noopSigHandler(_: c_int) callconv(.C) void {}
pub fn maybeIgnoreSigpipe() void {
if (have_sigpipe_support and !std.options.keep_sigpipe) {
const act = Sigaction{
// We set handler to a noop function instead of SIG.IGN so we don't leak our
// signal disposition to a child process
.handler = .{ .handler = noopSigHandler },
.mask = empty_sigset,
.flags = 0,
};
sigaction(SIG.PIPE, &act, null) catch |err|
std.debug.panic("failed to install noop SIGPIPE handler with '{s}'", .{@errorName(err)});
/// TODO on other OSes
pub fn ptrace(request: i32, pid: pid_t, addr: ?[*]u8, signal: i32) PtraceError!void {
switch (builtin.os.tag) {
.macos, .ios, .tvos, .watchos => {},
else => @compileError("TODO implement ptrace"),
}
return switch (errno(system.ptrace(request, pid, addr, signal))) {
.SUCCESS => {},
.SRCH => error.ProcessNotFound,
.INVAL => unreachable,
.PERM => error.PermissionDenied,
.BUSY => error.DeviceBusy,
else => |err| return unexpectedErrno(err),
};
}

View File

@ -1,540 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const log = std.log;
const mem = std.mem;
pub const cssm = @import("darwin/cssm.zig");
pub usingnamespace std.c;
pub usingnamespace mach_task;
const mach_task = if (builtin.target.isDarwin()) struct {
pub const MachError = error{
/// Not enough permissions held to perform the requested kernel
/// call.
PermissionDenied,
/// Kernel returned an unhandled and unexpected error code.
/// This is a catch-all for any yet unobserved kernel response
/// to some Mach message.
Unexpected,
};
pub const MachTask = extern struct {
port: std.c.mach_port_name_t,
pub fn isValid(self: MachTask) bool {
return self.port != std.c.TASK_NULL;
}
pub fn pidForTask(self: MachTask) MachError!std.os.pid_t {
var pid: std.os.pid_t = undefined;
switch (std.c.getKernError(std.c.pid_for_task(self.port, &pid))) {
.SUCCESS => return pid,
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("pid_for_task kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub fn allocatePort(self: MachTask, right: std.c.MACH_PORT_RIGHT) MachError!MachTask {
var out_port: std.c.mach_port_name_t = undefined;
switch (std.c.getKernError(std.c.mach_port_allocate(
self.port,
@enumToInt(right),
&out_port,
))) {
.SUCCESS => return .{ .port = out_port },
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("mach_task_allocate kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub fn deallocatePort(self: MachTask, port: MachTask) void {
_ = std.c.getKernError(std.c.mach_port_deallocate(self.port, port.port));
}
pub fn insertRight(self: MachTask, port: MachTask, msg: std.c.MACH_MSG_TYPE) !void {
switch (std.c.getKernError(std.c.mach_port_insert_right(
self.port,
port.port,
port.port,
@enumToInt(msg),
))) {
.SUCCESS => return,
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("mach_port_insert_right kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub const PortInfo = struct {
mask: std.c.exception_mask_t,
masks: [std.c.EXC_TYPES_COUNT]std.c.exception_mask_t,
ports: [std.c.EXC_TYPES_COUNT]std.c.mach_port_t,
behaviors: [std.c.EXC_TYPES_COUNT]std.c.exception_behavior_t,
flavors: [std.c.EXC_TYPES_COUNT]std.c.thread_state_flavor_t,
count: std.c.mach_msg_type_number_t,
};
pub fn getExceptionPorts(self: MachTask, mask: std.c.exception_mask_t) !PortInfo {
var info = PortInfo{
.mask = mask,
.masks = undefined,
.ports = undefined,
.behaviors = undefined,
.flavors = undefined,
.count = 0,
};
info.count = info.ports.len / @sizeOf(std.c.mach_port_t);
switch (std.c.getKernError(std.c.task_get_exception_ports(
self.port,
info.mask,
&info.masks,
&info.count,
&info.ports,
&info.behaviors,
&info.flavors,
))) {
.SUCCESS => return info,
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("task_get_exception_ports kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub fn setExceptionPorts(
self: MachTask,
mask: std.c.exception_mask_t,
new_port: MachTask,
behavior: std.c.exception_behavior_t,
new_flavor: std.c.thread_state_flavor_t,
) !void {
switch (std.c.getKernError(std.c.task_set_exception_ports(
self.port,
mask,
new_port.port,
behavior,
new_flavor,
))) {
.SUCCESS => return,
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("task_set_exception_ports kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub const RegionInfo = struct {
pub const Tag = enum {
basic,
extended,
top,
};
base_addr: u64,
tag: Tag,
info: union {
basic: std.c.vm_region_basic_info_64,
extended: std.c.vm_region_extended_info,
top: std.c.vm_region_top_info,
},
};
pub fn getRegionInfo(
task: MachTask,
address: u64,
len: usize,
tag: RegionInfo.Tag,
) MachError!RegionInfo {
var info: RegionInfo = .{
.base_addr = address,
.tag = tag,
.info = undefined,
};
switch (tag) {
.basic => info.info = .{ .basic = undefined },
.extended => info.info = .{ .extended = undefined },
.top => info.info = .{ .top = undefined },
}
var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len;
var objname: std.c.mach_port_t = undefined;
var count: std.c.mach_msg_type_number_t = switch (tag) {
.basic => std.c.VM_REGION_BASIC_INFO_COUNT,
.extended => std.c.VM_REGION_EXTENDED_INFO_COUNT,
.top => std.c.VM_REGION_TOP_INFO_COUNT,
};
switch (std.c.getKernError(std.c.mach_vm_region(
task.port,
&info.base_addr,
&base_len,
switch (tag) {
.basic => std.c.VM_REGION_BASIC_INFO_64,
.extended => std.c.VM_REGION_EXTENDED_INFO,
.top => std.c.VM_REGION_TOP_INFO,
},
switch (tag) {
.basic => @ptrCast(std.c.vm_region_info_t, &info.info.basic),
.extended => @ptrCast(std.c.vm_region_info_t, &info.info.extended),
.top => @ptrCast(std.c.vm_region_info_t, &info.info.top),
},
&count,
&objname,
))) {
.SUCCESS => return info,
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("mach_vm_region kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub const RegionSubmapInfo = struct {
pub const Tag = enum {
short,
full,
};
tag: Tag,
base_addr: u64,
info: union {
short: std.c.vm_region_submap_short_info_64,
full: std.c.vm_region_submap_info_64,
},
};
pub fn getRegionSubmapInfo(
task: MachTask,
address: u64,
len: usize,
nesting_depth: u32,
tag: RegionSubmapInfo.Tag,
) MachError!RegionSubmapInfo {
var info: RegionSubmapInfo = .{
.base_addr = address,
.tag = tag,
.info = undefined,
};
switch (tag) {
.short => info.info = .{ .short = undefined },
.full => info.info = .{ .full = undefined },
}
var nesting = nesting_depth;
var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len;
var count: std.c.mach_msg_type_number_t = switch (tag) {
.short => std.c.VM_REGION_SUBMAP_SHORT_INFO_COUNT_64,
.full => std.c.VM_REGION_SUBMAP_INFO_COUNT_64,
};
switch (std.c.getKernError(std.c.mach_vm_region_recurse(
task.port,
&info.base_addr,
&base_len,
&nesting,
switch (tag) {
.short => @ptrCast(std.c.vm_region_recurse_info_t, &info.info.short),
.full => @ptrCast(std.c.vm_region_recurse_info_t, &info.info.full),
},
&count,
))) {
.SUCCESS => return info,
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("mach_vm_region kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!std.c.vm_prot_t {
const info = try task.getRegionSubmapInfo(address, len, 0, .short);
return info.info.short.protection;
}
pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void {
return task.setProtectionImpl(address, len, true, prot);
}
pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void {
return task.setProtectionImpl(address, len, false, prot);
}
fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: std.c.vm_prot_t) MachError!void {
switch (std.c.getKernError(std.c.mach_vm_protect(task.port, address, len, @boolToInt(set_max), prot))) {
.SUCCESS => return,
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("mach_vm_protect kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
/// Will write to VM even if current protection attributes specifically prohibit
/// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY
/// variant, and resetting after a successful or unsuccessful write.
pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize {
const curr_prot = try task.getCurrProtection(address, buf.len);
try task.setCurrProtection(
address,
buf.len,
std.c.PROT.READ | std.c.PROT.WRITE | std.c.PROT.COPY,
);
defer {
task.setCurrProtection(address, buf.len, curr_prot) catch {};
}
return task.writeMem(address, buf, arch);
}
pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize {
const count = buf.len;
var total_written: usize = 0;
var curr_addr = address;
const page_size = try getPageSize(task); // TODO we probably can assume value here
var out_buf = buf[0..];
while (total_written < count) {
const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written);
switch (std.c.getKernError(std.c.mach_vm_write(
task.port,
curr_addr,
@ptrToInt(out_buf.ptr),
@intCast(std.c.mach_msg_type_number_t, curr_size),
))) {
.SUCCESS => {},
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("mach_vm_write kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
switch (arch) {
.aarch64 => {
var mattr_value: std.c.vm_machine_attribute_val_t = std.c.MATTR_VAL_CACHE_FLUSH;
switch (std.c.getKernError(std.c.vm_machine_attribute(
task.port,
curr_addr,
curr_size,
std.c.MATTR_CACHE,
&mattr_value,
))) {
.SUCCESS => {},
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("vm_machine_attribute kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
},
.x86_64 => {},
else => unreachable,
}
out_buf = out_buf[curr_size..];
total_written += curr_size;
curr_addr += curr_size;
}
return total_written;
}
pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize {
const count = buf.len;
var total_read: usize = 0;
var curr_addr = address;
const page_size = try getPageSize(task); // TODO we probably can assume value here
var out_buf = buf[0..];
while (total_read < count) {
const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read);
var curr_bytes_read: std.c.mach_msg_type_number_t = 0;
var vm_memory: std.c.vm_offset_t = undefined;
switch (std.c.getKernError(std.c.mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) {
.SUCCESS => {},
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("mach_vm_read kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
@memcpy(out_buf[0..].ptr, @intToPtr([*]const u8, vm_memory), curr_bytes_read);
_ = std.c.vm_deallocate(std.c.mach_task_self(), vm_memory, curr_bytes_read);
out_buf = out_buf[curr_bytes_read..];
curr_addr += curr_bytes_read;
total_read += curr_bytes_read;
}
return total_read;
}
fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize {
var left = count;
if (page_size > 0) {
const page_offset = address % page_size;
const bytes_left_in_page = page_size - page_offset;
if (count > bytes_left_in_page) {
left = bytes_left_in_page;
}
}
return left;
}
fn getPageSize(task: MachTask) MachError!usize {
if (task.isValid()) {
var info_count = std.c.TASK_VM_INFO_COUNT;
var vm_info: std.c.task_vm_info_data_t = undefined;
switch (std.c.getKernError(std.c.task_info(
task.port,
std.c.TASK_VM_INFO,
@ptrCast(std.c.task_info_t, &vm_info),
&info_count,
))) {
.SUCCESS => return @intCast(usize, vm_info.page_size),
else => {},
}
}
var page_size: std.c.vm_size_t = undefined;
switch (std.c.getKernError(std.c._host_page_size(std.c.mach_host_self(), &page_size))) {
.SUCCESS => return page_size,
else => |err| {
log.err("_host_page_size kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub fn basicTaskInfo(task: MachTask) MachError!std.c.mach_task_basic_info {
var info: std.c.mach_task_basic_info = undefined;
var count = std.c.MACH_TASK_BASIC_INFO_COUNT;
switch (std.c.getKernError(std.c.task_info(
task.port,
std.c.MACH_TASK_BASIC_INFO,
@ptrCast(std.c.task_info_t, &info),
&count,
))) {
.SUCCESS => return info,
else => |err| {
log.err("task_info kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub fn @"resume"(task: MachTask) MachError!void {
switch (std.c.getKernError(std.c.task_resume(task.port))) {
.SUCCESS => {},
else => |err| {
log.err("task_resume kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub fn @"suspend"(task: MachTask) MachError!void {
switch (std.c.getKernError(std.c.task_suspend(task.port))) {
.SUCCESS => {},
else => |err| {
log.err("task_suspend kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
const ThreadList = struct {
buf: []MachThread,
pub fn deinit(list: ThreadList) void {
const self_task = machTaskForSelf();
_ = std.c.vm_deallocate(
self_task.port,
@ptrToInt(list.buf.ptr),
@intCast(std.c.vm_size_t, list.buf.len * @sizeOf(std.c.mach_port_t)),
);
}
};
pub fn getThreads(task: MachTask) MachError!ThreadList {
var thread_list: std.c.mach_port_array_t = undefined;
var thread_count: std.c.mach_msg_type_number_t = undefined;
switch (std.c.getKernError(std.c.task_threads(task.port, &thread_list, &thread_count))) {
.SUCCESS => return ThreadList{ .buf = @ptrCast([*]MachThread, thread_list)[0..thread_count] },
else => |err| {
log.err("task_threads kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
};
pub const MachThread = extern struct {
port: std.c.mach_port_t,
pub fn isValid(thread: MachThread) bool {
return thread.port != std.c.THREAD_NULL;
}
pub fn getBasicInfo(thread: MachThread) MachError!std.c.thread_basic_info {
var info: std.c.thread_basic_info = undefined;
var count = std.c.THREAD_BASIC_INFO_COUNT;
switch (std.c.getKernError(std.c.thread_info(
thread.port,
std.c.THREAD_BASIC_INFO,
@ptrCast(std.c.thread_info_t, &info),
&count,
))) {
.SUCCESS => return info,
else => |err| {
log.err("thread_info kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
pub fn getIdentifierInfo(thread: MachThread) MachError!std.c.thread_identifier_info {
var info: std.c.thread_identifier_info = undefined;
var count = std.c.THREAD_IDENTIFIER_INFO_COUNT;
switch (std.c.getKernError(std.c.thread_info(
thread.port,
std.c.THREAD_IDENTIFIER_INFO,
@ptrCast(std.c.thread_info_t, &info),
&count,
))) {
.SUCCESS => return info,
else => |err| {
log.err("thread_info kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
}
};
pub fn machTaskForPid(pid: std.os.pid_t) MachError!MachTask {
var port: std.c.mach_port_name_t = undefined;
switch (std.c.getKernError(std.c.task_for_pid(std.c.mach_task_self(), pid, &port))) {
.SUCCESS => {},
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("task_for_pid kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
return MachTask{ .port = port };
}
pub fn machTaskForSelf() MachTask {
return .{ .port = std.c.mach_task_self() };
}
} else struct {};

View File

@ -944,6 +944,16 @@ pub fn waitpid(pid: pid_t, status: *u32, flags: u32) usize {
return syscall4(.wait4, @bitCast(usize, @as(isize, pid)), @ptrToInt(status), flags, 0);
}
pub fn wait4(pid: pid_t, status: *u32, flags: u32, usage: ?*rusage) usize {
return syscall4(
.wait4,
@bitCast(usize, @as(isize, pid)),
@ptrToInt(status),
flags,
@ptrToInt(usage),
);
}
pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32) usize {
return syscall5(.waitid, @enumToInt(id_type), @bitCast(usize, @as(isize, id)), @ptrToInt(infop), flags, 0);
}
@ -1716,26 +1726,26 @@ pub fn pidfd_send_signal(pidfd: fd_t, sig: i32, info: ?*siginfo_t, flags: u32) u
);
}
pub fn process_vm_readv(pid: pid_t, local: [*]const iovec, local_count: usize, remote: [*]const iovec, remote_count: usize, flags: usize) usize {
pub fn process_vm_readv(pid: pid_t, local: []iovec, remote: []const iovec_const, flags: usize) usize {
return syscall6(
.process_vm_readv,
@bitCast(usize, @as(isize, pid)),
@ptrToInt(local),
local_count,
@ptrToInt(remote),
remote_count,
@ptrToInt(local.ptr),
local.len,
@ptrToInt(remote.ptr),
remote.len,
flags,
);
}
pub fn process_vm_writev(pid: pid_t, local: [*]const iovec, local_count: usize, remote: [*]const iovec, remote_count: usize, flags: usize) usize {
pub fn process_vm_writev(pid: pid_t, local: []const iovec_const, remote: []const iovec_const, flags: usize) usize {
return syscall6(
.process_vm_writev,
@bitCast(usize, @as(isize, pid)),
@ptrToInt(local),
local_count,
@ptrToInt(remote),
remote_count,
@ptrToInt(local.ptr),
local.len,
@ptrToInt(remote.ptr),
remote.len,
flags,
);
}
@ -1820,6 +1830,23 @@ pub fn seccomp(operation: u32, flags: u32, args: ?*const anyopaque) usize {
return syscall3(.seccomp, operation, flags, @ptrToInt(args));
}
pub fn ptrace(
req: u32,
pid: pid_t,
addr: usize,
data: usize,
addr2: usize,
) usize {
return syscall5(
.ptrace,
req,
@bitCast(usize, @as(isize, pid)),
addr,
data,
addr2,
);
}
pub const E = switch (native_arch) {
.mips, .mipsel => @import("linux/errno/mips.zig").E,
.sparc, .sparcel, .sparc64 => @import("linux/errno/sparc.zig").E,
@ -5721,3 +5748,40 @@ pub const AUDIT = struct {
}
};
};
pub const PTRACE = struct {
pub const TRACEME = 0;
pub const PEEKTEXT = 1;
pub const PEEKDATA = 2;
pub const PEEKUSER = 3;
pub const POKETEXT = 4;
pub const POKEDATA = 5;
pub const POKEUSER = 6;
pub const CONT = 7;
pub const KILL = 8;
pub const SINGLESTEP = 9;
pub const GETREGS = 12;
pub const SETREGS = 13;
pub const GETFPREGS = 14;
pub const SETFPREGS = 15;
pub const ATTACH = 16;
pub const DETACH = 17;
pub const GETFPXREGS = 18;
pub const SETFPXREGS = 19;
pub const SYSCALL = 24;
pub const SETOPTIONS = 0x4200;
pub const GETEVENTMSG = 0x4201;
pub const GETSIGINFO = 0x4202;
pub const SETSIGINFO = 0x4203;
pub const GETREGSET = 0x4204;
pub const SETREGSET = 0x4205;
pub const SEIZE = 0x4206;
pub const INTERRUPT = 0x4207;
pub const LISTEN = 0x4208;
pub const PEEKSIGINFO = 0x4209;
pub const GETSIGMASK = 0x420a;
pub const SETSIGMASK = 0x420b;
pub const SECCOMP_GET_FILTER = 0x420c;
pub const SECCOMP_GET_METADATA = 0x420d;
pub const GET_SYSCALL_INFO = 0x420e;
};

View File

@ -1728,10 +1728,12 @@ test "writev/fsync/readv" {
};
defer ring.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = "test_io_uring_writev_fsync_readv";
const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true });
const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true });
defer file.close();
defer std.fs.cwd().deleteFile(path) catch {};
const fd = file.handle;
const buffer_write = [_]u8{42} ** 128;
@ -1796,10 +1798,11 @@ test "write/read" {
};
defer ring.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = "test_io_uring_write_read";
const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true });
const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true });
defer file.close();
defer std.fs.cwd().deleteFile(path) catch {};
const fd = file.handle;
const buffer_write = [_]u8{97} ** 20;
@ -1842,10 +1845,12 @@ test "write_fixed/read_fixed" {
};
defer ring.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = "test_io_uring_write_read_fixed";
const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true });
const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true });
defer file.close();
defer std.fs.cwd().deleteFile(path) catch {};
const fd = file.handle;
var raw_buffers: [2][11]u8 = undefined;
@ -1899,8 +1904,10 @@ test "openat" {
};
defer ring.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = "test_io_uring_openat";
defer std.fs.cwd().deleteFile(path) catch {};
// Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014
const path_addr = if (builtin.zig_backend == .stage2_llvm) p: {
@ -1910,12 +1917,12 @@ test "openat" {
const flags: u32 = os.O.CLOEXEC | os.O.RDWR | os.O.CREAT;
const mode: os.mode_t = 0o666;
const sqe_openat = try ring.openat(0x33333333, linux.AT.FDCWD, path, flags, mode);
const sqe_openat = try ring.openat(0x33333333, tmp.dir.fd, path, flags, mode);
try testing.expectEqual(linux.io_uring_sqe{
.opcode = .OPENAT,
.flags = 0,
.ioprio = 0,
.fd = linux.AT.FDCWD,
.fd = tmp.dir.fd,
.off = 0,
.addr = path_addr,
.len = mode,
@ -1931,12 +1938,6 @@ test "openat" {
const cqe_openat = try ring.copy_cqe();
try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data);
if (cqe_openat.err() == .INVAL) return error.SkipZigTest;
// AT.FDCWD is not fully supported before kernel 5.6:
// See https://lore.kernel.org/io-uring/20200207155039.12819-1-axboe@kernel.dk/T/
// We use IORING_FEAT_RW_CUR_POS to know if we are pre-5.6 since that feature was added in 5.6.
if (cqe_openat.err() == .BADF and (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0) {
return error.SkipZigTest;
}
if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res});
try testing.expect(cqe_openat.res > 0);
try testing.expectEqual(@as(u32, 0), cqe_openat.flags);
@ -1954,10 +1955,12 @@ test "close" {
};
defer ring.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = "test_io_uring_close";
const file = try std.fs.cwd().createFile(path, .{});
const file = try tmp.dir.createFile(path, .{});
errdefer file.close();
defer std.fs.cwd().deleteFile(path) catch {};
const sqe_close = try ring.close(0x44444444, file.handle);
try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode);
@ -1976,6 +1979,11 @@ test "close" {
test "accept/connect/send/recv" {
if (builtin.os.tag != .linux) return error.SkipZigTest;
if (true) {
// https://github.com/ziglang/zig/issues/14907
return error.SkipZigTest;
}
var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
error.SystemOutdated => return error.SkipZigTest,
error.PermissionDenied => return error.SkipZigTest,
@ -2017,6 +2025,11 @@ test "accept/connect/send/recv" {
test "sendmsg/recvmsg" {
if (builtin.os.tag != .linux) return error.SkipZigTest;
if (true) {
// https://github.com/ziglang/zig/issues/14907
return error.SkipZigTest;
}
var ring = IO_Uring.init(2, 0) catch |err| switch (err) {
error.SystemOutdated => return error.SkipZigTest,
error.PermissionDenied => return error.SkipZigTest,
@ -2024,6 +2037,7 @@ test "sendmsg/recvmsg" {
};
defer ring.deinit();
if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907
const address_server = try net.Address.parseIp4("127.0.0.1", 3131);
const server = try os.socket(address_server.any.family, os.SOCK.DGRAM, 0);
@ -2223,6 +2237,11 @@ test "timeout_remove" {
test "accept/connect/recv/link_timeout" {
if (builtin.os.tag != .linux) return error.SkipZigTest;
if (true) {
// https://github.com/ziglang/zig/issues/14907
return error.SkipZigTest;
}
var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
error.SystemOutdated => return error.SkipZigTest,
error.PermissionDenied => return error.SkipZigTest,
@ -2279,10 +2298,12 @@ test "fallocate" {
};
defer ring.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = "test_io_uring_fallocate";
const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
defer file.close();
defer std.fs.cwd().deleteFile(path) catch {};
try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
@ -2323,10 +2344,11 @@ test "statx" {
};
defer ring.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = "test_io_uring_statx";
const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
defer file.close();
defer std.fs.cwd().deleteFile(path) catch {};
try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
@ -2335,14 +2357,14 @@ test "statx" {
var buf: linux.Statx = undefined;
const sqe = try ring.statx(
0xaaaaaaaa,
linux.AT.FDCWD,
tmp.dir.fd,
path,
0,
linux.STATX_SIZE,
&buf,
);
try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode);
try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
try testing.expectEqual(@as(u32, 1), try ring.submit());
const cqe = try ring.copy_cqe();
@ -2355,8 +2377,6 @@ test "statx" {
// The filesystem containing the file referred to by fd does not support this operation;
// or the mode is not supported by the filesystem containing the file referred to by fd:
.OPNOTSUPP => return error.SkipZigTest,
// The kernel is too old to support FDCWD for dir_fd
.BADF => return error.SkipZigTest,
else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
}
try testing.expectEqual(linux.io_uring_cqe{
@ -2372,6 +2392,11 @@ test "statx" {
test "accept/connect/recv/cancel" {
if (builtin.os.tag != .linux) return error.SkipZigTest;
if (true) {
// https://github.com/ziglang/zig/issues/14907
return error.SkipZigTest;
}
var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
error.SystemOutdated => return error.SkipZigTest,
error.PermissionDenied => return error.SkipZigTest,
@ -2509,6 +2534,11 @@ test "register_files_update" {
test "shutdown" {
if (builtin.os.tag != .linux) return error.SkipZigTest;
if (true) {
// https://github.com/ziglang/zig/issues/14907
return error.SkipZigTest;
}
var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
error.SystemOutdated => return error.SkipZigTest,
error.PermissionDenied => return error.SkipZigTest,
@ -2516,6 +2546,7 @@ test "shutdown" {
};
defer ring.deinit();
if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907
const address = try net.Address.parseIp4("127.0.0.1", 3131);
// Socket bound, expect shutdown to work
@ -2579,28 +2610,28 @@ test "renameat" {
const old_path = "test_io_uring_renameat_old";
const new_path = "test_io_uring_renameat_new";
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
// Write old file with data
const old_file = try std.fs.cwd().createFile(old_path, .{ .truncate = true, .mode = 0o666 });
defer {
old_file.close();
std.fs.cwd().deleteFile(new_path) catch {};
}
const old_file = try tmp.dir.createFile(old_path, .{ .truncate = true, .mode = 0o666 });
defer old_file.close();
try old_file.writeAll("hello");
// Submit renameat
var sqe = try ring.renameat(
0x12121212,
linux.AT.FDCWD,
tmp.dir.fd,
old_path,
linux.AT.FDCWD,
tmp.dir.fd,
new_path,
0,
);
try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode);
try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
try testing.expectEqual(@as(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len));
try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
try testing.expectEqual(@as(i32, tmp.dir.fd), @bitCast(i32, sqe.len));
try testing.expectEqual(@as(u32, 1), try ring.submit());
const cqe = try ring.copy_cqe();
@ -2618,7 +2649,7 @@ test "renameat" {
// Validate that the old file doesn't exist anymore
{
_ = std.fs.cwd().openFile(old_path, .{}) catch |err| switch (err) {
_ = tmp.dir.openFile(old_path, .{}) catch |err| switch (err) {
error.FileNotFound => {},
else => std.debug.panic("unexpected error: {}", .{err}),
};
@ -2626,7 +2657,7 @@ test "renameat" {
// Validate that the new file exists with the proper content
{
const new_file = try std.fs.cwd().openFile(new_path, .{});
const new_file = try tmp.dir.openFile(new_path, .{});
defer new_file.close();
var new_file_data: [16]u8 = undefined;
@ -2647,22 +2678,24 @@ test "unlinkat" {
const path = "test_io_uring_unlinkat";
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
// Write old file with data
const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
defer file.close();
defer std.fs.cwd().deleteFile(path) catch {};
// Submit unlinkat
var sqe = try ring.unlinkat(
0x12121212,
linux.AT.FDCWD,
tmp.dir.fd,
path,
0,
);
try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode);
try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
try testing.expectEqual(@as(u32, 1), try ring.submit());
const cqe = try ring.copy_cqe();
@ -2679,7 +2712,7 @@ test "unlinkat" {
}, cqe);
// Validate that the file doesn't exist anymore
_ = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
_ = tmp.dir.openFile(path, .{}) catch |err| switch (err) {
error.FileNotFound => {},
else => std.debug.panic("unexpected error: {}", .{err}),
};
@ -2695,20 +2728,21 @@ test "mkdirat" {
};
defer ring.deinit();
const path = "test_io_uring_mkdirat";
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
defer std.fs.cwd().deleteDir(path) catch {};
const path = "test_io_uring_mkdirat";
// Submit mkdirat
var sqe = try ring.mkdirat(
0x12121212,
linux.AT.FDCWD,
tmp.dir.fd,
path,
0o0755,
);
try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode);
try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
try testing.expectEqual(@as(u32, 1), try ring.submit());
const cqe = try ring.copy_cqe();
@ -2725,7 +2759,7 @@ test "mkdirat" {
}, cqe);
// Validate that the directory exist
_ = try std.fs.cwd().openDir(path, .{});
_ = try tmp.dir.openDir(path, .{});
}
test "symlinkat" {
@ -2738,26 +2772,25 @@ test "symlinkat" {
};
defer ring.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = "test_io_uring_symlinkat";
const link_path = "test_io_uring_symlinkat_link";
const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
defer {
file.close();
std.fs.cwd().deleteFile(path) catch {};
std.fs.cwd().deleteFile(link_path) catch {};
}
const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
defer file.close();
// Submit symlinkat
var sqe = try ring.symlinkat(
0x12121212,
path,
linux.AT.FDCWD,
tmp.dir.fd,
link_path,
);
try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode);
try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
try testing.expectEqual(@as(u32, 1), try ring.submit());
const cqe = try ring.copy_cqe();
@ -2774,7 +2807,7 @@ test "symlinkat" {
}, cqe);
// Validate that the symlink exist
_ = try std.fs.cwd().openFile(link_path, .{});
_ = try tmp.dir.openFile(link_path, .{});
}
test "linkat" {
@ -2787,32 +2820,31 @@ test "linkat" {
};
defer ring.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const first_path = "test_io_uring_linkat_first";
const second_path = "test_io_uring_linkat_second";
// Write file with data
const first_file = try std.fs.cwd().createFile(first_path, .{ .truncate = true, .mode = 0o666 });
defer {
first_file.close();
std.fs.cwd().deleteFile(first_path) catch {};
std.fs.cwd().deleteFile(second_path) catch {};
}
const first_file = try tmp.dir.createFile(first_path, .{ .truncate = true, .mode = 0o666 });
defer first_file.close();
try first_file.writeAll("hello");
// Submit linkat
var sqe = try ring.linkat(
0x12121212,
linux.AT.FDCWD,
tmp.dir.fd,
first_path,
linux.AT.FDCWD,
tmp.dir.fd,
second_path,
0,
);
try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode);
try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
try testing.expectEqual(@as(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len));
try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
try testing.expectEqual(@as(i32, tmp.dir.fd), @bitCast(i32, sqe.len));
try testing.expectEqual(@as(u32, 1), try ring.submit());
const cqe = try ring.copy_cqe();
@ -2829,7 +2861,7 @@ test "linkat" {
}, cqe);
// Validate the second file
const second_file = try std.fs.cwd().openFile(second_path, .{});
const second_file = try tmp.dir.openFile(second_path, .{});
defer second_file.close();
var second_file_data: [16]u8 = undefined;
@ -3060,6 +3092,11 @@ test "remove_buffers" {
test "provide_buffers: accept/connect/send/recv" {
if (builtin.os.tag != .linux) return error.SkipZigTest;
if (true) {
// https://github.com/ziglang/zig/issues/14907
return error.SkipZigTest;
}
var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
error.SystemOutdated => return error.SkipZigTest,
error.PermissionDenied => return error.SkipZigTest,
@ -3236,6 +3273,7 @@ const SocketTestHarness = struct {
fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
// Create a TCP server socket
if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907
const address = try net.Address.parseIp4("127.0.0.1", 3131);
const kernel_backlog = 1;
const listener_socket = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);

View File

@ -8,10 +8,12 @@ const expectEqual = std.testing.expectEqual;
const fs = std.fs;
test "fallocate" {
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = "test_fallocate";
const file = try fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
defer file.close();
defer fs.cwd().deleteFile(path) catch {};
try expect((try file.stat()).size == 0);
@ -67,12 +69,12 @@ test "timer" {
}
test "statx" {
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const tmp_file_name = "just_a_temporary_file.txt";
var file = try fs.cwd().createFile(tmp_file_name, .{});
defer {
file.close();
fs.cwd().deleteFile(tmp_file_name) catch {};
}
var file = try tmp.dir.createFile(tmp_file_name, .{});
defer file.close();
var statx_buf: linux.Statx = undefined;
switch (linux.getErrno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) {
@ -105,21 +107,16 @@ test "user and group ids" {
}
test "fadvise" {
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const tmp_file_name = "temp_posix_fadvise.txt";
var file = try fs.cwd().createFile(tmp_file_name, .{});
defer {
file.close();
fs.cwd().deleteFile(tmp_file_name) catch {};
}
var file = try tmp.dir.createFile(tmp_file_name, .{});
defer file.close();
var buf: [2048]u8 = undefined;
try file.writeAll(&buf);
const ret = linux.fadvise(
file.handle,
0,
0,
linux.POSIX_FADV.SEQUENTIAL,
);
const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL);
try expectEqual(@as(usize, 0), ret);
}

View File

@ -1,290 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const os = @import("../os.zig");
const system = os.system;
const errno = system.getErrno;
const fd_t = system.fd_t;
const mode_t = system.mode_t;
const pid_t = system.pid_t;
const unexpectedErrno = os.unexpectedErrno;
const UnexpectedError = os.UnexpectedError;
const toPosixPath = os.toPosixPath;
const WaitPidResult = os.WaitPidResult;
pub usingnamespace posix_spawn;
pub const Error = error{
SystemResources,
InvalidFileDescriptor,
NameTooLong,
TooBig,
PermissionDenied,
InputOutput,
FileSystem,
FileNotFound,
InvalidExe,
NotDir,
FileBusy,
/// Returned when the child fails to execute either in the pre-exec() initialization step, or
/// when exec(3) is invoked.
ChildExecFailed,
} || UnexpectedError;
const posix_spawn = if (builtin.target.isDarwin()) struct {
pub const Attr = struct {
attr: system.posix_spawnattr_t,
pub fn init() Error!Attr {
var attr: system.posix_spawnattr_t = undefined;
switch (errno(system.posix_spawnattr_init(&attr))) {
.SUCCESS => return Attr{ .attr = attr },
.NOMEM => return error.SystemResources,
.INVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn deinit(self: *Attr) void {
defer self.* = undefined;
switch (errno(system.posix_spawnattr_destroy(&self.attr))) {
.SUCCESS => return,
.INVAL => unreachable, // Invalid parameters.
else => unreachable,
}
}
pub fn get(self: Attr) Error!u16 {
var flags: c_short = undefined;
switch (errno(system.posix_spawnattr_getflags(&self.attr, &flags))) {
.SUCCESS => return @bitCast(u16, flags),
.INVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn set(self: *Attr, flags: u16) Error!void {
switch (errno(system.posix_spawnattr_setflags(&self.attr, @bitCast(c_short, flags)))) {
.SUCCESS => return,
.INVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
};
pub const Actions = struct {
actions: system.posix_spawn_file_actions_t,
pub fn init() Error!Actions {
var actions: system.posix_spawn_file_actions_t = undefined;
switch (errno(system.posix_spawn_file_actions_init(&actions))) {
.SUCCESS => return Actions{ .actions = actions },
.NOMEM => return error.SystemResources,
.INVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn deinit(self: *Actions) void {
defer self.* = undefined;
switch (errno(system.posix_spawn_file_actions_destroy(&self.actions))) {
.SUCCESS => return,
.INVAL => unreachable, // Invalid parameters.
else => unreachable,
}
}
pub fn open(self: *Actions, fd: fd_t, path: []const u8, flags: u32, mode: mode_t) Error!void {
const posix_path = try toPosixPath(path);
return self.openZ(fd, &posix_path, flags, mode);
}
pub fn openZ(self: *Actions, fd: fd_t, path: [*:0]const u8, flags: u32, mode: mode_t) Error!void {
switch (errno(system.posix_spawn_file_actions_addopen(&self.actions, fd, path, @bitCast(c_int, flags), mode))) {
.SUCCESS => return,
.BADF => return error.InvalidFileDescriptor,
.NOMEM => return error.SystemResources,
.NAMETOOLONG => return error.NameTooLong,
.INVAL => unreachable, // the value of file actions is invalid
else => |err| return unexpectedErrno(err),
}
}
pub fn close(self: *Actions, fd: fd_t) Error!void {
switch (errno(system.posix_spawn_file_actions_addclose(&self.actions, fd))) {
.SUCCESS => return,
.BADF => return error.InvalidFileDescriptor,
.NOMEM => return error.SystemResources,
.INVAL => unreachable, // the value of file actions is invalid
.NAMETOOLONG => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn dup2(self: *Actions, fd: fd_t, newfd: fd_t) Error!void {
switch (errno(system.posix_spawn_file_actions_adddup2(&self.actions, fd, newfd))) {
.SUCCESS => return,
.BADF => return error.InvalidFileDescriptor,
.NOMEM => return error.SystemResources,
.INVAL => unreachable, // the value of file actions is invalid
.NAMETOOLONG => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn inherit(self: *Actions, fd: fd_t) Error!void {
switch (errno(system.posix_spawn_file_actions_addinherit_np(&self.actions, fd))) {
.SUCCESS => return,
.BADF => return error.InvalidFileDescriptor,
.NOMEM => return error.SystemResources,
.INVAL => unreachable, // the value of file actions is invalid
.NAMETOOLONG => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn chdir(self: *Actions, path: []const u8) Error!void {
const posix_path = try toPosixPath(path);
return self.chdirZ(&posix_path);
}
pub fn chdirZ(self: *Actions, path: [*:0]const u8) Error!void {
switch (errno(system.posix_spawn_file_actions_addchdir_np(&self.actions, path))) {
.SUCCESS => return,
.NOMEM => return error.SystemResources,
.NAMETOOLONG => return error.NameTooLong,
.BADF => unreachable,
.INVAL => unreachable, // the value of file actions is invalid
else => |err| return unexpectedErrno(err),
}
}
pub fn fchdir(self: *Actions, fd: fd_t) Error!void {
switch (errno(system.posix_spawn_file_actions_addfchdir_np(&self.actions, fd))) {
.SUCCESS => return,
.BADF => return error.InvalidFileDescriptor,
.NOMEM => return error.SystemResources,
.INVAL => unreachable, // the value of file actions is invalid
.NAMETOOLONG => unreachable,
else => |err| return unexpectedErrno(err),
}
}
};
pub fn spawn(
path: []const u8,
actions: ?Actions,
attr: ?Attr,
argv: [*:null]?[*:0]const u8,
envp: [*:null]?[*:0]const u8,
) Error!pid_t {
const posix_path = try toPosixPath(path);
return spawnZ(&posix_path, actions, attr, argv, envp);
}
pub fn spawnZ(
path: [*:0]const u8,
actions: ?Actions,
attr: ?Attr,
argv: [*:null]?[*:0]const u8,
envp: [*:null]?[*:0]const u8,
) Error!pid_t {
var pid: pid_t = undefined;
switch (errno(system.posix_spawn(
&pid,
path,
if (actions) |a| &a.actions else null,
if (attr) |a| &a.attr else null,
argv,
envp,
))) {
.SUCCESS => return pid,
.@"2BIG" => return error.TooBig,
.NOMEM => return error.SystemResources,
.BADF => return error.InvalidFileDescriptor,
.ACCES => return error.PermissionDenied,
.IO => return error.InputOutput,
.LOOP => return error.FileSystem,
.NAMETOOLONG => return error.NameTooLong,
.NOENT => return error.FileNotFound,
.NOEXEC => return error.InvalidExe,
.NOTDIR => return error.NotDir,
.TXTBSY => return error.FileBusy,
.BADARCH => return error.InvalidExe,
.BADEXEC => return error.InvalidExe,
.FAULT => unreachable,
.INVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn spawnp(
file: []const u8,
actions: ?Actions,
attr: ?Attr,
argv: [*:null]?[*:0]const u8,
envp: [*:null]?[*:0]const u8,
) Error!pid_t {
const posix_file = try toPosixPath(file);
return spawnpZ(&posix_file, actions, attr, argv, envp);
}
pub fn spawnpZ(
file: [*:0]const u8,
actions: ?Actions,
attr: ?Attr,
argv: [*:null]?[*:0]const u8,
envp: [*:null]?[*:0]const u8,
) Error!pid_t {
var pid: pid_t = undefined;
switch (errno(system.posix_spawnp(
&pid,
file,
if (actions) |a| &a.actions else null,
if (attr) |a| &a.attr else null,
argv,
envp,
))) {
.SUCCESS => return pid,
.@"2BIG" => return error.TooBig,
.NOMEM => return error.SystemResources,
.BADF => return error.InvalidFileDescriptor,
.ACCES => return error.PermissionDenied,
.IO => return error.InputOutput,
.LOOP => return error.FileSystem,
.NAMETOOLONG => return error.NameTooLong,
.NOENT => return error.FileNotFound,
.NOEXEC => return error.InvalidExe,
.NOTDIR => return error.NotDir,
.TXTBSY => return error.FileBusy,
.BADARCH => return error.InvalidExe,
.BADEXEC => return error.InvalidExe,
.FAULT => unreachable,
.INVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
/// Use this version of the `waitpid` wrapper if you spawned your child process using `posix_spawn`
/// or `posix_spawnp` syscalls.
/// See also `std.os.waitpid` for an alternative if your child process was spawned via `fork` and
/// `execve` method.
pub fn waitpid(pid: pid_t, flags: u32) Error!WaitPidResult {
const Status = if (builtin.link_libc) c_int else u32;
var status: Status = undefined;
while (true) {
const rc = system.waitpid(pid, &status, if (builtin.link_libc) @intCast(c_int, flags) else flags);
switch (errno(rc)) {
.SUCCESS => return WaitPidResult{
.pid = @intCast(pid_t, rc),
.status = @bitCast(u32, status),
},
.INTR => continue,
.CHILD => return error.ChildExecFailed,
.INVAL => unreachable, // Invalid flags.
else => unreachable,
}
}
}
} else struct {};

View File

@ -1,28 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const os = @import("../os.zig");
const system = os.system;
const errno = system.getErrno;
const pid_t = system.pid_t;
const unexpectedErrno = os.unexpectedErrno;
const UnexpectedError = os.UnexpectedError;
pub usingnamespace ptrace;
const ptrace = if (builtin.target.isDarwin()) struct {
pub const PtraceError = error{
ProcessNotFound,
PermissionDenied,
} || UnexpectedError;
pub fn ptrace(request: i32, pid: pid_t, addr: ?[*]u8, signal: i32) PtraceError!void {
switch (errno(system.ptrace(request, pid, addr, signal))) {
.SUCCESS => return,
.SRCH => return error.ProcessNotFound,
.INVAL => unreachable,
.BUSY, .PERM => return error.PermissionDenied,
else => |err| return unexpectedErrno(err),
}
}
} else struct {};

View File

@ -105,41 +105,53 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
// If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT;
const rc = ntdll.NtCreateFile(
&result,
options.access_mask,
&attr,
&io,
null,
FILE_ATTRIBUTE_NORMAL,
options.share_access,
options.creation,
flags,
null,
0,
);
switch (rc) {
.SUCCESS => {
if (std.io.is_async and options.io_mode == .evented) {
_ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined;
}
return result;
},
.OBJECT_NAME_INVALID => unreachable,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
.NO_MEDIA_IN_DEVICE => return error.NoDevice,
.INVALID_PARAMETER => unreachable,
.SHARING_VIOLATION => return error.AccessDenied,
.ACCESS_DENIED => return error.AccessDenied,
.PIPE_BUSY => return error.PipeBusy,
.OBJECT_PATH_SYNTAX_BAD => unreachable,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
.FILE_IS_A_DIRECTORY => return error.IsDir,
.NOT_A_DIRECTORY => return error.NotDir,
.USER_MAPPED_FILE => return error.AccessDenied,
.INVALID_HANDLE => unreachable,
else => return unexpectedStatus(rc),
while (true) {
const rc = ntdll.NtCreateFile(
&result,
options.access_mask,
&attr,
&io,
null,
FILE_ATTRIBUTE_NORMAL,
options.share_access,
options.creation,
flags,
null,
0,
);
switch (rc) {
.SUCCESS => {
if (std.io.is_async and options.io_mode == .evented) {
_ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined;
}
return result;
},
.OBJECT_NAME_INVALID => unreachable,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
.NO_MEDIA_IN_DEVICE => return error.NoDevice,
.INVALID_PARAMETER => unreachable,
.SHARING_VIOLATION => return error.AccessDenied,
.ACCESS_DENIED => return error.AccessDenied,
.PIPE_BUSY => return error.PipeBusy,
.OBJECT_PATH_SYNTAX_BAD => unreachable,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
.FILE_IS_A_DIRECTORY => return error.IsDir,
.NOT_A_DIRECTORY => return error.NotDir,
.USER_MAPPED_FILE => return error.AccessDenied,
.INVALID_HANDLE => unreachable,
.DELETE_PENDING => {
// This error means that there *was* a file in this location on
// the file system, but it was deleted. However, the OS is not
// finished with the deletion operation, and so this CreateFile
// call has failed. There is not really a sane way to handle
// this other than retrying the creation after the OS finishes
// the deletion.
std.time.sleep(std.time.ns_per_ms);
continue;
},
else => return unexpectedStatus(rc),
}
}
}

View File

@ -27,6 +27,8 @@ pub extern "advapi32" fn RegQueryValueExW(
lpcbData: ?*DWORD,
) callconv(WINAPI) LSTATUS;
pub extern "advapi32" fn RegCloseKey(hKey: HKEY) callconv(WINAPI) LSTATUS;
// RtlGenRandom is known as SystemFunction036 under advapi32
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx */
pub extern "advapi32" fn SystemFunction036(output: [*]u8, length: ULONG) callconv(WINAPI) BOOL;

View File

@ -67,6 +67,7 @@ const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;
const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;
const MODULEENTRY32 = windows.MODULEENTRY32;
const ULONGLONG = windows.ULONGLONG;
pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*anyopaque;
pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong;
@ -457,3 +458,5 @@ pub extern "kernel32" fn RegOpenKeyExW(
samDesired: REGSAM,
phkResult: *HKEY,
) callconv(WINAPI) LSTATUS;
pub extern "kernel32" fn GetPhysicallyInstalledSystemMemory(TotalMemoryInKilobytes: *ULONGLONG) BOOL;

View File

@ -828,24 +828,6 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator
return ArgIterator.initWithAllocator(allocator);
}
test "args iterator" {
var ga = std.testing.allocator;
var it = try argsWithAllocator(ga);
defer it.deinit(); // no-op unless WASI or Windows
const prog_name = it.next() orelse unreachable;
const expected_suffix = switch (builtin.os.tag) {
.wasi => "test.wasm",
.windows => "test.exe",
else => "test",
};
const given_suffix = std.fs.path.basename(prog_name);
try testing.expect(mem.eql(u8, expected_suffix, given_suffix));
try testing.expect(it.next() == null);
try testing.expect(!it.skip());
}
/// Caller must call argsFree on result.
pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
// TODO refactor to only make 1 allocation.
@ -1169,3 +1151,51 @@ pub fn execve(
return os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp);
}
pub const TotalSystemMemoryError = error{
UnknownTotalSystemMemory,
};
/// Returns the total system memory, in bytes.
pub fn totalSystemMemory() TotalSystemMemoryError!usize {
switch (builtin.os.tag) {
.linux => {
return totalSystemMemoryLinux() catch return error.UnknownTotalSystemMemory;
},
.windows => {
var kilobytes: std.os.windows.ULONGLONG = undefined;
assert(std.os.windows.kernel32.GetPhysicallyInstalledSystemMemory(&kilobytes) == std.os.windows.TRUE);
return kilobytes * 1024;
},
else => return error.UnknownTotalSystemMemory,
}
}
fn totalSystemMemoryLinux() !usize {
var file = try std.fs.openFileAbsoluteZ("/proc/meminfo", .{});
defer file.close();
var buf: [50]u8 = undefined;
const amt = try file.read(&buf);
if (amt != 50) return error.Unexpected;
var it = std.mem.tokenize(u8, buf[0..amt], " \n");
const label = it.next().?;
if (!std.mem.eql(u8, label, "MemTotal:")) return error.Unexpected;
const int_text = it.next() orelse return error.Unexpected;
const units = it.next() orelse return error.Unexpected;
if (!std.mem.eql(u8, units, "kB")) return error.Unexpected;
const kilobytes = try std.fmt.parseInt(usize, int_text, 10);
return kilobytes * 1024;
}
/// Indicate that we are now terminating with a successful exit code.
/// In debug builds, this is a no-op, so that the calling code's
/// cleanup mechanisms are tested and so that external tools that
/// check for resource leaks can be accurate. In release builds, this
/// calls exit(0), and does not return.
pub fn cleanExit() void {
if (builtin.mode == .Debug) {
return;
} else {
exit(0);
}
}

View File

@ -185,6 +185,16 @@ pub const options = struct {
options_override.keep_sigpipe
else
false;
pub const http_connection_pool_size = if (@hasDecl(options_override, "http_connection_pool_size"))
options_override.http_connection_pool_size
else
http.Client.default_connection_pool_size;
pub const side_channels_mitigations: crypto.SideChannelsMitigations = if (@hasDecl(options_override, "side_channels_mitigations"))
options_override.side_channels_mitigations
else
crypto.default_side_channels_mitigations;
};
// This forces the start.zig file to be imported, and the comptime logic inside that

View File

@ -724,11 +724,7 @@ pub const Target = struct {
/// Adds the specified feature set but not its dependencies.
pub fn addFeatureSet(set: *Set, other_set: Set) void {
if (builtin.zig_backend == .stage2_c) {
for (&set.ints, 0..) |*int, i| int.* |= other_set.ints[i];
} else {
set.ints = @as(@Vector(usize_count, usize), set.ints) | @as(@Vector(usize_count, usize), other_set.ints);
}
set.ints = @as(@Vector(usize_count, usize), set.ints) | @as(@Vector(usize_count, usize), other_set.ints);
}
/// Removes the specified feature but not its dependents.
@ -740,11 +736,7 @@ pub const Target = struct {
/// Removes the specified feature but not its dependents.
pub fn removeFeatureSet(set: *Set, other_set: Set) void {
if (builtin.zig_backend == .stage2_c) {
for (&set.ints, 0..) |*int, i| int.* &= ~other_set.ints[i];
} else {
set.ints = @as(@Vector(usize_count, usize), set.ints) & ~@as(@Vector(usize_count, usize), other_set.ints);
}
set.ints = @as(@Vector(usize_count, usize), set.ints) & ~@as(@Vector(usize_count, usize), other_set.ints);
}
pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void {

View File

@ -3,6 +3,9 @@ const tokenizer = @import("zig/tokenizer.zig");
const fmt = @import("zig/fmt.zig");
const assert = std.debug.assert;
pub const ErrorBundle = @import("zig/ErrorBundle.zig");
pub const Server = @import("zig/Server.zig");
pub const Client = @import("zig/Client.zig");
pub const Token = tokenizer.Token;
pub const Tokenizer = tokenizer.Tokenizer;
pub const fmtId = fmt.fmtId;

View File

@ -1407,7 +1407,8 @@ pub fn containerField(tree: Ast, node: Node.Index) full.ContainerField {
.type_expr = data.lhs,
.value_expr = extra.value_expr,
.align_expr = extra.align_expr,
.tuple_like = tree.tokens.items(.tag)[main_token + 1] != .colon,
.tuple_like = tree.tokens.items(.tag)[main_token] != .identifier or
tree.tokens.items(.tag)[main_token + 1] != .colon,
});
}
@ -1420,7 +1421,8 @@ pub fn containerFieldInit(tree: Ast, node: Node.Index) full.ContainerField {
.type_expr = data.lhs,
.value_expr = data.rhs,
.align_expr = 0,
.tuple_like = tree.tokens.items(.tag)[main_token + 1] != .colon,
.tuple_like = tree.tokens.items(.tag)[main_token] != .identifier or
tree.tokens.items(.tag)[main_token + 1] != .colon,
});
}
@ -1433,7 +1435,8 @@ pub fn containerFieldAlign(tree: Ast, node: Node.Index) full.ContainerField {
.type_expr = data.lhs,
.value_expr = 0,
.align_expr = data.rhs,
.tuple_like = tree.tokens.items(.tag)[main_token + 1] != .colon,
.tuple_like = tree.tokens.items(.tag)[main_token] != .identifier or
tree.tokens.items(.tag)[main_token + 1] != .colon,
});
}

39
lib/std/zig/Client.zig Normal file
View File

@ -0,0 +1,39 @@
pub const Message = struct {
pub const Header = extern struct {
tag: Tag,
/// Size of the body only; does not include this Header.
bytes_len: u32,
};
pub const Tag = enum(u32) {
/// Tells the compiler to shut down cleanly.
/// No body.
exit,
/// Tells the compiler to detect changes in source files and update the
/// affected output compilation artifacts.
/// If one of the compilation artifacts is an executable that is
/// running as a child process, the compiler will wait for it to exit
/// before performing the update.
/// No body.
update,
/// Tells the compiler to execute the executable as a child process.
/// No body.
run,
/// Tells the compiler to detect changes in source files and update the
/// affected output compilation artifacts.
/// If one of the compilation artifacts is an executable that is
/// running as a child process, the compiler will perform a hot code
/// swap.
/// No body.
hot_update,
/// Ask the test runner for metadata about all the unit tests that can
/// be run. Server will respond with a `test_metadata` message.
/// No body.
query_test_metadata,
/// Ask the test runner to run a particular test.
/// The message body is a u32 test index.
run_test,
_,
};
};

515
lib/std/zig/ErrorBundle.zig Normal file
View File

@ -0,0 +1,515 @@
//! To support incremental compilation, errors are stored in various places
//! so that they can be created and destroyed appropriately. This structure
//! is used to collect all the errors from the various places into one
//! convenient place for API users to consume.
//!
//! There is one special encoding for this data structure. If both arrays are
//! empty, it means there are no errors. This special encoding exists so that
//! heap allocation is not needed in the common case of no errors.
string_bytes: []const u8,
/// The first thing in this array is an `ErrorMessageList`.
extra: []const u32,
/// Special encoding when there are no errors.
pub const empty: ErrorBundle = .{
.string_bytes = &.{},
.extra = &.{},
};
// An index into `extra` pointing at an `ErrorMessage`.
pub const MessageIndex = enum(u32) {
_,
};
// An index into `extra` pointing at an `SourceLocation`.
pub const SourceLocationIndex = enum(u32) {
none = 0,
_,
};
/// There will be a MessageIndex for each len at start.
pub const ErrorMessageList = struct {
len: u32,
start: u32,
/// null-terminated string index. 0 means no compile log text.
compile_log_text: u32,
};
/// Trailing:
/// * ReferenceTrace for each reference_trace_len
pub const SourceLocation = struct {
/// null terminated string index
src_path: u32,
line: u32,
column: u32,
/// byte offset of starting token
span_start: u32,
/// byte offset of main error location
span_main: u32,
/// byte offset of end of last token
span_end: u32,
/// null terminated string index, possibly null.
/// Does not include the trailing newline.
source_line: u32 = 0,
reference_trace_len: u32 = 0,
};
/// Trailing:
/// * MessageIndex for each notes_len.
pub const ErrorMessage = struct {
/// null terminated string index
msg: u32,
/// Usually one, but incremented for redundant messages.
count: u32 = 1,
src_loc: SourceLocationIndex = .none,
notes_len: u32 = 0,
};
pub const ReferenceTrace = struct {
/// null terminated string index
/// Except for the sentinel ReferenceTrace element, in which case:
/// * 0 means remaining references hidden
/// * >0 means N references hidden
decl_name: u32,
/// Index into extra of a SourceLocation
/// If this is 0, this is the sentinel ReferenceTrace element.
src_loc: SourceLocationIndex,
};
pub fn deinit(eb: *ErrorBundle, gpa: Allocator) void {
gpa.free(eb.string_bytes);
gpa.free(eb.extra);
eb.* = undefined;
}
pub fn errorMessageCount(eb: ErrorBundle) u32 {
if (eb.extra.len == 0) return 0;
return eb.getErrorMessageList().len;
}
pub fn getErrorMessageList(eb: ErrorBundle) ErrorMessageList {
return eb.extraData(ErrorMessageList, 0).data;
}
pub fn getMessages(eb: ErrorBundle) []const MessageIndex {
const list = eb.getErrorMessageList();
return @ptrCast([]const MessageIndex, eb.extra[list.start..][0..list.len]);
}
pub fn getErrorMessage(eb: ErrorBundle, index: MessageIndex) ErrorMessage {
return eb.extraData(ErrorMessage, @enumToInt(index)).data;
}
pub fn getSourceLocation(eb: ErrorBundle, index: SourceLocationIndex) SourceLocation {
assert(index != .none);
return eb.extraData(SourceLocation, @enumToInt(index)).data;
}
pub fn getNotes(eb: ErrorBundle, index: MessageIndex) []const MessageIndex {
const notes_len = eb.getErrorMessage(index).notes_len;
const start = @enumToInt(index) + @typeInfo(ErrorMessage).Struct.fields.len;
return @ptrCast([]const MessageIndex, eb.extra[start..][0..notes_len]);
}
pub fn getCompileLogOutput(eb: ErrorBundle) [:0]const u8 {
return nullTerminatedString(eb, getErrorMessageList(eb).compile_log_text);
}
/// Returns the requested data, as well as the new index which is at the start of the
/// trailers for the object.
fn extraData(eb: ErrorBundle, comptime T: type, index: usize) struct { data: T, end: usize } {
const fields = @typeInfo(T).Struct.fields;
var i: usize = index;
var result: T = undefined;
inline for (fields) |field| {
@field(result, field.name) = switch (field.type) {
u32 => eb.extra[i],
MessageIndex => @intToEnum(MessageIndex, eb.extra[i]),
SourceLocationIndex => @intToEnum(SourceLocationIndex, eb.extra[i]),
else => @compileError("bad field type"),
};
i += 1;
}
return .{
.data = result,
.end = i,
};
}
/// Given an index into `string_bytes` returns the null-terminated string found there.
pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 {
const string_bytes = eb.string_bytes;
var end: usize = index;
while (string_bytes[end] != 0) {
end += 1;
}
return string_bytes[index..end :0];
}
pub const RenderOptions = struct {
ttyconf: std.debug.TTY.Config,
include_reference_trace: bool = true,
include_source_line: bool = true,
include_log_text: bool = true,
};
pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void {
std.debug.getStderrMutex().lock();
defer std.debug.getStderrMutex().unlock();
const stderr = std.io.getStdErr();
return renderToWriter(eb, options, stderr.writer()) catch return;
}
pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, writer: anytype) anyerror!void {
for (eb.getMessages()) |err_msg| {
try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .Red, 0);
}
if (options.include_log_text) {
const log_text = eb.getCompileLogOutput();
if (log_text.len != 0) {
try writer.writeAll("\nCompile Log Output:\n");
try writer.writeAll(log_text);
}
}
}
fn renderErrorMessageToWriter(
eb: ErrorBundle,
options: RenderOptions,
err_msg_index: MessageIndex,
stderr: anytype,
kind: []const u8,
color: std.debug.TTY.Color,
indent: usize,
) anyerror!void {
const ttyconf = options.ttyconf;
var counting_writer = std.io.countingWriter(stderr);
const counting_stderr = counting_writer.writer();
const err_msg = eb.getErrorMessage(err_msg_index);
if (err_msg.src_loc != .none) {
const src = eb.extraData(SourceLocation, @enumToInt(err_msg.src_loc));
try counting_stderr.writeByteNTimes(' ', indent);
try ttyconf.setColor(stderr, .Bold);
try counting_stderr.print("{s}:{d}:{d}: ", .{
eb.nullTerminatedString(src.data.src_path),
src.data.line + 1,
src.data.column + 1,
});
try ttyconf.setColor(stderr, color);
try counting_stderr.writeAll(kind);
try counting_stderr.writeAll(": ");
// This is the length of the part before the error message:
// e.g. "file.zig:4:5: error: "
const prefix_len = @intCast(usize, counting_stderr.context.bytes_written);
try ttyconf.setColor(stderr, .Reset);
try ttyconf.setColor(stderr, .Bold);
if (err_msg.count == 1) {
try writeMsg(eb, err_msg, stderr, prefix_len);
try stderr.writeByte('\n');
} else {
try writeMsg(eb, err_msg, stderr, prefix_len);
try ttyconf.setColor(stderr, .Dim);
try stderr.print(" ({d} times)\n", .{err_msg.count});
}
try ttyconf.setColor(stderr, .Reset);
if (src.data.source_line != 0 and options.include_source_line) {
const line = eb.nullTerminatedString(src.data.source_line);
for (line) |b| switch (b) {
'\t' => try stderr.writeByte(' '),
else => try stderr.writeByte(b),
};
try stderr.writeByte('\n');
// TODO basic unicode code point monospace width
const before_caret = src.data.span_main - src.data.span_start;
// -1 since span.main includes the caret
const after_caret = src.data.span_end - src.data.span_main -| 1;
try stderr.writeByteNTimes(' ', src.data.column - before_caret);
try ttyconf.setColor(stderr, .Green);
try stderr.writeByteNTimes('~', before_caret);
try stderr.writeByte('^');
try stderr.writeByteNTimes('~', after_caret);
try stderr.writeByte('\n');
try ttyconf.setColor(stderr, .Reset);
}
for (eb.getNotes(err_msg_index)) |note| {
try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent);
}
if (src.data.reference_trace_len > 0 and options.include_reference_trace) {
try ttyconf.setColor(stderr, .Reset);
try ttyconf.setColor(stderr, .Dim);
try stderr.print("referenced by:\n", .{});
var ref_index = src.end;
for (0..src.data.reference_trace_len) |_| {
const ref_trace = eb.extraData(ReferenceTrace, ref_index);
ref_index = ref_trace.end;
if (ref_trace.data.src_loc != .none) {
const ref_src = eb.getSourceLocation(ref_trace.data.src_loc);
try stderr.print(" {s}: {s}:{d}:{d}\n", .{
eb.nullTerminatedString(ref_trace.data.decl_name),
eb.nullTerminatedString(ref_src.src_path),
ref_src.line + 1,
ref_src.column + 1,
});
} else if (ref_trace.data.decl_name != 0) {
const count = ref_trace.data.decl_name;
try stderr.print(
" {d} reference(s) hidden; use '-freference-trace={d}' to see all references\n",
.{ count, count + src.data.reference_trace_len - 1 },
);
} else {
try stderr.print(
" remaining reference traces hidden; use '-freference-trace' to see all reference traces\n",
.{},
);
}
}
try stderr.writeByte('\n');
try ttyconf.setColor(stderr, .Reset);
}
} else {
try ttyconf.setColor(stderr, color);
try stderr.writeByteNTimes(' ', indent);
try stderr.writeAll(kind);
try stderr.writeAll(": ");
try ttyconf.setColor(stderr, .Reset);
const msg = eb.nullTerminatedString(err_msg.msg);
if (err_msg.count == 1) {
try stderr.print("{s}\n", .{msg});
} else {
try stderr.print("{s}", .{msg});
try ttyconf.setColor(stderr, .Dim);
try stderr.print(" ({d} times)\n", .{err_msg.count});
}
try ttyconf.setColor(stderr, .Reset);
for (eb.getNotes(err_msg_index)) |note| {
try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent + 4);
}
}
}
/// Splits the error message up into lines to properly indent them
/// to allow for long, good-looking error messages.
///
/// This is used to split the message in `@compileError("hello\nworld")` for example.
fn writeMsg(eb: ErrorBundle, err_msg: ErrorMessage, stderr: anytype, indent: usize) !void {
var lines = std.mem.split(u8, eb.nullTerminatedString(err_msg.msg), "\n");
while (lines.next()) |line| {
try stderr.writeAll(line);
if (lines.index == null) break;
try stderr.writeByte('\n');
try stderr.writeByteNTimes(' ', indent);
}
}
const std = @import("std");
const ErrorBundle = @This();
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
pub const Wip = struct {
gpa: Allocator,
string_bytes: std.ArrayListUnmanaged(u8),
/// The first thing in this array is a ErrorMessageList.
extra: std.ArrayListUnmanaged(u32),
root_list: std.ArrayListUnmanaged(MessageIndex),
pub fn init(wip: *Wip, gpa: Allocator) !void {
wip.* = .{
.gpa = gpa,
.string_bytes = .{},
.extra = .{},
.root_list = .{},
};
// So that 0 can be used to indicate a null string.
try wip.string_bytes.append(gpa, 0);
assert(0 == try addExtra(wip, ErrorMessageList{
.len = 0,
.start = 0,
.compile_log_text = 0,
}));
}
pub fn deinit(wip: *Wip) void {
const gpa = wip.gpa;
wip.root_list.deinit(gpa);
wip.string_bytes.deinit(gpa);
wip.extra.deinit(gpa);
wip.* = undefined;
}
pub fn toOwnedBundle(wip: *Wip, compile_log_text: []const u8) !ErrorBundle {
const gpa = wip.gpa;
if (wip.root_list.items.len == 0) {
assert(compile_log_text.len == 0);
// Special encoding when there are no errors.
wip.deinit();
wip.* = .{
.gpa = gpa,
.string_bytes = .{},
.extra = .{},
.root_list = .{},
};
return empty;
}
const compile_log_str_index = if (compile_log_text.len == 0) 0 else str: {
const str = @intCast(u32, wip.string_bytes.items.len);
try wip.string_bytes.ensureUnusedCapacity(gpa, compile_log_text.len + 1);
wip.string_bytes.appendSliceAssumeCapacity(compile_log_text);
wip.string_bytes.appendAssumeCapacity(0);
break :str str;
};
wip.setExtra(0, ErrorMessageList{
.len = @intCast(u32, wip.root_list.items.len),
.start = @intCast(u32, wip.extra.items.len),
.compile_log_text = compile_log_str_index,
});
try wip.extra.appendSlice(gpa, @ptrCast([]const u32, wip.root_list.items));
wip.root_list.clearAndFree(gpa);
return .{
.string_bytes = try wip.string_bytes.toOwnedSlice(gpa),
.extra = try wip.extra.toOwnedSlice(gpa),
};
}
pub fn tmpBundle(wip: Wip) ErrorBundle {
return .{
.string_bytes = wip.string_bytes.items,
.extra = wip.extra.items,
};
}
pub fn addString(wip: *Wip, s: []const u8) !u32 {
const gpa = wip.gpa;
const index = @intCast(u32, wip.string_bytes.items.len);
try wip.string_bytes.ensureUnusedCapacity(gpa, s.len + 1);
wip.string_bytes.appendSliceAssumeCapacity(s);
wip.string_bytes.appendAssumeCapacity(0);
return index;
}
pub fn printString(wip: *Wip, comptime fmt: []const u8, args: anytype) !u32 {
const gpa = wip.gpa;
const index = @intCast(u32, wip.string_bytes.items.len);
try wip.string_bytes.writer(gpa).print(fmt, args);
try wip.string_bytes.append(gpa, 0);
return index;
}
pub fn addRootErrorMessage(wip: *Wip, em: ErrorMessage) !void {
try wip.root_list.ensureUnusedCapacity(wip.gpa, 1);
wip.root_list.appendAssumeCapacity(try addErrorMessage(wip, em));
}
pub fn addErrorMessage(wip: *Wip, em: ErrorMessage) !MessageIndex {
return @intToEnum(MessageIndex, try addExtra(wip, em));
}
pub fn addErrorMessageAssumeCapacity(wip: *Wip, em: ErrorMessage) MessageIndex {
return @intToEnum(MessageIndex, addExtraAssumeCapacity(wip, em));
}
pub fn addSourceLocation(wip: *Wip, sl: SourceLocation) !SourceLocationIndex {
return @intToEnum(SourceLocationIndex, try addExtra(wip, sl));
}
pub fn addReferenceTrace(wip: *Wip, rt: ReferenceTrace) !void {
_ = try addExtra(wip, rt);
}
pub fn addBundle(wip: *Wip, other: ErrorBundle) !void {
const gpa = wip.gpa;
try wip.string_bytes.ensureUnusedCapacity(gpa, other.string_bytes.len);
try wip.extra.ensureUnusedCapacity(gpa, other.extra.len);
const other_list = other.getMessages();
// The ensureUnusedCapacity call above guarantees this.
const notes_start = wip.reserveNotes(@intCast(u32, other_list.len)) catch unreachable;
for (notes_start.., other_list) |note, message| {
wip.extra.items[note] = @enumToInt(wip.addOtherMessage(other, message) catch unreachable);
}
}
pub fn reserveNotes(wip: *Wip, notes_len: u32) !u32 {
try wip.extra.ensureUnusedCapacity(wip.gpa, notes_len +
notes_len * @typeInfo(ErrorBundle.ErrorMessage).Struct.fields.len);
wip.extra.items.len += notes_len;
return @intCast(u32, wip.extra.items.len - notes_len);
}
fn addOtherMessage(wip: *Wip, other: ErrorBundle, msg_index: MessageIndex) !MessageIndex {
const other_msg = other.getErrorMessage(msg_index);
const src_loc = try wip.addOtherSourceLocation(other, other_msg.src_loc);
const msg = try wip.addErrorMessage(.{
.msg = try wip.addString(other.nullTerminatedString(other_msg.msg)),
.count = other_msg.count,
.src_loc = src_loc,
.notes_len = other_msg.notes_len,
});
const notes_start = try wip.reserveNotes(other_msg.notes_len);
for (notes_start.., other.getNotes(msg_index)) |note, other_note| {
wip.extra.items[note] = @enumToInt(try wip.addOtherMessage(other, other_note));
}
return msg;
}
fn addOtherSourceLocation(
wip: *Wip,
other: ErrorBundle,
index: SourceLocationIndex,
) !SourceLocationIndex {
if (index == .none) return .none;
const other_sl = other.getSourceLocation(index);
const src_loc = try wip.addSourceLocation(.{
.src_path = try wip.addString(other.nullTerminatedString(other_sl.src_path)),
.line = other_sl.line,
.column = other_sl.column,
.span_start = other_sl.span_start,
.span_main = other_sl.span_main,
.span_end = other_sl.span_end,
.source_line = try wip.addString(other.nullTerminatedString(other_sl.source_line)),
.reference_trace_len = other_sl.reference_trace_len,
});
// TODO: also add the reference trace
return src_loc;
}
fn addExtra(wip: *Wip, extra: anytype) Allocator.Error!u32 {
const gpa = wip.gpa;
const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
try wip.extra.ensureUnusedCapacity(gpa, fields.len);
return addExtraAssumeCapacity(wip, extra);
}
fn addExtraAssumeCapacity(wip: *Wip, extra: anytype) u32 {
const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
const result = @intCast(u32, wip.extra.items.len);
wip.extra.items.len += fields.len;
setExtra(wip, result, extra);
return result;
}
fn setExtra(wip: *Wip, index: usize, extra: anytype) void {
const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
var i = index;
inline for (fields) |field| {
wip.extra.items[i] = switch (field.type) {
u32 => @field(extra, field.name),
MessageIndex => @enumToInt(@field(extra, field.name)),
SourceLocationIndex => @enumToInt(@field(extra, field.name)),
else => @compileError("bad field type"),
};
i += 1;
}
}
};

305
lib/std/zig/Server.zig Normal file
View File

@ -0,0 +1,305 @@
in: std.fs.File,
out: std.fs.File,
receive_fifo: std.fifo.LinearFifo(u8, .Dynamic),
pub const Message = struct {
pub const Header = extern struct {
tag: Tag,
/// Size of the body only; does not include this Header.
bytes_len: u32,
};
pub const Tag = enum(u32) {
/// Body is a UTF-8 string.
zig_version,
/// Body is an ErrorBundle.
error_bundle,
/// Body is a UTF-8 string.
progress,
/// Body is a EmitBinPath.
emit_bin_path,
/// Body is a TestMetadata
test_metadata,
/// Body is a TestResults
test_results,
_,
};
/// Trailing:
/// * extra: [extra_len]u32,
/// * string_bytes: [string_bytes_len]u8,
/// See `std.zig.ErrorBundle`.
pub const ErrorBundle = extern struct {
extra_len: u32,
string_bytes_len: u32,
};
/// Trailing:
/// * name: [tests_len]u32
/// - null-terminated string_bytes index
/// * async_frame_len: [tests_len]u32,
/// - 0 means not async
/// * expected_panic_msg: [tests_len]u32,
/// - null-terminated string_bytes index
/// - 0 means does not expect pani
/// * string_bytes: [string_bytes_len]u8,
pub const TestMetadata = extern struct {
string_bytes_len: u32,
tests_len: u32,
};
pub const TestResults = extern struct {
index: u32,
flags: Flags,
pub const Flags = packed struct(u8) {
fail: bool,
skip: bool,
leak: bool,
reserved: u5 = 0,
};
};
/// Trailing:
/// * the file system path the emitted binary can be found
pub const EmitBinPath = extern struct {
flags: Flags,
pub const Flags = packed struct(u8) {
cache_hit: bool,
reserved: u7 = 0,
};
};
};
pub const Options = struct {
gpa: Allocator,
in: std.fs.File,
out: std.fs.File,
zig_version: []const u8,
};
pub fn init(options: Options) !Server {
var s: Server = .{
.in = options.in,
.out = options.out,
.receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(options.gpa),
};
try s.serveStringMessage(.zig_version, options.zig_version);
return s;
}
pub fn deinit(s: *Server) void {
s.receive_fifo.deinit();
s.* = undefined;
}
pub fn receiveMessage(s: *Server) !InMessage.Header {
const Header = InMessage.Header;
const fifo = &s.receive_fifo;
while (true) {
const buf = fifo.readableSlice(0);
assert(fifo.readableLength() == buf.len);
if (buf.len >= @sizeOf(Header)) {
const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]);
// workaround for https://github.com/ziglang/zig/issues/14904
const bytes_len = bswap_and_workaround_u32(&header.bytes_len);
// workaround for https://github.com/ziglang/zig/issues/14904
const tag = bswap_and_workaround_tag(&header.tag);
if (buf.len - @sizeOf(Header) >= bytes_len) {
fifo.discard(@sizeOf(Header));
return .{
.tag = tag,
.bytes_len = bytes_len,
};
} else {
const needed = bytes_len - (buf.len - @sizeOf(Header));
const write_buffer = try fifo.writableWithSize(needed);
const amt = try s.in.read(write_buffer);
fifo.update(amt);
continue;
}
}
const write_buffer = try fifo.writableWithSize(256);
const amt = try s.in.read(write_buffer);
fifo.update(amt);
}
}
pub fn receiveBody_u32(s: *Server) !u32 {
const fifo = &s.receive_fifo;
const buf = fifo.readableSlice(0);
const result = @ptrCast(*align(1) const u32, buf[0..4]).*;
fifo.discard(4);
return bswap(result);
}
pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void {
return s.serveMessage(.{
.tag = tag,
.bytes_len = @intCast(u32, msg.len),
}, &.{msg});
}
pub fn serveMessage(
s: *const Server,
header: OutMessage.Header,
bufs: []const []const u8,
) !void {
var iovecs: [10]std.os.iovec_const = undefined;
const header_le = bswap(header);
iovecs[0] = .{
.iov_base = @ptrCast([*]const u8, &header_le),
.iov_len = @sizeOf(OutMessage.Header),
};
for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| {
iovec.* = .{
.iov_base = buf.ptr,
.iov_len = buf.len,
};
}
try s.out.writevAll(iovecs[0 .. bufs.len + 1]);
}
pub fn serveEmitBinPath(
s: *Server,
fs_path: []const u8,
header: OutMessage.EmitBinPath,
) !void {
try s.serveMessage(.{
.tag = .emit_bin_path,
.bytes_len = @intCast(u32, fs_path.len + @sizeOf(OutMessage.EmitBinPath)),
}, &.{
std.mem.asBytes(&header),
fs_path,
});
}
pub fn serveTestResults(
s: *Server,
msg: OutMessage.TestResults,
) !void {
const msg_le = bswap(msg);
try s.serveMessage(.{
.tag = .test_results,
.bytes_len = @intCast(u32, @sizeOf(OutMessage.TestResults)),
}, &.{
std.mem.asBytes(&msg_le),
});
}
pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
const eb_hdr: OutMessage.ErrorBundle = .{
.extra_len = @intCast(u32, error_bundle.extra.len),
.string_bytes_len = @intCast(u32, error_bundle.string_bytes.len),
};
const bytes_len = @sizeOf(OutMessage.ErrorBundle) +
4 * error_bundle.extra.len + error_bundle.string_bytes.len;
try s.serveMessage(.{
.tag = .error_bundle,
.bytes_len = @intCast(u32, bytes_len),
}, &.{
std.mem.asBytes(&eb_hdr),
// TODO: implement @ptrCast between slices changing the length
std.mem.sliceAsBytes(error_bundle.extra),
error_bundle.string_bytes,
});
}
pub const TestMetadata = struct {
names: []u32,
async_frame_sizes: []u32,
expected_panic_msgs: []u32,
string_bytes: []const u8,
};
pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void {
const header: OutMessage.TestMetadata = .{
.tests_len = bswap(@intCast(u32, test_metadata.names.len)),
.string_bytes_len = bswap(@intCast(u32, test_metadata.string_bytes.len)),
};
const bytes_len = @sizeOf(OutMessage.TestMetadata) +
3 * 4 * test_metadata.names.len + test_metadata.string_bytes.len;
if (need_bswap) {
bswap_u32_array(test_metadata.names);
bswap_u32_array(test_metadata.async_frame_sizes);
bswap_u32_array(test_metadata.expected_panic_msgs);
}
defer if (need_bswap) {
bswap_u32_array(test_metadata.names);
bswap_u32_array(test_metadata.async_frame_sizes);
bswap_u32_array(test_metadata.expected_panic_msgs);
};
return s.serveMessage(.{
.tag = .test_metadata,
.bytes_len = @intCast(u32, bytes_len),
}, &.{
std.mem.asBytes(&header),
// TODO: implement @ptrCast between slices changing the length
std.mem.sliceAsBytes(test_metadata.names),
std.mem.sliceAsBytes(test_metadata.async_frame_sizes),
std.mem.sliceAsBytes(test_metadata.expected_panic_msgs),
test_metadata.string_bytes,
});
}
fn bswap(x: anytype) @TypeOf(x) {
if (!need_bswap) return x;
const T = @TypeOf(x);
switch (@typeInfo(T)) {
.Enum => return @intToEnum(T, @byteSwap(@enumToInt(x))),
.Int => return @byteSwap(x),
.Struct => |info| switch (info.layout) {
.Extern => {
var result: T = undefined;
inline for (info.fields) |field| {
@field(result, field.name) = bswap(@field(x, field.name));
}
return result;
},
.Packed => {
const I = info.backing_integer.?;
return @bitCast(T, @byteSwap(@bitCast(I, x)));
},
.Auto => @compileError("auto layout struct"),
},
else => @compileError("bswap on type " ++ @typeName(T)),
}
}
fn bswap_u32_array(slice: []u32) void {
comptime assert(need_bswap);
for (slice) |*elem| elem.* = @byteSwap(elem.*);
}
/// workaround for https://github.com/ziglang/zig/issues/14904
fn bswap_and_workaround_u32(x: *align(1) const u32) u32 {
const bytes_ptr = @ptrCast(*const [4]u8, x);
return std.mem.readIntLittle(u32, bytes_ptr);
}
/// workaround for https://github.com/ziglang/zig/issues/14904
fn bswap_and_workaround_tag(x: *align(1) const InMessage.Tag) InMessage.Tag {
const bytes_ptr = @ptrCast(*const [4]u8, x);
const int = std.mem.readIntLittle(u32, bytes_ptr);
return @intToEnum(InMessage.Tag, int);
}
const OutMessage = std.zig.Server.Message;
const InMessage = std.zig.Client.Message;
const Server = @This();
const builtin = @import("builtin");
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const native_endian = builtin.target.cpu.arch.endian();
const need_bswap = native_endian != .Little;

View File

@ -1090,6 +1090,11 @@ pub fn getExternalExecutor(
switch (candidate.target.os.tag) {
.windows => {
if (options.allow_wine) {
// x86_64 wine does not support emulating aarch64-windows and
// vice versa.
if (candidate.target.cpu.arch != builtin.cpu.arch) {
return bad_result;
}
switch (candidate.target.cpu.arch.ptrBitWidth()) {
32 => return Executor{ .wine = "wine" },
64 => return Executor{ .wine = "wine64" },

View File

@ -8,14 +8,126 @@ pub const std_options = struct {
};
var log_err_count: usize = 0;
var cmdline_buffer: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);
pub fn main() void {
if (builtin.zig_backend != .stage1 and
builtin.zig_backend != .stage2_llvm and
builtin.zig_backend != .stage2_c)
if (builtin.zig_backend == .stage2_wasm or
builtin.zig_backend == .stage2_x86_64 or
builtin.zig_backend == .stage2_aarch64)
{
return main2() catch @panic("test failure");
return mainSimple() catch @panic("test failure");
}
const args = std.process.argsAlloc(fba.allocator()) catch
@panic("unable to parse command line args");
var listen = false;
for (args[1..]) |arg| {
if (std.mem.eql(u8, arg, "--listen=-")) {
listen = true;
} else {
@panic("unrecognized command line argument");
}
}
if (listen) {
return mainServer() catch @panic("internal test runner failure");
} else {
return mainTerminal();
}
}
fn mainServer() !void {
var server = try std.zig.Server.init(.{
.gpa = fba.allocator(),
.in = std.io.getStdIn(),
.out = std.io.getStdOut(),
.zig_version = builtin.zig_version_string,
});
defer server.deinit();
while (true) {
const hdr = try server.receiveMessage();
switch (hdr.tag) {
.exit => {
return std.process.exit(0);
},
.query_test_metadata => {
std.testing.allocator_instance = .{};
defer if (std.testing.allocator_instance.deinit()) {
@panic("internal test runner memory leak");
};
var string_bytes: std.ArrayListUnmanaged(u8) = .{};
defer string_bytes.deinit(std.testing.allocator);
try string_bytes.append(std.testing.allocator, 0); // Reserve 0 for null.
const test_fns = builtin.test_functions;
const names = try std.testing.allocator.alloc(u32, test_fns.len);
defer std.testing.allocator.free(names);
const async_frame_sizes = try std.testing.allocator.alloc(u32, test_fns.len);
defer std.testing.allocator.free(async_frame_sizes);
const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len);
defer std.testing.allocator.free(expected_panic_msgs);
for (test_fns, names, async_frame_sizes, expected_panic_msgs) |test_fn, *name, *async_frame_size, *expected_panic_msg| {
name.* = @intCast(u32, string_bytes.items.len);
try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1);
string_bytes.appendSliceAssumeCapacity(test_fn.name);
string_bytes.appendAssumeCapacity(0);
async_frame_size.* = @intCast(u32, test_fn.async_frame_size orelse 0);
expected_panic_msg.* = 0;
}
try server.serveTestMetadata(.{
.names = names,
.async_frame_sizes = async_frame_sizes,
.expected_panic_msgs = expected_panic_msgs,
.string_bytes = string_bytes.items,
});
},
.run_test => {
std.testing.allocator_instance = .{};
const index = try server.receiveBody_u32();
const test_fn = builtin.test_functions[index];
if (test_fn.async_frame_size != null)
@panic("TODO test runner implement async tests");
var fail = false;
var skip = false;
var leak = false;
test_fn.func() catch |err| switch (err) {
error.SkipZigTest => skip = true,
else => {
fail = true;
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
},
};
leak = std.testing.allocator_instance.deinit();
try server.serveTestResults(.{
.index = index,
.flags = .{
.fail = fail,
.skip = skip,
.leak = leak,
},
});
},
else => {
std.debug.print("unsupported message: {x}", .{@enumToInt(hdr.tag)});
std.process.exit(1);
},
}
}
}
fn mainTerminal() void {
const test_fn_list = builtin.test_functions;
var ok_count: usize = 0;
var skip_count: usize = 0;
@ -118,51 +230,17 @@ pub fn log(
}
}
pub fn main2() anyerror!void {
var skipped: usize = 0;
var failed: usize = 0;
// Simpler main(), exercising fewer language features, so that stage2 can handle it.
/// Simpler main(), exercising fewer language features, so that
/// work-in-progress backends can handle it.
pub fn mainSimple() anyerror!void {
//const stderr = std.io.getStdErr();
for (builtin.test_functions) |test_fn| {
test_fn.func() catch |err| {
if (err != error.SkipZigTest) {
failed += 1;
} else {
skipped += 1;
//stderr.writeAll(test_fn.name) catch {};
//stderr.writeAll("\n") catch {};
return err;
}
};
}
if (builtin.zig_backend == .stage2_wasm or
builtin.zig_backend == .stage2_x86_64 or
builtin.zig_backend == .stage2_aarch64 or
builtin.zig_backend == .stage2_llvm or
builtin.zig_backend == .stage2_c)
{
const passed = builtin.test_functions.len - skipped - failed;
const stderr = std.io.getStdErr();
writeInt(stderr, passed) catch {};
stderr.writeAll(" passed; ") catch {};
writeInt(stderr, skipped) catch {};
stderr.writeAll(" skipped; ") catch {};
writeInt(stderr, failed) catch {};
stderr.writeAll(" failed.\n") catch {};
}
if (failed != 0) {
return error.TestsFailed;
}
}
fn writeInt(stderr: std.fs.File, int: usize) anyerror!void {
const base = 10;
var buf: [100]u8 = undefined;
var a: usize = int;
var index: usize = buf.len;
while (true) {
const digit = a % base;
index -= 1;
buf[index] = std.fmt.digitToChar(@intCast(u8, digit), .lower);
a /= base;
if (a == 0) break;
}
const slice = buf[index..];
try stderr.writeAll(slice);
}

1311
lib/zig.h

File diff suppressed because it is too large Load Diff

View File

@ -232,7 +232,14 @@ pub const Inst = struct {
/// Result type is always noreturn; no instructions in a block follow this one.
/// Uses the `br` field.
br,
/// Lowers to a hardware trap instruction, or the next best thing.
/// Lowers to a trap/jam instruction causing program abortion.
/// This may lower to an instruction known to be invalid.
/// Sometimes, for the lack of a better instruction, `trap` and `breakpoint` may compile down to the same code.
/// Result type is always noreturn; no instructions in a block follow this one.
trap,
/// Lowers to a trap instruction causing debuggers to break here, or the next best thing.
/// The debugger or something else may allow the program to resume after this point.
/// Sometimes, for the lack of a better instruction, `trap` and `breakpoint` may compile down to the same code.
/// Result type is always void.
breakpoint,
/// Yields the return address of the current function.
@ -1186,6 +1193,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.ret,
.ret_load,
.unreach,
.trap,
=> return Type.initTag(.noreturn),
.breakpoint,

View File

@ -148,18 +148,24 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
};
defer gz_instructions.deinit(gpa);
if (AstGen.structDeclInner(
&gen_scope,
&gen_scope.base,
0,
tree.containerDeclRoot(),
.Auto,
0,
)) |struct_decl_ref| {
assert(refToIndex(struct_decl_ref).? == 0);
} else |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {}, // Handled via compile_errors below.
// The AST -> ZIR lowering process assumes an AST that does not have any
// parse errors.
if (tree.errors.len == 0) {
if (AstGen.structDeclInner(
&gen_scope,
&gen_scope.base,
0,
tree.containerDeclRoot(),
.Auto,
0,
)) |struct_decl_ref| {
assert(refToIndex(struct_decl_ref).? == 0);
} else |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {}, // Handled via compile_errors below.
}
} else {
try lowerAstErrors(&astgen);
}
const err_index = @enumToInt(Zir.ExtraIndex.compile_errors);
@ -205,7 +211,7 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
};
}
pub fn deinit(astgen: *AstGen, gpa: Allocator) void {
fn deinit(astgen: *AstGen, gpa: Allocator) void {
astgen.instructions.deinit(gpa);
astgen.extra.deinit(gpa);
astgen.string_table.deinit(gpa);
@ -216,7 +222,7 @@ pub fn deinit(astgen: *AstGen, gpa: Allocator) void {
astgen.ref_table.deinit(gpa);
}
pub const ResultInfo = struct {
const ResultInfo = struct {
/// The semantics requested for the result location
rl: Loc,
@ -245,7 +251,7 @@ pub const ResultInfo = struct {
}
}
pub const Loc = union(enum) {
const Loc = union(enum) {
/// The expression is the right-hand side of assignment to `_`. Only the side-effects of the
/// expression should be generated. The result instruction from the expression must
/// be ignored.
@ -277,11 +283,11 @@ pub const ResultInfo = struct {
src_node: ?Ast.Node.Index = null,
};
pub const Strategy = struct {
const Strategy = struct {
elide_store_to_block_ptr_instructions: bool,
tag: Tag,
pub const Tag = enum {
const Tag = enum {
/// Both branches will use break_void; result location is used to communicate the
/// result instruction.
break_void,
@ -331,7 +337,7 @@ pub const ResultInfo = struct {
}
};
pub const Context = enum {
const Context = enum {
/// The expression is the operand to a return expression.
@"return",
/// The expression is the input to an error-handling operator (if-else, try, or catch).
@ -349,11 +355,11 @@ pub const ResultInfo = struct {
};
};
pub const align_ri: ResultInfo = .{ .rl = .{ .ty = .u29_type } };
pub const coerced_align_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .u29_type } };
pub const bool_ri: ResultInfo = .{ .rl = .{ .ty = .bool_type } };
pub const type_ri: ResultInfo = .{ .rl = .{ .ty = .type_type } };
pub const coerced_type_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .type_type } };
const align_ri: ResultInfo = .{ .rl = .{ .ty = .u29_type } };
const coerced_align_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .u29_type } };
const bool_ri: ResultInfo = .{ .rl = .{ .ty = .bool_type } };
const type_ri: ResultInfo = .{ .rl = .{ .ty = .type_type } };
const coerced_type_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .type_type } };
fn typeExpr(gz: *GenZir, scope: *Scope, type_node: Ast.Node.Index) InnerError!Zir.Inst.Ref {
const prev_force_comptime = gz.force_comptime;
@ -1960,7 +1966,10 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
else
.@"break";
block_gz.break_count += 1;
if (rhs == 0) {
_ = try rvalue(parent_gz, block_gz.break_result_info, .void_value, node);
try genDefers(parent_gz, scope, parent_scope, .normal_only);
// As our last action before the break, "pop" the error trace if needed
@ -1970,7 +1979,6 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
_ = try parent_gz.addBreak(break_tag, block_inst, .void_value);
return Zir.Inst.Ref.unreachable_value;
}
block_gz.break_count += 1;
const operand = try reachableExpr(parent_gz, parent_scope, block_gz.break_result_info, rhs, node);
const search_index = @intCast(Zir.Inst.Index, astgen.instructions.len);
@ -2609,8 +2617,9 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.extended => switch (gz.astgen.instructions.items(.data)[inst].extended.opcode) {
.breakpoint,
.fence,
.set_align_stack,
.set_float_mode,
.set_align_stack,
.set_cold,
=> break :b true,
else => break :b false,
},
@ -2630,6 +2639,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.repeat_inline,
.panic,
.panic_comptime,
.trap,
.check_comptime_control_flow,
=> {
noreturn_src_node = statement;
@ -2658,7 +2668,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.validate_struct_init_comptime,
.validate_array_init,
.validate_array_init_comptime,
.set_cold,
.set_runtime_safety,
.closure_capture,
.memcpy,
@ -3506,7 +3515,7 @@ const WipMembers = struct {
/// (4 for src_hash + line + name + value + doc_comment + align + link_section + address_space )
const max_decl_size = 11;
pub fn init(gpa: Allocator, payload: *ArrayListUnmanaged(u32), decl_count: u32, field_count: u32, comptime bits_per_field: u32, comptime max_field_size: u32) Allocator.Error!Self {
fn init(gpa: Allocator, payload: *ArrayListUnmanaged(u32), decl_count: u32, field_count: u32, comptime bits_per_field: u32, comptime max_field_size: u32) Allocator.Error!Self {
const payload_top = @intCast(u32, payload.items.len);
const decls_start = payload_top + (decl_count + decls_per_u32 - 1) / decls_per_u32;
const field_bits_start = decls_start + decl_count * max_decl_size;
@ -3527,7 +3536,7 @@ const WipMembers = struct {
};
}
pub fn nextDecl(self: *Self, is_pub: bool, is_export: bool, has_align: bool, has_section_or_addrspace: bool) void {
fn nextDecl(self: *Self, is_pub: bool, is_export: bool, has_align: bool, has_section_or_addrspace: bool) void {
const index = self.payload_top + self.decl_index / decls_per_u32;
assert(index < self.decls_start);
const bit_bag: u32 = if (self.decl_index % decls_per_u32 == 0) 0 else self.payload.items[index];
@ -3539,7 +3548,7 @@ const WipMembers = struct {
self.decl_index += 1;
}
pub fn nextField(self: *Self, comptime bits_per_field: u32, bits: [bits_per_field]bool) void {
fn nextField(self: *Self, comptime bits_per_field: u32, bits: [bits_per_field]bool) void {
const fields_per_u32 = 32 / bits_per_field;
const index = self.field_bits_start + self.field_index / fields_per_u32;
assert(index < self.fields_start);
@ -3553,25 +3562,25 @@ const WipMembers = struct {
self.field_index += 1;
}
pub fn appendToDecl(self: *Self, data: u32) void {
fn appendToDecl(self: *Self, data: u32) void {
assert(self.decls_end < self.field_bits_start);
self.payload.items[self.decls_end] = data;
self.decls_end += 1;
}
pub fn appendToDeclSlice(self: *Self, data: []const u32) void {
fn appendToDeclSlice(self: *Self, data: []const u32) void {
assert(self.decls_end + data.len <= self.field_bits_start);
mem.copy(u32, self.payload.items[self.decls_end..], data);
self.decls_end += @intCast(u32, data.len);
}
pub fn appendToField(self: *Self, data: u32) void {
fn appendToField(self: *Self, data: u32) void {
assert(self.fields_end < self.payload.items.len);
self.payload.items[self.fields_end] = data;
self.fields_end += 1;
}
pub fn finishBits(self: *Self, comptime bits_per_field: u32) void {
fn finishBits(self: *Self, comptime bits_per_field: u32) void {
const empty_decl_slots = decls_per_u32 - (self.decl_index % decls_per_u32);
if (self.decl_index > 0 and empty_decl_slots < decls_per_u32) {
const index = self.payload_top + self.decl_index / decls_per_u32;
@ -3587,15 +3596,15 @@ const WipMembers = struct {
}
}
pub fn declsSlice(self: *Self) []u32 {
fn declsSlice(self: *Self) []u32 {
return self.payload.items[self.payload_top..self.decls_end];
}
pub fn fieldsSlice(self: *Self) []u32 {
fn fieldsSlice(self: *Self) []u32 {
return self.payload.items[self.field_bits_start..self.fields_end];
}
pub fn deinit(self: *Self) void {
fn deinit(self: *Self) void {
self.payload.items.len = self.payload_top;
}
};
@ -6583,6 +6592,9 @@ fn forExpr(
cond_block,
break_tag,
);
if (ri.rl.strategy(&loop_scope).tag == .break_void and loop_scope.break_count == 0) {
_ = try rvalue(parent_gz, ri, .void_value, node);
}
if (is_statement) {
_ = try parent_gz.addUnNode(.ensure_result_used, result, node);
}
@ -7976,6 +7988,9 @@ fn builtinCall(
switch (node_tags[params[0]]) {
.identifier => {
const ident_token = main_tokens[params[0]];
if (isPrimitive(tree.tokenSlice(ident_token))) {
return astgen.failTok(ident_token, "unable to export primitive value", .{});
}
decl_name = try astgen.identAsString(ident_token);
var s = scope;
@ -8056,27 +8071,35 @@ fn builtinCall(
},
.fence => {
const order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[0]);
const result = try gz.addExtendedPayload(.fence, Zir.Inst.UnNode{
_ = try gz.addExtendedPayload(.fence, Zir.Inst.UnNode{
.node = gz.nodeIndexToRelative(node),
.operand = order,
});
return rvalue(gz, ri, result, node);
return rvalue(gz, ri, .void_value, node);
},
.set_float_mode => {
const order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .float_mode_type } }, params[0]);
const result = try gz.addExtendedPayload(.set_float_mode, Zir.Inst.UnNode{
_ = try gz.addExtendedPayload(.set_float_mode, Zir.Inst.UnNode{
.node = gz.nodeIndexToRelative(node),
.operand = order,
});
return rvalue(gz, ri, result, node);
return rvalue(gz, ri, .void_value, node);
},
.set_align_stack => {
const order = try expr(gz, scope, align_ri, params[0]);
const result = try gz.addExtendedPayload(.set_align_stack, Zir.Inst.UnNode{
_ = try gz.addExtendedPayload(.set_align_stack, Zir.Inst.UnNode{
.node = gz.nodeIndexToRelative(node),
.operand = order,
});
return rvalue(gz, ri, result, node);
return rvalue(gz, ri, .void_value, node);
},
.set_cold => {
const order = try expr(gz, scope, ri, params[0]);
_ = try gz.addExtendedPayload(.set_cold, Zir.Inst.UnNode{
.node = gz.nodeIndexToRelative(node),
.operand = order,
});
return rvalue(gz, ri, .void_value, node);
},
.src => {
@ -8097,7 +8120,7 @@ fn builtinCall(
.error_return_trace => return rvalue(gz, ri, try gz.addNodeExtended(.error_return_trace, node), node),
.frame => return rvalue(gz, ri, try gz.addNodeExtended(.frame, node), node),
.frame_address => return rvalue(gz, ri, try gz.addNodeExtended(.frame_address, node), node),
.breakpoint => return rvalue(gz, ri, try gz.addNodeExtended(.breakpoint, node), node),
.breakpoint => return rvalue(gz, ri, try gz.addNodeExtended(.breakpoint, node), node),
.type_info => return simpleUnOpType(gz, scope, ri, node, params[0], .type_info),
.size_of => return simpleUnOpType(gz, scope, ri, node, params[0], .size_of),
@ -8111,7 +8134,6 @@ fn builtinCall(
.bool_to_int => return simpleUnOp(gz, scope, ri, node, bool_ri, params[0], .bool_to_int),
.embed_file => return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .ty = .const_slice_u8_type } }, params[0], .embed_file),
.error_name => return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .ty = .anyerror_type } }, params[0], .error_name),
.set_cold => return simpleUnOp(gz, scope, ri, node, bool_ri, params[0], .set_cold),
.set_runtime_safety => return simpleUnOp(gz, scope, ri, node, bool_ri, params[0], .set_runtime_safety),
.sqrt => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .sqrt),
.sin => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .sin),
@ -8171,6 +8193,11 @@ fn builtinCall(
try emitDbgNode(gz, node);
return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .ty = .const_slice_u8_type } }, params[0], if (gz.force_comptime) .panic_comptime else .panic);
},
.trap => {
try emitDbgNode(gz, node);
_ = try gz.addNode(.trap, node);
return rvalue(gz, ri, .void_value, node);
},
.error_to_int => {
const operand = try expr(gz, scope, .{ .rl = .none }, params[0]);
const result = try gz.addExtendedPayload(.error_to_int, Zir.Inst.UnNode{
@ -8357,14 +8384,14 @@ fn builtinCall(
},
.atomic_store => {
const int_type = try typeExpr(gz, scope, params[0]);
const result = try gz.addPlNode(.atomic_store, node, Zir.Inst.AtomicStore{
_ = try gz.addPlNode(.atomic_store, node, Zir.Inst.AtomicStore{
// zig fmt: off
.ptr = try expr(gz, scope, .{ .rl = .none }, params[1]),
.operand = try expr(gz, scope, .{ .rl = .{ .ty = int_type } }, params[2]),
.ordering = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[3]),
// zig fmt: on
});
return rvalue(gz, ri, result, node);
return rvalue(gz, ri, .void_value, node);
},
.mul_add => {
const float_type = try typeExpr(gz, scope, params[0]);
@ -8405,20 +8432,20 @@ fn builtinCall(
return rvalue(gz, ri, result, node);
},
.memcpy => {
const result = try gz.addPlNode(.memcpy, node, Zir.Inst.Memcpy{
_ = try gz.addPlNode(.memcpy, node, Zir.Inst.Memcpy{
.dest = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .manyptr_u8_type } }, params[0]),
.source = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .manyptr_const_u8_type } }, params[1]),
.byte_count = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, params[2]),
});
return rvalue(gz, ri, result, node);
return rvalue(gz, ri, .void_value, node);
},
.memset => {
const result = try gz.addPlNode(.memset, node, Zir.Inst.Memset{
_ = try gz.addPlNode(.memset, node, Zir.Inst.Memset{
.dest = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .manyptr_u8_type } }, params[0]),
.byte = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .u8_type } }, params[1]),
.byte_count = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, params[2]),
});
return rvalue(gz, ri, result, node);
return rvalue(gz, ri, .void_value, node);
},
.shuffle => {
const result = try gz.addPlNode(.shuffle, node, Zir.Inst.Shuffle{
@ -8459,12 +8486,12 @@ fn builtinCall(
.prefetch => {
const ptr = try expr(gz, scope, .{ .rl = .none }, params[0]);
const options = try comptimeExpr(gz, scope, .{ .rl = .{ .ty = .prefetch_options_type } }, params[1]);
const result = try gz.addExtendedPayload(.prefetch, Zir.Inst.BinNode{
_ = try gz.addExtendedPayload(.prefetch, Zir.Inst.BinNode{
.node = gz.nodeIndexToRelative(node),
.lhs = ptr,
.rhs = options,
});
return rvalue(gz, ri, result, node);
return rvalue(gz, ri, .void_value, node);
},
.c_va_arg => {
if (astgen.fn_block == null) {
@ -8509,16 +8536,6 @@ fn builtinCall(
}
}
fn simpleNoOpVoid(
gz: *GenZir,
ri: ResultInfo,
node: Ast.Node.Index,
tag: Zir.Inst.Tag,
) InnerError!Zir.Inst.Ref {
_ = try gz.addNode(tag, node);
return rvalue(gz, ri, .void_value, node);
}
fn hasDeclOrField(
gz: *GenZir,
scope: *Scope,
@ -8988,7 +9005,7 @@ const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
});
comptime {
// These checks ensure that std.zig.primitives stays in synce with the primitive->Zir map.
// These checks ensure that std.zig.primitives stays in sync with the primitive->Zir map.
const primitives = std.zig.primitives;
for (primitive_instrs.kvs) |kv| {
if (!primitives.isPrimitive(kv.key)) {
@ -10369,7 +10386,7 @@ fn appendErrorTok(
comptime format: []const u8,
args: anytype,
) !void {
try astgen.appendErrorTokNotes(token, format, args, &[0]u32{});
try astgen.appendErrorTokNotesOff(token, 0, format, args, &[0]u32{});
}
fn failTokNotes(
@ -10379,7 +10396,7 @@ fn failTokNotes(
args: anytype,
notes: []const u32,
) InnerError {
try appendErrorTokNotes(astgen, token, format, args, notes);
try appendErrorTokNotesOff(astgen, token, 0, format, args, notes);
return error.AnalysisFail;
}
@ -10390,27 +10407,11 @@ fn appendErrorTokNotes(
args: anytype,
notes: []const u32,
) !void {
@setCold(true);
const string_bytes = &astgen.string_bytes;
const msg = @intCast(u32, string_bytes.items.len);
try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);
const notes_index: u32 = if (notes.len != 0) blk: {
const notes_start = astgen.extra.items.len;
try astgen.extra.ensureTotalCapacity(astgen.gpa, notes_start + 1 + notes.len);
astgen.extra.appendAssumeCapacity(@intCast(u32, notes.len));
astgen.extra.appendSliceAssumeCapacity(notes);
break :blk @intCast(u32, notes_start);
} else 0;
try astgen.compile_errors.append(astgen.gpa, .{
.msg = msg,
.node = 0,
.token = token,
.byte_offset = 0,
.notes = notes_index,
});
return appendErrorTokNotesOff(astgen, token, 0, format, args, notes);
}
/// Same as `fail`, except given an absolute byte offset.
/// Same as `fail`, except given a token plus an offset from its starting byte
/// offset.
fn failOff(
astgen: *AstGen,
token: Ast.TokenIndex,
@ -10418,27 +10419,36 @@ fn failOff(
comptime format: []const u8,
args: anytype,
) InnerError {
try appendErrorOff(astgen, token, byte_offset, format, args);
try appendErrorTokNotesOff(astgen, token, byte_offset, format, args, &.{});
return error.AnalysisFail;
}
fn appendErrorOff(
fn appendErrorTokNotesOff(
astgen: *AstGen,
token: Ast.TokenIndex,
byte_offset: u32,
comptime format: []const u8,
args: anytype,
) Allocator.Error!void {
notes: []const u32,
) !void {
@setCold(true);
const gpa = astgen.gpa;
const string_bytes = &astgen.string_bytes;
const msg = @intCast(u32, string_bytes.items.len);
try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);
try astgen.compile_errors.append(astgen.gpa, .{
try string_bytes.writer(gpa).print(format ++ "\x00", args);
const notes_index: u32 = if (notes.len != 0) blk: {
const notes_start = astgen.extra.items.len;
try astgen.extra.ensureTotalCapacity(gpa, notes_start + 1 + notes.len);
astgen.extra.appendAssumeCapacity(@intCast(u32, notes.len));
astgen.extra.appendSliceAssumeCapacity(notes);
break :blk @intCast(u32, notes_start);
} else 0;
try astgen.compile_errors.append(gpa, .{
.msg = msg,
.node = 0,
.token = token,
.byte_offset = byte_offset,
.notes = 0,
.notes = notes_index,
});
}
@ -10447,6 +10457,16 @@ fn errNoteTok(
token: Ast.TokenIndex,
comptime format: []const u8,
args: anytype,
) Allocator.Error!u32 {
return errNoteTokOff(astgen, token, 0, format, args);
}
fn errNoteTokOff(
astgen: *AstGen,
token: Ast.TokenIndex,
byte_offset: u32,
comptime format: []const u8,
args: anytype,
) Allocator.Error!u32 {
@setCold(true);
const string_bytes = &astgen.string_bytes;
@ -10456,7 +10476,7 @@ fn errNoteTok(
.msg = msg,
.node = 0,
.token = token,
.byte_offset = 0,
.byte_offset = byte_offset,
.notes = 0,
});
}
@ -10787,7 +10807,7 @@ const Scope = struct {
/// ref of the capture for decls in this namespace
captures: std.AutoArrayHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{},
pub fn deinit(self: *Namespace, gpa: Allocator) void {
fn deinit(self: *Namespace, gpa: Allocator) void {
self.decls.deinit(gpa);
self.captures.deinit(gpa);
self.* = undefined;
@ -12623,3 +12643,42 @@ fn emitDbgStmt(gz: *GenZir, line: u32, column: u32) !void {
},
} });
}
fn lowerAstErrors(astgen: *AstGen) !void {
const tree = astgen.tree;
assert(tree.errors.len > 0);
const gpa = astgen.gpa;
const parse_err = tree.errors[0];
var msg: std.ArrayListUnmanaged(u8) = .{};
defer msg.deinit(gpa);
const token_starts = tree.tokens.items(.start);
const token_tags = tree.tokens.items(.tag);
var notes: std.ArrayListUnmanaged(u32) = .{};
defer notes.deinit(gpa);
if (token_tags[parse_err.token + @boolToInt(parse_err.token_is_prev)] == .invalid) {
const tok = parse_err.token + @boolToInt(parse_err.token_is_prev);
const bad_off = @intCast(u32, tree.tokenSlice(parse_err.token + @boolToInt(parse_err.token_is_prev)).len);
const byte_abs = token_starts[parse_err.token + @boolToInt(parse_err.token_is_prev)] + bad_off;
try notes.append(gpa, try astgen.errNoteTokOff(tok, bad_off, "invalid byte: '{'}'", .{
std.zig.fmtEscapes(tree.source[byte_abs..][0..1]),
}));
}
for (tree.errors[1..]) |note| {
if (!note.is_note) break;
msg.clearRetainingCapacity();
try tree.renderError(note, msg.writer(gpa));
try notes.append(gpa, try astgen.errNoteTok(note.token, "{s}", .{msg.items}));
}
const extra_offset = tree.errorOffset(parse_err);
msg.clearRetainingCapacity();
try tree.renderError(parse_err, msg.writer(gpa));
try astgen.appendErrorTokNotesOff(parse_err.token, extra_offset, "{s}", .{msg.items}, notes.items);
}

View File

@ -1338,7 +1338,6 @@ fn walkInstruction(
.embed_file,
.error_name,
.panic,
.set_cold, // @check
.set_runtime_safety, // @check
.sqrt,
.sin,

View File

@ -109,6 +109,7 @@ pub const Tag = enum {
sub_with_overflow,
tag_name,
This,
trap,
truncate,
Type,
type_info,
@ -915,6 +916,13 @@ pub const list = list: {
.param_count = 0,
},
},
.{
"@trap",
.{
.tag = .trap,
.param_count = 0,
},
},
.{
"@truncate",
.{

File diff suppressed because it is too large Load Diff

View File

@ -226,6 +226,7 @@ pub fn categorizeOperand(
.ret_ptr,
.constant,
.const_ty,
.trap,
.breakpoint,
.dbg_stmt,
.dbg_inline_begin,
@ -848,6 +849,7 @@ fn analyzeInst(
.ret_ptr,
.constant,
.const_ty,
.trap,
.breakpoint,
.dbg_stmt,
.dbg_inline_begin,

View File

@ -3528,6 +3528,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
const digest = hash: {
var path_hash: Cache.HashHelper = .{};
path_hash.addBytes(build_options.version);
path_hash.add(builtin.zig_backend);
if (!want_local_cache) {
path_hash.addOptionalBytes(file.pkg.root_src_directory.path);
}
@ -3537,44 +3538,66 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
const cache_directory = if (want_local_cache) mod.local_zir_cache else mod.global_zir_cache;
const zir_dir = cache_directory.handle;
var cache_file: ?std.fs.File = null;
defer if (cache_file) |f| f.close();
// Determine whether we need to reload the file from disk and redo parsing and AstGen.
switch (file.status) {
.never_loaded, .retryable_failure => cached: {
var lock: std.fs.File.Lock = switch (file.status) {
.never_loaded, .retryable_failure => lock: {
// First, load the cached ZIR code, if any.
log.debug("AstGen checking cache: {s} (local={}, digest={s})", .{
file.sub_file_path, want_local_cache, &digest,
});
// We ask for a lock in order to coordinate with other zig processes.
// If another process is already working on this file, we will get the cached
// version. Likewise if we're working on AstGen and another process asks for
// the cached file, they'll get it.
cache_file = zir_dir.openFile(&digest, .{ .lock = .Shared }) catch |err| switch (err) {
error.PathAlreadyExists => unreachable, // opening for reading
error.NoSpaceLeft => unreachable, // opening for reading
error.NotDir => unreachable, // no dir components
error.InvalidUtf8 => unreachable, // it's a hex encoded name
error.BadPathName => unreachable, // it's a hex encoded name
error.NameTooLong => unreachable, // it's a fixed size name
error.PipeBusy => unreachable, // it's not a pipe
error.WouldBlock => unreachable, // not asking for non-blocking I/O
break :lock .Shared;
},
.parse_failure, .astgen_failure, .success_zir => lock: {
const unchanged_metadata =
stat.size == file.stat.size and
stat.mtime == file.stat.mtime and
stat.inode == file.stat.inode;
error.SymLinkLoop,
error.FileNotFound,
error.Unexpected,
=> break :cached,
if (unchanged_metadata) {
log.debug("unmodified metadata of file: {s}", .{file.sub_file_path});
return;
}
else => |e| return e, // Retryable errors are handled at callsite.
};
log.debug("metadata changed: {s}", .{file.sub_file_path});
break :lock .Exclusive;
},
};
// We ask for a lock in order to coordinate with other zig processes.
// If another process is already working on this file, we will get the cached
// version. Likewise if we're working on AstGen and another process asks for
// the cached file, they'll get it.
const cache_file = while (true) {
break zir_dir.createFile(&digest, .{
.read = true,
.truncate = false,
.lock = lock,
}) catch |err| switch (err) {
error.NotDir => unreachable, // no dir components
error.InvalidUtf8 => unreachable, // it's a hex encoded name
error.BadPathName => unreachable, // it's a hex encoded name
error.NameTooLong => unreachable, // it's a fixed size name
error.PipeBusy => unreachable, // it's not a pipe
error.WouldBlock => unreachable, // not asking for non-blocking I/O
// There are no dir components, so you would think that this was
// unreachable, however we have observed on macOS two processes racing
// to do openat() with O_CREAT manifest in ENOENT.
error.FileNotFound => continue,
else => |e| return e, // Retryable errors are handled at callsite.
};
};
defer cache_file.close();
while (true) {
update: {
// First we read the header to determine the lengths of arrays.
const header = cache_file.?.reader().readStruct(Zir.Header) catch |err| switch (err) {
const header = cache_file.reader().readStruct(Zir.Header) catch |err| switch (err) {
// This can happen if Zig bails out of this function between creating
// the cached file and writing it.
error.EndOfStream => break :cached,
error.EndOfStream => break :update,
else => |e| return e,
};
const unchanged_metadata =
@ -3584,7 +3607,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
if (!unchanged_metadata) {
log.debug("AstGen cache stale: {s}", .{file.sub_file_path});
break :cached;
break :update;
}
log.debug("AstGen cache hit: {s} instructions_len={d}", .{
file.sub_file_path, header.instructions_len,
@ -3636,13 +3659,13 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
.iov_len = header.extra_len * 4,
},
};
const amt_read = try cache_file.?.readvAll(&iovecs);
const amt_read = try cache_file.readvAll(&iovecs);
const amt_expected = zir.instructions.len * 9 +
zir.string_bytes.len +
zir.extra.len * 4;
if (amt_read != amt_expected) {
log.warn("unexpected EOF reading cached ZIR for {s}", .{file.sub_file_path});
break :cached;
break :update;
}
if (data_has_safety_tag) {
const tags = zir.instructions.items(.tag);
@ -3678,42 +3701,22 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
return error.AnalysisFail;
}
return;
},
.parse_failure, .astgen_failure, .success_zir => {
const unchanged_metadata =
stat.size == file.stat.size and
stat.mtime == file.stat.mtime and
stat.inode == file.stat.inode;
}
if (unchanged_metadata) {
log.debug("unmodified metadata of file: {s}", .{file.sub_file_path});
return;
}
log.debug("metadata changed: {s}", .{file.sub_file_path});
},
// If we already have the exclusive lock then it is our job to update.
if (builtin.os.tag == .wasi or lock == .Exclusive) break;
// Otherwise, unlock to give someone a chance to get the exclusive lock
// and then upgrade to an exclusive lock.
cache_file.unlock();
lock = .Exclusive;
try cache_file.lock(lock);
}
if (cache_file) |f| {
f.close();
cache_file = null;
}
cache_file = zir_dir.createFile(&digest, .{ .lock = .Exclusive }) catch |err| switch (err) {
error.NotDir => unreachable, // no dir components
error.InvalidUtf8 => unreachable, // it's a hex encoded name
error.BadPathName => unreachable, // it's a hex encoded name
error.NameTooLong => unreachable, // it's a fixed size name
error.PipeBusy => unreachable, // it's not a pipe
error.WouldBlock => unreachable, // not asking for non-blocking I/O
error.FileNotFound => unreachable, // no dir components
else => |e| {
const pkg_path = file.pkg.root_src_directory.path orelse ".";
const cache_path = cache_directory.path orelse ".";
log.warn("unable to save cached ZIR code for {s}/{s} to {s}/{s}: {s}", .{
pkg_path, file.sub_file_path, cache_path, &digest, @errorName(e),
});
return;
},
// The cache is definitely stale so delete the contents to avoid an underwrite later.
cache_file.setEndPos(0) catch |err| switch (err) {
error.FileTooBig => unreachable, // 0 is not too big
else => |e| return e,
};
mod.lockAndClearFileCompileError(file);
@ -3753,67 +3756,9 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
file.source_loaded = true;
file.tree = try Ast.parse(gpa, source, .zig);
defer if (!file.tree_loaded) file.tree.deinit(gpa);
if (file.tree.errors.len != 0) {
const parse_err = file.tree.errors[0];
var msg = std.ArrayList(u8).init(gpa);
defer msg.deinit();
const token_starts = file.tree.tokens.items(.start);
const token_tags = file.tree.tokens.items(.tag);
const extra_offset = file.tree.errorOffset(parse_err);
try file.tree.renderError(parse_err, msg.writer());
const err_msg = try gpa.create(ErrorMsg);
err_msg.* = .{
.src_loc = .{
.file_scope = file,
.parent_decl_node = 0,
.lazy = if (extra_offset == 0) .{
.token_abs = parse_err.token,
} else .{
.byte_abs = token_starts[parse_err.token] + extra_offset,
},
},
.msg = try msg.toOwnedSlice(),
};
if (token_tags[parse_err.token + @boolToInt(parse_err.token_is_prev)] == .invalid) {
const bad_off = @intCast(u32, file.tree.tokenSlice(parse_err.token + @boolToInt(parse_err.token_is_prev)).len);
const byte_abs = token_starts[parse_err.token + @boolToInt(parse_err.token_is_prev)] + bad_off;
try mod.errNoteNonLazy(.{
.file_scope = file,
.parent_decl_node = 0,
.lazy = .{ .byte_abs = byte_abs },
}, err_msg, "invalid byte: '{'}'", .{std.zig.fmtEscapes(source[byte_abs..][0..1])});
}
for (file.tree.errors[1..]) |note| {
if (!note.is_note) break;
try file.tree.renderError(note, msg.writer());
err_msg.notes = try mod.gpa.realloc(err_msg.notes, err_msg.notes.len + 1);
err_msg.notes[err_msg.notes.len - 1] = .{
.src_loc = .{
.file_scope = file,
.parent_decl_node = 0,
.lazy = .{ .token_abs = note.token },
},
.msg = try msg.toOwnedSlice(),
};
}
{
comp.mutex.lock();
defer comp.mutex.unlock();
try mod.failed_files.putNoClobber(gpa, file, err_msg);
}
file.status = .parse_failure;
return error.AnalysisFail;
}
file.tree_loaded = true;
// Any potential AST errors are converted to ZIR errors here.
file.zir = try AstGen.generate(gpa, file.tree);
file.zir_loaded = true;
file.status = .success_zir;
@ -3870,7 +3815,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
.iov_len = file.zir.extra.len * 4,
},
};
cache_file.?.writevAll(&iovecs) catch |err| {
cache_file.writevAll(&iovecs) catch |err| {
const pkg_path = file.pkg.root_src_directory.path orelse ".";
const cache_path = cache_directory.path orelse ".";
log.warn("unable to write cached ZIR code for {s}/{s} to {s}/{s}: {s}", .{
@ -3922,6 +3867,9 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void {
const gpa = mod.gpa;
const new_zir = file.zir;
// The root decl will be null if the previous ZIR had AST errors.
const root_decl = file.root_decl.unwrap() orelse return;
// Maps from old ZIR to new ZIR, struct_decl, enum_decl, etc. Any instruction which
// creates a namespace, gets mapped from old to new here.
var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};
@ -3939,7 +3887,6 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void {
var decl_stack: ArrayListUnmanaged(Decl.Index) = .{};
defer decl_stack.deinit(gpa);
const root_decl = file.root_decl.unwrap().?;
try decl_stack.append(gpa, root_decl);
file.deleted_decls.clearRetainingCapacity();

View File

@ -8,11 +8,11 @@ const Allocator = mem.Allocator;
const assert = std.debug.assert;
const log = std.log.scoped(.package);
const main = @import("main.zig");
const ThreadPool = std.Thread.Pool;
const WaitGroup = std.Thread.WaitGroup;
const Compilation = @import("Compilation.zig");
const Module = @import("Module.zig");
const ThreadPool = @import("ThreadPool.zig");
const WaitGroup = @import("WaitGroup.zig");
const Cache = std.Build.Cache;
const build_options = @import("build_options");
const Manifest = @import("Manifest.zig");
@ -215,6 +215,7 @@ pub const build_zig_basename = "build.zig";
pub fn fetchAndAddDependencies(
pkg: *Package,
root_pkg: *Package,
arena: Allocator,
thread_pool: *ThreadPool,
http_client: *std.http.Client,
@ -224,7 +225,7 @@ pub fn fetchAndAddDependencies(
dependencies_source: *std.ArrayList(u8),
build_roots_source: *std.ArrayList(u8),
name_prefix: []const u8,
color: main.Color,
error_bundle: *std.zig.ErrorBundle.Wip,
all_modules: *AllModules,
) !void {
const max_bytes = 10 * 1024 * 1024;
@ -249,7 +250,7 @@ pub fn fetchAndAddDependencies(
if (ast.errors.len > 0) {
const file_path = try directory.join(arena, &.{Manifest.basename});
try main.printErrsMsgToStdErr(gpa, arena, ast, file_path, color);
try main.putAstErrorsIntoBundle(gpa, ast, file_path, error_bundle);
return error.PackageFetchFailed;
}
@ -257,14 +258,9 @@ pub fn fetchAndAddDependencies(
defer manifest.deinit(gpa);
if (manifest.errors.len > 0) {
const ttyconf: std.debug.TTY.Config = switch (color) {
.auto => std.debug.detectTTYConfig(std.io.getStdErr()),
.on => .escape_codes,
.off => .no_color,
};
const file_path = try directory.join(arena, &.{Manifest.basename});
for (manifest.errors) |msg| {
Report.renderErrorMessage(ast, file_path, ttyconf, msg, &.{});
try Report.addErrorMessage(ast, file_path, error_bundle, 0, msg);
}
return error.PackageFetchFailed;
}
@ -272,8 +268,7 @@ pub fn fetchAndAddDependencies(
const report: Report = .{
.ast = &ast,
.directory = directory,
.color = color,
.arena = arena,
.error_bundle = error_bundle,
};
var any_error = false;
@ -295,7 +290,8 @@ pub fn fetchAndAddDependencies(
all_modules,
);
try pkg.fetchAndAddDependencies(
try sub_pkg.fetchAndAddDependencies(
root_pkg,
arena,
thread_pool,
http_client,
@ -305,11 +301,12 @@ pub fn fetchAndAddDependencies(
dependencies_source,
build_roots_source,
sub_prefix,
color,
error_bundle,
all_modules,
);
try add(pkg, gpa, fqn, sub_pkg);
try pkg.add(gpa, name, sub_pkg);
try root_pkg.add(gpa, fqn, sub_pkg);
try dependencies_source.writer().print(" pub const {s} = @import(\"{}\");\n", .{
std.zig.fmtId(fqn), std.zig.fmtEscapes(fqn),
@ -347,8 +344,7 @@ pub fn createFilePkg(
const Report = struct {
ast: *const std.zig.Ast,
directory: Compilation.Directory,
color: main.Color,
arena: Allocator,
error_bundle: *std.zig.ErrorBundle.Wip,
fn fail(
report: Report,
@ -356,52 +352,46 @@ const Report = struct {
comptime fmt_string: []const u8,
fmt_args: anytype,
) error{ PackageFetchFailed, OutOfMemory } {
return failWithNotes(report, &.{}, tok, fmt_string, fmt_args);
}
const gpa = report.error_bundle.gpa;
fn failWithNotes(
report: Report,
notes: []const Compilation.AllErrors.Message,
tok: std.zig.Ast.TokenIndex,
comptime fmt_string: []const u8,
fmt_args: anytype,
) error{ PackageFetchFailed, OutOfMemory } {
const ttyconf: std.debug.TTY.Config = switch (report.color) {
.auto => std.debug.detectTTYConfig(std.io.getStdErr()),
.on => .escape_codes,
.off => .no_color,
};
const file_path = try report.directory.join(report.arena, &.{Manifest.basename});
renderErrorMessage(report.ast.*, file_path, ttyconf, .{
const file_path = try report.directory.join(gpa, &.{Manifest.basename});
defer gpa.free(file_path);
const msg = try std.fmt.allocPrint(gpa, fmt_string, fmt_args);
defer gpa.free(msg);
try addErrorMessage(report.ast.*, file_path, report.error_bundle, 0, .{
.tok = tok,
.off = 0,
.msg = try std.fmt.allocPrint(report.arena, fmt_string, fmt_args),
}, notes);
.msg = msg,
});
return error.PackageFetchFailed;
}
fn renderErrorMessage(
fn addErrorMessage(
ast: std.zig.Ast,
file_path: []const u8,
ttyconf: std.debug.TTY.Config,
eb: *std.zig.ErrorBundle.Wip,
notes_len: u32,
msg: Manifest.ErrorMessage,
notes: []const Compilation.AllErrors.Message,
) void {
) error{OutOfMemory}!void {
const token_starts = ast.tokens.items(.start);
const start_loc = ast.tokenLocation(0, msg.tok);
Compilation.AllErrors.Message.renderToStdErr(.{ .src = .{
.msg = msg.msg,
.src_path = file_path,
.line = @intCast(u32, start_loc.line),
.column = @intCast(u32, start_loc.column),
.span = .{
.start = token_starts[msg.tok],
.end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
.main = token_starts[msg.tok] + msg.off,
},
.source_line = ast.source[start_loc.line_start..start_loc.line_end],
.notes = notes,
} }, ttyconf);
try eb.addRootErrorMessage(.{
.msg = try eb.addString(msg.msg),
.src_loc = try eb.addSourceLocation(.{
.src_path = try eb.addString(file_path),
.span_start = token_starts[msg.tok],
.span_end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
.span_main = token_starts[msg.tok] + msg.off,
.line = @intCast(u32, start_loc.line),
.column = @intCast(u32, start_loc.column),
.source_line = try eb.addString(ast.source[start_loc.line_start..start_loc.line_end]),
}),
.notes_len = notes_len,
});
}
};
@ -432,6 +422,12 @@ fn fetchAndUnpack(
const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});
errdefer gpa.free(build_root);
var pkg_dir = global_cache_directory.handle.openDir(pkg_dir_sub_path, .{}) catch |err| switch (err) {
error.FileNotFound => break :cached,
else => |e| return e,
};
errdefer pkg_dir.close();
try build_roots_source.writer().print(" pub const {s} = \"{}\";\n", .{
std.zig.fmtId(fqn), std.zig.fmtEscapes(build_root),
});
@ -444,12 +440,6 @@ fn fetchAndUnpack(
return gop.value_ptr.*;
}
var pkg_dir = global_cache_directory.handle.openDir(pkg_dir_sub_path, .{}) catch |err| switch (err) {
error.FileNotFound => break :cached,
else => |e| return e,
};
errdefer pkg_dir.close();
const ptr = try gpa.create(Package);
errdefer gpa.destroy(ptr);
@ -501,9 +491,7 @@ fn fetchAndUnpack(
// by default, so the same logic applies for buffering the reader as for gzip.
try unpackTarball(gpa, &req, tmp_directory.handle, std.compress.xz);
} else {
return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{
uri.path,
});
return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{uri.path});
}
// TODO: delete files not included in the package prior to computing the package hash.
@ -530,10 +518,21 @@ fn fetchAndUnpack(
});
}
} else {
const notes: [1]Compilation.AllErrors.Message = .{.{ .plain = .{
.msg = try std.fmt.allocPrint(report.arena, "expected .hash = \"{s}\",", .{&actual_hex}),
} }};
return report.failWithNotes(&notes, dep.url_tok, "url field is missing corresponding hash field", .{});
const file_path = try report.directory.join(gpa, &.{Manifest.basename});
defer gpa.free(file_path);
const eb = report.error_bundle;
const notes_len = 1;
try Report.addErrorMessage(report.ast.*, file_path, eb, notes_len, .{
.tok = dep.url_tok,
.off = 0,
.msg = "url field is missing corresponding hash field",
});
const notes_start = try eb.reserveNotes(notes_len);
eb.extra.items[notes_start] = @enumToInt(try eb.addErrorMessage(.{
.msg = try eb.printString("expected .hash = \"{s}\",", .{&actual_hex}),
}));
return error.PackageFetchFailed;
}
const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});

View File

@ -574,11 +574,13 @@ pub const Block = struct {
});
}
fn addCmpVector(block: *Block, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref, cmp_op: std.math.CompareOperator, vector_ty: Air.Inst.Ref) !Air.Inst.Ref {
fn addCmpVector(block: *Block, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref, cmp_op: std.math.CompareOperator) !Air.Inst.Ref {
return block.addInst(.{
.tag = if (block.float_mode == .Optimized) .cmp_vector_optimized else .cmp_vector,
.data = .{ .ty_pl = .{
.ty = vector_ty,
.ty = try block.sema.addType(
try Type.vector(block.sema.arena, block.sema.typeOf(lhs).vectorLen(), Type.bool),
),
.payload = try block.sema.addExtra(Air.VectorCmp{
.lhs = lhs,
.rhs = rhs,
@ -1101,6 +1103,7 @@ fn analyzeBodyInner(
.@"unreachable" => break sema.zirUnreachable(block, inst),
.panic => break sema.zirPanic(block, inst, false),
.panic_comptime => break sema.zirPanic(block, inst, true),
.trap => break sema.zirTrap(block, inst),
// zig fmt: on
.extended => ext: {
@ -1167,6 +1170,11 @@ fn analyzeBodyInner(
i += 1;
continue;
},
.set_cold => {
try sema.zirSetCold(block, extended);
i += 1;
continue;
},
.breakpoint => {
if (!block.is_comptime) {
_ = try block.addNoOp(.breakpoint);
@ -1304,11 +1312,6 @@ fn analyzeBodyInner(
i += 1;
continue;
},
.set_cold => {
try sema.zirSetCold(block, inst);
i += 1;
continue;
},
.set_runtime_safety => {
try sema.zirSetRuntimeSafety(block, inst);
i += 1;
@ -1609,6 +1612,12 @@ fn analyzeBodyInner(
const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index);
const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len];
const err_union = try sema.resolveInst(extra.data.operand);
const err_union_ty = sema.typeOf(err_union);
if (err_union_ty.zigTypeTag() != .ErrorUnion) {
return sema.fail(block, operand_src, "expected error union type, found '{}'", .{
err_union_ty.fmt(sema.mod),
});
}
const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union);
assert(is_non_err != .none);
const is_non_err_tv = sema.resolveInstConst(block, operand_src, is_non_err, "try operand inside comptime block must be comptime-known") catch |err| {
@ -1616,7 +1625,6 @@ fn analyzeBodyInner(
return err;
};
if (is_non_err_tv.val.toBool()) {
const err_union_ty = sema.typeOf(err_union);
break :blk try sema.analyzeErrUnionPayload(block, src, err_union_ty, err_union, operand_src, false);
}
const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse
@ -2203,29 +2211,27 @@ pub fn fail(
fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
@setCold(true);
const gpa = sema.gpa;
if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) {
if (err_msg.src_loc.lazy == .unneeded) return error.NeededSourceLocation;
var arena = std.heap.ArenaAllocator.init(sema.gpa);
errdefer arena.deinit();
var errors = std.ArrayList(Compilation.AllErrors.Message).init(sema.gpa);
defer errors.deinit();
Compilation.AllErrors.add(sema.mod, &arena, &errors, err_msg.*) catch unreachable;
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
wip_errors.init(gpa) catch unreachable;
Compilation.addModuleErrorMsg(&wip_errors, err_msg.*) catch unreachable;
std.debug.print("compile error during Sema:\n", .{});
Compilation.AllErrors.Message.renderToStdErr(errors.items[0], .no_color);
var error_bundle = wip_errors.toOwnedBundle("") catch unreachable;
error_bundle.renderToStdErr(.{ .ttyconf = .no_color });
crash_report.compilerPanic("unexpected compile error occurred", null, null);
}
const mod = sema.mod;
ref: {
errdefer err_msg.destroy(mod.gpa);
errdefer err_msg.destroy(gpa);
if (err_msg.src_loc.lazy == .unneeded) {
return error.NeededSourceLocation;
}
try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1);
try mod.failed_files.ensureUnusedCapacity(mod.gpa, 1);
try mod.failed_decls.ensureUnusedCapacity(gpa, 1);
try mod.failed_files.ensureUnusedCapacity(gpa, 1);
const max_references = blk: {
if (sema.mod.comp.reference_trace) |num| break :blk num;
@ -2235,11 +2241,11 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
};
var referenced_by = if (sema.func) |some| some.owner_decl else sema.owner_decl_index;
var reference_stack = std.ArrayList(Module.ErrorMsg.Trace).init(sema.gpa);
var reference_stack = std.ArrayList(Module.ErrorMsg.Trace).init(gpa);
defer reference_stack.deinit();
// Avoid infinite loops.
var seen = std.AutoHashMap(Module.Decl.Index, void).init(sema.gpa);
var seen = std.AutoHashMap(Module.Decl.Index, void).init(gpa);
defer seen.deinit();
var cur_reference_trace: u32 = 0;
@ -2280,7 +2286,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
if (gop.found_existing) {
// If there are multiple errors for the same Decl, prefer the first one added.
sema.err = null;
err_msg.destroy(mod.gpa);
err_msg.destroy(gpa);
} else {
sema.err = err_msg;
gop.value_ptr.* = err_msg;
@ -5144,6 +5150,14 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index, force_comptime: bo
return always_noreturn;
}
fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index {
const src_node = sema.code.instructions.items(.data)[inst].node;
const src = LazySrcLoc.nodeOffset(src_node);
sema.src = src;
_ = try block.addNoOp(.trap);
return always_noreturn;
}
fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@ -5721,10 +5735,10 @@ fn zirSetAlignStack(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Inst
gop.value_ptr.* = .{ .alignment = alignment, .src = src };
}
fn zirSetCold(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
const is_cold = try sema.resolveConstBool(block, operand_src, inst_data.operand, "operand to @setCold must be comptime-known");
fn zirSetCold(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
const is_cold = try sema.resolveConstBool(block, operand_src, extra.operand, "operand to @setCold must be comptime-known");
const func = sema.func orelse return; // does nothing outside a function
func.is_cold = is_cold;
}
@ -8766,7 +8780,7 @@ fn funcCommon(
};
return sema.failWithOwnedErrorMsg(msg);
}
if (!Type.fnCallingConventionAllowsZigTypes(cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) {
if (!ret_poison and !Type.fnCallingConventionAllowsZigTypes(cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) {
const msg = msg: {
const msg = try sema.errMsg(block, ret_ty_src, "return type '{}' not allowed in function with calling convention '{s}'", .{
return_type.fmt(sema.mod), @tagName(cc_resolved),
@ -9403,7 +9417,7 @@ fn intCast(
const ok = if (is_vector) ok: {
const zeros = try Value.Tag.repeated.create(sema.arena, Value.zero);
const zero_inst = try sema.addConstant(sema.typeOf(operand), zeros);
const is_in_range = try block.addCmpVector(operand, zero_inst, .eq, try sema.addType(operand_ty));
const is_in_range = try block.addCmpVector(operand, zero_inst, .eq);
const all_in_range = try block.addInst(.{
.tag = .reduce,
.data = .{ .reduce = .{ .operand = is_in_range, .operation = .And } },
@ -9457,7 +9471,7 @@ fn intCast(
const dest_range = try sema.addConstant(unsigned_operand_ty, dest_range_val);
const ok = if (is_vector) ok: {
const is_in_range = try block.addCmpVector(diff_unsigned, dest_range, .lte, try sema.addType(operand_ty));
const is_in_range = try block.addCmpVector(diff_unsigned, dest_range, .lte);
const all_in_range = try block.addInst(.{
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
.data = .{ .reduce = .{
@ -9474,7 +9488,7 @@ fn intCast(
try sema.addSafetyCheck(block, ok, .cast_truncated_data);
} else {
const ok = if (is_vector) ok: {
const is_in_range = try block.addCmpVector(diff, dest_max, .lte, try sema.addType(operand_ty));
const is_in_range = try block.addCmpVector(diff, dest_max, .lte);
const all_in_range = try block.addInst(.{
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
.data = .{ .reduce = .{
@ -9495,7 +9509,7 @@ fn intCast(
const ok = if (is_vector) ok: {
const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
const zero_inst = try sema.addConstant(operand_ty, zero_val);
const is_in_range = try block.addCmpVector(operand, zero_inst, .gte, try sema.addType(operand_ty));
const is_in_range = try block.addCmpVector(operand, zero_inst, .gte);
const all_in_range = try block.addInst(.{
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
.data = .{ .reduce = .{
@ -12007,7 +12021,7 @@ fn zirShl(
const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: {
const bit_count_inst = try sema.addConstant(rhs_ty, try Value.Tag.repeated.create(sema.arena, bit_count_val));
const lt = try block.addCmpVector(rhs, bit_count_inst, .lt, try sema.addType(rhs_ty));
const lt = try block.addCmpVector(rhs, bit_count_inst, .lt);
break :ok try block.addInst(.{
.tag = .reduce,
.data = .{ .reduce = .{
@ -12163,7 +12177,7 @@ fn zirShr(
const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: {
const bit_count_inst = try sema.addConstant(rhs_ty, try Value.Tag.repeated.create(sema.arena, bit_count_val));
const lt = try block.addCmpVector(rhs, bit_count_inst, .lt, try sema.addType(rhs_ty));
const lt = try block.addCmpVector(rhs, bit_count_inst, .lt);
break :ok try block.addInst(.{
.tag = .reduce,
.data = .{ .reduce = .{
@ -12182,7 +12196,7 @@ fn zirShr(
const back = try block.addBinOp(.shl, result, rhs);
const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: {
const eql = try block.addCmpVector(lhs, back, .eq, try sema.addType(rhs_ty));
const eql = try block.addCmpVector(lhs, back, .eq);
break :ok try block.addInst(.{
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
.data = .{ .reduce = .{
@ -13183,7 +13197,7 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
const floored = try block.addUnOp(.floor, result);
if (resolved_type.zigTypeTag() == .Vector) {
const eql = try block.addCmpVector(result, floored, .eq, try sema.addType(resolved_type));
const eql = try block.addCmpVector(result, floored, .eq);
break :ok try block.addInst(.{
.tag = switch (block.float_mode) {
.Strict => .reduce,
@ -13207,7 +13221,7 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
if (resolved_type.zigTypeTag() == .Vector) {
const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
const zero = try sema.addConstant(resolved_type, zero_val);
const eql = try block.addCmpVector(remainder, zero, .eq, try sema.addType(resolved_type));
const eql = try block.addCmpVector(remainder, zero, .eq);
break :ok try block.addInst(.{
.tag = .reduce,
.data = .{ .reduce = .{
@ -13505,14 +13519,13 @@ fn addDivIntOverflowSafety(
var ok: Air.Inst.Ref = .none;
if (resolved_type.zigTypeTag() == .Vector) {
const vector_ty_ref = try sema.addType(resolved_type);
if (maybe_lhs_val == null) {
const min_int_ref = try sema.addConstant(resolved_type, min_int);
ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref);
ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq);
}
if (maybe_rhs_val == null) {
const neg_one_ref = try sema.addConstant(resolved_type, neg_one);
const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref);
const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq);
if (ok == .none) {
ok = rhs_ok;
} else {
@ -13564,7 +13577,7 @@ fn addDivByZeroSafety(
const ok = if (resolved_type.zigTypeTag() == .Vector) ok: {
const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
const zero = try sema.addConstant(resolved_type, zero_val);
const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type));
const ok = try block.addCmpVector(casted_rhs, zero, .neq);
break :ok try block.addInst(.{
.tag = if (is_int) .reduce else .reduce_optimized,
.data = .{ .reduce = .{
@ -15193,9 +15206,7 @@ fn cmpSelf(
};
try sema.requireRuntimeBlock(block, src, runtime_src);
if (resolved_type.zigTypeTag() == .Vector) {
const result_ty = try Type.vector(sema.arena, resolved_type.vectorLen(), Type.bool);
const result_ty_ref = try sema.addType(result_ty);
return block.addCmpVector(casted_lhs, casted_rhs, op, result_ty_ref);
return block.addCmpVector(casted_lhs, casted_rhs, op);
}
const tag = Air.Inst.Tag.fromCmpOp(op, block.float_mode == .Optimized);
return block.addBinOp(tag, casted_lhs, casted_rhs);
@ -18360,7 +18371,7 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in
const union_val = val.cast(Value.Payload.Union).?.data;
const target = mod.getTarget();
const tag_index = type_info_ty.unionTagFieldIndex(union_val.tag, mod).?;
if (union_val.val.anyUndef()) return sema.failWithUseOfUndef(block, src);
if (union_val.val.anyUndef(mod)) return sema.failWithUseOfUndef(block, src);
switch (@intToEnum(std.builtin.TypeId, tag_index)) {
.Type => return Air.Inst.Ref.type_type,
.Void => return Air.Inst.Ref.void_type,
@ -22274,7 +22285,6 @@ fn zirBuiltinExtern(
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const src = LazySrcLoc.nodeOffset(extra.node);
const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node };
@ -22302,39 +22312,41 @@ fn zirBuiltinExtern(
const new_decl = sema.mod.declPtr(new_decl_index);
new_decl.name = try sema.gpa.dupeZ(u8, options.name);
var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
errdefer new_decl_arena.deinit();
const new_decl_arena_allocator = new_decl_arena.allocator();
{
var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
errdefer new_decl_arena.deinit();
const new_decl_arena_allocator = new_decl_arena.allocator();
const new_var = try new_decl_arena_allocator.create(Module.Var);
errdefer new_decl_arena_allocator.destroy(new_var);
const new_var = try new_decl_arena_allocator.create(Module.Var);
new_var.* = .{
.owner_decl = sema.owner_decl_index,
.init = Value.initTag(.unreachable_value),
.is_extern = true,
.is_mutable = false,
.is_threadlocal = options.is_thread_local,
.is_weak_linkage = options.linkage == .Weak,
.lib_name = null,
};
new_var.* = .{
.owner_decl = sema.owner_decl_index,
.init = Value.initTag(.unreachable_value),
.is_extern = true,
.is_mutable = false,
.is_threadlocal = options.is_thread_local,
.is_weak_linkage = options.linkage == .Weak,
.lib_name = null,
};
new_decl.src_line = sema.owner_decl.src_line;
// We only access this decl through the decl_ref with the correct type created
// below, so this type doesn't matter
new_decl.ty = Type.Tag.init(.anyopaque);
new_decl.val = try Value.Tag.variable.create(new_decl_arena_allocator, new_var);
new_decl.@"align" = 0;
new_decl.@"linksection" = null;
new_decl.has_tv = true;
new_decl.analysis = .complete;
new_decl.generation = sema.mod.generation;
new_decl.src_line = sema.owner_decl.src_line;
new_decl.ty = try ty.copy(new_decl_arena_allocator);
new_decl.val = try Value.Tag.variable.create(new_decl_arena_allocator, new_var);
new_decl.@"align" = 0;
new_decl.@"linksection" = null;
new_decl.has_tv = true;
new_decl.analysis = .complete;
new_decl.generation = sema.mod.generation;
try new_decl.finalizeNewArena(&new_decl_arena);
}
const arena_state = try new_decl_arena_allocator.create(std.heap.ArenaAllocator.State);
arena_state.* = new_decl_arena.state;
new_decl.value_arena = arena_state;
try sema.mod.declareDeclDependency(sema.owner_decl_index, new_decl_index);
try sema.ensureDeclAnalyzed(new_decl_index);
const ref = try sema.analyzeDeclRef(new_decl_index);
try sema.requireRuntimeBlock(block, src, null);
return block.addBitCast(ty, ref);
const ref = try Value.Tag.decl_ref.create(sema.arena, new_decl_index);
return sema.addConstant(ty, ref);
}
fn requireRuntimeBlock(sema: *Sema, block: *Block, src: LazySrcLoc, runtime_src: ?LazySrcLoc) !void {
@ -23026,7 +23038,7 @@ fn panicSentinelMismatch(
const ok = if (sentinel_ty.zigTypeTag() == .Vector) ok: {
const eql =
try parent_block.addCmpVector(expected_sentinel, actual_sentinel, .eq, try sema.addType(sentinel_ty));
try parent_block.addCmpVector(expected_sentinel, actual_sentinel, .eq);
break :ok try parent_block.addInst(.{
.tag = .reduce,
.data = .{ .reduce = .{
@ -23592,10 +23604,13 @@ fn fieldCallBind(
}
// If we get here, we need to look for a decl in the struct type instead.
switch (concrete_ty.zigTypeTag()) {
.Struct, .Opaque, .Union, .Enum => {
const found_decl = switch (concrete_ty.zigTypeTag()) {
.Struct, .Opaque, .Union, .Enum => found_decl: {
if (concrete_ty.getNamespace()) |namespace| {
if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
if (try sema.namespaceLookup(block, src, namespace, field_name)) |decl_idx| {
try sema.addReferencedBy(block, src, decl_idx);
const inst = try sema.analyzeDeclRef(decl_idx);
const decl_val = try sema.analyzeLoad(block, src, inst, src);
const decl_type = sema.typeOf(decl_val);
if (decl_type.zigTypeTag() == .Fn and
@ -23612,7 +23627,7 @@ fn fieldCallBind(
first_param_type.ptrSize() == .C) and
first_param_type.childType().eql(concrete_ty, sema.mod)))
{
// zig fmt: on
// zig fmt: on
// TODO: bound fn calls on rvalues should probably
// generate a by-value argument somehow.
const ty = Type.Tag.bound_fn.init();
@ -23651,16 +23666,22 @@ fn fieldCallBind(
return sema.addConstant(ty, value);
}
}
break :found_decl decl_idx;
}
}
break :found_decl null;
},
else => {},
}
else => null,
};
const msg = msg: {
const msg = try sema.errMsg(block, src, "no field or member function named '{s}' in '{}'", .{ field_name, concrete_ty.fmt(sema.mod) });
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, concrete_ty);
if (found_decl) |decl_idx| {
const decl = sema.mod.declPtr(decl_idx);
try sema.mod.errNoteNonLazy(decl.srcLoc(), msg, "'{s}' is not a member function", .{field_name});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(msg);
@ -24832,6 +24853,7 @@ fn coerceExtra(
const array_ty = dest_info.pointee_type;
if (array_ty.zigTypeTag() != .Array) break :single_item;
const array_elem_ty = array_ty.childType();
if (array_ty.arrayLen() != 1) break :single_item;
const dest_is_mut = dest_info.mutable;
switch (try sema.coerceInMemoryAllowed(block, array_elem_ty, ptr_elem_ty, dest_is_mut, target, dest_ty_src, inst_src)) {
.ok => {},
@ -26625,6 +26647,23 @@ fn beginComptimePtrMutation(
});
}
const elem_ty = parent.ty.childType();
// We might have a pointer to multiple elements of the array (e.g. a pointer
// to a sub-array). In this case, we just have to reinterpret the relevant
// bytes of the whole array rather than any single element.
const elem_abi_size_u64 = try sema.typeAbiSize(elem_ptr.elem_ty);
if (elem_abi_size_u64 < try sema.typeAbiSize(ptr_elem_ty)) {
const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64);
return .{
.decl_ref_mut = parent.decl_ref_mut,
.pointee = .{ .reinterpret = .{
.val_ptr = val_ptr,
.byte_offset = elem_abi_size * elem_ptr.index,
} },
.ty = parent.ty,
};
}
switch (val_ptr.tag()) {
.undef => {
// An array has been initialized to undefined at comptime and now we
@ -29359,8 +29398,7 @@ fn cmpVector(
};
try sema.requireRuntimeBlock(block, src, runtime_src);
const result_ty_inst = try sema.addType(result_ty);
return block.addCmpVector(lhs, rhs, op, result_ty_inst);
return block.addCmpVector(lhs, rhs, op);
}
fn wrapOptional(

View File

@ -617,7 +617,7 @@ pub const Inst = struct {
/// Uses the `un_node` field.
typeof_log2_int_type,
/// Asserts control-flow will not reach this instruction (`unreachable`).
/// Uses the `unreachable` union field.
/// Uses the `@"unreachable"` union field.
@"unreachable",
/// Bitwise XOR. `^`
/// Uses the `pl_node` union field. Payload is `Bin`.
@ -808,8 +808,9 @@ pub const Inst = struct {
panic,
/// Same as `panic` but forces comptime.
panic_comptime,
/// Implement builtin `@setCold`. Uses `un_node`.
set_cold,
/// Implements `@trap`.
/// Uses the `node` field.
trap,
/// Implement builtin `@setRuntimeSafety`. Uses `un_node`.
set_runtime_safety,
/// Implement builtin `@sqrt`. Uses `un_node`.
@ -1187,7 +1188,6 @@ pub const Inst = struct {
.bool_to_int,
.embed_file,
.error_name,
.set_cold,
.set_runtime_safety,
.sqrt,
.sin,
@ -1277,6 +1277,7 @@ pub const Inst = struct {
.repeat_inline,
.panic,
.panic_comptime,
.trap,
.check_comptime_control_flow,
=> true,
};
@ -1323,7 +1324,6 @@ pub const Inst = struct {
.validate_deref,
.@"export",
.export_value,
.set_cold,
.set_runtime_safety,
.memcpy,
.memset,
@ -1553,6 +1553,7 @@ pub const Inst = struct {
.repeat_inline,
.panic,
.panic_comptime,
.trap,
.for_len,
.@"try",
.try_ptr,
@ -1561,7 +1562,7 @@ pub const Inst = struct {
=> false,
.extended => switch (data.extended.opcode) {
.breakpoint, .fence => true,
.fence, .set_cold, .breakpoint => true,
else => false,
},
};
@ -1750,7 +1751,7 @@ pub const Inst = struct {
.error_name = .un_node,
.panic = .un_node,
.panic_comptime = .un_node,
.set_cold = .un_node,
.trap = .node,
.set_runtime_safety = .un_node,
.sqrt = .un_node,
.sin = .un_node,
@ -1979,11 +1980,15 @@ pub const Inst = struct {
/// Implement builtin `@setAlignStack`.
/// `operand` is payload index to `UnNode`.
set_align_stack,
/// Implements `@setCold`.
/// `operand` is payload index to `UnNode`.
set_cold,
/// Implements the `@errSetCast` builtin.
/// `operand` is payload index to `BinNode`. `lhs` is dest type, `rhs` is operand.
err_set_cast,
/// `operand` is payload index to `UnNode`.
await_nosuspend,
/// Implements `@breakpoint`.
/// `operand` is `src_node: i32`.
breakpoint,
/// Implements the `@select` builtin.
@ -1997,7 +2002,7 @@ pub const Inst = struct {
int_to_error,
/// Implement builtin `@Type`.
/// `operand` is payload index to `UnNode`.
/// `small` contains `NameStrategy
/// `small` contains `NameStrategy`.
reify,
/// Implements the `@asyncCall` builtin.
/// `operand` is payload index to `AsyncCall`.
@ -2040,8 +2045,7 @@ pub const Inst = struct {
/// A reference to a TypedValue or ZIR instruction.
///
/// If the Ref has a tag in this enum, it refers to a TypedValue which may be
/// retrieved with Ref.toTypedValue().
/// If the Ref has a tag in this enum, it refers to a TypedValue.
///
/// If the value of a Ref does not have a tag, it refers to a ZIR instruction.
///
@ -3590,6 +3594,12 @@ pub const Inst = struct {
/// 0 or a payload index of a `Block`, each is a payload
/// index of another `Item`.
notes: u32,
pub fn notesLen(item: Item, zir: Zir) u32 {
if (item.notes == 0) return 0;
const block = zir.extraData(Block, item.notes);
return block.data.body_len;
}
};
};

Some files were not shown because too many files have changed in this diff Show More