mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
Merge remote-tracking branch 'origin/master' into llvm16
This commit is contained in:
commit
1ed569e0b2
@ -296,14 +296,12 @@ set(ZIG_STAGE2_SOURCES
|
|||||||
"${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/multi_array_list.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/multi_array_list.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/os.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.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/os/linux/errno/generic.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/x86_64.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/os/linux.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/io_uring.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.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.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.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.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/Thread/Futex.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/Thread/Futex.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/Thread/Mutex.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/ResetEvent.zig"
|
||||||
|
"${CMAKE_SOURCE_DIR}/lib/std/Thread/WaitGroup.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/time.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/time.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/treap.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/treap.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/unicode.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/c_builtins.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/zig/render.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/string_literal.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
|
"${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.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/Package.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/src/RangeSet.zig"
|
"${CMAKE_SOURCE_DIR}/src/RangeSet.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/src/Sema.zig"
|
"${CMAKE_SOURCE_DIR}/src/Sema.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/src/ThreadPool.zig"
|
|
||||||
"${CMAKE_SOURCE_DIR}/src/TypedValue.zig"
|
"${CMAKE_SOURCE_DIR}/src/TypedValue.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/src/WaitGroup.zig"
|
|
||||||
"${CMAKE_SOURCE_DIR}/src/Zir.zig"
|
"${CMAKE_SOURCE_DIR}/src/Zir.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/src/arch/aarch64/CodeGen.zig"
|
"${CMAKE_SOURCE_DIR}/src/arch/aarch64/CodeGen.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/src/arch/aarch64/Emit.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/wasm/Mir.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/src/arch/x86_64/CodeGen.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/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/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/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.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/src/clang_options.zig"
|
"${CMAKE_SOURCE_DIR}/src/clang_options.zig"
|
||||||
"${CMAKE_SOURCE_DIR}/src/clang_options_data.zig"
|
"${CMAKE_SOURCE_DIR}/src/clang_options_data.zig"
|
||||||
@ -827,6 +829,12 @@ else()
|
|||||||
set(ZIG_STATIC_ARG "")
|
set(ZIG_STATIC_ARG "")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_POSITION_INDEPENDENT_CODE)
|
||||||
|
set(ZIG_PIE_ARG="-Dpie")
|
||||||
|
else()
|
||||||
|
set(ZIG_PIE_ARG="")
|
||||||
|
endif()
|
||||||
|
|
||||||
set(ZIG_BUILD_ARGS
|
set(ZIG_BUILD_ARGS
|
||||||
--zig-lib-dir "${CMAKE_SOURCE_DIR}/lib"
|
--zig-lib-dir "${CMAKE_SOURCE_DIR}/lib"
|
||||||
"-Dconfig_h=${ZIG_CONFIG_H_OUT}"
|
"-Dconfig_h=${ZIG_CONFIG_H_OUT}"
|
||||||
@ -835,6 +843,7 @@ set(ZIG_BUILD_ARGS
|
|||||||
${ZIG_STATIC_ARG}
|
${ZIG_STATIC_ARG}
|
||||||
${ZIG_NO_LIB_ARG}
|
${ZIG_NO_LIB_ARG}
|
||||||
${ZIG_SINGLE_THREADED_ARG}
|
${ZIG_SINGLE_THREADED_ARG}
|
||||||
|
${ZIG_PIE_ARG}
|
||||||
"-Dtarget=${ZIG_TARGET_TRIPLE}"
|
"-Dtarget=${ZIG_TARGET_TRIPLE}"
|
||||||
"-Dcpu=${ZIG_TARGET_MCPU}"
|
"-Dcpu=${ZIG_TARGET_MCPU}"
|
||||||
"-Dversion-string=${RESOLVED_ZIG_VERSION}"
|
"-Dversion-string=${RESOLVED_ZIG_VERSION}"
|
||||||
|
|||||||
220
build.zig
220
build.zig
@ -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 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 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(.{
|
const docgen_exe = b.addExecutable(.{
|
||||||
.name = "docgen",
|
.name = "docgen",
|
||||||
@ -40,28 +45,35 @@ pub fn build(b: *std.Build) !void {
|
|||||||
});
|
});
|
||||||
docgen_exe.single_threaded = single_threaded;
|
docgen_exe.single_threaded = single_threaded;
|
||||||
|
|
||||||
const langref_out_path = try b.cache_root.join(b.allocator, &.{"langref.html"});
|
const docgen_cmd = b.addRunArtifact(docgen_exe);
|
||||||
const docgen_cmd = docgen_exe.run();
|
docgen_cmd.addArgs(&.{ "--zig", b.zig_exe });
|
||||||
docgen_cmd.addArgs(&[_][]const u8{
|
if (b.zig_lib_dir) |p| {
|
||||||
"--zig",
|
docgen_cmd.addArgs(&.{ "--zig-lib-dir", p });
|
||||||
b.zig_exe,
|
}
|
||||||
"doc" ++ fs.path.sep_str ++ "langref.html.in",
|
docgen_cmd.addFileSourceArg(.{ .path = "doc/langref.html.in" });
|
||||||
langref_out_path,
|
const langref_file = docgen_cmd.addOutputFileArg("langref.html");
|
||||||
});
|
const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "doc/langref.html");
|
||||||
docgen_cmd.step.dependOn(&docgen_exe.step);
|
if (!skip_install_lib_files) {
|
||||||
|
b.getInstallStep().dependOn(&install_langref.step);
|
||||||
|
}
|
||||||
|
|
||||||
const docs_step = b.step("docs", "Build documentation");
|
const docs_step = b.step("docs", "Build documentation");
|
||||||
docs_step.dependOn(&docgen_cmd.step);
|
docs_step.dependOn(&docgen_cmd.step);
|
||||||
|
|
||||||
const test_cases = b.addTest(.{
|
// This is for legacy reasons, to be removed after our CI scripts are upgraded to use
|
||||||
.root_source_file = .{ .path = "src/test.zig" },
|
// 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,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
test_cases.main_pkg_path = ".";
|
check_case_exe.main_pkg_path = ".";
|
||||||
test_cases.stack_size = stack_size;
|
check_case_exe.stack_size = stack_size;
|
||||||
test_cases.single_threaded = single_threaded;
|
check_case_exe.single_threaded = single_threaded;
|
||||||
|
|
||||||
const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"});
|
|
||||||
|
|
||||||
const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false;
|
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;
|
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_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_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 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;
|
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 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 sanitize_thread = b.option(bool, "sanitize-thread", "Enable thread-sanitization") orelse false;
|
||||||
const strip = b.option(bool, "strip", "Omit debug information");
|
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 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: {
|
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);
|
const exe = addCompilerStep(b, optimize, target);
|
||||||
exe.strip = strip;
|
exe.strip = strip;
|
||||||
|
exe.pie = pie;
|
||||||
exe.sanitize_thread = sanitize_thread;
|
exe.sanitize_thread = sanitize_thread;
|
||||||
exe.build_id = b.option(bool, "build-id", "Include a build id note") orelse false;
|
exe.build_id = b.option(bool, "build-id", "Include a build id note") orelse false;
|
||||||
exe.install();
|
exe.install();
|
||||||
@ -178,13 +187,12 @@ pub fn build(b: *std.Build) !void {
|
|||||||
test_step.dependOn(&exe.step);
|
test_step.dependOn(&exe.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
b.default_step.dependOn(&exe.step);
|
|
||||||
exe.single_threaded = single_threaded;
|
exe.single_threaded = single_threaded;
|
||||||
|
|
||||||
if (target.isWindows() and target.getAbi() == .gnu) {
|
if (target.isWindows() and target.getAbi() == .gnu) {
|
||||||
// LTO is currently broken on mingw, this can be removed when it's fixed.
|
// LTO is currently broken on mingw, this can be removed when it's fixed.
|
||||||
exe.want_lto = false;
|
exe.want_lto = false;
|
||||||
test_cases.want_lto = false;
|
check_case_exe.want_lto = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const exe_options = b.addOptions();
|
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, "llvm_has_xtensa", llvm_has_xtensa);
|
||||||
exe_options.addOption(bool, "force_gpa", force_gpa);
|
exe_options.addOption(bool, "force_gpa", force_gpa);
|
||||||
exe_options.addOption(bool, "only_c", only_c);
|
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) {
|
if (link_libc) {
|
||||||
exe.linkLibC();
|
exe.linkLibC();
|
||||||
test_cases.linkLibC();
|
check_case_exe.linkLibC();
|
||||||
}
|
}
|
||||||
|
|
||||||
const is_debug = optimize == .Debug;
|
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, exe, use_zig_libcxx);
|
||||||
try addCmakeCfgOptionsToExe(b, cfg, test_cases, use_zig_libcxx);
|
try addCmakeCfgOptionsToExe(b, cfg, check_case_exe, use_zig_libcxx);
|
||||||
} else {
|
} else {
|
||||||
// Here we are -Denable-llvm but no cmake integration.
|
// Here we are -Denable-llvm but no cmake integration.
|
||||||
try addStaticLlvmOptionsToExe(exe);
|
try addStaticLlvmOptionsToExe(exe);
|
||||||
try addStaticLlvmOptionsToExe(test_cases);
|
try addStaticLlvmOptionsToExe(check_case_exe);
|
||||||
}
|
}
|
||||||
if (target.isWindows()) {
|
if (target.isWindows()) {
|
||||||
inline for (.{ exe, test_cases }) |artifact| {
|
inline for (.{ exe, check_case_exe }) |artifact| {
|
||||||
artifact.linkSystemLibrary("version");
|
artifact.linkSystemLibrary("version");
|
||||||
artifact.linkSystemLibrary("uuid");
|
artifact.linkSystemLibrary("uuid");
|
||||||
artifact.linkSystemLibrary("ole32");
|
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_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
|
||||||
|
|
||||||
const test_cases_options = b.addOptions();
|
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_logging", enable_logging);
|
||||||
test_cases_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots);
|
test_cases_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots);
|
||||||
test_cases_options.addOption(bool, "skip_non_native", skip_non_native);
|
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(std.SemanticVersion, "semver", semver);
|
||||||
test_cases_options.addOption(?[]const u8, "test_filter", test_filter);
|
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_opt_modes_buf: [4]builtin.Mode = undefined;
|
||||||
var chosen_mode_index: usize = 0;
|
var chosen_mode_index: usize = 0;
|
||||||
if (!skip_debug) {
|
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];
|
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
|
const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" };
|
||||||
test_step.dependOn(&fmt_build_zig.step);
|
const fmt_exclude_paths = &.{"test/cases"};
|
||||||
const fmt_step = b.step("test-fmt", "Run zig fmt against build.zig to make sure it works");
|
const do_fmt = b.addFmt(.{
|
||||||
fmt_step.dependOn(&fmt_build_zig.step);
|
.paths = fmt_include_paths,
|
||||||
|
.exclude_paths = fmt_exclude_paths,
|
||||||
|
});
|
||||||
|
|
||||||
test_step.dependOn(tests.addPkgTests(
|
b.step("test-fmt", "Check source files having conforming formatting").dependOn(&b.addFmt(.{
|
||||||
b,
|
.paths = fmt_include_paths,
|
||||||
test_filter,
|
.exclude_paths = fmt_exclude_paths,
|
||||||
"test/behavior.zig",
|
.check = true,
|
||||||
"behavior",
|
}).step);
|
||||||
"Run the behavior tests",
|
|
||||||
optimization_modes,
|
|
||||||
skip_single_threaded,
|
|
||||||
skip_non_native,
|
|
||||||
skip_libc,
|
|
||||||
skip_stage1,
|
|
||||||
skip_stage2_tests,
|
|
||||||
));
|
|
||||||
|
|
||||||
test_step.dependOn(tests.addPkgTests(
|
const test_cases_step = b.step("test-cases", "Run the main compiler test cases");
|
||||||
b,
|
try tests.addCases(b, test_cases_step, test_filter, check_case_exe);
|
||||||
test_filter,
|
if (!skip_stage2_tests) test_step.dependOn(test_cases_step);
|
||||||
"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
|
|
||||||
));
|
|
||||||
|
|
||||||
test_step.dependOn(tests.addPkgTests(
|
test_step.dependOn(tests.addModuleTests(b, .{
|
||||||
b,
|
.test_filter = test_filter,
|
||||||
test_filter,
|
.root_src = "test/behavior.zig",
|
||||||
"lib/c.zig",
|
.name = "behavior",
|
||||||
"universal-libc",
|
.desc = "Run the behavior tests",
|
||||||
"Run the universal libc tests",
|
.optimize_modes = optimization_modes,
|
||||||
optimization_modes,
|
.skip_single_threaded = skip_single_threaded,
|
||||||
true, // skip_single_threaded
|
.skip_non_native = skip_non_native,
|
||||||
skip_non_native,
|
.skip_libc = skip_libc,
|
||||||
true, // skip_libc
|
.skip_stage1 = skip_stage1,
|
||||||
skip_stage1,
|
.skip_stage2 = skip_stage2_tests,
|
||||||
skip_stage2_tests or true, // TODO get these all passing
|
.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.addCompareOutputTests(b, test_filter, optimization_modes));
|
||||||
test_step.dependOn(tests.addStandaloneTests(
|
test_step.dependOn(tests.addStandaloneTests(
|
||||||
b,
|
b,
|
||||||
test_filter,
|
|
||||||
optimization_modes,
|
optimization_modes,
|
||||||
skip_non_native,
|
|
||||||
enable_macos_sdk,
|
enable_macos_sdk,
|
||||||
target,
|
|
||||||
skip_stage2_tests,
|
skip_stage2_tests,
|
||||||
b.enable_darling,
|
|
||||||
b.enable_qemu,
|
|
||||||
b.enable_rosetta,
|
|
||||||
b.enable_wasmtime,
|
|
||||||
b.enable_wine,
|
|
||||||
enable_symlinks_windows,
|
enable_symlinks_windows,
|
||||||
));
|
));
|
||||||
test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release));
|
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.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.addAssembleAndLinkTests(b, test_filter, optimization_modes));
|
||||||
test_step.dependOn(tests.addTranslateCTests(b, test_filter));
|
test_step.dependOn(tests.addTranslateCTests(b, test_filter));
|
||||||
if (!skip_run_translated_c) {
|
if (!skip_run_translated_c) {
|
||||||
test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter, target));
|
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(
|
test_step.dependOn(tests.addModuleTests(b, .{
|
||||||
b,
|
.test_filter = test_filter,
|
||||||
test_filter,
|
.root_src = "lib/std/std.zig",
|
||||||
"lib/std/std.zig",
|
.name = "std",
|
||||||
"std",
|
.desc = "Run the standard library tests",
|
||||||
"Run the standard library tests",
|
.optimize_modes = optimization_modes,
|
||||||
optimization_modes,
|
.skip_single_threaded = skip_single_threaded,
|
||||||
skip_single_threaded,
|
.skip_non_native = skip_non_native,
|
||||||
skip_non_native,
|
.skip_libc = skip_libc,
|
||||||
skip_libc,
|
.skip_stage1 = skip_stage1,
|
||||||
skip_stage1,
|
.skip_stage2 = true, // TODO get all these passing
|
||||||
true, // TODO get these all 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);
|
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 {
|
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_callstack", false);
|
||||||
exe_options.addOption(bool, "enable_tracy_allocation", false);
|
exe_options.addOption(bool, "enable_tracy_allocation", false);
|
||||||
exe_options.addOption(bool, "value_tracing", 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" });
|
const run_opt = b.addSystemCommand(&.{ "wasm-opt", "-Oz", "--enable-bulk-memory" });
|
||||||
run_opt.addArtifactArg(exe);
|
run_opt.addArtifactArg(exe);
|
||||||
@ -681,10 +690,7 @@ fn addCxxKnownPath(
|
|||||||
) !void {
|
) !void {
|
||||||
if (!std.process.can_spawn)
|
if (!std.process.can_spawn)
|
||||||
return error.RequiredLibraryNotFound;
|
return error.RequiredLibraryNotFound;
|
||||||
const path_padded = try b.exec(&[_][]const u8{
|
const path_padded = b.exec(&.{ ctx.cxx_compiler, b.fmt("-print-file-name={s}", .{objname}) });
|
||||||
ctx.cxx_compiler,
|
|
||||||
b.fmt("-print-file-name={s}", .{objname}),
|
|
||||||
});
|
|
||||||
var tokenizer = mem.tokenize(u8, path_padded, "\r\n");
|
var tokenizer = mem.tokenize(u8, path_padded, "\r\n");
|
||||||
const path_unpadded = tokenizer.next().?;
|
const path_unpadded = tokenizer.next().?;
|
||||||
if (mem.eql(u8, path_unpadded, objname)) {
|
if (mem.eql(u8, path_unpadded, objname)) {
|
||||||
|
|||||||
@ -67,7 +67,7 @@ stage3-debug/bin/zig build test docs \
|
|||||||
--zig-lib-dir "$(pwd)/../lib"
|
--zig-lib-dir "$(pwd)/../lib"
|
||||||
|
|
||||||
# Look for HTML errors.
|
# 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.
|
# Produce the experimental std lib documentation.
|
||||||
stage3-debug/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
|
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
|
ninja install
|
||||||
|
|
||||||
stage3/bin/zig test ../test/behavior.zig -I../test
|
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
|
||||||
|
|||||||
@ -67,7 +67,7 @@ stage3-release/bin/zig build test docs \
|
|||||||
--zig-lib-dir "$(pwd)/../lib"
|
--zig-lib-dir "$(pwd)/../lib"
|
||||||
|
|
||||||
# Look for HTML errors.
|
# 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.
|
# Produce the experimental std lib documentation.
|
||||||
stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
|
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
|
ninja install
|
||||||
|
|
||||||
stage3/bin/zig test ../test/behavior.zig -I../test
|
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
|
||||||
|
|||||||
@ -66,7 +66,7 @@ stage3-debug/bin/zig build test docs \
|
|||||||
--zig-lib-dir "$(pwd)/../lib"
|
--zig-lib-dir "$(pwd)/../lib"
|
||||||
|
|
||||||
# Look for HTML errors.
|
# 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.
|
# Produce the experimental std lib documentation.
|
||||||
stage3-debug/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
|
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
|
ninja install
|
||||||
|
|
||||||
stage3/bin/zig test ../test/behavior.zig -I../test
|
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
|
||||||
|
|||||||
@ -67,7 +67,7 @@ stage3-release/bin/zig build test docs \
|
|||||||
--zig-lib-dir "$(pwd)/../lib"
|
--zig-lib-dir "$(pwd)/../lib"
|
||||||
|
|
||||||
# Look for HTML errors.
|
# 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.
|
# Produce the experimental std lib documentation.
|
||||||
stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
|
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
|
ninja install
|
||||||
|
|
||||||
stage3/bin/zig test ../test/behavior.zig -I../test
|
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
|
||||||
|
|||||||
@ -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();
|
const stderr = io.getStdErr().writer();
|
||||||
|
|
||||||
stderr.print("error: " ++ format, args) catch {};
|
stderr.print("error: " ++ format ++ "\n", args) catch {};
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +45,7 @@ pub fn main() !void {
|
|||||||
if (!args_it.skip()) @panic("expected self arg");
|
if (!args_it.skip()) @panic("expected self arg");
|
||||||
|
|
||||||
var zig_exe: []const u8 = "zig";
|
var zig_exe: []const u8 = "zig";
|
||||||
|
var opt_zig_lib_dir: ?[]const u8 = null;
|
||||||
var do_code_tests = true;
|
var do_code_tests = true;
|
||||||
var files = [_][]const u8{ "", "" };
|
var files = [_][]const u8{ "", "" };
|
||||||
|
|
||||||
@ -59,24 +60,29 @@ pub fn main() !void {
|
|||||||
if (args_it.next()) |param| {
|
if (args_it.next()) |param| {
|
||||||
zig_exe = param;
|
zig_exe = param;
|
||||||
} else {
|
} 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")) {
|
} else if (mem.eql(u8, arg, "--skip-code-tests")) {
|
||||||
do_code_tests = false;
|
do_code_tests = false;
|
||||||
} else {
|
} else {
|
||||||
errorf("unrecognized option: '{s}'\n", .{arg});
|
fatal("unrecognized option: '{s}'", .{arg});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (i > 1) {
|
if (i > 1) {
|
||||||
errorf("too many arguments\n", .{});
|
fatal("too many arguments", .{});
|
||||||
}
|
}
|
||||||
files[i] = arg;
|
files[i] = arg;
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i < 2) {
|
if (i < 2) {
|
||||||
errorf("not enough arguments\n", .{});
|
fatal("not enough arguments", .{});
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var in_file = try fs.cwd().openFile(files[0], .{ .mode = .read_only });
|
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);
|
try fs.cwd().makePath(tmp_dir_name);
|
||||||
defer fs.cwd().deleteTree(tmp_dir_name) catch {};
|
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();
|
try buffered_writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1268,9 +1274,10 @@ fn genHtml(
|
|||||||
toc: *Toc,
|
toc: *Toc,
|
||||||
out: anytype,
|
out: anytype,
|
||||||
zig_exe: []const u8,
|
zig_exe: []const u8,
|
||||||
|
opt_zig_lib_dir: ?[]const u8,
|
||||||
do_code_tests: bool,
|
do_code_tests: bool,
|
||||||
) !void {
|
) !void {
|
||||||
var progress = Progress{};
|
var progress = Progress{ .dont_print_on_dumb = true };
|
||||||
const root_node = progress.start("Generating docgen examples", toc.nodes.len);
|
const root_node = progress.start("Generating docgen examples", toc.nodes.len);
|
||||||
defer root_node.end();
|
defer root_node.end();
|
||||||
|
|
||||||
@ -1278,7 +1285,7 @@ fn genHtml(
|
|||||||
try env_map.put("ZIG_DEBUG_COLOR", "1");
|
try env_map.put("ZIG_DEBUG_COLOR", "1");
|
||||||
|
|
||||||
const host = try std.zig.system.NativeTargetInfo.detect(.{});
|
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| {
|
for (toc.nodes) |node| {
|
||||||
defer root_node.completeOne();
|
defer root_node.completeOne();
|
||||||
@ -1370,6 +1377,9 @@ fn genHtml(
|
|||||||
"--color", "on",
|
"--color", "on",
|
||||||
"--enable-cache", tmp_source_file_name,
|
"--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});
|
try shell_out.print("$ zig build-exe {s} ", .{name_plus_ext});
|
||||||
|
|
||||||
@ -1512,8 +1522,12 @@ fn genHtml(
|
|||||||
defer test_args.deinit();
|
defer test_args.deinit();
|
||||||
|
|
||||||
try test_args.appendSlice(&[_][]const u8{
|
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});
|
try shell_out.print("$ zig test {s}.zig ", .{code.name});
|
||||||
|
|
||||||
switch (code.mode) {
|
switch (code.mode) {
|
||||||
@ -1564,12 +1578,13 @@ fn genHtml(
|
|||||||
defer test_args.deinit();
|
defer test_args.deinit();
|
||||||
|
|
||||||
try test_args.appendSlice(&[_][]const u8{
|
try test_args.appendSlice(&[_][]const u8{
|
||||||
zig_exe,
|
zig_exe, "test",
|
||||||
"test",
|
"--color", "on",
|
||||||
"--color",
|
|
||||||
"on",
|
|
||||||
tmp_source_file_name,
|
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});
|
try shell_out.print("$ zig test {s}.zig ", .{code.name});
|
||||||
|
|
||||||
switch (code.mode) {
|
switch (code.mode) {
|
||||||
@ -1624,8 +1639,12 @@ fn genHtml(
|
|||||||
defer test_args.deinit();
|
defer test_args.deinit();
|
||||||
|
|
||||||
try test_args.appendSlice(&[_][]const u8{
|
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 = "";
|
var mode_arg: []const u8 = "";
|
||||||
switch (code.mode) {
|
switch (code.mode) {
|
||||||
.Debug => {},
|
.Debug => {},
|
||||||
@ -1684,17 +1703,17 @@ fn genHtml(
|
|||||||
defer build_args.deinit();
|
defer build_args.deinit();
|
||||||
|
|
||||||
try build_args.appendSlice(&[_][]const u8{
|
try build_args.appendSlice(&[_][]const u8{
|
||||||
zig_exe,
|
zig_exe, "build-obj",
|
||||||
"build-obj",
|
"--color", "on",
|
||||||
|
"--name", code.name,
|
||||||
tmp_source_file_name,
|
tmp_source_file_name,
|
||||||
"--color",
|
|
||||||
"on",
|
|
||||||
"--name",
|
|
||||||
code.name,
|
|
||||||
try std.fmt.allocPrint(allocator, "-femit-bin={s}{c}{s}", .{
|
try std.fmt.allocPrint(allocator, "-femit-bin={s}{c}{s}", .{
|
||||||
tmp_dir_name, fs.path.sep, name_plus_obj_ext,
|
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});
|
try shell_out.print("$ zig build-obj {s}.zig ", .{code.name});
|
||||||
|
|
||||||
@ -1758,13 +1777,15 @@ fn genHtml(
|
|||||||
defer test_args.deinit();
|
defer test_args.deinit();
|
||||||
|
|
||||||
try test_args.appendSlice(&[_][]const u8{
|
try test_args.appendSlice(&[_][]const u8{
|
||||||
zig_exe,
|
zig_exe, "build-lib",
|
||||||
"build-lib",
|
|
||||||
tmp_source_file_name,
|
tmp_source_file_name,
|
||||||
try std.fmt.allocPrint(allocator, "-femit-bin={s}{s}{s}", .{
|
try std.fmt.allocPrint(allocator, "-femit-bin={s}{s}{s}", .{
|
||||||
tmp_dir_name, fs.path.sep_str, bin_basename,
|
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});
|
try shell_out.print("$ zig build-lib {s}.zig ", .{code.name});
|
||||||
|
|
||||||
switch (code.mode) {
|
switch (code.mode) {
|
||||||
@ -1829,9 +1850,23 @@ fn exec(allocator: Allocator, env_map: *process.EnvMap, args: []const []const u8
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getBuiltinCode(allocator: Allocator, env_map: *process.EnvMap, zig_exe: []const u8) ![]const u8 {
|
fn getBuiltinCode(
|
||||||
const result = try exec(allocator, env_map, &[_][]const u8{ zig_exe, "build-obj", "--show-builtin" });
|
allocator: Allocator,
|
||||||
return result.stdout;
|
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 {
|
fn dumpArgs(args: []const []const u8) void {
|
||||||
|
|||||||
@ -7457,20 +7457,20 @@ pub const STDOUT_FILENO = 1;
|
|||||||
|
|
||||||
pub fn syscall1(number: usize, arg1: usize) usize {
|
pub fn syscall1(number: usize, arg1: usize) usize {
|
||||||
return asm volatile ("syscall"
|
return asm volatile ("syscall"
|
||||||
: [ret] "={rax}" (-> usize)
|
: [ret] "={rax}" (-> usize),
|
||||||
: [number] "{rax}" (number),
|
: [number] "{rax}" (number),
|
||||||
[arg1] "{rdi}" (arg1)
|
[arg1] "{rdi}" (arg1),
|
||||||
: "rcx", "r11"
|
: "rcx", "r11"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) usize {
|
pub fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) usize {
|
||||||
return asm volatile ("syscall"
|
return asm volatile ("syscall"
|
||||||
: [ret] "={rax}" (-> usize)
|
: [ret] "={rax}" (-> usize),
|
||||||
: [number] "{rax}" (number),
|
: [number] "{rax}" (number),
|
||||||
[arg1] "{rdi}" (arg1),
|
[arg1] "{rdi}" (arg1),
|
||||||
[arg2] "{rsi}" (arg2),
|
[arg2] "{rsi}" (arg2),
|
||||||
[arg3] "{rdx}" (arg3)
|
[arg3] "{rdx}" (arg3),
|
||||||
: "rcx", "r11"
|
: "rcx", "r11"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -7519,14 +7519,14 @@ pub fn syscall1(number: usize, arg1: usize) usize {
|
|||||||
// type is the result type of the inline assembly expression.
|
// type is the result type of the inline assembly expression.
|
||||||
// If it is a value binding, then `%[ret]` syntax would be used
|
// If it is a value binding, then `%[ret]` syntax would be used
|
||||||
// to refer to the register bound to the value.
|
// to refer to the register bound to the value.
|
||||||
(-> usize)
|
(-> usize),
|
||||||
// Next is the list of inputs.
|
// Next is the list of inputs.
|
||||||
// The constraint for these inputs means, "when the assembly code is
|
// The constraint for these inputs means, "when the assembly code is
|
||||||
// executed, $rax shall have the value of `number` and $rdi shall have
|
// executed, $rax shall have the value of `number` and $rdi shall have
|
||||||
// the value of `arg1`". Any number of input parameters is allowed,
|
// the value of `arg1`". Any number of input parameters is allowed,
|
||||||
// including none.
|
// including none.
|
||||||
: [number] "{rax}" (number),
|
: [number] "{rax}" (number),
|
||||||
[arg1] "{rdi}" (arg1)
|
[arg1] "{rdi}" (arg1),
|
||||||
// Next is the list of clobbers. These declare a set of registers whose
|
// 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.
|
// values will not be preserved by the execution of this assembly code.
|
||||||
// These do not include output or input registers. The special clobber
|
// These do not include output or input registers. The special clobber
|
||||||
@ -7818,12 +7818,14 @@ comptime {
|
|||||||
<p>
|
<p>
|
||||||
This function inserts a platform-specific debug trap instruction which causes
|
This function inserts a platform-specific debug trap instruction which causes
|
||||||
debuggers to break there.
|
debuggers to break there.
|
||||||
|
Unlike for {#syntax#}@trap(){#endsyntax#}, execution may continue after this point if the program is resumed.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This function is only valid within function scope.
|
This function is only valid within function scope.
|
||||||
</p>
|
</p>
|
||||||
|
{#see_also|@trap#}
|
||||||
{#header_close#}
|
{#header_close#}
|
||||||
|
|
||||||
{#header_open|@mulAdd#}
|
{#header_open|@mulAdd#}
|
||||||
<pre>{#syntax#}@mulAdd(comptime T: type, a: T, b: T, c: T) T{#endsyntax#}</pre>
|
<pre>{#syntax#}@mulAdd(comptime T: type, a: T, b: T, c: T) T{#endsyntax#}</pre>
|
||||||
<p>
|
<p>
|
||||||
@ -9393,6 +9395,19 @@ fn List(comptime T: type) type {
|
|||||||
</p>
|
</p>
|
||||||
{#header_close#}
|
{#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#}
|
{#header_open|@truncate#}
|
||||||
<pre>{#syntax#}@truncate(comptime T: type, integer: anytype) T{#endsyntax#}</pre>
|
<pre>{#syntax#}@truncate(comptime T: type, integer: anytype) T{#endsyntax#}</pre>
|
||||||
<p>
|
<p>
|
||||||
@ -9565,9 +9580,10 @@ pub fn build(b: *std.Build) void {
|
|||||||
This causes these options to be available:
|
This causes these options to be available:
|
||||||
</p>
|
</p>
|
||||||
<dl>
|
<dl>
|
||||||
<dt><kbd>-Drelease-safe=[bool]</kbd></dt><dd>Optimizations on and safety on</dd>
|
<dt><kbd>-Doptimize=Debug</kbd></dt><dd>Optimizations off and safety on (default)</dd>
|
||||||
<dt><kbd>-Drelease-fast=[bool]</kbd></dt><dd>Optimizations on and safety off</dd>
|
<dt><kbd>-Doptimize=ReleaseSafe</kbd></dt><dd>Optimizations on and safety on</dd>
|
||||||
<dt><kbd>-Drelease-small=[bool]</kbd></dt><dd>Size optimizations on and safety off</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>
|
</dl>
|
||||||
{#header_open|Debug#}
|
{#header_open|Debug#}
|
||||||
{#shell_samp#}$ zig build-exe example.zig{#end_shell_samp#}
|
{#shell_samp#}$ zig build-exe example.zig{#end_shell_samp#}
|
||||||
@ -9788,8 +9804,8 @@ pub fn main() !void {
|
|||||||
{#header_close#}
|
{#header_close#}
|
||||||
{#header_open|Builtin Overflow Functions#}
|
{#header_open|Builtin Overflow Functions#}
|
||||||
<p>
|
<p>
|
||||||
These builtins return a {#syntax#}bool{#endsyntax#} of whether or not overflow
|
These builtins return a tuple containing whether there was an overflow
|
||||||
occurred, as well as returning the overflowed bits:
|
(as a {#syntax#}u1{#endsyntax#}) and the possibly overflowed bits of the operation:
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>{#link|@addWithOverflow#}</li>
|
<li>{#link|@addWithOverflow#}</li>
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
const root = @import("@build");
|
const root = @import("@build");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const assert = std.debug.assert;
|
||||||
const io = std.io;
|
const io = std.io;
|
||||||
const fmt = std.fmt;
|
const fmt = std.fmt;
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const process = std.process;
|
const process = std.process;
|
||||||
const ArrayList = std.ArrayList;
|
const ArrayList = std.ArrayList;
|
||||||
const File = std.fs.File;
|
const File = std.fs.File;
|
||||||
|
const Step = std.Build.Step;
|
||||||
|
|
||||||
pub const dependencies = @import("@dependencies");
|
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,
|
// 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
|
// 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.
|
// bytes into. So we free everything all at once at the very end.
|
||||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
var single_threaded_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
defer arena.deinit();
|
defer single_threaded_arena.deinit();
|
||||||
|
|
||||||
const allocator = arena.allocator();
|
var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
|
||||||
var args = try process.argsAlloc(allocator);
|
.child_allocator = single_threaded_arena.allocator(),
|
||||||
defer process.argsFree(allocator, args);
|
};
|
||||||
|
const arena = thread_safe_arena.allocator();
|
||||||
|
|
||||||
|
var args = try process.argsAlloc(arena);
|
||||||
|
|
||||||
// skip my own exe name
|
// skip my own exe name
|
||||||
var arg_idx: usize = 1;
|
var arg_idx: usize = 1;
|
||||||
@ -59,18 +64,17 @@ pub fn main() !void {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var cache: std.Build.Cache = .{
|
var cache: std.Build.Cache = .{
|
||||||
.gpa = allocator,
|
.gpa = arena,
|
||||||
.manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
|
.manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
|
||||||
};
|
};
|
||||||
cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
|
cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
|
||||||
cache.addPrefix(build_root_directory);
|
cache.addPrefix(build_root_directory);
|
||||||
cache.addPrefix(local_cache_directory);
|
cache.addPrefix(local_cache_directory);
|
||||||
cache.addPrefix(global_cache_directory);
|
cache.addPrefix(global_cache_directory);
|
||||||
|
cache.hash.addBytes(builtin.zig_version_string);
|
||||||
//cache.hash.addBytes(builtin.zig_version);
|
|
||||||
|
|
||||||
const builder = try std.Build.create(
|
const builder = try std.Build.create(
|
||||||
allocator,
|
arena,
|
||||||
zig_exe,
|
zig_exe,
|
||||||
build_root_directory,
|
build_root_directory,
|
||||||
local_cache_directory,
|
local_cache_directory,
|
||||||
@ -80,35 +84,34 @@ pub fn main() !void {
|
|||||||
);
|
);
|
||||||
defer builder.destroy();
|
defer builder.destroy();
|
||||||
|
|
||||||
var targets = ArrayList([]const u8).init(allocator);
|
var targets = ArrayList([]const u8).init(arena);
|
||||||
var debug_log_scopes = ArrayList([]const u8).init(allocator);
|
var debug_log_scopes = ArrayList([]const u8).init(arena);
|
||||||
|
var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena };
|
||||||
const stderr_stream = io.getStdErr().writer();
|
|
||||||
const stdout_stream = io.getStdOut().writer();
|
|
||||||
|
|
||||||
var install_prefix: ?[]const u8 = null;
|
var install_prefix: ?[]const u8 = null;
|
||||||
var dir_list = std.Build.DirList{};
|
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
|
const stderr_stream = io.getStdErr().writer();
|
||||||
// if it exists, default the color setting to .off
|
const stdout_stream = io.getStdOut().writer();
|
||||||
// explicit --color arguments will still override this setting.
|
|
||||||
builder.color = if (std.process.hasEnvVarConstant("NO_COLOR")) .off else .auto;
|
|
||||||
|
|
||||||
while (nextArg(args, &arg_idx)) |arg| {
|
while (nextArg(args, &arg_idx)) |arg| {
|
||||||
if (mem.startsWith(u8, arg, "-D")) {
|
if (mem.startsWith(u8, arg, "-D")) {
|
||||||
const option_contents = arg[2..];
|
const option_contents = arg[2..];
|
||||||
if (option_contents.len == 0) {
|
if (option_contents.len == 0) {
|
||||||
std.debug.print("Expected option name after '-D'\n\n", .{});
|
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| {
|
if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
|
||||||
const option_name = option_contents[0..name_end];
|
const option_name = option_contents[0..name_end];
|
||||||
const option_value = option_contents[name_end + 1 ..];
|
const option_value = option_contents[name_end + 1 ..];
|
||||||
if (try builder.addUserInputOption(option_name, option_value))
|
if (try builder.addUserInputOption(option_name, option_value))
|
||||||
return usageAndErr(builder, false, stderr_stream);
|
usageAndErr(builder, false, stderr_stream);
|
||||||
} else {
|
} else {
|
||||||
if (try builder.addUserInputFlag(option_contents))
|
if (try builder.addUserInputFlag(option_contents))
|
||||||
return usageAndErr(builder, false, stderr_stream);
|
usageAndErr(builder, false, stderr_stream);
|
||||||
}
|
}
|
||||||
} else if (mem.startsWith(u8, arg, "-")) {
|
} else if (mem.startsWith(u8, arg, "-")) {
|
||||||
if (mem.eql(u8, arg, "--verbose")) {
|
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")) {
|
} else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
|
||||||
install_prefix = nextArg(args, &arg_idx) orelse {
|
install_prefix = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("Expected argument after {s}\n\n", .{arg});
|
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")) {
|
} else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
|
||||||
return steps(builder, false, stdout_stream);
|
return steps(builder, false, stdout_stream);
|
||||||
} else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
|
} else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
|
||||||
dir_list.lib_dir = nextArg(args, &arg_idx) orelse {
|
dir_list.lib_dir = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("Expected argument after {s}\n\n", .{arg});
|
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")) {
|
} else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
|
||||||
dir_list.exe_dir = nextArg(args, &arg_idx) orelse {
|
dir_list.exe_dir = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("Expected argument after {s}\n\n", .{arg});
|
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")) {
|
} else if (mem.eql(u8, arg, "--prefix-include-dir")) {
|
||||||
dir_list.include_dir = nextArg(args, &arg_idx) orelse {
|
dir_list.include_dir = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("Expected argument after {s}\n\n", .{arg});
|
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")) {
|
} else if (mem.eql(u8, arg, "--sysroot")) {
|
||||||
const sysroot = nextArg(args, &arg_idx) orelse {
|
const sysroot = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("Expected argument after --sysroot\n\n", .{});
|
std.debug.print("Expected argument after --sysroot\n\n", .{});
|
||||||
return usageAndErr(builder, false, stderr_stream);
|
usageAndErr(builder, false, stderr_stream);
|
||||||
};
|
};
|
||||||
builder.sysroot = sysroot;
|
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")) {
|
} else if (mem.eql(u8, arg, "--search-prefix")) {
|
||||||
const search_prefix = nextArg(args, &arg_idx) orelse {
|
const search_prefix = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("Expected argument after --search-prefix\n\n", .{});
|
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);
|
builder.addSearchPrefix(search_prefix);
|
||||||
} else if (mem.eql(u8, arg, "--libc")) {
|
} else if (mem.eql(u8, arg, "--libc")) {
|
||||||
const libc_file = nextArg(args, &arg_idx) orelse {
|
const libc_file = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("Expected argument after --libc\n\n", .{});
|
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;
|
builder.libc_file = libc_file;
|
||||||
} else if (mem.eql(u8, arg, "--color")) {
|
} else if (mem.eql(u8, arg, "--color")) {
|
||||||
const next_arg = nextArg(args, &arg_idx) orelse {
|
const next_arg = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("expected [auto|on|off] after --color", .{});
|
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});
|
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")) {
|
} else if (mem.eql(u8, arg, "--zig-lib-dir")) {
|
||||||
builder.zig_lib_dir = nextArg(args, &arg_idx) orelse {
|
builder.zig_lib_dir = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("Expected argument after --zig-lib-dir\n\n", .{});
|
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")) {
|
} else if (mem.eql(u8, arg, "--debug-log")) {
|
||||||
const next_arg = nextArg(args, &arg_idx) orelse {
|
const next_arg = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("Expected argument after {s}\n\n", .{arg});
|
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);
|
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")) {
|
} else if (mem.eql(u8, arg, "--debug-compile-errors")) {
|
||||||
builder.debug_compile_errors = true;
|
builder.debug_compile_errors = true;
|
||||||
} else if (mem.eql(u8, arg, "--glibc-runtimes")) {
|
} else if (mem.eql(u8, arg, "--glibc-runtimes")) {
|
||||||
builder.glibc_runtimes_dir = nextArg(args, &arg_idx) orelse {
|
builder.glibc_runtimes_dir = nextArg(args, &arg_idx) orelse {
|
||||||
std.debug.print("Expected argument after --glibc-runtimes\n\n", .{});
|
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")) {
|
} else if (mem.eql(u8, arg, "--verbose-link")) {
|
||||||
builder.verbose_link = true;
|
builder.verbose_link = true;
|
||||||
@ -194,8 +211,6 @@ pub fn main() !void {
|
|||||||
builder.verbose_cc = true;
|
builder.verbose_cc = true;
|
||||||
} else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
|
} else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
|
||||||
builder.verbose_llvm_cpu_features = true;
|
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")) {
|
} else if (mem.eql(u8, arg, "-fwine")) {
|
||||||
builder.enable_wine = true;
|
builder.enable_wine = true;
|
||||||
} else if (mem.eql(u8, arg, "-fno-wine")) {
|
} else if (mem.eql(u8, arg, "-fno-wine")) {
|
||||||
@ -216,6 +231,10 @@ pub fn main() !void {
|
|||||||
builder.enable_darling = true;
|
builder.enable_darling = true;
|
||||||
} else if (mem.eql(u8, arg, "-fno-darling")) {
|
} else if (mem.eql(u8, arg, "-fno-darling")) {
|
||||||
builder.enable_darling = false;
|
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")) {
|
} else if (mem.eql(u8, arg, "-freference-trace")) {
|
||||||
builder.reference_trace = 256;
|
builder.reference_trace = 256;
|
||||||
} else if (mem.startsWith(u8, arg, "-freference-trace=")) {
|
} 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")) {
|
} else if (mem.eql(u8, arg, "-fno-reference-trace")) {
|
||||||
builder.reference_trace = null;
|
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, "--")) {
|
} else if (mem.eql(u8, arg, "--")) {
|
||||||
builder.args = argsRest(args, arg_idx);
|
builder.args = argsRest(args, arg_idx);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
std.debug.print("Unrecognized argument: {s}\n\n", .{arg});
|
std.debug.print("Unrecognized argument: {s}\n\n", .{arg});
|
||||||
return usageAndErr(builder, false, stderr_stream);
|
usageAndErr(builder, false, stderr_stream);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try targets.append(arg);
|
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.debug_log_scopes = debug_log_scopes.items;
|
||||||
builder.resolveInstallPrefix(install_prefix, dir_list);
|
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())
|
if (builder.validateUserInputDidItFail())
|
||||||
return usageAndErr(builder, true, stderr_stream);
|
usageAndErr(builder, true, stderr_stream);
|
||||||
|
|
||||||
builder.make(targets.items) catch |err| {
|
var run: Run = .{
|
||||||
switch (err) {
|
.max_rss = max_rss,
|
||||||
error.InvalidStepName => {
|
.max_rss_is_default = false,
|
||||||
return usageAndErr(builder, true, stderr_stream);
|
.max_rss_mutex = .{},
|
||||||
},
|
.memory_blocked_steps = std.ArrayList(*Step).init(arena),
|
||||||
error.UncleanExit => process.exit(1),
|
|
||||||
// This error is intended to indicate that the step has already
|
.claimed_rss = 0,
|
||||||
// logged an error message and so printing the error return trace
|
.enable_summary = enable_summary,
|
||||||
// here would be unwanted extra information, unless the user opts
|
.ttyconf = ttyconf,
|
||||||
// into it with a debug flag.
|
.stderr = stderr,
|
||||||
error.StepFailed => process.exit(1),
|
|
||||||
else => return err,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 {
|
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;
|
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)
|
const name = if (&top_level_step.step == builder.default_step)
|
||||||
try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
|
try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
|
||||||
else
|
else
|
||||||
@ -327,6 +946,10 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
|
|||||||
\\ --verbose Print commands before executing them
|
\\ --verbose Print commands before executing them
|
||||||
\\ --color [auto|off|on] Enable or disable colored error messages
|
\\ --color [auto|off|on] Enable or disable colored error messages
|
||||||
\\ --prominent-compile-errors Output compile errors formatted for a human to read
|
\\ --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:
|
\\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
|
\\ --zig-lib-dir [arg] Override path to Zig lib directory
|
||||||
\\ --build-runner [file] Override path to build runner
|
\\ --build-runner [file] Override path to build runner
|
||||||
\\ --debug-log [scope] Enable debugging the compiler
|
\\ --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-link Enable compiler debug output for linking
|
||||||
\\ --verbose-air Enable compiler debug output for Zig AIR
|
\\ --verbose-air Enable compiler debug output for Zig AIR
|
||||||
\\ --verbose-llvm-ir Enable compiler debug output for LLVM IR
|
\\ --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 {};
|
usage(builder, already_ran_build, out_stream) catch {};
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@ -389,3 +1013,28 @@ fn argsRest(args: [][]const u8, idx: usize) ?[][]const u8 {
|
|||||||
if (idx >= args.len) return null;
|
if (idx >= args.len) return null;
|
||||||
return args[idx..];
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -79,16 +79,16 @@ fn divmod(q: ?[]u32, r: ?[]u32, u: []const u32, v: []const u32) !void {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var carry: u64 = 0;
|
var carry: i64 = 0;
|
||||||
i = 0;
|
i = 0;
|
||||||
while (i <= n) : (i += 1) {
|
while (i <= n) : (i += 1) {
|
||||||
const p = qhat * limb(&vn, i);
|
const p = qhat * limb(&vn, i);
|
||||||
const t = limb(&un, i + j) - carry - @truncate(u32, p);
|
const t = limb(&un, i + j) - carry - @truncate(u32, p);
|
||||||
limb_set(&un, i + j, @truncate(u32, t));
|
limb_set(&un, i + j, @truncate(u32, @bitCast(u64, t)));
|
||||||
carry = @intCast(u64, p >> 32) - @intCast(u64, t >> 32);
|
carry = @intCast(i64, p >> 32) - @intCast(i64, t >> 32);
|
||||||
}
|
}
|
||||||
const t = limb(&un, j + n + 1) - carry;
|
const t = limb(&un, j + n + 1) -% carry;
|
||||||
limb_set(&un, j + n + 1, @truncate(u32, t));
|
limb_set(&un, j + n + 1, @truncate(u32, @bitCast(u64, t)));
|
||||||
if (q) |q_| limb_set(q_, j, @truncate(u32, qhat));
|
if (q) |q_| limb_set(q_, j, @truncate(u32, qhat));
|
||||||
if (t < 0) {
|
if (t < 0) {
|
||||||
if (q) |q_| limb_set(q_, j, limb(q_, j) - 1);
|
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));
|
limb_set(&un, i + j, @truncate(u32, t2));
|
||||||
carry2 = t2 >> 32;
|
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;
|
if (j == 0) break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1187,10 +1187,6 @@ const NAV_MODES = {
|
|||||||
payloadHtml += "panic";
|
payloadHtml += "panic";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "set_cold": {
|
|
||||||
payloadHtml += "setCold";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "set_runtime_safety": {
|
case "set_runtime_safety": {
|
||||||
payloadHtml += "setRuntimeSafety";
|
payloadHtml += "setRuntimeSafety";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -32,14 +32,12 @@ pub const Step = @import("Build/Step.zig");
|
|||||||
pub const CheckFileStep = @import("Build/CheckFileStep.zig");
|
pub const CheckFileStep = @import("Build/CheckFileStep.zig");
|
||||||
pub const CheckObjectStep = @import("Build/CheckObjectStep.zig");
|
pub const CheckObjectStep = @import("Build/CheckObjectStep.zig");
|
||||||
pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig");
|
pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig");
|
||||||
pub const EmulatableRunStep = @import("Build/EmulatableRunStep.zig");
|
|
||||||
pub const FmtStep = @import("Build/FmtStep.zig");
|
pub const FmtStep = @import("Build/FmtStep.zig");
|
||||||
pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig");
|
pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig");
|
||||||
pub const InstallDirStep = @import("Build/InstallDirStep.zig");
|
pub const InstallDirStep = @import("Build/InstallDirStep.zig");
|
||||||
pub const InstallFileStep = @import("Build/InstallFileStep.zig");
|
pub const InstallFileStep = @import("Build/InstallFileStep.zig");
|
||||||
pub const ObjCopyStep = @import("Build/ObjCopyStep.zig");
|
pub const ObjCopyStep = @import("Build/ObjCopyStep.zig");
|
||||||
pub const CompileStep = @import("Build/CompileStep.zig");
|
pub const CompileStep = @import("Build/CompileStep.zig");
|
||||||
pub const LogStep = @import("Build/LogStep.zig");
|
|
||||||
pub const OptionsStep = @import("Build/OptionsStep.zig");
|
pub const OptionsStep = @import("Build/OptionsStep.zig");
|
||||||
pub const RemoveDirStep = @import("Build/RemoveDirStep.zig");
|
pub const RemoveDirStep = @import("Build/RemoveDirStep.zig");
|
||||||
pub const RunStep = @import("Build/RunStep.zig");
|
pub const RunStep = @import("Build/RunStep.zig");
|
||||||
@ -59,15 +57,12 @@ verbose_air: bool,
|
|||||||
verbose_llvm_ir: bool,
|
verbose_llvm_ir: bool,
|
||||||
verbose_cimport: bool,
|
verbose_cimport: bool,
|
||||||
verbose_llvm_cpu_features: 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,
|
reference_trace: ?u32 = null,
|
||||||
invalid_user_input: bool,
|
invalid_user_input: bool,
|
||||||
zig_exe: []const u8,
|
zig_exe: []const u8,
|
||||||
default_step: *Step,
|
default_step: *Step,
|
||||||
env_map: *EnvMap,
|
env_map: *EnvMap,
|
||||||
top_level_steps: ArrayList(*TopLevelStep),
|
top_level_steps: std.StringArrayHashMapUnmanaged(*TopLevelStep),
|
||||||
install_prefix: []const u8,
|
install_prefix: []const u8,
|
||||||
dest_dir: ?[]const u8,
|
dest_dir: ?[]const u8,
|
||||||
lib_dir: []const u8,
|
lib_dir: []const u8,
|
||||||
@ -90,6 +85,7 @@ pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
|
|||||||
args: ?[][]const u8 = null,
|
args: ?[][]const u8 = null,
|
||||||
debug_log_scopes: []const []const u8 = &.{},
|
debug_log_scopes: []const []const u8 = &.{},
|
||||||
debug_compile_errors: bool = false,
|
debug_compile_errors: bool = false,
|
||||||
|
debug_pkg_config: bool = false,
|
||||||
|
|
||||||
/// Experimental. Use system Darling installation to run cross compiled macOS build artifacts.
|
/// Experimental. Use system Darling installation to run cross compiled macOS build artifacts.
|
||||||
enable_darling: bool = false,
|
enable_darling: bool = false,
|
||||||
@ -198,7 +194,7 @@ pub fn create(
|
|||||||
env_map.* = try process.getEnvMap(allocator);
|
env_map.* = try process.getEnvMap(allocator);
|
||||||
|
|
||||||
const self = try allocator.create(Build);
|
const self = try allocator.create(Build);
|
||||||
self.* = Build{
|
self.* = .{
|
||||||
.zig_exe = zig_exe,
|
.zig_exe = zig_exe,
|
||||||
.build_root = build_root,
|
.build_root = build_root,
|
||||||
.cache_root = cache_root,
|
.cache_root = cache_root,
|
||||||
@ -211,13 +207,12 @@ pub fn create(
|
|||||||
.verbose_llvm_ir = false,
|
.verbose_llvm_ir = false,
|
||||||
.verbose_cimport = false,
|
.verbose_cimport = false,
|
||||||
.verbose_llvm_cpu_features = false,
|
.verbose_llvm_cpu_features = false,
|
||||||
.prominent_compile_errors = false,
|
|
||||||
.invalid_user_input = false,
|
.invalid_user_input = false,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.user_input_options = UserInputOptionsMap.init(allocator),
|
.user_input_options = UserInputOptionsMap.init(allocator),
|
||||||
.available_options_map = AvailableOptionsMap.init(allocator),
|
.available_options_map = AvailableOptionsMap.init(allocator),
|
||||||
.available_options_list = ArrayList(AvailableOption).init(allocator),
|
.available_options_list = ArrayList(AvailableOption).init(allocator),
|
||||||
.top_level_steps = ArrayList(*TopLevelStep).init(allocator),
|
.top_level_steps = .{},
|
||||||
.default_step = undefined,
|
.default_step = undefined,
|
||||||
.env_map = env_map,
|
.env_map = env_map,
|
||||||
.search_prefixes = ArrayList([]const u8).init(allocator),
|
.search_prefixes = ArrayList([]const u8).init(allocator),
|
||||||
@ -227,12 +222,21 @@ pub fn create(
|
|||||||
.h_dir = undefined,
|
.h_dir = undefined,
|
||||||
.dest_dir = env_map.get("DESTDIR"),
|
.dest_dir = env_map.get("DESTDIR"),
|
||||||
.installed_files = ArrayList(InstalledFile).init(allocator),
|
.installed_files = ArrayList(InstalledFile).init(allocator),
|
||||||
.install_tls = TopLevelStep{
|
.install_tls = .{
|
||||||
.step = Step.initNoOp(.top_level, "install", allocator),
|
.step = Step.init(.{
|
||||||
|
.id = .top_level,
|
||||||
|
.name = "install",
|
||||||
|
.owner = self,
|
||||||
|
}),
|
||||||
.description = "Copy build artifacts to prefix path",
|
.description = "Copy build artifacts to prefix path",
|
||||||
},
|
},
|
||||||
.uninstall_tls = TopLevelStep{
|
.uninstall_tls = .{
|
||||||
.step = Step.init(.top_level, "uninstall", allocator, makeUninstall),
|
.step = Step.init(.{
|
||||||
|
.id = .top_level,
|
||||||
|
.name = "uninstall",
|
||||||
|
.owner = self,
|
||||||
|
.makeFn = makeUninstall,
|
||||||
|
}),
|
||||||
.description = "Remove build artifacts from prefix path",
|
.description = "Remove build artifacts from prefix path",
|
||||||
},
|
},
|
||||||
.zig_lib_dir = null,
|
.zig_lib_dir = null,
|
||||||
@ -241,8 +245,8 @@ pub fn create(
|
|||||||
.host = host,
|
.host = host,
|
||||||
.modules = std.StringArrayHashMap(*Module).init(allocator),
|
.modules = std.StringArrayHashMap(*Module).init(allocator),
|
||||||
};
|
};
|
||||||
try self.top_level_steps.append(&self.install_tls);
|
try self.top_level_steps.put(allocator, self.install_tls.step.name, &self.install_tls);
|
||||||
try self.top_level_steps.append(&self.uninstall_tls);
|
try self.top_level_steps.put(allocator, self.uninstall_tls.step.name, &self.uninstall_tls);
|
||||||
self.default_step = &self.install_tls.step;
|
self.default_step = &self.install_tls.step;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@ -264,11 +268,20 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
|
|||||||
child.* = .{
|
child.* = .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.install_tls = .{
|
.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",
|
.description = "Copy build artifacts to prefix path",
|
||||||
},
|
},
|
||||||
.uninstall_tls = .{
|
.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",
|
.description = "Remove build artifacts from prefix path",
|
||||||
},
|
},
|
||||||
.user_input_options = UserInputOptionsMap.init(allocator),
|
.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_llvm_ir = parent.verbose_llvm_ir,
|
||||||
.verbose_cimport = parent.verbose_cimport,
|
.verbose_cimport = parent.verbose_cimport,
|
||||||
.verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features,
|
.verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features,
|
||||||
.prominent_compile_errors = parent.prominent_compile_errors,
|
|
||||||
.color = parent.color,
|
|
||||||
.reference_trace = parent.reference_trace,
|
.reference_trace = parent.reference_trace,
|
||||||
.invalid_user_input = false,
|
.invalid_user_input = false,
|
||||||
.zig_exe = parent.zig_exe,
|
.zig_exe = parent.zig_exe,
|
||||||
.default_step = undefined,
|
.default_step = undefined,
|
||||||
.env_map = parent.env_map,
|
.env_map = parent.env_map,
|
||||||
.top_level_steps = ArrayList(*TopLevelStep).init(allocator),
|
.top_level_steps = .{},
|
||||||
.install_prefix = undefined,
|
.install_prefix = undefined,
|
||||||
.dest_dir = parent.dest_dir,
|
.dest_dir = parent.dest_dir,
|
||||||
.lib_dir = parent.lib_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,
|
.zig_lib_dir = parent.zig_lib_dir,
|
||||||
.debug_log_scopes = parent.debug_log_scopes,
|
.debug_log_scopes = parent.debug_log_scopes,
|
||||||
.debug_compile_errors = parent.debug_compile_errors,
|
.debug_compile_errors = parent.debug_compile_errors,
|
||||||
|
.debug_pkg_config = parent.debug_pkg_config,
|
||||||
.enable_darling = parent.enable_darling,
|
.enable_darling = parent.enable_darling,
|
||||||
.enable_qemu = parent.enable_qemu,
|
.enable_qemu = parent.enable_qemu,
|
||||||
.enable_rosetta = parent.enable_rosetta,
|
.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 }),
|
.dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }),
|
||||||
.modules = std.StringArrayHashMap(*Module).init(allocator),
|
.modules = std.StringArrayHashMap(*Module).init(allocator),
|
||||||
};
|
};
|
||||||
try child.top_level_steps.append(&child.install_tls);
|
try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls);
|
||||||
try child.top_level_steps.append(&child.uninstall_tls);
|
try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls);
|
||||||
child.default_step = &child.install_tls.step;
|
child.default_step = &child.install_tls.step;
|
||||||
return child;
|
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
|
// Random bytes to make unique. Refresh this with new random bytes when
|
||||||
// implementation is modified in a non-backwards-compatible way.
|
// implementation is modified in a non-backwards-compatible way.
|
||||||
var hash = Hasher.init("ZaEsvQ5ClaA2IdH9");
|
hash.add(@as(u32, 0xd8cb0055));
|
||||||
hash.update(b.dep_prefix);
|
hash.addBytes(b.dep_prefix);
|
||||||
// TODO additionally update the hash with `args`.
|
// TODO additionally update the hash with `args`.
|
||||||
|
const digest = hash.final();
|
||||||
var digest: [16]u8 = undefined;
|
const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest });
|
||||||
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 });
|
|
||||||
b.resolveInstallPrefix(install_prefix, .{});
|
b.resolveInstallPrefix(install_prefix, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self: *Build) void {
|
pub fn destroy(b: *Build) void {
|
||||||
self.env_map.deinit();
|
b.env_map.deinit();
|
||||||
self.top_level_steps.deinit();
|
b.top_level_steps.deinit(b.allocator);
|
||||||
self.allocator.destroy(self);
|
b.allocator.destroy(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function is intended to be called by lib/build_runner.zig, not a build.zig file.
|
/// 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 = .{},
|
target: CrossTarget = .{},
|
||||||
optimize: std.builtin.Mode = .Debug,
|
optimize: std.builtin.Mode = .Debug,
|
||||||
linkage: ?CompileStep.Linkage = null,
|
linkage: ?CompileStep.Linkage = null,
|
||||||
|
max_rss: usize = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {
|
pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {
|
||||||
@ -452,6 +462,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {
|
|||||||
.optimize = options.optimize,
|
.optimize = options.optimize,
|
||||||
.kind = .exe,
|
.kind = .exe,
|
||||||
.linkage = options.linkage,
|
.linkage = options.linkage,
|
||||||
|
.max_rss = options.max_rss,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,6 +471,7 @@ pub const ObjectOptions = struct {
|
|||||||
root_source_file: ?FileSource = null,
|
root_source_file: ?FileSource = null,
|
||||||
target: CrossTarget,
|
target: CrossTarget,
|
||||||
optimize: std.builtin.Mode,
|
optimize: std.builtin.Mode,
|
||||||
|
max_rss: usize = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {
|
pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {
|
||||||
@ -469,6 +481,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {
|
|||||||
.target = options.target,
|
.target = options.target,
|
||||||
.optimize = options.optimize,
|
.optimize = options.optimize,
|
||||||
.kind = .obj,
|
.kind = .obj,
|
||||||
|
.max_rss = options.max_rss,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,6 +491,7 @@ pub const SharedLibraryOptions = struct {
|
|||||||
version: ?std.builtin.Version = null,
|
version: ?std.builtin.Version = null,
|
||||||
target: CrossTarget,
|
target: CrossTarget,
|
||||||
optimize: std.builtin.Mode,
|
optimize: std.builtin.Mode,
|
||||||
|
max_rss: usize = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {
|
pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {
|
||||||
@ -489,6 +503,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {
|
|||||||
.version = options.version,
|
.version = options.version,
|
||||||
.target = options.target,
|
.target = options.target,
|
||||||
.optimize = options.optimize,
|
.optimize = options.optimize,
|
||||||
|
.max_rss = options.max_rss,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,6 +513,7 @@ pub const StaticLibraryOptions = struct {
|
|||||||
target: CrossTarget,
|
target: CrossTarget,
|
||||||
optimize: std.builtin.Mode,
|
optimize: std.builtin.Mode,
|
||||||
version: ?std.builtin.Version = null,
|
version: ?std.builtin.Version = null,
|
||||||
|
max_rss: usize = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
|
pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
|
||||||
@ -509,25 +525,27 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
|
|||||||
.version = options.version,
|
.version = options.version,
|
||||||
.target = options.target,
|
.target = options.target,
|
||||||
.optimize = options.optimize,
|
.optimize = options.optimize,
|
||||||
|
.max_rss = options.max_rss,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const TestOptions = struct {
|
pub const TestOptions = struct {
|
||||||
name: []const u8 = "test",
|
name: []const u8 = "test",
|
||||||
kind: CompileStep.Kind = .@"test",
|
|
||||||
root_source_file: FileSource,
|
root_source_file: FileSource,
|
||||||
target: CrossTarget = .{},
|
target: CrossTarget = .{},
|
||||||
optimize: std.builtin.Mode = .Debug,
|
optimize: std.builtin.Mode = .Debug,
|
||||||
version: ?std.builtin.Version = null,
|
version: ?std.builtin.Version = null,
|
||||||
|
max_rss: usize = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn addTest(b: *Build, options: TestOptions) *CompileStep {
|
pub fn addTest(b: *Build, options: TestOptions) *CompileStep {
|
||||||
return CompileStep.create(b, .{
|
return CompileStep.create(b, .{
|
||||||
.name = options.name,
|
.name = options.name,
|
||||||
.kind = options.kind,
|
.kind = .@"test",
|
||||||
.root_source_file = options.root_source_file,
|
.root_source_file = options.root_source_file,
|
||||||
.target = options.target,
|
.target = options.target,
|
||||||
.optimize = options.optimize,
|
.optimize = options.optimize,
|
||||||
|
.max_rss = options.max_rss,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,6 +554,7 @@ pub const AssemblyOptions = struct {
|
|||||||
source_file: FileSource,
|
source_file: FileSource,
|
||||||
target: CrossTarget,
|
target: CrossTarget,
|
||||||
optimize: std.builtin.Mode,
|
optimize: std.builtin.Mode,
|
||||||
|
max_rss: usize = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {
|
pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {
|
||||||
@ -545,22 +564,19 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {
|
|||||||
.root_source_file = null,
|
.root_source_file = null,
|
||||||
.target = options.target,
|
.target = options.target,
|
||||||
.optimize = options.optimize,
|
.optimize = options.optimize,
|
||||||
|
.max_rss = options.max_rss,
|
||||||
});
|
});
|
||||||
obj_step.addAssemblyFileSource(options.source_file.dupe(b));
|
obj_step.addAssemblyFileSource(options.source_file.dupe(b));
|
||||||
return obj_step;
|
return obj_step;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const AddModuleOptions = struct {
|
/// This function creates a module and adds it to the package's module set, making
|
||||||
name: []const u8,
|
/// it available to other packages which depend on this one.
|
||||||
source_file: FileSource,
|
/// `createModule` can be used instead to create a private module.
|
||||||
dependencies: []const ModuleDependency = &.{},
|
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");
|
||||||
pub fn addModule(b: *Build, options: AddModuleOptions) void {
|
return module;
|
||||||
b.modules.put(b.dupe(options.name), b.createModule(.{
|
|
||||||
.source_file = options.source_file,
|
|
||||||
.dependencies = options.dependencies,
|
|
||||||
})) catch @panic("OOM");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ModuleDependency = struct {
|
pub const ModuleDependency = struct {
|
||||||
@ -573,8 +589,9 @@ pub const CreateModuleOptions = struct {
|
|||||||
dependencies: []const ModuleDependency = &.{},
|
dependencies: []const ModuleDependency = &.{},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Prefer to use `addModule` which will make the module available to other
|
/// This function creates a private module, to be used by the current package,
|
||||||
/// packages which depend on this 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 {
|
pub fn createModule(b: *Build, options: CreateModuleOptions) *Module {
|
||||||
const module = b.allocator.create(Module) catch @panic("OOM");
|
const module = b.allocator.create(Module) catch @panic("OOM");
|
||||||
module.* = .{
|
module.* = .{
|
||||||
@ -608,16 +625,15 @@ pub fn addSystemCommand(self: *Build, argv: []const []const u8) *RunStep {
|
|||||||
/// Creates a `RunStep` with an executable built with `addExecutable`.
|
/// Creates a `RunStep` with an executable built with `addExecutable`.
|
||||||
/// Add command line arguments with methods of `RunStep`.
|
/// Add command line arguments with methods of `RunStep`.
|
||||||
pub fn addRunArtifact(b: *Build, exe: *CompileStep) *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.
|
// 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
|
// Consider that this is declarative; the run step may not be run unless a user
|
||||||
// option is supplied.
|
// 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);
|
run_step.addArtifactArg(exe);
|
||||||
|
|
||||||
if (exe.kind == .test_exe) {
|
if (exe.kind == .@"test") {
|
||||||
run_step.addArg(b.zig_exe);
|
run_step.stdio = .zig_test;
|
||||||
|
run_step.addArgs(&.{"--listen=-"});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exe.vcpkg_bin_path) |path| {
|
if (exe.vcpkg_bin_path) |path| {
|
||||||
@ -637,7 +653,11 @@ pub fn addConfigHeader(
|
|||||||
options: ConfigHeaderStep.Options,
|
options: ConfigHeaderStep.Options,
|
||||||
values: anytype,
|
values: anytype,
|
||||||
) *ConfigHeaderStep {
|
) *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);
|
config_header_step.addValues(values);
|
||||||
return config_header_step;
|
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;
|
return write_file_step;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addWriteFiles(self: *Build) *WriteFileStep {
|
pub fn addWriteFiles(b: *Build) *WriteFileStep {
|
||||||
const write_file_step = self.allocator.create(WriteFileStep) catch @panic("OOM");
|
return WriteFileStep.create(b);
|
||||||
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 addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {
|
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;
|
return remove_dir_step;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addFmt(self: *Build, paths: []const []const u8) *FmtStep {
|
pub fn addFmt(b: *Build, options: FmtStep.Options) *FmtStep {
|
||||||
return FmtStep.create(self, paths);
|
return FmtStep.create(b, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addTranslateC(self: *Build, options: TranslateCStep.Options) *TranslateCStep {
|
pub fn addTranslateC(self: *Build, options: TranslateCStep.Options) *TranslateCStep {
|
||||||
return TranslateCStep.create(self, options);
|
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 {
|
pub fn getInstallStep(self: *Build) *Step {
|
||||||
return &self.install_tls.step;
|
return &self.install_tls.step;
|
||||||
}
|
}
|
||||||
@ -727,7 +720,8 @@ pub fn getUninstallStep(self: *Build) *Step {
|
|||||||
return &self.uninstall_tls.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 uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step);
|
||||||
const self = @fieldParentPtr(Build, "uninstall_tls", uninstall_tls);
|
const self = @fieldParentPtr(Build, "uninstall_tls", uninstall_tls);
|
||||||
|
|
||||||
@ -742,37 +736,6 @@ fn makeUninstall(uninstall_step: *Step) anyerror!void {
|
|||||||
// TODO remove empty directories
|
// 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 {
|
pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T {
|
||||||
const name = self.dupe(name_raw);
|
const name = self.dupe(name_raw);
|
||||||
const description = self.dupe(description_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 {
|
pub fn step(self: *Build, name: []const u8, description: []const u8) *Step {
|
||||||
const step_info = self.allocator.create(TopLevelStep) catch @panic("OOM");
|
const step_info = self.allocator.create(TopLevelStep) catch @panic("OOM");
|
||||||
step_info.* = TopLevelStep{
|
step_info.* = .{
|
||||||
.step = Step.initNoOp(.top_level, name, self.allocator),
|
.step = Step.init(.{
|
||||||
|
.id = .top_level,
|
||||||
|
.name = name,
|
||||||
|
.owner = self,
|
||||||
|
}),
|
||||||
.description = self.dupe(description),
|
.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;
|
return &step_info.step;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1181,50 +1148,18 @@ pub fn validateUserInputDidItFail(self: *Build) bool {
|
|||||||
return self.invalid_user_input;
|
return self.invalid_user_input;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawnChild(self: *Build, argv: []const []const u8) !void {
|
fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 {
|
||||||
return self.spawnChildEnvMap(null, self.env_map, argv);
|
var buf = ArrayList(u8).init(ally);
|
||||||
}
|
if (opt_cwd) |cwd| try buf.writer().print("cd {s} && ", .{cwd});
|
||||||
|
|
||||||
fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void {
|
|
||||||
if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd});
|
|
||||||
for (argv) |arg| {
|
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 {
|
fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void {
|
||||||
if (self.verbose) {
|
const text = allocPrintCmd(ally, cwd, argv) catch @panic("OOM");
|
||||||
printCmd(cwd, argv);
|
std.debug.print("{s}\n", .{text});
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn installArtifact(self: *Build, artifact: *CompileStep) void {
|
pub fn installArtifact(self: *Build, artifact: *CompileStep) void {
|
||||||
@ -1283,12 +1218,7 @@ pub fn addInstallFileWithDir(
|
|||||||
install_dir: InstallDir,
|
install_dir: InstallDir,
|
||||||
dest_rel_path: []const u8,
|
dest_rel_path: []const u8,
|
||||||
) *InstallFileStep {
|
) *InstallFileStep {
|
||||||
if (dest_rel_path.len == 0) {
|
return InstallFileStep.create(self, source.dupe(self), install_dir, dest_rel_path);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *InstallDirStep {
|
pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *InstallDirStep {
|
||||||
@ -1297,6 +1227,14 @@ pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Inst
|
|||||||
return install_step;
|
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 {
|
pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void {
|
||||||
const file = InstalledFile{
|
const file = InstalledFile{
|
||||||
.dir = dir,
|
.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");
|
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 {
|
pub fn truncateFile(self: *Build, dest_path: []const u8) !void {
|
||||||
if (self.verbose) {
|
if (self.verbose) {
|
||||||
log.info("truncate {s}", .{dest_path});
|
log.info("truncate {s}", .{dest_path});
|
||||||
@ -1400,7 +1326,7 @@ pub fn execAllowFail(
|
|||||||
) ExecError![]u8 {
|
) ExecError![]u8 {
|
||||||
assert(argv.len != 0);
|
assert(argv.len != 0);
|
||||||
|
|
||||||
if (!std.process.can_spawn)
|
if (!process.can_spawn)
|
||||||
return error.ExecNotSupported;
|
return error.ExecNotSupported;
|
||||||
|
|
||||||
const max_output_size = 400 * 1024;
|
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 {
|
/// This is a helper function to be called from build.zig scripts, *not* from
|
||||||
assert(argv.len != 0);
|
/// inside step make() functions. If any errors occur, it fails the build with
|
||||||
|
/// a helpful message.
|
||||||
if (self.verbose) {
|
pub fn exec(b: *Build, argv: []const []const u8) []u8 {
|
||||||
printCmd(null, argv);
|
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),
|
||||||
if (!std.process.can_spawn) {
|
});
|
||||||
if (src_step) |s| log.err("{s}...", .{s.name});
|
process.exit(1);
|
||||||
log.err("Unable to spawn the following command: cannot spawn child process", .{});
|
|
||||||
printCmd(null, argv);
|
|
||||||
std.os.abort();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var code: u8 = undefined;
|
var code: u8 = undefined;
|
||||||
return self.execAllowFail(argv, &code, .Inherit) catch |err| switch (err) {
|
return b.execAllowFail(argv, &code, .Inherit) catch |err| {
|
||||||
error.ExecNotSupported => {
|
const printed_cmd = allocPrintCmd(b.allocator, null, argv) catch @panic("OOM");
|
||||||
if (src_step) |s| log.err("{s}...", .{s.name});
|
std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{
|
||||||
log.err("Unable to spawn the following command: cannot spawn child process", .{});
|
@errorName(err), printed_cmd,
|
||||||
printCmd(null, argv);
|
});
|
||||||
std.os.abort();
|
process.exit(1);
|
||||||
},
|
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
pub fn addSearchPrefix(self: *Build, search_prefix: []const u8) void {
|
||||||
self.search_prefixes.append(self.dupePath(search_prefix)) catch @panic("OOM");
|
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");
|
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.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,
|
b: *Build,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
build_root_string: []const u8,
|
build_root_string: []const u8,
|
||||||
@ -1566,7 +1479,7 @@ fn dependencyInner(
|
|||||||
std.debug.print("unable to open '{s}': {s}\n", .{
|
std.debug.print("unable to open '{s}': {s}\n", .{
|
||||||
build_root_string, @errorName(err),
|
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");
|
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 {
|
pub fn getPath(self: GeneratedFile) []const u8 {
|
||||||
return self.path orelse std.debug.panic(
|
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},
|
.{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.
|
/// 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 {
|
pub fn getPath(self: FileSource, src_builder: *Build) []const u8 {
|
||||||
const path = switch (self) {
|
return getPath2(self, src_builder, null);
|
||||||
.path => |p| builder.pathFromRoot(p),
|
}
|
||||||
.generated => |gen| gen.getPath(),
|
|
||||||
};
|
/// Should only be called during make(), returns a path relative to the build root or absolute.
|
||||||
return path;
|
/// 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.
|
/// 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.
|
/// Allocates a new string for assigning a value to a named macro.
|
||||||
/// If the value is omitted, it is set to 1.
|
/// If the value is omitted, it is set to 1.
|
||||||
/// `name` and `value` need not live longer than the function call.
|
/// `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.
|
/// Duplicates the install directory including the path if set to custom.
|
||||||
pub fn dupe(self: InstallDir, builder: *Build) InstallDir {
|
pub fn dupe(self: InstallDir, builder: *Build) InstallDir {
|
||||||
if (self == .custom) {
|
if (self == .custom) {
|
||||||
// Written with this temporary to avoid RLS problems
|
return .{ .custom = builder.dupe(self.custom) };
|
||||||
const duped_path = builder.dupe(self.custom);
|
|
||||||
return .{ .custom = duped_path };
|
|
||||||
} else {
|
} else {
|
||||||
return self;
|
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 {
|
test {
|
||||||
_ = CheckFileStep;
|
_ = CheckFileStep;
|
||||||
_ = CheckObjectStep;
|
_ = CheckObjectStep;
|
||||||
_ = EmulatableRunStep;
|
|
||||||
_ = FmtStep;
|
_ = FmtStep;
|
||||||
_ = InstallArtifactStep;
|
_ = InstallArtifactStep;
|
||||||
_ = InstallDirStep;
|
_ = InstallDirStep;
|
||||||
_ = InstallFileStep;
|
_ = InstallFileStep;
|
||||||
_ = ObjCopyStep;
|
_ = ObjCopyStep;
|
||||||
_ = CompileStep;
|
_ = CompileStep;
|
||||||
_ = LogStep;
|
|
||||||
_ = OptionsStep;
|
_ = OptionsStep;
|
||||||
_ = RemoveDirStep;
|
_ = RemoveDirStep;
|
||||||
_ = RunStep;
|
_ = RunStep;
|
||||||
|
|||||||
@ -7,27 +7,27 @@ pub const Directory = struct {
|
|||||||
/// directly, but it is needed when passing the directory to a child process.
|
/// directly, but it is needed when passing the directory to a child process.
|
||||||
/// `null` means cwd.
|
/// `null` means cwd.
|
||||||
path: ?[]const u8,
|
path: ?[]const u8,
|
||||||
handle: std.fs.Dir,
|
handle: fs.Dir,
|
||||||
|
|
||||||
pub fn join(self: Directory, allocator: Allocator, paths: []const []const u8) ![]u8 {
|
pub fn join(self: Directory, allocator: Allocator, paths: []const []const u8) ![]u8 {
|
||||||
if (self.path) |p| {
|
if (self.path) |p| {
|
||||||
// TODO clean way to do this with only 1 allocation
|
// 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);
|
defer allocator.free(part2);
|
||||||
return std.fs.path.join(allocator, &[_][]const u8{ p, part2 });
|
return fs.path.join(allocator, &[_][]const u8{ p, part2 });
|
||||||
} else {
|
} 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 {
|
pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) ![:0]u8 {
|
||||||
if (self.path) |p| {
|
if (self.path) |p| {
|
||||||
// TODO clean way to do this with only 1 allocation
|
// 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);
|
defer allocator.free(part2);
|
||||||
return std.fs.path.joinZ(allocator, &[_][]const u8{ p, part2 });
|
return fs.path.joinZ(allocator, &[_][]const u8{ p, part2 });
|
||||||
} else {
|
} 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);
|
if (self.path) |p| gpa.free(p);
|
||||||
self.* = undefined;
|
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,
|
gpa: Allocator,
|
||||||
@ -243,10 +257,10 @@ pub const HashHelper = struct {
|
|||||||
hh.hasher.final(&bin_digest);
|
hh.hasher.final(&bin_digest);
|
||||||
|
|
||||||
var out_digest: [hex_digest_len]u8 = undefined;
|
var out_digest: [hex_digest_len]u8 = undefined;
|
||||||
_ = std.fmt.bufPrint(
|
_ = fmt.bufPrint(
|
||||||
&out_digest,
|
&out_digest,
|
||||||
"{s}",
|
"{s}",
|
||||||
.{std.fmt.fmtSliceHexLower(&bin_digest)},
|
.{fmt.fmtSliceHexLower(&bin_digest)},
|
||||||
) catch unreachable;
|
) catch unreachable;
|
||||||
return out_digest;
|
return out_digest;
|
||||||
}
|
}
|
||||||
@ -365,10 +379,10 @@ pub const Manifest = struct {
|
|||||||
var bin_digest: BinDigest = undefined;
|
var bin_digest: BinDigest = undefined;
|
||||||
self.hash.hasher.final(&bin_digest);
|
self.hash.hasher.final(&bin_digest);
|
||||||
|
|
||||||
_ = std.fmt.bufPrint(
|
_ = fmt.bufPrint(
|
||||||
&self.hex_digest,
|
&self.hex_digest,
|
||||||
"{s}",
|
"{s}",
|
||||||
.{std.fmt.fmtSliceHexLower(&bin_digest)},
|
.{fmt.fmtSliceHexLower(&bin_digest)},
|
||||||
) catch unreachable;
|
) catch unreachable;
|
||||||
|
|
||||||
self.hash.hasher = hasher_init;
|
self.hash.hasher = hasher_init;
|
||||||
@ -408,7 +422,11 @@ pub const Manifest = struct {
|
|||||||
self.have_exclusive_lock = true;
|
self.have_exclusive_lock = true;
|
||||||
return false; // cache miss; exclusive lock already held
|
return false; // cache miss; exclusive lock already held
|
||||||
} else |err| switch (err) {
|
} 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,
|
else => |e| return e,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -425,7 +443,10 @@ pub const Manifest = struct {
|
|||||||
self.manifest_file = manifest_file;
|
self.manifest_file = manifest_file;
|
||||||
self.have_exclusive_lock = true;
|
self.have_exclusive_lock = true;
|
||||||
} else |err| switch (err) {
|
} 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, .{
|
self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{
|
||||||
.lock = .Shared,
|
.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.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.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;
|
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;
|
const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat;
|
||||||
if (prefix >= self.cache.prefixes_len) 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);
|
self.hash.hasher.final(&bin_digest);
|
||||||
|
|
||||||
var out_digest: [hex_digest_len]u8 = undefined;
|
var out_digest: [hex_digest_len]u8 = undefined;
|
||||||
_ = std.fmt.bufPrint(
|
_ = fmt.bufPrint(
|
||||||
&out_digest,
|
&out_digest,
|
||||||
"{s}",
|
"{s}",
|
||||||
.{std.fmt.fmtSliceHexLower(&bin_digest)},
|
.{fmt.fmtSliceHexLower(&bin_digest)},
|
||||||
) catch unreachable;
|
) catch unreachable;
|
||||||
|
|
||||||
return out_digest;
|
return out_digest;
|
||||||
@ -831,10 +852,10 @@ pub const Manifest = struct {
|
|||||||
var encoded_digest: [hex_digest_len]u8 = undefined;
|
var encoded_digest: [hex_digest_len]u8 = undefined;
|
||||||
|
|
||||||
for (self.files.items) |file| {
|
for (self.files.items) |file| {
|
||||||
_ = std.fmt.bufPrint(
|
_ = fmt.bufPrint(
|
||||||
&encoded_digest,
|
&encoded_digest,
|
||||||
"{s}",
|
"{s}",
|
||||||
.{std.fmt.fmtSliceHexLower(&file.bin_digest)},
|
.{fmt.fmtSliceHexLower(&file.bin_digest)},
|
||||||
) catch unreachable;
|
) catch unreachable;
|
||||||
try writer.print("{d} {d} {d} {s} {d} {s}\n", .{
|
try writer.print("{d} {d} {d} {s} {d} {s}\n", .{
|
||||||
file.stat.size,
|
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.
|
// 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";
|
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,
|
.read = true,
|
||||||
.truncate = true,
|
.truncate = true,
|
||||||
});
|
});
|
||||||
defer {
|
defer {
|
||||||
file.close();
|
file.close();
|
||||||
fs.cwd().deleteFile(test_out_file) catch {};
|
dir.deleteFile(test_out_file) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (try file.stat()).mtime;
|
return (try file.stat()).mtime;
|
||||||
@ -976,16 +997,17 @@ test "cache file and then recall it" {
|
|||||||
return error.SkipZigTest;
|
return error.SkipZigTest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cwd = fs.cwd();
|
var tmp = testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const temp_file = "test.txt";
|
const temp_file = "test.txt";
|
||||||
const temp_manifest_dir = "temp_manifest_dir";
|
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
|
// Wait for file timestamps to tick
|
||||||
const initial_time = try testGetCurrentFileTimestamp();
|
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
|
||||||
while ((try testGetCurrentFileTimestamp()) == initial_time) {
|
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
|
||||||
std.time.sleep(1);
|
std.time.sleep(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -995,9 +1017,9 @@ test "cache file and then recall it" {
|
|||||||
{
|
{
|
||||||
var cache = Cache{
|
var cache = Cache{
|
||||||
.gpa = testing.allocator,
|
.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();
|
defer cache.manifest_dir.close();
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -1033,9 +1055,6 @@ test "cache file and then recall it" {
|
|||||||
|
|
||||||
try testing.expectEqual(digest1, digest2);
|
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" {
|
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
|
// https://github.com/ziglang/zig/issues/5437
|
||||||
return error.SkipZigTest;
|
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_file = "cache_hash_change_file_test.txt";
|
||||||
const temp_manifest_dir = "cache_hash_change_file_manifest_dir";
|
const temp_manifest_dir = "cache_hash_change_file_manifest_dir";
|
||||||
const original_temp_file_contents = "Hello, world!\n";
|
const original_temp_file_contents = "Hello, world!\n";
|
||||||
const updated_temp_file_contents = "Hello, world; but updated!\n";
|
const updated_temp_file_contents = "Hello, world; but updated!\n";
|
||||||
|
|
||||||
try cwd.deleteTree(temp_manifest_dir);
|
try tmp.dir.writeFile(temp_file, original_temp_file_contents);
|
||||||
try cwd.deleteTree(temp_file);
|
|
||||||
|
|
||||||
try cwd.writeFile(temp_file, original_temp_file_contents);
|
|
||||||
|
|
||||||
// Wait for file timestamps to tick
|
// Wait for file timestamps to tick
|
||||||
const initial_time = try testGetCurrentFileTimestamp();
|
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
|
||||||
while ((try testGetCurrentFileTimestamp()) == initial_time) {
|
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
|
||||||
std.time.sleep(1);
|
std.time.sleep(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1067,9 +1084,9 @@ test "check that changing a file makes cache fail" {
|
|||||||
{
|
{
|
||||||
var cache = Cache{
|
var cache = Cache{
|
||||||
.gpa = testing.allocator,
|
.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();
|
defer cache.manifest_dir.close();
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -1089,7 +1106,7 @@ test "check that changing a file makes cache fail" {
|
|||||||
try ch.writeManifest();
|
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();
|
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 testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));
|
||||||
}
|
}
|
||||||
|
|
||||||
try cwd.deleteTree(temp_manifest_dir);
|
|
||||||
try cwd.deleteTree(temp_file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "no file inputs" {
|
test "no file inputs" {
|
||||||
@ -1121,18 +1135,20 @@ test "no file inputs" {
|
|||||||
// https://github.com/ziglang/zig/issues/5437
|
// https://github.com/ziglang/zig/issues/5437
|
||||||
return error.SkipZigTest;
|
return error.SkipZigTest;
|
||||||
}
|
}
|
||||||
const cwd = fs.cwd();
|
|
||||||
|
var tmp = testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const temp_manifest_dir = "no_file_inputs_manifest_dir";
|
const temp_manifest_dir = "no_file_inputs_manifest_dir";
|
||||||
defer cwd.deleteTree(temp_manifest_dir) catch {};
|
|
||||||
|
|
||||||
var digest1: [hex_digest_len]u8 = undefined;
|
var digest1: [hex_digest_len]u8 = undefined;
|
||||||
var digest2: [hex_digest_len]u8 = undefined;
|
var digest2: [hex_digest_len]u8 = undefined;
|
||||||
|
|
||||||
var cache = Cache{
|
var cache = Cache{
|
||||||
.gpa = testing.allocator,
|
.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();
|
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
|
// https://github.com/ziglang/zig/issues/5437
|
||||||
return error.SkipZigTest;
|
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_file1 = "cache_hash_post_file_test1.txt";
|
||||||
const temp_file2 = "cache_hash_post_file_test2.txt";
|
const temp_file2 = "cache_hash_post_file_test2.txt";
|
||||||
const temp_manifest_dir = "cache_hash_post_file_manifest_dir";
|
const temp_manifest_dir = "cache_hash_post_file_manifest_dir";
|
||||||
|
|
||||||
try cwd.writeFile(temp_file1, "Hello, world!\n");
|
try tmp.dir.writeFile(temp_file1, "Hello, world!\n");
|
||||||
try cwd.writeFile(temp_file2, "Hello world the second!\n");
|
try tmp.dir.writeFile(temp_file2, "Hello world the second!\n");
|
||||||
|
|
||||||
// Wait for file timestamps to tick
|
// Wait for file timestamps to tick
|
||||||
const initial_time = try testGetCurrentFileTimestamp();
|
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
|
||||||
while ((try testGetCurrentFileTimestamp()) == initial_time) {
|
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
|
||||||
std.time.sleep(1);
|
std.time.sleep(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1189,9 +1206,9 @@ test "Manifest with files added after initial hash work" {
|
|||||||
{
|
{
|
||||||
var cache = Cache{
|
var cache = Cache{
|
||||||
.gpa = testing.allocator,
|
.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();
|
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));
|
try testing.expect(mem.eql(u8, &digest1, &digest2));
|
||||||
|
|
||||||
// Modify the file added after initial hash
|
// 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
|
// Wait for file timestamps to tick
|
||||||
const initial_time2 = try testGetCurrentFileTimestamp();
|
const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
|
||||||
while ((try testGetCurrentFileTimestamp()) == initial_time2) {
|
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) {
|
||||||
std.time.sleep(1);
|
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 testing.expect(!mem.eql(u8, &digest1, &digest3));
|
||||||
}
|
}
|
||||||
|
|
||||||
try cwd.deleteTree(temp_manifest_dir);
|
|
||||||
try cwd.deleteFile(temp_file1);
|
|
||||||
try cwd.deleteFile(temp_file2);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,51 +1,88 @@
|
|||||||
const std = @import("../std.zig");
|
//! Fail the build step if a file does not match certain checks.
|
||||||
const Step = std.Build.Step;
|
//! TODO: make this more flexible, supporting more kinds of checks.
|
||||||
const fs = std.fs;
|
//! TODO: generalize the code in std.testing.expectEqualStrings and make this
|
||||||
const mem = std.mem;
|
//! CheckFileStep produce those helpful diagnostics when there is not a match.
|
||||||
|
|
||||||
const CheckFileStep = @This();
|
|
||||||
|
|
||||||
pub const base_id = .check_file;
|
|
||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
builder: *std.Build,
|
|
||||||
expected_matches: []const []const u8,
|
expected_matches: []const []const u8,
|
||||||
|
expected_exact: ?[]const u8,
|
||||||
source: std.Build.FileSource,
|
source: std.Build.FileSource,
|
||||||
max_bytes: usize = 20 * 1024 * 1024,
|
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(
|
pub fn create(
|
||||||
builder: *std.Build,
|
owner: *std.Build,
|
||||||
source: std.Build.FileSource,
|
source: std.Build.FileSource,
|
||||||
expected_matches: []const []const u8,
|
options: Options,
|
||||||
) *CheckFileStep {
|
) *CheckFileStep {
|
||||||
const self = builder.allocator.create(CheckFileStep) catch @panic("OOM");
|
const self = owner.allocator.create(CheckFileStep) catch @panic("OOM");
|
||||||
self.* = CheckFileStep{
|
self.* = .{
|
||||||
.builder = builder,
|
.step = Step.init(.{
|
||||||
.step = Step.init(.check_file, "CheckFile", builder.allocator, make),
|
.id = .check_file,
|
||||||
.source = source.dupe(builder),
|
.name = "CheckFile",
|
||||||
.expected_matches = builder.dupeStrings(expected_matches),
|
.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);
|
self.source.addStepDependencies(&self.step);
|
||||||
return self;
|
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 self = @fieldParentPtr(CheckFileStep, "step", step);
|
||||||
|
|
||||||
const src_path = self.source.getPath(self.builder);
|
const src_path = self.source.getPath(b);
|
||||||
const contents = try fs.cwd().readFileAlloc(self.builder.allocator, src_path, self.max_bytes);
|
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| {
|
for (self.expected_matches) |expected_match| {
|
||||||
if (mem.indexOf(u8, contents, expected_match) == null) {
|
if (mem.indexOf(u8, contents, expected_match) == null) {
|
||||||
std.debug.print(
|
return step.fail(
|
||||||
\\
|
\\
|
||||||
\\========= Expected to find: ===================
|
\\========= expected to find: ===================
|
||||||
\\{s}
|
\\{s}
|
||||||
\\========= But file does not contain it: =======
|
\\========= but file does not contain it: =======
|
||||||
\\{s}
|
\\{s}
|
||||||
\\
|
\\===============================================
|
||||||
, .{ expected_match, contents });
|
, .{ 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;
|
||||||
|
|||||||
@ -10,25 +10,31 @@ const CheckObjectStep = @This();
|
|||||||
|
|
||||||
const Allocator = mem.Allocator;
|
const Allocator = mem.Allocator;
|
||||||
const Step = std.Build.Step;
|
const Step = std.Build.Step;
|
||||||
const EmulatableRunStep = std.Build.EmulatableRunStep;
|
|
||||||
|
|
||||||
pub const base_id = .check_object;
|
pub const base_id = .check_object;
|
||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
builder: *std.Build,
|
|
||||||
source: std.Build.FileSource,
|
source: std.Build.FileSource,
|
||||||
max_bytes: usize = 20 * 1024 * 1024,
|
max_bytes: usize = 20 * 1024 * 1024,
|
||||||
checks: std.ArrayList(Check),
|
checks: std.ArrayList(Check),
|
||||||
dump_symtab: bool = false,
|
dump_symtab: bool = false,
|
||||||
obj_format: std.Target.ObjectFormat,
|
obj_format: std.Target.ObjectFormat,
|
||||||
|
|
||||||
pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep {
|
pub fn create(
|
||||||
const gpa = builder.allocator;
|
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");
|
const self = gpa.create(CheckObjectStep) catch @panic("OOM");
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.builder = builder,
|
.step = Step.init(.{
|
||||||
.step = Step.init(.check_file, "CheckObject", gpa, make),
|
.id = .check_file,
|
||||||
.source = source.dupe(builder),
|
.name = "CheckObject",
|
||||||
|
.owner = owner,
|
||||||
|
.makeFn = make,
|
||||||
|
}),
|
||||||
|
.source = source.dupe(owner),
|
||||||
.checks = std.ArrayList(Check).init(gpa),
|
.checks = std.ArrayList(Check).init(gpa),
|
||||||
.obj_format = obj_format,
|
.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.
|
/// Runs and (optionally) compares the output of a binary.
|
||||||
/// Asserts `self` was generated from an executable step.
|
/// 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;
|
const dependencies_len = self.step.dependencies.items.len;
|
||||||
assert(dependencies_len > 0);
|
assert(dependencies_len > 0);
|
||||||
const exe_step = self.step.dependencies.items[dependencies_len - 1];
|
const exe_step = self.step.dependencies.items[dependencies_len - 1];
|
||||||
const exe = exe_step.cast(std.Build.CompileStep).?;
|
const exe = exe_step.cast(std.Build.CompileStep).?;
|
||||||
const emulatable_step = EmulatableRunStep.create(self.builder, "EmulatableRun", exe);
|
const run = self.step.owner.addRunArtifact(exe);
|
||||||
emulatable_step.step.dependOn(&self.step);
|
run.skip_foreign_checks = true;
|
||||||
return emulatable_step;
|
run.step.dependOn(&self.step);
|
||||||
|
return run;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// There two types of actions currently suported:
|
/// 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
|
/// 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
|
/// its reduced, computed value compares using `op` with the expected value, either
|
||||||
/// a literal or another extracted variable.
|
/// 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 op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa);
|
||||||
var values = std.ArrayList(u64).init(gpa);
|
var values = std.ArrayList(u64).init(gpa);
|
||||||
|
|
||||||
@ -140,11 +151,11 @@ const Action = struct {
|
|||||||
} else {
|
} else {
|
||||||
const val = std.fmt.parseInt(u64, next, 0) catch blk: {
|
const val = std.fmt.parseInt(u64, next, 0) catch blk: {
|
||||||
break :blk global_vars.get(next) orelse {
|
break :blk global_vars.get(next) orelse {
|
||||||
std.debug.print(
|
try step.addError(
|
||||||
\\
|
\\
|
||||||
\\========= Variable was not extracted: ===========
|
\\========= variable was not extracted: ===========
|
||||||
\\{s}
|
\\{s}
|
||||||
\\
|
\\=================================================
|
||||||
, .{next});
|
, .{next});
|
||||||
return error.UnknownVariable;
|
return error.UnknownVariable;
|
||||||
};
|
};
|
||||||
@ -176,11 +187,11 @@ const Action = struct {
|
|||||||
|
|
||||||
const exp_value = switch (act.expected.?.value) {
|
const exp_value = switch (act.expected.?.value) {
|
||||||
.variable => |name| global_vars.get(name) orelse {
|
.variable => |name| global_vars.get(name) orelse {
|
||||||
std.debug.print(
|
try step.addError(
|
||||||
\\
|
\\
|
||||||
\\========= Variable was not extracted: ===========
|
\\========= variable was not extracted: ===========
|
||||||
\\{s}
|
\\{s}
|
||||||
\\
|
\\=================================================
|
||||||
, .{name});
|
, .{name});
|
||||||
return error.UnknownVariable;
|
return error.UnknownVariable;
|
||||||
},
|
},
|
||||||
@ -249,7 +260,7 @@ const Check = struct {
|
|||||||
|
|
||||||
/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase.
|
/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase.
|
||||||
pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void {
|
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);
|
new_check.match(phrase);
|
||||||
self.checks.append(new_check) catch @panic("OOM");
|
self.checks.append(new_check) catch @panic("OOM");
|
||||||
}
|
}
|
||||||
@ -291,34 +302,34 @@ pub fn checkComputeCompare(
|
|||||||
program: []const u8,
|
program: []const u8,
|
||||||
expected: ComputeCompareExpected,
|
expected: ComputeCompareExpected,
|
||||||
) void {
|
) void {
|
||||||
var new_check = Check.create(self.builder);
|
var new_check = Check.create(self.step.owner);
|
||||||
new_check.computeCmp(program, expected);
|
new_check.computeCmp(program, expected);
|
||||||
self.checks.append(new_check) catch @panic("OOM");
|
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 self = @fieldParentPtr(CheckObjectStep, "step", step);
|
||||||
|
|
||||||
const gpa = self.builder.allocator;
|
const src_path = self.source.getPath(b);
|
||||||
const src_path = self.source.getPath(self.builder);
|
const contents = fs.cwd().readFileAllocOptions(
|
||||||
const contents = try fs.cwd().readFileAllocOptions(
|
|
||||||
gpa,
|
gpa,
|
||||||
src_path,
|
src_path,
|
||||||
self.max_bytes,
|
self.max_bytes,
|
||||||
null,
|
null,
|
||||||
@alignOf(u64),
|
@alignOf(u64),
|
||||||
null,
|
null,
|
||||||
);
|
) catch |err| return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err) });
|
||||||
|
|
||||||
const output = switch (self.obj_format) {
|
const output = switch (self.obj_format) {
|
||||||
.macho => try MachODumper.parseAndDump(contents, .{
|
.macho => try MachODumper.parseAndDump(step, contents, .{
|
||||||
.gpa = gpa,
|
|
||||||
.dump_symtab = self.dump_symtab,
|
.dump_symtab = self.dump_symtab,
|
||||||
}),
|
}),
|
||||||
.elf => @panic("TODO elf parser"),
|
.elf => @panic("TODO elf parser"),
|
||||||
.coff => @panic("TODO coff parser"),
|
.coff => @panic("TODO coff parser"),
|
||||||
.wasm => try WasmDumper.parseAndDump(contents, .{
|
.wasm => try WasmDumper.parseAndDump(step, contents, .{
|
||||||
.gpa = gpa,
|
|
||||||
.dump_symtab = self.dump_symtab,
|
.dump_symtab = self.dump_symtab,
|
||||||
}),
|
}),
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
@ -334,54 +345,50 @@ fn make(step: *Step) !void {
|
|||||||
while (it.next()) |line| {
|
while (it.next()) |line| {
|
||||||
if (try act.match(line, &vars)) break;
|
if (try act.match(line, &vars)) break;
|
||||||
} else {
|
} else {
|
||||||
std.debug.print(
|
return step.fail(
|
||||||
\\
|
\\
|
||||||
\\========= Expected to find: ==========================
|
\\========= expected to find: ==========================
|
||||||
\\{s}
|
\\{s}
|
||||||
\\========= But parsed file does not contain it: =======
|
\\========= but parsed file does not contain it: =======
|
||||||
\\{s}
|
\\{s}
|
||||||
\\
|
\\======================================================
|
||||||
, .{ act.phrase, output });
|
, .{ act.phrase, output });
|
||||||
return error.TestFailed;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.not_present => {
|
.not_present => {
|
||||||
while (it.next()) |line| {
|
while (it.next()) |line| {
|
||||||
if (try act.match(line, &vars)) {
|
if (try act.match(line, &vars)) {
|
||||||
std.debug.print(
|
return step.fail(
|
||||||
\\
|
\\
|
||||||
\\========= Expected not to find: ===================
|
\\========= expected not to find: ===================
|
||||||
\\{s}
|
\\{s}
|
||||||
\\========= But parsed file does contain it: ========
|
\\========= but parsed file does contain it: ========
|
||||||
\\{s}
|
\\{s}
|
||||||
\\
|
\\===================================================
|
||||||
, .{ act.phrase, output });
|
, .{ act.phrase, output });
|
||||||
return error.TestFailed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.compute_cmp => {
|
.compute_cmp => {
|
||||||
const res = act.computeCmp(gpa, vars) catch |err| switch (err) {
|
const res = act.computeCmp(step, vars) catch |err| switch (err) {
|
||||||
error.UnknownVariable => {
|
error.UnknownVariable => {
|
||||||
std.debug.print(
|
return step.fail(
|
||||||
\\========= From parsed file: =====================
|
\\========= from parsed file: =====================
|
||||||
\\{s}
|
\\{s}
|
||||||
\\
|
\\=================================================
|
||||||
, .{output});
|
, .{output});
|
||||||
return error.TestFailed;
|
|
||||||
},
|
},
|
||||||
else => |e| return e,
|
else => |e| return e,
|
||||||
};
|
};
|
||||||
if (!res) {
|
if (!res) {
|
||||||
std.debug.print(
|
return step.fail(
|
||||||
\\
|
\\
|
||||||
\\========= Comparison failed for action: ===========
|
\\========= comparison failed for action: ===========
|
||||||
\\{s} {}
|
\\{s} {}
|
||||||
\\========= From parsed file: =======================
|
\\========= from parsed file: =======================
|
||||||
\\{s}
|
\\{s}
|
||||||
\\
|
\\===================================================
|
||||||
, .{ act.phrase, act.expected.?, output });
|
, .{ act.phrase, act.expected.?, output });
|
||||||
return error.TestFailed;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -390,7 +397,6 @@ fn make(step: *Step) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Opts = struct {
|
const Opts = struct {
|
||||||
gpa: ?Allocator = null,
|
|
||||||
dump_symtab: bool = false,
|
dump_symtab: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -398,8 +404,8 @@ const MachODumper = struct {
|
|||||||
const LoadCommandIterator = macho.LoadCommandIterator;
|
const LoadCommandIterator = macho.LoadCommandIterator;
|
||||||
const symtab_label = "symtab";
|
const symtab_label = "symtab";
|
||||||
|
|
||||||
fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {
|
fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {
|
||||||
const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator
|
const gpa = step.owner.allocator;
|
||||||
var stream = std.io.fixedBufferStream(bytes);
|
var stream = std.io.fixedBufferStream(bytes);
|
||||||
const reader = stream.reader();
|
const reader = stream.reader();
|
||||||
|
|
||||||
@ -681,8 +687,8 @@ const MachODumper = struct {
|
|||||||
const WasmDumper = struct {
|
const WasmDumper = struct {
|
||||||
const symtab_label = "symbols";
|
const symtab_label = "symbols";
|
||||||
|
|
||||||
fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 {
|
fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 {
|
||||||
const gpa = opts.gpa orelse unreachable; // Wasm dumper requires an allocator
|
const gpa = step.owner.allocator;
|
||||||
if (opts.dump_symtab) {
|
if (opts.dump_symtab) {
|
||||||
@panic("TODO: Implement symbol table parsing and dumping");
|
@panic("TODO: Implement symbol table parsing and dumping");
|
||||||
}
|
}
|
||||||
@ -703,20 +709,24 @@ const WasmDumper = struct {
|
|||||||
const writer = output.writer();
|
const writer = output.writer();
|
||||||
|
|
||||||
while (reader.readByte()) |current_byte| {
|
while (reader.readByte()) |current_byte| {
|
||||||
const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch |err| {
|
const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch {
|
||||||
std.debug.print("Found invalid section id '{d}'\n", .{current_byte});
|
return step.fail("Found invalid section id '{d}'", .{current_byte});
|
||||||
return err;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const section_length = try std.leb.readULEB128(u32, reader);
|
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;
|
fbs.pos += section_length;
|
||||||
} else |_| {} // reached end of stream
|
} else |_| {} // reached end of stream
|
||||||
|
|
||||||
return output.toOwnedSlice();
|
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);
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
const reader = fbs.reader();
|
const reader = fbs.reader();
|
||||||
|
|
||||||
@ -739,7 +749,7 @@ const WasmDumper = struct {
|
|||||||
=> {
|
=> {
|
||||||
const entries = try std.leb.readULEB128(u32, reader);
|
const entries = try std.leb.readULEB128(u32, reader);
|
||||||
try writer.print("\nentries {d}\n", .{entries});
|
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 => {
|
.custom => {
|
||||||
const name_length = try std.leb.readULEB128(u32, reader);
|
const name_length = try std.leb.readULEB128(u32, reader);
|
||||||
@ -748,7 +758,7 @@ const WasmDumper = struct {
|
|||||||
try writer.print("\nname {s}\n", .{name});
|
try writer.print("\nname {s}\n", .{name});
|
||||||
|
|
||||||
if (mem.eql(u8, name, "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")) {
|
} else if (mem.eql(u8, name, "producers")) {
|
||||||
try parseDumpProducers(reader, writer, data);
|
try parseDumpProducers(reader, writer, data);
|
||||||
} else if (mem.eql(u8, name, "target_features")) {
|
} 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);
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
const reader = fbs.reader();
|
const reader = fbs.reader();
|
||||||
|
|
||||||
@ -774,19 +784,18 @@ const WasmDumper = struct {
|
|||||||
while (i < entries) : (i += 1) {
|
while (i < entries) : (i += 1) {
|
||||||
const func_type = try reader.readByte();
|
const func_type = try reader.readByte();
|
||||||
if (func_type != std.wasm.function_type) {
|
if (func_type != std.wasm.function_type) {
|
||||||
std.debug.print("Expected function type, found byte '{d}'\n", .{func_type});
|
return step.fail("expected function type, found byte '{d}'", .{func_type});
|
||||||
return error.UnexpectedByte;
|
|
||||||
}
|
}
|
||||||
const params = try std.leb.readULEB128(u32, reader);
|
const params = try std.leb.readULEB128(u32, reader);
|
||||||
try writer.print("params {d}\n", .{params});
|
try writer.print("params {d}\n", .{params});
|
||||||
var index: u32 = 0;
|
var index: u32 = 0;
|
||||||
while (index < params) : (index += 1) {
|
while (index < params) : (index += 1) {
|
||||||
try parseDumpType(std.wasm.Valtype, reader, writer);
|
try parseDumpType(step, std.wasm.Valtype, reader, writer);
|
||||||
} else index = 0;
|
} else index = 0;
|
||||||
const returns = try std.leb.readULEB128(u32, reader);
|
const returns = try std.leb.readULEB128(u32, reader);
|
||||||
try writer.print("returns {d}\n", .{returns});
|
try writer.print("returns {d}\n", .{returns});
|
||||||
while (index < returns) : (index += 1) {
|
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];
|
const name = data[fbs.pos..][0..name_len];
|
||||||
fbs.pos += name_len;
|
fbs.pos += name_len;
|
||||||
|
|
||||||
const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch |err| {
|
const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch {
|
||||||
std.debug.print("Invalid import kind\n", .{});
|
return step.fail("invalid import kind", .{});
|
||||||
return err;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try writer.print(
|
try writer.print(
|
||||||
@ -819,11 +827,11 @@ const WasmDumper = struct {
|
|||||||
try parseDumpLimits(reader, writer);
|
try parseDumpLimits(reader, writer);
|
||||||
},
|
},
|
||||||
.global => {
|
.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)});
|
try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)});
|
||||||
},
|
},
|
||||||
.table => {
|
.table => {
|
||||||
try parseDumpType(std.wasm.RefType, reader, writer);
|
try parseDumpType(step, std.wasm.RefType, reader, writer);
|
||||||
try parseDumpLimits(reader, writer);
|
try parseDumpLimits(reader, writer);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -838,7 +846,7 @@ const WasmDumper = struct {
|
|||||||
.table => {
|
.table => {
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (i < entries) : (i += 1) {
|
while (i < entries) : (i += 1) {
|
||||||
try parseDumpType(std.wasm.RefType, reader, writer);
|
try parseDumpType(step, std.wasm.RefType, reader, writer);
|
||||||
try parseDumpLimits(reader, writer);
|
try parseDumpLimits(reader, writer);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -851,9 +859,9 @@ const WasmDumper = struct {
|
|||||||
.global => {
|
.global => {
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (i < entries) : (i += 1) {
|
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 writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)});
|
||||||
try parseDumpInit(reader, writer);
|
try parseDumpInit(step, reader, writer);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.@"export" => {
|
.@"export" => {
|
||||||
@ -863,9 +871,8 @@ const WasmDumper = struct {
|
|||||||
const name = data[fbs.pos..][0..name_len];
|
const name = data[fbs.pos..][0..name_len];
|
||||||
fbs.pos += name_len;
|
fbs.pos += name_len;
|
||||||
const kind_byte = try std.leb.readULEB128(u8, reader);
|
const kind_byte = try std.leb.readULEB128(u8, reader);
|
||||||
const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch |err| {
|
const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch {
|
||||||
std.debug.print("invalid export kind value '{d}'\n", .{kind_byte});
|
return step.fail("invalid export kind value '{d}'", .{kind_byte});
|
||||||
return err;
|
|
||||||
};
|
};
|
||||||
const index = try std.leb.readULEB128(u32, reader);
|
const index = try std.leb.readULEB128(u32, reader);
|
||||||
try writer.print(
|
try writer.print(
|
||||||
@ -880,7 +887,7 @@ const WasmDumper = struct {
|
|||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (i < entries) : (i += 1) {
|
while (i < entries) : (i += 1) {
|
||||||
try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)});
|
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);
|
const function_indexes = try std.leb.readULEB128(u32, reader);
|
||||||
var function_index: u32 = 0;
|
var function_index: u32 = 0;
|
||||||
@ -896,7 +903,7 @@ const WasmDumper = struct {
|
|||||||
while (i < entries) : (i += 1) {
|
while (i < entries) : (i += 1) {
|
||||||
const index = try std.leb.readULEB128(u32, reader);
|
const index = try std.leb.readULEB128(u32, reader);
|
||||||
try writer.print("memory index 0x{x}\n", .{index});
|
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);
|
const size = try std.leb.readULEB128(u32, reader);
|
||||||
try writer.print("size {d}\n", .{size});
|
try writer.print("size {d}\n", .{size});
|
||||||
try reader.skipBytes(size, .{}); // we do not care about the content of the segments
|
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 type_byte = try reader.readByte();
|
||||||
const valtype = std.meta.intToEnum(WasmType, type_byte) catch |err| {
|
const valtype = std.meta.intToEnum(WasmType, type_byte) catch {
|
||||||
std.debug.print("Invalid wasm type value '{d}'\n", .{type_byte});
|
return step.fail("Invalid wasm type value '{d}'", .{type_byte});
|
||||||
return err;
|
|
||||||
};
|
};
|
||||||
try writer.print("type {s}\n", .{@tagName(valtype)});
|
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 byte = try std.leb.readULEB128(u8, reader);
|
||||||
const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch |err| {
|
const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch {
|
||||||
std.debug.print("invalid wasm opcode '{d}'\n", .{byte});
|
return step.fail("invalid wasm opcode '{d}'", .{byte});
|
||||||
return err;
|
|
||||||
};
|
};
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
.i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}),
|
.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);
|
const end_opcode = try std.leb.readULEB128(u8, reader);
|
||||||
if (end_opcode != std.wasm.opcode(.end)) {
|
if (end_opcode != std.wasm.opcode(.end)) {
|
||||||
std.debug.print("expected 'end' opcode in init expression\n", .{});
|
return step.fail("expected 'end' opcode in init expression", .{});
|
||||||
return error.MissingEndOpcode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
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 size = try std.leb.readULEB128(u32, reader);
|
||||||
const entries = try std.leb.readULEB128(u32, reader);
|
const entries = try std.leb.readULEB128(u32, reader);
|
||||||
try writer.print(
|
try writer.print(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -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) {
|
pub const Style = union(enum) {
|
||||||
/// The configure format supported by autotools. It uses `#undef foo` to
|
/// The configure format supported by autotools. It uses `#undef foo` to
|
||||||
/// mark lines that can be substituted with different values.
|
/// mark lines that can be substituted with different values.
|
||||||
@ -34,7 +28,6 @@ pub const Value = union(enum) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
builder: *std.Build,
|
|
||||||
values: std.StringArrayHashMap(Value),
|
values: std.StringArrayHashMap(Value),
|
||||||
output_file: std.Build.GeneratedFile,
|
output_file: std.Build.GeneratedFile,
|
||||||
|
|
||||||
@ -42,43 +35,57 @@ style: Style,
|
|||||||
max_bytes: usize,
|
max_bytes: usize,
|
||||||
include_path: []const u8,
|
include_path: []const u8,
|
||||||
|
|
||||||
|
pub const base_id: Step.Id = .config_header;
|
||||||
|
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
style: Style = .blank,
|
style: Style = .blank,
|
||||||
max_bytes: usize = 2 * 1024 * 1024,
|
max_bytes: usize = 2 * 1024 * 1024,
|
||||||
include_path: ?[]const u8 = null,
|
include_path: ?[]const u8 = null,
|
||||||
|
first_ret_addr: ?usize = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn create(builder: *std.Build, options: Options) *ConfigHeaderStep {
|
pub fn create(owner: *std.Build, options: Options) *ConfigHeaderStep {
|
||||||
const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM");
|
const self = owner.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),
|
|
||||||
|
|
||||||
.max_bytes = options.max_bytes,
|
var include_path: []const u8 = "config.h";
|
||||||
.include_path = "config.h",
|
|
||||||
.output_file = .{ .step = &self.step },
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.style.getFileSource()) |s| switch (s) {
|
if (options.style.getFileSource()) |s| switch (s) {
|
||||||
.path => |p| {
|
.path => |p| {
|
||||||
const basename = std.fs.path.basename(p);
|
const basename = std.fs.path.basename(p);
|
||||||
if (std.mem.endsWith(u8, basename, ".h.in")) {
|
if (std.mem.endsWith(u8, basename, ".h.in")) {
|
||||||
self.include_path = basename[0 .. basename.len - 3];
|
include_path = basename[0 .. basename.len - 3];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.include_path) |include_path| {
|
if (options.include_path) |p| {
|
||||||
self.include_path = include_path;
|
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;
|
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 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
|
var man = b.cache.obtain();
|
||||||
// the data to a file would probably be very fast - but as a way to find a canonical
|
defer man.deinit();
|
||||||
// location to put build artifacts.
|
|
||||||
|
|
||||||
// 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 to make ConfigHeaderStep unique. Refresh this with new
|
||||||
// random bytes when ConfigHeaderStep implementation is modified in a
|
// random bytes when ConfigHeaderStep implementation is modified in a
|
||||||
// non-backwards-compatible way.
|
// non-backwards-compatible way.
|
||||||
var hash = Hasher.init("PGuDTpidxyMqnkGM");
|
man.hash.add(@as(u32, 0xdef08d23));
|
||||||
|
|
||||||
var output = std.ArrayList(u8).init(gpa);
|
var output = std.ArrayList(u8).init(gpa);
|
||||||
defer output.deinit();
|
defer output.deinit();
|
||||||
@ -177,15 +178,15 @@ fn make(step: *Step) !void {
|
|||||||
switch (self.style) {
|
switch (self.style) {
|
||||||
.autoconf => |file_source| {
|
.autoconf => |file_source| {
|
||||||
try output.appendSlice(c_generated_line);
|
try output.appendSlice(c_generated_line);
|
||||||
const src_path = file_source.getPath(self.builder);
|
const src_path = file_source.getPath(b);
|
||||||
const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
|
const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes);
|
||||||
try render_autoconf(contents, &output, self.values, src_path);
|
try render_autoconf(step, contents, &output, self.values, src_path);
|
||||||
},
|
},
|
||||||
.cmake => |file_source| {
|
.cmake => |file_source| {
|
||||||
try output.appendSlice(c_generated_line);
|
try output.appendSlice(c_generated_line);
|
||||||
const src_path = file_source.getPath(self.builder);
|
const src_path = file_source.getPath(b);
|
||||||
const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
|
const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes);
|
||||||
try render_cmake(contents, &output, self.values, src_path);
|
try render_cmake(step, contents, &output, self.values, src_path);
|
||||||
},
|
},
|
||||||
.blank => {
|
.blank => {
|
||||||
try output.appendSlice(c_generated_line);
|
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;
|
if (try step.cacheHit(&man)) {
|
||||||
hash.final(&digest);
|
const digest = man.final();
|
||||||
var hash_basename: [digest.len * 2]u8 = undefined;
|
self.output_file.path = try b.cache_root.join(arena, &.{
|
||||||
_ = std.fmt.bufPrint(
|
"o", &digest, self.include_path,
|
||||||
&hash_basename,
|
});
|
||||||
"{s}",
|
return;
|
||||||
.{std.fmt.fmtSliceHexLower(&digest)},
|
}
|
||||||
) catch unreachable;
|
|
||||||
|
|
||||||
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:
|
// If output_path has directory parts, deal with them. Example:
|
||||||
// output_dir is zig-cache/o/HASH
|
// output_dir is zig-cache/o/HASH
|
||||||
// output_path is libavutil/avconfig.h
|
// output_path is libavutil/avconfig.h
|
||||||
// We want to open directory zig-cache/o/HASH/libavutil/
|
// We want to open directory zig-cache/o/HASH/libavutil/
|
||||||
// but keep output_dir as zig-cache/o/HASH for -I include
|
// 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|
|
const sub_path = try std.fs.path.join(arena, &.{ "o", &digest, self.include_path });
|
||||||
try std.fs.path.join(gpa, &.{ output_dir, d })
|
const sub_path_dirname = std.fs.path.dirname(sub_path).?;
|
||||||
else
|
|
||||||
output_dir;
|
|
||||||
|
|
||||||
var dir = std.fs.cwd().makeOpenPath(sub_dir_path, .{}) catch |err| {
|
b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
|
||||||
std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) });
|
return step.fail("unable to make path '{}{s}': {s}", .{
|
||||||
return err;
|
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, &.{
|
self.output_file.path = try b.cache_root.join(arena, &.{sub_path});
|
||||||
output_dir, self.include_path,
|
try man.writeManifest();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_autoconf(
|
fn render_autoconf(
|
||||||
|
step: *Step,
|
||||||
contents: []const u8,
|
contents: []const u8,
|
||||||
output: *std.ArrayList(u8),
|
output: *std.ArrayList(u8),
|
||||||
values: std.StringArrayHashMap(Value),
|
values: std.StringArrayHashMap(Value),
|
||||||
@ -260,7 +262,7 @@ fn render_autoconf(
|
|||||||
}
|
}
|
||||||
const name = it.rest();
|
const name = it.rest();
|
||||||
const kv = values_copy.fetchSwapRemove(name) orelse {
|
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,
|
src_path, line_index + 1, name,
|
||||||
});
|
});
|
||||||
any_errors = true;
|
any_errors = true;
|
||||||
@ -270,15 +272,17 @@ fn render_autoconf(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (values_copy.keys()) |name| {
|
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) {
|
if (any_errors) {
|
||||||
return error.HeaderConfigFailed;
|
return error.MakeFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_cmake(
|
fn render_cmake(
|
||||||
|
step: *Step,
|
||||||
contents: []const u8,
|
contents: []const u8,
|
||||||
output: *std.ArrayList(u8),
|
output: *std.ArrayList(u8),
|
||||||
values: std.StringArrayHashMap(Value),
|
values: std.StringArrayHashMap(Value),
|
||||||
@ -304,14 +308,14 @@ fn render_cmake(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const name = it.next() orelse {
|
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,
|
src_path, line_index + 1,
|
||||||
});
|
});
|
||||||
any_errors = true;
|
any_errors = true;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
const kv = values_copy.fetchSwapRemove(name) orelse {
|
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,
|
src_path, line_index + 1, name,
|
||||||
});
|
});
|
||||||
any_errors = true;
|
any_errors = true;
|
||||||
@ -321,7 +325,8 @@ fn render_cmake(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (values_copy.keys()) |name| {
|
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) {
|
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;
|
||||||
|
|||||||
@ -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 },
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +1,73 @@
|
|||||||
const std = @import("../std.zig");
|
//! This step has two modes:
|
||||||
const Step = std.Build.Step;
|
//! * Modify mode: directly modify source files, formatting them in place.
|
||||||
const FmtStep = @This();
|
//! * 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;
|
pub const base_id = .fmt;
|
||||||
|
|
||||||
step: Step,
|
pub const Options = struct {
|
||||||
builder: *std.Build,
|
paths: []const []const u8 = &.{},
|
||||||
argv: [][]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 {
|
pub fn create(owner: *std.Build, options: Options) *FmtStep {
|
||||||
const self = builder.allocator.create(FmtStep) catch @panic("OOM");
|
const self = owner.allocator.create(FmtStep) catch @panic("OOM");
|
||||||
const name = "zig fmt";
|
const name = if (options.check) "zig fmt --check" else "zig fmt";
|
||||||
self.* = FmtStep{
|
self.* = .{
|
||||||
.step = Step.init(.fmt, name, builder.allocator, make),
|
.step = Step.init(.{
|
||||||
.builder = builder,
|
.id = base_id,
|
||||||
.argv = builder.allocator.alloc([]u8, paths.len + 2) catch @panic("OOM"),
|
.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;
|
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);
|
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();
|
||||||
|
|||||||
@ -3,83 +3,133 @@ const Step = std.Build.Step;
|
|||||||
const CompileStep = std.Build.CompileStep;
|
const CompileStep = std.Build.CompileStep;
|
||||||
const InstallDir = std.Build.InstallDir;
|
const InstallDir = std.Build.InstallDir;
|
||||||
const InstallArtifactStep = @This();
|
const InstallArtifactStep = @This();
|
||||||
|
const fs = std.fs;
|
||||||
|
|
||||||
pub const base_id = .install_artifact;
|
pub const base_id = .install_artifact;
|
||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
builder: *std.Build,
|
dest_builder: *std.Build,
|
||||||
artifact: *CompileStep,
|
artifact: *CompileStep,
|
||||||
dest_dir: InstallDir,
|
dest_dir: InstallDir,
|
||||||
pdb_dir: ?InstallDir,
|
pdb_dir: ?InstallDir,
|
||||||
h_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;
|
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{
|
self.* = InstallArtifactStep{
|
||||||
.builder = builder,
|
.step = Step.init(.{
|
||||||
.step = Step.init(.install_artifact, builder.fmt("install {s}", .{artifact.step.name}), builder.allocator, make),
|
.id = base_id,
|
||||||
|
.name = owner.fmt("install {s}", .{artifact.name}),
|
||||||
|
.owner = owner,
|
||||||
|
.makeFn = make,
|
||||||
|
}),
|
||||||
|
.dest_builder = owner,
|
||||||
.artifact = artifact,
|
.artifact = artifact,
|
||||||
.dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) {
|
.dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) {
|
||||||
.obj => @panic("Cannot install a .obj build artifact."),
|
.obj => @panic("Cannot install a .obj build artifact."),
|
||||||
.@"test" => @panic("Cannot install a .test build artifact, use .test_exe instead."),
|
.exe, .@"test" => InstallDir{ .bin = {} },
|
||||||
.exe, .test_exe => InstallDir{ .bin = {} },
|
|
||||||
.lib => InstallDir{ .lib = {} },
|
.lib => InstallDir{ .lib = {} },
|
||||||
},
|
},
|
||||||
.pdb_dir = if (artifact.producesPdbFile()) blk: {
|
.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 = {} };
|
break :blk InstallDir{ .bin = {} };
|
||||||
} else {
|
} else {
|
||||||
break :blk InstallDir{ .lib = {} };
|
break :blk InstallDir{ .lib = {} };
|
||||||
}
|
}
|
||||||
} else null,
|
} else null,
|
||||||
.h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null,
|
.h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null,
|
||||||
|
.dest_sub_path = null,
|
||||||
};
|
};
|
||||||
self.step.dependOn(&artifact.step);
|
self.step.dependOn(&artifact.step);
|
||||||
artifact.install_step = self;
|
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 (self.artifact.isDynamicLibrary()) {
|
||||||
if (artifact.major_only_filename) |name| {
|
if (artifact.major_only_filename) |name| {
|
||||||
builder.pushInstalledFile(.lib, name);
|
owner.pushInstalledFile(.lib, name);
|
||||||
}
|
}
|
||||||
if (artifact.name_only_filename) |name| {
|
if (artifact.name_only_filename) |name| {
|
||||||
builder.pushInstalledFile(.lib, name);
|
owner.pushInstalledFile(.lib, name);
|
||||||
}
|
}
|
||||||
if (self.artifact.target.isWindows()) {
|
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| {
|
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| {
|
if (self.h_dir) |h_dir| {
|
||||||
builder.pushInstalledFile(h_dir, artifact.out_h_filename);
|
owner.pushInstalledFile(h_dir, artifact.out_h_filename);
|
||||||
}
|
}
|
||||||
return self;
|
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 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);
|
const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename;
|
||||||
try builder.updateFile(self.artifact.getOutputSource().getPath(builder), full_dest_path);
|
const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path);
|
||||||
if (self.artifact.isDynamicLibrary() and self.artifact.version != null and self.artifact.target.wantSharedLibSymLinks()) {
|
const cwd = fs.cwd();
|
||||||
try CompileStep.doAtomicSymLinks(builder.allocator, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?);
|
|
||||||
|
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);
|
if (self.artifact.isDynamicLibrary() and
|
||||||
try builder.updateFile(self.artifact.getOutputLibSource().getPath(builder), full_implib_path);
|
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| {
|
if (self.pdb_dir) |pdb_dir| {
|
||||||
const full_pdb_path = builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename);
|
const full_src_path = self.artifact.getOutputPdbSource().getPath(src_builder);
|
||||||
try builder.updateFile(self.artifact.getOutputPdbSource().getPath(builder), full_pdb_path);
|
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| {
|
if (self.h_dir) |h_dir| {
|
||||||
const full_h_path = builder.getInstallPath(h_dir, self.artifact.out_h_filename);
|
const full_src_path = self.artifact.getOutputHSource().getPath(src_builder);
|
||||||
try builder.updateFile(self.artifact.getOutputHSource().getPath(builder), full_h_path);
|
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;
|
self.artifact.installed_path = full_dest_path;
|
||||||
|
step.result_cached = all_cached;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,14 +4,12 @@ const fs = std.fs;
|
|||||||
const Step = std.Build.Step;
|
const Step = std.Build.Step;
|
||||||
const InstallDir = std.Build.InstallDir;
|
const InstallDir = std.Build.InstallDir;
|
||||||
const InstallDirStep = @This();
|
const InstallDirStep = @This();
|
||||||
const log = std.log;
|
|
||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
builder: *std.Build,
|
|
||||||
options: Options,
|
options: Options,
|
||||||
/// This is used by the build system when a file being installed comes from one
|
/// This is used by the build system when a file being installed comes from one
|
||||||
/// package but is being installed by another.
|
/// package but is being installed by another.
|
||||||
override_source_builder: ?*std.Build = null,
|
dest_builder: *std.Build,
|
||||||
|
|
||||||
pub const base_id = .install_dir;
|
pub const base_id = .install_dir;
|
||||||
|
|
||||||
@ -40,31 +38,35 @@ pub const Options = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(owner: *std.Build, options: Options) InstallDirStep {
|
||||||
builder: *std.Build,
|
owner.pushInstalledFile(options.install_dir, options.install_subdir);
|
||||||
options: Options,
|
return .{
|
||||||
) InstallDirStep {
|
.step = Step.init(.{
|
||||||
builder.pushInstalledFile(options.install_dir, options.install_subdir);
|
.id = .install_dir,
|
||||||
return InstallDirStep{
|
.name = owner.fmt("install {s}/", .{options.source_dir}),
|
||||||
.builder = builder,
|
.owner = owner,
|
||||||
.step = Step.init(.install_dir, builder.fmt("install {s}/", .{options.source_dir}), builder.allocator, make),
|
.makeFn = make,
|
||||||
.options = options.dupe(builder),
|
}),
|
||||||
|
.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 self = @fieldParentPtr(InstallDirStep, "step", step);
|
||||||
const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
|
const dest_builder = self.dest_builder;
|
||||||
const src_builder = self.override_source_builder orelse self.builder;
|
const arena = dest_builder.allocator;
|
||||||
const full_src_dir = src_builder.pathFromRoot(self.options.source_dir);
|
const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
|
||||||
var src_dir = std.fs.cwd().openIterableDir(full_src_dir, .{}) catch |err| {
|
const src_builder = self.step.owner;
|
||||||
log.err("InstallDirStep: unable to open source directory '{s}': {s}", .{
|
var src_dir = src_builder.build_root.handle.openIterableDir(self.options.source_dir, .{}) catch |err| {
|
||||||
full_src_dir, @errorName(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();
|
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| {
|
next_entry: while (try it.next()) |entry| {
|
||||||
for (self.options.exclude_extensions) |ext| {
|
for (self.options.exclude_extensions) |ext| {
|
||||||
if (mem.endsWith(u8, entry.path, 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 });
|
// relative to src build root
|
||||||
const dest_path = self.builder.pathJoin(&.{ dest_prefix, entry.path });
|
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) {
|
switch (entry.kind) {
|
||||||
.Directory => try fs.cwd().makePath(dest_path),
|
.Directory => try cwd.makePath(dest_path),
|
||||||
.File => {
|
.File => {
|
||||||
for (self.options.blank_extensions) |ext| {
|
for (self.options.blank_extensions) |ext| {
|
||||||
if (mem.endsWith(u8, entry.path, ext)) {
|
if (mem.endsWith(u8, entry.path, ext)) {
|
||||||
try self.builder.truncateFile(dest_path);
|
try dest_builder.truncateFile(dest_path);
|
||||||
continue :next_entry;
|
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,
|
else => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
step.result_cached = all_cached;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,38 +3,55 @@ const Step = std.Build.Step;
|
|||||||
const FileSource = std.Build.FileSource;
|
const FileSource = std.Build.FileSource;
|
||||||
const InstallDir = std.Build.InstallDir;
|
const InstallDir = std.Build.InstallDir;
|
||||||
const InstallFileStep = @This();
|
const InstallFileStep = @This();
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
pub const base_id = .install_file;
|
pub const base_id = .install_file;
|
||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
builder: *std.Build,
|
|
||||||
source: FileSource,
|
source: FileSource,
|
||||||
dir: InstallDir,
|
dir: InstallDir,
|
||||||
dest_rel_path: []const u8,
|
dest_rel_path: []const u8,
|
||||||
/// This is used by the build system when a file being installed comes from one
|
/// This is used by the build system when a file being installed comes from one
|
||||||
/// package but is being installed by another.
|
/// package but is being installed by another.
|
||||||
override_source_builder: ?*std.Build = null,
|
dest_builder: *std.Build,
|
||||||
|
|
||||||
pub fn init(
|
pub fn create(
|
||||||
builder: *std.Build,
|
owner: *std.Build,
|
||||||
source: FileSource,
|
source: FileSource,
|
||||||
dir: InstallDir,
|
dir: InstallDir,
|
||||||
dest_rel_path: []const u8,
|
dest_rel_path: []const u8,
|
||||||
) InstallFileStep {
|
) *InstallFileStep {
|
||||||
builder.pushInstalledFile(dir, dest_rel_path);
|
assert(dest_rel_path.len != 0);
|
||||||
return InstallFileStep{
|
owner.pushInstalledFile(dir, dest_rel_path);
|
||||||
.builder = builder,
|
const self = owner.allocator.create(InstallFileStep) catch @panic("OOM");
|
||||||
.step = Step.init(.install_file, builder.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), builder.allocator, make),
|
self.* = .{
|
||||||
.source = source.dupe(builder),
|
.step = Step.init(.{
|
||||||
.dir = dir.dupe(builder),
|
.id = base_id,
|
||||||
.dest_rel_path = builder.dupePath(dest_rel_path),
|
.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 self = @fieldParentPtr(InstallFileStep, "step", step);
|
||||||
const src_builder = self.override_source_builder orelse self.builder;
|
const dest_builder = self.dest_builder;
|
||||||
const full_src_path = self.source.getPath(src_builder);
|
const full_src_path = self.source.getPath2(src_builder, step);
|
||||||
const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path);
|
const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path);
|
||||||
try self.builder.updateFile(full_src_path, full_dest_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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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});
|
|
||||||
}
|
|
||||||
@ -21,7 +21,6 @@ pub const RawFormat = enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
builder: *std.Build,
|
|
||||||
file_source: std.Build.FileSource,
|
file_source: std.Build.FileSource,
|
||||||
basename: []const u8,
|
basename: []const u8,
|
||||||
output_file: std.Build.GeneratedFile,
|
output_file: std.Build.GeneratedFile,
|
||||||
@ -38,19 +37,18 @@ pub const Options = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn create(
|
pub fn create(
|
||||||
builder: *std.Build,
|
owner: *std.Build,
|
||||||
file_source: std.Build.FileSource,
|
file_source: std.Build.FileSource,
|
||||||
options: Options,
|
options: Options,
|
||||||
) *ObjCopyStep {
|
) *ObjCopyStep {
|
||||||
const self = builder.allocator.create(ObjCopyStep) catch @panic("OOM");
|
const self = owner.allocator.create(ObjCopyStep) catch @panic("OOM");
|
||||||
self.* = ObjCopyStep{
|
self.* = ObjCopyStep{
|
||||||
.step = Step.init(
|
.step = Step.init(.{
|
||||||
base_id,
|
.id = base_id,
|
||||||
builder.fmt("objcopy {s}", .{file_source.getDisplayName()}),
|
.name = owner.fmt("objcopy {s}", .{file_source.getDisplayName()}),
|
||||||
builder.allocator,
|
.owner = owner,
|
||||||
make,
|
.makeFn = make,
|
||||||
),
|
}),
|
||||||
.builder = builder,
|
|
||||||
.file_source = file_source,
|
.file_source = file_source,
|
||||||
.basename = options.basename orelse file_source.getDisplayName(),
|
.basename = options.basename orelse file_source.getDisplayName(),
|
||||||
.output_file = std.Build.GeneratedFile{ .step = &self.step },
|
.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 };
|
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 self = @fieldParentPtr(ObjCopyStep, "step", step);
|
||||||
const b = self.builder;
|
|
||||||
|
|
||||||
var man = b.cache.obtain();
|
var man = b.cache.obtain();
|
||||||
defer man.deinit();
|
defer man.deinit();
|
||||||
@ -84,7 +82,7 @@ fn make(step: *Step) !void {
|
|||||||
man.hash.addOptional(self.pad_to);
|
man.hash.addOptional(self.pad_to);
|
||||||
man.hash.addOptional(self.format);
|
man.hash.addOptional(self.format);
|
||||||
|
|
||||||
if (man.hit() catch |err| failWithCacheError(man, err)) {
|
if (try step.cacheHit(&man)) {
|
||||||
// Cache hit, skip subprocess execution.
|
// Cache hit, skip subprocess execution.
|
||||||
const digest = man.final();
|
const digest = man.final();
|
||||||
self.output_file.path = try b.cache_root.join(b.allocator, &.{
|
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 full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename });
|
||||||
const cache_path = "o" ++ fs.path.sep_str ++ digest;
|
const cache_path = "o" ++ fs.path.sep_str ++ digest;
|
||||||
b.cache_root.handle.makePath(cache_path) catch |err| {
|
b.cache_root.handle.makePath(cache_path) catch |err| {
|
||||||
std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) });
|
return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });
|
||||||
return err;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var argv = std.ArrayList([]const u8).init(b.allocator);
|
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 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;
|
self.output_file.path = full_dest_path;
|
||||||
try man.writeManifest();
|
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);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -12,21 +12,24 @@ pub const base_id = .options;
|
|||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
generated_file: GeneratedFile,
|
generated_file: GeneratedFile,
|
||||||
builder: *std.Build,
|
|
||||||
|
|
||||||
contents: std.ArrayList(u8),
|
contents: std.ArrayList(u8),
|
||||||
artifact_args: std.ArrayList(OptionArtifactArg),
|
artifact_args: std.ArrayList(OptionArtifactArg),
|
||||||
file_source_args: std.ArrayList(OptionFileSourceArg),
|
file_source_args: std.ArrayList(OptionFileSourceArg),
|
||||||
|
|
||||||
pub fn create(builder: *std.Build) *OptionsStep {
|
pub fn create(owner: *std.Build) *OptionsStep {
|
||||||
const self = builder.allocator.create(OptionsStep) catch @panic("OOM");
|
const self = owner.allocator.create(OptionsStep) catch @panic("OOM");
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.builder = builder,
|
.step = Step.init(.{
|
||||||
.step = Step.init(.options, "options", builder.allocator, make),
|
.id = base_id,
|
||||||
|
.name = "options",
|
||||||
|
.owner = owner,
|
||||||
|
.makeFn = make,
|
||||||
|
}),
|
||||||
.generated_file = undefined,
|
.generated_file = undefined,
|
||||||
.contents = std.ArrayList(u8).init(builder.allocator),
|
.contents = std.ArrayList(u8).init(owner.allocator),
|
||||||
.artifact_args = std.ArrayList(OptionArtifactArg).init(builder.allocator),
|
.artifact_args = std.ArrayList(OptionArtifactArg).init(owner.allocator),
|
||||||
.file_source_args = std.ArrayList(OptionFileSourceArg).init(builder.allocator),
|
.file_source_args = std.ArrayList(OptionFileSourceArg).init(owner.allocator),
|
||||||
};
|
};
|
||||||
self.generated_file = .{ .step = &self.step };
|
self.generated_file = .{ .step = &self.step };
|
||||||
|
|
||||||
@ -192,7 +195,7 @@ pub fn addOptionFileSource(
|
|||||||
) void {
|
) void {
|
||||||
self.file_source_args.append(.{
|
self.file_source_args.append(.{
|
||||||
.name = name,
|
.name = name,
|
||||||
.source = source.dupe(self.builder),
|
.source = source.dupe(self.step.owner),
|
||||||
}) catch @panic("OOM");
|
}) catch @panic("OOM");
|
||||||
source.addStepDependencies(&self.step);
|
source.addStepDependencies(&self.step);
|
||||||
}
|
}
|
||||||
@ -200,12 +203,12 @@ pub fn addOptionFileSource(
|
|||||||
/// The value is the path in the cache dir.
|
/// The value is the path in the cache dir.
|
||||||
/// Adds a dependency automatically.
|
/// Adds a dependency automatically.
|
||||||
pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *CompileStep) void {
|
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);
|
self.step.dependOn(&artifact.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createModule(self: *OptionsStep) *std.Build.Module {
|
pub fn createModule(self: *OptionsStep) *std.Build.Module {
|
||||||
return self.builder.createModule(.{
|
return self.step.owner.createModule(.{
|
||||||
.source_file = self.getSource(),
|
.source_file = self.getSource(),
|
||||||
.dependencies = &.{},
|
.dependencies = &.{},
|
||||||
});
|
});
|
||||||
@ -215,14 +218,18 @@ pub fn getSource(self: *OptionsStep) FileSource {
|
|||||||
return .{ .generated = &self.generated_file };
|
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);
|
const self = @fieldParentPtr(OptionsStep, "step", step);
|
||||||
|
|
||||||
for (self.artifact_args.items) |item| {
|
for (self.artifact_args.items) |item| {
|
||||||
self.addOption(
|
self.addOption(
|
||||||
[]const u8,
|
[]const u8,
|
||||||
item.name,
|
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(
|
self.addOption(
|
||||||
[]const u8,
|
[]const u8,
|
||||||
item.name,
|
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();
|
defer options_dir.close();
|
||||||
|
|
||||||
const basename = self.hashContentsToFileName();
|
const basename = self.hashContentsToFileName();
|
||||||
|
|
||||||
try options_dir.writeFile(&basename, self.contents.items);
|
try options_dir.writeFile(&basename, self.contents.items);
|
||||||
|
|
||||||
self.generated_file.path = try self.builder.cache_root.join(self.builder.allocator, &.{
|
self.generated_file.path = try b.cache_root.join(b.allocator, &.{ "options", &basename });
|
||||||
"options", &basename,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hashContentsToFileName(self: *OptionsStep) [64]u8 {
|
fn hashContentsToFileName(self: *OptionsStep) [64]u8 {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
const std = @import("../std.zig");
|
const std = @import("../std.zig");
|
||||||
const log = std.log;
|
|
||||||
const fs = std.fs;
|
const fs = std.fs;
|
||||||
const Step = std.Build.Step;
|
const Step = std.Build.Step;
|
||||||
const RemoveDirStep = @This();
|
const RemoveDirStep = @This();
|
||||||
@ -7,23 +6,37 @@ const RemoveDirStep = @This();
|
|||||||
pub const base_id = .remove_dir;
|
pub const base_id = .remove_dir;
|
||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
builder: *std.Build,
|
|
||||||
dir_path: []const u8,
|
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{
|
return RemoveDirStep{
|
||||||
.builder = builder,
|
.step = Step.init(.{
|
||||||
.step = Step.init(.remove_dir, builder.fmt("RemoveDir {s}", .{dir_path}), builder.allocator, make),
|
.id = .remove_dir,
|
||||||
.dir_path = builder.dupePath(dir_path),
|
.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 self = @fieldParentPtr(RemoveDirStep, "step", step);
|
||||||
|
|
||||||
const full_path = self.builder.pathFromRoot(self.dir_path);
|
b.build_root.handle.deleteTree(self.dir_path) catch |err| {
|
||||||
fs.cwd().deleteTree(full_path) catch |err| {
|
if (b.build_root.path) |base| {
|
||||||
log.err("Unable to remove {s}: {s}", .{ full_path, @errorName(err) });
|
return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{
|
||||||
return err;
|
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
@ -1,9 +1,77 @@
|
|||||||
id: Id,
|
id: Id,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
makeFn: *const fn (self: *Step) anyerror!void,
|
owner: *Build,
|
||||||
|
makeFn: MakeFn,
|
||||||
|
|
||||||
dependencies: std.ArrayList(*Step),
|
dependencies: std.ArrayList(*Step),
|
||||||
loop_flag: bool,
|
/// This field is empty during execution of the user's build script, and
|
||||||
done_flag: bool,
|
/// 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 {
|
pub const Id = enum {
|
||||||
top_level,
|
top_level,
|
||||||
@ -17,7 +85,6 @@ pub const Id = enum {
|
|||||||
translate_c,
|
translate_c,
|
||||||
write_file,
|
write_file,
|
||||||
run,
|
run,
|
||||||
emulatable_run,
|
|
||||||
check_file,
|
check_file,
|
||||||
check_object,
|
check_object,
|
||||||
config_header,
|
config_header,
|
||||||
@ -38,7 +105,6 @@ pub const Id = enum {
|
|||||||
.translate_c => Build.TranslateCStep,
|
.translate_c => Build.TranslateCStep,
|
||||||
.write_file => Build.WriteFileStep,
|
.write_file => Build.WriteFileStep,
|
||||||
.run => Build.RunStep,
|
.run => Build.RunStep,
|
||||||
.emulatable_run => Build.EmulatableRunStep,
|
|
||||||
.check_file => Build.CheckFileStep,
|
.check_file => Build.CheckFileStep,
|
||||||
.check_object => Build.CheckObjectStep,
|
.check_object => Build.CheckObjectStep,
|
||||||
.config_header => Build.ConfigHeaderStep,
|
.config_header => Build.ConfigHeaderStep,
|
||||||
@ -49,39 +115,99 @@ pub const Id = enum {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(
|
pub const Options = struct {
|
||||||
id: Id,
|
id: Id,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
allocator: Allocator,
|
owner: *Build,
|
||||||
makeFn: *const fn (self: *Step) anyerror!void,
|
makeFn: MakeFn = makeNoOp,
|
||||||
) Step {
|
first_ret_addr: ?usize = null,
|
||||||
return Step{
|
max_rss: usize = 0,
|
||||||
.id = id,
|
};
|
||||||
.name = allocator.dupe(u8, name) catch @panic("OOM"),
|
|
||||||
.makeFn = makeFn,
|
pub fn init(options: Options) Step {
|
||||||
.dependencies = std.ArrayList(*Step).init(allocator),
|
const arena = options.owner.allocator;
|
||||||
.loop_flag = false,
|
|
||||||
.done_flag = false,
|
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 {
|
/// If the Step's `make` function reports `error.MakeFailed`, it indicates they
|
||||||
return init(id, name, allocator, makeNoOp);
|
/// 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 {
|
s.makeFn(s, prog_node) catch |err| switch (err) {
|
||||||
if (self.done_flag) return;
|
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);
|
if (!s.test_results.isSuccess()) {
|
||||||
self.done_flag = true;
|
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 {
|
pub fn dependOn(self: *Step, other: *Step) void {
|
||||||
self.dependencies.append(other) catch @panic("OOM");
|
self.dependencies.append(other) catch @panic("OOM");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn makeNoOp(self: *Step) anyerror!void {
|
pub fn getStackTrace(s: *Step) std.builtin.StackTrace {
|
||||||
_ = self;
|
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 {
|
pub fn cast(step: *Step, comptime T: type) ?*T {
|
||||||
@ -91,7 +217,323 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
|
|||||||
return null;
|
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 Step = @This();
|
||||||
const std = @import("../std.zig");
|
const std = @import("../std.zig");
|
||||||
const Build = std.Build;
|
const Build = std.Build;
|
||||||
const Allocator = std.mem.Allocator;
|
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)});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ const TranslateCStep = @This();
|
|||||||
pub const base_id = .translate_c;
|
pub const base_id = .translate_c;
|
||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
builder: *std.Build,
|
|
||||||
source: std.Build.FileSource,
|
source: std.Build.FileSource,
|
||||||
include_dirs: std.ArrayList([]const u8),
|
include_dirs: std.ArrayList([]const u8),
|
||||||
c_macros: std.ArrayList([]const u8),
|
c_macros: std.ArrayList([]const u8),
|
||||||
@ -26,15 +25,19 @@ pub const Options = struct {
|
|||||||
optimize: std.builtin.OptimizeMode,
|
optimize: std.builtin.OptimizeMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn create(builder: *std.Build, options: Options) *TranslateCStep {
|
pub fn create(owner: *std.Build, options: Options) *TranslateCStep {
|
||||||
const self = builder.allocator.create(TranslateCStep) catch @panic("OOM");
|
const self = owner.allocator.create(TranslateCStep) catch @panic("OOM");
|
||||||
const source = options.source_file.dupe(builder);
|
const source = options.source_file.dupe(owner);
|
||||||
self.* = TranslateCStep{
|
self.* = TranslateCStep{
|
||||||
.step = Step.init(.translate_c, "translate-c", builder.allocator, make),
|
.step = Step.init(.{
|
||||||
.builder = builder,
|
.id = .translate_c,
|
||||||
|
.name = "translate-c",
|
||||||
|
.owner = owner,
|
||||||
|
.makeFn = make,
|
||||||
|
}),
|
||||||
.source = source,
|
.source = source,
|
||||||
.include_dirs = std.ArrayList([]const u8).init(builder.allocator),
|
.include_dirs = std.ArrayList([]const u8).init(owner.allocator),
|
||||||
.c_macros = std.ArrayList([]const u8).init(builder.allocator),
|
.c_macros = std.ArrayList([]const u8).init(owner.allocator),
|
||||||
.out_basename = undefined,
|
.out_basename = undefined,
|
||||||
.target = options.target,
|
.target = options.target,
|
||||||
.optimize = options.optimize,
|
.optimize = options.optimize,
|
||||||
@ -54,7 +57,7 @@ pub const AddExecutableOptions = struct {
|
|||||||
|
|
||||||
/// Creates a step to build an executable from the translated source.
|
/// Creates a step to build an executable from the translated source.
|
||||||
pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep {
|
pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep {
|
||||||
return self.builder.addExecutable(.{
|
return self.step.owner.addExecutable(.{
|
||||||
.root_source_file = .{ .generated = &self.output_file },
|
.root_source_file = .{ .generated = &self.output_file },
|
||||||
.name = options.name orelse "translated_c",
|
.name = options.name orelse "translated_c",
|
||||||
.version = options.version,
|
.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 {
|
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 {
|
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.
|
/// If the value is omitted, it is set to 1.
|
||||||
/// `name` and `value` need not live longer than the function call.
|
/// `name` and `value` need not live longer than the function call.
|
||||||
pub fn defineCMacro(self: *TranslateCStep, name: []const u8, value: ?[]const u8) void {
|
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");
|
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.
|
/// 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 {
|
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);
|
const self = @fieldParentPtr(TranslateCStep, "step", step);
|
||||||
|
|
||||||
var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
|
var argv_list = std.ArrayList([]const u8).init(b.allocator);
|
||||||
try argv_list.append(self.builder.zig_exe);
|
try argv_list.append(b.zig_exe);
|
||||||
try argv_list.append("translate-c");
|
try argv_list.append("translate-c");
|
||||||
try argv_list.append("-lc");
|
try argv_list.append("-lc");
|
||||||
|
|
||||||
try argv_list.append("--enable-cache");
|
try argv_list.append("--enable-cache");
|
||||||
|
try argv_list.append("--listen=-");
|
||||||
|
|
||||||
if (!self.target.isNative()) {
|
if (!self.target.isNative()) {
|
||||||
try argv_list.append("-target");
|
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) {
|
switch (self.optimize) {
|
||||||
.Debug => {}, // Skip since it's the default.
|
.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| {
|
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(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 = try step.evalZigProcess(argv_list.items, prog_node);
|
||||||
const output_path = mem.trimRight(u8, output_path_nl, "\r\n");
|
|
||||||
|
|
||||||
self.out_basename = fs.path.basename(output_path);
|
self.out_basename = fs.path.basename(output_path);
|
||||||
const output_dir = fs.path.dirname(output_path).?;
|
const output_dir = fs.path.dirname(output_path).?;
|
||||||
|
|
||||||
self.output_file.path = try fs.path.join(
|
self.output_file.path = try fs.path.join(
|
||||||
self.builder.allocator,
|
b.allocator,
|
||||||
&[_][]const u8{ output_dir, self.out_basename },
|
&[_][]const u8{ output_dir, self.out_basename },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,11 +10,11 @@
|
|||||||
//! control.
|
//! control.
|
||||||
|
|
||||||
step: Step,
|
step: Step,
|
||||||
builder: *std.Build,
|
|
||||||
/// The elements here are pointers because we need stable pointers for the
|
/// The elements here are pointers because we need stable pointers for the
|
||||||
/// GeneratedFile field.
|
/// GeneratedFile field.
|
||||||
files: std.ArrayListUnmanaged(*File),
|
files: std.ArrayListUnmanaged(*File),
|
||||||
output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
|
output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
|
||||||
|
generated_directory: std.Build.GeneratedFile,
|
||||||
|
|
||||||
pub const base_id = .write_file;
|
pub const base_id = .write_file;
|
||||||
|
|
||||||
@ -34,24 +34,34 @@ pub const Contents = union(enum) {
|
|||||||
copy: std.Build.FileSource,
|
copy: std.Build.FileSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(builder: *std.Build) WriteFileStep {
|
pub fn create(owner: *std.Build) *WriteFileStep {
|
||||||
return .{
|
const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM");
|
||||||
.builder = builder,
|
wf.* = .{
|
||||||
.step = Step.init(.write_file, "writefile", builder.allocator, make),
|
.step = Step.init(.{
|
||||||
|
.id = .write_file,
|
||||||
|
.name = "WriteFile",
|
||||||
|
.owner = owner,
|
||||||
|
.makeFn = make,
|
||||||
|
}),
|
||||||
.files = .{},
|
.files = .{},
|
||||||
.output_source_files = .{},
|
.output_source_files = .{},
|
||||||
|
.generated_directory = .{ .step = &wf.step },
|
||||||
};
|
};
|
||||||
|
return wf;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {
|
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");
|
const file = gpa.create(File) catch @panic("OOM");
|
||||||
file.* = .{
|
file.* = .{
|
||||||
.generated_file = .{ .step = &wf.step },
|
.generated_file = .{ .step = &wf.step },
|
||||||
.sub_path = wf.builder.dupePath(sub_path),
|
.sub_path = b.dupePath(sub_path),
|
||||||
.contents = .{ .bytes = wf.builder.dupe(bytes) },
|
.contents = .{ .bytes = b.dupe(bytes) },
|
||||||
};
|
};
|
||||||
wf.files.append(gpa, file) catch @panic("OOM");
|
wf.files.append(gpa, file) catch @panic("OOM");
|
||||||
|
|
||||||
|
wf.maybeUpdateName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Place the file into the generated directory within the local cache,
|
/// 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.
|
/// required sub-path exists.
|
||||||
/// This is the option expected to be used most commonly with `addCopyFile`.
|
/// 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 {
|
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");
|
const file = gpa.create(File) catch @panic("OOM");
|
||||||
file.* = .{
|
file.* = .{
|
||||||
.generated_file = .{ .step = &wf.step },
|
.generated_file = .{ .step = &wf.step },
|
||||||
.sub_path = wf.builder.dupePath(sub_path),
|
.sub_path = b.dupePath(sub_path),
|
||||||
.contents = .{ .copy = source },
|
.contents = .{ .copy = source },
|
||||||
};
|
};
|
||||||
wf.files.append(gpa, file) catch @panic("OOM");
|
wf.files.append(gpa, file) catch @panic("OOM");
|
||||||
|
|
||||||
|
wf.maybeUpdateName();
|
||||||
|
source.addStepDependencies(&wf.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A path relative to the package root.
|
/// 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.
|
/// those changes to version control.
|
||||||
/// A file added this way is not available with `getFileSource`.
|
/// A file added this way is not available with `getFileSource`.
|
||||||
pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void {
|
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 },
|
.contents = .{ .copy = source },
|
||||||
.sub_path = sub_path,
|
.sub_path = sub_path,
|
||||||
}) catch @panic("OOM");
|
}) 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`.
|
/// 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;
|
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);
|
const wf = @fieldParentPtr(WriteFileStep, "step", step);
|
||||||
|
|
||||||
// Writing to source files is kind of an extra capability of this
|
// Writing to source files is kind of an extra capability of this
|
||||||
// WriteFileStep - arguably it should be a different step. But anyway here
|
// WriteFileStep - arguably it should be a different step. But anyway here
|
||||||
// it is, it happens unconditionally and does not interact with the other
|
// it is, it happens unconditionally and does not interact with the other
|
||||||
// files here.
|
// files here.
|
||||||
|
var any_miss = false;
|
||||||
for (wf.output_source_files.items) |output_source_file| {
|
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| {
|
if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
|
||||||
var dir = try wf.builder.build_root.handle.makeOpenPath(dirname, .{});
|
b.build_root.handle.makePath(dirname) catch |err| {
|
||||||
defer dir.close();
|
return step.fail("unable to make path '{}{s}': {s}", .{
|
||||||
try writeFile(wf, dir, output_source_file.contents, basename);
|
b.build_root, dirname, @errorName(err),
|
||||||
} else {
|
});
|
||||||
try writeFile(wf, wf.builder.build_root.handle, output_source_file.contents, basename);
|
};
|
||||||
|
}
|
||||||
|
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
|
// 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.
|
// 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();
|
defer man.deinit();
|
||||||
|
|
||||||
// Random bytes to make WriteFileStep unique. Refresh this with
|
// Random bytes to make WriteFileStep unique. Refresh this with
|
||||||
@ -135,76 +207,82 @@ fn make(step: *Step) !void {
|
|||||||
man.hash.addBytes(bytes);
|
man.hash.addBytes(bytes);
|
||||||
},
|
},
|
||||||
.copy => |file_source| {
|
.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)) {
|
if (try step.cacheHit(&man)) {
|
||||||
// Cache hit, skip writing file data.
|
|
||||||
const digest = man.final();
|
const digest = man.final();
|
||||||
for (wf.files.items) |file| {
|
for (wf.files.items) |file| {
|
||||||
file.generated_file.path = try wf.builder.cache_root.join(
|
file.generated_file.path = try b.cache_root.join(b.allocator, &.{
|
||||||
wf.builder.allocator,
|
"o", &digest, file.sub_path,
|
||||||
&.{ "o", &digest, file.sub_path },
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const digest = man.final();
|
const digest = man.final();
|
||||||
const cache_path = "o" ++ fs.path.sep_str ++ digest;
|
const cache_path = "o" ++ fs.path.sep_str ++ digest;
|
||||||
|
|
||||||
var cache_dir = wf.builder.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {
|
wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
|
||||||
std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) });
|
|
||||||
return err;
|
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();
|
defer cache_dir.close();
|
||||||
|
|
||||||
for (wf.files.items) |file| {
|
for (wf.files.items) |file| {
|
||||||
const basename = fs.path.basename(file.sub_path);
|
|
||||||
if (fs.path.dirname(file.sub_path)) |dirname| {
|
if (fs.path.dirname(file.sub_path)) |dirname| {
|
||||||
var dir = try wf.builder.cache_root.handle.makeOpenPath(dirname, .{});
|
cache_dir.makePath(dirname) catch |err| {
|
||||||
defer dir.close();
|
return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{
|
||||||
try writeFile(wf, dir, file.contents, basename);
|
b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err),
|
||||||
} else {
|
});
|
||||||
try writeFile(wf, cache_dir, file.contents, basename);
|
};
|
||||||
|
}
|
||||||
|
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(
|
file.generated_file.path = try b.cache_root.join(b.allocator, &.{
|
||||||
wf.builder.allocator,
|
cache_path, file.sub_path,
|
||||||
&.{ cache_path, file.sub_path },
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try man.writeManifest();
|
try step.writeManifest(&man);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std = @import("../std.zig");
|
const std = @import("../std.zig");
|
||||||
|
|||||||
@ -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.
|
/// Thread-safe. 0 means unknown.
|
||||||
pub fn setEstimatedTotalItems(self: *Node, count: usize) void {
|
pub fn setEstimatedTotalItems(self: *Node, count: usize) void {
|
||||||
@atomicStore(usize, &self.unprotected_estimated_total_items, count, .Monotonic);
|
@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.timer) |*timer| {
|
||||||
if (!self.update_mutex.tryLock()) return;
|
if (!self.update_mutex.tryLock()) return;
|
||||||
defer self.update_mutex.unlock();
|
defer self.update_mutex.unlock();
|
||||||
const now = timer.read();
|
maybeRefreshWithHeldLock(self, timer);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe.
|
||||||
pub fn refresh(self: *Progress) void {
|
pub fn refresh(self: *Progress) void {
|
||||||
if (!self.update_mutex.tryLock()) return;
|
if (!self.update_mutex.tryLock()) return;
|
||||||
@ -192,32 +211,28 @@ pub fn refresh(self: *Progress) void {
|
|||||||
return self.refreshWithHeldLock();
|
return self.refreshWithHeldLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refreshWithHeldLock(self: *Progress) void {
|
fn clearWithHeldLock(p: *Progress, end_ptr: *usize) void {
|
||||||
const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal;
|
const file = p.terminal orelse return;
|
||||||
if (is_dumb and self.dont_print_on_dumb) return;
|
var end = end_ptr.*;
|
||||||
|
if (p.columns_written > 0) {
|
||||||
const file = self.terminal orelse return;
|
|
||||||
|
|
||||||
var end: usize = 0;
|
|
||||||
if (self.columns_written > 0) {
|
|
||||||
// restore the cursor position by moving the cursor
|
// restore the cursor position by moving the cursor
|
||||||
// `columns_written` cells to the left, then clear the rest of the
|
// `columns_written` cells to the left, then clear the rest of the
|
||||||
// line
|
// line
|
||||||
if (self.supports_ansi_escape_codes) {
|
if (p.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(p.output_buffer[end..], "\x1b[{d}D", .{p.columns_written}) catch unreachable).len;
|
||||||
end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
|
end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
|
||||||
} else if (builtin.os.tag == .windows) winapi: {
|
} 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;
|
var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
||||||
if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {
|
if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {
|
||||||
// stop trying to write to this file
|
// stop trying to write to this file
|
||||||
self.terminal = null;
|
p.terminal = null;
|
||||||
break :winapi;
|
break :winapi;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cursor_pos = windows.COORD{
|
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,
|
.Y = info.dwCursorPosition.Y,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -235,7 +250,7 @@ fn refreshWithHeldLock(self: *Progress) void {
|
|||||||
&written,
|
&written,
|
||||||
) != windows.TRUE) {
|
) != windows.TRUE) {
|
||||||
// stop trying to write to this file
|
// stop trying to write to this file
|
||||||
self.terminal = null;
|
p.terminal = null;
|
||||||
break :winapi;
|
break :winapi;
|
||||||
}
|
}
|
||||||
if (windows.kernel32.FillConsoleOutputCharacterW(
|
if (windows.kernel32.FillConsoleOutputCharacterW(
|
||||||
@ -246,22 +261,33 @@ fn refreshWithHeldLock(self: *Progress) void {
|
|||||||
&written,
|
&written,
|
||||||
) != windows.TRUE) {
|
) != windows.TRUE) {
|
||||||
// stop trying to write to this file
|
// stop trying to write to this file
|
||||||
self.terminal = null;
|
p.terminal = null;
|
||||||
break :winapi;
|
break :winapi;
|
||||||
}
|
}
|
||||||
if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) {
|
if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) {
|
||||||
// stop trying to write to this file
|
// stop trying to write to this file
|
||||||
self.terminal = null;
|
p.terminal = null;
|
||||||
break :winapi;
|
break :winapi;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we are in a "dumb" terminal like in acme or writing to a file
|
// 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;
|
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) {
|
if (!self.done) {
|
||||||
var need_ellipse = false;
|
var need_ellipse = false;
|
||||||
@ -318,6 +344,26 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
|
|||||||
self.columns_written = 0;
|
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 {
|
fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
|
||||||
if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
|
if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
|
||||||
const amt = written.len;
|
const amt = written.len;
|
||||||
|
|||||||
@ -16,6 +16,8 @@ pub const Mutex = @import("Thread/Mutex.zig");
|
|||||||
pub const Semaphore = @import("Thread/Semaphore.zig");
|
pub const Semaphore = @import("Thread/Semaphore.zig");
|
||||||
pub const Condition = @import("Thread/Condition.zig");
|
pub const Condition = @import("Thread/Condition.zig");
|
||||||
pub const RwLock = @import("Thread/RwLock.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;
|
pub const use_pthreads = target.os.tag != .windows and target.os.tag != .wasi and builtin.link_libc;
|
||||||
const is_gnu = target.abi.isGnu();
|
const is_gnu = target.abi.isGnu();
|
||||||
@ -945,6 +947,7 @@ const LinuxThreadImpl = struct {
|
|||||||
|
|
||||||
// map all memory needed without read/write permissions
|
// map all memory needed without read/write permissions
|
||||||
// to avoid committing the whole region right away
|
// to avoid committing the whole region right away
|
||||||
|
// anonymous mapping ensures file descriptor limits are not exceeded
|
||||||
const mapped = os.mmap(
|
const mapped = os.mmap(
|
||||||
null,
|
null,
|
||||||
map_bytes,
|
map_bytes,
|
||||||
@ -956,6 +959,8 @@ const LinuxThreadImpl = struct {
|
|||||||
error.MemoryMappingNotSupported => unreachable,
|
error.MemoryMappingNotSupported => unreachable,
|
||||||
error.AccessDenied => unreachable,
|
error.AccessDenied => unreachable,
|
||||||
error.PermissionDenied => unreachable,
|
error.PermissionDenied => unreachable,
|
||||||
|
error.ProcessFdQuotaExceeded => unreachable,
|
||||||
|
error.SystemFdQuotaExceeded => unreachable,
|
||||||
else => |e| return e,
|
else => |e| return e,
|
||||||
};
|
};
|
||||||
assert(mapped.len >= map_bytes);
|
assert(mapped.len >= map_bytes);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const ThreadPool = @This();
|
const Pool = @This();
|
||||||
const WaitGroup = @import("WaitGroup.zig");
|
const WaitGroup = @import("WaitGroup.zig");
|
||||||
|
|
||||||
mutex: std.Thread.Mutex = .{},
|
mutex: std.Thread.Mutex = .{},
|
||||||
@ -17,7 +17,14 @@ const Runnable = struct {
|
|||||||
|
|
||||||
const RunProto = *const fn (*Runnable) void;
|
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.* = .{
|
pool.* = .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.threads = &[_]std.Thread{},
|
.threads = &[_]std.Thread{},
|
||||||
@ -27,7 +34,7 @@ pub fn init(pool: *ThreadPool, allocator: std.mem.Allocator) !void {
|
|||||||
return;
|
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);
|
pool.threads = try allocator.alloc(std.Thread, thread_count);
|
||||||
errdefer allocator.free(pool.threads);
|
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.join(pool.threads.len); // kill and join all threads.
|
||||||
pool.* = undefined;
|
pool.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join(pool: *ThreadPool, spawned: usize) void {
|
fn join(pool: *Pool, spawned: usize) void {
|
||||||
if (builtin.single_threaded) {
|
if (builtin.single_threaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -69,7 +76,7 @@ fn join(pool: *ThreadPool, spawned: usize) void {
|
|||||||
pool.allocator.free(pool.threads);
|
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) {
|
if (builtin.single_threaded) {
|
||||||
@call(.auto, func, args);
|
@call(.auto, func, args);
|
||||||
return;
|
return;
|
||||||
@ -78,7 +85,7 @@ pub fn spawn(pool: *ThreadPool, comptime func: anytype, args: anytype) !void {
|
|||||||
const Args = @TypeOf(args);
|
const Args = @TypeOf(args);
|
||||||
const Closure = struct {
|
const Closure = struct {
|
||||||
arguments: Args,
|
arguments: Args,
|
||||||
pool: *ThreadPool,
|
pool: *Pool,
|
||||||
run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
|
run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
|
||||||
|
|
||||||
fn runFn(runnable: *Runnable) void {
|
fn runFn(runnable: *Runnable) void {
|
||||||
@ -112,7 +119,7 @@ pub fn spawn(pool: *ThreadPool, comptime func: anytype, args: anytype) !void {
|
|||||||
pool.cond.signal();
|
pool.cond.signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn worker(pool: *ThreadPool) void {
|
fn worker(pool: *Pool) void {
|
||||||
pool.mutex.lock();
|
pool.mutex.lock();
|
||||||
defer pool.mutex.unlock();
|
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()) {
|
while (!wait_group.isDone()) {
|
||||||
if (blk: {
|
if (blk: {
|
||||||
pool.mutex.lock();
|
pool.mutex.lock();
|
||||||
109
lib/std/Uri.zig
109
lib/std/Uri.zig
@ -16,15 +16,27 @@ fragment: ?[]const u8,
|
|||||||
|
|
||||||
/// Applies URI encoding and replaces all reserved characters with their respective %XX code.
|
/// 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 {
|
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;
|
var outsize: usize = 0;
|
||||||
for (input) |c| {
|
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 output = try allocator.alloc(u8, outsize);
|
||||||
var outptr: usize = 0;
|
var outptr: usize = 0;
|
||||||
|
|
||||||
for (input) |c| {
|
for (input) |c| {
|
||||||
if (isUnreserved(c)) {
|
if (keepUnescaped(c)) {
|
||||||
output[outptr] = c;
|
output[outptr] = c;
|
||||||
outptr += 1;
|
outptr += 1;
|
||||||
} else {
|
} else {
|
||||||
@ -94,13 +106,14 @@ pub fn unescapeString(allocator: std.mem.Allocator, input: []const u8) error{Out
|
|||||||
|
|
||||||
pub const ParseError = error{ UnexpectedCharacter, InvalidFormat, InvalidPort };
|
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
|
/// The return value will contain unescaped strings pointing into the
|
||||||
/// original `text`. Each component that is provided, will be non-`null`.
|
/// 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 reader = SliceReader{ .slice = text };
|
||||||
var uri = Uri{
|
var uri = Uri{
|
||||||
.scheme = reader.readWhile(isSchemeChar),
|
.scheme = "",
|
||||||
.user = null,
|
.user = null,
|
||||||
.password = null,
|
.password = null,
|
||||||
.host = null,
|
.host = null,
|
||||||
@ -110,14 +123,6 @@ pub fn parse(text: []const u8) ParseError!Uri {
|
|||||||
.fragment = null,
|
.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
|
if (reader.peekPrefix("//")) { // authority part
|
||||||
std.debug.assert(reader.get().? == '/');
|
std.debug.assert(reader.get().? == '/');
|
||||||
std.debug.assert(reader.get().? == '/');
|
std.debug.assert(reader.get().? == '/');
|
||||||
@ -179,6 +184,76 @@ pub fn parse(text: []const u8) ParseError!Uri {
|
|||||||
return 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 SliceReader = struct {
|
||||||
const Self = @This();
|
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 {
|
fn isQuerySeparator(c: u8) bool {
|
||||||
return switch (c) {
|
return switch (c) {
|
||||||
'#' => true,
|
'#' => true,
|
||||||
|
|||||||
@ -141,11 +141,21 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
|
|||||||
return cloned;
|
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).
|
/// This operation is O(N).
|
||||||
/// Invalidates pointers if additional memory is needed.
|
/// Invalidates pointers if additional memory is needed.
|
||||||
pub fn insert(self: *Self, n: usize, item: T) Allocator.Error!void {
|
pub fn insert(self: *Self, n: usize, item: T) Allocator.Error!void {
|
||||||
try self.ensureUnusedCapacity(1);
|
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;
|
self.items.len += 1;
|
||||||
|
|
||||||
mem.copyBackwards(T, self.items[n + 1 .. self.items.len], self.items[n .. 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;
|
return cloned;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert `item` at index `n`. Moves `list[n .. list.len]`
|
/// Insert `item` at index `n`. Moves `list[n .. list.len]` to higher indices to make room.
|
||||||
/// 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).
|
/// This operation is O(N).
|
||||||
/// Invalidates pointers if additional memory is needed.
|
/// Invalidates pointers if additional memory is needed.
|
||||||
pub fn insert(self: *Self, allocator: Allocator, n: usize, item: T) Allocator.Error!void {
|
pub fn insert(self: *Self, allocator: Allocator, n: usize, item: T) Allocator.Error!void {
|
||||||
try self.ensureUnusedCapacity(allocator, 1);
|
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;
|
self.items.len += 1;
|
||||||
|
|
||||||
mem.copyBackwards(T, self.items[n + 1 .. self.items.len], self.items[n .. 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);
|
var list = ArrayList(i32).init(a);
|
||||||
defer list.deinit();
|
defer list.deinit();
|
||||||
|
|
||||||
try list.append(1);
|
try list.insert(0, 1);
|
||||||
try list.append(2);
|
try list.append(2);
|
||||||
try list.append(3);
|
try list.insert(2, 3);
|
||||||
try list.insert(0, 5);
|
try list.insert(0, 5);
|
||||||
try testing.expect(list.items[0] == 5);
|
try testing.expect(list.items[0] == 5);
|
||||||
try testing.expect(list.items[1] == 1);
|
try testing.expect(list.items[1] == 1);
|
||||||
@ -1322,9 +1341,9 @@ test "std.ArrayList/ArrayListUnmanaged.insert" {
|
|||||||
var list = ArrayListUnmanaged(i32){};
|
var list = ArrayListUnmanaged(i32){};
|
||||||
defer list.deinit(a);
|
defer list.deinit(a);
|
||||||
|
|
||||||
try list.append(a, 1);
|
try list.insert(a, 0, 1);
|
||||||
try list.append(a, 2);
|
try list.append(a, 2);
|
||||||
try list.append(a, 3);
|
try list.insert(a, 2, 3);
|
||||||
try list.insert(a, 0, 5);
|
try list.insert(a, 0, 5);
|
||||||
try testing.expect(list.items[0] == 5);
|
try testing.expect(list.items[0] == 5);
|
||||||
try testing.expect(list.items[1] == 1);
|
try testing.expect(list.items[1] == 1);
|
||||||
|
|||||||
@ -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 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 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 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 fork() c_int;
|
||||||
pub extern "c" fn access(path: [*:0]const u8, mode: c_uint) 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;
|
pub extern "c" fn faccessat(dirfd: c.fd_t, path: [*:0]const u8, mode: c_uint, flags: c_uint) c_int;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ const iovec_const = std.os.iovec_const;
|
|||||||
|
|
||||||
pub const aarch64 = @import("darwin/aarch64.zig");
|
pub const aarch64 = @import("darwin/aarch64.zig");
|
||||||
pub const x86_64 = @import("darwin/x86_64.zig");
|
pub const x86_64 = @import("darwin/x86_64.zig");
|
||||||
|
pub const cssm = @import("darwin/cssm.zig");
|
||||||
|
|
||||||
const arch_bits = switch (native_arch) {
|
const arch_bits = switch (native_arch) {
|
||||||
.aarch64 => @import("darwin/aarch64.zig"),
|
.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)));
|
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
|
/// Kernel return values
|
||||||
pub const KernE = enum(u32) {
|
pub const KernE = enum(u32) {
|
||||||
SUCCESS = 0,
|
SUCCESS = 0,
|
||||||
@ -3085,3 +3094,471 @@ pub const PT_DENY_ATTACH = 31;
|
|||||||
pub const caddr_t = ?[*]u8;
|
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 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() };
|
||||||
|
}
|
||||||
|
|||||||
@ -17,10 +17,12 @@ const Os = std.builtin.Os;
|
|||||||
const TailQueue = std.TailQueue;
|
const TailQueue = std.TailQueue;
|
||||||
const maxInt = std.math.maxInt;
|
const maxInt = std.math.maxInt;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const is_darwin = builtin.target.isDarwin();
|
||||||
|
|
||||||
pub const ChildProcess = struct {
|
pub const ChildProcess = struct {
|
||||||
pub const Id = switch (builtin.os.tag) {
|
pub const Id = switch (builtin.os.tag) {
|
||||||
.windows => windows.HANDLE,
|
.windows => windows.HANDLE,
|
||||||
|
.wasi => void,
|
||||||
else => os.pid_t,
|
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.
|
/// Darwin-only. Start child process in suspended state as if SIGSTOP was sent.
|
||||||
start_suspended: bool = false,
|
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 Arg0Expand = os.Arg0Expand;
|
||||||
|
|
||||||
pub const SpawnError = error{
|
pub const SpawnError = error{
|
||||||
@ -90,8 +129,7 @@ pub const ChildProcess = struct {
|
|||||||
os.SetIdError ||
|
os.SetIdError ||
|
||||||
os.ChangeCurDirError ||
|
os.ChangeCurDirError ||
|
||||||
windows.CreateProcessError ||
|
windows.CreateProcessError ||
|
||||||
windows.WaitForSingleObjectError ||
|
windows.WaitForSingleObjectError;
|
||||||
os.posix_spawn.Error;
|
|
||||||
|
|
||||||
pub const Term = union(enum) {
|
pub const Term = union(enum) {
|
||||||
Exited: u8,
|
Exited: u8,
|
||||||
@ -143,10 +181,6 @@ pub const ChildProcess = struct {
|
|||||||
@compileError("the target operating system cannot spawn processes");
|
@compileError("the target operating system cannot spawn processes");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comptime builtin.target.isDarwin()) {
|
|
||||||
return self.spawnMacos();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (builtin.os.tag == .windows) {
|
if (builtin.os.tag == .windows) {
|
||||||
return self.spawnWindows();
|
return self.spawnWindows();
|
||||||
} else {
|
} else {
|
||||||
@ -337,10 +371,16 @@ pub const ChildProcess = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn waitUnwrapped(self: *ChildProcess) !void {
|
fn waitUnwrapped(self: *ChildProcess) !void {
|
||||||
const res: os.WaitPidResult = if (comptime builtin.target.isDarwin())
|
const res: os.WaitPidResult = res: {
|
||||||
try os.posix_spawn.waitpid(self.id, 0)
|
if (builtin.os.tag == .linux and self.request_resource_usage_statistics) {
|
||||||
else
|
var ru: std.os.rusage = undefined;
|
||||||
os.waitpid(self.id, 0);
|
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;
|
const status = res.status;
|
||||||
self.cleanupStreams();
|
self.cleanupStreams();
|
||||||
self.handleWaitResult(status);
|
self.handleWaitResult(status);
|
||||||
@ -416,121 +456,6 @@ pub const ChildProcess = struct {
|
|||||||
Term{ .Unknown = status };
|
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 {
|
fn spawnPosix(self: *ChildProcess) SpawnError!void {
|
||||||
const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0;
|
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;
|
const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
const root = @import("root");
|
||||||
|
|
||||||
/// Authenticated Encryption with Associated Data
|
/// Authenticated Encryption with Associated Data
|
||||||
pub const aead = struct {
|
pub const aead = struct {
|
||||||
pub const aegis = struct {
|
pub const aegis = struct {
|
||||||
@ -66,6 +68,11 @@ pub const dh = struct {
|
|||||||
pub const X25519 = @import("crypto/25519/x25519.zig").X25519;
|
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.
|
/// Elliptic-curve arithmetic.
|
||||||
pub const ecc = struct {
|
pub const ecc = struct {
|
||||||
pub const Curve25519 = @import("crypto/25519/curve25519.zig").Curve25519;
|
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 tls = @import("crypto/tls.zig");
|
||||||
pub const Certificate = @import("crypto/Certificate.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 {
|
test {
|
||||||
_ = aead.aegis.Aegis128L;
|
_ = aead.aegis.Aegis128L;
|
||||||
_ = aead.aegis.Aegis256;
|
_ = aead.aegis.Aegis256;
|
||||||
@ -217,6 +245,8 @@ test {
|
|||||||
|
|
||||||
_ = dh.X25519;
|
_ = dh.X25519;
|
||||||
|
|
||||||
|
_ = kem.kyber_d00;
|
||||||
|
|
||||||
_ = ecc.Curve25519;
|
_ = ecc.Curve25519;
|
||||||
_ = ecc.Edwards25519;
|
_ = ecc.Edwards25519;
|
||||||
_ = ecc.P256;
|
_ = ecc.P256;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
const crypto = std.crypto;
|
const crypto = std.crypto;
|
||||||
const readIntLittle = std.mem.readIntLittle;
|
const readIntLittle = std.mem.readIntLittle;
|
||||||
const writeIntLittle = std.mem.writeIntLittle;
|
const writeIntLittle = std.mem.writeIntLittle;
|
||||||
@ -6,6 +7,12 @@ const writeIntLittle = std.mem.writeIntLittle;
|
|||||||
const NonCanonicalError = crypto.errors.NonCanonicalError;
|
const NonCanonicalError = crypto.errors.NonCanonicalError;
|
||||||
const NotSquareError = crypto.errors.NotSquareError;
|
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 {
|
pub const Fe = struct {
|
||||||
limbs: [5]u64,
|
limbs: [5]u64,
|
||||||
|
|
||||||
@ -264,7 +271,7 @@ pub const Fe = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Multiply two field elements
|
/// 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 ax: [5]u128 = undefined;
|
||||||
var bx: [5]u128 = undefined;
|
var bx: [5]u128 = undefined;
|
||||||
var a19: [5]u128 = undefined;
|
var a19: [5]u128 = undefined;
|
||||||
|
|||||||
@ -32,62 +32,54 @@ pub const Block = struct {
|
|||||||
/// Encrypt a block with a round key.
|
/// Encrypt a block with a round key.
|
||||||
pub inline fn encrypt(block: Block, round_key: Block) Block {
|
pub inline fn encrypt(block: Block, round_key: Block) Block {
|
||||||
return Block{
|
return Block{
|
||||||
.repr = asm (
|
.repr = (asm (
|
||||||
\\ mov %[out].16b, %[in].16b
|
\\ mov %[out].16b, %[in].16b
|
||||||
\\ aese %[out].16b, %[zero].16b
|
\\ aese %[out].16b, %[zero].16b
|
||||||
\\ aesmc %[out].16b, %[out].16b
|
\\ aesmc %[out].16b, %[out].16b
|
||||||
\\ eor %[out].16b, %[out].16b, %[rk].16b
|
|
||||||
: [out] "=&x" (-> BlockVec),
|
: [out] "=&x" (-> BlockVec),
|
||||||
: [in] "x" (block.repr),
|
: [in] "x" (block.repr),
|
||||||
[rk] "x" (round_key.repr),
|
|
||||||
[zero] "x" (zero),
|
[zero] "x" (zero),
|
||||||
),
|
)) ^ round_key.repr,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypt a block with the last round key.
|
/// Encrypt a block with the last round key.
|
||||||
pub inline fn encryptLast(block: Block, round_key: Block) Block {
|
pub inline fn encryptLast(block: Block, round_key: Block) Block {
|
||||||
return Block{
|
return Block{
|
||||||
.repr = asm (
|
.repr = (asm (
|
||||||
\\ mov %[out].16b, %[in].16b
|
\\ mov %[out].16b, %[in].16b
|
||||||
\\ aese %[out].16b, %[zero].16b
|
\\ aese %[out].16b, %[zero].16b
|
||||||
\\ eor %[out].16b, %[out].16b, %[rk].16b
|
|
||||||
: [out] "=&x" (-> BlockVec),
|
: [out] "=&x" (-> BlockVec),
|
||||||
: [in] "x" (block.repr),
|
: [in] "x" (block.repr),
|
||||||
[rk] "x" (round_key.repr),
|
|
||||||
[zero] "x" (zero),
|
[zero] "x" (zero),
|
||||||
),
|
)) ^ round_key.repr,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypt a block with a round key.
|
/// Decrypt a block with a round key.
|
||||||
pub inline fn decrypt(block: Block, inv_round_key: Block) Block {
|
pub inline fn decrypt(block: Block, inv_round_key: Block) Block {
|
||||||
return Block{
|
return Block{
|
||||||
.repr = asm (
|
.repr = (asm (
|
||||||
\\ mov %[out].16b, %[in].16b
|
\\ mov %[out].16b, %[in].16b
|
||||||
\\ aesd %[out].16b, %[zero].16b
|
\\ aesd %[out].16b, %[zero].16b
|
||||||
\\ aesimc %[out].16b, %[out].16b
|
\\ aesimc %[out].16b, %[out].16b
|
||||||
\\ eor %[out].16b, %[out].16b, %[rk].16b
|
|
||||||
: [out] "=&x" (-> BlockVec),
|
: [out] "=&x" (-> BlockVec),
|
||||||
: [in] "x" (block.repr),
|
: [in] "x" (block.repr),
|
||||||
[rk] "x" (inv_round_key.repr),
|
|
||||||
[zero] "x" (zero),
|
[zero] "x" (zero),
|
||||||
),
|
)) ^ inv_round_key.repr,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypt a block with the last round key.
|
/// Decrypt a block with the last round key.
|
||||||
pub inline fn decryptLast(block: Block, inv_round_key: Block) Block {
|
pub inline fn decryptLast(block: Block, inv_round_key: Block) Block {
|
||||||
return Block{
|
return Block{
|
||||||
.repr = asm (
|
.repr = (asm (
|
||||||
\\ mov %[out].16b, %[in].16b
|
\\ mov %[out].16b, %[in].16b
|
||||||
\\ aesd %[out].16b, %[zero].16b
|
\\ aesd %[out].16b, %[zero].16b
|
||||||
\\ eor %[out].16b, %[out].16b, %[rk].16b
|
|
||||||
: [out] "=&x" (-> BlockVec),
|
: [out] "=&x" (-> BlockVec),
|
||||||
: [in] "x" (block.repr),
|
: [in] "x" (block.repr),
|
||||||
[rk] "x" (inv_round_key.repr),
|
|
||||||
[zero] "x" (zero),
|
[zero] "x" (zero),
|
||||||
),
|
)) ^ inv_round_key.repr,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
// Based on Go stdlib implementation
|
|
||||||
|
|
||||||
const std = @import("../../std.zig");
|
const std = @import("../../std.zig");
|
||||||
const math = std.math;
|
const math = std.math;
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
|
|
||||||
const BlockVec = [4]u32;
|
const BlockVec = [4]u32;
|
||||||
|
|
||||||
|
const side_channels_mitigations = std.options.side_channels_mitigations;
|
||||||
|
|
||||||
/// A single AES block.
|
/// A single AES block.
|
||||||
pub const Block = struct {
|
pub const Block = struct {
|
||||||
pub const block_length: usize = 16;
|
pub const block_length: usize = 16;
|
||||||
@ -15,20 +15,20 @@ pub const Block = struct {
|
|||||||
|
|
||||||
/// Convert a byte sequence into an internal representation.
|
/// Convert a byte sequence into an internal representation.
|
||||||
pub inline fn fromBytes(bytes: *const [16]u8) Block {
|
pub inline fn fromBytes(bytes: *const [16]u8) Block {
|
||||||
const s0 = mem.readIntBig(u32, bytes[0..4]);
|
const s0 = mem.readIntLittle(u32, bytes[0..4]);
|
||||||
const s1 = mem.readIntBig(u32, bytes[4..8]);
|
const s1 = mem.readIntLittle(u32, bytes[4..8]);
|
||||||
const s2 = mem.readIntBig(u32, bytes[8..12]);
|
const s2 = mem.readIntLittle(u32, bytes[8..12]);
|
||||||
const s3 = mem.readIntBig(u32, bytes[12..16]);
|
const s3 = mem.readIntLittle(u32, bytes[12..16]);
|
||||||
return Block{ .repr = BlockVec{ s0, s1, s2, s3 } };
|
return Block{ .repr = BlockVec{ s0, s1, s2, s3 } };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the internal representation of a block into a byte sequence.
|
/// Convert the internal representation of a block into a byte sequence.
|
||||||
pub inline fn toBytes(block: Block) [16]u8 {
|
pub inline fn toBytes(block: Block) [16]u8 {
|
||||||
var bytes: [16]u8 = undefined;
|
var bytes: [16]u8 = undefined;
|
||||||
mem.writeIntBig(u32, bytes[0..4], block.repr[0]);
|
mem.writeIntLittle(u32, bytes[0..4], block.repr[0]);
|
||||||
mem.writeIntBig(u32, bytes[4..8], block.repr[1]);
|
mem.writeIntLittle(u32, bytes[4..8], block.repr[1]);
|
||||||
mem.writeIntBig(u32, bytes[8..12], block.repr[2]);
|
mem.writeIntLittle(u32, bytes[8..12], block.repr[2]);
|
||||||
mem.writeIntBig(u32, bytes[12..16], block.repr[3]);
|
mem.writeIntLittle(u32, bytes[12..16], block.repr[3]);
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,32 +50,93 @@ pub const Block = struct {
|
|||||||
const s2 = block.repr[2];
|
const s2 = block.repr[2];
|
||||||
const s3 = block.repr[3];
|
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)];
|
var x: [4]u32 = undefined;
|
||||||
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)];
|
x = table_lookup(&table_encrypt, @truncate(u8, s0), @truncate(u8, s1 >> 8), @truncate(u8, s2 >> 16), @truncate(u8, s3 >> 24));
|
||||||
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)];
|
var t0 = x[0] ^ x[1] ^ x[2] ^ x[3];
|
||||||
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)];
|
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 } };
|
return Block{ .repr = BlockVec{ t0, t1, t2, t3 } };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypt a block with the last round key.
|
/// Encrypt a block with the last round key.
|
||||||
pub inline fn encryptLast(block: Block, round_key: Block) Block {
|
pub inline fn encryptLast(block: Block, round_key: Block) Block {
|
||||||
const t0 = block.repr[0];
|
const s0 = block.repr[0];
|
||||||
const t1 = block.repr[1];
|
const s1 = block.repr[1];
|
||||||
const t2 = block.repr[2];
|
const s2 = block.repr[2];
|
||||||
const t3 = block.repr[3];
|
const s3 = block.repr[3];
|
||||||
|
|
||||||
// Last round uses s-box directly and XORs to produce output.
|
// 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 x: [4]u8 = undefined;
|
||||||
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]);
|
x = sbox_lookup(&sbox_encrypt, @truncate(u8, s3 >> 24), @truncate(u8, s2 >> 16), @truncate(u8, s1 >> 8), @truncate(u8, s0));
|
||||||
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 t0 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
|
||||||
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]);
|
x = sbox_lookup(&sbox_encrypt, @truncate(u8, s0 >> 24), @truncate(u8, s3 >> 16), @truncate(u8, s2 >> 8), @truncate(u8, s1));
|
||||||
s0 ^= round_key.repr[0];
|
var t1 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
|
||||||
s1 ^= round_key.repr[1];
|
x = sbox_lookup(&sbox_encrypt, @truncate(u8, s1 >> 24), @truncate(u8, s0 >> 16), @truncate(u8, s3 >> 8), @truncate(u8, s2));
|
||||||
s2 ^= round_key.repr[2];
|
var t2 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
|
||||||
s3 ^= round_key.repr[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.
|
/// Decrypt a block with a round key.
|
||||||
@ -85,32 +146,93 @@ pub const Block = struct {
|
|||||||
const s2 = block.repr[2];
|
const s2 = block.repr[2];
|
||||||
const s3 = block.repr[3];
|
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)];
|
var x: [4]u32 = undefined;
|
||||||
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)];
|
x = table_lookup(&table_decrypt, @truncate(u8, s0), @truncate(u8, s3 >> 8), @truncate(u8, s2 >> 16), @truncate(u8, s1 >> 24));
|
||||||
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)];
|
var t0 = x[0] ^ x[1] ^ x[2] ^ x[3];
|
||||||
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)];
|
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 } };
|
return Block{ .repr = BlockVec{ t0, t1, t2, t3 } };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypt a block with the last round key.
|
/// Decrypt a block with the last round key.
|
||||||
pub inline fn decryptLast(block: Block, round_key: Block) Block {
|
pub inline fn decryptLast(block: Block, round_key: Block) Block {
|
||||||
const t0 = block.repr[0];
|
const s0 = block.repr[0];
|
||||||
const t1 = block.repr[1];
|
const s1 = block.repr[1];
|
||||||
const t2 = block.repr[2];
|
const s2 = block.repr[2];
|
||||||
const t3 = block.repr[3];
|
const s3 = block.repr[3];
|
||||||
|
|
||||||
// Last round uses s-box directly and XORs to produce output.
|
// 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 x: [4]u8 = undefined;
|
||||||
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]);
|
x = sbox_lookup(&sbox_decrypt, @truncate(u8, s1 >> 24), @truncate(u8, s2 >> 16), @truncate(u8, s3 >> 8), @truncate(u8, s0));
|
||||||
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 t0 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
|
||||||
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]);
|
x = sbox_lookup(&sbox_decrypt, @truncate(u8, s2 >> 24), @truncate(u8, s3 >> 16), @truncate(u8, s0 >> 8), @truncate(u8, s1));
|
||||||
s0 ^= round_key.repr[0];
|
var t1 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
|
||||||
s1 ^= round_key.repr[1];
|
x = sbox_lookup(&sbox_decrypt, @truncate(u8, s3 >> 24), @truncate(u8, s0 >> 16), @truncate(u8, s1 >> 8), @truncate(u8, s2));
|
||||||
s2 ^= round_key.repr[2];
|
var t2 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]);
|
||||||
s3 ^= round_key.repr[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.
|
/// Apply the bitwise XOR operation to the content of two blocks.
|
||||||
@ -226,7 +348,8 @@ fn KeySchedule(comptime Aes: type) type {
|
|||||||
const subw = struct {
|
const subw = struct {
|
||||||
// Apply sbox_encrypt to each byte in w.
|
// Apply sbox_encrypt to each byte in w.
|
||||||
fn func(w: u32) u32 {
|
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;
|
}.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;
|
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 };
|
return Self{ .round_keys = round_keys };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,11 +384,13 @@ fn KeySchedule(comptime Aes: type) type {
|
|||||||
const ei = total_words - i - 4;
|
const ei = total_words - i - 4;
|
||||||
comptime var j: usize = 0;
|
comptime var j: usize = 0;
|
||||||
inline while (j < 4) : (j += 1) {
|
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) {
|
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 };
|
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;
|
const round_keys = ctx.key_schedule.round_keys;
|
||||||
var t = Block.fromBytes(src).xorBlocks(round_keys[0]);
|
var t = Block.fromBytes(src).xorBlocks(round_keys[0]);
|
||||||
comptime var i = 1;
|
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.encrypt(round_keys[i]);
|
||||||
}
|
}
|
||||||
t = t.encryptLast(round_keys[rounds]);
|
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;
|
const round_keys = ctx.key_schedule.round_keys;
|
||||||
var t = Block.fromBytes(&counter).xorBlocks(round_keys[0]);
|
var t = Block.fromBytes(&counter).xorBlocks(round_keys[0]);
|
||||||
comptime var i = 1;
|
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.encrypt(round_keys[i]);
|
||||||
}
|
}
|
||||||
t = t.encryptLast(round_keys[rounds]);
|
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;
|
const inv_round_keys = ctx.key_schedule.round_keys;
|
||||||
var t = Block.fromBytes(src).xorBlocks(inv_round_keys[0]);
|
var t = Block.fromBytes(src).xorBlocks(inv_round_keys[0]);
|
||||||
comptime var i = 1;
|
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.decrypt(inv_round_keys[i]);
|
||||||
}
|
}
|
||||||
t = t.decryptLast(inv_round_keys[rounds]);
|
t = t.decryptLast(inv_round_keys[rounds]);
|
||||||
@ -428,10 +587,11 @@ const powx = init: {
|
|||||||
break :init array;
|
break :init array;
|
||||||
};
|
};
|
||||||
|
|
||||||
const sbox_encrypt align(64) = generateSbox(false);
|
const sbox_encrypt align(64) = generateSbox(false); // S-box for encryption
|
||||||
const sbox_decrypt align(64) = generateSbox(true);
|
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 table_encrypt align(64) = generateTable(false);
|
const sbox_decrypt align(64) = generateSbox(true); // S-box for decryption
|
||||||
const table_decrypt align(64) = generateTable(true);
|
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.
|
// Generate S-box substitution values.
|
||||||
fn generateSbox(invert: bool) [256]u8 {
|
fn generateSbox(invert: bool) [256]u8 {
|
||||||
@ -472,14 +632,14 @@ fn generateTable(invert: bool) [4][256]u32 {
|
|||||||
var table: [4][256]u32 = undefined;
|
var table: [4][256]u32 = undefined;
|
||||||
|
|
||||||
for (generateSbox(invert), 0..) |value, index| {
|
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) 0xb else 0x3), 24);
|
||||||
table[0][index] |= math.shl(u32, mul(value, if (invert) 0xd else 0x1), 8);
|
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), 16);
|
table[0][index] |= math.shl(u32, mul(value, if (invert) 0x9 else 0x1), 8);
|
||||||
table[0][index] |= math.shl(u32, mul(value, if (invert) 0xe else 0x2), 24);
|
table[0][index] |= mul(value, if (invert) 0xe else 0x2);
|
||||||
|
|
||||||
table[1][index] = math.rotr(u32, table[0][index], 8);
|
table[1][index] = math.rotl(u32, table[0][index], 8);
|
||||||
table[2][index] = math.rotr(u32, table[0][index], 16);
|
table[2][index] = math.rotl(u32, table[0][index], 16);
|
||||||
table[3][index] = math.rotr(u32, table[0][index], 24);
|
table[3][index] = math.rotl(u32, table[0][index], 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
@ -506,3 +666,82 @@ fn mul(a: u8, b: u8) u8 {
|
|||||||
|
|
||||||
return @truncate(u8, s);
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -138,40 +138,39 @@ fn initHash(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn blake2bLong(out: []u8, in: []const u8) void {
|
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;
|
var out_buf: [H.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);
|
|
||||||
|
|
||||||
if (out.len <= Blake2b512.digest_length) {
|
if (out.len <= H.digest_length) {
|
||||||
mem.copy(u8, out, buffer[0..out.len]);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
b2 = Blake2b512.init(.{});
|
var h = H.init(.{});
|
||||||
mem.copy(u8, out, buffer[0..32]);
|
h.update(&outlen_bytes);
|
||||||
var out_slice = out[32..];
|
h.update(in);
|
||||||
while (out_slice.len > Blake2b512.digest_length) : ({
|
h.final(&out_buf);
|
||||||
out_slice = out_slice[32..];
|
var out_slice = out;
|
||||||
b2 = Blake2b512.init(.{});
|
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..32]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var r = Blake2b512.digest_length;
|
var in_buf: [H.digest_length]u8 = undefined;
|
||||||
if (out.len % Blake2b512.digest_length > 0) {
|
while (out_slice.len > H.digest_length) {
|
||||||
r = ((out.len + 31) / 32) - 2;
|
mem.copy(u8, &in_buf, &out_buf);
|
||||||
b2 = Blake2b512.init(.{ .expected_out_bits = r * 8 });
|
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 ..];
|
||||||
}
|
}
|
||||||
|
mem.copy(u8, &in_buf, &out_buf);
|
||||||
b2.update(&buffer);
|
H.hash(&in_buf, &out_buf, .{ .expected_out_bits = out_slice.len * 8 });
|
||||||
b2.final(&buffer);
|
mem.copy(u8, out_slice, out_buf[0..out_slice.len]);
|
||||||
mem.copy(u8, out_slice, buffer[0..r]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initBlocks(
|
fn initBlocks(
|
||||||
|
|||||||
@ -27,6 +27,8 @@ const hashes = [_]Crypto{
|
|||||||
Crypto{ .ty = crypto.hash.sha3.Sha3_512, .name = "sha3-512" },
|
Crypto{ .ty = crypto.hash.sha3.Sha3_512, .name = "sha3-512" },
|
||||||
Crypto{ .ty = crypto.hash.sha3.Shake128, .name = "shake-128" },
|
Crypto{ .ty = crypto.hash.sha3.Shake128, .name = "shake-128" },
|
||||||
Crypto{ .ty = crypto.hash.sha3.Shake256, .name = "shake-256" },
|
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.Gimli, .name = "gimli-hash" },
|
||||||
Crypto{ .ty = crypto.hash.blake2.Blake2s256, .name = "blake2s" },
|
Crypto{ .ty = crypto.hash.blake2.Blake2s256, .name = "blake2s" },
|
||||||
Crypto{ .ty = crypto.hash.blake2.Blake2b512, .name = "blake2b" },
|
Crypto{ .ty = crypto.hash.blake2.Blake2b512, .name = "blake2b" },
|
||||||
@ -201,6 +203,72 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime
|
|||||||
return throughput;
|
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{
|
const aeads = [_]Crypto{
|
||||||
Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },
|
Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" },
|
||||||
Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
|
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 });
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
CompressVectorized.compress
|
||||||
else
|
else
|
||||||
CompressGeneric.compress;
|
CompressGeneric.compress;
|
||||||
|
|||||||
@ -152,7 +152,7 @@ pub const State = struct {
|
|||||||
self.endianSwap();
|
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;
|
break :impl permute_vectorized;
|
||||||
} else if (builtin.mode == .ReleaseSmall) impl: {
|
} else if (builtin.mode == .ReleaseSmall) impl: {
|
||||||
break :impl permute_small;
|
break :impl permute_small;
|
||||||
|
|||||||
@ -175,12 +175,12 @@ pub fn KeccakF(comptime f: u11) type {
|
|||||||
/// Apply a (possibly) reduced-round permutation to the state.
|
/// Apply a (possibly) reduced-round permutation to the state.
|
||||||
pub fn permuteR(self: *Self, comptime rounds: u5) void {
|
pub fn permuteR(self: *Self, comptime rounds: u5) void {
|
||||||
var i = RC.len - rounds;
|
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]);
|
||||||
self.round(RC[i + 1]);
|
self.round(RC[i + 1]);
|
||||||
self.round(RC[i + 2]);
|
self.round(RC[i + 2]);
|
||||||
}
|
}
|
||||||
while (i < rounds) : (i += 1) {
|
while (i < RC.len) : (i += 1) {
|
||||||
self.round(RC[i]);
|
self.round(RC[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +231,7 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime delim: u8, compti
|
|||||||
bytes = bytes[rate..];
|
bytes = bytes[rate..];
|
||||||
}
|
}
|
||||||
if (bytes.len > 0) {
|
if (bytes.len > 0) {
|
||||||
self.st.addBytes(bytes[0..]);
|
mem.copy(u8, &self.buf, bytes);
|
||||||
self.offset = bytes.len;
|
self.offset = bytes.len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1780
lib/std/crypto/kyber_d00.zig
Normal file
1780
lib/std/crypto/kyber_d00.zig
Normal file
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,20 @@ pub const Keccak_512 = @compileError("Deprecated: use `Keccak512` instead");
|
|||||||
pub const Shake128 = Shake(128);
|
pub const Shake128 = Shake(128);
|
||||||
pub const Shake256 = Shake(256);
|
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.
|
/// A generic Keccak hash function.
|
||||||
pub fn Keccak(comptime f: u11, comptime output_bits: u11, comptime delim: u8, comptime rounds: u5) type {
|
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
|
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.
|
/// The SHAKE extendable output hash function.
|
||||||
pub fn Shake(comptime security_level: u11) type {
|
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 f = 1600;
|
||||||
const rounds = 24;
|
const State = KeccakState(f, security_level * 2, delim, rounds);
|
||||||
const State = KeccakState(f, security_level * 2, 0x1f, rounds);
|
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
@ -348,3 +371,23 @@ test "SHAKE-256 single" {
|
|||||||
Shake256.hash("hello123", &out, .{});
|
Shake256.hash("hello123", &out, .{});
|
||||||
try htest.assertEqual("ade612ba265f92de4a37", &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);
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
/// Initiates a TLS handshake and establishes a TLSv1.3 session with `stream`, which
|
||||||
/// must conform to `StreamInterface`.
|
/// must conform to `StreamInterface`.
|
||||||
///
|
///
|
||||||
/// `host` is only borrowed during this function call.
|
/// `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);
|
const host_len = @intCast(u16, host.len);
|
||||||
|
|
||||||
var random_buffer: [128]u8 = undefined;
|
var random_buffer: [128]u8 = undefined;
|
||||||
|
|||||||
@ -635,6 +635,7 @@ pub const TTY = struct {
|
|||||||
pub const Color = enum {
|
pub const Color = enum {
|
||||||
Red,
|
Red,
|
||||||
Green,
|
Green,
|
||||||
|
Yellow,
|
||||||
Cyan,
|
Cyan,
|
||||||
White,
|
White,
|
||||||
Dim,
|
Dim,
|
||||||
@ -659,6 +660,7 @@ pub const TTY = struct {
|
|||||||
const color_string = switch (color) {
|
const color_string = switch (color) {
|
||||||
.Red => "\x1b[31;1m",
|
.Red => "\x1b[31;1m",
|
||||||
.Green => "\x1b[32;1m",
|
.Green => "\x1b[32;1m",
|
||||||
|
.Yellow => "\x1b[33;1m",
|
||||||
.Cyan => "\x1b[36;1m",
|
.Cyan => "\x1b[36;1m",
|
||||||
.White => "\x1b[37;1m",
|
.White => "\x1b[37;1m",
|
||||||
.Bold => "\x1b[1m",
|
.Bold => "\x1b[1m",
|
||||||
@ -671,6 +673,7 @@ pub const TTY = struct {
|
|||||||
const attributes = switch (color) {
|
const attributes = switch (color) {
|
||||||
.Red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
|
.Red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
|
||||||
.Green => windows.FOREGROUND_GREEN | 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,
|
.Cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
|
||||||
.White, .Bold => windows.FOREGROUND_RED | 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,
|
.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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -164,6 +164,17 @@ pub fn LinearFifo(
|
|||||||
return self.readableSliceMut(offset);
|
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
|
/// Discard first `count` items in the fifo
|
||||||
pub fn discard(self: *Self, count: usize) void {
|
pub fn discard(self: *Self, count: usize) void {
|
||||||
assert(count <= self.count);
|
assert(count <= self.count);
|
||||||
@ -383,6 +394,22 @@ pub fn LinearFifo(
|
|||||||
self.discard(try dest_writer.write(self.readableSlice(0)));
|
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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2555,6 +2555,21 @@ test "bytes.hex" {
|
|||||||
try expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{fmtSliceHexLower(bytes_with_zeros)});
|
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
|
/// Decodes the sequence of bytes represented by the specified string of
|
||||||
/// hexadecimal characters.
|
/// hexadecimal characters.
|
||||||
/// Returns a slice of the output buffer containing the decoded bytes.
|
/// 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];
|
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" {
|
test "hexToBytes" {
|
||||||
var buf: [32]u8 = undefined;
|
var buf: [32]u8 = undefined;
|
||||||
try expectFmt("90" ** 32, "{s}", .{fmtSliceHexUpper(try hexToBytes(&buf, "90" ** 32))});
|
try expectFmt("90" ** 32, "{s}", .{fmtSliceHexUpper(try hexToBytes(&buf, "90" ** 32))});
|
||||||
|
|||||||
@ -119,6 +119,7 @@ test "fmt.parseFloat hex.f16" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "fmt.parseFloat hex.f32" {
|
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, "0x1p0"), 1.0);
|
||||||
try testing.expectEqual(try parseFloat(f32, "-0x1p-1"), -0.5);
|
try testing.expectEqual(try parseFloat(f32, "-0x1p-1"), -0.5);
|
||||||
try testing.expectEqual(try parseFloat(f32, "0x10p+10"), 16384.0);
|
try testing.expectEqual(try parseFloat(f32, "0x10p+10"), 16384.0);
|
||||||
|
|||||||
@ -107,6 +107,8 @@ fn parsePartialNumberBase(comptime T: type, stream: *FloatStream, negative: bool
|
|||||||
tryParseDigits(MantissaT, stream, &mantissa, info.base);
|
tryParseDigits(MantissaT, stream, &mantissa, info.base);
|
||||||
var int_end = stream.offsetTrue();
|
var int_end = stream.offsetTrue();
|
||||||
var n_digits = @intCast(isize, 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
|
// handle dot with the following digits
|
||||||
var exponent: i64 = 0;
|
var exponent: i64 = 0;
|
||||||
|
|||||||
@ -1048,12 +1048,27 @@ pub const File = struct {
|
|||||||
/// Returns the number of bytes read. If the number read is smaller than the total bytes
|
/// 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
|
/// from all the buffers, it means the file reached the end. Reaching the end of a file
|
||||||
/// is not an error condition.
|
/// 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.
|
/// The `iovecs` parameter is mutable because:
|
||||||
/// See https://github.com/ziglang/zig/issues/7699
|
/// * 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 {
|
pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize {
|
||||||
if (iovecs.len == 0) return 0;
|
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 i: usize = 0;
|
||||||
var off: usize = 0;
|
var off: usize = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -1181,13 +1196,26 @@ pub const File = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
/// The `iovecs` parameter is mutable because:
|
||||||
/// order to handle partial writes from the underlying OS layer.
|
/// * 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 https://github.com/ziglang/zig/issues/7699
|
||||||
/// See equivalent function: `std.net.Stream.writevAll`.
|
/// See equivalent function: `std.net.Stream.writevAll`.
|
||||||
pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
|
pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
|
||||||
if (iovecs.len == 0) return;
|
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;
|
var i: usize = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
var amt = try self.writev(iovecs[i..]);
|
var amt = try self.writev(iovecs[i..]);
|
||||||
|
|||||||
@ -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)" {
|
test "open file with exclusive nonblocking lock twice (absolute paths)" {
|
||||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
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);
|
var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
|
||||||
defer allocator.free(cwd);
|
_ = fs.base64_encoder.encode(&random_b64, &random_bytes);
|
||||||
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);
|
|
||||||
|
|
||||||
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();
|
file1.close();
|
||||||
try testing.expectError(error.WouldBlock, file2);
|
try testing.expectError(error.WouldBlock, file2);
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig"
|
|||||||
pub const WasmAllocator = @import("heap/WasmAllocator.zig");
|
pub const WasmAllocator = @import("heap/WasmAllocator.zig");
|
||||||
pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig");
|
pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig");
|
||||||
pub const PageAllocator = @import("heap/PageAllocator.zig");
|
pub const PageAllocator = @import("heap/PageAllocator.zig");
|
||||||
|
pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig");
|
||||||
|
|
||||||
const memory_pool = @import("heap/memory_pool.zig");
|
const memory_pool = @import("heap/memory_pool.zig");
|
||||||
pub const MemoryPool = memory_pool.MemoryPool;
|
pub const MemoryPool = memory_pool.MemoryPool;
|
||||||
|
|||||||
45
lib/std/heap/ThreadSafeAllocator.zig
Normal file
45
lib/std/heap/ThreadSafeAllocator.zig
Normal 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;
|
||||||
@ -248,9 +248,24 @@ pub const Status = enum(u10) {
|
|||||||
|
|
||||||
pub const TransferEncoding = enum {
|
pub const TransferEncoding = enum {
|
||||||
chunked,
|
chunked,
|
||||||
|
// compression is intentionally omitted here, as std.http.Client stores it as content-encoding
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ContentEncoding = enum {
|
||||||
compress,
|
compress,
|
||||||
deflate,
|
deflate,
|
||||||
gzip,
|
gzip,
|
||||||
|
zstd,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Connection = enum {
|
||||||
|
keep_alive,
|
||||||
|
close,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CustomHeader = struct {
|
||||||
|
name: []const u8,
|
||||||
|
value: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
const std = @import("std.zig");
|
const std = @import("std.zig");
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
482
lib/std/http/Client/Request.zig
Normal file
482
lib/std/http/Client/Request.zig
Normal 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;
|
||||||
|
}
|
||||||
509
lib/std/http/Client/Response.zig
Normal file
509
lib/std/http/Client/Response.zig
Normal 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));
|
||||||
|
}
|
||||||
@ -1674,6 +1674,7 @@ pub const Mutable = struct {
|
|||||||
|
|
||||||
/// If a is positive, this passes through to truncate.
|
/// 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).
|
/// 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.
|
/// Asserts `r` has enough storage to store the result.
|
||||||
/// The upper bound is `calcTwosCompLimbCount(a.len)`.
|
/// The upper bound is `calcTwosCompLimbCount(a.len)`.
|
||||||
|
|||||||
@ -196,13 +196,8 @@ test "Allocator.resize" {
|
|||||||
/// dest.len must be >= source.len.
|
/// dest.len must be >= source.len.
|
||||||
/// If the slices overlap, dest.ptr must be <= src.ptr.
|
/// If the slices overlap, dest.ptr must be <= src.ptr.
|
||||||
pub fn copy(comptime T: type, dest: []T, source: []const T) void {
|
pub fn copy(comptime T: type, dest: []T, source: []const T) void {
|
||||||
// TODO instead of manually doing this check for the whole array
|
for (dest[0..source.len], source) |*d, s|
|
||||||
// and turning off runtime safety, the compiler should detect loops like
|
d.* = s;
|
||||||
// this and automatically omit safety checks for loops
|
|
||||||
@setRuntimeSafety(false);
|
|
||||||
assert(dest.len >= source.len);
|
|
||||||
for (source, 0..) |s, i|
|
|
||||||
dest[i] = s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy all of source into dest at position 0.
|
/// 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 {
|
pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
|
||||||
if (a.len != b.len) return false;
|
if (a.len != b.len) return false;
|
||||||
if (a.ptr == b.ptr) return true;
|
if (a.ptr == b.ptr) return true;
|
||||||
for (a, 0..) |item, index| {
|
for (a, b) |a_elem, b_elem| {
|
||||||
if (b[index] != item) return false;
|
if (a_elem != b_elem) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,6 +68,15 @@ pub fn MultiArrayList(comptime S: type) type {
|
|||||||
other.deinit(gpa);
|
other.deinit(gpa);
|
||||||
self.* = undefined;
|
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();
|
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
|
/// This function is used in the debugger pretty formatters in tools/ to fetch the
|
||||||
/// child type to facilitate fancy debug printing for this type.
|
/// child field order and entry type to facilitate fancy debug printing for this type.
|
||||||
fn dbHelper(self: *Self, child: *S, entry: *Entry) void {
|
fn dbHelper(self: *Self, child: *S, field: *Field, entry: *Entry) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = child;
|
_ = child;
|
||||||
|
_ = field;
|
||||||
_ = entry;
|
_ = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
comptime {
|
comptime {
|
||||||
if (builtin.mode == .Debug) {
|
if (builtin.mode == .Debug) {
|
||||||
_ = dbHelper;
|
_ = dbHelper;
|
||||||
|
_ = Slice.dbHelper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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.
|
/// 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);
|
const list = try getAddressList(allocator, name, port);
|
||||||
defer list.deinit();
|
defer list.deinit();
|
||||||
|
|
||||||
@ -720,7 +722,9 @@ pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) !
|
|||||||
return std.os.ConnectError.ConnectionRefused;
|
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 nonblock = if (std.io.is_async) os.SOCK.NONBLOCK else 0;
|
||||||
const sock_flags = os.SOCK.STREAM | nonblock |
|
const sock_flags = os.SOCK.STREAM | nonblock |
|
||||||
(if (builtin.target.os.tag == .windows) 0 else os.SOCK.CLOEXEC);
|
(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 };
|
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.
|
/// 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: {
|
const result = blk: {
|
||||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||||
errdefer arena.deinit();
|
errdefer arena.deinit();
|
||||||
|
|||||||
123
lib/std/os.zig
123
lib/std/os.zig
@ -29,7 +29,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
const Preopen = std.fs.wasi.Preopen;
|
const Preopen = std.fs.wasi.Preopen;
|
||||||
const PreopenList = std.fs.wasi.PreopenList;
|
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 dragonfly = std.c;
|
||||||
pub const freebsd = std.c;
|
pub const freebsd = std.c;
|
||||||
pub const haiku = 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 uefi = @import("os/uefi.zig");
|
||||||
pub const wasi = @import("os/wasi.zig");
|
pub const wasi = @import("os/wasi.zig");
|
||||||
pub const windows = @import("os/windows.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 {
|
comptime {
|
||||||
assert(@import("std") == std); // std lib tests require --zig-lib-dir
|
assert(@import("std") == std); // std lib tests require --zig-lib-dir
|
||||||
@ -56,7 +54,6 @@ test {
|
|||||||
}
|
}
|
||||||
_ = wasi;
|
_ = wasi;
|
||||||
_ = windows;
|
_ = windows;
|
||||||
_ = posix_spawn;
|
|
||||||
|
|
||||||
_ = @import("os/test.zig");
|
_ = @import("os/test.zig");
|
||||||
}
|
}
|
||||||
@ -253,6 +250,25 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (builtin.
|
|||||||
else => undefined,
|
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
|
/// To obtain errno, call this function with the return value of the
|
||||||
/// system function call. For some systems this will obtain the value directly
|
/// 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.
|
/// 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 {};
|
raise(SIG.KILL) catch {};
|
||||||
exit(127); // Pid 1 might not be signalled in some containers.
|
exit(127); // Pid 1 might not be signalled in some containers.
|
||||||
}
|
}
|
||||||
if (builtin.os.tag == .uefi) {
|
switch (builtin.os.tag) {
|
||||||
exit(0); // TODO choose appropriate exit code
|
.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 const RaiseError = UnexpectedError;
|
||||||
|
|
||||||
pub fn raise(sig: u8) RaiseError!void {
|
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:
|
/// This operation is non-atomic on the following systems:
|
||||||
/// * Windows
|
/// * Windows
|
||||||
/// On these systems, the read races with concurrent writes to the same file descriptor.
|
/// 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 {
|
pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
|
||||||
if (builtin.os.tag == .windows) {
|
if (builtin.os.tag == .windows) {
|
||||||
// TODO improve this to use ReadFileScatter
|
// TODO improve this to use ReadFileScatter
|
||||||
@ -1036,6 +1045,8 @@ pub const WriteError = error{
|
|||||||
FileTooBig,
|
FileTooBig,
|
||||||
InputOutput,
|
InputOutput,
|
||||||
NoSpaceLeft,
|
NoSpaceLeft,
|
||||||
|
DeviceBusy,
|
||||||
|
InvalidArgument,
|
||||||
|
|
||||||
/// In WASI, this error may occur when the file descriptor does
|
/// In WASI, this error may occur when the file descriptor does
|
||||||
/// not hold the required rights to write to it.
|
/// 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)) {
|
switch (errno(rc)) {
|
||||||
.SUCCESS => return @intCast(usize, rc),
|
.SUCCESS => return @intCast(usize, rc),
|
||||||
.INTR => continue,
|
.INTR => continue,
|
||||||
.INVAL => unreachable,
|
.INVAL => return error.InvalidArgument,
|
||||||
.FAULT => unreachable,
|
.FAULT => unreachable,
|
||||||
.AGAIN => return error.WouldBlock,
|
.AGAIN => return error.WouldBlock,
|
||||||
.BADF => return error.NotOpenForWriting, // can be a race condition.
|
.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,
|
.PERM => return error.AccessDenied,
|
||||||
.PIPE => return error.BrokenPipe,
|
.PIPE => return error.BrokenPipe,
|
||||||
.CONNRESET => return error.ConnectionResetByPeer,
|
.CONNRESET => return error.ConnectionResetByPeer,
|
||||||
|
.BUSY => return error.DeviceBusy,
|
||||||
else => |err| return unexpectedErrno(err),
|
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.
|
/// 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.
|
/// 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 {
|
pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
|
||||||
if (builtin.os.tag == .windows) {
|
if (builtin.os.tag == .windows) {
|
||||||
// TODO improve this to use WriteFileScatter
|
// 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)) {
|
switch (errno(rc)) {
|
||||||
.SUCCESS => return @intCast(usize, rc),
|
.SUCCESS => return @intCast(usize, rc),
|
||||||
.INTR => continue,
|
.INTR => continue,
|
||||||
.INVAL => unreachable,
|
.INVAL => return error.InvalidArgument,
|
||||||
.FAULT => unreachable,
|
.FAULT => unreachable,
|
||||||
.AGAIN => return error.WouldBlock,
|
.AGAIN => return error.WouldBlock,
|
||||||
.BADF => return error.NotOpenForWriting, // Can be a race condition.
|
.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,
|
.PERM => return error.AccessDenied,
|
||||||
.PIPE => return error.BrokenPipe,
|
.PIPE => return error.BrokenPipe,
|
||||||
.CONNRESET => return error.ConnectionResetByPeer,
|
.CONNRESET => return error.ConnectionResetByPeer,
|
||||||
|
.BUSY => return error.DeviceBusy,
|
||||||
else => |err| return unexpectedErrno(err),
|
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)) {
|
switch (errno(rc)) {
|
||||||
.SUCCESS => return @intCast(usize, rc),
|
.SUCCESS => return @intCast(usize, rc),
|
||||||
.INTR => continue,
|
.INTR => continue,
|
||||||
.INVAL => unreachable,
|
.INVAL => return error.InvalidArgument,
|
||||||
.FAULT => unreachable,
|
.FAULT => unreachable,
|
||||||
.AGAIN => return error.WouldBlock,
|
.AGAIN => return error.WouldBlock,
|
||||||
.BADF => return error.NotOpenForWriting, // Can be a race condition.
|
.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,
|
.NXIO => return error.Unseekable,
|
||||||
.SPIPE => return error.Unseekable,
|
.SPIPE => return error.Unseekable,
|
||||||
.OVERFLOW => return error.Unseekable,
|
.OVERFLOW => return error.Unseekable,
|
||||||
|
.BUSY => return error.DeviceBusy,
|
||||||
else => |err| return unexpectedErrno(err),
|
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)) {
|
switch (errno(rc)) {
|
||||||
.SUCCESS => return @intCast(usize, rc),
|
.SUCCESS => return @intCast(usize, rc),
|
||||||
.INTR => continue,
|
.INTR => continue,
|
||||||
.INVAL => unreachable,
|
.INVAL => return error.InvalidArgument,
|
||||||
.FAULT => unreachable,
|
.FAULT => unreachable,
|
||||||
.AGAIN => return error.WouldBlock,
|
.AGAIN => return error.WouldBlock,
|
||||||
.BADF => return error.NotOpenForWriting, // Can be a race condition.
|
.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,
|
.NXIO => return error.Unseekable,
|
||||||
.SPIPE => return error.Unseekable,
|
.SPIPE => return error.Unseekable,
|
||||||
.OVERFLOW => return error.Unseekable,
|
.OVERFLOW => return error.Unseekable,
|
||||||
|
.BUSY => return error.DeviceBusy,
|
||||||
else => |err| return unexpectedErrno(err),
|
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);
|
const rc = system.bind(sock, addr, len);
|
||||||
switch (errno(rc)) {
|
switch (errno(rc)) {
|
||||||
.SUCCESS => return,
|
.SUCCESS => return,
|
||||||
.ACCES => return error.AccessDenied,
|
.ACCES, .PERM => return error.AccessDenied,
|
||||||
.ADDRINUSE => return error.AddressInUse,
|
.ADDRINUSE => return error.AddressInUse,
|
||||||
.BADF => unreachable, // always a race condition if this error is returned
|
.BADF => unreachable, // always a race condition if this error is returned
|
||||||
.INVAL => unreachable, // invalid parameters
|
.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
|
/// 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,
|
/// `fork` and `execve` method.
|
||||||
/// use `std.os.posix_spawn.waitpid` instead.
|
|
||||||
pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {
|
pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {
|
||||||
const Status = if (builtin.link_libc) c_int else u32;
|
const Status = if (builtin.link_libc) c_int else u32;
|
||||||
var status: Status = undefined;
|
var status: Status = undefined;
|
||||||
|
const coerced_flags = if (builtin.link_libc) @intCast(c_int, flags) else flags;
|
||||||
while (true) {
|
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)) {
|
switch (errno(rc)) {
|
||||||
.SUCCESS => return .{
|
.SUCCESS => return .{
|
||||||
.pid = @intCast(pid_t, rc),
|
.pid = @intCast(pid_t, rc),
|
||||||
@ -4310,6 +4347,8 @@ pub const MMapError = error{
|
|||||||
/// a filesystem that was mounted no-exec.
|
/// a filesystem that was mounted no-exec.
|
||||||
PermissionDenied,
|
PermissionDenied,
|
||||||
LockedMemoryLimitExceeded,
|
LockedMemoryLimitExceeded,
|
||||||
|
ProcessFdQuotaExceeded,
|
||||||
|
SystemFdQuotaExceeded,
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
} || UnexpectedError;
|
} || UnexpectedError;
|
||||||
|
|
||||||
@ -4351,6 +4390,8 @@ pub fn mmap(
|
|||||||
.OVERFLOW => unreachable, // The number of pages used for length + offset would overflow.
|
.OVERFLOW => unreachable, // The number of pages used for length + offset would overflow.
|
||||||
.NODEV => return error.MemoryMappingNotSupported,
|
.NODEV => return error.MemoryMappingNotSupported,
|
||||||
.INVAL => unreachable, // Invalid parameters to mmap()
|
.INVAL => unreachable, // Invalid parameters to mmap()
|
||||||
|
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||||
|
.NFILE => return error.SystemFdQuotaExceeded,
|
||||||
.NOMEM => return error.OutOfMemory,
|
.NOMEM => return error.OutOfMemory,
|
||||||
else => return unexpectedErrno(err),
|
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 {}
|
/// TODO on other OSes
|
||||||
|
pub fn ptrace(request: i32, pid: pid_t, addr: ?[*]u8, signal: i32) PtraceError!void {
|
||||||
pub fn maybeIgnoreSigpipe() void {
|
switch (builtin.os.tag) {
|
||||||
if (have_sigpipe_support and !std.options.keep_sigpipe) {
|
.macos, .ios, .tvos, .watchos => {},
|
||||||
const act = Sigaction{
|
else => @compileError("TODO implement ptrace"),
|
||||||
// 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)});
|
|
||||||
}
|
}
|
||||||
|
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),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {};
|
|
||||||
@ -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);
|
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 {
|
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);
|
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(
|
return syscall6(
|
||||||
.process_vm_readv,
|
.process_vm_readv,
|
||||||
@bitCast(usize, @as(isize, pid)),
|
@bitCast(usize, @as(isize, pid)),
|
||||||
@ptrToInt(local),
|
@ptrToInt(local.ptr),
|
||||||
local_count,
|
local.len,
|
||||||
@ptrToInt(remote),
|
@ptrToInt(remote.ptr),
|
||||||
remote_count,
|
remote.len,
|
||||||
flags,
|
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(
|
return syscall6(
|
||||||
.process_vm_writev,
|
.process_vm_writev,
|
||||||
@bitCast(usize, @as(isize, pid)),
|
@bitCast(usize, @as(isize, pid)),
|
||||||
@ptrToInt(local),
|
@ptrToInt(local.ptr),
|
||||||
local_count,
|
local.len,
|
||||||
@ptrToInt(remote),
|
@ptrToInt(remote.ptr),
|
||||||
remote_count,
|
remote.len,
|
||||||
flags,
|
flags,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1820,6 +1830,23 @@ pub fn seccomp(operation: u32, flags: u32, args: ?*const anyopaque) usize {
|
|||||||
return syscall3(.seccomp, operation, flags, @ptrToInt(args));
|
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) {
|
pub const E = switch (native_arch) {
|
||||||
.mips, .mipsel => @import("linux/errno/mips.zig").E,
|
.mips, .mipsel => @import("linux/errno/mips.zig").E,
|
||||||
.sparc, .sparcel, .sparc64 => @import("linux/errno/sparc.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;
|
||||||
|
};
|
||||||
|
|||||||
@ -1728,10 +1728,12 @@ test "writev/fsync/readv" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
defer ring.deinit();
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const path = "test_io_uring_writev_fsync_readv";
|
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 file.close();
|
||||||
defer std.fs.cwd().deleteFile(path) catch {};
|
|
||||||
const fd = file.handle;
|
const fd = file.handle;
|
||||||
|
|
||||||
const buffer_write = [_]u8{42} ** 128;
|
const buffer_write = [_]u8{42} ** 128;
|
||||||
@ -1796,10 +1798,11 @@ test "write/read" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
defer ring.deinit();
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
const path = "test_io_uring_write_read";
|
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 file.close();
|
||||||
defer std.fs.cwd().deleteFile(path) catch {};
|
|
||||||
const fd = file.handle;
|
const fd = file.handle;
|
||||||
|
|
||||||
const buffer_write = [_]u8{97} ** 20;
|
const buffer_write = [_]u8{97} ** 20;
|
||||||
@ -1842,10 +1845,12 @@ test "write_fixed/read_fixed" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
defer ring.deinit();
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const path = "test_io_uring_write_read_fixed";
|
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 file.close();
|
||||||
defer std.fs.cwd().deleteFile(path) catch {};
|
|
||||||
const fd = file.handle;
|
const fd = file.handle;
|
||||||
|
|
||||||
var raw_buffers: [2][11]u8 = undefined;
|
var raw_buffers: [2][11]u8 = undefined;
|
||||||
@ -1899,8 +1904,10 @@ test "openat" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
defer ring.deinit();
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const path = "test_io_uring_openat";
|
const path = "test_io_uring_openat";
|
||||||
defer std.fs.cwd().deleteFile(path) catch {};
|
|
||||||
|
|
||||||
// Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014
|
// Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014
|
||||||
const path_addr = if (builtin.zig_backend == .stage2_llvm) p: {
|
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 flags: u32 = os.O.CLOEXEC | os.O.RDWR | os.O.CREAT;
|
||||||
const mode: os.mode_t = 0o666;
|
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{
|
try testing.expectEqual(linux.io_uring_sqe{
|
||||||
.opcode = .OPENAT,
|
.opcode = .OPENAT,
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.ioprio = 0,
|
.ioprio = 0,
|
||||||
.fd = linux.AT.FDCWD,
|
.fd = tmp.dir.fd,
|
||||||
.off = 0,
|
.off = 0,
|
||||||
.addr = path_addr,
|
.addr = path_addr,
|
||||||
.len = mode,
|
.len = mode,
|
||||||
@ -1931,12 +1938,6 @@ test "openat" {
|
|||||||
const cqe_openat = try ring.copy_cqe();
|
const cqe_openat = try ring.copy_cqe();
|
||||||
try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data);
|
try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data);
|
||||||
if (cqe_openat.err() == .INVAL) return error.SkipZigTest;
|
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});
|
if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res});
|
||||||
try testing.expect(cqe_openat.res > 0);
|
try testing.expect(cqe_openat.res > 0);
|
||||||
try testing.expectEqual(@as(u32, 0), cqe_openat.flags);
|
try testing.expectEqual(@as(u32, 0), cqe_openat.flags);
|
||||||
@ -1954,10 +1955,12 @@ test "close" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
defer ring.deinit();
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const path = "test_io_uring_close";
|
const path = "test_io_uring_close";
|
||||||
const file = try std.fs.cwd().createFile(path, .{});
|
const file = try tmp.dir.createFile(path, .{});
|
||||||
errdefer file.close();
|
errdefer file.close();
|
||||||
defer std.fs.cwd().deleteFile(path) catch {};
|
|
||||||
|
|
||||||
const sqe_close = try ring.close(0x44444444, file.handle);
|
const sqe_close = try ring.close(0x44444444, file.handle);
|
||||||
try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode);
|
try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode);
|
||||||
@ -1976,6 +1979,11 @@ test "close" {
|
|||||||
test "accept/connect/send/recv" {
|
test "accept/connect/send/recv" {
|
||||||
if (builtin.os.tag != .linux) return error.SkipZigTest;
|
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) {
|
var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
|
||||||
error.SystemOutdated => return error.SkipZigTest,
|
error.SystemOutdated => return error.SkipZigTest,
|
||||||
error.PermissionDenied => return error.SkipZigTest,
|
error.PermissionDenied => return error.SkipZigTest,
|
||||||
@ -2017,6 +2025,11 @@ test "accept/connect/send/recv" {
|
|||||||
test "sendmsg/recvmsg" {
|
test "sendmsg/recvmsg" {
|
||||||
if (builtin.os.tag != .linux) return error.SkipZigTest;
|
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) {
|
var ring = IO_Uring.init(2, 0) catch |err| switch (err) {
|
||||||
error.SystemOutdated => return error.SkipZigTest,
|
error.SystemOutdated => return error.SkipZigTest,
|
||||||
error.PermissionDenied => return error.SkipZigTest,
|
error.PermissionDenied => return error.SkipZigTest,
|
||||||
@ -2024,6 +2037,7 @@ test "sendmsg/recvmsg" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
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 address_server = try net.Address.parseIp4("127.0.0.1", 3131);
|
||||||
|
|
||||||
const server = try os.socket(address_server.any.family, os.SOCK.DGRAM, 0);
|
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" {
|
test "accept/connect/recv/link_timeout" {
|
||||||
if (builtin.os.tag != .linux) return error.SkipZigTest;
|
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) {
|
var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
|
||||||
error.SystemOutdated => return error.SkipZigTest,
|
error.SystemOutdated => return error.SkipZigTest,
|
||||||
error.PermissionDenied => return error.SkipZigTest,
|
error.PermissionDenied => return error.SkipZigTest,
|
||||||
@ -2279,10 +2298,12 @@ test "fallocate" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
defer ring.deinit();
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const path = "test_io_uring_fallocate";
|
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 file.close();
|
||||||
defer std.fs.cwd().deleteFile(path) catch {};
|
|
||||||
|
|
||||||
try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
|
try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
|
||||||
|
|
||||||
@ -2323,10 +2344,11 @@ test "statx" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
defer ring.deinit();
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
const path = "test_io_uring_statx";
|
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 file.close();
|
||||||
defer std.fs.cwd().deleteFile(path) catch {};
|
|
||||||
|
|
||||||
try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
|
try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
|
||||||
|
|
||||||
@ -2335,14 +2357,14 @@ test "statx" {
|
|||||||
var buf: linux.Statx = undefined;
|
var buf: linux.Statx = undefined;
|
||||||
const sqe = try ring.statx(
|
const sqe = try ring.statx(
|
||||||
0xaaaaaaaa,
|
0xaaaaaaaa,
|
||||||
linux.AT.FDCWD,
|
tmp.dir.fd,
|
||||||
path,
|
path,
|
||||||
0,
|
0,
|
||||||
linux.STATX_SIZE,
|
linux.STATX_SIZE,
|
||||||
&buf,
|
&buf,
|
||||||
);
|
);
|
||||||
try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode);
|
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());
|
try testing.expectEqual(@as(u32, 1), try ring.submit());
|
||||||
|
|
||||||
const cqe = try ring.copy_cqe();
|
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;
|
// 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:
|
// or the mode is not supported by the filesystem containing the file referred to by fd:
|
||||||
.OPNOTSUPP => return error.SkipZigTest,
|
.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}),
|
else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
|
||||||
}
|
}
|
||||||
try testing.expectEqual(linux.io_uring_cqe{
|
try testing.expectEqual(linux.io_uring_cqe{
|
||||||
@ -2372,6 +2392,11 @@ test "statx" {
|
|||||||
test "accept/connect/recv/cancel" {
|
test "accept/connect/recv/cancel" {
|
||||||
if (builtin.os.tag != .linux) return error.SkipZigTest;
|
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) {
|
var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
|
||||||
error.SystemOutdated => return error.SkipZigTest,
|
error.SystemOutdated => return error.SkipZigTest,
|
||||||
error.PermissionDenied => return error.SkipZigTest,
|
error.PermissionDenied => return error.SkipZigTest,
|
||||||
@ -2509,6 +2534,11 @@ test "register_files_update" {
|
|||||||
test "shutdown" {
|
test "shutdown" {
|
||||||
if (builtin.os.tag != .linux) return error.SkipZigTest;
|
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) {
|
var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
|
||||||
error.SystemOutdated => return error.SkipZigTest,
|
error.SystemOutdated => return error.SkipZigTest,
|
||||||
error.PermissionDenied => return error.SkipZigTest,
|
error.PermissionDenied => return error.SkipZigTest,
|
||||||
@ -2516,6 +2546,7 @@ test "shutdown" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
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);
|
const address = try net.Address.parseIp4("127.0.0.1", 3131);
|
||||||
|
|
||||||
// Socket bound, expect shutdown to work
|
// Socket bound, expect shutdown to work
|
||||||
@ -2579,28 +2610,28 @@ test "renameat" {
|
|||||||
const old_path = "test_io_uring_renameat_old";
|
const old_path = "test_io_uring_renameat_old";
|
||||||
const new_path = "test_io_uring_renameat_new";
|
const new_path = "test_io_uring_renameat_new";
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
// Write old file with data
|
// Write old file with data
|
||||||
|
|
||||||
const old_file = try std.fs.cwd().createFile(old_path, .{ .truncate = true, .mode = 0o666 });
|
const old_file = try tmp.dir.createFile(old_path, .{ .truncate = true, .mode = 0o666 });
|
||||||
defer {
|
defer old_file.close();
|
||||||
old_file.close();
|
|
||||||
std.fs.cwd().deleteFile(new_path) catch {};
|
|
||||||
}
|
|
||||||
try old_file.writeAll("hello");
|
try old_file.writeAll("hello");
|
||||||
|
|
||||||
// Submit renameat
|
// Submit renameat
|
||||||
|
|
||||||
var sqe = try ring.renameat(
|
var sqe = try ring.renameat(
|
||||||
0x12121212,
|
0x12121212,
|
||||||
linux.AT.FDCWD,
|
tmp.dir.fd,
|
||||||
old_path,
|
old_path,
|
||||||
linux.AT.FDCWD,
|
tmp.dir.fd,
|
||||||
new_path,
|
new_path,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode);
|
try testing.expectEqual(linux.IORING_OP.RENAMEAT, 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(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len));
|
try testing.expectEqual(@as(i32, tmp.dir.fd), @bitCast(i32, sqe.len));
|
||||||
try testing.expectEqual(@as(u32, 1), try ring.submit());
|
try testing.expectEqual(@as(u32, 1), try ring.submit());
|
||||||
|
|
||||||
const cqe = try ring.copy_cqe();
|
const cqe = try ring.copy_cqe();
|
||||||
@ -2618,7 +2649,7 @@ test "renameat" {
|
|||||||
|
|
||||||
// Validate that the old file doesn't exist anymore
|
// 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 => {},
|
error.FileNotFound => {},
|
||||||
else => std.debug.panic("unexpected error: {}", .{err}),
|
else => std.debug.panic("unexpected error: {}", .{err}),
|
||||||
};
|
};
|
||||||
@ -2626,7 +2657,7 @@ test "renameat" {
|
|||||||
|
|
||||||
// Validate that the new file exists with the proper content
|
// 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();
|
defer new_file.close();
|
||||||
|
|
||||||
var new_file_data: [16]u8 = undefined;
|
var new_file_data: [16]u8 = undefined;
|
||||||
@ -2647,22 +2678,24 @@ test "unlinkat" {
|
|||||||
|
|
||||||
const path = "test_io_uring_unlinkat";
|
const path = "test_io_uring_unlinkat";
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
// Write old file with data
|
// 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 file.close();
|
||||||
defer std.fs.cwd().deleteFile(path) catch {};
|
|
||||||
|
|
||||||
// Submit unlinkat
|
// Submit unlinkat
|
||||||
|
|
||||||
var sqe = try ring.unlinkat(
|
var sqe = try ring.unlinkat(
|
||||||
0x12121212,
|
0x12121212,
|
||||||
linux.AT.FDCWD,
|
tmp.dir.fd,
|
||||||
path,
|
path,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode);
|
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());
|
try testing.expectEqual(@as(u32, 1), try ring.submit());
|
||||||
|
|
||||||
const cqe = try ring.copy_cqe();
|
const cqe = try ring.copy_cqe();
|
||||||
@ -2679,7 +2712,7 @@ test "unlinkat" {
|
|||||||
}, cqe);
|
}, cqe);
|
||||||
|
|
||||||
// Validate that the file doesn't exist anymore
|
// 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 => {},
|
error.FileNotFound => {},
|
||||||
else => std.debug.panic("unexpected error: {}", .{err}),
|
else => std.debug.panic("unexpected error: {}", .{err}),
|
||||||
};
|
};
|
||||||
@ -2695,20 +2728,21 @@ test "mkdirat" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
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
|
// Submit mkdirat
|
||||||
|
|
||||||
var sqe = try ring.mkdirat(
|
var sqe = try ring.mkdirat(
|
||||||
0x12121212,
|
0x12121212,
|
||||||
linux.AT.FDCWD,
|
tmp.dir.fd,
|
||||||
path,
|
path,
|
||||||
0o0755,
|
0o0755,
|
||||||
);
|
);
|
||||||
try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode);
|
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());
|
try testing.expectEqual(@as(u32, 1), try ring.submit());
|
||||||
|
|
||||||
const cqe = try ring.copy_cqe();
|
const cqe = try ring.copy_cqe();
|
||||||
@ -2725,7 +2759,7 @@ test "mkdirat" {
|
|||||||
}, cqe);
|
}, cqe);
|
||||||
|
|
||||||
// Validate that the directory exist
|
// Validate that the directory exist
|
||||||
_ = try std.fs.cwd().openDir(path, .{});
|
_ = try tmp.dir.openDir(path, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "symlinkat" {
|
test "symlinkat" {
|
||||||
@ -2738,26 +2772,25 @@ test "symlinkat" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
defer ring.deinit();
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const path = "test_io_uring_symlinkat";
|
const path = "test_io_uring_symlinkat";
|
||||||
const link_path = "test_io_uring_symlinkat_link";
|
const link_path = "test_io_uring_symlinkat_link";
|
||||||
|
|
||||||
const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
|
const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
|
||||||
defer {
|
defer file.close();
|
||||||
file.close();
|
|
||||||
std.fs.cwd().deleteFile(path) catch {};
|
|
||||||
std.fs.cwd().deleteFile(link_path) catch {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Submit symlinkat
|
// Submit symlinkat
|
||||||
|
|
||||||
var sqe = try ring.symlinkat(
|
var sqe = try ring.symlinkat(
|
||||||
0x12121212,
|
0x12121212,
|
||||||
path,
|
path,
|
||||||
linux.AT.FDCWD,
|
tmp.dir.fd,
|
||||||
link_path,
|
link_path,
|
||||||
);
|
);
|
||||||
try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode);
|
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());
|
try testing.expectEqual(@as(u32, 1), try ring.submit());
|
||||||
|
|
||||||
const cqe = try ring.copy_cqe();
|
const cqe = try ring.copy_cqe();
|
||||||
@ -2774,7 +2807,7 @@ test "symlinkat" {
|
|||||||
}, cqe);
|
}, cqe);
|
||||||
|
|
||||||
// Validate that the symlink exist
|
// Validate that the symlink exist
|
||||||
_ = try std.fs.cwd().openFile(link_path, .{});
|
_ = try tmp.dir.openFile(link_path, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "linkat" {
|
test "linkat" {
|
||||||
@ -2787,32 +2820,31 @@ test "linkat" {
|
|||||||
};
|
};
|
||||||
defer ring.deinit();
|
defer ring.deinit();
|
||||||
|
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const first_path = "test_io_uring_linkat_first";
|
const first_path = "test_io_uring_linkat_first";
|
||||||
const second_path = "test_io_uring_linkat_second";
|
const second_path = "test_io_uring_linkat_second";
|
||||||
|
|
||||||
// Write file with data
|
// Write file with data
|
||||||
|
|
||||||
const first_file = try std.fs.cwd().createFile(first_path, .{ .truncate = true, .mode = 0o666 });
|
const first_file = try tmp.dir.createFile(first_path, .{ .truncate = true, .mode = 0o666 });
|
||||||
defer {
|
defer first_file.close();
|
||||||
first_file.close();
|
|
||||||
std.fs.cwd().deleteFile(first_path) catch {};
|
|
||||||
std.fs.cwd().deleteFile(second_path) catch {};
|
|
||||||
}
|
|
||||||
try first_file.writeAll("hello");
|
try first_file.writeAll("hello");
|
||||||
|
|
||||||
// Submit linkat
|
// Submit linkat
|
||||||
|
|
||||||
var sqe = try ring.linkat(
|
var sqe = try ring.linkat(
|
||||||
0x12121212,
|
0x12121212,
|
||||||
linux.AT.FDCWD,
|
tmp.dir.fd,
|
||||||
first_path,
|
first_path,
|
||||||
linux.AT.FDCWD,
|
tmp.dir.fd,
|
||||||
second_path,
|
second_path,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode);
|
try testing.expectEqual(linux.IORING_OP.LINKAT, 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(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len));
|
try testing.expectEqual(@as(i32, tmp.dir.fd), @bitCast(i32, sqe.len));
|
||||||
try testing.expectEqual(@as(u32, 1), try ring.submit());
|
try testing.expectEqual(@as(u32, 1), try ring.submit());
|
||||||
|
|
||||||
const cqe = try ring.copy_cqe();
|
const cqe = try ring.copy_cqe();
|
||||||
@ -2829,7 +2861,7 @@ test "linkat" {
|
|||||||
}, cqe);
|
}, cqe);
|
||||||
|
|
||||||
// Validate the second file
|
// 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();
|
defer second_file.close();
|
||||||
|
|
||||||
var second_file_data: [16]u8 = undefined;
|
var second_file_data: [16]u8 = undefined;
|
||||||
@ -3060,6 +3092,11 @@ test "remove_buffers" {
|
|||||||
test "provide_buffers: accept/connect/send/recv" {
|
test "provide_buffers: accept/connect/send/recv" {
|
||||||
if (builtin.os.tag != .linux) return error.SkipZigTest;
|
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) {
|
var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
|
||||||
error.SystemOutdated => return error.SkipZigTest,
|
error.SystemOutdated => return error.SkipZigTest,
|
||||||
error.PermissionDenied => return error.SkipZigTest,
|
error.PermissionDenied => return error.SkipZigTest,
|
||||||
@ -3236,6 +3273,7 @@ const SocketTestHarness = struct {
|
|||||||
fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
|
fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
|
||||||
// Create a TCP server socket
|
// 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 address = try net.Address.parseIp4("127.0.0.1", 3131);
|
||||||
const kernel_backlog = 1;
|
const kernel_backlog = 1;
|
||||||
const listener_socket = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
|
const listener_socket = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
|
||||||
|
|||||||
@ -8,10 +8,12 @@ const expectEqual = std.testing.expectEqual;
|
|||||||
const fs = std.fs;
|
const fs = std.fs;
|
||||||
|
|
||||||
test "fallocate" {
|
test "fallocate" {
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const path = "test_fallocate";
|
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 file.close();
|
||||||
defer fs.cwd().deleteFile(path) catch {};
|
|
||||||
|
|
||||||
try expect((try file.stat()).size == 0);
|
try expect((try file.stat()).size == 0);
|
||||||
|
|
||||||
@ -67,12 +69,12 @@ test "timer" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "statx" {
|
test "statx" {
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const tmp_file_name = "just_a_temporary_file.txt";
|
const tmp_file_name = "just_a_temporary_file.txt";
|
||||||
var file = try fs.cwd().createFile(tmp_file_name, .{});
|
var file = try tmp.dir.createFile(tmp_file_name, .{});
|
||||||
defer {
|
defer file.close();
|
||||||
file.close();
|
|
||||||
fs.cwd().deleteFile(tmp_file_name) catch {};
|
|
||||||
}
|
|
||||||
|
|
||||||
var statx_buf: linux.Statx = undefined;
|
var statx_buf: linux.Statx = undefined;
|
||||||
switch (linux.getErrno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) {
|
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" {
|
test "fadvise" {
|
||||||
|
var tmp = std.testing.tmpDir(.{});
|
||||||
|
defer tmp.cleanup();
|
||||||
|
|
||||||
const tmp_file_name = "temp_posix_fadvise.txt";
|
const tmp_file_name = "temp_posix_fadvise.txt";
|
||||||
var file = try fs.cwd().createFile(tmp_file_name, .{});
|
var file = try tmp.dir.createFile(tmp_file_name, .{});
|
||||||
defer {
|
defer file.close();
|
||||||
file.close();
|
|
||||||
fs.cwd().deleteFile(tmp_file_name) catch {};
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf: [2048]u8 = undefined;
|
var buf: [2048]u8 = undefined;
|
||||||
try file.writeAll(&buf);
|
try file.writeAll(&buf);
|
||||||
|
|
||||||
const ret = linux.fadvise(
|
const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL);
|
||||||
file.handle,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
linux.POSIX_FADV.SEQUENTIAL,
|
|
||||||
);
|
|
||||||
try expectEqual(@as(usize, 0), ret);
|
try expectEqual(@as(usize, 0), ret);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {};
|
|
||||||
@ -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 {};
|
|
||||||
@ -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.
|
// 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 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(
|
while (true) {
|
||||||
&result,
|
const rc = ntdll.NtCreateFile(
|
||||||
options.access_mask,
|
&result,
|
||||||
&attr,
|
options.access_mask,
|
||||||
&io,
|
&attr,
|
||||||
null,
|
&io,
|
||||||
FILE_ATTRIBUTE_NORMAL,
|
null,
|
||||||
options.share_access,
|
FILE_ATTRIBUTE_NORMAL,
|
||||||
options.creation,
|
options.share_access,
|
||||||
flags,
|
options.creation,
|
||||||
null,
|
flags,
|
||||||
0,
|
null,
|
||||||
);
|
0,
|
||||||
switch (rc) {
|
);
|
||||||
.SUCCESS => {
|
switch (rc) {
|
||||||
if (std.io.is_async and options.io_mode == .evented) {
|
.SUCCESS => {
|
||||||
_ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined;
|
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;
|
}
|
||||||
},
|
return result;
|
||||||
.OBJECT_NAME_INVALID => unreachable,
|
},
|
||||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
.OBJECT_NAME_INVALID => unreachable,
|
||||||
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||||
.NO_MEDIA_IN_DEVICE => return error.NoDevice,
|
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
||||||
.INVALID_PARAMETER => unreachable,
|
.NO_MEDIA_IN_DEVICE => return error.NoDevice,
|
||||||
.SHARING_VIOLATION => return error.AccessDenied,
|
.INVALID_PARAMETER => unreachable,
|
||||||
.ACCESS_DENIED => return error.AccessDenied,
|
.SHARING_VIOLATION => return error.AccessDenied,
|
||||||
.PIPE_BUSY => return error.PipeBusy,
|
.ACCESS_DENIED => return error.AccessDenied,
|
||||||
.OBJECT_PATH_SYNTAX_BAD => unreachable,
|
.PIPE_BUSY => return error.PipeBusy,
|
||||||
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
|
.OBJECT_PATH_SYNTAX_BAD => unreachable,
|
||||||
.FILE_IS_A_DIRECTORY => return error.IsDir,
|
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
|
||||||
.NOT_A_DIRECTORY => return error.NotDir,
|
.FILE_IS_A_DIRECTORY => return error.IsDir,
|
||||||
.USER_MAPPED_FILE => return error.AccessDenied,
|
.NOT_A_DIRECTORY => return error.NotDir,
|
||||||
.INVALID_HANDLE => unreachable,
|
.USER_MAPPED_FILE => return error.AccessDenied,
|
||||||
else => return unexpectedStatus(rc),
|
.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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,8 @@ pub extern "advapi32" fn RegQueryValueExW(
|
|||||||
lpcbData: ?*DWORD,
|
lpcbData: ?*DWORD,
|
||||||
) callconv(WINAPI) LSTATUS;
|
) callconv(WINAPI) LSTATUS;
|
||||||
|
|
||||||
|
pub extern "advapi32" fn RegCloseKey(hKey: HKEY) callconv(WINAPI) LSTATUS;
|
||||||
|
|
||||||
// RtlGenRandom is known as SystemFunction036 under advapi32
|
// RtlGenRandom is known as SystemFunction036 under advapi32
|
||||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx */
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx */
|
||||||
pub extern "advapi32" fn SystemFunction036(output: [*]u8, length: ULONG) callconv(WINAPI) BOOL;
|
pub extern "advapi32" fn SystemFunction036(output: [*]u8, length: ULONG) callconv(WINAPI) BOOL;
|
||||||
|
|||||||
@ -67,6 +67,7 @@ const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;
|
|||||||
const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
|
const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
|
||||||
const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;
|
const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;
|
||||||
const MODULEENTRY32 = windows.MODULEENTRY32;
|
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 AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*anyopaque;
|
||||||
pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong;
|
pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong;
|
||||||
@ -457,3 +458,5 @@ pub extern "kernel32" fn RegOpenKeyExW(
|
|||||||
samDesired: REGSAM,
|
samDesired: REGSAM,
|
||||||
phkResult: *HKEY,
|
phkResult: *HKEY,
|
||||||
) callconv(WINAPI) LSTATUS;
|
) callconv(WINAPI) LSTATUS;
|
||||||
|
|
||||||
|
pub extern "kernel32" fn GetPhysicallyInstalledSystemMemory(TotalMemoryInKilobytes: *ULONGLONG) BOOL;
|
||||||
|
|||||||
@ -828,24 +828,6 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator
|
|||||||
return ArgIterator.initWithAllocator(allocator);
|
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.
|
/// Caller must call argsFree on result.
|
||||||
pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
|
pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
|
||||||
// TODO refactor to only make 1 allocation.
|
// 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);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -185,6 +185,16 @@ pub const options = struct {
|
|||||||
options_override.keep_sigpipe
|
options_override.keep_sigpipe
|
||||||
else
|
else
|
||||||
false;
|
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
|
// This forces the start.zig file to be imported, and the comptime logic inside that
|
||||||
|
|||||||
@ -724,11 +724,7 @@ pub const Target = struct {
|
|||||||
|
|
||||||
/// Adds the specified feature set but not its dependencies.
|
/// Adds the specified feature set but not its dependencies.
|
||||||
pub fn addFeatureSet(set: *Set, other_set: Set) void {
|
pub fn addFeatureSet(set: *Set, other_set: Set) void {
|
||||||
if (builtin.zig_backend == .stage2_c) {
|
set.ints = @as(@Vector(usize_count, usize), set.ints) | @as(@Vector(usize_count, usize), other_set.ints);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the specified feature but not its dependents.
|
/// Removes the specified feature but not its dependents.
|
||||||
@ -740,11 +736,7 @@ pub const Target = struct {
|
|||||||
|
|
||||||
/// Removes the specified feature but not its dependents.
|
/// Removes the specified feature but not its dependents.
|
||||||
pub fn removeFeatureSet(set: *Set, other_set: Set) void {
|
pub fn removeFeatureSet(set: *Set, other_set: Set) void {
|
||||||
if (builtin.zig_backend == .stage2_c) {
|
set.ints = @as(@Vector(usize_count, usize), set.ints) & ~@as(@Vector(usize_count, usize), other_set.ints);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void {
|
pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void {
|
||||||
|
|||||||
@ -3,6 +3,9 @@ const tokenizer = @import("zig/tokenizer.zig");
|
|||||||
const fmt = @import("zig/fmt.zig");
|
const fmt = @import("zig/fmt.zig");
|
||||||
const assert = std.debug.assert;
|
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 Token = tokenizer.Token;
|
||||||
pub const Tokenizer = tokenizer.Tokenizer;
|
pub const Tokenizer = tokenizer.Tokenizer;
|
||||||
pub const fmtId = fmt.fmtId;
|
pub const fmtId = fmt.fmtId;
|
||||||
|
|||||||
@ -1407,7 +1407,8 @@ pub fn containerField(tree: Ast, node: Node.Index) full.ContainerField {
|
|||||||
.type_expr = data.lhs,
|
.type_expr = data.lhs,
|
||||||
.value_expr = extra.value_expr,
|
.value_expr = extra.value_expr,
|
||||||
.align_expr = extra.align_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,
|
.type_expr = data.lhs,
|
||||||
.value_expr = data.rhs,
|
.value_expr = data.rhs,
|
||||||
.align_expr = 0,
|
.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,
|
.type_expr = data.lhs,
|
||||||
.value_expr = 0,
|
.value_expr = 0,
|
||||||
.align_expr = data.rhs,
|
.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
39
lib/std/zig/Client.zig
Normal 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
515
lib/std/zig/ErrorBundle.zig
Normal 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
305
lib/std/zig/Server.zig
Normal 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;
|
||||||
@ -1090,6 +1090,11 @@ pub fn getExternalExecutor(
|
|||||||
switch (candidate.target.os.tag) {
|
switch (candidate.target.os.tag) {
|
||||||
.windows => {
|
.windows => {
|
||||||
if (options.allow_wine) {
|
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()) {
|
switch (candidate.target.cpu.arch.ptrBitWidth()) {
|
||||||
32 => return Executor{ .wine = "wine" },
|
32 => return Executor{ .wine = "wine" },
|
||||||
64 => return Executor{ .wine = "wine64" },
|
64 => return Executor{ .wine = "wine64" },
|
||||||
|
|||||||
@ -8,14 +8,126 @@ pub const std_options = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var log_err_count: usize = 0;
|
var log_err_count: usize = 0;
|
||||||
|
var cmdline_buffer: [4096]u8 = undefined;
|
||||||
|
var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
if (builtin.zig_backend != .stage1 and
|
if (builtin.zig_backend == .stage2_wasm or
|
||||||
builtin.zig_backend != .stage2_llvm and
|
builtin.zig_backend == .stage2_x86_64 or
|
||||||
builtin.zig_backend != .stage2_c)
|
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;
|
const test_fn_list = builtin.test_functions;
|
||||||
var ok_count: usize = 0;
|
var ok_count: usize = 0;
|
||||||
var skip_count: usize = 0;
|
var skip_count: usize = 0;
|
||||||
@ -118,51 +230,17 @@ pub fn log(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main2() anyerror!void {
|
/// Simpler main(), exercising fewer language features, so that
|
||||||
var skipped: usize = 0;
|
/// work-in-progress backends can handle it.
|
||||||
var failed: usize = 0;
|
pub fn mainSimple() anyerror!void {
|
||||||
// Simpler main(), exercising fewer language features, so that stage2 can handle it.
|
//const stderr = std.io.getStdErr();
|
||||||
for (builtin.test_functions) |test_fn| {
|
for (builtin.test_functions) |test_fn| {
|
||||||
test_fn.func() catch |err| {
|
test_fn.func() catch |err| {
|
||||||
if (err != error.SkipZigTest) {
|
if (err != error.SkipZigTest) {
|
||||||
failed += 1;
|
//stderr.writeAll(test_fn.name) catch {};
|
||||||
} else {
|
//stderr.writeAll("\n") catch {};
|
||||||
skipped += 1;
|
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/Air.zig
10
src/Air.zig
@ -232,7 +232,14 @@ pub const Inst = struct {
|
|||||||
/// Result type is always noreturn; no instructions in a block follow this one.
|
/// Result type is always noreturn; no instructions in a block follow this one.
|
||||||
/// Uses the `br` field.
|
/// Uses the `br` field.
|
||||||
br,
|
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.
|
/// Result type is always void.
|
||||||
breakpoint,
|
breakpoint,
|
||||||
/// Yields the return address of the current function.
|
/// Yields the return address of the current function.
|
||||||
@ -1186,6 +1193,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
|
|||||||
.ret,
|
.ret,
|
||||||
.ret_load,
|
.ret_load,
|
||||||
.unreach,
|
.unreach,
|
||||||
|
.trap,
|
||||||
=> return Type.initTag(.noreturn),
|
=> return Type.initTag(.noreturn),
|
||||||
|
|
||||||
.breakpoint,
|
.breakpoint,
|
||||||
|
|||||||
243
src/AstGen.zig
243
src/AstGen.zig
@ -148,18 +148,24 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
|
|||||||
};
|
};
|
||||||
defer gz_instructions.deinit(gpa);
|
defer gz_instructions.deinit(gpa);
|
||||||
|
|
||||||
if (AstGen.structDeclInner(
|
// The AST -> ZIR lowering process assumes an AST that does not have any
|
||||||
&gen_scope,
|
// parse errors.
|
||||||
&gen_scope.base,
|
if (tree.errors.len == 0) {
|
||||||
0,
|
if (AstGen.structDeclInner(
|
||||||
tree.containerDeclRoot(),
|
&gen_scope,
|
||||||
.Auto,
|
&gen_scope.base,
|
||||||
0,
|
0,
|
||||||
)) |struct_decl_ref| {
|
tree.containerDeclRoot(),
|
||||||
assert(refToIndex(struct_decl_ref).? == 0);
|
.Auto,
|
||||||
} else |err| switch (err) {
|
0,
|
||||||
error.OutOfMemory => return error.OutOfMemory,
|
)) |struct_decl_ref| {
|
||||||
error.AnalysisFail => {}, // Handled via compile_errors below.
|
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);
|
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.instructions.deinit(gpa);
|
||||||
astgen.extra.deinit(gpa);
|
astgen.extra.deinit(gpa);
|
||||||
astgen.string_table.deinit(gpa);
|
astgen.string_table.deinit(gpa);
|
||||||
@ -216,7 +222,7 @@ pub fn deinit(astgen: *AstGen, gpa: Allocator) void {
|
|||||||
astgen.ref_table.deinit(gpa);
|
astgen.ref_table.deinit(gpa);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ResultInfo = struct {
|
const ResultInfo = struct {
|
||||||
/// The semantics requested for the result location
|
/// The semantics requested for the result location
|
||||||
rl: Loc,
|
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
|
/// 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
|
/// expression should be generated. The result instruction from the expression must
|
||||||
/// be ignored.
|
/// be ignored.
|
||||||
@ -277,11 +283,11 @@ pub const ResultInfo = struct {
|
|||||||
src_node: ?Ast.Node.Index = null,
|
src_node: ?Ast.Node.Index = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Strategy = struct {
|
const Strategy = struct {
|
||||||
elide_store_to_block_ptr_instructions: bool,
|
elide_store_to_block_ptr_instructions: bool,
|
||||||
tag: Tag,
|
tag: Tag,
|
||||||
|
|
||||||
pub const Tag = enum {
|
const Tag = enum {
|
||||||
/// Both branches will use break_void; result location is used to communicate the
|
/// Both branches will use break_void; result location is used to communicate the
|
||||||
/// result instruction.
|
/// result instruction.
|
||||||
break_void,
|
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.
|
/// The expression is the operand to a return expression.
|
||||||
@"return",
|
@"return",
|
||||||
/// The expression is the input to an error-handling operator (if-else, try, or catch).
|
/// 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 } };
|
const align_ri: ResultInfo = .{ .rl = .{ .ty = .u29_type } };
|
||||||
pub const coerced_align_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .u29_type } };
|
const coerced_align_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .u29_type } };
|
||||||
pub const bool_ri: ResultInfo = .{ .rl = .{ .ty = .bool_type } };
|
const bool_ri: ResultInfo = .{ .rl = .{ .ty = .bool_type } };
|
||||||
pub const type_ri: ResultInfo = .{ .rl = .{ .ty = .type_type } };
|
const type_ri: ResultInfo = .{ .rl = .{ .ty = .type_type } };
|
||||||
pub const coerced_type_ri: ResultInfo = .{ .rl = .{ .coerced_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 {
|
fn typeExpr(gz: *GenZir, scope: *Scope, type_node: Ast.Node.Index) InnerError!Zir.Inst.Ref {
|
||||||
const prev_force_comptime = gz.force_comptime;
|
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
|
else
|
||||||
.@"break";
|
.@"break";
|
||||||
|
|
||||||
|
block_gz.break_count += 1;
|
||||||
if (rhs == 0) {
|
if (rhs == 0) {
|
||||||
|
_ = try rvalue(parent_gz, block_gz.break_result_info, .void_value, node);
|
||||||
|
|
||||||
try genDefers(parent_gz, scope, parent_scope, .normal_only);
|
try genDefers(parent_gz, scope, parent_scope, .normal_only);
|
||||||
|
|
||||||
// As our last action before the break, "pop" the error trace if needed
|
// 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);
|
_ = try parent_gz.addBreak(break_tag, block_inst, .void_value);
|
||||||
return Zir.Inst.Ref.unreachable_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 operand = try reachableExpr(parent_gz, parent_scope, block_gz.break_result_info, rhs, node);
|
||||||
const search_index = @intCast(Zir.Inst.Index, astgen.instructions.len);
|
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) {
|
.extended => switch (gz.astgen.instructions.items(.data)[inst].extended.opcode) {
|
||||||
.breakpoint,
|
.breakpoint,
|
||||||
.fence,
|
.fence,
|
||||||
.set_align_stack,
|
|
||||||
.set_float_mode,
|
.set_float_mode,
|
||||||
|
.set_align_stack,
|
||||||
|
.set_cold,
|
||||||
=> break :b true,
|
=> break :b true,
|
||||||
else => break :b false,
|
else => break :b false,
|
||||||
},
|
},
|
||||||
@ -2630,6 +2639,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
|
|||||||
.repeat_inline,
|
.repeat_inline,
|
||||||
.panic,
|
.panic,
|
||||||
.panic_comptime,
|
.panic_comptime,
|
||||||
|
.trap,
|
||||||
.check_comptime_control_flow,
|
.check_comptime_control_flow,
|
||||||
=> {
|
=> {
|
||||||
noreturn_src_node = statement;
|
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_struct_init_comptime,
|
||||||
.validate_array_init,
|
.validate_array_init,
|
||||||
.validate_array_init_comptime,
|
.validate_array_init_comptime,
|
||||||
.set_cold,
|
|
||||||
.set_runtime_safety,
|
.set_runtime_safety,
|
||||||
.closure_capture,
|
.closure_capture,
|
||||||
.memcpy,
|
.memcpy,
|
||||||
@ -3506,7 +3515,7 @@ const WipMembers = struct {
|
|||||||
/// (4 for src_hash + line + name + value + doc_comment + align + link_section + address_space )
|
/// (4 for src_hash + line + name + value + doc_comment + align + link_section + address_space )
|
||||||
const max_decl_size = 11;
|
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 payload_top = @intCast(u32, payload.items.len);
|
||||||
const decls_start = payload_top + (decl_count + decls_per_u32 - 1) / decls_per_u32;
|
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;
|
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;
|
const index = self.payload_top + self.decl_index / decls_per_u32;
|
||||||
assert(index < self.decls_start);
|
assert(index < self.decls_start);
|
||||||
const bit_bag: u32 = if (self.decl_index % decls_per_u32 == 0) 0 else self.payload.items[index];
|
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;
|
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 fields_per_u32 = 32 / bits_per_field;
|
||||||
const index = self.field_bits_start + self.field_index / fields_per_u32;
|
const index = self.field_bits_start + self.field_index / fields_per_u32;
|
||||||
assert(index < self.fields_start);
|
assert(index < self.fields_start);
|
||||||
@ -3553,25 +3562,25 @@ const WipMembers = struct {
|
|||||||
self.field_index += 1;
|
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);
|
assert(self.decls_end < self.field_bits_start);
|
||||||
self.payload.items[self.decls_end] = data;
|
self.payload.items[self.decls_end] = data;
|
||||||
self.decls_end += 1;
|
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);
|
assert(self.decls_end + data.len <= self.field_bits_start);
|
||||||
mem.copy(u32, self.payload.items[self.decls_end..], data);
|
mem.copy(u32, self.payload.items[self.decls_end..], data);
|
||||||
self.decls_end += @intCast(u32, data.len);
|
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);
|
assert(self.fields_end < self.payload.items.len);
|
||||||
self.payload.items[self.fields_end] = data;
|
self.payload.items[self.fields_end] = data;
|
||||||
self.fields_end += 1;
|
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);
|
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) {
|
if (self.decl_index > 0 and empty_decl_slots < decls_per_u32) {
|
||||||
const index = self.payload_top + self.decl_index / 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];
|
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];
|
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;
|
self.payload.items.len = self.payload_top;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -6583,6 +6592,9 @@ fn forExpr(
|
|||||||
cond_block,
|
cond_block,
|
||||||
break_tag,
|
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) {
|
if (is_statement) {
|
||||||
_ = try parent_gz.addUnNode(.ensure_result_used, result, node);
|
_ = try parent_gz.addUnNode(.ensure_result_used, result, node);
|
||||||
}
|
}
|
||||||
@ -7976,6 +7988,9 @@ fn builtinCall(
|
|||||||
switch (node_tags[params[0]]) {
|
switch (node_tags[params[0]]) {
|
||||||
.identifier => {
|
.identifier => {
|
||||||
const ident_token = main_tokens[params[0]];
|
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);
|
decl_name = try astgen.identAsString(ident_token);
|
||||||
|
|
||||||
var s = scope;
|
var s = scope;
|
||||||
@ -8056,27 +8071,35 @@ fn builtinCall(
|
|||||||
},
|
},
|
||||||
.fence => {
|
.fence => {
|
||||||
const order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[0]);
|
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),
|
.node = gz.nodeIndexToRelative(node),
|
||||||
.operand = order,
|
.operand = order,
|
||||||
});
|
});
|
||||||
return rvalue(gz, ri, result, node);
|
return rvalue(gz, ri, .void_value, node);
|
||||||
},
|
},
|
||||||
.set_float_mode => {
|
.set_float_mode => {
|
||||||
const order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .float_mode_type } }, params[0]);
|
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),
|
.node = gz.nodeIndexToRelative(node),
|
||||||
.operand = order,
|
.operand = order,
|
||||||
});
|
});
|
||||||
return rvalue(gz, ri, result, node);
|
return rvalue(gz, ri, .void_value, node);
|
||||||
},
|
},
|
||||||
.set_align_stack => {
|
.set_align_stack => {
|
||||||
const order = try expr(gz, scope, align_ri, params[0]);
|
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),
|
.node = gz.nodeIndexToRelative(node),
|
||||||
.operand = order,
|
.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 => {
|
.src => {
|
||||||
@ -8097,7 +8120,7 @@ fn builtinCall(
|
|||||||
.error_return_trace => return rvalue(gz, ri, try gz.addNodeExtended(.error_return_trace, node), node),
|
.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 => return rvalue(gz, ri, try gz.addNodeExtended(.frame, node), node),
|
||||||
.frame_address => return rvalue(gz, ri, try gz.addNodeExtended(.frame_address, 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),
|
.type_info => return simpleUnOpType(gz, scope, ri, node, params[0], .type_info),
|
||||||
.size_of => return simpleUnOpType(gz, scope, ri, node, params[0], .size_of),
|
.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),
|
.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),
|
.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),
|
.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),
|
.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),
|
.sqrt => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .sqrt),
|
||||||
.sin => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .sin),
|
.sin => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .sin),
|
||||||
@ -8171,6 +8193,11 @@ fn builtinCall(
|
|||||||
try emitDbgNode(gz, node);
|
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);
|
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 => {
|
.error_to_int => {
|
||||||
const operand = try expr(gz, scope, .{ .rl = .none }, params[0]);
|
const operand = try expr(gz, scope, .{ .rl = .none }, params[0]);
|
||||||
const result = try gz.addExtendedPayload(.error_to_int, Zir.Inst.UnNode{
|
const result = try gz.addExtendedPayload(.error_to_int, Zir.Inst.UnNode{
|
||||||
@ -8357,14 +8384,14 @@ fn builtinCall(
|
|||||||
},
|
},
|
||||||
.atomic_store => {
|
.atomic_store => {
|
||||||
const int_type = try typeExpr(gz, scope, params[0]);
|
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
|
// zig fmt: off
|
||||||
.ptr = try expr(gz, scope, .{ .rl = .none }, params[1]),
|
.ptr = try expr(gz, scope, .{ .rl = .none }, params[1]),
|
||||||
.operand = try expr(gz, scope, .{ .rl = .{ .ty = int_type } }, params[2]),
|
.operand = try expr(gz, scope, .{ .rl = .{ .ty = int_type } }, params[2]),
|
||||||
.ordering = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[3]),
|
.ordering = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[3]),
|
||||||
// zig fmt: on
|
// zig fmt: on
|
||||||
});
|
});
|
||||||
return rvalue(gz, ri, result, node);
|
return rvalue(gz, ri, .void_value, node);
|
||||||
},
|
},
|
||||||
.mul_add => {
|
.mul_add => {
|
||||||
const float_type = try typeExpr(gz, scope, params[0]);
|
const float_type = try typeExpr(gz, scope, params[0]);
|
||||||
@ -8405,20 +8432,20 @@ fn builtinCall(
|
|||||||
return rvalue(gz, ri, result, node);
|
return rvalue(gz, ri, result, node);
|
||||||
},
|
},
|
||||||
.memcpy => {
|
.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]),
|
.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]),
|
.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]),
|
.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 => {
|
.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]),
|
.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 = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .u8_type } }, params[1]),
|
||||||
.byte_count = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, params[2]),
|
.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 => {
|
.shuffle => {
|
||||||
const result = try gz.addPlNode(.shuffle, node, Zir.Inst.Shuffle{
|
const result = try gz.addPlNode(.shuffle, node, Zir.Inst.Shuffle{
|
||||||
@ -8459,12 +8486,12 @@ fn builtinCall(
|
|||||||
.prefetch => {
|
.prefetch => {
|
||||||
const ptr = try expr(gz, scope, .{ .rl = .none }, params[0]);
|
const ptr = try expr(gz, scope, .{ .rl = .none }, params[0]);
|
||||||
const options = try comptimeExpr(gz, scope, .{ .rl = .{ .ty = .prefetch_options_type } }, params[1]);
|
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),
|
.node = gz.nodeIndexToRelative(node),
|
||||||
.lhs = ptr,
|
.lhs = ptr,
|
||||||
.rhs = options,
|
.rhs = options,
|
||||||
});
|
});
|
||||||
return rvalue(gz, ri, result, node);
|
return rvalue(gz, ri, .void_value, node);
|
||||||
},
|
},
|
||||||
.c_va_arg => {
|
.c_va_arg => {
|
||||||
if (astgen.fn_block == null) {
|
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(
|
fn hasDeclOrField(
|
||||||
gz: *GenZir,
|
gz: *GenZir,
|
||||||
scope: *Scope,
|
scope: *Scope,
|
||||||
@ -8988,7 +9005,7 @@ const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
|
|||||||
});
|
});
|
||||||
|
|
||||||
comptime {
|
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;
|
const primitives = std.zig.primitives;
|
||||||
for (primitive_instrs.kvs) |kv| {
|
for (primitive_instrs.kvs) |kv| {
|
||||||
if (!primitives.isPrimitive(kv.key)) {
|
if (!primitives.isPrimitive(kv.key)) {
|
||||||
@ -10369,7 +10386,7 @@ fn appendErrorTok(
|
|||||||
comptime format: []const u8,
|
comptime format: []const u8,
|
||||||
args: anytype,
|
args: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
try astgen.appendErrorTokNotes(token, format, args, &[0]u32{});
|
try astgen.appendErrorTokNotesOff(token, 0, format, args, &[0]u32{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn failTokNotes(
|
fn failTokNotes(
|
||||||
@ -10379,7 +10396,7 @@ fn failTokNotes(
|
|||||||
args: anytype,
|
args: anytype,
|
||||||
notes: []const u32,
|
notes: []const u32,
|
||||||
) InnerError {
|
) InnerError {
|
||||||
try appendErrorTokNotes(astgen, token, format, args, notes);
|
try appendErrorTokNotesOff(astgen, token, 0, format, args, notes);
|
||||||
return error.AnalysisFail;
|
return error.AnalysisFail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10390,27 +10407,11 @@ fn appendErrorTokNotes(
|
|||||||
args: anytype,
|
args: anytype,
|
||||||
notes: []const u32,
|
notes: []const u32,
|
||||||
) !void {
|
) !void {
|
||||||
@setCold(true);
|
return appendErrorTokNotesOff(astgen, token, 0, format, args, notes);
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(
|
fn failOff(
|
||||||
astgen: *AstGen,
|
astgen: *AstGen,
|
||||||
token: Ast.TokenIndex,
|
token: Ast.TokenIndex,
|
||||||
@ -10418,27 +10419,36 @@ fn failOff(
|
|||||||
comptime format: []const u8,
|
comptime format: []const u8,
|
||||||
args: anytype,
|
args: anytype,
|
||||||
) InnerError {
|
) InnerError {
|
||||||
try appendErrorOff(astgen, token, byte_offset, format, args);
|
try appendErrorTokNotesOff(astgen, token, byte_offset, format, args, &.{});
|
||||||
return error.AnalysisFail;
|
return error.AnalysisFail;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn appendErrorOff(
|
fn appendErrorTokNotesOff(
|
||||||
astgen: *AstGen,
|
astgen: *AstGen,
|
||||||
token: Ast.TokenIndex,
|
token: Ast.TokenIndex,
|
||||||
byte_offset: u32,
|
byte_offset: u32,
|
||||||
comptime format: []const u8,
|
comptime format: []const u8,
|
||||||
args: anytype,
|
args: anytype,
|
||||||
) Allocator.Error!void {
|
notes: []const u32,
|
||||||
|
) !void {
|
||||||
@setCold(true);
|
@setCold(true);
|
||||||
|
const gpa = astgen.gpa;
|
||||||
const string_bytes = &astgen.string_bytes;
|
const string_bytes = &astgen.string_bytes;
|
||||||
const msg = @intCast(u32, string_bytes.items.len);
|
const msg = @intCast(u32, string_bytes.items.len);
|
||||||
try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);
|
try string_bytes.writer(gpa).print(format ++ "\x00", args);
|
||||||
try astgen.compile_errors.append(astgen.gpa, .{
|
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,
|
.msg = msg,
|
||||||
.node = 0,
|
.node = 0,
|
||||||
.token = token,
|
.token = token,
|
||||||
.byte_offset = byte_offset,
|
.byte_offset = byte_offset,
|
||||||
.notes = 0,
|
.notes = notes_index,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10447,6 +10457,16 @@ fn errNoteTok(
|
|||||||
token: Ast.TokenIndex,
|
token: Ast.TokenIndex,
|
||||||
comptime format: []const u8,
|
comptime format: []const u8,
|
||||||
args: anytype,
|
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 {
|
) Allocator.Error!u32 {
|
||||||
@setCold(true);
|
@setCold(true);
|
||||||
const string_bytes = &astgen.string_bytes;
|
const string_bytes = &astgen.string_bytes;
|
||||||
@ -10456,7 +10476,7 @@ fn errNoteTok(
|
|||||||
.msg = msg,
|
.msg = msg,
|
||||||
.node = 0,
|
.node = 0,
|
||||||
.token = token,
|
.token = token,
|
||||||
.byte_offset = 0,
|
.byte_offset = byte_offset,
|
||||||
.notes = 0,
|
.notes = 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -10787,7 +10807,7 @@ const Scope = struct {
|
|||||||
/// ref of the capture for decls in this namespace
|
/// ref of the capture for decls in this namespace
|
||||||
captures: std.AutoArrayHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{},
|
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.decls.deinit(gpa);
|
||||||
self.captures.deinit(gpa);
|
self.captures.deinit(gpa);
|
||||||
self.* = undefined;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@ -1338,7 +1338,6 @@ fn walkInstruction(
|
|||||||
.embed_file,
|
.embed_file,
|
||||||
.error_name,
|
.error_name,
|
||||||
.panic,
|
.panic,
|
||||||
.set_cold, // @check
|
|
||||||
.set_runtime_safety, // @check
|
.set_runtime_safety, // @check
|
||||||
.sqrt,
|
.sqrt,
|
||||||
.sin,
|
.sin,
|
||||||
|
|||||||
@ -109,6 +109,7 @@ pub const Tag = enum {
|
|||||||
sub_with_overflow,
|
sub_with_overflow,
|
||||||
tag_name,
|
tag_name,
|
||||||
This,
|
This,
|
||||||
|
trap,
|
||||||
truncate,
|
truncate,
|
||||||
Type,
|
Type,
|
||||||
type_info,
|
type_info,
|
||||||
@ -915,6 +916,13 @@ pub const list = list: {
|
|||||||
.param_count = 0,
|
.param_count = 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
.{
|
||||||
|
"@trap",
|
||||||
|
.{
|
||||||
|
.tag = .trap,
|
||||||
|
.param_count = 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
.{
|
.{
|
||||||
"@truncate",
|
"@truncate",
|
||||||
.{
|
.{
|
||||||
|
|||||||
1032
src/Compilation.zig
1032
src/Compilation.zig
File diff suppressed because it is too large
Load Diff
@ -226,6 +226,7 @@ pub fn categorizeOperand(
|
|||||||
.ret_ptr,
|
.ret_ptr,
|
||||||
.constant,
|
.constant,
|
||||||
.const_ty,
|
.const_ty,
|
||||||
|
.trap,
|
||||||
.breakpoint,
|
.breakpoint,
|
||||||
.dbg_stmt,
|
.dbg_stmt,
|
||||||
.dbg_inline_begin,
|
.dbg_inline_begin,
|
||||||
@ -848,6 +849,7 @@ fn analyzeInst(
|
|||||||
.ret_ptr,
|
.ret_ptr,
|
||||||
.constant,
|
.constant,
|
||||||
.const_ty,
|
.const_ty,
|
||||||
|
.trap,
|
||||||
.breakpoint,
|
.breakpoint,
|
||||||
.dbg_stmt,
|
.dbg_stmt,
|
||||||
.dbg_inline_begin,
|
.dbg_inline_begin,
|
||||||
|
|||||||
193
src/Module.zig
193
src/Module.zig
@ -3528,6 +3528,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
|
|||||||
const digest = hash: {
|
const digest = hash: {
|
||||||
var path_hash: Cache.HashHelper = .{};
|
var path_hash: Cache.HashHelper = .{};
|
||||||
path_hash.addBytes(build_options.version);
|
path_hash.addBytes(build_options.version);
|
||||||
|
path_hash.add(builtin.zig_backend);
|
||||||
if (!want_local_cache) {
|
if (!want_local_cache) {
|
||||||
path_hash.addOptionalBytes(file.pkg.root_src_directory.path);
|
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 cache_directory = if (want_local_cache) mod.local_zir_cache else mod.global_zir_cache;
|
||||||
const zir_dir = cache_directory.handle;
|
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.
|
// Determine whether we need to reload the file from disk and redo parsing and AstGen.
|
||||||
switch (file.status) {
|
var lock: std.fs.File.Lock = switch (file.status) {
|
||||||
.never_loaded, .retryable_failure => cached: {
|
.never_loaded, .retryable_failure => lock: {
|
||||||
// First, load the cached ZIR code, if any.
|
// First, load the cached ZIR code, if any.
|
||||||
log.debug("AstGen checking cache: {s} (local={}, digest={s})", .{
|
log.debug("AstGen checking cache: {s} (local={}, digest={s})", .{
|
||||||
file.sub_file_path, want_local_cache, &digest,
|
file.sub_file_path, want_local_cache, &digest,
|
||||||
});
|
});
|
||||||
|
|
||||||
// We ask for a lock in order to coordinate with other zig processes.
|
break :lock .Shared;
|
||||||
// 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
|
.parse_failure, .astgen_failure, .success_zir => lock: {
|
||||||
// the cached file, they'll get it.
|
const unchanged_metadata =
|
||||||
cache_file = zir_dir.openFile(&digest, .{ .lock = .Shared }) catch |err| switch (err) {
|
stat.size == file.stat.size and
|
||||||
error.PathAlreadyExists => unreachable, // opening for reading
|
stat.mtime == file.stat.mtime and
|
||||||
error.NoSpaceLeft => unreachable, // opening for reading
|
stat.inode == file.stat.inode;
|
||||||
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.SymLinkLoop,
|
if (unchanged_metadata) {
|
||||||
error.FileNotFound,
|
log.debug("unmodified metadata of file: {s}", .{file.sub_file_path});
|
||||||
error.Unexpected,
|
return;
|
||||||
=> break :cached,
|
}
|
||||||
|
|
||||||
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.
|
// 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
|
// This can happen if Zig bails out of this function between creating
|
||||||
// the cached file and writing it.
|
// the cached file and writing it.
|
||||||
error.EndOfStream => break :cached,
|
error.EndOfStream => break :update,
|
||||||
else => |e| return e,
|
else => |e| return e,
|
||||||
};
|
};
|
||||||
const unchanged_metadata =
|
const unchanged_metadata =
|
||||||
@ -3584,7 +3607,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
|
|||||||
|
|
||||||
if (!unchanged_metadata) {
|
if (!unchanged_metadata) {
|
||||||
log.debug("AstGen cache stale: {s}", .{file.sub_file_path});
|
log.debug("AstGen cache stale: {s}", .{file.sub_file_path});
|
||||||
break :cached;
|
break :update;
|
||||||
}
|
}
|
||||||
log.debug("AstGen cache hit: {s} instructions_len={d}", .{
|
log.debug("AstGen cache hit: {s} instructions_len={d}", .{
|
||||||
file.sub_file_path, header.instructions_len,
|
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,
|
.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 +
|
const amt_expected = zir.instructions.len * 9 +
|
||||||
zir.string_bytes.len +
|
zir.string_bytes.len +
|
||||||
zir.extra.len * 4;
|
zir.extra.len * 4;
|
||||||
if (amt_read != amt_expected) {
|
if (amt_read != amt_expected) {
|
||||||
log.warn("unexpected EOF reading cached ZIR for {s}", .{file.sub_file_path});
|
log.warn("unexpected EOF reading cached ZIR for {s}", .{file.sub_file_path});
|
||||||
break :cached;
|
break :update;
|
||||||
}
|
}
|
||||||
if (data_has_safety_tag) {
|
if (data_has_safety_tag) {
|
||||||
const tags = zir.instructions.items(.tag);
|
const tags = zir.instructions.items(.tag);
|
||||||
@ -3678,42 +3701,22 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
|
|||||||
return error.AnalysisFail;
|
return error.AnalysisFail;
|
||||||
}
|
}
|
||||||
return;
|
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) {
|
// If we already have the exclusive lock then it is our job to update.
|
||||||
log.debug("unmodified metadata of file: {s}", .{file.sub_file_path});
|
if (builtin.os.tag == .wasi or lock == .Exclusive) break;
|
||||||
return;
|
// Otherwise, unlock to give someone a chance to get the exclusive lock
|
||||||
}
|
// and then upgrade to an exclusive lock.
|
||||||
|
cache_file.unlock();
|
||||||
log.debug("metadata changed: {s}", .{file.sub_file_path});
|
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| {
|
// The cache is definitely stale so delete the contents to avoid an underwrite later.
|
||||||
const pkg_path = file.pkg.root_src_directory.path orelse ".";
|
cache_file.setEndPos(0) catch |err| switch (err) {
|
||||||
const cache_path = cache_directory.path orelse ".";
|
error.FileTooBig => unreachable, // 0 is not too big
|
||||||
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),
|
else => |e| return e,
|
||||||
});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod.lockAndClearFileCompileError(file);
|
mod.lockAndClearFileCompileError(file);
|
||||||
@ -3753,67 +3756,9 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
|
|||||||
file.source_loaded = true;
|
file.source_loaded = true;
|
||||||
|
|
||||||
file.tree = try Ast.parse(gpa, source, .zig);
|
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;
|
file.tree_loaded = true;
|
||||||
|
|
||||||
|
// Any potential AST errors are converted to ZIR errors here.
|
||||||
file.zir = try AstGen.generate(gpa, file.tree);
|
file.zir = try AstGen.generate(gpa, file.tree);
|
||||||
file.zir_loaded = true;
|
file.zir_loaded = true;
|
||||||
file.status = .success_zir;
|
file.status = .success_zir;
|
||||||
@ -3870,7 +3815,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
|
|||||||
.iov_len = file.zir.extra.len * 4,
|
.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 pkg_path = file.pkg.root_src_directory.path orelse ".";
|
||||||
const cache_path = cache_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}", .{
|
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 gpa = mod.gpa;
|
||||||
const new_zir = file.zir;
|
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
|
// 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.
|
// creates a namespace, gets mapped from old to new here.
|
||||||
var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};
|
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) = .{};
|
var decl_stack: ArrayListUnmanaged(Decl.Index) = .{};
|
||||||
defer decl_stack.deinit(gpa);
|
defer decl_stack.deinit(gpa);
|
||||||
|
|
||||||
const root_decl = file.root_decl.unwrap().?;
|
|
||||||
try decl_stack.append(gpa, root_decl);
|
try decl_stack.append(gpa, root_decl);
|
||||||
|
|
||||||
file.deleted_decls.clearRetainingCapacity();
|
file.deleted_decls.clearRetainingCapacity();
|
||||||
|
|||||||
129
src/Package.zig
129
src/Package.zig
@ -8,11 +8,11 @@ const Allocator = mem.Allocator;
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const log = std.log.scoped(.package);
|
const log = std.log.scoped(.package);
|
||||||
const main = @import("main.zig");
|
const main = @import("main.zig");
|
||||||
|
const ThreadPool = std.Thread.Pool;
|
||||||
|
const WaitGroup = std.Thread.WaitGroup;
|
||||||
|
|
||||||
const Compilation = @import("Compilation.zig");
|
const Compilation = @import("Compilation.zig");
|
||||||
const Module = @import("Module.zig");
|
const Module = @import("Module.zig");
|
||||||
const ThreadPool = @import("ThreadPool.zig");
|
|
||||||
const WaitGroup = @import("WaitGroup.zig");
|
|
||||||
const Cache = std.Build.Cache;
|
const Cache = std.Build.Cache;
|
||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const Manifest = @import("Manifest.zig");
|
const Manifest = @import("Manifest.zig");
|
||||||
@ -215,6 +215,7 @@ pub const build_zig_basename = "build.zig";
|
|||||||
|
|
||||||
pub fn fetchAndAddDependencies(
|
pub fn fetchAndAddDependencies(
|
||||||
pkg: *Package,
|
pkg: *Package,
|
||||||
|
root_pkg: *Package,
|
||||||
arena: Allocator,
|
arena: Allocator,
|
||||||
thread_pool: *ThreadPool,
|
thread_pool: *ThreadPool,
|
||||||
http_client: *std.http.Client,
|
http_client: *std.http.Client,
|
||||||
@ -224,7 +225,7 @@ pub fn fetchAndAddDependencies(
|
|||||||
dependencies_source: *std.ArrayList(u8),
|
dependencies_source: *std.ArrayList(u8),
|
||||||
build_roots_source: *std.ArrayList(u8),
|
build_roots_source: *std.ArrayList(u8),
|
||||||
name_prefix: []const u8,
|
name_prefix: []const u8,
|
||||||
color: main.Color,
|
error_bundle: *std.zig.ErrorBundle.Wip,
|
||||||
all_modules: *AllModules,
|
all_modules: *AllModules,
|
||||||
) !void {
|
) !void {
|
||||||
const max_bytes = 10 * 1024 * 1024;
|
const max_bytes = 10 * 1024 * 1024;
|
||||||
@ -249,7 +250,7 @@ pub fn fetchAndAddDependencies(
|
|||||||
|
|
||||||
if (ast.errors.len > 0) {
|
if (ast.errors.len > 0) {
|
||||||
const file_path = try directory.join(arena, &.{Manifest.basename});
|
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;
|
return error.PackageFetchFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,14 +258,9 @@ pub fn fetchAndAddDependencies(
|
|||||||
defer manifest.deinit(gpa);
|
defer manifest.deinit(gpa);
|
||||||
|
|
||||||
if (manifest.errors.len > 0) {
|
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});
|
const file_path = try directory.join(arena, &.{Manifest.basename});
|
||||||
for (manifest.errors) |msg| {
|
for (manifest.errors) |msg| {
|
||||||
Report.renderErrorMessage(ast, file_path, ttyconf, msg, &.{});
|
try Report.addErrorMessage(ast, file_path, error_bundle, 0, msg);
|
||||||
}
|
}
|
||||||
return error.PackageFetchFailed;
|
return error.PackageFetchFailed;
|
||||||
}
|
}
|
||||||
@ -272,8 +268,7 @@ pub fn fetchAndAddDependencies(
|
|||||||
const report: Report = .{
|
const report: Report = .{
|
||||||
.ast = &ast,
|
.ast = &ast,
|
||||||
.directory = directory,
|
.directory = directory,
|
||||||
.color = color,
|
.error_bundle = error_bundle,
|
||||||
.arena = arena,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var any_error = false;
|
var any_error = false;
|
||||||
@ -295,7 +290,8 @@ pub fn fetchAndAddDependencies(
|
|||||||
all_modules,
|
all_modules,
|
||||||
);
|
);
|
||||||
|
|
||||||
try pkg.fetchAndAddDependencies(
|
try sub_pkg.fetchAndAddDependencies(
|
||||||
|
root_pkg,
|
||||||
arena,
|
arena,
|
||||||
thread_pool,
|
thread_pool,
|
||||||
http_client,
|
http_client,
|
||||||
@ -305,11 +301,12 @@ pub fn fetchAndAddDependencies(
|
|||||||
dependencies_source,
|
dependencies_source,
|
||||||
build_roots_source,
|
build_roots_source,
|
||||||
sub_prefix,
|
sub_prefix,
|
||||||
color,
|
error_bundle,
|
||||||
all_modules,
|
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", .{
|
try dependencies_source.writer().print(" pub const {s} = @import(\"{}\");\n", .{
|
||||||
std.zig.fmtId(fqn), std.zig.fmtEscapes(fqn),
|
std.zig.fmtId(fqn), std.zig.fmtEscapes(fqn),
|
||||||
@ -347,8 +344,7 @@ pub fn createFilePkg(
|
|||||||
const Report = struct {
|
const Report = struct {
|
||||||
ast: *const std.zig.Ast,
|
ast: *const std.zig.Ast,
|
||||||
directory: Compilation.Directory,
|
directory: Compilation.Directory,
|
||||||
color: main.Color,
|
error_bundle: *std.zig.ErrorBundle.Wip,
|
||||||
arena: Allocator,
|
|
||||||
|
|
||||||
fn fail(
|
fn fail(
|
||||||
report: Report,
|
report: Report,
|
||||||
@ -356,52 +352,46 @@ const Report = struct {
|
|||||||
comptime fmt_string: []const u8,
|
comptime fmt_string: []const u8,
|
||||||
fmt_args: anytype,
|
fmt_args: anytype,
|
||||||
) error{ PackageFetchFailed, OutOfMemory } {
|
) error{ PackageFetchFailed, OutOfMemory } {
|
||||||
return failWithNotes(report, &.{}, tok, fmt_string, fmt_args);
|
const gpa = report.error_bundle.gpa;
|
||||||
}
|
|
||||||
|
|
||||||
fn failWithNotes(
|
const file_path = try report.directory.join(gpa, &.{Manifest.basename});
|
||||||
report: Report,
|
defer gpa.free(file_path);
|
||||||
notes: []const Compilation.AllErrors.Message,
|
|
||||||
tok: std.zig.Ast.TokenIndex,
|
const msg = try std.fmt.allocPrint(gpa, fmt_string, fmt_args);
|
||||||
comptime fmt_string: []const u8,
|
defer gpa.free(msg);
|
||||||
fmt_args: anytype,
|
|
||||||
) error{ PackageFetchFailed, OutOfMemory } {
|
try addErrorMessage(report.ast.*, file_path, report.error_bundle, 0, .{
|
||||||
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, .{
|
|
||||||
.tok = tok,
|
.tok = tok,
|
||||||
.off = 0,
|
.off = 0,
|
||||||
.msg = try std.fmt.allocPrint(report.arena, fmt_string, fmt_args),
|
.msg = msg,
|
||||||
}, notes);
|
});
|
||||||
|
|
||||||
return error.PackageFetchFailed;
|
return error.PackageFetchFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderErrorMessage(
|
fn addErrorMessage(
|
||||||
ast: std.zig.Ast,
|
ast: std.zig.Ast,
|
||||||
file_path: []const u8,
|
file_path: []const u8,
|
||||||
ttyconf: std.debug.TTY.Config,
|
eb: *std.zig.ErrorBundle.Wip,
|
||||||
|
notes_len: u32,
|
||||||
msg: Manifest.ErrorMessage,
|
msg: Manifest.ErrorMessage,
|
||||||
notes: []const Compilation.AllErrors.Message,
|
) error{OutOfMemory}!void {
|
||||||
) void {
|
|
||||||
const token_starts = ast.tokens.items(.start);
|
const token_starts = ast.tokens.items(.start);
|
||||||
const start_loc = ast.tokenLocation(0, msg.tok);
|
const start_loc = ast.tokenLocation(0, msg.tok);
|
||||||
Compilation.AllErrors.Message.renderToStdErr(.{ .src = .{
|
|
||||||
.msg = msg.msg,
|
try eb.addRootErrorMessage(.{
|
||||||
.src_path = file_path,
|
.msg = try eb.addString(msg.msg),
|
||||||
.line = @intCast(u32, start_loc.line),
|
.src_loc = try eb.addSourceLocation(.{
|
||||||
.column = @intCast(u32, start_loc.column),
|
.src_path = try eb.addString(file_path),
|
||||||
.span = .{
|
.span_start = token_starts[msg.tok],
|
||||||
.start = token_starts[msg.tok],
|
.span_end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
|
||||||
.end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
|
.span_main = token_starts[msg.tok] + msg.off,
|
||||||
.main = token_starts[msg.tok] + msg.off,
|
.line = @intCast(u32, start_loc.line),
|
||||||
},
|
.column = @intCast(u32, start_loc.column),
|
||||||
.source_line = ast.source[start_loc.line_start..start_loc.line_end],
|
.source_line = try eb.addString(ast.source[start_loc.line_start..start_loc.line_end]),
|
||||||
.notes = notes,
|
}),
|
||||||
} }, ttyconf);
|
.notes_len = notes_len,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -432,6 +422,12 @@ fn fetchAndUnpack(
|
|||||||
const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});
|
const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});
|
||||||
errdefer gpa.free(build_root);
|
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", .{
|
try build_roots_source.writer().print(" pub const {s} = \"{}\";\n", .{
|
||||||
std.zig.fmtId(fqn), std.zig.fmtEscapes(build_root),
|
std.zig.fmtId(fqn), std.zig.fmtEscapes(build_root),
|
||||||
});
|
});
|
||||||
@ -444,12 +440,6 @@ fn fetchAndUnpack(
|
|||||||
return gop.value_ptr.*;
|
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);
|
const ptr = try gpa.create(Package);
|
||||||
errdefer gpa.destroy(ptr);
|
errdefer gpa.destroy(ptr);
|
||||||
|
|
||||||
@ -501,9 +491,7 @@ fn fetchAndUnpack(
|
|||||||
// by default, so the same logic applies for buffering the reader as for gzip.
|
// by default, so the same logic applies for buffering the reader as for gzip.
|
||||||
try unpackTarball(gpa, &req, tmp_directory.handle, std.compress.xz);
|
try unpackTarball(gpa, &req, tmp_directory.handle, std.compress.xz);
|
||||||
} else {
|
} else {
|
||||||
return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{
|
return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{uri.path});
|
||||||
uri.path,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: delete files not included in the package prior to computing the package hash.
|
// TODO: delete files not included in the package prior to computing the package hash.
|
||||||
@ -530,10 +518,21 @@ fn fetchAndUnpack(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const notes: [1]Compilation.AllErrors.Message = .{.{ .plain = .{
|
const file_path = try report.directory.join(gpa, &.{Manifest.basename});
|
||||||
.msg = try std.fmt.allocPrint(report.arena, "expected .hash = \"{s}\",", .{&actual_hex}),
|
defer gpa.free(file_path);
|
||||||
} }};
|
|
||||||
return report.failWithNotes(¬es, dep.url_tok, "url field is missing corresponding hash field", .{});
|
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});
|
const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});
|
||||||
|
|||||||
202
src/Sema.zig
202
src/Sema.zig
@ -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(.{
|
return block.addInst(.{
|
||||||
.tag = if (block.float_mode == .Optimized) .cmp_vector_optimized else .cmp_vector,
|
.tag = if (block.float_mode == .Optimized) .cmp_vector_optimized else .cmp_vector,
|
||||||
.data = .{ .ty_pl = .{
|
.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{
|
.payload = try block.sema.addExtra(Air.VectorCmp{
|
||||||
.lhs = lhs,
|
.lhs = lhs,
|
||||||
.rhs = rhs,
|
.rhs = rhs,
|
||||||
@ -1101,6 +1103,7 @@ fn analyzeBodyInner(
|
|||||||
.@"unreachable" => break sema.zirUnreachable(block, inst),
|
.@"unreachable" => break sema.zirUnreachable(block, inst),
|
||||||
.panic => break sema.zirPanic(block, inst, false),
|
.panic => break sema.zirPanic(block, inst, false),
|
||||||
.panic_comptime => break sema.zirPanic(block, inst, true),
|
.panic_comptime => break sema.zirPanic(block, inst, true),
|
||||||
|
.trap => break sema.zirTrap(block, inst),
|
||||||
// zig fmt: on
|
// zig fmt: on
|
||||||
|
|
||||||
.extended => ext: {
|
.extended => ext: {
|
||||||
@ -1167,6 +1170,11 @@ fn analyzeBodyInner(
|
|||||||
i += 1;
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
|
.set_cold => {
|
||||||
|
try sema.zirSetCold(block, extended);
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
},
|
||||||
.breakpoint => {
|
.breakpoint => {
|
||||||
if (!block.is_comptime) {
|
if (!block.is_comptime) {
|
||||||
_ = try block.addNoOp(.breakpoint);
|
_ = try block.addNoOp(.breakpoint);
|
||||||
@ -1304,11 +1312,6 @@ fn analyzeBodyInner(
|
|||||||
i += 1;
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
.set_cold => {
|
|
||||||
try sema.zirSetCold(block, inst);
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
.set_runtime_safety => {
|
.set_runtime_safety => {
|
||||||
try sema.zirSetRuntimeSafety(block, inst);
|
try sema.zirSetRuntimeSafety(block, inst);
|
||||||
i += 1;
|
i += 1;
|
||||||
@ -1609,6 +1612,12 @@ fn analyzeBodyInner(
|
|||||||
const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index);
|
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 inline_body = sema.code.extra[extra.end..][0..extra.data.body_len];
|
||||||
const err_union = try sema.resolveInst(extra.data.operand);
|
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);
|
const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union);
|
||||||
assert(is_non_err != .none);
|
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| {
|
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;
|
return err;
|
||||||
};
|
};
|
||||||
if (is_non_err_tv.val.toBool()) {
|
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);
|
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
|
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 {
|
fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
|
||||||
@setCold(true);
|
@setCold(true);
|
||||||
|
const gpa = sema.gpa;
|
||||||
|
|
||||||
if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) {
|
if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) {
|
||||||
if (err_msg.src_loc.lazy == .unneeded) return error.NeededSourceLocation;
|
if (err_msg.src_loc.lazy == .unneeded) return error.NeededSourceLocation;
|
||||||
var arena = std.heap.ArenaAllocator.init(sema.gpa);
|
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
|
||||||
errdefer arena.deinit();
|
wip_errors.init(gpa) catch unreachable;
|
||||||
var errors = std.ArrayList(Compilation.AllErrors.Message).init(sema.gpa);
|
Compilation.addModuleErrorMsg(&wip_errors, err_msg.*) catch unreachable;
|
||||||
defer errors.deinit();
|
|
||||||
|
|
||||||
Compilation.AllErrors.add(sema.mod, &arena, &errors, err_msg.*) catch unreachable;
|
|
||||||
|
|
||||||
std.debug.print("compile error during Sema:\n", .{});
|
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);
|
crash_report.compilerPanic("unexpected compile error occurred", null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mod = sema.mod;
|
const mod = sema.mod;
|
||||||
ref: {
|
ref: {
|
||||||
errdefer err_msg.destroy(mod.gpa);
|
errdefer err_msg.destroy(gpa);
|
||||||
if (err_msg.src_loc.lazy == .unneeded) {
|
if (err_msg.src_loc.lazy == .unneeded) {
|
||||||
return error.NeededSourceLocation;
|
return error.NeededSourceLocation;
|
||||||
}
|
}
|
||||||
try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1);
|
try mod.failed_decls.ensureUnusedCapacity(gpa, 1);
|
||||||
try mod.failed_files.ensureUnusedCapacity(mod.gpa, 1);
|
try mod.failed_files.ensureUnusedCapacity(gpa, 1);
|
||||||
|
|
||||||
const max_references = blk: {
|
const max_references = blk: {
|
||||||
if (sema.mod.comp.reference_trace) |num| break :blk num;
|
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 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();
|
defer reference_stack.deinit();
|
||||||
|
|
||||||
// Avoid infinite loops.
|
// 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();
|
defer seen.deinit();
|
||||||
|
|
||||||
var cur_reference_trace: u32 = 0;
|
var cur_reference_trace: u32 = 0;
|
||||||
@ -2280,7 +2286,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
|
|||||||
if (gop.found_existing) {
|
if (gop.found_existing) {
|
||||||
// If there are multiple errors for the same Decl, prefer the first one added.
|
// If there are multiple errors for the same Decl, prefer the first one added.
|
||||||
sema.err = null;
|
sema.err = null;
|
||||||
err_msg.destroy(mod.gpa);
|
err_msg.destroy(gpa);
|
||||||
} else {
|
} else {
|
||||||
sema.err = err_msg;
|
sema.err = err_msg;
|
||||||
gop.value_ptr.* = 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;
|
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 {
|
fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
defer tracy.end();
|
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 };
|
gop.value_ptr.* = .{ .alignment = alignment, .src = src };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zirSetCold(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
|
fn zirSetCold(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {
|
||||||
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
|
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
|
||||||
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
|
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
|
||||||
const is_cold = try sema.resolveConstBool(block, operand_src, inst_data.operand, "operand to @setCold must be comptime-known");
|
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
|
const func = sema.func orelse return; // does nothing outside a function
|
||||||
func.is_cold = is_cold;
|
func.is_cold = is_cold;
|
||||||
}
|
}
|
||||||
@ -8766,7 +8780,7 @@ fn funcCommon(
|
|||||||
};
|
};
|
||||||
return sema.failWithOwnedErrorMsg(msg);
|
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 = msg: {
|
||||||
const msg = try sema.errMsg(block, ret_ty_src, "return type '{}' not allowed in function with calling convention '{s}'", .{
|
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),
|
return_type.fmt(sema.mod), @tagName(cc_resolved),
|
||||||
@ -9403,7 +9417,7 @@ fn intCast(
|
|||||||
const ok = if (is_vector) ok: {
|
const ok = if (is_vector) ok: {
|
||||||
const zeros = try Value.Tag.repeated.create(sema.arena, Value.zero);
|
const zeros = try Value.Tag.repeated.create(sema.arena, Value.zero);
|
||||||
const zero_inst = try sema.addConstant(sema.typeOf(operand), zeros);
|
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(.{
|
const all_in_range = try block.addInst(.{
|
||||||
.tag = .reduce,
|
.tag = .reduce,
|
||||||
.data = .{ .reduce = .{ .operand = is_in_range, .operation = .And } },
|
.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 dest_range = try sema.addConstant(unsigned_operand_ty, dest_range_val);
|
||||||
|
|
||||||
const ok = if (is_vector) ok: {
|
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(.{
|
const all_in_range = try block.addInst(.{
|
||||||
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
|
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
|
||||||
.data = .{ .reduce = .{
|
.data = .{ .reduce = .{
|
||||||
@ -9474,7 +9488,7 @@ fn intCast(
|
|||||||
try sema.addSafetyCheck(block, ok, .cast_truncated_data);
|
try sema.addSafetyCheck(block, ok, .cast_truncated_data);
|
||||||
} else {
|
} else {
|
||||||
const ok = if (is_vector) ok: {
|
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(.{
|
const all_in_range = try block.addInst(.{
|
||||||
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
|
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
|
||||||
.data = .{ .reduce = .{
|
.data = .{ .reduce = .{
|
||||||
@ -9495,7 +9509,7 @@ fn intCast(
|
|||||||
const ok = if (is_vector) ok: {
|
const ok = if (is_vector) ok: {
|
||||||
const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
|
const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
|
||||||
const zero_inst = try sema.addConstant(operand_ty, zero_val);
|
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(.{
|
const all_in_range = try block.addInst(.{
|
||||||
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
|
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
|
||||||
.data = .{ .reduce = .{
|
.data = .{ .reduce = .{
|
||||||
@ -12007,7 +12021,7 @@ fn zirShl(
|
|||||||
|
|
||||||
const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: {
|
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 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(.{
|
break :ok try block.addInst(.{
|
||||||
.tag = .reduce,
|
.tag = .reduce,
|
||||||
.data = .{ .reduce = .{
|
.data = .{ .reduce = .{
|
||||||
@ -12163,7 +12177,7 @@ fn zirShr(
|
|||||||
|
|
||||||
const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: {
|
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 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(.{
|
break :ok try block.addInst(.{
|
||||||
.tag = .reduce,
|
.tag = .reduce,
|
||||||
.data = .{ .reduce = .{
|
.data = .{ .reduce = .{
|
||||||
@ -12182,7 +12196,7 @@ fn zirShr(
|
|||||||
const back = try block.addBinOp(.shl, result, rhs);
|
const back = try block.addBinOp(.shl, result, rhs);
|
||||||
|
|
||||||
const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: {
|
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(.{
|
break :ok try block.addInst(.{
|
||||||
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
|
.tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
|
||||||
.data = .{ .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);
|
const floored = try block.addUnOp(.floor, result);
|
||||||
|
|
||||||
if (resolved_type.zigTypeTag() == .Vector) {
|
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(.{
|
break :ok try block.addInst(.{
|
||||||
.tag = switch (block.float_mode) {
|
.tag = switch (block.float_mode) {
|
||||||
.Strict => .reduce,
|
.Strict => .reduce,
|
||||||
@ -13207,7 +13221,7 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
|
|||||||
if (resolved_type.zigTypeTag() == .Vector) {
|
if (resolved_type.zigTypeTag() == .Vector) {
|
||||||
const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
|
const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
|
||||||
const zero = try sema.addConstant(resolved_type, zero_val);
|
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(.{
|
break :ok try block.addInst(.{
|
||||||
.tag = .reduce,
|
.tag = .reduce,
|
||||||
.data = .{ .reduce = .{
|
.data = .{ .reduce = .{
|
||||||
@ -13505,14 +13519,13 @@ fn addDivIntOverflowSafety(
|
|||||||
|
|
||||||
var ok: Air.Inst.Ref = .none;
|
var ok: Air.Inst.Ref = .none;
|
||||||
if (resolved_type.zigTypeTag() == .Vector) {
|
if (resolved_type.zigTypeTag() == .Vector) {
|
||||||
const vector_ty_ref = try sema.addType(resolved_type);
|
|
||||||
if (maybe_lhs_val == null) {
|
if (maybe_lhs_val == null) {
|
||||||
const min_int_ref = try sema.addConstant(resolved_type, min_int);
|
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) {
|
if (maybe_rhs_val == null) {
|
||||||
const neg_one_ref = try sema.addConstant(resolved_type, neg_one);
|
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) {
|
if (ok == .none) {
|
||||||
ok = rhs_ok;
|
ok = rhs_ok;
|
||||||
} else {
|
} else {
|
||||||
@ -13564,7 +13577,7 @@ fn addDivByZeroSafety(
|
|||||||
const ok = if (resolved_type.zigTypeTag() == .Vector) ok: {
|
const ok = if (resolved_type.zigTypeTag() == .Vector) ok: {
|
||||||
const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
|
const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
|
||||||
const zero = try sema.addConstant(resolved_type, zero_val);
|
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(.{
|
break :ok try block.addInst(.{
|
||||||
.tag = if (is_int) .reduce else .reduce_optimized,
|
.tag = if (is_int) .reduce else .reduce_optimized,
|
||||||
.data = .{ .reduce = .{
|
.data = .{ .reduce = .{
|
||||||
@ -15193,9 +15206,7 @@ fn cmpSelf(
|
|||||||
};
|
};
|
||||||
try sema.requireRuntimeBlock(block, src, runtime_src);
|
try sema.requireRuntimeBlock(block, src, runtime_src);
|
||||||
if (resolved_type.zigTypeTag() == .Vector) {
|
if (resolved_type.zigTypeTag() == .Vector) {
|
||||||
const result_ty = try Type.vector(sema.arena, resolved_type.vectorLen(), Type.bool);
|
return block.addCmpVector(casted_lhs, casted_rhs, op);
|
||||||
const result_ty_ref = try sema.addType(result_ty);
|
|
||||||
return block.addCmpVector(casted_lhs, casted_rhs, op, result_ty_ref);
|
|
||||||
}
|
}
|
||||||
const tag = Air.Inst.Tag.fromCmpOp(op, block.float_mode == .Optimized);
|
const tag = Air.Inst.Tag.fromCmpOp(op, block.float_mode == .Optimized);
|
||||||
return block.addBinOp(tag, casted_lhs, casted_rhs);
|
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 union_val = val.cast(Value.Payload.Union).?.data;
|
||||||
const target = mod.getTarget();
|
const target = mod.getTarget();
|
||||||
const tag_index = type_info_ty.unionTagFieldIndex(union_val.tag, mod).?;
|
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)) {
|
switch (@intToEnum(std.builtin.TypeId, tag_index)) {
|
||||||
.Type => return Air.Inst.Ref.type_type,
|
.Type => return Air.Inst.Ref.type_type,
|
||||||
.Void => return Air.Inst.Ref.void_type,
|
.Void => return Air.Inst.Ref.void_type,
|
||||||
@ -22274,7 +22285,6 @@ fn zirBuiltinExtern(
|
|||||||
extended: Zir.Inst.Extended.InstData,
|
extended: Zir.Inst.Extended.InstData,
|
||||||
) CompileError!Air.Inst.Ref {
|
) CompileError!Air.Inst.Ref {
|
||||||
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
|
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 ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
|
||||||
const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = 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);
|
const new_decl = sema.mod.declPtr(new_decl_index);
|
||||||
new_decl.name = try sema.gpa.dupeZ(u8, options.name);
|
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();
|
var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
|
||||||
const new_decl_arena_allocator = new_decl_arena.allocator();
|
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);
|
const new_var = try new_decl_arena_allocator.create(Module.Var);
|
||||||
errdefer new_decl_arena_allocator.destroy(new_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.* = .{
|
new_decl.src_line = sema.owner_decl.src_line;
|
||||||
.owner_decl = sema.owner_decl_index,
|
// We only access this decl through the decl_ref with the correct type created
|
||||||
.init = Value.initTag(.unreachable_value),
|
// below, so this type doesn't matter
|
||||||
.is_extern = true,
|
new_decl.ty = Type.Tag.init(.anyopaque);
|
||||||
.is_mutable = false,
|
new_decl.val = try Value.Tag.variable.create(new_decl_arena_allocator, new_var);
|
||||||
.is_threadlocal = options.is_thread_local,
|
new_decl.@"align" = 0;
|
||||||
.is_weak_linkage = options.linkage == .Weak,
|
new_decl.@"linksection" = null;
|
||||||
.lib_name = 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;
|
try new_decl.finalizeNewArena(&new_decl_arena);
|
||||||
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;
|
|
||||||
|
|
||||||
const arena_state = try new_decl_arena_allocator.create(std.heap.ArenaAllocator.State);
|
try sema.mod.declareDeclDependency(sema.owner_decl_index, new_decl_index);
|
||||||
arena_state.* = new_decl_arena.state;
|
try sema.ensureDeclAnalyzed(new_decl_index);
|
||||||
new_decl.value_arena = arena_state;
|
|
||||||
|
|
||||||
const ref = try sema.analyzeDeclRef(new_decl_index);
|
const ref = try Value.Tag.decl_ref.create(sema.arena, new_decl_index);
|
||||||
try sema.requireRuntimeBlock(block, src, null);
|
return sema.addConstant(ty, ref);
|
||||||
return block.addBitCast(ty, ref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn requireRuntimeBlock(sema: *Sema, block: *Block, src: LazySrcLoc, runtime_src: ?LazySrcLoc) !void {
|
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 ok = if (sentinel_ty.zigTypeTag() == .Vector) ok: {
|
||||||
const eql =
|
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(.{
|
break :ok try parent_block.addInst(.{
|
||||||
.tag = .reduce,
|
.tag = .reduce,
|
||||||
.data = .{ .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.
|
// If we get here, we need to look for a decl in the struct type instead.
|
||||||
switch (concrete_ty.zigTypeTag()) {
|
const found_decl = switch (concrete_ty.zigTypeTag()) {
|
||||||
.Struct, .Opaque, .Union, .Enum => {
|
.Struct, .Opaque, .Union, .Enum => found_decl: {
|
||||||
if (concrete_ty.getNamespace()) |namespace| {
|
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_val = try sema.analyzeLoad(block, src, inst, src);
|
||||||
const decl_type = sema.typeOf(decl_val);
|
const decl_type = sema.typeOf(decl_val);
|
||||||
if (decl_type.zigTypeTag() == .Fn and
|
if (decl_type.zigTypeTag() == .Fn and
|
||||||
@ -23612,7 +23627,7 @@ fn fieldCallBind(
|
|||||||
first_param_type.ptrSize() == .C) and
|
first_param_type.ptrSize() == .C) and
|
||||||
first_param_type.childType().eql(concrete_ty, sema.mod)))
|
first_param_type.childType().eql(concrete_ty, sema.mod)))
|
||||||
{
|
{
|
||||||
// zig fmt: on
|
// zig fmt: on
|
||||||
// TODO: bound fn calls on rvalues should probably
|
// TODO: bound fn calls on rvalues should probably
|
||||||
// generate a by-value argument somehow.
|
// generate a by-value argument somehow.
|
||||||
const ty = Type.Tag.bound_fn.init();
|
const ty = Type.Tag.bound_fn.init();
|
||||||
@ -23651,16 +23666,22 @@ fn fieldCallBind(
|
|||||||
return sema.addConstant(ty, value);
|
return sema.addConstant(ty, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break :found_decl decl_idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break :found_decl null;
|
||||||
},
|
},
|
||||||
else => {},
|
else => null,
|
||||||
}
|
};
|
||||||
|
|
||||||
const msg = msg: {
|
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) });
|
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);
|
errdefer msg.destroy(sema.gpa);
|
||||||
try sema.addDeclaredHereNote(msg, concrete_ty);
|
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;
|
break :msg msg;
|
||||||
};
|
};
|
||||||
return sema.failWithOwnedErrorMsg(msg);
|
return sema.failWithOwnedErrorMsg(msg);
|
||||||
@ -24832,6 +24853,7 @@ fn coerceExtra(
|
|||||||
const array_ty = dest_info.pointee_type;
|
const array_ty = dest_info.pointee_type;
|
||||||
if (array_ty.zigTypeTag() != .Array) break :single_item;
|
if (array_ty.zigTypeTag() != .Array) break :single_item;
|
||||||
const array_elem_ty = array_ty.childType();
|
const array_elem_ty = array_ty.childType();
|
||||||
|
if (array_ty.arrayLen() != 1) break :single_item;
|
||||||
const dest_is_mut = dest_info.mutable;
|
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)) {
|
switch (try sema.coerceInMemoryAllowed(block, array_elem_ty, ptr_elem_ty, dest_is_mut, target, dest_ty_src, inst_src)) {
|
||||||
.ok => {},
|
.ok => {},
|
||||||
@ -26625,6 +26647,23 @@ fn beginComptimePtrMutation(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const elem_ty = parent.ty.childType();
|
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()) {
|
switch (val_ptr.tag()) {
|
||||||
.undef => {
|
.undef => {
|
||||||
// An array has been initialized to undefined at comptime and now we
|
// 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);
|
try sema.requireRuntimeBlock(block, src, runtime_src);
|
||||||
const result_ty_inst = try sema.addType(result_ty);
|
return block.addCmpVector(lhs, rhs, op);
|
||||||
return block.addCmpVector(lhs, rhs, op, result_ty_inst);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrapOptional(
|
fn wrapOptional(
|
||||||
|
|||||||
30
src/Zir.zig
30
src/Zir.zig
@ -617,7 +617,7 @@ pub const Inst = struct {
|
|||||||
/// Uses the `un_node` field.
|
/// Uses the `un_node` field.
|
||||||
typeof_log2_int_type,
|
typeof_log2_int_type,
|
||||||
/// Asserts control-flow will not reach this instruction (`unreachable`).
|
/// Asserts control-flow will not reach this instruction (`unreachable`).
|
||||||
/// Uses the `unreachable` union field.
|
/// Uses the `@"unreachable"` union field.
|
||||||
@"unreachable",
|
@"unreachable",
|
||||||
/// Bitwise XOR. `^`
|
/// Bitwise XOR. `^`
|
||||||
/// Uses the `pl_node` union field. Payload is `Bin`.
|
/// Uses the `pl_node` union field. Payload is `Bin`.
|
||||||
@ -808,8 +808,9 @@ pub const Inst = struct {
|
|||||||
panic,
|
panic,
|
||||||
/// Same as `panic` but forces comptime.
|
/// Same as `panic` but forces comptime.
|
||||||
panic_comptime,
|
panic_comptime,
|
||||||
/// Implement builtin `@setCold`. Uses `un_node`.
|
/// Implements `@trap`.
|
||||||
set_cold,
|
/// Uses the `node` field.
|
||||||
|
trap,
|
||||||
/// Implement builtin `@setRuntimeSafety`. Uses `un_node`.
|
/// Implement builtin `@setRuntimeSafety`. Uses `un_node`.
|
||||||
set_runtime_safety,
|
set_runtime_safety,
|
||||||
/// Implement builtin `@sqrt`. Uses `un_node`.
|
/// Implement builtin `@sqrt`. Uses `un_node`.
|
||||||
@ -1187,7 +1188,6 @@ pub const Inst = struct {
|
|||||||
.bool_to_int,
|
.bool_to_int,
|
||||||
.embed_file,
|
.embed_file,
|
||||||
.error_name,
|
.error_name,
|
||||||
.set_cold,
|
|
||||||
.set_runtime_safety,
|
.set_runtime_safety,
|
||||||
.sqrt,
|
.sqrt,
|
||||||
.sin,
|
.sin,
|
||||||
@ -1277,6 +1277,7 @@ pub const Inst = struct {
|
|||||||
.repeat_inline,
|
.repeat_inline,
|
||||||
.panic,
|
.panic,
|
||||||
.panic_comptime,
|
.panic_comptime,
|
||||||
|
.trap,
|
||||||
.check_comptime_control_flow,
|
.check_comptime_control_flow,
|
||||||
=> true,
|
=> true,
|
||||||
};
|
};
|
||||||
@ -1323,7 +1324,6 @@ pub const Inst = struct {
|
|||||||
.validate_deref,
|
.validate_deref,
|
||||||
.@"export",
|
.@"export",
|
||||||
.export_value,
|
.export_value,
|
||||||
.set_cold,
|
|
||||||
.set_runtime_safety,
|
.set_runtime_safety,
|
||||||
.memcpy,
|
.memcpy,
|
||||||
.memset,
|
.memset,
|
||||||
@ -1553,6 +1553,7 @@ pub const Inst = struct {
|
|||||||
.repeat_inline,
|
.repeat_inline,
|
||||||
.panic,
|
.panic,
|
||||||
.panic_comptime,
|
.panic_comptime,
|
||||||
|
.trap,
|
||||||
.for_len,
|
.for_len,
|
||||||
.@"try",
|
.@"try",
|
||||||
.try_ptr,
|
.try_ptr,
|
||||||
@ -1561,7 +1562,7 @@ pub const Inst = struct {
|
|||||||
=> false,
|
=> false,
|
||||||
|
|
||||||
.extended => switch (data.extended.opcode) {
|
.extended => switch (data.extended.opcode) {
|
||||||
.breakpoint, .fence => true,
|
.fence, .set_cold, .breakpoint => true,
|
||||||
else => false,
|
else => false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -1750,7 +1751,7 @@ pub const Inst = struct {
|
|||||||
.error_name = .un_node,
|
.error_name = .un_node,
|
||||||
.panic = .un_node,
|
.panic = .un_node,
|
||||||
.panic_comptime = .un_node,
|
.panic_comptime = .un_node,
|
||||||
.set_cold = .un_node,
|
.trap = .node,
|
||||||
.set_runtime_safety = .un_node,
|
.set_runtime_safety = .un_node,
|
||||||
.sqrt = .un_node,
|
.sqrt = .un_node,
|
||||||
.sin = .un_node,
|
.sin = .un_node,
|
||||||
@ -1979,11 +1980,15 @@ pub const Inst = struct {
|
|||||||
/// Implement builtin `@setAlignStack`.
|
/// Implement builtin `@setAlignStack`.
|
||||||
/// `operand` is payload index to `UnNode`.
|
/// `operand` is payload index to `UnNode`.
|
||||||
set_align_stack,
|
set_align_stack,
|
||||||
|
/// Implements `@setCold`.
|
||||||
|
/// `operand` is payload index to `UnNode`.
|
||||||
|
set_cold,
|
||||||
/// Implements the `@errSetCast` builtin.
|
/// Implements the `@errSetCast` builtin.
|
||||||
/// `operand` is payload index to `BinNode`. `lhs` is dest type, `rhs` is operand.
|
/// `operand` is payload index to `BinNode`. `lhs` is dest type, `rhs` is operand.
|
||||||
err_set_cast,
|
err_set_cast,
|
||||||
/// `operand` is payload index to `UnNode`.
|
/// `operand` is payload index to `UnNode`.
|
||||||
await_nosuspend,
|
await_nosuspend,
|
||||||
|
/// Implements `@breakpoint`.
|
||||||
/// `operand` is `src_node: i32`.
|
/// `operand` is `src_node: i32`.
|
||||||
breakpoint,
|
breakpoint,
|
||||||
/// Implements the `@select` builtin.
|
/// Implements the `@select` builtin.
|
||||||
@ -1997,7 +2002,7 @@ pub const Inst = struct {
|
|||||||
int_to_error,
|
int_to_error,
|
||||||
/// Implement builtin `@Type`.
|
/// Implement builtin `@Type`.
|
||||||
/// `operand` is payload index to `UnNode`.
|
/// `operand` is payload index to `UnNode`.
|
||||||
/// `small` contains `NameStrategy
|
/// `small` contains `NameStrategy`.
|
||||||
reify,
|
reify,
|
||||||
/// Implements the `@asyncCall` builtin.
|
/// Implements the `@asyncCall` builtin.
|
||||||
/// `operand` is payload index to `AsyncCall`.
|
/// `operand` is payload index to `AsyncCall`.
|
||||||
@ -2040,8 +2045,7 @@ pub const Inst = struct {
|
|||||||
|
|
||||||
/// A reference to a TypedValue or ZIR instruction.
|
/// 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
|
/// If the Ref has a tag in this enum, it refers to a TypedValue.
|
||||||
/// retrieved with Ref.toTypedValue().
|
|
||||||
///
|
///
|
||||||
/// If the value of a Ref does not have a tag, it refers to a ZIR instruction.
|
/// 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
|
/// 0 or a payload index of a `Block`, each is a payload
|
||||||
/// index of another `Item`.
|
/// index of another `Item`.
|
||||||
notes: u32,
|
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
Loading…
x
Reference in New Issue
Block a user