From 6bcacb493f0756d591622a68601dce837494a010 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 23 Aug 2024 10:39:53 -0700 Subject: [PATCH] update zig sources to 0.14.0-dev.1294+df6907f60 --- README.md | 2 +- build | 2 +- build.bat | 2 +- zig/README.md | 18 +- zig/bootstrap.c | 2 +- zig/build.zig | 71 +- zig/cmake/Findclang.cmake | 1 + zig/lib/compiler/aro/aro/Builtins/Builtin.zig | 2 +- zig/lib/compiler/aro/aro/Compilation.zig | 1 - zig/lib/compiler/aro/aro/Parser.zig | 2 + zig/lib/compiler/aro/backend/Object/Elf.zig | 2 +- zig/lib/compiler/objcopy.zig | 7 +- zig/lib/compiler/std-docs.zig | 40 +- zig/lib/init/src/main.zig | 2 +- .../sysdeps/unix/sysv/linux/alpha/xstatver.h | 14 + .../sysdeps/unix/sysv/linux/arm/xstatver.h | 13 + .../sysdeps/unix/sysv/linux/hppa/xstatver.h | 13 + .../unix/sysv/linux/microblaze/xstatver.h | 13 + .../sysdeps/unix/sysv/linux/sh/xstatver.h | 13 + zig/lib/std/Build.zig | 2 +- zig/lib/std/Build/Cache.zig | 9 +- zig/lib/std/Build/Fuzz.zig | 18 +- zig/lib/std/Build/Fuzz/WebServer.zig | 74 +- zig/lib/std/Build/Step.zig | 23 +- zig/lib/std/Build/Step/Compile.zig | 29 +- zig/lib/std/Build/Step/InstallArtifact.zig | 49 +- zig/lib/std/Build/Step/Run.zig | 5 +- zig/lib/std/Build/Step/TranslateC.zig | 19 +- zig/lib/std/Target.zig | 238 +- zig/lib/std/Thread.zig | 52 +- zig/lib/std/array_list.zig | 18 + zig/lib/std/bounded_array.zig | 5 + zig/lib/std/c.zig | 136 +- zig/lib/std/c/darwin.zig | 5 + zig/lib/std/coff.zig | 23 +- zig/lib/std/crypto.zig | 5 +- zig/lib/std/crypto/aes/soft.zig | 2 + zig/lib/std/crypto/blake2.zig | 4 +- zig/lib/std/crypto/ml_kem.zig | 38 +- zig/lib/std/crypto/pcurves/p384.zig | 2 +- zig/lib/std/debug.zig | 5 + zig/lib/std/debug/Dwarf.zig | 8 +- zig/lib/std/dwarf.zig | 37 + zig/lib/std/dwarf/AT.zig | 8 + zig/lib/std/dwarf/LANG.zig | 24 + zig/lib/std/enums.zig | 2 +- zig/lib/std/fs/get_app_data_dir.zig | 4 +- zig/lib/std/hash/xxhash.zig | 2 +- zig/lib/std/io.zig | 2 +- zig/lib/std/json/stringify.zig | 135 +- zig/lib/std/json/stringify_test.zig | 50 + zig/lib/std/leb128.zig | 55 +- zig/lib/std/math.zig | 24 +- zig/lib/std/math/big/int.zig | 10 +- zig/lib/std/math/hypot.zig | 1 + zig/lib/std/math/nextafter.zig | 2 +- zig/lib/std/mem.zig | 81 +- zig/lib/std/os/linux.zig | 648 +- zig/lib/std/os/linux/bpf/btf.zig | 3 +- zig/lib/std/os/linux/mips.zig | 123 +- zig/lib/std/os/linux/mips64.zig | 122 +- zig/lib/std/os/linux/sparc64.zig | 60 - zig/lib/std/os/linux/test.zig | 17 + zig/lib/std/os/linux/tls.zig | 2 +- zig/lib/std/posix.zig | 3 + zig/lib/std/start.zig | 45 +- zig/lib/std/tar.zig | 6 +- zig/lib/std/tar/output.zig | 85 - zig/lib/std/tar/writer.zig | 497 ++ zig/lib/std/time/epoch.zig | 4 +- zig/lib/std/unicode.zig | 1 + zig/lib/std/zig/AstGen.zig | 245 +- zig/lib/std/zig/Server.zig | 21 +- zig/lib/std/zig/Zir.zig | 718 +- zig/lib/std/zig/system.zig | 24 +- zig/lib/std/zig/system/darwin/macos.zig | 5 + zig/src/Air.zig | 38 +- zig/src/Air/types_resolved.zig | 1 + zig/src/Builtin.zig | 2 +- zig/src/Compilation.zig | 398 +- zig/src/InternPool.zig | 513 +- zig/src/Liveness.zig | 2 + zig/src/Liveness/Verify.zig | 1 + zig/src/Sema.zig | 995 ++- zig/src/Type.zig | 136 +- zig/src/Zcu.zig | 679 +- zig/src/Zcu/PerThread.zig | 1357 ++-- zig/src/arch/aarch64/CodeGen.zig | 117 +- zig/src/arch/aarch64/bits.zig | 12 +- zig/src/arch/arm/CodeGen.zig | 88 +- zig/src/arch/arm/bits.zig | 9 +- zig/src/arch/riscv64/CodeGen.zig | 62 +- zig/src/arch/riscv64/Emit.zig | 46 +- zig/src/arch/riscv64/bits.zig | 5 +- zig/src/arch/sparc64/CodeGen.zig | 48 +- zig/src/arch/sparc64/bits.zig | 13 +- zig/src/arch/wasm/CodeGen.zig | 41 +- zig/src/arch/x86/bits.zig | 17 +- zig/src/arch/x86_64/CodeGen.zig | 778 +- zig/src/arch/x86_64/Disassembler.zig | 2 +- zig/src/arch/x86_64/Emit.zig | 335 +- zig/src/arch/x86_64/Lower.zig | 170 +- zig/src/arch/x86_64/Mir.zig | 100 +- zig/src/arch/x86_64/bits.zig | 71 +- zig/src/arch/x86_64/encoder.zig | 161 +- zig/src/codegen.zig | 95 +- zig/src/codegen/c.zig | 9 +- zig/src/codegen/c/Type.zig | 24 +- zig/src/codegen/llvm.zig | 99 +- zig/src/codegen/spirv.zig | 4 +- zig/src/crash_report.zig | 16 +- zig/src/glibc.zig | 1 + zig/src/introspect.zig | 7 +- zig/src/link.zig | 38 +- zig/src/link/C.zig | 19 +- zig/src/link/Coff.zig | 71 +- zig/src/link/Coff/lld.zig | 4 +- zig/src/link/Dwarf.zig | 6562 ++++++++++------- zig/src/link/Elf.zig | 514 +- zig/src/link/Elf/Atom.zig | 132 +- zig/src/link/Elf/Symbol.zig | 52 +- zig/src/link/Elf/ZigObject.zig | 661 +- zig/src/link/Elf/merge_section.zig | 34 +- zig/src/link/Elf/relocatable.zig | 42 +- zig/src/link/Elf/relocation.zig | 58 +- zig/src/link/Elf/synthetic_sections.zig | 209 - zig/src/link/MachO.zig | 304 +- zig/src/link/MachO/Atom.zig | 22 +- zig/src/link/MachO/DebugSymbols.zig | 67 +- zig/src/link/MachO/Relocation.zig | 3 - zig/src/link/MachO/Symbol.zig | 43 +- zig/src/link/MachO/ZigObject.zig | 284 +- zig/src/link/MachO/dyld_info/Rebase.zig | 12 - zig/src/link/MachO/load_commands.zig | 4 +- zig/src/link/MachO/relocatable.zig | 2 +- zig/src/link/MachO/synthetic.zig | 108 - zig/src/link/MachO/thunks.zig | 29 + zig/src/link/NvPtx.zig | 5 +- zig/src/link/Plan9.zig | 51 +- zig/src/link/SpirV.zig | 7 +- zig/src/link/Wasm.zig | 11 +- zig/src/link/Wasm/ZigObject.zig | 61 +- zig/src/main.zig | 136 +- zig/src/print_air.zig | 10 +- zig/src/print_zir.zig | 9 +- zig/src/register_manager.zig | 5 +- zig/test/behavior/basic.zig | 1 - zig/test/behavior/export_builtin.zig | 2 - zig/test/behavior/extern.zig | 1 - zig/test/behavior/fn.zig | 1 - zig/test/behavior/maximum_minimum.zig | 2 + zig/test/behavior/pointers.zig | 1 - zig/test/behavior/vector.zig | 1 + .../stack_usage_in_naked_function.zig | 45 + zig/test/incremental/add_decl | 59 + zig/test/incremental/add_decl_namespaced | 59 + zig/test/incremental/delete_comptime_decls | 38 + zig/test/incremental/modify_inline_fn | 23 + zig/test/incremental/move_src | 29 + .../incremental/type_becomes_comptime_only | 39 + zig/test/incremental/unreferenced_error | 38 + zig/test/link/elf.zig | 81 +- zig/test/link/macho.zig | 27 +- zig/test/src/Debugger.zig | 840 +++ zig/test/standalone/build.zig.zon | 17 +- zig/test/standalone/emit_asm_no_bin/build.zig | 19 + zig/test/standalone/emit_asm_no_bin/main.zig | 1 + .../build.zig | 0 .../main.zig | 0 zig/test/tests.zig | 148 +- zig/tools/incr-check.zig | 236 +- 171 files changed, 13891 insertions(+), 7893 deletions(-) create mode 100644 zig/lib/libc/glibc/sysdeps/unix/sysv/linux/alpha/xstatver.h create mode 100644 zig/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/xstatver.h create mode 100644 zig/lib/libc/glibc/sysdeps/unix/sysv/linux/hppa/xstatver.h create mode 100644 zig/lib/libc/glibc/sysdeps/unix/sysv/linux/microblaze/xstatver.h create mode 100644 zig/lib/libc/glibc/sysdeps/unix/sysv/linux/sh/xstatver.h delete mode 100644 zig/lib/std/tar/output.zig create mode 100644 zig/lib/std/tar/writer.zig create mode 100644 zig/test/cases/compile_errors/stack_usage_in_naked_function.zig create mode 100644 zig/test/incremental/add_decl create mode 100644 zig/test/incremental/add_decl_namespaced create mode 100644 zig/test/incremental/delete_comptime_decls create mode 100644 zig/test/incremental/modify_inline_fn create mode 100644 zig/test/incremental/move_src create mode 100644 zig/test/incremental/type_becomes_comptime_only create mode 100644 zig/test/incremental/unreferenced_error create mode 100644 zig/test/src/Debugger.zig create mode 100644 zig/test/standalone/emit_asm_no_bin/build.zig create mode 100644 zig/test/standalone/emit_asm_no_bin/main.zig rename zig/test/standalone/{issue_12588 => emit_llvm_no_bin}/build.zig (100%) rename zig/test/standalone/{issue_12588 => emit_llvm_no_bin}/main.zig (100%) diff --git a/README.md b/README.md index e7b8593e6f..07a10622f0 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ to find and inspect the patch diffs. * LLVM, LLD, Clang 18.1.8 * zlib 1.3.1 * zstd 1.5.2 - * zig 0.14.0-dev.1078+0f0f543a9 + * zig 0.14.0-dev.1294+df6907f60 For other versions, check the git tags of this repository. diff --git a/build b/build index f7f8823117..d860a0496c 100755 --- a/build +++ b/build @@ -7,7 +7,7 @@ TARGET="$1" # Example: riscv64-linux-gnu MCPU="$2" # Examples: `baseline`, `native`, `generic+v7a`, or `arm1176jzf_s` ROOTDIR="$(pwd)" -ZIG_VERSION="0.14.0-dev.1078+0f0f543a9" +ZIG_VERSION="0.14.0-dev.1294+df6907f60" TARGET_OS_AND_ABI=${TARGET#*-} # Example: linux-gnu diff --git a/build.bat b/build.bat index 6acb9757a3..432b81dbda 100644 --- a/build.bat +++ b/build.bat @@ -35,7 +35,7 @@ if "%VSCMD_ARG_HOST_ARCH%"=="x86" set OUTDIR=out-win-x86 set ROOTDIR=%~dp0 set "ROOTDIR_CMAKE=%ROOTDIR:\=/%" -set ZIG_VERSION="0.14.0-dev.1078+0f0f543a9" +set ZIG_VERSION="0.14.0-dev.1294+df6907f60" set JOBS_ARG= pushd %ROOTDIR% diff --git a/zig/README.md b/zig/README.md index 7c4adbf82a..865bd178b3 100644 --- a/zig/README.md +++ b/zig/README.md @@ -76,21 +76,25 @@ This produces a `zig2` executable in the current working directory. This is a [without LLVM extensions](https://github.com/ziglang/zig/issues/16270), and is therefore lacking these features: - Release mode optimizations -- aarch64 machine code backend -- `@cImport` / `zig translate-c` -- Ability to compile C files -- Ability to compile assembly files +- [aarch64 machine code backend](https://github.com/ziglang/zig/issues/21172) +- [@cImport](https://github.com/ziglang/zig/issues/20630) +- [zig translate-c](https://github.com/ziglang/zig/issues/20875) +- [Ability to compile assembly files](https://github.com/ziglang/zig/issues/21169) - [Some ELF linking features](https://github.com/ziglang/zig/issues/17749) - [Most COFF/PE linking features](https://github.com/ziglang/zig/issues/17751) - [Some WebAssembly linking features](https://github.com/ziglang/zig/issues/17750) - [Ability to create import libs from def files](https://github.com/ziglang/zig/issues/17807) -- [Automatic importlib file generation for Windows DLLs](https://github.com/ziglang/zig/issues/17753) - [Ability to create static archives from object files](https://github.com/ziglang/zig/issues/9828) -- Ability to compile C++, Objective-C, and Objective-C++ files +- Ability to compile C, C++, Objective-C, and Objective-C++ files However, a compiler built this way does provide a C backend, which may be useful for creating system packages of Zig projects using the system C -toolchain. In such case, LLVM is not needed! +toolchain. **In this case, LLVM is not needed!** + +Furthermore, a compiler built this way provides an LLVM backend that produces +bitcode files, which may be compiled into object files via a system Clang +package. This can be used to produce system packages of Zig applications +without the Zig package dependency on LLVM. ## Contributing diff --git a/zig/bootstrap.c b/zig/bootstrap.c index fa708c9c6c..cfc008df5e 100644 --- a/zig/bootstrap.c +++ b/zig/bootstrap.c @@ -123,7 +123,7 @@ int main(int argc, char **argv) { if (f == NULL) panic("unable to open config.zig for writing"); - const char *zig_version = "0.14.0-dev.1078+0f0f543a9"; + const char *zig_version = "0.14.0-dev.1294+df6907f60"; int written = fprintf(f, "pub const have_llvm = false;\n" diff --git a/zig/build.zig b/zig/build.zig index 085201e954..1b95c190d1 100644 --- a/zig/build.zig +++ b/zig/build.zig @@ -379,6 +379,8 @@ pub fn build(b: *std.Build) !void { } const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{}; + const test_target_filters = b.option([]const []const u8, "test-target-filter", "Skip tests whose target triple do not match any filter") orelse &[0][]const u8{}; + const test_slow_targets = b.option(bool, "test-slow-targets", "Enable running module tests for targets that have a slow compiler backend") orelse false; const test_cases_options = b.addOptions(); @@ -455,8 +457,12 @@ pub fn build(b: *std.Build) !void { }); test_step.dependOn(test_cases_step); - test_step.dependOn(tests.addModuleTests(b, .{ + const test_modules_step = b.step("test-modules", "Run the per-target module tests"); + + test_modules_step.dependOn(tests.addModuleTests(b, .{ .test_filters = test_filters, + .test_target_filters = test_target_filters, + .test_slow_targets = test_slow_targets, .root_src = "test/behavior.zig", .name = "behavior", .desc = "Run the behavior tests", @@ -468,8 +474,10 @@ pub fn build(b: *std.Build) !void { .max_rss = 1 * 1024 * 1024 * 1024, })); - test_step.dependOn(tests.addModuleTests(b, .{ + test_modules_step.dependOn(tests.addModuleTests(b, .{ .test_filters = test_filters, + .test_target_filters = test_target_filters, + .test_slow_targets = test_slow_targets, .root_src = "test/c_import.zig", .name = "c-import", .desc = "Run the @cImport tests", @@ -480,8 +488,10 @@ pub fn build(b: *std.Build) !void { .skip_libc = skip_libc, })); - test_step.dependOn(tests.addModuleTests(b, .{ + test_modules_step.dependOn(tests.addModuleTests(b, .{ .test_filters = test_filters, + .test_target_filters = test_target_filters, + .test_slow_targets = test_slow_targets, .root_src = "lib/compiler_rt.zig", .name = "compiler-rt", .desc = "Run the compiler_rt tests", @@ -493,8 +503,10 @@ pub fn build(b: *std.Build) !void { .no_builtin = true, })); - test_step.dependOn(tests.addModuleTests(b, .{ + test_modules_step.dependOn(tests.addModuleTests(b, .{ .test_filters = test_filters, + .test_target_filters = test_target_filters, + .test_slow_targets = test_slow_targets, .root_src = "lib/c.zig", .name = "universal-libc", .desc = "Run the universal libc tests", @@ -506,6 +518,24 @@ pub fn build(b: *std.Build) !void { .no_builtin = true, })); + test_modules_step.dependOn(tests.addModuleTests(b, .{ + .test_filters = test_filters, + .test_target_filters = test_target_filters, + .test_slow_targets = test_slow_targets, + .root_src = "lib/std/std.zig", + .name = "std", + .desc = "Run the standard library tests", + .optimize_modes = optimization_modes, + .include_paths = &.{}, + .skip_single_threaded = skip_single_threaded, + .skip_non_native = skip_non_native, + .skip_libc = skip_libc, + // I observed a value of 4572626944 on the M2 CI. + .max_rss = 5029889638, + })); + + test_step.dependOn(test_modules_step); + test_step.dependOn(tests.addCompareOutputTests(b, test_filters, optimization_modes)); test_step.dependOn(tests.addStandaloneTests( b, @@ -519,39 +549,34 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(tests.addStackTraceTests(b, test_filters, optimization_modes)); test_step.dependOn(tests.addCliTests(b)); test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filters, optimization_modes)); - test_step.dependOn(tests.addModuleTests(b, .{ + if (tests.addDebuggerTests(b, .{ .test_filters = test_filters, - .root_src = "lib/std/std.zig", - .name = "std", - .desc = "Run the standard library tests", + .gdb = b.option([]const u8, "gdb", "path to gdb binary"), + .lldb = b.option([]const u8, "lldb", "path to lldb binary"), .optimize_modes = optimization_modes, - .include_paths = &.{}, .skip_single_threaded = skip_single_threaded, .skip_non_native = skip_non_native, .skip_libc = skip_libc, - // I observed a value of 4572626944 on the M2 CI. - .max_rss = 5029889638, - })); + })) |test_debugger_step| test_step.dependOn(test_debugger_step); try addWasiUpdateStep(b, version); const update_mingw_step = b.step("update-mingw", "Update zig's bundled mingw"); const opt_mingw_src_path = b.option([]const u8, "mingw-src", "path to mingw-w64 source directory"); - const update_mingw_exe = b.addExecutable(.{ - .name = "update_mingw", - .target = b.graph.host, - .root_source_file = b.path("tools/update_mingw.zig"), - }); - const update_mingw_run = b.addRunArtifact(update_mingw_exe); - update_mingw_run.addDirectoryArg(b.path("lib")); if (opt_mingw_src_path) |mingw_src_path| { + const update_mingw_exe = b.addExecutable(.{ + .name = "update_mingw", + .target = b.graph.host, + .root_source_file = b.path("tools/update_mingw.zig"), + }); + const update_mingw_run = b.addRunArtifact(update_mingw_exe); + update_mingw_run.addDirectoryArg(b.path("lib")); update_mingw_run.addDirectoryArg(.{ .cwd_relative = mingw_src_path }); + + update_mingw_step.dependOn(&update_mingw_run.step); } else { - // Intentionally cause an error if this build step is requested. - update_mingw_run.addArg("--missing-mingw-source-directory"); + update_mingw_step.dependOn(&b.addFail("The -Dmingw-src=... option is required for this step").step); } - - update_mingw_step.dependOn(&update_mingw_run.step); } fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { diff --git a/zig/cmake/Findclang.cmake b/zig/cmake/Findclang.cmake index 4dc833af3f..c21fe18837 100644 --- a/zig/cmake/Findclang.cmake +++ b/zig/cmake/Findclang.cmake @@ -18,6 +18,7 @@ if(${LLVM_LINK_MODE} STREQUAL "shared") find_library(CLANG_LIBRARIES NAMES libclang-cpp.so.18 + libclang-cpp.so.18.1 clang-cpp-18.0 clang-cpp180 clang-cpp diff --git a/zig/lib/compiler/aro/aro/Builtins/Builtin.zig b/zig/lib/compiler/aro/aro/Builtins/Builtin.zig index 9564bfecb5..c5cf98608b 100644 --- a/zig/lib/compiler/aro/aro/Builtins/Builtin.zig +++ b/zig/lib/compiler/aro/aro/Builtins/Builtin.zig @@ -5165,7 +5165,7 @@ const dafsa = [_]Node{ .{ .char = 'e', .end_of_word = false, .end_of_list = true, .number = 1, .child_index = 4913 }, }; pub const data = blk: { - @setEvalBranchQuota(3986); + @setEvalBranchQuota(30_000); break :blk [_]@This(){ // _Block_object_assign .{ .tag = @enumFromInt(0), .properties = .{ .param_str = "vv*vC*iC", .header = .blocks, .attributes = .{ .lib_function_without_prefix = true } } }, diff --git a/zig/lib/compiler/aro/aro/Compilation.zig b/zig/lib/compiler/aro/aro/Compilation.zig index 21d6823253..d361f42c90 100644 --- a/zig/lib/compiler/aro/aro/Compilation.zig +++ b/zig/lib/compiler/aro/aro/Compilation.zig @@ -217,7 +217,6 @@ fn generateDateAndTime(w: anytype, timestamp: u47) !void { }); const day_names = [_][]const u8{ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; - // days since Thu Oct 1 1970 const day_name = day_names[@intCast((epoch_day.day + 3) % 7)]; try w.print("#define __TIMESTAMP__ \"{s} {s} {d: >2} {d:0>2}:{d:0>2}:{d:0>2} {d}\"\n", .{ day_name, diff --git a/zig/lib/compiler/aro/aro/Parser.zig b/zig/lib/compiler/aro/aro/Parser.zig index 956dc5e114..1cb5e18934 100644 --- a/zig/lib/compiler/aro/aro/Parser.zig +++ b/zig/lib/compiler/aro/aro/Parser.zig @@ -4802,6 +4802,7 @@ const CallExpr = union(enum) { } fn shouldPromoteVarArg(self: CallExpr, arg_idx: u32) bool { + @setEvalBranchQuota(2000); return switch (self) { .standard => true, .builtin => |builtin| switch (builtin.tag) { @@ -4902,6 +4903,7 @@ const CallExpr = union(enum) { } fn returnType(self: CallExpr, p: *Parser, callable_ty: Type) Type { + @setEvalBranchQuota(6000); return switch (self) { .standard => callable_ty.returnType(), .builtin => |builtin| switch (builtin.tag) { diff --git a/zig/lib/compiler/aro/backend/Object/Elf.zig b/zig/lib/compiler/aro/backend/Object/Elf.zig index a14830813f..2a303d348c 100644 --- a/zig/lib/compiler/aro/backend/Object/Elf.zig +++ b/zig/lib/compiler/aro/backend/Object/Elf.zig @@ -199,7 +199,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void { const elf_header = std.elf.Elf64_Ehdr{ .e_ident = .{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .e_type = std.elf.ET.REL, // we only produce relocatables - .e_machine = elf.obj.target.cpu.arch.toElfMachine(), + .e_machine = elf.obj.target.toElfMachine(), .e_version = 1, .e_entry = 0, // linker will handle this .e_phoff = 0, // no program header diff --git a/zig/lib/compiler/objcopy.zig b/zig/lib/compiler/objcopy.zig index f3360c8108..b48fb52e82 100644 --- a/zig/lib/compiler/objcopy.zig +++ b/zig/lib/compiler/objcopy.zig @@ -201,9 +201,10 @@ fn cmdObjCopy( if (seen_update) fatal("zig objcopy only supports 1 update for now", .{}); seen_update = true; - try server.serveEmitBinPath(output, .{ - .flags = .{ .cache_hit = false }, - }); + // The build system already knows what the output is at this point, we + // only need to communicate that the process has finished. + // Use the empty error bundle to indicate that the update is done. + try server.serveErrorBundle(std.zig.ErrorBundle.empty); }, else => fatal("unsupported message: {s}", .{@tagName(hdr.tag)}), } diff --git a/zig/lib/compiler/std-docs.zig b/zig/lib/compiler/std-docs.zig index c11665101c..4cfdf9b1e3 100644 --- a/zig/lib/compiler/std-docs.zig +++ b/zig/lib/compiler/std-docs.zig @@ -181,7 +181,6 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { }, }, }); - const w = response.writer(); var std_dir = try context.lib_dir.openDir("std", .{ .iterate = true }); defer std_dir.close(); @@ -189,6 +188,9 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { var walker = try std_dir.walk(gpa); defer walker.deinit(); + var archiver = std.tar.writer(response.writer()); + archiver.prefix = "std"; + while (try walker.next()) |entry| { switch (entry.kind) { .file => { @@ -199,47 +201,21 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void { }, else => continue, } - - var file = try std_dir.openFile(entry.path, .{}); + var file = try entry.dir.openFile(entry.basename, .{}); defer file.close(); - - const stat = try file.stat(); - const padding = p: { - const remainder = stat.size % 512; - break :p if (remainder > 0) 512 - remainder else 0; - }; - - var file_header = std.tar.output.Header.init(); - file_header.typeflag = .regular; - try file_header.setPath("std", entry.path); - try file_header.setSize(stat.size); - try file_header.updateChecksum(); - try w.writeAll(std.mem.asBytes(&file_header)); - try w.writeFile(file); - try w.writeByteNTimes(0, padding); + try archiver.writeFile(entry.path, file); } { // Since this command is JIT compiled, the builtin module available in // this source file corresponds to the user's host system. const builtin_zig = @embedFile("builtin"); - - var file_header = std.tar.output.Header.init(); - file_header.typeflag = .regular; - try file_header.setPath("builtin", "builtin.zig"); - try file_header.setSize(builtin_zig.len); - try file_header.updateChecksum(); - try w.writeAll(std.mem.asBytes(&file_header)); - try w.writeAll(builtin_zig); - const padding = p: { - const remainder = builtin_zig.len % 512; - break :p if (remainder > 0) 512 - remainder else 0; - }; - try w.writeByteNTimes(0, padding); + archiver.prefix = "builtin"; + try archiver.writeFileBytes("builtin.zig", builtin_zig, .{}); } // intentionally omitting the pointless trailer - //try w.writeByteNTimes(0, 512 * 2); + //try archiver.finish(); try response.end(); } diff --git a/zig/lib/init/src/main.zig b/zig/lib/init/src/main.zig index a347913094..0c4bb73429 100644 --- a/zig/lib/init/src/main.zig +++ b/zig/lib/init/src/main.zig @@ -27,7 +27,7 @@ test "simple test" { } test "fuzz example" { - // Try passing `--fuzz` to `zig build` and see if it manages to fail this test case! + // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case! const input_bytes = std.testing.fuzzInput(.{}); try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input_bytes)); } diff --git a/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/alpha/xstatver.h b/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/alpha/xstatver.h new file mode 100644 index 0000000000..1cb5d26bb1 --- /dev/null +++ b/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/alpha/xstatver.h @@ -0,0 +1,14 @@ +/* Versions of the 'struct stat' data structure used in compatibility xstat + functions. */ +#define _STAT_VER_KERNEL 0 +#define _STAT_VER_GLIBC2 1 +#define _STAT_VER_GLIBC2_1 2 +#define _STAT_VER_KERNEL64 3 +#define _STAT_VER_GLIBC2_3_4 3 +#define _STAT_VER_LINUX 3 +#define _STAT_VER _STAT_VER_LINUX + +/* Versions of the 'xmknod' interface used in compatibility xmknod + functions. */ +#define _MKNOD_VER_LINUX 0 +#define _MKNOD_VER _MKNOD_VER_LINUX diff --git a/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/xstatver.h b/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/xstatver.h new file mode 100644 index 0000000000..8e1801b603 --- /dev/null +++ b/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/xstatver.h @@ -0,0 +1,13 @@ +/* Versions of the 'struct stat' data structure used in compatibility xstat + functions. */ +#define _STAT_VER_LINUX_OLD 1 +#define _STAT_VER_KERNEL 1 +#define _STAT_VER_SVR4 2 +#define _STAT_VER_LINUX 3 +#define _STAT_VER _STAT_VER_LINUX + +/* Versions of the 'xmknod' interface used in compatibility xmknod + functions. */ +#define _MKNOD_VER_LINUX 1 +#define _MKNOD_VER_SVR4 2 +#define _MKNOD_VER _MKNOD_VER_LINUX diff --git a/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/hppa/xstatver.h b/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/hppa/xstatver.h new file mode 100644 index 0000000000..8e1801b603 --- /dev/null +++ b/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/hppa/xstatver.h @@ -0,0 +1,13 @@ +/* Versions of the 'struct stat' data structure used in compatibility xstat + functions. */ +#define _STAT_VER_LINUX_OLD 1 +#define _STAT_VER_KERNEL 1 +#define _STAT_VER_SVR4 2 +#define _STAT_VER_LINUX 3 +#define _STAT_VER _STAT_VER_LINUX + +/* Versions of the 'xmknod' interface used in compatibility xmknod + functions. */ +#define _MKNOD_VER_LINUX 1 +#define _MKNOD_VER_SVR4 2 +#define _MKNOD_VER _MKNOD_VER_LINUX diff --git a/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/microblaze/xstatver.h b/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/microblaze/xstatver.h new file mode 100644 index 0000000000..790cc834d2 --- /dev/null +++ b/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/microblaze/xstatver.h @@ -0,0 +1,13 @@ +/* Versions of the 'struct stat' data structure used in compatibility xstat + functions. */ +#define _STAT_VER_LINUX_OLD 1 +#define _STAT_VER_KERNEL 1 +#define _STAT_VER_SVR4 2 +#define _STAT_VER_LINUX 3 +#define _STAT_VER _STAT_VER_LINUX /* The one defined below. */ + +/* Versions of the 'xmknod' interface used in compatibility xmknod + functions. */ +#define _MKNOD_VER_LINUX 1 +#define _MKNOD_VER_SVR4 2 +#define _MKNOD_VER _MKNOD_VER_LINUX diff --git a/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/sh/xstatver.h b/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/sh/xstatver.h new file mode 100644 index 0000000000..8e1801b603 --- /dev/null +++ b/zig/lib/libc/glibc/sysdeps/unix/sysv/linux/sh/xstatver.h @@ -0,0 +1,13 @@ +/* Versions of the 'struct stat' data structure used in compatibility xstat + functions. */ +#define _STAT_VER_LINUX_OLD 1 +#define _STAT_VER_KERNEL 1 +#define _STAT_VER_SVR4 2 +#define _STAT_VER_LINUX 3 +#define _STAT_VER _STAT_VER_LINUX + +/* Versions of the 'xmknod' interface used in compatibility xmknod + functions. */ +#define _MKNOD_VER_LINUX 1 +#define _MKNOD_VER_SVR4 2 +#define _MKNOD_VER _MKNOD_VER_LINUX diff --git a/zig/lib/std/Build.zig b/zig/lib/std/Build.zig index 03743cf52e..82810bb02f 100644 --- a/zig/lib/std/Build.zig +++ b/zig/lib/std/Build.zig @@ -2373,7 +2373,7 @@ pub const LazyPath = union(enum) { // basis for not traversing up too many directories. var file_path: Cache.Path = .{ - .root_dir = gen.file.step.owner.build_root, + .root_dir = Cache.Directory.cwd(), .sub_path = gen.file.path orelse { std.debug.lockStdErr(); const stderr = std.io.getStdErr(); diff --git a/zig/lib/std/Build/Cache.zig b/zig/lib/std/Build/Cache.zig index 1dafcf3bb7..1eabdd54e6 100644 --- a/zig/lib/std/Build/Cache.zig +++ b/zig/lib/std/Build/Cache.zig @@ -896,8 +896,8 @@ pub const Manifest = struct { } } - /// Returns a hex encoded hash of the inputs. - pub fn final(self: *Manifest) HexDigest { + /// Returns a binary hash of the inputs. + pub fn finalBin(self: *Manifest) BinDigest { assert(self.manifest_file != null); // We don't close the manifest file yet, because we want to @@ -908,7 +908,12 @@ pub const Manifest = struct { var bin_digest: BinDigest = undefined; self.hash.hasher.final(&bin_digest); + return bin_digest; + } + /// Returns a hex encoded hash of the inputs. + pub fn final(self: *Manifest) HexDigest { + const bin_digest = self.finalBin(); return binToHex(bin_digest); } diff --git a/zig/lib/std/Build/Fuzz.zig b/zig/lib/std/Build/Fuzz.zig index 9857db5a1f..23f8a02692 100644 --- a/zig/lib/std/Build/Fuzz.zig +++ b/zig/lib/std/Build/Fuzz.zig @@ -100,6 +100,15 @@ pub fn start( } fn rebuildTestsWorkerRun(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog_node: std.Progress.Node) void { + rebuildTestsWorkerRunFallible(run, ttyconf, parent_prog_node) catch |err| { + const compile = run.producer.?; + log.err("step '{s}': failed to rebuild in fuzz mode: {s}", .{ + compile.step.name, @errorName(err), + }); + }; +} + +fn rebuildTestsWorkerRunFallible(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog_node: std.Progress.Node) !void { const gpa = run.step.owner.allocator; const stderr = std.io.getStdErr(); @@ -121,14 +130,9 @@ fn rebuildTestsWorkerRun(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog const rebuilt_bin_path = result catch |err| switch (err) { error.MakeFailed => return, - else => { - log.err("step '{s}': failed to rebuild in fuzz mode: {s}", .{ - compile.step.name, @errorName(err), - }); - return; - }, + else => |other| return other, }; - run.rebuilt_executable = rebuilt_bin_path; + run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename); } fn fuzzWorkerRun( diff --git a/zig/lib/std/Build/Fuzz/WebServer.zig b/zig/lib/std/Build/Fuzz/WebServer.zig index 950f3645ba..fbf6b8dbce 100644 --- a/zig/lib/std/Build/Fuzz/WebServer.zig +++ b/zig/lib/std/Build/Fuzz/WebServer.zig @@ -8,6 +8,8 @@ const Coverage = std.debug.Coverage; const abi = std.Build.Fuzz.abi; const log = std.log; const assert = std.debug.assert; +const Cache = std.Build.Cache; +const Path = Cache.Path; const WebServer = @This(); @@ -31,6 +33,10 @@ coverage_mutex: std.Thread.Mutex, /// Signaled when `coverage_files` changes. coverage_condition: std.Thread.Condition, +const fuzzer_bin_name = "fuzzer"; +const fuzzer_arch_os_abi = "wasm32-freestanding"; +const fuzzer_cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext"; + const CoverageMap = struct { mapped_memory: []align(std.mem.page_size) const u8, coverage: Coverage, @@ -181,9 +187,18 @@ fn serveWasm( // Do the compilation every request, so that the user can edit the files // and see the changes without restarting the server. - const wasm_binary_path = try buildWasmBinary(ws, arena, optimize_mode); + const wasm_base_path = try buildWasmBinary(ws, arena, optimize_mode); + const bin_name = try std.zig.binNameAlloc(arena, .{ + .root_name = fuzzer_bin_name, + .target = std.zig.system.resolveTargetQuery(std.Build.parseTargetQuery(.{ + .arch_os_abi = fuzzer_arch_os_abi, + .cpu_features = fuzzer_cpu_features, + }) catch unreachable) catch unreachable, + .output_mode = .Exe, + }); // std.http.Server does not have a sendfile API yet. - const file_contents = try std.fs.cwd().readFileAlloc(gpa, wasm_binary_path, 10 * 1024 * 1024); + const bin_path = try wasm_base_path.join(arena, bin_name); + const file_contents = try bin_path.root_dir.handle.readFileAlloc(gpa, bin_path.sub_path, 10 * 1024 * 1024); defer gpa.free(file_contents); try request.respond(file_contents, .{ .extra_headers = &.{ @@ -197,7 +212,7 @@ fn buildWasmBinary( ws: *WebServer, arena: Allocator, optimize_mode: std.builtin.OptimizeMode, -) ![]const u8 { +) !Path { const gpa = ws.gpa; const main_src_path: Build.Cache.Path = .{ @@ -219,11 +234,11 @@ fn buildWasmBinary( ws.zig_exe_path, "build-exe", // "-fno-entry", // "-O", @tagName(optimize_mode), // - "-target", "wasm32-freestanding", // - "-mcpu", "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext", // + "-target", fuzzer_arch_os_abi, // + "-mcpu", fuzzer_cpu_features, // "--cache-dir", ws.global_cache_directory.path orelse ".", // "--global-cache-dir", ws.global_cache_directory.path orelse ".", // - "--name", "fuzzer", // + "--name", fuzzer_bin_name, // "-rdynamic", // "-fsingle-threaded", // "--dep", "Walk", // @@ -251,7 +266,7 @@ fn buildWasmBinary( try sendMessage(child.stdin.?, .exit); const Header = std.zig.Server.Message.Header; - var result: ?[]const u8 = null; + var result: ?Path = null; var result_error_bundle = std.zig.ErrorBundle.empty; const stdout = poller.fifo(.stdout); @@ -288,13 +303,17 @@ fn buildWasmBinary( .extra = extra_array, }; }, - .emit_bin_path => { - const EbpHdr = std.zig.Server.Message.EmitBinPath; + .emit_digest => { + const EbpHdr = std.zig.Server.Message.EmitDigest; const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body)); if (!ebp_hdr.flags.cache_hit) { log.info("source changes detected; rebuilt wasm component", .{}); } - result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); + const digest = body[@sizeOf(EbpHdr)..][0..Cache.bin_digest_len]; + result = Path{ + .root_dir = ws.global_cache_directory, + .sub_path = try arena.dupe(u8, "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*)), + }; }, else => {}, // ignore other messages } @@ -456,7 +475,6 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { }, }, }); - const w = response.writer(); const DedupeTable = std.ArrayHashMapUnmanaged(Build.Cache.Path, void, Build.Cache.Path.TableAdapter, false); var dedupe_table: DedupeTable = .{}; @@ -490,6 +508,8 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { var cwd_cache: ?[]const u8 = null; + var archiver = std.tar.writer(response.writer()); + for (deduped_paths) |joined_path| { var file = joined_path.root_dir.handle.openFile(joined_path.sub_path, .{}) catch |err| { log.err("failed to open {}: {s}", .{ joined_path, @errorName(err) }); @@ -497,33 +517,12 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { }; defer file.close(); - const stat = file.stat() catch |err| { - log.err("failed to stat {}: {s}", .{ joined_path, @errorName(err) }); - continue; - }; - if (stat.kind != .file) - continue; - - const padding = p: { - const remainder = stat.size % 512; - break :p if (remainder > 0) 512 - remainder else 0; - }; - - var file_header = std.tar.output.Header.init(); - file_header.typeflag = .regular; - try file_header.setPath( - joined_path.root_dir.path orelse try memoizedCwd(arena, &cwd_cache), - joined_path.sub_path, - ); - try file_header.setSize(stat.size); - try file_header.updateChecksum(); - try w.writeAll(std.mem.asBytes(&file_header)); - try w.writeFile(file); - try w.writeByteNTimes(0, padding); + archiver.prefix = joined_path.root_dir.path orelse try memoizedCwd(arena, &cwd_cache); + try archiver.writeFile(joined_path.sub_path, file); } // intentionally omitting the pointless trailer - //try w.writeByteNTimes(0, 512 * 2); + //try archiver.finish(); try response.end(); } @@ -588,10 +587,7 @@ fn prepareTables( }; errdefer gop.value_ptr.coverage.deinit(gpa); - const rebuilt_exe_path: Build.Cache.Path = .{ - .root_dir = Build.Cache.Directory.cwd(), - .sub_path = run_step.rebuilt_executable.?, - }; + const rebuilt_exe_path = run_step.rebuilt_executable.?; var debug_info = std.debug.Info.load(gpa, rebuilt_exe_path, &gop.value_ptr.coverage) catch |err| { log.err("step '{s}': failed to load debug information for '{}': {s}", .{ run_step.step.name, rebuilt_exe_path, @errorName(err), diff --git a/zig/lib/std/Build/Step.zig b/zig/lib/std/Build/Step.zig index 47a6e49a82..346ab2c9b3 100644 --- a/zig/lib/std/Build/Step.zig +++ b/zig/lib/std/Build/Step.zig @@ -317,6 +317,8 @@ const Build = std.Build; const Allocator = std.mem.Allocator; const assert = std.debug.assert; const builtin = @import("builtin"); +const Cache = Build.Cache; +const Path = Cache.Path; pub fn evalChildProcess(s: *Step, argv: []const []const u8) ![]u8 { const run_result = try captureChildProcess(s, std.Progress.Node.none, argv); @@ -373,7 +375,7 @@ pub fn evalZigProcess( argv: []const []const u8, prog_node: std.Progress.Node, watch: bool, -) !?[]const u8 { +) !?Path { if (s.getZigProcess()) |zp| update: { assert(watch); if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd); @@ -477,7 +479,7 @@ pub fn evalZigProcess( return result; } -fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?[]const u8 { +fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?Path { const b = s.owner; const arena = b.allocator; @@ -487,7 +489,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?[]const u8 { if (!watch) try sendMessage(zp.child.stdin.?, .exit); const Header = std.zig.Server.Message.Header; - var result: ?[]const u8 = null; + var result: ?Path = null; const stdout = zp.poller.fifo(.stdout); @@ -531,16 +533,15 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?[]const u8 { break; } }, - .emit_bin_path => { - const EbpHdr = std.zig.Server.Message.EmitBinPath; + .emit_digest => { + const EbpHdr = std.zig.Server.Message.EmitDigest; const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body)); s.result_cached = ebp_hdr.flags.cache_hit; - result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); - if (watch) { - // This message indicates the end of the update. - stdout.discard(body.len); - break; - } + const digest = body[@sizeOf(EbpHdr)..][0..Cache.bin_digest_len]; + result = Path{ + .root_dir = b.cache_root, + .sub_path = try arena.dupe(u8, "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*)), + }; }, .file_system_inputs => { s.clearWatchInputs(); diff --git a/zig/lib/std/Build/Step/Compile.zig b/zig/lib/std/Build/Step/Compile.zig index 054dda9e90..1aeebbb55b 100644 --- a/zig/lib/std/Build/Step/Compile.zig +++ b/zig/lib/std/Build/Step/Compile.zig @@ -17,6 +17,7 @@ const Module = std.Build.Module; const InstallDir = std.Build.InstallDir; const GeneratedFile = std.Build.GeneratedFile; const Compile = @This(); +const Path = std.Build.Cache.Path; pub const base_id: Step.Id = .compile; @@ -1765,7 +1766,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const zig_args = try getZigArgs(compile, false); - const maybe_output_bin_path = step.evalZigProcess( + const maybe_output_dir = step.evalZigProcess( zig_args, options.progress_node, (b.graph.incremental == true) and options.watch, @@ -1779,53 +1780,51 @@ fn make(step: *Step, options: Step.MakeOptions) !void { }; // Update generated files - if (maybe_output_bin_path) |output_bin_path| { - const output_dir = fs.path.dirname(output_bin_path).?; - + if (maybe_output_dir) |output_dir| { if (compile.emit_directory) |lp| { - lp.path = output_dir; + lp.path = b.fmt("{}", .{output_dir}); } // -femit-bin[=path] (default) Output machine code if (compile.generated_bin) |bin| { - bin.path = b.pathJoin(&.{ output_dir, compile.out_filename }); + bin.path = output_dir.joinString(b.allocator, compile.out_filename) catch @panic("OOM"); } - const sep = std.fs.path.sep; + const sep = std.fs.path.sep_str; // output PDB if someone requested it if (compile.generated_pdb) |pdb| { - pdb.path = b.fmt("{s}{c}{s}.pdb", .{ output_dir, sep, compile.name }); + pdb.path = b.fmt("{}" ++ sep ++ "{s}.pdb", .{ output_dir, compile.name }); } // -femit-implib[=path] (default) Produce an import .lib when building a Windows DLL if (compile.generated_implib) |implib| { - implib.path = b.fmt("{s}{c}{s}.lib", .{ output_dir, sep, compile.name }); + implib.path = b.fmt("{}" ++ sep ++ "{s}.lib", .{ output_dir, compile.name }); } // -femit-h[=path] Generate a C header file (.h) if (compile.generated_h) |lp| { - lp.path = b.fmt("{s}{c}{s}.h", .{ output_dir, sep, compile.name }); + lp.path = b.fmt("{}" ++ sep ++ "{s}.h", .{ output_dir, compile.name }); } // -femit-docs[=path] Create a docs/ dir with html documentation if (compile.generated_docs) |generated_docs| { - generated_docs.path = b.pathJoin(&.{ output_dir, "docs" }); + generated_docs.path = output_dir.joinString(b.allocator, "docs") catch @panic("OOM"); } // -femit-asm[=path] Output .s (assembly code) if (compile.generated_asm) |lp| { - lp.path = b.fmt("{s}{c}{s}.s", .{ output_dir, sep, compile.name }); + lp.path = b.fmt("{}" ++ sep ++ "{s}.s", .{ output_dir, compile.name }); } // -femit-llvm-ir[=path] Produce a .ll file with optimized LLVM IR (requires LLVM extensions) if (compile.generated_llvm_ir) |lp| { - lp.path = b.fmt("{s}{c}{s}.ll", .{ output_dir, sep, compile.name }); + lp.path = b.fmt("{}" ++ sep ++ "{s}.ll", .{ output_dir, compile.name }); } // -femit-llvm-bc[=path] Produce an optimized LLVM module as a .bc file (requires LLVM extensions) if (compile.generated_llvm_bc) |lp| { - lp.path = b.fmt("{s}{c}{s}.bc", .{ output_dir, sep, compile.name }); + lp.path = b.fmt("{}" ++ sep ++ "{s}.bc", .{ output_dir, compile.name }); } } @@ -1841,7 +1840,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { } } -pub fn rebuildInFuzzMode(c: *Compile, progress_node: std.Progress.Node) ![]const u8 { +pub fn rebuildInFuzzMode(c: *Compile, progress_node: std.Progress.Node) !Path { const gpa = c.step.owner.allocator; c.step.result_error_msgs.clearRetainingCapacity(); diff --git a/zig/lib/std/Build/Step/InstallArtifact.zig b/zig/lib/std/Build/Step/InstallArtifact.zig index 4e778d897c..3d404eb8ca 100644 --- a/zig/lib/std/Build/Step/InstallArtifact.zig +++ b/zig/lib/std/Build/Step/InstallArtifact.zig @@ -125,10 +125,10 @@ fn make(step: *Step, options: Step.MakeOptions) !void { if (install_artifact.dest_dir) |dest_dir| { const full_dest_path = b.getInstallPath(dest_dir, install_artifact.dest_sub_path); - const full_src_path = install_artifact.emitted_bin.?.getPath2(b, step); - const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { + const src_path = install_artifact.emitted_bin.?.getPath3(b, step); + const p = fs.Dir.updateFile(src_path.root_dir.handle, src_path.sub_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), + src_path.sub_path, full_dest_path, @errorName(err), }); }; all_cached = all_cached and p == .fresh; @@ -141,22 +141,22 @@ fn make(step: *Step, options: Step.MakeOptions) !void { } if (install_artifact.implib_dir) |implib_dir| { - const full_src_path = install_artifact.emitted_implib.?.getPath2(b, step); - const full_implib_path = b.getInstallPath(implib_dir, fs.path.basename(full_src_path)); - const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| { + const src_path = install_artifact.emitted_implib.?.getPath3(b, step); + const full_implib_path = b.getInstallPath(implib_dir, fs.path.basename(src_path.sub_path)); + const p = fs.Dir.updateFile(src_path.root_dir.handle, src_path.sub_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), + src_path.sub_path, full_implib_path, @errorName(err), }); }; all_cached = all_cached and p == .fresh; } if (install_artifact.pdb_dir) |pdb_dir| { - const full_src_path = install_artifact.emitted_pdb.?.getPath2(b, step); - const full_pdb_path = b.getInstallPath(pdb_dir, fs.path.basename(full_src_path)); - const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| { + const src_path = install_artifact.emitted_pdb.?.getPath3(b, step); + const full_pdb_path = b.getInstallPath(pdb_dir, fs.path.basename(src_path.sub_path)); + const p = fs.Dir.updateFile(src_path.root_dir.handle, src_path.sub_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), + src_path.sub_path, full_pdb_path, @errorName(err), }); }; all_cached = all_cached and p == .fresh; @@ -164,11 +164,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void { if (install_artifact.h_dir) |h_dir| { if (install_artifact.emitted_h) |emitted_h| { - const full_src_path = emitted_h.getPath2(b, step); - const full_h_path = b.getInstallPath(h_dir, fs.path.basename(full_src_path)); - const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| { + const src_path = emitted_h.getPath3(b, step); + const full_h_path = b.getInstallPath(h_dir, fs.path.basename(src_path.sub_path)); + const p = fs.Dir.updateFile(src_path.root_dir.handle, src_path.sub_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), + src_path.sub_path, full_h_path, @errorName(err), }); }; all_cached = all_cached and p == .fresh; @@ -176,22 +176,22 @@ fn make(step: *Step, options: Step.MakeOptions) !void { for (install_artifact.artifact.installed_headers.items) |installation| switch (installation) { .file => |file| { - const full_src_path = file.source.getPath2(b, step); + const src_path = file.source.getPath3(b, step); const full_h_path = b.getInstallPath(h_dir, file.dest_rel_path); - const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| { + const p = fs.Dir.updateFile(src_path.root_dir.handle, src_path.sub_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), + src_path.sub_path, full_h_path, @errorName(err), }); }; all_cached = all_cached and p == .fresh; }, .directory => |dir| { - const full_src_dir_path = dir.source.getPath2(b, step); + const src_dir_path = dir.source.getPath3(b, step); const full_h_prefix = b.getInstallPath(h_dir, dir.dest_rel_path); - var src_dir = b.build_root.handle.openDir(full_src_dir_path, .{ .iterate = true }) catch |err| { + var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.sub_path, .{ .iterate = true }) catch |err| { return step.fail("unable to open source directory '{s}': {s}", .{ - full_src_dir_path, @errorName(err), + src_dir_path.sub_path, @errorName(err), }); }; defer src_dir.close(); @@ -208,14 +208,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void { continue :next_entry; } } - const full_src_entry_path = b.pathJoin(&.{ full_src_dir_path, entry.path }); + + const src_entry_path = src_dir_path.join(b.allocator, entry.path) catch @panic("OOM"); const full_dest_path = b.pathJoin(&.{ full_h_prefix, entry.path }); switch (entry.kind) { .directory => try cwd.makePath(full_dest_path), .file => { - const p = fs.Dir.updateFile(cwd, full_src_entry_path, cwd, full_dest_path, .{}) catch |err| { + const p = fs.Dir.updateFile(src_entry_path.root_dir.handle, src_entry_path.sub_path, cwd, full_dest_path, .{}) catch |err| { return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ - full_src_entry_path, full_dest_path, @errorName(err), + src_entry_path.sub_path, full_dest_path, @errorName(err), }); }; all_cached = all_cached and p == .fresh; diff --git a/zig/lib/std/Build/Step/Run.zig b/zig/lib/std/Build/Step/Run.zig index 5d9ebce9aa..0c011e25ed 100644 --- a/zig/lib/std/Build/Step/Run.zig +++ b/zig/lib/std/Build/Step/Run.zig @@ -7,6 +7,7 @@ const mem = std.mem; const process = std.process; const EnvMap = process.EnvMap; const assert = std.debug.assert; +const Path = Build.Cache.Path; const Run = @This(); @@ -93,7 +94,7 @@ cached_test_metadata: ?CachedTestMetadata = null, /// Populated during the fuzz phase if this run step corresponds to a unit test /// executable that contains fuzz tests. -rebuilt_executable: ?[]const u8, +rebuilt_executable: ?Path, /// If this Run step was produced by a Compile step, it is tracked here. producer: ?*Step.Compile, @@ -872,7 +873,7 @@ pub fn rerunInFuzzMode( .artifact => |pa| { const artifact = pa.artifact; const file_path = if (artifact == run.producer.?) - run.rebuilt_executable.? + b.fmt("{}", .{run.rebuilt_executable.?}) else (artifact.installed_path orelse artifact.generated_bin.?.path.?); try argv_list.append(arena, b.fmt("{s}{s}", .{ pa.prefix, file_path })); diff --git a/zig/lib/std/Build/Step/TranslateC.zig b/zig/lib/std/Build/Step/TranslateC.zig index ac4729abd0..9ef5f7acdf 100644 --- a/zig/lib/std/Build/Step/TranslateC.zig +++ b/zig/lib/std/Build/Step/TranslateC.zig @@ -29,7 +29,7 @@ pub const Options = struct { pub fn create(owner: *std.Build, options: Options) *TranslateC { const translate_c = owner.allocator.create(TranslateC) catch @panic("OOM"); const source = options.root_source_file.dupe(owner); - translate_c.* = TranslateC{ + translate_c.* = .{ .step = Step.init(.{ .id = base_id, .name = "translate-c", @@ -42,7 +42,7 @@ pub fn create(owner: *std.Build, options: Options) *TranslateC { .out_basename = undefined, .target = options.target, .optimize = options.optimize, - .output_file = std.Build.GeneratedFile{ .step = &translate_c.step }, + .output_file = .{ .step = &translate_c.step }, .link_libc = options.link_libc, .use_clang = options.use_clang, }; @@ -89,6 +89,9 @@ pub fn addModule(translate_c: *TranslateC, name: []const u8) *std.Build.Module { pub fn createModule(translate_c: *TranslateC) *std.Build.Module { return translate_c.step.owner.createModule(.{ .root_source_file = translate_c.getOutput(), + .target = translate_c.target, + .optimize = translate_c.optimize, + .link_libc = translate_c.link_libc, }); } @@ -153,12 +156,12 @@ fn make(step: *Step, options: Step.MakeOptions) !void { try argv_list.append(c_macro); } - try argv_list.append(translate_c.source.getPath2(b, step)); + const c_source_path = translate_c.source.getPath2(b, step); + try argv_list.append(c_source_path); - const output_path = try step.evalZigProcess(argv_list.items, prog_node, false); + const output_dir = try step.evalZigProcess(argv_list.items, prog_node, false); - translate_c.out_basename = fs.path.basename(output_path.?); - const output_dir = fs.path.dirname(output_path.?).?; - - translate_c.output_file.path = b.pathJoin(&.{ output_dir, translate_c.out_basename }); + const basename = std.fs.path.stem(std.fs.path.basename(c_source_path)); + translate_c.out_basename = b.fmt("{s}.zig", .{basename}); + translate_c.output_file.path = output_dir.?.joinString(b.allocator, translate_c.out_basename) catch @panic("OOM"); } diff --git a/zig/lib/std/Target.zig b/zig/lib/std/Target.zig index 2ca02aa2d3..a231892e6e 100644 --- a/zig/lib/std/Target.zig +++ b/zig/lib/std/Target.zig @@ -75,7 +75,13 @@ pub const Os = struct { pub inline fn isDarwin(tag: Tag) bool { return switch (tag) { - .ios, .macos, .watchos, .tvos, .visionos => true, + .driverkit, + .ios, + .macos, + .tvos, + .visionos, + .watchos, + => true, else => false, }; } @@ -116,7 +122,13 @@ pub const Os = struct { pub fn dynamicLibSuffix(tag: Tag) [:0]const u8 { return switch (tag) { .windows, .uefi => ".dll", - .ios, .macos, .watchos, .tvos, .visionos => ".dylib", + .driverkit, + .ios, + .macos, + .tvos, + .visionos, + .watchos, + => ".dylib", else => ".so", }; } @@ -132,7 +144,7 @@ pub const Os = struct { } pub inline fn isGnuLibC(tag: Os.Tag, abi: Abi) bool { - return tag == .linux and abi.isGnu(); + return (tag == .hurd or tag == .linux) and abi.isGnu(); } pub fn defaultVersionRange(tag: Tag, arch: Cpu.Arch) Os { @@ -163,7 +175,6 @@ pub const Os = struct { .hermit, .hurd, .emscripten, - .driverkit, .shadermodel, .uefi, .opencl, // TODO: OpenCL versions @@ -175,6 +186,7 @@ pub const Os = struct { .other, => .none, + .driverkit, .freebsd, .macos, .ios, @@ -392,7 +404,6 @@ pub const Os = struct { .hermit, .hurd, .emscripten, - .driverkit, .shadermodel, .uefi, .opencl, // TODO: OpenCL versions @@ -410,6 +421,12 @@ pub const Os = struct { .max = .{ .major = 14, .minor = 0, .patch = 0 }, }, }, + .driverkit => .{ + .semver = .{ + .min = .{ .major = 19, .minor = 0, .patch = 0 }, + .max = .{ .major = 24, .minor = 0, .patch = 0 }, + }, + }, .macos => switch (arch) { .aarch64 => VersionRange{ .semver = .{ @@ -554,7 +571,9 @@ pub const Os = struct { pub fn requiresLibC(os: Os) bool { return switch (os.tag) { .freebsd, + .aix, .netbsd, + .driverkit, .macos, .ios, .tvos, @@ -575,7 +594,6 @@ pub const Os = struct { .ps3, .zos, .rtems, - .aix, .cuda, .nvcl, .amdhsa, @@ -589,7 +607,6 @@ pub const Os = struct { .hurd, .wasi, .emscripten, - .driverkit, .shadermodel, .uefi, .opencl, @@ -818,6 +835,102 @@ pub const ObjectFormat = enum { } }; +pub fn toElfMachine(target: Target) std.elf.EM { + if (target.os.tag == .elfiamcu) return .IAMCU; + + return switch (target.cpu.arch) { + .amdgcn => .AMDGPU, + .arc => .ARC_COMPACT2, + .arm, .armeb, .thumb, .thumbeb => .ARM, + .aarch64, .aarch64_be => .AARCH64, + .avr => .AVR, + .bpfel, .bpfeb => .BPF, + .csky => .CSKY, + .hexagon => .HEXAGON, + .kalimba => .CSR_KALIMBA, + .lanai => .LANAI, + .loongarch32, .loongarch64 => .LOONGARCH, + .m68k => .@"68K", + .mips, .mips64, .mipsel, .mips64el => .MIPS, + .msp430 => .MSP430, + .powerpc, .powerpcle => .PPC, + .powerpc64, .powerpc64le => .PPC64, + .riscv32, .riscv64 => .RISCV, + .s390x => .S390, + .sparc => if (Target.sparc.featureSetHas(target.cpu.features, .v9)) .SPARC32PLUS else .SPARC, + .sparc64 => .SPARCV9, + .spu_2 => .SPU_2, + .x86 => .@"386", + .x86_64 => .X86_64, + .xcore => .XCORE, + .xtensa => .XTENSA, + + .dxil, + .nvptx, + .nvptx64, + .spirv, + .spirv32, + .spirv64, + .ve, + .wasm32, + .wasm64, + => .NONE, + }; +} + +pub fn toCoffMachine(target: Target) std.coff.MachineType { + return switch (target.cpu.arch) { + .arm => .ARM, + .thumb => .THUMB, + .aarch64 => .ARM64, + .loongarch32 => .LOONGARCH32, + .loongarch64 => .LOONGARCH64, + .riscv32 => .RISCV32, + .riscv64 => .RISCV64, + .x86 => .I386, + .x86_64 => .X64, + + .amdgcn, + .arc, + .armeb, + .thumbeb, + .aarch64_be, + .avr, + .bpfel, + .bpfeb, + .csky, + .dxil, + .hexagon, + .kalimba, + .lanai, + .m68k, + .mips, + .mipsel, + .mips64, + .mips64el, + .msp430, + .nvptx, + .nvptx64, + .powerpc, + .powerpcle, + .powerpc64, + .powerpc64le, + .s390x, + .sparc, + .sparc64, + .spirv, + .spirv32, + .spirv64, + .spu_2, + .ve, + .wasm32, + .wasm64, + .xcore, + .xtensa, + => .UNKNOWN, + }; +} + pub const SubSystem = enum { Console, Windows, @@ -1191,106 +1304,6 @@ pub const Cpu = struct { return error.UnknownCpuModel; } - pub fn toElfMachine(arch: Arch) std.elf.EM { - return switch (arch) { - .avr => .AVR, - .msp430 => .MSP430, - .arc => .ARC, - .arm => .ARM, - .armeb => .ARM, - .hexagon => .HEXAGON, - .dxil => .NONE, - .m68k => .@"68K", - .mips => .MIPS, - .mipsel => .MIPS_RS3_LE, - .powerpc, .powerpcle => .PPC, - .riscv32 => .RISCV, - .sparc => .SPARC, - .thumb => .ARM, - .thumbeb => .ARM, - .x86 => .@"386", - .xcore => .XCORE, - .xtensa => .XTENSA, - .nvptx => .NONE, - .kalimba => .CSR_KALIMBA, - .lanai => .LANAI, - .wasm32 => .NONE, - .aarch64 => .AARCH64, - .aarch64_be => .AARCH64, - .mips64 => .MIPS, - .mips64el => .MIPS_RS3_LE, - .powerpc64 => .PPC64, - .powerpc64le => .PPC64, - .riscv64 => .RISCV, - .x86_64 => .X86_64, - .nvptx64 => .NONE, - .wasm64 => .NONE, - .amdgcn => .AMDGPU, - .bpfel => .BPF, - .bpfeb => .BPF, - .csky => .CSKY, - .sparc64 => .SPARCV9, - .s390x => .S390, - .ve => .NONE, - .spu_2 => .SPU_2, - .spirv => .NONE, - .spirv32 => .NONE, - .spirv64 => .NONE, - .loongarch32 => .LOONGARCH, - .loongarch64 => .LOONGARCH, - }; - } - - pub fn toCoffMachine(arch: Arch) std.coff.MachineType { - return switch (arch) { - .avr => .Unknown, - .msp430 => .Unknown, - .arc => .Unknown, - .arm => .ARM, - .armeb => .Unknown, - .dxil => .Unknown, - .hexagon => .Unknown, - .m68k => .Unknown, - .mips => .Unknown, - .mipsel => .Unknown, - .powerpc, .powerpcle => .POWERPC, - .riscv32 => .RISCV32, - .sparc => .Unknown, - .thumb => .Thumb, - .thumbeb => .Thumb, - .x86 => .I386, - .xcore => .Unknown, - .xtensa => .Unknown, - .nvptx => .Unknown, - .kalimba => .Unknown, - .lanai => .Unknown, - .wasm32 => .Unknown, - .aarch64 => .ARM64, - .aarch64_be => .ARM64, - .mips64 => .Unknown, - .mips64el => .Unknown, - .powerpc64 => .Unknown, - .powerpc64le => .Unknown, - .riscv64 => .RISCV64, - .x86_64 => .X64, - .nvptx64 => .Unknown, - .wasm64 => .Unknown, - .amdgcn => .Unknown, - .bpfel => .Unknown, - .bpfeb => .Unknown, - .csky => .Unknown, - .sparc64 => .Unknown, - .s390x => .Unknown, - .ve => .Unknown, - .spu_2 => .Unknown, - .spirv => .Unknown, - .spirv32 => .Unknown, - .spirv64 => .Unknown, - .loongarch32 => .LOONGARCH32, - .loongarch64 => .LOONGARCH64, - }; - } - pub fn endian(arch: Arch) std.builtin.Endian { return switch (arch) { .avr, @@ -1802,6 +1815,7 @@ pub const DynamicLinker = struct { => none, }, + .driverkit, .ios, .tvos, .watchos, @@ -1846,7 +1860,6 @@ pub const DynamicLinker = struct { .amdpal, .hermit, .hurd, - .driverkit, .shadermodel, => none, }; @@ -2261,7 +2274,13 @@ pub fn cTypeBitSize(target: Target, c_type: CType) u16 { }, }, - .macos, .ios, .tvos, .watchos, .visionos => switch (c_type) { + .driverkit, + .ios, + .macos, + .tvos, + .visionos, + .watchos, + => switch (c_type) { .char => return 8, .short, .ushort => return 16, .int, .uint, .float => return 32, @@ -2334,7 +2353,6 @@ pub fn cTypeBitSize(target: Target, c_type: CType) u16 { .hermit, .hurd, .opengl, - .driverkit, .shadermodel, => @panic("TODO specify the C integer and float type sizes for this OS"), } diff --git a/zig/lib/std/Thread.zig b/zig/lib/std/Thread.zig index 2ed6610bc0..87a4eec921 100644 --- a/zig/lib/std/Thread.zig +++ b/zig/lib/std/Thread.zig @@ -1130,6 +1130,19 @@ const LinuxThreadImpl = struct { [len] "r" (self.mapped.len), : "memory" ), + .hexagon => asm volatile ( + \\ r6 = #215 // SYS_munmap + \\ r0 = %[ptr] + \\ r1 = %[len] + \\ trap0(#1) + \\ r6 = #93 // SYS_exit + \\ r0 = #0 + \\ trap0(#1) + : + : [ptr] "r" (@intFromPtr(self.mapped.ptr)), + [len] "r" (self.mapped.len), + : "memory" + ), // We set `sp` to the address of the current function as a workaround for a Linux // kernel bug that caused syscalls to return EFAULT if the stack pointer is invalid. // The bug was introduced in 46e12c07b3b9603c60fc1d421ff18618241cb081 and fixed in @@ -1188,18 +1201,51 @@ const LinuxThreadImpl = struct { [len] "r" (self.mapped.len), : "memory" ), + .s390x => asm volatile ( + \\ lgr %%r2, %[ptr] + \\ lgr %%r3, %[len] + \\ svc 91 # SYS_munmap + \\ lghi %%r2, 0 + \\ svc 1 # SYS_exit + : + : [ptr] "r" (@intFromPtr(self.mapped.ptr)), + [len] "r" (self.mapped.len), + : "memory" + ), + .sparc => asm volatile ( + \\ # See sparc64 comments below. + \\ 1: + \\ cmp %%fp, 0 + \\ beq 2f + \\ nop + \\ ba 1b + \\ restore + \\ 2: + \\ mov 73, %%g1 # SYS_munmap + \\ mov %[ptr], %%o0 + \\ mov %[len], %%o1 + \\ t 0x3 # ST_FLUSH_WINDOWS + \\ t 0x10 + \\ mov 1, %%g1 # SYS_exit + \\ mov 0, %%o0 + \\ t 0x10 + : + : [ptr] "r" (@intFromPtr(self.mapped.ptr)), + [len] "r" (self.mapped.len), + : "memory" + ), .sparc64 => asm volatile ( \\ # SPARCs really don't like it when active stack frames \\ # is unmapped (it will result in a segfault), so we \\ # force-deactivate it by running `restore` until \\ # all frames are cleared. - \\ 1: + \\ 1: \\ cmp %%fp, 0 \\ beq 2f \\ nop \\ ba 1b \\ restore - \\ 2: + \\ 2: \\ mov 73, %%g1 # SYS_munmap \\ mov %[ptr], %%o0 \\ mov %[len], %%o1 @@ -1208,7 +1254,7 @@ const LinuxThreadImpl = struct { \\ flushw \\ t 0x6d \\ mov 1, %%g1 # SYS_exit - \\ mov 1, %%o0 + \\ mov 0, %%o0 \\ t 0x6d : : [ptr] "r" (@intFromPtr(self.mapped.ptr)), diff --git a/zig/lib/std/array_list.zig b/zig/lib/std/array_list.zig index 7b93668c5c..2510973692 100644 --- a/zig/lib/std/array_list.zig +++ b/zig/lib/std/array_list.zig @@ -359,6 +359,24 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { return m.len; } + pub const FixedWriter = std.io.Writer(*Self, Allocator.Error, appendWriteFixed); + + /// Initializes a Writer which will append to the list but will return + /// `error.OutOfMemory` rather than increasing capacity. + pub fn fixedWriter(self: *Self) FixedWriter { + return .{ .context = self }; + } + + /// The purpose of this function existing is to match `std.io.Writer` API. + fn appendWriteFixed(self: *Self, m: []const u8) error{OutOfMemory}!usize { + const available_capacity = self.capacity - self.items.len; + if (m.len > available_capacity) + return error.OutOfMemory; + + self.appendSliceAssumeCapacity(m); + return m.len; + } + /// Append a value to the list `n` times. /// Allocates more memory as necessary. /// Invalidates element pointers if additional memory is needed. diff --git a/zig/lib/std/bounded_array.zig b/zig/lib/std/bounded_array.zig index 59f68e8cf8..85a175003d 100644 --- a/zig/lib/std/bounded_array.zig +++ b/zig/lib/std/bounded_array.zig @@ -72,6 +72,11 @@ pub fn BoundedArrayAligned( self.len = @intCast(len); } + /// Remove all elements from the slice. + pub fn clear(self: *Self) void { + self.len = 0; + } + /// Copy the content of an existing slice. pub fn fromSlice(m: []const T) error{Overflow}!Self { var list = try init(m.len); diff --git a/zig/lib/std/c.zig b/zig/lib/std/c.zig index 92d36dd135..611f707e15 100644 --- a/zig/lib/std/c.zig +++ b/zig/lib/std/c.zig @@ -2722,7 +2722,39 @@ pub const SYS = switch (native_os) { }; /// Renamed from `sigaction` to `Sigaction` to avoid conflict with function name. pub const Sigaction = switch (native_os) { - .linux => linux.Sigaction, + .linux => switch (native_arch) { + .mips, + .mipsel, + .mips64, + .mips64el, + => if (builtin.target.isMusl()) + linux.Sigaction + else if (builtin.target.ptrBitWidth() == 64) extern struct { + pub const handler_fn = *align(1) const fn (i32) callconv(.C) void; + pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.C) void; + + flags: c_uint, + handler: extern union { + handler: ?handler_fn, + sigaction: ?sigaction_fn, + }, + mask: sigset_t, + restorer: ?*const fn () callconv(.C) void = null, + } else extern struct { + pub const handler_fn = *align(1) const fn (i32) callconv(.C) void; + pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.C) void; + + flags: c_uint, + handler: extern union { + handler: ?handler_fn, + sigaction: ?sigaction_fn, + }, + mask: sigset_t, + restorer: ?*const fn () callconv(.C) void = null, + __resv: [1]c_int = .{0}, + }, + else => linux.Sigaction, + }, .emscripten => emscripten.Sigaction, .netbsd, .macos, .ios, .tvos, .watchos, .visionos => extern struct { pub const handler_fn = *align(1) const fn (i32) callconv(.C) void; @@ -6326,16 +6358,46 @@ pub const Stat = switch (native_os) { return self.ctim; } }, - .mips, .mipsel => extern struct { + .mips, .mipsel => if (builtin.target.isMusl()) extern struct { dev: dev_t, - __pad0: [2]u32, + __pad0: [2]i32, ino: ino_t, mode: mode_t, nlink: nlink_t, uid: uid_t, gid: gid_t, rdev: dev_t, - __pad1: [2]u32, + __pad1: [2]i32, + size: off_t, + atim: timespec, + mtim: timespec, + ctim: timespec, + blksize: blksize_t, + __pad3: i32, + blocks: blkcnt_t, + __pad4: [14]i32, + + pub fn atime(self: @This()) timespec { + return self.atim; + } + + pub fn mtime(self: @This()) timespec { + return self.mtim; + } + + pub fn ctime(self: @This()) timespec { + return self.ctim; + } + } else extern struct { + dev: dev_t, + __pad0: [3]u32, + ino: ino_t, + mode: mode_t, + nlink: nlink_t, + uid: uid_t, + gid: gid_t, + rdev: dev_t, + __pad1: [3]u32, size: off_t, atim: timespec, mtim: timespec, @@ -6357,6 +6419,68 @@ pub const Stat = switch (native_os) { return self.ctim; } }, + .mips64, .mips64el => if (builtin.target.isMusl()) extern struct { + dev: dev_t, + __pad0: [3]i32, + ino: ino_t, + mode: mode_t, + nlink: nlink_t, + uid: uid_t, + gid: gid_t, + rdev: dev_t, + __pad1: [2]u32, + size: off_t, + __pad2: i32, + atim: timespec, + mtim: timespec, + ctim: timespec, + blksize: blksize_t, + __pad3: u32, + blocks: blkcnt_t, + __pad4: [14]i32, + + pub fn atime(self: @This()) timespec { + return self.atim; + } + + pub fn mtime(self: @This()) timespec { + return self.mtim; + } + + pub fn ctime(self: @This()) timespec { + return self.ctim; + } + } else extern struct { + dev: dev_t, + __pad0: [3]u32, + ino: ino_t, + mode: mode_t, + nlink: nlink_t, + uid: uid_t, + gid: gid_t, + rdev: dev_t, + __pad1: [3]u32, + size: off_t, + atim: timespec, + mtim: timespec, + ctim: timespec, + blksize: blksize_t, + __pad3: u32, + blocks: blkcnt_t, + __pad4: [14]i32, + + pub fn atime(self: @This()) timespec { + return self.atim; + } + + pub fn mtime(self: @This()) timespec { + return self.mtim; + } + + pub fn ctime(self: @This()) timespec { + return self.ctim; + } + }, else => std.os.linux.Stat, // libc stat is the same as kernel stat. }, @@ -9375,8 +9499,8 @@ pub const LC = enum(c_int) { pub extern "c" fn setlocale(category: LC, locale: ?[*:0]const u8) ?[*:0]const u8; -pub const getcontext = if (builtin.target.isAndroid()) -{} // android bionic libc does not implement getcontext +pub const getcontext = if (builtin.target.isAndroid() or builtin.target.os.tag == .openbsd) +{} // android bionic and openbsd libc does not implement getcontext else if (native_os == .linux and builtin.target.isMusl()) linux.getcontext else diff --git a/zig/lib/std/c/darwin.zig b/zig/lib/std/c/darwin.zig index 1e184e4e8e..3e2cf53b60 100644 --- a/zig/lib/std/c/darwin.zig +++ b/zig/lib/std/c/darwin.zig @@ -1156,6 +1156,11 @@ pub const CPUFAMILY = enum(u32) { ARM_FIRESTORM_ICESTORM = 0x1b588bb3, ARM_BLIZZARD_AVALANCHE = 0xda33d83d, ARM_EVEREST_SAWTOOTH = 0x8765edea, + ARM_COLL = 0x2876f5b5, + ARM_IBIZA = 0xfa33415e, + ARM_LOBOS = 0x5f4dea93, + ARM_PALMA = 0x72015832, + ARM_DONAN = 0x6f5129ac, _, }; diff --git a/zig/lib/std/coff.zig b/zig/lib/std/coff.zig index bb7be2537d..ca05ce3cf9 100644 --- a/zig/lib/std/coff.zig +++ b/zig/lib/std/coff.zig @@ -983,7 +983,7 @@ pub const DebugInfoDefinition = struct { }; pub const MachineType = enum(u16) { - Unknown = 0x0, + UNKNOWN = 0x0, /// Alpha AXP, 32-bit address space ALPHA = 0x184, /// Alpha 64, 64-bit address space @@ -1053,7 +1053,7 @@ pub const MachineType = enum(u16) { /// Hitachi SH5 SH5 = 0x1a8, /// Thumb - Thumb = 0x1c2, + THUMB = 0x1c2, /// Infineon TRICORE = 0x520, /// MIPS little-endian WCE v2 @@ -1061,29 +1061,12 @@ pub const MachineType = enum(u16) { _, - pub fn fromTargetCpuArch(arch: std.Target.Cpu.Arch) MachineType { - return switch (arch) { - .arm => .ARM, - .powerpc => .POWERPC, - .riscv32 => .RISCV32, - .thumb => .Thumb, - .x86 => .I386, - .aarch64 => .ARM64, - .riscv64 => .RISCV64, - .x86_64 => .X64, - .loongarch32 => .LOONGARCH32, - .loongarch64 => .LOONGARCH64, - // there's cases we don't (yet) handle - else => unreachable, - }; - } - pub fn toTargetCpuArch(machine_type: MachineType) ?std.Target.Cpu.Arch { return switch (machine_type) { .ARM => .arm, .POWERPC => .powerpc, .RISCV32 => .riscv32, - .Thumb => .thumb, + .THUMB => .thumb, .I386 => .x86, .ARM64 => .aarch64, .RISCV64 => .riscv64, diff --git a/zig/lib/std/crypto.zig b/zig/lib/std/crypto.zig index 186f287fdd..aa524fa2c2 100644 --- a/zig/lib/std/crypto.zig +++ b/zig/lib/std/crypto.zig @@ -74,8 +74,9 @@ pub const dh = struct { /// Key Encapsulation Mechanisms. pub const kem = struct { - pub const kyber_d00 = @import("crypto/ml_kem.zig").kyber_d00; - pub const ml_kem_01 = @import("crypto/ml_kem.zig").ml_kem_01; + pub const kyber_d00 = @import("crypto/ml_kem.zig").d00; + pub const ml_kem = @import("crypto/ml_kem.zig").nist; + pub const ml_kem_01 = @compileError("deprecated: final version of the specification has been published, use ml_kem instead"); }; /// Elliptic-curve arithmetic. diff --git a/zig/lib/std/crypto/aes/soft.zig b/zig/lib/std/crypto/aes/soft.zig index 9e7af0606d..fd0dfaf001 100644 --- a/zig/lib/std/crypto/aes/soft.zig +++ b/zig/lib/std/crypto/aes/soft.zig @@ -629,6 +629,8 @@ fn generateSbox(invert: bool) [256]u8 { // Generate lookup tables. fn generateTable(invert: bool) [4][256]u32 { + @setEvalBranchQuota(50000); + var table: [4][256]u32 = undefined; for (generateSbox(invert), 0..) |value, index| { diff --git a/zig/lib/std/crypto/blake2.zig b/zig/lib/std/crypto/blake2.zig index 255011de87..1a285080b5 100644 --- a/zig/lib/std/crypto/blake2.zig +++ b/zig/lib/std/crypto/blake2.zig @@ -786,7 +786,7 @@ test "blake2b384 streaming" { test "comptime blake2b384" { comptime { - @setEvalBranchQuota(10000); + @setEvalBranchQuota(20000); var block = [_]u8{0} ** Blake2b384.block_length; var out: [Blake2b384.digest_length]u8 = undefined; @@ -878,7 +878,7 @@ test "blake2b512 keyed" { test "comptime blake2b512" { comptime { - @setEvalBranchQuota(10000); + @setEvalBranchQuota(12000); var block = [_]u8{0} ** Blake2b512.block_length; var out: [Blake2b512.digest_length]u8 = undefined; diff --git a/zig/lib/std/crypto/ml_kem.zig b/zig/lib/std/crypto/ml_kem.zig index 00eade1c71..9a3a35492c 100644 --- a/zig/lib/std/crypto/ml_kem.zig +++ b/zig/lib/std/crypto/ml_kem.zig @@ -1,15 +1,8 @@ //! Implementation of the IND-CCA2 post-quantum secure key encapsulation mechanism (KEM) //! ML-KEM (NIST FIPS-203 publication) and CRYSTALS-Kyber (v3.02/"draft00" CFRG draft). //! -//! The schemes are not finalized yet, and are still subject to breaking changes. -//! -//! The Kyber namespace suffix (currently `_d00`) refers to the version currently -//! implemented, in accordance with the draft. -//! The ML-KEM namespace suffix (currently `_01`) refers to the NIST FIPS-203 draft -//! published on August 24, 2023, with the unintentional transposition of  having been reverted. -//! -//! Suffixes may not be updated if new versions of the documents only include editorial changes. -//! The suffixes will be removed once the schemes are finalized. +//! The namespace `d00` refers to the version currently implemented, in accordance with the CFRG draft. +//! The `nist` namespace refers to the FIPS-203 publication. //! //! Quoting from the CFRG I-D: //! @@ -148,7 +141,7 @@ const Params = struct { dv: u8, }; -pub const kyber_d00 = struct { +pub const d00 = struct { pub const Kyber512 = Kyber(.{ .name = "Kyber512", .k = 2, @@ -174,7 +167,7 @@ pub const kyber_d00 = struct { }); }; -pub const ml_kem_01 = struct { +pub const nist = struct { pub const MLKem512 = Kyber(.{ .name = "ML-KEM-512", .ml_kem = true, @@ -204,12 +197,12 @@ pub const ml_kem_01 = struct { }; const modes = [_]type{ - kyber_d00.Kyber512, - kyber_d00.Kyber768, - kyber_d00.Kyber1024, - ml_kem_01.MLKem512, - ml_kem_01.MLKem768, - ml_kem_01.MLKem1024, + d00.Kyber512, + d00.Kyber768, + d00.Kyber1024, + nist.MLKem512, + nist.MLKem768, + nist.MLKem1024, }; const h_length: usize = 32; const inner_seed_length: usize = 32; @@ -505,7 +498,10 @@ fn Kyber(comptime p: Params) type { // Derives inner PKE keypair from given seed. fn innerKeyFromSeed(seed: [inner_seed_length]u8, pk: *InnerPk, sk: *InnerSk) void { var expanded_seed: [64]u8 = undefined; - sha3.Sha3_512.hash(&seed, &expanded_seed, .{}); + var h = sha3.Sha3_512.init(.{}); + if (p.ml_kem) h.update(&[1]u8{p.k}); + h.update(&seed); + h.final(&expanded_seed); pk.rho = expanded_seed[0..32].*; const sigma = expanded_seed[32..64]; pk.aT = M.uniform(pk.rho, false); // Expand ρ to A; we'll transpose later on @@ -1722,9 +1718,9 @@ const sha2 = crypto.hash.sha2; test "NIST KAT test" { inline for (.{ - .{ kyber_d00.Kyber512, "e9c2bd37133fcb40772f81559f14b1f58dccd1c816701be9ba6214d43baf4547" }, - .{ kyber_d00.Kyber1024, "89248f2f33f7f4f7051729111f3049c409a933ec904aedadf035f30fa5646cd5" }, - .{ kyber_d00.Kyber768, "a1e122cad3c24bc51622e4c242d8b8acbcd3f618fee4220400605ca8f9ea02c2" }, + .{ d00.Kyber512, "e9c2bd37133fcb40772f81559f14b1f58dccd1c816701be9ba6214d43baf4547" }, + .{ d00.Kyber1024, "89248f2f33f7f4f7051729111f3049c409a933ec904aedadf035f30fa5646cd5" }, + .{ d00.Kyber768, "a1e122cad3c24bc51622e4c242d8b8acbcd3f618fee4220400605ca8f9ea02c2" }, }) |modeHash| { const mode = modeHash[0]; var seed: [48]u8 = undefined; diff --git a/zig/lib/std/crypto/pcurves/p384.zig b/zig/lib/std/crypto/pcurves/p384.zig index f8c5713209..3ab95da5e8 100644 --- a/zig/lib/std/crypto/pcurves/p384.zig +++ b/zig/lib/std/crypto/pcurves/p384.zig @@ -393,7 +393,7 @@ pub const P384 = struct { } const basePointPc = pc: { - @setEvalBranchQuota(50000); + @setEvalBranchQuota(70000); break :pc precompute(P384.basePoint, 15); }; diff --git a/zig/lib/std/debug.zig b/zig/lib/std/debug.zig index 5ddc751f3d..826978abe4 100644 --- a/zig/lib/std/debug.zig +++ b/zig/lib/std/debug.zig @@ -56,6 +56,8 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { // TODO: Make this work. .mips, .mipsel, + .mips64, + .mips64el, => false, // `@returnAddress()` in LLVM 10 gives @@ -1360,6 +1362,9 @@ test "manage resources correctly" { return error.SkipZigTest; } + // self-hosted debug info is still too buggy + if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; + const writer = std.io.null_writer; var di = try SelfInfo.open(testing.allocator); defer di.deinit(); diff --git a/zig/lib/std/debug/Dwarf.zig b/zig/lib/std/debug/Dwarf.zig index fb72316675..7cce30df38 100644 --- a/zig/lib/std/debug/Dwarf.zig +++ b/zig/lib/std/debug/Dwarf.zig @@ -2275,9 +2275,11 @@ pub const ElfModule = struct { break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk; } if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| { - const path = std.fs.path.join(gpa, &[_][]const u8{ cache_path, "debuginfod_client" }) catch break :blk; - defer gpa.free(path); - break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk; + if (cache_path.len > 0) { + const path = std.fs.path.join(gpa, &[_][]const u8{ cache_path, "debuginfod_client" }) catch break :blk; + defer gpa.free(path); + break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk; + } } if (std.posix.getenv("HOME")) |home_path| { const path = std.fs.path.join(gpa, &[_][]const u8{ home_path, ".cache", "debuginfod_client" }) catch break :blk; diff --git a/zig/lib/std/dwarf.zig b/zig/lib/std/dwarf.zig index 6703574d4e..b17533ebf8 100644 --- a/zig/lib/std/dwarf.zig +++ b/zig/lib/std/dwarf.zig @@ -89,12 +89,16 @@ pub const LNS = struct { }; pub const LNE = struct { + pub const padding = 0x00; pub const end_sequence = 0x01; pub const set_address = 0x02; pub const define_file = 0x03; pub const set_discriminator = 0x04; pub const lo_user = 0x80; pub const hi_user = 0xff; + + // Zig extensions + pub const ZIG_set_decl = 0xec; }; pub const UT = struct { @@ -118,6 +122,8 @@ pub const LNCT = struct { pub const lo_user = 0x2000; pub const hi_user = 0x3fff; + + pub const LLVM_source = 0x2001; }; pub const RLE = struct { @@ -142,6 +148,37 @@ pub const CC = enum(u8) { GNU_renesas_sh = 0x40, GNU_borland_fastcall_i386 = 0x41, + BORLAND_safecall = 0xb0, + BORLAND_stdcall = 0xb1, + BORLAND_pascal = 0xb2, + BORLAND_msfastcall = 0xb3, + BORLAND_msreturn = 0xb4, + BORLAND_thiscall = 0xb5, + BORLAND_fastcall = 0xb6, + + LLVM_vectorcall = 0xc0, + LLVM_Win64 = 0xc1, + LLVM_X86_64SysV = 0xc2, + LLVM_AAPCS = 0xc3, + LLVM_AAPCS_VFP = 0xc4, + LLVM_IntelOclBicc = 0xc5, + LLVM_SpirFunction = 0xc6, + LLVM_OpenCLKernel = 0xc7, + LLVM_Swift = 0xc8, + LLVM_PreserveMost = 0xc9, + LLVM_PreserveAll = 0xca, + LLVM_X86RegCall = 0xcb, + LLVM_M68kRTD = 0xcc, + LLVM_PreserveNone = 0xcd, + LLVM_RISCVVectorCall = 0xce, + LLVM_SwiftTail = 0xcf, + pub const lo_user = 0x40; pub const hi_user = 0xff; }; + +pub const ACCESS = struct { + pub const public = 0x01; + pub const protected = 0x02; + pub const private = 0x03; +}; diff --git a/zig/lib/std/dwarf/AT.zig b/zig/lib/std/dwarf/AT.zig index 8a242d8330..3477fd4079 100644 --- a/zig/lib/std/dwarf/AT.zig +++ b/zig/lib/std/dwarf/AT.zig @@ -218,6 +218,14 @@ pub const VMS_rtnbeg_pd_address = 0x2201; // See http://gcc.gnu.org/wiki/DW_AT_GNAT_descriptive_type . pub const use_GNAT_descriptive_type = 0x2301; pub const GNAT_descriptive_type = 0x2302; + +// Zig extensions. +pub const ZIG_parent = 0x2ccd; +pub const ZIG_padding = 0x2cce; +pub const ZIG_relative_decl = 0x2cd0; +pub const ZIG_decl_line_relative = 0x2cd1; +pub const ZIG_sentinel = 0x2ce2; + // UPC extension. pub const upc_threads_scaled = 0x3210; // PGI (STMicroelectronics) extensions. diff --git a/zig/lib/std/dwarf/LANG.zig b/zig/lib/std/dwarf/LANG.zig index e24b61ecae..e890b6e38f 100644 --- a/zig/lib/std/dwarf/LANG.zig +++ b/zig/lib/std/dwarf/LANG.zig @@ -35,6 +35,30 @@ pub const Fortran03 = 0x0022; pub const Fortran08 = 0x0023; pub const RenderScript = 0x0024; pub const BLISS = 0x0025; +pub const Kotlin = 0x0026; +pub const Zig = 0x0027; +pub const Crystal = 0x0028; +pub const C_plus_plus_17 = 0x002a; +pub const C_plus_plus_20 = 0x002b; +pub const C17 = 0x002c; +pub const Fortran18 = 0x002d; +pub const Ada2005 = 0x002e; +pub const Ada2012 = 0x002f; +pub const HIP = 0x0030; +pub const Assembly = 0x0031; +pub const C_sharp = 0x0032; +pub const Mojo = 0x0033; +pub const GLSL = 0x0034; +pub const GLSL_ES = 0x0035; +pub const HLSL = 0x0036; +pub const OpenCL_CPP = 0x0037; +pub const CPP_for_OpenCL = 0x0038; +pub const SYCL = 0x0039; +pub const C_plus_plus_23 = 0x003a; +pub const Odin = 0x003b; +pub const Ruby = 0x0040; +pub const Move = 0x0041; +pub const Hylo = 0x0042; pub const lo_user = 0x8000; pub const hi_user = 0xffff; diff --git a/zig/lib/std/enums.zig b/zig/lib/std/enums.zig index aea194683d..1cc7bde8d2 100644 --- a/zig/lib/std/enums.zig +++ b/zig/lib/std/enums.zig @@ -6,7 +6,7 @@ const testing = std.testing; const EnumField = std.builtin.Type.EnumField; /// Increment this value when adding APIs that add single backwards branches. -const eval_branch_quota_cushion = 5; +const eval_branch_quota_cushion = 10; /// Returns a struct with a field matching each unique named enum element. /// If the enum is extern and has multiple names for the same value, only diff --git a/zig/lib/std/fs/get_app_data_dir.zig b/zig/lib/std/fs/get_app_data_dir.zig index a05a431648..cce99ea8cc 100644 --- a/zig/lib/std/fs/get_app_data_dir.zig +++ b/zig/lib/std/fs/get_app_data_dir.zig @@ -32,7 +32,9 @@ pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDi }, .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => { if (posix.getenv("XDG_DATA_HOME")) |xdg| { - return fs.path.join(allocator, &[_][]const u8{ xdg, appname }); + if (xdg.len > 0) { + return fs.path.join(allocator, &[_][]const u8{ xdg, appname }); + } } const home_dir = posix.getenv("HOME") orelse { diff --git a/zig/lib/std/hash/xxhash.zig b/zig/lib/std/hash/xxhash.zig index 88ec3ba372..1d7c8399fc 100644 --- a/zig/lib/std/hash/xxhash.zig +++ b/zig/lib/std/hash/xxhash.zig @@ -890,7 +890,7 @@ test "xxhash32 smhasher" { } }; try Test.do(); - @setEvalBranchQuota(75000); + @setEvalBranchQuota(85000); comptime try Test.do(); } diff --git a/zig/lib/std/io.zig b/zig/lib/std/io.zig index dd6daef7d0..1f4cb411b4 100644 --- a/zig/lib/std/io.zig +++ b/zig/lib/std/io.zig @@ -419,7 +419,7 @@ pub const tty = @import("io/tty.zig"); /// A Writer that doesn't write to anything. pub const null_writer: NullWriter = .{ .context = {} }; -const NullWriter = Writer(void, error{}, dummyWrite); +pub const NullWriter = Writer(void, error{}, dummyWrite); fn dummyWrite(context: void, data: []const u8) error{}!usize { _ = context; return data.len; diff --git a/zig/lib/std/json/stringify.zig b/zig/lib/std/json/stringify.zig index 6a8efb7e0e..965b7c3113 100644 --- a/zig/lib/std/json/stringify.zig +++ b/zig/lib/std/json/stringify.zig @@ -156,36 +156,23 @@ pub fn writeStreamArbitraryDepth( /// | /// | write /// | print +/// | /// = beginObject ( )* endObject -/// = objectField | objectFieldRaw +/// = objectField | objectFieldRaw | /// = beginArray ( )* endArray +/// = beginWriteRaw ( stream.writeAll )* endWriteRaw +/// = beginObjectFieldRaw ( stream.writeAll )* endObjectFieldRaw /// ``` /// -/// Supported types: -/// * Zig `bool` -> JSON `true` or `false`. -/// * Zig `?T` -> `null` or the rendering of `T`. -/// * Zig `i32`, `u64`, etc. -> JSON number or string. -/// * When option `emit_nonportable_numbers_as_strings` is true, if the value is outside the range `+-1<<53` (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number. -/// * Zig floats -> JSON number or string. -/// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number. -/// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00". -/// * Zig `[]const u8`, `[]u8`, `*[N]u8`, `@Vector(N, u8)`, and similar -> JSON string. -/// * See `StringifyOptions.emit_strings_as_arrays`. -/// * If the content is not valid UTF-8, rendered as an array of numbers instead. -/// * Zig `[]T`, `[N]T`, `*[N]T`, `@Vector(N, T)`, and similar -> JSON array of the rendering of each item. -/// * Zig tuple -> JSON array of the rendering of each item. -/// * Zig `struct` -> JSON object with each field in declaration order. -/// * If the struct declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. See `std.json.Value` for an example. -/// * See `StringifyOptions.emit_null_optional_fields`. -/// * Zig `union(enum)` -> JSON object with one field named for the active tag and a value representing the payload. -/// * If the payload is `void`, then the emitted value is `{}`. -/// * If the union declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. -/// * Zig `enum` -> JSON string naming the active tag. -/// * If the enum declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. -/// * Zig untyped enum literal -> JSON string naming the active tag. -/// * Zig error -> JSON string naming the error. -/// * Zig `*T` -> the rendering of `T`. Note there is no guard against circular-reference infinite recursion. -/// +/// The `safety_checks_hint` parameter determines how much memory is used to enable assertions that the above grammar is being followed, +/// e.g. tripping an assertion rather than allowing `endObject` to emit the final `}` in `[[[]]}`. +/// "Depth" in this context means the depth of nested `[]` or `{}` expressions +/// (or equivalently the amount of recursion on the `` grammar expression above). +/// For example, emitting the JSON `[[[]]]` requires a depth of 3. +/// If `.checked_to_fixed_depth` is used, there is additionally an assertion that the nesting depth never exceeds the given limit. +/// `.checked_to_arbitrary_depth` requires a runtime allocator for the memory. +/// `.checked_to_fixed_depth` embeds the storage required in the `WriteStream` struct. +/// `.assumed_correct` requires no space and performs none of these assertions. /// In `ReleaseFast` and `ReleaseSmall` mode, the given `safety_checks_hint` is ignored and is always treated as `.assumed_correct`. pub fn WriteStream( comptime OutStream: type, @@ -197,10 +184,14 @@ pub fn WriteStream( ) type { return struct { const Self = @This(); - const safety_checks: @TypeOf(safety_checks_hint) = switch (@import("builtin").mode) { - .Debug, .ReleaseSafe => safety_checks_hint, - .ReleaseFast, .ReleaseSmall => .assumed_correct, + const build_mode_has_safety = switch (@import("builtin").mode) { + .Debug, .ReleaseSafe => true, + .ReleaseFast, .ReleaseSmall => false, }; + const safety_checks: @TypeOf(safety_checks_hint) = if (build_mode_has_safety) + safety_checks_hint + else + .assumed_correct; pub const Stream = OutStream; pub const Error = switch (safety_checks) { @@ -225,6 +216,11 @@ pub fn WriteStream( .assumed_correct => void, }, + raw_streaming_mode: if (build_mode_has_safety) + enum { none, value, objectField } + else + void = if (build_mode_has_safety) .none else {}, + pub fn init(safety_allocator: Allocator, stream: OutStream, options: StringifyOptions) Self { return .{ .options = options, @@ -237,6 +233,7 @@ pub fn WriteStream( }; } + /// Only necessary with .checked_to_arbitrary_depth. pub fn deinit(self: *Self) void { switch (safety_checks) { .checked_to_arbitrary_depth => self.nesting_stack.deinit(), @@ -246,6 +243,7 @@ pub fn WriteStream( } pub fn beginArray(self: *Self) Error!void { + if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); try self.valueStart(); try self.stream.writeByte('['); try self.pushIndentation(ARRAY_MODE); @@ -253,6 +251,7 @@ pub fn WriteStream( } pub fn beginObject(self: *Self) Error!void { + if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); try self.valueStart(); try self.stream.writeByte('{'); try self.pushIndentation(OBJECT_MODE); @@ -260,6 +259,7 @@ pub fn WriteStream( } pub fn endArray(self: *Self) Error!void { + if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); self.popIndentation(ARRAY_MODE); switch (self.next_punctuation) { .none => {}, @@ -273,6 +273,7 @@ pub fn WriteStream( } pub fn endObject(self: *Self) Error!void { + if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); self.popIndentation(OBJECT_MODE); switch (self.next_punctuation) { .none => {}, @@ -389,16 +390,39 @@ pub fn WriteStream( /// e.g. `"1"`, `"[]"`, `"[1,2]"`, not `"1,2"`. /// This function may be useful for doing your own number formatting. pub fn print(self: *Self, comptime fmt: []const u8, args: anytype) Error!void { + if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); try self.valueStart(); try self.stream.print(fmt, args); self.valueDone(); } + /// An alternative to calling `write` that allows you to write directly to the `.stream` field, e.g. with `.stream.writeAll()`. + /// Call `beginWriteRaw()`, then write a complete value (including any quotes if necessary) directly to the `.stream` field, + /// then call `endWriteRaw()`. + /// This can be useful for streaming very long strings into the output without needing it all buffered in memory. + pub fn beginWriteRaw(self: *Self) !void { + if (build_mode_has_safety) { + assert(self.raw_streaming_mode == .none); + self.raw_streaming_mode = .value; + } + try self.valueStart(); + } + + /// See `beginWriteRaw`. + pub fn endWriteRaw(self: *Self) void { + if (build_mode_has_safety) { + assert(self.raw_streaming_mode == .value); + self.raw_streaming_mode = .none; + } + self.valueDone(); + } + /// See `WriteStream` for when to call this method. /// `key` is the string content of the property name. /// Surrounding quotes will be added and any special characters will be escaped. /// See also `objectFieldRaw`. pub fn objectField(self: *Self, key: []const u8) Error!void { + if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); try self.objectFieldStart(); try encodeJsonString(key, self.options, self.stream); self.next_punctuation = .colon; @@ -408,14 +432,65 @@ pub fn WriteStream( /// A few assertions are performed on the given value to ensure that the caller of this function understands the API contract. /// See also `objectField`. pub fn objectFieldRaw(self: *Self, quoted_key: []const u8) Error!void { + if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); assert(quoted_key.len >= 2 and quoted_key[0] == '"' and quoted_key[quoted_key.len - 1] == '"'); // quoted_key should be "quoted". try self.objectFieldStart(); try self.stream.writeAll(quoted_key); self.next_punctuation = .colon; } - /// See `WriteStream`. + /// In the rare case that you need to write very long object field names, + /// this is an alternative to `objectField` and `objectFieldRaw` that allows you to write directly to the `.stream` field + /// similar to `beginWriteRaw`. + /// Call `endObjectFieldRaw()` when you're done. + pub fn beginObjectFieldRaw(self: *Self) !void { + if (build_mode_has_safety) { + assert(self.raw_streaming_mode == .none); + self.raw_streaming_mode = .objectField; + } + try self.objectFieldStart(); + } + + /// See `beginObjectFieldRaw`. + pub fn endObjectFieldRaw(self: *Self) void { + if (build_mode_has_safety) { + assert(self.raw_streaming_mode == .objectField); + self.raw_streaming_mode = .none; + } + self.next_punctuation = .colon; + } + + /// Renders the given Zig value as JSON. + /// + /// Supported types: + /// * Zig `bool` -> JSON `true` or `false`. + /// * Zig `?T` -> `null` or the rendering of `T`. + /// * Zig `i32`, `u64`, etc. -> JSON number or string. + /// * When option `emit_nonportable_numbers_as_strings` is true, if the value is outside the range `+-1<<53` (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number. + /// * Zig floats -> JSON number or string. + /// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number. + /// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00". + /// * Zig `[]const u8`, `[]u8`, `*[N]u8`, `@Vector(N, u8)`, and similar -> JSON string. + /// * See `StringifyOptions.emit_strings_as_arrays`. + /// * If the content is not valid UTF-8, rendered as an array of numbers instead. + /// * Zig `[]T`, `[N]T`, `*[N]T`, `@Vector(N, T)`, and similar -> JSON array of the rendering of each item. + /// * Zig tuple -> JSON array of the rendering of each item. + /// * Zig `struct` -> JSON object with each field in declaration order. + /// * If the struct declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. See `std.json.Value` for an example. + /// * See `StringifyOptions.emit_null_optional_fields`. + /// * Zig `union(enum)` -> JSON object with one field named for the active tag and a value representing the payload. + /// * If the payload is `void`, then the emitted value is `{}`. + /// * If the union declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. + /// * Zig `enum` -> JSON string naming the active tag. + /// * If the enum declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. + /// * Zig untyped enum literal -> JSON string naming the active tag. + /// * Zig error -> JSON string naming the error. + /// * Zig `*T` -> the rendering of `T`. Note there is no guard against circular-reference infinite recursion. + /// + /// See also alternative functions `print` and `beginWriteRaw`. + /// For writing object field names, use `objectField` instead. pub fn write(self: *Self, value: anytype) Error!void { + if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); const T = @TypeOf(value); switch (@typeInfo(T)) { .Int => { diff --git a/zig/lib/std/json/stringify_test.zig b/zig/lib/std/json/stringify_test.zig index 1722868d2a..c0003b87dc 100644 --- a/zig/lib/std/json/stringify_test.zig +++ b/zig/lib/std/json/stringify_test.zig @@ -443,3 +443,53 @@ test "nonportable numbers" { try testStringify("9999999999999999", 9999999999999999, .{}); try testStringify("\"9999999999999999\"", 9999999999999999, .{ .emit_nonportable_numbers_as_strings = true }); } + +test "stringify raw streaming" { + var out_buf: [1024]u8 = undefined; + var slice_stream = std.io.fixedBufferStream(&out_buf); + const out = slice_stream.writer(); + + { + var w = writeStream(out, .{ .whitespace = .indent_2 }); + try testRawStreaming(&w, &slice_stream); + } + + { + var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, 8); + try testRawStreaming(&w, &slice_stream); + } + + { + var w = writeStreamMaxDepth(out, .{ .whitespace = .indent_2 }, null); + try testRawStreaming(&w, &slice_stream); + } + + { + var w = writeStreamArbitraryDepth(testing.allocator, out, .{ .whitespace = .indent_2 }); + defer w.deinit(); + try testRawStreaming(&w, &slice_stream); + } +} + +fn testRawStreaming(w: anytype, slice_stream: anytype) !void { + slice_stream.reset(); + + try w.beginObject(); + try w.beginObjectFieldRaw(); + try w.stream.writeAll("\"long"); + try w.stream.writeAll(" key\""); + w.endObjectFieldRaw(); + try w.beginWriteRaw(); + try w.stream.writeAll("\"long"); + try w.stream.writeAll(" value\""); + w.endWriteRaw(); + try w.endObject(); + + const result = slice_stream.getWritten(); + const expected = + \\{ + \\ "long key": "long value" + \\} + ; + try std.testing.expectEqualStrings(expected, result); +} diff --git a/zig/lib/std/leb128.zig b/zig/lib/std/leb128.zig index 087df559fe..1915782459 100644 --- a/zig/lib/std/leb128.zig +++ b/zig/lib/std/leb128.zig @@ -36,10 +36,14 @@ pub fn readUleb128(comptime T: type, reader: anytype) !T { pub const readULEB128 = readUleb128; /// Write a single unsigned integer as unsigned LEB128 to the given writer. -pub fn writeUleb128(writer: anytype, uint_value: anytype) !void { - const T = @TypeOf(uint_value); - const U = if (@typeInfo(T).Int.bits < 8) u8 else T; - var value: U = @intCast(uint_value); +pub fn writeUleb128(writer: anytype, arg: anytype) !void { + const Arg = @TypeOf(arg); + const Int = switch (Arg) { + comptime_int => std.math.IntFittingRange(arg, arg), + else => Arg, + }; + const Value = if (@typeInfo(Int).Int.bits < 8) u8 else Int; + var value: Value = arg; while (true) { const byte: u8 = @truncate(value & 0x7f); @@ -118,16 +122,19 @@ pub fn readIleb128(comptime T: type, reader: anytype) !T { pub const readILEB128 = readIleb128; /// Write a single signed integer as signed LEB128 to the given writer. -pub fn writeIleb128(writer: anytype, int_value: anytype) !void { - const T = @TypeOf(int_value); - const S = if (@typeInfo(T).Int.bits < 8) i8 else T; - const U = std.meta.Int(.unsigned, @typeInfo(S).Int.bits); - - var value: S = @intCast(int_value); +pub fn writeIleb128(writer: anytype, arg: anytype) !void { + const Arg = @TypeOf(arg); + const Int = switch (Arg) { + comptime_int => std.math.IntFittingRange(-arg - 1, arg), + else => Arg, + }; + const Signed = if (@typeInfo(Int).Int.bits < 8) i8 else Int; + const Unsigned = std.meta.Int(.unsigned, @typeInfo(Signed).Int.bits); + var value: Signed = arg; while (true) { - const uvalue: U = @bitCast(value); - const byte: u8 = @truncate(uvalue); + const unsigned: Unsigned = @bitCast(value); + const byte: u8 = @truncate(unsigned); value >>= 6; if (value == -1 or value == 0) { try writer.writeByte(byte & 0x7F); @@ -147,17 +154,25 @@ pub fn writeIleb128(writer: anytype, int_value: anytype) !void { /// "relocatable", meaning that it becomes possible to later go back and patch the number to be a /// different value without shifting all the following code. pub fn writeUnsignedFixed(comptime l: usize, ptr: *[l]u8, int: std.meta.Int(.unsigned, l * 7)) void { - const T = @TypeOf(int); - const U = if (@typeInfo(T).Int.bits < 8) u8 else T; - var value: U = @intCast(int); + writeUnsignedExtended(ptr, int); +} - comptime var i = 0; - inline while (i < (l - 1)) : (i += 1) { - const byte = @as(u8, @truncate(value)) | 0b1000_0000; +/// Same as `writeUnsignedFixed` but with a runtime-known length. +/// Asserts `slice.len > 0`. +pub fn writeUnsignedExtended(slice: []u8, arg: anytype) void { + const Arg = @TypeOf(arg); + const Int = switch (Arg) { + comptime_int => std.math.IntFittingRange(arg, arg), + else => Arg, + }; + const Value = if (@typeInfo(Int).Int.bits < 8) u8 else Int; + var value: Value = arg; + + for (slice[0 .. slice.len - 1]) |*byte| { + byte.* = @truncate(0x80 | value); value >>= 7; - ptr[i] = byte; } - ptr[i] = @truncate(value); + slice[slice.len - 1] = @as(u7, @intCast(value)); } /// Deprecated: use `writeIleb128` diff --git a/zig/lib/std/math.zig b/zig/lib/std/math.zig index 0c00818a1e..f18739095c 100644 --- a/zig/lib/std/math.zig +++ b/zig/lib/std/math.zig @@ -749,31 +749,23 @@ test rotl { try testing.expect(rotl(@Vector(1, u32), @Vector(1, u32){1 << 31}, @as(isize, -1))[0] == @as(u32, 1) << 30); } -/// Returns an unsigned int type that can hold the number of bits in T -/// - 1. Suitable for 0-based bit indices of T. +/// Returns an unsigned int type that can hold the number of bits in T - 1. +/// Suitable for 0-based bit indices of T. pub fn Log2Int(comptime T: type) type { // comptime ceil log2 if (T == comptime_int) return comptime_int; - comptime var count = 0; - comptime var s = @typeInfo(T).Int.bits - 1; - inline while (s != 0) : (s >>= 1) { - count += 1; - } - - return std.meta.Int(.unsigned, count); + const bits: u16 = @typeInfo(T).Int.bits; + const log2_bits = 16 - @clz(bits - 1); + return std.meta.Int(.unsigned, log2_bits); } /// Returns an unsigned int type that can hold the number of bits in T. pub fn Log2IntCeil(comptime T: type) type { // comptime ceil log2 if (T == comptime_int) return comptime_int; - comptime var count = 0; - comptime var s = @typeInfo(T).Int.bits; - inline while (s != 0) : (s >>= 1) { - count += 1; - } - - return std.meta.Int(.unsigned, count); + const bits: u16 = @typeInfo(T).Int.bits; + const log2_bits = 16 - @clz(bits); + return std.meta.Int(.unsigned, log2_bits); } /// Returns the smallest integer type that can hold both from and to. diff --git a/zig/lib/std/math/big/int.zig b/zig/lib/std/math/big/int.zig index a7881a8905..f0fd6fca4e 100644 --- a/zig/lib/std/math/big/int.zig +++ b/zig/lib/std/math/big/int.zig @@ -2092,6 +2092,12 @@ pub const Const = struct { return bits; } + /// Returns the number of bits required to represent the integer in twos-complement form + /// with the given signedness. + pub fn bitCountTwosCompForSignedness(self: Const, signedness: std.builtin.Signedness) usize { + return self.bitCountTwosComp() + @intFromBool(self.positive and signedness == .signed); + } + /// @popCount with two's complement semantics. /// /// This returns the number of 1 bits set when the value would be represented in @@ -2147,9 +2153,7 @@ pub const Const = struct { if (signedness == .unsigned and !self.positive) { return false; } - - const req_bits = self.bitCountTwosComp() + @intFromBool(self.positive and signedness == .signed); - return bit_count >= req_bits; + return bit_count >= self.bitCountTwosCompForSignedness(signedness); } /// Returns whether self can fit into an integer of the requested type. diff --git a/zig/lib/std/math/hypot.zig b/zig/lib/std/math/hypot.zig index cc0dc17ab1..ddc9408aba 100644 --- a/zig/lib/std/math/hypot.zig +++ b/zig/lib/std/math/hypot.zig @@ -114,6 +114,7 @@ test "hypot.precise" { } test "hypot.special" { + @setEvalBranchQuota(2000); inline for (.{ f16, f32, f64, f128 }) |T| { try expect(math.isNan(hypot(nan(T), 0.0))); try expect(math.isNan(hypot(0.0, nan(T)))); diff --git a/zig/lib/std/math/nextafter.zig b/zig/lib/std/math/nextafter.zig index 717cbf4700..b88648229b 100644 --- a/zig/lib/std/math/nextafter.zig +++ b/zig/lib/std/math/nextafter.zig @@ -144,7 +144,7 @@ test "int" { } test "float" { - @setEvalBranchQuota(3000); + @setEvalBranchQuota(4000); // normal -> normal try expect(nextAfter(f16, 0x1.234p0, 2.0) == 0x1.238p0); diff --git a/zig/lib/std/mem.zig b/zig/lib/std/mem.zig index 1fd8af950a..c2c168a2ae 100644 --- a/zig/lib/std/mem.zig +++ b/zig/lib/std/mem.zig @@ -128,7 +128,7 @@ pub fn alignAllocLen(full_len: usize, alloc_len: usize, len_align: u29) usize { assert(full_len >= alloc_len); if (len_align == 0) return alloc_len; - const adjusted = alignBackwardAnyAlign(full_len, len_align); + const adjusted = alignBackwardAnyAlign(usize, full_len, len_align); assert(adjusted >= alloc_len); return adjusted; } @@ -1647,12 +1647,6 @@ test readVarInt { /// Loads an integer from packed memory with provided bit_count, bit_offset, and signedness. /// Asserts that T is large enough to store the read value. -/// -/// Example: -/// const T = packed struct(u16){ a: u3, b: u7, c: u6 }; -/// var st = T{ .a = 1, .b = 2, .c = 4 }; -/// const b_field = readVarPackedInt(u64, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 7, builtin.cpu.arch.endian(), .unsigned); -/// pub fn readVarPackedInt( comptime T: type, bytes: []const u8, @@ -1715,6 +1709,13 @@ pub fn readVarPackedInt( } } +test readVarPackedInt { + const T = packed struct(u16) { a: u3, b: u7, c: u6 }; + var st = T{ .a = 1, .b = 2, .c = 4 }; + const b_field = readVarPackedInt(u64, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 7, builtin.cpu.arch.endian(), .unsigned); + try std.testing.expectEqual(st.b, b_field); +} + /// Reads an integer from memory with bit count specified by T. /// The bit count of T must be evenly divisible by 8. /// This function cannot fail and cannot cause undefined behavior. @@ -1811,12 +1812,6 @@ pub const readPackedIntForeign = switch (native_endian) { /// Loads an integer from packed memory. /// Asserts that buffer contains at least bit_offset + @bitSizeOf(T) bits. -/// -/// Example: -/// const T = packed struct(u16){ a: u3, b: u7, c: u6 }; -/// var st = T{ .a = 1, .b = 2, .c = 4 }; -/// const b_field = readPackedInt(u7, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), builtin.cpu.arch.endian()); -/// pub fn readPackedInt(comptime T: type, bytes: []const u8, bit_offset: usize, endian: Endian) T { switch (endian) { .little => return readPackedIntLittle(T, bytes, bit_offset), @@ -1824,6 +1819,13 @@ pub fn readPackedInt(comptime T: type, bytes: []const u8, bit_offset: usize, end } } +test readPackedInt { + const T = packed struct(u16) { a: u3, b: u7, c: u6 }; + var st = T{ .a = 1, .b = 2, .c = 4 }; + const b_field = readPackedInt(u7, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), builtin.cpu.arch.endian()); + try std.testing.expectEqual(st.b, b_field); +} + test "comptime read/write int" { comptime { var bytes: [2]u8 = undefined; @@ -1963,13 +1965,6 @@ pub const writePackedIntForeign = switch (native_endian) { /// Stores an integer to packed memory. /// Asserts that buffer contains at least bit_offset + @bitSizeOf(T) bits. -/// -/// Example: -/// const T = packed struct(u16){ a: u3, b: u7, c: u6 }; -/// var st = T{ .a = 1, .b = 2, .c = 4 }; -/// // st.b = 0x7f; -/// writePackedInt(u7, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 0x7f, builtin.cpu.arch.endian()); -/// pub fn writePackedInt(comptime T: type, bytes: []u8, bit_offset: usize, value: T, endian: Endian) void { switch (endian) { .little => writePackedIntLittle(T, bytes, bit_offset, value), @@ -1977,16 +1972,15 @@ pub fn writePackedInt(comptime T: type, bytes: []u8, bit_offset: usize, value: T } } -/// Stores an integer to packed memory with provided bit_count, bit_offset, and signedness. +test writePackedInt { + const T = packed struct(u16) { a: u3, b: u7, c: u6 }; + var st = T{ .a = 1, .b = 2, .c = 4 }; + writePackedInt(u7, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 0x7f, builtin.cpu.arch.endian()); + try std.testing.expectEqual(T{ .a = 1, .b = 0x7f, .c = 4 }, st); +} + +/// Stores an integer to packed memory with provided bit_offset, bit_count, and signedness. /// If negative, the written value is sign-extended. -/// -/// Example: -/// const T = packed struct(u16){ a: u3, b: u7, c: u6 }; -/// var st = T{ .a = 1, .b = 2, .c = 4 }; -/// // st.b = 0x7f; -/// var value: u64 = 0x7f; -/// writeVarPackedInt(std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 7, value, builtin.cpu.arch.endian()); -/// pub fn writeVarPackedInt(bytes: []u8, bit_offset: usize, bit_count: usize, value: anytype, endian: std.builtin.Endian) void { const T = @TypeOf(value); const uN = std.meta.Int(.unsigned, @bitSizeOf(T)); @@ -1999,7 +1993,9 @@ pub fn writeVarPackedInt(bytes: []u8, bit_offset: usize, bit_count: usize, value }; const write_bytes = bytes[lowest_byte..][0..write_size]; - if (write_size == 1) { + if (write_size == 0) { + return; + } else if (write_size == 1) { // Single byte writes are handled specially, since we need to mask bits // on both ends of the byte. const mask = (@as(u8, 0xff) >> @as(u3, @intCast(8 - bit_count))); @@ -2039,6 +2035,14 @@ pub fn writeVarPackedInt(bytes: []u8, bit_offset: usize, bit_count: usize, value write_bytes[@as(usize, @intCast(i))] |= @as(u8, @intCast(@as(uN, @bitCast(remaining)) & tail_mask)); } +test writeVarPackedInt { + const T = packed struct(u16) { a: u3, b: u7, c: u6 }; + var st = T{ .a = 1, .b = 2, .c = 4 }; + const value: u64 = 0x7f; + writeVarPackedInt(std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 7, value, builtin.cpu.arch.endian()); + try testing.expectEqual(T{ .a = 1, .b = value, .c = 4 }, st); +} + /// Swap the byte order of all the members of the fields of a struct /// (Changing their endianness) pub fn byteSwapAllFields(comptime S: type, ptr: *S) void { @@ -4308,6 +4312,15 @@ test "sliceAsBytes preserves pointer attributes" { try testing.expectEqual(in.alignment, out.alignment); } +/// Round an address down to the next (or current) aligned address. +/// Unlike `alignForward`, `alignment` can be any positive number, not just a power of 2. +pub fn alignForwardAnyAlign(comptime T: type, addr: T, alignment: T) T { + if (isValidAlignGeneric(T, alignment)) + return alignForward(T, addr, alignment); + assert(alignment != 0); + return alignBackwardAnyAlign(T, addr + (alignment - 1), alignment); +} + /// Round an address up to the next (or current) aligned address. /// The alignment must be a power of 2 and greater than 0. /// Asserts that rounding up the address does not cause integer overflow. @@ -4429,11 +4442,11 @@ test alignForward { /// Round an address down to the previous (or current) aligned address. /// Unlike `alignBackward`, `alignment` can be any positive number, not just a power of 2. -pub fn alignBackwardAnyAlign(i: usize, alignment: usize) usize { - if (isValidAlign(alignment)) - return alignBackward(usize, i, alignment); +pub fn alignBackwardAnyAlign(comptime T: type, addr: T, alignment: T) T { + if (isValidAlignGeneric(T, alignment)) + return alignBackward(T, addr, alignment); assert(alignment != 0); - return i - @mod(i, alignment); + return addr - @mod(addr, alignment); } /// Round an address down to the previous (or current) aligned address. diff --git a/zig/lib/std/os/linux.zig b/zig/lib/std/os/linux.zig index 67468641c9..a29b381c40 100644 --- a/zig/lib/std/os/linux.zig +++ b/zig/lib/std/os/linux.zig @@ -630,12 +630,13 @@ pub fn futex2_waitv( nr_futexes, flags, @intFromPtr(timeout), - @bitCast(@as(isize, clockid)), + @bitCast(@as(isize, @intFromEnum(clockid))), ); } /// Wait on a futex. -/// Identical to `FUTEX.WAIT`, except it is part of the futex2 family of calls. +/// Identical to the traditional `FUTEX.FUTEX_WAIT_BITSET` op, except it is part of the +/// futex2 familiy of calls. pub fn futex2_wait( /// Address of the futex to wait on. uaddr: *const anyopaque, @@ -646,7 +647,7 @@ pub fn futex2_wait( /// `FUTEX2` flags. flags: u32, /// Optional absolute timeout. - timeout: *const timespec, + timeout: ?*const timespec, /// Clock to be used for the timeout, realtime or monotonic. clockid: clockid_t, ) usize { @@ -657,15 +658,16 @@ pub fn futex2_wait( mask, flags, @intFromPtr(timeout), - @bitCast(@as(isize, clockid)), + @bitCast(@as(isize, @intFromEnum(clockid))), ); } /// Wake a number of futexes. -/// Identical to `FUTEX.WAKE`, except it is part of the futex2 family of calls. +/// Identical to the traditional `FUTEX.FUTEX_WAIT_BITSET` op, except it is part of the +/// futex2 family of calls. pub fn futex2_wake( /// Address of the futex(es) to wake. - uaddr: [*]const anyopaque, + uaddr: *const anyopaque, /// Bitmask mask: usize, /// Number of the futexes to wake. @@ -874,6 +876,11 @@ pub const MSF = struct { pub const SYNC = 4; }; +/// Can only be called on 64 bit systems. +pub fn mseal(address: [*]const u8, length: usize, flags: usize) usize { + return syscall3(.mseal, @intFromPtr(address), length, flags); +} + pub fn msync(address: [*]const u8, length: usize, flags: i32) usize { return syscall3(.msync, @intFromPtr(address), length, @as(u32, @bitCast(flags))); } @@ -1678,7 +1685,7 @@ pub fn sigaddset(set: *sigset_t, sig: u6) void { pub fn sigismember(set: *const sigset_t, sig: u6) bool { const s = sig - 1; - return ((set.*)[@as(usize, @intCast(s)) / usize_bits] & (@as(usize, @intCast(1)) << (s & (usize_bits - 1)))) != 0; + return ((set.*)[@as(usize, @intCast(s)) / usize_bits] & (@as(usize, @intCast(1)) << @intCast(s & (usize_bits - 1)))) != 0; } pub fn getsockname(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize { @@ -1735,31 +1742,31 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize var next_unsent: usize = 0; for (msgvec[0..kvlen], 0..) |*msg, i| { var size: i32 = 0; - const msg_iovlen = @as(usize, @intCast(msg.msg_hdr.msg_iovlen)); // kernel side this is treated as unsigned - for (msg.msg_hdr.msg_iov[0..msg_iovlen]) |iov| { + const msg_iovlen = @as(usize, @intCast(msg.hdr.iovlen)); // kernel side this is treated as unsigned + for (msg.hdr.iov[0..msg_iovlen]) |iov| { if (iov.len > std.math.maxInt(i32) or @addWithOverflow(size, @as(i32, @intCast(iov.len)))[1] != 0) { // batch-send all messages up to the current message if (next_unsent < i) { const batch_size = i - next_unsent; const r = syscall4(.sendmmsg, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(&msgvec[next_unsent]), batch_size, flags); - if (E.init(r) != 0) return next_unsent; + if (E.init(r) != .SUCCESS) return next_unsent; if (r < batch_size) return next_unsent + r; } // send current message as own packet - const r = sendmsg(fd, &msg.msg_hdr, flags); - if (E.init(r) != 0) return r; + const r = sendmsg(fd, &msg.hdr, flags); + if (E.init(r) != .SUCCESS) return r; // Linux limits the total bytes sent by sendmsg to INT_MAX, so this cast is safe. - msg.msg_len = @as(u32, @intCast(r)); + msg.len = @as(u32, @intCast(r)); next_unsent = i + 1; break; } - size += iov.len; + size += @intCast(iov.len); } } if (next_unsent < kvlen or next_unsent == 0) { // want to make sure at least one syscall occurs (e.g. to trigger MSG.EOR) const batch_size = kvlen - next_unsent; const r = syscall4(.sendmmsg, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(&msgvec[next_unsent]), batch_size, flags); - if (E.init(r) != 0) return r; + if (E.init(r) != .SUCCESS) return r; return next_unsent + r; } return kvlen; @@ -2267,7 +2274,7 @@ pub fn fadvise(fd: fd_t, offset: i64, len: i64, advice: usize) usize { length_halves[0], length_halves[1], ); - } else if (comptime native_arch == .mips or native_arch == .mipsel) { + } else if (native_arch.isMIPS32()) { // MIPS O32 does not deal with the register alignment issue, so pass a dummy value. const offset_halves = splitValue64(offset); @@ -2377,7 +2384,7 @@ pub fn map_shadow_stack(addr: u64, size: u64, flags: u32) usize { } pub const E = switch (native_arch) { - .mips, .mipsel => enum(u16) { + .mips, .mipsel, .mips64, .mips64el => enum(u16) { /// No error occurred. SUCCESS = 0, @@ -5090,7 +5097,7 @@ pub const epoll_event = extern struct { pub const VFS_CAP_REVISION_MASK = 0xFF000000; pub const VFS_CAP_REVISION_SHIFT = 24; -pub const VFS_CAP_FLAGS_MASK = ~VFS_CAP_REVISION_MASK; +pub const VFS_CAP_FLAGS_MASK = ~@as(u32, VFS_CAP_REVISION_MASK); pub const VFS_CAP_FLAGS_EFFECTIVE = 0x000001; pub const VFS_CAP_REVISION_1 = 0x01000000; @@ -5108,7 +5115,7 @@ pub const VFS_CAP_REVISION = VFS_CAP_REVISION_2; pub const vfs_cap_data = extern struct { //all of these are mandated as little endian //when on disk. - const Data = struct { + const Data = extern struct { permitted: u32, inheritable: u32, }; @@ -6779,7 +6786,258 @@ pub const termios2 = if (is_mips) extern struct { ospeed: speed_t, }; +/// Linux-specific socket ioctls +pub const SIOCINQ = T.FIONREAD; + +/// Linux-specific socket ioctls +/// output queue size (not sent + not acked) +pub const SIOCOUTQ = T.IOCOUTQ; + +pub const SOCK_IOC_TYPE = 0x89; + +pub const SIOCGSTAMP_NEW = IOCTL.IOR(SOCK_IOC_TYPE, 0x06, i64[2]); +pub const SIOCGSTAMP_OLD = IOCTL.IOR('s', 100, timeval); + +/// Get stamp (timeval) +pub const SIOCGSTAMP = if (native_arch == .x86_64 or @sizeOf(timeval) == 8) SIOCGSTAMP_OLD else SIOCGSTAMP_NEW; + +pub const SIOCGSTAMPNS_NEW = IOCTL.IOR(SOCK_IOC_TYPE, 0x07, i64[2]); +pub const SIOCGSTAMPNS_OLD = IOCTL.IOR('s', 101, kernel_timespec); + +/// Get stamp (timespec) +pub const SIOCGSTAMPNS = if (native_arch == .x86_64 or @sizeOf(timespec) == 8) SIOCGSTAMPNS_OLD else SIOCGSTAMPNS_NEW; + +// Routing table calls. +/// Add routing table entry +pub const SIOCADDRT = 0x890B; + +/// Delete routing table entry +pub const SIOCDELRT = 0x890C; + +/// Unused +pub const SIOCRTMSG = 0x890D; + +// Socket configuration controls. +/// Get iface name +pub const SIOCGIFNAME = 0x8910; + +/// Set iface channel +pub const SIOCSIFLINK = 0x8911; + +/// Get iface list +pub const SIOCGIFCONF = 0x8912; + +/// Get flags +pub const SIOCGIFFLAGS = 0x8913; + +/// Set flags +pub const SIOCSIFFLAGS = 0x8914; + +/// Get PA address +pub const SIOCGIFADDR = 0x8915; + +/// Set PA address +pub const SIOCSIFADDR = 0x8916; + +/// Get remote PA address +pub const SIOCGIFDSTADDR = 0x8917; + +/// Set remote PA address +pub const SIOCSIFDSTADDR = 0x8918; + +/// Get broadcast PA address +pub const SIOCGIFBRDADDR = 0x8919; + +/// Set broadcast PA address +pub const SIOCSIFBRDADDR = 0x891a; + +/// Get network PA mask +pub const SIOCGIFNETMASK = 0x891b; + +/// Set network PA mask +pub const SIOCSIFNETMASK = 0x891c; + +/// Get metric +pub const SIOCGIFMETRIC = 0x891d; + +/// Set metric +pub const SIOCSIFMETRIC = 0x891e; + +/// Get memory address (BSD) +pub const SIOCGIFMEM = 0x891f; + +/// Set memory address (BSD) +pub const SIOCSIFMEM = 0x8920; + +/// Get MTU size +pub const SIOCGIFMTU = 0x8921; + +/// Set MTU size +pub const SIOCSIFMTU = 0x8922; + +/// Set interface name +pub const SIOCSIFNAME = 0x8923; + +/// Set hardware address +pub const SIOCSIFHWADDR = 0x8924; + +/// Get encapsulations +pub const SIOCGIFENCAP = 0x8925; + +/// Set encapsulations +pub const SIOCSIFENCAP = 0x8926; + +/// Get hardware address +pub const SIOCGIFHWADDR = 0x8927; + +/// Driver slaving support +pub const SIOCGIFSLAVE = 0x8929; + +/// Driver slaving support +pub const SIOCSIFSLAVE = 0x8930; + +/// Add to Multicast address lists +pub const SIOCADDMULTI = 0x8931; + +/// Delete from Multicast address lists +pub const SIOCDELMULTI = 0x8932; + +/// name -> if_index mapping pub const SIOCGIFINDEX = 0x8933; + +/// Set extended flags set +pub const SIOCSIFPFLAGS = 0x8934; + +/// Get extended flags set +pub const SIOCGIFPFLAGS = 0x8935; + +/// Delete PA address +pub const SIOCDIFADDR = 0x8936; + +/// Set hardware broadcast addr +pub const SIOCSIFHWBROADCAST = 0x8937; + +/// Get number of devices +pub const SIOCGIFCOUNT = 0x8938; + +/// Bridging support +pub const SIOCGIFBR = 0x8940; + +/// Set bridging options +pub const SIOCSIFBR = 0x8941; + +/// Get the tx queue length +pub const SIOCGIFTXQLEN = 0x8942; + +/// Set the tx queue length +pub const SIOCSIFTXQLEN = 0x8943; + +/// Ethtool interface +pub const SIOCETHTOOL = 0x8946; + +/// Get address of MII PHY in use. +pub const SIOCGMIIPHY = 0x8947; + +/// Read MII PHY register. +pub const SIOCGMIIREG = 0x8948; + +/// Write MII PHY register. +pub const SIOCSMIIREG = 0x8949; + +/// Get / Set netdev parameters +pub const SIOCWANDEV = 0x894A; + +/// Output queue size (not sent only) +pub const SIOCOUTQNSD = 0x894B; + +/// Get socket network namespace +pub const SIOCGSKNS = 0x894C; + +// ARP cache control calls. +// 0x8950 - 0x8952 obsolete calls. +/// Delete ARP table entry +pub const SIOCDARP = 0x8953; + +/// Get ARP table entry +pub const SIOCGARP = 0x8954; + +/// Set ARP table entry +pub const SIOCSARP = 0x8955; + +// RARP cache control calls. +/// Delete RARP table entry +pub const SIOCDRARP = 0x8960; + +/// Get RARP table entry +pub const SIOCGRARP = 0x8961; + +/// Set RARP table entry +pub const SIOCSRARP = 0x8962; + +// Driver configuration calls +/// Get device parameters +pub const SIOCGIFMAP = 0x8970; + +/// Set device parameters +pub const SIOCSIFMAP = 0x8971; + +// DLCI configuration calls +/// Create new DLCI device +pub const SIOCADDDLCI = 0x8980; + +/// Delete DLCI device +pub const SIOCDELDLCI = 0x8981; + +/// 802.1Q VLAN support +pub const SIOCGIFVLAN = 0x8982; + +/// Set 802.1Q VLAN options +pub const SIOCSIFVLAN = 0x8983; + +// bonding calls +/// Enslave a device to the bond +pub const SIOCBONDENSLAVE = 0x8990; + +/// Release a slave from the bond +pub const SIOCBONDRELEASE = 0x8991; + +/// Set the hw addr of the bond +pub const SIOCBONDSETHWADDR = 0x8992; + +/// rtn info about slave state +pub const SIOCBONDSLAVEINFOQUERY = 0x8993; + +/// rtn info about bond state +pub const SIOCBONDINFOQUERY = 0x8994; + +/// Update to a new active slave +pub const SIOCBONDCHANGEACTIVE = 0x8995; + +// Bridge calls +/// Create new bridge device +pub const SIOCBRADDBR = 0x89a0; + +/// Remove bridge device +pub const SIOCBRDELBR = 0x89a1; + +/// Add interface to bridge +pub const SIOCBRADDIF = 0x89a2; + +/// Remove interface from bridge +pub const SIOCBRDELIF = 0x89a3; + +/// Get hardware time stamp config +pub const SIOCSHWTSTAMP = 0x89b0; + +/// Set hardware time stamp config +pub const SIOCGHWTSTAMP = 0x89b1; + +/// Device private ioctl calls +pub const SIOCDEVPRIVATE = 0x89F0; + +/// These 16 ioctl calls are protocol private +pub const SIOCPROTOPRIVATE = 0x89E0; + pub const IFNAMESIZE = 16; pub const ifmap = extern struct { @@ -6811,54 +7069,314 @@ pub const ifreq = extern struct { }, }; +pub const PACKET = struct { + pub const HOST = 0; + pub const BROADCAST = 1; + pub const MULTICAST = 2; + pub const OTHERHOST = 3; + pub const OUTGOING = 4; + pub const LOOPBACK = 5; + pub const USER = 6; + pub const KERNEL = 7; + + pub const ADD_MEMBERSHIP = 1; + pub const DROP_MEMBERSHIP = 2; + pub const RECV_OUTPUT = 3; + pub const RX_RING = 5; + pub const STATISTICS = 6; + pub const COPY_THRESH = 7; + pub const AUXDATA = 8; + pub const ORIGDEV = 9; + pub const VERSION = 10; + pub const HDRLEN = 11; + pub const RESERVE = 12; + pub const TX_RING = 13; + pub const LOSS = 14; + pub const VNET_HDR = 15; + pub const TX_TIMESTAMP = 16; + pub const TIMESTAMP = 17; + pub const FANOUT = 18; + pub const TX_HAS_OFF = 19; + pub const QDISC_BYPASS = 20; + pub const ROLLOVER_STATS = 21; + pub const FANOUT_DATA = 22; + pub const IGNORE_OUTGOING = 23; + pub const VNET_HDR_SZ = 24; + + pub const FANOUT_HASH = 0; + pub const FANOUT_LB = 1; + pub const FANOUT_CPU = 2; + pub const FANOUT_ROLLOVER = 3; + pub const FANOUT_RND = 4; + pub const FANOUT_QM = 5; + pub const FANOUT_CBPF = 6; + pub const FANOUT_EBPF = 7; + pub const FANOUT_FLAG_ROLLOVER = 0x1000; + pub const FANOUT_FLAG_UNIQUEID = 0x2000; + pub const FANOUT_FLAG_IGNORE_OUTGOING = 0x4000; + pub const FANOUT_FLAG_DEFRAG = 0x8000; +}; + +pub const tpacket_versions = enum(u32) { + V1 = 0, + V2 = 1, + V3 = 2, +}; + +pub const tpacket_req3 = extern struct { + block_size: c_uint, // Minimal size of contiguous block + block_nr: c_uint, // Number of blocks + frame_size: c_uint, // Size of frame + frame_nr: c_uint, // Total number of frames + retire_blk_tov: c_uint, // Timeout in msecs + sizeof_priv: c_uint, // Offset to private data area + feature_req_word: c_uint, +}; + +pub const tpacket_bd_ts = extern struct { + sec: c_uint, + frac: extern union { + usec: c_uint, + nsec: c_uint, + }, +}; + +pub const TP_STATUS = extern union { + rx: packed struct(u32) { + USER: bool, + COPY: bool, + LOSING: bool, + CSUMNOTREADY: bool, + VLAN_VALID: bool, + BLK_TMO: bool, + VLAN_TPID_VALID: bool, + CSUM_VALID: bool, + GSO_TCP: bool, + _: u20, + TS_SOFTWARE: bool, + TS_SYS_HARDWARE: bool, + TS_RAW_HARDWARE: bool, + }, + tx: packed struct(u32) { + SEND_REQUEST: bool, + SENDING: bool, + WRONG_FORMAT: bool, + _: u26, + TS_SOFTWARE: bool, + TS_SYS_HARDWARE: bool, + TS_RAW_HARDWARE: bool, + }, +}; + +pub const tpacket_hdr_v1 = extern struct { + block_status: TP_STATUS, + num_pkts: u32, + offset_to_first_pkt: u32, + blk_len: u32, + seq_num: u64 align(8), + ts_first_pkt: tpacket_bd_ts, + ts_last_pkt: tpacket_bd_ts, +}; + +pub const tpacket_bd_header_u = extern union { + bh1: tpacket_hdr_v1, +}; + +pub const tpacket_block_desc = extern struct { + version: u32, + offset_to_priv: u32, + hdr: tpacket_bd_header_u, +}; + +pub const tpacket_hdr_variant1 = extern struct { + rxhash: u32, + vlan_tci: u32, + vlan_tpid: u16, + padding: u16, +}; + +pub const tpacket3_hdr = extern struct { + next_offset: u32, + sec: u32, + nsec: u32, + snaplen: u32, + len: u32, + status: u32, + mac: u16, + net: u16, + variant: extern union { + hv1: tpacket_hdr_variant1, + }, + padding: [8]u8, +}; + +pub const tpacket_stats_v3 = extern struct { + packets: c_uint, + drops: c_uint, + freeze_q_cnt: c_uint, +}; + // doc comments copied from musl -pub const rlimit_resource = if (native_arch.isMIPS() or native_arch.isSPARC()) - arch_bits.rlimit_resource -else - enum(c_int) { - /// Per-process CPU limit, in seconds. - CPU, - /// Largest file that can be created, in bytes. - FSIZE, - /// Maximum size of data segment, in bytes. - DATA, - /// Maximum size of stack segment, in bytes. - STACK, - /// Largest core file that can be created, in bytes. - CORE, - /// Largest resident set size, in bytes. - /// This affects swapping; processes that are exceeding their - /// resident set size will be more likely to have physical memory - /// taken from them. - RSS, - /// Number of processes. - NPROC, - /// Number of open files. - NOFILE, - /// Locked-in-memory address space. - MEMLOCK, - /// Address space limit. - AS, - /// Maximum number of file locks. - LOCKS, - /// Maximum number of pending signals. - SIGPENDING, - /// Maximum bytes in POSIX message queues. - MSGQUEUE, - /// Maximum nice priority allowed to raise to. - /// Nice levels 19 .. -20 correspond to 0 .. 39 - /// values of this resource limit. - NICE, - /// Maximum realtime priority allowed for non-privileged - /// processes. - RTPRIO, - /// Maximum CPU time in µs that a process scheduled under a real-time - /// scheduling policy may consume without making a blocking system - /// call before being forcibly descheduled. - RTTIME, +pub const rlimit_resource = if (native_arch.isMIPS()) enum(c_int) { + /// Per-process CPU limit, in seconds. + CPU = 0, - _, - }; + /// Largest file that can be created, in bytes. + FSIZE = 1, + + /// Maximum size of data segment, in bytes. + DATA = 2, + + /// Maximum size of stack segment, in bytes. + STACK = 3, + + /// Largest core file that can be created, in bytes. + CORE = 4, + + /// Number of open files. + NOFILE = 5, + + /// Address space limit. + AS = 6, + + /// Largest resident set size, in bytes. + /// This affects swapping; processes that are exceeding their + /// resident set size will be more likely to have physical memory + /// taken from them. + RSS = 7, + + /// Number of processes. + NPROC = 8, + + /// Locked-in-memory address space. + MEMLOCK = 9, + + /// Maximum number of file locks. + LOCKS = 10, + + /// Maximum number of pending signals. + SIGPENDING = 11, + + /// Maximum bytes in POSIX message queues. + MSGQUEUE = 12, + + /// Maximum nice priority allowed to raise to. + /// Nice levels 19 .. -20 correspond to 0 .. 39 + /// values of this resource limit. + NICE = 13, + + /// Maximum realtime priority allowed for non-privileged + /// processes. + RTPRIO = 14, + + /// Maximum CPU time in µs that a process scheduled under a real-time + /// scheduling policy may consume without making a blocking system + /// call before being forcibly descheduled. + RTTIME = 15, + + _, +} else if (native_arch.isSPARC()) enum(c_int) { + /// Per-process CPU limit, in seconds. + CPU = 0, + + /// Largest file that can be created, in bytes. + FSIZE = 1, + + /// Maximum size of data segment, in bytes. + DATA = 2, + + /// Maximum size of stack segment, in bytes. + STACK = 3, + + /// Largest core file that can be created, in bytes. + CORE = 4, + + /// Largest resident set size, in bytes. + /// This affects swapping; processes that are exceeding their + /// resident set size will be more likely to have physical memory + /// taken from them. + RSS = 5, + + /// Number of open files. + NOFILE = 6, + + /// Number of processes. + NPROC = 7, + + /// Locked-in-memory address space. + MEMLOCK = 8, + + /// Address space limit. + AS = 9, + + /// Maximum number of file locks. + LOCKS = 10, + + /// Maximum number of pending signals. + SIGPENDING = 11, + + /// Maximum bytes in POSIX message queues. + MSGQUEUE = 12, + + /// Maximum nice priority allowed to raise to. + /// Nice levels 19 .. -20 correspond to 0 .. 39 + /// values of this resource limit. + NICE = 13, + + /// Maximum realtime priority allowed for non-privileged + /// processes. + RTPRIO = 14, + + /// Maximum CPU time in µs that a process scheduled under a real-time + /// scheduling policy may consume without making a blocking system + /// call before being forcibly descheduled. + RTTIME = 15, + + _, +} else enum(c_int) { + /// Per-process CPU limit, in seconds. + CPU = 0, + /// Largest file that can be created, in bytes. + FSIZE = 1, + /// Maximum size of data segment, in bytes. + DATA = 2, + /// Maximum size of stack segment, in bytes. + STACK = 3, + /// Largest core file that can be created, in bytes. + CORE = 4, + /// Largest resident set size, in bytes. + /// This affects swapping; processes that are exceeding their + /// resident set size will be more likely to have physical memory + /// taken from them. + RSS = 5, + /// Number of processes. + NPROC = 6, + /// Number of open files. + NOFILE = 7, + /// Locked-in-memory address space. + MEMLOCK = 8, + /// Address space limit. + AS = 9, + /// Maximum number of file locks. + LOCKS = 10, + /// Maximum number of pending signals. + SIGPENDING = 11, + /// Maximum bytes in POSIX message queues. + MSGQUEUE = 12, + /// Maximum nice priority allowed to raise to. + /// Nice levels 19 .. -20 correspond to 0 .. 39 + /// values of this resource limit. + NICE = 13, + /// Maximum realtime priority allowed for non-privileged + /// processes. + RTPRIO = 14, + /// Maximum CPU time in µs that a process scheduled under a real-time + /// scheduling policy may consume without making a blocking system + /// call before being forcibly descheduled. + RTTIME = 15, + + _, +}; pub const rlim_t = u64; diff --git a/zig/lib/std/os/linux/bpf/btf.zig b/zig/lib/std/os/linux/bpf/btf.zig index 39d25014da..3988fce349 100644 --- a/zig/lib/std/os/linux/bpf/btf.zig +++ b/zig/lib/std/os/linux/bpf/btf.zig @@ -83,13 +83,14 @@ pub const Kind = enum(u5) { /// int kind is followed by this struct pub const IntInfo = packed struct(u32) { bits: u8, - unused: u8, + reserved_1: u8, offset: u8, encoding: enum(u4) { signed = 1 << 0, char = 1 << 1, boolean = 1 << 2, }, + reserved_2: u4, }; test "IntInfo is 32 bits" { diff --git a/zig/lib/std/os/linux/mips.zig b/zig/lib/std/os/linux/mips.zig index aa02841926..e6cb5f7900 100644 --- a/zig/lib/std/os/linux/mips.zig +++ b/zig/lib/std/os/linux/mips.zig @@ -14,7 +14,8 @@ const timespec = linux.timespec; pub fn syscall0(number: SYS) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ subu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -28,7 +29,7 @@ pub fn syscall_pipe(fd: *[2]i32) usize { \\ .set noat \\ .set noreorder \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f \\ nop \\ b 2f \\ subu $2, $0, $2 @@ -46,7 +47,8 @@ pub fn syscall_pipe(fd: *[2]i32) usize { pub fn syscall1(number: SYS, arg1: usize) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ subu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -59,7 +61,8 @@ pub fn syscall1(number: SYS, arg1: usize) usize { pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ subu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -73,7 +76,8 @@ pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize { pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ subu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -88,7 +92,8 @@ pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize { pub fn syscall4(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ subu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -108,7 +113,8 @@ pub fn syscall5(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize, \\ sw %[arg5], 16($sp) \\ syscall \\ addu $sp, $sp, 24 - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ subu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -141,7 +147,8 @@ pub fn syscall6( \\ sw %[arg6], 20($sp) \\ syscall \\ addu $sp, $sp, 24 - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ subu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -174,7 +181,8 @@ pub fn syscall7( \\ sw %[arg7], 24($sp) \\ syscall \\ addu $sp, $sp, 32 - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ subu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -314,7 +322,7 @@ pub const msghdr_const = extern struct { flags: i32, }; -pub const blksize_t = i32; +pub const blksize_t = u32; pub const nlink_t = u32; pub const time_t = i32; pub const mode_t = u32; @@ -323,36 +331,47 @@ pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; -// The `stat` definition used by the Linux kernel. +// The `stat64` definition used by the Linux kernel. pub const Stat = extern struct { - dev: u32, - __pad0: [3]u32, // Reserved for st_dev expansion + dev: dev_t, + __pad0: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32). ino: ino_t, mode: mode_t, nlink: nlink_t, uid: uid_t, gid: gid_t, - rdev: u32, - __pad1: [3]u32, + rdev: dev_t, + __pad1: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32). size: off_t, - atim: timespec, - mtim: timespec, - ctim: timespec, + atim: i32, + atim_nsec: u32, + mtim: i32, + mtim_nsec: u32, + ctim: i32, + ctim_nsec: u32, blksize: blksize_t, __pad3: u32, blocks: blkcnt_t, - __pad4: [14]usize, pub fn atime(self: @This()) timespec { - return self.atim; + return .{ + .sec = self.atim, + .nsec = self.atim_nsec, + }; } pub fn mtime(self: @This()) timespec { - return self.mtim; + return .{ + .sec = self.mtim, + .nsec = self.mtim_nsec, + }; } pub fn ctime(self: @This()) timespec { - return self.ctim; + return .{ + .sec = self.ctim, + .nsec = self.ctim_nsec, + }; } }; @@ -368,66 +387,6 @@ pub const timezone = extern struct { pub const Elf_Symndx = u32; -pub const rlimit_resource = enum(c_int) { - /// Per-process CPU limit, in seconds. - CPU, - - /// Largest file that can be created, in bytes. - FSIZE, - - /// Maximum size of data segment, in bytes. - DATA, - - /// Maximum size of stack segment, in bytes. - STACK, - - /// Largest core file that can be created, in bytes. - CORE, - - /// Number of open files. - NOFILE, - - /// Address space limit. - AS, - - /// Largest resident set size, in bytes. - /// This affects swapping; processes that are exceeding their - /// resident set size will be more likely to have physical memory - /// taken from them. - RSS, - - /// Number of processes. - NPROC, - - /// Locked-in-memory address space. - MEMLOCK, - - /// Maximum number of file locks. - LOCKS, - - /// Maximum number of pending signals. - SIGPENDING, - - /// Maximum bytes in POSIX message queues. - MSGQUEUE, - - /// Maximum nice priority allowed to raise to. - /// Nice levels 19 .. -20 correspond to 0 .. 39 - /// values of this resource limit. - NICE, - - /// Maximum realtime priority allowed for non-privileged - /// processes. - RTPRIO, - - /// Maximum CPU time in µs that a process scheduled under a real-time - /// scheduling policy may consume without making a blocking system - /// call before being forcibly descheduled. - RTTIME, - - _, -}; - /// TODO pub const ucontext_t = void; diff --git a/zig/lib/std/os/linux/mips64.zig b/zig/lib/std/os/linux/mips64.zig index 579d41ca75..5e6661eae5 100644 --- a/zig/lib/std/os/linux/mips64.zig +++ b/zig/lib/std/os/linux/mips64.zig @@ -14,7 +14,8 @@ const timespec = linux.timespec; pub fn syscall0(number: SYS) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ dsubu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -28,7 +29,7 @@ pub fn syscall_pipe(fd: *[2]i32) usize { \\ .set noat \\ .set noreorder \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f \\ nop \\ b 2f \\ subu $2, $0, $2 @@ -46,7 +47,9 @@ pub fn syscall_pipe(fd: *[2]i32) usize { pub fn syscall1(number: SYS, arg1: usize) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f + \\ nop \\ dsubu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -59,7 +62,8 @@ pub fn syscall1(number: SYS, arg1: usize) usize { pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ dsubu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -73,7 +77,8 @@ pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize { pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ dsubu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -88,7 +93,8 @@ pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize { pub fn syscall4(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ dsubu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -104,7 +110,8 @@ pub fn syscall4(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize) pub fn syscall5(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ dsubu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -129,7 +136,8 @@ pub fn syscall6( ) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ dsubu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -156,7 +164,8 @@ pub fn syscall7( ) usize { return asm volatile ( \\ syscall - \\ blez $7, 1f + \\ beq $7, $zero, 1f + \\ blez $2, 1f \\ dsubu $2, $0, $2 \\ 1: : [ret] "={$2}" (-> usize), @@ -292,7 +301,7 @@ pub const msghdr_const = extern struct { flags: i32, }; -pub const blksize_t = i32; +pub const blksize_t = u32; pub const nlink_t = u32; pub const time_t = i32; pub const mode_t = u32; @@ -303,34 +312,45 @@ pub const blkcnt_t = i64; // The `stat` definition used by the Linux kernel. pub const Stat = extern struct { - dev: u32, - __pad0: [3]u32, // Reserved for st_dev expansion + dev: dev_t, + __pad0: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32). ino: ino_t, mode: mode_t, nlink: nlink_t, uid: uid_t, gid: gid_t, - rdev: u32, - __pad1: [3]u32, + rdev: dev_t, + __pad1: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32). size: off_t, - atim: timespec, - mtim: timespec, - ctim: timespec, + atim: u32, + atim_nsec: u32, + mtim: u32, + mtim_nsec: u32, + ctim: u32, + ctim_nsec: u32, blksize: blksize_t, __pad3: u32, blocks: blkcnt_t, - __pad4: [14]usize, pub fn atime(self: @This()) timespec { - return self.atim; + return .{ + .sec = self.atim, + .nsec = self.atim_nsec, + }; } pub fn mtime(self: @This()) timespec { - return self.mtim; + return .{ + .sec = self.mtim, + .nsec = self.mtim_nsec, + }; } pub fn ctime(self: @This()) timespec { - return self.ctim; + return .{ + .sec = self.ctim, + .nsec = self.ctim_nsec, + }; } }; @@ -346,66 +366,6 @@ pub const timezone = extern struct { pub const Elf_Symndx = u32; -pub const rlimit_resource = enum(c_int) { - /// Per-process CPU limit, in seconds. - CPU, - - /// Largest file that can be created, in bytes. - FSIZE, - - /// Maximum size of data segment, in bytes. - DATA, - - /// Maximum size of stack segment, in bytes. - STACK, - - /// Largest core file that can be created, in bytes. - CORE, - - /// Number of open files. - NOFILE, - - /// Address space limit. - AS, - - /// Largest resident set size, in bytes. - /// This affects swapping; processes that are exceeding their - /// resident set size will be more likely to have physical memory - /// taken from them. - RSS, - - /// Number of processes. - NPROC, - - /// Locked-in-memory address space. - MEMLOCK, - - /// Maximum number of file locks. - LOCKS, - - /// Maximum number of pending signals. - SIGPENDING, - - /// Maximum bytes in POSIX message queues. - MSGQUEUE, - - /// Maximum nice priority allowed to raise to. - /// Nice levels 19 .. -20 correspond to 0 .. 39 - /// values of this resource limit. - NICE, - - /// Maximum realtime priority allowed for non-privileged - /// processes. - RTPRIO, - - /// Maximum CPU time in µs that a process scheduled under a real-time - /// scheduling policy may consume without making a blocking system - /// call before being forcibly descheduled. - RTTIME, - - _, -}; - /// TODO pub const ucontext_t = void; diff --git a/zig/lib/std/os/linux/sparc64.zig b/zig/lib/std/os/linux/sparc64.zig index a705b58fb6..b30f001000 100644 --- a/zig/lib/std/os/linux/sparc64.zig +++ b/zig/lib/std/os/linux/sparc64.zig @@ -448,63 +448,3 @@ pub const ucontext_t = extern struct { /// TODO pub const getcontext = {}; - -pub const rlimit_resource = enum(c_int) { - /// Per-process CPU limit, in seconds. - CPU, - - /// Largest file that can be created, in bytes. - FSIZE, - - /// Maximum size of data segment, in bytes. - DATA, - - /// Maximum size of stack segment, in bytes. - STACK, - - /// Largest core file that can be created, in bytes. - CORE, - - /// Largest resident set size, in bytes. - /// This affects swapping; processes that are exceeding their - /// resident set size will be more likely to have physical memory - /// taken from them. - RSS, - - /// Number of open files. - NOFILE, - - /// Number of processes. - NPROC, - - /// Locked-in-memory address space. - MEMLOCK, - - /// Address space limit. - AS, - - /// Maximum number of file locks. - LOCKS, - - /// Maximum number of pending signals. - SIGPENDING, - - /// Maximum bytes in POSIX message queues. - MSGQUEUE, - - /// Maximum nice priority allowed to raise to. - /// Nice levels 19 .. -20 correspond to 0 .. 39 - /// values of this resource limit. - NICE, - - /// Maximum realtime priority allowed for non-privileged - /// processes. - RTPRIO, - - /// Maximum CPU time in µs that a process scheduled under a real-time - /// scheduling policy may consume without making a blocking system - /// call before being forcibly descheduled. - RTTIME, - - _, -}; diff --git a/zig/lib/std/os/linux/test.zig b/zig/lib/std/os/linux/test.zig index 8562d4be8e..a8ebec47a5 100644 --- a/zig/lib/std/os/linux/test.zig +++ b/zig/lib/std/os/linux/test.zig @@ -125,6 +125,23 @@ test "fadvise" { try expectEqual(@as(usize, 0), ret); } +test "sigset_t" { + var sigset = linux.empty_sigset; + + try expectEqual(linux.sigismember(&sigset, linux.SIG.USR1), false); + try expectEqual(linux.sigismember(&sigset, linux.SIG.USR2), false); + + linux.sigaddset(&sigset, linux.SIG.USR1); + + try expectEqual(linux.sigismember(&sigset, linux.SIG.USR1), true); + try expectEqual(linux.sigismember(&sigset, linux.SIG.USR2), false); + + linux.sigaddset(&sigset, linux.SIG.USR2); + + try expectEqual(linux.sigismember(&sigset, linux.SIG.USR1), true); + try expectEqual(linux.sigismember(&sigset, linux.SIG.USR2), true); +} + test { _ = linux.IoUring; } diff --git a/zig/lib/std/os/linux/tls.zig b/zig/lib/std/os/linux/tls.zig index d785539434..7dab38024c 100644 --- a/zig/lib/std/os/linux/tls.zig +++ b/zig/lib/std/os/linux/tls.zig @@ -269,7 +269,7 @@ pub fn setThreadPointer(addr: usize) void { }, .loongarch32, .loongarch64 => { asm volatile ( - \\ mv tp, %[addr] + \\ move $tp, %[addr] : : [addr] "r" (addr), ); diff --git a/zig/lib/std/posix.zig b/zig/lib/std/posix.zig index bc270395cc..665ea17788 100644 --- a/zig/lib/std/posix.zig +++ b/zig/lib/std/posix.zig @@ -4746,11 +4746,13 @@ pub fn munmap(memory: []align(mem.page_size) const u8) void { pub const MSyncError = error{ UnmappedMemory, + PermissionDenied, } || UnexpectedError; pub fn msync(memory: []align(mem.page_size) u8, flags: i32) MSyncError!void { switch (errno(system.msync(memory.ptr, memory.len, flags))) { .SUCCESS => return, + .PERM => return error.PermissionDenied, .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory .INVAL => unreachable, // Invalid parameters. else => unreachable, @@ -7106,6 +7108,7 @@ pub const MadviseError = error{ pub fn madvise(ptr: [*]align(mem.page_size) u8, length: usize, advice: u32) MadviseError!void { switch (errno(system.madvise(ptr, length, advice))) { .SUCCESS => return, + .PERM => return error.PermissionDenied, .ACCES => return error.AccessDenied, .AGAIN => return error.SystemResources, .BADF => unreachable, // The map exists, but the area maps something that isn't a file. diff --git a/zig/lib/std/start.zig b/zig/lib/std/start.zig index ca6a5416eb..e899e80568 100644 --- a/zig/lib/std/start.zig +++ b/zig/lib/std/start.zig @@ -232,6 +232,16 @@ fn _start() callconv(.Naked) noreturn { ); } + // Move this to the riscv prong below when this is resolved: https://github.com/ziglang/zig/issues/20918 + if (builtin.cpu.arch.isRISCV() and builtin.zig_backend != .stage2_riscv64) asm volatile ( + \\ .weak __global_pointer$ + \\ .hidden __global_pointer$ + \\ .option push + \\ .option norelax + \\ lla gp, __global_pointer$ + \\ .option pop + ); + // Note that we maintain a very low level of trust with regards to ABI guarantees at this point. // We will redundantly align the stack, clear the link register, etc. While e.g. the Linux // kernel is usually good about upholding the ABI guarantees, the same cannot be said of dynamic @@ -275,24 +285,19 @@ fn _start() callconv(.Naked) noreturn { \\ and sp, #-16 \\ b %[posixCallMainAndExit] , - // zig fmt: off .csky => - if (builtin.position_independent_code) - // The CSKY ABI assumes that `gb` is set to the address of the GOT in order for - // position-independent code to work. We depend on this in `std.os.linux.start_pie` - // to locate `_DYNAMIC` as well. - \\ grs t0, 1f - \\ 1: - \\ lrw gb, 1b@GOTPC - \\ addu gb, t0 - else "" - ++ + // The CSKY ABI assumes that `gb` is set to the address of the GOT in order for + // position-independent code to work. We depend on this in `std.os.linux.start_pie` + // to locate `_DYNAMIC` as well. + \\ grs t0, 1f + \\ 1: + \\ lrw gb, 1b@GOTPC + \\ addu gb, t0 \\ movi lr, 0 \\ mov a0, sp \\ andi sp, sp, -8 \\ jmpi %[posixCallMainAndExit] , - // zig fmt: on .hexagon => // r29 = SP, r30 = FP \\ r30 = #0 @@ -308,27 +313,13 @@ fn _start() callconv(.Naked) noreturn { \\ bstrins.d $sp, $zero, 3, 0 \\ b %[posixCallMainAndExit] , - // zig fmt: off .riscv32, .riscv64 => - // The self-hosted riscv64 backend is not able to assemble this yet. - if (builtin.zig_backend != .stage2_riscv64) - // The RISC-V ELF ABI assumes that `gp` is set to the value of `__global_pointer$` at - // startup in order for GP relaxation to work, even in static builds. - \\ .weak __global_pointer$ - \\ .hidden __global_pointer$ - \\ .option push - \\ .option norelax - \\ lla gp, __global_pointer$ - \\ .option pop - else "" - ++ \\ li s0, 0 \\ li ra, 0 \\ mv a0, sp \\ andi sp, sp, -16 \\ tail %[posixCallMainAndExit]@plt , - // zig fmt: off .m68k => // Note that the - 8 is needed because pc in the jsr instruction points into the middle // of the jsr instruction. (The lea is 6 bytes, the jsr is 4 bytes.) @@ -426,7 +417,7 @@ fn _start() callconv(.Naked) noreturn { else => @compileError("unsupported arch"), } : - : [_start] "X" (_start), + : [_start] "X" (&_start), [posixCallMainAndExit] "X" (&posixCallMainAndExit), ); } diff --git a/zig/lib/std/tar.zig b/zig/lib/std/tar.zig index e81aac0eb5..491d11af2a 100644 --- a/zig/lib/std/tar.zig +++ b/zig/lib/std/tar.zig @@ -19,7 +19,7 @@ const std = @import("std"); const assert = std.debug.assert; const testing = std.testing; -pub const output = @import("tar/output.zig"); +pub const writer = @import("tar/writer.zig").writer; /// Provide this to receive detailed error messages. /// When this is provided, some errors which would otherwise be returned @@ -355,13 +355,13 @@ pub fn Iterator(comptime ReaderType: type) type { } // Writes file content to writer. - pub fn writeAll(self: File, writer: anytype) !void { + pub fn writeAll(self: File, out_writer: anytype) !void { var buffer: [4096]u8 = undefined; while (self.unread_bytes.* > 0) { const buf = buffer[0..@min(buffer.len, self.unread_bytes.*)]; try self.parent_reader.readNoEof(buf); - try writer.writeAll(buf); + try out_writer.writeAll(buf); self.unread_bytes.* -= buf.len; } } diff --git a/zig/lib/std/tar/output.zig b/zig/lib/std/tar/output.zig deleted file mode 100644 index 73cfca58b1..0000000000 --- a/zig/lib/std/tar/output.zig +++ /dev/null @@ -1,85 +0,0 @@ -/// A struct that is exactly 512 bytes and matches tar file format. This is -/// intended to be used for outputting tar files; for parsing there is -/// `std.tar.Header`. -pub const Header = extern struct { - // This struct was originally copied from - // https://github.com/mattnite/tar/blob/main/src/main.zig which is MIT - // licensed. - - name: [100]u8, - mode: [7:0]u8, - uid: [7:0]u8, - gid: [7:0]u8, - size: [11:0]u8, - mtime: [11:0]u8, - checksum: [7:0]u8, - typeflag: FileType, - linkname: [100]u8, - magic: [5:0]u8, - version: [2]u8, - uname: [31:0]u8, - gname: [31:0]u8, - devmajor: [7:0]u8, - devminor: [7:0]u8, - prefix: [155]u8, - pad: [12]u8, - - pub const FileType = enum(u8) { - regular = '0', - hard_link = '1', - symbolic_link = '2', - character = '3', - block = '4', - directory = '5', - fifo = '6', - reserved = '7', - pax_global = 'g', - extended = 'x', - _, - }; - - pub fn init() Header { - var ret = std.mem.zeroes(Header); - ret.magic = [_:0]u8{ 'u', 's', 't', 'a', 'r' }; - ret.version = [_:0]u8{ '0', '0' }; - return ret; - } - - pub fn setPath(self: *Header, prefix: []const u8, path: []const u8) !void { - if (prefix.len + 1 + path.len > 100) { - var i: usize = 0; - while (i < path.len and path.len - i > 100) { - while (path[i] != '/') : (i += 1) {} - } - - _ = try std.fmt.bufPrint(&self.prefix, "{s}/{s}", .{ prefix, path[0..i] }); - _ = try std.fmt.bufPrint(&self.name, "{s}", .{path[i + 1 ..]}); - } else { - _ = try std.fmt.bufPrint(&self.name, "{s}/{s}", .{ prefix, path }); - } - } - - pub fn setSize(self: *Header, size: u64) !void { - _ = try std.fmt.bufPrint(&self.size, "{o:0>11}", .{size}); - } - - pub fn updateChecksum(self: *Header) !void { - const offset = @offsetOf(Header, "checksum"); - var checksum: usize = 0; - for (std.mem.asBytes(self), 0..) |val, i| { - checksum += if (i >= offset and i < offset + @sizeOf(@TypeOf(self.checksum))) - ' ' - else - val; - } - - _ = try std.fmt.bufPrint(&self.checksum, "{o:0>7}", .{checksum}); - } - - comptime { - assert(@sizeOf(Header) == 512); - } -}; - -const std = @import("../std.zig"); -const assert = std.debug.assert; diff --git a/zig/lib/std/tar/writer.zig b/zig/lib/std/tar/writer.zig new file mode 100644 index 0000000000..e75e6c42d6 --- /dev/null +++ b/zig/lib/std/tar/writer.zig @@ -0,0 +1,497 @@ +const std = @import("std"); +const assert = std.debug.assert; +const testing = std.testing; + +/// Creates tar Writer which will write tar content to the `underlying_writer`. +/// Use setRoot to nest all following entries under single root. If file don't +/// fit into posix header (name+prefix: 100+155 bytes) gnu extented header will +/// be used for long names. Options enables setting file premission mode and +/// mtime. Default is to use current time for mtime and 0o664 for file mode. +pub fn writer(underlying_writer: anytype) Writer(@TypeOf(underlying_writer)) { + return .{ .underlying_writer = underlying_writer }; +} + +pub fn Writer(comptime WriterType: type) type { + return struct { + const block_size = @sizeOf(Header); + const empty_block: [block_size]u8 = [_]u8{0} ** block_size; + + /// Options for writing file/dir/link. If left empty 0o664 is used for + /// file mode and current time for mtime. + pub const Options = struct { + /// File system permission mode. + mode: u32 = 0, + /// File system modification time. + mtime: u64 = 0, + }; + const Self = @This(); + + underlying_writer: WriterType, + prefix: []const u8 = "", + mtime_now: u64 = 0, + + /// Sets prefix for all other write* method paths. + pub fn setRoot(self: *Self, root: []const u8) !void { + if (root.len > 0) + try self.writeDir(root, .{}); + + self.prefix = root; + } + + /// Writes directory. + pub fn writeDir(self: *Self, sub_path: []const u8, opt: Options) !void { + try self.writeHeader(.directory, sub_path, "", 0, opt); + } + + /// Writes file system file. + pub fn writeFile(self: *Self, sub_path: []const u8, file: std.fs.File) !void { + const stat = try file.stat(); + const mtime: u64 = @intCast(@divFloor(stat.mtime, std.time.ns_per_s)); + + var header = Header{}; + try self.setPath(&header, sub_path); + try header.setSize(stat.size); + try header.setMtime(mtime); + try header.write(self.underlying_writer); + + try self.underlying_writer.writeFile(file); + try self.writePadding(stat.size); + } + + /// Writes file reading file content from `reader`. Number of bytes in + /// reader must be equal to `size`. + pub fn writeFileStream(self: *Self, sub_path: []const u8, size: usize, reader: anytype, opt: Options) !void { + try self.writeHeader(.regular, sub_path, "", @intCast(size), opt); + + var counting_reader = std.io.countingReader(reader); + var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init(); + try fifo.pump(counting_reader.reader(), self.underlying_writer); + if (counting_reader.bytes_read != size) return error.WrongReaderSize; + try self.writePadding(size); + } + + /// Writes file using bytes buffer `content` for size and file content. + pub fn writeFileBytes(self: *Self, sub_path: []const u8, content: []const u8, opt: Options) !void { + try self.writeHeader(.regular, sub_path, "", @intCast(content.len), opt); + try self.underlying_writer.writeAll(content); + try self.writePadding(content.len); + } + + /// Writes symlink. + pub fn writeLink(self: *Self, sub_path: []const u8, link_name: []const u8, opt: Options) !void { + try self.writeHeader(.symbolic_link, sub_path, link_name, 0, opt); + } + + /// Writes fs.Dir.WalkerEntry. Uses `mtime` from file system entry and + /// default for entry mode . + pub fn writeEntry(self: *Self, entry: std.fs.Dir.Walker.WalkerEntry) !void { + switch (entry.kind) { + .directory => { + try self.writeDir(entry.path, .{ .mtime = try entryMtime(entry) }); + }, + .file => { + var file = try entry.dir.openFile(entry.basename, .{}); + defer file.close(); + try self.writeFile(entry.path, file); + }, + .sym_link => { + var link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const link_name = try entry.dir.readLink(entry.basename, &link_name_buffer); + try self.writeLink(entry.path, link_name, .{ .mtime = try entryMtime(entry) }); + }, + else => { + return error.UnsupportedWalkerEntryKind; + }, + } + } + + fn writeHeader( + self: *Self, + typeflag: Header.FileType, + sub_path: []const u8, + link_name: []const u8, + size: u64, + opt: Options, + ) !void { + var header = Header.init(typeflag); + try self.setPath(&header, sub_path); + try header.setSize(size); + try header.setMtime(if (opt.mtime != 0) opt.mtime else self.mtimeNow()); + if (opt.mode != 0) + try header.setMode(opt.mode); + if (typeflag == .symbolic_link) + header.setLinkname(link_name) catch |err| switch (err) { + error.NameTooLong => try self.writeExtendedHeader(.gnu_long_link, &.{link_name}), + else => return err, + }; + try header.write(self.underlying_writer); + } + + fn mtimeNow(self: *Self) u64 { + if (self.mtime_now == 0) + self.mtime_now = @intCast(std.time.timestamp()); + return self.mtime_now; + } + + fn entryMtime(entry: std.fs.Dir.Walker.WalkerEntry) !u64 { + const stat = try entry.dir.statFile(entry.basename); + return @intCast(@divFloor(stat.mtime, std.time.ns_per_s)); + } + + /// Writes path in posix header, if don't fit (in name+prefix; 100+155 + /// bytes) writes it in gnu extended header. + fn setPath(self: *Self, header: *Header, sub_path: []const u8) !void { + header.setPath(self.prefix, sub_path) catch |err| switch (err) { + error.NameTooLong => { + // write extended header + const buffers: []const []const u8 = if (self.prefix.len == 0) + &.{sub_path} + else + &.{ self.prefix, "/", sub_path }; + try self.writeExtendedHeader(.gnu_long_name, buffers); + }, + else => return err, + }; + } + + /// Writes gnu extended header: gnu_long_name or gnu_long_link. + fn writeExtendedHeader(self: *Self, typeflag: Header.FileType, buffers: []const []const u8) !void { + var len: usize = 0; + for (buffers) |buf| + len += buf.len; + + var header = Header.init(typeflag); + try header.setSize(len); + try header.write(self.underlying_writer); + for (buffers) |buf| + try self.underlying_writer.writeAll(buf); + try self.writePadding(len); + } + + fn writePadding(self: *Self, bytes: u64) !void { + const pos: usize = @intCast(bytes % block_size); + if (pos == 0) return; + try self.underlying_writer.writeAll(empty_block[pos..]); + } + + /// Tar should finish with two zero blocks, but 'reasonable system must + /// not assume that such a block exists when reading an archive' (from + /// reference). In practice it is safe to skip this finish. + pub fn finish(self: *Self) !void { + try self.underlying_writer.writeAll(&empty_block); + try self.underlying_writer.writeAll(&empty_block); + } + }; +} + +/// A struct that is exactly 512 bytes and matches tar file format. This is +/// intended to be used for outputting tar files; for parsing there is +/// `std.tar.Header`. +const Header = extern struct { + // This struct was originally copied from + // https://github.com/mattnite/tar/blob/main/src/main.zig which is MIT + // licensed. + // + // The name, linkname, magic, uname, and gname are null-terminated character + // strings. All other fields are zero-filled octal numbers in ASCII. Each + // numeric field of width w contains w minus 1 digits, and a null. + // Reference: https://www.gnu.org/software/tar/manual/html_node/Standard.html + // POSIX header: byte offset + name: [100]u8 = [_]u8{0} ** 100, // 0 + mode: [7:0]u8 = default_mode.file, // 100 + uid: [7:0]u8 = [_:0]u8{0} ** 7, // unused 108 + gid: [7:0]u8 = [_:0]u8{0} ** 7, // unused 116 + size: [11:0]u8 = [_:0]u8{'0'} ** 11, // 124 + mtime: [11:0]u8 = [_:0]u8{'0'} ** 11, // 136 + checksum: [7:0]u8 = [_:0]u8{' '} ** 7, // 148 + typeflag: FileType = .regular, // 156 + linkname: [100]u8 = [_]u8{0} ** 100, // 157 + magic: [6]u8 = [_]u8{ 'u', 's', 't', 'a', 'r', 0 }, // 257 + version: [2]u8 = [_]u8{ '0', '0' }, // 263 + uname: [32]u8 = [_]u8{0} ** 32, // unused 265 + gname: [32]u8 = [_]u8{0} ** 32, // unused 297 + devmajor: [7:0]u8 = [_:0]u8{0} ** 7, // unused 329 + devminor: [7:0]u8 = [_:0]u8{0} ** 7, // unused 337 + prefix: [155]u8 = [_]u8{0} ** 155, // 345 + pad: [12]u8 = [_]u8{0} ** 12, // unused 500 + + pub const FileType = enum(u8) { + regular = '0', + symbolic_link = '2', + directory = '5', + gnu_long_name = 'L', + gnu_long_link = 'K', + }; + + const default_mode = struct { + const file = [_:0]u8{ '0', '0', '0', '0', '6', '6', '4' }; // 0o664 + const dir = [_:0]u8{ '0', '0', '0', '0', '7', '7', '5' }; // 0o775 + const sym_link = [_:0]u8{ '0', '0', '0', '0', '7', '7', '7' }; // 0o777 + const other = [_:0]u8{ '0', '0', '0', '0', '0', '0', '0' }; // 0o000 + }; + + pub fn init(typeflag: FileType) Header { + return .{ + .typeflag = typeflag, + .mode = switch (typeflag) { + .directory => default_mode.dir, + .symbolic_link => default_mode.sym_link, + .regular => default_mode.file, + else => default_mode.other, + }, + }; + } + + pub fn setSize(self: *Header, size: u64) !void { + try octal(&self.size, size); + } + + fn octal(buf: []u8, value: u64) !void { + var remainder: u64 = value; + var pos: usize = buf.len; + while (remainder > 0 and pos > 0) { + pos -= 1; + const c: u8 = @as(u8, @intCast(remainder % 8)) + '0'; + buf[pos] = c; + remainder /= 8; + if (pos == 0 and remainder > 0) return error.OctalOverflow; + } + } + + pub fn setMode(self: *Header, mode: u32) !void { + try octal(&self.mode, mode); + } + + // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time. + // mtime == 0 will use current time + pub fn setMtime(self: *Header, mtime: u64) !void { + try octal(&self.mtime, mtime); + } + + pub fn updateChecksum(self: *Header) !void { + var checksum: usize = ' '; // other 7 self.checksum bytes are initialized to ' ' + for (std.mem.asBytes(self)) |val| + checksum += val; + try octal(&self.checksum, checksum); + } + + pub fn write(self: *Header, output_writer: anytype) !void { + try self.updateChecksum(); + try output_writer.writeAll(std.mem.asBytes(self)); + } + + pub fn setLinkname(self: *Header, link: []const u8) !void { + if (link.len > self.linkname.len) return error.NameTooLong; + @memcpy(self.linkname[0..link.len], link); + } + + pub fn setPath(self: *Header, prefix: []const u8, sub_path: []const u8) !void { + const max_prefix = self.prefix.len; + const max_name = self.name.len; + const sep = std.fs.path.sep_posix; + + if (prefix.len + sub_path.len > max_name + max_prefix or prefix.len > max_prefix) + return error.NameTooLong; + + // both fit into name + if (prefix.len > 0 and prefix.len + sub_path.len < max_name) { + @memcpy(self.name[0..prefix.len], prefix); + self.name[prefix.len] = sep; + @memcpy(self.name[prefix.len + 1 ..][0..sub_path.len], sub_path); + return; + } + + // sub_path fits into name + // there is no prefix or prefix fits into prefix + if (sub_path.len <= max_name) { + @memcpy(self.name[0..sub_path.len], sub_path); + @memcpy(self.prefix[0..prefix.len], prefix); + return; + } + + if (prefix.len > 0) { + @memcpy(self.prefix[0..prefix.len], prefix); + self.prefix[prefix.len] = sep; + } + const prefix_pos = if (prefix.len > 0) prefix.len + 1 else 0; + + // add as much to prefix as you can, must split at / + const prefix_remaining = max_prefix - prefix_pos; + if (std.mem.lastIndexOf(u8, sub_path[0..@min(prefix_remaining, sub_path.len)], &.{'/'})) |sep_pos| { + @memcpy(self.prefix[prefix_pos..][0..sep_pos], sub_path[0..sep_pos]); + if ((sub_path.len - sep_pos - 1) > max_name) return error.NameTooLong; + @memcpy(self.name[0..][0 .. sub_path.len - sep_pos - 1], sub_path[sep_pos + 1 ..]); + return; + } + + return error.NameTooLong; + } + + comptime { + assert(@sizeOf(Header) == 512); + } + + test setPath { + const cases = [_]struct { + in: []const []const u8, + out: []const []const u8, + }{ + .{ + .in = &.{ "", "123456789" }, + .out = &.{ "", "123456789" }, + }, + // can fit into name + .{ + .in = &.{ "prefix", "sub_path" }, + .out = &.{ "", "prefix/sub_path" }, + }, + // no more both fits into name + .{ + .in = &.{ "prefix", "0123456789/" ** 8 ++ "basename" }, + .out = &.{ "prefix", "0123456789/" ** 8 ++ "basename" }, + }, + // put as much as you can into prefix the rest goes into name + .{ + .in = &.{ "prefix", "0123456789/" ** 10 ++ "basename" }, + .out = &.{ "prefix/" ++ "0123456789/" ** 9 ++ "0123456789", "basename" }, + }, + + .{ + .in = &.{ "prefix", "0123456789/" ** 15 ++ "basename" }, + .out = &.{ "prefix/" ++ "0123456789/" ** 12 ++ "0123456789", "0123456789/0123456789/basename" }, + }, + .{ + .in = &.{ "prefix", "0123456789/" ** 21 ++ "basename" }, + .out = &.{ "prefix/" ++ "0123456789/" ** 12 ++ "0123456789", "0123456789/" ** 8 ++ "basename" }, + }, + .{ + .in = &.{ "", "012345678/" ** 10 ++ "foo" }, + .out = &.{ "012345678/" ** 9 ++ "012345678", "foo" }, + }, + }; + + for (cases) |case| { + var header = Header.init(.regular); + try header.setPath(case.in[0], case.in[1]); + try testing.expectEqualStrings(case.out[0], str(&header.prefix)); + try testing.expectEqualStrings(case.out[1], str(&header.name)); + } + + const error_cases = [_]struct { + in: []const []const u8, + }{ + // basename can't fit into name (106 characters) + .{ .in = &.{ "zig", "test/cases/compile_errors/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig" } }, + // cant fit into 255 + sep + .{ .in = &.{ "prefix", "0123456789/" ** 22 ++ "basename" } }, + // can fit but sub_path can't be split (there is no separator) + .{ .in = &.{ "prefix", "0123456789" ** 10 ++ "a" } }, + .{ .in = &.{ "prefix", "0123456789" ** 14 ++ "basename" } }, + }; + + for (error_cases) |case| { + var header = Header.init(.regular); + try testing.expectError( + error.NameTooLong, + header.setPath(case.in[0], case.in[1]), + ); + } + } + + // Breaks string on first null character. + fn str(s: []const u8) []const u8 { + for (s, 0..) |c, i| { + if (c == 0) return s[0..i]; + } + return s; + } +}; + +test { + _ = Header; +} + +test "write files" { + const files = [_]struct { + path: []const u8, + content: []const u8, + }{ + .{ .path = "foo", .content = "bar" }, + .{ .path = "a12345678/" ** 10 ++ "foo", .content = "a" ** 511 }, + .{ .path = "b12345678/" ** 24 ++ "foo", .content = "b" ** 512 }, + .{ .path = "c12345678/" ** 25 ++ "foo", .content = "c" ** 513 }, + .{ .path = "d12345678/" ** 51 ++ "foo", .content = "d" ** 1025 }, + .{ .path = "e123456789" ** 11, .content = "e" }, + }; + + var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + var link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + + // with root + { + const root = "root"; + + var output = std.ArrayList(u8).init(testing.allocator); + defer output.deinit(); + var wrt = writer(output.writer()); + try wrt.setRoot(root); + for (files) |file| + try wrt.writeFileBytes(file.path, file.content, .{}); + + var input = std.io.fixedBufferStream(output.items); + var iter = std.tar.iterator( + input.reader(), + .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer }, + ); + + // first entry is directory with prefix + { + const actual = (try iter.next()).?; + try testing.expectEqualStrings(root, actual.name); + try testing.expectEqual(std.tar.FileKind.directory, actual.kind); + } + + var i: usize = 0; + while (try iter.next()) |actual| { + defer i += 1; + const expected = files[i]; + try testing.expectEqualStrings(root, actual.name[0..root.len]); + try testing.expectEqual('/', actual.name[root.len..][0]); + try testing.expectEqualStrings(expected.path, actual.name[root.len + 1 ..]); + + var content = std.ArrayList(u8).init(testing.allocator); + defer content.deinit(); + try actual.writeAll(content.writer()); + try testing.expectEqualSlices(u8, expected.content, content.items); + } + } + // without root + { + var output = std.ArrayList(u8).init(testing.allocator); + defer output.deinit(); + var wrt = writer(output.writer()); + for (files) |file| { + var content = std.io.fixedBufferStream(file.content); + try wrt.writeFileStream(file.path, file.content.len, content.reader(), .{}); + } + + var input = std.io.fixedBufferStream(output.items); + var iter = std.tar.iterator( + input.reader(), + .{ .file_name_buffer = &file_name_buffer, .link_name_buffer = &link_name_buffer }, + ); + + var i: usize = 0; + while (try iter.next()) |actual| { + defer i += 1; + const expected = files[i]; + try testing.expectEqualStrings(expected.path, actual.name); + + var content = std.ArrayList(u8).init(testing.allocator); + defer content.deinit(); + try actual.writeAll(content.writer()); + try testing.expectEqualSlices(u8, expected.content, content.items); + } + try wrt.finish(); + } +} diff --git a/zig/lib/std/time/epoch.zig b/zig/lib/std/time/epoch.zig index 631fa178e4..b409d3e9eb 100644 --- a/zig/lib/std/time/epoch.zig +++ b/zig/lib/std/time/epoch.zig @@ -133,7 +133,7 @@ pub const MonthAndDay = struct { day_index: u5, // days into the month (0 to 30) }; -// days since epoch Oct 1, 1970 +/// days since epoch Jan 1, 1970 pub const EpochDay = struct { day: u47, // u47 = u64 - u17 (because day = sec(u64) / secs_per_day(u17) pub fn calculateYearDay(self: EpochDay) YearAndDay { @@ -168,7 +168,7 @@ pub const DaySeconds = struct { } }; -/// seconds since epoch Oct 1, 1970 at 12:00 AM +/// seconds since epoch Jan 1, 1970 at 12:00 AM pub const EpochSeconds = struct { secs: u64, diff --git a/zig/lib/std/unicode.zig b/zig/lib/std/unicode.zig index a8fa1454a5..4c6ec1294b 100644 --- a/zig/lib/std/unicode.zig +++ b/zig/lib/std/unicode.zig @@ -535,6 +535,7 @@ fn testUtf16CountCodepoints() !void { } test "utf16 count codepoints" { + @setEvalBranchQuota(2000); try testUtf16CountCodepoints(); try comptime testUtf16CountCodepoints(); } diff --git a/zig/lib/std/zig/AstGen.zig b/zig/lib/std/zig/AstGen.zig index c24aa6d063..3450e39cc6 100644 --- a/zig/lib/std/zig/AstGen.zig +++ b/zig/lib/std/zig/AstGen.zig @@ -66,6 +66,10 @@ scratch: std.ArrayListUnmanaged(u32) = .{}, /// of ZIR. /// The key is the ref operand; the value is the ref instruction. ref_table: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{}, +/// Any information which should trigger invalidation of incremental compilation +/// data should be used to update this hasher. The result is the final source +/// hash of the enclosing declaration/etc. +src_hasher: std.zig.SrcHasher, const InnerError = error{ OutOfMemory, AnalysisFail }; @@ -137,6 +141,7 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir { .arena = arena.allocator(), .tree = &tree, .nodes_need_rl = &nodes_need_rl, + .src_hasher = undefined, // `structDeclInner` for the root struct will set this }; defer astgen.deinit(gpa); @@ -366,7 +371,6 @@ const ResultInfo = struct { }; const coerced_align_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .u29_type } }; -const coerced_addrspace_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .address_space_type } }; const coerced_linksection_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .slice_const_u8_type } }; const coerced_type_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .type_type } }; const coerced_bool_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .bool_type } }; @@ -1387,7 +1391,7 @@ fn fnProtoExpr( try expr( &block_scope, scope, - .{ .rl = .{ .coerced_ty = .calling_convention_type } }, + .{ .rl = .{ .coerced_ty = try block_scope.addBuiltinValue(fn_proto.ast.callconv_expr, .calling_convention) } }, fn_proto.ast.callconv_expr, ) else @@ -1423,6 +1427,8 @@ fn fnProtoExpr( .is_extern = false, .is_noinline = false, .noalias_bits = noalias_bits, + + .proto_hash = undefined, // ignored for `body_gz == null` }); _ = try block_scope.addBreak(.break_inline, block_inst, result); @@ -3804,7 +3810,8 @@ fn ptrType( gz.astgen.source_line = source_line; gz.astgen.source_column = source_column; - addrspace_ref = try expr(gz, scope, coerced_addrspace_ri, ptr_info.ast.addrspace_node); + const addrspace_ty = try gz.addBuiltinValue(ptr_info.ast.addrspace_node, .address_space); + addrspace_ref = try expr(gz, scope, .{ .rl = .{ .coerced_ty = addrspace_ty } }, ptr_info.ast.addrspace_node); trailing_count += 1; } if (ptr_info.ast.align_node != 0) { @@ -4007,6 +4014,13 @@ fn fnDecl( const tree = astgen.tree; const token_tags = tree.tokens.items(.tag); + const old_hasher = astgen.src_hasher; + defer astgen.src_hasher = old_hasher; + astgen.src_hasher = std.zig.SrcHasher.init(.{}); + // We don't add the full source yet, because we also need the prototype hash! + // The source slice is added towards the *end* of this function. + astgen.src_hasher.update(std.mem.asBytes(&astgen.source_column)); + // missing function name already happened in scanDecls() const fn_name_token = fn_proto.name_token orelse return error.AnalysisFail; @@ -4202,7 +4216,8 @@ fn fnDecl( var addrspace_gz = decl_gz.makeSubBlock(params_scope); defer addrspace_gz.unstack(); const addrspace_ref: Zir.Inst.Ref = if (fn_proto.ast.addrspace_expr == 0) .none else inst: { - const inst = try expr(&decl_gz, params_scope, coerced_addrspace_ri, fn_proto.ast.addrspace_expr); + const addrspace_ty = try decl_gz.addBuiltinValue(fn_proto.ast.addrspace_expr, .address_space); + const inst = try expr(&decl_gz, params_scope, .{ .rl = .{ .coerced_ty = addrspace_ty } }, fn_proto.ast.addrspace_expr); if (addrspace_gz.instructionsSlice().len == 0) { // In this case we will send a len=0 body which can be encoded more efficiently. break :inst inst; @@ -4235,9 +4250,9 @@ fn fnDecl( ); } const inst = try expr( - &decl_gz, + &cc_gz, params_scope, - .{ .rl = .{ .coerced_ty = .calling_convention_type } }, + .{ .rl = .{ .coerced_ty = try cc_gz.addBuiltinValue(fn_proto.ast.callconv_expr, .calling_convention) } }, fn_proto.ast.callconv_expr, ); if (cc_gz.instructionsSlice().len == 0) { @@ -4247,10 +4262,13 @@ fn fnDecl( _ = try cc_gz.addBreak(.break_inline, @enumFromInt(0), inst); break :blk inst; } else if (is_extern) { - // note: https://github.com/ziglang/zig/issues/5269 - break :blk .calling_convention_c; + const inst = try cc_gz.addBuiltinValue(decl_node, .calling_convention_c); + _ = try cc_gz.addBreak(.break_inline, @enumFromInt(0), inst); + break :blk inst; } else if (has_inline_keyword) { - break :blk .calling_convention_inline; + const inst = try cc_gz.addBuiltinValue(decl_node, .calling_convention_inline); + _ = try cc_gz.addBreak(.break_inline, @enumFromInt(0), inst); + break :blk inst; } else { break :blk .none; } @@ -4296,11 +4314,21 @@ fn fnDecl( .is_extern = true, .is_noinline = is_noinline, .noalias_bits = noalias_bits, + .proto_hash = undefined, // ignored for `body_gz == null` }); } else func: { // as a scope, fn_gz encloses ret_gz, but for instruction list, fn_gz stacks on ret_gz fn_gz.instructions_top = ret_gz.instructions.items.len; + // Construct the prototype hash. + // Leave `astgen.src_hasher` unmodified; this will be used for hashing + // the *whole* function declaration, including its body. + var proto_hasher = astgen.src_hasher; + const proto_node = tree.nodes.items(.data)[decl_node].lhs; + proto_hasher.update(tree.getNodeSource(proto_node)); + var proto_hash: std.zig.SrcHash = undefined; + proto_hasher.final(&proto_hash); + const prev_fn_block = astgen.fn_block; const prev_fn_ret_ty = astgen.fn_ret_ty; defer { @@ -4358,16 +4386,22 @@ fn fnDecl( .is_extern = false, .is_noinline = is_noinline, .noalias_bits = noalias_bits, + .proto_hash = proto_hash, }); }; + // *Now* we can incorporate the full source code into the hasher. + astgen.src_hasher.update(tree.getNodeSource(decl_node)); + // We add this at the end so that its instruction index marks the end range // of the top level declaration. addFunc already unstacked fn_gz and ret_gz. _ = try decl_gz.addBreak(.break_inline, decl_inst, func_inst); + var hash: std.zig.SrcHash = undefined; + astgen.src_hasher.final(&hash); try setDeclaration( decl_inst, - std.zig.hashSrc(tree.getNodeSource(decl_node)), + hash, .{ .named = fn_name_token }, decl_gz.decl_line, is_pub, @@ -4391,6 +4425,12 @@ fn globalVarDecl( const tree = astgen.tree; const token_tags = tree.tokens.items(.tag); + const old_hasher = astgen.src_hasher; + defer astgen.src_hasher = old_hasher; + astgen.src_hasher = std.zig.SrcHasher.init(.{}); + astgen.src_hasher.update(tree.getNodeSource(node)); + astgen.src_hasher.update(std.mem.asBytes(&astgen.source_column)); + const is_mutable = token_tags[var_decl.ast.mut_token] == .keyword_var; // We do this at the beginning so that the instruction index marks the range start // of the top level declaration. @@ -4405,7 +4445,6 @@ fn globalVarDecl( .decl_line = astgen.source_line, .astgen = astgen, .is_comptime = true, - .anon_name_strategy = .parent, .instructions = gz.instructions, .instructions_top = gz.instructions.items.len, }; @@ -4463,6 +4502,8 @@ fn globalVarDecl( else .none; + block_scope.anon_name_strategy = .parent; + const init_inst = try expr( &block_scope, &block_scope.base, @@ -4490,6 +4531,8 @@ fn globalVarDecl( // Extern variable which has an explicit type. const type_inst = try typeExpr(&block_scope, &block_scope.base, var_decl.ast.type_node); + block_scope.anon_name_strategy = .parent; + const var_inst = try block_scope.addVar(.{ .var_type = type_inst, .lib_name = lib_name, @@ -4522,13 +4565,16 @@ fn globalVarDecl( var addrspace_gz = linksection_gz.makeSubBlock(scope); if (var_decl.ast.addrspace_node != 0) { - const addrspace_inst = try fullBodyExpr(&addrspace_gz, &addrspace_gz.base, coerced_addrspace_ri, var_decl.ast.addrspace_node); + const addrspace_ty = try addrspace_gz.addBuiltinValue(var_decl.ast.addrspace_node, .address_space); + const addrspace_inst = try fullBodyExpr(&addrspace_gz, &addrspace_gz.base, .{ .rl = .{ .coerced_ty = addrspace_ty } }, var_decl.ast.addrspace_node); _ = try addrspace_gz.addBreakWithSrcNode(.break_inline, decl_inst, addrspace_inst, node); } + var hash: std.zig.SrcHash = undefined; + astgen.src_hasher.final(&hash); try setDeclaration( decl_inst, - std.zig.hashSrc(tree.getNodeSource(node)), + hash, .{ .named = name_token }, block_scope.decl_line, is_pub, @@ -4554,6 +4600,12 @@ fn comptimeDecl( const node_datas = tree.nodes.items(.data); const body_node = node_datas[node].lhs; + const old_hasher = astgen.src_hasher; + defer astgen.src_hasher = old_hasher; + astgen.src_hasher = std.zig.SrcHasher.init(.{}); + astgen.src_hasher.update(tree.getNodeSource(node)); + astgen.src_hasher.update(std.mem.asBytes(&astgen.source_column)); + // Up top so the ZIR instruction index marks the start range of this // top-level declaration. const decl_inst = try gz.makeDeclaration(node); @@ -4576,9 +4628,11 @@ fn comptimeDecl( _ = try decl_block.addBreak(.break_inline, decl_inst, .void_value); } + var hash: std.zig.SrcHash = undefined; + astgen.src_hasher.final(&hash); try setDeclaration( decl_inst, - std.zig.hashSrc(tree.getNodeSource(node)), + hash, .@"comptime", decl_block.decl_line, false, @@ -4599,6 +4653,12 @@ fn usingnamespaceDecl( const tree = astgen.tree; const node_datas = tree.nodes.items(.data); + const old_hasher = astgen.src_hasher; + defer astgen.src_hasher = old_hasher; + astgen.src_hasher = std.zig.SrcHasher.init(.{}); + astgen.src_hasher.update(tree.getNodeSource(node)); + astgen.src_hasher.update(std.mem.asBytes(&astgen.source_column)); + const type_expr = node_datas[node].lhs; const is_pub = blk: { const main_tokens = tree.nodes.items(.main_token); @@ -4626,9 +4686,11 @@ fn usingnamespaceDecl( const namespace_inst = try typeExpr(&decl_block, &decl_block.base, type_expr); _ = try decl_block.addBreak(.break_inline, decl_inst, namespace_inst); + var hash: std.zig.SrcHash = undefined; + astgen.src_hasher.final(&hash); try setDeclaration( decl_inst, - std.zig.hashSrc(tree.getNodeSource(node)), + hash, .@"usingnamespace", decl_block.decl_line, is_pub, @@ -4650,6 +4712,12 @@ fn testDecl( const node_datas = tree.nodes.items(.data); const body_node = node_datas[node].rhs; + const old_hasher = astgen.src_hasher; + defer astgen.src_hasher = old_hasher; + astgen.src_hasher = std.zig.SrcHasher.init(.{}); + astgen.src_hasher.update(tree.getNodeSource(node)); + astgen.src_hasher.update(std.mem.asBytes(&astgen.source_column)); + // Up top so the ZIR instruction index marks the start range of this // top-level declaration. const decl_inst = try gz.makeDeclaration(node); @@ -4811,13 +4879,18 @@ fn testDecl( .is_extern = false, .is_noinline = false, .noalias_bits = 0, + + // Tests don't have a prototype that needs hashing + .proto_hash = .{0} ** 16, }); _ = try decl_block.addBreak(.break_inline, decl_inst, func_inst); + var hash: std.zig.SrcHash = undefined; + astgen.src_hasher.final(&hash); try setDeclaration( decl_inst, - std.zig.hashSrc(tree.getNodeSource(node)), + hash, test_name, decl_block.decl_line, false, @@ -4975,10 +5048,12 @@ fn structDeclInner( } }; - var fields_hasher = std.zig.SrcHasher.init(.{}); - fields_hasher.update(@tagName(layout)); + const old_hasher = astgen.src_hasher; + defer astgen.src_hasher = old_hasher; + astgen.src_hasher = std.zig.SrcHasher.init(.{}); + astgen.src_hasher.update(@tagName(layout)); if (backing_int_node != 0) { - fields_hasher.update(tree.getNodeSource(backing_int_node)); + astgen.src_hasher.update(tree.getNodeSource(backing_int_node)); } var sfba = std.heap.stackFallback(256, astgen.arena); @@ -5001,7 +5076,7 @@ fn structDeclInner( .field => |field| field, }; - fields_hasher.update(tree.getNodeSource(member_node)); + astgen.src_hasher.update(tree.getNodeSource(member_node)); if (!is_tuple) { const field_name = try astgen.identAsString(member.ast.main_token); @@ -5131,7 +5206,7 @@ fn structDeclInner( } var fields_hash: std.zig.SrcHash = undefined; - fields_hasher.final(&fields_hash); + astgen.src_hasher.final(&fields_hash); try gz.setStruct(decl_inst, .{ .src_node = node, @@ -5232,11 +5307,13 @@ fn unionDeclInner( var wip_members = try WipMembers.init(gpa, &astgen.scratch, decl_count, field_count, bits_per_field, max_field_size); defer wip_members.deinit(); - var fields_hasher = std.zig.SrcHasher.init(.{}); - fields_hasher.update(@tagName(layout)); - fields_hasher.update(&.{@intFromBool(auto_enum_tok != null)}); + const old_hasher = astgen.src_hasher; + defer astgen.src_hasher = old_hasher; + astgen.src_hasher = std.zig.SrcHasher.init(.{}); + astgen.src_hasher.update(@tagName(layout)); + astgen.src_hasher.update(&.{@intFromBool(auto_enum_tok != null)}); if (arg_node != 0) { - fields_hasher.update(astgen.tree.getNodeSource(arg_node)); + astgen.src_hasher.update(astgen.tree.getNodeSource(arg_node)); } var sfba = std.heap.stackFallback(256, astgen.arena); @@ -5253,7 +5330,7 @@ fn unionDeclInner( .decl => continue, .field => |field| field, }; - fields_hasher.update(astgen.tree.getNodeSource(member_node)); + astgen.src_hasher.update(astgen.tree.getNodeSource(member_node)); member.convertToNonTupleLike(astgen.tree.nodes); if (member.ast.tuple_like) { return astgen.failTok(member.ast.main_token, "union field missing name", .{}); @@ -5356,7 +5433,7 @@ fn unionDeclInner( } var fields_hash: std.zig.SrcHash = undefined; - fields_hasher.final(&fields_hash); + astgen.src_hasher.final(&fields_hash); if (!block_scope.isEmpty()) { _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); @@ -5570,11 +5647,13 @@ fn containerDecl( var wip_members = try WipMembers.init(gpa, &astgen.scratch, @intCast(counts.decls), @intCast(counts.total_fields), bits_per_field, max_field_size); defer wip_members.deinit(); - var fields_hasher = std.zig.SrcHasher.init(.{}); + const old_hasher = astgen.src_hasher; + defer astgen.src_hasher = old_hasher; + astgen.src_hasher = std.zig.SrcHasher.init(.{}); if (container_decl.ast.arg != 0) { - fields_hasher.update(tree.getNodeSource(container_decl.ast.arg)); + astgen.src_hasher.update(tree.getNodeSource(container_decl.ast.arg)); } - fields_hasher.update(&.{@intFromBool(nonexhaustive)}); + astgen.src_hasher.update(&.{@intFromBool(nonexhaustive)}); var sfba = std.heap.stackFallback(256, astgen.arena); const sfba_allocator = sfba.get(); @@ -5588,7 +5667,7 @@ fn containerDecl( for (container_decl.ast.members) |member_node| { if (member_node == counts.nonexhaustive_node) continue; - fields_hasher.update(tree.getNodeSource(member_node)); + astgen.src_hasher.update(tree.getNodeSource(member_node)); var member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) { .decl => continue, .field => |field| field, @@ -5668,7 +5747,7 @@ fn containerDecl( } var fields_hash: std.zig.SrcHash = undefined; - fields_hasher.final(&fields_hash); + astgen.src_hasher.final(&fields_hash); const body = block_scope.instructionsSlice(); const body_len = astgen.countBodyLenAfterFixups(body); @@ -8470,6 +8549,10 @@ fn tunnelThroughClosure( }); } + // Incorporate the capture index into the source hash, so that changes in + // the order of captures cause suitable re-analysis. + astgen.src_hasher.update(std.mem.asBytes(&cur_capture_index)); + // Add an instruction to get the value from the closure. return gz.addExtendedNodeSmall(.closure_get, inner_ref_node, cur_capture_index); } @@ -9166,6 +9249,7 @@ fn builtinCall( // zig fmt: on .@"export" => { + const export_options_ty = try gz.addBuiltinValue(node, .export_options); const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); // This function causes a Decl to be exported. The first parameter is not an expression, @@ -9189,7 +9273,7 @@ fn builtinCall( local_val.used = ident_token; _ = try gz.addPlNode(.export_value, node, Zir.Inst.ExportValue{ .operand = local_val.inst, - .options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .export_options_type } }, params[1]), + .options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = export_options_ty } }, params[1]), }); return rvalue(gz, ri, .void_value, node); } @@ -9204,7 +9288,7 @@ fn builtinCall( const loaded = try gz.addUnNode(.load, local_ptr.ptr, node); _ = try gz.addPlNode(.export_value, node, Zir.Inst.ExportValue{ .operand = loaded, - .options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .export_options_type } }, params[1]), + .options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = export_options_ty } }, params[1]), }); return rvalue(gz, ri, .void_value, node); } @@ -9242,7 +9326,7 @@ fn builtinCall( }, else => return astgen.failNode(params[0], "symbol to export must identify a declaration", .{}), } - const options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .export_options_type } }, params[1]); + const options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = export_options_ty } }, params[1]); _ = try gz.addPlNode(.@"export", node, Zir.Inst.Export{ .namespace = namespace, .decl_name = decl_name, @@ -9252,7 +9336,8 @@ fn builtinCall( }, .@"extern" => { const type_inst = try typeExpr(gz, scope, params[0]); - const options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .extern_options_type } }, params[1]); + const extern_options_ty = try gz.addBuiltinValue(node, .extern_options); + const options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = extern_options_ty } }, params[1]); const result = try gz.addExtendedPayload(.builtin_extern, Zir.Inst.BinNode{ .node = gz.nodeIndexToRelative(node), .lhs = type_inst, @@ -9261,7 +9346,8 @@ fn builtinCall( return rvalue(gz, ri, result, node); }, .fence => { - const order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[0]); + const atomic_order_ty = try gz.addBuiltinValue(node, .atomic_order); + const order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = atomic_order_ty } }, params[0]); _ = try gz.addExtendedPayload(.fence, Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(node), .operand = order, @@ -9269,7 +9355,8 @@ fn builtinCall( return rvalue(gz, ri, .void_value, node); }, .set_float_mode => { - const order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .float_mode_type } }, params[0]); + const float_mode_ty = try gz.addBuiltinValue(node, .float_mode); + const order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = float_mode_ty } }, params[0]); _ = try gz.addExtendedPayload(.set_float_mode, Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(node), .operand = order, @@ -9294,6 +9381,13 @@ fn builtinCall( }, .src => { + // Incorporate the source location into the source hash, so that + // changes in the source location of `@src()` result in re-analysis. + astgen.src_hasher.update( + std.mem.asBytes(&astgen.source_line) ++ + std.mem.asBytes(&astgen.source_column), + ); + const token_starts = tree.tokens.items(.start); const node_start = token_starts[tree.firstToken(node)]; astgen.advanceSourceCursor(node_start); @@ -9362,7 +9456,8 @@ fn builtinCall( }, .Type => { - const operand = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .type_info_type } }, params[0]); + const type_info_ty = try gz.addBuiltinValue(node, .type_info); + const operand = try expr(gz, scope, .{ .rl = .{ .coerced_ty = type_info_ty } }, params[0]); const gpa = gz.astgen.gpa; @@ -9499,7 +9594,8 @@ fn builtinCall( return rvalue(gz, ri, result, node); }, .reduce => { - const op = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .reduce_op_type } }, params[0]); + const reduce_op_ty = try gz.addBuiltinValue(node, .reduce_op); + const op = try expr(gz, scope, .{ .rl = .{ .coerced_ty = reduce_op_ty } }, params[0]); const scalar = try expr(gz, scope, .{ .rl = .none }, params[1]); const result = try gz.addPlNode(.reduce, node, Zir.Inst.Bin{ .lhs = op, @@ -9514,34 +9610,38 @@ fn builtinCall( .shl_with_overflow => return overflowArithmetic(gz, scope, ri, node, params, .shl_with_overflow), .atomic_load => { + const atomic_order_type = try gz.addBuiltinValue(node, .atomic_order); const result = try gz.addPlNode(.atomic_load, node, Zir.Inst.AtomicLoad{ // zig fmt: off - .elem_type = try typeExpr(gz, scope, params[0]), - .ptr = try expr (gz, scope, .{ .rl = .none }, params[1]), - .ordering = try expr (gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[2]), + .elem_type = try typeExpr(gz, scope, params[0]), + .ptr = try expr (gz, scope, .{ .rl = .none }, params[1]), + .ordering = try expr (gz, scope, .{ .rl = .{ .coerced_ty = atomic_order_type } }, params[2]), // zig fmt: on }); return rvalue(gz, ri, result, node); }, .atomic_rmw => { + const atomic_order_type = try gz.addBuiltinValue(node, .atomic_order); + const atomic_rmw_op_type = try gz.addBuiltinValue(node, .atomic_rmw_op); const int_type = try typeExpr(gz, scope, params[0]); const result = try gz.addPlNode(.atomic_rmw, node, Zir.Inst.AtomicRmw{ // zig fmt: off - .ptr = try expr(gz, scope, .{ .rl = .none }, params[1]), - .operation = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_rmw_op_type } }, params[2]), - .operand = try expr(gz, scope, .{ .rl = .{ .ty = int_type } }, params[3]), - .ordering = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[4]), + .ptr = try expr(gz, scope, .{ .rl = .none }, params[1]), + .operation = try expr(gz, scope, .{ .rl = .{ .coerced_ty = atomic_rmw_op_type } }, params[2]), + .operand = try expr(gz, scope, .{ .rl = .{ .ty = int_type } }, params[3]), + .ordering = try expr(gz, scope, .{ .rl = .{ .coerced_ty = atomic_order_type } }, params[4]), // zig fmt: on }); return rvalue(gz, ri, result, node); }, .atomic_store => { + const atomic_order_type = try gz.addBuiltinValue(node, .atomic_order); const int_type = try typeExpr(gz, scope, params[0]); _ = try gz.addPlNode(.atomic_store, node, Zir.Inst.AtomicStore{ // zig fmt: off - .ptr = try expr(gz, scope, .{ .rl = .none }, params[1]), - .operand = try expr(gz, scope, .{ .rl = .{ .ty = int_type } }, params[2]), - .ordering = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[3]), + .ptr = try expr(gz, scope, .{ .rl = .none }, params[1]), + .operand = try expr(gz, scope, .{ .rl = .{ .ty = int_type } }, params[2]), + .ordering = try expr(gz, scope, .{ .rl = .{ .coerced_ty = atomic_order_type } }, params[3]), // zig fmt: on }); return rvalue(gz, ri, .void_value, node); @@ -9559,7 +9659,8 @@ fn builtinCall( return rvalue(gz, ri, result, node); }, .call => { - const modifier = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .call_modifier_type } }, params[0]); + const call_modifier_ty = try gz.addBuiltinValue(node, .call_modifier); + const modifier = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = call_modifier_ty } }, params[0]); const callee = try expr(gz, scope, .{ .rl = .none }, params[1]); const args = try expr(gz, scope, .{ .rl = .none }, params[2]); const result = try gz.addPlNode(.builtin_call, node, Zir.Inst.BuiltinCall{ @@ -9638,8 +9739,9 @@ fn builtinCall( return rvalue(gz, ri, result, node); }, .prefetch => { + const prefetch_options_ty = try gz.addBuiltinValue(node, .prefetch_options); const ptr = try expr(gz, scope, .{ .rl = .none }, params[0]); - const options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .prefetch_options_type } }, params[1]); + const options = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = prefetch_options_ty } }, params[1]); _ = try gz.addExtendedPayload(.prefetch, Zir.Inst.BinNode{ .node = gz.nodeIndexToRelative(node), .lhs = ptr, @@ -9809,14 +9911,15 @@ fn cmpxchg( small: u16, ) InnerError!Zir.Inst.Ref { const int_type = try typeExpr(gz, scope, params[0]); + const atomic_order_type = try gz.addBuiltinValue(node, .atomic_order); const result = try gz.addExtendedPayloadSmall(.cmpxchg, small, Zir.Inst.Cmpxchg{ // zig fmt: off .node = gz.nodeIndexToRelative(node), - .ptr = try expr(gz, scope, .{ .rl = .none }, params[1]), - .expected_value = try expr(gz, scope, .{ .rl = .{ .ty = int_type } }, params[2]), - .new_value = try expr(gz, scope, .{ .rl = .{ .coerced_ty = int_type } }, params[3]), - .success_order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[4]), - .failure_order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[5]), + .ptr = try expr(gz, scope, .{ .rl = .none }, params[1]), + .expected_value = try expr(gz, scope, .{ .rl = .{ .ty = int_type } }, params[2]), + .new_value = try expr(gz, scope, .{ .rl = .{ .coerced_ty = int_type } }, params[3]), + .success_order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = atomic_order_type } }, params[4]), + .failure_order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = atomic_order_type } }, params[5]), // zig fmt: on }); return rvalue(gz, ri, result, node); @@ -11103,17 +11206,6 @@ fn rvalueInner( as_ty | @intFromEnum(Zir.Inst.Ref.null_type), as_ty | @intFromEnum(Zir.Inst.Ref.undefined_type), as_ty | @intFromEnum(Zir.Inst.Ref.enum_literal_type), - as_ty | @intFromEnum(Zir.Inst.Ref.atomic_order_type), - as_ty | @intFromEnum(Zir.Inst.Ref.atomic_rmw_op_type), - as_ty | @intFromEnum(Zir.Inst.Ref.calling_convention_type), - as_ty | @intFromEnum(Zir.Inst.Ref.address_space_type), - as_ty | @intFromEnum(Zir.Inst.Ref.float_mode_type), - as_ty | @intFromEnum(Zir.Inst.Ref.reduce_op_type), - as_ty | @intFromEnum(Zir.Inst.Ref.call_modifier_type), - as_ty | @intFromEnum(Zir.Inst.Ref.prefetch_options_type), - as_ty | @intFromEnum(Zir.Inst.Ref.export_options_type), - as_ty | @intFromEnum(Zir.Inst.Ref.extern_options_type), - as_ty | @intFromEnum(Zir.Inst.Ref.type_info_type), as_ty | @intFromEnum(Zir.Inst.Ref.manyptr_u8_type), as_ty | @intFromEnum(Zir.Inst.Ref.manyptr_const_u8_type), as_ty | @intFromEnum(Zir.Inst.Ref.manyptr_const_u8_sentinel_0_type), @@ -12112,6 +12204,9 @@ const GenZir = struct { is_test: bool, is_extern: bool, is_noinline: bool, + + /// Ignored if `body_gz == null`. + proto_hash: std.zig.SrcHash, }) !Zir.Inst.Ref { assert(args.src_node != 0); const astgen = gz.astgen; @@ -12140,15 +12235,7 @@ const GenZir = struct { const columns = args.lbrace_column | (rbrace_column << 16); - const proto_hash: std.zig.SrcHash = switch (node_tags[fn_decl]) { - .fn_decl => sig_hash: { - const proto_node = node_datas[fn_decl].lhs; - break :sig_hash std.zig.hashSrc(tree.getNodeSource(proto_node)); - }, - .test_decl => std.zig.hashSrc(""), // tests don't have a prototype - else => unreachable, - }; - const proto_hash_arr: [4]u32 = @bitCast(proto_hash); + const proto_hash_arr: [4]u32 = @bitCast(args.proto_hash); src_locs_and_hash_buffer = .{ args.lbrace_line, @@ -12569,6 +12656,10 @@ const GenZir = struct { return new_index; } + fn addBuiltinValue(gz: *GenZir, src_node: Ast.Node.Index, val: Zir.Inst.BuiltinValue) !Zir.Inst.Ref { + return addExtendedNodeSmall(gz, .builtin_value, src_node, @intFromEnum(val)); + } + fn addExtendedPayload(gz: *GenZir, opcode: Zir.Inst.Extended, extra: anytype) !Zir.Inst.Ref { return addExtendedPayloadSmall(gz, opcode, undefined, extra); } diff --git a/zig/lib/std/zig/Server.zig b/zig/lib/std/zig/Server.zig index 7ce017045d..0ed9cfcd0b 100644 --- a/zig/lib/std/zig/Server.zig +++ b/zig/lib/std/zig/Server.zig @@ -14,8 +14,8 @@ pub const Message = struct { zig_version, /// Body is an ErrorBundle. error_bundle, - /// Body is a EmitBinPath. - emit_bin_path, + /// Body is a EmitDigest. + emit_digest, /// Body is a TestMetadata test_metadata, /// Body is a TestResults @@ -82,8 +82,8 @@ pub const Message = struct { }; /// Trailing: - /// * file system path where the emitted binary can be found - pub const EmitBinPath = extern struct { + /// * the hex digest of the cache directory within the /o/ subdirectory. + pub const EmitDigest = extern struct { flags: Flags, pub const Flags = packed struct(u8) { @@ -196,17 +196,17 @@ pub fn serveU64Message(s: *Server, tag: OutMessage.Tag, int: u64) !void { }, &.{std.mem.asBytes(&msg_le)}); } -pub fn serveEmitBinPath( +pub fn serveEmitDigest( s: *Server, - fs_path: []const u8, - header: OutMessage.EmitBinPath, + digest: *const [Cache.bin_digest_len]u8, + header: OutMessage.EmitDigest, ) !void { try s.serveMessage(.{ - .tag = .emit_bin_path, - .bytes_len = @intCast(fs_path.len + @sizeOf(OutMessage.EmitBinPath)), + .tag = .emit_digest, + .bytes_len = @intCast(digest.len + @sizeOf(OutMessage.EmitDigest)), }, &.{ std.mem.asBytes(&header), - fs_path, + digest, }); } @@ -328,3 +328,4 @@ const Allocator = std.mem.Allocator; const assert = std.debug.assert; const native_endian = builtin.target.cpu.arch.endian(); const need_bswap = native_endian != .little; +const Cache = std.Build.Cache; diff --git a/zig/lib/std/zig/Zir.zig b/zig/lib/std/zig/Zir.zig index 051a799db6..26f4c4780f 100644 --- a/zig/lib/std/zig/Zir.zig +++ b/zig/lib/std/zig/Zir.zig @@ -603,7 +603,7 @@ pub const Inst = struct { /// Uses the `un_node` field. typeof, /// Implements `@TypeOf` for one operand. - /// Uses the `pl_node` field. + /// Uses the `pl_node` field. Payload is `Block`. typeof_builtin, /// Given a value, look at the type of it, which must be an integer type. /// Returns the integer type for the RHS of a shift operation. @@ -2055,6 +2055,10 @@ pub const Inst = struct { /// Guaranteed to not have the `ptr_cast` flag. /// Uses the `pl_node` union field with payload `FieldParentPtr`. field_parent_ptr, + /// Get a type or value from `std.builtin`. + /// `operand` is `src_node: i32`. + /// `small` is an `Inst.BuiltinValue`. + builtin_value, pub const InstData = struct { opcode: Extended, @@ -2071,7 +2075,7 @@ pub const Inst = struct { ref_start_index = static_len, _, - pub const static_len = 84; + pub const static_len = 71; pub fn toRef(i: Index) Inst.Ref { return @enumFromInt(@intFromEnum(Index.ref_start_index) + @intFromEnum(i)); @@ -2148,17 +2152,6 @@ pub const Inst = struct { null_type, undefined_type, enum_literal_type, - atomic_order_type, - atomic_rmw_op_type, - calling_convention_type, - address_space_type, - float_mode_type, - reduce_op_type, - call_modifier_type, - prefetch_options_type, - export_options_type, - extern_options_type, - type_info_type, manyptr_u8_type, manyptr_const_u8_type, manyptr_const_u8_sentinel_0_type, @@ -2179,8 +2172,6 @@ pub const Inst = struct { one_u8, four_u8, negative_one, - calling_convention_c, - calling_convention_inline, void_value, unreachable_value, null_value, @@ -2727,6 +2718,9 @@ pub const Inst = struct { field_name_start: NullTerminatedString, }; + /// There is a body of instructions at `extra[body_index..][0..body_len]`. + /// Trailing: + /// 0. operand: Ref // for each `operands_len` pub const TypeOfPeer = struct { src_node: i32, body_len: u32, @@ -2844,6 +2838,40 @@ pub const Inst = struct { src_line: u32, }; + /// Trailing: + /// 0. multi_cases_len: u32 // if `has_multi_cases` + /// 1. err_capture_inst: u32 // if `any_uses_err_capture` + /// 2. non_err_body { + /// info: ProngInfo, + /// inst: Index // for every `info.body_len` + /// } + /// 3. else_body { // if `has_else` + /// info: ProngInfo, + /// inst: Index // for every `info.body_len` + /// } + /// 4. scalar_cases: { // for every `scalar_cases_len` + /// item: Ref, + /// info: ProngInfo, + /// inst: Index // for every `info.body_len` + /// } + /// 5. multi_cases: { // for every `multi_cases_len` + /// items_len: u32, + /// ranges_len: u32, + /// info: ProngInfo, + /// item: Ref // for every `items_len` + /// ranges: { // for every `ranges_len` + /// item_first: Ref, + /// item_last: Ref, + /// } + /// inst: Index // for every `info.body_len` + /// } + /// + /// When analyzing a case body, the switch instruction itself refers to the + /// captured error, or to the success value in `non_err_body`. Whether this + /// is captured by reference or by value depends on whether the `byref` bit + /// is set for the corresponding body. `err_capture_inst` refers to the error + /// capture outside of the `switch`, i.e. `err` in + /// `x catch |err| switch (err) { ... }`. pub const SwitchBlockErrUnion = struct { operand: Ref, bits: Bits, @@ -3109,6 +3137,24 @@ pub const Inst = struct { } }; + pub const BuiltinValue = enum(u16) { + // Types + atomic_order, + atomic_rmw_op, + calling_convention, + address_space, + float_mode, + reduce_op, + call_modifier, + prefetch_options, + export_options, + extern_options, + type_info, + // Values + calling_convention_c, + calling_convention_inline, + }; + /// Trailing: /// 0. tag_type: Ref, // if has_tag_type /// 1. captures_len: u32, // if has_captures_len @@ -3153,7 +3199,7 @@ pub const Inst = struct { /// 1. captures_len: u32 // if has_captures_len /// 2. body_len: u32, // if has_body_len /// 3. fields_len: u32, // if has_fields_len - /// 4. decls_len: u37, // if has_decls_len + /// 4. decls_len: u32, // if has_decls_len /// 5. capture: Capture // for every captures_len /// 6. decl: Index, // for every decls_len; points to a `declaration` instruction /// 7. inst: Index // for every body_len @@ -3502,7 +3548,7 @@ pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator { const datas = zir.instructions.items(.data); switch (tags[@intFromEnum(decl_inst)]) { // Functions are allowed and yield no iterations. - // There is one case matching this in the extended instruction set below. + // This is because they are returned by `findDecls`. .func, .func_inferred, .func_fancy => return .{ .extra_index = undefined, .decls_remaining = 0, @@ -3512,6 +3558,13 @@ pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator { .extended => { const extended = datas[@intFromEnum(decl_inst)].extended; switch (extended.opcode) { + // Reifications are allowed and yield no iterations. + // This is because they are returned by `findDecls`. + .reify => return .{ + .extra_index = undefined, + .decls_remaining = 0, + .zir = zir, + }, .struct_decl => { const small: Inst.StructDecl.Small = @bitCast(extended.small); var extra_index: u32 = @intCast(extended.operand + @typeInfo(Inst.StructDecl).Struct.fields.len); @@ -3624,33 +3677,504 @@ pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator { } } -/// The iterator would have to allocate memory anyway to iterate. So here we populate -/// an ArrayList as the result. -pub fn findDecls(zir: Zir, list: *std.ArrayList(Inst.Index), decl_inst: Zir.Inst.Index) !void { +/// Find all type declarations, recursively, within a `declaration` instruction. Does not recurse through +/// said type declarations' declarations; to find all declarations, call this function on the declarations +/// of the discovered types recursively. +/// The iterator would have to allocate memory anyway to iterate, so an `ArrayList` is populated as the result. +pub fn findDecls(zir: Zir, gpa: Allocator, list: *std.ArrayListUnmanaged(Inst.Index), decl_inst: Zir.Inst.Index) !void { list.clearRetainingCapacity(); const declaration, const extra_end = zir.getDeclaration(decl_inst); const bodies = declaration.getBodies(extra_end, zir); - try zir.findDeclsBody(list, bodies.value_body); - if (bodies.align_body) |b| try zir.findDeclsBody(list, b); - if (bodies.linksection_body) |b| try zir.findDeclsBody(list, b); - if (bodies.addrspace_body) |b| try zir.findDeclsBody(list, b); + // `defer` instructions duplicate the same body arbitrarily many times, but we only want to traverse + // their contents once per defer. So, we store the extra index of the body here to deduplicate. + var found_defers: std.AutoHashMapUnmanaged(u32, void) = .{}; + defer found_defers.deinit(gpa); + + try zir.findDeclsBody(gpa, list, &found_defers, bodies.value_body); + if (bodies.align_body) |b| try zir.findDeclsBody(gpa, list, &found_defers, b); + if (bodies.linksection_body) |b| try zir.findDeclsBody(gpa, list, &found_defers, b); + if (bodies.addrspace_body) |b| try zir.findDeclsBody(gpa, list, &found_defers, b); +} + +/// Like `findDecls`, but only considers the `main_struct_inst` instruction. This may return more than +/// just that instruction because it will also traverse fields. +pub fn findDeclsRoot(zir: Zir, gpa: Allocator, list: *std.ArrayListUnmanaged(Inst.Index)) !void { + list.clearRetainingCapacity(); + + var found_defers: std.AutoHashMapUnmanaged(u32, void) = .{}; + defer found_defers.deinit(gpa); + + try zir.findDeclsInner(gpa, list, &found_defers, .main_struct_inst); } fn findDeclsInner( zir: Zir, - list: *std.ArrayList(Inst.Index), + gpa: Allocator, + list: *std.ArrayListUnmanaged(Inst.Index), + defers: *std.AutoHashMapUnmanaged(u32, void), inst: Inst.Index, ) Allocator.Error!void { const tags = zir.instructions.items(.tag); const datas = zir.instructions.items(.data); switch (tags[@intFromEnum(inst)]) { + .declaration => unreachable, + + // Boring instruction tags first. These have no body and are not declarations or type declarations. + .add, + .addwrap, + .add_sat, + .add_unsafe, + .sub, + .subwrap, + .sub_sat, + .mul, + .mulwrap, + .mul_sat, + .div_exact, + .div_floor, + .div_trunc, + .mod, + .rem, + .mod_rem, + .shl, + .shl_exact, + .shl_sat, + .shr, + .shr_exact, + .param_anytype, + .param_anytype_comptime, + .array_cat, + .array_mul, + .array_type, + .array_type_sentinel, + .vector_type, + .elem_type, + .indexable_ptr_elem_type, + .vector_elem_type, + .indexable_ptr_len, + .anyframe_type, + .as_node, + .as_shift_operand, + .bit_and, + .bitcast, + .bit_not, + .bit_or, + .bool_not, + .bool_br_and, + .bool_br_or, + .@"break", + .break_inline, + .check_comptime_control_flow, + .builtin_call, + .cmp_lt, + .cmp_lte, + .cmp_eq, + .cmp_gte, + .cmp_gt, + .cmp_neq, + .error_set_decl, + .dbg_stmt, + .dbg_var_ptr, + .dbg_var_val, + .decl_ref, + .decl_val, + .load, + .div, + .elem_ptr_node, + .elem_ptr, + .elem_val_node, + .elem_val, + .elem_val_imm, + .ensure_result_used, + .ensure_result_non_error, + .ensure_err_union_payload_void, + .error_union_type, + .error_value, + .@"export", + .export_value, + .field_ptr, + .field_val, + .field_ptr_named, + .field_val_named, + .import, + .int, + .int_big, + .float, + .float128, + .int_type, + .is_non_null, + .is_non_null_ptr, + .is_non_err, + .is_non_err_ptr, + .ret_is_non_err, + .repeat, + .repeat_inline, + .for_len, + .merge_error_sets, + .ref, + .ret_node, + .ret_load, + .ret_implicit, + .ret_err_value, + .ret_err_value_code, + .ret_ptr, + .ret_type, + .ptr_type, + .slice_start, + .slice_end, + .slice_sentinel, + .slice_length, + .store_node, + .store_to_inferred_ptr, + .str, + .negate, + .negate_wrap, + .typeof, + .typeof_log2_int_type, + .@"unreachable", + .xor, + .optional_type, + .optional_payload_safe, + .optional_payload_unsafe, + .optional_payload_safe_ptr, + .optional_payload_unsafe_ptr, + .err_union_payload_unsafe, + .err_union_payload_unsafe_ptr, + .err_union_code, + .err_union_code_ptr, + .enum_literal, + .validate_deref, + .validate_destructure, + .field_type_ref, + .opt_eu_base_ptr_init, + .coerce_ptr_elem_ty, + .validate_ref_ty, + .struct_init_empty, + .struct_init_empty_result, + .struct_init_empty_ref_result, + .struct_init_anon, + .struct_init, + .struct_init_ref, + .validate_struct_init_ty, + .validate_struct_init_result_ty, + .validate_ptr_struct_init, + .struct_init_field_type, + .struct_init_field_ptr, + .array_init_anon, + .array_init, + .array_init_ref, + .validate_array_init_ty, + .validate_array_init_result_ty, + .validate_array_init_ref_ty, + .validate_ptr_array_init, + .array_init_elem_type, + .array_init_elem_ptr, + .union_init, + .type_info, + .size_of, + .bit_size_of, + .int_from_ptr, + .compile_error, + .set_eval_branch_quota, + .int_from_enum, + .align_of, + .int_from_bool, + .embed_file, + .error_name, + .panic, + .trap, + .set_runtime_safety, + .sqrt, + .sin, + .cos, + .tan, + .exp, + .exp2, + .log, + .log2, + .log10, + .abs, + .floor, + .ceil, + .trunc, + .round, + .tag_name, + .type_name, + .frame_type, + .frame_size, + .int_from_float, + .float_from_int, + .ptr_from_int, + .enum_from_int, + .float_cast, + .int_cast, + .ptr_cast, + .truncate, + .has_decl, + .has_field, + .clz, + .ctz, + .pop_count, + .byte_swap, + .bit_reverse, + .bit_offset_of, + .offset_of, + .splat, + .reduce, + .shuffle, + .atomic_load, + .atomic_rmw, + .atomic_store, + .mul_add, + .memcpy, + .memset, + .min, + .max, + .alloc, + .alloc_mut, + .alloc_comptime_mut, + .alloc_inferred, + .alloc_inferred_mut, + .alloc_inferred_comptime, + .alloc_inferred_comptime_mut, + .resolve_inferred_alloc, + .make_ptr_const, + .@"resume", + .@"await", + .save_err_ret_index, + .restore_err_ret_index_unconditional, + .restore_err_ret_index_fn_entry, + => return, + + .extended => { + const extended = datas[@intFromEnum(inst)].extended; + switch (extended.opcode) { + .value_placeholder => unreachable, + + // Once again, we start with the boring tags. + .variable, + .this, + .ret_addr, + .builtin_src, + .error_return_trace, + .frame, + .frame_address, + .alloc, + .builtin_extern, + .@"asm", + .asm_expr, + .compile_log, + .min_multi, + .max_multi, + .add_with_overflow, + .sub_with_overflow, + .mul_with_overflow, + .shl_with_overflow, + .c_undef, + .c_include, + .c_define, + .wasm_memory_size, + .wasm_memory_grow, + .prefetch, + .fence, + .set_float_mode, + .set_align_stack, + .set_cold, + .error_cast, + .await_nosuspend, + .breakpoint, + .disable_instrumentation, + .select, + .int_from_error, + .error_from_int, + .builtin_async_call, + .cmpxchg, + .c_va_arg, + .c_va_copy, + .c_va_end, + .c_va_start, + .ptr_cast_full, + .ptr_cast_no_dest, + .work_item_id, + .work_group_size, + .work_group_id, + .in_comptime, + .restore_err_ret_index, + .closure_get, + .field_parent_ptr, + .builtin_value, + => return, + + // `@TypeOf` has a body. + .typeof_peer => { + const extra = zir.extraData(Zir.Inst.TypeOfPeer, extended.operand); + const body = zir.bodySlice(extra.data.body_index, extra.data.body_len); + try zir.findDeclsBody(gpa, list, defers, body); + }, + + // Reifications and opaque declarations need tracking, but have no body. + .reify, .opaque_decl => return list.append(gpa, inst), + + // Struct declarations need tracking and have bodies. + .struct_decl => { + try list.append(gpa, inst); + + const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + const extra = zir.extraData(Zir.Inst.StructDecl, extended.operand); + var extra_index = extra.end; + const captures_len = if (small.has_captures_len) blk: { + const captures_len = zir.extra[extra_index]; + extra_index += 1; + break :blk captures_len; + } else 0; + const fields_len = if (small.has_fields_len) blk: { + const fields_len = zir.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + const decls_len = if (small.has_decls_len) blk: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + extra_index += captures_len; + if (small.has_backing_int) { + const backing_int_body_len = zir.extra[extra_index]; + extra_index += 1; + if (backing_int_body_len == 0) { + extra_index += 1; // backing_int_ref + } else { + const body = zir.bodySlice(extra_index, backing_int_body_len); + extra_index += backing_int_body_len; + try zir.findDeclsBody(gpa, list, defers, body); + } + } + extra_index += decls_len; + + // This ZIR is structured in a slightly awkward way, so we have to split up the iteration. + // `extra_index` iterates `flags` (bags of bits). + // `fields_extra_index` iterates `fields`. + // We accumulate the total length of bodies into `total_bodies_len`. This is sufficient because + // the bodies are packed together in `extra` and we only need to traverse their instructions (we + // don't really care about the structure). + + const bits_per_field = 4; + const fields_per_u32 = 32 / bits_per_field; + const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; + var cur_bit_bag: u32 = undefined; + + var fields_extra_index = extra_index + bit_bags_count; + var total_bodies_len: u32 = 0; + + for (0..fields_len) |field_i| { + if (field_i % fields_per_u32 == 0) { + cur_bit_bag = zir.extra[extra_index]; + extra_index += 1; + } + + const has_align = @as(u1, @truncate(cur_bit_bag)) != 0; + cur_bit_bag >>= 1; + const has_init = @as(u1, @truncate(cur_bit_bag)) != 0; + cur_bit_bag >>= 2; // also skip `is_comptime`; we don't care + const has_type_body = @as(u1, @truncate(cur_bit_bag)) != 0; + cur_bit_bag >>= 1; + + fields_extra_index += @intFromBool(!small.is_tuple); // field_name + fields_extra_index += 1; // doc_comment + + if (has_type_body) { + const field_type_body_len = zir.extra[fields_extra_index]; + total_bodies_len += field_type_body_len; + } + fields_extra_index += 1; // field_type or field_type_body_len + + if (has_align) { + const align_body_len = zir.extra[fields_extra_index]; + fields_extra_index += 1; + total_bodies_len += align_body_len; + } + + if (has_init) { + const init_body_len = zir.extra[fields_extra_index]; + fields_extra_index += 1; + total_bodies_len += init_body_len; + } + } + + // Now, `fields_extra_index` points to `bodies`. Let's treat this as one big body. + const merged_bodies = zir.bodySlice(fields_extra_index, total_bodies_len); + try zir.findDeclsBody(gpa, list, defers, merged_bodies); + }, + + // Union declarations need tracking and have a body. + .union_decl => { + try list.append(gpa, inst); + + const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small); + const extra = zir.extraData(Zir.Inst.UnionDecl, extended.operand); + var extra_index = extra.end; + extra_index += @intFromBool(small.has_tag_type); + const captures_len = if (small.has_captures_len) blk: { + const captures_len = zir.extra[extra_index]; + extra_index += 1; + break :blk captures_len; + } else 0; + const body_len = if (small.has_body_len) blk: { + const body_len = zir.extra[extra_index]; + extra_index += 1; + break :blk body_len; + } else 0; + extra_index += @intFromBool(small.has_fields_len); + const decls_len = if (small.has_decls_len) blk: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + extra_index += captures_len; + extra_index += decls_len; + const body = zir.bodySlice(extra_index, body_len); + try zir.findDeclsBody(gpa, list, defers, body); + }, + + // Enum declarations need tracking and have a body. + .enum_decl => { + try list.append(gpa, inst); + + const small: Zir.Inst.EnumDecl.Small = @bitCast(extended.small); + const extra = zir.extraData(Zir.Inst.EnumDecl, extended.operand); + var extra_index = extra.end; + extra_index += @intFromBool(small.has_tag_type); + const captures_len = if (small.has_captures_len) blk: { + const captures_len = zir.extra[extra_index]; + extra_index += 1; + break :blk captures_len; + } else 0; + const body_len = if (small.has_body_len) blk: { + const body_len = zir.extra[extra_index]; + extra_index += 1; + break :blk body_len; + } else 0; + extra_index += @intFromBool(small.has_fields_len); + const decls_len = if (small.has_decls_len) blk: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + extra_index += captures_len; + extra_index += decls_len; + const body = zir.bodySlice(extra_index, body_len); + try zir.findDeclsBody(gpa, list, defers, body); + }, + } + }, + // Functions instructions are interesting and have a body. .func, .func_inferred, => { - try list.append(inst); + try list.append(gpa, inst); const inst_data = datas[@intFromEnum(inst)].pl_node; const extra = zir.extraData(Inst.Func, inst_data.payload_index); @@ -3661,14 +4185,14 @@ fn findDeclsInner( else => { const body = zir.bodySlice(extra_index, extra.data.ret_body_len); extra_index += body.len; - try zir.findDeclsBody(list, body); + try zir.findDeclsBody(gpa, list, defers, body); }, } const body = zir.bodySlice(extra_index, extra.data.body_len); - return zir.findDeclsBody(list, body); + return zir.findDeclsBody(gpa, list, defers, body); }, .func_fancy => { - try list.append(inst); + try list.append(gpa, inst); const inst_data = datas[@intFromEnum(inst)].pl_node; const extra = zir.extraData(Inst.FuncFancy, inst_data.payload_index); @@ -3679,7 +4203,7 @@ fn findDeclsInner( const body_len = zir.extra[extra_index]; extra_index += 1; const body = zir.bodySlice(extra_index, body_len); - try zir.findDeclsBody(list, body); + try zir.findDeclsBody(gpa, list, defers, body); extra_index += body.len; } else if (extra.data.bits.has_align_ref) { extra_index += 1; @@ -3689,7 +4213,7 @@ fn findDeclsInner( const body_len = zir.extra[extra_index]; extra_index += 1; const body = zir.bodySlice(extra_index, body_len); - try zir.findDeclsBody(list, body); + try zir.findDeclsBody(gpa, list, defers, body); extra_index += body.len; } else if (extra.data.bits.has_addrspace_ref) { extra_index += 1; @@ -3699,7 +4223,7 @@ fn findDeclsInner( const body_len = zir.extra[extra_index]; extra_index += 1; const body = zir.bodySlice(extra_index, body_len); - try zir.findDeclsBody(list, body); + try zir.findDeclsBody(gpa, list, defers, body); extra_index += body.len; } else if (extra.data.bits.has_section_ref) { extra_index += 1; @@ -3709,7 +4233,7 @@ fn findDeclsInner( const body_len = zir.extra[extra_index]; extra_index += 1; const body = zir.bodySlice(extra_index, body_len); - try zir.findDeclsBody(list, body); + try zir.findDeclsBody(gpa, list, defers, body); extra_index += body.len; } else if (extra.data.bits.has_cc_ref) { extra_index += 1; @@ -3719,7 +4243,7 @@ fn findDeclsInner( const body_len = zir.extra[extra_index]; extra_index += 1; const body = zir.bodySlice(extra_index, body_len); - try zir.findDeclsBody(list, body); + try zir.findDeclsBody(gpa, list, defers, body); extra_index += body.len; } else if (extra.data.bits.has_ret_ty_ref) { extra_index += 1; @@ -3728,62 +4252,99 @@ fn findDeclsInner( extra_index += @intFromBool(extra.data.bits.has_any_noalias); const body = zir.bodySlice(extra_index, extra.data.body_len); - return zir.findDeclsBody(list, body); - }, - .extended => { - const extended = datas[@intFromEnum(inst)].extended; - switch (extended.opcode) { - - // Decl instructions are interesting but have no body. - // TODO yes they do have a body actually. recurse over them just like block instructions. - .struct_decl, - .union_decl, - .enum_decl, - .opaque_decl, - .reify, - => return list.append(inst), - - else => return, - } + return zir.findDeclsBody(gpa, list, defers, body); }, // Block instructions, recurse over the bodies. - .block, .block_comptime, .block_inline => { + .block, + .block_comptime, + .block_inline, + .c_import, + .typeof_builtin, + .loop, + => { const inst_data = datas[@intFromEnum(inst)].pl_node; const extra = zir.extraData(Inst.Block, inst_data.payload_index); const body = zir.bodySlice(extra.end, extra.data.body_len); - return zir.findDeclsBody(list, body); + return zir.findDeclsBody(gpa, list, defers, body); }, .condbr, .condbr_inline => { const inst_data = datas[@intFromEnum(inst)].pl_node; const extra = zir.extraData(Inst.CondBr, inst_data.payload_index); const then_body = zir.bodySlice(extra.end, extra.data.then_body_len); const else_body = zir.bodySlice(extra.end + then_body.len, extra.data.else_body_len); - try zir.findDeclsBody(list, then_body); - try zir.findDeclsBody(list, else_body); + try zir.findDeclsBody(gpa, list, defers, then_body); + try zir.findDeclsBody(gpa, list, defers, else_body); }, .@"try", .try_ptr => { const inst_data = datas[@intFromEnum(inst)].pl_node; const extra = zir.extraData(Inst.Try, inst_data.payload_index); const body = zir.bodySlice(extra.end, extra.data.body_len); - try zir.findDeclsBody(list, body); + try zir.findDeclsBody(gpa, list, defers, body); }, - .switch_block => return findDeclsSwitch(zir, list, inst), + .switch_block, .switch_block_ref => return zir.findDeclsSwitch(gpa, list, defers, inst, .normal), + .switch_block_err_union => return zir.findDeclsSwitch(gpa, list, defers, inst, .err_union), .suspend_block => @panic("TODO iterate suspend block"), - else => return, // Regular instruction, not interesting. + .param, .param_comptime => { + const inst_data = datas[@intFromEnum(inst)].pl_tok; + const extra = zir.extraData(Inst.Param, inst_data.payload_index); + const body = zir.bodySlice(extra.end, extra.data.body_len); + try zir.findDeclsBody(gpa, list, defers, body); + }, + + inline .call, .field_call => |tag| { + const inst_data = datas[@intFromEnum(inst)].pl_node; + const extra = zir.extraData(switch (tag) { + .call => Inst.Call, + .field_call => Inst.FieldCall, + else => unreachable, + }, inst_data.payload_index); + // It's easiest to just combine all the arg bodies into one body, like we do above for `struct_decl`. + const args_len = extra.data.flags.args_len; + if (args_len > 0) { + const first_arg_start_off = args_len; + const final_arg_end_off = zir.extra[extra.end + args_len - 1]; + const args_body = zir.bodySlice(extra.end + first_arg_start_off, final_arg_end_off - first_arg_start_off); + try zir.findDeclsBody(gpa, list, defers, args_body); + } + }, + .@"defer" => { + const inst_data = datas[@intFromEnum(inst)].@"defer"; + const gop = try defers.getOrPut(gpa, inst_data.index); + if (!gop.found_existing) { + const body = zir.bodySlice(inst_data.index, inst_data.len); + try zir.findDeclsBody(gpa, list, defers, body); + } + }, + .defer_err_code => { + const inst_data = datas[@intFromEnum(inst)].defer_err_code; + const extra = zir.extraData(Inst.DeferErrCode, inst_data.payload_index).data; + const gop = try defers.getOrPut(gpa, extra.index); + if (!gop.found_existing) { + const body = zir.bodySlice(extra.index, extra.len); + try zir.findDeclsBody(gpa, list, defers, body); + } + }, } } fn findDeclsSwitch( zir: Zir, - list: *std.ArrayList(Inst.Index), + gpa: Allocator, + list: *std.ArrayListUnmanaged(Inst.Index), + defers: *std.AutoHashMapUnmanaged(u32, void), inst: Inst.Index, + /// Distinguishes between `switch_block[_ref]` and `switch_block_err_union`. + comptime kind: enum { normal, err_union }, ) Allocator.Error!void { const inst_data = zir.instructions.items(.data)[@intFromEnum(inst)].pl_node; - const extra = zir.extraData(Inst.SwitchBlock, inst_data.payload_index); + const extra = zir.extraData(switch (kind) { + .normal => Inst.SwitchBlock, + .err_union => Inst.SwitchBlockErrUnion, + }, inst_data.payload_index); var extra_index: usize = extra.end; @@ -3793,18 +4354,35 @@ fn findDeclsSwitch( break :blk multi_cases_len; } else 0; - if (extra.data.bits.any_has_tag_capture) { + if (switch (kind) { + .normal => extra.data.bits.any_has_tag_capture, + .err_union => extra.data.bits.any_uses_err_capture, + }) { extra_index += 1; } - const special_prong = extra.data.bits.specialProng(); - if (special_prong != .none) { + const has_special = switch (kind) { + .normal => extra.data.bits.specialProng() != .none, + .err_union => has_special: { + // Handle `non_err_body` first. + const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); + extra_index += 1; + const body = zir.bodySlice(extra_index, prong_info.body_len); + extra_index += body.len; + + try zir.findDeclsBody(gpa, list, defers, body); + + break :has_special extra.data.bits.has_else; + }, + }; + + if (has_special) { const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); extra_index += 1; const body = zir.bodySlice(extra_index, prong_info.body_len); extra_index += body.len; - try zir.findDeclsBody(list, body); + try zir.findDeclsBody(gpa, list, defers, body); } { @@ -3816,7 +4394,7 @@ fn findDeclsSwitch( const body = zir.bodySlice(extra_index, prong_info.body_len); extra_index += body.len; - try zir.findDeclsBody(list, body); + try zir.findDeclsBody(gpa, list, defers, body); } } { @@ -3833,18 +4411,20 @@ fn findDeclsSwitch( const body = zir.bodySlice(extra_index, prong_info.body_len); extra_index += body.len; - try zir.findDeclsBody(list, body); + try zir.findDeclsBody(gpa, list, defers, body); } } } fn findDeclsBody( zir: Zir, - list: *std.ArrayList(Inst.Index), + gpa: Allocator, + list: *std.ArrayListUnmanaged(Inst.Index), + defers: *std.AutoHashMapUnmanaged(u32, void), body: []const Inst.Index, ) Allocator.Error!void { for (body) |member| { - try zir.findDeclsInner(list, member); + try zir.findDeclsInner(gpa, list, defers, member); } } @@ -4042,7 +4622,7 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash { return null; } const extra_index = extra.end + - 1 + + extra.data.ret_body_len + extra.data.body_len + @typeInfo(Inst.Func.SrcLocs).Struct.fields.len; return @bitCast([4]u32{ diff --git a/zig/lib/std/zig/system.zig b/zig/lib/std/zig/system.zig index fe3394b615..92058fbf3b 100644 --- a/zig/lib/std/zig/system.zig +++ b/zig/lib/std/zig/system.zig @@ -86,21 +86,37 @@ pub fn getExternalExecutor( .arm => Executor{ .qemu = "qemu-arm" }, .armeb => Executor{ .qemu = "qemu-armeb" }, .hexagon => Executor{ .qemu = "qemu-hexagon" }, - .x86 => Executor{ .qemu = "qemu-i386" }, .m68k => Executor{ .qemu = "qemu-m68k" }, .mips => Executor{ .qemu = "qemu-mips" }, .mipsel => Executor{ .qemu = "qemu-mipsel" }, - .mips64 => Executor{ .qemu = "qemu-mips64" }, - .mips64el => Executor{ .qemu = "qemu-mips64el" }, + .mips64 => Executor{ + .qemu = if (candidate.abi == .gnuabin32) + "qemu-mipsn32" + else + "qemu-mips64", + }, + .mips64el => Executor{ + .qemu = if (candidate.abi == .gnuabin32) + "qemu-mipsn32el" + else + "qemu-mips64el", + }, .powerpc => Executor{ .qemu = "qemu-ppc" }, .powerpc64 => Executor{ .qemu = "qemu-ppc64" }, .powerpc64le => Executor{ .qemu = "qemu-ppc64le" }, .riscv32 => Executor{ .qemu = "qemu-riscv32" }, .riscv64 => Executor{ .qemu = "qemu-riscv64" }, .s390x => Executor{ .qemu = "qemu-s390x" }, - .sparc => Executor{ .qemu = "qemu-sparc" }, + .sparc => Executor{ + .qemu = if (std.Target.sparc.featureSetHas(candidate.cpu.features, .v9)) + "qemu-sparc32plus" + else + "qemu-sparc", + }, .sparc64 => Executor{ .qemu = "qemu-sparc64" }, + .x86 => Executor{ .qemu = "qemu-i386" }, .x86_64 => Executor{ .qemu = "qemu-x86_64" }, + .xtensa => Executor{ .qemu = "qemu-xtensa" }, else => return bad_result, }; } diff --git a/zig/lib/std/zig/system/darwin/macos.zig b/zig/lib/std/zig/system/darwin/macos.zig index 0bc08b319c..c0a9aa47d7 100644 --- a/zig/lib/std/zig/system/darwin/macos.zig +++ b/zig/lib/std/zig/system/darwin/macos.zig @@ -419,6 +419,11 @@ pub fn detectNativeCpuAndFeatures() ?Target.Cpu { .ARM_TYPHOON => &Target.aarch64.cpu.apple_a8, .ARM_CYCLONE => &Target.aarch64.cpu.cyclone, else => return null, + .ARM_COLL => &Target.aarch64.cpu.apple_a17, + .ARM_IBIZA => &Target.aarch64.cpu.apple_m3, // base + .ARM_LOBOS => &Target.aarch64.cpu.apple_m3, // pro + .ARM_PALMA => &Target.aarch64.cpu.apple_m3, // max + // .ARM_DONAN => &Target.aarch64.cpu.apple_m4, // decl not available until llvm 19 }; return Target.Cpu{ diff --git a/zig/src/Air.zig b/zig/src/Air.zig index 2144dd3d61..5c559a4088 100644 --- a/zig/src/Air.zig +++ b/zig/src/Air.zig @@ -456,6 +456,8 @@ pub const Inst = struct { /// Same as `dbg_var_ptr` except the local is a const, not a var, and the /// operand is the local's value. dbg_var_val, + /// Same as `dbg_var_val` except the local is an inline function argument. + dbg_arg_inline, /// ?T => bool /// Result type is always bool. /// Uses the `un_op` field. @@ -938,17 +940,6 @@ pub const Inst = struct { null_type = @intFromEnum(InternPool.Index.null_type), undefined_type = @intFromEnum(InternPool.Index.undefined_type), enum_literal_type = @intFromEnum(InternPool.Index.enum_literal_type), - atomic_order_type = @intFromEnum(InternPool.Index.atomic_order_type), - atomic_rmw_op_type = @intFromEnum(InternPool.Index.atomic_rmw_op_type), - calling_convention_type = @intFromEnum(InternPool.Index.calling_convention_type), - address_space_type = @intFromEnum(InternPool.Index.address_space_type), - float_mode_type = @intFromEnum(InternPool.Index.float_mode_type), - reduce_op_type = @intFromEnum(InternPool.Index.reduce_op_type), - call_modifier_type = @intFromEnum(InternPool.Index.call_modifier_type), - prefetch_options_type = @intFromEnum(InternPool.Index.prefetch_options_type), - export_options_type = @intFromEnum(InternPool.Index.export_options_type), - extern_options_type = @intFromEnum(InternPool.Index.extern_options_type), - type_info_type = @intFromEnum(InternPool.Index.type_info_type), manyptr_u8_type = @intFromEnum(InternPool.Index.manyptr_u8_type), manyptr_const_u8_type = @intFromEnum(InternPool.Index.manyptr_const_u8_type), manyptr_const_u8_sentinel_0_type = @intFromEnum(InternPool.Index.manyptr_const_u8_sentinel_0_type), @@ -969,8 +960,6 @@ pub const Inst = struct { one_u8 = @intFromEnum(InternPool.Index.one_u8), four_u8 = @intFromEnum(InternPool.Index.four_u8), negative_one = @intFromEnum(InternPool.Index.negative_one), - calling_convention_c = @intFromEnum(InternPool.Index.calling_convention_c), - calling_convention_inline = @intFromEnum(InternPool.Index.calling_convention_inline), void_value = @intFromEnum(InternPool.Index.void_value), unreachable_value = @intFromEnum(InternPool.Index.unreachable_value), null_value = @intFromEnum(InternPool.Index.null_value), @@ -1035,10 +1024,7 @@ pub const Inst = struct { ty: Ref, /// Index into `extra` of a null-terminated string representing the parameter name. /// This is `.none` if debug info is stripped. - name: enum(u32) { - none = std.math.maxInt(u32), - _, - }, + name: NullTerminatedString, }, ty_op: struct { ty: Ref, @@ -1453,6 +1439,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool) .dbg_stmt, .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, .store, .store_safe, .fence, @@ -1575,14 +1562,16 @@ pub fn value(air: Air, inst: Inst.Ref, pt: Zcu.PerThread) !?Value { return air.typeOfIndex(index, &pt.zcu.intern_pool).onePossibleValue(pt); } -pub fn nullTerminatedString(air: Air, index: usize) [:0]const u8 { - const bytes = std.mem.sliceAsBytes(air.extra[index..]); - var end: usize = 0; - while (bytes[end] != 0) { - end += 1; +pub const NullTerminatedString = enum(u32) { + none = std.math.maxInt(u32), + _, + + pub fn toSlice(nts: NullTerminatedString, air: Air) [:0]const u8 { + if (nts == .none) return ""; + const bytes = std.mem.sliceAsBytes(air.extra[@intFromEnum(nts)..]); + return bytes[0..std.mem.indexOfScalar(u8, bytes, 0).? :0]; } - return bytes[0..end :0]; -} +}; /// Returns whether the given instruction must always be lowered, for instance /// because it can cause side effects. If an instruction does not need to be @@ -1609,6 +1598,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { .dbg_inline_block, .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, .ret, .ret_safe, .ret_load, diff --git a/zig/src/Air/types_resolved.zig b/zig/src/Air/types_resolved.zig index 77c8344a86..4b92a3a94f 100644 --- a/zig/src/Air/types_resolved.zig +++ b/zig/src/Air/types_resolved.zig @@ -339,6 +339,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, => { if (!checkRef(data.pl_op.operand, zcu)) return false; }, diff --git a/zig/src/Builtin.zig b/zig/src/Builtin.zig index 6e573d843f..58abcb7a4b 100644 --- a/zig/src/Builtin.zig +++ b/zig/src/Builtin.zig @@ -218,7 +218,7 @@ pub fn append(opts: @This(), buffer: *std.ArrayList(u8)) Allocator.Error!void { if (opts.is_test) { try buffer.appendSlice( - \\pub var test_functions: []const std.builtin.TestFn = undefined; // overwritten later + \\pub var test_functions: []const std.builtin.TestFn = &.{}; // overwritten later \\ ); } diff --git a/zig/src/Compilation.zig b/zig/src/Compilation.zig index d7dabd5164..dc7d0ba925 100644 --- a/zig/src/Compilation.zig +++ b/zig/src/Compilation.zig @@ -39,6 +39,8 @@ const Air = @import("Air.zig"); const Builtin = @import("Builtin.zig"); const LlvmObject = @import("codegen/llvm.zig").Object; const dev = @import("dev.zig"); +pub const Directory = Cache.Directory; +const Path = Cache.Path; pub const Config = @import("Compilation/Config.zig"); @@ -70,9 +72,9 @@ bin_file: ?*link.File, /// The root path for the dynamic linker and system libraries (as well as frameworks on Darwin) sysroot: ?[]const u8, /// This is `null` when not building a Windows DLL, or when `-fno-emit-implib` is used. -implib_emit: ?Emit, +implib_emit: ?Path, /// This is non-null when `-femit-docs` is provided. -docs_emit: ?Emit, +docs_emit: ?Path, root_name: [:0]const u8, include_compiler_rt: bool, objects: []Compilation.LinkObject, @@ -269,27 +271,9 @@ llvm_opt_bisect_limit: c_int, file_system_inputs: ?*std.ArrayListUnmanaged(u8), -pub const Emit = struct { - /// Where the output will go. - directory: Directory, - /// Path to the output file, relative to `directory`. - sub_path: []const u8, - - /// Returns the full path to `basename` if it were in the same directory as the - /// `Emit` sub_path. - pub fn basenamePath(emit: Emit, arena: Allocator, basename: []const u8) ![:0]const u8 { - const full_path = if (emit.directory.path) |p| - try std.fs.path.join(arena, &[_][]const u8{ p, emit.sub_path }) - else - emit.sub_path; - - if (std.fs.path.dirname(full_path)) |dirname| { - return try std.fs.path.joinZ(arena, &.{ dirname, basename }); - } else { - return try arena.dupeZ(u8, basename); - } - } -}; +/// This is the digest of the cache for the current compilation. +/// This digest will be known after update() is called. +digest: ?[Cache.bin_digest_len]u8 = null, pub const default_stack_protector_buffer_size = target_util.default_stack_protector_buffer_size; pub const SemaError = Zcu.SemaError; @@ -363,6 +347,7 @@ const Job = union(enum) { /// It must be deinited when the job is processed. air: Air, }, + codegen_type: InternPool.Index, /// The `Cau` must be semantically analyzed (and possibly export itself). /// This may be its first time being analyzed, or it may be outdated. analyze_cau: InternPool.Cau.Index, @@ -423,6 +408,7 @@ const CodegenJob = union(enum) { /// It must be deinited when the job is processed. air: Air, }, + type: InternPool.Index, }; pub const CObject = struct { @@ -866,8 +852,6 @@ pub const LldError = struct { } }; -pub const Directory = Cache.Directory; - pub const EmitLoc = struct { /// If this is `null` it means the file will be output to the cache directory. /// When provided, both the open file handle and the path name must outlive the `Compilation`. @@ -1670,7 +1654,9 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil // In the case of incremental cache mode, this `artifact_directory` // is computed based on a hash of non-linker inputs, and it is where all // build artifacts are stored (even while in-progress). + comp.digest = hash.peekBin(); const digest = hash.final(); + const artifact_sub_dir = "o" ++ std.fs.path.sep_str ++ digest; var artifact_dir = try options.local_cache_directory.handle.makeOpenPath(artifact_sub_dir, .{}); errdefer artifact_dir.close(); @@ -1686,8 +1672,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil comp.cache_use = .{ .incremental = incremental }; if (options.emit_bin) |emit_bin| { - const emit: Emit = .{ - .directory = emit_bin.directory orelse artifact_directory, + const emit: Path = .{ + .root_dir = emit_bin.directory orelse artifact_directory, .sub_path = emit_bin.basename, }; comp.bin_file = try link.File.open(arena, comp, emit, lf_open_opts); @@ -1695,14 +1681,14 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil if (options.emit_implib) |emit_implib| { comp.implib_emit = .{ - .directory = emit_implib.directory orelse artifact_directory, + .root_dir = emit_implib.directory orelse artifact_directory, .sub_path = emit_implib.basename, }; } if (options.emit_docs) |emit_docs| { comp.docs_emit = .{ - .directory = emit_docs.directory orelse artifact_directory, + .root_dir = emit_docs.directory orelse artifact_directory, .sub_path = emit_docs.basename, }; } @@ -2119,9 +2105,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { comp.last_update_was_cache_hit = true; log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name}); - const digest = man.final(); + const bin_digest = man.finalBin(); + const hex_digest = Cache.binToHex(bin_digest); - comp.wholeCacheModeSetBinFilePath(whole, &digest); + comp.digest = bin_digest; + comp.wholeCacheModeSetBinFilePath(whole, &hex_digest); assert(whole.lock == null); whole.lock = man.toOwnedLock(); @@ -2153,21 +2141,21 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { if (whole.implib_sub_path) |sub_path| { comp.implib_emit = .{ - .directory = tmp_artifact_directory, + .root_dir = tmp_artifact_directory, .sub_path = std.fs.path.basename(sub_path), }; } if (whole.docs_sub_path) |sub_path| { comp.docs_emit = .{ - .directory = tmp_artifact_directory, + .root_dir = tmp_artifact_directory, .sub_path = std.fs.path.basename(sub_path), }; } if (whole.bin_sub_path) |sub_path| { - const emit: Emit = .{ - .directory = tmp_artifact_directory, + const emit: Path = .{ + .root_dir = tmp_artifact_directory, .sub_path = std.fs.path.basename(sub_path), }; comp.bin_file = try link.File.createEmpty(arena, comp, emit, whole.lf_open_opts); @@ -2262,13 +2250,19 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { } } + zcu.analysis_roots.clear(); + try comp.queueJob(.{ .analyze_mod = std_mod }); - if (comp.config.is_test) { + zcu.analysis_roots.appendAssumeCapacity(std_mod); + + if (comp.config.is_test and zcu.main_mod != std_mod) { try comp.queueJob(.{ .analyze_mod = zcu.main_mod }); + zcu.analysis_roots.appendAssumeCapacity(zcu.main_mod); } if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { try comp.queueJob(.{ .analyze_mod = compiler_rt_mod }); + zcu.analysis_roots.appendAssumeCapacity(compiler_rt_mod); } } @@ -2292,7 +2286,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { zcu.intern_pool.dumpGenericInstances(gpa); } - if (comp.config.is_test and comp.totalErrorCount() == 0) { + if (comp.config.is_test) { // The `test_functions` decl has been intentionally postponed until now, // at which point we must populate it with the list of test functions that // have been discovered and not filtered out. @@ -2302,7 +2296,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { try pt.processExports(); } - if (comp.totalErrorCount() != 0) { + if (try comp.totalErrorCount() != 0) { // Skip flushing and keep source files loaded for error reporting. comp.link_error_flags = .{}; return; @@ -2321,7 +2315,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { try man.populateOtherManifest(pwc.manifest, pwc.prefix_map); } - const digest = man.final(); + const bin_digest = man.finalBin(); + const hex_digest = Cache.binToHex(bin_digest); // Rename the temporary directory into place. // Close tmp dir and link.File to avoid open handle during rename. @@ -2333,7 +2328,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { const s = std.fs.path.sep_str; const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); - const o_sub_path = "o" ++ s ++ digest; + const o_sub_path = "o" ++ s ++ hex_digest; // Work around windows `AccessDenied` if any files within this // directory are open by closing and reopening the file handles. @@ -2368,14 +2363,15 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { }, ); }; - comp.wholeCacheModeSetBinFilePath(whole, &digest); + comp.digest = bin_digest; + comp.wholeCacheModeSetBinFilePath(whole, &hex_digest); // The linker flush functions need to know the final output path // for debug info purposes because executable debug info contains // references object file paths. if (comp.bin_file) |lf| { lf.emit = .{ - .directory = comp.local_cache_directory, + .root_dir = comp.local_cache_directory, .sub_path = whole.bin_sub_path.?, }; @@ -2385,8 +2381,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { } } - try flush(comp, arena, .main, main_progress_node); - if (comp.totalErrorCount() != 0) return; + try flush(comp, arena, .{ + .root_dir = comp.local_cache_directory, + .sub_path = o_sub_path, + }, .main, main_progress_node); // Failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { @@ -2401,9 +2399,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { assert(whole.lock == null); whole.lock = man.toOwnedLock(); }, - .incremental => { - try flush(comp, arena, .main, main_progress_node); - if (comp.totalErrorCount() != 0) return; + .incremental => |incremental| { + try flush(comp, arena, .{ + .root_dir = incremental.artifact_directory, + }, .main, main_progress_node); }, } } @@ -2432,7 +2431,13 @@ pub fn appendFileSystemInput( std.debug.panic("missing prefix directory: {}, {s}", .{ root, sub_file_path }); } -fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { +fn flush( + comp: *Compilation, + arena: Allocator, + default_artifact_directory: Path, + tid: Zcu.PerThread.Id, + prog_node: std.Progress.Node, +) !void { if (comp.bin_file) |lf| { // This is needed before reading the error flags. lf.flush(arena, tid, prog_node) catch |err| switch (err) { @@ -2446,17 +2451,7 @@ fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: try link.File.C.flushEmitH(zcu); if (zcu.llvm_object) |llvm_object| { - const default_emit = switch (comp.cache_use) { - .whole => |whole| .{ - .directory = whole.tmp_artifact_directory.?, - .sub_path = "dummy", - }, - .incremental => |incremental| .{ - .directory = incremental.artifact_directory, - .sub_path = "dummy", - }, - }; - try emitLlvmObject(comp, arena, default_emit, null, llvm_object, prog_node); + try emitLlvmObject(comp, arena, default_artifact_directory, null, llvm_object, prog_node); } } } @@ -2525,7 +2520,7 @@ fn wholeCacheModeSetBinFilePath( @memcpy(sub_path[digest_start..][0..digest.len], digest); comp.implib_emit = .{ - .directory = comp.local_cache_directory, + .root_dir = comp.local_cache_directory, .sub_path = sub_path, }; } @@ -2534,7 +2529,7 @@ fn wholeCacheModeSetBinFilePath( @memcpy(sub_path[digest_start..][0..digest.len], digest); comp.docs_emit = .{ - .directory = comp.local_cache_directory, + .root_dir = comp.local_cache_directory, .sub_path = sub_path, }; } @@ -2737,7 +2732,7 @@ fn emitOthers(comp: *Compilation) void { pub fn emitLlvmObject( comp: *Compilation, arena: Allocator, - default_emit: Emit, + default_artifact_directory: Path, bin_emit_loc: ?EmitLoc, llvm_object: LlvmObject.Ptr, prog_node: std.Progress.Node, @@ -2748,10 +2743,10 @@ pub fn emitLlvmObject( try llvm_object.emit(.{ .pre_ir_path = comp.verbose_llvm_ir, .pre_bc_path = comp.verbose_llvm_bc, - .bin_path = try resolveEmitLoc(arena, default_emit, bin_emit_loc), - .asm_path = try resolveEmitLoc(arena, default_emit, comp.emit_asm), - .post_ir_path = try resolveEmitLoc(arena, default_emit, comp.emit_llvm_ir), - .post_bc_path = try resolveEmitLoc(arena, default_emit, comp.emit_llvm_bc), + .bin_path = try resolveEmitLoc(arena, default_artifact_directory, bin_emit_loc), + .asm_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_asm), + .post_ir_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_ir), + .post_bc_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_bc), .is_debug = comp.root_mod.optimize_mode == .Debug, .is_small = comp.root_mod.optimize_mode == .ReleaseSmall, @@ -2764,14 +2759,14 @@ pub fn emitLlvmObject( fn resolveEmitLoc( arena: Allocator, - default_emit: Emit, + default_artifact_directory: Path, opt_loc: ?EmitLoc, ) Allocator.Error!?[*:0]const u8 { const loc = opt_loc orelse return null; const slice = if (loc.directory) |directory| try directory.joinZ(arena, &.{loc.basename}) else - try default_emit.basenamePath(arena, loc.basename); + try default_artifact_directory.joinStringZ(arena, loc.basename); return slice.ptr; } @@ -3002,14 +2997,26 @@ pub fn saveState(comp: *Compilation) !void { addBuf(&bufs, mem.sliceAsBytes(ip.free_dep_entries.items)); for (ip.locals, pt_headers.items) |*local, pt_header| { - addBuf(&bufs, mem.sliceAsBytes(local.shared.limbs.view().items(.@"0")[0..pt_header.intern_pool.limbs_len])); - addBuf(&bufs, mem.sliceAsBytes(local.shared.extra.view().items(.@"0")[0..pt_header.intern_pool.extra_len])); - addBuf(&bufs, mem.sliceAsBytes(local.shared.items.view().items(.data)[0..pt_header.intern_pool.items_len])); - addBuf(&bufs, mem.sliceAsBytes(local.shared.items.view().items(.tag)[0..pt_header.intern_pool.items_len])); - addBuf(&bufs, local.shared.strings.view().items(.@"0")[0..pt_header.intern_pool.string_bytes_len]); - addBuf(&bufs, mem.sliceAsBytes(local.shared.tracked_insts.view().items(.@"0")[0..pt_header.intern_pool.tracked_insts_len])); - addBuf(&bufs, mem.sliceAsBytes(local.shared.files.view().items(.bin_digest)[0..pt_header.intern_pool.files_len])); - addBuf(&bufs, mem.sliceAsBytes(local.shared.files.view().items(.root_type)[0..pt_header.intern_pool.files_len])); + if (pt_header.intern_pool.limbs_len > 0) { + addBuf(&bufs, mem.sliceAsBytes(local.shared.limbs.view().items(.@"0")[0..pt_header.intern_pool.limbs_len])); + } + if (pt_header.intern_pool.extra_len > 0) { + addBuf(&bufs, mem.sliceAsBytes(local.shared.extra.view().items(.@"0")[0..pt_header.intern_pool.extra_len])); + } + if (pt_header.intern_pool.items_len > 0) { + addBuf(&bufs, mem.sliceAsBytes(local.shared.items.view().items(.data)[0..pt_header.intern_pool.items_len])); + addBuf(&bufs, mem.sliceAsBytes(local.shared.items.view().items(.tag)[0..pt_header.intern_pool.items_len])); + } + if (pt_header.intern_pool.string_bytes_len > 0) { + addBuf(&bufs, local.shared.strings.view().items(.@"0")[0..pt_header.intern_pool.string_bytes_len]); + } + if (pt_header.intern_pool.tracked_insts_len > 0) { + addBuf(&bufs, mem.sliceAsBytes(local.shared.tracked_insts.view().items(.@"0")[0..pt_header.intern_pool.tracked_insts_len])); + } + if (pt_header.intern_pool.files_len > 0) { + addBuf(&bufs, mem.sliceAsBytes(local.shared.files.view().items(.bin_digest)[0..pt_header.intern_pool.files_len])); + addBuf(&bufs, mem.sliceAsBytes(local.shared.files.view().items(.root_type)[0..pt_header.intern_pool.files_len])); + } } //// TODO: compilation errors @@ -3027,7 +3034,7 @@ pub fn saveState(comp: *Compilation) !void { // Using an atomic file prevents a crash or power failure from corrupting // the previous incremental compilation state. - var af = try lf.emit.directory.handle.atomicFile(basename, .{}); + var af = try lf.emit.root_dir.handle.atomicFile(basename, .{}); defer af.deinit(); try af.file.pwritevAll(bufs.items, 0); try af.finish(); @@ -3039,78 +3046,6 @@ fn addBuf(list: *std.ArrayList(std.posix.iovec_const), buf: []const u8) void { list.appendAssumeCapacity(.{ .base = buf.ptr, .len = buf.len }); } -/// This function is temporally single-threaded. -pub fn totalErrorCount(comp: *Compilation) u32 { - var total: usize = - comp.misc_failures.count() + - @intFromBool(comp.alloc_failure_occurred) + - comp.lld_errors.items.len; - - for (comp.failed_c_objects.values()) |bundle| { - total += bundle.diags.len; - } - - for (comp.failed_win32_resources.values()) |errs| { - total += errs.errorMessageCount(); - } - - if (comp.module) |zcu| { - const ip = &zcu.intern_pool; - - total += zcu.failed_exports.count(); - total += zcu.failed_embed_files.count(); - - for (zcu.failed_files.keys(), zcu.failed_files.values()) |file, error_msg| { - if (error_msg) |_| { - total += 1; - } else { - assert(file.zir_loaded); - const payload_index = file.zir.extra[@intFromEnum(Zir.ExtraIndex.compile_errors)]; - assert(payload_index != 0); - const header = file.zir.extraData(Zir.Inst.CompileErrors, payload_index); - total += header.data.items_len; - } - } - - // Skip errors for Decls within files that failed parsing. - // When a parse error is introduced, we keep all the semantic analysis for - // the previous parse success, including compile errors, but we cannot - // emit them until the file succeeds parsing. - for (zcu.failed_analysis.keys()) |anal_unit| { - const file_index = switch (anal_unit.unwrap()) { - .cau => |cau| zcu.namespacePtr(ip.getCau(cau).namespace).file_scope, - .func => |ip_index| zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip).file, - }; - if (zcu.fileByIndex(file_index).okToReportErrors()) { - total += 1; - if (zcu.cimport_errors.get(anal_unit)) |errors| { - total += errors.errorMessageCount(); - } - } - } - - if (zcu.intern_pool.global_error_set.getNamesFromMainThread().len > zcu.error_limit) { - total += 1; - } - } - - // The "no entry point found" error only counts if there are no semantic analysis errors. - if (total == 0) { - total += @intFromBool(comp.link_error_flags.no_entry_point_found); - } - total += @intFromBool(comp.link_error_flags.missing_libc); - total += comp.link_errors.items.len; - - // Compile log errors only count if there are no other errors. - if (total == 0) { - if (comp.module) |zcu| { - total += @intFromBool(zcu.compile_log_sources.count() != 0); - } - } - - return @as(u32, @intCast(total)); -} - /// This function is temporally single-threaded. pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { const gpa = comp.gpa; @@ -3153,12 +3088,13 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { .msg = try bundle.addString("memory allocation failure"), }); } + + var all_references: ?std.AutoHashMapUnmanaged(InternPool.AnalUnit, ?Zcu.ResolvedReference) = null; + defer if (all_references) |*a| a.deinit(gpa); + if (comp.module) |zcu| { const ip = &zcu.intern_pool; - var all_references = try zcu.resolveReferences(); - defer all_references.deinit(gpa); - for (zcu.failed_files.keys(), zcu.failed_files.values()) |file, error_msg| { if (error_msg) |msg| { try addModuleErrorMsg(zcu, &bundle, msg.*, &all_references); @@ -3184,8 +3120,14 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool { if (ctx.err.*) |_| return lhs_index < rhs_index; const errors = ctx.zcu.failed_analysis.values(); - const lhs_src_loc = errors[lhs_index].src_loc.upgrade(ctx.zcu); - const rhs_src_loc = errors[rhs_index].src_loc.upgrade(ctx.zcu); + const lhs_src_loc = errors[lhs_index].src_loc.upgradeOrLost(ctx.zcu) orelse { + // LHS source location lost, so should never be referenced. Just sort it to the end. + return false; + }; + const rhs_src_loc = errors[rhs_index].src_loc.upgradeOrLost(ctx.zcu) orelse { + // RHS source location lost, so should never be referenced. Just sort it to the end. + return true; + }; return if (lhs_src_loc.file_scope != rhs_src_loc.file_scope) std.mem.order( u8, lhs_src_loc.file_scope.sub_file_path, @@ -3206,9 +3148,16 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { if (err) |e| return e; } for (zcu.failed_analysis.keys(), zcu.failed_analysis.values()) |anal_unit, error_msg| { + if (comp.incremental) { + if (all_references == null) { + all_references = try zcu.resolveReferences(); + } + if (!all_references.?.contains(anal_unit)) continue; + } + const file_index = switch (anal_unit.unwrap()) { .cau => |cau| zcu.namespacePtr(ip.getCau(cau).namespace).file_scope, - .func => |ip_index| zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip).file, + .func => |ip_index| (zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip) orelse continue).file, }; // Skip errors for AnalUnits within files that had a parse failure. @@ -3237,6 +3186,10 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { } } } + for (zcu.failed_codegen.keys(), zcu.failed_codegen.values()) |nav, error_msg| { + if (!zcu.navFileScope(nav).okToReportErrors()) continue; + try addModuleErrorMsg(zcu, &bundle, error_msg.*, &all_references); + } for (zcu.failed_exports.values()) |value| { try addModuleErrorMsg(zcu, &bundle, value.*, &all_references); } @@ -3295,9 +3248,6 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { if (comp.module) |zcu| { if (bundle.root_list.items.len == 0 and zcu.compile_log_sources.count() != 0) { - var all_references = try zcu.resolveReferences(); - defer all_references.deinit(gpa); - const values = zcu.compile_log_sources.values(); // First one will be the error; subsequent ones will be notes. const src_loc = values[0].src(); @@ -3319,12 +3269,30 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { } } - assert(comp.totalErrorCount() == bundle.root_list.items.len); + if (comp.module) |zcu| { + if (comp.incremental and bundle.root_list.items.len == 0) { + const should_have_error = for (zcu.transitive_failed_analysis.keys()) |failed_unit| { + if (all_references == null) { + all_references = try zcu.resolveReferences(); + } + if (all_references.?.contains(failed_unit)) break true; + } else false; + if (should_have_error) { + @panic("referenced transitive analysis errors, but none actually emitted"); + } + } + } const compile_log_text = if (comp.module) |m| m.compile_log_text.items else ""; return bundle.toOwnedBundle(compile_log_text); } +fn totalErrorCount(comp: *Compilation) !u32 { + var errors = try comp.getAllErrorsAlloc(); + defer errors.deinit(comp.gpa); + return errors.errorMessageCount(); +} + pub const ErrorNoteHashContext = struct { eb: *const ErrorBundle.Wip, @@ -3375,7 +3343,7 @@ pub fn addModuleErrorMsg( mod: *Zcu, eb: *ErrorBundle.Wip, module_err_msg: Zcu.ErrorMsg, - all_references: *const std.AutoHashMapUnmanaged(InternPool.AnalUnit, Zcu.ResolvedReference), + all_references: *?std.AutoHashMapUnmanaged(InternPool.AnalUnit, ?Zcu.ResolvedReference), ) !void { const gpa = eb.gpa; const ip = &mod.intern_pool; @@ -3399,13 +3367,18 @@ pub fn addModuleErrorMsg( defer ref_traces.deinit(gpa); if (module_err_msg.reference_trace_root.unwrap()) |rt_root| { + if (all_references.* == null) { + all_references.* = try mod.resolveReferences(); + } + var seen: std.AutoHashMapUnmanaged(InternPool.AnalUnit, void) = .{}; defer seen.deinit(gpa); const max_references = mod.comp.reference_trace orelse Sema.default_reference_trace_len; var referenced_by = rt_root; - while (all_references.get(referenced_by)) |ref| { + while (all_references.*.?.get(referenced_by)) |maybe_ref| { + const ref = maybe_ref orelse break; const gop = try seen.getOrPut(gpa, ref.referencer); if (gop.found_existing) break; if (ref_traces.items.len < max_references) { @@ -3414,6 +3387,7 @@ pub fn addModuleErrorMsg( const span = try src.span(gpa); const loc = std.zig.findLineColumn(source.bytes, span.main); const rt_file_path = try src.file_scope.fullPath(gpa); + defer gpa.free(rt_file_path); const name = switch (ref.referencer.unwrap()) { .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) { .nav => |nav| ip.getNav(nav).name.toSlice(ip), @@ -3528,6 +3502,8 @@ pub fn performAllTheWork( mod.sema_prog_node = std.Progress.Node.none; mod.codegen_prog_node.end(); mod.codegen_prog_node = std.Progress.Node.none; + + mod.generation += 1; }; try comp.performAllTheWorkInner(main_progress_node); if (!InternPool.single_threaded) if (comp.codegen_work.job_error) |job_error| return job_error; @@ -3599,10 +3575,9 @@ fn performAllTheWorkInner( // Pre-load these things from our single-threaded context since they // will be needed by the worker threads. const path_digest = zcu.filePathDigest(file_index); - const old_root_type = zcu.fileRootType(file_index); const file = zcu.fileByIndex(file_index); comp.thread_pool.spawnWgId(&astgen_wait_group, workerAstGenFile, .{ - comp, file, file_index, path_digest, old_root_type, zir_prog_node, &astgen_wait_group, .root, + comp, file, file_index, path_digest, zir_prog_node, &astgen_wait_group, .root, }); } } @@ -3640,11 +3615,15 @@ fn performAllTheWorkInner( } try reportMultiModuleErrors(pt); try zcu.flushRetryableFailures(); + zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0); } - if (!InternPool.single_threaded) comp.thread_pool.spawnWgId(&work_queue_wait_group, codegenThread, .{comp}); + if (!InternPool.single_threaded) { + comp.codegen_work.done = false; // may be `true` from a prior update + comp.thread_pool.spawnWgId(&work_queue_wait_group, codegenThread, .{comp}); + } defer if (!InternPool.single_threaded) { { comp.codegen_work.mutex.lock(); @@ -3705,6 +3684,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre .air = func.air, } }); }, + .codegen_type => |ty| try comp.queueCodegenJob(tid, .{ .type = ty }), .analyze_func => |func| { const named_frame = tracy.namedFrame("analyze_func"); defer named_frame.end(); @@ -3994,6 +3974,13 @@ fn processOneCodegenJob(tid: usize, comp: *Compilation, codegen_job: CodegenJob) // This call takes ownership of `func.air`. try pt.linkerUpdateFunc(func.func, func.air); }, + .type => |ty| { + const named_frame = tracy.namedFrame("codegen_type"); + defer named_frame.end(); + + const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) }; + try pt.linkerUpdateContainerType(ty); + }, } } @@ -4012,11 +3999,11 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { return comp.lockAndSetMiscFailure(.docs_copy, "no Zig code to document", .{}); const emit = comp.docs_emit.?; - var out_dir = emit.directory.handle.makeOpenPath(emit.sub_path, .{}) catch |err| { + var out_dir = emit.root_dir.handle.makeOpenPath(emit.sub_path, .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, "unable to create output directory '{}{s}': {s}", - .{ emit.directory, emit.sub_path, @errorName(err) }, + .{ emit.root_dir, emit.sub_path, @errorName(err) }, ); }; defer out_dir.close(); @@ -4036,7 +4023,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { return comp.lockAndSetMiscFailure( .docs_copy, "unable to create '{}{s}/sources.tar': {s}", - .{ emit.directory, emit.sub_path, @errorName(err) }, + .{ emit.root_dir, emit.sub_path, @errorName(err) }, ); }; defer tar_file.close(); @@ -4071,7 +4058,8 @@ fn docsCopyModule(comp: *Compilation, module: *Package.Module, name: []const u8, var walker = try mod_dir.walk(comp.gpa); defer walker.deinit(); - const padding_buffer = [1]u8{0} ** 512; + var archiver = std.tar.writer(tar_file.writer().any()); + archiver.prefix = name; while (try walker.next()) |entry| { switch (entry.kind) { @@ -4082,43 +4070,17 @@ fn docsCopyModule(comp: *Compilation, module: *Package.Module, name: []const u8, }, else => continue, } - var file = mod_dir.openFile(entry.path, .{}) catch |err| { return comp.lockAndSetMiscFailure(.docs_copy, "unable to open '{}{s}': {s}", .{ root, entry.path, @errorName(err), }); }; defer file.close(); - - const stat = file.stat() catch |err| { - return comp.lockAndSetMiscFailure(.docs_copy, "unable to stat '{}{s}': {s}", .{ + archiver.writeFile(entry.path, file) catch |err| { + return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive '{}{s}': {s}", .{ root, entry.path, @errorName(err), }); }; - - var file_header = std.tar.output.Header.init(); - file_header.typeflag = .regular; - try file_header.setPath(name, entry.path); - try file_header.setSize(stat.size); - try file_header.updateChecksum(); - - const header_bytes = std.mem.asBytes(&file_header); - const padding = p: { - const remainder: u16 = @intCast(stat.size % 512); - const n = if (remainder > 0) 512 - remainder else 0; - break :p padding_buffer[0..n]; - }; - - var header_and_trailer: [2]std.posix.iovec_const = .{ - .{ .base = header_bytes.ptr, .len = header_bytes.len }, - .{ .base = padding.ptr, .len = padding.len }, - }; - - try tar_file.writeFileAll(file, .{ - .in_len = stat.size, - .headers_and_trailers = &header_and_trailer, - .header_count = 1, - }); } } @@ -4260,11 +4222,11 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye try comp.updateSubCompilation(sub_compilation, .docs_wasm, prog_node); const emit = comp.docs_emit.?; - var out_dir = emit.directory.handle.makeOpenPath(emit.sub_path, .{}) catch |err| { + var out_dir = emit.root_dir.handle.makeOpenPath(emit.sub_path, .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, "unable to create output directory '{}{s}': {s}", - .{ emit.directory, emit.sub_path, @errorName(err) }, + .{ emit.root_dir, emit.sub_path, @errorName(err) }, ); }; defer out_dir.close(); @@ -4278,7 +4240,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{}{s}' to '{}{s}': {s}", .{ sub_compilation.local_cache_directory, sub_compilation.cache_use.whole.bin_sub_path.?, - emit.directory, + emit.root_dir, emit.sub_path, @errorName(err), }); @@ -4291,7 +4253,6 @@ fn workerAstGenFile( file: *Zcu.File, file_index: Zcu.File.Index, path_digest: Cache.BinDigest, - old_root_type: InternPool.Index, prog_node: std.Progress.Node, wg: *WaitGroup, src: Zcu.AstGenSrc, @@ -4300,7 +4261,7 @@ fn workerAstGenFile( defer child_prog_node.end(); const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) }; - pt.astGenFile(file, path_digest, old_root_type) catch |err| switch (err) { + pt.astGenFile(file, path_digest) catch |err| switch (err) { error.AnalysisFail => return, else => { file.status = .retryable_failure; @@ -4331,7 +4292,7 @@ fn workerAstGenFile( // `@import("builtin")` is handled specially. if (mem.eql(u8, import_path, "builtin")) continue; - const import_result, const imported_path_digest, const imported_root_type = blk: { + const import_result, const imported_path_digest = blk: { comp.mutex.lock(); defer comp.mutex.unlock(); @@ -4346,8 +4307,7 @@ fn workerAstGenFile( comp.appendFileSystemInput(fsi, res.file.mod.root, res.file.sub_file_path) catch continue; }; const imported_path_digest = pt.zcu.filePathDigest(res.file_index); - const imported_root_type = pt.zcu.fileRootType(res.file_index); - break :blk .{ res, imported_path_digest, imported_root_type }; + break :blk .{ res, imported_path_digest }; }; if (import_result.is_new) { log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{ @@ -4358,7 +4318,7 @@ fn workerAstGenFile( .import_tok = item.data.token, } }; comp.thread_pool.spawnWgId(wg, workerAstGenFile, .{ - comp, import_result.file, import_result.file_index, imported_path_digest, imported_root_type, prog_node, wg, sub_src, + comp, import_result.file, import_result.file_index, imported_path_digest, prog_node, wg, sub_src, }); } } @@ -4442,7 +4402,7 @@ pub fn obtainWin32ResourceCacheManifest(comp: *const Compilation) Cache.Manifest } pub const CImportResult = struct { - out_zig_path: []u8, + digest: [Cache.bin_digest_len]u8, cache_hit: bool, errors: std.zig.ErrorBundle, @@ -4452,8 +4412,6 @@ pub const CImportResult = struct { }; /// Caller owns returned memory. -/// This API is currently coupled pretty tightly to stage1's needs; it will need to be reworked -/// a bit when we want to start using it from self-hosted. pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module) !CImportResult { dev.check(.translate_c_command); @@ -4542,7 +4500,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module error.OutOfMemory => return error.OutOfMemory, error.SemanticAnalyzeFail => { return CImportResult{ - .out_zig_path = "", + .digest = undefined, .cache_hit = actual_hit, .errors = errors, }; @@ -4567,8 +4525,9 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module .incremental => {}, } - const digest = man.final(); - const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); + const bin_digest = man.finalBin(); + const hex_digest = Cache.binToHex(bin_digest); + const o_sub_path = "o" ++ std.fs.path.sep_str ++ hex_digest; var o_dir = try comp.local_cache_directory.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); @@ -4580,8 +4539,8 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module try out_zig_file.writeAll(formatted); - break :digest digest; - } else man.final(); + break :digest bin_digest; + } else man.finalBin(); if (man.have_exclusive_lock) { // Write the updated manifest. This is a no-op if the manifest is not dirty. Note that it is @@ -4593,14 +4552,8 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module }; } - const out_zig_path = try comp.local_cache_directory.join(comp.arena, &.{ - "o", &digest, cimport_zig_basename, - }); - if (comp.verbose_cimport) { - log.info("C import output: {s}", .{out_zig_path}); - } return CImportResult{ - .out_zig_path = out_zig_path, + .digest = digest, .cache_hit = actual_hit, .errors = std.zig.ErrorBundle.empty, }; @@ -4839,7 +4792,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr try argv.appendSlice(c_object.src.cache_exempt_flags); const out_obj_path = if (comp.bin_file) |lf| - try lf.emit.directory.join(arena, &.{lf.emit.sub_path}) + try lf.emit.root_dir.join(arena, &.{lf.emit.sub_path}) else "/dev/null"; @@ -6451,7 +6404,8 @@ fn buildOutputFromZig( try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node); - assert(out.* == null); + // Under incremental compilation, `out` may already be populated from a prior update. + assert(out.* == null or comp.incremental); out.* = try sub_compilation.toCrtFile(); } diff --git a/zig/src/InternPool.zig b/zig/src/InternPool.zig index 0816890068..83732a29f6 100644 --- a/zig/src/InternPool.zig +++ b/zig/src/InternPool.zig @@ -62,22 +62,60 @@ const want_multi_threaded = true; /// Whether a single-threaded intern pool impl is in use. pub const single_threaded = builtin.single_threaded or !want_multi_threaded; +/// A `TrackedInst.Index` provides a single, unchanging reference to a ZIR instruction across a whole +/// compilation. From this index, you can acquire a `TrackedInst`, which containss a reference to both +/// the file which the instruction lives in, and the instruction index itself, which is updated on +/// incremental updates by `Zcu.updateZirRefs`. pub const TrackedInst = extern struct { file: FileIndex, inst: Zir.Inst.Index, - comptime { - // The fields should be tightly packed. See also serialiation logic in `Compilation.saveState`. - assert(@sizeOf(@This()) == @sizeOf(FileIndex) + @sizeOf(Zir.Inst.Index)); - } + + /// It is possible on an incremental update that we "lose" a ZIR instruction: some tracked `%x` in + /// the old ZIR failed to map to any `%y` in the new ZIR. For this reason, we actually store values + /// of type `MaybeLost`, which uses `ZirIndex.lost` to represent this case. `Index.resolve` etc + /// return `null` when the `TrackedInst` being resolved has been lost. + pub const MaybeLost = extern struct { + file: FileIndex, + inst: ZirIndex, + pub const ZirIndex = enum(u32) { + /// Tracking failed for this ZIR instruction. Uses of it should fail. + lost = std.math.maxInt(u32), + _, + pub fn unwrap(inst: ZirIndex) ?Zir.Inst.Index { + return switch (inst) { + .lost => null, + _ => @enumFromInt(@intFromEnum(inst)), + }; + } + pub fn wrap(inst: Zir.Inst.Index) ZirIndex { + return @enumFromInt(@intFromEnum(inst)); + } + }; + comptime { + // The fields should be tightly packed. See also serialiation logic in `Compilation.saveState`. + assert(@sizeOf(@This()) == @sizeOf(FileIndex) + @sizeOf(ZirIndex)); + } + }; + pub const Index = enum(u32) { _, - pub fn resolveFull(tracked_inst_index: TrackedInst.Index, ip: *const InternPool) TrackedInst { + pub fn resolveFull(tracked_inst_index: TrackedInst.Index, ip: *const InternPool) ?TrackedInst { const tracked_inst_unwrapped = tracked_inst_index.unwrap(ip); const tracked_insts = ip.getLocalShared(tracked_inst_unwrapped.tid).tracked_insts.acquire(); - return tracked_insts.view().items(.@"0")[tracked_inst_unwrapped.index]; + const maybe_lost = tracked_insts.view().items(.@"0")[tracked_inst_unwrapped.index]; + return .{ + .file = maybe_lost.file, + .inst = maybe_lost.inst.unwrap() orelse return null, + }; } - pub fn resolve(i: TrackedInst.Index, ip: *const InternPool) Zir.Inst.Index { - return i.resolveFull(ip).inst; + pub fn resolveFile(tracked_inst_index: TrackedInst.Index, ip: *const InternPool) FileIndex { + const tracked_inst_unwrapped = tracked_inst_index.unwrap(ip); + const tracked_insts = ip.getLocalShared(tracked_inst_unwrapped.tid).tracked_insts.acquire(); + const maybe_lost = tracked_insts.view().items(.@"0")[tracked_inst_unwrapped.index]; + return maybe_lost.file; + } + pub fn resolve(i: TrackedInst.Index, ip: *const InternPool) ?Zir.Inst.Index { + return (i.resolveFull(ip) orelse return null).inst; } pub fn toOptional(i: TrackedInst.Index) Optional { @@ -120,7 +158,11 @@ pub fn trackZir( tid: Zcu.PerThread.Id, key: TrackedInst, ) Allocator.Error!TrackedInst.Index { - const full_hash = Hash.hash(0, std.mem.asBytes(&key)); + const maybe_lost_key: TrackedInst.MaybeLost = .{ + .file = key.file, + .inst = TrackedInst.MaybeLost.ZirIndex.wrap(key.inst), + }; + const full_hash = Hash.hash(0, std.mem.asBytes(&maybe_lost_key)); const hash: u32 = @truncate(full_hash >> 32); const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))]; var map = shard.shared.tracked_inst_map.acquire(); @@ -132,12 +174,11 @@ pub fn trackZir( const entry = &map.entries[map_index]; const index = entry.acquire().unwrap() orelse break; if (entry.hash != hash) continue; - if (std.meta.eql(index.resolveFull(ip), key)) return index; + if (std.meta.eql(index.resolveFull(ip) orelse continue, key)) return index; } shard.mutate.tracked_inst_map.mutex.lock(); defer shard.mutate.tracked_inst_map.mutex.unlock(); if (map.entries != shard.shared.tracked_inst_map.entries) { - shard.mutate.tracked_inst_map.len += 1; map = shard.shared.tracked_inst_map; map_mask = map.header().mask(); map_index = hash; @@ -147,7 +188,7 @@ pub fn trackZir( const entry = &map.entries[map_index]; const index = entry.acquire().unwrap() orelse break; if (entry.hash != hash) continue; - if (std.meta.eql(index.resolveFull(ip), key)) return index; + if (std.meta.eql(index.resolveFull(ip) orelse continue, key)) return index; } defer shard.mutate.tracked_inst_map.len += 1; const local = ip.getLocal(tid); @@ -161,7 +202,7 @@ pub fn trackZir( .tid = tid, .index = list.mutate.len, }).wrap(ip); - list.appendAssumeCapacity(.{key}); + list.appendAssumeCapacity(.{maybe_lost_key}); entry.release(index.toOptional()); return index; } @@ -205,12 +246,94 @@ pub fn trackZir( .tid = tid, .index = list.mutate.len, }).wrap(ip); - list.appendAssumeCapacity(.{key}); + list.appendAssumeCapacity(.{maybe_lost_key}); map.entries[map_index] = .{ .value = index.toOptional(), .hash = hash }; shard.shared.tracked_inst_map.release(new_map); return index; } +/// At the start of an incremental update, we update every entry in `tracked_insts` to include +/// the new ZIR index. Once this is done, we must update the hashmap metadata so that lookups +/// return correct entries where they already exist. +pub fn rehashTrackedInsts( + ip: *InternPool, + gpa: Allocator, + tid: Zcu.PerThread.Id, +) Allocator.Error!void { + assert(tid == .main); // we shouldn't have any other threads active right now + + // TODO: this function doesn't handle OOM well. What should it do? + + // We don't lock anything, as this function assumes that no other thread is + // accessing `tracked_insts`. This is necessary because we're going to be + // iterating the `TrackedInst`s in each `Local`, so we have to know that + // none will be added as we work. + + // Figure out how big each shard need to be and store it in its mutate `len`. + for (ip.shards) |*shard| shard.mutate.tracked_inst_map.len = 0; + for (ip.locals) |*local| { + // `getMutableTrackedInsts` is okay only because no other thread is currently active. + // We need the `mutate` for the len. + for (local.getMutableTrackedInsts(gpa).viewAllowEmpty().items(.@"0")) |tracked_inst| { + if (tracked_inst.inst == .lost) continue; // we can ignore this one! + const full_hash = Hash.hash(0, std.mem.asBytes(&tracked_inst)); + const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))]; + shard.mutate.tracked_inst_map.len += 1; + } + } + + const Map = Shard.Map(TrackedInst.Index.Optional); + + const arena_state = &ip.getLocal(tid).mutate.arena; + + // We know how big each shard must be, so ensure we have the capacity we need. + for (ip.shards) |*shard| { + const want_capacity = std.math.ceilPowerOfTwo(u32, shard.mutate.tracked_inst_map.len * 5 / 3) catch unreachable; + const have_capacity = shard.shared.tracked_inst_map.header().capacity; // no acquire because we hold the mutex + if (have_capacity >= want_capacity) { + @memset(shard.shared.tracked_inst_map.entries[0..have_capacity], .{ .value = .none, .hash = undefined }); + continue; + } + var arena = arena_state.promote(gpa); + defer arena_state.* = arena.state; + const new_map_buf = try arena.allocator().alignedAlloc( + u8, + Map.alignment, + Map.entries_offset + want_capacity * @sizeOf(Map.Entry), + ); + const new_map: Map = .{ .entries = @ptrCast(new_map_buf[Map.entries_offset..].ptr) }; + new_map.header().* = .{ .capacity = want_capacity }; + @memset(new_map.entries[0..want_capacity], .{ .value = .none, .hash = undefined }); + shard.shared.tracked_inst_map.release(new_map); + } + + // Now, actually insert the items. + for (ip.locals, 0..) |*local, local_tid| { + // `getMutableTrackedInsts` is okay only because no other thread is currently active. + // We need the `mutate` for the len. + for (local.getMutableTrackedInsts(gpa).viewAllowEmpty().items(.@"0"), 0..) |tracked_inst, local_inst_index| { + if (tracked_inst.inst == .lost) continue; // we can ignore this one! + const full_hash = Hash.hash(0, std.mem.asBytes(&tracked_inst)); + const hash: u32 = @truncate(full_hash >> 32); + const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))]; + const map = shard.shared.tracked_inst_map; // no acquire because we hold the mutex + const map_mask = map.header().mask(); + var map_index = hash; + const entry = while (true) : (map_index += 1) { + map_index &= map_mask; + const entry = &map.entries[map_index]; + if (entry.acquire() == .none) break entry; + }; + const index = TrackedInst.Index.Unwrapped.wrap(.{ + .tid = @enumFromInt(local_tid), + .index = @intCast(local_inst_index), + }, ip); + entry.hash = hash; + entry.release(index.toOptional()); + } + } +} + /// Analysis Unit. Represents a single entity which undergoes semantic analysis. /// This is either a `Cau` or a runtime function. /// The LSB is used as a tag bit. @@ -572,10 +695,6 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI .ip = ip, .next_entry = .none, }; - if (ip.dep_entries.items[@intFromEnum(first_entry)].depender == .none) return .{ - .ip = ip, - .next_entry = .none, - }; return .{ .ip = ip, .next_entry = first_entry.toOptional(), @@ -612,7 +731,6 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend if (gop.found_existing and ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].depender == .none) { // Dummy entry, so we can reuse it rather than allocating a new one! - ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].next = .none; break :new_index gop.value_ptr.*; } @@ -620,7 +738,12 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend const new_index: DepEntry.Index, const ptr = if (ip.free_dep_entries.popOrNull()) |new_index| new: { break :new .{ new_index, &ip.dep_entries.items[@intFromEnum(new_index)] }; } else .{ @enumFromInt(ip.dep_entries.items.len), ip.dep_entries.addOneAssumeCapacity() }; - ptr.next = if (gop.found_existing) gop.value_ptr.*.toOptional() else .none; + if (gop.found_existing) { + ptr.next = gop.value_ptr.*.toOptional(); + ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].prev = new_index.toOptional(); + } else { + ptr.next = .none; + } gop.value_ptr.* = new_index; break :new_index new_index; }, @@ -642,10 +765,9 @@ pub const NamespaceNameKey = struct { }; pub const DepEntry = extern struct { - /// If null, this is a dummy entry - all other fields are `undefined`. It is - /// the first and only entry in one of `intern_pool.*_deps`, and does not - /// appear in any list by `first_dependency`, but is not in - /// `free_dep_entries` since `*_deps` stores a reference to it. + /// If null, this is a dummy entry. `next_dependee` is undefined. This is the first + /// entry in one of `*_deps`, and does not appear in any list by `first_dependency`, + /// but is not in `free_dep_entries` since `*_deps` stores a reference to it. depender: AnalUnit.Optional, /// Index into `dep_entries` forming a doubly linked list of all dependencies on this dependee. /// Used to iterate all dependers for a given dependee during an update. @@ -684,6 +806,14 @@ const Local = struct { /// This state is fully local to the owning thread and does not require any /// atomic access. mutate: struct { + /// When we need to allocate any long-lived buffer for mutating the `InternPool`, it is + /// allocated into this `arena` (for the `Id` of the thread performing the mutation). An + /// arena is used to avoid contention on the GPA, and to ensure that any code which retains + /// references to old state remains valid. For instance, when reallocing hashmap metadata, + /// a racing lookup on another thread may still retain a handle to the old metadata pointer, + /// so it must remain valid. + /// This arena's lifetime is tied to that of `Compilation`, although it can be cleared on + /// garbage collection (currently vaporware). arena: std.heap.ArenaAllocator.State, items: ListMutate, @@ -728,7 +858,7 @@ const Local = struct { else => @compileError("unsupported host"), }; const Strings = List(struct { u8 }); - const TrackedInsts = List(struct { TrackedInst }); + const TrackedInsts = List(struct { TrackedInst.MaybeLost }); const Maps = List(struct { FieldMap }); const Caus = List(struct { Cau }); const Navs = List(Nav.Repr); @@ -959,6 +1089,14 @@ const Local = struct { mutable.list.release(new_list); } + pub fn viewAllowEmpty(mutable: Mutable) View { + const capacity = mutable.list.header().capacity; + return .{ + .bytes = mutable.list.bytes, + .len = mutable.mutate.len, + .capacity = capacity, + }; + } pub fn view(mutable: Mutable) View { const capacity = mutable.list.header().capacity; assert(capacity > 0); // optimizes `MultiArrayList.Slice.items` @@ -996,7 +1134,6 @@ const Local = struct { fn header(list: ListSelf) *Header { return @ptrFromInt(@intFromPtr(list.bytes) - bytes_offset); } - pub fn view(list: ListSelf) View { const capacity = list.header().capacity; assert(capacity > 0); // optimizes `MultiArrayList.Slice.items` @@ -2023,6 +2160,14 @@ pub const Key = union(enum) { pub fn resolvedErrorSetUnordered(func: Func, ip: *const InternPool) Index { return @atomicLoad(Index, func.resolvedErrorSetPtr(@constCast(ip)), .unordered); } + + pub fn setResolvedErrorSet(func: Func, ip: *InternPool, ies: Index) void { + const extra_mutex = &ip.getLocal(func.tid).mutate.extra.mutex; + extra_mutex.lock(); + defer extra_mutex.unlock(); + + @atomicStore(Index, func.resolvedErrorSetPtr(ip), ies, .release); + } }; pub const Int = struct { @@ -2246,6 +2391,7 @@ pub const Key = union(enum) { func: Index, arg_values: []const Index, result: Index, + branch_count: u32, }; pub fn hash32(key: Key, ip: *const InternPool) u32 { @@ -2570,7 +2716,12 @@ pub const Key = union(enum) { .variable => |a_info| { const b_info = b.variable; - return a_info.owner_nav == b_info.owner_nav; + return a_info.owner_nav == b_info.owner_nav and + a_info.ty == b_info.ty and + a_info.init == b_info.init and + a_info.lib_name == b_info.lib_name and + a_info.is_threadlocal == b_info.is_threadlocal and + a_info.is_weak_linkage == b_info.is_weak_linkage; }, .@"extern" => |a_info| { const b_info = b.@"extern"; @@ -4003,7 +4154,7 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { } } -const LoadedEnumType = struct { +pub const LoadedEnumType = struct { // TODO: the non-fqn will be needed by the new dwarf structure /// The name of this enum type. name: NullTerminatedString, @@ -4261,17 +4412,6 @@ pub const Index = enum(u32) { null_type, undefined_type, enum_literal_type, - atomic_order_type, - atomic_rmw_op_type, - calling_convention_type, - address_space_type, - float_mode_type, - reduce_op_type, - call_modifier_type, - prefetch_options_type, - export_options_type, - extern_options_type, - type_info_type, manyptr_u8_type, manyptr_const_u8_type, manyptr_const_u8_sentinel_0_type, @@ -4304,10 +4444,6 @@ pub const Index = enum(u32) { four_u8, /// `-1` (comptime_int) negative_one, - /// `std.builtin.CallingConvention.C` - calling_convention_c, - /// `std.builtin.CallingConvention.Inline` - calling_convention_inline, /// `{}` void_value, /// `unreachable` (noreturn type) @@ -4687,17 +4823,6 @@ pub const static_keys = [_]Key{ .{ .simple_type = .null }, .{ .simple_type = .undefined }, .{ .simple_type = .enum_literal }, - .{ .simple_type = .atomic_order }, - .{ .simple_type = .atomic_rmw_op }, - .{ .simple_type = .calling_convention }, - .{ .simple_type = .address_space }, - .{ .simple_type = .float_mode }, - .{ .simple_type = .reduce_op }, - .{ .simple_type = .call_modifier }, - .{ .simple_type = .prefetch_options }, - .{ .simple_type = .export_options }, - .{ .simple_type = .extern_options }, - .{ .simple_type = .type_info }, // [*]u8 .{ .ptr_type = .{ @@ -4726,7 +4851,7 @@ pub const static_keys = [_]Key{ }, } }, - // comptime_int + // *const comptime_int .{ .ptr_type = .{ .child = .comptime_int_type, .flags = .{ @@ -4817,16 +4942,6 @@ pub const static_keys = [_]Key{ .ty = .comptime_int_type, .storage = .{ .i64 = -1 }, } }, - // calling_convention_c - .{ .enum_tag = .{ - .ty = .calling_convention_type, - .int = .one_u8, - } }, - // calling_convention_inline - .{ .enum_tag = .{ - .ty = .calling_convention_type, - .int = .four_u8, - } }, .{ .simple_value = .void }, .{ .simple_value = .@"unreachable" }, @@ -5540,18 +5655,6 @@ pub const SimpleType = enum(u32) { undefined = @intFromEnum(Index.undefined_type), enum_literal = @intFromEnum(Index.enum_literal_type), - atomic_order = @intFromEnum(Index.atomic_order_type), - atomic_rmw_op = @intFromEnum(Index.atomic_rmw_op_type), - calling_convention = @intFromEnum(Index.calling_convention_type), - address_space = @intFromEnum(Index.address_space_type), - float_mode = @intFromEnum(Index.float_mode_type), - reduce_op = @intFromEnum(Index.reduce_op_type), - call_modifier = @intFromEnum(Index.call_modifier_type), - prefetch_options = @intFromEnum(Index.prefetch_options_type), - export_options = @intFromEnum(Index.export_options_type), - extern_options = @intFromEnum(Index.extern_options_type), - type_info = @intFromEnum(Index.type_info_type), - adhoc_inferred_error_set = @intFromEnum(Index.adhoc_inferred_error_set_type), generic_poison = @intFromEnum(Index.generic_poison_type), }; @@ -6055,6 +6158,7 @@ pub const MemoizedCall = struct { func: Index, args_len: u32, result: Index, + branch_count: u32, }; pub fn init(ip: *InternPool, gpa: Allocator, available_threads: usize) !void { @@ -6131,18 +6235,6 @@ pub fn init(ip: *InternPool, gpa: Allocator, available_threads: usize) !void { // Sanity check. assert(ip.indexToKey(.bool_true).simple_value == .true); assert(ip.indexToKey(.bool_false).simple_value == .false); - - const cc_inline = ip.indexToKey(.calling_convention_inline).enum_tag.int; - const cc_c = ip.indexToKey(.calling_convention_c).enum_tag.int; - - assert(ip.indexToKey(cc_inline).int.storage.u64 == - @intFromEnum(std.builtin.CallingConvention.Inline)); - - assert(ip.indexToKey(cc_c).int.storage.u64 == - @intFromEnum(std.builtin.CallingConvention.C)); - - assert(ip.indexToKey(ip.typeOf(cc_inline)).int_type.bits == - @typeInfo(@typeInfo(std.builtin.CallingConvention).Enum.tag_type).Int.bits); } } @@ -6695,6 +6787,7 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .func = extra.data.func, .arg_values = @ptrCast(extra_list.view().items(.@"0")[extra.end..][0..extra.data.args_len]), .result = extra.data.result, + .branch_count = extra.data.branch_count, } }; }, }; @@ -6958,6 +7051,7 @@ fn getOrPutKeyEnsuringAdditionalCapacity( const index = entry.acquire(); if (index == .none) break; if (entry.hash != hash) continue; + if (index.unwrap(ip).getTag(ip) == .removed) continue; if (ip.indexToKey(index).eql(key, ip)) return .{ .existing = index }; } shard.mutate.map.mutex.lock(); @@ -7032,6 +7126,43 @@ fn getOrPutKeyEnsuringAdditionalCapacity( .map_index = map_index, } }; } +/// Like `getOrPutKey`, but asserts that the key already exists, and prepares to replace +/// its shard entry with a new `Index` anyway. After finalizing this, the old index remains +/// valid (in that `indexToKey` and similar queries will behave as before), but it will +/// never be returned from a lookup (`getOrPutKey` etc). +/// This is used by incremental compilation when an existing container type is outdated. In +/// this case, the type must be recreated at a new `InternPool.Index`, but the old index must +/// remain valid since now-unreferenced `AnalUnit`s may retain references to it. The old index +/// will be cleaned up when the `Zcu` undergoes garbage collection. +fn putKeyReplace( + ip: *InternPool, + tid: Zcu.PerThread.Id, + key: Key, +) GetOrPutKey { + const full_hash = key.hash64(ip); + const hash: u32 = @truncate(full_hash >> 32); + const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))]; + shard.mutate.map.mutex.lock(); + errdefer shard.mutate.map.mutex.unlock(); + const map = shard.shared.map; + const map_mask = map.header().mask(); + var map_index = hash; + while (true) : (map_index += 1) { + map_index &= map_mask; + const entry = &map.entries[map_index]; + const index = entry.value; + assert(index != .none); // key not present + if (entry.hash == hash and ip.indexToKey(index).eql(key, ip)) { + break; // we found the entry to replace + } + } + return .{ .new = .{ + .ip = ip, + .tid = tid, + .shard = shard, + .map_index = map_index, + } }; +} pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) Allocator.Error!Index { var gop = try ip.getOrPutKey(gpa, tid, key); @@ -7827,6 +7958,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All .func = memoized_call.func, .args_len = @intCast(memoized_call.arg_values.len), .result = memoized_call.result, + .branch_count = memoized_call.branch_count, }), }); extra.appendSliceAssumeCapacity(.{@ptrCast(memoized_call.arg_values)}); @@ -7859,6 +7991,10 @@ pub const UnionTypeInit = struct { zir_index: TrackedInst.Index, captures: []const CaptureValue, }, + declared_owned_captures: struct { + zir_index: TrackedInst.Index, + captures: CaptureValue.Slice, + }, reified: struct { zir_index: TrackedInst.Index, type_hash: u64, @@ -7871,17 +8007,28 @@ pub fn getUnionType( gpa: Allocator, tid: Zcu.PerThread.Id, ini: UnionTypeInit, + /// If it is known that there is an existing type with this key which is outdated, + /// this is passed as `true`, and the type is replaced with one at a fresh index. + replace_existing: bool, ) Allocator.Error!WipNamespaceType.Result { - var gop = try ip.getOrPutKey(gpa, tid, .{ .union_type = switch (ini.key) { + const key: Key = .{ .union_type = switch (ini.key) { .declared => |d| .{ .declared = .{ .zir_index = d.zir_index, .captures = .{ .external = d.captures }, } }, + .declared_owned_captures => |d| .{ .declared = .{ + .zir_index = d.zir_index, + .captures = .{ .owned = d.captures }, + } }, .reified => |r| .{ .reified = .{ .zir_index = r.zir_index, .type_hash = r.type_hash, } }, - } }); + } }; + var gop = if (replace_existing) + ip.putKeyReplace(tid, key) + else + try ip.getOrPutKey(gpa, tid, key); defer gop.deinit(); if (gop == .existing) return .{ .existing = gop.existing }; @@ -7896,7 +8043,7 @@ pub fn getUnionType( // TODO: fmt bug // zig fmt: off switch (ini.key) { - .declared => |d| @intFromBool(d.captures.len != 0) + d.captures.len, + inline .declared, .declared_owned_captures => |d| @intFromBool(d.captures.len != 0) + d.captures.len, .reified => 2, // type_hash: PackedU64 } + // zig fmt: on @@ -7905,7 +8052,10 @@ pub fn getUnionType( const extra_index = addExtraAssumeCapacity(extra, Tag.TypeUnion{ .flags = .{ - .any_captures = ini.key == .declared and ini.key.declared.captures.len != 0, + .any_captures = switch (ini.key) { + inline .declared, .declared_owned_captures => |d| d.captures.len != 0, + .reified => false, + }, .runtime_tag = ini.flags.runtime_tag, .any_aligned_fields = ini.flags.any_aligned_fields, .layout = ini.flags.layout, @@ -7914,7 +8064,10 @@ pub fn getUnionType( .assumed_runtime_bits = ini.flags.assumed_runtime_bits, .assumed_pointer_aligned = ini.flags.assumed_pointer_aligned, .alignment = ini.flags.alignment, - .is_reified = ini.key == .reified, + .is_reified = switch (ini.key) { + .declared, .declared_owned_captures => false, + .reified => true, + }, }, .fields_len = ini.fields_len, .size = std.math.maxInt(u32), @@ -7938,6 +8091,10 @@ pub fn getUnionType( extra.appendAssumeCapacity(.{@intCast(d.captures.len)}); extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}); }, + .declared_owned_captures => |d| if (d.captures.len != 0) { + extra.appendAssumeCapacity(.{@intCast(d.captures.len)}); + extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}); + }, .reified => |r| _ = addExtraAssumeCapacity(extra, PackedU64.init(r.type_hash)), } @@ -8035,6 +8192,10 @@ pub const StructTypeInit = struct { zir_index: TrackedInst.Index, captures: []const CaptureValue, }, + declared_owned_captures: struct { + zir_index: TrackedInst.Index, + captures: CaptureValue.Slice, + }, reified: struct { zir_index: TrackedInst.Index, type_hash: u64, @@ -8047,17 +8208,28 @@ pub fn getStructType( gpa: Allocator, tid: Zcu.PerThread.Id, ini: StructTypeInit, + /// If it is known that there is an existing type with this key which is outdated, + /// this is passed as `true`, and the type is replaced with one at a fresh index. + replace_existing: bool, ) Allocator.Error!WipNamespaceType.Result { - var gop = try ip.getOrPutKey(gpa, tid, .{ .struct_type = switch (ini.key) { + const key: Key = .{ .struct_type = switch (ini.key) { .declared => |d| .{ .declared = .{ .zir_index = d.zir_index, .captures = .{ .external = d.captures }, } }, + .declared_owned_captures => |d| .{ .declared = .{ + .zir_index = d.zir_index, + .captures = .{ .owned = d.captures }, + } }, .reified => |r| .{ .reified = .{ .zir_index = r.zir_index, .type_hash = r.type_hash, } }, - } }); + } }; + var gop = if (replace_existing) + ip.putKeyReplace(tid, key) + else + try ip.getOrPutKey(gpa, tid, key); defer gop.deinit(); if (gop == .existing) return .{ .existing = gop.existing }; @@ -8080,7 +8252,7 @@ pub fn getStructType( // TODO: fmt bug // zig fmt: off switch (ini.key) { - .declared => |d| @intFromBool(d.captures.len != 0) + d.captures.len, + inline .declared, .declared_owned_captures => |d| @intFromBool(d.captures.len != 0) + d.captures.len, .reified => 2, // type_hash: PackedU64 } + // zig fmt: on @@ -8096,10 +8268,16 @@ pub fn getStructType( .backing_int_ty = .none, .names_map = names_map, .flags = .{ - .any_captures = ini.key == .declared and ini.key.declared.captures.len != 0, + .any_captures = switch (ini.key) { + inline .declared, .declared_owned_captures => |d| d.captures.len != 0, + .reified => false, + }, .field_inits_wip = false, .inits_resolved = ini.inits_resolved, - .is_reified = ini.key == .reified, + .is_reified = switch (ini.key) { + .declared, .declared_owned_captures => false, + .reified => true, + }, }, }); try items.append(.{ @@ -8111,6 +8289,10 @@ pub fn getStructType( extra.appendAssumeCapacity(.{@intCast(d.captures.len)}); extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}); }, + .declared_owned_captures => |d| if (d.captures.len != 0) { + extra.appendAssumeCapacity(.{@intCast(d.captures.len)}); + extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}); + }, .reified => |r| { _ = addExtraAssumeCapacity(extra, PackedU64.init(r.type_hash)); }, @@ -8138,7 +8320,7 @@ pub fn getStructType( // TODO: fmt bug // zig fmt: off switch (ini.key) { - .declared => |d| @intFromBool(d.captures.len != 0) + d.captures.len, + inline .declared, .declared_owned_captures => |d| @intFromBool(d.captures.len != 0) + d.captures.len, .reified => 2, // type_hash: PackedU64 } + // zig fmt: on @@ -8153,7 +8335,10 @@ pub fn getStructType( .fields_len = ini.fields_len, .size = std.math.maxInt(u32), .flags = .{ - .any_captures = ini.key == .declared and ini.key.declared.captures.len != 0, + .any_captures = switch (ini.key) { + inline .declared, .declared_owned_captures => |d| d.captures.len != 0, + .reified => false, + }, .is_extern = is_extern, .known_non_opv = ini.known_non_opv, .requires_comptime = ini.requires_comptime, @@ -8171,7 +8356,10 @@ pub fn getStructType( .field_inits_wip = false, .inits_resolved = ini.inits_resolved, .fully_resolved = false, - .is_reified = ini.key == .reified, + .is_reified = switch (ini.key) { + .declared, .declared_owned_captures => false, + .reified => true, + }, }, }); try items.append(.{ @@ -8183,6 +8371,10 @@ pub fn getStructType( extra.appendAssumeCapacity(.{@intCast(d.captures.len)}); extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}); }, + .declared_owned_captures => |d| if (d.captures.len != 0) { + extra.appendAssumeCapacity(.{@intCast(d.captures.len)}); + extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}); + }, .reified => |r| { _ = addExtraAssumeCapacity(extra, PackedU64.init(r.type_hash)); }, @@ -8986,6 +9178,10 @@ pub const EnumTypeInit = struct { zir_index: TrackedInst.Index, captures: []const CaptureValue, }, + declared_owned_captures: struct { + zir_index: TrackedInst.Index, + captures: CaptureValue.Slice, + }, reified: struct { zir_index: TrackedInst.Index, type_hash: u64, @@ -9081,17 +9277,28 @@ pub fn getEnumType( gpa: Allocator, tid: Zcu.PerThread.Id, ini: EnumTypeInit, + /// If it is known that there is an existing type with this key which is outdated, + /// this is passed as `true`, and the type is replaced with one at a fresh index. + replace_existing: bool, ) Allocator.Error!WipEnumType.Result { - var gop = try ip.getOrPutKey(gpa, tid, .{ .enum_type = switch (ini.key) { + const key: Key = .{ .enum_type = switch (ini.key) { .declared => |d| .{ .declared = .{ .zir_index = d.zir_index, .captures = .{ .external = d.captures }, } }, + .declared_owned_captures => |d| .{ .declared = .{ + .zir_index = d.zir_index, + .captures = .{ .owned = d.captures }, + } }, .reified => |r| .{ .reified = .{ .zir_index = r.zir_index, .type_hash = r.type_hash, } }, - } }); + } }; + var gop = if (replace_existing) + ip.putKeyReplace(tid, key) + else + try ip.getOrPutKey(gpa, tid, key); defer gop.deinit(); if (gop == .existing) return .{ .existing = gop.existing }; @@ -9110,7 +9317,7 @@ pub fn getEnumType( // TODO: fmt bug // zig fmt: off switch (ini.key) { - .declared => |d| d.captures.len, + inline .declared, .declared_owned_captures => |d| d.captures.len, .reified => 2, // type_hash: PackedU64 } + // zig fmt: on @@ -9120,7 +9327,7 @@ pub fn getEnumType( const extra_index = addExtraAssumeCapacity(extra, EnumAuto{ .name = undefined, // set by `prepare` .captures_len = switch (ini.key) { - .declared => |d| @intCast(d.captures.len), + inline .declared, .declared_owned_captures => |d| @intCast(d.captures.len), .reified => std.math.maxInt(u32), }, .namespace = undefined, // set by `prepare` @@ -9139,6 +9346,7 @@ pub fn getEnumType( extra.appendAssumeCapacity(undefined); // `cau` will be set by `finish` switch (ini.key) { .declared => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}), + .declared_owned_captures => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}), .reified => |r| _ = addExtraAssumeCapacity(extra, PackedU64.init(r.type_hash)), } const names_start = extra.mutate.len; @@ -9169,7 +9377,7 @@ pub fn getEnumType( // TODO: fmt bug // zig fmt: off switch (ini.key) { - .declared => |d| d.captures.len, + inline .declared, .declared_owned_captures => |d| d.captures.len, .reified => 2, // type_hash: PackedU64 } + // zig fmt: on @@ -9180,7 +9388,7 @@ pub fn getEnumType( const extra_index = addExtraAssumeCapacity(extra, EnumExplicit{ .name = undefined, // set by `prepare` .captures_len = switch (ini.key) { - .declared => |d| @intCast(d.captures.len), + inline .declared, .declared_owned_captures => |d| @intCast(d.captures.len), .reified => std.math.maxInt(u32), }, .namespace = undefined, // set by `prepare` @@ -9204,6 +9412,7 @@ pub fn getEnumType( extra.appendAssumeCapacity(undefined); // `cau` will be set by `finish` switch (ini.key) { .declared => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}), + .declared_owned_captures => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}), .reified => |r| _ = addExtraAssumeCapacity(extra, PackedU64.init(r.type_hash)), } const names_start = extra.mutate.len; @@ -9267,10 +9476,12 @@ pub fn getGeneratedTagEnumType( .tid = tid, .index = items.mutate.len, }, ip); + const parent_namespace = ip.namespacePtr(ini.parent_namespace); const namespace = try ip.createNamespace(gpa, tid, .{ .parent = ini.parent_namespace.toOptional(), .owner_type = enum_index, - .file_scope = ip.namespacePtr(ini.parent_namespace).file_scope, + .file_scope = parent_namespace.file_scope, + .generation = parent_namespace.generation, }); errdefer ip.destroyNamespace(tid, namespace); @@ -9476,14 +9687,6 @@ fn addMap(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, cap: usize) Al /// Invalidates all references to this index. pub fn remove(ip: *InternPool, tid: Zcu.PerThread.Id, index: Index) void { const unwrapped_index = index.unwrap(ip); - if (@intFromEnum(index) < static_keys.len) { - // The item being removed replaced a special index via `InternPool.resolveBuiltinType`. - // Restore the original item at this index. - assert(static_keys[@intFromEnum(index)] == .simple_type); - const items = ip.getLocalShared(unwrapped_index.tid).items.acquire().view(); - @atomicStore(Tag, &items.items(.tag)[unwrapped_index.index], .simple_type, .unordered); - return; - } if (unwrapped_index.tid == tid) { const items_len = &ip.getLocal(unwrapped_index.tid).mutate.items.len; @@ -10123,17 +10326,7 @@ pub fn isIntegerType(ip: *const InternPool, ty: Index) bool { /// does not include .enum_literal_type pub fn isEnumType(ip: *const InternPool, ty: Index) bool { - return switch (ty) { - .atomic_order_type, - .atomic_rmw_op_type, - .calling_convention_type, - .address_space_type, - .float_mode_type, - .reduce_op_type, - .call_modifier_type, - => true, - else => ip.indexToKey(ty) == .enum_type, - }; + return ip.indexToKey(ty) == .enum_type; } pub fn isUnion(ip: *const InternPool, ty: Index) bool { @@ -10866,6 +11059,7 @@ pub fn destroyNamespace( .parent = undefined, .file_scope = undefined, .owner_type = undefined, + .generation = undefined, }; @field(namespace, Local.namespace_next_free_field) = @enumFromInt(local.mutate.namespaces.free_list); @@ -11000,7 +11194,6 @@ pub fn getOrPutTrailingString( shard.mutate.string_map.mutex.lock(); defer shard.mutate.string_map.mutex.unlock(); if (map.entries != shard.shared.string_map.entries) { - shard.mutate.string_map.len += 1; map = shard.shared.string_map; map_mask = map.header().mask(); map_index = hash; @@ -11134,17 +11327,6 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { .null_type, .undefined_type, .enum_literal_type, - .atomic_order_type, - .atomic_rmw_op_type, - .calling_convention_type, - .address_space_type, - .float_mode_type, - .reduce_op_type, - .call_modifier_type, - .prefetch_options_type, - .export_options_type, - .extern_options_type, - .type_info_type, .manyptr_u8_type, .manyptr_const_u8_type, .manyptr_const_u8_sentinel_0_type, @@ -11162,7 +11344,6 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { .zero, .one, .negative_one => .comptime_int_type, .zero_usize, .one_usize => .usize_type, .zero_u8, .one_u8, .four_u8 => .u8_type, - .calling_convention_c, .calling_convention_inline => .calling_convention_type, .void_value => .void_type, .unreachable_value => .noreturn_type, .null_value => .null_type, @@ -11458,22 +11639,6 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois .undefined_type => .Undefined, .enum_literal_type => .EnumLiteral, - .atomic_order_type, - .atomic_rmw_op_type, - .calling_convention_type, - .address_space_type, - .float_mode_type, - .reduce_op_type, - .call_modifier_type, - => .Enum, - - .prefetch_options_type, - .export_options_type, - .extern_options_type, - => .Struct, - - .type_info_type => .Union, - .manyptr_u8_type, .manyptr_const_u8_type, .manyptr_const_u8_sentinel_0_type, @@ -11498,8 +11663,6 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois .one_u8 => unreachable, .four_u8 => unreachable, .negative_one => unreachable, - .calling_convention_c => unreachable, - .calling_convention_inline => unreachable, .void_value => unreachable, .unreachable_value => unreachable, .null_value => unreachable, @@ -11818,34 +11981,6 @@ pub fn unwrapCoercedFunc(ip: *const InternPool, index: Index) Index { }; } -/// Having resolved a builtin type to a real struct/union/enum (which is now at `resolverd_index`), -/// make `want_index` refer to this type instead. This invalidates `resolved_index`, so must be -/// called only when it is guaranteed that no reference to `resolved_index` exists. -pub fn resolveBuiltinType( - ip: *InternPool, - tid: Zcu.PerThread.Id, - want_index: Index, - resolved_index: Index, -) void { - assert(@intFromEnum(want_index) >= @intFromEnum(Index.first_type)); - assert(@intFromEnum(want_index) <= @intFromEnum(Index.last_type)); - - // Make sure the type isn't already resolved! - assert(ip.indexToKey(want_index) == .simple_type); - - // Make sure it's the same kind of type - assert((ip.zigTypeTagOrPoison(want_index) catch unreachable) == - (ip.zigTypeTagOrPoison(resolved_index) catch unreachable)); - - // Copy the data - const item = resolved_index.unwrap(ip).getItem(ip); - const unwrapped_index = want_index.unwrap(ip); - var items = ip.getLocalShared(unwrapped_index.tid).items.acquire().view().slice(); - items.items(.data)[unwrapped_index.index] = item.data; - @atomicStore(Tag, &items.items(.tag)[unwrapped_index.index], item.tag, .release); - ip.remove(tid, resolved_index); -} - pub fn anonStructFieldTypes(ip: *const InternPool, i: Index) []const Index { return ip.indexToKey(i).anon_struct_type.types; } @@ -12044,7 +12179,3 @@ pub fn getErrorValue( pub fn getErrorValueIfExists(ip: *const InternPool, name: NullTerminatedString) ?Zcu.ErrorInt { return @intFromEnum(ip.global_error_set.getErrorValueIfExists(name) orelse return null); } - -pub fn isRemoved(ip: *const InternPool, ty: Index) bool { - return ty.unwrap(ip).getTag(ip) == .removed; -} diff --git a/zig/src/Liveness.zig b/zig/src/Liveness.zig index 4ca28758e2..b75fc402dd 100644 --- a/zig/src/Liveness.zig +++ b/zig/src/Liveness.zig @@ -464,6 +464,7 @@ pub fn categorizeOperand( .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, => { const o = air_datas[@intFromEnum(inst)].pl_op.operand; if (o == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none); @@ -1097,6 +1098,7 @@ fn analyzeInst( .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, => { const operand = inst_datas[@intFromEnum(inst)].pl_op.operand; return analyzeOperands(a, pass, data, inst, .{ operand, .none, .none }); diff --git a/zig/src/Liveness/Verify.zig b/zig/src/Liveness/Verify.zig index 4392f25e10..7a9959481a 100644 --- a/zig/src/Liveness/Verify.zig +++ b/zig/src/Liveness/Verify.zig @@ -157,6 +157,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { }, .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, .wasm_memory_grow, => { const pl_op = data[@intFromEnum(inst)].pl_op; diff --git a/zig/src/Sema.zig b/zig/src/Sema.zig index cdcbee42a5..3752cefe3f 100644 --- a/zig/src/Sema.zig +++ b/zig/src/Sema.zig @@ -76,10 +76,6 @@ no_partial_func_ty: bool = false, /// here so the values can be dropped without any cleanup. unresolved_inferred_allocs: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, InferredAlloc) = .{}, -/// While analyzing a type which has a special InternPool index, this is set to the index at which -/// the struct/enum/union type created should be placed. Otherwise, it is `.none`. -builtin_type_target_index: InternPool.Index = .none, - /// Links every pointer derived from a base `alloc` back to that `alloc`. Used /// to detect comptime-known `const`s. /// TODO: ZIR liveness analysis would allow us to remove elements from this map. @@ -110,6 +106,17 @@ exports: std.ArrayListUnmanaged(Zcu.Export) = .{}, /// of data stored in `Zcu.all_references`. It exists to avoid adding references to /// a given `AnalUnit` multiple times. references: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .{}, +type_references: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .{}, + +/// All dependencies registered so far by this `Sema`. This is a temporary duplicate +/// of the main dependency data. It exists to avoid adding dependencies to a given +/// `AnalUnit` multiple times. +dependencies: std.AutoArrayHashMapUnmanaged(InternPool.Dependee, void) = .{}, + +/// Whether memoization of this call is permitted. Operations with side effects global +/// to the `Sema`, such as `@setEvalBranchQuota`, set this to `false`. It is observed +/// by `analyzeCall`. +allow_memoize: bool = true, const MaybeComptimeAlloc = struct { /// The runtime index of the `alloc` instruction. @@ -181,6 +188,7 @@ const InternPool = @import("InternPool.zig"); const Alignment = InternPool.Alignment; const AnalUnit = InternPool.AnalUnit; const ComptimeAllocIndex = InternPool.ComptimeAllocIndex; +const Cache = std.Build.Cache; pub const default_branch_quota = 1000; pub const default_reference_trace_len = 2; @@ -373,7 +381,7 @@ pub const Block = struct { c_import_buf: ?*std.ArrayList(u8) = null, - /// If not `null`, this boolean is set when a `dbg_var_ptr` or `dbg_var_val` + /// If not `null`, this boolean is set when a `dbg_var_ptr`, `dbg_var_val`, or `dbg_arg_inline`. /// instruction is emitted. It signals that the innermost lexically /// enclosing `block`/`block_inline` should be translated into a real AIR /// `block` in order for codegen to match lexical scoping for debug vars. @@ -877,6 +885,8 @@ pub fn deinit(sema: *Sema) void { sema.comptime_allocs.deinit(gpa); sema.exports.deinit(gpa); sema.references.deinit(gpa); + sema.type_references.deinit(gpa); + sema.dependencies.deinit(gpa); sema.* = undefined; } @@ -999,7 +1009,7 @@ fn analyzeBodyInner( // The hashmap lookup in here is a little expensive, and LLVM fails to optimize it away. if (build_options.enable_logging) { std.log.scoped(.sema_zir).debug("sema ZIR {s} %{d}", .{ sub_file_path: { - const file_index = block.src_base_inst.resolveFull(&zcu.intern_pool).file; + const file_index = block.src_base_inst.resolveFile(&zcu.intern_pool); const file = zcu.fileByIndex(file_index); break :sub_file_path file.sub_file_path; }, inst }); @@ -1319,6 +1329,7 @@ fn analyzeBodyInner( }, .value_placeholder => unreachable, // never appears in a body .field_parent_ptr => try sema.zirFieldParentPtr(block, extended), + .builtin_value => try sema.zirBuiltinValue(extended), }; }, @@ -2496,12 +2507,12 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Module.Error const mod = sema.pt.zcu; if (build_options.enable_debug_extensions and mod.comp.debug_compile_errors) { - var all_references = mod.resolveReferences() catch @panic("out of memory"); + var all_references: ?std.AutoHashMapUnmanaged(AnalUnit, ?Zcu.ResolvedReference) = null; var wip_errors: std.zig.ErrorBundle.Wip = undefined; wip_errors.init(gpa) catch @panic("out of memory"); - Compilation.addModuleErrorMsg(mod, &wip_errors, err_msg.*, &all_references) catch unreachable; + Compilation.addModuleErrorMsg(mod, &wip_errors, err_msg.*, &all_references) catch @panic("out of memory"); std.debug.print("compile error during Sema:\n", .{}); - var error_bundle = wip_errors.toOwnedBundle("") catch unreachable; + var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory"); error_bundle.renderToStdErr(.{ .ttyconf = .no_color }); crash_report.compilerPanic("unexpected compile error occurred", null, null); } @@ -2704,44 +2715,6 @@ fn getCaptures(sema: *Sema, block: *Block, type_src: LazySrcLoc, extra_index: us return captures; } -/// Given an `InternPool.WipNamespaceType` or `InternPool.WipEnumType`, apply -/// `sema.builtin_type_target_index` to it if necessary. -fn wrapWipTy(sema: *Sema, wip_ty: anytype) @TypeOf(wip_ty) { - const pt = sema.pt; - if (sema.builtin_type_target_index == .none) return wip_ty; - var new = wip_ty; - new.index = sema.builtin_type_target_index; - pt.zcu.intern_pool.resolveBuiltinType(pt.tid, new.index, wip_ty.index); - return new; -} - -/// Given a type just looked up in the `InternPool`, check whether it is -/// considered outdated on this update. If so, remove it from the pool -/// and return `true`. -fn maybeRemoveOutdatedType(sema: *Sema, ty: InternPool.Index) !bool { - const pt = sema.pt; - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - - if (!zcu.comp.incremental) return false; - - const cau_index = switch (ip.indexToKey(ty)) { - .struct_type => ip.loadStructType(ty).cau.unwrap().?, - .union_type => ip.loadUnionType(ty).cau, - .enum_type => ip.loadEnumType(ty).cau.unwrap().?, - else => unreachable, - }; - const cau_unit = AnalUnit.wrap(.{ .cau = cau_index }); - const was_outdated = zcu.outdated.swapRemove(cau_unit) or - zcu.potentially_outdated.swapRemove(cau_unit); - if (!was_outdated) return false; - _ = zcu.outdated_ready.swapRemove(cau_unit); - zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, cau_unit); - zcu.intern_pool.remove(pt.tid, ty); - try zcu.markDependeeOutdated(.{ .interned = ty }); - return true; -} - fn zirStructDecl( sema: *Sema, block: *Block, @@ -2807,13 +2780,20 @@ fn zirStructDecl( .captures = captures, } }, }; - const wip_ty = sema.wrapWipTy(switch (try ip.getStructType(gpa, pt.tid, struct_init)) { - .existing => |ty| wip: { - if (!try sema.maybeRemoveOutdatedType(ty)) return Air.internedToRef(ty); - break :wip (try ip.getStructType(gpa, pt.tid, struct_init)).wip; + const wip_ty = switch (try ip.getStructType(gpa, pt.tid, struct_init, false)) { + .existing => |ty| { + const new_ty = try pt.ensureTypeUpToDate(ty, false); + + // Make sure we update the namespace if the declaration is re-analyzed, to pick + // up on e.g. changed comptime decls. + try pt.ensureNamespaceUpToDate(Type.fromInterned(new_ty).getNamespaceIndex(mod)); + + try sema.declareDependency(.{ .interned = new_ty }); + try sema.addTypeReferenceEntry(src, new_ty); + return Air.internedToRef(new_ty); }, .wip => |wip| wip, - }); + }; errdefer wip_ty.cancel(ip, pt.tid); wip_ty.setName(ip, try sema.createTypeName( @@ -2828,6 +2808,7 @@ fn zirStructDecl( .parent = block.namespace.toOptional(), .owner_type = wip_ty.index, .file_scope = block.getFileScopeIndex(mod), + .generation = mod.generation, }); errdefer pt.destroyNamespace(new_namespace_index); @@ -2845,8 +2826,13 @@ fn zirStructDecl( try pt.scanNamespace(new_namespace_index, decls); try mod.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .cau = new_cau_index })); + codegen_type: { + if (mod.comp.config.use_llvm) break :codegen_type; + if (block.ownerModule().strip) break :codegen_type; + try mod.comp.queueJob(.{ .codegen_type = wip_ty.index }); + } try sema.declareDependency(.{ .interned = wip_ty.index }); + try sema.addTypeReferenceEntry(src, wip_ty.index); return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); } @@ -2868,7 +2854,7 @@ fn createTypeName( .anon => {}, // handled after switch .parent => return block.type_name_ctx, .func => func_strat: { - const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip)); + const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip) orelse return error.AnalysisFail); const zir_tags = sema.code.instructions.items(.tag); var buf: std.ArrayListUnmanaged(u8) = .{}; @@ -2961,7 +2947,6 @@ fn zirEnumDecl( const tracked_inst = try block.trackZir(inst); const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(0) }; - const tag_ty_src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = .{ .node_offset_container_tag = 0 } }; const tag_type_ref = if (small.has_tag_type) blk: { const tag_type_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]); @@ -3024,13 +3009,20 @@ fn zirEnumDecl( .captures = captures, } }, }; - const wip_ty = sema.wrapWipTy(switch (try ip.getEnumType(gpa, pt.tid, enum_init)) { - .existing => |ty| wip: { - if (!try sema.maybeRemoveOutdatedType(ty)) return Air.internedToRef(ty); - break :wip (try ip.getEnumType(gpa, pt.tid, enum_init)).wip; + const wip_ty = switch (try ip.getEnumType(gpa, pt.tid, enum_init, false)) { + .existing => |ty| { + const new_ty = try pt.ensureTypeUpToDate(ty, false); + + // Make sure we update the namespace if the declaration is re-analyzed, to pick + // up on e.g. changed comptime decls. + try pt.ensureNamespaceUpToDate(Type.fromInterned(new_ty).getNamespaceIndex(mod)); + + try sema.declareDependency(.{ .interned = new_ty }); + try sema.addTypeReferenceEntry(src, new_ty); + return Air.internedToRef(new_ty); }, .wip => |wip| wip, - }); + }; // Once this is `true`, we will not delete the decl or type even upon failure, since we // have finished constructing the type and are in the process of analyzing it. @@ -3051,168 +3043,44 @@ fn zirEnumDecl( .parent = block.namespace.toOptional(), .owner_type = wip_ty.index, .file_scope = block.getFileScopeIndex(mod), + .generation = mod.generation, }); errdefer if (!done) pt.destroyNamespace(new_namespace_index); const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - if (pt.zcu.comp.incremental) { - try mod.intern_pool.addDependency( - gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), - .{ .src_hash = try block.trackZir(inst) }, - ); - } - try pt.scanNamespace(new_namespace_index, decls); - try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .cau = new_cau_index })); try sema.declareDependency(.{ .interned = wip_ty.index }); + try sema.addTypeReferenceEntry(src, wip_ty.index); // We've finished the initial construction of this type, and are about to perform analysis. // Set the Cau and namespace appropriately, and don't destroy anything on failure. wip_ty.prepare(ip, new_cau_index, new_namespace_index); done = true; - const int_tag_ty = ty: { - // We create a block for the field type instructions because they - // may need to reference Decls from inside the enum namespace. - // Within the field type, default value, and alignment expressions, the owner should be the enum's `Cau`. - - const prev_owner = sema.owner; - sema.owner = AnalUnit.wrap(.{ .cau = new_cau_index }); - defer sema.owner = prev_owner; - - const prev_func_index = sema.func_index; - sema.func_index = .none; - defer sema.func_index = prev_func_index; - - var enum_block: Block = .{ - .parent = null, - .sema = sema, - .namespace = new_namespace_index, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - .src_base_inst = tracked_inst, - .type_name_ctx = type_name, - }; - defer enum_block.instructions.deinit(sema.gpa); - - if (body.len != 0) { - _ = try sema.analyzeInlineBody(&enum_block, body, inst); - } - - if (tag_type_ref != .none) { - const ty = try sema.resolveType(&enum_block, tag_ty_src, tag_type_ref); - if (ty.zigTypeTag(mod) != .Int and ty.zigTypeTag(mod) != .ComptimeInt) { - return sema.fail(&enum_block, tag_ty_src, "expected integer tag type, found '{}'", .{ty.fmt(pt)}); - } - break :ty ty; - } else if (fields_len == 0) { - break :ty try pt.intType(.unsigned, 0); - } else { - const bits = std.math.log2_int_ceil(usize, fields_len); - break :ty try pt.intType(.unsigned, bits); - } - }; - - wip_ty.setTagTy(ip, int_tag_ty.toIntern()); - - if (small.nonexhaustive and int_tag_ty.toIntern() != .comptime_int_type) { - if (fields_len > 1 and std.math.log2_int(u64, fields_len) == int_tag_ty.bitSize(pt)) { - return sema.fail(block, src, "non-exhaustive enum specifies every value", .{}); - } - } - - var bit_bag_index: usize = body_end; - var cur_bit_bag: u32 = undefined; - var field_i: u32 = 0; - var last_tag_val: ?Value = null; - while (field_i < fields_len) : (field_i += 1) { - if (field_i % 32 == 0) { - cur_bit_bag = sema.code.extra[bit_bag_index]; - bit_bag_index += 1; - } - const has_tag_value = @as(u1, @truncate(cur_bit_bag)) != 0; - cur_bit_bag >>= 1; - - const field_name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]); - const field_name_zir = sema.code.nullTerminatedString(field_name_index); - extra_index += 2; // field name, doc comment - - const field_name = try mod.intern_pool.getOrPutString(gpa, pt.tid, field_name_zir, .no_embedded_nulls); - - const value_src: LazySrcLoc = .{ - .base_node_inst = tracked_inst, - .offset = .{ .container_field_value = field_i }, - }; - - const tag_overflow = if (has_tag_value) overflow: { - const tag_val_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]); - extra_index += 1; - const tag_inst = try sema.resolveInst(tag_val_ref); - last_tag_val = try sema.resolveConstDefinedValue(block, .{ - .base_node_inst = tracked_inst, - .offset = .{ .container_field_name = field_i }, - }, tag_inst, .{ - .needed_comptime_reason = "enum tag value must be comptime-known", - }); - if (!(try sema.intFitsInType(last_tag_val.?, int_tag_ty, null))) break :overflow true; - last_tag_val = try pt.getCoerced(last_tag_val.?, int_tag_ty); - if (wip_ty.nextField(&mod.intern_pool, field_name, last_tag_val.?.toIntern())) |conflict| { - assert(conflict.kind == .value); // AstGen validated names are unique - const other_field_src: LazySrcLoc = .{ - .base_node_inst = tracked_inst, - .offset = .{ .container_field_value = conflict.prev_field_idx }, - }; - const msg = msg: { - const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValueSema(pt, sema)}); - errdefer msg.destroy(gpa); - try sema.errNote(other_field_src, msg, "other occurrence here", .{}); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } - break :overflow false; - } else if (any_values) overflow: { - var overflow: ?usize = null; - last_tag_val = if (last_tag_val) |val| - try sema.intAdd(val, try pt.intValue(int_tag_ty, 1), int_tag_ty, &overflow) - else - try pt.intValue(int_tag_ty, 0); - if (overflow != null) break :overflow true; - if (wip_ty.nextField(&mod.intern_pool, field_name, last_tag_val.?.toIntern())) |conflict| { - assert(conflict.kind == .value); // AstGen validated names are unique - const other_field_src: LazySrcLoc = .{ - .base_node_inst = tracked_inst, - .offset = .{ .container_field_value = conflict.prev_field_idx }, - }; - const msg = msg: { - const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValueSema(pt, sema)}); - errdefer msg.destroy(gpa); - try sema.errNote(other_field_src, msg, "other occurrence here", .{}); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } - break :overflow false; - } else overflow: { - assert(wip_ty.nextField(&mod.intern_pool, field_name, .none) == null); - last_tag_val = try pt.intValue(Type.comptime_int, field_i); - if (!try sema.intFitsInType(last_tag_val.?, int_tag_ty, null)) break :overflow true; - last_tag_val = try pt.getCoerced(last_tag_val.?, int_tag_ty); - break :overflow false; - }; + try Sema.resolveDeclaredEnum( + pt, + wip_ty, + inst, + tracked_inst, + new_namespace_index, + type_name, + new_cau_index, + small, + body, + tag_type_ref, + any_values, + fields_len, + sema.code, + body_end, + ); - if (tag_overflow) { - const msg = try sema.errMsg(value_src, "enumeration value '{}' too large for type '{}'", .{ - last_tag_val.?.fmtValueSema(pt, sema), int_tag_ty.fmt(pt), - }); - return sema.failWithOwnedErrorMsg(block, msg); - } + codegen_type: { + if (mod.comp.config.use_llvm) break :codegen_type; + if (block.ownerModule().strip) break :codegen_type; + try mod.comp.queueJob(.{ .codegen_type = wip_ty.index }); } - return Air.internedToRef(wip_ty.index); } @@ -3285,13 +3153,20 @@ fn zirUnionDecl( .captures = captures, } }, }; - const wip_ty = sema.wrapWipTy(switch (try ip.getUnionType(gpa, pt.tid, union_init)) { - .existing => |ty| wip: { - if (!try sema.maybeRemoveOutdatedType(ty)) return Air.internedToRef(ty); - break :wip (try ip.getUnionType(gpa, pt.tid, union_init)).wip; + const wip_ty = switch (try ip.getUnionType(gpa, pt.tid, union_init, false)) { + .existing => |ty| { + const new_ty = try pt.ensureTypeUpToDate(ty, false); + + // Make sure we update the namespace if the declaration is re-analyzed, to pick + // up on e.g. changed comptime decls. + try pt.ensureNamespaceUpToDate(Type.fromInterned(new_ty).getNamespaceIndex(mod)); + + try sema.declareDependency(.{ .interned = new_ty }); + try sema.addTypeReferenceEntry(src, new_ty); + return Air.internedToRef(new_ty); }, .wip => |wip| wip, - }); + }; errdefer wip_ty.cancel(ip, pt.tid); wip_ty.setName(ip, try sema.createTypeName( @@ -3306,6 +3181,7 @@ fn zirUnionDecl( .parent = block.namespace.toOptional(), .owner_type = wip_ty.index, .file_scope = block.getFileScopeIndex(mod), + .generation = mod.generation, }); errdefer pt.destroyNamespace(new_namespace_index); @@ -3315,7 +3191,7 @@ fn zirUnionDecl( try mod.intern_pool.addDependency( gpa, AnalUnit.wrap(.{ .cau = new_cau_index }), - .{ .src_hash = try block.trackZir(inst) }, + .{ .src_hash = tracked_inst }, ); } @@ -3323,8 +3199,13 @@ fn zirUnionDecl( try pt.scanNamespace(new_namespace_index, decls); try mod.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .cau = new_cau_index })); + codegen_type: { + if (mod.comp.config.use_llvm) break :codegen_type; + if (block.ownerModule().strip) break :codegen_type; + try mod.comp.queueJob(.{ .codegen_type = wip_ty.index }); + } try sema.declareDependency(.{ .interned = wip_ty.index }); + try sema.addTypeReferenceEntry(src, wip_ty.index); return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); } @@ -3370,10 +3251,16 @@ fn zirOpaqueDecl( .captures = captures, } }, }; - // No `wrapWipTy` needed as no std.builtin types are opaque. const wip_ty = switch (try ip.getOpaqueType(gpa, pt.tid, opaque_init)) { - // No `maybeRemoveOutdatedType` as opaque types are never outdated. - .existing => |ty| return Air.internedToRef(ty), + .existing => |ty| { + // Make sure we update the namespace if the declaration is re-analyzed, to pick + // up on e.g. changed comptime decls. + try pt.ensureNamespaceUpToDate(Type.fromInterned(ty).getNamespaceIndex(mod)); + + try sema.declareDependency(.{ .interned = ty }); + try sema.addTypeReferenceEntry(src, ty); + return Air.internedToRef(ty); + }, .wip => |wip| wip, }; errdefer wip_ty.cancel(ip, pt.tid); @@ -3390,12 +3277,19 @@ fn zirOpaqueDecl( .parent = block.namespace.toOptional(), .owner_type = wip_ty.index, .file_scope = block.getFileScopeIndex(mod), + .generation = mod.generation, }); errdefer pt.destroyNamespace(new_namespace_index); const decls = sema.code.bodySlice(extra_index, decls_len); try pt.scanNamespace(new_namespace_index, decls); + codegen_type: { + if (mod.comp.config.use_llvm) break :codegen_type; + if (block.ownerModule().strip) break :codegen_type; + try mod.comp.queueJob(.{ .codegen_type = wip_ty.index }); + } + try sema.addTypeReferenceEntry(src, wip_ty.index); return Air.internedToRef(wip_ty.finish(ip, .none, new_namespace_index)); } @@ -3626,8 +3520,7 @@ fn zirAllocExtended( const alignment = if (small.has_align) blk: { const align_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]); extra_index += 1; - const alignment = try sema.resolveAlign(block, align_src, align_ref); - break :blk alignment; + break :blk try sema.resolveAlign(block, align_src, align_ref); } else .none; if (block.is_comptime or small.is_comptime) { @@ -3652,6 +3545,10 @@ fn zirAllocExtended( } const target = pt.zcu.getTarget(); try var_ty.resolveLayout(pt); + if (sema.func_is_naked and try sema.typeHasRuntimeBits(var_ty)) { + const var_src = block.src(.{ .node_offset_store_ptr = extra.data.src_node }); + return sema.fail(block, var_src, "local variable in naked function", .{}); + } const ptr_type = try sema.pt.ptrTypeSema(.{ .child = var_ty.toIntern(), .flags = .{ @@ -4087,10 +3984,15 @@ fn zirAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; const ty_src = block.src(.{ .node_offset_var_decl_ty = inst_data.src_node }); + const var_ty = try sema.resolveType(block, ty_src, inst_data.operand); if (block.is_comptime) { return sema.analyzeComptimeAlloc(block, var_ty, .none); } + if (sema.func_is_naked and try sema.typeHasRuntimeBits(var_ty)) { + const mut_src = block.src(.{ .node_offset_store_ptr = inst_data.src_node }); + return sema.fail(block, mut_src, "local variable in naked function", .{}); + } const target = pt.zcu.getTarget(); const ptr_type = try pt.ptrTypeSema(.{ .child = var_ty.toIntern(), @@ -4115,6 +4017,10 @@ fn zirAllocMut(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (block.is_comptime) { return sema.analyzeComptimeAlloc(block, var_ty, .none); } + if (sema.func_is_naked and try sema.typeHasRuntimeBits(var_ty)) { + const var_src = block.src(.{ .node_offset_store_ptr = inst_data.src_node }); + return sema.fail(block, var_src, "local variable in naked function", .{}); + } try sema.validateVarType(block, ty_src, var_ty, false); const target = pt.zcu.getTarget(); const ptr_type = try pt.ptrTypeSema(.{ @@ -4248,7 +4154,10 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com // TODO: source location of runtime control flow return sema.fail(block, src, "value with comptime-only type '{}' depends on runtime control flow", .{final_elem_ty.fmt(pt)}); } - + if (sema.func_is_naked and try sema.typeHasRuntimeBits(final_elem_ty)) { + const mut_src = block.src(.{ .node_offset_store_ptr = inst_data.src_node }); + return sema.fail(block, mut_src, "local variable in naked function", .{}); + } // Change it to a normal alloc. sema.air_instructions.set(@intFromEnum(ptr_inst), .{ .tag = .alloc, @@ -5452,7 +5361,7 @@ fn failWithBadMemberAccess( .Enum => "enum", else => unreachable, }; - if (agg_ty.typeDeclInst(zcu)) |inst| if (inst.resolve(ip) == .main_struct_inst) { + if (agg_ty.typeDeclInst(zcu)) |inst| if ((inst.resolve(ip) orelse return error.AnalysisFail) == .main_struct_inst) { return sema.fail(block, field_src, "root struct of file '{}' has no member named '{}'", .{ agg_ty.fmt(pt), field_name.fmt(ip), }); @@ -5620,6 +5529,7 @@ fn zirSetEvalBranchQuota(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi .needed_comptime_reason = "eval branch quota must be comptime-known", })); sema.branch_quota = @max(sema.branch_quota, quota); + sema.allow_memoize = false; } fn zirStoreNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { @@ -5968,16 +5878,18 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr return sema.failWithOwnedErrorMsg(&child_block, msg); } const parent_mod = parent_block.ownerModule(); + const digest = Cache.binToHex(c_import_res.digest); + const c_import_zig_path = try comp.arena.dupe(u8, "o" ++ std.fs.path.sep_str ++ digest); const c_import_mod = Package.Module.create(comp.arena, .{ .global_cache_directory = comp.global_cache_directory, .paths = .{ .root = .{ - .root_dir = Compilation.Directory.cwd(), - .sub_path = std.fs.path.dirname(c_import_res.out_zig_path) orelse "", + .root_dir = comp.local_cache_directory, + .sub_path = c_import_zig_path, }, - .root_src_path = std.fs.path.basename(c_import_res.out_zig_path), + .root_src_path = "cimport.zig", }, - .fully_qualified_name = c_import_res.out_zig_path, + .fully_qualified_name = c_import_zig_path, .cc_argv = parent_mod.cc_argv, .inherited = .{}, .global = comp.config, @@ -6006,15 +5918,17 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); const path_digest = zcu.filePathDigest(result.file_index); - const old_root_type = zcu.fileRootType(result.file_index); - pt.astGenFile(result.file, path_digest, old_root_type) catch |err| + pt.astGenFile(result.file, path_digest) catch |err| return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); // TODO: register some kind of dependency on the file. // That way, if this returns `error.AnalysisFail`, we have the dependency banked ready to // trigger re-analysis later. try pt.ensureFileAnalyzed(result.file_index); - return Air.internedToRef(zcu.fileRootType(result.file_index)); + const ty = zcu.fileRootType(result.file_index); + try sema.declareDependency(.{ .interned = ty }); + try sema.addTypeReferenceEntry(src, ty); + return Air.internedToRef(ty); } fn zirSuspendBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -6508,6 +6422,7 @@ fn zirSetAlignStack(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Inst } zcu.intern_pool.funcMaxStackAlignment(sema.func_index, alignment); + sema.allow_memoize = false; } fn zirSetCold(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void { @@ -6526,6 +6441,7 @@ fn zirSetCold(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) .cau => return, // does nothing outside a function }; ip.funcSetCold(func, is_cold); + sema.allow_memoize = false; } fn zirDisableInstrumentation(sema: *Sema) CompileError!void { @@ -6537,6 +6453,7 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void { .cau => return, // does nothing outside a function }; ip.funcSetDisableInstrumentation(func); + sema.allow_memoize = false; } fn zirSetFloatMode(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void { @@ -6659,7 +6576,7 @@ fn addDbgVar( const operand_ty = sema.typeOf(operand); const val_ty = switch (air_tag) { .dbg_var_ptr => operand_ty.childType(mod), - .dbg_var_val => operand_ty, + .dbg_var_val, .dbg_arg_inline => operand_ty, else => unreachable, }; if (try sema.typeRequiresComptime(val_ty)) return; @@ -6678,25 +6595,26 @@ fn addDbgVar( if (block.need_debug_scope) |ptr| ptr.* = true; // Add the name to the AIR. - const name_extra_index = try sema.appendAirString(name); + const name_nts = try sema.appendAirString(name); _ = try block.addInst(.{ .tag = air_tag, .data = .{ .pl_op = .{ - .payload = name_extra_index, + .payload = @intFromEnum(name_nts), .operand = operand, } }, }); } -pub fn appendAirString(sema: *Sema, str: []const u8) Allocator.Error!u32 { - const str_extra_index: u32 = @intCast(sema.air_extra.items.len); +pub fn appendAirString(sema: *Sema, str: []const u8) Allocator.Error!Air.NullTerminatedString { + if (str.len == 0) return .none; + const nts: Air.NullTerminatedString = @enumFromInt(sema.air_extra.items.len); const elements_used = str.len / 4 + 1; const elements = try sema.air_extra.addManyAsSlice(sema.gpa, elements_used); const buffer = mem.sliceAsBytes(elements); @memcpy(buffer[0..str.len], str); buffer[str.len] = 0; - return str_extra_index; + return nts; } fn zirDeclRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -6762,12 +6680,21 @@ fn lookupInNamespace( const zcu = pt.zcu; const ip = &zcu.intern_pool; + try pt.ensureNamespaceUpToDate(namespace_index); + const namespace = zcu.namespacePtr(namespace_index); const adapter: Zcu.Namespace.NameAdapter = .{ .zcu = zcu }; const src_file = zcu.namespacePtr(block.namespace).file_scope; + if (Type.fromInterned(namespace.owner_type).typeDeclInst(zcu)) |type_decl_inst| { + try sema.declareDependency(.{ .namespace_name = .{ + .namespace = type_decl_inst, + .name = ident_name, + } }); + } + if (observe_usingnamespace and (namespace.pub_usingnamespace.items.len != 0 or namespace.priv_usingnamespace.items.len != 0)) { const gpa = sema.gpa; var checked_namespaces: std.AutoArrayHashMapUnmanaged(*Namespace, void) = .{}; @@ -7493,14 +7420,14 @@ fn analyzeCall( operation: CallOperation, ) CompileError!Air.Inst.Ref { const pt = sema.pt; - const mod = pt.zcu; - const ip = &mod.intern_pool; + const zcu = pt.zcu; + const ip = &zcu.intern_pool; const callee_ty = sema.typeOf(func); - const func_ty_info = mod.typeToFunc(func_ty).?; + const func_ty_info = zcu.typeToFunc(func_ty).?; const cc = func_ty_info.cc; if (try sema.resolveValue(func)) |func_val| - if (func_val.isUndef(mod)) + if (func_val.isUndef(zcu)) return sema.failWithUseOfUndef(block, call_src); if (cc == .Naked) { const maybe_func_inst = try sema.funcDeclSrcInst(func); @@ -7612,7 +7539,7 @@ fn analyzeCall( .needed_comptime_reason = "function being called at comptime must be comptime-known", .block_comptime_reason = comptime_reason, }); - const module_fn_index = switch (mod.intern_pool.indexToKey(func_val.toIntern())) { + const module_fn_index = switch (zcu.intern_pool.indexToKey(func_val.toIntern())) { .@"extern" => return sema.fail(block, call_src, "{s} call of extern function", .{ @as([]const u8, if (is_comptime_call) "comptime" else "inline"), }), @@ -7629,7 +7556,7 @@ fn analyzeCall( }, else => {}, } - assert(callee_ty.isPtrAtRuntime(mod)); + assert(callee_ty.isPtrAtRuntime(zcu)); return sema.fail(block, call_src, "{s} call of function pointer", .{ if (is_comptime_call) "comptime" else "inline", }); @@ -7669,7 +7596,10 @@ fn analyzeCall( }, }; - const module_fn = mod.funcInfo(module_fn_index); + const module_fn = zcu.funcInfo(module_fn_index); + + // The call site definitely depends on the function's signature. + try sema.declareDependency(.{ .src_hash = module_fn.zir_body_inst }); // This is not a function instance, so the function's `Nav` has a // `Cau` -- we don't need to check `generic_owner`. @@ -7683,7 +7613,7 @@ fn analyzeCall( // whenever performing an operation where the difference matters. var ics = InlineCallSema.init( sema, - mod.cauFileScope(fn_cau_index).zir, + zcu.cauFileScope(fn_cau_index).zir, module_fn_index, block.error_return_trace_index, ); @@ -7717,13 +7647,16 @@ fn analyzeCall( // Whether this call should be memoized, set to false if the call can // mutate comptime state. - var should_memoize = true; + // TODO: comptime call memoization is currently not supported under incremental compilation + // since dependencies are not marked on callers. If we want to keep this around (we should + // check that it's worthwhile first!), each memoized call needs a `Cau`. + var should_memoize = !zcu.comp.incremental; // If it's a comptime function call, we need to memoize it as long as no external // comptime memory is mutated. const memoized_arg_values = try sema.arena.alloc(InternPool.Index, func_ty_info.param_types.len); - const owner_info = mod.typeToFunc(Type.fromInterned(module_fn.ty)).?; + const owner_info = zcu.typeToFunc(Type.fromInterned(module_fn.ty)).?; const new_param_types = try sema.arena.alloc(InternPool.Index, owner_info.param_types.len); var new_fn_info: InternPool.GetFuncTypeKey = .{ .param_types = new_param_types, @@ -7743,7 +7676,7 @@ fn analyzeCall( // the AIR instructions of the callsite. The callee could be a generic function // which means its parameter type expressions must be resolved in order and used // to successively coerce the arguments. - const fn_info = ics.callee().code.getFnInfo(module_fn.zir_body_inst.resolve(ip)); + const fn_info = ics.callee().code.getFnInfo(module_fn.zir_body_inst.resolve(ip) orelse return error.AnalysisFail); try ics.callee().inst_map.ensureSpaceForInstructions(gpa, fn_info.param_body); var arg_i: u32 = 0; @@ -7788,7 +7721,7 @@ fn analyzeCall( // each of the parameters, resolving the return type and providing it to the child // `Sema` so that it can be used for the `ret_ptr` instruction. const ret_ty_inst = if (fn_info.ret_ty_body.len != 0) - try sema.resolveInlineBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst.resolve(ip)) + try sema.resolveInlineBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst.resolve(ip) orelse return error.AnalysisFail) else try sema.resolveInst(fn_info.ret_ty_ref); const ret_ty_src: LazySrcLoc = .{ .base_node_inst = module_fn.zir_body_inst, .offset = .{ .node_offset_fn_type_ret_ty = 0 } }; @@ -7804,101 +7737,121 @@ fn analyzeCall( } })); } - // This `res2` is here instead of directly breaking from `res` due to a stage1 - // bug generating invalid LLVM IR. - const res2: Air.Inst.Ref = res2: { - if (should_memoize and is_comptime_call) { - if (mod.intern_pool.getIfExists(.{ .memoized_call = .{ + memoize: { + if (!should_memoize) break :memoize; + if (!is_comptime_call) break :memoize; + const memoized_call_index = ip.getIfExists(.{ + .memoized_call = .{ .func = module_fn_index, .arg_values = memoized_arg_values, - .result = .none, - } })) |memoized_call_index| { - const memoized_call = mod.intern_pool.indexToKey(memoized_call_index).memoized_call; - break :res2 Air.internedToRef(memoized_call.result); - } + .result = undefined, // ignored by hash+eql + .branch_count = undefined, // ignored by hash+eql + }, + }) orelse break :memoize; + const memoized_call = ip.indexToKey(memoized_call_index).memoized_call; + if (sema.branch_count + memoized_call.branch_count > sema.branch_quota) { + // Let the call play out se we get the correct source location for the + // "evaluation exceeded X backwards branches" error. + break :memoize; } + sema.branch_count += memoized_call.branch_count; + break :res Air.internedToRef(memoized_call.result); + } - new_fn_info.return_type = sema.fn_ret_ty.toIntern(); - if (!is_comptime_call and !block.is_typeof) { - const zir_tags = sema.code.instructions.items(.tag); - for (fn_info.param_body) |param| switch (zir_tags[@intFromEnum(param)]) { - .param, .param_comptime => { - const inst_data = sema.code.instructions.items(.data)[@intFromEnum(param)].pl_tok; - const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index); - const param_name = sema.code.nullTerminatedString(extra.data.name); - const inst = sema.inst_map.get(param).?; + // Since we're doing an inline call, we depend on the source code of the whole + // function declaration. + try sema.declareDependency(.{ .src_hash = fn_cau.zir_index }); - try sema.addDbgVar(&child_block, inst, .dbg_var_val, param_name); - }, - .param_anytype, .param_anytype_comptime => { - const inst_data = sema.code.instructions.items(.data)[@intFromEnum(param)].str_tok; - const param_name = inst_data.get(sema.code); - const inst = sema.inst_map.get(param).?; + new_fn_info.return_type = sema.fn_ret_ty.toIntern(); + if (!is_comptime_call and !block.is_typeof) { + const zir_tags = sema.code.instructions.items(.tag); + for (fn_info.param_body) |param| switch (zir_tags[@intFromEnum(param)]) { + .param, .param_comptime => { + const inst_data = sema.code.instructions.items(.data)[@intFromEnum(param)].pl_tok; + const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index); + const param_name = sema.code.nullTerminatedString(extra.data.name); + const inst = sema.inst_map.get(param).?; + + try sema.addDbgVar(&child_block, inst, .dbg_arg_inline, param_name); + }, + .param_anytype, .param_anytype_comptime => { + const inst_data = sema.code.instructions.items(.data)[@intFromEnum(param)].str_tok; + const param_name = inst_data.get(sema.code); + const inst = sema.inst_map.get(param).?; - try sema.addDbgVar(&child_block, inst, .dbg_var_val, param_name); - }, - else => continue, - }; - } + try sema.addDbgVar(&child_block, inst, .dbg_arg_inline, param_name); + }, + else => continue, + }; + } - if (is_comptime_call and ensure_result_used) { - try sema.ensureResultUsed(block, sema.fn_ret_ty, call_src); - } + if (is_comptime_call and ensure_result_used) { + try sema.ensureResultUsed(block, sema.fn_ret_ty, call_src); + } - if (is_comptime_call or block.is_typeof) { - // Save the error trace as our first action in the function - // to match the behavior of runtime function calls. - const error_return_trace_index = try sema.analyzeSaveErrRetIndex(&child_block); - sema.error_return_trace_index_on_fn_entry = error_return_trace_index; - child_block.error_return_trace_index = error_return_trace_index; - } + if (is_comptime_call or block.is_typeof) { + // Save the error trace as our first action in the function + // to match the behavior of runtime function calls. + const error_return_trace_index = try sema.analyzeSaveErrRetIndex(&child_block); + sema.error_return_trace_index_on_fn_entry = error_return_trace_index; + child_block.error_return_trace_index = error_return_trace_index; + } - const result = result: { - sema.analyzeFnBody(&child_block, fn_info.body) catch |err| switch (err) { - error.ComptimeReturn => break :result inlining.comptime_result, - else => |e| return e, - }; - break :result try sema.resolveAnalyzedBlock(block, call_src, &child_block, merges, need_debug_scope); + // We temporarily set `allow_memoize` to `true` to track this comptime call. + // It is restored after this call finishes analysis, so that a caller may + // know whether an in-progress call (containing this call) may be memoized. + const old_allow_memoize = sema.allow_memoize; + defer sema.allow_memoize = old_allow_memoize and sema.allow_memoize; + sema.allow_memoize = true; + + // Store the current eval branch count so we can find out how many eval branches + // the comptime call caused. + const old_branch_count = sema.branch_count; + + const result = result: { + sema.analyzeFnBody(&child_block, fn_info.body) catch |err| switch (err) { + error.ComptimeReturn => break :result inlining.comptime_result, + else => |e| return e, }; + break :result try sema.resolveAnalyzedBlock(block, call_src, &child_block, merges, need_debug_scope); + }; - if (is_comptime_call) { - const result_val = try sema.resolveConstValue(block, LazySrcLoc.unneeded, result, undefined); - const result_interned = result_val.toIntern(); - - // Transform ad-hoc inferred error set types into concrete error sets. - const result_transformed = try sema.resolveAdHocInferredErrorSet(block, call_src, result_interned); - - // If the result can mutate comptime vars, we must not memoize it, as it contains - // a reference to `comptime_allocs` so is not stable across instances of `Sema`. - // TODO: check whether any external comptime memory was mutated by the - // comptime function call. If so, then do not memoize the call here. - if (should_memoize and !Value.fromInterned(result_interned).canMutateComptimeVarState(mod)) { - _ = try pt.intern(.{ .memoized_call = .{ - .func = module_fn_index, - .arg_values = memoized_arg_values, - .result = result_transformed, - } }); - } + if (is_comptime_call) { + const result_val = try sema.resolveConstValue(block, LazySrcLoc.unneeded, result, undefined); + const result_interned = result_val.toIntern(); - break :res2 Air.internedToRef(result_transformed); - } + // Transform ad-hoc inferred error set types into concrete error sets. + const result_transformed = try sema.resolveAdHocInferredErrorSet(block, call_src, result_interned); - if (try sema.resolveValue(result)) |result_val| { - const result_transformed = try sema.resolveAdHocInferredErrorSet(block, call_src, result_val.toIntern()); - break :res2 Air.internedToRef(result_transformed); + // If the result can mutate comptime vars, we must not memoize it, as it contains + // a reference to `comptime_allocs` so is not stable across instances of `Sema`. + // TODO: check whether any external comptime memory was mutated by the + // comptime function call. If so, then do not memoize the call here. + if (should_memoize and sema.allow_memoize and !Value.fromInterned(result_interned).canMutateComptimeVarState(zcu)) { + _ = try pt.intern(.{ .memoized_call = .{ + .func = module_fn_index, + .arg_values = memoized_arg_values, + .result = result_transformed, + .branch_count = sema.branch_count - old_branch_count, + } }); } - const new_ty = try sema.resolveAdHocInferredErrorSetTy(block, call_src, sema.typeOf(result).toIntern()); - if (new_ty != .none) { - // TODO: mutate in place the previous instruction if possible - // rather than adding a bitcast instruction. - break :res2 try block.addBitCast(Type.fromInterned(new_ty), result); - } + break :res Air.internedToRef(result_transformed); + } - break :res2 result; - }; + if (try sema.resolveValue(result)) |result_val| { + const result_transformed = try sema.resolveAdHocInferredErrorSet(block, call_src, result_val.toIntern()); + break :res Air.internedToRef(result_transformed); + } + + const new_ty = try sema.resolveAdHocInferredErrorSetTy(block, call_src, sema.typeOf(result).toIntern()); + if (new_ty != .none) { + // TODO: mutate in place the previous instruction if possible + // rather than adding a bitcast instruction. + break :res try block.addBitCast(Type.fromInterned(new_ty), result); + } - break :res res2; + break :res result; } else res: { assert(!func_ty_info.is_generic); @@ -7911,7 +7864,7 @@ fn analyzeCall( if (param_ty) |t| assert(!t.isGenericPoison()); arg_out.* = try args_info.analyzeArg(sema, block, arg_idx, param_ty, func_ty_info, func); try sema.validateRuntimeValue(block, args_info.argSrc(block, arg_idx), arg_out.*); - if (sema.typeOf(arg_out.*).zigTypeTag(mod) == .NoReturn) { + if (sema.typeOf(arg_out.*).zigTypeTag(zcu) == .NoReturn) { return arg_out.*; } } @@ -7920,15 +7873,15 @@ fn analyzeCall( switch (sema.owner.unwrap()) { .cau => {}, - .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(mod)) { + .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, } if (try sema.resolveValue(func)) |func_val| { - if (mod.intern_pool.isFuncBody(func_val.toIntern())) { + if (zcu.intern_pool.isFuncBody(func_val.toIntern())) { try sema.addReferenceEntry(call_src, AnalUnit.wrap(.{ .func = func_val.toIntern() })); - try mod.ensureFuncBodyAnalysisQueued(func_val.toIntern()); + try zcu.ensureFuncBodyAnalysisQueued(func_val.toIntern()); } } @@ -7955,7 +7908,7 @@ fn analyzeCall( // Function pointers and extern functions aren't guaranteed to // actually be noreturn so we add a safety check for them. if (try sema.resolveValue(func)) |func_val| { - switch (mod.intern_pool.indexToKey(func_val.toIntern())) { + switch (zcu.intern_pool.indexToKey(func_val.toIntern())) { .func => break :skip_safety, .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { .nav => |nav| if (!ip.getNav(nav).isExtern(ip)) break :skip_safety, @@ -8175,7 +8128,7 @@ fn instantiateGenericCall( const fn_nav = ip.getNav(generic_owner_func.owner_nav); const fn_cau = ip.getCau(fn_nav.analysis_owner.unwrap().?); const fn_zir = zcu.namespacePtr(fn_cau.namespace).fileScope(zcu).zir; - const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip)); + const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip) orelse return error.AnalysisFail); const comptime_args = try sema.arena.alloc(InternPool.Index, args_info.count()); @memset(comptime_args, .none); @@ -8346,7 +8299,7 @@ fn instantiateGenericCall( .name = if (child_block.ownerModule().strip) .none else - @enumFromInt(try sema.appendAirString(fn_zir.nullTerminatedString(param_name))), + try sema.appendAirString(fn_zir.nullTerminatedString(param_name)), } }, })); try child_block.params.append(sema.arena, .{ @@ -9381,7 +9334,7 @@ fn zirFunc( break :cau generic_owner_nav.analysis_owner.unwrap().?; } else sema.owner.unwrap().cau; const fn_is_exported = exported: { - const decl_inst = ip.getCau(func_decl_cau).zir_index.resolve(ip); + const decl_inst = ip.getCau(func_decl_cau).zir_index.resolve(ip) orelse return error.AnalysisFail; const zir_decl = sema.code.getDeclaration(decl_inst)[0]; break :exported zir_decl.flags.is_export; }; @@ -13929,12 +13882,6 @@ fn zirHasDecl(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air }); try sema.checkNamespaceType(block, lhs_src, container_type); - if (container_type.typeDeclInst(mod)) |type_decl_inst| { - try sema.declareDependency(.{ .namespace_name = .{ - .namespace = type_decl_inst, - .name = decl_name, - } }); - } const namespace = container_type.getNamespace(mod).unwrap() orelse return .bool_false; if (try sema.lookupInNamespace(block, src, namespace, decl_name, true)) |lookup| { @@ -13974,7 +13921,10 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. // That way, if this returns `error.AnalysisFail`, we have the dependency banked ready to // trigger re-analysis later. try pt.ensureFileAnalyzed(result.file_index); - return Air.internedToRef(zcu.fileRootType(result.file_index)); + const ty = zcu.fileRootType(result.file_index); + try sema.declareDependency(.{ .interned = ty }); + try sema.addTypeReferenceEntry(operand_src, ty); + return Air.internedToRef(ty); } fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -17638,7 +17588,13 @@ fn zirThis( _ = extended; const pt = sema.pt; const namespace = pt.zcu.namespacePtr(block.namespace); - return Air.internedToRef(namespace.owner_type); + const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type, false); + switch (pt.zcu.intern_pool.indexToKey(new_ty)) { + .struct_type, .union_type, .enum_type => try sema.declareDependency(.{ .interned = new_ty }), + .opaque_type => {}, + else => unreachable, + } + return Air.internedToRef(new_ty); } fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { @@ -17663,7 +17619,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat const msg = msg: { const name = name: { // TODO: we should probably store this name in the ZIR to avoid this complexity. - const file, const src_base_node = Module.LazySrcLoc.resolveBaseNode(block.src_base_inst, mod); + const file, const src_base_node = Module.LazySrcLoc.resolveBaseNode(block.src_base_inst, mod).?; const tree = file.getTree(sema.gpa) catch |err| { // In this case we emit a warning + a less precise source location. log.warn("unable to load {s}: {s}", .{ @@ -17691,7 +17647,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat if (!block.is_typeof and !block.is_comptime and sema.func_index != .none) { const msg = msg: { const name = name: { - const file, const src_base_node = Module.LazySrcLoc.resolveBaseNode(block.src_base_inst, mod); + const file, const src_base_node = Module.LazySrcLoc.resolveBaseNode(block.src_base_inst, mod).?; const tree = file.getTree(sema.gpa) catch |err| { // In this case we emit a warning + a less precise source location. log.warn("unable to load {s}: {s}", .{ @@ -18940,6 +18896,7 @@ fn typeInfoNamespaceDecls( const ip = &zcu.intern_pool; const namespace_index = opt_namespace_index.unwrap() orelse return; + try pt.ensureNamespaceUpToDate(namespace_index); const namespace = zcu.namespacePtr(namespace_index); const gop = try seen_namespaces.getOrPut(namespace); @@ -21786,7 +21743,10 @@ fn zirReify( .zir_index = try block.trackZir(inst), } }, })) { - .existing => |ty| return Air.internedToRef(ty), + .existing => |ty| { + try sema.addTypeReferenceEntry(src, ty); + return Air.internedToRef(ty); + }, .wip => |wip| wip, }; errdefer wip_ty.cancel(ip, pt.tid); @@ -21803,8 +21763,10 @@ fn zirReify( .parent = block.namespace.toOptional(), .owner_type = wip_ty.index, .file_scope = block.getFileScopeIndex(mod), + .generation = mod.generation, }); + try sema.addTypeReferenceEntry(src, wip_ty.index); return Air.internedToRef(wip_ty.finish(ip, .none, new_namespace_index)); }, .Union => { @@ -21984,11 +21946,16 @@ fn reifyEnum( .zir_index = tracked_inst, .type_hash = hasher.final(), } }, - })) { + }, false)) { .wip => |wip| wip, - .existing => |ty| return Air.internedToRef(ty), + .existing => |ty| { + try sema.declareDependency(.{ .interned = ty }); + try sema.addTypeReferenceEntry(src, ty); + return Air.internedToRef(ty); + }, }; - errdefer wip_ty.cancel(ip, pt.tid); + var done = false; + errdefer if (!done) wip_ty.cancel(ip, pt.tid); if (tag_ty.zigTypeTag(mod) != .Int) { return sema.fail(block, src, "Type.Enum.tag_type must be an integer type", .{}); @@ -22006,12 +21973,16 @@ fn reifyEnum( .parent = block.namespace.toOptional(), .owner_type = wip_ty.index, .file_scope = block.getFileScopeIndex(mod), + .generation = mod.generation, }); const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); + try sema.declareDependency(.{ .interned = wip_ty.index }); + try sema.addTypeReferenceEntry(src, wip_ty.index); wip_ty.prepare(ip, new_cau_index, new_namespace_index); wip_ty.setTagTy(ip, tag_ty.toIntern()); + done = true; for (0..fields_len) |field_idx| { const field_info = try fields_val.elemValue(pt, field_idx); @@ -22056,6 +22027,11 @@ fn reifyEnum( return sema.fail(block, src, "non-exhaustive enum specified every value", .{}); } + codegen_type: { + if (mod.comp.config.use_llvm) break :codegen_type; + if (block.ownerModule().strip) break :codegen_type; + try mod.comp.queueJob(.{ .codegen_type = wip_ty.index }); + } return Air.internedToRef(wip_ty.index); } @@ -22141,9 +22117,13 @@ fn reifyUnion( .zir_index = tracked_inst, .type_hash = hasher.final(), } }, - })) { + }, false)) { .wip => |wip| wip, - .existing => |ty| return Air.internedToRef(ty), + .existing => |ty| { + try sema.declareDependency(.{ .interned = ty }); + try sema.addTypeReferenceEntry(src, ty); + return Air.internedToRef(ty); + }, }; errdefer wip_ty.cancel(ip, pt.tid); @@ -22298,12 +22278,19 @@ fn reifyUnion( .parent = block.namespace.toOptional(), .owner_type = wip_ty.index, .file_scope = block.getFileScopeIndex(mod), + .generation = mod.generation, }); const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); try mod.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .cau = new_cau_index })); + codegen_type: { + if (mod.comp.config.use_llvm) break :codegen_type; + if (block.ownerModule().strip) break :codegen_type; + try mod.comp.queueJob(.{ .codegen_type = wip_ty.index }); + } + try sema.declareDependency(.{ .interned = wip_ty.index }); + try sema.addTypeReferenceEntry(src, wip_ty.index); return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); } @@ -22401,9 +22388,13 @@ fn reifyStruct( .zir_index = tracked_inst, .type_hash = hasher.final(), } }, - })) { + }, false)) { .wip => |wip| wip, - .existing => |ty| return Air.internedToRef(ty), + .existing => |ty| { + try sema.declareDependency(.{ .interned = ty }); + try sema.addTypeReferenceEntry(src, ty); + return Air.internedToRef(ty); + }, }; errdefer wip_ty.cancel(ip, pt.tid); @@ -22571,12 +22562,19 @@ fn reifyStruct( .parent = block.namespace.toOptional(), .owner_type = wip_ty.index, .file_scope = block.getFileScopeIndex(mod), + .generation = mod.generation, }); const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); try mod.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .cau = new_cau_index })); + codegen_type: { + if (mod.comp.config.use_llvm) break :codegen_type; + if (block.ownerModule().strip) break :codegen_type; + try mod.comp.queueJob(.{ .codegen_type = wip_ty.index }); + } + try sema.declareDependency(.{ .interned = wip_ty.index }); + try sema.addTypeReferenceEntry(src, wip_ty.index); return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); } @@ -26075,7 +26073,7 @@ fn zirVarExtended( const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); const decl_inst, const decl_bodies = decl: { - const decl_inst = sema.getOwnerCauDeclInst().resolve(ip); + const decl_inst = sema.getOwnerCauDeclInst().resolve(ip) orelse return error.AnalysisFail; const zir_decl, const extra_end = sema.code.getDeclaration(decl_inst); break :decl .{ decl_inst, zir_decl.getBodies(extra_end, sema.code) }; }; @@ -26202,7 +26200,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A const body = sema.code.bodySlice(extra_index, body_len); extra_index += body.len; - const addrspace_ty = Type.fromInterned(.address_space_type); + const addrspace_ty = try pt.getBuiltinType("AddressSpace"); const val = try sema.resolveGenericBody(block, addrspace_src, body, inst, addrspace_ty, .{ .needed_comptime_reason = "addrspace must be comptime-known", }); @@ -26213,7 +26211,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A } else if (extra.data.bits.has_addrspace_ref) blk: { const addrspace_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]); extra_index += 1; - const addrspace_ty = Type.fromInterned(.address_space_type); + const addrspace_ty = try pt.getBuiltinType("AddressSpace"); const uncoerced_addrspace = sema.resolveInst(addrspace_ref) catch |err| switch (err) { error.GenericPoison => break :blk null, else => |e| return e, @@ -26278,7 +26276,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A } else if (extra.data.bits.has_cc_ref) blk: { const cc_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]); extra_index += 1; - const cc_ty = Type.fromInterned(.calling_convention_type); + const cc_ty = try pt.getBuiltinType("CallingConvention"); const uncoerced_cc = sema.resolveInst(cc_ref) catch |err| switch (err) { error.GenericPoison => break :blk null, else => |e| return e, @@ -26304,7 +26302,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A break :decl_inst cau.zir_index; } else sema.getOwnerCauDeclInst(); // not an instantiation so we're analyzing a function declaration Cau - const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&mod.intern_pool))[0]; + const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&mod.intern_pool) orelse return error.AnalysisFail)[0]; if (zir_decl.flags.is_export) { break :cc .C; } @@ -26736,6 +26734,46 @@ fn zirInComptime( return if (block.is_comptime) .bool_true else .bool_false; } +fn zirBuiltinValue(sema: *Sema, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { + const pt = sema.pt; + const value: Zir.Inst.BuiltinValue = @enumFromInt(extended.small); + const type_name = switch (value) { + .atomic_order => "AtomicOrder", + .atomic_rmw_op => "AtomicRmwOp", + .calling_convention => "CallingConvention", + .address_space => "AddressSpace", + .float_mode => "FloatMode", + .reduce_op => "ReduceOp", + .call_modifier => "CallModifier", + .prefetch_options => "PrefetchOptions", + .export_options => "ExportOptions", + .extern_options => "ExternOptions", + .type_info => "Type", + + // Values are handled here. + .calling_convention_c => { + const callconv_ty = try pt.getBuiltinType("CallingConvention"); + comptime assert(@intFromEnum(std.builtin.CallingConvention.C) == 1); + const val = try pt.intern(.{ .enum_tag = .{ + .ty = callconv_ty.toIntern(), + .int = .one_u8, + } }); + return Air.internedToRef(val); + }, + .calling_convention_inline => { + const callconv_ty = try pt.getBuiltinType("CallingConvention"); + comptime assert(@intFromEnum(std.builtin.CallingConvention.Inline) == 4); + const val = try pt.intern(.{ .enum_tag = .{ + .ty = callconv_ty.toIntern(), + .int = .four_u8, + } }); + return Air.internedToRef(val); + }, + }; + const ty = try pt.getBuiltinType(type_name); + return Air.internedToRef(ty.toIntern()); +} + fn requireRuntimeBlock(sema: *Sema, block: *Block, src: LazySrcLoc, runtime_src: ?LazySrcLoc) !void { if (block.is_comptime) { const msg = msg: { @@ -27609,13 +27647,6 @@ fn fieldVal( const val = (try sema.resolveDefinedValue(block, object_src, dereffed_type)).?; const child_type = val.toType(); - if (child_type.typeDeclInst(mod)) |type_decl_inst| { - try sema.declareDependency(.{ .namespace_name = .{ - .namespace = type_decl_inst, - .name = field_name, - } }); - } - switch (try child_type.zigTypeTagOrPoison(mod)) { .ErrorSet => { switch (ip.indexToKey(child_type.toIntern())) { @@ -27847,13 +27878,6 @@ fn fieldPtr( const val = (sema.resolveDefinedValue(block, src, inner) catch unreachable).?; const child_type = val.toType(); - if (child_type.typeDeclInst(mod)) |type_decl_inst| { - try sema.declareDependency(.{ .namespace_name = .{ - .namespace = type_decl_inst, - .name = field_name, - } }); - } - switch (child_type.zigTypeTag(mod)) { .ErrorSet => { switch (ip.indexToKey(child_type.toIntern())) { @@ -32173,7 +32197,7 @@ fn addReferenceEntry( referenced_unit: AnalUnit, ) !void { const zcu = sema.pt.zcu; - if (zcu.comp.reference_trace == 0) return; + if (!zcu.comp.incremental and zcu.comp.reference_trace == 0) return; const gop = try sema.references.getOrPut(sema.gpa, referenced_unit); if (gop.found_existing) return; // TODO: we need to figure out how to model inline calls here. @@ -32182,6 +32206,18 @@ fn addReferenceEntry( try zcu.addUnitReference(sema.owner, referenced_unit, src); } +fn addTypeReferenceEntry( + sema: *Sema, + src: LazySrcLoc, + referenced_type: InternPool.Index, +) !void { + const zcu = sema.pt.zcu; + if (!zcu.comp.incremental and zcu.comp.reference_trace == 0) return; + const gop = try sema.type_references.getOrPut(sema.gpa, referenced_type); + if (gop.found_existing) return; + try zcu.addTypeReference(sema.owner, referenced_type, src); +} + pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index) CompileError!void { const pt = sema.pt; const zcu = pt.zcu; @@ -32552,6 +32588,7 @@ fn analyzeIsNonErrComptimeOnly( // If the error set is empty, we must return a comptime true or false. // However we want to avoid unnecessarily resolving an inferred error set // in case it is already non-empty. + try mod.maybeUnresolveIes(func_index); switch (ip.funcIesResolvedUnordered(func_index)) { .anyerror_type => break :blk, .none => {}, @@ -33621,6 +33658,7 @@ fn wrapErrorUnionSet( .inferred_error_set_type => |func_index| ok: { // We carefully do this in an order that avoids unnecessarily // resolving the destination error set type. + try mod.maybeUnresolveIes(func_index); switch (ip.funcIesResolvedUnordered(func_index)) { .anyerror_type => break :ok, .none => if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, dest_err_set_ty, inst_ty, inst_src, inst_src)) { @@ -35273,7 +35311,7 @@ pub fn resolveStructLayout(sema: *Sema, ty: Type) SemaError!void { if (struct_type.haveLayout(ip)) return; - try ty.resolveFields(pt); + try sema.resolveTypeFieldsStruct(ty.toIntern(), struct_type); if (struct_type.layout == .@"packed") { semaBackingIntType(pt, struct_type) catch |err| switch (err) { @@ -35455,7 +35493,7 @@ fn semaBackingIntType(pt: Zcu.PerThread, struct_type: InternPool.LoadedStructTyp break :blk accumulator; }; - const zir_index = struct_type.zir_index.unwrap().?.resolve(ip); + const zir_index = struct_type.zir_index.unwrap().?.resolve(ip) orelse return error.AnalysisFail; const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended; assert(extended.opcode == .struct_decl); const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); @@ -35878,8 +35916,7 @@ fn resolveInferredErrorSet( try sema.declareDependency(.{ .interned = func_index }); // resolved IES - // TODO: during an incremental update this might not be `.none`, but the - // function might be out-of-date! + try zcu.maybeUnresolveIes(func_index); const resolved_ty = func.resolvedErrorSetUnordered(ip); if (resolved_ty != .none) return resolved_ty; @@ -36070,7 +36107,7 @@ fn semaStructFields( const cau_index = struct_type.cau.unwrap().?; const namespace_index = ip.getCau(cau_index).namespace; const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir; - const zir_index = struct_type.zir_index.unwrap().?.resolve(ip); + const zir_index = struct_type.zir_index.unwrap().?.resolve(ip) orelse return error.AnalysisFail; const fields_len, const small, var extra_index = structZirInfo(zir, zir_index); @@ -36293,7 +36330,7 @@ fn semaStructFieldInits( const cau_index = struct_type.cau.unwrap().?; const namespace_index = ip.getCau(cau_index).namespace; const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir; - const zir_index = struct_type.zir_index.unwrap().?.resolve(ip); + const zir_index = struct_type.zir_index.unwrap().?.resolve(ip) orelse return error.AnalysisFail; const fields_len, const small, var extra_index = structZirInfo(zir, zir_index); var comptime_err_ret_trace = std.ArrayList(LazySrcLoc).init(gpa); @@ -36427,7 +36464,7 @@ fn semaUnionFields(pt: Zcu.PerThread, arena: Allocator, union_ty: InternPool.Ind const ip = &zcu.intern_pool; const cau_index = union_type.cau; const zir = zcu.namespacePtr(union_type.namespace).fileScope(zcu).zir; - const zir_index = union_type.zir_index.resolve(ip); + const zir_index = union_type.zir_index.resolve(ip) orelse return error.AnalysisFail; const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended; assert(extended.opcode == .union_decl); const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small); @@ -36541,11 +36578,11 @@ fn semaUnionFields(pt: Zcu.PerThread, arena: Allocator, union_ty: InternPool.Ind } } else { // The provided type is the enum tag type. - union_type.setTagType(ip, provided_ty.toIntern()); const enum_type = switch (ip.indexToKey(provided_ty.toIntern())) { .enum_type => ip.loadEnumType(provided_ty.toIntern()), else => return sema.fail(&block_scope, tag_ty_src, "expected enum tag type, found '{}'", .{provided_ty.fmt(pt)}), }; + union_type.setTagType(ip, provided_ty.toIntern()); // The fields of the union must match the enum exactly. // A flag per field is used to check for missing and extraneous fields. explicit_tags_seen = try sema.arena.alloc(bool, enum_type.names.len); @@ -36923,17 +36960,6 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value { .comptime_int_type, .comptime_float_type, .enum_literal_type, - .atomic_order_type, - .atomic_rmw_op_type, - .calling_convention_type, - .address_space_type, - .float_mode_type, - .reduce_op_type, - .call_modifier_type, - .prefetch_options_type, - .export_options_type, - .extern_options_type, - .type_info_type, .manyptr_u8_type, .manyptr_const_u8_type, .manyptr_const_u8_sentinel_0_type, @@ -36960,8 +36986,6 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value { .one_u8, .four_u8, .negative_one, - .calling_convention_c, - .calling_convention_inline, .void_value, .unreachable_value, .null_value, @@ -37315,7 +37339,8 @@ pub fn analyzeAsAddressSpace( ) !std.builtin.AddressSpace { const pt = sema.pt; const mod = pt.zcu; - const coerced = try sema.coerce(block, Type.fromInterned(.address_space_type), air_ref, src); + const addrspace_ty = try pt.getBuiltinType("AddressSpace"); + const coerced = try sema.coerce(block, addrspace_ty, air_ref, src); const addrspace_val = try sema.resolveConstDefinedValue(block, src, coerced, .{ .needed_comptime_reason = "address space must be comptime-known", }); @@ -38173,6 +38198,9 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { const zcu = sema.pt.zcu; if (!zcu.comp.incremental) return; + const gop = try sema.dependencies.getOrPut(sema.gpa, dependee); + if (gop.found_existing) return; + // Avoid creating dependencies on ourselves. This situation can arise when we analyze the fields // of a type and they use `@This()`. This dependency would be unnecessary, and in fact would // just result in over-analysis since `Zcu.findOutdatedToAnalyze` would never be able to resolve @@ -38396,6 +38424,187 @@ fn getOwnerFuncDeclInst(sema: *Sema) InternPool.TrackedInst.Index { return ip.getCau(cau).zir_index; } +/// Called as soon as a `declared` enum type is created. +/// Resolves the tag type and field inits. +/// Marks the `src_inst` dependency on the enum's declaration, so call sites need not do this. +pub fn resolveDeclaredEnum( + pt: Zcu.PerThread, + wip_ty: InternPool.WipEnumType, + inst: Zir.Inst.Index, + tracked_inst: InternPool.TrackedInst.Index, + namespace: InternPool.NamespaceIndex, + type_name: InternPool.NullTerminatedString, + enum_cau: InternPool.Cau.Index, + small: Zir.Inst.EnumDecl.Small, + body: []const Zir.Inst.Index, + tag_type_ref: Zir.Inst.Ref, + any_values: bool, + fields_len: u32, + zir: Zir, + body_end: usize, +) Zcu.CompileError!void { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable; + + const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(0) }; + const tag_ty_src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = .{ .node_offset_container_tag = 0 } }; + + const anal_unit = AnalUnit.wrap(.{ .cau = enum_cau }); + + var arena = std.heap.ArenaAllocator.init(gpa); + defer arena.deinit(); + + var comptime_err_ret_trace = std.ArrayList(Zcu.LazySrcLoc).init(gpa); + defer comptime_err_ret_trace.deinit(); + + var sema: Sema = .{ + .pt = pt, + .gpa = gpa, + .arena = arena.allocator(), + .code = zir, + .owner = anal_unit, + .func_index = .none, + .func_is_naked = false, + .fn_ret_ty = Type.void, + .fn_ret_ty_ies = null, + .comptime_err_ret_trace = &comptime_err_ret_trace, + }; + defer sema.deinit(); + + try sema.declareDependency(.{ .src_hash = tracked_inst }); + + var block: Block = .{ + .parent = null, + .sema = &sema, + .namespace = namespace, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + .src_base_inst = tracked_inst, + .type_name_ctx = type_name, + }; + defer block.instructions.deinit(gpa); + + const int_tag_ty = ty: { + if (body.len != 0) { + _ = try sema.analyzeInlineBody(&block, body, inst); + } + + if (tag_type_ref != .none) { + const ty = try sema.resolveType(&block, tag_ty_src, tag_type_ref); + if (ty.zigTypeTag(zcu) != .Int and ty.zigTypeTag(zcu) != .ComptimeInt) { + return sema.fail(&block, tag_ty_src, "expected integer tag type, found '{}'", .{ty.fmt(pt)}); + } + break :ty ty; + } else if (fields_len == 0) { + break :ty try pt.intType(.unsigned, 0); + } else { + const bits = std.math.log2_int_ceil(usize, fields_len); + break :ty try pt.intType(.unsigned, bits); + } + }; + + wip_ty.setTagTy(ip, int_tag_ty.toIntern()); + + if (small.nonexhaustive and int_tag_ty.toIntern() != .comptime_int_type) { + if (fields_len > 1 and std.math.log2_int(u64, fields_len) == int_tag_ty.bitSize(pt)) { + return sema.fail(&block, src, "non-exhaustive enum specifies every value", .{}); + } + } + + var extra_index = body_end + bit_bags_count; + var bit_bag_index: usize = body_end; + var cur_bit_bag: u32 = undefined; + var last_tag_val: ?Value = null; + for (0..fields_len) |field_i_usize| { + const field_i: u32 = @intCast(field_i_usize); + if (field_i % 32 == 0) { + cur_bit_bag = zir.extra[bit_bag_index]; + bit_bag_index += 1; + } + const has_tag_value = @as(u1, @truncate(cur_bit_bag)) != 0; + cur_bit_bag >>= 1; + + const field_name_index: Zir.NullTerminatedString = @enumFromInt(zir.extra[extra_index]); + const field_name_zir = zir.nullTerminatedString(field_name_index); + extra_index += 2; // field name, doc comment + + const field_name = try ip.getOrPutString(gpa, pt.tid, field_name_zir, .no_embedded_nulls); + + const value_src: LazySrcLoc = .{ + .base_node_inst = tracked_inst, + .offset = .{ .container_field_value = field_i }, + }; + + const tag_overflow = if (has_tag_value) overflow: { + const tag_val_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]); + extra_index += 1; + const tag_inst = try sema.resolveInst(tag_val_ref); + last_tag_val = try sema.resolveConstDefinedValue(&block, .{ + .base_node_inst = tracked_inst, + .offset = .{ .container_field_name = field_i }, + }, tag_inst, .{ + .needed_comptime_reason = "enum tag value must be comptime-known", + }); + if (!(try sema.intFitsInType(last_tag_val.?, int_tag_ty, null))) break :overflow true; + last_tag_val = try pt.getCoerced(last_tag_val.?, int_tag_ty); + if (wip_ty.nextField(ip, field_name, last_tag_val.?.toIntern())) |conflict| { + assert(conflict.kind == .value); // AstGen validated names are unique + const other_field_src: LazySrcLoc = .{ + .base_node_inst = tracked_inst, + .offset = .{ .container_field_value = conflict.prev_field_idx }, + }; + const msg = msg: { + const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValueSema(pt, &sema)}); + errdefer msg.destroy(gpa); + try sema.errNote(other_field_src, msg, "other occurrence here", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(&block, msg); + } + break :overflow false; + } else if (any_values) overflow: { + var overflow: ?usize = null; + last_tag_val = if (last_tag_val) |val| + try sema.intAdd(val, try pt.intValue(int_tag_ty, 1), int_tag_ty, &overflow) + else + try pt.intValue(int_tag_ty, 0); + if (overflow != null) break :overflow true; + if (wip_ty.nextField(ip, field_name, last_tag_val.?.toIntern())) |conflict| { + assert(conflict.kind == .value); // AstGen validated names are unique + const other_field_src: LazySrcLoc = .{ + .base_node_inst = tracked_inst, + .offset = .{ .container_field_value = conflict.prev_field_idx }, + }; + const msg = msg: { + const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValueSema(pt, &sema)}); + errdefer msg.destroy(gpa); + try sema.errNote(other_field_src, msg, "other occurrence here", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(&block, msg); + } + break :overflow false; + } else overflow: { + assert(wip_ty.nextField(ip, field_name, .none) == null); + last_tag_val = try pt.intValue(Type.comptime_int, field_i); + if (!try sema.intFitsInType(last_tag_val.?, int_tag_ty, null)) break :overflow true; + last_tag_val = try pt.getCoerced(last_tag_val.?, int_tag_ty); + break :overflow false; + }; + + if (tag_overflow) { + const msg = try sema.errMsg(value_src, "enumeration value '{}' too large for type '{}'", .{ + last_tag_val.?.fmtValueSema(pt, &sema), int_tag_ty.fmt(pt), + }); + return sema.failWithOwnedErrorMsg(&block, msg); + } + } +} + pub const bitCastVal = @import("Sema/bitcast.zig").bitCast; pub const bitCastSpliceVal = @import("Sema/bitcast.zig").bitCastSplice; diff --git a/zig/src/Type.zig b/zig/src/Type.zig index f2e51614e8..4437722f7d 100644 --- a/zig/src/Type.zig +++ b/zig/src/Type.zig @@ -202,6 +202,7 @@ pub fn print(ty: Type, writer: anytype, pt: Zcu.PerThread) @TypeOf(writer).Error .C => try writer.writeAll("[*c]"), .Slice => try writer.writeAll("[]"), } + if (info.flags.is_allowzero and info.flags.size != .C) try writer.writeAll("allowzero "); if (info.flags.alignment != .none or info.packed_offset.host_size != 0 or info.flags.vector_index != .none) @@ -229,7 +230,6 @@ pub fn print(ty: Type, writer: anytype, pt: Zcu.PerThread) @TypeOf(writer).Error } if (info.flags.is_const) try writer.writeAll("const "); if (info.flags.is_volatile) try writer.writeAll("volatile "); - if (info.flags.is_allowzero and info.flags.size != .C) try writer.writeAll("allowzero "); try print(Type.fromInterned(info.child), writer, pt); return; @@ -316,17 +316,6 @@ pub fn print(ty: Type, writer: anytype, pt: Zcu.PerThread) @TypeOf(writer).Error => try writer.print("@TypeOf({s})", .{@tagName(s)}), .enum_literal => try writer.print("@TypeOf(.{s})", .{@tagName(s)}), - .atomic_order => try writer.writeAll("std.builtin.AtomicOrder"), - .atomic_rmw_op => try writer.writeAll("std.builtin.AtomicRmwOp"), - .calling_convention => try writer.writeAll("std.builtin.CallingConvention"), - .address_space => try writer.writeAll("std.builtin.AddressSpace"), - .float_mode => try writer.writeAll("std.builtin.FloatMode"), - .reduce_op => try writer.writeAll("std.builtin.ReduceOp"), - .call_modifier => try writer.writeAll("std.builtin.CallModifier"), - .prefetch_options => try writer.writeAll("std.builtin.PrefetchOptions"), - .export_options => try writer.writeAll("std.builtin.ExportOptions"), - .extern_options => try writer.writeAll("std.builtin.ExternOptions"), - .type_info => try writer.writeAll("std.builtin.Type"), .generic_poison => unreachable, }, @@ -544,16 +533,6 @@ pub fn hasRuntimeBitsAdvanced( .anyerror, .adhoc_inferred_error_set, .anyopaque, - .atomic_order, - .atomic_rmw_op, - .calling_convention, - .address_space, - .float_mode, - .reduce_op, - .call_modifier, - .prefetch_options, - .export_options, - .extern_options, => true, // These are false because they are comptime-only types. @@ -565,7 +544,6 @@ pub fn hasRuntimeBitsAdvanced( .null, .undefined, .enum_literal, - .type_info, => false, .generic_poison => unreachable, @@ -711,16 +689,6 @@ pub fn hasWellDefinedLayout(ty: Type, mod: *Module) bool { .anyerror, .adhoc_inferred_error_set, .anyopaque, - .atomic_order, - .atomic_rmw_op, - .calling_convention, - .address_space, - .float_mode, - .reduce_op, - .call_modifier, - .prefetch_options, - .export_options, - .extern_options, .type, .comptime_int, .comptime_float, @@ -728,7 +696,6 @@ pub fn hasWellDefinedLayout(ty: Type, mod: *Module) bool { .null, .undefined, .enum_literal, - .type_info, .generic_poison, => false, }, @@ -972,14 +939,6 @@ pub fn abiAlignmentAdvanced( .simple_type => |t| switch (t) { .bool, - .atomic_order, - .atomic_rmw_op, - .calling_convention, - .address_space, - .float_mode, - .reduce_op, - .call_modifier, - .prefetch_options, .anyopaque, => return .{ .scalar = .@"1" }, @@ -987,11 +946,6 @@ pub fn abiAlignmentAdvanced( .isize, => return .{ .scalar = intAbiAlignment(target.ptrBitWidth(), target, use_llvm) }, - .export_options, - .extern_options, - .type_info, - => return .{ .scalar = ptrAbiAlignment(target) }, - .c_char => return .{ .scalar = cTypeAlign(target, .char) }, .c_short => return .{ .scalar = cTypeAlign(target, .short) }, .c_ushort => return .{ .scalar = cTypeAlign(target, .ushort) }, @@ -1352,15 +1306,7 @@ pub fn abiSizeAdvanced( }, .func_type => unreachable, // represents machine code; not a pointer .simple_type => |t| switch (t) { - .bool, - .atomic_order, - .atomic_rmw_op, - .calling_convention, - .address_space, - .float_mode, - .reduce_op, - .call_modifier, - => return .{ .scalar = 1 }, + .bool => return .{ .scalar = 1 }, .f16 => return .{ .scalar = 2 }, .f32 => return .{ .scalar = 4 }, @@ -1402,11 +1348,6 @@ pub fn abiSizeAdvanced( return .{ .scalar = intAbiSize(bits, target, use_llvm) }; }, - .prefetch_options => unreachable, // missing call to resolveTypeFields - .export_options => unreachable, // missing call to resolveTypeFields - .extern_options => unreachable, // missing call to resolveTypeFields - - .type_info => unreachable, .noreturn => unreachable, .generic_poison => unreachable, }, @@ -1751,18 +1692,6 @@ pub fn bitSizeAdvanced( .undefined => unreachable, .enum_literal => unreachable, .generic_poison => unreachable, - - .atomic_order => unreachable, - .atomic_rmw_op => unreachable, - .calling_convention => unreachable, - .address_space => unreachable, - .float_mode => unreachable, - .reduce_op => unreachable, - .call_modifier => unreachable, - .prefetch_options => unreachable, - .export_options => unreachable, - .extern_options => unreachable, - .type_info => unreachable, }, .struct_type => { const struct_type = ip.loadStructType(ty.toIntern()); @@ -2208,7 +2137,7 @@ pub fn errorSetHasField(ty: Type, name: []const u8, mod: *Module) bool { const field_name_interned = ip.getString(name).unwrap() orelse return false; return error_set_type.nameIndex(ip, field_name_interned) != null; }, - .inferred_error_set_type => |i| switch (ip.funcIesResolved(i).*) { + .inferred_error_set_type => |i| switch (ip.funcIesResolvedUnordered(i)) { .anyerror_type => true, .none => false, else => |t| { @@ -2565,17 +2494,6 @@ pub fn onePossibleValue(starting_type: Type, pt: Zcu.PerThread) !?Value { .comptime_int, .comptime_float, .enum_literal, - .atomic_order, - .atomic_rmw_op, - .calling_convention, - .address_space, - .float_mode, - .reduce_op, - .call_modifier, - .prefetch_options, - .export_options, - .extern_options, - .type_info, .adhoc_inferred_error_set, => return null, @@ -2782,16 +2700,6 @@ pub fn comptimeOnlyAdvanced(ty: Type, pt: Zcu.PerThread, comptime strat: Resolve .adhoc_inferred_error_set, .noreturn, .generic_poison, - .atomic_order, - .atomic_rmw_op, - .calling_convention, - .address_space, - .float_mode, - .reduce_op, - .call_modifier, - .prefetch_options, - .export_options, - .extern_options, => false, .type, @@ -2800,7 +2708,6 @@ pub fn comptimeOnlyAdvanced(ty: Type, pt: Zcu.PerThread, comptime strat: Resolve .null, .undefined, .enum_literal, - .type_info, => true, }, .struct_type => { @@ -3437,7 +3344,7 @@ pub fn typeDeclSrcLine(ty: Type, zcu: *Zcu) ?u32 { }, else => return null, }; - const info = tracked.resolveFull(&zcu.intern_pool); + const info = tracked.resolveFull(&zcu.intern_pool) orelse return null; const file = zcu.fileByIndex(info.file); assert(file.zir_loaded); const zir = file.zir; @@ -3534,10 +3441,6 @@ pub fn packedStructFieldPtrInfo(struct_ty: Type, parent_ptr_ty: Type, field_idx: pub fn resolveLayout(ty: Type, pt: Zcu.PerThread) SemaError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; - switch (ip.indexToKey(ty.toIntern())) { - .simple_type => |simple_type| return resolveSimpleType(simple_type, pt), - else => {}, - } switch (ty.zigTypeTag(zcu)) { .Struct => switch (ip.indexToKey(ty.toIntern())) { .anon_struct_type => |anon_struct_type| for (0..anon_struct_type.types.len) |i| { @@ -3651,8 +3554,6 @@ pub fn resolveFields(ty: Type, pt: Zcu.PerThread) SemaError!void { .one_u8 => unreachable, .four_u8 => unreachable, .negative_one => unreachable, - .calling_convention_c => unreachable, - .calling_convention_inline => unreachable, .void_value => unreachable, .unreachable_value => unreachable, .null_value => unreachable, @@ -3669,8 +3570,6 @@ pub fn resolveFields(ty: Type, pt: Zcu.PerThread) SemaError!void { .type_union => return ty.resolveUnionInner(pt, .fields), - .simple_type => return resolveSimpleType(ip.indexToKey(ty_ip).simple_type, pt), - else => {}, }, } @@ -3680,11 +3579,6 @@ pub fn resolveFully(ty: Type, pt: Zcu.PerThread) SemaError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; - switch (ip.indexToKey(ty.toIntern())) { - .simple_type => |simple_type| return resolveSimpleType(simple_type, pt), - else => {}, - } - switch (ty.zigTypeTag(zcu)) { .Type, .Void, @@ -3850,28 +3744,6 @@ fn resolveUnionInner( }; } -/// Fully resolves a simple type. This is usually a nop, but for builtin types with -/// special InternPool indices (such as std.builtin.Type) it will analyze and fully -/// resolve the type. -fn resolveSimpleType(simple_type: InternPool.SimpleType, pt: Zcu.PerThread) Allocator.Error!void { - const builtin_type_name: []const u8 = switch (simple_type) { - .atomic_order => "AtomicOrder", - .atomic_rmw_op => "AtomicRmwOp", - .calling_convention => "CallingConvention", - .address_space => "AddressSpace", - .float_mode => "FloatMode", - .reduce_op => "ReduceOp", - .call_modifier => "CallModifer", - .prefetch_options => "PrefetchOptions", - .export_options => "ExportOptions", - .extern_options => "ExternOptions", - .type_info => "Type", - else => return, - }; - // This will fully resolve the type. - _ = try pt.getBuiltinType(builtin_type_name); -} - /// Returns the type of a pointer to an element. /// Asserts that the type is a pointer, and that the element type is indexable. /// If the element index is comptime-known, it must be passed in `offset`. diff --git a/zig/src/Zcu.zig b/zig/src/Zcu.zig index 10fa45a62b..9754740833 100644 --- a/zig/src/Zcu.zig +++ b/zig/src/Zcu.zig @@ -10,7 +10,7 @@ const builtin = @import("builtin"); const mem = std.mem; const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const log = std.log.scoped(.module); +const log = std.log.scoped(.zcu); const BigIntConst = std.math.big.int.Const; const BigIntMutable = std.math.big.int.Mutable; const Target = std.Target; @@ -153,27 +153,27 @@ cimport_errors: std.AutoArrayHashMapUnmanaged(AnalUnit, std.zig.ErrorBundle) = . /// Maximum amount of distinct error values, set by --error-limit error_limit: ErrorInt, -/// Value is the number of PO or outdated Decls which this AnalUnit depends on. +/// Value is the number of PO dependencies of this AnalUnit. +/// This value will decrease as we perform semantic analysis to learn what is outdated. +/// If any of these PO deps is outdated, this value will be moved to `outdated`. potentially_outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .{}, -/// Value is the number of PO or outdated Decls which this AnalUnit depends on. +/// Value is the number of PO dependencies of this AnalUnit. /// Once this value drops to 0, the AnalUnit is a candidate for re-analysis. outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .{}, /// This contains all `AnalUnit`s in `outdated` whose PO dependency count is 0. /// Such `AnalUnit`s are ready for immediate re-analysis. /// See `findOutdatedToAnalyze` for details. outdated_ready: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .{}, -/// This contains a set of struct types whose corresponding `Cau` may not be in -/// `outdated`, but are the root types of files which have updated source and -/// thus must be re-analyzed. If such a type is only in this set, the struct type -/// index may be preserved (only the namespace might change). If its owned `Cau` -/// is also outdated, the struct type index must be recreated. -outdated_file_root: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .{}, /// This contains a list of AnalUnit whose analysis or codegen failed, but the /// failure was something like running out of disk space, and trying again may /// succeed. On the next update, we will flush this list, marking all members of /// it as outdated. retryable_failures: std.ArrayListUnmanaged(AnalUnit) = .{}, +/// These are the modules which we initially queue for analysis in `Compilation.update`. +/// `resolveReferences` will use these as the root of its reachability traversal. +analysis_roots: std.BoundedArray(*Package.Module, 3) = .{}, + stage1_flags: packed struct { have_winmain: bool = false, have_wwinmain: bool = false, @@ -192,7 +192,7 @@ global_assembly: std.AutoArrayHashMapUnmanaged(InternPool.Cau.Index, []u8) = .{} /// Key is the `AnalUnit` *performing* the reference. This representation allows /// incremental updates to quickly delete references caused by a specific `AnalUnit`. -/// Value is index into `all_reference` of the first reference triggered by the unit. +/// Value is index into `all_references` of the first reference triggered by the unit. /// The `next` field on the `Reference` forms a linked list of all references /// triggered by the key `AnalUnit`. reference_table: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .{}, @@ -200,11 +200,23 @@ all_references: std.ArrayListUnmanaged(Reference) = .{}, /// Freelist of indices in `all_references`. free_references: std.ArrayListUnmanaged(u32) = .{}, +/// Key is the `AnalUnit` *performing* the reference. This representation allows +/// incremental updates to quickly delete references caused by a specific `AnalUnit`. +/// Value is index into `all_type_reference` of the first reference triggered by the unit. +/// The `next` field on the `TypeReference` forms a linked list of all type references +/// triggered by the key `AnalUnit`. +type_reference_table: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .{}, +all_type_references: std.ArrayListUnmanaged(TypeReference) = .{}, +/// Freelist of indices in `all_type_references`. +free_type_references: std.ArrayListUnmanaged(u32) = .{}, + panic_messages: [PanicId.len]InternPool.Nav.Index.Optional = .{.none} ** PanicId.len, /// The panic function body. panic_func_index: InternPool.Index = .none, null_stack_trace: InternPool.Index = .none, +generation: u32 = 0, + pub const PerThread = @import("Zcu/PerThread.zig"); pub const PanicId = enum { @@ -308,10 +320,21 @@ pub const Reference = struct { src: LazySrcLoc, }; +pub const TypeReference = struct { + /// The container type which was referenced. + referenced: InternPool.Index, + /// Index into `all_type_references` of the next `TypeReference` triggered by the same `AnalUnit`. + /// `std.math.maxInt(u32)` is the sentinel. + next: u32, + /// The source location of the reference. + src: LazySrcLoc, +}; + /// The container that structs, enums, unions, and opaques have. pub const Namespace = struct { parent: OptionalIndex, file_scope: File.Index, + generation: u32, /// Will be a struct, enum, union, or opaque. owner_type: InternPool.Index, /// Members of the namespace which are marked `pub`. @@ -2022,10 +2045,11 @@ pub const LazySrcLoc = struct { .offset = .unneeded, }; - pub fn resolveBaseNode(base_node_inst: InternPool.TrackedInst.Index, zcu: *Zcu) struct { *File, Ast.Node.Index } { + /// Returns `null` if the ZIR instruction has been lost across incremental updates. + pub fn resolveBaseNode(base_node_inst: InternPool.TrackedInst.Index, zcu: *Zcu) ?struct { *File, Ast.Node.Index } { const ip = &zcu.intern_pool; const file_index, const zir_inst = inst: { - const info = base_node_inst.resolveFull(ip); + const info = base_node_inst.resolveFull(ip) orelse return null; break :inst .{ info.file, info.inst }; }; const file = zcu.fileByIndex(file_index); @@ -2051,7 +2075,15 @@ pub const LazySrcLoc = struct { /// Resolve the file and AST node of `base_node_inst` to get a resolved `SrcLoc`. /// The resulting `SrcLoc` should only be used ephemerally, as it is not correct across incremental updates. pub fn upgrade(lazy: LazySrcLoc, zcu: *Zcu) SrcLoc { - const file, const base_node = resolveBaseNode(lazy.base_node_inst, zcu); + return lazy.upgradeOrLost(zcu).?; + } + + /// Like `upgrade`, but returns `null` if the source location has been lost across incremental updates. + pub fn upgradeOrLost(lazy: LazySrcLoc, zcu: *Zcu) ?SrcLoc { + const file, const base_node: Ast.Node.Index = if (lazy.offset == .entire_file) .{ + zcu.fileByIndex(lazy.base_node_inst.resolveFile(&zcu.intern_pool)), + 0, + } else resolveBaseNode(lazy.base_node_inst, zcu) orelse return null; return .{ .file_scope = file, .base_node = base_node, @@ -2148,7 +2180,6 @@ pub fn deinit(zcu: *Zcu) void { zcu.potentially_outdated.deinit(gpa); zcu.outdated.deinit(gpa); zcu.outdated_ready.deinit(gpa); - zcu.outdated_file_root.deinit(gpa); zcu.retryable_failures.deinit(gpa); zcu.test_functions.deinit(gpa); @@ -2162,6 +2193,10 @@ pub fn deinit(zcu: *Zcu) void { zcu.all_references.deinit(gpa); zcu.free_references.deinit(gpa); + zcu.type_reference_table.deinit(gpa); + zcu.all_type_references.deinit(gpa); + zcu.free_type_references.deinit(gpa); + zcu.intern_pool.deinit(gpa); } @@ -2255,55 +2290,89 @@ pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_file: std.fs.F return zir; } -pub fn markDependeeOutdated(zcu: *Zcu, dependee: InternPool.Dependee) !void { - log.debug("outdated dependee: {}", .{dependee}); +pub fn markDependeeOutdated( + zcu: *Zcu, + /// When we are diffing ZIR and marking things as outdated, we won't yet have marked the dependencies as PO. + /// However, when we discover during analysis that something was outdated, the `Dependee` was already + /// marked as PO, so we need to decrement the PO dep count for each depender. + marked_po: enum { not_marked_po, marked_po }, + dependee: InternPool.Dependee, +) !void { + log.debug("outdated dependee: {}", .{zcu.fmtDependee(dependee)}); var it = zcu.intern_pool.dependencyIterator(dependee); while (it.next()) |depender| { - if (zcu.outdated.contains(depender)) { - // We do not need to increment the PO dep count, as if the outdated - // dependee is a Decl, we had already marked this as PO. + if (zcu.outdated.getPtr(depender)) |po_dep_count| { + switch (marked_po) { + .not_marked_po => {}, + .marked_po => { + po_dep_count.* -= 1; + log.debug("outdated {} => already outdated {} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), po_dep_count.* }); + if (po_dep_count.* == 0) { + log.debug("outdated ready: {}", .{zcu.fmtAnalUnit(depender)}); + try zcu.outdated_ready.put(zcu.gpa, depender, {}); + } + }, + } continue; } const opt_po_entry = zcu.potentially_outdated.fetchSwapRemove(depender); + const new_po_dep_count = switch (marked_po) { + .not_marked_po => if (opt_po_entry) |e| e.value else 0, + .marked_po => if (opt_po_entry) |e| e.value - 1 else { + // This `AnalUnit` has already been re-analyzed this update, and registered a dependency + // on this thing, but already has sufficiently up-to-date information. Nothing to do. + continue; + }, + }; try zcu.outdated.putNoClobber( zcu.gpa, depender, - // We do not need to increment this count for the same reason as above. - if (opt_po_entry) |e| e.value else 0, + new_po_dep_count, ); - log.debug("outdated: {}", .{depender}); - if (opt_po_entry == null) { - // This is a new entry with no PO dependencies. + log.debug("outdated {} => new outdated {} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), new_po_dep_count }); + if (new_po_dep_count == 0) { + log.debug("outdated ready: {}", .{zcu.fmtAnalUnit(depender)}); try zcu.outdated_ready.put(zcu.gpa, depender, {}); } // If this is a Decl and was not previously PO, we must recursively // mark dependencies on its tyval as PO. if (opt_po_entry == null) { + assert(marked_po == .not_marked_po); try zcu.markTransitiveDependersPotentiallyOutdated(depender); } } } pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { + log.debug("up-to-date dependee: {}", .{zcu.fmtDependee(dependee)}); var it = zcu.intern_pool.dependencyIterator(dependee); while (it.next()) |depender| { if (zcu.outdated.getPtr(depender)) |po_dep_count| { // This depender is already outdated, but it now has one // less PO dependency! po_dep_count.* -= 1; + log.debug("up-to-date {} => {} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), po_dep_count.* }); if (po_dep_count.* == 0) { + log.debug("outdated ready: {}", .{zcu.fmtAnalUnit(depender)}); try zcu.outdated_ready.put(zcu.gpa, depender, {}); } continue; } // This depender is definitely at least PO, because this Decl was just analyzed // due to being outdated. - const ptr = zcu.potentially_outdated.getPtr(depender).?; + const ptr = zcu.potentially_outdated.getPtr(depender) orelse { + // This dependency has been registered during in-progress analysis, but the unit is + // not in `potentially_outdated` because analysis is in-progress. Nothing to do. + continue; + }; if (ptr.* > 1) { ptr.* -= 1; + log.debug("up-to-date {} => {} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), ptr.* }); continue; } + log.debug("up-to-date {} => {} po_deps=0 (up-to-date)", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender) }); + // This dependency is no longer PO, i.e. is known to be up-to-date. assert(zcu.potentially_outdated.swapRemove(depender)); // If this is a Decl, we must recursively mark dependencies on its tyval @@ -2323,14 +2392,16 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { /// in turn be PO, due to a dependency on the original AnalUnit's tyval or IES. fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUnit) !void { const ip = &zcu.intern_pool; - var it = ip.dependencyIterator(switch (maybe_outdated.unwrap()) { + const dependee: InternPool.Dependee = switch (maybe_outdated.unwrap()) { .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) { .nav => |nav| .{ .nav_val = nav }, // TODO: also `nav_ref` deps when introduced - .none, .type => return, // analysis of this `Cau` can't outdate any dependencies + .type => |ty| .{ .interned = ty }, + .none => return, // analysis of this `Cau` can't outdate any dependencies }, .func => |func_index| .{ .interned = func_index }, // IES - }); - + }; + log.debug("potentially outdated dependee: {}", .{zcu.fmtDependee(dependee)}); + var it = ip.dependencyIterator(dependee); while (it.next()) |po| { if (zcu.outdated.getPtr(po)) |po_dep_count| { // This dependency is already outdated, but it now has one more PO @@ -2339,14 +2410,17 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni _ = zcu.outdated_ready.swapRemove(po); } po_dep_count.* += 1; + log.debug("po {} => {} [outdated] po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po), po_dep_count.* }); continue; } if (zcu.potentially_outdated.getPtr(po)) |n| { // There is now one more PO dependency. n.* += 1; + log.debug("po {} => {} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po), n.* }); continue; } try zcu.potentially_outdated.putNoClobber(zcu.gpa, po, 1); + log.debug("po {} => {} po_deps=1", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po) }); // This AnalUnit was not already PO, so we must recursively mark its dependers as also PO. try zcu.markTransitiveDependersPotentiallyOutdated(po); } @@ -2355,9 +2429,11 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { if (!zcu.comp.incremental) return null; - if (true) @panic("TODO: findOutdatedToAnalyze"); - - if (zcu.outdated.count() == 0 and zcu.potentially_outdated.count() == 0) { + if (zcu.outdated.count() == 0) { + // Any units in `potentially_outdated` must just be stuck in loops with one another: none of those + // units have had any outdated dependencies so far, and all of their remaining PO deps are triggered + // by other units in `potentially_outdated`. So, we can safety assume those units up-to-date. + zcu.potentially_outdated.clearRetainingCapacity(); log.debug("findOutdatedToAnalyze: no outdated depender", .{}); return null; } @@ -2372,96 +2448,75 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { // In this case, we must defer to more complex logic below. if (zcu.outdated_ready.count() > 0) { - log.debug("findOutdatedToAnalyze: trivial '{s} {d}'", .{ - @tagName(zcu.outdated_ready.keys()[0].unwrap()), - switch (zcu.outdated_ready.keys()[0].unwrap()) { - inline else => |x| @intFromEnum(x), - }, - }); - return zcu.outdated_ready.keys()[0]; + const unit = zcu.outdated_ready.keys()[0]; + log.debug("findOutdatedToAnalyze: trivial {}", .{zcu.fmtAnalUnit(unit)}); + return unit; } - // Next, we will see if there is any outdated file root which was not in - // `outdated`. This set will be small (number of files changed in this - // update), so it's alright for us to just iterate here. - for (zcu.outdated_file_root.keys()) |file_decl| { - const decl_depender = AnalUnit.wrap(.{ .decl = file_decl }); - if (zcu.outdated.contains(decl_depender)) { - // Since we didn't hit this in the first loop, this Decl must have - // pending dependencies, so is ineligible. - continue; - } - if (zcu.potentially_outdated.contains(decl_depender)) { - // This Decl's struct may or may not need to be recreated depending - // on whether it is outdated. If we analyzed it now, we would have - // to assume it was outdated and recreate it! - continue; - } - log.debug("findOutdatedToAnalyze: outdated file root decl '{d}'", .{file_decl}); - return decl_depender; - } - - // There is no single AnalUnit which is ready for re-analysis. Instead, we - // must assume that some Decl with PO dependencies is outdated - e.g. in the - // above example we arbitrarily pick one of A or B. We should select a Decl, - // since a Decl is definitely responsible for the loop in the dependency - // graph (since you can't depend on a runtime function analysis!). + // There is no single AnalUnit which is ready for re-analysis. Instead, we must assume that some + // Cau with PO dependencies is outdated -- e.g. in the above example we arbitrarily pick one of + // A or B. We should select a Cau, since a Cau is definitely responsible for the loop in the + // dependency graph (since IES dependencies can't have loops). We should also, of course, not + // select a Cau owned by a `comptime` declaration, since you can't depend on those! - // The choice of this Decl could have a big impact on how much total - // analysis we perform, since if analysis concludes its tyval is unchanged, - // then other PO AnalUnit may be resolved as up-to-date. To hopefully avoid - // doing too much work, let's find a Decl which the most things depend on - - // the idea is that this will resolve a lot of loops (but this is only a - // heuristic). + // The choice of this Cau could have a big impact on how much total analysis we perform, since + // if analysis concludes any dependencies on its result are up-to-date, then other PO AnalUnit + // may be resolved as up-to-date. To hopefully avoid doing too much work, let's find a Decl + // which the most things depend on - the idea is that this will resolve a lot of loops (but this + // is only a heuristic). log.debug("findOutdatedToAnalyze: no trivial ready, using heuristic; {d} outdated, {d} PO", .{ zcu.outdated.count(), zcu.potentially_outdated.count(), }); - const Decl = {}; + const ip = &zcu.intern_pool; - var chosen_decl_idx: ?Decl.Index = null; - var chosen_decl_dependers: u32 = undefined; + var chosen_cau: ?InternPool.Cau.Index = null; + var chosen_cau_dependers: u32 = undefined; - for (zcu.outdated.keys()) |depender| { - const decl_index = switch (depender.unwrap()) { - .decl => |d| d, - .func => continue, - }; + inline for (.{ zcu.outdated.keys(), zcu.potentially_outdated.keys() }) |outdated_units| { + for (outdated_units) |unit| { + const cau = switch (unit.unwrap()) { + .cau => |cau| cau, + .func => continue, // a `func` definitely can't be causing the loop so it is a bad choice + }; + const cau_owner = ip.getCau(cau).owner; - var n: u32 = 0; - var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index }); - while (it.next()) |_| n += 1; + var n: u32 = 0; + var it = ip.dependencyIterator(switch (cau_owner.unwrap()) { + .none => continue, // there can be no dependencies on this `Cau` so it is a terrible choice + .type => |ty| .{ .interned = ty }, + .nav => |nav| .{ .nav_val = nav }, + }); + while (it.next()) |_| n += 1; - if (chosen_decl_idx == null or n > chosen_decl_dependers) { - chosen_decl_idx = decl_index; - chosen_decl_dependers = n; + if (chosen_cau == null or n > chosen_cau_dependers) { + chosen_cau = cau; + chosen_cau_dependers = n; + } } } - for (zcu.potentially_outdated.keys()) |depender| { - const decl_index = switch (depender.unwrap()) { - .decl => |d| d, - .func => continue, - }; - - var n: u32 = 0; - var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index }); - while (it.next()) |_| n += 1; - - if (chosen_decl_idx == null or n > chosen_decl_dependers) { - chosen_decl_idx = decl_index; - chosen_decl_dependers = n; + if (chosen_cau == null) { + for (zcu.outdated.keys(), zcu.outdated.values()) |o, opod| { + const func = o.unwrap().func; + const nav = zcu.funcInfo(func).owner_nav; + std.io.getStdErr().writer().print("outdated: func {}, nav {}, name '{}', [p]o deps {}\n", .{ func, nav, ip.getNav(nav).fqn.fmt(ip), opod }) catch {}; + } + for (zcu.potentially_outdated.keys(), zcu.potentially_outdated.values()) |o, opod| { + const func = o.unwrap().func; + const nav = zcu.funcInfo(func).owner_nav; + std.io.getStdErr().writer().print("po: func {}, nav {}, name '{}', [p]o deps {}\n", .{ func, nav, ip.getNav(nav).fqn.fmt(ip), opod }) catch {}; } } - log.debug("findOutdatedToAnalyze: heuristic returned Decl {d} ({d} dependers)", .{ - chosen_decl_idx.?, - chosen_decl_dependers, + log.debug("findOutdatedToAnalyze: heuristic returned '{}' ({d} dependers)", .{ + zcu.fmtAnalUnit(AnalUnit.wrap(.{ .cau = chosen_cau.? })), + chosen_cau_dependers, }); - return AnalUnit.wrap(.{ .decl = chosen_decl_idx.? }); + return AnalUnit.wrap(.{ .cau = chosen_cau.? }); } /// During an incremental update, before semantic analysis, call this to flush all values from @@ -2499,17 +2554,28 @@ pub fn mapOldZirToNew( var match_stack: std.ArrayListUnmanaged(MatchedZirDecl) = .{}; defer match_stack.deinit(gpa); - // Main struct inst is always matched - try match_stack.append(gpa, .{ - .old_inst = .main_struct_inst, - .new_inst = .main_struct_inst, - }); - // Used as temporary buffers for namespace declaration instructions - var old_decls = std.ArrayList(Zir.Inst.Index).init(gpa); - defer old_decls.deinit(); - var new_decls = std.ArrayList(Zir.Inst.Index).init(gpa); - defer new_decls.deinit(); + var old_decls: std.ArrayListUnmanaged(Zir.Inst.Index) = .{}; + defer old_decls.deinit(gpa); + var new_decls: std.ArrayListUnmanaged(Zir.Inst.Index) = .{}; + defer new_decls.deinit(gpa); + + // Map the main struct inst (and anything in its fields) + { + try old_zir.findDeclsRoot(gpa, &old_decls); + try new_zir.findDeclsRoot(gpa, &new_decls); + + assert(old_decls.items[0] == .main_struct_inst); + assert(new_decls.items[0] == .main_struct_inst); + + // We don't have any smart way of matching up these type declarations, so we always + // correlate them based on source order. + const n = @min(old_decls.items.len, new_decls.items.len); + try match_stack.ensureUnusedCapacity(gpa, n); + for (old_decls.items[0..n], new_decls.items[0..n]) |old_inst, new_inst| { + match_stack.appendAssumeCapacity(.{ .old_inst = old_inst, .new_inst = new_inst }); + } + } while (match_stack.popOrNull()) |match_item| { // Match the namespace declaration itself @@ -2583,7 +2649,7 @@ pub fn mapOldZirToNew( break :inst unnamed_tests.items[unnamed_test_idx]; }, _ => inst: { - const name_nts = new_decl.name.toString(old_zir).?; + const name_nts = new_decl.name.toString(new_zir).?; const name = new_zir.nullTerminatedString(name_nts); if (new_decl.name.isNamedTest(new_zir)) { break :inst named_tests.get(name) orelse continue; @@ -2596,11 +2662,11 @@ pub fn mapOldZirToNew( // Match the `declaration` instruction try inst_map.put(gpa, old_decl_inst, new_decl_inst); - // Find namespace declarations within this declaration - try old_zir.findDecls(&old_decls, old_decl_inst); - try new_zir.findDecls(&new_decls, new_decl_inst); + // Find container type declarations within this declaration + try old_zir.findDecls(gpa, &old_decls, old_decl_inst); + try new_zir.findDecls(gpa, &new_decls, new_decl_inst); - // We don't have any smart way of matching up these namespace declarations, so we always + // We don't have any smart way of matching up these type declarations, so we always // correlate them based on source order. const n = @min(old_decls.items.len, new_decls.items.len); try match_stack.ensureUnusedCapacity(gpa, n); @@ -2699,16 +2765,32 @@ pub fn deleteUnitExports(zcu: *Zcu, anal_unit: AnalUnit) void { pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void { const gpa = zcu.gpa; - const kv = zcu.reference_table.fetchSwapRemove(anal_unit) orelse return; - var idx = kv.value; + unit_refs: { + const kv = zcu.reference_table.fetchSwapRemove(anal_unit) orelse break :unit_refs; + var idx = kv.value; - while (idx != std.math.maxInt(u32)) { - zcu.free_references.append(gpa, idx) catch { - // This space will be reused eventually, so we need not propagate this error. - // Just leak it for now, and let GC reclaim it later on. - return; - }; - idx = zcu.all_references.items[idx].next; + while (idx != std.math.maxInt(u32)) { + zcu.free_references.append(gpa, idx) catch { + // This space will be reused eventually, so we need not propagate this error. + // Just leak it for now, and let GC reclaim it later on. + break :unit_refs; + }; + idx = zcu.all_references.items[idx].next; + } + } + + type_refs: { + const kv = zcu.type_reference_table.fetchSwapRemove(anal_unit) orelse break :type_refs; + var idx = kv.value; + + while (idx != std.math.maxInt(u32)) { + zcu.free_type_references.append(gpa, idx) catch { + // This space will be reused eventually, so we need not propagate this error. + // Just leak it for now, and let GC reclaim it later on. + break :type_refs; + }; + idx = zcu.all_type_references.items[idx].next; + } } } @@ -2735,9 +2817,32 @@ pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit gop.value_ptr.* = @intCast(ref_idx); } +pub fn addTypeReference(zcu: *Zcu, src_unit: AnalUnit, referenced_type: InternPool.Index, ref_src: LazySrcLoc) Allocator.Error!void { + const gpa = zcu.gpa; + + try zcu.type_reference_table.ensureUnusedCapacity(gpa, 1); + + const ref_idx = zcu.free_type_references.popOrNull() orelse idx: { + _ = try zcu.all_type_references.addOne(gpa); + break :idx zcu.all_type_references.items.len - 1; + }; + + errdefer comptime unreachable; + + const gop = zcu.type_reference_table.getOrPutAssumeCapacity(src_unit); + + zcu.all_type_references.items[ref_idx] = .{ + .referenced = referenced_type, + .next = if (gop.found_existing) gop.value_ptr.* else std.math.maxInt(u32), + .src = ref_src, + }; + + gop.value_ptr.* = @intCast(ref_idx); +} + pub fn errorSetBits(mod: *Zcu) u16 { if (mod.error_limit == 0) return 0; - return std.math.log2_int_ceil(ErrorInt, mod.error_limit + 1); // +1 for no error + return @as(u16, std.math.log2_int(ErrorInt, mod.error_limit)) + 1; } pub fn errNote( @@ -3005,6 +3110,14 @@ pub const UnionLayout = struct { tag_align: Alignment, tag_size: u64, padding: u32, + + pub fn tagOffset(layout: UnionLayout) u64 { + return if (layout.tag_align.compare(.lt, layout.payload_align)) layout.payload_size else 0; + } + + pub fn payloadOffset(layout: UnionLayout) u64 { + return if (layout.tag_align.compare(.lt, layout.payload_align)) 0 else layout.tag_size; + } }; /// Returns the index of the active field, given the current tag value @@ -3021,28 +3134,215 @@ pub const ResolvedReference = struct { }; /// Returns a mapping from an `AnalUnit` to where it is referenced. -/// TODO: in future, this must be adapted to traverse from roots of analysis. That way, we can -/// use the returned map to determine which units have become unreferenced in an incremental update. -pub fn resolveReferences(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ResolvedReference) { +/// If the value is `null`, the `AnalUnit` is a root of analysis. +/// If an `AnalUnit` is not in the returned map, it is unreferenced. +pub fn resolveReferences(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) { const gpa = zcu.gpa; + const comp = zcu.comp; + const ip = &zcu.intern_pool; - var result: std.AutoHashMapUnmanaged(AnalUnit, ResolvedReference) = .{}; + var result: std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) = .{}; errdefer result.deinit(gpa); + var checked_types: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .{}; + var type_queue: std.AutoArrayHashMapUnmanaged(InternPool.Index, ?ResolvedReference) = .{}; + var unit_queue: std.AutoArrayHashMapUnmanaged(AnalUnit, ?ResolvedReference) = .{}; + defer { + checked_types.deinit(gpa); + type_queue.deinit(gpa); + unit_queue.deinit(gpa); + } + // This is not a sufficient size, but a lower bound. try result.ensureTotalCapacity(gpa, @intCast(zcu.reference_table.count())); - for (zcu.reference_table.keys(), zcu.reference_table.values()) |referencer, first_ref_idx| { - assert(first_ref_idx != std.math.maxInt(u32)); - var ref_idx = first_ref_idx; - while (ref_idx != std.math.maxInt(u32)) { - const ref = zcu.all_references.items[ref_idx]; - const gop = try result.getOrPut(gpa, ref.referenced); - if (!gop.found_existing) { - gop.value_ptr.* = .{ .referencer = referencer, .src = ref.src }; + try type_queue.ensureTotalCapacity(gpa, zcu.analysis_roots.len); + for (zcu.analysis_roots.slice()) |mod| { + // Logic ripped from `Zcu.PerThread.importPkg`. + // TODO: this is silly, `Module` should just store a reference to its root `File`. + const resolved_path = try std.fs.path.resolve(gpa, &.{ + mod.root.root_dir.path orelse ".", + mod.root.sub_path, + mod.root_src_path, + }); + defer gpa.free(resolved_path); + const file = zcu.import_table.get(resolved_path).?; + const root_ty = zcu.fileRootType(file); + if (root_ty == .none) continue; + type_queue.putAssumeCapacityNoClobber(root_ty, null); + } + + while (true) { + if (type_queue.popOrNull()) |kv| { + const ty = kv.key; + const referencer = kv.value; + try checked_types.putNoClobber(gpa, ty, {}); + + log.debug("handle type '{}'", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}); + + // If this type has a `Cau` for resolution, it's automatically referenced. + const resolution_cau: InternPool.Cau.Index.Optional = switch (ip.indexToKey(ty)) { + .struct_type => ip.loadStructType(ty).cau, + .union_type => ip.loadUnionType(ty).cau.toOptional(), + .enum_type => ip.loadEnumType(ty).cau, + .opaque_type => .none, + else => unreachable, + }; + if (resolution_cau.unwrap()) |cau| { + // this should only be referenced by the type + const unit = AnalUnit.wrap(.{ .cau = cau }); + assert(!result.contains(unit)); + try unit_queue.putNoClobber(gpa, unit, referencer); + } + + // If this is a union with a generated tag, its tag type is automatically referenced. + // We don't add this reference for non-generated tags, as those will already be referenced via the union's `Cau`, with a better source location. + if (zcu.typeToUnion(Type.fromInterned(ty))) |union_obj| { + const tag_ty = union_obj.enum_tag_ty; + if (tag_ty != .none) { + if (ip.indexToKey(tag_ty).enum_type == .generated_tag) { + if (!checked_types.contains(tag_ty)) { + try type_queue.put(gpa, tag_ty, referencer); + } + } + } + } + + // Queue any decls within this type which would be automatically analyzed. + // Keep in sync with analysis queueing logic in `Zcu.PerThread.ScanDeclIter.scanDecl`. + const ns = Type.fromInterned(ty).getNamespace(zcu).unwrap().?; + for (zcu.namespacePtr(ns).other_decls.items) |cau| { + // These are `comptime` and `test` declarations. + // `comptime` decls are always analyzed; `test` declarations are analyzed depending on the test filter. + const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue; + const file = zcu.fileByIndex(inst_info.file); + // If the file failed AstGen, the TrackedInst refers to the old ZIR. + const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*; + const declaration = zir.getDeclaration(inst_info.inst)[0]; + const want_analysis = switch (declaration.name) { + .@"usingnamespace" => unreachable, + .@"comptime" => true, + else => a: { + if (!comp.config.is_test) break :a false; + if (file.mod != zcu.main_mod) break :a false; + if (declaration.name.isNamedTest(zir) or declaration.name == .decltest) { + const nav = ip.getCau(cau).owner.unwrap().nav; + const fqn_slice = ip.getNav(nav).fqn.toSlice(ip); + for (comp.test_filters) |test_filter| { + if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break; + } else break :a false; + } + break :a true; + }, + }; + if (want_analysis) { + const unit = AnalUnit.wrap(.{ .cau = cau }); + if (!result.contains(unit)) { + log.debug("type '{}': ref cau %{}", .{ + Type.fromInterned(ty).containerTypeName(ip).fmt(ip), + @intFromEnum(inst_info.inst), + }); + try unit_queue.put(gpa, unit, referencer); + } + } + } + for (zcu.namespacePtr(ns).pub_decls.keys()) |nav| { + // These are named declarations. They are analyzed only if marked `export`. + const cau = ip.getNav(nav).analysis_owner.unwrap().?; + const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue; + const file = zcu.fileByIndex(inst_info.file); + // If the file failed AstGen, the TrackedInst refers to the old ZIR. + const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*; + const declaration = zir.getDeclaration(inst_info.inst)[0]; + if (declaration.flags.is_export) { + const unit = AnalUnit.wrap(.{ .cau = cau }); + if (!result.contains(unit)) { + log.debug("type '{}': ref cau %{}", .{ + Type.fromInterned(ty).containerTypeName(ip).fmt(ip), + @intFromEnum(inst_info.inst), + }); + try unit_queue.put(gpa, unit, referencer); + } + } + } + for (zcu.namespacePtr(ns).priv_decls.keys()) |nav| { + // These are named declarations. They are analyzed only if marked `export`. + const cau = ip.getNav(nav).analysis_owner.unwrap().?; + const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue; + const file = zcu.fileByIndex(inst_info.file); + // If the file failed AstGen, the TrackedInst refers to the old ZIR. + const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*; + const declaration = zir.getDeclaration(inst_info.inst)[0]; + if (declaration.flags.is_export) { + const unit = AnalUnit.wrap(.{ .cau = cau }); + if (!result.contains(unit)) { + log.debug("type '{}': ref cau %{}", .{ + Type.fromInterned(ty).containerTypeName(ip).fmt(ip), + @intFromEnum(inst_info.inst), + }); + try unit_queue.put(gpa, unit, referencer); + } + } + } + // Incremental compilation does not support `usingnamespace`. + // These are only included to keep good reference traces in non-incremental updates. + for (zcu.namespacePtr(ns).pub_usingnamespace.items) |nav| { + const cau = ip.getNav(nav).analysis_owner.unwrap().?; + const unit = AnalUnit.wrap(.{ .cau = cau }); + if (!result.contains(unit)) try unit_queue.put(gpa, unit, referencer); + } + for (zcu.namespacePtr(ns).priv_usingnamespace.items) |nav| { + const cau = ip.getNav(nav).analysis_owner.unwrap().?; + const unit = AnalUnit.wrap(.{ .cau = cau }); + if (!result.contains(unit)) try unit_queue.put(gpa, unit, referencer); + } + continue; + } + if (unit_queue.popOrNull()) |kv| { + const unit = kv.key; + try result.putNoClobber(gpa, unit, kv.value); + + log.debug("handle unit '{}'", .{zcu.fmtAnalUnit(unit)}); + + if (zcu.reference_table.get(unit)) |first_ref_idx| { + assert(first_ref_idx != std.math.maxInt(u32)); + var ref_idx = first_ref_idx; + while (ref_idx != std.math.maxInt(u32)) { + const ref = zcu.all_references.items[ref_idx]; + if (!result.contains(ref.referenced)) { + log.debug("unit '{}': ref unit '{}'", .{ + zcu.fmtAnalUnit(unit), + zcu.fmtAnalUnit(ref.referenced), + }); + try unit_queue.put(gpa, ref.referenced, .{ + .referencer = unit, + .src = ref.src, + }); + } + ref_idx = ref.next; + } } - ref_idx = ref.next; + if (zcu.type_reference_table.get(unit)) |first_ref_idx| { + assert(first_ref_idx != std.math.maxInt(u32)); + var ref_idx = first_ref_idx; + while (ref_idx != std.math.maxInt(u32)) { + const ref = zcu.all_type_references.items[ref_idx]; + if (!checked_types.contains(ref.referenced)) { + log.debug("unit '{}': ref type '{}'", .{ + zcu.fmtAnalUnit(unit), + Type.fromInterned(ref.referenced).containerTypeName(ip).fmt(ip), + }); + try type_queue.put(gpa, ref.referenced, .{ + .referencer = unit, + .src = ref.src, + }); + } + ref_idx = ref.next; + } + } + continue; } + break; } return result; @@ -3085,7 +3385,7 @@ pub fn navSrcLoc(zcu: *const Zcu, nav_index: InternPool.Nav.Index) LazySrcLoc { pub fn navSrcLine(zcu: *Zcu, nav_index: InternPool.Nav.Index) u32 { const ip = &zcu.intern_pool; - const inst_info = ip.getNav(nav_index).srcInst(ip).resolveFull(ip); + const inst_info = ip.getNav(nav_index).srcInst(ip).resolveFull(ip).?; const zir = zcu.fileByIndex(inst_info.file).zir; const inst = zir.instructions.get(@intFromEnum(inst_info.inst)); assert(inst.tag == .declaration); @@ -3098,7 +3398,7 @@ pub fn navValue(zcu: *const Zcu, nav_index: InternPool.Nav.Index) Value { pub fn navFileScopeIndex(zcu: *Zcu, nav: InternPool.Nav.Index) File.Index { const ip = &zcu.intern_pool; - return ip.getNav(nav).srcInst(ip).resolveFull(ip).file; + return ip.getNav(nav).srcInst(ip).resolveFile(ip); } pub fn navFileScope(zcu: *Zcu, nav: InternPool.Nav.Index) *File { @@ -3107,6 +3407,101 @@ pub fn navFileScope(zcu: *Zcu, nav: InternPool.Nav.Index) *File { pub fn cauFileScope(zcu: *Zcu, cau: InternPool.Cau.Index) *File { const ip = &zcu.intern_pool; - const file_index = ip.getCau(cau).zir_index.resolveFull(ip).file; + const file_index = ip.getCau(cau).zir_index.resolveFile(ip); return zcu.fileByIndex(file_index); } + +pub fn fmtAnalUnit(zcu: *Zcu, unit: AnalUnit) std.fmt.Formatter(formatAnalUnit) { + return .{ .data = .{ .unit = unit, .zcu = zcu } }; +} +pub fn fmtDependee(zcu: *Zcu, d: InternPool.Dependee) std.fmt.Formatter(formatDependee) { + return .{ .data = .{ .dependee = d, .zcu = zcu } }; +} + +fn formatAnalUnit(data: struct { unit: AnalUnit, zcu: *Zcu }, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = .{ fmt, options }; + const zcu = data.zcu; + const ip = &zcu.intern_pool; + switch (data.unit.unwrap()) { + .cau => |cau_index| { + const cau = ip.getCau(cau_index); + switch (cau.owner.unwrap()) { + .nav => |nav| return writer.print("cau(decl='{}')", .{ip.getNav(nav).fqn.fmt(ip)}), + .type => |ty| return writer.print("cau(ty='{}')", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}), + .none => if (cau.zir_index.resolveFull(ip)) |resolved| { + const file_path = zcu.fileByIndex(resolved.file).sub_file_path; + return writer.print("cau(inst=('{s}', %{}))", .{ file_path, @intFromEnum(resolved.inst) }); + } else { + return writer.writeAll("cau(inst=)"); + }, + } + }, + .func => |func| { + const nav = zcu.funcInfo(func).owner_nav; + return writer.print("func('{}')", .{ip.getNav(nav).fqn.fmt(ip)}); + }, + } +} +fn formatDependee(data: struct { dependee: InternPool.Dependee, zcu: *Zcu }, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = .{ fmt, options }; + const zcu = data.zcu; + const ip = &zcu.intern_pool; + switch (data.dependee) { + .src_hash => |ti| { + const info = ti.resolveFull(ip) orelse { + return writer.writeAll("inst()"); + }; + const file_path = zcu.fileByIndex(info.file).sub_file_path; + return writer.print("inst('{s}', %{d})", .{ file_path, @intFromEnum(info.inst) }); + }, + .nav_val => |nav| { + const fqn = ip.getNav(nav).fqn; + return writer.print("nav('{}')", .{fqn.fmt(ip)}); + }, + .interned => |ip_index| switch (ip.indexToKey(ip_index)) { + .struct_type, .union_type, .enum_type => return writer.print("type('{}')", .{Type.fromInterned(ip_index).containerTypeName(ip).fmt(ip)}), + .func => |f| return writer.print("ies('{}')", .{ip.getNav(f.owner_nav).fqn.fmt(ip)}), + else => unreachable, + }, + .namespace => |ti| { + const info = ti.resolveFull(ip) orelse { + return writer.writeAll("namespace()"); + }; + const file_path = zcu.fileByIndex(info.file).sub_file_path; + return writer.print("namespace('{s}', %{d})", .{ file_path, @intFromEnum(info.inst) }); + }, + .namespace_name => |k| { + const info = k.namespace.resolveFull(ip) orelse { + return writer.print("namespace(, '{}')", .{k.name.fmt(ip)}); + }; + const file_path = zcu.fileByIndex(info.file).sub_file_path; + return writer.print("namespace('{s}', %{d}, '{}')", .{ file_path, @intFromEnum(info.inst), k.name.fmt(ip) }); + }, + } +} + +/// Given the `InternPool.Index` of a function, set its resolved IES to `.none` if it +/// may be outdated. `Sema` should do this before ever loading a resolved IES. +pub fn maybeUnresolveIes(zcu: *Zcu, func_index: InternPool.Index) !void { + const unit = AnalUnit.wrap(.{ .func = func_index }); + if (zcu.outdated.contains(unit) or zcu.potentially_outdated.contains(unit)) { + // We're consulting the resolved IES now, but the function is outdated, so its + // IES may have changed. We have to assume the IES is outdated and set the resolved + // set back to `.none`. + // + // This will cause `PerThread.analyzeFnBody` to mark the IES as outdated when it's + // eventually hit. + // + // Since the IES needs to be resolved, the function body will now definitely need + // re-analysis (even if the IES turns out to be the same!), so mark it as + // definitely-outdated if it's only PO. + if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| { + const gpa = zcu.gpa; + try zcu.outdated.putNoClobber(gpa, unit, kv.value); + if (kv.value == 0) { + try zcu.outdated_ready.put(gpa, unit, {}); + } + } + zcu.intern_pool.funcSetIesResolved(func_index, .none); + } +} diff --git a/zig/src/Zcu/PerThread.zig b/zig/src/Zcu/PerThread.zig index 1074062d55..291518f5f0 100644 --- a/zig/src/Zcu/PerThread.zig +++ b/zig/src/Zcu/PerThread.zig @@ -1,3 +1,6 @@ +//! This type provides a wrapper around a `*Zcu` for uses which require a thread `Id`. +//! Any operation which mutates `InternPool` state lives here rather than on `Zcu`. + zcu: *Zcu, /// Dense, per-thread unique index. @@ -39,7 +42,6 @@ pub fn astGenFile( pt: Zcu.PerThread, file: *Zcu.File, path_digest: Cache.BinDigest, - old_root_type: InternPool.Index, ) !void { dev.check(.ast_gen); assert(!file.mod.isBuiltin()); @@ -299,25 +301,15 @@ pub fn astGenFile( file.status = .astgen_failure; return error.AnalysisFail; } - - if (old_root_type != .none) { - // The root of this file must be re-analyzed, since the file has changed. - comp.mutex.lock(); - defer comp.mutex.unlock(); - - log.debug("outdated file root type: {}", .{old_root_type}); - try zcu.outdated_file_root.put(gpa, old_root_type, {}); - } } const UpdatedFile = struct { - file_index: Zcu.File.Index, file: *Zcu.File, inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index), }; -fn cleanupUpdatedFiles(gpa: Allocator, updated_files: *std.ArrayListUnmanaged(UpdatedFile)) void { - for (updated_files.items) |*elem| elem.inst_map.deinit(gpa); +fn cleanupUpdatedFiles(gpa: Allocator, updated_files: *std.AutoArrayHashMapUnmanaged(Zcu.File.Index, UpdatedFile)) void { + for (updated_files.values()) |*elem| elem.inst_map.deinit(gpa); updated_files.deinit(gpa); } @@ -328,143 +320,166 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { const gpa = zcu.gpa; // We need to visit every updated File for every TrackedInst in InternPool. - var updated_files: std.ArrayListUnmanaged(UpdatedFile) = .{}; + var updated_files: std.AutoArrayHashMapUnmanaged(Zcu.File.Index, UpdatedFile) = .{}; defer cleanupUpdatedFiles(gpa, &updated_files); for (zcu.import_table.values()) |file_index| { const file = zcu.fileByIndex(file_index); const old_zir = file.prev_zir orelse continue; const new_zir = file.zir; - try updated_files.append(gpa, .{ - .file_index = file_index, + const gop = try updated_files.getOrPut(gpa, file_index); + assert(!gop.found_existing); + gop.value_ptr.* = .{ .file = file, .inst_map = .{}, - }); - const inst_map = &updated_files.items[updated_files.items.len - 1].inst_map; - try Zcu.mapOldZirToNew(gpa, old_zir.*, new_zir, inst_map); + }; + if (!new_zir.hasCompileErrors()) { + try Zcu.mapOldZirToNew(gpa, old_zir.*, file.zir, &gop.value_ptr.inst_map); + } } - if (updated_files.items.len == 0) + if (updated_files.count() == 0) return; for (ip.locals, 0..) |*local, tid| { const tracked_insts_list = local.getMutableTrackedInsts(gpa); - for (tracked_insts_list.view().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| { - for (updated_files.items) |updated_file| { - const file_index = updated_file.file_index; - if (tracked_inst.file != file_index) continue; - - const file = updated_file.file; - const old_zir = file.prev_zir.?.*; - const new_zir = file.zir; - const old_tag = old_zir.instructions.items(.tag); - const old_data = old_zir.instructions.items(.data); - const inst_map = &updated_file.inst_map; - - const old_inst = tracked_inst.inst; - const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{ - .tid = @enumFromInt(tid), - .index = @intCast(tracked_inst_unwrapped_index), - }).wrap(ip); - tracked_inst.inst = inst_map.get(old_inst) orelse { - // Tracking failed for this instruction. Invalidate associated `src_hash` deps. - log.debug("tracking failed for %{d}", .{old_inst}); - try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index }); - continue; - }; + for (tracked_insts_list.viewAllowEmpty().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| { + const file_index = tracked_inst.file; + const updated_file = updated_files.get(file_index) orelse continue; - if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: { - if (new_zir.getAssociatedSrcHash(tracked_inst.inst)) |new_hash| { - if (std.zig.srcHashEql(old_hash, new_hash)) { - break :hash_changed; - } - log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{ - old_inst, - tracked_inst.inst, - std.fmt.fmtSliceHexLower(&old_hash), - std.fmt.fmtSliceHexLower(&new_hash), - }); + const file = updated_file.file; + + if (file.zir.hasCompileErrors()) { + // If we mark this as outdated now, users of this inst will just get a transitive analysis failure. + // Ultimately, they would end up throwing out potentially useful analysis results. + // So, do nothing. We already have the file failure -- that's sufficient for now! + continue; + } + const old_inst = tracked_inst.inst.unwrap() orelse continue; // we can't continue tracking lost insts + const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{ + .tid = @enumFromInt(tid), + .index = @intCast(tracked_inst_unwrapped_index), + }).wrap(ip); + const new_inst = updated_file.inst_map.get(old_inst) orelse { + // Tracking failed for this instruction. Invalidate associated `src_hash` deps. + log.debug("tracking failed for %{d}", .{old_inst}); + tracked_inst.inst = .lost; + try zcu.markDependeeOutdated(.not_marked_po, .{ .src_hash = tracked_inst_index }); + continue; + }; + tracked_inst.inst = InternPool.TrackedInst.MaybeLost.ZirIndex.wrap(new_inst); + + const old_zir = file.prev_zir.?.*; + const new_zir = file.zir; + const old_tag = old_zir.instructions.items(.tag); + const old_data = old_zir.instructions.items(.data); + + if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: { + if (new_zir.getAssociatedSrcHash(new_inst)) |new_hash| { + if (std.zig.srcHashEql(old_hash, new_hash)) { + break :hash_changed; } - // The source hash associated with this instruction changed - invalidate relevant dependencies. - try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index }); + log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{ + old_inst, + new_inst, + std.fmt.fmtSliceHexLower(&old_hash), + std.fmt.fmtSliceHexLower(&new_hash), + }); } + // The source hash associated with this instruction changed - invalidate relevant dependencies. + try zcu.markDependeeOutdated(.not_marked_po, .{ .src_hash = tracked_inst_index }); + } - // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies. - const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) { - .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) { - .struct_decl, .union_decl, .opaque_decl, .enum_decl => true, - else => false, - }, + // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies. + const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) { + .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) { + .struct_decl, .union_decl, .opaque_decl, .enum_decl => true, else => false, - }; - if (!has_namespace) continue; - - var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; - defer old_names.deinit(zcu.gpa); - { - var it = old_zir.declIterator(old_inst); - while (it.next()) |decl_inst| { - const decl_name = old_zir.getDeclaration(decl_inst)[0].name; - switch (decl_name) { - .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, - _ => if (decl_name.isNamedTest(old_zir)) continue, - } - const name_zir = decl_name.toString(old_zir).?; - const name_ip = try zcu.intern_pool.getOrPutString( - zcu.gpa, - pt.tid, - old_zir.nullTerminatedString(name_zir), - .no_embedded_nulls, - ); - try old_names.put(zcu.gpa, name_ip, {}); + }, + else => false, + }; + if (!has_namespace) continue; + + var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; + defer old_names.deinit(zcu.gpa); + { + var it = old_zir.declIterator(old_inst); + while (it.next()) |decl_inst| { + const decl_name = old_zir.getDeclaration(decl_inst)[0].name; + switch (decl_name) { + .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, + _ => if (decl_name.isNamedTest(old_zir)) continue, } + const name_zir = decl_name.toString(old_zir).?; + const name_ip = try zcu.intern_pool.getOrPutString( + zcu.gpa, + pt.tid, + old_zir.nullTerminatedString(name_zir), + .no_embedded_nulls, + ); + try old_names.put(zcu.gpa, name_ip, {}); } - var any_change = false; - { - var it = new_zir.declIterator(tracked_inst.inst); - while (it.next()) |decl_inst| { - const decl_name = new_zir.getDeclaration(decl_inst)[0].name; - switch (decl_name) { - .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, - _ => if (decl_name.isNamedTest(new_zir)) continue, - } - const name_zir = decl_name.toString(new_zir).?; - const name_ip = try zcu.intern_pool.getOrPutString( - zcu.gpa, - pt.tid, - new_zir.nullTerminatedString(name_zir), - .no_embedded_nulls, - ); - if (!old_names.swapRemove(name_ip)) continue; - // Name added - any_change = true; - try zcu.markDependeeOutdated(.{ .namespace_name = .{ - .namespace = tracked_inst_index, - .name = name_ip, - } }); + } + var any_change = false; + { + var it = new_zir.declIterator(new_inst); + while (it.next()) |decl_inst| { + const decl_name = new_zir.getDeclaration(decl_inst)[0].name; + switch (decl_name) { + .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, + _ => if (decl_name.isNamedTest(new_zir)) continue, } - } - // The only elements remaining in `old_names` now are any names which were removed. - for (old_names.keys()) |name_ip| { + const name_zir = decl_name.toString(new_zir).?; + const name_ip = try zcu.intern_pool.getOrPutString( + zcu.gpa, + pt.tid, + new_zir.nullTerminatedString(name_zir), + .no_embedded_nulls, + ); + if (old_names.swapRemove(name_ip)) continue; + // Name added any_change = true; - try zcu.markDependeeOutdated(.{ .namespace_name = .{ + try zcu.markDependeeOutdated(.not_marked_po, .{ .namespace_name = .{ .namespace = tracked_inst_index, .name = name_ip, } }); } + } + // The only elements remaining in `old_names` now are any names which were removed. + for (old_names.keys()) |name_ip| { + any_change = true; + try zcu.markDependeeOutdated(.not_marked_po, .{ .namespace_name = .{ + .namespace = tracked_inst_index, + .name = name_ip, + } }); + } - if (any_change) { - try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index }); - } + if (any_change) { + try zcu.markDependeeOutdated(.not_marked_po, .{ .namespace = tracked_inst_index }); } } } - for (updated_files.items) |updated_file| { + try ip.rehashTrackedInsts(gpa, pt.tid); + + for (updated_files.keys(), updated_files.values()) |file_index, updated_file| { const file = updated_file.file; - const prev_zir = file.prev_zir.?; - file.prev_zir = null; - prev_zir.deinit(gpa); - gpa.destroy(prev_zir); + if (file.zir.hasCompileErrors()) { + // Keep `prev_zir` around: it's the last non-error ZIR. + // Don't update the namespace, as we have no new data to update *to*. + } else { + const prev_zir = file.prev_zir.?; + file.prev_zir = null; + prev_zir.deinit(gpa); + gpa.destroy(prev_zir); + + // For every file which has changed, re-scan the namespace of the file's root struct type. + // These types are special-cased because they don't have an enclosing declaration which will + // be re-analyzed (causing the struct's namespace to be re-scanned). It's fine to do this + // now because this work is fast (no actual Sema work is happening, we're just updating the + // namespace contents). We must do this after updating ZIR refs above, since `scanNamespace` + // will track some instructions. + try pt.updateFileNamespace(file_index); + } } } @@ -473,8 +488,7 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { const file_root_type = pt.zcu.fileRootType(file_index); if (file_root_type != .none) { - const file_root_type_cau = pt.zcu.intern_pool.loadStructType(file_root_type).cau.unwrap().?; - return pt.ensureCauAnalyzed(file_root_type_cau); + _ = try pt.ensureTypeUpToDate(file_root_type, false); } else { return pt.semaFile(file_index); } @@ -491,9 +505,8 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index }); + const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); const cau = ip.getCau(cau_index); - const inst_info = cau.zir_index.resolveFull(ip); log.debug("ensureCauAnalyzed {d}", .{@intFromEnum(cau_index)}); @@ -514,14 +527,96 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu if (cau_outdated) { _ = zcu.outdated_ready.swapRemove(anal_unit); + } else { + // We can trust the current information about this `Cau`. + if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) { + return error.AnalysisFail; + } + // If it wasn't failed and wasn't marked outdated, then either... + // * it is a type and is up-to-date, or + // * it is a `comptime` decl and is up-to-date, or + // * it is another decl and is EITHER up-to-date OR never-referenced (so unresolved) + // We just need to check for that last case. + switch (cau.owner.unwrap()) { + .type, .none => return, + .nav => |nav| if (ip.getNav(nav).status == .resolved) return, + } + } + + const sema_result: SemaCauResult, const analysis_fail = if (pt.ensureCauAnalyzedInner(cau_index, cau_outdated)) |result| + .{ result, false } + else |err| switch (err) { + error.AnalysisFail => res: { + if (!zcu.failed_analysis.contains(anal_unit)) { + // If this `Cau` caused the error, it would have an entry in `failed_analysis`. + // Since it does not, this must be a transitive failure. + try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); + } + // We treat errors as up-to-date, since those uses would just trigger a transitive error. + // The exception is types, since type declarations may require re-analysis if the type, e.g. its captures, changed. + const outdated = cau.owner.unwrap() == .type; + break :res .{ .{ + .invalidate_decl_val = outdated, + .invalidate_decl_ref = outdated, + }, true }; + }, + error.OutOfMemory => res: { + try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1); + try zcu.retryable_failures.ensureUnusedCapacity(gpa, 1); + const msg = try Zcu.ErrorMsg.create( + gpa, + .{ .base_node_inst = cau.zir_index, .offset = Zcu.LazySrcLoc.Offset.nodeOffset(0) }, + "unable to analyze: OutOfMemory", + .{}, + ); + zcu.retryable_failures.appendAssumeCapacity(anal_unit); + zcu.failed_analysis.putAssumeCapacityNoClobber(anal_unit, msg); + // We treat errors as up-to-date, since those uses would just trigger a transitive error + break :res .{ .{ + .invalidate_decl_val = false, + .invalidate_decl_ref = false, + }, true }; + }, + }; + + if (cau_outdated) { + // TODO: we do not yet have separate dependencies for decl values vs types. + const invalidate = sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref; + const dependee: InternPool.Dependee = switch (cau.owner.unwrap()) { + .none => return, // there are no dependencies on a `comptime` decl! + .nav => |nav_index| .{ .nav_val = nav_index }, + .type => |ty| .{ .interned = ty }, + }; + + if (invalidate) { + // This dependency was marked as PO, meaning dependees were waiting + // on its analysis result, and it has turned out to be outdated. + // Update dependees accordingly. + try zcu.markDependeeOutdated(.marked_po, dependee); + } else { + // This dependency was previously PO, but turned out to be up-to-date. + // We do not need to queue successive analysis. + try zcu.markPoDependeeUpToDate(dependee); + } } - // TODO: this only works if namespace lookups in Sema trigger `ensureCauAnalyzed`, because - // `outdated_file_root` information is not "viral", so we need that a namespace lookup first - // handles the case where the file root is not an outdated *type* but does have an outdated - // *namespace*. A more logically simple alternative may be for a file's root struct to register - // a dependency on the file's entire source code (hash). Alternatively, we could make sure that - // these are always handled first in an update. Actually, that's probably the best option. + if (analysis_fail) return error.AnalysisFail; +} + +fn ensureCauAnalyzedInner( + pt: Zcu.PerThread, + cau_index: InternPool.Cau.Index, + cau_outdated: bool, +) Zcu.SemaError!SemaCauResult { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + + const cau = ip.getCau(cau_index); + const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); + + const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + + // TODO: document this elsewhere mlugg! // For my own benefit, here's how a namespace update for a normal (non-file-root) type works: // `const S = struct { ... };` // We are adding or removing a declaration within this `struct`. @@ -533,33 +628,12 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu // * so, it uses the same `struct` // * but this doesn't stop it from updating the namespace! // * we basically do `scanDecls`, updating the namespace as needed - // * TODO: optimize this to make sure we only do it once a generation i guess? // * so everyone lived happily ever after - const file_root_outdated = switch (cau.owner.unwrap()) { - .type => |ty| zcu.outdated_file_root.swapRemove(ty), - .nav, .none => false, - }; if (zcu.fileByIndex(inst_info.file).status != .success_zir) { return error.AnalysisFail; } - if (!cau_outdated and !file_root_outdated) { - // We can trust the current information about this `Cau`. - if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) { - return error.AnalysisFail; - } - // If it wasn't failed and wasn't marked outdated, then either... - // * it is a type and is up-to-date, or - // * it is a `comptime` decl and is up-to-date, or - // * it is another decl and is EITHER up-to-date OR never-referenced (so unresolved) - // We just need to check for that last case. - switch (cau.owner.unwrap()) { - .type, .none => return, - .nav => |nav| if (ip.getNav(nav).status == .resolved) return, - } - } - // `cau_outdated` can be true in the initial update for `comptime` declarations, // so this isn't a `dev.check`. if (cau_outdated and dev.env.supports(.incremental)) { @@ -567,73 +641,23 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu // prior to re-analysis. zcu.deleteUnitExports(anal_unit); zcu.deleteUnitReferences(anal_unit); - } - - const sema_result: SemaCauResult = res: { - if (inst_info.inst == .main_struct_inst) { - const changed = try pt.semaFileUpdate(inst_info.file, cau_outdated); - break :res .{ - .invalidate_decl_val = changed, - .invalidate_decl_ref = changed, - }; + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(zcu.gpa); } - - const decl_prog_node = zcu.sema_prog_node.start(switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).fqn.toSlice(ip), - .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), - .none => "comptime", - }, 0); - defer decl_prog_node.end(); - - break :res pt.semaCau(cau_index) catch |err| switch (err) { - error.AnalysisFail => { - if (!zcu.failed_analysis.contains(anal_unit)) { - // If this `Cau` caused the error, it would have an entry in `failed_analysis`. - // Since it does not, this must be a transitive failure. - try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); - } - return error.AnalysisFail; - }, - error.GenericPoison => unreachable, - error.ComptimeBreak => unreachable, - error.ComptimeReturn => unreachable, - error.OutOfMemory => { - try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1); - try zcu.retryable_failures.append(gpa, anal_unit); - zcu.failed_analysis.putAssumeCapacityNoClobber(anal_unit, try Zcu.ErrorMsg.create( - gpa, - .{ .base_node_inst = cau.zir_index, .offset = Zcu.LazySrcLoc.Offset.nodeOffset(0) }, - "unable to analyze: OutOfMemory", - .{}, - )); - return error.AnalysisFail; - }, - }; - }; - - if (!cau_outdated) { - // We definitely don't need to do any dependency tracking, so our work is done. - return; + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); } - // TODO: we do not yet have separate dependencies for decl values vs types. - const invalidate = sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref; - const dependee: InternPool.Dependee = switch (cau.owner.unwrap()) { - .none => return, // there are no dependencies on a `comptime` decl! - .nav => |nav_index| .{ .nav_val = nav_index }, - .type => |ty| .{ .interned = ty }, - }; + const decl_prog_node = zcu.sema_prog_node.start(switch (cau.owner.unwrap()) { + .nav => |nav| ip.getNav(nav).fqn.toSlice(ip), + .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), + .none => "comptime", + }, 0); + defer decl_prog_node.end(); - if (invalidate) { - // This dependency was marked as PO, meaning dependees were waiting - // on its analysis result, and it has turned out to be outdated. - // Update dependees accordingly. - try zcu.markDependeeOutdated(dependee); - } else { - // This dependency was previously PO, but turned out to be up-to-date. - // We do not need to queue successive analysis. - try zcu.markPoDependeeUpToDate(dependee); - } + return pt.semaCau(cau_index) catch |err| switch (err) { + error.GenericPoison, error.ComptimeBreak, error.ComptimeReturn => unreachable, + error.AnalysisFail, error.OutOfMemory => |e| return e, + }; } pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void { @@ -653,29 +677,82 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter log.debug("ensureFuncBodyAnalyzed {d}", .{@intFromEnum(func_index)}); - // Here's an interesting question: is this function actually valid? - // Maybe the signature changed, so we'll end up creating a whole different `func` - // in the InternPool, and this one is a waste of time to analyze. Worse, we'd be - // analyzing new ZIR with old data, and get bogus errors. They would be unused, - // but they would still hang around internally! So, let's detect this case. - // For function decls, we must ensure the declaration's `Cau` is up-to-date, and - // check if `func_index` was removed by that update. - // For function instances, we do that process on the generic owner. - - try pt.ensureCauAnalyzed(cau: { - const func_nav = if (func.generic_owner == .none) - func.owner_nav - else - zcu.funcInfo(func.generic_owner).owner_nav; + const anal_unit = AnalUnit.wrap(.{ .func = func_index }); + const func_outdated = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); - break :cau ip.getNav(func_nav).analysis_owner.unwrap().?; - }); + if (func_outdated) { + _ = zcu.outdated_ready.swapRemove(anal_unit); + } else { + // We can trust the current information about this function. + if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) { + return error.AnalysisFail; + } + switch (func.analysisUnordered(ip).state) { + .unreferenced => {}, // this is the first reference + .queued => {}, // we're waiting on first-time analysis + .analyzed => return, // up-to-date + } + } + + const ies_outdated, const analysis_fail = if (pt.ensureFuncBodyAnalyzedInner(func_index, func_outdated)) |result| + .{ result.ies_outdated, false } + else |err| switch (err) { + error.AnalysisFail => res: { + if (!zcu.failed_analysis.contains(anal_unit)) { + // If this function caused the error, it would have an entry in `failed_analysis`. + // Since it does not, this must be a transitive failure. + try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); + } + break :res .{ false, true }; // we treat errors as up-to-date IES, since those uses would just trigger a transitive error + }, + error.OutOfMemory => return error.OutOfMemory, // TODO: graceful handling like `ensureCauAnalyzed` + }; - if (ip.isRemoved(func_index) or (func.generic_owner != .none and ip.isRemoved(func.generic_owner))) { - try zcu.markDependeeOutdated(.{ .interned = func_index }); // IES - ip.removeDependenciesForDepender(gpa, InternPool.AnalUnit.wrap(.{ .func = func_index })); - ip.remove(pt.tid, func_index); - @panic("TODO: remove orphaned function from binary"); + if (func_outdated) { + if (ies_outdated) { + log.debug("func IES invalidated ('{d}')", .{@intFromEnum(func_index)}); + try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index }); + } else { + log.debug("func IES up-to-date ('{d}')", .{@intFromEnum(func_index)}); + try zcu.markPoDependeeUpToDate(.{ .interned = func_index }); + } + } + + if (analysis_fail) return error.AnalysisFail; +} + +fn ensureFuncBodyAnalyzedInner( + pt: Zcu.PerThread, + func_index: InternPool.Index, + func_outdated: bool, +) Zcu.SemaError!struct { ies_outdated: bool } { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const func = zcu.funcInfo(func_index); + const anal_unit = AnalUnit.wrap(.{ .func = func_index }); + + // Make sure that this function is still owned by the same `Nav`. Otherwise, analyzing + // it would be a waste of time in the best case, and could cause codegen to give bogus + // results in the worst case. + + if (func.generic_owner == .none) { + try pt.ensureCauAnalyzed(ip.getNav(func.owner_nav).analysis_owner.unwrap().?); + if (ip.getNav(func.owner_nav).status.resolved.val != func_index) { + // This function is no longer referenced! There's no point in re-analyzing it. + // Just mark a transitive failure and move on. + return error.AnalysisFail; + } + } else { + const go_nav = zcu.funcInfo(func.generic_owner).owner_nav; + try pt.ensureCauAnalyzed(ip.getNav(go_nav).analysis_owner.unwrap().?); + if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) { + // The generic owner is no longer referenced, so this function is also unreferenced. + // There's no point in re-analyzing it. Just mark a transitive failure and move on. + return error.AnalysisFail; + } } // We'll want to remember what the IES used to be before the update for @@ -685,15 +762,14 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter else .none; - const anal_unit = InternPool.AnalUnit.wrap(.{ .func = func_index }); - const func_outdated = zcu.outdated.swapRemove(anal_unit) or - zcu.potentially_outdated.swapRemove(anal_unit); - if (func_outdated) { dev.check(.incremental); - _ = zcu.outdated_ready.swapRemove(anal_unit); zcu.deleteUnitExports(anal_unit); zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); } if (!func_outdated) { @@ -704,7 +780,7 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter switch (func.analysisUnordered(ip).state) { .unreferenced => {}, // this is the first reference .queued => {}, // we're waiting on first-time analysis - .analyzed => return, // up-to-date + .analyzed => return .{ .ies_outdated = false }, // up-to-date } } @@ -713,28 +789,11 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter if (func_outdated) "outdated" else "never analyzed", }); - var air = pt.analyzeFnBody(func_index) catch |err| switch (err) { - error.AnalysisFail => { - if (!zcu.failed_analysis.contains(anal_unit)) { - // If this function caused the error, it would have an entry in `failed_analysis`. - // Since it does not, this must be a transitive failure. - try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); - } - return error.AnalysisFail; - }, - error.OutOfMemory => return error.OutOfMemory, - }; + var air = try pt.analyzeFnBody(func_index); errdefer air.deinit(gpa); - if (func_outdated) { - if (!func.analysisUnordered(ip).inferred_error_set or func.resolvedErrorSetUnordered(ip) != old_resolved_ies) { - log.debug("func IES invalidated ('{d}')", .{@intFromEnum(func_index)}); - try zcu.markDependeeOutdated(.{ .interned = func_index }); - } else { - log.debug("func IES up-to-date ('{d}')", .{@intFromEnum(func_index)}); - try zcu.markPoDependeeUpToDate(.{ .interned = func_index }); - } - } + const ies_outdated = func_outdated and + (!func.analysisUnordered(ip).inferred_error_set or func.resolvedErrorSetUnordered(ip) != old_resolved_ies); const comp = zcu.comp; @@ -743,13 +802,15 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) { air.deinit(gpa); - return; + return .{ .ies_outdated = ies_outdated }; } try comp.queueJob(.{ .codegen_func = .{ .func = func_index, .air = air, } }); + + return .{ .ies_outdated = ies_outdated }; } /// Takes ownership of `air`, even on error. @@ -824,7 +885,7 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai "unable to codegen: {s}", .{@errorName(err)}, )); - try zcu.retryable_failures.append(zcu.gpa, InternPool.AnalUnit.wrap(.{ .func = func_index })); + try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index })); }, }; } else if (zcu.llvm_object) |llvm_object| { @@ -848,6 +909,7 @@ fn createFileRootStruct( pt: Zcu.PerThread, file_index: Zcu.File.Index, namespace_index: Zcu.Namespace.Index, + replace_existing: bool, ) Allocator.Error!InternPool.Index { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -891,7 +953,7 @@ fn createFileRootStruct( .zir_index = tracked_inst, .captures = &.{}, } }, - })) { + }, replace_existing)) { .existing => unreachable, // we wouldn't be analysing the file root if this type existed .wip => |wip| wip, }; @@ -904,77 +966,58 @@ fn createFileRootStruct( if (zcu.comp.incremental) { try ip.addDependency( gpa, - InternPool.AnalUnit.wrap(.{ .cau = new_cau_index }), + AnalUnit.wrap(.{ .cau = new_cau_index }), .{ .src_hash = tracked_inst }, ); } try pt.scanNamespace(namespace_index, decls); try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); + codegen_type: { + if (zcu.comp.config.use_llvm) break :codegen_type; + if (file.mod.strip) break :codegen_type; + try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); + } zcu.setFileRootType(file_index, wip_ty.index); return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); } -/// Re-analyze the root type of a file on an incremental update. -/// If `type_outdated`, the struct type itself is considered outdated and is -/// reconstructed at a new InternPool index. Otherwise, the namespace is just -/// re-analyzed. Returns whether the decl's tyval was invalidated. -/// Returns `error.AnalysisFail` if the file has an error. -fn semaFileUpdate(pt: Zcu.PerThread, file_index: Zcu.File.Index, type_outdated: bool) Zcu.SemaError!bool { +/// Re-scan the namespace of a file's root struct type on an incremental update. +/// The file must have successfully populated ZIR. +/// If the file's root struct type is not populated (the file is unreferenced), nothing is done. +/// This is called by `updateZirRefs` for all updated files before the main work loop. +/// This function does not perform any semantic analysis. +fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void { const zcu = pt.zcu; - const ip = &zcu.intern_pool; + const file = zcu.fileByIndex(file_index); + assert(file.status == .success_zir); const file_root_type = zcu.fileRootType(file_index); - const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu); + if (file_root_type == .none) return; - assert(file_root_type != .none); - - log.debug("semaFileUpdate mod={s} sub_file_path={s} type_outdated={}", .{ + log.debug("updateFileNamespace mod={s} sub_file_path={s}", .{ file.mod.fully_qualified_name, file.sub_file_path, - type_outdated, }); - if (file.status != .success_zir) { - return error.AnalysisFail; - } - - if (type_outdated) { - // Invalidate the existing type, reusing its namespace. - const file_root_type_cau = ip.loadStructType(file_root_type).cau.unwrap().?; - ip.removeDependenciesForDepender( - zcu.gpa, - InternPool.AnalUnit.wrap(.{ .cau = file_root_type_cau }), - ); - ip.remove(pt.tid, file_root_type); - _ = try pt.createFileRootStruct(file_index, namespace_index); - return true; - } - - // Only the struct's namespace is outdated. - // Preserve the type - just scan the namespace again. - - const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; - const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); - - var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len; - extra_index += @intFromBool(small.has_fields_len); - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - const decls = file.zir.bodySlice(extra_index, decls_len); - - if (!type_outdated) { - try pt.scanNamespace(namespace_index, decls); - } - - return false; + const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu); + const decls = decls: { + const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; + const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + + var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len; + extra_index += @intFromBool(small.has_fields_len); + const decls_len = if (small.has_decls_len) blk: { + const decls_len = file.zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + break :decls file.zir.bodySlice(extra_index, decls_len); + }; + try pt.scanNamespace(namespace_index, decls); + zcu.namespacePtr(namespace_index).generation = zcu.generation; } -/// Regardless of the file status, will create a `Decl` if none exists so that we can track -/// dependencies and re-analyze when the file becomes outdated. fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { const tracy = trace(@src()); defer tracy.end(); @@ -993,8 +1036,9 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { .parent = .none, .owner_type = undefined, // set in `createFileRootStruct` .file_scope = file_index, + .generation = zcu.generation, }); - const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index); + const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index, false); errdefer zcu.intern_pool.remove(pt.tid, struct_ty); switch (zcu.comp.cache_use) { @@ -1044,10 +1088,10 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index }); + const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); const cau = ip.getCau(cau_index); - const inst_info = cau.zir_index.resolveFull(ip); + const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail; const file = zcu.fileByIndex(inst_info.file); const zir = file.zir; @@ -1058,69 +1102,21 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { // We are about to re-analyze this `Cau`; drop its depenndencies. zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); - const builtin_type_target_index: InternPool.Index = switch (cau.owner.unwrap()) { - .none => ip_index: { - // `comptime` decl -- we will re-analyze its body. - // This declaration has no value so is definitely not a std.builtin type. - break :ip_index .none; - }, + switch (cau.owner.unwrap()) { + .none => {}, // `comptime` decl -- we will re-analyze its body. + .nav => {}, // Other decl -- we will re-analyze its value. .type => |ty| { // This is an incremental update, and this type is being re-analyzed because it is outdated. - // The type must be recreated at a new `InternPool.Index`. - // Remove it from the InternPool and mark it outdated so that creation sites are re-analyzed. - ip.remove(pt.tid, ty); + // Create a new type in its place, and mark the old one as outdated so that use sites will + // be re-analyzed and discover an up-to-date type. + const new_ty = try pt.ensureTypeUpToDate(ty, true); + assert(new_ty != ty); return .{ .invalidate_decl_val = true, .invalidate_decl_ref = true, }; }, - .nav => |nav| ip_index: { - // Other decl -- we will re-analyze its value. - // This might be a type in `builtin.zig` -- check. - if (file.mod != zcu.std_mod) break :ip_index .none; - // We're in the std module. - const nav_name = ip.getNav(nav).name; - const std_file_imported = try pt.importPkg(zcu.std_mod); - const std_type = Type.fromInterned(zcu.fileRootType(std_file_imported.file_index)); - const std_namespace = zcu.namespacePtr(std_type.getNamespace(zcu).unwrap().?); - const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls); - const builtin_nav = ip.getNav(std_namespace.pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse break :ip_index .none); - const builtin_namespace = switch (builtin_nav.status) { - .unresolved => break :ip_index .none, - .resolved => |r| Type.fromInterned(r.val).getNamespace(zcu).unwrap().?, - }; - if (cau.namespace != builtin_namespace) break :ip_index .none; - // We're in builtin.zig. This could be a builtin we need to add to a specific InternPool index. - for ([_][]const u8{ - "AtomicOrder", - "AtomicRmwOp", - "CallingConvention", - "AddressSpace", - "FloatMode", - "ReduceOp", - "CallModifier", - "PrefetchOptions", - "ExportOptions", - "ExternOptions", - "Type", - }, [_]InternPool.Index{ - .atomic_order_type, - .atomic_rmw_op_type, - .calling_convention_type, - .address_space_type, - .float_mode_type, - .reduce_op_type, - .call_modifier_type, - .prefetch_options_type, - .export_options_type, - .extern_options_type, - .type_info_type, - }) |type_name, type_ip| { - if (nav_name.eqlSlice(type_name, ip)) break :ip_index type_ip; - } - break :ip_index .none; - }, - }; + } const is_usingnamespace = switch (cau.owner.unwrap()) { .nav => |nav| ip.getNav(nav).is_usingnamespace, @@ -1149,7 +1145,6 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { .fn_ret_ty = Type.void, .fn_ret_ty_ies = null, .comptime_err_ret_trace = &comptime_err_ret_trace, - .builtin_type_target_index = builtin_type_target_index, }; defer sema.deinit(); @@ -1204,9 +1199,6 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { .type => unreachable, // Handled at top of function. }; - // We'll do more work with the Sema. Clear the target type index just in case we analyze any type. - sema.builtin_type_target_index = .none; - const align_src = block.src(.{ .node_offset_var_decl_align = 0 }); const section_src = block.src(.{ .node_offset_var_decl_section = 0 }); const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); @@ -1248,11 +1240,11 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { }; } - const nav_already_populated, const queue_linker_work = switch (ip.indexToKey(decl_val.toIntern())) { - .func => |f| .{ f.owner_nav == nav_index, false }, - .variable => |v| .{ false, v.owner_nav == nav_index }, - .@"extern" => .{ false, false }, - else => .{ false, true }, + const nav_already_populated, const queue_linker_work, const resolve_type = switch (ip.indexToKey(decl_val.toIntern())) { + .func => |f| .{ f.owner_nav == nav_index, true, false }, + .variable => |v| .{ false, v.owner_nav == nav_index, true }, + .@"extern" => .{ false, false, false }, + else => .{ false, true, true }, }; if (nav_already_populated) { @@ -1325,14 +1317,19 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { queue_codegen: { if (!queue_linker_work) break :queue_codegen; - // Needed for codegen_nav which will call updateDecl and then the - // codegen backend wants full access to the Decl Type. - // We also need this for the `isFnOrHasRuntimeBits` check below. - // TODO: we could make the language more lenient by deferring this work - // to the `codegen_nav` job. - try decl_ty.resolveFully(pt); + if (resolve_type) { + // Needed for codegen_nav which will call updateDecl and then the + // codegen backend wants full access to the Decl Type. + // We also need this for the `isFnOrHasRuntimeBits` check below. + // TODO: we could make the language more lenient by deferring this work + // to the `codegen_nav` job. + try decl_ty.resolveFully(pt); + } - if (!decl_ty.isFnOrHasRuntimeBits(pt)) break :queue_codegen; + if (!resolve_type or !decl_ty.hasRuntimeBits(pt)) { + if (zcu.comp.config.use_llvm) break :queue_codegen; + if (file.mod.strip) break :queue_codegen; + } try zcu.comp.queueJob(.{ .codegen_nav = nav_index }); } @@ -1911,21 +1908,25 @@ const ScanDeclIter = struct { .@"comptime" => cau: { const cau = existing_cau orelse try ip.createComptimeCau(gpa, pt.tid, tracked_inst, namespace_index); - // For a `comptime` declaration, whether to re-analyze is based solely on whether the - // `Cau` is outdated. So, add this one to `outdated` and `outdated_ready` if not already. - const unit = InternPool.AnalUnit.wrap(.{ .cau = cau }); - if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| { - try zcu.outdated.ensureUnusedCapacity(gpa, 1); - try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); - zcu.outdated.putAssumeCapacityNoClobber(unit, kv.value); - if (kv.value == 0) { // no PO deps + try namespace.other_decls.append(gpa, cau); + + if (existing_cau == null) { + // For a `comptime` declaration, whether to analyze is based solely on whether the + // `Cau` is outdated. So, add this one to `outdated` and `outdated_ready` if not already. + const unit = AnalUnit.wrap(.{ .cau = cau }); + if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| { + try zcu.outdated.ensureUnusedCapacity(gpa, 1); + try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); + zcu.outdated.putAssumeCapacityNoClobber(unit, kv.value); + if (kv.value == 0) { // no PO deps + zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); + } + } else if (!zcu.outdated.contains(unit)) { + try zcu.outdated.ensureUnusedCapacity(gpa, 1); + try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); + zcu.outdated.putAssumeCapacityNoClobber(unit, 0); zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); } - } else if (!zcu.outdated.contains(unit)) { - try zcu.outdated.ensureUnusedCapacity(gpa, 1); - try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); - zcu.outdated.putAssumeCapacityNoClobber(unit, 0); - zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); } break :cau .{ cau, true }; @@ -1943,6 +1944,9 @@ const ScanDeclIter = struct { const want_analysis = switch (kind) { .@"comptime" => unreachable, .@"usingnamespace" => a: { + if (comp.incremental) { + @panic("'usingnamespace' is not supported by incremental compilation"); + } if (declaration.flags.is_pub) { try namespace.pub_usingnamespace.append(gpa, nav); } else { @@ -1981,7 +1985,7 @@ const ScanDeclIter = struct { }, }; - if (want_analysis or declaration.flags.is_export) { + if (existing_cau == null and (want_analysis or declaration.flags.is_export)) { log.debug( "scanDecl queue analyze_cau file='{s}' cau_index={d}", .{ namespace.fileScope(zcu).sub_file_path, cau }, @@ -2001,9 +2005,9 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const anal_unit = InternPool.AnalUnit.wrap(.{ .func = func_index }); + const anal_unit = AnalUnit.wrap(.{ .func = func_index }); const func = zcu.funcInfo(func_index); - const inst_info = func.zir_body_inst.resolveFull(ip); + const inst_info = func.zir_body_inst.resolveFull(ip) orelse return error.AnalysisFail; const file = zcu.fileByIndex(inst_info.file); const zir = file.zir; @@ -2011,6 +2015,9 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); func.setAnalysisState(ip, .analyzed); + if (func.analysisUnordered(ip).inferred_error_set) { + func.setResolvedErrorSet(ip, .none); + } // This is the `Cau` corresponding to the `declaration` instruction which the function or its generic owner originates from. const decl_cau = ip.getCau(cau: { @@ -2089,7 +2096,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! }; defer inner_block.instructions.deinit(gpa); - const fn_info = sema.code.getFnInfo(func.zirBodyInstUnordered(ip).resolve(ip)); + const fn_info = sema.code.getFnInfo(func.zirBodyInstUnordered(ip).resolve(ip) orelse return error.AnalysisFail); // Here we are performing "runtime semantic analysis" for a function body, which means // we must map the parameter ZIR instructions to `arg` AIR instructions. @@ -2153,7 +2160,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! .name = if (inner_block.ownerModule().strip) .none else - @enumFromInt(try sema.appendAirString(sema.code.nullTerminatedString(param_name))), + try sema.appendAirString(sema.code.nullTerminatedString(param_name)), } }, }); } @@ -2217,7 +2224,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! else => |e| return e, }; assert(ies.resolved != .none); - ip.funcSetIesResolved(func_index, ies.resolved); + func.setResolvedErrorSet(ip, ies.resolved); } assert(zcu.analysis_in_progress.swapRemove(anal_unit)); @@ -2387,7 +2394,7 @@ fn processExportsInner( const nav = ip.getNav(nav_index); if (zcu.failed_codegen.contains(nav_index)) break :failed true; if (nav.analysis_owner.unwrap()) |cau| { - const cau_unit = InternPool.AnalUnit.wrap(.{ .cau = cau }); + const cau_unit = AnalUnit.wrap(.{ .cau = cau }); if (zcu.failed_analysis.contains(cau_unit)) break :failed true; if (zcu.transitive_failed_analysis.contains(cau_unit)) break :failed true; } @@ -2397,7 +2404,7 @@ fn processExportsInner( }; // If the value is a function, we also need to check if that function succeeded analysis. if (val.typeOf(zcu).zigTypeTag(zcu) == .Fn) { - const func_unit = InternPool.AnalUnit.wrap(.{ .func = val.toIntern() }); + const func_unit = AnalUnit.wrap(.{ .func = val.toIntern() }); if (zcu.failed_analysis.contains(func_unit)) break :failed true; if (zcu.transitive_failed_analysis.contains(func_unit)) break :failed true; } @@ -2572,7 +2579,7 @@ pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void .{@errorName(err)}, )); if (nav.analysis_owner.unwrap()) |cau| { - try zcu.retryable_failures.append(zcu.gpa, InternPool.AnalUnit.wrap(.{ .cau = cau })); + try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .cau = cau })); } else { // TODO: we don't have a way to indicate that this failure is retryable! // Since these are really rare, we could as a cop-out retry the whole build next update. @@ -2588,6 +2595,22 @@ pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void } } +pub fn linkerUpdateContainerType(pt: Zcu.PerThread, ty: InternPool.Index) !void { + const zcu = pt.zcu; + const comp = zcu.comp; + const ip = &zcu.intern_pool; + + const codegen_prog_node = zcu.codegen_prog_node.start(Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), 0); + defer codegen_prog_node.end(); + + if (comp.bin_file) |lf| { + lf.updateContainerType(pt, ty) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => |e| log.err("codegen type failed: {s}", .{@errorName(e)}), + }; + } +} + pub fn reportRetryableAstGenError( pt: Zcu.PerThread, src: Zcu.AstGenSrc, @@ -2669,7 +2692,7 @@ pub fn reportRetryableFileError( gop.value_ptr.* = err_msg; } -/// Shortcut for calling `intern_pool.get`. +///Shortcut for calling `intern_pool.get`. pub fn intern(pt: Zcu.PerThread, key: InternPool.Key) Allocator.Error!InternPool.Index { return pt.zcu.intern_pool.get(pt.zcu.gpa, pt.tid, key); } @@ -3254,6 +3277,517 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo return Value.fromInterned(r.val).typeOf(zcu).abiAlignment(pt); } +/// Given a container type requiring resolution, ensures that it is up-to-date. +/// If not, the type is recreated at a new `InternPool.Index`. +/// The new index is returned. This is the same as the old index if the fields were up-to-date. +/// If `already_updating` is set, assumes the type is already outdated and undergoing re-analysis rather than checking `zcu.outdated`. +pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index, already_updating: bool) Zcu.SemaError!InternPool.Index { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + switch (ip.indexToKey(ty)) { + .struct_type => |key| { + const struct_obj = ip.loadStructType(ty); + const outdated = already_updating or o: { + const anal_unit = AnalUnit.wrap(.{ .cau = struct_obj.cau.unwrap().? }); + const o = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); + if (o) { + _ = zcu.outdated_ready.swapRemove(anal_unit); + try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); + } + break :o o; + }; + if (!outdated) return ty; + return pt.recreateStructType(key, struct_obj); + }, + .union_type => |key| { + const union_obj = ip.loadUnionType(ty); + const outdated = already_updating or o: { + const anal_unit = AnalUnit.wrap(.{ .cau = union_obj.cau }); + const o = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); + if (o) { + _ = zcu.outdated_ready.swapRemove(anal_unit); + try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); + } + break :o o; + }; + if (!outdated) return ty; + return pt.recreateUnionType(key, union_obj); + }, + .enum_type => |key| { + const enum_obj = ip.loadEnumType(ty); + const outdated = already_updating or o: { + const anal_unit = AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? }); + const o = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); + if (o) { + _ = zcu.outdated_ready.swapRemove(anal_unit); + try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); + } + break :o o; + }; + if (!outdated) return ty; + return pt.recreateEnumType(key, enum_obj); + }, + .opaque_type => { + assert(!already_updating); + return ty; + }, + else => unreachable, + } +} + +fn recreateStructType( + pt: Zcu.PerThread, + full_key: InternPool.Key.NamespaceType, + struct_obj: InternPool.LoadedStructType, +) Zcu.SemaError!InternPool.Index { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const key = switch (full_key) { + .reified => unreachable, // never outdated + .empty_struct => unreachable, // never outdated + .generated_tag => unreachable, // not a struct + .declared => |d| d, + }; + + const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_info.file); + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; + + assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended); + const extended = zir.instructions.items(.data)[@intFromEnum(inst_info.inst)].extended; + assert(extended.opcode == .struct_decl); + const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + const extra = zir.extraData(Zir.Inst.StructDecl, extended.operand); + var extra_index = extra.end; + + const captures_len = if (small.has_captures_len) blk: { + const captures_len = zir.extra[extra_index]; + extra_index += 1; + break :blk captures_len; + } else 0; + const fields_len = if (small.has_fields_len) blk: { + const fields_len = zir.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + + if (captures_len != key.captures.owned.len) return error.AnalysisFail; + if (fields_len != struct_obj.field_types.len) return error.AnalysisFail; + + // The old type will be unused, so drop its dependency information. + ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = struct_obj.cau.unwrap().? })); + + const namespace_index = struct_obj.namespace.unwrap().?; + + const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{ + .layout = small.layout, + .fields_len = fields_len, + .known_non_opv = small.known_non_opv, + .requires_comptime = if (small.known_comptime_only) .yes else .unknown, + .is_tuple = small.is_tuple, + .any_comptime_fields = small.any_comptime_fields, + .any_default_inits = small.any_default_inits, + .inits_resolved = false, + .any_aligned_fields = small.any_aligned_fields, + .key = .{ .declared_owned_captures = .{ + .zir_index = key.zir_index, + .captures = key.captures.owned, + } }, + }, true)) { + .wip => |wip| wip, + .existing => unreachable, // we passed `replace_existing` + }; + errdefer wip_ty.cancel(ip, pt.tid); + + wip_ty.setName(ip, struct_obj.name); + const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index); + try ip.addDependency( + gpa, + AnalUnit.wrap(.{ .cau = new_cau_index }), + .{ .src_hash = key.zir_index }, + ); + zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; + // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive. + try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); + + const new_ty = wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); + if (inst_info.inst == .main_struct_inst) { + // This is the root type of a file! Update the reference. + zcu.setFileRootType(inst_info.file, new_ty); + } + return new_ty; +} + +fn recreateUnionType( + pt: Zcu.PerThread, + full_key: InternPool.Key.NamespaceType, + union_obj: InternPool.LoadedUnionType, +) Zcu.SemaError!InternPool.Index { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const key = switch (full_key) { + .reified => unreachable, // never outdated + .empty_struct => unreachable, // never outdated + .generated_tag => unreachable, // not a union + .declared => |d| d, + }; + + const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_info.file); + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; + + assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended); + const extended = zir.instructions.items(.data)[@intFromEnum(inst_info.inst)].extended; + assert(extended.opcode == .union_decl); + const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small); + const extra = zir.extraData(Zir.Inst.UnionDecl, extended.operand); + var extra_index = extra.end; + + extra_index += @intFromBool(small.has_tag_type); + const captures_len = if (small.has_captures_len) blk: { + const captures_len = zir.extra[extra_index]; + extra_index += 1; + break :blk captures_len; + } else 0; + extra_index += @intFromBool(small.has_body_len); + const fields_len = if (small.has_fields_len) blk: { + const fields_len = zir.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + + if (captures_len != key.captures.owned.len) return error.AnalysisFail; + if (fields_len != union_obj.field_types.len) return error.AnalysisFail; + + // The old type will be unused, so drop its dependency information. + ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = union_obj.cau })); + + const namespace_index = union_obj.namespace; + + const wip_ty = switch (try ip.getUnionType(gpa, pt.tid, .{ + .flags = .{ + .layout = small.layout, + .status = .none, + .runtime_tag = if (small.has_tag_type or small.auto_enum_tag) + .tagged + else if (small.layout != .auto) + .none + else switch (true) { // TODO + true => .safety, + false => .none, + }, + .any_aligned_fields = small.any_aligned_fields, + .requires_comptime = .unknown, + .assumed_runtime_bits = false, + .assumed_pointer_aligned = false, + .alignment = .none, + }, + .fields_len = fields_len, + .enum_tag_ty = .none, // set later + .field_types = &.{}, // set later + .field_aligns = &.{}, // set later + .key = .{ .declared_owned_captures = .{ + .zir_index = key.zir_index, + .captures = key.captures.owned, + } }, + }, true)) { + .wip => |wip| wip, + .existing => unreachable, // we passed `replace_existing` + }; + errdefer wip_ty.cancel(ip, pt.tid); + + wip_ty.setName(ip, union_obj.name); + const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index); + try ip.addDependency( + gpa, + AnalUnit.wrap(.{ .cau = new_cau_index }), + .{ .src_hash = key.zir_index }, + ); + zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; + // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive. + try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); + return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); +} + +fn recreateEnumType( + pt: Zcu.PerThread, + full_key: InternPool.Key.NamespaceType, + enum_obj: InternPool.LoadedEnumType, +) Zcu.SemaError!InternPool.Index { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const key = switch (full_key) { + .reified => unreachable, // never outdated + .empty_struct => unreachable, // never outdated + .generated_tag => unreachable, // never outdated + .declared => |d| d, + }; + + const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_info.file); + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; + + assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended); + const extended = zir.instructions.items(.data)[@intFromEnum(inst_info.inst)].extended; + assert(extended.opcode == .enum_decl); + const small: Zir.Inst.EnumDecl.Small = @bitCast(extended.small); + const extra = zir.extraData(Zir.Inst.EnumDecl, extended.operand); + var extra_index = extra.end; + + const tag_type_ref = if (small.has_tag_type) blk: { + const tag_type_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]); + extra_index += 1; + break :blk tag_type_ref; + } else .none; + + const captures_len = if (small.has_captures_len) blk: { + const captures_len = zir.extra[extra_index]; + extra_index += 1; + break :blk captures_len; + } else 0; + + const body_len = if (small.has_body_len) blk: { + const body_len = zir.extra[extra_index]; + extra_index += 1; + break :blk body_len; + } else 0; + + const fields_len = if (small.has_fields_len) blk: { + const fields_len = zir.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + + const decls_len = if (small.has_decls_len) blk: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + + if (captures_len != key.captures.owned.len) return error.AnalysisFail; + if (fields_len != enum_obj.names.len) return error.AnalysisFail; + + extra_index += captures_len; + extra_index += decls_len; + + const body = zir.bodySlice(extra_index, body_len); + extra_index += body.len; + + const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable; + const body_end = extra_index; + extra_index += bit_bags_count; + + const any_values = for (zir.extra[body_end..][0..bit_bags_count]) |bag| { + if (bag != 0) break true; + } else false; + + // The old type will be unused, so drop its dependency information. + ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? })); + + const namespace_index = enum_obj.namespace; + + const wip_ty = switch (try ip.getEnumType(gpa, pt.tid, .{ + .has_values = any_values, + .tag_mode = if (small.nonexhaustive) + .nonexhaustive + else if (tag_type_ref == .none) + .auto + else + .explicit, + .fields_len = fields_len, + .key = .{ .declared_owned_captures = .{ + .zir_index = key.zir_index, + .captures = key.captures.owned, + } }, + }, true)) { + .wip => |wip| wip, + .existing => unreachable, // we passed `replace_existing` + }; + var done = true; + errdefer if (!done) wip_ty.cancel(ip, pt.tid); + + wip_ty.setName(ip, enum_obj.name); + + const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index); + + zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; + // No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive. + + wip_ty.prepare(ip, new_cau_index, namespace_index); + done = true; + + Sema.resolveDeclaredEnum( + pt, + wip_ty, + inst_info.inst, + key.zir_index, + namespace_index, + enum_obj.name, + new_cau_index, + small, + body, + tag_type_ref, + any_values, + fields_len, + zir, + body_end, + ) catch |err| switch (err) { + error.GenericPoison => unreachable, + error.ComptimeBreak => unreachable, + error.ComptimeReturn => unreachable, + error.AnalysisFail, error.OutOfMemory => |e| return e, + }; + + return wip_ty.index; +} + +/// Given a namespace, re-scan its declarations from the type definition if they have not +/// yet been re-scanned on this update. +/// If the type declaration instruction has been lost, returns `error.AnalysisFail`. +/// This will effectively short-circuit the caller, which will be semantic analysis of a +/// guaranteed-unreferenced `AnalUnit`, to trigger a transitive analysis error. +pub fn ensureNamespaceUpToDate(pt: Zcu.PerThread, namespace_index: Zcu.Namespace.Index) Zcu.SemaError!void { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const namespace = zcu.namespacePtr(namespace_index); + + if (namespace.generation == zcu.generation) return; + + const Container = enum { @"struct", @"union", @"enum", @"opaque" }; + const container: Container, const full_key = switch (ip.indexToKey(namespace.owner_type)) { + .struct_type => |k| .{ .@"struct", k }, + .union_type => |k| .{ .@"union", k }, + .enum_type => |k| .{ .@"enum", k }, + .opaque_type => |k| .{ .@"opaque", k }, + else => unreachable, // namespaces are owned by a container type + }; + + const key = switch (full_key) { + .reified, .empty_struct, .generated_tag => { + // Namespace always empty, so up-to-date. + namespace.generation = zcu.generation; + return; + }, + .declared => |d| d, + }; + + // Namespace outdated -- re-scan the type if necessary. + + const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_info.file); + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; + + assert(zir.instructions.items(.tag)[@intFromEnum(inst_info.inst)] == .extended); + const extended = zir.instructions.items(.data)[@intFromEnum(inst_info.inst)].extended; + + const decls = switch (container) { + .@"struct" => decls: { + assert(extended.opcode == .struct_decl); + const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + const extra = zir.extraData(Zir.Inst.StructDecl, extended.operand); + var extra_index = extra.end; + const captures_len = if (small.has_captures_len) blk: { + const captures_len = zir.extra[extra_index]; + extra_index += 1; + break :blk captures_len; + } else 0; + extra_index += @intFromBool(small.has_fields_len); + const decls_len = if (small.has_decls_len) blk: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + extra_index += captures_len; + if (small.has_backing_int) { + const backing_int_body_len = zir.extra[extra_index]; + extra_index += 1; // backing_int_body_len + if (backing_int_body_len == 0) { + extra_index += 1; // backing_int_ref + } else { + extra_index += backing_int_body_len; // backing_int_body_inst + } + } + break :decls zir.bodySlice(extra_index, decls_len); + }, + .@"union" => decls: { + assert(extended.opcode == .union_decl); + const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small); + const extra = zir.extraData(Zir.Inst.UnionDecl, extended.operand); + var extra_index = extra.end; + extra_index += @intFromBool(small.has_tag_type); + const captures_len = if (small.has_captures_len) blk: { + const captures_len = zir.extra[extra_index]; + extra_index += 1; + break :blk captures_len; + } else 0; + extra_index += @intFromBool(small.has_body_len); + extra_index += @intFromBool(small.has_fields_len); + const decls_len = if (small.has_decls_len) blk: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + extra_index += captures_len; + break :decls zir.bodySlice(extra_index, decls_len); + }, + .@"enum" => decls: { + assert(extended.opcode == .enum_decl); + const small: Zir.Inst.EnumDecl.Small = @bitCast(extended.small); + const extra = zir.extraData(Zir.Inst.EnumDecl, extended.operand); + var extra_index = extra.end; + extra_index += @intFromBool(small.has_tag_type); + const captures_len = if (small.has_captures_len) blk: { + const captures_len = zir.extra[extra_index]; + extra_index += 1; + break :blk captures_len; + } else 0; + extra_index += @intFromBool(small.has_body_len); + extra_index += @intFromBool(small.has_fields_len); + const decls_len = if (small.has_decls_len) blk: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + extra_index += captures_len; + break :decls zir.bodySlice(extra_index, decls_len); + }, + .@"opaque" => decls: { + assert(extended.opcode == .opaque_decl); + const small: Zir.Inst.OpaqueDecl.Small = @bitCast(extended.small); + const extra = zir.extraData(Zir.Inst.OpaqueDecl, extended.operand); + var extra_index = extra.end; + const captures_len = if (small.has_captures_len) blk: { + const captures_len = zir.extra[extra_index]; + extra_index += 1; + break :blk captures_len; + } else 0; + const decls_len = if (small.has_decls_len) blk: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + extra_index += captures_len; + break :decls zir.bodySlice(extra_index, decls_len); + }, + }; + + try pt.scanNamespace(namespace_index, decls); + namespace.generation = zcu.generation; +} + const Air = @import("../Air.zig"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; @@ -3266,6 +3800,7 @@ const builtin = @import("builtin"); const Cache = std.Build.Cache; const dev = @import("../dev.zig"); const InternPool = @import("../InternPool.zig"); +const AnalUnit = InternPool.AnalUnit; const isUpDir = @import("../introspect.zig").isUpDir; const Liveness = @import("../Liveness.zig"); const log = std.log.scoped(.zcu); diff --git a/zig/src/arch/aarch64/CodeGen.zig b/zig/src/arch/aarch64/CodeGen.zig index 2810b6b521..882f3e98e3 100644 --- a/zig/src/arch/aarch64/CodeGen.zig +++ b/zig/src/arch/aarch64/CodeGen.zig @@ -18,7 +18,6 @@ const ErrorMsg = Zcu.ErrorMsg; const Target = std.Target; const Allocator = mem.Allocator; const trace = @import("../../tracy.zig").trace; -const DW = std.dwarf; const leb128 = std.leb; const log = std.log.scoped(.codegen); const build_options = @import("build_options"); @@ -171,7 +170,9 @@ const DbgInfoReloc = struct { fn genDbgInfo(reloc: DbgInfoReloc, function: Self) !void { switch (reloc.tag) { - .arg => try reloc.genArgDbgInfo(function), + .arg, + .dbg_arg_inline, + => try reloc.genArgDbgInfo(function), .dbg_var_ptr, .dbg_var_val, @@ -181,11 +182,11 @@ const DbgInfoReloc = struct { } } - fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) error{OutOfMemory}!void { + fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) !void { switch (function.debug_output) { .dwarf => |dw| { - const loc: link.File.Dwarf.NavState.DbgInfoLoc = switch (reloc.mcv) { - .register => |reg| .{ .register = reg.dwarfLocOp() }, + const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { + .register => |reg| .{ .reg = reg.dwarfNum() }, .stack_offset, .stack_argument_offset, => |offset| blk: { @@ -194,15 +195,15 @@ const DbgInfoReloc = struct { .stack_argument_offset => @as(i32, @intCast(function.saved_regs_stack_space + offset)), else => unreachable, }; - break :blk .{ .stack = .{ - .fp_register = Register.x29.dwarfLocOpDeref(), - .offset = adjusted_offset, + break :blk .{ .plus = .{ + &.{ .breg = Register.x29.dwarfNum() }, + &.{ .consts = adjusted_offset }, } }; }, else => unreachable, // not a possible argument }; - try dw.genArgDbgInfo(reloc.name, reloc.ty, function.owner_nav, loc); + try dw.genLocalDebugInfo(.local_arg, reloc.name, reloc.ty, loc); }, .plan9 => {}, .none => {}, @@ -210,16 +211,10 @@ const DbgInfoReloc = struct { } fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) !void { - const is_ptr = switch (reloc.tag) { - .dbg_var_ptr => true, - .dbg_var_val => false, - else => unreachable, - }; - switch (function.debug_output) { - .dwarf => |dw| { - const loc: link.File.Dwarf.NavState.DbgInfoLoc = switch (reloc.mcv) { - .register => |reg| .{ .register = reg.dwarfLocOp() }, + .dwarf => |dwarf| { + const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { + .register => |reg| .{ .reg = reg.dwarfNum() }, .ptr_stack_offset, .stack_offset, .stack_argument_offset, @@ -231,24 +226,20 @@ const DbgInfoReloc = struct { .stack_argument_offset => @as(i32, @intCast(function.saved_regs_stack_space + offset)), else => unreachable, }; - break :blk .{ - .stack = .{ - .fp_register = Register.x29.dwarfLocOpDeref(), - .offset = adjusted_offset, - }, - }; + break :blk .{ .plus = .{ + &.{ .reg = Register.x29.dwarfNum() }, + &.{ .consts = adjusted_offset }, + } }; }, - .memory => |address| .{ .memory = address }, - .linker_load => |linker_load| .{ .linker_load = linker_load }, - .immediate => |x| .{ .immediate = x }, - .undef => .undef, - .none => .none, + .memory => |address| .{ .constu = address }, + .immediate => |x| .{ .constu = x }, + .none => .empty, else => blk: { log.debug("TODO generate debug info for {}", .{reloc.mcv}); - break :blk .nop; + break :blk .empty; }, }; - try dw.genVarDbgInfo(reloc.name, reloc.ty, function.owner_nav, is_ptr, loc); + try dwarf.genLocalDebugInfo(.local_var, reloc.name, reloc.ty, loc); }, .plan9 => {}, .none => {}, @@ -810,6 +801,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .dbg_inline_block => try self.airDbgInlineBlock(inst), .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, => try self.airDbgVar(inst), .call => try self.airCall(inst, .auto), @@ -4231,17 +4223,13 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { const ty = self.typeOfIndex(inst); const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; - - const name_nts = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; - if (name_nts != .none) { - const name = self.air.nullTerminatedString(@intFromEnum(name_nts)); - try self.dbg_info_relocs.append(self.gpa, .{ - .tag = tag, - .ty = ty, - .name = name, - .mcv = self.args[arg_index], - }); - } + const name = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; + if (name != .none) try self.dbg_info_relocs.append(self.gpa, .{ + .tag = tag, + .ty = ty, + .name = name.toSlice(self.air), + .mcv = self.args[arg_index], + }); const result: MCValue = if (self.liveness.isUnused(inst)) .dead else self.args[arg_index]; return self.finishAir(inst, result, .{ .none, .none, .none }); @@ -4352,24 +4340,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier // on linking. if (try self.air.value(callee, pt)) |func_value| switch (ip.indexToKey(func_value.toIntern())) { .func => |func| { - if (self.bin_file.cast(.elf)) |elf_file| { - const zo = elf_file.zigObjectPtr().?; - const sym_index = try zo.getOrCreateMetadataForNav(elf_file, func.owner_nav); - const sym = zo.symbol(sym_index); - _ = try sym.getOrCreateZigGotEntry(sym_index, elf_file); - const got_addr = @as(u32, @intCast(sym.zigGotAddress(elf_file))); - try self.genSetReg(Type.usize, .x30, .{ .memory = got_addr }); - } else if (self.bin_file.cast(.macho)) |macho_file| { - _ = macho_file; - @panic("TODO airCall"); - // const atom = try macho_file.getOrCreateAtomForNav(func.owner_nav); - // const sym_index = macho_file.getAtom(atom).getSymbolIndex().?; - // try self.genSetReg(Type.u64, .x30, .{ - // .linker_load = .{ - // .type = .got, - // .sym_index = sym_index, - // }, - // }); + if (self.bin_file.cast(.elf)) |_| { + return self.fail("TODO implement calling functions for Elf", .{}); + } else if (self.bin_file.cast(.macho)) |_| { + return self.fail("TODO implement calling functions for MachO", .{}); } else if (self.bin_file.cast(.coff)) |coff_file| { const atom = try coff_file.getOrCreateAtomForNav(func.owner_nav); const sym_index = coff_file.getAtom(atom).getSymbolIndex().?; @@ -4393,21 +4367,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .@"extern" => |@"extern"| { const nav_name = ip.getNav(@"extern".owner_nav).name.toSlice(ip); const lib_name = @"extern".lib_name.toSlice(ip); - if (self.bin_file.cast(.macho)) |macho_file| { - _ = macho_file; - @panic("TODO airCall"); - // const sym_index = try macho_file.getGlobalSymbol(nav_name, lib_name); - // const atom = try macho_file.getOrCreateAtomForNav(self.owner_nav); - // const atom_index = macho_file.getAtom(atom).getSymbolIndex().?; - // _ = try self.addInst(.{ - // .tag = .call_extern, - // .data = .{ - // .relocation = .{ - // .atom_index = atom_index, - // .sym_index = sym_index, - // }, - // }, - // }); + if (self.bin_file.cast(.macho)) |_| { + return self.fail("TODO implement calling extern functions for MachO", .{}); } else if (self.bin_file.cast(.coff)) |coff_file| { const sym_index = try coff_file.getGlobalSymbol(nav_name, lib_name); try self.genSetReg(Type.u64, .x30, .{ @@ -4682,14 +4643,14 @@ fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void { const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; const ty = self.typeOf(operand); const mcv = try self.resolveInst(operand); - const name = self.air.nullTerminatedString(pl_op.payload); + const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); log.debug("airDbgVar: %{d}: {}, {}", .{ inst, ty.fmtDebug(), mcv }); try self.dbg_info_relocs.append(self.gpa, .{ .tag = tag, .ty = ty, - .name = name, + .name = name.toSlice(self.air), .mcv = mcv, }); @@ -6234,7 +6195,7 @@ fn genTypedValue(self: *Self, val: Value) InnerError!MCValue { .memory => |addr| .{ .memory = addr }, .load_got => |sym_index| .{ .linker_load = .{ .type = .got, .sym_index = sym_index } }, .load_direct => |sym_index| .{ .linker_load = .{ .type = .direct, .sym_index = sym_index } }, - .load_symbol, .load_tlv => unreachable, // TODO + .load_symbol, .load_tlv, .lea_symbol, .lea_direct => unreachable, // TODO }, .fail => |msg| { self.err_msg = msg; diff --git a/zig/src/arch/aarch64/bits.zig b/zig/src/arch/aarch64/bits.zig index 6e4508fb0e..01699b7929 100644 --- a/zig/src/arch/aarch64/bits.zig +++ b/zig/src/arch/aarch64/bits.zig @@ -1,6 +1,5 @@ const std = @import("std"); const builtin = @import("builtin"); -const DW = std.dwarf; const assert = std.debug.assert; const testing = std.testing; @@ -295,15 +294,8 @@ pub const Register = enum(u8) { }; } - pub fn dwarfLocOp(self: Register) u8 { - return @as(u8, self.enc()) + DW.OP.reg0; - } - - /// DWARF encodings that push a value onto the DWARF stack that is either - /// the contents of a register or the result of adding the contents a given - /// register to a given signed offset. - pub fn dwarfLocOpDeref(self: Register) u8 { - return @as(u8, self.enc()) + DW.OP.breg0; + pub fn dwarfNum(self: Register) u5 { + return self.enc(); } }; diff --git a/zig/src/arch/arm/CodeGen.zig b/zig/src/arch/arm/CodeGen.zig index e4d106921e..796d3e34dc 100644 --- a/zig/src/arch/arm/CodeGen.zig +++ b/zig/src/arch/arm/CodeGen.zig @@ -18,7 +18,6 @@ const ErrorMsg = Zcu.ErrorMsg; const Target = std.Target; const Allocator = mem.Allocator; const trace = @import("../../tracy.zig").trace; -const DW = std.dwarf; const leb128 = std.leb; const log = std.log.scoped(.codegen); const build_options = @import("build_options"); @@ -249,7 +248,9 @@ const DbgInfoReloc = struct { fn genDbgInfo(reloc: DbgInfoReloc, function: Self) !void { switch (reloc.tag) { - .arg => try reloc.genArgDbgInfo(function), + .arg, + .dbg_arg_inline, + => try reloc.genArgDbgInfo(function), .dbg_var_ptr, .dbg_var_val, @@ -259,11 +260,11 @@ const DbgInfoReloc = struct { } } - fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) error{OutOfMemory}!void { + fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) !void { switch (function.debug_output) { .dwarf => |dw| { - const loc: link.File.Dwarf.NavState.DbgInfoLoc = switch (reloc.mcv) { - .register => |reg| .{ .register = reg.dwarfLocOp() }, + const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { + .register => |reg| .{ .reg = reg.dwarfNum() }, .stack_offset, .stack_argument_offset, => blk: { @@ -272,15 +273,15 @@ const DbgInfoReloc = struct { .stack_argument_offset => |offset| @as(i32, @intCast(function.saved_regs_stack_space + offset)), else => unreachable, }; - break :blk .{ .stack = .{ - .fp_register = DW.OP.breg11, - .offset = adjusted_stack_offset, + break :blk .{ .plus = .{ + &.{ .reg = 11 }, + &.{ .consts = adjusted_stack_offset }, } }; }, else => unreachable, // not a possible argument }; - try dw.genArgDbgInfo(reloc.name, reloc.ty, function.pt.zcu.funcInfo(function.func_index).owner_nav, loc); + try dw.genLocalDebugInfo(.local_arg, reloc.name, reloc.ty, loc); }, .plan9 => {}, .none => {}, @@ -288,16 +289,10 @@ const DbgInfoReloc = struct { } fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) !void { - const is_ptr = switch (reloc.tag) { - .dbg_var_ptr => true, - .dbg_var_val => false, - else => unreachable, - }; - switch (function.debug_output) { .dwarf => |dw| { - const loc: link.File.Dwarf.NavState.DbgInfoLoc = switch (reloc.mcv) { - .register => |reg| .{ .register = reg.dwarfLocOp() }, + const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { + .register => |reg| .{ .reg = reg.dwarfNum() }, .ptr_stack_offset, .stack_offset, .stack_argument_offset, @@ -309,21 +304,20 @@ const DbgInfoReloc = struct { .stack_argument_offset => @as(i32, @intCast(function.saved_regs_stack_space + offset)), else => unreachable, }; - break :blk .{ .stack = .{ - .fp_register = DW.OP.breg11, - .offset = adjusted_offset, + break :blk .{ .plus = .{ + &.{ .reg = 11 }, + &.{ .consts = adjusted_offset }, } }; }, - .memory => |address| .{ .memory = address }, - .immediate => |x| .{ .immediate = x }, - .undef => .undef, - .none => .none, + .memory => |address| .{ .constu = address }, + .immediate => |x| .{ .constu = x }, + .none => .empty, else => blk: { log.debug("TODO generate debug info for {}", .{reloc.mcv}); - break :blk .nop; + break :blk .empty; }, }; - try dw.genVarDbgInfo(reloc.name, reloc.ty, function.pt.zcu.funcInfo(function.func_index).owner_nav, is_ptr, loc); + try dw.genLocalDebugInfo(.local_var, reloc.name, reloc.ty, loc); }, .plan9 => {}, .none => {}, @@ -794,6 +788,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .dbg_inline_block => try self.airDbgInlineBlock(inst), .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, => try self.airDbgVar(inst), .call => try self.airCall(inst, .auto), @@ -4207,16 +4202,13 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { const ty = self.typeOfIndex(inst); const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; - const name_nts = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; - if (name_nts != .none) { - const name = self.air.nullTerminatedString(@intFromEnum(name_nts)); - try self.dbg_info_relocs.append(self.gpa, .{ - .tag = tag, - .ty = ty, - .name = name, - .mcv = self.args[arg_index], - }); - } + const name = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; + if (name != .none) try self.dbg_info_relocs.append(self.gpa, .{ + .tag = tag, + .ty = ty, + .name = name.toSlice(self.air), + .mcv = self.args[arg_index], + }); const result: MCValue = if (self.liveness.isUnused(inst)) .dead else self.args[arg_index]; return self.finishAir(inst, result, .{ .none, .none, .none }); @@ -4333,22 +4325,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier // Due to incremental compilation, how function calls are generated depends // on linking. if (try self.air.value(callee, pt)) |func_value| switch (ip.indexToKey(func_value.toIntern())) { - .func => |func| { - if (self.bin_file.cast(.elf)) |elf_file| { - const zo = elf_file.zigObjectPtr().?; - const sym_index = try zo.getOrCreateMetadataForNav(elf_file, func.owner_nav); - const sym = zo.symbol(sym_index); - _ = try sym.getOrCreateZigGotEntry(sym_index, elf_file); - const got_addr: u32 = @intCast(sym.zigGotAddress(elf_file)); - try self.genSetReg(Type.usize, .lr, .{ .memory = got_addr }); - } else if (self.bin_file.cast(.macho)) |_| { - unreachable; // unsupported architecture for MachO - } else { - return self.fail("TODO implement call on {s} for {s}", .{ - @tagName(self.bin_file.tag), - @tagName(self.target.cpu.arch), - }); - } + .func => { + return self.fail("TODO implement calling functions", .{}); }, .@"extern" => { return self.fail("TODO implement calling extern functions", .{}); @@ -4634,14 +4612,14 @@ fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void { const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; const ty = self.typeOf(operand); const mcv = try self.resolveInst(operand); - const name = self.air.nullTerminatedString(pl_op.payload); + const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); log.debug("airDbgVar: %{d}: {}, {}", .{ inst, ty.fmtDebug(), mcv }); try self.dbg_info_relocs.append(self.gpa, .{ .tag = tag, .ty = ty, - .name = name, + .name = name.toSlice(self.air), .mcv = mcv, }); @@ -6184,7 +6162,7 @@ fn genTypedValue(self: *Self, val: Value) InnerError!MCValue { .mcv => |mcv| switch (mcv) { .none => .none, .undef => .undef, - .load_got, .load_symbol, .load_direct, .load_tlv => unreachable, // TODO + .load_got, .load_symbol, .load_direct, .load_tlv, .lea_symbol, .lea_direct => unreachable, // TODO .immediate => |imm| .{ .immediate = @truncate(imm) }, .memory => |addr| .{ .memory = addr }, }, diff --git a/zig/src/arch/arm/bits.zig b/zig/src/arch/arm/bits.zig index 6c33f3e82a..5802b90953 100644 --- a/zig/src/arch/arm/bits.zig +++ b/zig/src/arch/arm/bits.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const DW = std.dwarf; const assert = std.debug.assert; const testing = std.testing; @@ -158,12 +157,12 @@ pub const Register = enum(u5) { /// Returns the unique 4-bit ID of this register which is used in /// the machine code - pub fn id(self: Register) u4 { - return @as(u4, @truncate(@intFromEnum(self))); + pub fn id(reg: Register) u4 { + return @truncate(@intFromEnum(reg)); } - pub fn dwarfLocOp(self: Register) u8 { - return @as(u8, self.id()) + DW.OP.reg0; + pub fn dwarfNum(reg: Register) u4 { + return reg.id(); } }; diff --git a/zig/src/arch/riscv64/CodeGen.zig b/zig/src/arch/riscv64/CodeGen.zig index a78ff4b1b8..deeb0dc4da 100644 --- a/zig/src/arch/riscv64/CodeGen.zig +++ b/zig/src/arch/riscv64/CodeGen.zig @@ -1644,6 +1644,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, => try func.airDbgVar(inst), .dbg_inline_block => try func.airDbgInlineBlock(inst), @@ -4673,13 +4674,15 @@ fn genArgDbgInfo(func: Func, inst: Air.Inst.Index, mcv: MCValue) !void { const arg = func.air.instructions.items(.data)[@intFromEnum(inst)].arg; const ty = arg.ty.toType(); if (arg.name == .none) return; - const name = func.air.nullTerminatedString(@intFromEnum(arg.name)); switch (func.debug_output) { .dwarf => |dw| switch (mcv) { - .register => |reg| try dw.genArgDbgInfo(name, ty, func.owner.nav_index, .{ - .register = reg.dwarfLocOp(), - }), + .register => |reg| try dw.genLocalDebugInfo( + .local_arg, + arg.name.toSlice(func.air), + ty, + .{ .reg = reg.dwarfNum() }, + ), .load_frame => {}, else => {}, }, @@ -4937,7 +4940,7 @@ fn genCall( if (func.mod.pic) { return func.fail("TODO: genCall pic", .{}); } else { - try func.genSetReg(Type.u64, .ra, .{ .load_symbol = .{ .sym = sym_index } }); + try func.genSetReg(Type.u64, .ra, .{ .lea_symbol = .{ .sym = sym_index } }); _ = try func.addInst(.{ .tag = .jalr, .data = .{ .i_type = .{ @@ -5181,11 +5184,10 @@ fn airDbgVar(func: *Func, inst: Air.Inst.Index) !void { const operand = pl_op.operand; const ty = func.typeOf(operand); const mcv = try func.resolveInst(operand); - - const name = func.air.nullTerminatedString(pl_op.payload); + const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); const tag = func.air.instructions.items(.tag)[@intFromEnum(inst)]; - try func.genVarDbgInfo(tag, ty, mcv, name); + try func.genVarDbgInfo(tag, ty, mcv, name.toSlice(func.air)); return func.finishAir(inst, .unreach, .{ operand, .none, .none }); } @@ -5195,32 +5197,25 @@ fn genVarDbgInfo( tag: Air.Inst.Tag, ty: Type, mcv: MCValue, - name: [:0]const u8, + name: []const u8, ) !void { - const is_ptr = switch (tag) { - .dbg_var_ptr => true, - .dbg_var_val => false, - else => unreachable, - }; - switch (func.debug_output) { - .dwarf => |dw| { - const loc: link.File.Dwarf.NavState.DbgInfoLoc = switch (mcv) { - .register => |reg| .{ .register = reg.dwarfLocOp() }, - .memory => |address| .{ .memory = address }, - .load_symbol => |sym_off| loc: { - assert(sym_off.off == 0); - break :loc .{ .linker_load = .{ .type = .direct, .sym_index = sym_off.sym } }; - }, - .immediate => |x| .{ .immediate = x }, - .undef => .undef, - .none => .none, + .dwarf => |dwarf| { + const loc: link.File.Dwarf.Loc = switch (mcv) { + .register => |reg| .{ .reg = reg.dwarfNum() }, + .memory => |address| .{ .constu = address }, + .immediate => |x| .{ .constu = x }, + .none => .empty, else => blk: { // log.warn("TODO generate debug info for {}", .{mcv}); - break :blk .nop; + break :blk .empty; }, }; - try dw.genVarDbgInfo(name, ty, func.owner.nav_index, is_ptr, loc); + try dwarf.genLocalDebugInfo(switch (tag) { + else => unreachable, + .dbg_var_ptr, .dbg_var_val => .local_var, + .dbg_arg_inline => .local_arg, + }, name, ty, loc); }, .plan9 => {}, .none => {}, @@ -6120,7 +6115,7 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void { arg_map.get(op_str["%[".len .. mod_index orelse op_str.len - "]".len]) orelse return func.fail("no matching constraint: '{s}'", .{op_str}) ]) { - .load_symbol => |sym_off| if (mem.eql(u8, modifier, "plt")) blk: { + .lea_symbol => |sym_off| if (mem.eql(u8, modifier, "plt")) blk: { assert(sym_off.off == 0); break :blk .{ .sym = sym_off }; } else return func.fail("invalid modifier: '{s}'", .{modifier}), @@ -6388,7 +6383,7 @@ fn genCopy(func: *Func, ty: Type, dst_mcv: MCValue, src_mcv: MCValue) !void { ty, src_mcv, ), - .load_tlv => { + .load_symbol, .load_tlv => { const addr_reg, const addr_lock = try func.allocReg(.int); defer func.register_manager.unlockReg(addr_lock); @@ -6433,7 +6428,7 @@ fn genCopy(func: *Func, ty: Type, dst_mcv: MCValue, src_mcv: MCValue) !void { part_disp += @intCast(dst_ty.abiSize(func.pt)); } }, - else => return func.fail("TODO: genCopy to {s} from {s}", .{ @tagName(dst_mcv), @tagName(src_mcv) }), + else => return std.debug.panic("TODO: genCopy to {s} from {s}", .{ @tagName(dst_mcv), @tagName(src_mcv) }), } } @@ -6594,7 +6589,7 @@ fn genInlineMemset( .tag = .beq, .data = .{ .b_type = .{ - .inst = @intCast(func.mir_instructions.len + 4), // points after the last inst + .inst = @intCast(func.mir_instructions.len + 3), // points after the last inst .rs1 = count, .rs2 = .zero, }, @@ -8026,11 +8021,12 @@ fn genTypedValue(func: *Func, val: Value) InnerError!MCValue { .mcv => |mcv| switch (mcv) { .none => .none, .undef => unreachable, + .lea_symbol => |sym_index| .{ .lea_symbol = .{ .sym = sym_index } }, .load_symbol => |sym_index| .{ .load_symbol = .{ .sym = sym_index } }, .load_tlv => |sym_index| .{ .lea_tlv = sym_index }, .immediate => |imm| .{ .immediate = imm }, .memory => |addr| .{ .memory = addr }, - .load_got, .load_direct => { + .load_got, .load_direct, .lea_direct => { return func.fail("TODO: genTypedValue {s}", .{@tagName(mcv)}); }, }, diff --git a/zig/src/arch/riscv64/Emit.zig b/zig/src/arch/riscv64/Emit.zig index 137bc39572..258941f19d 100644 --- a/zig/src/arch/riscv64/Emit.zig +++ b/zig/src/arch/riscv64/Emit.zig @@ -43,42 +43,30 @@ pub fn emitMir(emit: *Emit) Error!void { .fmt = std.meta.activeTag(lowered_inst), }), .load_symbol_reloc => |symbol| { - const is_obj_or_static_lib = switch (emit.lower.output_mode) { - .Exe => false, - .Obj => true, - .Lib => emit.lower.link_mode == .static, - }; - const elf_file = emit.bin_file.cast(.elf).?; const zo = elf_file.zigObjectPtr().?; const atom_ptr = zo.symbol(symbol.atom_index).atom(elf_file).?; const sym = zo.symbol(symbol.sym_index); - var hi_r_type: u32 = @intFromEnum(std.elf.R_RISCV.HI20); - var lo_r_type: u32 = @intFromEnum(std.elf.R_RISCV.LO12_I); - - if (sym.flags.needs_zig_got and !is_obj_or_static_lib) { - _ = try sym.getOrCreateZigGotEntry(symbol.sym_index, elf_file); - - hi_r_type = Elf.R_ZIG_GOT_HI20; - lo_r_type = Elf.R_ZIG_GOT_LO12; - } else if (sym.flags.needs_got) { - hi_r_type = Elf.R_GOT_HI20_STATIC; // TODO: rework this #20887 - lo_r_type = Elf.R_GOT_LO12_I_STATIC; // TODO: rework this #20887 + if (sym.flags.is_extern_ptr and emit.lower.pic) { + return emit.fail("emit GOT relocation for symbol '{s}'", .{sym.name(elf_file)}); } - try atom_ptr.addReloc(elf_file, .{ + const hi_r_type: u32 = @intFromEnum(std.elf.R_RISCV.HI20); + const lo_r_type: u32 = @intFromEnum(std.elf.R_RISCV.LO12_I); + + try atom_ptr.addReloc(elf_file.base.comp.gpa, .{ .r_offset = start_offset, .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | hi_r_type, .r_addend = 0, - }); + }, zo); - try atom_ptr.addReloc(elf_file, .{ + try atom_ptr.addReloc(elf_file.base.comp.gpa, .{ .r_offset = start_offset + 4, .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | lo_r_type, .r_addend = 0, - }); + }, zo); }, .load_tlv_reloc => |symbol| { const elf_file = emit.bin_file.cast(.elf).?; @@ -88,23 +76,23 @@ pub fn emitMir(emit: *Emit) Error!void { const R_RISCV = std.elf.R_RISCV; - try atom_ptr.addReloc(elf_file, .{ + try atom_ptr.addReloc(elf_file.base.comp.gpa, .{ .r_offset = start_offset, .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | @intFromEnum(R_RISCV.TPREL_HI20), .r_addend = 0, - }); + }, zo); - try atom_ptr.addReloc(elf_file, .{ + try atom_ptr.addReloc(elf_file.base.comp.gpa, .{ .r_offset = start_offset + 4, .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | @intFromEnum(R_RISCV.TPREL_ADD), .r_addend = 0, - }); + }, zo); - try atom_ptr.addReloc(elf_file, .{ + try atom_ptr.addReloc(elf_file.base.comp.gpa, .{ .r_offset = start_offset + 8, .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | @intFromEnum(R_RISCV.TPREL_LO12_I), .r_addend = 0, - }); + }, zo); }, .call_extern_fn_reloc => |symbol| { const elf_file = emit.bin_file.cast(.elf).?; @@ -113,11 +101,11 @@ pub fn emitMir(emit: *Emit) Error!void { const r_type: u32 = @intFromEnum(std.elf.R_RISCV.CALL_PLT); - try atom_ptr.addReloc(elf_file, .{ + try atom_ptr.addReloc(elf_file.base.comp.gpa, .{ .r_offset = start_offset, .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | r_type, .r_addend = 0, - }); + }, zo); }, }; } diff --git a/zig/src/arch/riscv64/bits.zig b/zig/src/arch/riscv64/bits.zig index 45fdd22bc0..9899345c0a 100644 --- a/zig/src/arch/riscv64/bits.zig +++ b/zig/src/arch/riscv64/bits.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const DW = std.dwarf; const assert = std.debug.assert; const testing = std.testing; const Target = std.Target; @@ -207,8 +206,8 @@ pub const Register = enum(u8) { return @truncate(@intFromEnum(reg)); } - pub fn dwarfLocOp(reg: Register) u8 { - return @as(u8, reg.id()); + pub fn dwarfNum(reg: Register) u8 { + return reg.id(); } pub fn bitSize(reg: Register, zcu: *const Zcu) u32 { diff --git a/zig/src/arch/sparc64/CodeGen.zig b/zig/src/arch/sparc64/CodeGen.zig index bba8cdec34..99192aa554 100644 --- a/zig/src/arch/sparc64/CodeGen.zig +++ b/zig/src/arch/sparc64/CodeGen.zig @@ -643,6 +643,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .dbg_inline_block => try self.airDbgInlineBlock(inst), .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, => try self.airDbgVar(inst), .call => try self.airCall(inst, .auto), @@ -1349,34 +1350,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier // Due to incremental compilation, how function calls are generated depends // on linking. if (try self.air.value(callee, pt)) |func_value| switch (ip.indexToKey(func_value.toIntern())) { - .func => |func| { - const got_addr = if (self.bin_file.cast(.elf)) |elf_file| blk: { - const zo = elf_file.zigObjectPtr().?; - const sym_index = try zo.getOrCreateMetadataForNav(elf_file, func.owner_nav); - const sym = zo.symbol(sym_index); - _ = try sym.getOrCreateZigGotEntry(sym_index, elf_file); - break :blk @as(u32, @intCast(sym.zigGotAddress(elf_file))); - } else @panic("TODO SPARCv9 currently does not support non-ELF binaries"); - - try self.genSetReg(Type.usize, .o7, .{ .memory = got_addr }); - - _ = try self.addInst(.{ - .tag = .jmpl, - .data = .{ - .arithmetic_3op = .{ - .is_imm = false, - .rd = .o7, - .rs1 = .o7, - .rs2_or_imm = .{ .rs2 = .g0 }, - }, - }, - }); - - // TODO Find a way to fill this delay slot - _ = try self.addInst(.{ - .tag = .nop, - .data = .{ .nop = {} }, - }); + .func => { + return self.fail("TODO implement calling functions", .{}); }, .@"extern" => { return self.fail("TODO implement calling extern functions", .{}); @@ -1688,7 +1663,7 @@ fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void { const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const name = self.air.nullTerminatedString(pl_op.payload); + const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); const operand = pl_op.operand; // TODO emit debug info for this variable _ = name; @@ -3605,19 +3580,18 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Live } fn genArgDbgInfo(self: Self, inst: Air.Inst.Index, mcv: MCValue) !void { - const pt = self.pt; - const mod = pt.zcu; const arg = self.air.instructions.items(.data)[@intFromEnum(inst)].arg; const ty = arg.ty.toType(); - const owner_nav = mod.funcInfo(self.func_index).owner_nav; if (arg.name == .none) return; - const name = self.air.nullTerminatedString(@intFromEnum(arg.name)); switch (self.debug_output) { .dwarf => |dw| switch (mcv) { - .register => |reg| try dw.genArgDbgInfo(name, ty, owner_nav, .{ - .register = reg.dwarfLocOp(), - }), + .register => |reg| try dw.genLocalDebugInfo( + .local_arg, + arg.name.toSlice(self.air), + ty, + .{ .reg = reg.dwarfNum() }, + ), else => {}, }, else => {}, @@ -4153,7 +4127,7 @@ fn genTypedValue(self: *Self, val: Value) InnerError!MCValue { .mcv => |mcv| switch (mcv) { .none => .none, .undef => .undef, - .load_got, .load_symbol, .load_direct, .load_tlv => unreachable, // TODO + .load_got, .load_symbol, .load_direct, .load_tlv, .lea_symbol, .lea_direct => unreachable, // TODO .immediate => |imm| .{ .immediate = imm }, .memory => |addr| .{ .memory = addr }, }, diff --git a/zig/src/arch/sparc64/bits.zig b/zig/src/arch/sparc64/bits.zig index 04da91ca74..d27371bf25 100644 --- a/zig/src/arch/sparc64/bits.zig +++ b/zig/src/arch/sparc64/bits.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const DW = std.dwarf; const assert = std.debug.assert; const testing = std.testing; @@ -15,17 +14,17 @@ pub const Register = enum(u6) { fp = 62, // frame pointer (i6) // zig fmt: on - pub fn id(self: Register) u5 { - return @as(u5, @truncate(@intFromEnum(self))); + pub fn id(reg: Register) u5 { + return @truncate(@intFromEnum(reg)); } - pub fn enc(self: Register) u5 { + pub fn enc(reg: Register) u5 { // For integer registers, enc() == id(). - return self.id(); + return reg.id(); } - pub fn dwarfLocOp(reg: Register) u8 { - return @as(u8, reg.id()) + DW.OP.reg0; + pub fn dwarfNum(reg: Register) u5 { + return reg.id(); } }; diff --git a/zig/src/arch/wasm/CodeGen.zig b/zig/src/arch/wasm/CodeGen.zig index be049ec975..d2e9db8062 100644 --- a/zig/src/arch/wasm/CodeGen.zig +++ b/zig/src/arch/wasm/CodeGen.zig @@ -742,7 +742,7 @@ const InnerError = error{ CodegenFail, /// Compiler implementation could not handle a large integer. Overflow, -}; +} || link.File.UpdateDebugInfoError; pub fn deinit(func: *CodeGen) void { // in case of an error and we still have branches @@ -1917,8 +1917,9 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { .dbg_stmt => func.airDbgStmt(inst), .dbg_inline_block => func.airDbgInlineBlock(inst), - .dbg_var_ptr => func.airDbgVar(inst, true), - .dbg_var_val => func.airDbgVar(inst, false), + .dbg_var_ptr => func.airDbgVar(inst, .local_var, true), + .dbg_var_val => func.airDbgVar(inst, .local_var, false), + .dbg_arg_inline => func.airDbgVar(inst, .local_arg, false), .call => func.airCall(inst, .auto), .call_always_tail => func.airCall(inst, .always_tail), @@ -2585,13 +2586,13 @@ fn airArg(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { switch (func.debug_output) { .dwarf => |dwarf| { - const name_nts = func.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; - if (name_nts != .none) { - const name = func.air.nullTerminatedString(@intFromEnum(name_nts)); - try dwarf.genArgDbgInfo(name, arg_ty, func.owner_nav, .{ - .wasm_local = arg.local.value, - }); - } + const name = func.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; + if (name != .none) try dwarf.genLocalDebugInfo( + .local_arg, + name.toSlice(func.air), + arg_ty, + .{ .wasm_ext = .{ .local = arg.local.value } }, + ); }, else => {}, } @@ -6454,7 +6455,13 @@ fn airDbgInlineBlock(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { try func.lowerBlock(inst, ty_pl.ty.toType(), @ptrCast(func.air.extra[extra.end..][0..extra.data.body_len])); } -fn airDbgVar(func: *CodeGen, inst: Air.Inst.Index, is_ptr: bool) InnerError!void { +fn airDbgVar( + func: *CodeGen, + inst: Air.Inst.Index, + local_tag: link.File.Dwarf.WipNav.LocalTag, + is_ptr: bool, +) InnerError!void { + _ = is_ptr; if (func.debug_output != .dwarf) return func.finishAir(inst, .none, &.{}); const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; @@ -6463,17 +6470,17 @@ fn airDbgVar(func: *CodeGen, inst: Air.Inst.Index, is_ptr: bool) InnerError!void log.debug("airDbgVar: %{d}: {}, {}", .{ inst, ty.fmtDebug(), operand }); - const name = func.air.nullTerminatedString(pl_op.payload); - log.debug(" var name = ({s})", .{name}); + const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); + log.debug(" var name = ({s})", .{name.toSlice(func.air)}); - const loc: link.File.Dwarf.NavState.DbgInfoLoc = switch (operand) { - .local => |local| .{ .wasm_local = local.value }, + const loc: link.File.Dwarf.Loc = switch (operand) { + .local => |local| .{ .wasm_ext = .{ .local = local.value } }, else => blk: { log.debug("TODO generate debug info for {}", .{operand}); - break :blk .nop; + break :blk .empty; }, }; - try func.debug_output.dwarf.genVarDbgInfo(name, ty, func.owner_nav, is_ptr, loc); + try func.debug_output.dwarf.genLocalDebugInfo(local_tag, name.toSlice(func.air), ty, loc); return func.finishAir(inst, .none, &.{}); } diff --git a/zig/src/arch/x86/bits.zig b/zig/src/arch/x86/bits.zig index b69e610d12..4b69238c4f 100644 --- a/zig/src/arch/x86/bits.zig +++ b/zig/src/arch/x86/bits.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const DW = std.dwarf; // zig fmt: off pub const Register = enum(u8) { @@ -44,18 +43,8 @@ pub const Register = enum(u8) { return @enumFromInt(@as(u8, self.id()) + 16); } - pub fn dwarfLocOp(reg: Register) u8 { - return switch (reg.to32()) { - .eax => DW.OP.reg0, - .ecx => DW.OP.reg1, - .edx => DW.OP.reg2, - .ebx => DW.OP.reg3, - .esp => DW.OP.reg4, - .ebp => DW.OP.reg5, - .esi => DW.OP.reg6, - .edi => DW.OP.reg7, - else => unreachable, - }; + pub fn dwarfNum(reg: Register) u8 { + return @intFromEnum(reg.to32()); } }; @@ -64,7 +53,7 @@ pub const Register = enum(u8) { /// TODO this set is actually a set of caller-saved registers. pub const callee_preserved_regs = [_]Register{ .eax, .ecx, .edx, .esi, .edi }; -// TODO add these to Register enum and corresponding dwarfLocOp +// TODO add these to Register enum and corresponding dwarfNum // // Return Address register. This is stored in `0(%esp, "")` and is not a physical register. // RA = (8, "RA"), // diff --git a/zig/src/arch/x86_64/CodeGen.zig b/zig/src/arch/x86_64/CodeGen.zig index 70601bdec6..316389a7c2 100644 --- a/zig/src/arch/x86_64/CodeGen.zig +++ b/zig/src/arch/x86_64/CodeGen.zig @@ -18,7 +18,6 @@ const Allocator = mem.Allocator; const CodeGenError = codegen.CodeGenError; const Compilation = @import("../../Compilation.zig"); const DebugInfoOutput = codegen.DebugInfoOutput; -const DW = std.dwarf; const ErrorMsg = Zcu.ErrorMsg; const Result = codegen.Result; const Emit = @import("Emit.zig"); @@ -60,19 +59,19 @@ owner: Owner, inline_func: InternPool.Index, mod: *Package.Module, err_msg: ?*ErrorMsg, +arg_index: u32, args: []MCValue, va_info: union { sysv: struct { gp_count: u32, fp_count: u32, - overflow_arg_area: FrameAddr, - reg_save_area: FrameAddr, + overflow_arg_area: bits.FrameAddr, + reg_save_area: bits.FrameAddr, }, win64: struct {}, }, ret_mcv: InstTracking, fn_type: Type, -arg_index: u32, src_loc: Zcu.LazySrcLoc, eflags_inst: ?Air.Inst.Index = null, @@ -111,10 +110,6 @@ air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init, const air_bookkeeping_init = if (std.debug.runtime_safety) @as(usize, 0) else {}; -const FrameAddr = struct { index: FrameIndex, off: i32 = 0 }; -const RegisterOffset = struct { reg: Register, off: i32 = 0 }; -const SymbolOffset = struct { sym: u32, off: i32 = 0 }; - const Owner = union(enum) { nav_index: InternPool.Nav.Index, lazy_sym: link.File.LazySymbol, @@ -172,7 +167,7 @@ pub const MCValue = union(enum) { /// The value is split across two registers. register_pair: [2]Register, /// The value is a constant offset from the value in a register. - register_offset: RegisterOffset, + register_offset: bits.RegisterOffset, /// The value is a tuple { wrapped, overflow } where wrapped value is stored in the GP register. register_overflow: struct { reg: Register, eflags: Condition }, /// The value is in memory at a hard-coded address. @@ -180,11 +175,11 @@ pub const MCValue = union(enum) { memory: u64, /// The value is in memory at an address not-yet-allocated by the linker. /// This traditionally corresponds to a relocation emitted in a relocatable object file. - load_symbol: SymbolOffset, + load_symbol: bits.SymbolOffset, /// The address of the memory location not-yet-allocated by the linker. - lea_symbol: SymbolOffset, + lea_symbol: bits.SymbolOffset, /// The value is in memory at a constant offset from the address in a register. - indirect: RegisterOffset, + indirect: bits.RegisterOffset, /// The value is in memory. /// Payload is a symbol index. load_direct: u32, @@ -205,10 +200,10 @@ pub const MCValue = union(enum) { lea_tlv: u32, /// The value stored at an offset from a frame index /// Payload is a frame address. - load_frame: FrameAddr, + load_frame: bits.FrameAddr, /// The address of an offset from a frame index /// Payload is a frame address. - lea_frame: FrameAddr, + lea_frame: bits.FrameAddr, /// Supports integer_per_element abi elementwise_regs_then_frame: packed struct { regs: u3 = 0, frame_off: i29 = 0, frame_index: FrameIndex }, /// This indicates that we have already allocated a frame index for this instruction, @@ -424,10 +419,7 @@ pub const MCValue = union(enum) { .load_symbol => |sym_off| { assert(sym_off.off == 0); return .{ - .base = .{ .reloc = .{ - .atom_index = try function.owner.getSymbolIndex(function), - .sym_index = sym_off.sym, - } }, + .base = .{ .reloc = sym_off.sym_index }, .mod = .{ .rm = .{ .size = size, .disp = sym_off.off, @@ -454,8 +446,8 @@ pub const MCValue = union(enum) { .register_overflow => |pl| try writer.print("{s}:{s}", .{ @tagName(pl.eflags), @tagName(pl.reg), }), - .load_symbol => |pl| try writer.print("[{} + 0x{x}]", .{ pl.sym, pl.off }), - .lea_symbol => |pl| try writer.print("{} + 0x{x}", .{ pl.sym, pl.off }), + .load_symbol => |pl| try writer.print("[{} + 0x{x}]", .{ pl.sym_index, pl.off }), + .lea_symbol => |pl| try writer.print("{} + 0x{x}", .{ pl.sym_index, pl.off }), .indirect => |pl| try writer.print("[{s} + 0x{x}]", .{ @tagName(pl.reg), pl.off }), .load_direct => |pl| try writer.print("[direct:{d}]", .{pl}), .lea_direct => |pl| try writer.print("direct:{d}", .{pl}), @@ -810,11 +802,11 @@ pub fn generate( .owner = .{ .nav_index = func.owner_nav }, .inline_func = func_index, .err_msg = null, + .arg_index = undefined, .args = undefined, // populated after `resolveCallingConventionValues` .va_info = undefined, // populated after `resolveCallingConventionValues` .ret_mcv = undefined, // populated after `resolveCallingConventionValues` .fn_type = fn_type, - .arg_index = 0, .src_loc = src_loc, .end_di_line = func.rbrace_line, .end_di_column = func.rbrace_column, @@ -885,6 +877,7 @@ pub fn generate( }), ); function.va_info = switch (cc) { + else => undefined, .SysV => .{ .sysv = .{ .gp_count = call_info.gp_count, .fp_count = call_info.fp_count, @@ -892,7 +885,6 @@ pub fn generate( .reg_save_area = undefined, } }, .Win64 => .{ .win64 = .{} }, - else => undefined, }; function.gen() catch |err| switch (err) { @@ -903,14 +895,15 @@ pub fn generate( else => |e| return e, }; - var mir = Mir{ + var mir: Mir = .{ .instructions = function.mir_instructions.toOwnedSlice(), .extra = try function.mir_extra.toOwnedSlice(gpa), .frame_locs = function.frame_locs.toOwnedSlice(), }; defer mir.deinit(gpa); - var emit = Emit{ + var emit: Emit = .{ + .air = function.air, .lower = .{ .bin_file = bin_file, .allocator = gpa, @@ -921,6 +914,13 @@ pub fn generate( .link_mode = comp.config.link_mode, .pic = mod.pic, }, + .atom_index = function.owner.getSymbolIndex(&function) catch |err| switch (err) { + error.CodegenFail => return Result{ .fail = function.err_msg.? }, + error.OutOfRegisters => return Result{ + .fail = try ErrorMsg.create(gpa, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), + }, + else => |e| return e, + }, .debug_output = debug_output, .code = code, .prev_di_pc = 0, @@ -978,11 +978,11 @@ pub fn generateLazy( .owner = .{ .lazy_sym = lazy_sym }, .inline_func = undefined, .err_msg = null, + .arg_index = undefined, .args = undefined, .va_info = undefined, .ret_mcv = undefined, .fn_type = undefined, - .arg_index = undefined, .src_loc = src_loc, .end_di_line = undefined, // no debug info yet .end_di_column = undefined, // no debug info yet @@ -1000,14 +1000,15 @@ pub fn generateLazy( else => |e| return e, }; - var mir = Mir{ + var mir: Mir = .{ .instructions = function.mir_instructions.toOwnedSlice(), .extra = try function.mir_extra.toOwnedSlice(gpa), .frame_locs = function.frame_locs.toOwnedSlice(), }; defer mir.deinit(gpa); - var emit = Emit{ + var emit: Emit = .{ + .air = function.air, .lower = .{ .bin_file = bin_file, .allocator = gpa, @@ -1018,6 +1019,13 @@ pub fn generateLazy( .link_mode = comp.config.link_mode, .pic = mod.pic, }, + .atom_index = function.owner.getSymbolIndex(&function) catch |err| switch (err) { + error.CodegenFail => return Result{ .fail = function.err_msg.? }, + error.OutOfRegisters => return Result{ + .fail = try ErrorMsg.create(gpa, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), + }, + else => |e| return e, + }, .debug_output = debug_output, .code = code, .prev_di_pc = undefined, // no debug info yet @@ -1103,7 +1111,7 @@ fn formatWipMir( ) @TypeOf(writer).Error!void { const comp = data.self.bin_file.comp; const mod = comp.root_mod; - var lower = Lower{ + var lower: Lower = .{ .bin_file = data.self.bin_file, .allocator = data.self.gpa, .mir = .{ @@ -1191,6 +1199,7 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { self.mir_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), i32, Mir.Memory.Info => @bitCast(@field(extra, field.name)), + bits.FrameIndex => @intFromEnum(@field(extra, field.name)), else => @compileError("bad field type: " ++ field.name ++ ": " ++ @typeName(field.type)), }); } @@ -1344,6 +1353,124 @@ fn asmPlaceholder(self: *Self) !Mir.Inst.Index { }); } +const MirTagAir = enum { dbg_local }; + +fn asmAir(self: *Self, tag: MirTagAir, inst: Air.Inst.Index) !void { + _ = try self.addInst(.{ + .tag = .pseudo, + .ops = switch (tag) { + .dbg_local => .pseudo_dbg_local_a, + }, + .data = .{ .a = .{ .air_inst = inst } }, + }); +} + +fn asmAirImmediate(self: *Self, tag: MirTagAir, inst: Air.Inst.Index, imm: Immediate) !void { + switch (imm) { + .signed => |s| _ = try self.addInst(.{ + .tag = .pseudo, + .ops = switch (tag) { + .dbg_local => .pseudo_dbg_local_ai_s, + }, + .data = .{ .ai = .{ + .air_inst = inst, + .i = @bitCast(s), + } }, + }), + .unsigned => |u| _ = if (math.cast(u32, u)) |small| try self.addInst(.{ + .tag = .pseudo, + .ops = switch (tag) { + .dbg_local => .pseudo_dbg_local_ai_u, + }, + .data = .{ .ai = .{ + .air_inst = inst, + .i = small, + } }, + }) else try self.addInst(.{ + .tag = .pseudo, + .ops = switch (tag) { + .dbg_local => .pseudo_dbg_local_ai_64, + }, + .data = .{ .ai = .{ + .air_inst = inst, + .i = try self.addExtra(Mir.Imm64.encode(u)), + } }, + }), + .reloc => |sym_off| _ = if (sym_off.off == 0) try self.addInst(.{ + .tag = .pseudo, + .ops = switch (tag) { + .dbg_local => .pseudo_dbg_local_as, + }, + .data = .{ .as = .{ + .air_inst = inst, + .sym_index = sym_off.sym_index, + } }, + }) else try self.addInst(.{ + .tag = .pseudo, + .ops = switch (tag) { + .dbg_local => .pseudo_dbg_local_aso, + }, + .data = .{ .ax = .{ + .air_inst = inst, + .payload = try self.addExtra(sym_off), + } }, + }), + } +} + +fn asmAirRegisterImmediate( + self: *Self, + tag: MirTagAir, + inst: Air.Inst.Index, + reg: Register, + imm: Immediate, +) !void { + _ = try self.addInst(.{ + .tag = .pseudo, + .ops = switch (tag) { + .dbg_local => .pseudo_dbg_local_aro, + }, + .data = .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.AirOffset{ + .air_inst = inst, + .off = imm.signed, + }), + } }, + }); +} + +fn asmAirFrameAddress( + self: *Self, + tag: MirTagAir, + inst: Air.Inst.Index, + frame_addr: bits.FrameAddr, +) !void { + _ = try self.addInst(.{ + .tag = .pseudo, + .ops = switch (tag) { + .dbg_local => .pseudo_dbg_local_af, + }, + .data = .{ .ax = .{ + .air_inst = inst, + .payload = try self.addExtra(frame_addr), + } }, + }); +} + +fn asmAirMemory(self: *Self, tag: MirTagAir, inst: Air.Inst.Index, m: Memory) !void { + _ = try self.addInst(.{ + .tag = .pseudo, + .ops = switch (tag) { + .dbg_local => .pseudo_dbg_local_am, + }, + .data = .{ .ax = .{ + .air_inst = inst, + .payload = try self.addExtra(Mir.Memory.encode(m)), + } }, + }); +} + fn asmOpOnly(self: *Self, tag: Mir.Inst.FixedTag) !void { _ = try self.addInst(.{ .tag = tag[1], @@ -1355,6 +1482,8 @@ fn asmOpOnly(self: *Self, tag: Mir.Inst.FixedTag) !void { } fn asmPseudo(self: *Self, ops: Mir.Inst.Ops) !void { + assert(std.mem.startsWith(u8, @tagName(ops), "pseudo_") and + std.mem.endsWith(u8, @tagName(ops), "_none")); _ = try self.addInst(.{ .tag = .pseudo, .ops = ops, @@ -1379,14 +1508,22 @@ fn asmImmediate(self: *Self, tag: Mir.Inst.FixedTag, imm: Immediate) !void { .ops = switch (imm) { .signed => .i_s, .unsigned => .i_u, + .reloc => .rel, }, - .data = .{ .i = .{ - .fixes = tag[0], - .i = switch (imm) { - .signed => |s| @bitCast(s), - .unsigned => |u| @intCast(u), + .data = switch (imm) { + .reloc => |sym_off| reloc: { + assert(tag[0] == ._); + break :reloc .{ .reloc = sym_off }; }, - } }, + .signed, .unsigned => .{ .i = .{ + .fixes = tag[0], + .i = switch (imm) { + .signed => |s| @bitCast(s), + .unsigned => |u| @intCast(u), + .reloc => unreachable, + }, + } }, + }, }); } @@ -1403,29 +1540,22 @@ fn asmRegisterRegister(self: *Self, tag: Mir.Inst.FixedTag, reg1: Register, reg2 } fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.FixedTag, reg: Register, imm: Immediate) !void { - const ops: Mir.Inst.Ops = switch (imm) { - .signed => .ri_s, - .unsigned => |u| if (math.cast(u32, u)) |_| .ri_u else .ri64, + const ops: Mir.Inst.Ops, const i: u32 = switch (imm) { + .signed => |s| .{ .ri_s, @bitCast(s) }, + .unsigned => |u| if (math.cast(u32, u)) |small| + .{ .ri_u, small } + else + .{ .ri_64, try self.addExtra(Mir.Imm64.encode(imm.unsigned)) }, + .reloc => unreachable, }; _ = try self.addInst(.{ .tag = tag[1], .ops = ops, - .data = switch (ops) { - .ri_s, .ri_u => .{ .ri = .{ - .fixes = tag[0], - .r1 = reg, - .i = switch (imm) { - .signed => |s| @bitCast(s), - .unsigned => |u| @intCast(u), - }, - } }, - .ri64 => .{ .rx = .{ - .fixes = tag[0], - .r1 = reg, - .payload = try self.addExtra(Mir.Imm64.encode(imm.unsigned)), - } }, - else => unreachable, - }, + .data = .{ .ri = .{ + .fixes = tag[0], + .r1 = reg, + .i = i, + } }, }); } @@ -1488,6 +1618,7 @@ fn asmRegisterRegisterRegisterImmediate( .i = switch (imm) { .signed => |s| @bitCast(@as(i8, @intCast(s))), .unsigned => |u| @intCast(u), + .reloc => unreachable, }, } }, }); @@ -1505,6 +1636,7 @@ fn asmRegisterRegisterImmediate( .ops = switch (imm) { .signed => .rri_s, .unsigned => .rri_u, + .reloc => unreachable, }, .data = .{ .rri = .{ .fixes = tag[0], @@ -1513,6 +1645,7 @@ fn asmRegisterRegisterImmediate( .i = switch (imm) { .signed => |s| @bitCast(s), .unsigned => |u| @intCast(u), + .reloc => unreachable, }, } }, }); @@ -1610,6 +1743,7 @@ fn asmRegisterMemoryImmediate( if (switch (imm) { .signed => |s| if (math.cast(i16, s)) |x| @as(u16, @bitCast(x)) else null, .unsigned => |u| math.cast(u16, u), + .reloc => unreachable, }) |small_imm| { _ = try self.addInst(.{ .tag = tag[1], @@ -1625,6 +1759,7 @@ fn asmRegisterMemoryImmediate( const payload = try self.addExtra(Mir.Imm32{ .imm = switch (imm) { .signed => |s| @bitCast(s), .unsigned => unreachable, + .reloc => unreachable, } }); assert(payload + 1 == try self.addExtra(Mir.Memory.encode(m))); _ = try self.addInst(.{ @@ -1632,6 +1767,7 @@ fn asmRegisterMemoryImmediate( .ops = switch (imm) { .signed => .rmi_s, .unsigned => .rmi_u, + .reloc => unreachable, }, .data = .{ .rx = .{ .fixes = tag[0], @@ -1679,6 +1815,7 @@ fn asmMemoryImmediate(self: *Self, tag: Mir.Inst.FixedTag, m: Memory, imm: Immed const payload = try self.addExtra(Mir.Imm32{ .imm = switch (imm) { .signed => |s| @bitCast(s), .unsigned => |u| @intCast(u), + .reloc => unreachable, } }); assert(payload + 1 == try self.addExtra(Mir.Memory.encode(m))); _ = try self.addInst(.{ @@ -1686,6 +1823,7 @@ fn asmMemoryImmediate(self: *Self, tag: Mir.Inst.FixedTag, m: Memory, imm: Immed .ops = switch (imm) { .signed => .mi_s, .unsigned => .mi_u, + .reloc => unreachable, }, .data = .{ .x = .{ .fixes = tag[0], @@ -1938,12 +2076,49 @@ fn gen(self: *Self) InnerError!void { }); } +fn checkInvariantsAfterAirInst(self: *Self, inst: Air.Inst.Index, old_air_bookkeeping: @TypeOf(air_bookkeeping_init)) void { + assert(!self.register_manager.lockedRegsExist()); + + if (std.debug.runtime_safety) { + if (self.air_bookkeeping < old_air_bookkeeping + 1) { + std.debug.panic("in codegen.zig, handling of AIR instruction %{d} ('{}') did not do proper bookkeeping. Look for a missing call to finishAir.", .{ inst, self.air.instructions.items(.tag)[@intFromEnum(inst)] }); + } + + { // check consistency of tracked registers + var it = self.register_manager.free_registers.iterator(.{ .kind = .unset }); + while (it.next()) |index| { + const tracked_inst = self.register_manager.registers[index]; + const tracking = self.getResolvedInstValue(tracked_inst); + for (tracking.getRegs()) |reg| { + if (RegisterManager.indexOfRegIntoTracked(reg).? == index) break; + } else unreachable; // tracked register not in use + } + } + } +} + fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { const pt = self.pt; const mod = pt.zcu; const ip = &mod.intern_pool; const air_tags = self.air.instructions.items(.tag); + self.arg_index = 0; + for (body) |inst| { + wip_mir_log.debug("{}", .{self.fmtAir(inst)}); + verbose_tracking_log.debug("{}", .{self.fmtTracking()}); + + const old_air_bookkeeping = self.air_bookkeeping; + try self.inst_tracking.ensureUnusedCapacity(self.gpa, 1); + switch (air_tags[@intFromEnum(inst)]) { + .arg => try self.airArg(inst), + else => break, + } + self.checkInvariantsAfterAirInst(inst, old_air_bookkeeping); + } + + if (self.arg_index == 0) try self.airDbgVarArgs(); + self.arg_index = 0; for (body) |inst| { if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip)) continue; wip_mir_log.debug("{}", .{self.fmtAir(inst)}); @@ -2023,7 +2198,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .alloc => try self.airAlloc(inst), .ret_ptr => try self.airRetPtr(inst), - .arg => try self.airArg(inst), + .arg => try self.airDbgArg(inst), .assembly => try self.airAsm(inst), .bitcast => try self.airBitCast(inst), .block => try self.airBlock(inst), @@ -2093,6 +2268,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .dbg_inline_block => try self.airDbgInlineBlock(inst), .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, => try self.airDbgVar(inst), .call => try self.airCall(inst, .auto), @@ -2187,25 +2363,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .work_group_id => unreachable, // zig fmt: on } - - assert(!self.register_manager.lockedRegsExist()); - - if (std.debug.runtime_safety) { - if (self.air_bookkeeping < old_air_bookkeeping + 1) { - std.debug.panic("in codegen.zig, handling of AIR instruction %{d} ('{}') did not do proper bookkeeping. Look for a missing call to finishAir.", .{ inst, air_tags[@intFromEnum(inst)] }); - } - - { // check consistency of tracked registers - var it = self.register_manager.free_registers.iterator(.{ .kind = .unset }); - while (it.next()) |index| { - const tracked_inst = self.register_manager.registers[index]; - const tracking = self.getResolvedInstValue(tracked_inst); - for (tracking.getRegs()) |reg| { - if (RegisterManager.indexOfRegIntoTracked(reg).? == index) break; - } else unreachable; // tracked register not in use - } - } - } + self.checkInvariantsAfterAirInst(inst, old_air_bookkeeping); } verbose_tracking_log.debug("{}", .{self.fmtTracking()}); } @@ -2320,7 +2478,7 @@ fn finishAirBookkeeping(self: *Self) void { } fn finishAirResult(self: *Self, inst: Air.Inst.Index, result: MCValue) void { - if (self.liveness.isUnused(inst)) switch (result) { + if (self.liveness.isUnused(inst) and self.air.instructions.items(.tag)[@intFromEnum(inst)] != .arg) switch (result) { .none, .dead, .unreach => {}, else => unreachable, // Why didn't the result die? } else { @@ -2407,7 +2565,7 @@ fn computeFrameLayout(self: *Self, cc: std.builtin.CallingConvention) !FrameLayo const callee_preserved_regs = abi.getCalleePreservedRegs(abi.resolveCallingConvention(cc, self.target.*)); for (callee_preserved_regs) |reg| { - if (self.register_manager.isRegAllocated(reg) or true) { + if (self.register_manager.isRegAllocated(reg)) { save_reg_list.push(callee_preserved_regs, reg); } } @@ -2438,12 +2596,12 @@ fn computeFrameLayout(self: *Self, cc: std.builtin.CallingConvention) !FrameLayo }; } -fn getFrameAddrAlignment(self: *Self, frame_addr: FrameAddr) Alignment { +fn getFrameAddrAlignment(self: *Self, frame_addr: bits.FrameAddr) Alignment { const alloc_align = self.frame_allocs.get(@intFromEnum(frame_addr.index)).abi_align; return @enumFromInt(@min(@intFromEnum(alloc_align), @ctz(frame_addr.off))); } -fn getFrameAddrSize(self: *Self, frame_addr: FrameAddr) u32 { +fn getFrameAddrSize(self: *Self, frame_addr: bits.FrameAddr) u32 { return self.frame_allocs.get(@intFromEnum(frame_addr.index)).abi_size - @as(u31, @intCast(frame_addr.off)); } @@ -5967,10 +6125,7 @@ fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void { switch (operand) { .load_frame => |frame_addr| { if (tag_abi_size <= 8) { - const off: i32 = if (layout.tag_align.compare(.lt, layout.payload_align)) - @intCast(layout.payload_size) - else - 0; + const off: i32 = @intCast(layout.tagOffset()); break :blk try self.copyToRegisterWithInstTracking(inst, tag_ty, .{ .load_frame = .{ .index = frame_addr.index, .off = frame_addr.off + off }, }); @@ -5982,10 +6137,7 @@ fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void { ); }, .register => { - const shift: u6 = if (layout.tag_align.compare(.lt, layout.payload_align)) - @intCast(layout.payload_size * 8) - else - 0; + const shift: u6 = @intCast(layout.tagOffset() * 8); const result = try self.copyToRegisterWithInstTracking(inst, union_ty, operand); try self.genShiftBinOpMir( .{ ._r, .sh }, @@ -11795,30 +11947,30 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M fn airArg(self: *Self, inst: Air.Inst.Index) !void { const pt = self.pt; - const mod = pt.zcu; + const zcu = pt.zcu; // skip zero-bit arguments as they don't have a corresponding arg instruction var arg_index = self.arg_index; while (self.args[arg_index] == .none) arg_index += 1; self.arg_index = arg_index + 1; - const result: MCValue = if (self.liveness.isUnused(inst)) .unreach else result: { + const result: MCValue = if (self.debug_output == .none and self.liveness.isUnused(inst)) .unreach else result: { const arg_ty = self.typeOfIndex(inst); const src_mcv = self.args[arg_index]; - const dst_mcv = switch (src_mcv) { - .register, .register_pair, .load_frame => dst: { + switch (src_mcv) { + .register, .register_pair, .load_frame => { for (src_mcv.getRegs()) |reg| self.register_manager.getRegAssumeFree(reg, inst); - break :dst src_mcv; + break :result src_mcv; }, - .indirect => |reg_off| dst: { + .indirect => |reg_off| { self.register_manager.getRegAssumeFree(reg_off.reg, inst); const dst_mcv = try self.allocRegOrMem(inst, false); try self.genCopy(arg_ty, dst_mcv, src_mcv, .{}); - break :dst dst_mcv; + break :result dst_mcv; }, - .elementwise_regs_then_frame => |regs_frame_addr| dst: { + .elementwise_regs_then_frame => |regs_frame_addr| { try self.spillEflagsIfOccupied(); - const fn_info = mod.typeToFunc(self.fn_type).?; + const fn_info = zcu.typeToFunc(self.fn_type).?; const cc = abi.resolveCallingConvention(fn_info.cc, self.target.*); const param_int_regs = abi.getCAbiIntParamRegs(cc); var prev_reg: Register = undefined; @@ -11895,102 +12047,94 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { try self.asmRegisterImmediate( .{ ._, .cmp }, index_reg.to32(), - Immediate.u(arg_ty.vectorLen(mod)), + Immediate.u(arg_ty.vectorLen(zcu)), ); _ = try self.asmJccReloc(.b, loop); - break :dst dst_mcv; + break :result dst_mcv; }, else => return self.fail("TODO implement arg for {}", .{src_mcv}), - }; - - const name_nts = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; - switch (name_nts) { - .none => {}, - _ => try self.genArgDbgInfo(arg_ty, self.air.nullTerminatedString(@intFromEnum(name_nts)), src_mcv), } - - break :result dst_mcv; }; return self.finishAir(inst, result, .{ .none, .none, .none }); } -fn genArgDbgInfo(self: Self, ty: Type, name: [:0]const u8, mcv: MCValue) !void { - switch (self.debug_output) { - .dwarf => |dw| { - const loc: link.File.Dwarf.NavState.DbgInfoLoc = switch (mcv) { - .register => |reg| .{ .register = reg.dwarfNum() }, - .register_pair => |regs| .{ .register_pair = .{ - regs[0].dwarfNum(), regs[1].dwarfNum(), - } }, - // TODO use a frame index - .load_frame, .elementwise_regs_then_frame => return, - //.stack_offset => |off| .{ - // .stack = .{ - // // TODO handle -fomit-frame-pointer - // .fp_register = Register.rbp.dwarfNum(), - // .offset = -off, - // }, - //}, - else => unreachable, // not a valid function parameter - }; - // TODO: this might need adjusting like the linkers do. - // Instead of flattening the owner and passing Decl.Index here we may - // want to special case LazySymbol in DWARF linker too. - try dw.genArgDbgInfo(name, ty, self.owner.nav_index, loc); - }, - .plan9 => {}, - .none => {}, +fn airDbgArg(self: *Self, inst: Air.Inst.Index) !void { + // skip zero-bit arguments as they don't have a corresponding arg instruction + var arg_index = self.arg_index; + while (self.args[arg_index] == .none) arg_index += 1; + self.arg_index = arg_index + 1; + + if (self.debug_output != .none) { + const name = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; + if (name != .none) try self.genLocalDebugInfo(inst, self.getResolvedInstValue(inst).short); + if (self.liveness.isUnused(inst)) try self.processDeath(inst); } + for (self.args[self.arg_index..]) |arg| { + if (arg != .none) break; + } else try self.airDbgVarArgs(); + self.finishAirBookkeeping(); } -fn genVarDbgInfo( - self: Self, - tag: Air.Inst.Tag, - ty: Type, +fn airDbgVarArgs(self: *Self) !void { + if (self.pt.zcu.typeToFunc(self.fn_type).?.is_var_args) + try self.asmPseudo(.pseudo_dbg_var_args_none); +} + +fn genLocalDebugInfo( + self: *Self, + inst: Air.Inst.Index, mcv: MCValue, - name: [:0]const u8, ) !void { - const is_ptr = switch (tag) { - .dbg_var_ptr => true, - .dbg_var_val => false, + if (self.debug_output == .none) return; + switch (self.air.instructions.items(.tag)[@intFromEnum(inst)]) { else => unreachable, - }; - - switch (self.debug_output) { - .dwarf => |dw| { - const loc: link.File.Dwarf.NavState.DbgInfoLoc = switch (mcv) { - .register => |reg| .{ .register = reg.dwarfNum() }, - // TODO use a frame index - .load_frame, .lea_frame => return, - //=> |off| .{ .stack = .{ - // .fp_register = Register.rbp.dwarfNum(), - // .offset = -off, - //} }, - .memory => |address| .{ .memory = address }, - .load_symbol => |sym_off| loc: { - assert(sym_off.off == 0); - break :loc .{ .linker_load = .{ .type = .direct, .sym_index = sym_off.sym } }; - }, // TODO - .load_got => |sym_index| .{ .linker_load = .{ .type = .got, .sym_index = sym_index } }, - .load_direct => |sym_index| .{ - .linker_load = .{ .type = .direct, .sym_index = sym_index }, - }, - .immediate => |x| .{ .immediate = x }, - .undef => .undef, - .none => .none, - else => blk: { - log.debug("TODO generate debug info for {}", .{mcv}); - break :blk .nop; + .arg, .dbg_arg_inline, .dbg_var_val => |tag| { + switch (mcv) { + .none => try self.asmAir(.dbg_local, inst), + .unreach, .dead, .elementwise_regs_then_frame, .reserved_frame, .air_ref => unreachable, + .immediate => |imm| try self.asmAirImmediate(.dbg_local, inst, Immediate.u(imm)), + .lea_frame => |frame_addr| try self.asmAirFrameAddress(.dbg_local, inst, frame_addr), + .lea_symbol => |sym_off| try self.asmAirImmediate(.dbg_local, inst, Immediate.rel(sym_off)), + else => { + const ty = switch (tag) { + else => unreachable, + .arg => self.typeOfIndex(inst), + .dbg_arg_inline, .dbg_var_val => self.typeOf( + self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op.operand, + ), + }; + const frame_index = try self.allocFrameIndex(FrameAlloc.initSpill(ty, self.pt)); + try self.genSetMem(.{ .frame = frame_index }, 0, ty, mcv, .{}); + try self.asmAirMemory(.dbg_local, inst, .{ + .base = .{ .frame = frame_index }, + .mod = .{ .rm = .{ .size = .qword } }, + }); }, - }; - // TODO: this might need adjusting like the linkers do. - // Instead of flattening the owner and passing Decl.Index here we may - // want to special case LazySymbol in DWARF linker too. - try dw.genVarDbgInfo(name, ty, self.owner.nav_index, is_ptr, loc); + } + }, + .dbg_var_ptr => switch (mcv) { + else => unreachable, + .unreach, .dead, .elementwise_regs_then_frame, .reserved_frame, .air_ref => unreachable, + .lea_frame => |frame_addr| try self.asmAirMemory(.dbg_local, inst, .{ + .base = .{ .frame = frame_addr.index }, + .mod = .{ .rm = .{ + .size = .qword, + .disp = frame_addr.off, + } }, + }), + .lea_symbol => |sym_off| try self.asmAirMemory(.dbg_local, inst, .{ + .base = .{ .reloc = sym_off.sym_index }, + .mod = .{ .rm = .{ + .size = .qword, + .disp = sym_off.off, + } }, + }), + .lea_direct, .lea_got, .lea_tlv => |sym_index| try self.asmAirMemory(.dbg_local, inst, .{ + .base = .{ .reloc = sym_index }, + .mod = .{ .rm = .{ .size = .qword } }, + }), }, - .plan9 => {}, - .none => {}, } } @@ -12310,33 +12454,7 @@ fn genCall(self: *Self, info: union(enum) { if (self.bin_file.cast(.elf)) |elf_file| { const zo = elf_file.zigObjectPtr().?; const sym_index = try zo.getOrCreateMetadataForNav(elf_file, func.owner_nav); - if (self.mod.pic) { - const callee_reg: Register = switch (resolved_cc) { - .SysV => callee: { - if (!fn_info.is_var_args) break :callee .rax; - const param_regs = abi.getCAbiIntParamRegs(resolved_cc); - break :callee if (call_info.gp_count < param_regs.len) - param_regs[call_info.gp_count] - else - .r10; - }, - .Win64 => .rax, - else => unreachable, - }; - try self.genSetReg( - callee_reg, - Type.usize, - .{ .load_symbol = .{ .sym = sym_index } }, - .{}, - ); - try self.asmRegister(.{ ._, .call }, callee_reg); - } else try self.asmMemory(.{ ._, .call }, .{ - .base = .{ .reloc = .{ - .atom_index = try self.owner.getSymbolIndex(self), - .sym_index = sym_index, - } }, - .mod = .{ .rm = .{ .size = .qword } }, - }); + try self.asmImmediate(.{ ._, .call }, Immediate.rel(.{ .sym_index = sym_index })); } else if (self.bin_file.cast(.coff)) |coff_file| { const atom = try coff_file.getOrCreateAtomForNav(func.owner_nav); const sym_index = coff_file.getAtom(atom).getSymbolIndex().?; @@ -12346,13 +12464,7 @@ fn genCall(self: *Self, info: union(enum) { const zo = macho_file.getZigObject().?; const sym_index = try zo.getOrCreateMetadataForNav(macho_file, func.owner_nav); const sym = zo.symbols.items[sym_index]; - try self.genSetReg( - .rax, - Type.usize, - .{ .load_symbol = .{ .sym = sym.nlist_idx } }, - .{}, - ); - try self.asmRegister(.{ ._, .call }, .rax); + try self.asmImmediate(.{ ._, .call }, Immediate.rel(.{ .sym_index = sym.nlist_idx })); } else if (self.bin_file.cast(.plan9)) |p9| { const atom_index = try p9.seeNav(pt, func.owner_nav); const atom = p9.getAtom(atom_index); @@ -12365,7 +12477,19 @@ fn genCall(self: *Self, info: union(enum) { }); } else unreachable; }, - .@"extern" => |@"extern"| try self.genExternSymbolRef( + .@"extern" => |@"extern"| if (self.bin_file.cast(.elf)) |elf_file| { + const target_sym_index = try elf_file.getGlobalSymbol( + @"extern".name.toSlice(ip), + @"extern".lib_name.toSlice(ip), + ); + try self.asmImmediate(.{ ._, .call }, Immediate.rel(.{ .sym_index = target_sym_index })); + } else if (self.bin_file.cast(.macho)) |macho_file| { + const target_sym_index = try macho_file.getGlobalSymbol( + @"extern".name.toSlice(ip), + @"extern".lib_name.toSlice(ip), + ); + try self.asmImmediate(.{ ._, .call }, Immediate.rel(.{ .sym_index = target_sym_index })); + } else try self.genExternSymbolRef( .call, @"extern".lib_name.toSlice(ip), @"extern".name.toSlice(ip), @@ -12377,7 +12501,13 @@ fn genCall(self: *Self, info: union(enum) { try self.genSetReg(.rax, Type.usize, .{ .air_ref = callee }, .{}); try self.asmRegister(.{ ._, .call }, .rax); }, - .lib => |lib| try self.genExternSymbolRef(.call, lib.lib, lib.callee), + .lib => |lib| if (self.bin_file.cast(.elf)) |elf_file| { + const target_sym_index = try elf_file.getGlobalSymbol(lib.callee, lib.lib); + try self.asmImmediate(.{ ._, .call }, Immediate.rel(.{ .sym_index = target_sym_index })); + } else if (self.bin_file.cast(.macho)) |macho_file| { + const target_sym_index = try macho_file.getGlobalSymbol(lib.callee, lib.lib); + try self.asmImmediate(.{ ._, .call }, Immediate.rel(.{ .sym_index = target_sym_index })); + } else try self.genExternSymbolRef(.call, lib.lib, lib.callee), } return call_info.return_value.short; } @@ -13015,29 +13145,21 @@ fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) !void { self.inline_func = extra.data.func; _ = try self.addInst(.{ .tag = .pseudo, - .ops = .pseudo_dbg_inline_func, + .ops = .pseudo_dbg_enter_inline_func, .data = .{ .func = extra.data.func }, }); try self.lowerBlock(inst, @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len])); _ = try self.addInst(.{ .tag = .pseudo, - .ops = .pseudo_dbg_inline_func, + .ops = .pseudo_dbg_leave_inline_func, .data = .{ .func = old_inline_func }, }); } fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void { const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const operand = pl_op.operand; - const ty = self.typeOf(operand); - const mcv = try self.resolveInst(operand); - - const name = self.air.nullTerminatedString(pl_op.payload); - - const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; - try self.genVarDbgInfo(tag, ty, mcv, name); - - return self.finishAir(inst, .unreach, .{ operand, .none, .none }); + try self.genLocalDebugInfo(inst, try self.resolveInst(pl_op.operand)); + return self.finishAir(inst, .unreach, .{ pl_op.operand, .none, .none }); } fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !Mir.Inst.Index { @@ -13144,13 +13266,17 @@ fn isNull(self: *Self, inst: Air.Inst.Index, opt_ty: Type, opt_mcv: MCValue) !MC .lea_direct, .lea_got, .lea_tlv, - .lea_frame, .lea_symbol, .elementwise_regs_then_frame, .reserved_frame, .air_ref, => unreachable, + .lea_frame => { + self.eflags_inst = null; + return .{ .immediate = @intFromBool(false) }; + }, + .register => |opt_reg| { if (some_info.off == 0) { const some_abi_size: u32 = @intCast(some_info.ty.abiSize(pt)); @@ -13392,7 +13518,8 @@ fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void { const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; const operand = try self.resolveInst(un_op); const ty = self.typeOf(un_op); - const result = switch (try self.isNull(inst, ty, operand)) { + const result: MCValue = switch (try self.isNull(inst, ty, operand)) { + .immediate => |imm| .{ .immediate = @intFromBool(imm == 0) }, .eflags => |cc| .{ .eflags = cc.negate() }, else => unreachable, }; @@ -14096,8 +14223,8 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { .{ .reg = try self.copyToTmpRegister(Type.usize, .{ .lea_got = sym_index }) } else return self.fail("invalid modifier: '{s}'", .{modifier}), - .load_symbol => |sym_off| if (mem.eql(u8, modifier, "P")) - .{ .reg = try self.copyToTmpRegister(Type.usize, .{ .load_symbol = sym_off }) } + .lea_symbol => |sym_off| if (mem.eql(u8, modifier, "P")) + .{ .reg = try self.copyToTmpRegister(Type.usize, .{ .lea_symbol = sym_off }) } else return self.fail("invalid modifier: '{s}'", .{modifier}), else => return self.fail("invalid constraint: '{s}'", .{op_str}), @@ -14920,10 +15047,7 @@ fn genSetReg( .general_purpose => { assert(sym_off.off == 0); try self.asmRegisterMemory(.{ ._, .mov }, registerAlias(dst_reg, abi_size), .{ - .base = .{ .reloc = .{ - .atom_index = try self.owner.getSymbolIndex(self), - .sym_index = sym_off.sym, - } }, + .base = .{ .reloc = sym_off.sym_index }, .mod = .{ .rm = .{ .size = self.memSize(ty), .disp = sym_off.off, @@ -14941,10 +15065,7 @@ fn genSetReg( .ops = .direct_reloc, .data = .{ .rx = .{ .r1 = registerAlias(dst_reg, abi_size), - .payload = try self.addExtra(bits.Symbol{ - .atom_index = try self.owner.getSymbolIndex(self), - .sym_index = sym_index, - }), + .payload = try self.addExtra(bits.SymbolOffset{ .sym_index = sym_index }), } }, }); return; @@ -14969,52 +15090,38 @@ fn genSetReg( }, ); }, - .lea_symbol => |sym_index| { - const atom_index = try self.owner.getSymbolIndex(self); - switch (self.bin_file.tag) { - .elf, .macho => { - try self.asmRegisterMemory( - .{ ._, .lea }, - dst_reg.to64(), - .{ - .base = .{ .reloc = .{ - .atom_index = atom_index, - .sym_index = sym_index.sym, - } }, - .mod = .{ .rm = .{ - .size = .qword, - .disp = sym_index.off, - } }, - }, - ); - }, - else => return self.fail("TODO emit symbol sequence on {s}", .{ - @tagName(self.bin_file.tag), - }), - } - }, - .lea_direct, .lea_got => |sym_index| { - const atom_index = try self.owner.getSymbolIndex(self); - _ = try self.addInst(.{ - .tag = switch (src_mcv) { - .lea_direct => .lea, - .lea_got => .mov, - else => unreachable, - }, - .ops = switch (src_mcv) { - .lea_direct => .direct_reloc, - .lea_got => .got_reloc, - else => unreachable, + .lea_symbol => |sym_off| switch (self.bin_file.tag) { + .elf, .macho => try self.asmRegisterMemory( + .{ ._, .lea }, + dst_reg.to64(), + .{ + .base = .{ .reloc = sym_off.sym_index }, + .mod = .{ .rm = .{ + .size = .qword, + .disp = sym_off.off, + } }, }, - .data = .{ .rx = .{ - .r1 = dst_reg.to64(), - .payload = try self.addExtra(bits.Symbol{ - .atom_index = atom_index, - .sym_index = sym_index, - }), - } }, - }); + ), + else => return self.fail("TODO emit symbol sequence on {s}", .{ + @tagName(self.bin_file.tag), + }), }, + .lea_direct, .lea_got => |sym_index| _ = try self.addInst(.{ + .tag = switch (src_mcv) { + .lea_direct => .lea, + .lea_got => .mov, + else => unreachable, + }, + .ops = switch (src_mcv) { + .lea_direct => .direct_reloc, + .lea_got => .got_reloc, + else => unreachable, + }, + .data = .{ .rx = .{ + .r1 = dst_reg.to64(), + .payload = try self.addExtra(bits.SymbolOffset{ .sym_index = sym_index }), + } }, + }), .lea_tlv => unreachable, // TODO: remove this .air_ref => |src_ref| try self.genSetReg(dst_reg, ty, try self.resolveInst(src_ref), opts), } @@ -15035,7 +15142,7 @@ fn genSetMem( .none => .{ .immediate = @bitCast(@as(i64, disp)) }, .reg => |base_reg| .{ .register_offset = .{ .reg = base_reg, .off = disp } }, .frame => |base_frame_index| .{ .lea_frame = .{ .index = base_frame_index, .off = disp } }, - .reloc => |base_symbol| .{ .lea_symbol = .{ .sym = base_symbol.sym_index, .off = disp } }, + .reloc => |sym_index| .{ .lea_symbol = .{ .sym_index = sym_index, .off = disp } }, }; switch (src_mcv) { .none, @@ -15146,7 +15253,7 @@ fn genSetMem( })).write( self, .{ .base = base, .mod = .{ .rm = .{ - .size = self.memSize(ty), + .size = Memory.Size.fromBitSize(@min(self.memSize(ty).bitSize(), src_alias.bitSize())), .disp = disp, } } }, src_alias, @@ -15192,7 +15299,33 @@ fn genSetMem( @tagName(src_mcv), ty.fmt(pt), }), }, - .register_offset, + .register_offset => |reg_off| { + const src_reg = self.copyToTmpRegister(ty, src_mcv) catch |err| switch (err) { + error.OutOfRegisters => { + const src_reg = registerAlias(reg_off.reg, abi_size); + try self.asmRegisterMemory(.{ ._, .lea }, src_reg, .{ + .base = .{ .reg = src_reg }, + .mod = .{ .rm = .{ + .size = .qword, + .disp = reg_off.off, + } }, + }); + try self.genSetMem(base, disp, ty, .{ .register = reg_off.reg }, opts); + return self.asmRegisterMemory(.{ ._, .lea }, src_reg, .{ + .base = .{ .reg = src_reg }, + .mod = .{ .rm = .{ + .size = .qword, + .disp = -reg_off.off, + } }, + }); + }, + else => |e| return e, + }; + const src_lock = self.register_manager.lockRegAssumeUnused(src_reg); + defer self.register_manager.unlockReg(src_lock); + + try self.genSetMem(base, disp, ty, .{ .register = src_reg }, opts); + }, .memory, .indirect, .load_direct, @@ -15252,25 +15385,14 @@ fn genExternSymbolRef( lib: ?[]const u8, callee: []const u8, ) InnerError!void { - const atom_index = try self.owner.getSymbolIndex(self); - if (self.bin_file.cast(.elf)) |elf_file| { - _ = try self.addInst(.{ - .tag = tag, - .ops = .extern_fn_reloc, - .data = .{ .reloc = .{ - .atom_index = atom_index, - .sym_index = try elf_file.getGlobalSymbol(callee, lib), - } }, - }); - } else if (self.bin_file.cast(.coff)) |coff_file| { + if (self.bin_file.cast(.coff)) |coff_file| { const global_index = try coff_file.getGlobalSymbol(callee, lib); _ = try self.addInst(.{ .tag = .mov, .ops = .import_reloc, .data = .{ .rx = .{ .r1 = .rax, - .payload = try self.addExtra(bits.Symbol{ - .atom_index = atom_index, + .payload = try self.addExtra(bits.SymbolOffset{ .sym_index = link.File.Coff.global_symbol_bit | global_index, }), } }, @@ -15280,15 +15402,6 @@ fn genExternSymbolRef( .call => try self.asmRegister(.{ ._, .call }, .rax), else => unreachable, } - } else if (self.bin_file.cast(.macho)) |macho_file| { - _ = try self.addInst(.{ - .tag = .call, - .ops = .extern_fn_reloc, - .data = .{ .reloc = .{ - .atom_index = atom_index, - .sym_index = try macho_file.getGlobalSymbol(callee, lib), - } }, - }); } else return self.fail("TODO implement calling extern functions", .{}); } @@ -15306,10 +15419,10 @@ fn genLazySymbolRef( if (self.mod.pic) { switch (tag) { .lea, .call => try self.genSetReg(reg, Type.usize, .{ - .load_symbol = .{ .sym = sym_index }, + .lea_symbol = .{ .sym_index = sym_index }, }, .{}), .mov => try self.genSetReg(reg, Type.usize, .{ - .load_symbol = .{ .sym = sym_index }, + .load_symbol = .{ .sym_index = sym_index }, }, .{}), else => unreachable, } @@ -15318,22 +15431,13 @@ fn genLazySymbolRef( .call => try self.asmRegister(.{ ._, .call }, reg), else => unreachable, } - } else { - const reloc = bits.Symbol{ - .atom_index = try self.owner.getSymbolIndex(self), - .sym_index = sym_index, - }; - switch (tag) { - .lea, .mov => try self.asmRegisterMemory(.{ ._, .mov }, reg.to64(), .{ - .base = .{ .reloc = reloc }, - .mod = .{ .rm = .{ .size = .qword } }, - }), - .call => try self.asmMemory(.{ ._, .call }, .{ - .base = .{ .reloc = reloc }, - .mod = .{ .rm = .{ .size = .qword } }, - }), - else => unreachable, - } + } else switch (tag) { + .lea, .mov => try self.asmRegisterMemory(.{ ._, tag }, reg.to64(), .{ + .base = .{ .reloc = sym_index }, + .mod = .{ .rm = .{ .size = .qword } }, + }), + .call => try self.asmImmediate(.{ ._, .call }, Immediate.rel(.{ .sym_index = sym_index })), + else => unreachable, } } else if (self.bin_file.cast(.plan9)) |p9_file| { const atom_index = p9_file.getOrCreateAtomForLazySymbol(pt, lazy_sym) catch |err| @@ -15382,13 +15486,12 @@ fn genLazySymbolRef( return self.fail("{s} creating lazy symbol", .{@errorName(err)}); const sym = zo.symbols.items[sym_index]; switch (tag) { - .lea, .call => try self.genSetReg( - reg, - Type.usize, - .{ .load_symbol = .{ .sym = sym.nlist_idx } }, - .{}, - ), - .mov => try self.genSetReg(reg, Type.usize, .{ .load_symbol = .{ .sym = sym.nlist_idx } }, .{}), + .lea, .call => try self.genSetReg(reg, Type.usize, .{ + .lea_symbol = .{ .sym_index = sym.nlist_idx }, + }, .{}), + .mov => try self.genSetReg(reg, Type.usize, .{ + .load_symbol = .{ .sym_index = sym.nlist_idx }, + }, .{}), else => unreachable, } switch (tag) { @@ -15424,9 +15527,14 @@ fn airBitCast(self: *Self, inst: Air.Inst.Index) !void { const src_ty = self.typeOf(ty_op.operand); const result = result: { + const src_mcv = try self.resolveInst(ty_op.operand); + if (dst_ty.isPtrAtRuntime(mod) and src_ty.isPtrAtRuntime(mod)) switch (src_mcv) { + .lea_frame => break :result src_mcv, + else => if (self.reuseOperand(inst, ty_op.operand, 0, src_mcv)) break :result src_mcv, + }; + const dst_rc = self.regClassForType(dst_ty); const src_rc = self.regClassForType(src_ty); - const src_mcv = try self.resolveInst(ty_op.operand); const src_lock = if (src_mcv.getReg()) |reg| self.register_manager.lockReg(reg) else null; defer if (src_lock) |lock| self.register_manager.unlockReg(lock); @@ -18238,10 +18346,7 @@ fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void { const tag_val = try pt.enumValueFieldIndex(tag_ty, field_index); const tag_int_val = try tag_val.intFromEnum(tag_ty, pt); const tag_int = tag_int_val.toUnsignedInt(pt); - const tag_off: i32 = if (layout.tag_align.compare(.lt, layout.payload_align)) - @intCast(layout.payload_size) - else - 0; + const tag_off: i32 = @intCast(layout.tagOffset()); try self.genCopy( tag_ty, dst_mcv.address().offset(tag_off).deref(), @@ -18249,10 +18354,7 @@ fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void { .{}, ); - const pl_off: i32 = if (layout.tag_align.compare(.lt, layout.payload_align)) - 0 - else - @intCast(layout.tag_size); + const pl_off: i32 = @intCast(layout.payloadOffset()); try self.genCopy(src_ty, dst_mcv.address().offset(pl_off).deref(), src_mcv, .{}); break :result dst_mcv; @@ -18733,7 +18835,7 @@ fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!MCValue { .{ .frame = frame_index }, 0, Type.usize, - .{ .lea_symbol = .{ .sym = tlv_sym } }, + .{ .lea_symbol = .{ .sym_index = tlv_sym } }, .{}, ); break :init .{ .load_frame = .{ .index = frame_index } }; @@ -18789,8 +18891,10 @@ fn genTypedValue(self: *Self, val: Value) InnerError!MCValue { .undef => .undef, .immediate => |imm| .{ .immediate = imm }, .memory => |addr| .{ .memory = addr }, - .load_symbol => |sym_index| .{ .load_symbol = .{ .sym = sym_index } }, + .load_symbol => |sym_index| .{ .load_symbol = .{ .sym_index = sym_index } }, + .lea_symbol => |sym_index| .{ .lea_symbol = .{ .sym_index = sym_index } }, .load_direct => |sym_index| .{ .load_direct = sym_index }, + .lea_direct => |sym_index| .{ .lea_direct = sym_index }, .load_got => |sym_index| .{ .lea_got = sym_index }, .load_tlv => |sym_index| .{ .lea_tlv = sym_index }, }, diff --git a/zig/src/arch/x86_64/Disassembler.zig b/zig/src/arch/x86_64/Disassembler.zig index e0117fa17b..d21cec28ab 100644 --- a/zig/src/arch/x86_64/Disassembler.zig +++ b/zig/src/arch/x86_64/Disassembler.zig @@ -8,7 +8,7 @@ const bits = @import("bits.zig"); const encoder = @import("encoder.zig"); const Encoding = @import("Encoding.zig"); -const Immediate = bits.Immediate; +const Immediate = Instruction.Immediate; const Instruction = encoder.Instruction; const LegacyPrefixes = encoder.LegacyPrefixes; const Memory = Instruction.Memory; diff --git a/zig/src/arch/x86_64/Emit.zig b/zig/src/arch/x86_64/Emit.zig index b6c1fbb968..0461ce245a 100644 --- a/zig/src/arch/x86_64/Emit.zig +++ b/zig/src/arch/x86_64/Emit.zig @@ -1,6 +1,8 @@ //! This file contains the functionality for emitting x86_64 MIR as machine code +air: Air, lower: Lower, +atom_index: u32, debug_output: DebugInfoOutput, code: *std.ArrayList(u8), @@ -14,7 +16,7 @@ relocs: std.ArrayListUnmanaged(Reloc) = .{}, pub const Error = Lower.Error || error{ EmitFail, -}; +} || link.File.UpdateDebugInfoError; pub fn emitMir(emit: *Emit) Error!void { for (0..emit.lower.mir.instructions.len) |mir_i| { @@ -36,145 +38,111 @@ pub fn emitMir(emit: *Emit) Error!void { }) switch (lowered_relocs[0].target) { .inst => |target| try emit.relocs.append(emit.lower.allocator, .{ .source = start_offset, + .source_offset = end_offset - 4, .target = target, - .offset = end_offset - 4, + .target_offset = lowered_relocs[0].off, .length = @intCast(end_offset - start_offset), }), - .linker_extern_fn => |symbol| if (emit.lower.bin_file.cast(.elf)) |elf_file| { + .linker_extern_fn => |sym_index| if (emit.lower.bin_file.cast(.elf)) |elf_file| { // Add relocation to the decl. const zo = elf_file.zigObjectPtr().?; - const atom_ptr = zo.symbol(symbol.atom_index).atom(elf_file).?; + const atom_ptr = zo.symbol(emit.atom_index).atom(elf_file).?; const r_type = @intFromEnum(std.elf.R_X86_64.PLT32); - try atom_ptr.addReloc(elf_file, .{ + try atom_ptr.addReloc(elf_file.base.comp.gpa, .{ .r_offset = end_offset - 4, - .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | r_type, - .r_addend = -4, - }); + .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type, + .r_addend = lowered_relocs[0].off - 4, + }, zo); } else if (emit.lower.bin_file.cast(.macho)) |macho_file| { // Add relocation to the decl. const zo = macho_file.getZigObject().?; - const atom = zo.symbols.items[symbol.atom_index].getAtom(macho_file).?; + const atom = zo.symbols.items[emit.atom_index].getAtom(macho_file).?; try atom.addReloc(macho_file, .{ .tag = .@"extern", .offset = end_offset - 4, - .target = symbol.sym_index, - .addend = 0, + .target = sym_index, + .addend = lowered_relocs[0].off, .type = .branch, .meta = .{ .pcrel = true, .has_subtractor = false, .length = 2, - .symbolnum = @intCast(symbol.sym_index), + .symbolnum = @intCast(sym_index), }, }); } else if (emit.lower.bin_file.cast(.coff)) |coff_file| { // Add relocation to the decl. const atom_index = coff_file.getAtomIndexForSymbol( - .{ .sym_index = symbol.atom_index, .file = null }, + .{ .sym_index = emit.atom_index, .file = null }, ).?; - const target = if (link.File.Coff.global_symbol_bit & symbol.sym_index != 0) - coff_file.getGlobalByIndex(link.File.Coff.global_symbol_mask & symbol.sym_index) + const target = if (link.File.Coff.global_symbol_bit & sym_index != 0) + coff_file.getGlobalByIndex(link.File.Coff.global_symbol_mask & sym_index) else - link.File.Coff.SymbolWithLoc{ .sym_index = symbol.sym_index, .file = null }; + link.File.Coff.SymbolWithLoc{ .sym_index = sym_index, .file = null }; try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ .type = .direct, .target = target, .offset = end_offset - 4, - .addend = 0, + .addend = @intCast(lowered_relocs[0].off), .pcrel = true, .length = 2, }); } else return emit.fail("TODO implement extern reloc for {s}", .{ @tagName(emit.lower.bin_file.tag), }), - .linker_tlsld => |data| { + .linker_tlsld => |sym_index| { const elf_file = emit.lower.bin_file.cast(.elf).?; const zo = elf_file.zigObjectPtr().?; - const atom = zo.symbol(data.atom_index).atom(elf_file).?; + const atom = zo.symbol(emit.atom_index).atom(elf_file).?; const r_type = @intFromEnum(std.elf.R_X86_64.TLSLD); - try atom.addReloc(elf_file, .{ + try atom.addReloc(elf_file.base.comp.gpa, .{ .r_offset = end_offset - 4, - .r_info = (@as(u64, @intCast(data.sym_index)) << 32) | r_type, - .r_addend = -4, - }); + .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type, + .r_addend = lowered_relocs[0].off - 4, + }, zo); }, - .linker_dtpoff => |data| { + .linker_dtpoff => |sym_index| { const elf_file = emit.lower.bin_file.cast(.elf).?; const zo = elf_file.zigObjectPtr().?; - const atom = zo.symbol(data.atom_index).atom(elf_file).?; + const atom = zo.symbol(emit.atom_index).atom(elf_file).?; const r_type = @intFromEnum(std.elf.R_X86_64.DTPOFF32); - try atom.addReloc(elf_file, .{ + try atom.addReloc(elf_file.base.comp.gpa, .{ .r_offset = end_offset - 4, - .r_info = (@as(u64, @intCast(data.sym_index)) << 32) | r_type, - .r_addend = 0, - }); + .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type, + .r_addend = lowered_relocs[0].off, + }, zo); }, - .linker_reloc => |data| if (emit.lower.bin_file.cast(.elf)) |elf_file| { - const is_obj_or_static_lib = switch (emit.lower.output_mode) { - .Exe => false, - .Obj => true, - .Lib => emit.lower.link_mode == .static, - }; + .linker_reloc => |sym_index| if (emit.lower.bin_file.cast(.elf)) |elf_file| { const zo = elf_file.zigObjectPtr().?; - const atom = zo.symbol(data.atom_index).atom(elf_file).?; - const sym = zo.symbol(data.sym_index); - if (sym.flags.needs_zig_got and !is_obj_or_static_lib) { - _ = try sym.getOrCreateZigGotEntry(data.sym_index, elf_file); - } + const atom = zo.symbol(emit.atom_index).atom(elf_file).?; + const sym = zo.symbol(sym_index); if (emit.lower.pic) { - const r_type: u32 = if (sym.flags.needs_zig_got and !is_obj_or_static_lib) - link.File.Elf.R_ZIG_GOTPCREL - else if (sym.flags.needs_got) + const r_type: u32 = if (sym.flags.is_extern_ptr) @intFromEnum(std.elf.R_X86_64.GOTPCREL) else @intFromEnum(std.elf.R_X86_64.PC32); - try atom.addReloc(elf_file, .{ + try atom.addReloc(elf_file.base.comp.gpa, .{ .r_offset = end_offset - 4, - .r_info = (@as(u64, @intCast(data.sym_index)) << 32) | r_type, - .r_addend = -4, - }); + .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type, + .r_addend = lowered_relocs[0].off - 4, + }, zo); } else { - if (lowered_inst.encoding.mnemonic == .call and sym.flags.needs_zig_got and is_obj_or_static_lib) { - const r_type = @intFromEnum(std.elf.R_X86_64.PC32); - try atom.addReloc(elf_file, .{ - .r_offset = end_offset - 4, - .r_info = (@as(u64, @intCast(data.sym_index)) << 32) | r_type, - .r_addend = -4, - }); - } else { - const r_type: u32 = if (sym.flags.needs_zig_got and !is_obj_or_static_lib) - link.File.Elf.R_ZIG_GOT32 - else if (sym.flags.needs_got) - @intFromEnum(std.elf.R_X86_64.GOT32) - else if (sym.flags.is_tls) - @intFromEnum(std.elf.R_X86_64.TPOFF32) - else - @intFromEnum(std.elf.R_X86_64.@"32"); - try atom.addReloc(elf_file, .{ - .r_offset = end_offset - 4, - .r_info = (@as(u64, @intCast(data.sym_index)) << 32) | r_type, - .r_addend = 0, - }); - } + const r_type: u32 = if (sym.flags.is_tls) + @intFromEnum(std.elf.R_X86_64.TPOFF32) + else + @intFromEnum(std.elf.R_X86_64.@"32"); + try atom.addReloc(elf_file.base.comp.gpa, .{ + .r_offset = end_offset - 4, + .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type, + .r_addend = lowered_relocs[0].off, + }, zo); } } else if (emit.lower.bin_file.cast(.macho)) |macho_file| { - const is_obj_or_static_lib = switch (emit.lower.output_mode) { - .Exe => false, - .Obj => true, - .Lib => emit.lower.link_mode == .static, - }; const zo = macho_file.getZigObject().?; - const atom = zo.symbols.items[data.atom_index].getAtom(macho_file).?; - const sym = &zo.symbols.items[data.sym_index]; - if (sym.getSectionFlags().needs_zig_got and !is_obj_or_static_lib) { - _ = try sym.getOrCreateZigGotEntry(data.sym_index, macho_file); - } - const @"type": link.File.MachO.Relocation.Type = if (sym.getSectionFlags().needs_zig_got and !is_obj_or_static_lib) - .zig_got_load - else if (sym.getSectionFlags().needs_got) - // TODO: it is possible to emit .got_load here that can potentially be relaxed - // however this requires always to use a MOVQ mnemonic - .got + const atom = zo.symbols.items[emit.atom_index].getAtom(macho_file).?; + const sym = &zo.symbols.items[sym_index]; + const @"type": link.File.MachO.Relocation.Type = if (sym.flags.is_extern_ptr) + .got_load else if (sym.flags.tlv) .tlv else @@ -182,33 +150,33 @@ pub fn emitMir(emit: *Emit) Error!void { try atom.addReloc(macho_file, .{ .tag = .@"extern", .offset = @intCast(end_offset - 4), - .target = data.sym_index, - .addend = 0, + .target = sym_index, + .addend = lowered_relocs[0].off, .type = @"type", .meta = .{ .pcrel = true, .has_subtractor = false, .length = 2, - .symbolnum = @intCast(data.sym_index), + .symbolnum = @intCast(sym_index), }, }); } else unreachable, .linker_got, .linker_direct, .linker_import, - => |symbol| if (emit.lower.bin_file.cast(.elf)) |_| { + => |sym_index| if (emit.lower.bin_file.cast(.elf)) |_| { unreachable; } else if (emit.lower.bin_file.cast(.macho)) |_| { unreachable; } else if (emit.lower.bin_file.cast(.coff)) |coff_file| { const atom_index = coff_file.getAtomIndexForSymbol(.{ - .sym_index = symbol.atom_index, + .sym_index = emit.atom_index, .file = null, }).?; - const target = if (link.File.Coff.global_symbol_bit & symbol.sym_index != 0) - coff_file.getGlobalByIndex(link.File.Coff.global_symbol_mask & symbol.sym_index) + const target = if (link.File.Coff.global_symbol_bit & sym_index != 0) + coff_file.getGlobalByIndex(link.File.Coff.global_symbol_mask & sym_index) else - link.File.Coff.SymbolWithLoc{ .sym_index = symbol.sym_index, .file = null }; + link.File.Coff.SymbolWithLoc{ .sym_index = sym_index, .file = null }; try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ .type = switch (lowered_relocs[0].target) { .linker_got => .got, @@ -218,16 +186,15 @@ pub fn emitMir(emit: *Emit) Error!void { }, .target = target, .offset = @intCast(end_offset - 4), - .addend = 0, + .addend = @intCast(lowered_relocs[0].off), .pcrel = true, .length = 2, }); } else if (emit.lower.bin_file.cast(.plan9)) |p9_file| { - const atom_index = symbol.atom_index; - try p9_file.addReloc(atom_index, .{ // TODO we may need to add a .type field to the relocs if they are .linker_got instead of just .linker_direct - .target = symbol.sym_index, // we set sym_index to just be the atom index + try p9_file.addReloc(emit.atom_index, .{ // TODO we may need to add a .type field to the relocs if they are .linker_got instead of just .linker_direct + .target = sym_index, // we set sym_index to just be the atom index .offset = @intCast(end_offset - 4), - .addend = 0, + .addend = @intCast(lowered_relocs[0].off), .type = .pcrel, }); } else return emit.fail("TODO implement linker reloc for {s}", .{ @@ -245,13 +212,7 @@ pub fn emitMir(emit: *Emit) Error!void { else => unreachable, .pseudo_dbg_prologue_end_none => { switch (emit.debug_output) { - .dwarf => |dw| { - try dw.setPrologueEnd(); - log.debug("mirDbgPrologueEnd (line={d}, col={d})", .{ - emit.prev_di_line, emit.prev_di_column, - }); - try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column); - }, + .dwarf => |dw| try dw.setPrologueEnd(), .plan9 => {}, .none => {}, } @@ -273,18 +234,163 @@ pub fn emitMir(emit: *Emit) Error!void { .none => {}, } }, - .pseudo_dbg_inline_func => { + .pseudo_dbg_enter_inline_func => { switch (emit.debug_output) { .dwarf => |dw| { - log.debug("mirDbgInline (line={d}, col={d})", .{ + log.debug("mirDbgEnterInline (line={d}, col={d})", .{ emit.prev_di_line, emit.prev_di_column, }); - try dw.setInlineFunc(mir_inst.data.func); + try dw.enterInlineFunc(mir_inst.data.func, emit.code.items.len, emit.prev_di_line, emit.prev_di_column); }, .plan9 => {}, .none => {}, } }, + .pseudo_dbg_leave_inline_func => { + switch (emit.debug_output) { + .dwarf => |dw| { + log.debug("mirDbgLeaveInline (line={d}, col={d})", .{ + emit.prev_di_line, emit.prev_di_column, + }); + try dw.leaveInlineFunc(mir_inst.data.func, emit.code.items.len); + }, + .plan9 => {}, + .none => {}, + } + }, + .pseudo_dbg_local_a, + .pseudo_dbg_local_ai_s, + .pseudo_dbg_local_ai_u, + .pseudo_dbg_local_ai_64, + .pseudo_dbg_local_as, + .pseudo_dbg_local_aso, + .pseudo_dbg_local_aro, + .pseudo_dbg_local_af, + .pseudo_dbg_local_am, + => { + switch (emit.debug_output) { + .dwarf => |dw| { + var loc_buf: [2]link.File.Dwarf.Loc = undefined; + const air_inst_index, const loc: link.File.Dwarf.Loc = switch (mir_inst.ops) { + else => unreachable, + .pseudo_dbg_local_a => .{ mir_inst.data.a.air_inst, .empty }, + .pseudo_dbg_local_ai_s, + .pseudo_dbg_local_ai_u, + .pseudo_dbg_local_ai_64, + => .{ mir_inst.data.ai.air_inst, .{ .stack_value = stack_value: { + loc_buf[0] = switch (emit.lower.imm(mir_inst.ops, mir_inst.data.ai.i)) { + .signed => |s| .{ .consts = s }, + .unsigned => |u| .{ .constu = u }, + }; + break :stack_value &loc_buf[0]; + } } }, + .pseudo_dbg_local_as => .{ mir_inst.data.as.air_inst, .{ .addr = .{ + .sym = mir_inst.data.as.sym_index, + } } }, + .pseudo_dbg_local_aso => loc: { + const sym_off = emit.lower.mir.extraData( + bits.SymbolOffset, + mir_inst.data.ax.payload, + ).data; + break :loc .{ mir_inst.data.ax.air_inst, .{ .plus = .{ + sym: { + loc_buf[0] = .{ .addr = .{ .sym = sym_off.sym_index } }; + break :sym &loc_buf[0]; + }, + off: { + loc_buf[1] = .{ .consts = sym_off.off }; + break :off &loc_buf[1]; + }, + } } }; + }, + .pseudo_dbg_local_aro => loc: { + const air_off = emit.lower.mir.extraData( + Mir.AirOffset, + mir_inst.data.rx.payload, + ).data; + break :loc .{ air_off.air_inst, .{ .plus = .{ + reg: { + loc_buf[0] = .{ .breg = mir_inst.data.rx.r1.dwarfNum() }; + break :reg &loc_buf[0]; + }, + off: { + loc_buf[1] = .{ .consts = air_off.off }; + break :off &loc_buf[1]; + }, + } } }; + }, + .pseudo_dbg_local_af => loc: { + const reg_off = emit.lower.mir.resolveFrameAddr(emit.lower.mir.extraData( + bits.FrameAddr, + mir_inst.data.ax.payload, + ).data); + break :loc .{ mir_inst.data.ax.air_inst, .{ .plus = .{ + reg: { + loc_buf[0] = .{ .breg = reg_off.reg.dwarfNum() }; + break :reg &loc_buf[0]; + }, + off: { + loc_buf[1] = .{ .consts = reg_off.off }; + break :off &loc_buf[1]; + }, + } } }; + }, + .pseudo_dbg_local_am => loc: { + const mem = emit.lower.mem(mir_inst.data.ax.payload); + break :loc .{ mir_inst.data.ax.air_inst, .{ .plus = .{ + base: { + loc_buf[0] = switch (mem.base()) { + .none => .{ .constu = 0 }, + .reg => |reg| .{ .breg = reg.dwarfNum() }, + .frame => unreachable, + .reloc => |sym_index| .{ .addr = .{ .sym = sym_index } }, + }; + break :base &loc_buf[0]; + }, + disp: { + loc_buf[1] = switch (mem.disp()) { + .signed => |s| .{ .consts = s }, + .unsigned => |u| .{ .constu = u }, + }; + break :disp &loc_buf[1]; + }, + } } }; + }, + }; + const ip = &emit.lower.bin_file.comp.module.?.intern_pool; + const air_inst = emit.air.instructions.get(@intFromEnum(air_inst_index)); + const name: Air.NullTerminatedString = switch (air_inst.tag) { + else => unreachable, + .arg => air_inst.data.arg.name, + .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => @enumFromInt(air_inst.data.pl_op.payload), + }; + try dw.genLocalDebugInfo( + switch (air_inst.tag) { + else => unreachable, + .arg, .dbg_arg_inline => .local_arg, + .dbg_var_ptr, .dbg_var_val => .local_var, + }, + name.toSlice(emit.air), + switch (air_inst.tag) { + else => unreachable, + .arg => emit.air.typeOfIndex(air_inst_index, ip), + .dbg_var_ptr => emit.air.typeOf(air_inst.data.pl_op.operand, ip).childTypeIp(ip), + .dbg_var_val, .dbg_arg_inline => emit.air.typeOf(air_inst.data.pl_op.operand, ip), + }, + loc, + ); + }, + .plan9 => {}, + .none => {}, + } + }, + .pseudo_dbg_var_args_none => { + switch (emit.debug_output) { + .dwarf => |dw| try dw.genVarArgsDebugInfo(), + .plan9 => {}, + .none => {}, + } + }, .pseudo_dead_none => {}, }, } @@ -309,10 +415,12 @@ fn fail(emit: *Emit, comptime format: []const u8, args: anytype) Error { const Reloc = struct { /// Offset of the instruction. source: usize, + /// Offset of the relocation within the instruction. + source_offset: u32, /// Target of the relocation. target: Mir.Inst.Index, - /// Offset of the relocation within the instruction. - offset: u32, + /// Offset from the target instruction. + target_offset: i32, /// Length of the instruction. length: u5, }; @@ -325,8 +433,8 @@ fn fixupRelocs(emit: *Emit) Error!void { for (emit.relocs.items) |reloc| { const target = emit.code_offset_mapping.get(reloc.target) orelse return emit.fail("JMP/CALL relocation target not found!", .{}); - const disp = @as(i64, @intCast(target)) - @as(i64, @intCast(reloc.source + reloc.length)); - mem.writeInt(i32, emit.code.items[reloc.offset..][0..4], @intCast(disp), .little); + const disp = @as(i64, @intCast(target)) - @as(i64, @intCast(reloc.source + reloc.length)) + reloc.target_offset; + std.mem.writeInt(i32, emit.code.items[reloc.source_offset..][0..4], @intCast(disp), .little); } } @@ -379,11 +487,12 @@ fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) Error!void { } } +const bits = @import("bits.zig"); const link = @import("../../link.zig"); const log = std.log.scoped(.emit); -const mem = std.mem; const std = @import("std"); +const Air = @import("../../Air.zig"); const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; const Emit = @This(); const Lower = @import("Lower.zig"); diff --git a/zig/src/arch/x86_64/Lower.zig b/zig/src/arch/x86_64/Lower.zig index 11fe279dd9..69cd548cc3 100644 --- a/zig/src/arch/x86_64/Lower.zig +++ b/zig/src/arch/x86_64/Lower.zig @@ -4,10 +4,10 @@ bin_file: *link.File, output_mode: std.builtin.OutputMode, link_mode: std.builtin.LinkMode, pic: bool, -allocator: Allocator, +allocator: std.mem.Allocator, mir: Mir, cc: std.builtin.CallingConvention, -err_msg: ?*ErrorMsg = null, +err_msg: ?*Zcu.ErrorMsg = null, src_loc: Zcu.LazySrcLoc, result_insts_len: u8 = undefined, result_relocs_len: u8 = undefined, @@ -52,16 +52,17 @@ pub const Error = error{ pub const Reloc = struct { lowered_inst_index: u8, target: Target, + off: i32, const Target = union(enum) { inst: Mir.Inst.Index, - linker_reloc: bits.Symbol, - linker_tlsld: bits.Symbol, - linker_dtpoff: bits.Symbol, - linker_extern_fn: bits.Symbol, - linker_got: bits.Symbol, - linker_direct: bits.Symbol, - linker_import: bits.Symbol, + linker_reloc: u32, + linker_tlsld: u32, + linker_dtpoff: u32, + linker_extern_fn: u32, + linker_got: u32, + linker_direct: u32, + linker_import: u32, }; }; @@ -173,19 +174,19 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { .pseudo_j_z_and_np_inst => { assert(inst.data.inst.fixes == ._); try lower.emit(.none, .jnz, &.{ - .{ .imm = lower.reloc(.{ .inst = index + 1 }) }, + .{ .imm = lower.reloc(.{ .inst = index + 1 }, 0) }, }); try lower.emit(.none, .jnp, &.{ - .{ .imm = lower.reloc(.{ .inst = inst.data.inst.inst }) }, + .{ .imm = lower.reloc(.{ .inst = inst.data.inst.inst }, 0) }, }); }, .pseudo_j_nz_or_p_inst => { assert(inst.data.inst.fixes == ._); try lower.emit(.none, .jnz, &.{ - .{ .imm = lower.reloc(.{ .inst = inst.data.inst.inst }) }, + .{ .imm = lower.reloc(.{ .inst = inst.data.inst.inst }, 0) }, }); try lower.emit(.none, .jp, &.{ - .{ .imm = lower.reloc(.{ .inst = inst.data.inst.inst }) }, + .{ .imm = lower.reloc(.{ .inst = inst.data.inst.inst }, 0) }, }); }, @@ -195,7 +196,7 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { .{ .imm = Immediate.s(@bitCast(inst.data.ri.i)) }, }); try lower.emit(.none, .jz, &.{ - .{ .imm = lower.reloc(.{ .inst = index + 1 }) }, + .{ .imm = lower.reloc(.{ .inst = index + 1 }, 0) }, }); try lower.emit(.none, .lea, &.{ .{ .reg = inst.data.ri.r1 }, @@ -211,7 +212,7 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { .{ .reg = inst.data.ri.r1.to32() }, }); try lower.emit(.none, .jmp, &.{ - .{ .imm = lower.reloc(.{ .inst = index }) }, + .{ .imm = lower.reloc(.{ .inst = index }, 0) }, }); assert(lower.result_insts_len == pseudo_probe_align_insts); }, @@ -257,7 +258,7 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { .{ .imm = Immediate.s(page_size) }, }); try lower.emit(.none, .jae, &.{ - .{ .imm = lower.reloc(.{ .inst = index }) }, + .{ .imm = lower.reloc(.{ .inst = index }, 0) }, }); assert(lower.result_insts_len == pseudo_probe_adjust_loop_insts); }, @@ -267,7 +268,18 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { .pseudo_dbg_prologue_end_none, .pseudo_dbg_line_line_column, .pseudo_dbg_epilogue_begin_none, - .pseudo_dbg_inline_func, + .pseudo_dbg_enter_inline_func, + .pseudo_dbg_leave_inline_func, + .pseudo_dbg_local_a, + .pseudo_dbg_local_ai_s, + .pseudo_dbg_local_ai_u, + .pseudo_dbg_local_ai_64, + .pseudo_dbg_local_as, + .pseudo_dbg_local_aso, + .pseudo_dbg_local_aro, + .pseudo_dbg_local_af, + .pseudo_dbg_local_am, + .pseudo_dbg_var_args_none, .pseudo_dead_none, => {}, else => unreachable, @@ -283,17 +295,18 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { pub fn fail(lower: *Lower, comptime format: []const u8, args: anytype) Error { @setCold(true); assert(lower.err_msg == null); - lower.err_msg = try ErrorMsg.create(lower.allocator, lower.src_loc, format, args); + lower.err_msg = try Zcu.ErrorMsg.create(lower.allocator, lower.src_loc, format, args); return error.LowerFail; } -fn imm(lower: Lower, ops: Mir.Inst.Ops, i: u32) Immediate { +pub fn imm(lower: Lower, ops: Mir.Inst.Ops, i: u32) Immediate { return switch (ops) { .rri_s, .ri_s, .i_s, .mi_s, .rmi_s, + .pseudo_dbg_local_ai_s, => Immediate.s(@bitCast(i)), .rrri, @@ -306,34 +319,32 @@ fn imm(lower: Lower, ops: Mir.Inst.Ops, i: u32) Immediate { .mri, .rrm, .rrmi, + .pseudo_dbg_local_ai_u, => Immediate.u(i), - .ri64 => Immediate.u(lower.mir.extraData(Mir.Imm64, i).data.decode()), + .ri_64, + .pseudo_dbg_local_ai_64, + => Immediate.u(lower.mir.extraData(Mir.Imm64, i).data.decode()), else => unreachable, }; } -fn mem(lower: Lower, payload: u32) Memory { +pub fn mem(lower: Lower, payload: u32) Memory { return lower.mir.resolveFrameLoc(lower.mir.extraData(Mir.Memory, payload).data).decode(); } -fn reloc(lower: *Lower, target: Reloc.Target) Immediate { +fn reloc(lower: *Lower, target: Reloc.Target, off: i32) Immediate { lower.result_relocs[lower.result_relocs_len] = .{ .lowered_inst_index = lower.result_insts_len, .target = target, + .off = off, }; lower.result_relocs_len += 1; return Immediate.s(0); } fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) Error!void { - const is_obj_or_static_lib = switch (lower.output_mode) { - .Exe => false, - .Obj => true, - .Lib => lower.link_mode == .static, - }; - const emit_prefix = prefix; var emit_mnemonic = mnemonic; var emit_ops_storage: [4]Operand = undefined; @@ -343,37 +354,36 @@ fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) else => op, .mem => |mem_op| switch (mem_op.base()) { else => op, - .reloc => |sym| op: { + .reloc => |sym_index| op: { assert(prefix == .none); assert(mem_op.sib.disp == 0); assert(mem_op.sib.scale_index.scale == 0); if (lower.bin_file.cast(.elf)) |elf_file| { const zo = elf_file.zigObjectPtr().?; - const elf_sym = zo.symbol(sym.sym_index); + const elf_sym = zo.symbol(sym_index); if (elf_sym.flags.is_tls) { // TODO handle extern TLS vars, i.e., emit GD model if (lower.pic) { // Here, we currently assume local dynamic TLS vars, and so // we emit LD model. - _ = lower.reloc(.{ .linker_tlsld = sym }); + _ = lower.reloc(.{ .linker_tlsld = sym_index }, 0); lower.result_insts[lower.result_insts_len] = try Instruction.new(.none, .lea, &[_]Operand{ .{ .reg = .rdi }, .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }, }); lower.result_insts_len += 1; - _ = lower.reloc(.{ .linker_extern_fn = .{ - .atom_index = sym.atom_index, - .sym_index = try elf_file.getGlobalSymbol("__tls_get_addr", null), - } }); + _ = lower.reloc(.{ + .linker_extern_fn = try elf_file.getGlobalSymbol("__tls_get_addr", null), + }, 0); lower.result_insts[lower.result_insts_len] = try Instruction.new(.none, .call, &[_]Operand{ .{ .imm = Immediate.s(0) }, }); lower.result_insts_len += 1; - _ = lower.reloc(.{ .linker_dtpoff = sym }); + _ = lower.reloc(.{ .linker_dtpoff = sym_index }, 0); emit_mnemonic = .lea; break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{ .base = .{ .reg = .rax }, @@ -387,7 +397,7 @@ fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) .{ .mem = Memory.sib(.qword, .{ .base = .{ .reg = .fs } }) }, }); lower.result_insts_len += 1; - _ = lower.reloc(.{ .linker_reloc = sym }); + _ = lower.reloc(.{ .linker_reloc = sym_index }, 0); emit_mnemonic = .lea; break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{ .base = .{ .reg = .rax }, @@ -396,40 +406,47 @@ fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) } } - _ = lower.reloc(.{ .linker_reloc = sym }); - break :op if (lower.pic) switch (mnemonic) { + _ = lower.reloc(.{ .linker_reloc = sym_index }, 0); + if (lower.pic) switch (mnemonic) { .lea => { + if (elf_sym.flags.is_extern_ptr) emit_mnemonic = .mov; break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }; }, .mov => { - if (is_obj_or_static_lib and elf_sym.flags.needs_zig_got) emit_mnemonic = .lea; + if (elf_sym.flags.is_extern_ptr) { + const reg = ops[0].reg; + lower.result_insts[lower.result_insts_len] = + try Instruction.new(.none, .mov, &[_]Operand{ + .{ .reg = reg.to64() }, + .{ .mem = Memory.rip(.qword, 0) }, + }); + lower.result_insts_len += 1; + break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{ .base = .{ + .reg = reg.to64(), + } }) }; + } break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }; }, else => unreachable, } else switch (mnemonic) { - .call => break :op if (is_obj_or_static_lib and elf_sym.flags.needs_zig_got) .{ - .imm = Immediate.s(0), - } else .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{ + .call => break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{ .base = .{ .reg = .ds }, }) }, .lea => { emit_mnemonic = .mov; break :op .{ .imm = Immediate.s(0) }; }, - .mov => { - if (is_obj_or_static_lib and elf_sym.flags.needs_zig_got) emit_mnemonic = .lea; - break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{ - .base = .{ .reg = .ds }, - }) }; - }, + .mov => break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{ + .base = .{ .reg = .ds }, + }) }, else => unreachable, - }; + } } else if (lower.bin_file.cast(.macho)) |macho_file| { const zo = macho_file.getZigObject().?; - const macho_sym = zo.symbols.items[sym.sym_index]; + const macho_sym = zo.symbols.items[sym_index]; if (macho_sym.flags.tlv) { - _ = lower.reloc(.{ .linker_reloc = sym }); + _ = lower.reloc(.{ .linker_reloc = sym_index }, 0); lower.result_insts[lower.result_insts_len] = try Instruction.new(.none, .mov, &[_]Operand{ .{ .reg = .rdi }, @@ -445,13 +462,25 @@ fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) break :op .{ .reg = .rax }; } - _ = lower.reloc(.{ .linker_reloc = sym }); + _ = lower.reloc(.{ .linker_reloc = sym_index }, 0); break :op switch (mnemonic) { .lea => { + if (macho_sym.flags.is_extern_ptr) emit_mnemonic = .mov; break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }; }, .mov => { - if (is_obj_or_static_lib and macho_sym.getSectionFlags().needs_zig_got) emit_mnemonic = .lea; + if (macho_sym.flags.is_extern_ptr) { + const reg = ops[0].reg; + lower.result_insts[lower.result_insts_len] = + try Instruction.new(.none, .mov, &[_]Operand{ + .{ .reg = reg.to64() }, + .{ .mem = Memory.rip(.qword, 0) }, + }); + lower.result_insts_len += 1; + break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{ .base = .{ + .reg = reg.to64(), + } }) }; + } break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }; }, else => unreachable, @@ -477,15 +506,15 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void { .rrrr => inst.data.rrrr.fixes, .rrri => inst.data.rrri.fixes, .rri_s, .rri_u => inst.data.rri.fixes, - .ri_s, .ri_u => inst.data.ri.fixes, - .ri64, .rm, .rmi_s, .mr => inst.data.rx.fixes, + .ri_s, .ri_u, .ri_64 => inst.data.ri.fixes, + .rm, .rmi_s, .mr => inst.data.rx.fixes, .mrr, .rrm, .rmr => inst.data.rrx.fixes, .rmi, .mri => inst.data.rix.fixes, .rrmr => inst.data.rrrx.fixes, .rrmi => inst.data.rrix.fixes, .mi_u, .mi_s => inst.data.x.fixes, .m => inst.data.x.fixes, - .extern_fn_reloc, .got_reloc, .direct_reloc, .import_reloc, .tlv_reloc => ._, + .extern_fn_reloc, .got_reloc, .direct_reloc, .import_reloc, .tlv_reloc, .rel => ._, else => return lower.fail("TODO lower .{s}", .{@tagName(inst.ops)}), }; try lower.emit(switch (fixes) { @@ -512,7 +541,7 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void { }, switch (inst.ops) { .none => &.{}, .inst => &.{ - .{ .imm = lower.reloc(.{ .inst = inst.data.inst.inst }) }, + .{ .imm = lower.reloc(.{ .inst = inst.data.inst.inst }, 0) }, }, .i_s, .i_u => &.{ .{ .imm = lower.imm(inst.ops, inst.data.i.i) }, @@ -541,14 +570,10 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void { .{ .reg = inst.data.rrri.r3 }, .{ .imm = lower.imm(inst.ops, inst.data.rrri.i) }, }, - .ri_s, .ri_u => &.{ + .ri_s, .ri_u, .ri_64 => &.{ .{ .reg = inst.data.ri.r1 }, .{ .imm = lower.imm(inst.ops, inst.data.ri.i) }, }, - .ri64 => &.{ - .{ .reg = inst.data.rx.r1 }, - .{ .imm = lower.imm(inst.ops, inst.data.rx.payload) }, - }, .rri_s, .rri_u => &.{ .{ .reg = inst.data.rri.r1 }, .{ .reg = inst.data.rri.r2 }, @@ -617,18 +642,18 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void { .{ .mem = lower.mem(inst.data.rrix.payload) }, .{ .imm = lower.imm(inst.ops, inst.data.rrix.i) }, }, - .extern_fn_reloc => &.{ - .{ .imm = lower.reloc(.{ .linker_extern_fn = inst.data.reloc }) }, + .extern_fn_reloc, .rel => &.{ + .{ .imm = lower.reloc(.{ .linker_extern_fn = inst.data.reloc.sym_index }, inst.data.reloc.off) }, }, .got_reloc, .direct_reloc, .import_reloc => ops: { const reg = inst.data.rx.r1; - const extra = lower.mir.extraData(bits.Symbol, inst.data.rx.payload).data; + const extra = lower.mir.extraData(bits.SymbolOffset, inst.data.rx.payload).data; _ = lower.reloc(switch (inst.ops) { - .got_reloc => .{ .linker_got = extra }, - .direct_reloc => .{ .linker_direct = extra }, - .import_reloc => .{ .linker_import = extra }, + .got_reloc => .{ .linker_got = extra.sym_index }, + .direct_reloc => .{ .linker_direct = extra.sym_index }, + .import_reloc => .{ .linker_import = extra.sym_index }, else => unreachable, - }); + }, extra.off); break :ops &.{ .{ .reg = reg }, .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(reg.bitSize()), 0) }, @@ -657,10 +682,7 @@ const encoder = @import("encoder.zig"); const link = @import("../../link.zig"); const std = @import("std"); -const Air = @import("../../Air.zig"); -const Allocator = std.mem.Allocator; -const ErrorMsg = Zcu.ErrorMsg; -const Immediate = bits.Immediate; +const Immediate = Instruction.Immediate; const Instruction = encoder.Instruction; const Lower = @This(); const Memory = Instruction.Memory; diff --git a/zig/src/arch/x86_64/Mir.zig b/zig/src/arch/x86_64/Mir.zig index d2dd6237a5..2ccb609839 100644 --- a/zig/src/arch/x86_64/Mir.zig +++ b/zig/src/arch/x86_64/Mir.zig @@ -760,8 +760,8 @@ pub const Inst = struct { /// Uses `ri` payload. ri_u, /// Register, 64-bit unsigned immediate operands. - /// Uses `rx` payload with payload type `Imm64`. - ri64, + /// Uses `ri` payload with `i` index of extra data of type `Imm64`. + ri_64, /// Immediate (sign-extended) operand. /// Uses `imm` payload. i_s, @@ -769,7 +769,7 @@ pub const Inst = struct { /// Uses `imm` payload. i_u, /// Relative displacement operand. - /// Uses `imm` payload. + /// Uses `reloc` payload. rel, /// Register, memory operands. /// Uses `rx` payload with extra data of type `Memory`. @@ -796,7 +796,7 @@ pub const Inst = struct { /// Uses `rrix` payload with extra data of type `Memory`. rrmi, /// Single memory operand. - /// Uses `x` with extra data of type `Memory`. + /// Uses `x` payload with extra data of type `Memory`. m, /// Memory, immediate (sign-extend) operands. /// Uses `x` payload with extra data of type `Imm32` followed by `Memory`. @@ -820,16 +820,16 @@ pub const Inst = struct { /// Uses `reloc` payload. extern_fn_reloc, /// Linker relocation - GOT indirection. - /// Uses `rx` payload with extra data of type `bits.Symbol`. + /// Uses `rx` payload with extra data of type `bits.SymbolOffset`. got_reloc, /// Linker relocation - direct reference. - /// Uses `rx` payload with extra data of type `bits.Symbol`. + /// Uses `rx` payload with extra data of type `bits.SymbolOffset`. direct_reloc, /// Linker relocation - imports table indirection (binding). - /// Uses `rx` payload with extra data of type `bits.Symbol`. + /// Uses `rx` payload with extra data of type `bits.SymbolOffset`. import_reloc, /// Linker relocation - threadlocal variable via GOT indirection. - /// Uses `rx` payload with extra data of type `bits.Symbol`. + /// Uses `rx` payload with extra data of type `bits.SymbolOffset`. tlv_reloc, // Pseudo instructions: @@ -868,16 +868,16 @@ pub const Inst = struct { pseudo_j_nz_or_p_inst, /// Probe alignment - /// Uses `ri` payload + /// Uses `ri` payload. pseudo_probe_align_ri_s, /// Probe adjust unrolled - /// Uses `ri` payload + /// Uses `ri` payload. pseudo_probe_adjust_unrolled_ri_s, /// Probe adjust setup - /// Uses `rri` payload + /// Uses `rri` payload. pseudo_probe_adjust_setup_rri_s, /// Probe adjust loop - /// Uses `rr` payload + /// Uses `rr` payload. pseudo_probe_adjust_loop_rr, /// Push registers /// Uses `reg_list` payload. @@ -893,8 +893,39 @@ pub const Inst = struct { pseudo_dbg_line_line_column, /// Start of epilogue pseudo_dbg_epilogue_begin_none, - /// Start or end of inline function - pseudo_dbg_inline_func, + /// Start of inline function + pseudo_dbg_enter_inline_func, + /// End of inline function + pseudo_dbg_leave_inline_func, + /// Local argument or variable. + /// Uses `a` payload. + pseudo_dbg_local_a, + /// Local argument or variable. + /// Uses `ai` payload. + pseudo_dbg_local_ai_s, + /// Local argument or variable. + /// Uses `ai` payload. + pseudo_dbg_local_ai_u, + /// Local argument or variable. + /// Uses `ai` payload with extra data of type `Imm64`. + pseudo_dbg_local_ai_64, + /// Local argument or variable. + /// Uses `as` payload. + pseudo_dbg_local_as, + /// Local argument or variable. + /// Uses `ax` payload with extra data of type `bits.SymbolOffset`. + pseudo_dbg_local_aso, + /// Local argument or variable. + /// Uses `rx` payload with extra data of type `AirOffset`. + pseudo_dbg_local_aro, + /// Local argument or variable. + /// Uses `ax` payload with extra data of type `bits.FrameAddr`. + pseudo_dbg_local_af, + /// Local argument or variable. + /// Uses `ax` payload with extra data of type `Memory`. + pseudo_dbg_local_am, + /// Remaining arguments are varargs. + pseudo_dbg_var_args_none, /// Tombstone /// Emitter should skip this instruction. @@ -997,10 +1028,28 @@ pub const Inst = struct { fixes: Fixes = ._, payload: u32, }, + ix: struct { + payload: u32, + }, + a: struct { + air_inst: Air.Inst.Index, + }, + ai: struct { + air_inst: Air.Inst.Index, + i: u32, + }, + as: struct { + air_inst: Air.Inst.Index, + sym_index: u32, + }, + ax: struct { + air_inst: Air.Inst.Index, + payload: u32, + }, /// Relocation for the linker where: - /// * `atom_index` is the index of the source /// * `sym_index` is the index of the target - reloc: bits.Symbol, + /// * `off` is the offset from the target + reloc: bits.SymbolOffset, /// Debug line and column position line_column: struct { line: u32, @@ -1020,6 +1069,8 @@ pub const Inst = struct { } }; +pub const AirOffset = struct { air_inst: Air.Inst.Index, off: i32 }; + /// Used in conjunction with payload to transfer a list of used registers in a compact manner. pub const RegisterList = struct { bitset: BitSet = BitSet.initEmpty(), @@ -1118,15 +1169,13 @@ pub const Memory = struct { .none => undefined, .reg => |reg| @intFromEnum(reg), .frame => |frame_index| @intFromEnum(frame_index), - .reloc => |symbol| symbol.sym_index, + .reloc => |sym_index| sym_index, }, .off = switch (mem.mod) { .rm => |rm| @bitCast(rm.disp), .off => |off| @truncate(off), }, - .extra = if (mem.base == .reloc) - mem.base.reloc.atom_index - else if (mem.mod == .off) + .extra = if (mem.mod == .off) @intCast(mem.mod.off >> 32) else undefined, @@ -1146,7 +1195,7 @@ pub const Memory = struct { .none => .none, .reg => .{ .reg = @enumFromInt(mem.base) }, .frame => .{ .frame = @enumFromInt(mem.base) }, - .reloc => .{ .reloc = .{ .atom_index = mem.extra, .sym_index = mem.base } }, + .reloc => .{ .reloc = mem.base }, }, .scale_index = switch (mem.info.index) { .none => null, @@ -1186,6 +1235,7 @@ pub fn extraData(mir: Mir, comptime T: type, index: u32) struct { data: T, end: @field(result, field.name) = switch (field.type) { u32 => mir.extra[i], i32, Memory.Info => @bitCast(mir.extra[i]), + bits.FrameIndex, Air.Inst.Index => @enumFromInt(mir.extra[i]), else => @compileError("bad field type: " ++ field.name ++ ": " ++ @typeName(field.type)), }; i += 1; @@ -1201,10 +1251,15 @@ pub const FrameLoc = struct { disp: i32, }; +pub fn resolveFrameAddr(mir: Mir, frame_addr: bits.FrameAddr) bits.RegisterOffset { + const frame_loc = mir.frame_locs.get(@intFromEnum(frame_addr.index)); + return .{ .reg = frame_loc.base, .off = frame_loc.disp + frame_addr.off }; +} + pub fn resolveFrameLoc(mir: Mir, mem: Memory) Memory { return switch (mem.info.base) { .none, .reg, .reloc => mem, - .frame => if (mir.frame_locs.len > 0) Memory{ + .frame => if (mir.frame_locs.len > 0) .{ .info = .{ .base = .reg, .mod = mem.info.mod, @@ -1225,6 +1280,7 @@ const builtin = @import("builtin"); const encoder = @import("encoder.zig"); const std = @import("std"); +const Air = @import("../../Air.zig"); const IntegerBitSet = std.bit_set.IntegerBitSet; const InternPool = @import("../../InternPool.zig"); const Mir = @This(); diff --git a/zig/src/arch/x86_64/bits.zig b/zig/src/arch/x86_64/bits.zig index 6896d6c2e7..839084456a 100644 --- a/zig/src/arch/x86_64/bits.zig +++ b/zig/src/arch/x86_64/bits.zig @@ -4,7 +4,6 @@ const expect = std.testing.expect; const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; -const DW = std.dwarf; /// EFLAGS condition codes pub const Condition = enum(u5) { @@ -448,26 +447,11 @@ pub const FrameIndex = enum(u32) { } }; -/// A linker symbol not yet allocated in VM. -pub const Symbol = struct { - /// Index of the containing atom. - atom_index: u32, - /// Index into the linker's symbol table. - sym_index: u32, +pub const FrameAddr = struct { index: FrameIndex, off: i32 = 0 }; - pub fn format( - sym: Symbol, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) @TypeOf(writer).Error!void { - try writer.writeAll("Symbol("); - try std.fmt.formatType(sym.atom_index, fmt, options, writer, 0); - try writer.writeAll(", "); - try std.fmt.formatType(sym.sym_index, fmt, options, writer, 0); - try writer.writeByte(')'); - } -}; +pub const RegisterOffset = struct { reg: Register, off: i32 = 0 }; + +pub const SymbolOffset = struct { sym_index: u32, off: i32 = 0 }; pub const Memory = struct { base: Base, @@ -477,7 +461,7 @@ pub const Memory = struct { none, reg: Register, frame: FrameIndex, - reloc: Symbol, + reloc: u32, pub const Tag = @typeInfo(Base).Union.tag_type.?; @@ -569,6 +553,7 @@ pub const Memory = struct { pub const Immediate = union(enum) { signed: i32, unsigned: u64, + reloc: SymbolOffset, pub fn u(x: u64) Immediate { return .{ .unsigned = x }; @@ -578,39 +563,19 @@ pub const Immediate = union(enum) { return .{ .signed = x }; } - pub fn asSigned(imm: Immediate, bit_size: u64) i64 { - return switch (imm) { - .signed => |x| switch (bit_size) { - 1, 8 => @as(i8, @intCast(x)), - 16 => @as(i16, @intCast(x)), - 32, 64 => x, - else => unreachable, - }, - .unsigned => |x| switch (bit_size) { - 1, 8 => @as(i8, @bitCast(@as(u8, @intCast(x)))), - 16 => @as(i16, @bitCast(@as(u16, @intCast(x)))), - 32 => @as(i32, @bitCast(@as(u32, @intCast(x)))), - 64 => @bitCast(x), - else => unreachable, - }, - }; + pub fn rel(sym_off: SymbolOffset) Immediate { + return .{ .reloc = sym_off }; } - pub fn asUnsigned(imm: Immediate, bit_size: u64) u64 { - return switch (imm) { - .signed => |x| switch (bit_size) { - 1, 8 => @as(u8, @bitCast(@as(i8, @intCast(x)))), - 16 => @as(u16, @bitCast(@as(i16, @intCast(x)))), - 32, 64 => @as(u32, @bitCast(x)), - else => unreachable, - }, - .unsigned => |x| switch (bit_size) { - 1, 8 => @as(u8, @intCast(x)), - 16 => @as(u16, @intCast(x)), - 32 => @as(u32, @intCast(x)), - 64 => x, - else => unreachable, - }, - }; + pub fn format( + imm: Immediate, + comptime _: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + switch (imm) { + inline else => |int| try writer.print("{d}", .{int}), + .reloc => |sym_off| try writer.print("Symbol({[sym_index]d}) + {[off]d}", sym_off), + } } }; diff --git a/zig/src/arch/x86_64/encoder.zig b/zig/src/arch/x86_64/encoder.zig index 5f49443934..ce5b9c4d0f 100644 --- a/zig/src/arch/x86_64/encoder.zig +++ b/zig/src/arch/x86_64/encoder.zig @@ -7,7 +7,6 @@ const testing = std.testing; const bits = @import("bits.zig"); const Encoding = @import("Encoding.zig"); const FrameIndex = bits.FrameIndex; -const Immediate = bits.Immediate; const Register = bits.Register; const Symbol = bits.Symbol; @@ -28,6 +27,55 @@ pub const Instruction = struct { repnz, }; + pub const Immediate = union(enum) { + signed: i32, + unsigned: u64, + + pub fn u(x: u64) Immediate { + return .{ .unsigned = x }; + } + + pub fn s(x: i32) Immediate { + return .{ .signed = x }; + } + + pub fn asSigned(imm: Immediate, bit_size: u64) i64 { + return switch (imm) { + .signed => |x| switch (bit_size) { + 1, 8 => @as(i8, @intCast(x)), + 16 => @as(i16, @intCast(x)), + 32, 64 => x, + else => unreachable, + }, + .unsigned => |x| switch (bit_size) { + 1, 8 => @as(i8, @bitCast(@as(u8, @intCast(x)))), + 16 => @as(i16, @bitCast(@as(u16, @intCast(x)))), + 32 => @as(i32, @bitCast(@as(u32, @intCast(x)))), + 64 => @bitCast(x), + else => unreachable, + }, + }; + } + + pub fn asUnsigned(imm: Immediate, bit_size: u64) u64 { + return switch (imm) { + .signed => |x| switch (bit_size) { + 1, 8 => @as(u8, @bitCast(@as(i8, @intCast(x)))), + 16 => @as(u16, @bitCast(@as(i16, @intCast(x)))), + 32, 64 => @as(u32, @bitCast(x)), + else => unreachable, + }, + .unsigned => |x| switch (bit_size) { + 1, 8 => @as(u8, @intCast(x)), + 16 => @as(u16, @intCast(x)), + 32 => @as(u32, @intCast(x)), + 64 => x, + else => unreachable, + }, + }; + } + }; + pub const Memory = union(enum) { sib: Sib, rip: Rip, @@ -80,8 +128,8 @@ pub const Instruction = struct { } }; } - pub fn rip(ptr_size: PtrSize, disp: i32) Memory { - return .{ .rip = .{ .ptr_size = ptr_size, .disp = disp } }; + pub fn rip(ptr_size: PtrSize, displacement: i32) Memory { + return .{ .rip = .{ .ptr_size = ptr_size, .disp = displacement } }; } pub fn isSegmentRegister(mem: Memory) bool { @@ -110,6 +158,14 @@ pub const Instruction = struct { }; } + pub fn disp(mem: Memory) Immediate { + return switch (mem) { + .sib => |s| Immediate.s(s.disp), + .rip => |r| Immediate.s(r.disp), + .moffs => |m| Immediate.u(m.offset), + }; + } + pub fn bitSize(mem: Memory) u64 { return switch (mem) { .rip => |r| r.ptr_size.bitSize(), @@ -210,17 +266,12 @@ pub const Instruction = struct { try writer.writeByte('['); - var any = false; + var any = true; switch (sib.base) { - .none => {}, - .reg => |reg| { - try writer.print("{s}", .{@tagName(reg)}); - any = true; - }, - inline .frame, .reloc => |payload| { - try writer.print("{}", .{payload}); - any = true; - }, + .none => any = false, + .reg => |reg| try writer.print("{s}", .{@tagName(reg)}), + .frame => |frame_index| try writer.print("{}", .{frame_index}), + .reloc => |sym_index| try writer.print("Symbol({d})", .{sym_index}), } if (mem.scaleIndex()) |si| { if (any) try writer.writeAll(" + "); @@ -1119,7 +1170,7 @@ test "encode" { const inst = try Instruction.new(.none, .mov, &.{ .{ .reg = .rbx }, - .{ .imm = Immediate.u(4) }, + .{ .imm = Instruction.Immediate.u(4) }, }); try inst.encode(buf.writer(), .{}); try testing.expectEqualSlices(u8, &.{ 0x48, 0xc7, 0xc3, 0x4, 0x0, 0x0, 0x0 }, buf.items); @@ -1129,47 +1180,47 @@ test "lower I encoding" { var enc = TestEncode{}; try enc.encode(.push, &.{ - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x6A\x10", enc.code(), "push 0x10"); try enc.encode(.push, &.{ - .{ .imm = Immediate.u(0x1000) }, + .{ .imm = Instruction.Immediate.u(0x1000) }, }); try expectEqualHexStrings("\x66\x68\x00\x10", enc.code(), "push 0x1000"); try enc.encode(.push, &.{ - .{ .imm = Immediate.u(0x10000000) }, + .{ .imm = Instruction.Immediate.u(0x10000000) }, }); try expectEqualHexStrings("\x68\x00\x00\x00\x10", enc.code(), "push 0x10000000"); try enc.encode(.adc, &.{ .{ .reg = .rax }, - .{ .imm = Immediate.u(0x10000000) }, + .{ .imm = Instruction.Immediate.u(0x10000000) }, }); try expectEqualHexStrings("\x48\x15\x00\x00\x00\x10", enc.code(), "adc rax, 0x10000000"); try enc.encode(.add, &.{ .{ .reg = .al }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x04\x10", enc.code(), "add al, 0x10"); try enc.encode(.add, &.{ .{ .reg = .rax }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x48\x83\xC0\x10", enc.code(), "add rax, 0x10"); try enc.encode(.sbb, &.{ .{ .reg = .ax }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x66\x1D\x10\x00", enc.code(), "sbb ax, 0x10"); try enc.encode(.xor, &.{ .{ .reg = .al }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x34\x10", enc.code(), "xor al, 0x10"); } @@ -1179,43 +1230,43 @@ test "lower MI encoding" { try enc.encode(.mov, &.{ .{ .reg = .r12 }, - .{ .imm = Immediate.u(0x1000) }, + .{ .imm = Instruction.Immediate.u(0x1000) }, }); try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000"); try enc.encode(.mov, &.{ .{ .mem = Instruction.Memory.sib(.byte, .{ .base = .{ .reg = .r12 } }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x41\xC6\x04\x24\x10", enc.code(), "mov BYTE PTR [r12], 0x10"); try enc.encode(.mov, &.{ .{ .reg = .r12 }, - .{ .imm = Immediate.u(0x1000) }, + .{ .imm = Instruction.Immediate.u(0x1000) }, }); try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000"); try enc.encode(.mov, &.{ .{ .reg = .r12 }, - .{ .imm = Immediate.u(0x1000) }, + .{ .imm = Instruction.Immediate.u(0x1000) }, }); try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000"); try enc.encode(.mov, &.{ .{ .reg = .rax }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", enc.code(), "mov rax, 0x10"); try enc.encode(.mov, &.{ .{ .mem = Instruction.Memory.sib(.dword, .{ .base = .{ .reg = .r11 } }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", enc.code(), "mov DWORD PTR [r11], 0x10"); try enc.encode(.mov, &.{ .{ .mem = Instruction.Memory.rip(.qword, 0x10) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings( "\x48\xC7\x05\x10\x00\x00\x00\x10\x00\x00\x00", @@ -1225,19 +1276,19 @@ test "lower MI encoding" { try enc.encode(.mov, &.{ .{ .mem = Instruction.Memory.sib(.qword, .{ .base = .{ .reg = .rbp }, .disp = -8 }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x48\xc7\x45\xf8\x10\x00\x00\x00", enc.code(), "mov QWORD PTR [rbp - 8], 0x10"); try enc.encode(.mov, &.{ .{ .mem = Instruction.Memory.sib(.word, .{ .base = .{ .reg = .rbp }, .disp = -2 }) }, - .{ .imm = Immediate.s(-16) }, + .{ .imm = Instruction.Immediate.s(-16) }, }); try expectEqualHexStrings("\x66\xC7\x45\xFE\xF0\xFF", enc.code(), "mov WORD PTR [rbp - 2], -16"); try enc.encode(.mov, &.{ .{ .mem = Instruction.Memory.sib(.byte, .{ .base = .{ .reg = .rbp }, .disp = -1 }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\xC6\x45\xFF\x10", enc.code(), "mov BYTE PTR [rbp - 1], 0x10"); @@ -1247,7 +1298,7 @@ test "lower MI encoding" { .disp = 0x10000000, .scale_index = .{ .scale = 2, .index = .rcx }, }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings( "\x48\xC7\x04\x4D\x00\x00\x00\x10\x10\x00\x00\x00", @@ -1257,43 +1308,43 @@ test "lower MI encoding" { try enc.encode(.adc, &.{ .{ .mem = Instruction.Memory.sib(.byte, .{ .base = .{ .reg = .rbp }, .disp = -0x10 }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x80\x55\xF0\x10", enc.code(), "adc BYTE PTR [rbp - 0x10], 0x10"); try enc.encode(.adc, &.{ .{ .mem = Instruction.Memory.rip(.qword, 0) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x48\x83\x15\x00\x00\x00\x00\x10", enc.code(), "adc QWORD PTR [rip], 0x10"); try enc.encode(.adc, &.{ .{ .reg = .rax }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x48\x83\xD0\x10", enc.code(), "adc rax, 0x10"); try enc.encode(.add, &.{ .{ .mem = Instruction.Memory.sib(.dword, .{ .base = .{ .reg = .rdx }, .disp = -8 }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x83\x42\xF8\x10", enc.code(), "add DWORD PTR [rdx - 8], 0x10"); try enc.encode(.add, &.{ .{ .reg = .rax }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x48\x83\xC0\x10", enc.code(), "add rax, 0x10"); try enc.encode(.add, &.{ .{ .mem = Instruction.Memory.sib(.qword, .{ .base = .{ .reg = .rbp }, .disp = -0x10 }) }, - .{ .imm = Immediate.s(-0x10) }, + .{ .imm = Instruction.Immediate.s(-0x10) }, }); try expectEqualHexStrings("\x48\x83\x45\xF0\xF0", enc.code(), "add QWORD PTR [rbp - 0x10], -0x10"); try enc.encode(.@"and", &.{ .{ .mem = Instruction.Memory.sib(.dword, .{ .base = .{ .reg = .ds }, .disp = 0x10000000 }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings( "\x83\x24\x25\x00\x00\x00\x10\x10", @@ -1303,7 +1354,7 @@ test "lower MI encoding" { try enc.encode(.@"and", &.{ .{ .mem = Instruction.Memory.sib(.dword, .{ .base = .{ .reg = .es }, .disp = 0x10000000 }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings( "\x26\x83\x24\x25\x00\x00\x00\x10\x10", @@ -1313,7 +1364,7 @@ test "lower MI encoding" { try enc.encode(.@"and", &.{ .{ .mem = Instruction.Memory.sib(.dword, .{ .base = .{ .reg = .r12 }, .disp = 0x10000000 }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings( "\x41\x83\xA4\x24\x00\x00\x00\x10\x10", @@ -1323,7 +1374,7 @@ test "lower MI encoding" { try enc.encode(.sub, &.{ .{ .mem = Instruction.Memory.sib(.dword, .{ .base = .{ .reg = .r11 }, .disp = 0x10000000 }) }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings( "\x41\x83\xAB\x00\x00\x00\x10\x10", @@ -1542,14 +1593,14 @@ test "lower RMI encoding" { try enc.encode(.imul, &.{ .{ .reg = .r11 }, .{ .reg = .r12 }, - .{ .imm = Immediate.s(-2) }, + .{ .imm = Instruction.Immediate.s(-2) }, }); try expectEqualHexStrings("\x4D\x6B\xDC\xFE", enc.code(), "imul r11, r12, -2"); try enc.encode(.imul, &.{ .{ .reg = .r11 }, .{ .mem = Instruction.Memory.rip(.qword, -16) }, - .{ .imm = Immediate.s(-1024) }, + .{ .imm = Instruction.Immediate.s(-1024) }, }); try expectEqualHexStrings( "\x4C\x69\x1D\xF0\xFF\xFF\xFF\x00\xFC\xFF\xFF", @@ -1560,7 +1611,7 @@ test "lower RMI encoding" { try enc.encode(.imul, &.{ .{ .reg = .bx }, .{ .mem = Instruction.Memory.sib(.word, .{ .base = .{ .reg = .rbp }, .disp = -16 }) }, - .{ .imm = Immediate.s(-1024) }, + .{ .imm = Instruction.Immediate.s(-1024) }, }); try expectEqualHexStrings( "\x66\x69\x5D\xF0\x00\xFC", @@ -1571,7 +1622,7 @@ test "lower RMI encoding" { try enc.encode(.imul, &.{ .{ .reg = .bx }, .{ .mem = Instruction.Memory.sib(.word, .{ .base = .{ .reg = .rbp }, .disp = -16 }) }, - .{ .imm = Immediate.u(1024) }, + .{ .imm = Instruction.Immediate.u(1024) }, }); try expectEqualHexStrings( "\x66\x69\x5D\xF0\x00\x04", @@ -1687,7 +1738,7 @@ test "lower M encoding" { try expectEqualHexStrings("\x65\xFF\x14\x25\x00\x00\x00\x00", enc.code(), "call gs:0x0"); try enc.encode(.call, &.{ - .{ .imm = Immediate.s(0) }, + .{ .imm = Instruction.Immediate.s(0) }, }); try expectEqualHexStrings("\xE8\x00\x00\x00\x00", enc.code(), "call 0x0"); @@ -1746,7 +1797,7 @@ test "lower OI encoding" { try enc.encode(.mov, &.{ .{ .reg = .rax }, - .{ .imm = Immediate.u(0x1000000000000000) }, + .{ .imm = Instruction.Immediate.u(0x1000000000000000) }, }); try expectEqualHexStrings( "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x10", @@ -1756,7 +1807,7 @@ test "lower OI encoding" { try enc.encode(.mov, &.{ .{ .reg = .r11 }, - .{ .imm = Immediate.u(0x1000000000000000) }, + .{ .imm = Instruction.Immediate.u(0x1000000000000000) }, }); try expectEqualHexStrings( "\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x10", @@ -1766,19 +1817,19 @@ test "lower OI encoding" { try enc.encode(.mov, &.{ .{ .reg = .r11d }, - .{ .imm = Immediate.u(0x10000000) }, + .{ .imm = Instruction.Immediate.u(0x10000000) }, }); try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", enc.code(), "mov r11d, 0x10000000"); try enc.encode(.mov, &.{ .{ .reg = .r11w }, - .{ .imm = Immediate.u(0x1000) }, + .{ .imm = Instruction.Immediate.u(0x1000) }, }); try expectEqualHexStrings("\x66\x41\xBB\x00\x10", enc.code(), "mov r11w, 0x1000"); try enc.encode(.mov, &.{ .{ .reg = .r11b }, - .{ .imm = Immediate.u(0x10) }, + .{ .imm = Instruction.Immediate.u(0x10) }, }); try expectEqualHexStrings("\x41\xB3\x10", enc.code(), "mov r11b, 0x10"); } @@ -1900,7 +1951,7 @@ test "invalid instruction" { .{ .reg = .r12d }, }); try invalidInstruction(.push, &.{ - .{ .imm = Immediate.u(0x1000000000000000) }, + .{ .imm = Instruction.Immediate.u(0x1000000000000000) }, }); } @@ -2213,7 +2264,7 @@ const Assembler = struct { .immediate => { const is_neg = if (as.expect(.minus)) |_| true else |_| false; const imm_tok = try as.expect(.numeral); - const imm: Immediate = if (is_neg) blk: { + const imm: Instruction.Immediate = if (is_neg) blk: { const imm = try std.fmt.parseInt(i32, as.source(imm_tok), 0); break :blk .{ .signed = imm * -1 }; } else .{ .unsigned = try std.fmt.parseInt(u64, as.source(imm_tok), 0) }; diff --git a/zig/src/codegen.zig b/zig/src/codegen.zig index f2fa60fdf8..0c592c6f19 100644 --- a/zig/src/codegen.zig +++ b/zig/src/codegen.zig @@ -36,10 +36,10 @@ pub const CodeGenError = error{ OutOfMemory, Overflow, CodegenFail, -}; +} || link.File.UpdateDebugInfoError; pub const DebugInfoOutput = union(enum) { - dwarf: *link.File.Dwarf.NavState, + dwarf: *link.File.Dwarf.WipNav, plan9: *link.File.Plan9.DebugInfoOutput, none, }; @@ -98,7 +98,7 @@ pub fn generateLazyFunction( debug_output: DebugInfoOutput, ) CodeGenError!Result { const zcu = pt.zcu; - const file = Type.fromInterned(lazy_sym.ty).typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(&zcu.intern_pool).file; + const file = Type.fromInterned(lazy_sym.ty).typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(&zcu.intern_pool); const target = zcu.fileByIndex(file).mod.resolved_target.result; switch (target_util.zigBackend(target, false)) { else => unreachable, @@ -819,6 +819,9 @@ pub const GenResult = union(enum) { /// Decl with address deferred until the linker allocates everything in virtual memory. /// Payload is a symbol index. load_direct: u32, + /// Decl with address deferred until the linker allocates everything in virtual memory. + /// Payload is a symbol index. + lea_direct: u32, /// Decl referenced via GOT with address deferred until the linker allocates /// everything in virtual memory. /// Payload is a symbol index. @@ -828,12 +831,11 @@ pub const GenResult = union(enum) { /// Reference to memory location but deferred until linker allocated the Decl in memory. /// Traditionally, this corresponds to emitting a relocation in a relocatable object file. load_symbol: u32, + /// Reference to memory location but deferred until linker allocated the Decl in memory. + /// Traditionally, this corresponds to emitting a relocation in a relocatable object file. + lea_symbol: u32, }; - fn mcv(val: MCValue) GenResult { - return .{ .mcv = val }; - } - fn fail( gpa: Allocator, src_loc: Zcu.LazySrcLoc, @@ -866,7 +868,7 @@ fn genNavRef( 8 => 0xaaaaaaaaaaaaaaaa, else => unreachable, }; - return GenResult.mcv(.{ .immediate = imm }); + return .{ .mcv = .{ .immediate = imm } }; } const comp = lf.comp; @@ -875,12 +877,12 @@ fn genNavRef( // TODO this feels clunky. Perhaps we should check for it in `genTypedValue`? if (ty.castPtrToFn(zcu)) |fn_ty| { if (zcu.typeToFunc(fn_ty).?.is_generic) { - return GenResult.mcv(.{ .immediate = fn_ty.abiAlignment(pt).toByteUnits().? }); + return .{ .mcv = .{ .immediate = fn_ty.abiAlignment(pt).toByteUnits().? } }; } } else if (ty.zigTypeTag(zcu) == .Pointer) { const elem_ty = ty.elemType2(zcu); if (!elem_ty.hasRuntimeBits(pt)) { - return GenResult.mcv(.{ .immediate = elem_ty.abiAlignment(pt).toByteUnits().? }); + return .{ .mcv = .{ .immediate = elem_ty.abiAlignment(pt).toByteUnits().? } }; } } @@ -895,43 +897,42 @@ fn genNavRef( if (lf.cast(.elf)) |elf_file| { const zo = elf_file.zigObjectPtr().?; if (is_extern) { - // TODO audit this const sym_index = try elf_file.getGlobalSymbol(name.toSlice(ip), lib_name.toSlice(ip)); - zo.symbol(sym_index).flags.needs_got = true; - return GenResult.mcv(.{ .load_symbol = sym_index }); + zo.symbol(sym_index).flags.is_extern_ptr = true; + return .{ .mcv = .{ .lea_symbol = sym_index } }; } const sym_index = try zo.getOrCreateMetadataForNav(elf_file, nav_index); if (!single_threaded and is_threadlocal) { - return GenResult.mcv(.{ .load_tlv = sym_index }); + return .{ .mcv = .{ .load_tlv = sym_index } }; } - return GenResult.mcv(.{ .load_symbol = sym_index }); + return .{ .mcv = .{ .lea_symbol = sym_index } }; } else if (lf.cast(.macho)) |macho_file| { const zo = macho_file.getZigObject().?; if (is_extern) { const sym_index = try macho_file.getGlobalSymbol(name.toSlice(ip), lib_name.toSlice(ip)); - zo.symbols.items[sym_index].setSectionFlags(.{ .needs_got = true }); - return GenResult.mcv(.{ .load_symbol = sym_index }); + zo.symbols.items[sym_index].flags.is_extern_ptr = true; + return .{ .mcv = .{ .lea_symbol = sym_index } }; } const sym_index = try zo.getOrCreateMetadataForNav(macho_file, nav_index); const sym = zo.symbols.items[sym_index]; if (!single_threaded and is_threadlocal) { - return GenResult.mcv(.{ .load_tlv = sym.nlist_idx }); + return .{ .mcv = .{ .load_tlv = sym.nlist_idx } }; } - return GenResult.mcv(.{ .load_symbol = sym.nlist_idx }); + return .{ .mcv = .{ .lea_symbol = sym.nlist_idx } }; } else if (lf.cast(.coff)) |coff_file| { if (is_extern) { // TODO audit this const global_index = try coff_file.getGlobalSymbol(name.toSlice(ip), lib_name.toSlice(ip)); try coff_file.need_got_table.put(gpa, global_index, {}); // needs GOT - return GenResult.mcv(.{ .load_got = link.File.Coff.global_symbol_bit | global_index }); + return .{ .mcv = .{ .load_got = link.File.Coff.global_symbol_bit | global_index } }; } const atom_index = try coff_file.getOrCreateAtomForNav(nav_index); const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; - return GenResult.mcv(.{ .load_got = sym_index }); + return .{ .mcv = .{ .load_got = sym_index } }; } else if (lf.cast(.plan9)) |p9| { const atom_index = try p9.seeNav(pt, nav_index); const atom = p9.getAtom(atom_index); - return GenResult.mcv(.{ .memory = atom.getOffsetTableAddress(p9) }); + return .{ .mcv = .{ .memory = atom.getOffsetTableAddress(p9) } }; } else { return GenResult.fail(gpa, src_loc, "TODO genNavRef for target {}", .{target}); } @@ -950,30 +951,40 @@ pub fn genTypedValue( log.debug("genTypedValue: val = {}", .{val.fmtValue(pt)}); - if (val.isUndef(zcu)) { - return GenResult.mcv(.undef); - } - - if (!ty.isSlice(zcu)) switch (ip.indexToKey(val.toIntern())) { - .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { - .nav => |nav| return genNavRef(lf, pt, src_loc, val, nav, target), - else => {}, - }, - else => {}, - }; + if (val.isUndef(zcu)) return .{ .mcv = .undef }; switch (ty.zigTypeTag(zcu)) { - .Void => return GenResult.mcv(.none), + .Void => return .{ .mcv = .none }, .Pointer => switch (ty.ptrSize(zcu)) { .Slice => {}, else => switch (val.toIntern()) { .null_value => { - return GenResult.mcv(.{ .immediate = 0 }); + return .{ .mcv = .{ .immediate = 0 } }; }, - .none => {}, else => switch (ip.indexToKey(val.toIntern())) { .int => { - return GenResult.mcv(.{ .immediate = val.toUnsignedInt(pt) }); + return .{ .mcv = .{ .immediate = val.toUnsignedInt(pt) } }; + }, + .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { + .nav => |nav| return genNavRef(lf, pt, src_loc, val, nav, target), + .uav => |uav| if (Value.fromInterned(uav.val).typeOf(zcu).hasRuntimeBits(pt)) + return switch (try lf.lowerUav( + pt, + uav.val, + Type.fromInterned(uav.orig_ty).ptrAlignment(pt), + src_loc, + )) { + .mcv => |mcv| return .{ .mcv = switch (mcv) { + .load_direct => |sym_index| .{ .lea_direct = sym_index }, + .load_symbol => |sym_index| .{ .lea_symbol = sym_index }, + else => unreachable, + } }, + .fail => |em| return .{ .fail = em }, + } + else + return .{ .mcv = .{ .immediate = Type.fromInterned(uav.orig_ty).ptrAlignment(pt) + .forward(@intCast((@as(u66, 1) << @intCast(target.ptrBitWidth() | 1)) / 3)) } }, + else => {}, }, else => {}, }, @@ -986,11 +997,11 @@ pub fn genTypedValue( .signed => @bitCast(val.toSignedInt(pt)), .unsigned => val.toUnsignedInt(pt), }; - return GenResult.mcv(.{ .immediate = unsigned }); + return .{ .mcv = .{ .immediate = unsigned } }; } }, .Bool => { - return GenResult.mcv(.{ .immediate = @intFromBool(val.toBool()) }); + return .{ .mcv = .{ .immediate = @intFromBool(val.toBool()) } }; }, .Optional => { if (ty.isPtrLikeOptional(zcu)) { @@ -998,11 +1009,11 @@ pub fn genTypedValue( lf, pt, src_loc, - val.optionalValue(zcu) orelse return GenResult.mcv(.{ .immediate = 0 }), + val.optionalValue(zcu) orelse return .{ .mcv = .{ .immediate = 0 } }, target, ); } else if (ty.abiSize(pt) == 1) { - return GenResult.mcv(.{ .immediate = @intFromBool(!val.isNull(zcu)) }); + return .{ .mcv = .{ .immediate = @intFromBool(!val.isNull(zcu)) } }; } }, .Enum => { @@ -1018,7 +1029,7 @@ pub fn genTypedValue( .ErrorSet => { const err_name = ip.indexToKey(val.toIntern()).err.name; const error_index = try pt.getErrorValue(err_name); - return GenResult.mcv(.{ .immediate = error_index }); + return .{ .mcv = .{ .immediate = error_index } }; }, .ErrorUnion => { const err_type = ty.errorUnionSet(zcu); diff --git a/zig/src/codegen/c.zig b/zig/src/codegen/c.zig index 03a1ea3746..397cb071b6 100644 --- a/zig/src/codegen/c.zig +++ b/zig/src/codegen/c.zig @@ -2585,7 +2585,7 @@ pub fn genTypeDecl( const ty = Type.fromInterned(index); _ = try renderTypePrefix(.flush, global_ctype_pool, zcu, writer, global_ctype, .suffix, .{}); try writer.writeByte(';'); - const file_scope = ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file; + const file_scope = ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip); if (!zcu.fileByIndex(file_scope).mod.strip) try writer.print(" /* {} */", .{ ty.containerTypeName(ip).fmt(ip), }); @@ -3293,7 +3293,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, .dbg_stmt => try airDbgStmt(f, inst), .dbg_inline_block => try airDbgInlineBlock(f, inst), - .dbg_var_ptr, .dbg_var_val => try airDbgVar(f, inst), + .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => try airDbgVar(f, inst), .call => try airCall(f, inst, .auto), .call_always_tail => .none, @@ -4590,14 +4590,15 @@ fn airDbgInlineBlock(f: *Function, inst: Air.Inst.Index) !CValue { fn airDbgVar(f: *Function, inst: Air.Inst.Index) !CValue { const pt = f.object.dg.pt; const zcu = pt.zcu; + const tag = f.air.instructions.items(.tag)[@intFromEnum(inst)]; const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const name = f.air.nullTerminatedString(pl_op.payload); + const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); const operand_is_undef = if (try f.air.value(pl_op.operand, pt)) |v| v.isUndefDeep(zcu) else false; if (!operand_is_undef) _ = try f.resolveInst(pl_op.operand); try reap(f, inst, &.{pl_op.operand}); const writer = f.object.writer(); - try writer.print("/* var:{s} */\n", .{name}); + try writer.print("/* {s}:{s} */\n", .{ @tagName(tag), name.toSlice(f.air) }); return .none; } diff --git a/zig/src/codegen/c/Type.zig b/zig/src/codegen/c/Type.zig index c89d9d19f7..63b7e4fd52 100644 --- a/zig/src/codegen/c/Type.zig +++ b/zig/src/codegen/c/Type.zig @@ -1389,21 +1389,6 @@ pub const Pool = struct { .anyframe_type, .generic_poison_type, => unreachable, - .atomic_order_type, - .atomic_rmw_op_type, - .calling_convention_type, - .address_space_type, - .float_mode_type, - .reduce_op_type, - .call_modifier_type, - => |ip_index| return pool.fromType( - allocator, - scratch, - Type.fromInterned(ip.loadEnumType(ip_index).tag_ty), - pt, - mod, - kind, - ), .anyerror_type, .anyerror_void_error_union_type, .adhoc_inferred_error_set_type, @@ -1459,8 +1444,6 @@ pub const Pool = struct { .one_u8, .four_u8, .negative_one, - .calling_convention_c, - .calling_convention_inline, .void_value, .unreachable_value, .null_value, @@ -1471,12 +1454,7 @@ pub const Pool = struct { .none, => unreachable, - //.prefetch_options_type, - //.export_options_type, - //.extern_options_type, - //.type_info_type, - //_, - else => |ip_index| switch (ip.indexToKey(ip_index)) { + _ => |ip_index| switch (ip.indexToKey(ip_index)) { .int_type => |int_info| return pool.fromIntInfo(allocator, int_info, mod, kind), .ptr_type => |ptr_info| switch (ptr_info.flags.size) { .One, .Many, .C => { diff --git a/zig/src/codegen/llvm.zig b/zig/src/codegen/llvm.zig index 4e056d2069..233cf7e3eb 100644 --- a/zig/src/codegen/llvm.zig +++ b/zig/src/codegen/llvm.zig @@ -1380,7 +1380,8 @@ pub const Object = struct { } else { _ = try attributes.removeFnAttr(.sanitize_thread); } - if (owner_mod.fuzz and !func_analysis.disable_instrumentation) { + const is_naked = fn_info.cc == .Naked; + if (owner_mod.fuzz and !func_analysis.disable_instrumentation and !is_naked) { try attributes.addFnAttr(.optforfuzzing, &o.builder); _ = try attributes.removeFnAttr(.skipprofile); _ = try attributes.removeFnAttr(.nosanitize_coverage); @@ -1664,6 +1665,7 @@ pub const Object = struct { .ret_ptr = ret_ptr, .args = args.items, .arg_index = 0, + .arg_inline_index = 0, .func_inst_table = .{}, .blocks = .{}, .sync_scope = if (owner_mod.single_threaded) .singlethread else .system, @@ -1959,7 +1961,7 @@ pub const Object = struct { ); } - const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file); + const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip)); const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace| try o.namespaceToDebugScope(parent_namespace) else @@ -2137,7 +2139,7 @@ pub const Object = struct { const name = try o.allocTypeName(ty); defer gpa.free(name); - const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file); + const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip)); const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace| try o.namespaceToDebugScope(parent_namespace) else @@ -2772,7 +2774,7 @@ pub const Object = struct { fn makeEmptyNamespaceDebugType(o: *Object, ty: Type) !Builder.Metadata { const zcu = o.pt.zcu; const ip = &zcu.intern_pool; - const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file); + const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip)); const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace| try o.namespaceToDebugScope(parent_namespace) else @@ -2909,7 +2911,17 @@ pub const Object = struct { function_index.setAlignment(resolved.alignment.toLlvm(), &o.builder); // Function attributes that are independent of analysis results of the function body. - try o.addCommonFnAttributes(&attributes, owner_mod); + try o.addCommonFnAttributes( + &attributes, + owner_mod, + // Some backends don't respect the `naked` attribute in `TargetFrameLowering::hasFP()`, + // so for these backends, LLVM will happily emit code that accesses the stack through + // the frame pointer. This is nonsensical since what the `naked` attribute does is + // suppress generation of the prologue and epilogue, and the prologue is where the + // frame pointer normally gets set up. At time of writing, this is the case for at + // least x86 and RISC-V. + owner_mod.omit_frame_pointer or fn_info.cc == .Naked, + ); if (fn_info.return_type == .noreturn_type) try attributes.addFnAttr(.noreturn, &o.builder); @@ -2956,13 +2968,14 @@ pub const Object = struct { o: *Object, attributes: *Builder.FunctionAttributes.Wip, owner_mod: *Package.Module, + omit_frame_pointer: bool, ) Allocator.Error!void { const comp = o.pt.zcu.comp; if (!owner_mod.red_zone) { try attributes.addFnAttr(.noredzone, &o.builder); } - if (owner_mod.omit_frame_pointer) { + if (omit_frame_pointer) { try attributes.addFnAttr(.{ .string = .{ .kind = try o.builder.string("frame-pointer"), .value = try o.builder.string("none"), @@ -3183,8 +3196,6 @@ pub const Object = struct { .one_u8, .four_u8, .negative_one, - .calling_convention_c, - .calling_convention_inline, .void_value, .unreachable_value, .null_value, @@ -4528,7 +4539,7 @@ pub const Object = struct { var attributes: Builder.FunctionAttributes.Wip = .{}; defer attributes.deinit(&o.builder); - try o.addCommonFnAttributes(&attributes, zcu.root_mod); + try o.addCommonFnAttributes(&attributes, zcu.root_mod, zcu.root_mod.omit_frame_pointer); function_index.setLinkage(.internal, &o.builder); function_index.setCallConv(.fastcc, &o.builder); @@ -4557,7 +4568,7 @@ pub const Object = struct { var attributes: Builder.FunctionAttributes.Wip = .{}; defer attributes.deinit(&o.builder); - try o.addCommonFnAttributes(&attributes, zcu.root_mod); + try o.addCommonFnAttributes(&attributes, zcu.root_mod, zcu.root_mod.omit_frame_pointer); function_index.setLinkage(.internal, &o.builder); function_index.setCallConv(.fastcc, &o.builder); @@ -4759,7 +4770,8 @@ pub const FuncGen = struct { /// it omits 0-bit types. If the function uses sret as the first parameter, /// this slice does not include it. args: []const Builder.Value, - arg_index: usize, + arg_index: u32, + arg_inline_index: u32, err_ret_trace: Builder.Value = .none, @@ -5072,7 +5084,8 @@ pub const FuncGen = struct { .dbg_stmt => try self.airDbgStmt(inst), .dbg_inline_block => try self.airDbgInlineBlock(inst), .dbg_var_ptr => try self.airDbgVarPtr(inst), - .dbg_var_val => try self.airDbgVarVal(inst), + .dbg_var_val => try self.airDbgVarVal(inst, false), + .dbg_arg_inline => try self.airDbgVarVal(inst, true), .c_va_arg => try self.airCVaArg(inst), .c_va_copy => try self.airCVaCopy(inst), @@ -6667,6 +6680,7 @@ pub const FuncGen = struct { fn airDbgInlineBlock(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.DbgInlineBlock, ty_pl.payload); + self.arg_inline_index = 0; return self.lowerBlock(inst, extra.data.func, @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len])); } @@ -6675,11 +6689,11 @@ pub const FuncGen = struct { const mod = o.pt.zcu; const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const operand = try self.resolveInst(pl_op.operand); - const name = self.air.nullTerminatedString(pl_op.payload); + const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); const ptr_ty = self.typeOf(pl_op.operand); const debug_local_var = try o.builder.debugLocalVar( - try o.builder.metadataString(name), + try o.builder.metadataString(name.toSlice(self.air)), self.file, self.scope, self.prev_dbg_line, @@ -6702,17 +6716,25 @@ pub const FuncGen = struct { return .none; } - fn airDbgVarVal(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value { + fn airDbgVarVal(self: *FuncGen, inst: Air.Inst.Index, is_arg: bool) !Builder.Value { const o = self.ng.object; const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const operand = try self.resolveInst(pl_op.operand); const operand_ty = self.typeOf(pl_op.operand); - const name = self.air.nullTerminatedString(pl_op.payload); - - if (needDbgVarWorkaround(o)) return .none; + const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); - const debug_local_var = try o.builder.debugLocalVar( - try o.builder.metadataString(name), + const debug_local_var = if (is_arg) try o.builder.debugParameter( + try o.builder.metadataString(name.toSlice(self.air)), + self.file, + self.scope, + self.prev_dbg_line, + try o.lowerDebugType(operand_ty), + arg_no: { + self.arg_inline_index += 1; + break :arg_no self.arg_inline_index; + }, + ) else try o.builder.debugLocalVar( + try o.builder.metadataString(name.toSlice(self.air)), self.file, self.scope, self.prev_dbg_line, @@ -6734,7 +6756,10 @@ pub const FuncGen = struct { }, "", ); - } else if (owner_mod.optimize_mode == .Debug) { + } else if (owner_mod.optimize_mode == .Debug and !self.is_naked) { + // We avoid taking this path for naked functions because there's no guarantee that such + // functions even have a valid stack pointer, making the `alloca` + `store` unsafe. + const alignment = operand_ty.abiAlignment(pt).toLlvm(); const alloca = try self.buildAlloca(operand.typeOfWip(&self.wip), alignment); _ = try self.wip.store(.normal, operand, alloca, alignment); @@ -8815,7 +8840,6 @@ pub const FuncGen = struct { if (self.is_naked) return arg_val; const inst_ty = self.typeOfIndex(inst); - if (needDbgVarWorkaround(o)) return arg_val; const name = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; if (name == .none) return arg_val; @@ -8825,12 +8849,12 @@ pub const FuncGen = struct { const lbrace_col = func.lbrace_column + 1; const debug_parameter = try o.builder.debugParameter( - try o.builder.metadataString(self.air.nullTerminatedString(@intFromEnum(name))), + try o.builder.metadataString(name.toSlice(self.air)), self.file, self.scope, lbrace_line, try o.lowerDebugType(inst_ty), - @intCast(self.arg_index), + self.arg_index, ); const old_location = self.wip.debug_location; @@ -9676,7 +9700,7 @@ pub const FuncGen = struct { var attributes: Builder.FunctionAttributes.Wip = .{}; defer attributes.deinit(&o.builder); - try o.addCommonFnAttributes(&attributes, zcu.root_mod); + try o.addCommonFnAttributes(&attributes, zcu.root_mod, zcu.root_mod.omit_frame_pointer); function_index.setLinkage(.internal, &o.builder); function_index.setCallConv(.fastcc, &o.builder); @@ -11776,7 +11800,9 @@ fn backendSupportsF16(target: std.Target) bool { .mips64el, .s390x, => false, - .aarch64 => std.Target.aarch64.featureSetHas(target.cpu.features, .fp_armv8), + .aarch64, + .aarch64_be, + => std.Target.aarch64.featureSetHas(target.cpu.features, .fp_armv8), else => true, }; } @@ -11787,9 +11813,18 @@ fn backendSupportsF16(target: std.Target) bool { fn backendSupportsF128(target: std.Target) bool { return switch (target.cpu.arch) { .amdgcn, + .mips64, + .mips64el, .sparc, => false, - .aarch64 => std.Target.aarch64.featureSetHas(target.cpu.features, .fp_armv8), + .powerpc, + .powerpcle, + .powerpc64, + .powerpc64le, + => target.os.tag != .aix, + .aarch64, + .aarch64_be, + => std.Target.aarch64.featureSetHas(target.cpu.features, .fp_armv8), else => true, }; } @@ -11820,16 +11855,6 @@ const optional_layout_version = 3; const lt_errors_fn_name = "__zig_lt_errors_len"; -/// Without this workaround, LLVM crashes with "unknown codeview register H1" -/// https://github.com/llvm/llvm-project/issues/56484 -fn needDbgVarWorkaround(o: *Object) bool { - const target = o.pt.zcu.getTarget(); - if (target.os.tag == .windows and target.cpu.arch == .aarch64) { - return true; - } - return false; -} - fn compilerRtIntBits(bits: u16) u16 { inline for (.{ 32, 64, 128 }) |b| { if (bits <= b) { diff --git a/zig/src/codegen/spirv.zig b/zig/src/codegen/spirv.zig index b13be401ab..a89dd8f10b 100644 --- a/zig/src/codegen/spirv.zig +++ b/zig/src/codegen/spirv.zig @@ -6366,8 +6366,8 @@ const NavGen = struct { fn airDbgVar(self: *NavGen, inst: Air.Inst.Index) !void { const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const target_id = try self.resolve(pl_op.operand); - const name = self.air.nullTerminatedString(pl_op.payload); - try self.spv.debugName(target_id, name); + const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); + try self.spv.debugName(target_id, name.toSlice(self.air)); } fn airAssembly(self: *NavGen, inst: Air.Inst.Index) !?IdRef { diff --git a/zig/src/crash_report.zig b/zig/src/crash_report.zig index d4fe72a8e8..67ec0e0eb0 100644 --- a/zig/src/crash_report.zig +++ b/zig/src/crash_report.zig @@ -78,7 +78,13 @@ fn dumpStatusReport() !void { const block: *Sema.Block = anal.block; const zcu = anal.sema.pt.zcu; - const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu); + const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse { + const file = zcu.fileByIndex(block.src_base_inst.resolveFile(&zcu.intern_pool)); + try stderr.writeAll("Analyzing lost instruction in file '"); + try writeFilePath(file, stderr); + try stderr.writeAll("'. This should not happen!\n\n"); + return; + }; try stderr.writeAll("Analyzing "); try writeFilePath(file, stderr); @@ -104,7 +110,13 @@ fn dumpStatusReport() !void { while (parent) |curr| { fba.reset(); try stderr.writeAll(" in "); - const cur_block_file, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu); + const cur_block_file, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse { + const cur_block_file = zcu.fileByIndex(curr.block.src_base_inst.resolveFile(&zcu.intern_pool)); + try writeFilePath(cur_block_file, stderr); + try stderr.writeAll("\n > [lost instruction; this should not happen]\n"); + parent = curr.parent; + continue; + }; try writeFilePath(cur_block_file, stderr); try stderr.writeAll("\n > "); print_zir.renderSingleInstruction( diff --git a/zig/src/glibc.zig b/zig/src/glibc.zig index c9d43f0199..57894f5921 100644 --- a/zig/src/glibc.zig +++ b/zig/src/glibc.zig @@ -369,6 +369,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progre "-fmath-errno", "-ftls-model=initial-exec", "-Wno-ignored-attributes", + "-Qunused-arguments", }); try add_include_dirs(comp, arena, &args); diff --git a/zig/src/introspect.zig b/zig/src/introspect.zig index 341b1cddeb..4193440461 100644 --- a/zig/src/introspect.zig +++ b/zig/src/introspect.zig @@ -90,8 +90,11 @@ pub fn resolveGlobalCacheDir(allocator: mem.Allocator) ![]u8 { if (builtin.os.tag != .windows) { if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| { - return fs.path.join(allocator, &[_][]const u8{ cache_root, appname }); - } else if (std.zig.EnvVar.HOME.getPosix()) |home| { + if (cache_root.len > 0) { + return fs.path.join(allocator, &[_][]const u8{ cache_root, appname }); + } + } + if (std.zig.EnvVar.HOME.getPosix()) |home| { return fs.path.join(allocator, &[_][]const u8{ home, ".cache", appname }); } } diff --git a/zig/src/link.zig b/zig/src/link.zig index 3463a77147..894074cdda 100644 --- a/zig/src/link.zig +++ b/zig/src/link.zig @@ -11,6 +11,7 @@ const wasi_libc = @import("wasi_libc.zig"); const Air = @import("Air.zig"); const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; +const Path = Cache.Path; const Compilation = @import("Compilation.zig"); const LibCInstallation = std.zig.LibCInstallation; const Liveness = @import("Liveness.zig"); @@ -56,7 +57,7 @@ pub const File = struct { /// The owner of this output File. comp: *Compilation, - emit: Compilation.Emit, + emit: Path, file: ?fs.File, /// When linking with LLD, this linker code will output an object file only at @@ -189,7 +190,7 @@ pub const File = struct { pub fn open( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: OpenOptions, ) !*File { switch (Tag.fromObjectFormat(comp.root_mod.resolved_target.result.ofmt)) { @@ -204,7 +205,7 @@ pub const File = struct { pub fn createEmpty( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: OpenOptions, ) !*File { switch (Tag.fromObjectFormat(comp.root_mod.resolved_target.result.ofmt)) { @@ -243,8 +244,8 @@ pub const File = struct { emit.sub_path, std.crypto.random.int(u32), }); defer gpa.free(tmp_sub_path); - try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{}); - try emit.directory.handle.rename(tmp_sub_path, emit.sub_path); + try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, .{}); + try emit.root_dir.handle.rename(tmp_sub_path, emit.sub_path); switch (builtin.os.tag) { .linux => std.posix.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { log.warn("ptrace failure: {s}", .{@errorName(err)}); @@ -260,7 +261,7 @@ pub const File = struct { const use_lld = build_options.have_llvm and comp.config.use_lld; const output_mode = comp.config.output_mode; const link_mode = comp.config.link_mode; - base.file = try emit.directory.handle.createFile(emit.sub_path, .{ + base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = false, .read = true, .mode = determineMode(use_lld, output_mode, link_mode), @@ -329,6 +330,9 @@ pub const File = struct { } } + pub const UpdateDebugInfoError = Dwarf.UpdateError; + pub const FlushDebugInfoError = Dwarf.FlushError; + pub const UpdateNavError = error{ OutOfMemory, Overflow, @@ -365,7 +369,7 @@ pub const File = struct { DeviceBusy, InvalidArgument, HotSwapUnavailableOnHostOperatingSystem, - }; + } || UpdateDebugInfoError; /// Called from within CodeGen to retrieve the symbol index of a global symbol. /// If no symbol exists yet with this name, a new undefined global symbol will @@ -398,6 +402,16 @@ pub const File = struct { } } + pub fn updateContainerType(base: *File, pt: Zcu.PerThread, ty: InternPool.Index) UpdateNavError!void { + switch (base.tag) { + else => {}, + inline .elf => |tag| { + dev.check(tag.devFeature()); + return @as(*tag.Type(), @fieldParentPtr("base", base)).updateContainerType(pt, ty); + }, + } + } + /// May be called before or after updateExports for any given Decl. pub fn updateFunc( base: *File, @@ -570,6 +584,7 @@ pub const File = struct { Unseekable, UnsupportedCpuArchitecture, UnsupportedVersion, + UnexpectedEndOfFile, } || fs.File.WriteFileError || fs.File.OpenError || @@ -589,7 +604,7 @@ pub const File = struct { // Until then, we do `lld -r -o output.o input.o` even though the output is the same // as the input. For the preprocessing case (`zig cc -E -o foo`) we copy the file // to the final location. See also the corresponding TODO in Coff linking. - const full_out_path = try emit.directory.join(gpa, &[_][]const u8{emit.sub_path}); + const full_out_path = try emit.root_dir.join(gpa, &[_][]const u8{emit.sub_path}); defer gpa.free(full_out_path); assert(comp.c_object_table.count() == 1); const the_key = comp.c_object_table.keys()[0]; @@ -737,7 +752,7 @@ pub const File = struct { const comp = base.comp; const gpa = comp.gpa; - const directory = base.emit.directory; // Just an alias to make it shorter to type. + const directory = base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); const full_out_path_z = try arena.dupeZ(u8, full_out_path); const opt_zcu = comp.module; @@ -1015,7 +1030,10 @@ pub const File = struct { llvm_object: LlvmObject.Ptr, prog_node: std.Progress.Node, ) !void { - return base.comp.emitLlvmObject(arena, base.emit, .{ + return base.comp.emitLlvmObject(arena, .{ + .root_dir = base.emit.root_dir, + .sub_path = std.fs.path.dirname(base.emit.sub_path) orelse "", + }, .{ .directory = null, .basename = base.zcu_object_sub_path.?, }, llvm_object, prog_node); diff --git a/zig/src/link/C.zig b/zig/src/link/C.zig index e7c8f6a7b0..585389aa3f 100644 --- a/zig/src/link/C.zig +++ b/zig/src/link/C.zig @@ -3,6 +3,7 @@ const mem = std.mem; const assert = std.debug.assert; const Allocator = std.mem.Allocator; const fs = std.fs; +const Path = std.Build.Cache.Path; const C = @This(); const build_options = @import("build_options"); @@ -104,7 +105,7 @@ pub fn addString(this: *C, s: []const u8) Allocator.Error!String { pub fn open( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*C { return createEmpty(arena, comp, emit, options); @@ -113,7 +114,7 @@ pub fn open( pub fn createEmpty( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*C { const target = comp.root_mod.resolved_target.result; @@ -127,7 +128,7 @@ pub fn createEmpty( assert(!use_lld); assert(!use_llvm); - const file = try emit.directory.handle.createFile(emit.sub_path, .{ + const file = try emit.root_dir.handle.createFile(emit.sub_path, .{ // Truncation is done on `flush`. .truncate = false, }); @@ -316,8 +317,18 @@ pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ! defer tracy.end(); const gpa = self.base.comp.gpa; - const zcu = pt.zcu; + const ip = &zcu.intern_pool; + + const nav = ip.getNav(nav_index); + const nav_init = switch (ip.indexToKey(nav.status.resolved.val)) { + .func => return, + .@"extern" => .none, + .variable => |variable| variable.init, + else => nav.status.resolved.val, + }; + if (nav_init != .none and !Value.fromInterned(nav_init).typeOf(zcu).hasRuntimeBits(pt)) return; + const gop = try self.navs.getOrPut(gpa, nav_index); errdefer _ = self.navs.pop(); if (!gop.found_existing) gop.value_ptr.* = .{}; diff --git a/zig/src/link/Coff.zig b/zig/src/link/Coff.zig index 73822dfec8..d6ebcc278e 100644 --- a/zig/src/link/Coff.zig +++ b/zig/src/link/Coff.zig @@ -219,7 +219,7 @@ pub const min_text_capacity = padToIdeal(minimum_text_block_size); pub fn createEmpty( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*Coff { const target = comp.root_mod.resolved_target.result; @@ -315,7 +315,7 @@ pub fn createEmpty( // If using LLD to link, this code should produce an object file so that it // can be passed to LLD. const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path; - self.base.file = try emit.directory.handle.createFile(sub_path, .{ + self.base.file = try emit.root_dir.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.File.determineMode(use_lld, output_mode, link_mode), @@ -416,7 +416,7 @@ pub fn createEmpty( pub fn open( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*Coff { // TODO: restore saved linker state, don't truncate the file, and @@ -1205,10 +1205,12 @@ pub fn updateNav( const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - const init_val = switch (ip.indexToKey(nav.status.resolved.val)) { - .variable => |variable| variable.init, + const nav_val = zcu.navValue(nav_index); + const nav_init = switch (ip.indexToKey(nav_val.toIntern())) { + .func => return, + .variable => |variable| Value.fromInterned(variable.init), .@"extern" => |@"extern"| { - if (ip.isFunctionType(nav.typeOf(ip))) return; + if (ip.isFunctionType(@"extern".ty)) return; // TODO make this part of getGlobalSymbol const name = nav.name.toSlice(ip); const lib_name = @"extern".lib_name.toSlice(ip); @@ -1216,34 +1218,36 @@ pub fn updateNav( try self.need_got_table.put(gpa, global_index, {}); return; }, - else => nav.status.resolved.val, + else => nav_val, }; - const atom_index = try self.getOrCreateAtomForNav(nav_index); - Atom.freeRelocations(self, atom_index); - const atom = self.getAtom(atom_index); + if (nav_init.typeOf(zcu).hasRuntimeBits(pt)) { + const atom_index = try self.getOrCreateAtomForNav(nav_index); + Atom.freeRelocations(self, atom_index); + const atom = self.getAtom(atom_index); - var code_buffer = std.ArrayList(u8).init(gpa); - defer code_buffer.deinit(); + var code_buffer = std.ArrayList(u8).init(gpa); + defer code_buffer.deinit(); - const res = try codegen.generateSymbol( - &self.base, - pt, - zcu.navSrcLoc(nav_index), - Value.fromInterned(init_val), - &code_buffer, - .none, - .{ .parent_atom_index = atom.getSymbolIndex().? }, - ); - const code = switch (res) { - .ok => code_buffer.items, - .fail => |em| { - try zcu.failed_codegen.put(gpa, nav_index, em); - return; - }, - }; + const res = try codegen.generateSymbol( + &self.base, + pt, + zcu.navSrcLoc(nav_index), + nav_init, + &code_buffer, + .none, + .{ .parent_atom_index = atom.getSymbolIndex().? }, + ); + const code = switch (res) { + .ok => code_buffer.items, + .fail => |em| { + try zcu.failed_codegen.put(gpa, nav_index, em); + return; + }, + }; - try self.updateNavCode(pt, nav_index, code, .NULL); + try self.updateNavCode(pt, nav_index, code, .NULL); + } // Exports will be updated by `Zcu.processExports` after the update. } @@ -1290,10 +1294,10 @@ fn updateLazySymbolAtom( }, }; - const code_len = @as(u32, @intCast(code.len)); + const code_len: u32 = @intCast(code.len); const symbol = atom.getSymbolPtr(self); try self.setSymbolName(symbol, name); - symbol.section_number = @as(coff.SectionNumber, @enumFromInt(section_index + 1)); + symbol.section_number = @enumFromInt(section_index + 1); symbol.type = .{ .complex_type = .NULL, .base_type = .NULL }; const vaddr = try self.allocateAtom(atom_index, code_len, @intCast(required_alignment.toByteUnits() orelse 0)); @@ -1691,7 +1695,7 @@ pub fn flushModule(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no .tid = tid, }; - if (self.lazy_syms.getPtr(.none)) |metadata| { + if (self.lazy_syms.getPtr(.anyerror_type)) |metadata| { // Most lazy symbols can be updated on first use, but // anyerror needs to wait for everything to be flushed. if (metadata.text_state != .unused) self.updateLazySymbolAtom( @@ -2259,7 +2263,7 @@ fn writeHeader(self: *Coff) !void { const timestamp = if (self.repro) 0 else std.time.timestamp(); const size_of_optional_header = @as(u16, @intCast(self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize())); var coff_header = coff.CoffHeader{ - .machine = coff.MachineType.fromTargetCpuArch(target.cpu.arch), + .machine = target.toCoffMachine(), .number_of_sections = @as(u16, @intCast(self.sections.slice().len)), // TODO what if we prune a section .time_date_stamp = @as(u32, @truncate(@as(u64, @bitCast(timestamp)))), .pointer_to_symbol_table = self.strtab_offset orelse 0, @@ -2711,6 +2715,7 @@ const math = std.math; const mem = std.mem; const Allocator = std.mem.Allocator; +const Path = std.Build.Cache.Path; const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); diff --git a/zig/src/link/Coff/lld.zig b/zig/src/link/Coff/lld.zig index ae56183e77..7273aa39b6 100644 --- a/zig/src/link/Coff/lld.zig +++ b/zig/src/link/Coff/lld.zig @@ -27,7 +27,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no const comp = self.base.comp; const gpa = comp.gpa; - const directory = self.base.emit.directory; // Just an alias to make it shorter to type. + const directory = self.base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); // If there is no Zig code to compile, then we should skip flushing the output file because it @@ -248,7 +248,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); if (comp.implib_emit) |emit| { - const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); + const implib_out_path = try emit.root_dir.join(arena, &[_][]const u8{emit.sub_path}); try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); } diff --git a/zig/src/link/Dwarf.zig b/zig/src/link/Dwarf.zig index 30b286cac4..26ea6207e8 100644 --- a/zig/src/link/Dwarf.zig +++ b/zig/src/link/Dwarf.zig @@ -1,2884 +1,4144 @@ -allocator: Allocator, -bin_file: *File, -format: Format, -ptr_width: PtrWidth, - -/// A list of `Atom`s whose Line Number Programs have surplus capacity. -/// This is the same concept as `Section.free_list` in Elf; see those doc comments. -src_fn_free_list: std.AutoHashMapUnmanaged(Atom.Index, void) = .{}, -src_fn_first_index: ?Atom.Index = null, -src_fn_last_index: ?Atom.Index = null, -src_fns: std.ArrayListUnmanaged(Atom) = .{}, -src_fn_navs: AtomTable = .{}, - -/// A list of `Atom`s whose corresponding .debug_info tags have surplus capacity. -/// This is the same concept as `text_block_free_list`; see those doc comments. -di_atom_free_list: std.AutoHashMapUnmanaged(Atom.Index, void) = .{}, -di_atom_first_index: ?Atom.Index = null, -di_atom_last_index: ?Atom.Index = null, -di_atoms: std.ArrayListUnmanaged(Atom) = .{}, -di_atom_navs: AtomTable = .{}, - -dbg_line_header: DbgLineHeader, - -abbrev_table_offset: ?u64 = null, - -/// TODO replace with InternPool -/// Table of debug symbol names. -strtab: StringTable = .{}, - -/// Quick lookup array of all defined source files referenced by at least one Nav. -/// They will end up in the DWARF debug_line header as two lists: -/// * []include_directory -/// * []file_names -di_files: std.AutoArrayHashMapUnmanaged(*const Zcu.File, void) = .{}, - -global_abbrev_relocs: std.ArrayListUnmanaged(AbbrevRelocation) = .{}, - -const AtomTable = std.AutoHashMapUnmanaged(InternPool.Nav.Index, Atom.Index); - -const Atom = struct { - /// Offset into .debug_info pointing to the tag for this Nav, or - /// offset from the beginning of the Debug Line Program header that contains this function. - off: u32, - /// Size of the .debug_info tag for this Nav, not including padding, or - /// size of the line number program component belonging to this function, not - /// including padding. - len: u32, +gpa: std.mem.Allocator, +bin_file: *link.File, +format: DW.Format, +endian: std.builtin.Endian, +address_size: AddressSize, + +mods: std.AutoArrayHashMapUnmanaged(*Module, ModInfo), +types: std.AutoArrayHashMapUnmanaged(InternPool.Index, Entry.Index), +navs: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, Entry.Index), + +debug_abbrev: DebugAbbrev, +debug_aranges: DebugAranges, +debug_info: DebugInfo, +debug_line: DebugLine, +debug_line_str: StringSection, +debug_loclists: DebugLocLists, +debug_rnglists: DebugRngLists, +debug_str: StringSection, + +pub const UpdateError = + std.fs.File.OpenError || + std.fs.File.SetEndPosError || + std.fs.File.CopyRangeError || + std.fs.File.PReadError || + std.fs.File.PWriteError || + error{ EndOfStream, Overflow, Underflow, UnexpectedEndOfFile }; + +pub const FlushError = + UpdateError || + std.process.GetCwdError; + +pub const RelocError = + std.fs.File.PWriteError; + +pub const AddressSize = enum(u8) { + @"32" = 4, + @"64" = 8, + _, +}; + +const ModInfo = struct { + root_dir_path: Entry.Index, + dirs: std.AutoArrayHashMapUnmanaged(Unit.Index, void), + files: Files, - prev_index: ?Index, - next_index: ?Index, + const Files = std.AutoArrayHashMapUnmanaged(Zcu.File.Index, void); - pub const Index = u32; + fn deinit(mod_info: *ModInfo, gpa: std.mem.Allocator) void { + mod_info.dirs.deinit(gpa); + mod_info.files.deinit(gpa); + mod_info.* = undefined; + } }; -const DbgLineHeader = struct { - minimum_instruction_length: u8, - maximum_operations_per_instruction: u8, - default_is_stmt: bool, - line_base: i8, - line_range: u8, - opcode_base: u8, +const DebugAbbrev = struct { + section: Section, + const unit: Unit.Index = @enumFromInt(0); + + const header_bytes = 0; + + const trailer_bytes = uleb128Bytes(@intFromEnum(AbbrevCode.null)); }; -/// Represents state of the analysed Nav. -/// Includes Nav's abbrev table of type Types, matching arena -/// and a set of relocations that will be resolved once this -/// Nav's inner Atom is assigned an offset within the DWARF section. -pub const NavState = struct { - dwarf: *Dwarf, - pt: Zcu.PerThread, - di_atom_navs: *const AtomTable, - dbg_line_func: InternPool.Index, - dbg_line: std.ArrayList(u8), - dbg_info: std.ArrayList(u8), - abbrev_type_arena: std.heap.ArenaAllocator, - abbrev_table: std.ArrayListUnmanaged(AbbrevEntry), - abbrev_resolver: std.AutoHashMapUnmanaged(InternPool.Index, u32), - abbrev_relocs: std.ArrayListUnmanaged(AbbrevRelocation), - exprloc_relocs: std.ArrayListUnmanaged(ExprlocRelocation), - - pub fn deinit(ns: *NavState) void { - const gpa = ns.dwarf.allocator; - ns.dbg_line.deinit(); - ns.dbg_info.deinit(); - ns.abbrev_type_arena.deinit(); - ns.abbrev_table.deinit(gpa); - ns.abbrev_resolver.deinit(gpa); - ns.abbrev_relocs.deinit(gpa); - ns.exprloc_relocs.deinit(gpa); - } - - /// Adds local type relocation of the form: @offset => @this + addend - /// @this signifies the offset within the .debug_abbrev section of the containing atom. - fn addTypeRelocLocal(self: *NavState, atom_index: Atom.Index, offset: u32, addend: u32) !void { - log.debug("{x}: @this + {x}", .{ offset, addend }); - try self.abbrev_relocs.append(self.dwarf.allocator, .{ - .target = null, - .atom_index = atom_index, - .offset = offset, - .addend = addend, - }); +const DebugAranges = struct { + section: Section, + + fn headerBytes(dwarf: *Dwarf) u32 { + return std.mem.alignForwardAnyAlign( + u32, + dwarf.unitLengthBytes() + 2 + dwarf.sectionOffsetBytes() + 1 + 1, + @intFromEnum(dwarf.address_size) * 2, + ); } - /// Adds global type relocation of the form: @offset => @symbol + 0 - /// @symbol signifies a type abbreviation posititioned somewhere in the .debug_abbrev section - /// which we use as our target of the relocation. - fn addTypeRelocGlobal(self: *NavState, atom_index: Atom.Index, ty: Type, offset: u32) !void { - const gpa = self.dwarf.allocator; - const resolv = self.abbrev_resolver.get(ty.toIntern()) orelse blk: { - const sym_index: u32 = @intCast(self.abbrev_table.items.len); - try self.abbrev_table.append(gpa, .{ - .atom_index = atom_index, - .type = ty, - .offset = undefined, - }); - log.debug("%{d}: {}", .{ sym_index, ty.fmt(self.pt) }); - try self.abbrev_resolver.putNoClobber(gpa, ty.toIntern(), sym_index); - break :blk sym_index; - }; - log.debug("{x}: %{d} + 0", .{ offset, resolv }); - try self.abbrev_relocs.append(gpa, .{ - .target = resolv, - .atom_index = atom_index, - .offset = offset, - .addend = 0, - }); + fn trailerBytes(dwarf: *Dwarf) u32 { + return @intFromEnum(dwarf.address_size) * 2; } +}; - fn addDbgInfoType( - self: *NavState, - pt: Zcu.PerThread, - atom_index: Atom.Index, - ty: Type, - ) error{OutOfMemory}!void { - const zcu = pt.zcu; - const dbg_info_buffer = &self.dbg_info; - const target = zcu.getTarget(); - const target_endian = target.cpu.arch.endian(); - const ip = &zcu.intern_pool; +const DebugInfo = struct { + section: Section, - switch (ty.zigTypeTag(zcu)) { - .NoReturn => unreachable, - .Void => { - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.zero_bit_type)); - }, - .Bool => { - try dbg_info_buffer.ensureUnusedCapacity(12); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.base_type)); - // DW.AT.encoding, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(DW.ATE.boolean); - // DW.AT.byte_size, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), ty.abiSize(pt)); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(pt)}); - }, - .Int => { - const info = ty.intInfo(zcu); - try dbg_info_buffer.ensureUnusedCapacity(12); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.base_type)); - // DW.AT.encoding, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(switch (info.signedness) { - .signed => DW.ATE.signed, - .unsigned => DW.ATE.unsigned, - }); - // DW.AT.byte_size, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), ty.abiSize(pt)); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(pt)}); - }, - .Optional => { - if (ty.isPtrLikeOptional(zcu)) { - try dbg_info_buffer.ensureUnusedCapacity(12); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.base_type)); - // DW.AT.encoding, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(DW.ATE.address); - // DW.AT.byte_size, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), ty.abiSize(pt)); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(pt)}); - } else { - // Non-pointer optionals are structs: struct { .maybe = *, .val = * } - const payload_ty = ty.optionalChild(zcu); - // DW.AT.structure_type - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.struct_type)); - // DW.AT.byte_size, DW.FORM.udata - const abi_size = ty.abiSize(pt); - try leb128.writeUleb128(dbg_info_buffer.writer(), abi_size); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(pt)}); - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(21); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("maybe"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - var index = dbg_info_buffer.items.len; - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocGlobal(atom_index, Type.bool, @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.member - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("val"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - index = dbg_info_buffer.items.len; - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocGlobal(atom_index, payload_ty, @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - const offset = abi_size - payload_ty.abiSize(pt); - try leb128.writeUleb128(dbg_info_buffer.writer(), offset); - // DW.AT.structure_type delimit children - try dbg_info_buffer.append(0); - } - }, - .Pointer => { - if (ty.isSlice(zcu)) { - // Slices are structs: struct { .ptr = *, .len = N } - const ptr_bits = target.ptrBitWidth(); - const ptr_bytes: u8 = @intCast(@divExact(ptr_bits, 8)); - // DW.AT.structure_type - try dbg_info_buffer.ensureUnusedCapacity(2); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.struct_type)); - // DW.AT.byte_size, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), ty.abiSize(pt)); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(pt)}); - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(21); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("ptr"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - var index = dbg_info_buffer.items.len; - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - const ptr_ty = ty.slicePtrFieldType(zcu); - try self.addTypeRelocGlobal(atom_index, ptr_ty, @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.member - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("len"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - index = dbg_info_buffer.items.len; - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocGlobal(atom_index, Type.usize, @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - dbg_info_buffer.appendAssumeCapacity(ptr_bytes); - // DW.AT.structure_type delimit children - dbg_info_buffer.appendAssumeCapacity(0); - } else { - try dbg_info_buffer.ensureUnusedCapacity(9); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.ptr_type)); - // DW.AT.type, DW.FORM.ref4 - const index = dbg_info_buffer.items.len; - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocGlobal(atom_index, ty.childType(zcu), @intCast(index)); - } - }, - .Array => { - // DW.AT.array_type - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.array_type)); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(pt)}); - // DW.AT.type, DW.FORM.ref4 - var index = dbg_info_buffer.items.len; - try dbg_info_buffer.ensureUnusedCapacity(9); - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocGlobal(atom_index, ty.childType(zcu), @intCast(index)); - // DW.AT.subrange_type - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.array_dim)); - // DW.AT.type, DW.FORM.ref4 - index = dbg_info_buffer.items.len; - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocGlobal(atom_index, Type.usize, @intCast(index)); - // DW.AT.count, DW.FORM.udata - const len = ty.arrayLenIncludingSentinel(pt.zcu); - try leb128.writeUleb128(dbg_info_buffer.writer(), len); - // DW.AT.array_type delimit children - try dbg_info_buffer.append(0); - }, - .Struct => { - // DW.AT.structure_type - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.struct_type)); - // DW.AT.byte_size, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), ty.abiSize(pt)); - - blk: { - switch (ip.indexToKey(ty.ip_index)) { - .anon_struct_type => |fields| { - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(pt)}); - - for (fields.types.get(ip), 0..) |field_ty, field_index| { - // DW.AT.member - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{d}\x00", .{field_index}); - // DW.AT.type, DW.FORM.ref4 - const index = dbg_info_buffer.items.len; - try dbg_info_buffer.appendNTimes(0, 4); - try self.addTypeRelocGlobal(atom_index, Type.fromInterned(field_ty), @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - const field_off = ty.structFieldOffset(field_index, pt); - try leb128.writeUleb128(dbg_info_buffer.writer(), field_off); - } - }, - .struct_type => { - const struct_type = ip.loadStructType(ty.toIntern()); - // DW.AT.name, DW.FORM.string - try ty.print(dbg_info_buffer.writer(), pt); - try dbg_info_buffer.append(0); - - if (struct_type.layout == .@"packed") { - log.debug("TODO implement .debug_info for packed structs", .{}); - break :blk; - } + fn headerBytes(dwarf: *Dwarf) u32 { + return dwarf.unitLengthBytes() + 2 + 1 + 1 + dwarf.sectionOffsetBytes() + + uleb128Bytes(@intFromEnum(AbbrevCode.compile_unit)) + 1 + dwarf.sectionOffsetBytes() * 6 + uleb128Bytes(0) + + uleb128Bytes(@intFromEnum(AbbrevCode.module)) + dwarf.sectionOffsetBytes() + uleb128Bytes(0); + } - if (struct_type.isTuple(ip)) { - for (struct_type.field_types.get(ip), struct_type.offsets.get(ip), 0..) |field_ty, field_off, field_index| { - if (!Type.fromInterned(field_ty).hasRuntimeBits(pt)) continue; - // DW.AT.member - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{d}\x00", .{field_index}); - // DW.AT.type, DW.FORM.ref4 - const index = dbg_info_buffer.items.len; - try dbg_info_buffer.appendNTimes(0, 4); - try self.addTypeRelocGlobal(atom_index, Type.fromInterned(field_ty), @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), field_off); - } - } else { - for ( - struct_type.field_names.get(ip), - struct_type.field_types.get(ip), - struct_type.offsets.get(ip), - ) |field_name, field_ty, field_off| { - if (!Type.fromInterned(field_ty).hasRuntimeBits(pt)) continue; - const field_name_slice = field_name.toSlice(ip); - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(field_name_slice.len + 2); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity(field_name_slice[0 .. field_name_slice.len + 1]); - // DW.AT.type, DW.FORM.ref4 - const index = dbg_info_buffer.items.len; - try dbg_info_buffer.appendNTimes(0, 4); - try self.addTypeRelocGlobal(atom_index, Type.fromInterned(field_ty), @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), field_off); - } - } - }, - else => unreachable, - } - } + fn declEntryLineOff(dwarf: *Dwarf) u32 { + return AbbrevCode.decl_bytes + dwarf.sectionOffsetBytes(); + } - // DW.AT.structure_type delimit children - try dbg_info_buffer.append(0); - }, - .Enum => { - // DW.AT.enumeration_type - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.enum_type)); - // DW.AT.byte_size, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), ty.abiSize(pt)); - // DW.AT.name, DW.FORM.string - try ty.print(dbg_info_buffer.writer(), pt); - try dbg_info_buffer.append(0); - - const enum_type = ip.loadEnumType(ty.ip_index); - for (enum_type.names.get(ip), 0..) |field_name, field_i| { - const field_name_slice = field_name.toSlice(ip); - // DW.AT.enumerator - try dbg_info_buffer.ensureUnusedCapacity(field_name_slice.len + 2 + @sizeOf(u64)); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.enum_variant)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity(field_name_slice[0 .. field_name_slice.len + 1]); - // DW.AT.const_value, DW.FORM.data8 - const value: u64 = value: { - if (enum_type.values.len == 0) break :value field_i; // auto-numbered - const value = enum_type.values.get(ip)[field_i]; - // TODO do not assume a 64bit enum value - could be bigger. - // See https://github.com/ziglang/zig/issues/645 - const field_int_val = try Value.fromInterned(value).intFromEnum(ty, pt); - break :value @bitCast(field_int_val.toSignedInt(pt)); - }; - mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), value, target_endian); - } + const trailer_bytes = 1 + 1; +}; - // DW.AT.enumeration_type delimit children - try dbg_info_buffer.append(0); - }, - .Union => { - const union_obj = zcu.typeToUnion(ty).?; - const layout = pt.getUnionLayout(union_obj); - const payload_offset = if (layout.tag_align.compare(.gte, layout.payload_align)) layout.tag_size else 0; - const tag_offset = if (layout.tag_align.compare(.gte, layout.payload_align)) 0 else layout.payload_size; - // TODO this is temporary to match current state of unions in Zig - we don't yet have - // safety checks implemented meaning the implicit tag is not yet stored and generated - // for untagged unions. - const is_tagged = layout.tag_size > 0; - if (is_tagged) { - // DW.AT.structure_type - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.struct_type)); - // DW.AT.byte_size, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), layout.abi_size); - // DW.AT.name, DW.FORM.string - try ty.print(dbg_info_buffer.writer(), pt); - try dbg_info_buffer.append(0); - - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(13); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("payload"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - const inner_union_index = dbg_info_buffer.items.len; - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocLocal(atom_index, @intCast(inner_union_index), 5); - // DW.AT.data_member_location, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), payload_offset); - } +const DebugLine = struct { + header: Header, + section: Section, + + const Header = struct { + minimum_instruction_length: u8, + maximum_operations_per_instruction: u8, + default_is_stmt: bool, + line_base: i8, + line_range: u8, + opcode_base: u8, + }; - // DW.AT.union_type - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.union_type)); - // DW.AT.byte_size, DW.FORM.udata, - try leb128.writeUleb128(dbg_info_buffer.writer(), layout.payload_size); - // DW.AT.name, DW.FORM.string - if (is_tagged) { - try dbg_info_buffer.writer().print("AnonUnion\x00", .{}); - } else { - try ty.print(dbg_info_buffer.writer(), pt); - try dbg_info_buffer.append(0); - } + fn dirIndexInfo(dir_count: u32) struct { bytes: u8, form: DeclValEnum(DW.FORM) } { + return if (dir_count <= 1 << 8) + .{ .bytes = 1, .form = .data1 } + else if (dir_count <= 1 << 16) + .{ .bytes = 2, .form = .data2 } + else + unreachable; + } - for (union_obj.field_types.get(ip), union_obj.loadTagType(ip).names.get(ip)) |field_ty, field_name| { - if (!Type.fromInterned(field_ty).hasRuntimeBits(pt)) continue; - const field_name_slice = field_name.toSlice(ip); - // DW.AT.member - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.appendSlice(field_name_slice[0 .. field_name_slice.len + 1]); - // DW.AT.type, DW.FORM.ref4 - const index = dbg_info_buffer.items.len; - try dbg_info_buffer.appendNTimes(0, 4); - try self.addTypeRelocGlobal(atom_index, Type.fromInterned(field_ty), @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - try dbg_info_buffer.append(0); - } - // DW.AT.union_type delimit children - try dbg_info_buffer.append(0); - - if (is_tagged) { - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(9); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("tag"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - const index = dbg_info_buffer.items.len; - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocGlobal(atom_index, Type.fromInterned(union_obj.enum_tag_ty), @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), tag_offset); - - // DW.AT.structure_type delimit children - try dbg_info_buffer.append(0); - } - }, - .ErrorSet => try addDbgInfoErrorSet(pt, ty, target, &self.dbg_info), - .ErrorUnion => { - const error_ty = ty.errorUnionSet(zcu); - const payload_ty = ty.errorUnionPayload(zcu); - const payload_align = if (payload_ty.isNoReturn(zcu)) .none else payload_ty.abiAlignment(pt); - const error_align = Type.anyerror.abiAlignment(pt); - const abi_size = ty.abiSize(pt); - const payload_off = if (error_align.compare(.gte, payload_align)) Type.anyerror.abiSize(pt) else 0; - const error_off = if (error_align.compare(.gte, payload_align)) 0 else payload_ty.abiSize(pt); - - // DW.AT.structure_type - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.struct_type)); - // DW.AT.byte_size, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), abi_size); - // DW.AT.name, DW.FORM.string - try ty.print(dbg_info_buffer.writer(), pt); - try dbg_info_buffer.append(0); - - if (!payload_ty.isNoReturn(zcu)) { - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(11); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("value"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - const index = dbg_info_buffer.items.len; - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocGlobal(atom_index, payload_ty, @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), payload_off); - } + fn headerBytes(dwarf: *Dwarf, dir_count: u32, file_count: u32) u32 { + const dir_index_info = dirIndexInfo(dir_count); + return dwarf.unitLengthBytes() + 2 + 1 + 1 + dwarf.sectionOffsetBytes() + 1 + 1 + 1 + 1 + 1 + 1 + 1 * (dwarf.debug_line.header.opcode_base - 1) + + 1 + uleb128Bytes(DW.LNCT.path) + uleb128Bytes(DW.FORM.line_strp) + uleb128Bytes(dir_count) + (dwarf.sectionOffsetBytes()) * dir_count + + 1 + uleb128Bytes(DW.LNCT.path) + uleb128Bytes(DW.FORM.line_strp) + uleb128Bytes(DW.LNCT.directory_index) + uleb128Bytes(@intFromEnum(dir_index_info.form)) + uleb128Bytes(DW.LNCT.LLVM_source) + uleb128Bytes(DW.FORM.line_strp) + uleb128Bytes(file_count) + (dwarf.sectionOffsetBytes() + dir_index_info.bytes + dwarf.sectionOffsetBytes()) * file_count; + } - { - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(9); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.struct_member)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("err"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - const index = dbg_info_buffer.items.len; - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocGlobal(atom_index, error_ty, @intCast(index)); - // DW.AT.data_member_location, DW.FORM.udata - try leb128.writeUleb128(dbg_info_buffer.writer(), error_off); - } + const trailer_bytes = 1 + uleb128Bytes(1) + 1; +}; - // DW.AT.structure_type delimit children - try dbg_info_buffer.append(0); - }, - else => { - log.debug("TODO implement .debug_info for type '{}'", .{ty.fmt(pt)}); - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.zero_bit_type)); - }, - } +const DebugLocLists = struct { + section: Section, + + fn baseOffset(dwarf: *Dwarf) u32 { + return dwarf.unitLengthBytes() + 2 + 1 + 1 + 4; } - pub const DbgInfoLoc = union(enum) { - register: u8, - register_pair: [2]u8, - stack: struct { - fp_register: u8, - offset: i32, - }, - wasm_local: u32, - memory: u64, - linker_load: LinkerLoad, - immediate: u64, - undef, - none, - nop, - }; + fn headerBytes(dwarf: *Dwarf) u32 { + return baseOffset(dwarf); + } - pub fn genArgDbgInfo( - self: *NavState, - name: [:0]const u8, - ty: Type, - owner_nav: InternPool.Nav.Index, - loc: DbgInfoLoc, - ) error{OutOfMemory}!void { - const pt = self.pt; - const dbg_info = &self.dbg_info; - const atom_index = self.di_atom_navs.get(owner_nav).?; - const name_with_null = name.ptr[0 .. name.len + 1]; + const trailer_bytes = 0; +}; - switch (loc) { - .register => |reg| { - try dbg_info.ensureUnusedCapacity(4); - dbg_info.appendAssumeCapacity(@intFromEnum(AbbrevCode.parameter)); - // DW.AT.location, DW.FORM.exprloc - var expr_len = std.io.countingWriter(std.io.null_writer); - if (reg < 32) { - expr_len.writer().writeByte(DW.OP.reg0 + reg) catch unreachable; - } else { - expr_len.writer().writeByte(DW.OP.regx) catch unreachable; - leb128.writeUleb128(expr_len.writer(), reg) catch unreachable; - } - leb128.writeUleb128(dbg_info.writer(), expr_len.bytes_written) catch unreachable; - if (reg < 32) { - dbg_info.appendAssumeCapacity(DW.OP.reg0 + reg); - } else { - dbg_info.appendAssumeCapacity(DW.OP.regx); - leb128.writeUleb128(dbg_info.writer(), reg) catch unreachable; - } - }, - .register_pair => |regs| { - const reg_bits = pt.zcu.getTarget().ptrBitWidth(); - const reg_bytes: u8 = @intCast(@divExact(reg_bits, 8)); - const abi_size = ty.abiSize(pt); - try dbg_info.ensureUnusedCapacity(10); - dbg_info.appendAssumeCapacity(@intFromEnum(AbbrevCode.parameter)); - // DW.AT.location, DW.FORM.exprloc - var expr_len = std.io.countingWriter(std.io.null_writer); - for (regs, 0..) |reg, reg_i| { - if (reg < 32) { - expr_len.writer().writeByte(DW.OP.reg0 + reg) catch unreachable; - } else { - expr_len.writer().writeByte(DW.OP.regx) catch unreachable; - leb128.writeUleb128(expr_len.writer(), reg) catch unreachable; - } - expr_len.writer().writeByte(DW.OP.piece) catch unreachable; - leb128.writeUleb128( - expr_len.writer(), - @min(abi_size - reg_i * reg_bytes, reg_bytes), - ) catch unreachable; - } - leb128.writeUleb128(dbg_info.writer(), expr_len.bytes_written) catch unreachable; - for (regs, 0..) |reg, reg_i| { - if (reg < 32) { - dbg_info.appendAssumeCapacity(DW.OP.reg0 + reg); - } else { - dbg_info.appendAssumeCapacity(DW.OP.regx); - leb128.writeUleb128(dbg_info.writer(), reg) catch unreachable; - } - dbg_info.appendAssumeCapacity(DW.OP.piece); - leb128.writeUleb128( - dbg_info.writer(), - @min(abi_size - reg_i * reg_bytes, reg_bytes), - ) catch unreachable; - } - }, - .stack => |info| { - try dbg_info.ensureUnusedCapacity(9); - dbg_info.appendAssumeCapacity(@intFromEnum(AbbrevCode.parameter)); - // DW.AT.location, DW.FORM.exprloc - var expr_len = std.io.countingWriter(std.io.null_writer); - if (info.fp_register < 32) { - expr_len.writer().writeByte(DW.OP.breg0 + info.fp_register) catch unreachable; - } else { - expr_len.writer().writeByte(DW.OP.bregx) catch unreachable; - leb128.writeUleb128(expr_len.writer(), info.fp_register) catch unreachable; - } - leb128.writeIleb128(expr_len.writer(), info.offset) catch unreachable; - leb128.writeUleb128(dbg_info.writer(), expr_len.bytes_written) catch unreachable; - if (info.fp_register < 32) { - dbg_info.appendAssumeCapacity(DW.OP.breg0 + info.fp_register); - } else { - dbg_info.appendAssumeCapacity(DW.OP.bregx); - leb128.writeUleb128(dbg_info.writer(), info.fp_register) catch unreachable; - } - leb128.writeIleb128(dbg_info.writer(), info.offset) catch unreachable; - }, - .wasm_local => |value| { - @import("../dev.zig").check(.wasm_linker); - const leb_size = link.File.Wasm.getUleb128Size(value); - try dbg_info.ensureUnusedCapacity(3 + leb_size); - // wasm locations are encoded as follow: - // DW_OP_WASM_location wasm-op - // where wasm-op is defined as - // wasm-op := wasm-local | wasm-global | wasm-operand_stack - // where each argument is encoded as - // i:uleb128 - dbg_info.appendSliceAssumeCapacity(&.{ - @intFromEnum(AbbrevCode.parameter), - DW.OP.WASM_location, - DW.OP.WASM_local, - }); - leb128.writeUleb128(dbg_info.writer(), value) catch unreachable; - }, - else => unreachable, - } +const DebugRngLists = struct { + section: Section, + + const baseOffset = DebugLocLists.baseOffset; - try dbg_info.ensureUnusedCapacity(5 + name_with_null.len); - const index = dbg_info.items.len; - dbg_info.appendNTimesAssumeCapacity(0, 4); - try self.addTypeRelocGlobal(atom_index, ty, @intCast(index)); // DW.AT.type, DW.FORM.ref4 - dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string + fn headerBytes(dwarf: *Dwarf) u32 { + return baseOffset(dwarf) + dwarf.sectionOffsetBytes() * 1; } - pub fn genVarDbgInfo( - self: *NavState, - name: [:0]const u8, - ty: Type, - owner_nav: InternPool.Nav.Index, - is_ptr: bool, - loc: DbgInfoLoc, - ) error{OutOfMemory}!void { - const dbg_info = &self.dbg_info; - const atom_index = self.di_atom_navs.get(owner_nav).?; - const name_with_null = name.ptr[0 .. name.len + 1]; - try dbg_info.append(@intFromEnum(AbbrevCode.variable)); - const gpa = self.dwarf.allocator; - const pt = self.pt; - const target = pt.zcu.getTarget(); - const endian = target.cpu.arch.endian(); - const child_ty = if (is_ptr) ty.childType(pt.zcu) else ty; + const trailer_bytes = 1; +}; - switch (loc) { - .register => |reg| { - try dbg_info.ensureUnusedCapacity(3); - // DW.AT.location, DW.FORM.exprloc - var expr_len = std.io.countingWriter(std.io.null_writer); - if (reg < 32) { - expr_len.writer().writeByte(DW.OP.reg0 + reg) catch unreachable; - } else { - expr_len.writer().writeByte(DW.OP.regx) catch unreachable; - leb128.writeUleb128(expr_len.writer(), reg) catch unreachable; - } - leb128.writeUleb128(dbg_info.writer(), expr_len.bytes_written) catch unreachable; - if (reg < 32) { - dbg_info.appendAssumeCapacity(DW.OP.reg0 + reg); - } else { - dbg_info.appendAssumeCapacity(DW.OP.regx); - leb128.writeUleb128(dbg_info.writer(), reg) catch unreachable; - } - }, +const StringSection = struct { + contents: std.ArrayListUnmanaged(u8), + map: std.AutoArrayHashMapUnmanaged(void, void), + section: Section, - .register_pair => |regs| { - const reg_bits = pt.zcu.getTarget().ptrBitWidth(); - const reg_bytes: u8 = @intCast(@divExact(reg_bits, 8)); - const abi_size = child_ty.abiSize(pt); - try dbg_info.ensureUnusedCapacity(9); - // DW.AT.location, DW.FORM.exprloc - var expr_len = std.io.countingWriter(std.io.null_writer); - for (regs, 0..) |reg, reg_i| { - if (reg < 32) { - expr_len.writer().writeByte(DW.OP.reg0 + reg) catch unreachable; - } else { - expr_len.writer().writeByte(DW.OP.regx) catch unreachable; - leb128.writeUleb128(expr_len.writer(), reg) catch unreachable; - } - expr_len.writer().writeByte(DW.OP.piece) catch unreachable; - leb128.writeUleb128( - expr_len.writer(), - @min(abi_size - reg_i * reg_bytes, reg_bytes), - ) catch unreachable; - } - leb128.writeUleb128(dbg_info.writer(), expr_len.bytes_written) catch unreachable; - for (regs, 0..) |reg, reg_i| { - if (reg < 32) { - dbg_info.appendAssumeCapacity(DW.OP.reg0 + reg); - } else { - dbg_info.appendAssumeCapacity(DW.OP.regx); - leb128.writeUleb128(dbg_info.writer(), reg) catch unreachable; - } - dbg_info.appendAssumeCapacity(DW.OP.piece); - leb128.writeUleb128( - dbg_info.writer(), - @min(abi_size - reg_i * reg_bytes, reg_bytes), - ) catch unreachable; - } - }, + const unit: Unit.Index = @enumFromInt(0); - .stack => |info| { - try dbg_info.ensureUnusedCapacity(9); - // DW.AT.location, DW.FORM.exprloc - var expr_len = std.io.countingWriter(std.io.null_writer); - if (info.fp_register < 32) { - expr_len.writer().writeByte(DW.OP.breg0 + info.fp_register) catch unreachable; - } else { - expr_len.writer().writeByte(DW.OP.bregx) catch unreachable; - leb128.writeUleb128(expr_len.writer(), info.fp_register) catch unreachable; - } - leb128.writeIleb128(expr_len.writer(), info.offset) catch unreachable; - leb128.writeUleb128(dbg_info.writer(), expr_len.bytes_written) catch unreachable; - if (info.fp_register < 32) { - dbg_info.appendAssumeCapacity(DW.OP.breg0 + info.fp_register); - } else { - dbg_info.appendAssumeCapacity(DW.OP.bregx); - leb128.writeUleb128(dbg_info.writer(), info.fp_register) catch unreachable; - } - leb128.writeIleb128(dbg_info.writer(), info.offset) catch unreachable; - }, - - .wasm_local => |value| { - const leb_size = link.File.Wasm.getUleb128Size(value); - try dbg_info.ensureUnusedCapacity(2 + leb_size); - // wasm locals are encoded as follow: - // DW_OP_WASM_location wasm-op - // where wasm-op is defined as - // wasm-op := wasm-local | wasm-global | wasm-operand_stack - // where wasm-local is encoded as - // wasm-local := 0x00 i:uleb128 - dbg_info.appendSliceAssumeCapacity(&.{ - DW.OP.WASM_location, - DW.OP.WASM_local, - }); - leb128.writeUleb128(dbg_info.writer(), value) catch unreachable; - }, + const init: StringSection = .{ + .contents = .{}, + .map = .{}, + .section = Section.init, + }; - .memory, - .linker_load, - => { - const ptr_width: u8 = @intCast(@divExact(target.ptrBitWidth(), 8)); - try dbg_info.ensureUnusedCapacity(2 + ptr_width); - dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc - 1 + ptr_width + @intFromBool(is_ptr), - DW.OP.addr, // literal address - }); - const offset: u32 = @intCast(dbg_info.items.len); - const addr = switch (loc) { - .memory => |x| x, - else => 0, - }; - switch (ptr_width) { - 0...4 => { - try dbg_info.writer().writeInt(u32, @intCast(addr), endian); - }, - 5...8 => { - try dbg_info.writer().writeInt(u64, addr, endian); - }, - else => unreachable, - } - if (is_ptr) { - // We need deref the address as we point to the value via GOT entry. - try dbg_info.append(DW.OP.deref); - } - switch (loc) { - .linker_load => |load_struct| switch (load_struct.type) { - .direct => { - log.debug("{x}: target sym %{d}", .{ offset, load_struct.sym_index }); - try self.exprloc_relocs.append(gpa, .{ - .type = .direct_load, - .target = load_struct.sym_index, - .offset = offset, - }); - }, - .got => { - log.debug("{x}: target sym %{d} via GOT", .{ offset, load_struct.sym_index }); - try self.exprloc_relocs.append(gpa, .{ - .type = .got_load, - .target = load_struct.sym_index, - .offset = offset, - }); - }, - else => {}, // TODO - }, - else => {}, - } - }, + fn deinit(str_sec: *StringSection, gpa: std.mem.Allocator) void { + str_sec.contents.deinit(gpa); + str_sec.map.deinit(gpa); + str_sec.section.deinit(gpa); + } - .immediate => |x| { - try dbg_info.ensureUnusedCapacity(2); - const fixup = dbg_info.items.len; - dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc - 1, - if (child_ty.isSignedInt(pt.zcu)) DW.OP.consts else DW.OP.constu, - }); - if (child_ty.isSignedInt(pt.zcu)) { - try leb128.writeIleb128(dbg_info.writer(), @as(i64, @bitCast(x))); - } else { - try leb128.writeUleb128(dbg_info.writer(), x); - } - try dbg_info.append(DW.OP.stack_value); - dbg_info.items[fixup] += @intCast(dbg_info.items.len - fixup - 2); - }, - - .undef => { - // DW.AT.location, DW.FORM.exprloc - // uleb128(exprloc_len) - // DW.OP.implicit_value uleb128(len_of_bytes) bytes - const abi_size: u32 = @intCast(child_ty.abiSize(self.pt)); - var implicit_value_len = std.ArrayList(u8).init(gpa); - defer implicit_value_len.deinit(); - try leb128.writeUleb128(implicit_value_len.writer(), abi_size); - const total_exprloc_len = 1 + implicit_value_len.items.len + abi_size; - try leb128.writeUleb128(dbg_info.writer(), total_exprloc_len); - try dbg_info.ensureUnusedCapacity(total_exprloc_len); - dbg_info.appendAssumeCapacity(DW.OP.implicit_value); - dbg_info.appendSliceAssumeCapacity(implicit_value_len.items); - dbg_info.appendNTimesAssumeCapacity(0xaa, abi_size); - }, + fn addString(str_sec: *StringSection, dwarf: *Dwarf, str: []const u8) UpdateError!Entry.Index { + const gop = try str_sec.map.getOrPutAdapted(dwarf.gpa, str, Adapter{ .str_sec = str_sec }); + errdefer _ = str_sec.map.pop(); + const entry: Entry.Index = @enumFromInt(gop.index); + if (!gop.found_existing) { + const unit_ptr = str_sec.section.getUnit(unit); + assert(try str_sec.section.getUnit(unit).addEntry(dwarf.gpa) == entry); + errdefer _ = unit_ptr.entries.pop(); + const entry_ptr = unit_ptr.getEntry(entry); + if (unit_ptr.last.unwrap()) |last_entry| + unit_ptr.getEntry(last_entry).next = entry.toOptional(); + entry_ptr.prev = unit_ptr.last; + unit_ptr.last = entry.toOptional(); + entry_ptr.off = @intCast(str_sec.contents.items.len); + entry_ptr.len = @intCast(str.len + 1); + try str_sec.contents.ensureUnusedCapacity(dwarf.gpa, str.len + 1); + str_sec.contents.appendSliceAssumeCapacity(str); + str_sec.contents.appendAssumeCapacity(0); + str_sec.section.dirty = true; + } + return entry; + } - .none => { - try dbg_info.ensureUnusedCapacity(3); - dbg_info.appendSliceAssumeCapacity(&[3]u8{ // DW.AT.location, DW.FORM.exprloc - 2, DW.OP.lit0, DW.OP.stack_value, - }); - }, + const Adapter = struct { + str_sec: *StringSection, - .nop => { - try dbg_info.ensureUnusedCapacity(2); - dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc - 1, DW.OP.nop, - }); - }, + pub fn hash(_: Adapter, key: []const u8) u32 { + return @truncate(std.hash.Wyhash.hash(0, key)); } - try dbg_info.ensureUnusedCapacity(5 + name_with_null.len); - const index = dbg_info.items.len; - dbg_info.appendNTimesAssumeCapacity(0, 4); // dw.at.type, dw.form.ref4 - try self.addTypeRelocGlobal(atom_index, child_ty, @intCast(index)); - dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string - } - - pub fn advancePCAndLine( - self: *NavState, - delta_line: i33, - delta_pc: u64, - ) error{OutOfMemory}!void { - const dbg_line = &self.dbg_line; - try dbg_line.ensureUnusedCapacity(5 + 5 + 1); + pub fn eql(adapter: Adapter, key: []const u8, _: void, rhs_index: usize) bool { + const entry = adapter.str_sec.section.getUnit(unit).getEntry(@enumFromInt(rhs_index)); + return std.mem.eql(u8, key, adapter.str_sec.contents.items[entry.off..][0 .. entry.len - 1 :0]); + } + }; +}; - const header = self.dwarf.dbg_line_header; - assert(header.maximum_operations_per_instruction == 1); - const delta_op: u64 = 0; +/// A linker section containing a sequence of `Unit`s. +pub const Section = struct { + dirty: bool, + pad_to_ideal: bool, + alignment: InternPool.Alignment, + index: u32, + first: Unit.Index.Optional, + last: Unit.Index.Optional, + off: u64, + len: u64, + units: std.ArrayListUnmanaged(Unit), + + const Index = enum { + debug_abbrev, + debug_info, + debug_line, + debug_line_str, + debug_loclists, + debug_rnglists, + debug_str, + }; - const remaining_delta_line: i9 = @intCast(if (delta_line < header.line_base or - delta_line - header.line_base >= header.line_range) - remaining: { - assert(delta_line != 0); - dbg_line.appendAssumeCapacity(DW.LNS.advance_line); - leb128.writeIleb128(dbg_line.writer(), delta_line) catch unreachable; - break :remaining 0; - } else delta_line); + const init: Section = .{ + .dirty = true, + .pad_to_ideal = true, + .alignment = .@"1", + .index = std.math.maxInt(u32), + .first = .none, + .last = .none, + .off = 0, + .len = 0, + .units = .{}, + }; - const op_advance = @divExact(delta_pc, header.minimum_instruction_length) * - header.maximum_operations_per_instruction + delta_op; - const max_op_advance: u9 = (std.math.maxInt(u8) - header.opcode_base) / header.line_range; - const remaining_op_advance: u8 = @intCast(if (op_advance >= 2 * max_op_advance) remaining: { - dbg_line.appendAssumeCapacity(DW.LNS.advance_pc); - leb128.writeUleb128(dbg_line.writer(), op_advance) catch unreachable; - break :remaining 0; - } else if (op_advance >= max_op_advance) remaining: { - dbg_line.appendAssumeCapacity(DW.LNS.const_add_pc); - break :remaining op_advance - max_op_advance; - } else op_advance); + fn deinit(sec: *Section, gpa: std.mem.Allocator) void { + for (sec.units.items) |*unit| unit.deinit(gpa); + sec.units.deinit(gpa); + sec.* = undefined; + } - if (remaining_delta_line == 0 and remaining_op_advance == 0) { - dbg_line.appendAssumeCapacity(DW.LNS.copy); - } else { - dbg_line.appendAssumeCapacity(@intCast((remaining_delta_line - header.line_base) + - (header.line_range * remaining_op_advance) + header.opcode_base)); + fn addUnit(sec: *Section, header_len: u32, trailer_len: u32, dwarf: *Dwarf) UpdateError!Unit.Index { + const unit: Unit.Index = @enumFromInt(sec.units.items.len); + const unit_ptr = try sec.units.addOne(dwarf.gpa); + errdefer sec.popUnit(dwarf.gpa); + unit_ptr.* = .{ + .prev = sec.last, + .next = .none, + .first = .none, + .last = .none, + .off = 0, + .header_len = header_len, + .trailer_len = trailer_len, + .len = header_len + trailer_len, + .entries = .{}, + .cross_unit_relocs = .{}, + .cross_section_relocs = .{}, + }; + if (sec.last.unwrap()) |last_unit| { + const last_unit_ptr = sec.getUnit(last_unit); + last_unit_ptr.next = unit.toOptional(); + unit_ptr.off = last_unit_ptr.off + sec.padToIdeal(last_unit_ptr.len); } + if (sec.first == .none) + sec.first = unit.toOptional(); + sec.last = unit.toOptional(); + try sec.resize(dwarf, unit_ptr.off + sec.padToIdeal(unit_ptr.len)); + return unit; } - pub fn setColumn(self: *NavState, column: u32) error{OutOfMemory}!void { - try self.dbg_line.ensureUnusedCapacity(1 + 5); - self.dbg_line.appendAssumeCapacity(DW.LNS.set_column); - leb128.writeUleb128(self.dbg_line.writer(), column + 1) catch unreachable; + fn unlinkUnit(sec: *Section, unit: Unit.Index) void { + const unit_ptr = sec.getUnit(unit); + if (unit_ptr.prev.unwrap()) |prev_unit| sec.getUnit(prev_unit).next = unit_ptr.next; + if (unit_ptr.next.unwrap()) |next_unit| sec.getUnit(next_unit).prev = unit_ptr.prev; + if (sec.first.unwrap().? == unit) sec.first = unit_ptr.next; + if (sec.last.unwrap().? == unit) sec.last = unit_ptr.prev; } - pub fn setPrologueEnd(self: *NavState) error{OutOfMemory}!void { - try self.dbg_line.append(DW.LNS.set_prologue_end); + fn popUnit(sec: *Section, gpa: std.mem.Allocator) void { + const unit_index: Unit.Index = @enumFromInt(sec.units.items.len - 1); + sec.unlinkUnit(unit_index); + var unit = sec.units.pop(); + unit.deinit(gpa); } - pub fn setEpilogueBegin(self: *NavState) error{OutOfMemory}!void { - try self.dbg_line.append(DW.LNS.set_epilogue_begin); + pub fn getUnit(sec: *Section, unit: Unit.Index) *Unit { + return &sec.units.items[@intFromEnum(unit)]; } - pub fn setInlineFunc(self: *NavState, func: InternPool.Index) error{OutOfMemory}!void { - const zcu = self.pt.zcu; - if (self.dbg_line_func == func) return; - - try self.dbg_line.ensureUnusedCapacity((1 + 4) + (1 + 5)); - - const old_func_info = zcu.funcInfo(self.dbg_line_func); - const new_func_info = zcu.funcInfo(func); + fn replaceEntry(sec: *Section, unit: Unit.Index, entry: Entry.Index, dwarf: *Dwarf, contents: []const u8) UpdateError!void { + const unit_ptr = sec.getUnit(unit); + const entry_ptr = unit_ptr.getEntry(entry); + if (contents.len > 0) { + if (entry_ptr.len == 0) { + assert(entry_ptr.prev == .none and entry_ptr.next == .none); + entry_ptr.off = if (unit_ptr.last.unwrap()) |last_entry| off: { + const last_entry_ptr = unit_ptr.getEntry(last_entry); + last_entry_ptr.next = entry.toOptional(); + break :off last_entry_ptr.off + sec.padToIdeal(last_entry_ptr.len); + } else 0; + entry_ptr.prev = unit_ptr.last; + unit_ptr.last = entry.toOptional(); + } + try entry_ptr.replace(unit_ptr, sec, dwarf, contents); + } + assert(entry_ptr.len == contents.len); + } - const old_file = try self.dwarf.addDIFile(zcu, old_func_info.owner_nav); - const new_file = try self.dwarf.addDIFile(zcu, new_func_info.owner_nav); - if (old_file != new_file) { - self.dbg_line.appendAssumeCapacity(DW.LNS.set_file); - leb128.writeUnsignedFixed(4, self.dbg_line.addManyAsArrayAssumeCapacity(4), new_file); + fn resize(sec: *Section, dwarf: *Dwarf, len: u64) UpdateError!void { + if (dwarf.bin_file.cast(.elf)) |elf_file| { + try elf_file.growNonAllocSection(sec.index, len, @intCast(sec.alignment.toByteUnits().?), true); + const shdr = &elf_file.shdrs.items[sec.index]; + sec.off = shdr.sh_offset; + sec.len = shdr.sh_size; + } else if (dwarf.bin_file.cast(.macho)) |macho_file| { + const header = if (macho_file.d_sym) |*d_sym| header: { + try d_sym.growSection(@intCast(sec.index), len, true, macho_file); + break :header &d_sym.sections.items[sec.index]; + } else header: { + try macho_file.growSection(@intCast(sec.index), len); + break :header &macho_file.sections.items(.header)[sec.index]; + }; + sec.off = header.offset; + sec.len = header.size; } + } - const old_src_line: i33 = zcu.navSrcLine(old_func_info.owner_nav); - const new_src_line: i33 = zcu.navSrcLine(new_func_info.owner_nav); - if (new_src_line != old_src_line) { - self.dbg_line.appendAssumeCapacity(DW.LNS.advance_line); - leb128.writeSignedFixed(5, self.dbg_line.addManyAsArrayAssumeCapacity(5), new_src_line - old_src_line); + fn trim(sec: *Section, dwarf: *Dwarf) void { + const len = sec.getUnit(sec.first.unwrap() orelse return).off; + if (len == 0) return; + for (sec.units.items) |*unit| unit.off -= len; + sec.off += len; + sec.len -= len; + if (dwarf.bin_file.cast(.elf)) |elf_file| { + const shdr = &elf_file.shdrs.items[sec.index]; + shdr.sh_offset = sec.off; + shdr.sh_size = sec.len; + } else if (dwarf.bin_file.cast(.macho)) |macho_file| { + const header = if (macho_file.d_sym) |*d_sym| + &d_sym.sections.items[sec.index] + else + &macho_file.sections.items(.header)[sec.index]; + header.offset = @intCast(sec.off); + header.size = sec.len; } + } - self.dbg_line_func = func; + fn resolveRelocs(sec: *Section, dwarf: *Dwarf) RelocError!void { + for (sec.units.items) |*unit| try unit.resolveRelocs(sec, dwarf); } -}; -pub const AbbrevEntry = struct { - atom_index: Atom.Index, - type: Type, - offset: u32, + fn padToIdeal(sec: *Section, actual_size: anytype) @TypeOf(actual_size) { + return if (sec.pad_to_ideal) Dwarf.padToIdeal(actual_size) else actual_size; + } }; -pub const AbbrevRelocation = struct { - /// If target is null, we deal with a local relocation that is based on simple offset + addend - /// only. - target: ?u32, - atom_index: Atom.Index, - offset: u32, - addend: u32, -}; +/// A unit within a `Section` containing a sequence of `Entry`s. +const Unit = struct { + prev: Index.Optional, + next: Index.Optional, + first: Entry.Index.Optional, + last: Entry.Index.Optional, + /// offset within containing section + off: u32, + header_len: u32, + trailer_len: u32, + /// data length in bytes + len: u32, + entries: std.ArrayListUnmanaged(Entry), + cross_unit_relocs: std.ArrayListUnmanaged(CrossUnitReloc), + cross_section_relocs: std.ArrayListUnmanaged(CrossSectionReloc), -pub const ExprlocRelocation = struct { - /// Type of the relocation: direct load ref, or GOT load ref (via GOT table) - type: enum { - direct_load, - got_load, - }, - /// Index of the target in the linker's locals symbol table. - target: u32, - /// Offset within the debug info buffer where to patch up the address value. - offset: u32, -}; + const Index = enum(u32) { + main, + _, -pub const PtrWidth = enum { p32, p64 }; + const Optional = enum(u32) { + none = std.math.maxInt(u32), + _, -pub const AbbrevCode = enum(u8) { - null, - padding, - compile_unit, - subprogram, - subprogram_retvoid, - base_type, - ptr_type, - struct_type, - struct_member, - enum_type, - enum_variant, - union_type, - zero_bit_type, - parameter, - variable, - array_type, - array_dim, -}; + pub fn unwrap(uio: Optional) ?Index { + return if (uio != .none) @enumFromInt(@intFromEnum(uio)) else null; + } + }; -/// The reloc offset for the virtual address of a function in its Line Number Program. -/// Size is a virtual address integer. -const dbg_line_vaddr_reloc_index = 3; -/// The reloc offset for the virtual address of a function in its .debug_info TAG.subprogram. -/// Size is a virtual address integer. -const dbg_info_low_pc_reloc_index = 1; + fn toOptional(ui: Index) Optional { + return @enumFromInt(@intFromEnum(ui)); + } + }; -const min_nop_size = 2; + fn clear(unit: *Unit) void { + unit.cross_unit_relocs.clearRetainingCapacity(); + unit.cross_section_relocs.clearRetainingCapacity(); + } -/// When allocating, the ideal_capacity is calculated by -/// actual_capacity + (actual_capacity / ideal_factor) -const ideal_factor = 3; + fn deinit(unit: *Unit, gpa: std.mem.Allocator) void { + for (unit.entries.items) |*entry| entry.deinit(gpa); + unit.entries.deinit(gpa); + unit.cross_unit_relocs.deinit(gpa); + unit.cross_section_relocs.deinit(gpa); + unit.* = undefined; + } -pub fn init(lf: *File, format: Format) Dwarf { - const comp = lf.comp; - const gpa = comp.gpa; - const target = comp.root_mod.resolved_target.result; - const ptr_width: PtrWidth = switch (target.ptrBitWidth()) { - 0...32 => .p32, - 33...64 => .p64, - else => unreachable, - }; - return .{ - .allocator = gpa, - .bin_file = lf, - .format = format, - .ptr_width = ptr_width, - .dbg_line_header = switch (target.cpu.arch) { - .x86_64, .aarch64 => .{ - .minimum_instruction_length = 1, - .maximum_operations_per_instruction = 1, - .default_is_stmt = true, - .line_base = -5, - .line_range = 14, - .opcode_base = DW.LNS.set_isa + 1, - }, - else => .{ - .minimum_instruction_length = 1, - .maximum_operations_per_instruction = 1, - .default_is_stmt = true, - .line_base = 1, - .line_range = 1, - .opcode_base = DW.LNS.set_isa + 1, - }, - }, - }; -} + fn addEntry(unit: *Unit, gpa: std.mem.Allocator) std.mem.Allocator.Error!Entry.Index { + const entry: Entry.Index = @enumFromInt(unit.entries.items.len); + const entry_ptr = try unit.entries.addOne(gpa); + entry_ptr.* = .{ + .prev = .none, + .next = .none, + .off = 0, + .len = 0, + .cross_entry_relocs = .{}, + .cross_unit_relocs = .{}, + .cross_section_relocs = .{}, + .external_relocs = .{}, + }; + return entry; + } -pub fn deinit(self: *Dwarf) void { - const gpa = self.allocator; + pub fn getEntry(unit: *Unit, entry: Entry.Index) *Entry { + return &unit.entries.items[@intFromEnum(entry)]; + } - self.src_fn_free_list.deinit(gpa); - self.src_fns.deinit(gpa); - self.src_fn_navs.deinit(gpa); + fn resize(unit_ptr: *Unit, sec: *Section, dwarf: *Dwarf, extra_header_len: u32, len: u32) UpdateError!void { + const end = if (unit_ptr.next.unwrap()) |next_unit| + sec.getUnit(next_unit).off + else + sec.len; + if (extra_header_len > 0 or unit_ptr.off + len > end) { + unit_ptr.len = @min(unit_ptr.len, len); + var new_off = unit_ptr.off; + if (unit_ptr.next.unwrap()) |next_unit| { + const next_unit_ptr = sec.getUnit(next_unit); + if (unit_ptr.prev.unwrap()) |prev_unit| + sec.getUnit(prev_unit).next = unit_ptr.next + else + sec.first = unit_ptr.next; + const unit = next_unit_ptr.prev; + next_unit_ptr.prev = unit_ptr.prev; + const last_unit_ptr = sec.getUnit(sec.last.unwrap().?); + last_unit_ptr.next = unit; + unit_ptr.prev = sec.last; + unit_ptr.next = .none; + new_off = last_unit_ptr.off + sec.padToIdeal(last_unit_ptr.len); + sec.last = unit; + sec.dirty = true; + } else if (extra_header_len > 0) { + // `copyRangeAll` in `move` does not support overlapping ranges + // so make sure new location is disjoint from current location. + new_off += unit_ptr.len -| extra_header_len; + } + try sec.resize(dwarf, new_off + len); + try unit_ptr.move(sec, dwarf, new_off + extra_header_len); + unit_ptr.off -= extra_header_len; + unit_ptr.header_len += extra_header_len; + sec.trim(dwarf); + } + unit_ptr.len = len; + } - self.di_atom_free_list.deinit(gpa); - self.di_atoms.deinit(gpa); - self.di_atom_navs.deinit(gpa); + fn trim(unit: *Unit) void { + const len = unit.getEntry(unit.first.unwrap() orelse return).off; + if (len == 0) return; + for (unit.entries.items) |*entry| entry.off -= len; + unit.off += len; + unit.len -= len; + } - self.strtab.deinit(gpa); - self.di_files.deinit(gpa); - self.global_abbrev_relocs.deinit(gpa); -} + fn move(unit: *Unit, sec: *Section, dwarf: *Dwarf, new_off: u32) UpdateError!void { + if (unit.off == new_off) return; + if (try dwarf.getFile().?.copyRangeAll( + sec.off + unit.off, + dwarf.getFile().?, + sec.off + new_off, + unit.len, + ) != unit.len) return error.InputOutput; + unit.off = new_off; + } -/// Initializes Nav's state and its matching output buffers. -/// Call this before `commitNavState`. -pub fn initNavState(self: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !NavState { - const tracy = trace(@src()); - defer tracy.end(); + fn resizeHeader(unit: *Unit, sec: *Section, dwarf: *Dwarf, len: u32) UpdateError!void { + unit.trim(); + if (unit.header_len == len) return; + const available_len = if (unit.prev.unwrap()) |prev_unit| prev_excess: { + const prev_unit_ptr = sec.getUnit(prev_unit); + break :prev_excess unit.off - prev_unit_ptr.off - prev_unit_ptr.len; + } else 0; + if (available_len + unit.header_len < len) + try unit.resize(sec, dwarf, len - unit.header_len, unit.len - unit.header_len + len); + if (unit.header_len > len) { + const excess_header_len = unit.header_len - len; + unit.off += excess_header_len; + unit.header_len -= excess_header_len; + unit.len -= excess_header_len; + } else if (unit.header_len < len) { + const needed_header_len = len - unit.header_len; + unit.off -= needed_header_len; + unit.header_len += needed_header_len; + unit.len += needed_header_len; + } + assert(unit.header_len == len); + sec.trim(dwarf); + } - const nav = pt.zcu.intern_pool.getNav(nav_index); - log.debug("initNavState {}", .{nav.fqn.fmt(&pt.zcu.intern_pool)}); + fn replaceHeader(unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void { + assert(contents.len == unit.header_len); + try dwarf.getFile().?.pwriteAll(contents, sec.off + unit.off); + } - const gpa = self.allocator; - var nav_state: NavState = .{ - .dwarf = self, - .pt = pt, - .di_atom_navs = &self.di_atom_navs, - .dbg_line_func = undefined, - .dbg_line = std.ArrayList(u8).init(gpa), - .dbg_info = std.ArrayList(u8).init(gpa), - .abbrev_type_arena = std.heap.ArenaAllocator.init(gpa), - .abbrev_table = .{}, - .abbrev_resolver = .{}, - .abbrev_relocs = .{}, - .exprloc_relocs = .{}, - }; - errdefer nav_state.deinit(); - const dbg_line_buffer = &nav_state.dbg_line; - const dbg_info_buffer = &nav_state.dbg_info; + fn writeTrailer(unit: *Unit, sec: *Section, dwarf: *Dwarf) UpdateError!void { + const start = unit.off + unit.header_len + if (unit.last.unwrap()) |last_entry| end: { + const last_entry_ptr = unit.getEntry(last_entry); + break :end last_entry_ptr.off + last_entry_ptr.len; + } else 0; + const end = if (unit.next.unwrap()) |next_unit| sec.getUnit(next_unit).off else sec.len; + const len: usize = @intCast(end - start); + assert(len >= unit.trailer_len); + if (sec == &dwarf.debug_line.section) { + var buf: [1 + uleb128Bytes(std.math.maxInt(u32)) + 1]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buf); + const writer = fbs.writer(); + writer.writeByte(DW.LNS.extended_op) catch unreachable; + const extended_op_bytes = fbs.pos; + var op_len_bytes: u5 = 1; + while (true) switch (std.math.order(len - extended_op_bytes - op_len_bytes, @as(u32, 1) << 7 * op_len_bytes)) { + .lt => break uleb128(writer, len - extended_op_bytes - op_len_bytes) catch unreachable, + .eq => { + // no length will ever work, so undercount and futz with the leb encoding to make up the missing byte + op_len_bytes += 1; + std.leb.writeUnsignedExtended(buf[fbs.pos..][0..op_len_bytes], len - extended_op_bytes - op_len_bytes); + fbs.pos += op_len_bytes; + break; + }, + .gt => op_len_bytes += 1, + }; + assert(fbs.pos == extended_op_bytes + op_len_bytes); + writer.writeByte(DW.LNE.padding) catch unreachable; + assert(fbs.pos >= unit.trailer_len and fbs.pos <= len); + return dwarf.getFile().?.pwriteAll(fbs.getWritten(), sec.off + start); + } + var trailer = try std.ArrayList(u8).initCapacity(dwarf.gpa, len); + defer trailer.deinit(); + const fill_byte: u8 = if (sec == &dwarf.debug_abbrev.section) fill: { + assert(uleb128Bytes(@intFromEnum(AbbrevCode.null)) == 1); + trailer.appendAssumeCapacity(@intFromEnum(AbbrevCode.null)); + break :fill @intFromEnum(AbbrevCode.null); + } else if (sec == &dwarf.debug_aranges.section) fill: { + trailer.appendNTimesAssumeCapacity(0, @intFromEnum(dwarf.address_size) * 2); + break :fill 0; + } else if (sec == &dwarf.debug_info.section) fill: { + assert(uleb128Bytes(@intFromEnum(AbbrevCode.null)) == 1); + trailer.appendNTimesAssumeCapacity(@intFromEnum(AbbrevCode.null), 2); + break :fill @intFromEnum(AbbrevCode.null); + } else if (sec == &dwarf.debug_rnglists.section) fill: { + trailer.appendAssumeCapacity(DW.RLE.end_of_list); + break :fill DW.RLE.end_of_list; + } else unreachable; + assert(trailer.items.len == unit.trailer_len); + trailer.appendNTimesAssumeCapacity(fill_byte, len - trailer.items.len); + assert(trailer.items.len == len); + try dwarf.getFile().?.pwriteAll(trailer.items, sec.off + start); + } - const di_atom_index = try self.getOrCreateAtomForNav(.di_atom, nav_index); + fn resolveRelocs(unit: *Unit, sec: *Section, dwarf: *Dwarf) RelocError!void { + const unit_off = sec.off + unit.off; + for (unit.cross_unit_relocs.items) |reloc| { + const target_unit = sec.getUnit(reloc.target_unit); + try dwarf.resolveReloc( + unit_off + reloc.source_off, + target_unit.off + (if (reloc.target_entry.unwrap()) |target_entry| + target_unit.header_len + target_unit.getEntry(target_entry).assertNonEmpty(unit, sec, dwarf).off + else + 0) + reloc.target_off, + dwarf.sectionOffsetBytes(), + ); + } + for (unit.cross_section_relocs.items) |reloc| { + const target_sec = switch (reloc.target_sec) { + inline else => |target_sec| &@field(dwarf, @tagName(target_sec)).section, + }; + const target_unit = target_sec.getUnit(reloc.target_unit); + try dwarf.resolveReloc( + unit_off + reloc.source_off, + target_unit.off + (if (reloc.target_entry.unwrap()) |target_entry| + target_unit.header_len + target_unit.getEntry(target_entry).assertNonEmpty(unit, sec, dwarf).off + else + 0) + reloc.target_off, + dwarf.sectionOffsetBytes(), + ); + } + for (unit.entries.items) |*entry| try entry.resolveRelocs(unit, sec, dwarf); + } +}; - const nav_val = Value.fromInterned(nav.status.resolved.val); +/// An indivisible entry within a `Unit` containing section-specific data. +const Entry = struct { + prev: Index.Optional, + next: Index.Optional, + /// offset from end of containing unit header + off: u32, + /// data length in bytes + len: u32, + cross_entry_relocs: std.ArrayListUnmanaged(CrossEntryReloc), + cross_unit_relocs: std.ArrayListUnmanaged(CrossUnitReloc), + cross_section_relocs: std.ArrayListUnmanaged(CrossSectionReloc), + external_relocs: std.ArrayListUnmanaged(ExternalReloc), + + fn clear(entry: *Entry) void { + entry.cross_entry_relocs.clearRetainingCapacity(); + entry.cross_unit_relocs.clearRetainingCapacity(); + entry.cross_section_relocs.clearRetainingCapacity(); + entry.external_relocs.clearRetainingCapacity(); + } - switch (nav_val.typeOf(pt.zcu).zigTypeTag(pt.zcu)) { - .Fn => { - _ = try self.getOrCreateAtomForNav(.src_fn, nav_index); + fn deinit(entry: *Entry, gpa: std.mem.Allocator) void { + entry.cross_entry_relocs.deinit(gpa); + entry.cross_unit_relocs.deinit(gpa); + entry.cross_section_relocs.deinit(gpa); + entry.external_relocs.deinit(gpa); + entry.* = undefined; + } - // For functions we need to add a prologue to the debug line program. - const ptr_width_bytes = self.ptrWidthBytes(); - try dbg_line_buffer.ensureTotalCapacity((3 + ptr_width_bytes) + (1 + 4) + (1 + 4) + (1 + 5) + 1); + const Index = enum(u32) { + _, - nav_state.dbg_line_func = nav_val.toIntern(); - const func = nav_val.getFunction(pt.zcu).?; - log.debug("src_line={d}, func.lbrace_line={d}, func.rbrace_line={d}", .{ - pt.zcu.navSrcLine(nav_index), - func.lbrace_line, - func.rbrace_line, - }); - const line: u28 = @intCast(pt.zcu.navSrcLine(nav_index) + func.lbrace_line); + const Optional = enum(u32) { + none = std.math.maxInt(u32), + _, - dbg_line_buffer.appendSliceAssumeCapacity(&.{ - DW.LNS.extended_op, - ptr_width_bytes + 1, - DW.LNE.set_address, - }); - // This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`. - assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len); - dbg_line_buffer.appendNTimesAssumeCapacity(0, ptr_width_bytes); - - dbg_line_buffer.appendAssumeCapacity(DW.LNS.advance_line); - // This is the "relocatable" relative line offset from the previous function's end curly - // to this function's begin curly. - assert(self.getRelocDbgLineOff() == dbg_line_buffer.items.len); - // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later. - leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line); - - dbg_line_buffer.appendAssumeCapacity(DW.LNS.set_file); - assert(self.getRelocDbgFileIndex() == dbg_line_buffer.items.len); - // Once we support more than one source file, this will have the ability to be more - // than one possible value. - const file_index = try self.addDIFile(pt.zcu, nav_index); - leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index); - - dbg_line_buffer.appendAssumeCapacity(DW.LNS.set_column); - leb128.writeUleb128(dbg_line_buffer.writer(), func.lbrace_column + 1) catch unreachable; - - // Emit a line for the begin curly with prologue_end=false. The codegen will - // do the work of setting prologue_end=true and epilogue_begin=true. - dbg_line_buffer.appendAssumeCapacity(DW.LNS.copy); - - // .debug_info subprogram - const nav_name_slice = nav.name.toSlice(&pt.zcu.intern_pool); - const nav_linkage_name_slice = nav.fqn.toSlice(&pt.zcu.intern_pool); - try dbg_info_buffer.ensureUnusedCapacity(1 + ptr_width_bytes + 4 + 4 + - (nav_name_slice.len + 1) + (nav_linkage_name_slice.len + 1)); - - const fn_ret_type = nav_val.typeOf(pt.zcu).fnReturnType(pt.zcu); - const fn_ret_has_bits = fn_ret_type.hasRuntimeBits(pt); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum( - @as(AbbrevCode, if (fn_ret_has_bits) .subprogram else .subprogram_retvoid), - )); - // These get overwritten after generating the machine code. These values are - // "relocations" and have to be in this fixed place so that functions can be - // moved in virtual address space. - assert(dbg_info_low_pc_reloc_index == dbg_info_buffer.items.len); - dbg_info_buffer.appendNTimesAssumeCapacity(0, ptr_width_bytes); // DW.AT.low_pc, DW.FORM.addr - assert(self.getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len); - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); // DW.AT.high_pc, DW.FORM.data4 - if (fn_ret_has_bits) { - try nav_state.addTypeRelocGlobal(di_atom_index, fn_ret_type, @intCast(dbg_info_buffer.items.len)); - dbg_info_buffer.appendNTimesAssumeCapacity(0, 4); // DW.AT.type, DW.FORM.ref4 + pub fn unwrap(eio: Optional) ?Index { + return if (eio != .none) @enumFromInt(@intFromEnum(eio)) else null; } - dbg_info_buffer.appendSliceAssumeCapacity( - nav_name_slice[0 .. nav_name_slice.len + 1], - ); // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity( - nav_linkage_name_slice[0 .. nav_linkage_name_slice.len + 1], - ); // DW.AT.linkage_name, DW.FORM.string - }, - else => { - // TODO implement .debug_info for global variables - }, - } + }; - return nav_state; -} - -pub fn commitNavState( - self: *Dwarf, - pt: Zcu.PerThread, - nav_index: InternPool.Nav.Index, - sym_addr: u64, - sym_size: u64, - nav_state: *NavState, -) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const gpa = self.allocator; - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - const nav = ip.getNav(nav_index); - const target = zcu.navFileScope(nav_index).mod.resolved_target.result; - const target_endian = target.cpu.arch.endian(); - - var dbg_line_buffer = &nav_state.dbg_line; - var dbg_info_buffer = &nav_state.dbg_info; + fn toOptional(ei: Index) Optional { + return @enumFromInt(@intFromEnum(ei)); + } + }; - const nav_val = Value.fromInterned(nav.status.resolved.val); - switch (nav_val.typeOf(zcu).zigTypeTag(zcu)) { - .Fn => { - try nav_state.setInlineFunc(nav_val.toIntern()); + fn pad(entry: *Entry, unit: *Unit, sec: *Section, dwarf: *Dwarf) UpdateError!void { + assert(entry.len > 0); + const start = entry.off + entry.len; + const len = unit.getEntry(entry.next.unwrap() orelse return).off - start; + var buf: [ + @max( + uleb128Bytes(@intFromEnum(AbbrevCode.pad_1)), + uleb128Bytes(@intFromEnum(AbbrevCode.pad_n)) + uleb128Bytes(std.math.maxInt(u32)), + 1 + uleb128Bytes(std.math.maxInt(u32)) + 1, + ) + ]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buf); + const writer = fbs.writer(); + if (sec == &dwarf.debug_info.section) switch (len) { + 0 => {}, + 1 => uleb128(writer, try dwarf.refAbbrevCode(.pad_1)) catch unreachable, + else => { + uleb128(writer, try dwarf.refAbbrevCode(.pad_n)) catch unreachable; + const abbrev_code_bytes = fbs.pos; + var block_len_bytes: u5 = 1; + while (true) switch (std.math.order(len - abbrev_code_bytes - block_len_bytes, @as(u32, 1) << 7 * block_len_bytes)) { + .lt => break uleb128(writer, len - abbrev_code_bytes - block_len_bytes) catch unreachable, + .eq => { + // no length will ever work, so undercount and futz with the leb encoding to make up the missing byte + block_len_bytes += 1; + std.leb.writeUnsignedExtended(buf[fbs.pos..][0..block_len_bytes], len - abbrev_code_bytes - block_len_bytes); + fbs.pos += block_len_bytes; + break; + }, + .gt => block_len_bytes += 1, + }; + assert(fbs.pos == abbrev_code_bytes + block_len_bytes); + }, + } else if (sec == &dwarf.debug_line.section) switch (len) { + 0 => {}, + 1 => writer.writeByte(DW.LNS.const_add_pc) catch unreachable, + else => { + writer.writeByte(DW.LNS.extended_op) catch unreachable; + const extended_op_bytes = fbs.pos; + var op_len_bytes: u5 = 1; + while (true) switch (std.math.order(len - extended_op_bytes - op_len_bytes, @as(u32, 1) << 7 * op_len_bytes)) { + .lt => break uleb128(writer, len - extended_op_bytes - op_len_bytes) catch unreachable, + .eq => { + // no length will ever work, so undercount and futz with the leb encoding to make up the missing byte + op_len_bytes += 1; + std.leb.writeUnsignedExtended(buf[fbs.pos..][0..op_len_bytes], len - extended_op_bytes - op_len_bytes); + fbs.pos += op_len_bytes; + break; + }, + .gt => op_len_bytes += 1, + }; + assert(fbs.pos == extended_op_bytes + op_len_bytes); + if (len > 2) writer.writeByte(DW.LNE.padding) catch unreachable; + }, + } else assert(!sec.pad_to_ideal and len == 0); + assert(fbs.pos <= len); + try dwarf.getFile().?.pwriteAll(fbs.getWritten(), sec.off + unit.off + unit.header_len + start); + } - // Since the Nav is a function, we need to update the .debug_line program. - // Perform the relocations based on vaddr. - switch (self.ptr_width) { - .p32 => { - { - const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..4]; - mem.writeInt(u32, ptr, @intCast(sym_addr), target_endian); - } - { - const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..4]; - mem.writeInt(u32, ptr, @intCast(sym_addr), target_endian); - } - }, - .p64 => { - { - const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8]; - mem.writeInt(u64, ptr, sym_addr, target_endian); - } - { - const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..8]; - mem.writeInt(u64, ptr, sym_addr, target_endian); - } - }, + fn replace(entry_ptr: *Entry, unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void { + const end = if (entry_ptr.next.unwrap()) |next_entry| + unit.getEntry(next_entry).off + else + unit.len -| (unit.header_len + unit.trailer_len); + if (entry_ptr.off + contents.len > end) { + if (entry_ptr.next.unwrap()) |next_entry| { + if (entry_ptr.prev.unwrap()) |prev_entry| { + const prev_entry_ptr = unit.getEntry(prev_entry); + prev_entry_ptr.next = entry_ptr.next; + try prev_entry_ptr.pad(unit, sec, dwarf); + } else unit.first = entry_ptr.next; + const next_entry_ptr = unit.getEntry(next_entry); + const entry = next_entry_ptr.prev; + next_entry_ptr.prev = entry_ptr.prev; + const last_entry_ptr = unit.getEntry(unit.last.unwrap().?); + last_entry_ptr.next = entry; + entry_ptr.prev = unit.last; + entry_ptr.next = .none; + entry_ptr.off = last_entry_ptr.off + sec.padToIdeal(last_entry_ptr.len); + unit.last = entry; } - { - log.debug("relocating subprogram high PC value: {x} => {x}", .{ - self.getRelocDbgInfoSubprogramHighPC(), - sym_size, + try unit.resize(sec, dwarf, 0, @intCast(unit.header_len + entry_ptr.off + sec.padToIdeal(contents.len) + unit.trailer_len)); + } + entry_ptr.len = @intCast(contents.len); + if (entry_ptr.prev.unwrap()) |prev_entry| try unit.getEntry(prev_entry).pad(unit, sec, dwarf); + try dwarf.getFile().?.pwriteAll(contents, sec.off + unit.off + unit.header_len + entry_ptr.off); + try entry_ptr.pad(unit, sec, dwarf); + if (false) { + const buf = try dwarf.gpa.alloc(u8, sec.len); + defer dwarf.gpa.free(buf); + _ = try dwarf.getFile().?.preadAll(buf, sec.off); + log.info("Section{{ .first = {}, .last = {}, .off = 0x{x}, .len = 0x{x} }}", .{ + @intFromEnum(sec.first), + @intFromEnum(sec.last), + sec.off, + sec.len, + }); + for (sec.units.items) |*unit_ptr| { + log.info(" Unit{{ .prev = {}, .next = {}, .first = {}, .last = {}, .off = 0x{x}, .header_len = 0x{x}, .trailer_len = 0x{x}, .len = 0x{x} }}", .{ + @intFromEnum(unit_ptr.prev), + @intFromEnum(unit_ptr.next), + @intFromEnum(unit_ptr.first), + @intFromEnum(unit_ptr.last), + unit_ptr.off, + unit_ptr.header_len, + unit_ptr.trailer_len, + unit_ptr.len, }); - const ptr = dbg_info_buffer.items[self.getRelocDbgInfoSubprogramHighPC()..][0..4]; - mem.writeInt(u32, ptr, @intCast(sym_size), target_endian); - } - - try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS.extended_op, 1, DW.LNE.end_sequence }); - - // Now we have the full contents and may allocate a region to store it. - - // This logic is nearly identical to the logic below in `updateNavDebugInfo` for - // `TextBlock` and the .debug_info. If you are editing this logic, you - // probably need to edit that logic too. - const src_fn_index = self.src_fn_navs.get(nav_index).?; - const src_fn = self.getAtomPtr(.src_fn, src_fn_index); - src_fn.len = @intCast(dbg_line_buffer.items.len); - - if (self.src_fn_last_index) |last_index| blk: { - if (src_fn_index == last_index) break :blk; - if (src_fn.next_index) |next_index| { - const next = self.getAtomPtr(.src_fn, next_index); - // Update existing function - non-last item. - if (src_fn.off + src_fn.len + min_nop_size > next.off) { - // It grew too big, so we move it to a new location. - if (src_fn.prev_index) |prev_index| { - self.src_fn_free_list.put(gpa, prev_index, {}) catch {}; - self.getAtomPtr(.src_fn, prev_index).next_index = src_fn.next_index; - } - next.prev_index = src_fn.prev_index; - src_fn.next_index = null; - // Populate where it used to be with NOPs. - if (self.bin_file.cast(.elf)) |elf_file| { - const debug_line_sect = &elf_file.shdrs.items[elf_file.debug_line_section_index.?]; - const file_pos = debug_line_sect.sh_offset + src_fn.off; - try pwriteDbgLineNops(elf_file.base.file.?, file_pos, 0, &[0]u8{}, src_fn.len); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - const debug_line_sect = &macho_file.sections.items(.header)[macho_file.debug_line_sect_index.?]; - const file_pos = debug_line_sect.offset + src_fn.off; - try pwriteDbgLineNops(macho_file.base.file.?, file_pos, 0, &[0]u8{}, src_fn.len); - } else { - const d_sym = macho_file.getDebugSymbols().?; - const debug_line_sect = d_sym.getSectionPtr(d_sym.debug_line_section_index.?); - const file_pos = debug_line_sect.offset + src_fn.off; - try pwriteDbgLineNops(d_sym.file, file_pos, 0, &[0]u8{}, src_fn.len); - } - } else if (self.bin_file.cast(.wasm)) |wasm_file| { - _ = wasm_file; - // const debug_line = wasm_file.getAtomPtr(wasm_file.debug_line_atom.?).code; - // writeDbgLineNopsBuffered(debug_line.items, src_fn.off, 0, &.{}, src_fn.len); - } else unreachable; - // TODO Look at the free list before appending at the end. - src_fn.prev_index = last_index; - const last = self.getAtomPtr(.src_fn, last_index); - last.next_index = src_fn_index; - self.src_fn_last_index = src_fn_index; - - src_fn.off = last.off + padToIdeal(last.len); - } - } else if (src_fn.prev_index == null) { - // Append new function. - // TODO Look at the free list before appending at the end. - src_fn.prev_index = last_index; - const last = self.getAtomPtr(.src_fn, last_index); - last.next_index = src_fn_index; - self.src_fn_last_index = src_fn_index; - - src_fn.off = last.off + padToIdeal(last.len); + for (unit_ptr.entries.items) |*entry| { + log.info(" Entry{{ .prev = {}, .next = {}, .off = 0x{x}, .len = 0x{x} }}", .{ + @intFromEnum(entry.prev), + @intFromEnum(entry.next), + entry.off, + entry.len, + }); } - } else { - // This is the first function of the Line Number Program. - self.src_fn_first_index = src_fn_index; - self.src_fn_last_index = src_fn_index; - - src_fn.off = padToIdeal(self.dbgLineNeededHeaderBytes(&[0][]u8{}, &[0][]u8{})); } - - const last_src_fn_index = self.src_fn_last_index.?; - const last_src_fn = self.getAtom(.src_fn, last_src_fn_index); - const needed_size = last_src_fn.off + last_src_fn.len; - const prev_padding_size: u32 = if (src_fn.prev_index) |prev_index| blk: { - const prev = self.getAtom(.src_fn, prev_index); - break :blk src_fn.off - (prev.off + prev.len); - } else 0; - const next_padding_size: u32 = if (src_fn.next_index) |next_index| blk: { - const next = self.getAtom(.src_fn, next_index); - break :blk next.off - (src_fn.off + src_fn.len); - } else 0; - - // We only have support for one compilation unit so far, so the offsets are directly - // from the .debug_line section. - if (self.bin_file.cast(.elf)) |elf_file| { - const shdr_index = elf_file.debug_line_section_index.?; - try elf_file.growNonAllocSection(shdr_index, needed_size, 1, true); - const debug_line_sect = elf_file.shdrs.items[shdr_index]; - const file_pos = debug_line_sect.sh_offset + src_fn.off; - try pwriteDbgLineNops( - elf_file.base.file.?, - file_pos, - prev_padding_size, - dbg_line_buffer.items, - next_padding_size, - ); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - const sect_index = macho_file.debug_line_sect_index.?; - try macho_file.growSection(sect_index, needed_size); - const sect = macho_file.sections.items(.header)[sect_index]; - const file_pos = sect.offset + src_fn.off; - try pwriteDbgLineNops( - macho_file.base.file.?, - file_pos, - prev_padding_size, - dbg_line_buffer.items, - next_padding_size, - ); - } else { - const d_sym = macho_file.getDebugSymbols().?; - const sect_index = d_sym.debug_line_section_index.?; - try d_sym.growSection(sect_index, needed_size, true, macho_file); - const sect = d_sym.getSection(sect_index); - const file_pos = sect.offset + src_fn.off; - try pwriteDbgLineNops( - d_sym.file, - file_pos, - prev_padding_size, - dbg_line_buffer.items, - next_padding_size, - ); - } - } else if (self.bin_file.cast(.wasm)) |wasm_file| { - _ = wasm_file; - // const atom = wasm_file.getAtomPtr(wasm_file.debug_line_atom.?); - // const debug_line = &atom.code; - // const segment_size = debug_line.items.len; - // if (needed_size != segment_size) { - // log.debug(" needed size does not equal allocated size: {d}", .{needed_size}); - // if (needed_size > segment_size) { - // log.debug(" allocating {d} bytes for 'debug line' information", .{needed_size - segment_size}); - // try debug_line.resize(self.allocator, needed_size); - // @memset(debug_line.items[segment_size..], 0); - // } - // debug_line.items.len = needed_size; - // } - // writeDbgLineNopsBuffered( - // debug_line.items, - // src_fn.off, - // prev_padding_size, - // dbg_line_buffer.items, - // next_padding_size, - // ); - } else unreachable; - - // .debug_info - End the TAG.subprogram children. - try dbg_info_buffer.append(0); - }, - else => {}, - } - - if (dbg_info_buffer.items.len == 0) - return; - - const di_atom_index = self.di_atom_navs.get(nav_index).?; - if (nav_state.abbrev_table.items.len > 0) { - // Now we emit the .debug_info types of the Nav. These will count towards the size of - // the buffer, so we have to do it before computing the offset, and we can't perform the actual - // relocations yet. - var sym_index: usize = 0; - while (sym_index < nav_state.abbrev_table.items.len) : (sym_index += 1) { - const symbol = &nav_state.abbrev_table.items[sym_index]; - const ty = symbol.type; - if (ip.isErrorSetType(ty.toIntern())) continue; - - symbol.offset = @intCast(dbg_info_buffer.items.len); - try nav_state.addDbgInfoType(pt, di_atom_index, ty); + std.debug.dumpHex(buf); } } - try self.updateNavDebugInfoAllocation(di_atom_index, @intCast(dbg_info_buffer.items.len)); - - while (nav_state.abbrev_relocs.popOrNull()) |reloc| { - if (reloc.target) |reloc_target| { - const symbol = nav_state.abbrev_table.items[reloc_target]; - const ty = symbol.type; - if (ip.isErrorSetType(ty.toIntern())) { - log.debug("resolving %{d} deferred until flush", .{reloc_target}); - try self.global_abbrev_relocs.append(gpa, .{ - .target = null, - .offset = reloc.offset, - .atom_index = reloc.atom_index, - .addend = reloc.addend, - }); - } else { - const atom = self.getAtom(.di_atom, symbol.atom_index); - const value = atom.off + symbol.offset + reloc.addend; - log.debug("{x}: [() => {x}] (%{d}, '{}')", .{ - reloc.offset, - value, - reloc_target, - ty.fmt(pt), - }); - mem.writeInt( - u32, - dbg_info_buffer.items[reloc.offset..][0..@sizeOf(u32)], - value, - target_endian, - ); + pub fn assertNonEmpty(entry: *Entry, unit: *Unit, sec: *Section, dwarf: *Dwarf) *Entry { + if (entry.len > 0) return entry; + if (std.debug.runtime_safety) { + log.err("missing {} from {s}", .{ + @as(Entry.Index, @enumFromInt(entry - unit.entries.items.ptr)), + std.mem.sliceTo(if (dwarf.bin_file.cast(.elf)) |elf_file| + elf_file.shstrtab.items[elf_file.shdrs.items[sec.index].sh_name..] + else if (dwarf.bin_file.cast(.macho)) |macho_file| + if (macho_file.d_sym) |*d_sym| + &d_sym.sections.items[sec.index].segname + else + &macho_file.sections.items(.header)[sec.index].segname + else + "?", 0), + }); + const zcu = dwarf.bin_file.comp.module.?; + const ip = &zcu.intern_pool; + for (dwarf.types.keys(), dwarf.types.values()) |ty, other_entry| { + const ty_unit: Unit.Index = if (Type.fromInterned(ty).typeDeclInst(zcu)) |inst_index| + dwarf.getUnit(zcu.fileByIndex(inst_index.resolveFile(ip)).mod) catch unreachable + else + .main; + if (sec.getUnit(ty_unit) == unit and unit.getEntry(other_entry) == entry) + log.err("missing Type({}({d}))", .{ + Type.fromInterned(ty).fmt(.{ .tid = .main, .zcu = zcu }), + @intFromEnum(ty), + }); + } + for (dwarf.navs.keys(), dwarf.navs.values()) |nav, other_entry| { + const nav_unit = dwarf.getUnit(zcu.fileByIndex(ip.getNav(nav).srcInst(ip).resolveFile(ip)).mod) catch unreachable; + if (sec.getUnit(nav_unit) == unit and unit.getEntry(other_entry) == entry) + log.err("missing Nav({}({d}))", .{ ip.getNav(nav).fqn.fmt(ip), @intFromEnum(nav) }); } - } else { - const atom = self.getAtom(.di_atom, reloc.atom_index); - mem.writeInt( - u32, - dbg_info_buffer.items[reloc.offset..][0..@sizeOf(u32)], - atom.off + reloc.offset + reloc.addend, - target_endian, - ); } + @panic("missing dwarf relocation target"); } - while (nav_state.exprloc_relocs.popOrNull()) |reloc| { - if (self.bin_file.cast(.elf)) |elf_file| { - _ = elf_file; // TODO - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - // TODO - } else { - const d_sym = macho_file.getDebugSymbols().?; - try d_sym.relocs.append(d_sym.allocator, .{ - .type = switch (reloc.type) { - .direct_load => .direct_load, - .got_load => .got_load, - }, - .target = reloc.target, - .offset = reloc.offset + self.getAtom(.di_atom, di_atom_index).off, - .addend = 0, - }); + fn resolveRelocs(entry: *Entry, unit: *Unit, sec: *Section, dwarf: *Dwarf) RelocError!void { + const entry_off = sec.off + unit.off + unit.header_len + entry.off; + for (entry.cross_entry_relocs.items) |reloc| { + try dwarf.resolveReloc( + entry_off + reloc.source_off, + unit.off + unit.header_len + unit.getEntry(reloc.target_entry).assertNonEmpty(unit, sec, dwarf).off + reloc.target_off, + dwarf.sectionOffsetBytes(), + ); + } + for (entry.cross_unit_relocs.items) |reloc| { + const target_unit = sec.getUnit(reloc.target_unit); + try dwarf.resolveReloc( + entry_off + reloc.source_off, + target_unit.off + (if (reloc.target_entry.unwrap()) |target_entry| + target_unit.header_len + target_unit.getEntry(target_entry).assertNonEmpty(unit, sec, dwarf).off + else + 0) + reloc.target_off, + dwarf.sectionOffsetBytes(), + ); + } + for (entry.cross_section_relocs.items) |reloc| { + const target_sec = switch (reloc.target_sec) { + inline else => |target_sec| &@field(dwarf, @tagName(target_sec)).section, + }; + const target_unit = target_sec.getUnit(reloc.target_unit); + try dwarf.resolveReloc( + entry_off + reloc.source_off, + target_unit.off + (if (reloc.target_entry.unwrap()) |target_entry| + target_unit.header_len + target_unit.getEntry(target_entry).assertNonEmpty(unit, sec, dwarf).off + else + 0) + reloc.target_off, + dwarf.sectionOffsetBytes(), + ); + } + if (dwarf.bin_file.cast(.elf)) |elf_file| { + const zo = elf_file.zigObjectPtr().?; + for (entry.external_relocs.items) |reloc| { + const symbol = zo.symbol(reloc.target_sym); + try dwarf.resolveReloc( + entry_off + reloc.source_off, + @bitCast(symbol.address(.{}, elf_file) + @as(i64, @intCast(reloc.target_off)) - + if (symbol.flags.is_tls) elf_file.dtpAddress() else 0), + @intFromEnum(dwarf.address_size), + ); } - } else unreachable; + } else if (dwarf.bin_file.cast(.macho)) |macho_file| { + const zo = macho_file.getZigObject().?; + for (entry.external_relocs.items) |reloc| { + const ref = zo.getSymbolRef(reloc.target_sym, macho_file); + try dwarf.resolveReloc( + entry_off + reloc.source_off, + ref.getSymbol(macho_file).?.getAddress(.{}, macho_file), + @intFromEnum(dwarf.address_size), + ); + } + } } +}; - try self.writeNavDebugInfo(di_atom_index, dbg_info_buffer.items); -} +const CrossEntryReloc = struct { + source_off: u32 = 0, + target_entry: Entry.Index, + target_off: u32 = 0, +}; +const CrossUnitReloc = struct { + source_off: u32 = 0, + target_unit: Unit.Index, + target_entry: Entry.Index.Optional = .none, + target_off: u32 = 0, +}; +const CrossSectionReloc = struct { + source_off: u32 = 0, + target_sec: Section.Index, + target_unit: Unit.Index, + target_entry: Entry.Index.Optional = .none, + target_off: u32 = 0, +}; +const ExternalReloc = struct { + source_off: u32 = 0, + target_sym: u32, + target_off: u64 = 0, +}; -fn updateNavDebugInfoAllocation(self: *Dwarf, atom_index: Atom.Index, len: u32) !void { - const tracy = trace(@src()); - defer tracy.end(); - - // This logic is nearly identical to the logic above in `updateNav` for - // `SrcFn` and the line number programs. If you are editing this logic, you - // probably need to edit that logic too. - const gpa = self.allocator; - - const atom = self.getAtomPtr(.di_atom, atom_index); - atom.len = len; - if (self.di_atom_last_index) |last_index| blk: { - if (atom_index == last_index) break :blk; - if (atom.next_index) |next_index| { - const next = self.getAtomPtr(.di_atom, next_index); - // Update existing Nav - non-last item. - if (atom.off + atom.len + min_nop_size > next.off) { - // It grew too big, so we move it to a new location. - if (atom.prev_index) |prev_index| { - self.di_atom_free_list.put(gpa, prev_index, {}) catch {}; - self.getAtomPtr(.di_atom, prev_index).next_index = atom.next_index; - } - next.prev_index = atom.prev_index; - atom.next_index = null; - // Populate where it used to be with NOPs. - if (self.bin_file.cast(.elf)) |elf_file| { - const debug_info_sect = &elf_file.shdrs.items[elf_file.debug_info_section_index.?]; - const file_pos = debug_info_sect.sh_offset + atom.off; - try pwriteDbgInfoNops(elf_file.base.file.?, file_pos, 0, &[0]u8{}, atom.len, false); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - const debug_info_sect = macho_file.sections.items(.header)[macho_file.debug_info_sect_index.?]; - const file_pos = debug_info_sect.offset + atom.off; - try pwriteDbgInfoNops(macho_file.base.file.?, file_pos, 0, &[0]u8{}, atom.len, false); - } else { - const d_sym = macho_file.getDebugSymbols().?; - const debug_info_sect = d_sym.getSectionPtr(d_sym.debug_info_section_index.?); - const file_pos = debug_info_sect.offset + atom.off; - try pwriteDbgInfoNops(d_sym.file, file_pos, 0, &[0]u8{}, atom.len, false); - } - } else if (self.bin_file.cast(.wasm)) |wasm_file| { - _ = wasm_file; - // const debug_info_index = wasm_file.debug_info_atom.?; - // const debug_info = &wasm_file.getAtomPtr(debug_info_index).code; - // try writeDbgInfoNopsToArrayList(gpa, debug_info, atom.off, 0, &.{0}, atom.len, false); - } else unreachable; - // TODO Look at the free list before appending at the end. - atom.prev_index = last_index; - const last = self.getAtomPtr(.di_atom, last_index); - last.next_index = atom_index; - self.di_atom_last_index = atom_index; - - atom.off = last.off + padToIdeal(last.len); - } - } else if (atom.prev_index == null) { - // Append new Nav. - // TODO Look at the free list before appending at the end. - atom.prev_index = last_index; - const last = self.getAtomPtr(.di_atom, last_index); - last.next_index = atom_index; - self.di_atom_last_index = atom_index; - - atom.off = last.off + padToIdeal(last.len); - } - } else { - // This is the first Nav of the .debug_info - self.di_atom_first_index = atom_index; - self.di_atom_last_index = atom_index; +pub const Loc = union(enum) { + empty, + addr: union(enum) { sym: u32 }, + constu: u64, + consts: i64, + plus: Bin, + reg: u32, + breg: u32, + push_object_address, + form_tls_address: *const Loc, + implicit_value: []const u8, + stack_value: *const Loc, + wasm_ext: union(enum) { + local: u32, + global: u32, + operand_stack: u32, + }, - atom.off = @intCast(padToIdeal(self.dbgInfoHeaderBytes())); + pub const Bin = struct { *const Loc, *const Loc }; + + fn getConst(loc: Loc, comptime Int: type) ?Int { + return switch (loc) { + .constu => |constu| std.math.cast(Int, constu), + .consts => |consts| std.math.cast(Int, consts), + else => null, + }; } -} -fn writeNavDebugInfo(self: *Dwarf, atom_index: Atom.Index, dbg_info_buf: []const u8) !void { - const tracy = trace(@src()); - defer tracy.end(); - - // This logic is nearly identical to the logic above in `updateNav` for - // `SrcFn` and the line number programs. If you are editing this logic, you - // probably need to edit that logic too. - - const atom = self.getAtom(.di_atom, atom_index); - const last_nav_index = self.di_atom_last_index.?; - const last_nav = self.getAtom(.di_atom, last_nav_index); - // +1 for a trailing zero to end the children of the nav tag. - const needed_size = last_nav.off + last_nav.len + 1; - const prev_padding_size: u32 = if (atom.prev_index) |prev_index| blk: { - const prev = self.getAtom(.di_atom, prev_index); - break :blk atom.off - (prev.off + prev.len); - } else 0; - const next_padding_size: u32 = if (atom.next_index) |next_index| blk: { - const next = self.getAtom(.di_atom, next_index); - break :blk next.off - (atom.off + atom.len); - } else 0; - - // To end the children of the nav tag. - const trailing_zero = atom.next_index == null; - - // We only have support for one compilation unit so far, so the offsets are directly - // from the .debug_info section. - if (self.bin_file.cast(.elf)) |elf_file| { - const shdr_index = elf_file.debug_info_section_index.?; - try elf_file.growNonAllocSection(shdr_index, needed_size, 1, true); - const debug_info_sect = &elf_file.shdrs.items[shdr_index]; - const file_pos = debug_info_sect.sh_offset + atom.off; - try pwriteDbgInfoNops( - elf_file.base.file.?, - file_pos, - prev_padding_size, - dbg_info_buf, - next_padding_size, - trailing_zero, - ); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - const sect_index = macho_file.debug_info_sect_index.?; - try macho_file.growSection(sect_index, needed_size); - const sect = macho_file.sections.items(.header)[sect_index]; - const file_pos = sect.offset + atom.off; - try pwriteDbgInfoNops( - macho_file.base.file.?, - file_pos, - prev_padding_size, - dbg_info_buf, - next_padding_size, - trailing_zero, - ); + fn getBaseReg(loc: Loc) ?u32 { + return switch (loc) { + .breg => |breg| breg, + else => null, + }; + } + + fn writeReg(reg: u32, op0: u8, opx: u8, writer: anytype) @TypeOf(writer).Error!void { + if (std.math.cast(u5, reg)) |small_reg| { + try writer.writeByte(op0 + small_reg); } else { - const d_sym = macho_file.getDebugSymbols().?; - const sect_index = d_sym.debug_info_section_index.?; - try d_sym.growSection(sect_index, needed_size, true, macho_file); - const sect = d_sym.getSection(sect_index); - const file_pos = sect.offset + atom.off; - try pwriteDbgInfoNops( - d_sym.file, - file_pos, - prev_padding_size, - dbg_info_buf, - next_padding_size, - trailing_zero, - ); + try writer.writeByte(opx); + try uleb128(writer, reg); } - } else if (self.bin_file.cast(.wasm)) |wasm_file| { - _ = wasm_file; - // const info_atom = wasm_file.debug_info_atom.?; - // const debug_info = &wasm_file.getAtomPtr(info_atom).code; - // const segment_size = debug_info.items.len; - // if (needed_size != segment_size) { - // log.debug(" needed size does not equal allocated size: {d}", .{needed_size}); - // if (needed_size > segment_size) { - // log.debug(" allocating {d} bytes for 'debug info' information", .{needed_size - segment_size}); - // try debug_info.resize(self.allocator, needed_size); - // @memset(debug_info.items[segment_size..], 0); - // } - // debug_info.items.len = needed_size; - // } - // log.debug(" writeDbgInfoNopsToArrayList debug_info_len={d} offset={d} content_len={d} next_padding_size={d}", .{ - // debug_info.items.len, atom.off, dbg_info_buf.len, next_padding_size, - // }); - // try writeDbgInfoNopsToArrayList( - // gpa, - // debug_info, - // atom.off, - // prev_padding_size, - // dbg_info_buf, - // next_padding_size, - // trailing_zero, - // ); - } else unreachable; -} - -pub fn updateNavLineNumber(self: *Dwarf, zcu: *Zcu, nav_index: InternPool.Nav.Index) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const atom_index = try self.getOrCreateAtomForNav(.src_fn, nav_index); - const atom = self.getAtom(.src_fn, atom_index); - if (atom.len == 0) return; - - const nav = zcu.intern_pool.getNav(nav_index); - const nav_val = Value.fromInterned(nav.status.resolved.val); - const func = nav_val.getFunction(zcu).?; - log.debug("src_line={d}, func.lbrace_line={d}, func.rbrace_line={d}", .{ - zcu.navSrcLine(nav_index), - func.lbrace_line, - func.rbrace_line, - }); - const line: u28 = @intCast(zcu.navSrcLine(nav_index) + func.lbrace_line); - var data: [4]u8 = undefined; - leb128.writeUnsignedFixed(4, &data, line); - - switch (self.bin_file.tag) { - .elf => { - const elf_file = self.bin_file.cast(File.Elf).?; - const shdr = elf_file.shdrs.items[elf_file.debug_line_section_index.?]; - const file_pos = shdr.sh_offset + atom.off + self.getRelocDbgLineOff(); - try elf_file.base.file.?.pwriteAll(&data, file_pos); - }, - .macho => { - const macho_file = self.bin_file.cast(File.MachO).?; - if (macho_file.base.isRelocatable()) { - const sect = macho_file.sections.items(.header)[macho_file.debug_line_sect_index.?]; - const file_pos = sect.offset + atom.off + self.getRelocDbgLineOff(); - try macho_file.base.file.?.pwriteAll(&data, file_pos); - } else { - const d_sym = macho_file.getDebugSymbols().?; - const sect = d_sym.getSection(d_sym.debug_line_section_index.?); - const file_pos = sect.offset + atom.off + self.getRelocDbgLineOff(); - try d_sym.file.pwriteAll(&data, file_pos); - } - }, - .wasm => { - // const wasm_file = self.bin_file.cast(File.Wasm).?; - // const offset = atom.off + self.getRelocDbgLineOff(); - // const line_atom_index = wasm_file.debug_line_atom.?; - // wasm_file.getAtomPtr(line_atom_index).code.items[offset..][0..data.len].* = data; - }, - else => unreachable, } -} -pub fn freeNav(self: *Dwarf, nav_index: InternPool.Nav.Index) void { - const gpa = self.allocator; - - // Free SrcFn atom - if (self.src_fn_navs.fetchRemove(nav_index)) |kv| { - const src_fn_index = kv.value; - const src_fn = self.getAtom(.src_fn, src_fn_index); - _ = self.src_fn_free_list.remove(src_fn_index); - - if (src_fn.prev_index) |prev_index| { - self.src_fn_free_list.put(gpa, prev_index, {}) catch {}; - const prev = self.getAtomPtr(.src_fn, prev_index); - prev.next_index = src_fn.next_index; - if (src_fn.next_index) |next_index| { - self.getAtomPtr(.src_fn, next_index).prev_index = prev_index; + fn write(loc: Loc, wip: anytype) UpdateError!void { + const writer = wip.infoWriter(); + switch (loc) { + .empty => unreachable, + .addr => |addr| { + try writer.writeByte(DW.OP.addr); + switch (addr) { + .sym => |sym_index| try wip.addrSym(sym_index), + } + }, + .constu => |constu| if (std.math.cast(u5, constu)) |lit| { + try writer.writeByte(@as(u8, DW.OP.lit0) + lit); + } else if (std.math.cast(u8, constu)) |const1u| { + try writer.writeAll(&.{ DW.OP.const1u, const1u }); + } else if (std.math.cast(u16, constu)) |const2u| { + try writer.writeByte(DW.OP.const2u); + try writer.writeInt(u16, const2u, wip.dwarf.endian); + } else if (std.math.cast(u21, constu)) |const3u| { + try writer.writeByte(DW.OP.constu); + try uleb128(writer, const3u); + } else if (std.math.cast(u32, constu)) |const4u| { + try writer.writeByte(DW.OP.const4u); + try writer.writeInt(u32, const4u, wip.dwarf.endian); + } else if (std.math.cast(u49, constu)) |const7u| { + try writer.writeByte(DW.OP.constu); + try uleb128(writer, const7u); } else { - self.src_fn_last_index = prev_index; - } - } else if (src_fn.next_index) |next_index| { - self.src_fn_first_index = next_index; - self.getAtomPtr(.src_fn, next_index).prev_index = null; - } - if (self.src_fn_first_index == src_fn_index) { - self.src_fn_first_index = src_fn.next_index; - } - if (self.src_fn_last_index == src_fn_index) { - self.src_fn_last_index = src_fn.prev_index; + try writer.writeByte(DW.OP.const8u); + try writer.writeInt(u64, constu, wip.dwarf.endian); + }, + .consts => |consts| if (std.math.cast(i8, consts)) |const1s| { + try writer.writeAll(&.{ DW.OP.const1s, @bitCast(const1s) }); + } else if (std.math.cast(i16, consts)) |const2s| { + try writer.writeByte(DW.OP.const2s); + try writer.writeInt(i16, const2s, wip.dwarf.endian); + } else if (std.math.cast(i21, consts)) |const3s| { + try writer.writeByte(DW.OP.consts); + try sleb128(writer, const3s); + } else if (std.math.cast(i32, consts)) |const4s| { + try writer.writeByte(DW.OP.const4s); + try writer.writeInt(i32, const4s, wip.dwarf.endian); + } else if (std.math.cast(i49, consts)) |const7s| { + try writer.writeByte(DW.OP.consts); + try sleb128(writer, const7s); + } else { + try writer.writeByte(DW.OP.const8s); + try writer.writeInt(i64, consts, wip.dwarf.endian); + }, + .plus => |plus| done: { + if (plus[0].getConst(u0)) |_| { + try plus[1].write(wip); + break :done; + } + if (plus[1].getConst(u0)) |_| { + try plus[0].write(wip); + break :done; + } + if (plus[0].getBaseReg()) |breg| { + if (plus[1].getConst(i65)) |offset| { + try writeReg(breg, DW.OP.breg0, DW.OP.bregx, writer); + try sleb128(writer, offset); + break :done; + } + } + if (plus[1].getBaseReg()) |breg| { + if (plus[0].getConst(i65)) |offset| { + try writeReg(breg, DW.OP.breg0, DW.OP.bregx, writer); + try sleb128(writer, offset); + break :done; + } + } + if (plus[0].getConst(u64)) |uconst| { + try plus[1].write(wip); + try writer.writeByte(DW.OP.plus_uconst); + try uleb128(writer, uconst); + break :done; + } + if (plus[1].getConst(u64)) |uconst| { + try plus[0].write(wip); + try writer.writeByte(DW.OP.plus_uconst); + try uleb128(writer, uconst); + break :done; + } + try plus[0].write(wip); + try plus[1].write(wip); + try writer.writeByte(DW.OP.plus); + }, + .reg => |reg| try writeReg(reg, DW.OP.reg0, DW.OP.regx, writer), + .breg => |breg| { + try writeReg(breg, DW.OP.breg0, DW.OP.bregx, writer); + try sleb128(writer, 0); + }, + .push_object_address => try writer.writeByte(DW.OP.push_object_address), + .form_tls_address => |addr| { + try addr.write(wip); + try writer.writeByte(DW.OP.form_tls_address); + }, + .implicit_value => |value| { + try writer.writeByte(DW.OP.implicit_value); + try uleb128(writer, value.len); + try writer.writeAll(value); + }, + .stack_value => |value| { + try value.write(wip); + try writer.writeByte(DW.OP.stack_value); + }, + .wasm_ext => |wasm_ext| { + try writer.writeByte(DW.OP.WASM_location); + switch (wasm_ext) { + .local => |local| { + try writer.writeByte(DW.OP.WASM_local); + try uleb128(writer, local); + }, + .global => |global| if (std.math.cast(u21, global)) |global_u21| { + try writer.writeByte(DW.OP.WASM_global); + try uleb128(writer, global_u21); + } else { + try writer.writeByte(DW.OP.WASM_global_u32); + try writer.writeInt(u32, global, wip.dwarf.endian); + }, + .operand_stack => |operand_stack| { + try writer.writeByte(DW.OP.WASM_operand_stack); + try uleb128(writer, operand_stack); + }, + } + }, } } +}; - // Free DI atom - if (self.di_atom_navs.fetchRemove(nav_index)) |kv| { - const di_atom_index = kv.value; - const di_atom = self.getAtomPtr(.di_atom, di_atom_index); +pub const WipNav = struct { + dwarf: *Dwarf, + pt: Zcu.PerThread, + unit: Unit.Index, + entry: Entry.Index, + any_children: bool, + func: InternPool.Index, + func_sym_index: u32, + func_high_reloc: u32, + inlined_funcs: std.ArrayListUnmanaged(struct { + abbrev_code: u32, + high_reloc: u32, + }), + debug_info: std.ArrayListUnmanaged(u8), + debug_line: std.ArrayListUnmanaged(u8), + debug_loclists: std.ArrayListUnmanaged(u8), + pending_types: std.ArrayListUnmanaged(InternPool.Index), + + pub fn deinit(wip_nav: *WipNav) void { + const gpa = wip_nav.dwarf.gpa; + if (wip_nav.func != .none) wip_nav.inlined_funcs.deinit(gpa); + wip_nav.debug_info.deinit(gpa); + wip_nav.debug_line.deinit(gpa); + wip_nav.debug_loclists.deinit(gpa); + wip_nav.pending_types.deinit(gpa); + } - if (self.di_atom_first_index == di_atom_index) { - self.di_atom_first_index = di_atom.next_index; - } - if (self.di_atom_last_index == di_atom_index) { - // TODO shrink the .debug_info section size here - self.di_atom_last_index = di_atom.prev_index; - } + pub fn infoWriter(wip_nav: *WipNav) std.ArrayListUnmanaged(u8).Writer { + return wip_nav.debug_info.writer(wip_nav.dwarf.gpa); + } - if (di_atom.prev_index) |prev_index| { - self.getAtomPtr(.di_atom, prev_index).next_index = di_atom.next_index; - // TODO the free list logic like we do for SrcFn above - } else { - di_atom.prev_index = null; - } + pub const LocalTag = enum { local_arg, local_var }; + pub fn genLocalDebugInfo( + wip_nav: *WipNav, + tag: LocalTag, + name: []const u8, + ty: Type, + loc: Loc, + ) UpdateError!void { + assert(wip_nav.func != .none); + try wip_nav.abbrevCode(switch (tag) { + inline else => |ct_tag| @field(AbbrevCode, @tagName(ct_tag)), + }); + try wip_nav.strp(name); + try wip_nav.refType(ty); + try wip_nav.exprloc(loc); + wip_nav.any_children = true; + } - if (di_atom.next_index) |next_index| { - self.getAtomPtr(.di_atom, next_index).prev_index = di_atom.prev_index; - } else { - di_atom.next_index = null; - } + pub fn genVarArgsDebugInfo(wip_nav: *WipNav) UpdateError!void { + assert(wip_nav.func != .none); + try wip_nav.abbrevCode(.is_var_args); + wip_nav.any_children = true; } -} -pub fn writeDbgAbbrev(self: *Dwarf) !void { - // These are LEB encoded but since the values are all less than 127 - // we can simply append these bytes. - // zig fmt: off - const abbrev_buf = [_]u8{ - @intFromEnum(AbbrevCode.padding), - @as(u8, 0x80) | @as(u7, @truncate(DW.TAG.ZIG_padding >> 0)), - @as(u8, 0x80) | @as(u7, @truncate(DW.TAG.ZIG_padding >> 7)), - @as(u8, 0x00) | @as(u7, @intCast(DW.TAG.ZIG_padding >> 14)), - DW.CHILDREN.no, - 0, 0, - - @intFromEnum(AbbrevCode.compile_unit), - DW.TAG.compile_unit, - DW.CHILDREN.yes, - DW.AT.stmt_list, DW.FORM.sec_offset, - DW.AT.low_pc, DW.FORM.addr, - DW.AT.high_pc, DW.FORM.addr, - DW.AT.name, DW.FORM.strp, - DW.AT.comp_dir, DW.FORM.strp, - DW.AT.producer, DW.FORM.strp, - DW.AT.language, DW.FORM.data2, - 0, 0, - - @intFromEnum(AbbrevCode.subprogram), - DW.TAG.subprogram, - DW.CHILDREN.yes, - DW.AT.low_pc, DW.FORM.addr, - DW.AT.high_pc, DW.FORM.data4, - DW.AT.type, DW.FORM.ref4, - DW.AT.name, DW.FORM.string, - DW.AT.linkage_name, DW.FORM.string, - 0, 0, - - @intFromEnum(AbbrevCode.subprogram_retvoid), - DW.TAG.subprogram, - DW.CHILDREN.yes, - DW.AT.low_pc, DW.FORM.addr, - DW.AT.high_pc, DW.FORM.data4, - DW.AT.name, DW.FORM.string, - DW.AT.linkage_name, DW.FORM.string, - 0, 0, - - @intFromEnum(AbbrevCode.base_type), - DW.TAG.base_type, DW.CHILDREN.no, - DW.AT.encoding, DW.FORM.data1, - DW.AT.byte_size, DW.FORM.udata, - DW.AT.name, DW.FORM.string, - 0, 0, - - @intFromEnum(AbbrevCode.ptr_type), - DW.TAG.pointer_type, DW.CHILDREN.no, - DW.AT.type, DW.FORM.ref4, - 0, 0, - - @intFromEnum(AbbrevCode.struct_type), - DW.TAG.structure_type, DW.CHILDREN.yes, - DW.AT.byte_size, DW.FORM.udata, - DW.AT.name, DW.FORM.string, - 0, 0, - - @intFromEnum(AbbrevCode.struct_member), - DW.TAG.member, - DW.CHILDREN.no, - DW.AT.name, DW.FORM.string, - DW.AT.type, DW.FORM.ref4, - DW.AT.data_member_location, DW.FORM.udata, - 0, 0, - - @intFromEnum(AbbrevCode.enum_type), - DW.TAG.enumeration_type, - DW.CHILDREN.yes, - DW.AT.byte_size, DW.FORM.udata, - DW.AT.name, DW.FORM.string, - 0, 0, - - @intFromEnum(AbbrevCode.enum_variant), - DW.TAG.enumerator, DW.CHILDREN.no, - DW.AT.name, DW.FORM.string, - DW.AT.const_value, DW.FORM.data8, - 0, 0, - - @intFromEnum(AbbrevCode.union_type), - DW.TAG.union_type, DW.CHILDREN.yes, - DW.AT.byte_size, DW.FORM.udata, - DW.AT.name, DW.FORM.string, - 0, 0, - - @intFromEnum(AbbrevCode.zero_bit_type), - DW.TAG.unspecified_type, - DW.CHILDREN.no, - 0, 0, - - @intFromEnum(AbbrevCode.parameter), - DW.TAG.formal_parameter, - DW.CHILDREN.no, - DW.AT.location, DW.FORM.exprloc, - DW.AT.type, DW.FORM.ref4, - DW.AT.name, DW.FORM.string, - 0, 0, - - @intFromEnum(AbbrevCode.variable), - DW.TAG.variable, - DW.CHILDREN.no, - DW.AT.location, DW.FORM.exprloc, - DW.AT.type, DW.FORM.ref4, - DW.AT.name, DW.FORM.string, - 0, 0, - - @intFromEnum(AbbrevCode.array_type), - DW.TAG.array_type, - DW.CHILDREN.yes, - DW.AT.name, DW.FORM.string, - DW.AT.type, DW.FORM.ref4, - 0, 0, - - @intFromEnum(AbbrevCode.array_dim), - DW.TAG.subrange_type, - DW.CHILDREN.no, - DW.AT.type, DW.FORM.ref4, - DW.AT.count, DW.FORM.udata, - 0, 0, - - 0, - }; - // zig fmt: on - const abbrev_offset = 0; - self.abbrev_table_offset = abbrev_offset; - - const needed_size = abbrev_buf.len; - if (self.bin_file.cast(.elf)) |elf_file| { - const shdr_index = elf_file.debug_abbrev_section_index.?; - try elf_file.growNonAllocSection(shdr_index, needed_size, 1, false); - const debug_abbrev_sect = &elf_file.shdrs.items[shdr_index]; - const file_pos = debug_abbrev_sect.sh_offset + abbrev_offset; - try elf_file.base.file.?.pwriteAll(&abbrev_buf, file_pos); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - const sect_index = macho_file.debug_abbrev_sect_index.?; - try macho_file.growSection(sect_index, needed_size); - const sect = macho_file.sections.items(.header)[sect_index]; - const file_pos = sect.offset + abbrev_offset; - try macho_file.base.file.?.pwriteAll(&abbrev_buf, file_pos); - } else { - const d_sym = macho_file.getDebugSymbols().?; - const sect_index = d_sym.debug_abbrev_section_index.?; - try d_sym.growSection(sect_index, needed_size, false, macho_file); - const sect = d_sym.getSection(sect_index); - const file_pos = sect.offset + abbrev_offset; - try d_sym.file.pwriteAll(&abbrev_buf, file_pos); - } - } else if (self.bin_file.cast(.wasm)) |wasm_file| { - _ = wasm_file; - // const debug_abbrev = &wasm_file.getAtomPtr(wasm_file.debug_abbrev_atom.?).code; - // try debug_abbrev.resize(gpa, needed_size); - // debug_abbrev.items[0..abbrev_buf.len].* = abbrev_buf; - } else unreachable; -} + pub fn advancePCAndLine( + wip_nav: *WipNav, + delta_line: i33, + delta_pc: u64, + ) error{OutOfMemory}!void { + const dlw = wip_nav.debug_line.writer(wip_nav.dwarf.gpa); -fn dbgInfoHeaderBytes(self: *Dwarf) usize { - _ = self; - return 120; -} + const header = wip_nav.dwarf.debug_line.header; + assert(header.maximum_operations_per_instruction == 1); + const delta_op: u64 = 0; -pub fn writeDbgInfoHeader(self: *Dwarf, zcu: *Zcu, low_pc: u64, high_pc: u64) !void { - // If this value is null it means there is an error in the module; - // leave debug_info_header_dirty=true. - const first_dbg_info_off = self.getDebugInfoOff() orelse return; + const remaining_delta_line: i9 = @intCast(if (delta_line < header.line_base or + delta_line - header.line_base >= header.line_range) + remaining: { + assert(delta_line != 0); + try dlw.writeByte(DW.LNS.advance_line); + try sleb128(dlw, delta_line); + break :remaining 0; + } else delta_line); - // We have a function to compute the upper bound size, because it's needed - // for determining where to put the offset of the first `LinkBlock`. - const needed_bytes = self.dbgInfoHeaderBytes(); - var di_buf = try std.ArrayList(u8).initCapacity(self.allocator, needed_bytes); - defer di_buf.deinit(); + const op_advance = @divExact(delta_pc, header.minimum_instruction_length) * + header.maximum_operations_per_instruction + delta_op; + const max_op_advance: u9 = (std.math.maxInt(u8) - header.opcode_base) / header.line_range; + const remaining_op_advance: u8 = @intCast(if (op_advance >= 2 * max_op_advance) remaining: { + try dlw.writeByte(DW.LNS.advance_pc); + try uleb128(dlw, op_advance); + break :remaining 0; + } else if (op_advance >= max_op_advance) remaining: { + try dlw.writeByte(DW.LNS.const_add_pc); + break :remaining op_advance - max_op_advance; + } else op_advance); - const comp = self.bin_file.comp; - const target = comp.root_mod.resolved_target.result; - const target_endian = target.cpu.arch.endian(); - const init_len_size: usize = switch (self.format) { - .dwarf32 => 4, - .dwarf64 => 12, - }; + if (remaining_delta_line == 0 and remaining_op_advance == 0) + try dlw.writeByte(DW.LNS.copy) + else + try dlw.writeByte(@intCast((remaining_delta_line - header.line_base) + + (header.line_range * remaining_op_advance) + header.opcode_base)); + } - // initial length - length of the .debug_info contribution for this compilation unit, - // not including the initial length itself. - // We have to come back and write it later after we know the size. - const after_init_len = di_buf.items.len + init_len_size; - const dbg_info_end = self.getDebugInfoEnd().?; - const init_len = dbg_info_end - after_init_len + 1; - - if (self.format == .dwarf64) di_buf.appendNTimesAssumeCapacity(0xff, 4); - self.writeOffsetAssumeCapacity(&di_buf, init_len); - - mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // DWARF version - const abbrev_offset = self.abbrev_table_offset.?; - - self.writeOffsetAssumeCapacity(&di_buf, abbrev_offset); - di_buf.appendAssumeCapacity(self.ptrWidthBytes()); // address size - - // Write the form for the compile unit, which must match the abbrev table above. - const name_strp = try self.strtab.insert(self.allocator, zcu.root_mod.root_src_path); - var compile_unit_dir_buffer: [std.fs.max_path_bytes]u8 = undefined; - const compile_unit_dir = resolveCompilationDir(zcu, &compile_unit_dir_buffer); - const comp_dir_strp = try self.strtab.insert(self.allocator, compile_unit_dir); - const producer_strp = try self.strtab.insert(self.allocator, link.producer_string); - - di_buf.appendAssumeCapacity(@intFromEnum(AbbrevCode.compile_unit)); - self.writeOffsetAssumeCapacity(&di_buf, 0); // DW.AT.stmt_list, DW.FORM.sec_offset - self.writeAddrAssumeCapacity(&di_buf, low_pc); - self.writeAddrAssumeCapacity(&di_buf, high_pc); - self.writeOffsetAssumeCapacity(&di_buf, name_strp); - self.writeOffsetAssumeCapacity(&di_buf, comp_dir_strp); - self.writeOffsetAssumeCapacity(&di_buf, producer_strp); - - // We are still waiting on dwarf-std.org to assign DW_LANG_Zig a number: - // http://dwarfstd.org/ShowIssue.php?issue=171115.1 - // Until then we say it is C99. - mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), DW.LANG.C99, target_endian); - - if (di_buf.items.len > first_dbg_info_off) { - // Move the first N navs to the end to make more padding for the header. - @panic("TODO: handle .debug_info header exceeding its padding"); - } - const jmp_amt = first_dbg_info_off - di_buf.items.len; - if (self.bin_file.cast(.elf)) |elf_file| { - const debug_info_sect = &elf_file.shdrs.items[elf_file.debug_info_section_index.?]; - const file_pos = debug_info_sect.sh_offset; - try pwriteDbgInfoNops(elf_file.base.file.?, file_pos, 0, di_buf.items, jmp_amt, false); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - const debug_info_sect = macho_file.sections.items(.header)[macho_file.debug_info_sect_index.?]; - const file_pos = debug_info_sect.offset; - try pwriteDbgInfoNops(macho_file.base.file.?, file_pos, 0, di_buf.items, jmp_amt, false); - } else { - const d_sym = macho_file.getDebugSymbols().?; - const debug_info_sect = d_sym.getSection(d_sym.debug_info_section_index.?); - const file_pos = debug_info_sect.offset; - try pwriteDbgInfoNops(d_sym.file, file_pos, 0, di_buf.items, jmp_amt, false); - } - } else if (self.bin_file.cast(.wasm)) |wasm_file| { - _ = wasm_file; - // const debug_info = &wasm_file.getAtomPtr(wasm_file.debug_info_atom.?).code; - // try writeDbgInfoNopsToArrayList(self.allocator, debug_info, 0, 0, di_buf.items, jmp_amt, false); - } else unreachable; -} + pub fn setColumn(wip_nav: *WipNav, column: u32) error{OutOfMemory}!void { + const dlw = wip_nav.debug_line.writer(wip_nav.dwarf.gpa); + try dlw.writeByte(DW.LNS.set_column); + try uleb128(dlw, column + 1); + } -fn resolveCompilationDir(zcu: *Zcu, buffer: *[std.fs.max_path_bytes]u8) []const u8 { - // We fully resolve all paths at this point to avoid lack of source line info in stack - // traces or lack of debugging information which, if relative paths were used, would - // be very location dependent. - // TODO: the only concern I have with this is WASI as either host or target, should - // we leave the paths as relative then? - const root_dir_path = zcu.root_mod.root.root_dir.path orelse "."; - const sub_path = zcu.root_mod.root.sub_path; - const realpath = if (std.fs.path.isAbsolute(root_dir_path)) r: { - @memcpy(buffer[0..root_dir_path.len], root_dir_path); - break :r root_dir_path; - } else std.fs.realpath(root_dir_path, buffer) catch return root_dir_path; - const len = realpath.len + 1 + sub_path.len; - if (buffer.len < len) return root_dir_path; - buffer[realpath.len] = '/'; - @memcpy(buffer[realpath.len + 1 ..][0..sub_path.len], sub_path); - return buffer[0..len]; -} + pub fn setPrologueEnd(wip_nav: *WipNav) error{OutOfMemory}!void { + const dlw = wip_nav.debug_line.writer(wip_nav.dwarf.gpa); + try dlw.writeByte(DW.LNS.set_prologue_end); + } -fn writeAddrAssumeCapacity(self: *Dwarf, buf: *std.ArrayList(u8), addr: u64) void { - const comp = self.bin_file.comp; - const target = comp.root_mod.resolved_target.result; - const target_endian = target.cpu.arch.endian(); - switch (self.ptr_width) { - .p32 => mem.writeInt(u32, buf.addManyAsArrayAssumeCapacity(4), @intCast(addr), target_endian), - .p64 => mem.writeInt(u64, buf.addManyAsArrayAssumeCapacity(8), addr, target_endian), + pub fn setEpilogueBegin(wip_nav: *WipNav) error{OutOfMemory}!void { + const dlw = wip_nav.debug_line.writer(wip_nav.dwarf.gpa); + try dlw.writeByte(DW.LNS.set_epilogue_begin); } -} -fn writeOffsetAssumeCapacity(self: *Dwarf, buf: *std.ArrayList(u8), off: u64) void { - const comp = self.bin_file.comp; - const target = comp.root_mod.resolved_target.result; - const target_endian = target.cpu.arch.endian(); - switch (self.format) { - .dwarf32 => mem.writeInt(u32, buf.addManyAsArrayAssumeCapacity(4), @intCast(off), target_endian), - .dwarf64 => mem.writeInt(u64, buf.addManyAsArrayAssumeCapacity(8), off, target_endian), + pub fn enterInlineFunc(wip_nav: *WipNav, func: InternPool.Index, code_off: u64, line: u32, column: u32) UpdateError!void { + const dwarf = wip_nav.dwarf; + const zcu = wip_nav.pt.zcu; + const diw = wip_nav.debug_info.writer(dwarf.gpa); + const inlined_func = try wip_nav.inlined_funcs.addOne(dwarf.gpa); + + inlined_func.abbrev_code = @intCast(wip_nav.debug_info.items.len); + try wip_nav.abbrevCode(.inlined_func); + try wip_nav.refNav(zcu.funcInfo(func).owner_nav); + try uleb128(diw, zcu.navSrcLine(zcu.funcInfo(wip_nav.func).owner_nav) + line + 1); + try uleb128(diw, column + 1); + const external_relocs = &dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).external_relocs; + try external_relocs.ensureUnusedCapacity(dwarf.gpa, 2); + external_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(wip_nav.debug_info.items.len), + .target_sym = wip_nav.func_sym_index, + .target_off = code_off, + }); + try diw.writeByteNTimes(0, @intFromEnum(dwarf.address_size)); + inlined_func.high_reloc = @intCast(external_relocs.items.len); + external_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(wip_nav.debug_info.items.len), + .target_sym = wip_nav.func_sym_index, + .target_off = undefined, + }); + try diw.writeByteNTimes(0, @intFromEnum(dwarf.address_size)); + try wip_nav.setInlineFunc(func); + wip_nav.any_children = false; } -} -/// Writes to the file a buffer, prefixed and suffixed by the specified number of -/// bytes of NOPs. Asserts each padding size is at least `min_nop_size` and total padding bytes -/// are less than 1044480 bytes (if this limit is ever reached, this function can be -/// improved to make more than one pwritev call, or the limit can be raised by a fixed -/// amount by increasing the length of `vecs`). -fn pwriteDbgLineNops( - file: fs.File, - offset: u64, - prev_padding_size: usize, - buf: []const u8, - next_padding_size: usize, -) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const page_of_nops = [1]u8{DW.LNS.negate_stmt} ** 4096; - const three_byte_nop = [3]u8{ DW.LNS.advance_pc, 0b1000_0000, 0 }; - var vecs: [512]std.posix.iovec_const = undefined; - var vec_index: usize = 0; - { - var padding_left = prev_padding_size; - if (padding_left % 2 != 0) { - vecs[vec_index] = .{ - .base = &three_byte_nop, - .len = three_byte_nop.len, - }; - vec_index += 1; - padding_left -= three_byte_nop.len; - } - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .base = &page_of_nops, - .len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .base = &page_of_nops, - .len = padding_left, - }; - vec_index += 1; - } + pub fn leaveInlineFunc(wip_nav: *WipNav, func: InternPool.Index, code_off: u64) UpdateError!void { + const inlined_func_bytes = comptime uleb128Bytes(@intFromEnum(AbbrevCode.inlined_func)); + const inlined_func = wip_nav.inlined_funcs.pop(); + const external_relocs = &wip_nav.dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).external_relocs; + external_relocs.items[inlined_func.high_reloc].target_off = code_off; + if (wip_nav.any_children) + try uleb128(wip_nav.debug_info.writer(wip_nav.dwarf.gpa), @intFromEnum(AbbrevCode.null)) + else + std.leb.writeUnsignedFixed( + inlined_func_bytes, + wip_nav.debug_info.items[inlined_func.abbrev_code..][0..inlined_func_bytes], + try wip_nav.dwarf.refAbbrevCode(.empty_inlined_func), + ); + try wip_nav.setInlineFunc(func); + wip_nav.any_children = true; } - vecs[vec_index] = .{ - .base = buf.ptr, - .len = buf.len, - }; - if (buf.len > 0) vec_index += 1; + pub fn setInlineFunc(wip_nav: *WipNav, func: InternPool.Index) UpdateError!void { + const zcu = wip_nav.pt.zcu; + const dwarf = wip_nav.dwarf; + if (wip_nav.func == func) return; - { - var padding_left = next_padding_size; - if (padding_left % 2 != 0) { - vecs[vec_index] = .{ - .base = &three_byte_nop, - .len = three_byte_nop.len, - }; - vec_index += 1; - padding_left -= three_byte_nop.len; - } - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .base = &page_of_nops, - .len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .base = &page_of_nops, - .len = padding_left, - }; - vec_index += 1; + const new_func_info = zcu.funcInfo(func); + const new_file = zcu.navFileScopeIndex(new_func_info.owner_nav); + const new_unit = try dwarf.getUnit(zcu.fileByIndex(new_file).mod); + + const dlw = wip_nav.debug_line.writer(dwarf.gpa); + if (dwarf.incremental()) { + const new_nav_gop = try dwarf.navs.getOrPut(dwarf.gpa, new_func_info.owner_nav); + errdefer _ = dwarf.navs.pop(); + if (!new_nav_gop.found_existing) new_nav_gop.value_ptr.* = try dwarf.addCommonEntry(new_unit); + + try dlw.writeByte(DW.LNS.extended_op); + try uleb128(dlw, 1 + dwarf.sectionOffsetBytes()); + try dlw.writeByte(DW.LNE.ZIG_set_decl); + try dwarf.debug_line.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).cross_section_relocs.append(dwarf.gpa, .{ + .source_off = @intCast(wip_nav.debug_line.items.len), + .target_sec = .debug_info, + .target_unit = new_unit, + .target_entry = new_nav_gop.value_ptr.toOptional(), + }); + try dlw.writeByteNTimes(0, dwarf.sectionOffsetBytes()); + return; } - } - try file.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); -} -fn writeDbgLineNopsBuffered( - buf: []u8, - offset: u32, - prev_padding_size: usize, - content: []const u8, - next_padding_size: usize, -) void { - assert(buf.len >= content.len + prev_padding_size + next_padding_size); - const tracy = trace(@src()); - defer tracy.end(); - - const three_byte_nop = [3]u8{ DW.LNS.advance_pc, 0b1000_0000, 0 }; - { - var padding_left = prev_padding_size; - if (padding_left % 2 != 0) { - buf[offset - padding_left ..][0..3].* = three_byte_nop; - padding_left -= 3; + const old_func_info = zcu.funcInfo(wip_nav.func); + const old_file = zcu.navFileScopeIndex(old_func_info.owner_nav); + if (old_file != new_file) { + const mod_info = dwarf.getModInfo(wip_nav.unit); + const mod_gop = try mod_info.dirs.getOrPut(dwarf.gpa, new_unit); + errdefer _ = if (!mod_gop.found_existing) mod_info.dirs.pop(); + const file_gop = try mod_info.files.getOrPut(dwarf.gpa, new_file); + errdefer _ = if (!file_gop.found_existing) mod_info.files.pop(); + + try dlw.writeByte(DW.LNS.set_file); + try uleb128(dlw, file_gop.index); } - while (padding_left > 0) : (padding_left -= 1) { - buf[offset - padding_left] = DW.LNS.negate_stmt; + const old_src_line: i33 = zcu.navSrcLine(old_func_info.owner_nav); + const new_src_line: i33 = zcu.navSrcLine(new_func_info.owner_nav); + if (new_src_line != old_src_line) { + try dlw.writeByte(DW.LNS.advance_line); + try sleb128(dlw, new_src_line - old_src_line); } - } - - @memcpy(buf[offset..][0..content.len], content); - { - var padding_left = next_padding_size; - if (padding_left % 2 != 0) { - buf[offset + content.len + padding_left ..][0..3].* = three_byte_nop; - padding_left -= 3; - } + wip_nav.func = func; + } - while (padding_left > 0) : (padding_left -= 1) { - buf[offset + content.len + padding_left] = DW.LNS.negate_stmt; - } + fn abbrevCode(wip_nav: *WipNav, abbrev_code: AbbrevCode) UpdateError!void { + try uleb128(wip_nav.debug_info.writer(wip_nav.dwarf.gpa), try wip_nav.dwarf.refAbbrevCode(abbrev_code)); } -} -/// Writes to the file a buffer, prefixed and suffixed by the specified number of -/// bytes of padding. -fn pwriteDbgInfoNops( - file: fs.File, - offset: u64, - prev_padding_size: usize, - buf: []const u8, - next_padding_size: usize, - trailing_zero: bool, -) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const page_of_nops = [1]u8{@intFromEnum(AbbrevCode.padding)} ** 4096; - var vecs: [32]std.posix.iovec_const = undefined; - var vec_index: usize = 0; - { - var padding_left = prev_padding_size; - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .base = &page_of_nops, - .len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .base = &page_of_nops, - .len = padding_left, - }; - vec_index += 1; + fn infoSectionOffset(wip_nav: *WipNav, sec: Section.Index, unit: Unit.Index, entry: Entry.Index, off: u32) UpdateError!void { + const dwarf = wip_nav.dwarf; + const gpa = dwarf.gpa; + const entry_ptr = dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry); + if (sec != .debug_info) { + try entry_ptr.cross_section_relocs.append(gpa, .{ + .source_off = @intCast(wip_nav.debug_info.items.len), + .target_sec = sec, + .target_unit = unit, + .target_entry = entry.toOptional(), + .target_off = off, + }); + } else if (unit != wip_nav.unit) { + try entry_ptr.cross_unit_relocs.append(gpa, .{ + .source_off = @intCast(wip_nav.debug_info.items.len), + .target_unit = unit, + .target_entry = entry.toOptional(), + .target_off = off, + }); + } else { + try entry_ptr.cross_entry_relocs.append(gpa, .{ + .source_off = @intCast(wip_nav.debug_info.items.len), + .target_entry = entry, + .target_off = off, + }); } + try wip_nav.debug_info.appendNTimes(gpa, 0, dwarf.sectionOffsetBytes()); } - vecs[vec_index] = .{ - .base = buf.ptr, - .len = buf.len, - }; - if (buf.len > 0) vec_index += 1; + fn strp(wip_nav: *WipNav, str: []const u8) UpdateError!void { + try wip_nav.infoSectionOffset(.debug_str, StringSection.unit, try wip_nav.dwarf.debug_str.addString(wip_nav.dwarf, str), 0); + } - { - var padding_left = next_padding_size; - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .base = &page_of_nops, - .len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .base = &page_of_nops, - .len = padding_left, - }; - vec_index += 1; - } + fn addrSym(wip_nav: *WipNav, sym_index: u32) UpdateError!void { + const dwarf = wip_nav.dwarf; + try dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).external_relocs.append(dwarf.gpa, .{ + .source_off = @intCast(wip_nav.debug_info.items.len), + .target_sym = sym_index, + }); + try wip_nav.debug_info.appendNTimes(dwarf.gpa, 0, @intFromEnum(dwarf.address_size)); } - if (trailing_zero) { - var zbuf = [1]u8{0}; - vecs[vec_index] = .{ - .base = &zbuf, - .len = zbuf.len, + fn exprloc(wip_nav: *WipNav, loc: Loc) UpdateError!void { + if (loc == .empty) return; + var wip: struct { + const Info = std.io.CountingWriter(std.io.NullWriter); + dwarf: *Dwarf, + debug_info: Info, + fn infoWriter(wip: *@This()) Info.Writer { + return wip.debug_info.writer(); + } + fn addrSym(wip: *@This(), _: u32) error{}!void { + wip.debug_info.bytes_written += @intFromEnum(wip.dwarf.address_size); + } + } = .{ + .dwarf = wip_nav.dwarf, + .debug_info = std.io.countingWriter(std.io.null_writer), }; - vec_index += 1; + try loc.write(&wip); + try uleb128(wip_nav.debug_info.writer(wip_nav.dwarf.gpa), wip.debug_info.bytes_written); + try loc.write(wip_nav); } - try file.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); -} - -fn writeDbgInfoNopsToArrayList( - gpa: Allocator, - buffer: *std.ArrayListUnmanaged(u8), - offset: u32, - prev_padding_size: usize, - content: []const u8, - next_padding_size: usize, - trailing_zero: bool, -) Allocator.Error!void { - try buffer.resize(gpa, @max( - buffer.items.len, - offset + content.len + next_padding_size + 1, - )); - @memset(buffer.items[offset - prev_padding_size .. offset], @intFromEnum(AbbrevCode.padding)); - @memcpy(buffer.items[offset..][0..content.len], content); - @memset(buffer.items[offset + content.len ..][0..next_padding_size], @intFromEnum(AbbrevCode.padding)); - - if (trailing_zero) { - buffer.items[offset + content.len + next_padding_size] = 0; + fn getTypeEntry(wip_nav: *WipNav, ty: Type) UpdateError!struct { Unit.Index, Entry.Index } { + const zcu = wip_nav.pt.zcu; + const ip = &zcu.intern_pool; + const maybe_inst_index = ty.typeDeclInst(zcu); + const unit = if (maybe_inst_index) |inst_index| + try wip_nav.dwarf.getUnit(zcu.fileByIndex(inst_index.resolveFile(ip)).mod) + else + .main; + const gop = try wip_nav.dwarf.types.getOrPut(wip_nav.dwarf.gpa, ty.toIntern()); + if (gop.found_existing) return .{ unit, gop.value_ptr.* }; + const entry = try wip_nav.dwarf.addCommonEntry(unit); + gop.value_ptr.* = entry; + if (maybe_inst_index == null) try wip_nav.pending_types.append(wip_nav.dwarf.gpa, ty.toIntern()); + return .{ unit, entry }; } -} -pub fn writeDbgAranges(self: *Dwarf, addr: u64, size: u64) !void { - const comp = self.bin_file.comp; - const target = comp.root_mod.resolved_target.result; - const target_endian = target.cpu.arch.endian(); - const ptr_width_bytes = self.ptrWidthBytes(); - - // Enough for all the data without resizing. When support for more compilation units - // is added, the size of this section will become more variable. - var di_buf = try std.ArrayList(u8).initCapacity(self.allocator, 100); - defer di_buf.deinit(); - - // initial length - length of the .debug_aranges contribution for this compilation unit, - // not including the initial length itself. - // We have to come back and write it later after we know the size. - if (self.format == .dwarf64) di_buf.appendNTimesAssumeCapacity(0xff, 4); - const init_len_index = di_buf.items.len; - self.writeOffsetAssumeCapacity(&di_buf, 0); - const after_init_len = di_buf.items.len; - mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 2, target_endian); // version - - // When more than one compilation unit is supported, this will be the offset to it. - // For now it is always at offset 0 in .debug_info. - self.writeOffsetAssumeCapacity(&di_buf, 0); // .debug_info offset - di_buf.appendAssumeCapacity(ptr_width_bytes); // address_size - di_buf.appendAssumeCapacity(0); // segment_selector_size - - const end_header_offset = di_buf.items.len; - const begin_entries_offset = mem.alignForward(usize, end_header_offset, ptr_width_bytes * 2); - di_buf.appendNTimesAssumeCapacity(0, begin_entries_offset - end_header_offset); - - // Currently only one compilation unit is supported, so the address range is simply - // identical to the main program header virtual address and memory size. - self.writeAddrAssumeCapacity(&di_buf, addr); - self.writeAddrAssumeCapacity(&di_buf, size); - - // Sentinel. - self.writeAddrAssumeCapacity(&di_buf, 0); - self.writeAddrAssumeCapacity(&di_buf, 0); - - // Go back and populate the initial length. - const init_len = di_buf.items.len - after_init_len; - switch (self.format) { - .dwarf32 => mem.writeInt(u32, di_buf.items[init_len_index..][0..4], @intCast(init_len), target_endian), - .dwarf64 => mem.writeInt(u64, di_buf.items[init_len_index..][0..8], init_len, target_endian), - } - - const needed_size: u32 = @intCast(di_buf.items.len); - if (self.bin_file.cast(.elf)) |elf_file| { - const shdr_index = elf_file.debug_aranges_section_index.?; - try elf_file.growNonAllocSection(shdr_index, needed_size, 16, false); - const debug_aranges_sect = &elf_file.shdrs.items[shdr_index]; - const file_pos = debug_aranges_sect.sh_offset; - try elf_file.base.file.?.pwriteAll(di_buf.items, file_pos); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - const sect_index = macho_file.debug_aranges_sect_index.?; - try macho_file.growSection(sect_index, needed_size); - const sect = macho_file.sections.items(.header)[sect_index]; - const file_pos = sect.offset; - try macho_file.base.file.?.pwriteAll(di_buf.items, file_pos); - } else { - const d_sym = macho_file.getDebugSymbols().?; - const sect_index = d_sym.debug_aranges_section_index.?; - try d_sym.growSection(sect_index, needed_size, false, macho_file); - const sect = d_sym.getSection(sect_index); - const file_pos = sect.offset; - try d_sym.file.pwriteAll(di_buf.items, file_pos); - } - } else if (self.bin_file.cast(.wasm)) |wasm_file| { - _ = wasm_file; - // const debug_ranges = &wasm_file.getAtomPtr(wasm_file.debug_ranges_atom.?).code; - // try debug_ranges.resize(gpa, needed_size); - // @memcpy(debug_ranges.items[0..di_buf.items.len], di_buf.items); - } else unreachable; -} - -pub fn writeDbgLineHeader(self: *Dwarf) !void { - const comp = self.bin_file.comp; - const gpa = self.allocator; - const target = comp.root_mod.resolved_target.result; - const target_endian = target.cpu.arch.endian(); - const init_len_size: usize = switch (self.format) { - .dwarf32 => 4, - .dwarf64 => 12, - }; - - const dbg_line_prg_off = self.getDebugLineProgramOff() orelse return; - assert(self.getDebugLineProgramEnd().? != 0); - - // Convert all input DI files into a set of include dirs and file names. - var arena = std.heap.ArenaAllocator.init(gpa); - defer arena.deinit(); - const paths = try self.genIncludeDirsAndFileNames(arena.allocator()); - - // The size of this header is variable, depending on the number of directories, - // files, and padding. We have a function to compute the upper bound size, however, - // because it's needed for determining where to put the offset of the first `SrcFn`. - const needed_bytes = self.dbgLineNeededHeaderBytes(paths.dirs, paths.files); - var di_buf = try std.ArrayList(u8).initCapacity(gpa, needed_bytes); - defer di_buf.deinit(); - - if (self.format == .dwarf64) di_buf.appendNTimesAssumeCapacity(0xff, 4); - self.writeOffsetAssumeCapacity(&di_buf, 0); - - mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // version - - // Empirically, debug info consumers do not respect this field, or otherwise - // consider it to be an error when it does not point exactly to the end of the header. - // Therefore we rely on the NOP jump at the beginning of the Line Number Program for - // padding rather than this field. - const before_header_len = di_buf.items.len; - self.writeOffsetAssumeCapacity(&di_buf, 0); // We will come back and write this. - const after_header_len = di_buf.items.len; - - assert(self.dbg_line_header.opcode_base == DW.LNS.set_isa + 1); - di_buf.appendSliceAssumeCapacity(&[_]u8{ - self.dbg_line_header.minimum_instruction_length, - self.dbg_line_header.maximum_operations_per_instruction, - @intFromBool(self.dbg_line_header.default_is_stmt), - @bitCast(self.dbg_line_header.line_base), - self.dbg_line_header.line_range, - self.dbg_line_header.opcode_base, - - // Standard opcode lengths. The number of items here is based on `opcode_base`. - // The value is the number of LEB128 operands the instruction takes. - 0, // `DW.LNS.copy` - 1, // `DW.LNS.advance_pc` - 1, // `DW.LNS.advance_line` - 1, // `DW.LNS.set_file` - 1, // `DW.LNS.set_column` - 0, // `DW.LNS.negate_stmt` - 0, // `DW.LNS.set_basic_block` - 0, // `DW.LNS.const_add_pc` - 1, // `DW.LNS.fixed_advance_pc` - 0, // `DW.LNS.set_prologue_end` - 0, // `DW.LNS.set_epilogue_begin` - 1, // `DW.LNS.set_isa` - }); + fn refType(wip_nav: *WipNav, ty: Type) UpdateError!void { + const unit, const entry = try wip_nav.getTypeEntry(ty); + try wip_nav.infoSectionOffset(.debug_info, unit, entry, 0); + } - for (paths.dirs, 0..) |dir, i| { - log.debug("adding new include dir at {d} of '{s}'", .{ i + 1, dir }); - di_buf.appendSliceAssumeCapacity(dir); - di_buf.appendAssumeCapacity(0); + fn refNav(wip_nav: *WipNav, nav_index: InternPool.Nav.Index) UpdateError!void { + const zcu = wip_nav.pt.zcu; + const ip = &zcu.intern_pool; + const unit = try wip_nav.dwarf.getUnit(zcu.fileByIndex(ip.getNav(nav_index).srcInst(ip).resolveFile(ip)).mod); + const nav_gop = try wip_nav.dwarf.navs.getOrPut(wip_nav.dwarf.gpa, nav_index); + if (!nav_gop.found_existing) nav_gop.value_ptr.* = try wip_nav.dwarf.addCommonEntry(unit); + try wip_nav.infoSectionOffset(.debug_info, unit, nav_gop.value_ptr.*, 0); } - di_buf.appendAssumeCapacity(0); // include directories sentinel - for (paths.files, 0..) |file, i| { - const dir_index = paths.files_dirs_indexes[i]; - log.debug("adding new file name at {d} of '{s}' referencing directory {d}", .{ - i + 1, - file, - dir_index + 1, - }); - di_buf.appendSliceAssumeCapacity(file); - di_buf.appendSliceAssumeCapacity(&[_]u8{ - 0, // null byte for the relative path name - @intCast(dir_index), // directory_index - 0, // mtime (TODO supply this) - 0, // file size bytes (TODO supply this) + fn refForward(wip_nav: *WipNav) std.mem.Allocator.Error!u32 { + const dwarf = wip_nav.dwarf; + const cross_entry_relocs = &dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).cross_entry_relocs; + const reloc_index: u32 = @intCast(cross_entry_relocs.items.len); + try cross_entry_relocs.append(dwarf.gpa, .{ + .source_off = @intCast(wip_nav.debug_info.items.len), + .target_entry = undefined, + .target_off = undefined, }); + try wip_nav.debug_info.appendNTimes(dwarf.gpa, 0, dwarf.sectionOffsetBytes()); + return reloc_index; } - di_buf.appendAssumeCapacity(0); // file names sentinel - const header_len = di_buf.items.len - after_header_len; - switch (self.format) { - .dwarf32 => mem.writeInt(u32, di_buf.items[before_header_len..][0..4], @intCast(header_len), target_endian), - .dwarf64 => mem.writeInt(u64, di_buf.items[before_header_len..][0..8], header_len, target_endian), + fn finishForward(wip_nav: *WipNav, reloc_index: u32) void { + const reloc = &wip_nav.dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).cross_entry_relocs.items[reloc_index]; + reloc.target_entry = wip_nav.entry; + reloc.target_off = @intCast(wip_nav.debug_info.items.len); } - assert(needed_bytes == di_buf.items.len); - - if (di_buf.items.len > dbg_line_prg_off) { - const needed_with_padding = padToIdeal(needed_bytes); - const delta = needed_with_padding - dbg_line_prg_off; - - const first_fn_index = self.src_fn_first_index.?; - const first_fn = self.getAtom(.src_fn, first_fn_index); - const last_fn_index = self.src_fn_last_index.?; - const last_fn = self.getAtom(.src_fn, last_fn_index); - - var src_fn_index = first_fn_index; - - const buffer = try gpa.alloc(u8, last_fn.off + last_fn.len - first_fn.off); - defer gpa.free(buffer); + fn enumConstValue( + wip_nav: *WipNav, + loaded_enum: InternPool.LoadedEnumType, + abbrev_code: struct { + sdata: AbbrevCode, + udata: AbbrevCode, + block: AbbrevCode, + }, + field_index: usize, + ) UpdateError!void { + const zcu = wip_nav.pt.zcu; + const ip = &zcu.intern_pool; + const diw = wip_nav.debug_info.writer(wip_nav.dwarf.gpa); + const signedness = switch (loaded_enum.tag_ty) { + .comptime_int_type => .signed, + else => Type.fromInterned(loaded_enum.tag_ty).intInfo(zcu).signedness, + }; + if (loaded_enum.values.len > 0) { + var big_int_space: InternPool.Key.Int.Storage.BigIntSpace = undefined; + const big_int = ip.indexToKey(loaded_enum.values.get(ip)[field_index]).int.storage.toBigInt(&big_int_space); + const bits = @max(1, big_int.bitCountTwosCompForSignedness(signedness)); + if (bits <= 64) { + try wip_nav.abbrevCode(switch (signedness) { + .signed => abbrev_code.sdata, + .unsigned => abbrev_code.udata, + }); + try wip_nav.debug_info.ensureUnusedCapacity(wip_nav.dwarf.gpa, std.math.divCeil(usize, bits, 7) catch unreachable); + var bit: usize = 0; + var carry: u1 = 1; + while (bit < bits) : (bit += 7) { + const limb_bits = @typeInfo(std.math.big.Limb).Int.bits; + const limb_index = bit / limb_bits; + const limb_shift: std.math.Log2Int(std.math.big.Limb) = @intCast(bit % limb_bits); + const low_abs_part: u7 = @truncate(big_int.limbs[limb_index] >> limb_shift); + const abs_part = if (limb_shift > limb_bits - 7 and limb_index + 1 < big_int.limbs.len) abs_part: { + const high_abs_part: u7 = @truncate(big_int.limbs[limb_index + 1] << -%limb_shift); + break :abs_part high_abs_part | low_abs_part; + } else low_abs_part; + const twos_comp_part = if (big_int.positive) abs_part else twos_comp_part: { + const twos_comp_part, carry = @addWithOverflow(~abs_part, carry); + break :twos_comp_part twos_comp_part; + }; + wip_nav.debug_info.appendAssumeCapacity(@as(u8, if (bit + 7 < bits) 0x80 else 0x00) | twos_comp_part); + } + } else { + try wip_nav.abbrevCode(abbrev_code.block); + const bytes = Type.fromInterned(loaded_enum.tag_ty).abiSize(wip_nav.pt); + try uleb128(diw, bytes); + big_int.writeTwosComplement(try wip_nav.debug_info.addManyAsSlice(wip_nav.dwarf.gpa, @intCast(bytes)), wip_nav.dwarf.endian); + } + } else switch (signedness) { + .signed => { + try wip_nav.abbrevCode(abbrev_code.sdata); + try sleb128(diw, field_index); + }, + .unsigned => { + try wip_nav.abbrevCode(abbrev_code.udata); + try uleb128(diw, field_index); + }, + } + } + + fn flush(wip_nav: *WipNav) UpdateError!void { + while (wip_nav.pending_types.popOrNull()) |ty| try wip_nav.dwarf.updateType(wip_nav.pt, ty, &wip_nav.pending_types); + } +}; - if (self.bin_file.cast(.elf)) |elf_file| { - const shdr_index = elf_file.debug_line_section_index.?; - const needed_size = elf_file.shdrs.items[shdr_index].sh_size + delta; - try elf_file.growNonAllocSection(shdr_index, needed_size, 1, true); - const file_pos = elf_file.shdrs.items[shdr_index].sh_offset + first_fn.off; +/// When allocating, the ideal_capacity is calculated by +/// actual_capacity + (actual_capacity / ideal_factor) +const ideal_factor = 3; - const amt = try elf_file.base.file.?.preadAll(buffer, file_pos); - if (amt != buffer.len) return error.InputOutput; +fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { + return actual_size +| (actual_size / ideal_factor); +} - try elf_file.base.file.?.pwriteAll(buffer, file_pos + delta); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - const sect_index = macho_file.debug_line_sect_index.?; - const needed_size: u32 = @intCast(macho_file.sections.items(.header)[sect_index].size + delta); - try macho_file.growSection(sect_index, needed_size); - const file_pos = macho_file.sections.items(.header)[sect_index].offset + first_fn.off; +pub fn init(lf: *link.File, format: DW.Format) Dwarf { + const comp = lf.comp; + const gpa = comp.gpa; + const target = comp.root_mod.resolved_target.result; + return .{ + .gpa = gpa, + .bin_file = lf, + .format = format, + .address_size = switch (target.ptrBitWidth()) { + 0...32 => .@"32", + 33...64 => .@"64", + else => unreachable, + }, + .endian = target.cpu.arch.endian(), + + .mods = .{}, + .types = .{}, + .navs = .{}, + + .debug_abbrev = .{ .section = Section.init }, + .debug_aranges = .{ .section = Section.init }, + .debug_info = .{ .section = Section.init }, + .debug_line = .{ + .header = switch (target.cpu.arch) { + .x86_64, .aarch64 => .{ + .minimum_instruction_length = 1, + .maximum_operations_per_instruction = 1, + .default_is_stmt = true, + .line_base = -5, + .line_range = 14, + .opcode_base = DW.LNS.set_isa + 1, + }, + else => .{ + .minimum_instruction_length = 1, + .maximum_operations_per_instruction = 1, + .default_is_stmt = true, + .line_base = 0, + .line_range = 1, + .opcode_base = DW.LNS.set_isa + 1, + }, + }, + .section = Section.init, + }, + .debug_line_str = StringSection.init, + .debug_loclists = .{ .section = Section.init }, + .debug_rnglists = .{ .section = Section.init }, + .debug_str = StringSection.init, + }; +} - const amt = try macho_file.base.file.?.preadAll(buffer, file_pos); - if (amt != buffer.len) return error.InputOutput; +pub fn reloadSectionMetadata(dwarf: *Dwarf) void { + if (dwarf.bin_file.cast(.elf)) |elf_file| { + for ([_]*Section{ + &dwarf.debug_abbrev.section, + &dwarf.debug_aranges.section, + &dwarf.debug_info.section, + &dwarf.debug_line.section, + &dwarf.debug_line_str.section, + &dwarf.debug_loclists.section, + &dwarf.debug_rnglists.section, + &dwarf.debug_str.section, + }, [_]u32{ + elf_file.debug_abbrev_section_index.?, + elf_file.debug_aranges_section_index.?, + elf_file.debug_info_section_index.?, + elf_file.debug_line_section_index.?, + elf_file.debug_line_str_section_index.?, + elf_file.debug_loclists_section_index.?, + elf_file.debug_rnglists_section_index.?, + elf_file.debug_str_section_index.?, + }) |sec, section_index| { + const shdr = &elf_file.shdrs.items[section_index]; + sec.index = section_index; + sec.off = shdr.sh_offset; + sec.len = shdr.sh_size; + } + } else if (dwarf.bin_file.cast(.macho)) |macho_file| { + if (macho_file.d_sym) |*d_sym| { + for ([_]*Section{ + &dwarf.debug_abbrev.section, + &dwarf.debug_aranges.section, + &dwarf.debug_info.section, + &dwarf.debug_line.section, + &dwarf.debug_line_str.section, + &dwarf.debug_loclists.section, + &dwarf.debug_rnglists.section, + &dwarf.debug_str.section, + }, [_]u8{ + d_sym.debug_abbrev_section_index.?, + d_sym.debug_aranges_section_index.?, + d_sym.debug_info_section_index.?, + d_sym.debug_line_section_index.?, + d_sym.debug_line_str_section_index.?, + d_sym.debug_loclists_section_index.?, + d_sym.debug_rnglists_section_index.?, + d_sym.debug_str_section_index.?, + }) |sec, sect_index| { + const header = &d_sym.sections.items[sect_index]; + sec.index = sect_index; + sec.off = header.offset; + sec.len = header.size; + } + } else { + for ([_]*Section{ + &dwarf.debug_abbrev.section, + &dwarf.debug_aranges.section, + &dwarf.debug_info.section, + &dwarf.debug_line.section, + &dwarf.debug_line_str.section, + &dwarf.debug_loclists.section, + &dwarf.debug_rnglists.section, + &dwarf.debug_str.section, + }, [_]u8{ + macho_file.debug_abbrev_sect_index.?, + macho_file.debug_aranges_sect_index.?, + macho_file.debug_info_sect_index.?, + macho_file.debug_line_sect_index.?, + macho_file.debug_line_str_sect_index.?, + macho_file.debug_loclists_sect_index.?, + macho_file.debug_rnglists_sect_index.?, + macho_file.debug_str_sect_index.?, + }) |sec, sect_index| { + const header = &macho_file.sections.items(.header)[sect_index]; + sec.index = sect_index; + sec.off = header.offset; + sec.len = header.size; + } + } + } +} - try macho_file.base.file.?.pwriteAll(buffer, file_pos + delta); - } else { - const d_sym = macho_file.getDebugSymbols().?; - const sect_index = d_sym.debug_line_section_index.?; - const needed_size: u32 = @intCast(d_sym.getSection(sect_index).size + delta); - try d_sym.growSection(sect_index, needed_size, true, macho_file); - const file_pos = d_sym.getSection(sect_index).offset + first_fn.off; +pub fn initMetadata(dwarf: *Dwarf) UpdateError!void { + dwarf.reloadSectionMetadata(); - const amt = try d_sym.file.preadAll(buffer, file_pos); - if (amt != buffer.len) return error.InputOutput; + dwarf.debug_abbrev.section.pad_to_ideal = false; + assert(try dwarf.debug_abbrev.section.addUnit(DebugAbbrev.header_bytes, DebugAbbrev.trailer_bytes, dwarf) == DebugAbbrev.unit); + errdefer dwarf.debug_abbrev.section.popUnit(dwarf.gpa); + for (std.enums.values(AbbrevCode)) |abbrev_code| + assert(@intFromEnum(try dwarf.debug_abbrev.section.getUnit(DebugAbbrev.unit).addEntry(dwarf.gpa)) == @intFromEnum(abbrev_code)); - try d_sym.file.pwriteAll(buffer, file_pos + delta); - } - } else if (self.bin_file.cast(.wasm)) |wasm_file| { - _ = wasm_file; - // const debug_line = &wasm_file.getAtomPtr(wasm_file.debug_line_atom.?).code; - // { - // const src = debug_line.items[first_fn.off..]; - // @memcpy(buffer[0..src.len], src); - // } - // try debug_line.resize(self.allocator, debug_line.items.len + delta); - // @memcpy(debug_line.items[first_fn.off + delta ..][0..buffer.len], buffer); - } else unreachable; + dwarf.debug_aranges.section.pad_to_ideal = false; + dwarf.debug_aranges.section.alignment = InternPool.Alignment.fromNonzeroByteUnits(@intFromEnum(dwarf.address_size) * 2); - while (true) { - const src_fn = self.getAtomPtr(.src_fn, src_fn_index); - src_fn.off += delta; + dwarf.debug_line_str.section.pad_to_ideal = false; + assert(try dwarf.debug_line_str.section.addUnit(0, 0, dwarf) == StringSection.unit); + errdefer dwarf.debug_line_str.section.popUnit(dwarf.gpa); - if (src_fn.next_index) |next_index| { - src_fn_index = next_index; - } else break; - } - } + dwarf.debug_str.section.pad_to_ideal = false; + assert(try dwarf.debug_str.section.addUnit(0, 0, dwarf) == StringSection.unit); + errdefer dwarf.debug_str.section.popUnit(dwarf.gpa); - // Backpatch actual length of the debug line program - const init_len = self.getDebugLineProgramEnd().? - init_len_size; - switch (self.format) { - .dwarf32 => { - mem.writeInt(u32, di_buf.items[0..4], @intCast(init_len), target_endian); - }, - .dwarf64 => { - mem.writeInt(u64, di_buf.items[4..][0..8], init_len, target_endian); - }, - } + dwarf.debug_loclists.section.pad_to_ideal = false; - // We use NOPs because consumers empirically do not respect the header length field. - const jmp_amt = self.getDebugLineProgramOff().? - di_buf.items.len; - if (self.bin_file.cast(.elf)) |elf_file| { - const debug_line_sect = &elf_file.shdrs.items[elf_file.debug_line_section_index.?]; - const file_pos = debug_line_sect.sh_offset; - try pwriteDbgLineNops(elf_file.base.file.?, file_pos, 0, di_buf.items, jmp_amt); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - const debug_line_sect = macho_file.sections.items(.header)[macho_file.debug_line_sect_index.?]; - const file_pos = debug_line_sect.offset; - try pwriteDbgLineNops(macho_file.base.file.?, file_pos, 0, di_buf.items, jmp_amt); - } else { - const d_sym = macho_file.getDebugSymbols().?; - const debug_line_sect = d_sym.getSection(d_sym.debug_line_section_index.?); - const file_pos = debug_line_sect.offset; - try pwriteDbgLineNops(d_sym.file, file_pos, 0, di_buf.items, jmp_amt); - } - } else if (self.bin_file.cast(.wasm)) |wasm_file| { - _ = wasm_file; - // const debug_line = &wasm_file.getAtomPtr(wasm_file.debug_line_atom.?).code; - // writeDbgLineNopsBuffered(debug_line.items, 0, 0, di_buf.items, jmp_amt); - } else unreachable; + dwarf.debug_rnglists.section.pad_to_ideal = false; } -fn getDebugInfoOff(self: Dwarf) ?u32 { - const first_index = self.di_atom_first_index orelse return null; - const first = self.getAtom(.di_atom, first_index); - return first.off; +pub fn deinit(dwarf: *Dwarf) void { + const gpa = dwarf.gpa; + for (dwarf.mods.values()) |*mod_info| mod_info.deinit(gpa); + dwarf.mods.deinit(gpa); + dwarf.types.deinit(gpa); + dwarf.navs.deinit(gpa); + dwarf.debug_abbrev.section.deinit(gpa); + dwarf.debug_aranges.section.deinit(gpa); + dwarf.debug_info.section.deinit(gpa); + dwarf.debug_line.section.deinit(gpa); + dwarf.debug_line_str.deinit(gpa); + dwarf.debug_loclists.section.deinit(gpa); + dwarf.debug_rnglists.section.deinit(gpa); + dwarf.debug_str.deinit(gpa); + dwarf.* = undefined; } -fn getDebugInfoEnd(self: Dwarf) ?u32 { - const last_index = self.di_atom_last_index orelse return null; - const last = self.getAtom(.di_atom, last_index); - return last.off + last.len; +fn getUnit(dwarf: *Dwarf, mod: *Module) UpdateError!Unit.Index { + const mod_gop = try dwarf.mods.getOrPut(dwarf.gpa, mod); + const unit: Unit.Index = @enumFromInt(mod_gop.index); + if (!mod_gop.found_existing) { + errdefer _ = dwarf.mods.pop(); + mod_gop.value_ptr.* = .{ + .root_dir_path = undefined, + .dirs = .{}, + .files = .{}, + }; + errdefer mod_gop.value_ptr.dirs.deinit(dwarf.gpa); + try mod_gop.value_ptr.dirs.putNoClobber(dwarf.gpa, unit, {}); + assert(try dwarf.debug_aranges.section.addUnit( + DebugAranges.headerBytes(dwarf), + DebugAranges.trailerBytes(dwarf), + dwarf, + ) == unit); + errdefer dwarf.debug_aranges.section.popUnit(dwarf.gpa); + assert(try dwarf.debug_info.section.addUnit( + DebugInfo.headerBytes(dwarf), + DebugInfo.trailer_bytes, + dwarf, + ) == unit); + errdefer dwarf.debug_info.section.popUnit(dwarf.gpa); + assert(try dwarf.debug_line.section.addUnit( + DebugLine.headerBytes(dwarf, 5, 25), + DebugLine.trailer_bytes, + dwarf, + ) == unit); + errdefer dwarf.debug_line.section.popUnit(dwarf.gpa); + assert(try dwarf.debug_loclists.section.addUnit( + DebugLocLists.headerBytes(dwarf), + DebugLocLists.trailer_bytes, + dwarf, + ) == unit); + errdefer dwarf.debug_loclists.section.popUnit(dwarf.gpa); + assert(try dwarf.debug_rnglists.section.addUnit( + DebugRngLists.headerBytes(dwarf), + DebugRngLists.trailer_bytes, + dwarf, + ) == unit); + errdefer dwarf.debug_rnglists.section.popUnit(dwarf.gpa); + } + return unit; } -fn getDebugLineProgramOff(self: Dwarf) ?u32 { - const first_index = self.src_fn_first_index orelse return null; - const first = self.getAtom(.src_fn, first_index); - return first.off; +fn getUnitIfExists(dwarf: *const Dwarf, mod: *Module) ?Unit.Index { + return @enumFromInt(dwarf.mods.getIndex(mod) orelse return null); } -fn getDebugLineProgramEnd(self: Dwarf) ?u32 { - const last_index = self.src_fn_last_index orelse return null; - const last = self.getAtom(.src_fn, last_index); - return last.off + last.len; +fn getModInfo(dwarf: *Dwarf, unit: Unit.Index) *ModInfo { + return &dwarf.mods.values()[@intFromEnum(unit)]; } -/// Always 4 or 8 depending on whether this is 32-bit or 64-bit format. -fn ptrWidthBytes(self: Dwarf) u8 { - return switch (self.ptr_width) { - .p32 => 4, - .p64 => 8, +pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, sym_index: u32) UpdateError!?WipNav { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + + const nav = ip.getNav(nav_index); + log.debug("initWipNav({})", .{nav.fqn.fmt(ip)}); + + const inst_info = nav.srcInst(ip).resolveFull(ip).?; + const file = zcu.fileByIndex(inst_info.file); + + const unit = try dwarf.getUnit(file.mod); + const nav_gop = try dwarf.navs.getOrPut(dwarf.gpa, nav_index); + errdefer _ = dwarf.navs.pop(); + if (nav_gop.found_existing) { + for ([_]*Section{ + &dwarf.debug_aranges.section, + &dwarf.debug_info.section, + &dwarf.debug_line.section, + &dwarf.debug_loclists.section, + &dwarf.debug_rnglists.section, + }) |sec| sec.getUnit(unit).getEntry(nav_gop.value_ptr.*).clear(); + } else nav_gop.value_ptr.* = try dwarf.addCommonEntry(unit); + const nav_val = zcu.navValue(nav_index); + var wip_nav: WipNav = .{ + .dwarf = dwarf, + .pt = pt, + .unit = unit, + .entry = nav_gop.value_ptr.*, + .any_children = false, + .func = .none, + .func_sym_index = undefined, + .func_high_reloc = undefined, + .inlined_funcs = undefined, + .debug_info = .{}, + .debug_line = .{}, + .debug_loclists = .{}, + .pending_types = .{}, }; + errdefer wip_nav.deinit(); + + switch (ip.indexToKey(nav_val.toIntern())) { + else => { + assert(file.zir_loaded); + const decl_inst = file.zir.instructions.get(@intFromEnum(inst_info.inst)); + assert(decl_inst.tag == .declaration); + const tree = try file.getTree(dwarf.gpa); + const loc = tree.tokenLocation(0, tree.nodes.items(.main_token)[decl_inst.data.declaration.src_node]); + assert(loc.line == zcu.navSrcLine(nav_index)); + + const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { + const decl_extra = file.zir.extraData(Zir.Inst.Declaration, decl_inst.data.declaration.payload_index).data; + const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + break :parent .{ + parent_namespace_ptr.owner_type, + switch (decl_extra.name) { + .@"comptime", + .@"usingnamespace", + .unnamed_test, + .decltest, + => DW.ACCESS.private, + _ => if (decl_extra.name.isNamedTest(file.zir)) + DW.ACCESS.private + else if (parent_namespace_ptr.pub_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.public + else if (parent_namespace_ptr.priv_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.private + else + unreachable, + }, + }; + } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; + + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(.decl_var); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try wip_nav.strp(nav.fqn.toSlice(ip)); + const ty = nav_val.typeOf(zcu); + const ty_reloc_index = try wip_nav.refForward(); + try wip_nav.exprloc(.{ .addr = .{ .sym = sym_index } }); + try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + ty.abiAlignment(pt).toByteUnits().?); + try diw.writeByte(@intFromBool(false)); + wip_nav.finishForward(ty_reloc_index); + try wip_nav.abbrevCode(.is_const); + try wip_nav.refType(ty); + }, + .variable => |variable| { + assert(file.zir_loaded); + const decl_inst = file.zir.instructions.get(@intFromEnum(inst_info.inst)); + assert(decl_inst.tag == .declaration); + const tree = try file.getTree(dwarf.gpa); + const loc = tree.tokenLocation(0, tree.nodes.items(.main_token)[decl_inst.data.declaration.src_node]); + assert(loc.line == zcu.navSrcLine(nav_index)); + + const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { + const decl_extra = file.zir.extraData(Zir.Inst.Declaration, decl_inst.data.declaration.payload_index).data; + const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + break :parent .{ + parent_namespace_ptr.owner_type, + switch (decl_extra.name) { + .@"comptime", + .@"usingnamespace", + .unnamed_test, + .decltest, + => DW.ACCESS.private, + _ => if (decl_extra.name.isNamedTest(file.zir)) + DW.ACCESS.private + else if (parent_namespace_ptr.pub_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.public + else if (parent_namespace_ptr.priv_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.private + else + unreachable, + }, + }; + } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; + + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(.decl_var); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try wip_nav.strp(nav.fqn.toSlice(ip)); + const ty = Type.fromInterned(variable.ty); + try wip_nav.refType(ty); + const addr: Loc = .{ .addr = .{ .sym = sym_index } }; + try wip_nav.exprloc(if (variable.is_threadlocal) .{ .form_tls_address = &addr } else addr); + try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + ty.abiAlignment(pt).toByteUnits().?); + try diw.writeByte(@intFromBool(false)); + }, + .func => |func| { + assert(file.zir_loaded); + const decl_inst = file.zir.instructions.get(@intFromEnum(inst_info.inst)); + assert(decl_inst.tag == .declaration); + const tree = try file.getTree(dwarf.gpa); + const loc = tree.tokenLocation(0, tree.nodes.items(.main_token)[decl_inst.data.declaration.src_node]); + assert(loc.line == zcu.navSrcLine(nav_index)); + + const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { + const decl_extra = file.zir.extraData(Zir.Inst.Declaration, decl_inst.data.declaration.payload_index).data; + const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + break :parent .{ + parent_namespace_ptr.owner_type, + switch (decl_extra.name) { + .@"comptime", + .@"usingnamespace", + .unnamed_test, + .decltest, + => DW.ACCESS.private, + _ => if (decl_extra.name.isNamedTest(file.zir)) + DW.ACCESS.private + else if (parent_namespace_ptr.pub_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.public + else if (parent_namespace_ptr.priv_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.private + else + unreachable, + }, + }; + } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; + + const func_type = ip.indexToKey(func.ty).func_type; + wip_nav.func = nav_val.toIntern(); + wip_nav.func_sym_index = sym_index; + wip_nav.inlined_funcs = .{}; + + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(.decl_func); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try wip_nav.strp(nav.fqn.toSlice(ip)); + try wip_nav.refType(Type.fromInterned(func_type.return_type)); + const external_relocs = &dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).external_relocs; + try external_relocs.ensureUnusedCapacity(dwarf.gpa, 2); + external_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(wip_nav.debug_info.items.len), + .target_sym = sym_index, + }); + try diw.writeByteNTimes(0, @intFromEnum(dwarf.address_size)); + wip_nav.func_high_reloc = @intCast(external_relocs.items.len); + external_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(wip_nav.debug_info.items.len), + .target_sym = sym_index, + .target_off = undefined, + }); + try diw.writeByteNTimes(0, @intFromEnum(dwarf.address_size)); + try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + target_info.defaultFunctionAlignment(file.mod.resolved_target.result).toByteUnits().?); + try diw.writeByte(@intFromBool(false)); + try diw.writeByte(@intFromBool(func_type.return_type == .noreturn_type)); + + const dlw = wip_nav.debug_line.writer(dwarf.gpa); + try dlw.writeByte(DW.LNS.extended_op); + if (dwarf.incremental()) { + try uleb128(dlw, 1 + dwarf.sectionOffsetBytes()); + try dlw.writeByte(DW.LNE.ZIG_set_decl); + try dwarf.debug_line.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).cross_section_relocs.append(dwarf.gpa, .{ + .source_off = @intCast(wip_nav.debug_line.items.len), + .target_sec = .debug_info, + .target_unit = wip_nav.unit, + .target_entry = wip_nav.entry.toOptional(), + }); + try dlw.writeByteNTimes(0, dwarf.sectionOffsetBytes()); + + try dlw.writeByte(DW.LNS.set_column); + try uleb128(dlw, func.lbrace_column + 1); + + try wip_nav.advancePCAndLine(func.lbrace_line, 0); + } else { + try uleb128(dlw, 1 + @intFromEnum(dwarf.address_size)); + try dlw.writeByte(DW.LNE.set_address); + try dwarf.debug_line.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).external_relocs.append(dwarf.gpa, .{ + .source_off = @intCast(wip_nav.debug_line.items.len), + .target_sym = sym_index, + }); + try dlw.writeByteNTimes(0, @intFromEnum(dwarf.address_size)); + + const file_gop = try dwarf.getModInfo(unit).files.getOrPut(dwarf.gpa, inst_info.file); + try dlw.writeByte(DW.LNS.set_file); + try uleb128(dlw, file_gop.index); + + try dlw.writeByte(DW.LNS.set_column); + try uleb128(dlw, func.lbrace_column + 1); + + try wip_nav.advancePCAndLine(@intCast(loc.line + func.lbrace_line), 0); + } + }, + } + return wip_nav; } -fn dbgLineNeededHeaderBytes(self: Dwarf, dirs: []const []const u8, files: []const []const u8) u32 { - var size: usize = switch (self.format) { // length field - .dwarf32 => 4, - .dwarf64 => 12, - }; - size += @sizeOf(u16); // version field - size += switch (self.format) { // offset to end-of-header - .dwarf32 => 4, - .dwarf64 => 8, - }; - size += 18; // opcodes +pub fn finishWipNav( + dwarf: *Dwarf, + pt: Zcu.PerThread, + nav_index: InternPool.Nav.Index, + sym: struct { index: u32, addr: u64, size: u64 }, + wip_nav: *WipNav, +) UpdateError!void { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const nav = ip.getNav(nav_index); + log.debug("finishWipNav({})", .{nav.fqn.fmt(ip)}); + + if (wip_nav.func != .none) { + const external_relocs = &dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).external_relocs; + external_relocs.items[wip_nav.func_high_reloc].target_off = sym.size; + if (wip_nav.any_children) { + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } else std.leb.writeUnsignedFixed( + AbbrevCode.decl_bytes, + wip_nav.debug_info.items[0..AbbrevCode.decl_bytes], + try dwarf.refAbbrevCode(.decl_empty_func), + ); + + var aranges_entry = [1]u8{0} ** (8 + 8); + try dwarf.debug_aranges.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).external_relocs.append(dwarf.gpa, .{ + .target_sym = sym.index, + }); + dwarf.writeInt(aranges_entry[0..@intFromEnum(dwarf.address_size)], 0); + dwarf.writeInt(aranges_entry[@intFromEnum(dwarf.address_size)..][0..@intFromEnum(dwarf.address_size)], sym.size); + + @memset(aranges_entry[0..@intFromEnum(dwarf.address_size)], 0); + try dwarf.debug_aranges.section.replaceEntry( + wip_nav.unit, + wip_nav.entry, + dwarf, + aranges_entry[0 .. @intFromEnum(dwarf.address_size) * 2], + ); - for (dirs) |dir| { // include dirs - size += dir.len + 1; + try dwarf.debug_rnglists.section.getUnit(wip_nav.unit).getEntry(wip_nav.entry).external_relocs.appendSlice(dwarf.gpa, &.{ + .{ + .source_off = 1, + .target_sym = sym.index, + }, + .{ + .source_off = 1 + @intFromEnum(dwarf.address_size), + .target_sym = sym.index, + .target_off = sym.size, + }, + }); + try dwarf.debug_rnglists.section.replaceEntry( + wip_nav.unit, + wip_nav.entry, + dwarf, + ([1]u8{DW.RLE.start_end} ++ [1]u8{0} ** (8 + 8))[0 .. 1 + @intFromEnum(dwarf.address_size) + @intFromEnum(dwarf.address_size)], + ); } - size += 1; // include dirs sentinel - for (files) |file| { // file names - size += file.len + 1 + 1 + 1 + 1; + try dwarf.debug_info.section.replaceEntry(wip_nav.unit, wip_nav.entry, dwarf, wip_nav.debug_info.items); + if (wip_nav.debug_line.items.len > 0) { + const dlw = wip_nav.debug_line.writer(dwarf.gpa); + try dlw.writeByte(DW.LNS.extended_op); + try uleb128(dlw, 1); + try dlw.writeByte(DW.LNE.end_sequence); + try dwarf.debug_line.section.replaceEntry(wip_nav.unit, wip_nav.entry, dwarf, wip_nav.debug_line.items); } - size += 1; // file names sentinel + try dwarf.debug_loclists.section.replaceEntry(wip_nav.unit, wip_nav.entry, dwarf, wip_nav.debug_loclists.items); - return @intCast(size); + try wip_nav.flush(); } -/// The reloc offset for the line offset of a function from the previous function's line. -/// It's a fixed-size 4-byte ULEB128. -fn getRelocDbgLineOff(self: Dwarf) usize { - return dbg_line_vaddr_reloc_index + self.ptrWidthBytes() + 1; -} +pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) UpdateError!void { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const nav_val = zcu.navValue(nav_index); -fn getRelocDbgFileIndex(self: Dwarf) usize { - return self.getRelocDbgLineOff() + 5; -} + const nav = ip.getNav(nav_index); + log.debug("updateComptimeNav({})", .{nav.fqn.fmt(ip)}); + + const inst_info = nav.srcInst(ip).resolveFull(ip).?; + const file = zcu.fileByIndex(inst_info.file); + assert(file.zir_loaded); + const decl_inst = file.zir.instructions.get(@intFromEnum(inst_info.inst)); + assert(decl_inst.tag == .declaration); + const tree = try file.getTree(dwarf.gpa); + const loc = tree.tokenLocation(0, tree.nodes.items(.main_token)[decl_inst.data.declaration.src_node]); + assert(loc.line == zcu.navSrcLine(nav_index)); + + var wip_nav: WipNav = .{ + .dwarf = dwarf, + .pt = pt, + .unit = try dwarf.getUnit(file.mod), + .entry = undefined, + .any_children = false, + .func = .none, + .func_sym_index = undefined, + .func_high_reloc = undefined, + .inlined_funcs = undefined, + .debug_info = .{}, + .debug_line = .{}, + .debug_loclists = .{}, + .pending_types = .{}, + }; + defer wip_nav.deinit(); + + const nav_gop = try dwarf.navs.getOrPut(dwarf.gpa, nav_index); + errdefer _ = dwarf.navs.pop(); + switch (ip.indexToKey(nav_val.toIntern())) { + .func => |func| { + if (nav_gop.found_existing) { + const unit_ptr = dwarf.debug_info.section.getUnit(wip_nav.unit); + const entry_ptr = unit_ptr.getEntry(nav_gop.value_ptr.*); + if (entry_ptr.len >= AbbrevCode.decl_bytes) { + var abbrev_code_buf: [AbbrevCode.decl_bytes]u8 = undefined; + if (try dwarf.getFile().?.preadAll( + &abbrev_code_buf, + dwarf.debug_info.section.off + unit_ptr.off + unit_ptr.header_len + entry_ptr.off, + ) != abbrev_code_buf.len) return error.InputOutput; + var abbrev_code_fbs = std.io.fixedBufferStream(&abbrev_code_buf); + const abbrev_code: AbbrevCode = @enumFromInt( + std.leb.readUleb128(@typeInfo(AbbrevCode).Enum.tag_type, abbrev_code_fbs.reader()) catch unreachable, + ); + switch (abbrev_code) { + else => unreachable, + .decl_func, .decl_empty_func => return, + .decl_func_generic, .decl_empty_func_generic => {}, + } + } + entry_ptr.clear(); + } else nav_gop.value_ptr.* = try dwarf.addCommonEntry(wip_nav.unit); + wip_nav.entry = nav_gop.value_ptr.*; + + const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { + const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + break :parent .{ + parent_namespace_ptr.owner_type, + if (parent_namespace_ptr.pub_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.public + else if (parent_namespace_ptr.priv_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.private + else + unreachable, + }; + } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; + + const func_type = ip.indexToKey(func.ty).func_type; + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(if (func_type.param_types.len > 0 or func_type.is_var_args) + .decl_func_generic + else + .decl_empty_func_generic); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try wip_nav.refType(Type.fromInterned(func_type.return_type)); + if (func_type.param_types.len > 0 or func_type.is_var_args) { + for (0..func_type.param_types.len) |param_index| { + try wip_nav.abbrevCode(.func_type_param); + try wip_nav.refType(Type.fromInterned(func_type.param_types.get(ip)[param_index])); + } + if (func_type.is_var_args) try wip_nav.abbrevCode(.is_var_args); + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } + }, + .struct_type => done: { + const loaded_struct = ip.loadStructType(nav_val.toIntern()); + + const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { + const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + break :parent .{ + parent_namespace_ptr.owner_type, + if (parent_namespace_ptr.pub_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.public + else if (parent_namespace_ptr.priv_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.private + else + unreachable, + }; + } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; + + decl_struct: { + if (loaded_struct.zir_index == .none) break :decl_struct; + + const value_inst = value_inst: { + const decl_extra = file.zir.extraData(Zir.Inst.Declaration, decl_inst.data.declaration.payload_index); + const decl_value_body = decl_extra.data.getBodies(@intCast(decl_extra.end), file.zir).value_body; + const break_inst = file.zir.instructions.get(@intFromEnum(decl_value_body[decl_value_body.len - 1])); + if (break_inst.tag != .break_inline) break :value_inst null; + assert(file.zir.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data.block_inst == inst_info.inst); + var value_inst = break_inst.data.@"break".operand.toIndex(); + while (value_inst) |value_inst_index| switch (file.zir.instructions.items(.tag)[@intFromEnum(value_inst_index)]) { + else => break, + .as_node => value_inst = file.zir.extraData( + Zir.Inst.As, + file.zir.instructions.items(.data)[@intFromEnum(value_inst_index)].pl_node.payload_index, + ).data.operand.toIndex(), + }; + break :value_inst value_inst; + }; + const type_inst_info = loaded_struct.zir_index.unwrap().?.resolveFull(ip).?; + if (type_inst_info.inst != value_inst) break :decl_struct; -fn getRelocDbgInfoSubprogramHighPC(self: Dwarf) u32 { - return dbg_info_low_pc_reloc_index + self.ptrWidthBytes(); -} + const type_gop = try dwarf.types.getOrPut(dwarf.gpa, nav_val.toIntern()); + if (type_gop.found_existing) { + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(type_gop.value_ptr.*).clear(); + nav_gop.value_ptr.* = type_gop.value_ptr.*; + } else { + if (nav_gop.found_existing) + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(nav_gop.value_ptr.*).clear() + else + nav_gop.value_ptr.* = try dwarf.addCommonEntry(wip_nav.unit); + type_gop.value_ptr.* = nav_gop.value_ptr.*; + } + wip_nav.entry = nav_gop.value_ptr.*; + const diw = wip_nav.debug_info.writer(dwarf.gpa); + + switch (loaded_struct.layout) { + .auto, .@"extern" => { + try wip_nav.abbrevCode(if (loaded_struct.field_types.len == 0) .decl_namespace_struct else .decl_struct); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + if (loaded_struct.field_types.len == 0) try diw.writeByte(@intFromBool(false)) else { + try uleb128(diw, nav_val.toType().abiSize(pt)); + try uleb128(diw, nav_val.toType().abiAlignment(pt).toByteUnits().?); + for (0..loaded_struct.field_types.len) |field_index| { + const is_comptime = loaded_struct.fieldIsComptime(ip, field_index); + try wip_nav.abbrevCode(if (is_comptime) .struct_field_comptime else .struct_field); + if (loaded_struct.fieldName(ip, field_index).unwrap()) |field_name| try wip_nav.strp(field_name.toSlice(ip)) else { + const field_name = try std.fmt.allocPrint(dwarf.gpa, "{d}", .{field_index}); + defer dwarf.gpa.free(field_name); + try wip_nav.strp(field_name); + } + const field_type = Type.fromInterned(loaded_struct.field_types.get(ip)[field_index]); + try wip_nav.refType(field_type); + if (!is_comptime) { + try uleb128(diw, loaded_struct.offsets.get(ip)[field_index]); + try uleb128(diw, loaded_struct.fieldAlign(ip, field_index).toByteUnits() orelse + field_type.abiAlignment(pt).toByteUnits().?); + } + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } + }, + .@"packed" => { + try wip_nav.abbrevCode(.decl_packed_struct); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try wip_nav.refType(Type.fromInterned(loaded_struct.backingIntTypeUnordered(ip))); + var field_bit_offset: u16 = 0; + for (0..loaded_struct.field_types.len) |field_index| { + try wip_nav.abbrevCode(.packed_struct_field); + try wip_nav.strp(loaded_struct.fieldName(ip, field_index).unwrap().?.toSlice(ip)); + const field_type = Type.fromInterned(loaded_struct.field_types.get(ip)[field_index]); + try wip_nav.refType(field_type); + try uleb128(diw, field_bit_offset); + field_bit_offset += @intCast(field_type.bitSize(pt)); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + } + break :done; + } -fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { - return actual_size +| (actual_size / ideal_factor); -} + if (nav_gop.found_existing) + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(nav_gop.value_ptr.*).clear() + else + nav_gop.value_ptr.* = try dwarf.addCommonEntry(wip_nav.unit); + wip_nav.entry = nav_gop.value_ptr.*; + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(.decl_alias); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try wip_nav.refType(nav_val.toType()); + }, + .enum_type => done: { + const loaded_enum = ip.loadEnumType(nav_val.toIntern()); + + const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { + const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + break :parent .{ + parent_namespace_ptr.owner_type, + if (parent_namespace_ptr.pub_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.public + else if (parent_namespace_ptr.priv_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.private + else + unreachable, + }; + } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; + + decl_enum: { + if (loaded_enum.zir_index == .none) break :decl_enum; + + const value_inst = value_inst: { + const decl_extra = file.zir.extraData(Zir.Inst.Declaration, decl_inst.data.declaration.payload_index); + const decl_value_body = decl_extra.data.getBodies(@intCast(decl_extra.end), file.zir).value_body; + const break_inst = file.zir.instructions.get(@intFromEnum(decl_value_body[decl_value_body.len - 1])); + if (break_inst.tag != .break_inline) break :value_inst null; + assert(file.zir.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data.block_inst == inst_info.inst); + var value_inst = break_inst.data.@"break".operand.toIndex(); + while (value_inst) |value_inst_index| switch (file.zir.instructions.items(.tag)[@intFromEnum(value_inst_index)]) { + else => break, + .as_node => value_inst = file.zir.extraData( + Zir.Inst.As, + file.zir.instructions.items(.data)[@intFromEnum(value_inst_index)].pl_node.payload_index, + ).data.operand.toIndex(), + }; + break :value_inst value_inst; + }; + const type_inst_info = loaded_enum.zir_index.unwrap().?.resolveFull(ip).?; + if (type_inst_info.inst != value_inst) break :decl_enum; -pub fn flushModule(self: *Dwarf, pt: Zcu.PerThread) !void { - const comp = self.bin_file.comp; - const target = comp.root_mod.resolved_target.result; + const type_gop = try dwarf.types.getOrPut(dwarf.gpa, nav_val.toIntern()); + if (type_gop.found_existing) { + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(type_gop.value_ptr.*).clear(); + nav_gop.value_ptr.* = type_gop.value_ptr.*; + } else { + if (nav_gop.found_existing) + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(nav_gop.value_ptr.*).clear() + else + nav_gop.value_ptr.* = try dwarf.addCommonEntry(wip_nav.unit); + type_gop.value_ptr.* = nav_gop.value_ptr.*; + } + wip_nav.entry = nav_gop.value_ptr.*; + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(if (loaded_enum.names.len > 0) .decl_enum else .decl_empty_enum); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try wip_nav.refType(Type.fromInterned(loaded_enum.tag_ty)); + for (0..loaded_enum.names.len) |field_index| { + try wip_nav.enumConstValue(loaded_enum, .{ + .sdata = .signed_enum_field, + .udata = .unsigned_enum_field, + .block = .big_enum_field, + }, field_index); + try wip_nav.strp(loaded_enum.names.get(ip)[field_index].toSlice(ip)); + } + if (loaded_enum.names.len > 0) try uleb128(diw, @intFromEnum(AbbrevCode.null)); + break :done; + } - if (self.global_abbrev_relocs.items.len > 0) { - const gpa = self.allocator; - var arena_alloc = std.heap.ArenaAllocator.init(gpa); - defer arena_alloc.deinit(); - const arena = arena_alloc.allocator(); - - var dbg_info_buffer = std.ArrayList(u8).init(arena); - try addDbgInfoErrorSetNames( - pt, - Type.anyerror, - pt.zcu.intern_pool.global_error_set.getNamesFromMainThread(), - target, - &dbg_info_buffer, - ); + if (nav_gop.found_existing) + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(nav_gop.value_ptr.*).clear() + else + nav_gop.value_ptr.* = try dwarf.addCommonEntry(wip_nav.unit); + wip_nav.entry = nav_gop.value_ptr.*; + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(.decl_alias); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try wip_nav.refType(nav_val.toType()); + }, + .union_type => done: { + const loaded_union = ip.loadUnionType(nav_val.toIntern()); + + const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { + const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + break :parent .{ + parent_namespace_ptr.owner_type, + if (parent_namespace_ptr.pub_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.public + else if (parent_namespace_ptr.priv_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.private + else + unreachable, + }; + } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; + + decl_union: { + const value_inst = value_inst: { + const decl_extra = file.zir.extraData(Zir.Inst.Declaration, decl_inst.data.declaration.payload_index); + const decl_value_body = decl_extra.data.getBodies(@intCast(decl_extra.end), file.zir).value_body; + const break_inst = file.zir.instructions.get(@intFromEnum(decl_value_body[decl_value_body.len - 1])); + if (break_inst.tag != .break_inline) break :value_inst null; + assert(file.zir.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data.block_inst == inst_info.inst); + var value_inst = break_inst.data.@"break".operand.toIndex(); + while (value_inst) |value_inst_index| switch (file.zir.instructions.items(.tag)[@intFromEnum(value_inst_index)]) { + else => break, + .as_node => value_inst = file.zir.extraData( + Zir.Inst.As, + file.zir.instructions.items(.data)[@intFromEnum(value_inst_index)].pl_node.payload_index, + ).data.operand.toIndex(), + }; + break :value_inst value_inst; + }; + const type_inst_info = loaded_union.zir_index.resolveFull(ip).?; + if (type_inst_info.inst != value_inst) break :decl_union; - const di_atom_index = try self.createAtom(.di_atom); - log.debug("updateNavDebugInfoAllocation in flushModule", .{}); - try self.updateNavDebugInfoAllocation(di_atom_index, @intCast(dbg_info_buffer.items.len)); - log.debug("writeNavDebugInfo in flushModule", .{}); - try self.writeNavDebugInfo(di_atom_index, dbg_info_buffer.items); - - const file_pos = if (self.bin_file.cast(.elf)) |elf_file| pos: { - const debug_info_sect = &elf_file.shdrs.items[elf_file.debug_info_section_index.?]; - break :pos debug_info_sect.sh_offset; - } else if (self.bin_file.cast(.macho)) |macho_file| pos: { - if (macho_file.base.isRelocatable()) { - const debug_info_sect = &macho_file.sections.items(.header)[macho_file.debug_info_sect_index.?]; - break :pos debug_info_sect.offset; - } else { - const d_sym = macho_file.getDebugSymbols().?; - const debug_info_sect = d_sym.getSectionPtr(d_sym.debug_info_section_index.?); - break :pos debug_info_sect.offset; + const type_gop = try dwarf.types.getOrPut(dwarf.gpa, nav_val.toIntern()); + if (type_gop.found_existing) { + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(type_gop.value_ptr.*).clear(); + nav_gop.value_ptr.* = type_gop.value_ptr.*; + } else { + if (nav_gop.found_existing) + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(nav_gop.value_ptr.*).clear() + else + nav_gop.value_ptr.* = try dwarf.addCommonEntry(wip_nav.unit); + type_gop.value_ptr.* = nav_gop.value_ptr.*; + } + wip_nav.entry = nav_gop.value_ptr.*; + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(.decl_union); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + const union_layout = pt.getUnionLayout(loaded_union); + try uleb128(diw, union_layout.abi_size); + try uleb128(diw, union_layout.abi_align.toByteUnits().?); + const loaded_tag = loaded_union.loadTagType(ip); + if (loaded_union.hasTag(ip)) { + try wip_nav.abbrevCode(.tagged_union); + try wip_nav.infoSectionOffset( + .debug_info, + wip_nav.unit, + wip_nav.entry, + @intCast(wip_nav.debug_info.items.len + dwarf.sectionOffsetBytes()), + ); + { + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("tag"); + try wip_nav.refType(Type.fromInterned(loaded_union.enum_tag_ty)); + try uleb128(diw, union_layout.tagOffset()); + + for (0..loaded_union.field_types.len) |field_index| { + try wip_nav.enumConstValue(loaded_tag, .{ + .sdata = .signed_tagged_union_field, + .udata = .unsigned_tagged_union_field, + .block = .big_tagged_union_field, + }, field_index); + { + try wip_nav.abbrevCode(.struct_field); + try wip_nav.strp(loaded_tag.names.get(ip)[field_index].toSlice(ip)); + const field_type = Type.fromInterned(loaded_union.field_types.get(ip)[field_index]); + try wip_nav.refType(field_type); + try uleb128(diw, union_layout.payloadOffset()); + try uleb128(diw, loaded_union.fieldAlign(ip, field_index).toByteUnits() orelse + if (field_type.isNoReturn(zcu)) 1 else field_type.abiAlignment(pt).toByteUnits().?); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + + if (ip.indexToKey(loaded_union.enum_tag_ty).enum_type == .generated_tag) + try wip_nav.pending_types.append(dwarf.gpa, loaded_union.enum_tag_ty); + } else for (0..loaded_union.field_types.len) |field_index| { + try wip_nav.abbrevCode(.untagged_union_field); + try wip_nav.strp(loaded_tag.names.get(ip)[field_index].toSlice(ip)); + const field_type = Type.fromInterned(loaded_union.field_types.get(ip)[field_index]); + try wip_nav.refType(field_type); + try uleb128(diw, loaded_union.fieldAlign(ip, field_index).toByteUnits() orelse + field_type.abiAlignment(pt).toByteUnits().?); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + break :done; } - } else if (self.bin_file.cast(.wasm)) |_| - // for wasm, the offset is always 0 as we write to memory first - 0 - else - unreachable; - var buf: [@sizeOf(u32)]u8 = undefined; - mem.writeInt(u32, &buf, self.getAtom(.di_atom, di_atom_index).off, target.cpu.arch.endian()); + if (nav_gop.found_existing) + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(nav_gop.value_ptr.*).clear() + else + nav_gop.value_ptr.* = try dwarf.addCommonEntry(wip_nav.unit); + wip_nav.entry = nav_gop.value_ptr.*; + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(.decl_alias); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try wip_nav.refType(nav_val.toType()); + }, + .opaque_type => done: { + const loaded_opaque = ip.loadOpaqueType(nav_val.toIntern()); + + const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { + const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + break :parent .{ + parent_namespace_ptr.owner_type, + if (parent_namespace_ptr.pub_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.public + else if (parent_namespace_ptr.priv_decls.containsContext(nav_index, .{ .zcu = zcu })) + DW.ACCESS.private + else + unreachable, + }; + } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; + + decl_opaque: { + const value_inst = value_inst: { + const decl_extra = file.zir.extraData(Zir.Inst.Declaration, decl_inst.data.declaration.payload_index); + const decl_value_body = decl_extra.data.getBodies(@intCast(decl_extra.end), file.zir).value_body; + const break_inst = file.zir.instructions.get(@intFromEnum(decl_value_body[decl_value_body.len - 1])); + if (break_inst.tag != .break_inline) break :value_inst null; + assert(file.zir.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data.block_inst == inst_info.inst); + var value_inst = break_inst.data.@"break".operand.toIndex(); + while (value_inst) |value_inst_index| switch (file.zir.instructions.items(.tag)[@intFromEnum(value_inst_index)]) { + else => break, + .as_node => value_inst = file.zir.extraData( + Zir.Inst.As, + file.zir.instructions.items(.data)[@intFromEnum(value_inst_index)].pl_node.payload_index, + ).data.operand.toIndex(), + }; + break :value_inst value_inst; + }; + const type_inst_info = loaded_opaque.zir_index.resolveFull(ip).?; + if (type_inst_info.inst != value_inst) break :decl_opaque; - while (self.global_abbrev_relocs.popOrNull()) |reloc| { - const atom = self.getAtom(.di_atom, reloc.atom_index); - if (self.bin_file.cast(.elf)) |elf_file| { - try elf_file.base.file.?.pwriteAll(&buf, file_pos + atom.off + reloc.offset); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - try macho_file.base.file.?.pwriteAll(&buf, file_pos + atom.off + reloc.offset); + const type_gop = try dwarf.types.getOrPut(dwarf.gpa, nav_val.toIntern()); + if (type_gop.found_existing) { + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(type_gop.value_ptr.*).clear(); + nav_gop.value_ptr.* = type_gop.value_ptr.*; } else { - const d_sym = macho_file.getDebugSymbols().?; - try d_sym.file.pwriteAll(&buf, file_pos + atom.off + reloc.offset); + if (nav_gop.found_existing) + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(nav_gop.value_ptr.*).clear() + else + nav_gop.value_ptr.* = try dwarf.addCommonEntry(wip_nav.unit); + type_gop.value_ptr.* = nav_gop.value_ptr.*; } - } else if (self.bin_file.cast(.wasm)) |wasm_file| { - _ = wasm_file; - // const debug_info = wasm_file.getAtomPtr(wasm_file.debug_info_atom.?).code; - // debug_info.items[atom.off + reloc.offset ..][0..buf.len].* = buf; - } else unreachable; - } + wip_nav.entry = nav_gop.value_ptr.*; + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(.decl_namespace_struct); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try diw.writeByte(@intFromBool(false)); + break :done; + } + + if (nav_gop.found_existing) + dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(nav_gop.value_ptr.*).clear() + else + nav_gop.value_ptr.* = try dwarf.addCommonEntry(wip_nav.unit); + wip_nav.entry = nav_gop.value_ptr.*; + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(.decl_alias); + try wip_nav.refType(Type.fromInterned(parent_type)); + assert(wip_nav.debug_info.items.len == DebugInfo.declEntryLineOff(dwarf)); + try diw.writeInt(u32, @intCast(loc.line + 1), dwarf.endian); + try uleb128(diw, loc.column + 1); + try diw.writeByte(accessibility); + try wip_nav.strp(nav.name.toSlice(ip)); + try wip_nav.refType(nav_val.toType()); + }, + else => { + _ = dwarf.navs.pop(); + return; + }, } + try dwarf.debug_info.section.replaceEntry(wip_nav.unit, wip_nav.entry, dwarf, wip_nav.debug_info.items); + try wip_nav.flush(); } -fn addDIFile(self: *Dwarf, zcu: *Zcu, nav_index: InternPool.Nav.Index) !u28 { - const file_scope = zcu.navFileScope(nav_index); - const gop = try self.di_files.getOrPut(self.allocator, file_scope); - if (!gop.found_existing) { - if (self.bin_file.cast(.elf)) |elf_file| { - elf_file.markDirty(elf_file.debug_line_section_index.?); - } else if (self.bin_file.cast(.macho)) |macho_file| { - if (macho_file.base.isRelocatable()) { - macho_file.markDirty(macho_file.debug_line_sect_index.?); +fn updateType( + dwarf: *Dwarf, + pt: Zcu.PerThread, + type_index: InternPool.Index, + pending_types: *std.ArrayListUnmanaged(InternPool.Index), +) UpdateError!void { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const ty = Type.fromInterned(type_index); + switch (type_index) { + .generic_poison_type => log.debug("updateType({s})", .{"anytype"}), + else => log.debug("updateType({})", .{ty.fmt(pt)}), + } + + var wip_nav: WipNav = .{ + .dwarf = dwarf, + .pt = pt, + .unit = .main, + .entry = dwarf.types.get(type_index).?, + .any_children = false, + .func = .none, + .func_sym_index = undefined, + .func_high_reloc = undefined, + .inlined_funcs = undefined, + .debug_info = .{}, + .debug_line = .{}, + .debug_loclists = .{}, + .pending_types = pending_types.*, + }; + defer { + pending_types.* = wip_nav.pending_types; + wip_nav.pending_types = .{}; + wip_nav.deinit(); + } + const diw = wip_nav.debug_info.writer(dwarf.gpa); + const name = switch (type_index) { + .generic_poison_type => "", + else => try std.fmt.allocPrint(dwarf.gpa, "{}", .{ty.fmt(pt)}), + }; + defer dwarf.gpa.free(name); + + switch (ip.indexToKey(type_index)) { + .int_type => |int_type| { + try wip_nav.abbrevCode(.numeric_type); + try wip_nav.strp(name); + try diw.writeByte(switch (int_type.signedness) { + inline .signed, .unsigned => |signedness| @field(DW.ATE, @tagName(signedness)), + }); + try uleb128(diw, int_type.bits); + try uleb128(diw, ty.abiSize(pt)); + try uleb128(diw, ty.abiAlignment(pt).toByteUnits().?); + }, + .ptr_type => |ptr_type| switch (ptr_type.flags.size) { + .One, .Many, .C => { + const ptr_child_type = Type.fromInterned(ptr_type.child); + try wip_nav.abbrevCode(.ptr_type); + try wip_nav.strp(name); + try uleb128(diw, ptr_type.flags.alignment.toByteUnits() orelse + ptr_child_type.abiAlignment(pt).toByteUnits().?); + try diw.writeByte(@intFromEnum(ptr_type.flags.address_space)); + if (ptr_type.flags.is_const or ptr_type.flags.is_volatile) try wip_nav.infoSectionOffset( + .debug_info, + wip_nav.unit, + wip_nav.entry, + @intCast(wip_nav.debug_info.items.len + dwarf.sectionOffsetBytes()), + ) else try wip_nav.refType(ptr_child_type); + if (ptr_type.flags.is_const) { + try wip_nav.abbrevCode(.is_const); + if (ptr_type.flags.is_volatile) try wip_nav.infoSectionOffset( + .debug_info, + wip_nav.unit, + wip_nav.entry, + @intCast(wip_nav.debug_info.items.len + dwarf.sectionOffsetBytes()), + ) else try wip_nav.refType(ptr_child_type); + } + if (ptr_type.flags.is_volatile) { + try wip_nav.abbrevCode(.is_volatile); + try wip_nav.refType(ptr_child_type); + } + }, + .Slice => { + try wip_nav.abbrevCode(.struct_type); + try wip_nav.strp(name); + try uleb128(diw, ty.abiSize(pt)); + try uleb128(diw, ty.abiAlignment(pt).toByteUnits().?); + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("ptr"); + const ptr_field_type = ty.slicePtrFieldType(zcu); + try wip_nav.refType(ptr_field_type); + try uleb128(diw, 0); + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("len"); + const len_field_type = Type.usize; + try wip_nav.refType(len_field_type); + try uleb128(diw, len_field_type.abiAlignment(pt).forward(ptr_field_type.abiSize(pt))); + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + }, + inline .array_type, .vector_type => |array_type, ty_tag| { + try wip_nav.abbrevCode(.array_type); + try wip_nav.strp(name); + try wip_nav.refType(Type.fromInterned(array_type.child)); + try diw.writeByte(@intFromBool(ty_tag == .vector_type)); + try wip_nav.abbrevCode(.array_index); + try wip_nav.refType(Type.usize); + try uleb128(diw, array_type.len); + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + .opt_type => |opt_child_type_index| { + const opt_child_type = Type.fromInterned(opt_child_type_index); + try wip_nav.abbrevCode(.union_type); + try wip_nav.strp(name); + try uleb128(diw, ty.abiSize(pt)); + try uleb128(diw, ty.abiAlignment(pt).toByteUnits().?); + if (opt_child_type.isNoReturn(zcu)) { + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("null"); + try wip_nav.refType(Type.null); + try uleb128(diw, 0); + } else { + try wip_nav.abbrevCode(.tagged_union); + try wip_nav.infoSectionOffset( + .debug_info, + wip_nav.unit, + wip_nav.entry, + @intCast(wip_nav.debug_info.items.len + dwarf.sectionOffsetBytes()), + ); + { + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("has_value"); + const repr: enum { unpacked, error_set, pointer } = switch (opt_child_type_index) { + .anyerror_type => .error_set, + else => switch (ip.indexToKey(opt_child_type_index)) { + else => .unpacked, + .error_set_type, .inferred_error_set_type => .error_set, + .ptr_type => |ptr_type| if (ptr_type.flags.is_allowzero) .unpacked else .pointer, + }, + }; + switch (repr) { + .unpacked => { + try wip_nav.refType(Type.bool); + try uleb128(diw, if (opt_child_type.hasRuntimeBits(pt)) + opt_child_type.abiSize(pt) + else + 0); + }, + .error_set => { + try wip_nav.refType(Type.fromInterned(try pt.intern(.{ .int_type = .{ + .signedness = .unsigned, + .bits = pt.zcu.errorSetBits(), + } }))); + try uleb128(diw, 0); + }, + .pointer => { + try wip_nav.refType(Type.usize); + try uleb128(diw, 0); + }, + } + + try wip_nav.abbrevCode(.unsigned_tagged_union_field); + try uleb128(diw, 0); + { + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("null"); + try wip_nav.refType(Type.null); + try uleb128(diw, 0); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + + try wip_nav.abbrevCode(.tagged_union_default_field); + { + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("?"); + try wip_nav.refType(opt_child_type); + try uleb128(diw, 0); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + .anyframe_type => unreachable, + .error_union_type => |error_union_type| { + const error_union_error_set_type = Type.fromInterned(error_union_type.error_set_type); + const error_union_payload_type = Type.fromInterned(error_union_type.payload_type); + const error_union_error_set_offset, const error_union_payload_offset = switch (error_union_type.payload_type) { + .generic_poison_type => .{ 0, 0 }, + else => .{ + codegen.errUnionErrorOffset(error_union_payload_type, pt), + codegen.errUnionPayloadOffset(error_union_payload_type, pt), + }, + }; + + try wip_nav.abbrevCode(.union_type); + try wip_nav.strp(name); + if (error_union_type.error_set_type != .generic_poison_type and + error_union_type.payload_type != .generic_poison_type) + { + try uleb128(diw, ty.abiSize(pt)); + try uleb128(diw, ty.abiAlignment(pt).toByteUnits().?); } else { - const d_sym = macho_file.getDebugSymbols().?; - d_sym.markDirty(d_sym.debug_line_section_index.?, macho_file); + try uleb128(diw, 0); + try uleb128(diw, 1); + } + { + try wip_nav.abbrevCode(.tagged_union); + try wip_nav.infoSectionOffset( + .debug_info, + wip_nav.unit, + wip_nav.entry, + @intCast(wip_nav.debug_info.items.len + dwarf.sectionOffsetBytes()), + ); + { + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("is_error"); + try wip_nav.refType(Type.fromInterned(try pt.intern(.{ .int_type = .{ + .signedness = .unsigned, + .bits = pt.zcu.errorSetBits(), + } }))); + try uleb128(diw, error_union_error_set_offset); + + try wip_nav.abbrevCode(.unsigned_tagged_union_field); + try uleb128(diw, 0); + { + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("value"); + try wip_nav.refType(error_union_payload_type); + try uleb128(diw, error_union_payload_offset); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + + try wip_nav.abbrevCode(.tagged_union_default_field); + { + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("error"); + try wip_nav.refType(error_union_error_set_type); + try uleb128(diw, error_union_error_set_offset); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + .simple_type => |simple_type| switch (simple_type) { + .f16, + .f32, + .f64, + .f80, + .f128, + .usize, + .isize, + .c_char, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .bool, + => { + try wip_nav.abbrevCode(.numeric_type); + try wip_nav.strp(name); + try diw.writeByte(if (type_index == .bool_type) + DW.ATE.boolean + else if (ty.isRuntimeFloat()) + DW.ATE.float + else if (ty.isSignedInt(zcu)) + DW.ATE.signed + else if (ty.isUnsignedInt(zcu)) + DW.ATE.unsigned + else + unreachable); + try uleb128(diw, ty.bitSize(pt)); + try uleb128(diw, ty.abiSize(pt)); + try uleb128(diw, ty.abiAlignment(pt).toByteUnits().?); + }, + .anyopaque, + .void, + .type, + .comptime_int, + .comptime_float, + .noreturn, + .null, + .undefined, + .enum_literal, + .generic_poison, + => { + try wip_nav.abbrevCode(.void_type); + try wip_nav.strp(if (type_index == .generic_poison_type) "anytype" else name); + }, + .anyerror => return, // delay until flush + .adhoc_inferred_error_set => unreachable, + }, + .struct_type, + .union_type, + .opaque_type, + => unreachable, + .anon_struct_type => |anon_struct_type| if (anon_struct_type.types.len == 0) { + try wip_nav.abbrevCode(.namespace_struct_type); + try wip_nav.strp(name); + try diw.writeByte(@intFromBool(false)); + } else { + try wip_nav.abbrevCode(.struct_type); + try wip_nav.strp(name); + try uleb128(diw, ty.abiSize(pt)); + try uleb128(diw, ty.abiAlignment(pt).toByteUnits().?); + var field_byte_offset: u64 = 0; + for (0..anon_struct_type.types.len) |field_index| { + const comptime_value = anon_struct_type.values.get(ip)[field_index]; + try wip_nav.abbrevCode(if (comptime_value != .none) .struct_field_comptime else .struct_field); + if (anon_struct_type.fieldName(ip, field_index).unwrap()) |field_name| try wip_nav.strp(field_name.toSlice(ip)) else { + const field_name = try std.fmt.allocPrint(dwarf.gpa, "{d}", .{field_index}); + defer dwarf.gpa.free(field_name); + try wip_nav.strp(field_name); + } + const field_type = Type.fromInterned(anon_struct_type.types.get(ip)[field_index]); + try wip_nav.refType(field_type); + if (comptime_value == .none) { + const field_align = field_type.abiAlignment(pt); + field_byte_offset = field_align.forward(field_byte_offset); + try uleb128(diw, field_byte_offset); + try uleb128(diw, field_type.abiAlignment(pt).toByteUnits().?); + field_byte_offset += field_type.abiSize(pt); + } + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + .enum_type => { + const loaded_enum = ip.loadEnumType(type_index); + try wip_nav.abbrevCode(if (loaded_enum.names.len > 0) .enum_type else .empty_enum_type); + try wip_nav.strp(name); + try wip_nav.refType(Type.fromInterned(loaded_enum.tag_ty)); + for (0..loaded_enum.names.len) |field_index| { + try wip_nav.enumConstValue(loaded_enum, .{ + .sdata = .signed_enum_field, + .udata = .unsigned_enum_field, + .block = .big_enum_field, + }, field_index); + try wip_nav.strp(loaded_enum.names.get(ip)[field_index].toSlice(ip)); + } + if (loaded_enum.names.len > 0) try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + .func_type => |func_type| { + const is_nullary = func_type.param_types.len == 0 and !func_type.is_var_args; + try wip_nav.abbrevCode(if (is_nullary) .nullary_func_type else .func_type); + try wip_nav.strp(name); + try diw.writeByte(@intFromEnum(@as(DW.CC, switch (func_type.cc) { + .Unspecified, .C => .normal, + .Naked, .Async, .Inline => .nocall, + .Interrupt, .Signal => .nocall, + .Stdcall => .BORLAND_stdcall, + .Fastcall => .BORLAND_fastcall, + .Vectorcall => .LLVM_vectorcall, + .Thiscall => .BORLAND_thiscall, + .APCS => .nocall, + .AAPCS => .LLVM_AAPCS, + .AAPCSVFP => .LLVM_AAPCS_VFP, + .SysV => .LLVM_X86_64SysV, + .Win64 => .LLVM_Win64, + .Kernel, .Fragment, .Vertex => .nocall, + }))); + try wip_nav.refType(Type.fromInterned(func_type.return_type)); + if (!is_nullary) { + for (0..func_type.param_types.len) |param_index| { + try wip_nav.abbrevCode(.func_type_param); + try wip_nav.refType(Type.fromInterned(func_type.param_types.get(ip)[param_index])); + } + if (func_type.is_var_args) try wip_nav.abbrevCode(.is_var_args); + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } + }, + .error_set_type => |error_set_type| { + try wip_nav.abbrevCode(if (error_set_type.names.len > 0) .enum_type else .empty_enum_type); + try wip_nav.strp(name); + try wip_nav.refType(Type.fromInterned(try pt.intern(.{ .int_type = .{ + .signedness = .unsigned, + .bits = pt.zcu.errorSetBits(), + } }))); + for (0..error_set_type.names.len) |field_index| { + const field_name = error_set_type.names.get(ip)[field_index]; + try wip_nav.abbrevCode(.unsigned_enum_field); + try uleb128(diw, ip.getErrorValueIfExists(field_name).?); + try wip_nav.strp(field_name.toSlice(ip)); } - } else if (self.bin_file.cast(.wasm)) |_| {} else unreachable; + if (error_set_type.names.len > 0) try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + .inferred_error_set_type => |func| switch (ip.funcIesResolvedUnordered(func)) { + .none => { + try wip_nav.abbrevCode(.void_type); + try wip_nav.strp(name); + }, + else => |ies| { + try wip_nav.abbrevCode(.inferred_error_set_type); + try wip_nav.strp(name); + try wip_nav.refType(Type.fromInterned(ies)); + }, + }, + + // values, not types + .undef, + .simple_value, + .variable, + .@"extern", + .func, + .int, + .err, + .error_union, + .enum_literal, + .enum_tag, + .empty_enum_value, + .float, + .ptr, + .slice, + .opt, + .aggregate, + .un, + // memoization, not types + .memoized_call, + => unreachable, } - return @intCast(gop.index + 1); + try dwarf.debug_info.section.replaceEntry(wip_nav.unit, wip_nav.entry, dwarf, wip_nav.debug_info.items); } -fn genIncludeDirsAndFileNames(self: *Dwarf, arena: Allocator) !struct { - dirs: []const []const u8, - files: []const []const u8, - files_dirs_indexes: []u28, -} { - var dirs = std.StringArrayHashMap(void).init(arena); - try dirs.ensureTotalCapacity(self.di_files.count()); - - var files = std.ArrayList([]const u8).init(arena); - try files.ensureTotalCapacityPrecise(self.di_files.count()); - - var files_dir_indexes = std.ArrayList(u28).init(arena); - try files_dir_indexes.ensureTotalCapacity(self.di_files.count()); - - for (self.di_files.keys()) |dif| { - const full_path = try dif.mod.root.joinString(arena, dif.sub_file_path); - const dir_path = std.fs.path.dirname(full_path) orelse "."; - const sub_file_path = std.fs.path.basename(full_path); - // https://github.com/ziglang/zig/issues/19353 - var buffer: [std.fs.max_path_bytes]u8 = undefined; - const resolved = if (!std.fs.path.isAbsolute(dir_path)) - std.posix.realpath(dir_path, &buffer) catch dir_path - else - dir_path; - - const dir_index: u28 = index: { - const dirs_gop = dirs.getOrPutAssumeCapacity(try arena.dupe(u8, resolved)); - break :index @intCast(dirs_gop.index + 1); +pub fn updateContainerType(dwarf: *Dwarf, pt: Zcu.PerThread, type_index: InternPool.Index) UpdateError!void { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const ty = Type.fromInterned(type_index); + log.debug("updateContainerType({}({d}))", .{ ty.fmt(pt), @intFromEnum(type_index) }); + + const inst_info = ty.typeDeclInst(zcu).?.resolveFull(ip).?; + const file = zcu.fileByIndex(inst_info.file); + if (inst_info.inst == .main_struct_inst) { + const unit = try dwarf.getUnit(file.mod); + const type_gop = try dwarf.types.getOrPut(dwarf.gpa, type_index); + if (!type_gop.found_existing) type_gop.value_ptr.* = try dwarf.addCommonEntry(unit); + var wip_nav: WipNav = .{ + .dwarf = dwarf, + .pt = pt, + .unit = unit, + .entry = type_gop.value_ptr.*, + .any_children = false, + .func = .none, + .func_sym_index = undefined, + .func_high_reloc = undefined, + .inlined_funcs = undefined, + .debug_info = .{}, + .debug_line = .{}, + .debug_loclists = .{}, + .pending_types = .{}, }; + defer wip_nav.deinit(); + + const loaded_struct = ip.loadStructType(type_index); + + const diw = wip_nav.debug_info.writer(dwarf.gpa); + try wip_nav.abbrevCode(if (loaded_struct.field_types.len == 0) .namespace_file else .file); + const file_gop = try dwarf.getModInfo(unit).files.getOrPut(dwarf.gpa, inst_info.file); + try uleb128(diw, file_gop.index); + try wip_nav.strp(loaded_struct.name.toSlice(ip)); + if (loaded_struct.field_types.len > 0) { + try uleb128(diw, ty.abiSize(pt)); + try uleb128(diw, ty.abiAlignment(pt).toByteUnits().?); + for (0..loaded_struct.field_types.len) |field_index| { + const is_comptime = loaded_struct.fieldIsComptime(ip, field_index); + try wip_nav.abbrevCode(if (is_comptime) .struct_field_comptime else .struct_field); + if (loaded_struct.fieldName(ip, field_index).unwrap()) |field_name| try wip_nav.strp(field_name.toSlice(ip)) else { + const field_name = try std.fmt.allocPrint(dwarf.gpa, "{d}", .{field_index}); + defer dwarf.gpa.free(field_name); + try wip_nav.strp(field_name); + } + const field_type = Type.fromInterned(loaded_struct.field_types.get(ip)[field_index]); + try wip_nav.refType(field_type); + if (!is_comptime) { + try uleb128(diw, loaded_struct.offsets.get(ip)[field_index]); + try uleb128(diw, loaded_struct.fieldAlign(ip, field_index).toByteUnits() orelse + field_type.abiAlignment(pt).toByteUnits().?); + } + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } - files_dir_indexes.appendAssumeCapacity(dir_index); - files.appendAssumeCapacity(sub_file_path); + try dwarf.debug_info.section.replaceEntry(wip_nav.unit, wip_nav.entry, dwarf, wip_nav.debug_info.items); + try wip_nav.flush(); + } else { + const decl_inst = file.zir.instructions.get(@intFromEnum(inst_info.inst)); + assert(decl_inst.tag == .extended); + if (switch (decl_inst.data.extended.opcode) { + .struct_decl => @as(Zir.Inst.StructDecl.Small, @bitCast(decl_inst.data.extended.small)).name_strategy, + .enum_decl => @as(Zir.Inst.EnumDecl.Small, @bitCast(decl_inst.data.extended.small)).name_strategy, + .union_decl => @as(Zir.Inst.UnionDecl.Small, @bitCast(decl_inst.data.extended.small)).name_strategy, + .opaque_decl => @as(Zir.Inst.OpaqueDecl.Small, @bitCast(decl_inst.data.extended.small)).name_strategy, + .reify => @as(Zir.Inst.NameStrategy, @enumFromInt(decl_inst.data.extended.small)), + else => unreachable, + } == .parent) return; + + const unit = try dwarf.getUnit(file.mod); + const type_gop = try dwarf.types.getOrPut(dwarf.gpa, type_index); + if (!type_gop.found_existing) type_gop.value_ptr.* = try dwarf.addCommonEntry(unit); + var wip_nav: WipNav = .{ + .dwarf = dwarf, + .pt = pt, + .unit = unit, + .entry = type_gop.value_ptr.*, + .any_children = false, + .func = .none, + .func_sym_index = undefined, + .func_high_reloc = undefined, + .inlined_funcs = undefined, + .debug_info = .{}, + .debug_line = .{}, + .debug_loclists = .{}, + .pending_types = .{}, + }; + defer wip_nav.deinit(); + const diw = wip_nav.debug_info.writer(dwarf.gpa); + const name = try std.fmt.allocPrint(dwarf.gpa, "{}", .{ty.fmt(pt)}); + defer dwarf.gpa.free(name); + + switch (ip.indexToKey(type_index)) { + .struct_type => { + const loaded_struct = ip.loadStructType(type_index); + switch (loaded_struct.layout) { + .auto, .@"extern" => { + try wip_nav.abbrevCode(if (loaded_struct.field_types.len == 0) .namespace_struct_type else .struct_type); + try wip_nav.strp(name); + if (loaded_struct.field_types.len == 0) try diw.writeByte(@intFromBool(false)) else { + try uleb128(diw, ty.abiSize(pt)); + try uleb128(diw, ty.abiAlignment(pt).toByteUnits().?); + for (0..loaded_struct.field_types.len) |field_index| { + const is_comptime = loaded_struct.fieldIsComptime(ip, field_index); + try wip_nav.abbrevCode(if (is_comptime) .struct_field_comptime else .struct_field); + if (loaded_struct.fieldName(ip, field_index).unwrap()) |field_name| try wip_nav.strp(field_name.toSlice(ip)) else { + const field_name = try std.fmt.allocPrint(dwarf.gpa, "{d}", .{field_index}); + defer dwarf.gpa.free(field_name); + try wip_nav.strp(field_name); + } + const field_type = Type.fromInterned(loaded_struct.field_types.get(ip)[field_index]); + try wip_nav.refType(field_type); + if (!is_comptime) { + try uleb128(diw, loaded_struct.offsets.get(ip)[field_index]); + try uleb128(diw, loaded_struct.fieldAlign(ip, field_index).toByteUnits() orelse + field_type.abiAlignment(pt).toByteUnits().?); + } + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } + }, + .@"packed" => { + try wip_nav.abbrevCode(if (loaded_struct.field_types.len > 0) .packed_struct_type else .empty_packed_struct_type); + try wip_nav.strp(name); + try wip_nav.refType(Type.fromInterned(loaded_struct.backingIntTypeUnordered(ip))); + var field_bit_offset: u16 = 0; + for (0..loaded_struct.field_types.len) |field_index| { + try wip_nav.abbrevCode(.packed_struct_field); + try wip_nav.strp(loaded_struct.fieldName(ip, field_index).unwrap().?.toSlice(ip)); + const field_type = Type.fromInterned(loaded_struct.field_types.get(ip)[field_index]); + try wip_nav.refType(field_type); + try uleb128(diw, field_bit_offset); + field_bit_offset += @intCast(field_type.bitSize(pt)); + } + if (loaded_struct.field_types.len > 0) try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + } + }, + .enum_type => { + const loaded_enum = ip.loadEnumType(type_index); + try wip_nav.abbrevCode(if (loaded_enum.names.len > 0) .enum_type else .empty_enum_type); + try wip_nav.strp(name); + try wip_nav.refType(Type.fromInterned(loaded_enum.tag_ty)); + for (0..loaded_enum.names.len) |field_index| { + try wip_nav.enumConstValue(loaded_enum, .{ + .sdata = .signed_enum_field, + .udata = .unsigned_enum_field, + .block = .big_enum_field, + }, field_index); + try wip_nav.strp(loaded_enum.names.get(ip)[field_index].toSlice(ip)); + } + if (loaded_enum.names.len > 0) try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + .union_type => { + const loaded_union = ip.loadUnionType(type_index); + try wip_nav.abbrevCode(if (loaded_union.field_types.len > 0) .union_type else .empty_union_type); + try wip_nav.strp(name); + const union_layout = pt.getUnionLayout(loaded_union); + try uleb128(diw, union_layout.abi_size); + try uleb128(diw, union_layout.abi_align.toByteUnits().?); + const loaded_tag = loaded_union.loadTagType(ip); + if (loaded_union.hasTag(ip)) { + try wip_nav.abbrevCode(.tagged_union); + try wip_nav.infoSectionOffset( + .debug_info, + wip_nav.unit, + wip_nav.entry, + @intCast(wip_nav.debug_info.items.len + dwarf.sectionOffsetBytes()), + ); + { + try wip_nav.abbrevCode(.generated_field); + try wip_nav.strp("tag"); + try wip_nav.refType(Type.fromInterned(loaded_union.enum_tag_ty)); + try uleb128(diw, union_layout.tagOffset()); + + for (0..loaded_union.field_types.len) |field_index| { + try wip_nav.enumConstValue(loaded_tag, .{ + .sdata = .signed_tagged_union_field, + .udata = .unsigned_tagged_union_field, + .block = .big_tagged_union_field, + }, field_index); + { + try wip_nav.abbrevCode(.struct_field); + try wip_nav.strp(loaded_tag.names.get(ip)[field_index].toSlice(ip)); + const field_type = Type.fromInterned(loaded_union.field_types.get(ip)[field_index]); + try wip_nav.refType(field_type); + try uleb128(diw, union_layout.payloadOffset()); + try uleb128(diw, loaded_union.fieldAlign(ip, field_index).toByteUnits() orelse + if (field_type.isNoReturn(zcu)) 1 else field_type.abiAlignment(pt).toByteUnits().?); + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + } + } + try uleb128(diw, @intFromEnum(AbbrevCode.null)); + + if (ip.indexToKey(loaded_union.enum_tag_ty).enum_type == .generated_tag) + try wip_nav.pending_types.append(dwarf.gpa, loaded_union.enum_tag_ty); + } else for (0..loaded_union.field_types.len) |field_index| { + try wip_nav.abbrevCode(.untagged_union_field); + try wip_nav.strp(loaded_tag.names.get(ip)[field_index].toSlice(ip)); + const field_type = Type.fromInterned(loaded_union.field_types.get(ip)[field_index]); + try wip_nav.refType(field_type); + try uleb128(diw, loaded_union.fieldAlign(ip, field_index).toByteUnits() orelse + field_type.abiAlignment(pt).toByteUnits().?); + } + if (loaded_union.field_types.len > 0) try uleb128(diw, @intFromEnum(AbbrevCode.null)); + }, + .opaque_type => { + try wip_nav.abbrevCode(.namespace_struct_type); + try wip_nav.strp(name); + try diw.writeByte(@intFromBool(true)); + }, + else => unreachable, + } + try dwarf.debug_info.section.replaceEntry(wip_nav.unit, wip_nav.entry, dwarf, wip_nav.debug_info.items); + try dwarf.debug_loclists.section.replaceEntry(wip_nav.unit, wip_nav.entry, dwarf, wip_nav.debug_loclists.items); + try wip_nav.flush(); } +} - return .{ - .dirs = dirs.keys(), - .files = files.items, - .files_dirs_indexes = files_dir_indexes.items, - }; +pub fn updateNavLineNumber(dwarf: *Dwarf, zcu: *Zcu, nav_index: InternPool.Nav.Index) UpdateError!void { + const ip = &zcu.intern_pool; + + const zir_index = ip.getCau(ip.getNav(nav_index).analysis_owner.unwrap() orelse return).zir_index; + const inst_info = zir_index.resolveFull(ip).?; + assert(inst_info.inst != .main_struct_inst); + const file = zcu.fileByIndex(inst_info.file); + + const inst = file.zir.instructions.get(@intFromEnum(inst_info.inst)); + assert(inst.tag == .declaration); + const line = file.zir.extraData(Zir.Inst.Declaration, inst.data.declaration.payload_index).data.src_line; + var line_buf: [4]u8 = undefined; + std.mem.writeInt(u32, &line_buf, line, dwarf.endian); + + const unit = dwarf.debug_line.section.getUnit(dwarf.mods.get(file.mod).?); + const entry = unit.getEntry(dwarf.navs.get(nav_index).?); + try dwarf.getFile().?.pwriteAll(&line, dwarf.debug_line.section.off + unit.off + unit.header_len + entry.off + DebugInfo.declEntryLineOff(dwarf)); } -fn addDbgInfoErrorSet( - pt: Zcu.PerThread, - ty: Type, - target: std.Target, - dbg_info_buffer: *std.ArrayList(u8), -) !void { - return addDbgInfoErrorSetNames(pt, ty, ty.errorSetNames(pt.zcu).get(&pt.zcu.intern_pool), target, dbg_info_buffer); +pub fn freeNav(dwarf: *Dwarf, nav_index: InternPool.Nav.Index) void { + _ = dwarf; + _ = nav_index; } -fn addDbgInfoErrorSetNames( - pt: Zcu.PerThread, - /// Used for printing the type name only. - ty: Type, - error_names: []const InternPool.NullTerminatedString, - target: std.Target, - dbg_info_buffer: *std.ArrayList(u8), -) !void { - const target_endian = target.cpu.arch.endian(); - - // DW.AT.enumeration_type - try dbg_info_buffer.append(@intFromEnum(AbbrevCode.enum_type)); - // DW.AT.byte_size, DW.FORM.udata - const abi_size = Type.anyerror.abiSize(pt); - try leb128.writeUleb128(dbg_info_buffer.writer(), abi_size); - // DW.AT.name, DW.FORM.string - try ty.print(dbg_info_buffer.writer(), pt); - try dbg_info_buffer.append(0); - - // DW.AT.enumerator - const no_error = "(no error)"; - try dbg_info_buffer.ensureUnusedCapacity(no_error.len + 2 + @sizeOf(u64)); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.enum_variant)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity(no_error); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.const_value, DW.FORM.data8 - mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), 0, target_endian); - - for (error_names) |error_name| { - const int = try pt.getErrorValue(error_name); - const error_name_slice = error_name.toSlice(&pt.zcu.intern_pool); - // DW.AT.enumerator - try dbg_info_buffer.ensureUnusedCapacity(error_name_slice.len + 2 + @sizeOf(u64)); - dbg_info_buffer.appendAssumeCapacity(@intFromEnum(AbbrevCode.enum_variant)); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity(error_name_slice[0 .. error_name_slice.len + 1]); - // DW.AT.const_value, DW.FORM.data8 - mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), int, target_endian); - } - - // DW.AT.enumeration_type delimit children - try dbg_info_buffer.append(0); +fn refAbbrevCode(dwarf: *Dwarf, abbrev_code: AbbrevCode) UpdateError!@typeInfo(AbbrevCode).Enum.tag_type { + assert(abbrev_code != .null); + const entry: Entry.Index = @enumFromInt(@intFromEnum(abbrev_code)); + if (dwarf.debug_abbrev.section.getUnit(DebugAbbrev.unit).getEntry(entry).len > 0) return @intFromEnum(abbrev_code); + var debug_abbrev = std.ArrayList(u8).init(dwarf.gpa); + defer debug_abbrev.deinit(); + const daw = debug_abbrev.writer(); + const abbrev = AbbrevCode.abbrevs.get(abbrev_code); + try uleb128(daw, @intFromEnum(abbrev_code)); + try uleb128(daw, @intFromEnum(abbrev.tag)); + try daw.writeByte(if (abbrev.children) DW.CHILDREN.yes else DW.CHILDREN.no); + for (abbrev.attrs) |*attr| inline for (attr) |info| try uleb128(daw, @intFromEnum(info)); + for (0..2) |_| try uleb128(daw, 0); + try dwarf.debug_abbrev.section.replaceEntry(DebugAbbrev.unit, entry, dwarf, debug_abbrev.items); + return @intFromEnum(abbrev_code); } -const Kind = enum { src_fn, di_atom }; +pub fn flushModule(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void { + const ip = &pt.zcu.intern_pool; + if (dwarf.types.get(.anyerror_type)) |entry| { + var wip_nav: WipNav = .{ + .dwarf = dwarf, + .pt = pt, + .unit = .main, + .entry = entry, + .any_children = false, + .func = .none, + .func_sym_index = undefined, + .func_high_reloc = undefined, + .inlined_funcs = undefined, + .debug_info = .{}, + .debug_line = .{}, + .debug_loclists = .{}, + .pending_types = .{}, + }; + defer wip_nav.deinit(); + const diw = wip_nav.debug_info.writer(dwarf.gpa); + const global_error_set_names = ip.global_error_set.getNamesFromMainThread(); + try wip_nav.abbrevCode(if (global_error_set_names.len > 0) .enum_type else .empty_enum_type); + try wip_nav.strp("anyerror"); + try wip_nav.refType(Type.fromInterned(try pt.intern(.{ .int_type = .{ + .signedness = .unsigned, + .bits = pt.zcu.errorSetBits(), + } }))); + for (global_error_set_names, 1..) |name, value| { + try wip_nav.abbrevCode(.unsigned_enum_field); + try uleb128(diw, value); + try wip_nav.strp(name.toSlice(ip)); + } + if (global_error_set_names.len > 0) try uleb128(diw, @intFromEnum(AbbrevCode.null)); + try dwarf.debug_info.section.replaceEntry(wip_nav.unit, wip_nav.entry, dwarf, wip_nav.debug_info.items); + } -fn createAtom(self: *Dwarf, comptime kind: Kind) !Atom.Index { - const index = blk: { - switch (kind) { - .src_fn => { - const index: Atom.Index = @intCast(self.src_fns.items.len); - _ = try self.src_fns.addOne(self.allocator); - break :blk index; - }, - .di_atom => { - const index: Atom.Index = @intCast(self.di_atoms.items.len); - _ = try self.di_atoms.addOne(self.allocator); - break :blk index; - }, + { + const cwd = try std.process.getCwdAlloc(dwarf.gpa); + defer dwarf.gpa.free(cwd); + for (dwarf.mods.keys(), dwarf.mods.values()) |mod, *mod_info| { + const root_dir_path = try std.fs.path.resolve(dwarf.gpa, &.{ + cwd, + mod.root.root_dir.path orelse "", + mod.root.sub_path, + }); + defer dwarf.gpa.free(root_dir_path); + mod_info.root_dir_path = try dwarf.debug_line_str.addString(dwarf, root_dir_path); } - }; - const atom = self.getAtomPtr(kind, index); - atom.* = .{ - .off = 0, - .len = 0, - .prev_index = null, - .next_index = null, - }; - return index; -} + } -fn getOrCreateAtomForNav(self: *Dwarf, comptime kind: Kind, nav_index: InternPool.Nav.Index) !Atom.Index { - switch (kind) { - .src_fn => { - const gop = try self.src_fn_navs.getOrPut(self.allocator, nav_index); - if (!gop.found_existing) { - gop.value_ptr.* = try self.createAtom(kind); + var header = std.ArrayList(u8).init(dwarf.gpa); + defer header.deinit(); + if (dwarf.debug_aranges.section.dirty) { + for (dwarf.debug_aranges.section.units.items, 0..) |*unit_ptr, unit_index| { + const unit: Unit.Index = @enumFromInt(unit_index); + unit_ptr.clear(); + try unit_ptr.cross_section_relocs.ensureTotalCapacity(dwarf.gpa, 1); + header.clearRetainingCapacity(); + try header.ensureTotalCapacity(unit_ptr.header_len); + const unit_len = (if (unit_ptr.next.unwrap()) |next_unit| + dwarf.debug_aranges.section.getUnit(next_unit).off + else + dwarf.debug_aranges.section.len) - unit_ptr.off - dwarf.unitLengthBytes(); + switch (dwarf.format) { + .@"32" => std.mem.writeInt(u32, header.addManyAsArrayAssumeCapacity(@sizeOf(u32)), @intCast(unit_len), dwarf.endian), + .@"64" => { + std.mem.writeInt(u32, header.addManyAsArrayAssumeCapacity(@sizeOf(u32)), std.math.maxInt(u32), dwarf.endian); + std.mem.writeInt(u64, header.addManyAsArrayAssumeCapacity(@sizeOf(u64)), unit_len, dwarf.endian); + }, } - return gop.value_ptr.*; - }, - .di_atom => { - const gop = try self.di_atom_navs.getOrPut(self.allocator, nav_index); - if (!gop.found_existing) { - gop.value_ptr.* = try self.createAtom(kind); + std.mem.writeInt(u16, header.addManyAsArrayAssumeCapacity(@sizeOf(u16)), 2, dwarf.endian); + unit_ptr.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_info, + .target_unit = unit, + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + header.appendSliceAssumeCapacity(&.{ @intFromEnum(dwarf.address_size), 0 }); + header.appendNTimesAssumeCapacity(0, unit_ptr.header_len - header.items.len); + try unit_ptr.replaceHeader(&dwarf.debug_aranges.section, dwarf, header.items); + try unit_ptr.writeTrailer(&dwarf.debug_aranges.section, dwarf); + } + dwarf.debug_aranges.section.dirty = false; + } + if (dwarf.debug_info.section.dirty) { + for (dwarf.mods.keys(), dwarf.mods.values(), dwarf.debug_info.section.units.items, 0..) |mod, mod_info, *unit_ptr, unit_index| { + const unit: Unit.Index = @enumFromInt(unit_index); + unit_ptr.clear(); + try unit_ptr.cross_unit_relocs.ensureTotalCapacity(dwarf.gpa, 1); + try unit_ptr.cross_section_relocs.ensureTotalCapacity(dwarf.gpa, 7); + header.clearRetainingCapacity(); + try header.ensureTotalCapacity(unit_ptr.header_len); + const unit_len = (if (unit_ptr.next.unwrap()) |next_unit| + dwarf.debug_info.section.getUnit(next_unit).off + else + dwarf.debug_info.section.len) - unit_ptr.off - dwarf.unitLengthBytes(); + switch (dwarf.format) { + .@"32" => std.mem.writeInt(u32, header.addManyAsArrayAssumeCapacity(@sizeOf(u32)), @intCast(unit_len), dwarf.endian), + .@"64" => { + std.mem.writeInt(u32, header.addManyAsArrayAssumeCapacity(@sizeOf(u32)), std.math.maxInt(u32), dwarf.endian); + std.mem.writeInt(u64, header.addManyAsArrayAssumeCapacity(@sizeOf(u64)), unit_len, dwarf.endian); + }, } - return gop.value_ptr.*; - }, + std.mem.writeInt(u16, header.addManyAsArrayAssumeCapacity(@sizeOf(u16)), 5, dwarf.endian); + header.appendSliceAssumeCapacity(&.{ DW.UT.compile, @intFromEnum(dwarf.address_size) }); + unit_ptr.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_abbrev, + .target_unit = DebugAbbrev.unit, + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + const compile_unit_off: u32 = @intCast(header.items.len); + uleb128(header.fixedWriter(), try dwarf.refAbbrevCode(.compile_unit)) catch unreachable; + header.appendAssumeCapacity(DW.LANG.Zig); + unit_ptr.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_line_str, + .target_unit = StringSection.unit, + .target_entry = (try dwarf.debug_line_str.addString(dwarf, "zig " ++ @import("build_options").version)).toOptional(), + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + unit_ptr.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_line_str, + .target_unit = StringSection.unit, + .target_entry = mod_info.root_dir_path.toOptional(), + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + unit_ptr.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_line_str, + .target_unit = StringSection.unit, + .target_entry = (try dwarf.debug_line_str.addString(dwarf, mod.root_src_path)).toOptional(), + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + unit_ptr.cross_unit_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_unit = .main, + .target_off = compile_unit_off, + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + unit_ptr.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_line, + .target_unit = unit, + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + unit_ptr.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_rnglists, + .target_unit = unit, + .target_off = DebugRngLists.baseOffset(dwarf), + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + uleb128(header.fixedWriter(), 0) catch unreachable; + uleb128(header.fixedWriter(), try dwarf.refAbbrevCode(.module)) catch unreachable; + unit_ptr.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_str, + .target_unit = StringSection.unit, + .target_entry = (try dwarf.debug_str.addString(dwarf, mod.fully_qualified_name)).toOptional(), + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + uleb128(header.fixedWriter(), 0) catch unreachable; + try unit_ptr.replaceHeader(&dwarf.debug_info.section, dwarf, header.items); + try unit_ptr.writeTrailer(&dwarf.debug_info.section, dwarf); + } + dwarf.debug_info.section.dirty = false; + } + if (dwarf.debug_abbrev.section.dirty) { + assert(!dwarf.debug_info.section.dirty); + try dwarf.debug_abbrev.section.getUnit(DebugAbbrev.unit).writeTrailer(&dwarf.debug_abbrev.section, dwarf); + dwarf.debug_abbrev.section.dirty = false; + } + if (dwarf.debug_str.section.dirty) { + const contents = dwarf.debug_str.contents.items; + try dwarf.debug_str.section.resize(dwarf, contents.len); + try dwarf.getFile().?.pwriteAll(contents, dwarf.debug_str.section.off); + dwarf.debug_str.section.dirty = false; + } + if (dwarf.debug_line.section.dirty) { + for (dwarf.mods.values(), dwarf.debug_line.section.units.items) |mod_info, *unit| try unit.resizeHeader( + &dwarf.debug_line.section, + dwarf, + DebugLine.headerBytes(dwarf, @intCast(mod_info.dirs.count()), @intCast(mod_info.files.count())), + ); + for (dwarf.mods.values(), dwarf.debug_line.section.units.items) |mod_info, *unit| { + unit.clear(); + try unit.cross_section_relocs.ensureTotalCapacity(dwarf.gpa, 2 * (1 + mod_info.files.count())); + header.clearRetainingCapacity(); + try header.ensureTotalCapacity(unit.header_len); + const unit_len = (if (unit.next.unwrap()) |next_unit| + dwarf.debug_line.section.getUnit(next_unit).off + else + dwarf.debug_line.section.len) - unit.off - dwarf.unitLengthBytes(); + switch (dwarf.format) { + .@"32" => std.mem.writeInt(u32, header.addManyAsArrayAssumeCapacity(@sizeOf(u32)), @intCast(unit_len), dwarf.endian), + .@"64" => { + std.mem.writeInt(u32, header.addManyAsArrayAssumeCapacity(@sizeOf(u32)), std.math.maxInt(u32), dwarf.endian); + std.mem.writeInt(u64, header.addManyAsArrayAssumeCapacity(@sizeOf(u64)), unit_len, dwarf.endian); + }, + } + std.mem.writeInt(u16, header.addManyAsArrayAssumeCapacity(@sizeOf(u16)), 5, dwarf.endian); + header.appendSliceAssumeCapacity(&.{ @intFromEnum(dwarf.address_size), 0 }); + dwarf.writeInt(header.addManyAsSliceAssumeCapacity(dwarf.sectionOffsetBytes()), unit.header_len - header.items.len); + const StandardOpcode = DeclValEnum(DW.LNS); + header.appendSliceAssumeCapacity(&[_]u8{ + dwarf.debug_line.header.minimum_instruction_length, + dwarf.debug_line.header.maximum_operations_per_instruction, + @intFromBool(dwarf.debug_line.header.default_is_stmt), + @bitCast(dwarf.debug_line.header.line_base), + dwarf.debug_line.header.line_range, + dwarf.debug_line.header.opcode_base, + }); + header.appendSliceAssumeCapacity(std.enums.EnumArray(StandardOpcode, u8).init(.{ + .extended_op = undefined, + .copy = 0, + .advance_pc = 1, + .advance_line = 1, + .set_file = 1, + .set_column = 1, + .negate_stmt = 0, + .set_basic_block = 0, + .const_add_pc = 0, + .fixed_advance_pc = 1, + .set_prologue_end = 0, + .set_epilogue_begin = 0, + .set_isa = 1, + }).values[1..dwarf.debug_line.header.opcode_base]); + header.appendAssumeCapacity(1); + uleb128(header.fixedWriter(), DW.LNCT.path) catch unreachable; + uleb128(header.fixedWriter(), DW.FORM.line_strp) catch unreachable; + uleb128(header.fixedWriter(), mod_info.dirs.count()) catch unreachable; + for (mod_info.dirs.keys()) |dir_unit| { + unit.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_line_str, + .target_unit = StringSection.unit, + .target_entry = dwarf.getModInfo(dir_unit).root_dir_path.toOptional(), + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + } + const dir_index_info = DebugLine.dirIndexInfo(@intCast(mod_info.dirs.count())); + header.appendAssumeCapacity(3); + uleb128(header.fixedWriter(), DW.LNCT.path) catch unreachable; + uleb128(header.fixedWriter(), DW.FORM.line_strp) catch unreachable; + uleb128(header.fixedWriter(), DW.LNCT.directory_index) catch unreachable; + uleb128(header.fixedWriter(), @intFromEnum(dir_index_info.form)) catch unreachable; + uleb128(header.fixedWriter(), DW.LNCT.LLVM_source) catch unreachable; + uleb128(header.fixedWriter(), DW.FORM.line_strp) catch unreachable; + uleb128(header.fixedWriter(), mod_info.files.count()) catch unreachable; + for (mod_info.files.keys()) |file_index| { + const file = pt.zcu.fileByIndex(file_index); + unit.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_line_str, + .target_unit = StringSection.unit, + .target_entry = (try dwarf.debug_line_str.addString(dwarf, file.sub_file_path)).toOptional(), + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + dwarf.writeInt( + header.addManyAsSliceAssumeCapacity(dir_index_info.bytes), + mod_info.dirs.getIndex(dwarf.getUnitIfExists(file.mod).?).?, + ); + unit.cross_section_relocs.appendAssumeCapacity(.{ + .source_off = @intCast(header.items.len), + .target_sec = .debug_line_str, + .target_unit = StringSection.unit, + .target_entry = (try dwarf.debug_line_str.addString( + dwarf, + if (file.mod.builtin_file == file) file.source else "", + )).toOptional(), + }); + header.appendNTimesAssumeCapacity(0, dwarf.sectionOffsetBytes()); + } + try unit.replaceHeader(&dwarf.debug_line.section, dwarf, header.items); + try unit.writeTrailer(&dwarf.debug_line.section, dwarf); + } + dwarf.debug_line.section.dirty = false; } + if (dwarf.debug_line_str.section.dirty) { + const contents = dwarf.debug_line_str.contents.items; + try dwarf.debug_line_str.section.resize(dwarf, contents.len); + try dwarf.getFile().?.pwriteAll(contents, dwarf.debug_line_str.section.off); + dwarf.debug_line_str.section.dirty = false; + } + if (dwarf.debug_loclists.section.dirty) { + dwarf.debug_loclists.section.dirty = false; + } + if (dwarf.debug_rnglists.section.dirty) { + for (dwarf.debug_rnglists.section.units.items) |*unit| { + header.clearRetainingCapacity(); + try header.ensureTotalCapacity(unit.header_len); + const unit_len = (if (unit.next.unwrap()) |next_unit| + dwarf.debug_rnglists.section.getUnit(next_unit).off + else + dwarf.debug_rnglists.section.len) - unit.off - dwarf.unitLengthBytes(); + switch (dwarf.format) { + .@"32" => std.mem.writeInt(u32, header.addManyAsArrayAssumeCapacity(@sizeOf(u32)), @intCast(unit_len), dwarf.endian), + .@"64" => { + std.mem.writeInt(u32, header.addManyAsArrayAssumeCapacity(@sizeOf(u32)), std.math.maxInt(u32), dwarf.endian); + std.mem.writeInt(u64, header.addManyAsArrayAssumeCapacity(@sizeOf(u64)), unit_len, dwarf.endian); + }, + } + std.mem.writeInt(u16, header.addManyAsArrayAssumeCapacity(@sizeOf(u16)), 5, dwarf.endian); + header.appendSliceAssumeCapacity(&.{ @intFromEnum(dwarf.address_size), 0 }); + std.mem.writeInt(u32, header.addManyAsArrayAssumeCapacity(@sizeOf(u32)), 1, dwarf.endian); + dwarf.writeInt(header.addManyAsSliceAssumeCapacity(dwarf.sectionOffsetBytes()), dwarf.sectionOffsetBytes() * 1); + try unit.replaceHeader(&dwarf.debug_rnglists.section, dwarf, header.items); + try unit.writeTrailer(&dwarf.debug_rnglists.section, dwarf); + } + dwarf.debug_rnglists.section.dirty = false; + } + assert(!dwarf.debug_abbrev.section.dirty); + assert(!dwarf.debug_aranges.section.dirty); + assert(!dwarf.debug_info.section.dirty); + assert(!dwarf.debug_line.section.dirty); + assert(!dwarf.debug_line_str.section.dirty); + assert(!dwarf.debug_loclists.section.dirty); + assert(!dwarf.debug_rnglists.section.dirty); + assert(!dwarf.debug_str.section.dirty); } -fn getAtom(self: *const Dwarf, comptime kind: Kind, index: Atom.Index) Atom { - return switch (kind) { - .src_fn => self.src_fns.items[index], - .di_atom => self.di_atoms.items[index], - }; +pub fn resolveRelocs(dwarf: *Dwarf) RelocError!void { + for ([_]*Section{ + &dwarf.debug_abbrev.section, + &dwarf.debug_aranges.section, + &dwarf.debug_info.section, + &dwarf.debug_line.section, + &dwarf.debug_line_str.section, + &dwarf.debug_loclists.section, + &dwarf.debug_rnglists.section, + &dwarf.debug_str.section, + }) |sec| try sec.resolveRelocs(dwarf); } -fn getAtomPtr(self: *Dwarf, comptime kind: Kind, index: Atom.Index) *Atom { - return switch (kind) { - .src_fn => &self.src_fns.items[index], - .di_atom => &self.di_atoms.items[index], - }; +fn DeclValEnum(comptime T: type) type { + const decls = @typeInfo(T).Struct.decls; + @setEvalBranchQuota(7 * decls.len); + var fields: [decls.len]std.builtin.Type.EnumField = undefined; + var fields_len = 0; + var min_value: ?comptime_int = null; + var max_value: ?comptime_int = null; + for (decls) |decl| { + if (std.mem.startsWith(u8, decl.name, "HP_") or std.mem.endsWith(u8, decl.name, "_user")) continue; + const value = @field(T, decl.name); + fields[fields_len] = .{ .name = decl.name, .value = value }; + fields_len += 1; + if (min_value == null or min_value.? > value) min_value = value; + if (max_value == null or max_value.? < value) max_value = value; + } + return @Type(.{ .Enum = .{ + .tag_type = std.math.IntFittingRange(min_value orelse 0, max_value orelse 0), + .fields = fields[0..fields_len], + .decls = &.{}, + .is_exhaustive = true, + } }); } -pub const Format = enum { - dwarf32, - dwarf64, +const AbbrevCode = enum { + null, + // padding codes must be one byte uleb128 values to function + pad_1, + pad_n, + // decl codes are assumed to all have the same uleb128 length + decl_alias, + decl_enum, + decl_empty_enum, + decl_namespace_struct, + decl_struct, + decl_packed_struct, + decl_union, + decl_var, + decl_func, + decl_empty_func, + decl_func_generic, + decl_empty_func_generic, + // the rest are unrestricted + compile_unit, + module, + namespace_file, + file, + signed_enum_field, + unsigned_enum_field, + big_enum_field, + generated_field, + struct_field, + struct_field_comptime, + packed_struct_field, + untagged_union_field, + tagged_union, + signed_tagged_union_field, + unsigned_tagged_union_field, + big_tagged_union_field, + tagged_union_default_field, + void_type, + numeric_type, + inferred_error_set_type, + ptr_type, + is_const, + is_volatile, + array_type, + array_index, + nullary_func_type, + func_type, + func_type_param, + is_var_args, + enum_type, + empty_enum_type, + namespace_struct_type, + struct_type, + packed_struct_type, + empty_packed_struct_type, + union_type, + empty_union_type, + empty_inlined_func, + inlined_func, + local_arg, + local_var, + + const decl_bytes = uleb128Bytes(@intFromEnum(AbbrevCode.decl_empty_func_generic)); + + const Attr = struct { + DeclValEnum(DW.AT), + DeclValEnum(DW.FORM), + }; + const decl_abbrev_common_attrs = &[_]Attr{ + .{ .ZIG_parent, .ref_addr }, + .{ .decl_line, .data4 }, + .{ .decl_column, .udata }, + .{ .accessibility, .data1 }, + .{ .name, .strp }, + }; + const abbrevs = std.EnumArray(AbbrevCode, struct { + tag: DeclValEnum(DW.TAG), + children: bool = false, + attrs: []const Attr = &.{}, + }).init(.{ + .pad_1 = .{ + .tag = .ZIG_padding, + }, + .pad_n = .{ + .tag = .ZIG_padding, + .attrs = &.{ + .{ .ZIG_padding, .block }, + }, + }, + .decl_alias = .{ + .tag = .imported_declaration, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .import, .ref_addr }, + }, + }, + .decl_enum = .{ + .tag = .enumeration_type, + .children = true, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .type, .ref_addr }, + }, + }, + .decl_empty_enum = .{ + .tag = .enumeration_type, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .type, .ref_addr }, + }, + }, + .decl_namespace_struct = .{ + .tag = .structure_type, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .declaration, .flag }, + }, + }, + .decl_struct = .{ + .tag = .structure_type, + .children = true, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .byte_size, .udata }, + .{ .alignment, .udata }, + }, + }, + .decl_packed_struct = .{ + .tag = .structure_type, + .children = true, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .type, .ref_addr }, + }, + }, + .decl_union = .{ + .tag = .union_type, + .children = true, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .byte_size, .udata }, + .{ .alignment, .udata }, + }, + }, + .decl_var = .{ + .tag = .variable, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .linkage_name, .strp }, + .{ .type, .ref_addr }, + .{ .location, .exprloc }, + .{ .alignment, .udata }, + .{ .external, .flag }, + }, + }, + .decl_func = .{ + .tag = .subprogram, + .children = true, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .linkage_name, .strp }, + .{ .type, .ref_addr }, + .{ .low_pc, .addr }, + .{ .high_pc, .addr }, + .{ .alignment, .udata }, + .{ .external, .flag }, + .{ .noreturn, .flag }, + }, + }, + .decl_empty_func = .{ + .tag = .subprogram, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .linkage_name, .strp }, + .{ .type, .ref_addr }, + .{ .low_pc, .addr }, + .{ .high_pc, .addr }, + .{ .alignment, .udata }, + .{ .external, .flag }, + .{ .noreturn, .flag }, + }, + }, + .decl_func_generic = .{ + .tag = .subprogram, + .children = true, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .type, .ref_addr }, + }, + }, + .decl_empty_func_generic = .{ + .tag = .subprogram, + .attrs = decl_abbrev_common_attrs ++ .{ + .{ .type, .ref_addr }, + }, + }, + .compile_unit = .{ + .tag = .compile_unit, + .children = true, + .attrs = &.{ + .{ .language, .data1 }, + .{ .producer, .line_strp }, + .{ .comp_dir, .line_strp }, + .{ .name, .line_strp }, + .{ .base_types, .ref_addr }, + .{ .stmt_list, .sec_offset }, + .{ .rnglists_base, .sec_offset }, + .{ .ranges, .rnglistx }, + }, + }, + .module = .{ + .tag = .module, + .children = true, + .attrs = &.{ + .{ .name, .strp }, + .{ .ranges, .rnglistx }, + }, + }, + .namespace_file = .{ + .tag = .structure_type, + .attrs = &.{ + .{ .decl_file, .udata }, + .{ .name, .strp }, + }, + }, + .file = .{ + .tag = .structure_type, + .children = true, + .attrs = &.{ + .{ .decl_file, .udata }, + .{ .name, .strp }, + .{ .byte_size, .udata }, + .{ .alignment, .udata }, + }, + }, + .signed_enum_field = .{ + .tag = .enumerator, + .attrs = &.{ + .{ .const_value, .sdata }, + .{ .name, .strp }, + }, + }, + .unsigned_enum_field = .{ + .tag = .enumerator, + .attrs = &.{ + .{ .const_value, .udata }, + .{ .name, .strp }, + }, + }, + .big_enum_field = .{ + .tag = .enumerator, + .attrs = &.{ + .{ .const_value, .block }, + .{ .name, .strp }, + }, + }, + .generated_field = .{ + .tag = .member, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .data_member_location, .udata }, + .{ .artificial, .flag_present }, + }, + }, + .struct_field = .{ + .tag = .member, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .data_member_location, .udata }, + .{ .alignment, .udata }, + }, + }, + .struct_field_comptime = .{ + .tag = .member, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .const_expr, .flag_present }, + }, + }, + .packed_struct_field = .{ + .tag = .member, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .data_bit_offset, .udata }, + }, + }, + .untagged_union_field = .{ + .tag = .member, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .alignment, .udata }, + }, + }, + .tagged_union = .{ + .tag = .variant_part, + .children = true, + .attrs = &.{ + .{ .discr, .ref_addr }, + }, + }, + .signed_tagged_union_field = .{ + .tag = .variant, + .children = true, + .attrs = &.{ + .{ .discr_value, .sdata }, + }, + }, + .unsigned_tagged_union_field = .{ + .tag = .variant, + .children = true, + .attrs = &.{ + .{ .discr_value, .udata }, + }, + }, + .big_tagged_union_field = .{ + .tag = .variant, + .children = true, + .attrs = &.{ + .{ .discr_value, .block }, + }, + }, + .tagged_union_default_field = .{ + .tag = .variant, + .children = true, + .attrs = &.{}, + }, + .void_type = .{ + .tag = .unspecified_type, + .attrs = &.{ + .{ .name, .strp }, + }, + }, + .numeric_type = .{ + .tag = .base_type, + .attrs = &.{ + .{ .name, .strp }, + .{ .encoding, .data1 }, + .{ .bit_size, .udata }, + .{ .byte_size, .udata }, + .{ .alignment, .udata }, + }, + }, + .inferred_error_set_type = .{ + .tag = .typedef, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + }, + }, + .ptr_type = .{ + .tag = .pointer_type, + .attrs = &.{ + .{ .name, .strp }, + .{ .alignment, .udata }, + .{ .address_class, .data1 }, + .{ .type, .ref_addr }, + }, + }, + .is_const = .{ + .tag = .const_type, + .attrs = &.{ + .{ .type, .ref_addr }, + }, + }, + .is_volatile = .{ + .tag = .volatile_type, + .attrs = &.{ + .{ .type, .ref_addr }, + }, + }, + .array_type = .{ + .tag = .array_type, + .children = true, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .GNU_vector, .flag }, + }, + }, + .array_index = .{ + .tag = .subrange_type, + .attrs = &.{ + .{ .type, .ref_addr }, + .{ .count, .udata }, + }, + }, + .nullary_func_type = .{ + .tag = .subroutine_type, + .attrs = &.{ + .{ .name, .strp }, + .{ .calling_convention, .data1 }, + .{ .type, .ref_addr }, + }, + }, + .func_type = .{ + .tag = .subroutine_type, + .children = true, + .attrs = &.{ + .{ .name, .strp }, + .{ .calling_convention, .data1 }, + .{ .type, .ref_addr }, + }, + }, + .func_type_param = .{ + .tag = .formal_parameter, + .attrs = &.{ + .{ .type, .ref_addr }, + }, + }, + .is_var_args = .{ + .tag = .unspecified_parameters, + }, + .enum_type = .{ + .tag = .enumeration_type, + .children = true, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + }, + }, + .empty_enum_type = .{ + .tag = .enumeration_type, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + }, + }, + .namespace_struct_type = .{ + .tag = .structure_type, + .attrs = &.{ + .{ .name, .strp }, + .{ .declaration, .flag }, + }, + }, + .struct_type = .{ + .tag = .structure_type, + .children = true, + .attrs = &.{ + .{ .name, .strp }, + .{ .byte_size, .udata }, + .{ .alignment, .udata }, + }, + }, + .packed_struct_type = .{ + .tag = .structure_type, + .children = true, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + }, + }, + .empty_packed_struct_type = .{ + .tag = .structure_type, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + }, + }, + .union_type = .{ + .tag = .union_type, + .children = true, + .attrs = &.{ + .{ .name, .strp }, + .{ .byte_size, .udata }, + .{ .alignment, .udata }, + }, + }, + .empty_union_type = .{ + .tag = .union_type, + .attrs = &.{ + .{ .name, .strp }, + .{ .byte_size, .udata }, + .{ .alignment, .udata }, + }, + }, + .empty_inlined_func = .{ + .tag = .inlined_subroutine, + .attrs = &.{ + .{ .abstract_origin, .ref_addr }, + .{ .call_line, .udata }, + .{ .call_column, .udata }, + .{ .low_pc, .addr }, + .{ .high_pc, .addr }, + }, + }, + .inlined_func = .{ + .tag = .inlined_subroutine, + .children = true, + .attrs = &.{ + .{ .abstract_origin, .ref_addr }, + .{ .call_line, .udata }, + .{ .call_column, .udata }, + .{ .low_pc, .addr }, + .{ .high_pc, .addr }, + }, + }, + .local_arg = .{ + .tag = .formal_parameter, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .location, .exprloc }, + }, + }, + .local_var = .{ + .tag = .variable, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .location, .exprloc }, + }, + }, + .null = undefined, + }); }; -const Dwarf = @This(); +fn getFile(dwarf: *Dwarf) ?std.fs.File { + if (dwarf.bin_file.cast(.macho)) |macho_file| if (macho_file.d_sym) |*d_sym| return d_sym.file; + return dwarf.bin_file.file; +} -const std = @import("std"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const fs = std.fs; -const leb128 = std.leb; -const log = std.log.scoped(.dwarf); -const mem = std.mem; +fn addCommonEntry(dwarf: *Dwarf, unit: Unit.Index) UpdateError!Entry.Index { + const entry = try dwarf.debug_aranges.section.getUnit(unit).addEntry(dwarf.gpa); + assert(try dwarf.debug_info.section.getUnit(unit).addEntry(dwarf.gpa) == entry); + assert(try dwarf.debug_line.section.getUnit(unit).addEntry(dwarf.gpa) == entry); + assert(try dwarf.debug_loclists.section.getUnit(unit).addEntry(dwarf.gpa) == entry); + assert(try dwarf.debug_rnglists.section.getUnit(unit).addEntry(dwarf.gpa) == entry); + return entry; +} -const link = @import("../link.zig"); -const trace = @import("../tracy.zig").trace; +fn writeInt(dwarf: *Dwarf, buf: []u8, int: u64) void { + switch (buf.len) { + inline 0...8 => |len| std.mem.writeInt(@Type(.{ .Int = .{ + .signedness = .unsigned, + .bits = len * 8, + } }), buf[0..len], @intCast(int), dwarf.endian), + else => unreachable, + } +} + +fn resolveReloc(dwarf: *Dwarf, source: u64, target: u64, size: u32) RelocError!void { + var buf: [8]u8 = undefined; + dwarf.writeInt(buf[0..size], target); + try dwarf.getFile().?.pwriteAll(buf[0..size], source); +} + +fn unitLengthBytes(dwarf: *Dwarf) u32 { + return switch (dwarf.format) { + .@"32" => 4, + .@"64" => 4 + 8, + }; +} + +fn sectionOffsetBytes(dwarf: *Dwarf) u32 { + return switch (dwarf.format) { + .@"32" => 4, + .@"64" => 8, + }; +} + +fn uleb128Bytes(value: anytype) u32 { + var cw = std.io.countingWriter(std.io.null_writer); + try uleb128(cw.writer(), value); + return @intCast(cw.bytes_written); +} + +/// overrides `-fno-incremental` for testing incremental debug info until `-fincremental` is functional +const force_incremental = false; +inline fn incremental(dwarf: Dwarf) bool { + return force_incremental or dwarf.bin_file.comp.incremental; +} -const Allocator = mem.Allocator; const DW = std.dwarf; -const File = link.File; -const LinkBlock = File.LinkBlock; -const LinkFn = File.LinkFn; -const LinkerLoad = @import("../codegen.zig").LinkerLoad; -const Zcu = @import("../Zcu.zig"); +const Dwarf = @This(); const InternPool = @import("../InternPool.zig"); -const StringTable = @import("StringTable.zig"); +const Module = @import("../Package.zig").Module; const Type = @import("../Type.zig"); -const Value = @import("../Value.zig"); +const Zcu = @import("../Zcu.zig"); +const Zir = std.zig.Zir; +const assert = std.debug.assert; +const codegen = @import("../codegen.zig"); +const link = @import("../link.zig"); +const log = std.log.scoped(.dwarf); +const sleb128 = std.leb.writeIleb128; +const std = @import("std"); +const target_info = @import("../target.zig"); +const uleb128 = std.leb.writeUleb128; diff --git a/zig/src/link/Elf.zig b/zig/src/link/Elf.zig index 103c69202b..333e490d17 100644 --- a/zig/src/link/Elf.zig +++ b/zig/src/link/Elf.zig @@ -64,9 +64,6 @@ phdrs: std.ArrayListUnmanaged(elf.Elf64_Phdr) = .{}, /// Tracked loadable segments during incremental linking. /// The index into the program headers of a PT_LOAD program header with Read and Execute flags phdr_zig_load_re_index: ?u16 = null, -/// The index into the program headers of the global offset table. -/// It needs PT_LOAD and Read flags. -phdr_zig_got_index: ?u16 = null, /// The index into the program headers of a PT_LOAD program header with Read flag phdr_zig_load_ro_index: ?u16 = null, /// The index into the program headers of a PT_LOAD program header with Write flag @@ -130,8 +127,6 @@ plt_got: PltGotSection = .{}, copy_rel: CopyRelSection = .{}, /// .rela.plt section rela_plt: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{}, -/// .got.zig section -zig_got: ZigGotSection = .{}, /// SHT_GROUP sections /// Applies only to a relocatable. comdat_group_sections: std.ArrayListUnmanaged(ComdatGroupSection) = .{}, @@ -142,13 +137,15 @@ zig_text_section_index: ?u32 = null, zig_data_rel_ro_section_index: ?u32 = null, zig_data_section_index: ?u32 = null, zig_bss_section_index: ?u32 = null, -zig_got_section_index: ?u32 = null, debug_info_section_index: ?u32 = null, debug_abbrev_section_index: ?u32 = null, debug_str_section_index: ?u32 = null, debug_aranges_section_index: ?u32 = null, debug_line_section_index: ?u32 = null, +debug_line_str_section_index: ?u32 = null, +debug_loclists_section_index: ?u32 = null, +debug_rnglists_section_index: ?u32 = null, copy_rel_section_index: ?u32 = null, dynamic_section_index: ?u32 = null, @@ -207,7 +204,7 @@ pub const SortSection = enum { name, alignment }; pub fn createEmpty( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*Elf { const target = comp.root_mod.resolved_target.result; @@ -324,7 +321,7 @@ pub fn createEmpty( // If using LLD to link, this code should produce an object file so that it // can be passed to LLD. const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path; - self.base.file = try emit.directory.handle.createFile(sub_path, .{ + self.base.file = try emit.root_dir.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.File.determineMode(use_lld, output_mode, link_mode), @@ -404,7 +401,7 @@ pub fn createEmpty( pub fn open( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*Elf { // TODO: restore saved linker state, don't truncate the file, and @@ -474,7 +471,6 @@ pub fn deinit(self: *Elf) void { self.copy_rel.deinit(gpa); self.rela_dyn.deinit(gpa); self.rela_plt.deinit(gpa); - self.zig_got.deinit(gpa); self.comdat_group_sections.deinit(gpa); } @@ -499,12 +495,13 @@ pub fn getUavVAddr(self: *Elf, uav: InternPool.Index, reloc_info: link.File.Relo } /// Returns end pos of collision, if any. -fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { +fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 { const small_ptr = self.ptr_width == .p32; const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); if (start < ehdr_size) return ehdr_size; + var at_end = true; const end = start + padToIdeal(size); if (self.shdr_table_offset) |off| { @@ -512,8 +509,9 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { const tight_size = self.shdrs.items.len * shdr_size; const increased_size = padToIdeal(tight_size); const test_end = off +| increased_size; - if (end > off and start < test_end) { - return test_end; + if (start < test_end) { + if (end > off) return test_end; + if (test_end < std.math.maxInt(u64)) at_end = false; } } @@ -521,8 +519,9 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { if (shdr.sh_type == elf.SHT_NOBITS) continue; const increased_size = padToIdeal(shdr.sh_size); const test_end = shdr.sh_offset +| increased_size; - if (end > shdr.sh_offset and start < test_end) { - return test_end; + if (start < test_end) { + if (end > shdr.sh_offset) return test_end; + if (test_end < std.math.maxInt(u64)) at_end = false; } } @@ -530,11 +529,13 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { if (phdr.p_type != elf.PT_LOAD) continue; const increased_size = padToIdeal(phdr.p_filesz); const test_end = phdr.p_offset +| increased_size; - if (end > phdr.p_offset and start < test_end) { - return test_end; + if (start < test_end) { + if (end > phdr.p_offset) return test_end; + if (test_end < std.math.maxInt(u64)) at_end = false; } } + if (at_end) try self.base.file.?.setEndPos(end); return null; } @@ -565,9 +566,9 @@ fn allocatedVirtualSize(self: *Elf, start: u64) u64 { return min_pos - start; } -pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u64) u64 { +pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u64) !u64 { var start: u64 = 0; - while (self.detectAllocCollision(start, object_size)) |item_end| { + while (try self.detectAllocCollision(start, object_size)) |item_end| { start = mem.alignForward(u64, item_end, min_alignment); } return start; @@ -584,12 +585,12 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { const ptr_size = self.ptrWidthBytes(); const target = self.base.comp.root_mod.resolved_target.result; const ptr_bit_width = target.ptrBitWidth(); - const zig_object = self.zigObjectPtr().?; + const zo = self.zigObjectPtr().?; const fillSection = struct { - fn fillSection(elf_file: *Elf, shdr: *elf.Elf64_Shdr, size: u64, phndx: ?u16) void { + fn fillSection(elf_file: *Elf, shdr: *elf.Elf64_Shdr, size: u64, phndx: ?u16) !void { if (elf_file.base.isRelocatable()) { - const off = elf_file.findFreeSpace(size, shdr.sh_addralign); + const off = try elf_file.findFreeSpace(size, shdr.sh_addralign); shdr.sh_offset = off; shdr.sh_size = size; } else { @@ -606,37 +607,22 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (!self.base.isRelocatable()) { if (self.phdr_zig_load_re_index == null) { const filesz = options.program_code_size_hint; - const off = self.findFreeSpace(filesz, self.page_size); + const off = try self.findFreeSpace(filesz, self.page_size); self.phdr_zig_load_re_index = try self.addPhdr(.{ .type = elf.PT_LOAD, .offset = off, .filesz = filesz, - .addr = if (ptr_bit_width >= 32) 0x8000000 else 0x8000, + .addr = if (ptr_bit_width >= 32) 0x4000000 else 0x4000, .memsz = filesz, .@"align" = self.page_size, .flags = elf.PF_X | elf.PF_R | elf.PF_W, }); } - if (self.phdr_zig_got_index == null) { - const alignment = self.page_size; - const filesz = @as(u64, ptr_size) * options.symbol_count_hint; - const off = self.findFreeSpace(filesz, alignment); - self.phdr_zig_got_index = try self.addPhdr(.{ - .type = elf.PT_LOAD, - .offset = off, - .filesz = filesz, - .addr = if (ptr_bit_width >= 32) 0x4000000 else 0x4000, - .memsz = filesz, - .@"align" = alignment, - .flags = elf.PF_R | elf.PF_W, - }); - } - if (self.phdr_zig_load_ro_index == null) { const alignment = self.page_size; const filesz: u64 = 1024; - const off = self.findFreeSpace(filesz, alignment); + const off = try self.findFreeSpace(filesz, alignment); self.phdr_zig_load_ro_index = try self.addPhdr(.{ .type = elf.PT_LOAD, .offset = off, @@ -651,7 +637,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (self.phdr_zig_load_rw_index == null) { const alignment = self.page_size; const filesz: u64 = 1024; - const off = self.findFreeSpace(filesz, alignment); + const off = try self.findFreeSpace(filesz, alignment); self.phdr_zig_load_rw_index = try self.addPhdr(.{ .type = elf.PT_LOAD, .offset = off, @@ -684,7 +670,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { .offset = std.math.maxInt(u64), }); const shdr = &self.shdrs.items[self.zig_text_section_index.?]; - fillSection(self, shdr, options.program_code_size_hint, self.phdr_zig_load_re_index); + try fillSection(self, shdr, options.program_code_size_hint, self.phdr_zig_load_re_index); if (self.base.isRelocatable()) { const rela_shndx = try self.addRelaShdr(try self.insertShString(".rela.text.zig"), self.zig_text_section_index.?); try self.output_rela_sections.putNoClobber(gpa, self.zig_text_section_index.?, .{ @@ -701,27 +687,6 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { try self.last_atom_and_free_list_table.putNoClobber(gpa, self.zig_text_section_index.?, .{}); } - if (self.zig_got_section_index == null and !self.base.isRelocatable()) { - self.zig_got_section_index = try self.addSection(.{ - .name = try self.insertShString(".got.zig"), - .type = elf.SHT_PROGBITS, - .addralign = ptr_size, - .flags = elf.SHF_ALLOC | elf.SHF_WRITE, - .offset = std.math.maxInt(u64), - }); - const shdr = &self.shdrs.items[self.zig_got_section_index.?]; - const phndx = self.phdr_zig_got_index.?; - const phdr = self.phdrs.items[phndx]; - shdr.sh_addr = phdr.p_vaddr; - shdr.sh_offset = phdr.p_offset; - shdr.sh_size = phdr.p_memsz; - try self.phdr_to_shdr_table.putNoClobber( - gpa, - self.zig_got_section_index.?, - self.phdr_zig_got_index.?, - ); - } - if (self.zig_data_rel_ro_section_index == null) { self.zig_data_rel_ro_section_index = try self.addSection(.{ .name = try self.insertShString(".data.rel.ro.zig"), @@ -731,7 +696,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { .offset = std.math.maxInt(u64), }); const shdr = &self.shdrs.items[self.zig_data_rel_ro_section_index.?]; - fillSection(self, shdr, 1024, self.phdr_zig_load_ro_index); + try fillSection(self, shdr, 1024, self.phdr_zig_load_ro_index); if (self.base.isRelocatable()) { const rela_shndx = try self.addRelaShdr( try self.insertShString(".rela.data.rel.ro.zig"), @@ -760,7 +725,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { .offset = std.math.maxInt(u64), }); const shdr = &self.shdrs.items[self.zig_data_section_index.?]; - fillSection(self, shdr, 1024, self.phdr_zig_load_rw_index); + try fillSection(self, shdr, 1024, self.phdr_zig_load_rw_index); if (self.base.isRelocatable()) { const rela_shndx = try self.addRelaShdr( try self.insertShString(".rela.data.zig"), @@ -801,24 +766,37 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { try self.last_atom_and_free_list_table.putNoClobber(gpa, self.zig_bss_section_index.?, .{}); } - if (zig_object.dwarf) |*dw| { + if (zo.dwarf) |*dwarf| { + const addSectionSymbol = struct { + fn addSectionSymbol( + zig_object: *ZigObject, + alloc: Allocator, + name: [:0]const u8, + alignment: Atom.Alignment, + shndx: u32, + ) !Symbol.Index { + const name_off = try zig_object.addString(alloc, name); + const index = try zig_object.newSymbolWithAtom(alloc, name_off); + const sym = zig_object.symbol(index); + const esym = &zig_object.symtab.items(.elf_sym)[sym.esym_index]; + esym.st_info |= elf.STT_SECTION; + const atom_ptr = zig_object.atom(sym.ref.index).?; + atom_ptr.alignment = alignment; + atom_ptr.output_section_index = shndx; + return index; + } + }.addSectionSymbol; + if (self.debug_str_section_index == null) { - assert(dw.strtab.buffer.items.len == 0); - try dw.strtab.buffer.append(gpa, 0); self.debug_str_section_index = try self.addSection(.{ .name = try self.insertShString(".debug_str"), .flags = elf.SHF_MERGE | elf.SHF_STRINGS, .entsize = 1, .type = elf.SHT_PROGBITS, .addralign = 1, - .offset = std.math.maxInt(u64), }); - const shdr = &self.shdrs.items[self.debug_str_section_index.?]; - const size = @as(u64, @intCast(dw.strtab.buffer.items.len)); - const off = self.findFreeSpace(size, 1); - shdr.sh_offset = off; - shdr.sh_size = size; - zig_object.debug_strtab_dirty = true; + zo.debug_str_section_dirty = true; + zo.debug_str_index = try addSectionSymbol(zo, gpa, ".debug_str", .@"1", self.debug_str_section_index.?); try self.output_sections.putNoClobber(gpa, self.debug_str_section_index.?, .{}); } @@ -827,14 +805,9 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { .name = try self.insertShString(".debug_info"), .type = elf.SHT_PROGBITS, .addralign = 1, - .offset = std.math.maxInt(u64), }); - const shdr = &self.shdrs.items[self.debug_info_section_index.?]; - const size: u64 = 200; - const off = self.findFreeSpace(size, 1); - shdr.sh_offset = off; - shdr.sh_size = size; - zig_object.debug_info_header_dirty = true; + zo.debug_info_section_dirty = true; + zo.debug_info_index = try addSectionSymbol(zo, gpa, ".debug_info", .@"1", self.debug_info_section_index.?); try self.output_sections.putNoClobber(gpa, self.debug_info_section_index.?, .{}); } @@ -843,14 +816,9 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { .name = try self.insertShString(".debug_abbrev"), .type = elf.SHT_PROGBITS, .addralign = 1, - .offset = std.math.maxInt(u64), }); - const shdr = &self.shdrs.items[self.debug_abbrev_section_index.?]; - const size: u64 = 128; - const off = self.findFreeSpace(size, 1); - shdr.sh_offset = off; - shdr.sh_size = size; - zig_object.debug_abbrev_section_dirty = true; + zo.debug_abbrev_section_dirty = true; + zo.debug_abbrev_index = try addSectionSymbol(zo, gpa, ".debug_abbrev", .@"1", self.debug_abbrev_section_index.?); try self.output_sections.putNoClobber(gpa, self.debug_abbrev_section_index.?, .{}); } @@ -859,14 +827,9 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { .name = try self.insertShString(".debug_aranges"), .type = elf.SHT_PROGBITS, .addralign = 16, - .offset = std.math.maxInt(u64), }); - const shdr = &self.shdrs.items[self.debug_aranges_section_index.?]; - const size: u64 = 160; - const off = self.findFreeSpace(size, 16); - shdr.sh_offset = off; - shdr.sh_size = size; - zig_object.debug_aranges_section_dirty = true; + zo.debug_aranges_section_dirty = true; + zo.debug_aranges_index = try addSectionSymbol(zo, gpa, ".debug_aranges", .@"16", self.debug_aranges_section_index.?); try self.output_sections.putNoClobber(gpa, self.debug_aranges_section_index.?, .{}); } @@ -875,57 +838,87 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { .name = try self.insertShString(".debug_line"), .type = elf.SHT_PROGBITS, .addralign = 1, - .offset = std.math.maxInt(u64), }); - const shdr = &self.shdrs.items[self.debug_line_section_index.?]; - const size: u64 = 250; - const off = self.findFreeSpace(size, 1); - shdr.sh_offset = off; - shdr.sh_size = size; - zig_object.debug_line_header_dirty = true; + zo.debug_line_section_dirty = true; + zo.debug_line_index = try addSectionSymbol(zo, gpa, ".debug_line", .@"1", self.debug_line_section_index.?); try self.output_sections.putNoClobber(gpa, self.debug_line_section_index.?, .{}); } - } - // We need to find current max assumed file offset, and actually write to file to make it a reality. - var end_pos: u64 = 0; - for (self.shdrs.items) |shdr| { - if (shdr.sh_offset == std.math.maxInt(u64)) continue; - end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size); + if (self.debug_line_str_section_index == null) { + self.debug_line_str_section_index = try self.addSection(.{ + .name = try self.insertShString(".debug_line_str"), + .flags = elf.SHF_MERGE | elf.SHF_STRINGS, + .entsize = 1, + .type = elf.SHT_PROGBITS, + .addralign = 1, + }); + zo.debug_line_str_section_dirty = true; + zo.debug_line_str_index = try addSectionSymbol(zo, gpa, ".debug_line_str", .@"1", self.debug_line_str_section_index.?); + try self.output_sections.putNoClobber(gpa, self.debug_line_str_section_index.?, .{}); + } + + if (self.debug_loclists_section_index == null) { + self.debug_loclists_section_index = try self.addSection(.{ + .name = try self.insertShString(".debug_loclists"), + .type = elf.SHT_PROGBITS, + .addralign = 1, + }); + zo.debug_loclists_section_dirty = true; + zo.debug_loclists_index = try addSectionSymbol(zo, gpa, ".debug_loclists", .@"1", self.debug_loclists_section_index.?); + try self.output_sections.putNoClobber(gpa, self.debug_loclists_section_index.?, .{}); + } + + if (self.debug_rnglists_section_index == null) { + self.debug_rnglists_section_index = try self.addSection(.{ + .name = try self.insertShString(".debug_rnglists"), + .type = elf.SHT_PROGBITS, + .addralign = 1, + }); + zo.debug_rnglists_section_dirty = true; + zo.debug_rnglists_index = try addSectionSymbol(zo, gpa, ".debug_rnglists", .@"1", self.debug_rnglists_section_index.?); + try self.output_sections.putNoClobber(gpa, self.debug_rnglists_section_index.?, .{}); + } + + try dwarf.initMetadata(); } - try self.base.file.?.pwriteAll(&[1]u8{0}, end_pos); } pub fn growAllocSection(self: *Elf, shdr_index: u32, needed_size: u64) !void { const shdr = &self.shdrs.items[shdr_index]; const maybe_phdr = if (self.phdr_to_shdr_table.get(shdr_index)) |phndx| &self.phdrs.items[phndx] else null; - const is_zerofill = shdr.sh_type == elf.SHT_NOBITS; - - if (needed_size > self.allocatedSize(shdr.sh_offset) and !is_zerofill) { - const existing_size = shdr.sh_size; - shdr.sh_size = 0; - // Must move the entire section. - const alignment = if (maybe_phdr) |phdr| phdr.p_align else shdr.sh_addralign; - const new_offset = self.findFreeSpace(needed_size, alignment); + log.debug("allocated size {x} of {s}, needed size {x}", .{ + self.allocatedSize(shdr.sh_offset), + self.getShString(shdr.sh_name), + needed_size, + }); - log.debug("new '{s}' file offset 0x{x} to 0x{x}", .{ - self.getShString(shdr.sh_name), - new_offset, - new_offset + existing_size, - }); + if (shdr.sh_type != elf.SHT_NOBITS) { + const allocated_size = self.allocatedSize(shdr.sh_offset); + if (shdr.sh_offset + allocated_size == std.math.maxInt(u64)) { + try self.base.file.?.setEndPos(shdr.sh_offset + needed_size); + } else if (needed_size > allocated_size) { + const existing_size = shdr.sh_size; + shdr.sh_size = 0; + // Must move the entire section. + const alignment = if (maybe_phdr) |phdr| phdr.p_align else shdr.sh_addralign; + const new_offset = try self.findFreeSpace(needed_size, alignment); - const amt = try self.base.file.?.copyRangeAll(shdr.sh_offset, self.base.file.?, new_offset, existing_size); - // TODO figure out what to about this error condition - how to communicate it up. - if (amt != existing_size) return error.InputOutput; + log.debug("new '{s}' file offset 0x{x} to 0x{x}", .{ + self.getShString(shdr.sh_name), + new_offset, + new_offset + existing_size, + }); - shdr.sh_offset = new_offset; - if (maybe_phdr) |phdr| phdr.p_offset = new_offset; - } + const amt = try self.base.file.?.copyRangeAll(shdr.sh_offset, self.base.file.?, new_offset, existing_size); + // TODO figure out what to about this error condition - how to communicate it up. + if (amt != existing_size) return error.InputOutput; - shdr.sh_size = needed_size; - if (!is_zerofill) { + shdr.sh_offset = new_offset; + if (maybe_phdr) |phdr| phdr.p_offset = new_offset; + } if (maybe_phdr) |phdr| phdr.p_filesz = needed_size; } + shdr.sh_size = needed_size; if (maybe_phdr) |phdr| { const mem_capacity = self.allocatedVirtualSize(phdr.p_vaddr); @@ -953,11 +946,14 @@ pub fn growNonAllocSection( ) !void { const shdr = &self.shdrs.items[shdr_index]; - if (needed_size > self.allocatedSize(shdr.sh_offset)) { + const allocated_size = self.allocatedSize(shdr.sh_offset); + if (shdr.sh_offset + allocated_size == std.math.maxInt(u64)) { + try self.base.file.?.setEndPos(shdr.sh_offset + needed_size); + } else if (needed_size > allocated_size) { const existing_size = shdr.sh_size; shdr.sh_size = 0; // Move all the symbols to a new file location. - const new_offset = self.findFreeSpace(needed_size, min_alignment); + const new_offset = try self.findFreeSpace(needed_size, min_alignment); log.debug("new '{s}' file offset 0x{x} to 0x{x}", .{ self.getShString(shdr.sh_name), @@ -977,7 +973,6 @@ pub fn growNonAllocSection( shdr.sh_offset = new_offset; } - shdr.sh_size = needed_size; self.markDirty(shdr_index); @@ -987,15 +982,21 @@ pub fn markDirty(self: *Elf, shdr_index: u32) void { const zig_object = self.zigObjectPtr().?; if (zig_object.dwarf) |_| { if (self.debug_info_section_index.? == shdr_index) { - zig_object.debug_info_header_dirty = true; - } else if (self.debug_line_section_index.? == shdr_index) { - zig_object.debug_line_header_dirty = true; + zig_object.debug_info_section_dirty = true; } else if (self.debug_abbrev_section_index.? == shdr_index) { zig_object.debug_abbrev_section_dirty = true; } else if (self.debug_str_section_index.? == shdr_index) { - zig_object.debug_strtab_dirty = true; + zig_object.debug_str_section_dirty = true; } else if (self.debug_aranges_section_index.? == shdr_index) { zig_object.debug_aranges_section_dirty = true; + } else if (self.debug_line_section_index.? == shdr_index) { + zig_object.debug_line_section_dirty = true; + } else if (self.debug_line_str_section_index.? == shdr_index) { + zig_object.debug_line_str_section_dirty = true; + } else if (self.debug_loclists_section_index.? == shdr_index) { + zig_object.debug_loclists_section_dirty = true; + } else if (self.debug_rnglists_section_index.? == shdr_index) { + zig_object.debug_rnglists_section_dirty = true; } } } @@ -1026,7 +1027,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod const target = comp.root_mod.resolved_target.result; const link_mode = comp.config.link_mode; - const directory = self.base.emit.directory; // Just an alias to make it shorter to type. + const directory = self.base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: { if (fs.path.dirname(full_out_path)) |dirname| { @@ -1281,7 +1282,6 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod try self.addCommentString(); try self.finalizeMergeSections(); try self.initOutputSections(); - try self.initMergeSections(); if (self.linkerDefinedPtr()) |obj| { try obj.initStartStopSymbols(self); } @@ -1381,7 +1381,7 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void { const target = self.base.comp.root_mod.resolved_target.result; const link_mode = self.base.comp.config.link_mode; - const directory = self.base.emit.directory; // Just an alias to make it shorter to type. + const directory = self.base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: { if (fs.path.dirname(full_out_path)) |dirname| { @@ -2079,7 +2079,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s const comp = self.base.comp; const gpa = comp.gpa; - const directory = self.base.emit.directory; // Just an alias to make it shorter to type. + const directory = self.base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); // If there is no Zig code to compile, then we should skip flushing the output file because it @@ -2677,15 +2677,6 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s } } -fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void { - const target = self.base.comp.root_mod.resolved_target.result; - const target_endian = target.cpu.arch.endian(); - switch (self.ptr_width) { - .p32 => mem.writeInt(u32, buf.addManyAsArrayAssumeCapacity(4), @as(u32, @intCast(addr)), target_endian), - .p64 => mem.writeInt(u64, buf.addManyAsArrayAssumeCapacity(8), addr, target_endian), - } -} - pub fn writeShdrTable(self: *Elf) !void { const gpa = self.base.comp.gpa; const target = self.base.comp.root_mod.resolved_target.result; @@ -2705,7 +2696,7 @@ pub fn writeShdrTable(self: *Elf) !void { if (needed_size > self.allocatedSize(shoff)) { self.shdr_table_offset = null; - self.shdr_table_offset = self.findFreeSpace(needed_size, shalign); + self.shdr_table_offset = try self.findFreeSpace(needed_size, shalign); } log.debug("writing section headers from 0x{x} to 0x{x}", .{ @@ -2831,7 +2822,7 @@ pub fn writeElfHeader(self: *Elf) !void { mem.writeInt(u16, hdr_buf[index..][0..2], @intFromEnum(elf_type), endian); index += 2; - const machine = target.cpu.arch.toElfMachine(); + const machine = target.toElfMachine(); mem.writeInt(u16, hdr_buf[index..][0..2], @intFromEnum(machine), endian); index += 2; @@ -2938,6 +2929,18 @@ pub fn updateNav( return self.zigObjectPtr().?.updateNav(self, pt, nav); } +pub fn updateContainerType( + self: *Elf, + pt: Zcu.PerThread, + ty: InternPool.Index, +) link.File.UpdateNavError!void { + if (build_options.skip_non_native and builtin.object_format != .elf) { + @panic("Attempted to compile for object format that was disabled by build configuration"); + } + if (self.llvm_object) |_| return; + return self.zigObjectPtr().?.updateContainerType(pt, ty); +} + pub fn updateExports( self: *Elf, pt: Zcu.PerThread, @@ -3044,17 +3047,17 @@ pub fn finalizeMergeSections(self: *Elf) !void { } pub fn updateMergeSectionSizes(self: *Elf) !void { + for (self.merge_sections.items) |*msec| { + msec.updateSize(); + } for (self.merge_sections.items) |*msec| { const shdr = &self.shdrs.items[msec.output_section_index]; - for (msec.finalized_subsections.items) |msub_index| { - const msub = msec.mergeSubsection(msub_index); - assert(msub.alive); - const offset = msub.alignment.forward(shdr.sh_size); - const padding = offset - shdr.sh_size; - msub.value = @intCast(offset); - shdr.sh_size += padding + msub.size; - shdr.sh_addralign = @max(shdr.sh_addralign, msub.alignment.toByteUnits() orelse 1); - } + const offset = msec.alignment.forward(shdr.sh_size); + const padding = offset - shdr.sh_size; + msec.value = @intCast(offset); + shdr.sh_size += padding + msec.size; + shdr.sh_addralign = @max(shdr.sh_addralign, msec.alignment.toByteUnits() orelse 1); + shdr.sh_entsize = if (shdr.sh_entsize == 0) msec.entsize else @min(shdr.sh_entsize, msec.entsize); } } @@ -3065,7 +3068,8 @@ pub fn writeMergeSections(self: *Elf) !void { for (self.merge_sections.items) |*msec| { const shdr = self.shdrs.items[msec.output_section_index]; - const size = math.cast(usize, shdr.sh_size) orelse return error.Overflow; + const fileoff = math.cast(usize, msec.value + shdr.sh_offset) orelse return error.Overflow; + const size = math.cast(usize, msec.size) orelse return error.Overflow; try buffer.ensureTotalCapacity(size); buffer.appendNTimesAssumeCapacity(0, size); @@ -3077,7 +3081,7 @@ pub fn writeMergeSections(self: *Elf) !void { @memcpy(buffer.items[off..][0..string.len], string); } - try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + try self.base.file.?.pwriteAll(buffer.items, fileoff); buffer.clearRetainingCapacity(); } } @@ -3086,26 +3090,9 @@ fn initOutputSections(self: *Elf) !void { for (self.objects.items) |index| { try self.file(index).?.object.initOutputSections(self); } -} - -pub fn initMergeSections(self: *Elf) !void { for (self.merge_sections.items) |*msec| { if (msec.finalized_subsections.items.len == 0) continue; - const name = msec.name(self); - const shndx = self.sectionByName(name) orelse try self.addSection(.{ - .name = msec.name_offset, - .type = msec.type, - .flags = msec.flags, - }); - msec.output_section_index = shndx; - - var entsize = msec.mergeSubsection(msec.finalized_subsections.items[0]).entsize; - for (msec.finalized_subsections.items) |msub_index| { - const msub = msec.mergeSubsection(msub_index); - entsize = @min(entsize, msub.entsize); - } - const shdr = &self.shdrs.items[shndx]; - shdr.sh_entsize = entsize; + try msec.initOutputSection(self); } } @@ -3156,8 +3143,8 @@ fn initSyntheticSections(self: *Elf) !void { }); const needs_rela_dyn = blk: { - if (self.got.flags.needs_rela or self.got.flags.needs_tlsld or - self.zig_got.flags.needs_rela or self.copy_rel.symbols.items.len > 0) break :blk true; + if (self.got.flags.needs_rela or self.got.flags.needs_tlsld or self.copy_rel.symbols.items.len > 0) + break :blk true; if (self.zigObjectPtr()) |zig_object| { if (zig_object.num_dynrelocs > 0) break :blk true; } @@ -3562,7 +3549,6 @@ fn sortPhdrs(self: *Elf) error{OutOfMemory}!void { for (&[_]*?u16{ &self.phdr_zig_load_re_index, - &self.phdr_zig_got_index, &self.phdr_zig_load_ro_index, &self.phdr_zig_load_zerofill_index, &self.phdr_table_index, @@ -3694,15 +3680,17 @@ fn resetShdrIndexes(self: *Elf, backlinks: []const u32) !void { &self.versym_section_index, &self.verneed_section_index, &self.zig_text_section_index, - &self.zig_got_section_index, &self.zig_data_rel_ro_section_index, &self.zig_data_section_index, &self.zig_bss_section_index, - &self.debug_str_section_index, &self.debug_info_section_index, &self.debug_abbrev_section_index, + &self.debug_str_section_index, &self.debug_aranges_section_index, &self.debug_line_section_index, + &self.debug_line_str_section_index, + &self.debug_loclists_section_index, + &self.debug_rnglists_section_index, }) |maybe_index| { if (maybe_index.*) |*index| { index.* = backlinks[index.*]; @@ -3827,6 +3815,7 @@ fn resetShdrIndexes(self: *Elf, backlinks: []const u32) !void { const atom_ptr = zo.atom(atom_index) orelse continue; atom_ptr.output_section_index = backlinks[atom_ptr.output_section_index]; } + if (zo.dwarf) |*dwarf| dwarf.reloadSectionMetadata(); } for (self.output_rela_sections.keys(), self.output_rela_sections.values()) |shndx, sec| { @@ -3893,7 +3882,7 @@ fn updateSectionSizes(self: *Elf) !void { } if (self.rela_dyn_section_index) |shndx| { - var num = self.got.numRela(self) + self.copy_rel.numRela() + self.zig_got.numRela(); + var num = self.got.numRela(self) + self.copy_rel.numRela(); if (self.zigObjectPtr()) |zig_object| { num += zig_object.num_dynrelocs; } @@ -4032,7 +4021,7 @@ fn allocatePhdrTable(self: *Elf) error{OutOfMemory}!void { /// Allocates alloc sections and creates load segments for sections /// extracted from input object files. -pub fn allocateAllocSections(self: *Elf) error{OutOfMemory}!void { +pub fn allocateAllocSections(self: *Elf) !void { // We use this struct to track maximum alignment of all TLS sections. // According to https://github.com/rui314/mold/commit/bd46edf3f0fe9e1a787ea453c4657d535622e61f in mold, // in-file offsets have to be aligned against the start of TLS program header. @@ -4152,7 +4141,7 @@ pub fn allocateAllocSections(self: *Elf) error{OutOfMemory}!void { } const first = self.shdrs.items[cover.items[0]]; - var off = self.findFreeSpace(filesz, @"align"); + var off = try self.findFreeSpace(filesz, @"align"); const phndx = try self.addPhdr(.{ .type = elf.PT_LOAD, .offset = off, @@ -4187,7 +4176,7 @@ pub fn allocateNonAllocSections(self: *Elf) !void { const needed_size = shdr.sh_size; if (needed_size > self.allocatedSize(shdr.sh_offset)) { shdr.sh_size = 0; - const new_offset = self.findFreeSpace(needed_size, shdr.sh_addralign); + const new_offset = try self.findFreeSpace(needed_size, shdr.sh_addralign); if (self.isDebugSection(@intCast(shndx))) { log.debug("moving {s} from 0x{x} to 0x{x}", .{ @@ -4195,20 +4184,21 @@ pub fn allocateNonAllocSections(self: *Elf) !void { shdr.sh_offset, new_offset, }); - const zig_object = self.zigObjectPtr().?; - const existing_size = blk: { - if (shndx == self.debug_info_section_index.?) - break :blk zig_object.debug_info_section_zig_size; - if (shndx == self.debug_abbrev_section_index.?) - break :blk zig_object.debug_abbrev_section_zig_size; - if (shndx == self.debug_str_section_index.?) - break :blk zig_object.debug_str_section_zig_size; - if (shndx == self.debug_aranges_section_index.?) - break :blk zig_object.debug_aranges_section_zig_size; - if (shndx == self.debug_line_section_index.?) - break :blk zig_object.debug_line_section_zig_size; - unreachable; - }; + const zo = self.zigObjectPtr().?; + const existing_size = for ([_]Symbol.Index{ + zo.debug_info_index.?, + zo.debug_abbrev_index.?, + zo.debug_aranges_index.?, + zo.debug_str_index.?, + zo.debug_line_index.?, + zo.debug_line_str_index.?, + zo.debug_loclists_index.?, + zo.debug_rnglists_index.?, + }) |sym_index| { + const sym = zo.symbol(sym_index); + const atom_ptr = sym.atom(self).?; + if (atom_ptr.output_section_index == shndx) break atom_ptr.size; + } else 0; const amt = try self.base.file.?.copyRangeAll( shdr.sh_offset, self.base.file.?, @@ -4304,18 +4294,21 @@ fn writeAtoms(self: *Elf) !void { // TODO really, really handle debug section separately const base_offset = if (self.isDebugSection(@intCast(shndx))) blk: { - const zig_object = self.zigObjectPtr().?; - if (shndx == self.debug_info_section_index.?) - break :blk zig_object.debug_info_section_zig_size; - if (shndx == self.debug_abbrev_section_index.?) - break :blk zig_object.debug_abbrev_section_zig_size; - if (shndx == self.debug_str_section_index.?) - break :blk zig_object.debug_str_section_zig_size; - if (shndx == self.debug_aranges_section_index.?) - break :blk zig_object.debug_aranges_section_zig_size; - if (shndx == self.debug_line_section_index.?) - break :blk zig_object.debug_line_section_zig_size; - unreachable; + const zo = self.zigObjectPtr().?; + break :blk for ([_]Symbol.Index{ + zo.debug_info_index.?, + zo.debug_abbrev_index.?, + zo.debug_aranges_index.?, + zo.debug_str_index.?, + zo.debug_line_index.?, + zo.debug_line_str_index.?, + zo.debug_loclists_index.?, + zo.debug_rnglists_index.?, + }) |sym_index| { + const sym = zo.symbol(sym_index); + const atom_ptr = sym.atom(self).?; + if (atom_ptr.output_section_index == shndx) break atom_ptr.size; + } else 0; } else 0; const sh_offset = shdr.sh_offset + base_offset; const sh_size = math.cast(usize, shdr.sh_size - base_offset) orelse return error.Overflow; @@ -4409,7 +4402,6 @@ pub fn updateSymtabSize(self: *Elf) !void { if (self.eh_frame_section_index) |_| { nlocals += 1; } - nlocals += @intCast(self.merge_sections.items.len); if (self.requiresThunks()) for (self.thunks.items) |*th| { th.output_symtab_ctx.ilocal = nlocals + 1; @@ -4431,15 +4423,6 @@ pub fn updateSymtabSize(self: *Elf) !void { strsize += ctx.strsize; } - if (self.zigObjectPtr()) |_| { - if (self.zig_got_section_index) |_| { - self.zig_got.output_symtab_ctx.ilocal = nlocals + 1; - self.zig_got.updateSymtabSize(self); - nlocals += self.zig_got.output_symtab_ctx.nlocals; - strsize += self.zig_got.output_symtab_ctx.strsize; - } - } - if (self.got_section_index) |_| { self.got.output_symtab_ctx.ilocal = nlocals + 1; self.got.updateSymtabSize(self); @@ -4576,9 +4559,6 @@ fn writeSyntheticSections(self: *Elf) !void { const shdr = self.shdrs.items[shndx]; try self.got.addRela(self); try self.copy_rel.addRela(self); - if (self.zigObjectPtr()) |_| { - try self.zig_got.addRela(self); - } self.sortRelaDyn(); try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.rela_dyn.items), shdr.sh_offset); } @@ -4674,10 +4654,6 @@ pub fn writeSymtab(self: *Elf) !void { obj.asFile().writeSymtab(self); } - if (self.zig_got_section_index) |_| { - self.zig_got.writeSymtab(self); - } - if (self.got_section_index) |_| { self.got.writeSymtab(self); } @@ -4749,30 +4725,12 @@ fn writeSectionSymbols(self: *Elf) void { }; ilocal += 1; } - - for (self.merge_sections.items) |msec| { - const shdr = self.shdrs.items[msec.output_section_index]; - const out_sym = &self.symtab.items[ilocal]; - out_sym.* = .{ - .st_name = 0, - .st_value = shdr.sh_addr, - .st_info = elf.STT_SECTION, - .st_shndx = @intCast(msec.output_section_index), - .st_size = 0, - .st_other = 0, - }; - ilocal += 1; - } } pub fn sectionSymbolOutputSymtabIndex(self: Elf, shndx: u32) u32 { if (self.eh_frame_section_index) |index| { if (index == shndx) return @intCast(self.output_sections.keys().len + 1); } - const base: usize = if (self.eh_frame_section_index == null) 0 else 1; - for (self.merge_sections.items, 0..) |msec, index| { - if (msec.output_section_index == shndx) return @intCast(self.output_sections.keys().len + 1 + index + base); - } return @intCast(self.output_sections.getIndex(shndx).? + 1); } @@ -5085,7 +5043,6 @@ pub fn isZigSection(self: Elf, shndx: u32) bool { self.zig_data_rel_ro_section_index, self.zig_data_section_index, self.zig_bss_section_index, - self.zig_got_section_index, }) |maybe_index| { if (maybe_index) |index| { if (index == shndx) return true; @@ -5101,6 +5058,9 @@ pub fn isDebugSection(self: Elf, shndx: u32) bool { self.debug_str_section_index, self.debug_aranges_section_index, self.debug_line_section_index, + self.debug_line_str_section_index, + self.debug_loclists_section_index, + self.debug_rnglists_section_index, }) |maybe_index| { if (maybe_index) |index| { if (index == shndx) return true; @@ -5166,7 +5126,7 @@ pub const AddSectionOpts = struct { pub fn addSection(self: *Elf, opts: AddSectionOpts) !u32 { const gpa = self.base.comp.gpa; - const index = @as(u32, @intCast(self.shdrs.items.len)); + const index: u32 = @intCast(self.shdrs.items.len); const shdr = try self.shdrs.addOne(gpa); shdr.* = .{ .sh_name = opts.name, @@ -5533,10 +5493,11 @@ fn formatShdr( _ = options; _ = unused_fmt_string; const shdr = ctx.shdr; - try writer.print("{s} : @{x} ({x}) : align({x}) : size({x}) : flags({})", .{ + try writer.print("{s} : @{x} ({x}) : align({x}) : size({x}) : entsize({x}) : flags({})", .{ ctx.elf_file.getShString(shdr.sh_name), shdr.sh_offset, shdr.sh_addr, shdr.sh_addralign, - shdr.sh_size, fmtShdrFlags(shdr.sh_flags), + shdr.sh_size, shdr.sh_entsize, + fmtShdrFlags(shdr.sh_flags), }); } @@ -5657,10 +5618,11 @@ fn fmtDumpState( if (self.zigObjectPtr()) |zig_object| { try writer.print("zig_object({d}) : {s}\n", .{ zig_object.index, zig_object.path }); - try writer.print("{}{}\n", .{ + try writer.print("{}{}", .{ zig_object.fmtAtoms(self), zig_object.fmtSymtab(self), }); + try writer.writeByte('\n'); } for (self.objects.items) |index| { @@ -5700,7 +5662,6 @@ fn fmtDumpState( } } - try writer.print("{}\n", .{self.zig_got.fmt(self)}); try writer.print("{}\n", .{self.got.fmt(self)}); try writer.print("{}\n", .{self.plt.fmt(self)}); @@ -5991,41 +5952,6 @@ const RelaSection = struct { }; const RelaSectionTable = std.AutoArrayHashMapUnmanaged(u32, RelaSection); -pub const R_ZIG_GOT32: u32 = 0xff00; -pub const R_ZIG_GOTPCREL: u32 = 0xff01; -pub const R_ZIG_GOT_HI20: u32 = 0xff02; -pub const R_ZIG_GOT_LO12: u32 = 0xff03; -pub const R_GOT_HI20_STATIC: u32 = 0xff04; -pub const R_GOT_LO12_I_STATIC: u32 = 0xff05; - -// Comptime asserts that no Zig relocs overlap with another ISA's reloc number -comptime { - const zig_relocs = .{ - R_ZIG_GOT32, - R_ZIG_GOT_HI20, - R_ZIG_GOT_LO12, - R_ZIG_GOTPCREL, - R_GOT_HI20_STATIC, - R_GOT_LO12_I_STATIC, - }; - - const other_relocs = .{ - elf.R_X86_64, - elf.R_AARCH64, - elf.R_RISCV, - elf.R_PPC64, - }; - - @setEvalBranchQuota(@min(other_relocs.len * zig_relocs.len * 256, 6200)); - for (other_relocs) |relocs| { - for (@typeInfo(relocs).Enum.fields) |reloc| { - for (zig_relocs) |zig_reloc| { - assert(reloc.value != zig_reloc); - } - } - } -} - fn defaultEntrySymbolName(cpu_arch: std.Target.Cpu.Arch) []const u8 { return switch (cpu_arch) { .mips, .mipsel, .mips64, .mips64el => "__start", @@ -6064,6 +5990,7 @@ const Allocator = std.mem.Allocator; const Archive = @import("Elf/Archive.zig"); pub const Atom = @import("Elf/Atom.zig"); const Cache = std.Build.Cache; +const Path = Cache.Path; const Compilation = @import("../Compilation.zig"); const ComdatGroupSection = synthetic_sections.ComdatGroupSection; const CopyRelSection = synthetic_sections.CopyRelSection; @@ -6095,6 +6022,5 @@ const StringTable = @import("StringTable.zig"); const Thunk = thunks.Thunk; const Value = @import("../Value.zig"); const VerneedSection = synthetic_sections.VerneedSection; -const ZigGotSection = synthetic_sections.ZigGotSection; const ZigObject = @import("Elf/ZigObject.zig"); const riscv = @import("riscv.zig"); diff --git a/zig/src/link/Elf/Atom.zig b/zig/src/link/Elf/Atom.zig index f2757f570b..3eb447ab75 100644 --- a/zig/src/link/Elf/Atom.zig +++ b/zig/src/link/Elf/Atom.zig @@ -201,11 +201,12 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void { // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address // range of the compilation unit. When we expand the text section, this range changes, // so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty. - zig_object.debug_info_header_dirty = true; + zig_object.debug_info_section_dirty = true; // This becomes dirty for the same reason. We could potentially make this more // fine-grained with the addition of support for more compilation units. It is planned to // model each package as a different compilation unit. zig_object.debug_aranges_section_dirty = true; + zig_object.debug_rnglists_section_dirty = true; } } shdr.sh_addralign = @max(shdr.sh_addralign, self.alignment.toByteUnits().?); @@ -301,7 +302,7 @@ pub fn free(self: *Atom, elf_file: *Elf) void { } // TODO create relocs free list - self.freeRelocs(elf_file); + self.freeRelocs(zo); // TODO figure out how to free input section mappind in ZigModule // const zig_object = elf_file.zigObjectPtr().? // assert(zig_object.atoms.swapRemove(self.atom_index)); @@ -376,21 +377,19 @@ pub fn markFdesDead(self: Atom, elf_file: *Elf) void { } } -pub fn addReloc(self: Atom, elf_file: *Elf, reloc: elf.Elf64_Rela) !void { - const comp = elf_file.base.comp; - const gpa = comp.gpa; - const file_ptr = self.file(elf_file).?; - assert(file_ptr == .zig_object); - const zig_object = file_ptr.zig_object; - const rels = &zig_object.relocs.items[self.relocs_section_index]; - try rels.append(gpa, reloc); +pub fn addReloc(self: Atom, alloc: Allocator, reloc: elf.Elf64_Rela, zo: *ZigObject) !void { + const rels = &zo.relocs.items[self.relocs_section_index]; + try rels.ensureUnusedCapacity(alloc, 1); + self.addRelocAssumeCapacity(reloc, zo); } -pub fn freeRelocs(self: Atom, elf_file: *Elf) void { - const file_ptr = self.file(elf_file).?; - assert(file_ptr == .zig_object); - const zig_object = file_ptr.zig_object; - zig_object.relocs.items[self.relocs_section_index].clearRetainingCapacity(); +pub fn addRelocAssumeCapacity(self: Atom, reloc: elf.Elf64_Rela, zo: *ZigObject) void { + const rels = &zo.relocs.items[self.relocs_section_index]; + rels.appendAssumeCapacity(reloc); +} + +pub fn freeRelocs(self: Atom, zo: *ZigObject) void { + zo.relocs.items[self.relocs_section_index].clearRetainingCapacity(); } pub fn scanRelocsRequiresCode(self: Atom, elf_file: *Elf) bool { @@ -746,12 +745,10 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) RelocError!voi const P = self.address(elf_file) + @as(i64, @intCast(rel.r_offset)); // Addend from the relocation. const A = rel.r_addend; - // Address of the target symbol - can be address of the symbol within an atom or address of PLT stub. + // Address of the target symbol - can be address of the symbol within an atom or address of PLT stub, or address of a Zig trampoline. const S = target.address(.{}, elf_file); // Address of the global offset table. const GOT = elf_file.gotAddress(); - // Address of the .zig.got table entry if any. - const ZIG_GOT = target.zigGotAddress(elf_file); // Relative offset to the start of the global offset table. const G = target.gotAddress(elf_file) - GOT; // // Address of the thread pointer. @@ -759,19 +756,18 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) RelocError!voi // Address of the dynamic thread pointer. const DTP = elf_file.dtpAddress(); - relocs_log.debug(" {s}: {x}: [{x} => {x}] G({x}) ZG({x}) ({s})", .{ + relocs_log.debug(" {s}: {x}: [{x} => {x}] GOT({x}) ({s})", .{ relocation.fmtRelocType(rel.r_type(), cpu_arch), r_offset, P, S + A, G + GOT + A, - ZIG_GOT + A, target.name(elf_file), }); try stream.seekTo(r_offset); - const args = ResolveArgs{ P, A, S, GOT, G, TP, DTP, ZIG_GOT }; + const args = ResolveArgs{ P, A, S, GOT, G, TP, DTP }; switch (cpu_arch) { .x86_64 => x86_64.resolveRelocAlloc(self, elf_file, rel, target, args, &it, code, &stream) catch |err| switch (err) { @@ -956,7 +952,7 @@ pub fn resolveRelocsNonAlloc(self: Atom, elf_file: *Elf, code: []u8, undefs: any // Address of the dynamic thread pointer. const DTP = elf_file.dtpAddress(); - const args = ResolveArgs{ P, A, S, GOT, 0, 0, DTP, 0 }; + const args = ResolveArgs{ P, A, S, GOT, 0, 0, DTP }; relocs_log.debug(" {}: {x}: [{x} => {x}] ({s})", .{ relocation.fmtRelocType(rel.r_type(), cpu_arch), @@ -1025,7 +1021,7 @@ pub fn format( _ = unused_fmt_string; _ = options; _ = writer; - @compileError("do not format symbols directly"); + @compileError("do not format Atom directly"); } pub fn fmt(atom: Atom, elf_file: *Elf) std.fmt.Formatter(format2) { @@ -1051,8 +1047,8 @@ fn format2( const atom = ctx.atom; const elf_file = ctx.elf_file; try writer.print("atom({d}) : {s} : @{x} : shdr({d}) : align({x}) : size({x})", .{ - atom.atom_index, atom.name(elf_file), atom.address(elf_file), - atom.output_section_index, atom.alignment, atom.size, + atom.atom_index, atom.name(elf_file), atom.address(elf_file), + atom.output_section_index, atom.alignment.toByteUnits() orelse 0, atom.size, }); if (atom.fdes(elf_file).len > 0) { try writer.writeAll(" : fdes{ "); @@ -1180,16 +1176,7 @@ const x86_64 = struct { .TLSDESC_CALL, => {}, - else => |x| switch (@intFromEnum(x)) { - // Zig custom relocations - Elf.R_ZIG_GOT32, - Elf.R_ZIG_GOTPCREL, - => { - assert(symbol.flags.has_zig_got); - }, - - else => try atom.reportUnhandledRelocError(rel, elf_file), - }, + else => try atom.reportUnhandledRelocError(rel, elf_file), } } @@ -1209,7 +1196,7 @@ const x86_64 = struct { const cwriter = stream.writer(); - const P, const A, const S, const GOT, const G, const TP, const DTP, const ZIG_GOT = args; + const P, const A, const S, const GOT, const G, const TP, const DTP = args; switch (r_type) { .NONE => unreachable, @@ -1224,9 +1211,8 @@ const x86_64 = struct { ); }, - .PLT32, - .PC32, - => try cwriter.writeInt(i32, @as(i32, @intCast(S + A - P)), .little), + .PLT32 => try cwriter.writeInt(i32, @as(i32, @intCast(S + A - P)), .little), + .PC32 => try cwriter.writeInt(i32, @as(i32, @intCast(S + A - P)), .little), .GOTPCREL => try cwriter.writeInt(i32, @as(i32, @intCast(G + GOT + A - P)), .little), .GOTPC32 => try cwriter.writeInt(i32, @as(i32, @intCast(GOT + A - P)), .little), @@ -1327,15 +1313,9 @@ const x86_64 = struct { } }, - .GOT32 => try cwriter.writeInt(i32, @as(i32, @intCast(G + GOT + A)), .little), - - else => |x| switch (@intFromEnum(x)) { - // Zig custom relocations - Elf.R_ZIG_GOT32 => try cwriter.writeInt(u32, @as(u32, @intCast(ZIG_GOT + A)), .little), - Elf.R_ZIG_GOTPCREL => try cwriter.writeInt(i32, @as(i32, @intCast(ZIG_GOT + A - P)), .little), + .GOT32 => try cwriter.writeInt(i32, @as(i32, @intCast(G + A)), .little), - else => try atom.reportUnhandledRelocError(rel, elf_file), - }, + else => try atom.reportUnhandledRelocError(rel, elf_file), } } @@ -1355,7 +1335,7 @@ const x86_64 = struct { const r_type: elf.R_X86_64 = @enumFromInt(rel.r_type()); const cwriter = stream.writer(); - _, const A, const S, const GOT, _, _, const DTP, _ = args; + _, const A, const S, const GOT, _, _, const DTP = args; switch (r_type) { .NONE => unreachable, @@ -1629,7 +1609,7 @@ const x86_64 = struct { const bits = @import("../../arch/x86_64/bits.zig"); const encoder = @import("../../arch/x86_64/encoder.zig"); const Disassembler = @import("../../arch/x86_64/Disassembler.zig"); - const Immediate = bits.Immediate; + const Immediate = Instruction.Immediate; const Instruction = encoder.Instruction; }; @@ -1738,9 +1718,8 @@ const aarch64 = struct { const code = code_buffer[r_offset..][0..4]; const file_ptr = atom.file(elf_file).?; - const P, const A, const S, const GOT, const G, const TP, const DTP, const ZIG_GOT = args; + const P, const A, const S, const GOT, const G, const TP, const DTP = args; _ = DTP; - _ = ZIG_GOT; switch (r_type) { .NONE => unreachable, @@ -1942,7 +1921,7 @@ const aarch64 = struct { const r_type: elf.R_AARCH64 = @enumFromInt(rel.r_type()); const cwriter = stream.writer(); - _, const A, const S, _, _, _, _, _ = args; + _, const A, const S, _, _, _, _ = args; switch (r_type) { .NONE => unreachable, @@ -1998,19 +1977,7 @@ const riscv = struct { .SUB32, => {}, - else => |x| switch (@intFromEnum(x)) { - Elf.R_ZIG_GOT_HI20, - Elf.R_ZIG_GOT_LO12, - => { - assert(symbol.flags.has_zig_got); - }, - - Elf.R_GOT_HI20_STATIC, - Elf.R_GOT_LO12_I_STATIC, - => symbol.flags.needs_got = true, - - else => try atom.reportUnhandledRelocError(rel, elf_file), - }, + else => try atom.reportUnhandledRelocError(rel, elf_file), } } @@ -2028,7 +1995,7 @@ const riscv = struct { const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow; const cwriter = stream.writer(); - const P, const A, const S, const GOT, const G, const TP, const DTP, const ZIG_GOT = args; + const P, const A, const S, const GOT, const G, const TP, const DTP = args; _ = TP; _ = DTP; @@ -2147,34 +2114,7 @@ const riscv = struct { // TODO: annotates an ADD instruction that can be removed when TPREL is relaxed }, - else => |x| switch (@intFromEnum(x)) { - // Zig custom relocations - Elf.R_ZIG_GOT_HI20 => { - assert(target.flags.has_zig_got); - const disp: u32 = @bitCast(math.cast(i32, ZIG_GOT + A) orelse return error.Overflow); - riscv_util.writeInstU(code[r_offset..][0..4], disp); - }, - - Elf.R_ZIG_GOT_LO12 => { - assert(target.flags.has_zig_got); - const value: u32 = @bitCast(math.cast(i32, ZIG_GOT + A) orelse return error.Overflow); - riscv_util.writeInstI(code[r_offset..][0..4], value); - }, - - Elf.R_GOT_HI20_STATIC => { - assert(target.flags.has_got); - const disp: u32 = @bitCast(math.cast(i32, G + GOT + A) orelse return error.Overflow); - riscv_util.writeInstU(code[r_offset..][0..4], disp); - }, - - Elf.R_GOT_LO12_I_STATIC => { - assert(target.flags.has_got); - const disp: u32 = @bitCast(math.cast(i32, G + GOT + A) orelse return error.Overflow); - riscv_util.writeInstI(code[r_offset..][0..4], disp); - }, - - else => try atom.reportUnhandledRelocError(rel, elf_file), - }, + else => try atom.reportUnhandledRelocError(rel, elf_file), } } @@ -2194,7 +2134,7 @@ const riscv = struct { const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow; const cwriter = stream.writer(); - _, const A, const S, const GOT, _, _, const DTP, _ = args; + _, const A, const S, const GOT, _, _, const DTP = args; _ = GOT; _ = DTP; @@ -2230,7 +2170,7 @@ const riscv = struct { const riscv_util = @import("../riscv.zig"); }; -const ResolveArgs = struct { i64, i64, i64, i64, i64, i64, i64, i64 }; +const ResolveArgs = struct { i64, i64, i64, i64, i64, i64, i64 }; const RelocError = error{ Overflow, diff --git a/zig/src/link/Elf/Symbol.zig b/zig/src/link/Elf/Symbol.zig index ce6b94a185..4f6c2b8c7e 100644 --- a/zig/src/link/Elf/Symbol.zig +++ b/zig/src/link/Elf/Symbol.zig @@ -101,7 +101,7 @@ pub fn symbolRank(symbol: Symbol, elf_file: *Elf) u32 { return file_ptr.symbolRank(sym, in_archive); } -pub fn address(symbol: Symbol, opts: struct { plt: bool = true }, elf_file: *Elf) i64 { +pub fn address(symbol: Symbol, opts: struct { plt: bool = true, trampoline: bool = true }, elf_file: *Elf) i64 { if (symbol.mergeSubsection(elf_file)) |msub| { if (!msub.alive) return 0; return msub.address(elf_file) + symbol.value; @@ -109,6 +109,9 @@ pub fn address(symbol: Symbol, opts: struct { plt: bool = true }, elf_file: *Elf if (symbol.flags.has_copy_rel) { return symbol.copyRelAddress(elf_file); } + if (symbol.flags.has_trampoline and opts.trampoline) { + return symbol.trampolineAddress(elf_file); + } if (symbol.flags.has_plt and opts.plt) { if (!symbol.flags.is_canonical and symbol.flags.has_got) { // We have a non-lazy bound function pointer, use that! @@ -217,23 +220,11 @@ pub fn tlsDescAddress(symbol: Symbol, elf_file: *Elf) i64 { return entry.address(elf_file); } -const GetOrCreateZigGotEntryResult = struct { - found_existing: bool, - index: ZigGotSection.Index, -}; - -pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, elf_file: *Elf) !GetOrCreateZigGotEntryResult { - assert(!elf_file.base.isRelocatable()); - assert(symbol.flags.needs_zig_got); - if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.extra(elf_file).zig_got }; - const index = try elf_file.zig_got.addSymbol(symbol_index, elf_file); - return .{ .found_existing = false, .index = index }; -} - -pub fn zigGotAddress(symbol: Symbol, elf_file: *Elf) i64 { - if (!symbol.flags.has_zig_got) return 0; - const extras = symbol.extra(elf_file); - return elf_file.zig_got.entryAddress(extras.zig_got, elf_file); +pub fn trampolineAddress(symbol: Symbol, elf_file: *Elf) i64 { + if (!symbol.flags.has_trampoline) return 0; + const zo = elf_file.zigObjectPtr().?; + const index = symbol.extra(elf_file).trampoline; + return zo.symbol(index).address(.{}, elf_file); } pub fn dsoAlignment(symbol: Symbol, elf_file: *Elf) !u64 { @@ -259,7 +250,7 @@ const AddExtraOpts = struct { tlsgd: ?u32 = null, gottp: ?u32 = null, tlsdesc: ?u32 = null, - zig_got: ?u32 = null, + trampoline: ?u32 = null, }; pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, elf_file: *Elf) void { @@ -312,7 +303,7 @@ pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void { const shdr = elf_file.shdrs.items[st_shndx]; if (shdr.sh_flags & elf.SHF_TLS != 0 and file_ptr != .linker_defined) break :blk symbol.address(.{ .plt = false }, elf_file) - elf_file.tlsAddress(); - break :blk symbol.address(.{ .plt = false }, elf_file); + break :blk symbol.address(.{ .plt = false, .trampoline = false }, elf_file); }; out.st_info = (st_bind << 4) | st_type; out.st_other = esym.st_other; @@ -331,7 +322,7 @@ pub fn format( _ = unused_fmt_string; _ = options; _ = writer; - @compileError("do not format symbols directly"); + @compileError("do not format Symbol directly"); } const FormatContext = struct { @@ -388,7 +379,7 @@ fn format2( try writer.print("%{d} : {s} : @{x}", .{ symbol.esym_index, symbol.fmtName(elf_file), - symbol.address(.{}, elf_file), + symbol.address(.{ .plt = false, .trampoline = false }, elf_file), }); if (symbol.file(elf_file)) |file_ptr| { if (symbol.isAbs(elf_file)) { @@ -456,17 +447,18 @@ pub const Flags = packed struct { needs_tlsdesc: bool = false, has_tlsdesc: bool = false, - /// Whether the symbol contains .zig.got indirection. - needs_zig_got: bool = false, - has_zig_got: bool = false, + /// Whether the symbol is a merge subsection. + merge_subsection: bool = false, + + /// ZigObject specific flags + /// Whether the symbol has a trampoline. + has_trampoline: bool = false, /// Whether the symbol is a TLS variable. - /// TODO this is really not needed if only we operated on esyms between - /// codegen and ZigObject. is_tls: bool = false, - /// Whether the symbol is a merge subsection. - merge_subsection: bool = false, + /// Whether the symbol is an extern pointer (as opposed to function). + is_extern_ptr: bool = false, }; pub const Extra = struct { @@ -479,8 +471,8 @@ pub const Extra = struct { tlsgd: u32 = 0, gottp: u32 = 0, tlsdesc: u32 = 0, - zig_got: u32 = 0, merge_section: u32 = 0, + trampoline: u32 = 0, }; pub const Index = u32; diff --git a/zig/src/link/Elf/ZigObject.zig b/zig/src/link/Elf/ZigObject.zig index ef3e2ed77c..ef6ec27b41 100644 --- a/zig/src/link/Elf/ZigObject.zig +++ b/zig/src/link/Elf/ZigObject.zig @@ -41,19 +41,23 @@ tls_variables: TlsTable = .{}, /// Table of tracked `Uav`s. uavs: UavTable = .{}, -debug_strtab_dirty: bool = false, +debug_info_section_dirty: bool = false, debug_abbrev_section_dirty: bool = false, debug_aranges_section_dirty: bool = false, -debug_info_header_dirty: bool = false, -debug_line_header_dirty: bool = false, - -/// Size contribution of Zig's metadata to each debug section. -/// Used to track start of metadata from input object files. -debug_info_section_zig_size: u64 = 0, -debug_abbrev_section_zig_size: u64 = 0, -debug_str_section_zig_size: u64 = 0, -debug_aranges_section_zig_size: u64 = 0, -debug_line_section_zig_size: u64 = 0, +debug_str_section_dirty: bool = false, +debug_line_section_dirty: bool = false, +debug_line_str_section_dirty: bool = false, +debug_loclists_section_dirty: bool = false, +debug_rnglists_section_dirty: bool = false, + +debug_info_index: ?Symbol.Index = null, +debug_abbrev_index: ?Symbol.Index = null, +debug_aranges_index: ?Symbol.Index = null, +debug_str_index: ?Symbol.Index = null, +debug_line_index: ?Symbol.Index = null, +debug_line_str_index: ?Symbol.Index = null, +debug_loclists_index: ?Symbol.Index = null, +debug_rnglists_index: ?Symbol.Index = null, pub const global_symbol_bit: u32 = 0x80000000; pub const symbol_mask: u32 = 0x7fffffff; @@ -76,10 +80,7 @@ pub fn init(self: *ZigObject, elf_file: *Elf) !void { switch (comp.config.debug_format) { .strip => {}, - .dwarf => |v| { - assert(v == .@"32"); - self.dwarf = Dwarf.init(&elf_file.base, .dwarf32); - }, + .dwarf => |v| self.dwarf = Dwarf.init(&elf_file.base, v), .code_view => unreachable, } } @@ -102,31 +103,25 @@ pub fn deinit(self: *ZigObject, allocator: Allocator) void { } self.relocs.deinit(allocator); - { - var it = self.navs.iterator(); - while (it.next()) |entry| { - entry.value_ptr.exports.deinit(allocator); - } - self.navs.deinit(allocator); + for (self.navs.values()) |*meta| { + meta.exports.deinit(allocator); } + self.navs.deinit(allocator); self.lazy_syms.deinit(allocator); - { - var it = self.uavs.iterator(); - while (it.next()) |entry| { - entry.value_ptr.exports.deinit(allocator); - } - self.uavs.deinit(allocator); + for (self.uavs.values()) |*meta| { + meta.exports.deinit(allocator); } + self.uavs.deinit(allocator); for (self.tls_variables.values()) |*tlv| { tlv.deinit(allocator); } self.tls_variables.deinit(allocator); - if (self.dwarf) |*dw| { - dw.deinit(); + if (self.dwarf) |*dwarf| { + dwarf.deinit(); } } @@ -161,46 +156,223 @@ pub fn flushModule(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !voi if (metadata.rodata_state != .unused) metadata.rodata_state = .flushed; } - if (self.dwarf) |*dw| { + if (build_options.enable_logging) { const pt: Zcu.PerThread = .{ .zcu = elf_file.base.comp.module.?, .tid = tid }; - try dw.flushModule(pt); - - // TODO I need to re-think how to handle ZigObject's debug sections AND debug sections - // extracted from input object files correctly. - if (self.debug_abbrev_section_dirty) { - try dw.writeDbgAbbrev(); - self.debug_abbrev_section_dirty = false; + for (self.navs.keys(), self.navs.values()) |nav_index, meta| { + checkNavAllocated(pt, nav_index, meta); } - - if (self.debug_info_header_dirty) { - const text_shdr = elf_file.shdrs.items[elf_file.zig_text_section_index.?]; - const low_pc = text_shdr.sh_addr; - const high_pc = text_shdr.sh_addr + text_shdr.sh_size; - try dw.writeDbgInfoHeader(pt.zcu, low_pc, high_pc); - self.debug_info_header_dirty = false; + for (self.uavs.keys(), self.uavs.values()) |uav_index, meta| { + checkUavAllocated(pt, uav_index, meta); } + } - if (self.debug_aranges_section_dirty) { - const text_shdr = elf_file.shdrs.items[elf_file.zig_text_section_index.?]; - try dw.writeDbgAranges(text_shdr.sh_addr, text_shdr.sh_size); - self.debug_aranges_section_dirty = false; - } + if (self.dwarf) |*dwarf| { + const pt: Zcu.PerThread = .{ .zcu = elf_file.base.comp.module.?, .tid = tid }; + try dwarf.flushModule(pt); - if (self.debug_line_header_dirty) { - try dw.writeDbgLineHeader(); - self.debug_line_header_dirty = false; - } + const gpa = elf_file.base.comp.gpa; + const cpu_arch = elf_file.getTarget().cpu.arch; + + // TODO invert this logic so that we manage the output section with the atom, not the + // other way around + for ([_]u32{ + self.debug_info_index.?, + self.debug_abbrev_index.?, + self.debug_str_index.?, + self.debug_aranges_index.?, + self.debug_line_index.?, + self.debug_line_str_index.?, + self.debug_loclists_index.?, + self.debug_rnglists_index.?, + }, [_]*Dwarf.Section{ + &dwarf.debug_info.section, + &dwarf.debug_abbrev.section, + &dwarf.debug_str.section, + &dwarf.debug_aranges.section, + &dwarf.debug_line.section, + &dwarf.debug_line_str.section, + &dwarf.debug_loclists.section, + &dwarf.debug_rnglists.section, + }) |sym_index, sect| { + const sym = self.symbol(sym_index); + const atom_ptr = self.atom(sym.ref.index).?; + if (!atom_ptr.alive) continue; + const shndx = sym.outputShndx(elf_file).?; + const shdr = elf_file.shdrs.items[shndx]; + const esym = &self.symtab.items(.elf_sym)[sym.esym_index]; + esym.st_size = shdr.sh_size; + atom_ptr.size = shdr.sh_size; + atom_ptr.alignment = Atom.Alignment.fromNonzeroByteUnits(shdr.sh_addralign); + + log.debug("parsing relocs in {s}", .{sym.name(elf_file)}); + + const relocs = &self.relocs.items[atom_ptr.relocsShndx().?]; + for (sect.units.items) |*unit| { + try relocs.ensureUnusedCapacity(gpa, unit.cross_unit_relocs.items.len + + unit.cross_section_relocs.items.len); + for (unit.cross_unit_relocs.items) |reloc| { + const target_unit = sect.getUnit(reloc.target_unit); + const r_offset = unit.off + reloc.source_off; + const r_addend: i64 = @intCast(target_unit.off + reloc.target_off + (if (reloc.target_entry.unwrap()) |target_entry| + target_unit.header_len + target_unit.getEntry(target_entry).assertNonEmpty(unit, sect, dwarf).off + else + 0)); + const r_type = relocation.dwarf.crossSectionRelocType(dwarf.format, cpu_arch); + log.debug(" {s} <- r_off={x}, r_add={x}, r_type={}", .{ + self.symbol(sym_index).name(elf_file), + r_offset, + r_addend, + relocation.fmtRelocType(r_type, cpu_arch), + }); + atom_ptr.addRelocAssumeCapacity(.{ + .r_offset = r_offset, + .r_addend = r_addend, + .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type, + }, self); + } + for (unit.cross_section_relocs.items) |reloc| { + const target_sym_index = switch (reloc.target_sec) { + .debug_abbrev => self.debug_abbrev_index.?, + .debug_info => self.debug_info_index.?, + .debug_line => self.debug_line_index.?, + .debug_line_str => self.debug_line_str_index.?, + .debug_loclists => self.debug_loclists_index.?, + .debug_rnglists => self.debug_rnglists_index.?, + .debug_str => self.debug_str_index.?, + }; + const target_sec = switch (reloc.target_sec) { + inline else => |target_sec| &@field(dwarf, @tagName(target_sec)).section, + }; + const target_unit = target_sec.getUnit(reloc.target_unit); + const r_offset = unit.off + reloc.source_off; + const r_addend: i64 = @intCast(target_unit.off + reloc.target_off + (if (reloc.target_entry.unwrap()) |target_entry| + target_unit.header_len + target_unit.getEntry(target_entry).assertNonEmpty(unit, sect, dwarf).off + else + 0)); + const r_type = relocation.dwarf.crossSectionRelocType(dwarf.format, cpu_arch); + log.debug(" {s} <- r_off={x}, r_add={x}, r_type={}", .{ + self.symbol(target_sym_index).name(elf_file), + r_offset, + r_addend, + relocation.fmtRelocType(r_type, cpu_arch), + }); + atom_ptr.addRelocAssumeCapacity(.{ + .r_offset = r_offset, + .r_addend = r_addend, + .r_info = (@as(u64, @intCast(target_sym_index)) << 32) | r_type, + }, self); + } - if (elf_file.debug_str_section_index) |shndx| { - if (self.debug_strtab_dirty or dw.strtab.buffer.items.len != elf_file.shdrs.items[shndx].sh_size) { - try elf_file.growNonAllocSection(shndx, dw.strtab.buffer.items.len, 1, false); - const shdr = elf_file.shdrs.items[shndx]; - try elf_file.base.file.?.pwriteAll(dw.strtab.buffer.items, shdr.sh_offset); - self.debug_strtab_dirty = false; + for (unit.entries.items) |*entry| { + const entry_off = unit.off + unit.header_len + entry.off; + + try relocs.ensureUnusedCapacity(gpa, entry.cross_entry_relocs.items.len + + entry.cross_unit_relocs.items.len + entry.cross_section_relocs.items.len + + entry.external_relocs.items.len); + for (entry.cross_entry_relocs.items) |reloc| { + const r_offset = entry_off + reloc.source_off; + const r_addend: i64 = @intCast(unit.off + reloc.target_off + unit.header_len + unit.getEntry(reloc.target_entry).assertNonEmpty(unit, sect, dwarf).off); + const r_type = relocation.dwarf.crossSectionRelocType(dwarf.format, cpu_arch); + log.debug(" {s} <- r_off={x}, r_add={x}, r_type={}", .{ + self.symbol(sym_index).name(elf_file), + r_offset, + r_addend, + relocation.fmtRelocType(r_type, cpu_arch), + }); + atom_ptr.addRelocAssumeCapacity(.{ + .r_offset = r_offset, + .r_addend = r_addend, + .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type, + }, self); + } + for (entry.cross_unit_relocs.items) |reloc| { + const target_unit = sect.getUnit(reloc.target_unit); + const r_offset = entry_off + reloc.source_off; + const r_addend: i64 = @intCast(target_unit.off + reloc.target_off + (if (reloc.target_entry.unwrap()) |target_entry| + target_unit.header_len + target_unit.getEntry(target_entry).assertNonEmpty(unit, sect, dwarf).off + else + 0)); + const r_type = relocation.dwarf.crossSectionRelocType(dwarf.format, cpu_arch); + log.debug(" {s} <- r_off={x}, r_add={x}, r_type={}", .{ + self.symbol(sym_index).name(elf_file), + r_offset, + r_addend, + relocation.fmtRelocType(r_type, cpu_arch), + }); + atom_ptr.addRelocAssumeCapacity(.{ + .r_offset = r_offset, + .r_addend = r_addend, + .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type, + }, self); + } + for (entry.cross_section_relocs.items) |reloc| { + const target_sym_index = switch (reloc.target_sec) { + .debug_abbrev => self.debug_abbrev_index.?, + .debug_info => self.debug_info_index.?, + .debug_line => self.debug_line_index.?, + .debug_line_str => self.debug_line_str_index.?, + .debug_loclists => self.debug_loclists_index.?, + .debug_rnglists => self.debug_rnglists_index.?, + .debug_str => self.debug_str_index.?, + }; + const target_sec = switch (reloc.target_sec) { + inline else => |target_sec| &@field(dwarf, @tagName(target_sec)).section, + }; + const target_unit = target_sec.getUnit(reloc.target_unit); + const r_offset = entry_off + reloc.source_off; + const r_addend: i64 = @intCast(target_unit.off + reloc.target_off + (if (reloc.target_entry.unwrap()) |target_entry| + target_unit.header_len + target_unit.getEntry(target_entry).assertNonEmpty(unit, sect, dwarf).off + else + 0)); + const r_type = relocation.dwarf.crossSectionRelocType(dwarf.format, cpu_arch); + log.debug(" {s} <- r_off={x}, r_add={x}, r_type={}", .{ + self.symbol(target_sym_index).name(elf_file), + r_offset, + r_addend, + relocation.fmtRelocType(r_type, cpu_arch), + }); + atom_ptr.addRelocAssumeCapacity(.{ + .r_offset = r_offset, + .r_addend = r_addend, + .r_info = (@as(u64, @intCast(target_sym_index)) << 32) | r_type, + }, self); + } + for (entry.external_relocs.items) |reloc| { + const target_sym = self.symbol(reloc.target_sym); + const r_offset = entry_off + reloc.source_off; + const r_addend: i64 = @intCast(reloc.target_off); + const r_type = relocation.dwarf.externalRelocType(target_sym.*, dwarf.address_size, cpu_arch); + log.debug(" {s} <- r_off={x}, r_add={x}, r_type={}", .{ + target_sym.name(elf_file), + r_offset, + r_addend, + relocation.fmtRelocType(r_type, cpu_arch), + }); + atom_ptr.addRelocAssumeCapacity(.{ + .r_offset = r_offset, + .r_addend = r_addend, + .r_info = (@as(u64, @intCast(reloc.target_sym)) << 32) | r_type, + }, self); + } + } + } + + if (elf_file.base.isRelocatable() and relocs.items.len > 0) { + const gop = try elf_file.output_rela_sections.getOrPut(gpa, shndx); + if (!gop.found_existing) { + const rela_sect_name = try std.fmt.allocPrintZ(gpa, ".rela{s}", .{elf_file.getShString(shdr.sh_name)}); + defer gpa.free(rela_sect_name); + const rela_sh_name = try elf_file.insertShString(rela_sect_name); + const rela_shndx = try elf_file.addRelaShdr(rela_sh_name, shndx); + gop.value_ptr.* = .{ .shndx = rela_shndx }; + } } } - self.saveDebugSectionsSizes(elf_file); + self.debug_abbrev_section_dirty = false; + self.debug_aranges_section_dirty = false; + self.debug_rnglists_section_dirty = false; + self.debug_str_section_dirty = false; } // The point of flushModule() is to commit changes, so in theory, nothing should @@ -209,25 +381,8 @@ pub fn flushModule(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !voi // such as debug_line_header_dirty and debug_info_header_dirty. assert(!self.debug_abbrev_section_dirty); assert(!self.debug_aranges_section_dirty); - assert(!self.debug_strtab_dirty); -} - -fn saveDebugSectionsSizes(self: *ZigObject, elf_file: *Elf) void { - if (elf_file.debug_info_section_index) |shndx| { - self.debug_info_section_zig_size = elf_file.shdrs.items[shndx].sh_size; - } - if (elf_file.debug_abbrev_section_index) |shndx| { - self.debug_abbrev_section_zig_size = elf_file.shdrs.items[shndx].sh_size; - } - if (elf_file.debug_str_section_index) |shndx| { - self.debug_str_section_zig_size = elf_file.shdrs.items[shndx].sh_size; - } - if (elf_file.debug_aranges_section_index) |shndx| { - self.debug_aranges_section_zig_size = elf_file.shdrs.items[shndx].sh_size; - } - if (elf_file.debug_line_section_index) |shndx| { - self.debug_line_section_zig_size = elf_file.shdrs.items[shndx].sh_size; - } + assert(!self.debug_rnglists_section_dirty); + assert(!self.debug_str_section_dirty); } fn newSymbol(self: *ZigObject, allocator: Allocator, name_off: u32, st_bind: u4) !Symbol.Index { @@ -291,7 +446,7 @@ fn newAtom(self: *ZigObject, allocator: Allocator, name_off: u32) !Atom.Index { return index; } -fn newSymbolWithAtom(self: *ZigObject, allocator: Allocator, name_off: u32) !Symbol.Index { +pub fn newSymbolWithAtom(self: *ZigObject, allocator: Allocator, name_off: u32) !Symbol.Index { const atom_index = try self.newAtom(allocator, name_off); const sym_index = try self.newLocalSymbol(allocator, name_off); const sym = self.symbol(sym_index); @@ -655,11 +810,11 @@ pub fn getNavVAddr( const vaddr = this_sym.address(.{}, elf_file); const parent_atom = self.symbol(reloc_info.parent_atom_index).atom(elf_file).?; const r_type = relocation.encode(.abs, elf_file.getTarget().cpu.arch); - try parent_atom.addReloc(elf_file, .{ + try parent_atom.addReloc(elf_file.base.comp.gpa, .{ .r_offset = reloc_info.offset, .r_info = (@as(u64, @intCast(this_sym_index)) << 32) | r_type, .r_addend = reloc_info.addend, - }); + }, self); return @intCast(vaddr); } @@ -674,11 +829,11 @@ pub fn getUavVAddr( const vaddr = sym.address(.{}, elf_file); const parent_atom = self.symbol(reloc_info.parent_atom_index).atom(elf_file).?; const r_type = relocation.encode(.abs, elf_file.getTarget().cpu.arch); - try parent_atom.addReloc(elf_file, .{ + try parent_atom.addReloc(elf_file.base.comp.gpa, .{ .r_offset = reloc_info.offset, .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type, .r_addend = reloc_info.addend, - }); + }, self); return @intCast(vaddr); } @@ -698,6 +853,7 @@ pub fn lowerUav( else => explicit_alignment, }; if (self.uavs.get(uav)) |metadata| { + assert(metadata.allocated); const sym = self.symbol(metadata.symbol_index); const existing_alignment = sym.atom(elf_file).?.alignment; if (uav_alignment.order(existing_alignment).compare(.lte)) @@ -729,7 +885,7 @@ pub fn lowerUav( .ok => |sym_index| sym_index, .fail => |em| return .{ .fail = em }, }; - try self.uavs.put(gpa, uav, .{ .symbol_index = sym_index }); + try self.uavs.put(gpa, uav, .{ .symbol_index = sym_index, .allocated = true }); return .{ .mcv = .{ .load_symbol = sym_index } }; } @@ -747,13 +903,7 @@ pub fn getOrCreateMetadataForLazySymbol( .const_data => .{ &gop.value_ptr.rodata_symbol_index, &gop.value_ptr.rodata_state }, }; switch (state_ptr.*) { - .unused => { - const gpa = elf_file.base.comp.gpa; - const symbol_index = try self.newSymbolWithAtom(gpa, 0); - const sym = self.symbol(symbol_index); - sym.flags.needs_zig_got = true; - symbol_index_ptr.* = symbol_index; - }, + .unused => symbol_index_ptr.* = try self.newSymbolWithAtom(pt.zcu.gpa, 0), .pending_flush => return symbol_index_ptr.*, .flushed => {}, } @@ -784,8 +934,8 @@ pub fn freeNav(self: *ZigObject, elf_file: *Elf, nav_index: InternPool.Nav.Index kv.value.exports.deinit(gpa); } - if (self.dwarf) |*dw| { - dw.freeNav(nav_index); + if (self.dwarf) |*dwarf| { + dwarf.freeNav(nav_index); } } @@ -807,9 +957,6 @@ pub fn getOrCreateMetadataForNav( sym.flags.is_tls = true; } } - if (!sym.flags.is_tls) { - sym.flags.needs_zig_got = true; - } gop.value_ptr.* = .{ .symbol_index = symbol_index }; } return gop.value_ptr.symbol_index; @@ -820,9 +967,9 @@ fn getNavShdrIndex( elf_file: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index, + sym_index: Symbol.Index, code: []const u8, ) error{OutOfMemory}!u32 { - _ = self; const ip = &zcu.intern_pool; const any_non_single_threaded = elf_file.base.comp.config.any_non_single_threaded; const nav_val = zcu.navValue(nav_index); @@ -832,10 +979,12 @@ fn getNavShdrIndex( .@"extern" => |@"extern"| .{ @"extern".is_const, @"extern".is_threadlocal, .none }, else => .{ true, false, nav_val.toIntern() }, }; + const has_relocs = self.symbol(sym_index).atom(elf_file).?.relocs(elf_file).len > 0; if (any_non_single_threaded and is_threadlocal) { - for (code) |byte| { - if (byte != 0) break; - } else return elf_file.sectionByName(".tbss") orelse try elf_file.addSection(.{ + const is_bss = !has_relocs and for (code) |byte| { + if (byte != 0) break false; + } else true; + if (is_bss) return elf_file.sectionByName(".tbss") orelse try elf_file.addSection(.{ .type = elf.SHT_NOBITS, .flags = elf.SHF_ALLOC | elf.SHF_WRITE | elf.SHF_TLS, .name = try elf_file.insertShString(".tbss"), @@ -854,9 +1003,10 @@ fn getNavShdrIndex( .Debug, .ReleaseSafe => elf_file.zig_data_section_index.?, .ReleaseFast, .ReleaseSmall => elf_file.zig_bss_section_index.?, }; - for (code) |byte| { - if (byte != 0) break; - } else return elf_file.zig_bss_section_index.?; + const is_bss = !has_relocs and for (code) |byte| { + if (byte != 0) break false; + } else true; + if (is_bss) return elf_file.zig_bss_section_index.?; return elf_file.zig_data_section_index.?; } @@ -909,13 +1059,6 @@ fn updateNavCode( if (old_vaddr != atom_ptr.value) { sym.value = 0; esym.st_value = 0; - - if (!elf_file.base.isRelocatable()) { - log.debug(" (writing new offset table entry)", .{}); - assert(sym.flags.has_zig_got); - const extra = sym.extra(elf_file); - try elf_file.zig_got.writeOne(elf_file, extra.zig_got); - } } } else if (code.len < old_size) { atom_ptr.shrink(elf_file); @@ -925,15 +1068,11 @@ fn updateNavCode( errdefer self.freeNavMetadata(elf_file, sym_index); sym.value = 0; - sym.flags.needs_zig_got = true; esym.st_value = 0; - - if (!elf_file.base.isRelocatable()) { - const gop = try sym.getOrCreateZigGotEntry(sym_index, elf_file); - try elf_file.zig_got.writeOne(elf_file, gop.index); - } } + self.navs.getPtr(nav_index).?.allocated = true; + if (elf_file.base.child_pid) |pid| { switch (builtin.os.tag) { .linux => { @@ -959,6 +1098,7 @@ fn updateNavCode( if (shdr.sh_type != elf.SHT_NOBITS) { const file_offset = shdr.sh_offset + @as(u64, @intCast(atom_ptr.value)); try elf_file.base.file.?.pwriteAll(code, file_offset); + log.debug("writing {} from 0x{x} to 0x{x}", .{ nav.fqn.fmt(ip), file_offset, file_offset + code.len }); } } @@ -1001,6 +1141,8 @@ fn updateTlv( atom_ptr.alignment = required_alignment; atom_ptr.size = code.len; + self.navs.getPtr(nav_index).?.allocated = true; + { const gop = try self.tls_variables.getOrPut(gpa, atom_ptr.atom_index); assert(!gop.found_existing); // TODO incremental updates @@ -1038,13 +1180,13 @@ pub fn updateFunc( log.debug("updateFunc {}({d})", .{ ip.getNav(func.owner_nav).fqn.fmt(ip), func.owner_nav }); const sym_index = try self.getOrCreateMetadataForNav(elf_file, func.owner_nav); - self.symbol(sym_index).atom(elf_file).?.freeRelocs(elf_file); + self.symbol(sym_index).atom(elf_file).?.freeRelocs(self); var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); - var dwarf_state = if (self.dwarf) |*dw| try dw.initNavState(pt, func.owner_nav) else null; - defer if (dwarf_state) |*ds| ds.deinit(); + var debug_wip_nav = if (self.dwarf) |*dwarf| try dwarf.initWipNav(pt, func.owner_nav, sym_index) else null; + defer if (debug_wip_nav) |*wip_nav| wip_nav.deinit(); const res = try codegen.generateFunction( &elf_file.base, @@ -1054,7 +1196,7 @@ pub fn updateFunc( air, liveness, &code_buffer, - if (dwarf_state) |*ds| .{ .dwarf = ds } else .none, + if (debug_wip_nav) |*dn| .{ .dwarf = dn } else .none, ); const code = switch (res) { @@ -1065,21 +1207,72 @@ pub fn updateFunc( }, }; - const shndx = try self.getNavShdrIndex(elf_file, zcu, func.owner_nav, code); + const shndx = try self.getNavShdrIndex(elf_file, zcu, func.owner_nav, sym_index, code); + log.debug("setting shdr({x},{s}) for {}", .{ + shndx, + elf_file.getShString(elf_file.shdrs.items[shndx].sh_name), + ip.getNav(func.owner_nav).fqn.fmt(ip), + }); + const old_rva, const old_alignment = blk: { + const atom_ptr = self.symbol(sym_index).atom(elf_file).?; + break :blk .{ atom_ptr.value, atom_ptr.alignment }; + }; try self.updateNavCode(elf_file, pt, func.owner_nav, sym_index, shndx, code, elf.STT_FUNC); + const new_rva, const new_alignment = blk: { + const atom_ptr = self.symbol(sym_index).atom(elf_file).?; + break :blk .{ atom_ptr.value, atom_ptr.alignment }; + }; - if (dwarf_state) |*ds| { + if (debug_wip_nav) |*wip_nav| { const sym = self.symbol(sym_index); - try self.dwarf.?.commitNavState( + try self.dwarf.?.finishWipNav( pt, func.owner_nav, - @intCast(sym.address(.{}, elf_file)), - sym.atom(elf_file).?.size, - ds, + .{ + .index = sym_index, + .addr = @intCast(sym.address(.{}, elf_file)), + .size = sym.atom(elf_file).?.size, + }, + wip_nav, ); } // Exports will be updated by `Zcu.processExports` after the update. + + if (old_rva != new_rva and old_rva > 0) { + // If we had to reallocate the function, we re-use the existing slot for a trampoline. + // In the rare case that the function has been further overaligned we skip creating a + // trampoline and update all symbols referring this function. + if (old_alignment.order(new_alignment) == .lt) { + @panic("TODO update all symbols referring this function"); + } + + // Create a trampoline to the new location at `old_rva`. + if (!self.symbol(sym_index).flags.has_trampoline) { + const name = try std.fmt.allocPrint(gpa, "{s}$trampoline", .{ + self.symbol(sym_index).name(elf_file), + }); + defer gpa.free(name); + const name_off = try self.addString(gpa, name); + const tr_size = trampolineSize(elf_file.getTarget().cpu.arch); + const tr_sym_index = try self.newSymbolWithAtom(gpa, name_off); + const tr_sym = self.symbol(tr_sym_index); + const tr_esym = &self.symtab.items(.elf_sym)[tr_sym.esym_index]; + tr_esym.st_info |= elf.STT_OBJECT; + tr_esym.st_size = tr_size; + const tr_atom_ptr = tr_sym.atom(elf_file).?; + tr_atom_ptr.value = old_rva; + tr_atom_ptr.alive = true; + tr_atom_ptr.alignment = old_alignment; + tr_atom_ptr.output_section_index = elf_file.zig_text_section_index.?; + tr_atom_ptr.size = tr_size; + const target_sym = self.symbol(sym_index); + target_sym.addExtra(.{ .trampoline = tr_sym_index }, elf_file); + target_sym.flags.has_trampoline = true; + } + const target_sym = self.symbol(sym_index); + try writeTrampoline(self.symbol(target_sym.extra(elf_file).trampoline).*, target_sym.*, elf_file); + } } pub fn updateNav( @@ -1097,71 +1290,91 @@ pub fn updateNav( log.debug("updateNav {}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const nav_val = zcu.navValue(nav_index); - const nav_init = switch (ip.indexToKey(nav_val.toIntern())) { - .variable => |variable| Value.fromInterned(variable.init), + const nav_init = switch (ip.indexToKey(nav.status.resolved.val)) { + .func => .none, + .variable => |variable| variable.init, .@"extern" => |@"extern"| { if (ip.isFunctionType(@"extern".ty)) return; - // Extern variable gets a .got entry only. const sym_index = try self.getGlobalSymbol( elf_file, nav.name.toSlice(ip), @"extern".lib_name.toSlice(ip), ); - self.symbol(sym_index).flags.needs_got = true; + self.symbol(sym_index).flags.is_extern_ptr = true; return; }, - else => nav_val, + else => nav.status.resolved.val, }; - const sym_index = try self.getOrCreateMetadataForNav(elf_file, nav_index); - self.symbol(sym_index).atom(elf_file).?.freeRelocs(elf_file); - - var code_buffer = std.ArrayList(u8).init(zcu.gpa); - defer code_buffer.deinit(); - - var nav_state: ?Dwarf.NavState = if (self.dwarf) |*dw| try dw.initNavState(pt, nav_index) else null; - defer if (nav_state) |*ns| ns.deinit(); - - // TODO implement .debug_info for global variables - const res = try codegen.generateSymbol( - &elf_file.base, - pt, - zcu.navSrcLoc(nav_index), - nav_init, - &code_buffer, - if (nav_state) |*ns| .{ .dwarf = ns } else .none, - .{ .parent_atom_index = sym_index }, - ); + if (nav_init != .none and Value.fromInterned(nav_init).typeOf(zcu).hasRuntimeBits(pt)) { + const sym_index = try self.getOrCreateMetadataForNav(elf_file, nav_index); + self.symbol(sym_index).atom(elf_file).?.freeRelocs(self); - const code = switch (res) { - .ok => code_buffer.items, - .fail => |em| { - try zcu.failed_codegen.put(zcu.gpa, nav_index, em); - return; - }, - }; + var code_buffer = std.ArrayList(u8).init(zcu.gpa); + defer code_buffer.deinit(); - const shndx = try self.getNavShdrIndex(elf_file, zcu, nav_index, code); - if (elf_file.shdrs.items[shndx].sh_flags & elf.SHF_TLS != 0) - try self.updateTlv(elf_file, pt, nav_index, sym_index, shndx, code) - else - try self.updateNavCode(elf_file, pt, nav_index, sym_index, shndx, code, elf.STT_OBJECT); + var debug_wip_nav = if (self.dwarf) |*dwarf| try dwarf.initWipNav(pt, nav_index, sym_index) else null; + defer if (debug_wip_nav) |*wip_nav| wip_nav.deinit(); - if (nav_state) |*ns| { - const sym = self.symbol(sym_index); - try self.dwarf.?.commitNavState( + // TODO implement .debug_info for global variables + const res = try codegen.generateSymbol( + &elf_file.base, pt, - nav_index, - @intCast(sym.address(.{}, elf_file)), - sym.atom(elf_file).?.size, - ns, + zcu.navSrcLoc(nav_index), + Value.fromInterned(nav_init), + &code_buffer, + if (debug_wip_nav) |*wip_nav| .{ .dwarf = wip_nav } else .none, + .{ .parent_atom_index = sym_index }, ); - } + + const code = switch (res) { + .ok => code_buffer.items, + .fail => |em| { + try zcu.failed_codegen.put(zcu.gpa, nav_index, em); + return; + }, + }; + + const shndx = try self.getNavShdrIndex(elf_file, zcu, nav_index, sym_index, code); + log.debug("setting shdr({x},{s}) for {}", .{ + shndx, + elf_file.getShString(elf_file.shdrs.items[shndx].sh_name), + nav.fqn.fmt(ip), + }); + if (elf_file.shdrs.items[shndx].sh_flags & elf.SHF_TLS != 0) + try self.updateTlv(elf_file, pt, nav_index, sym_index, shndx, code) + else + try self.updateNavCode(elf_file, pt, nav_index, sym_index, shndx, code, elf.STT_OBJECT); + + if (debug_wip_nav) |*wip_nav| { + const sym = self.symbol(sym_index); + try self.dwarf.?.finishWipNav( + pt, + nav_index, + .{ + .index = sym_index, + .addr = @intCast(sym.address(.{}, elf_file)), + .size = sym.atom(elf_file).?.size, + }, + wip_nav, + ); + } + } else if (self.dwarf) |*dwarf| try dwarf.updateComptimeNav(pt, nav_index); // Exports will be updated by `Zcu.processExports` after the update. } +pub fn updateContainerType( + self: *ZigObject, + pt: Zcu.PerThread, + ty: InternPool.Index, +) link.File.UpdateNavError!void { + const tracy = trace(@src()); + defer tracy.end(); + + if (self.dwarf) |*dwarf| try dwarf.updateContainerType(pt, ty); +} + fn updateLazySymbol( self: *ZigObject, elf_file: *Elf, @@ -1225,14 +1438,8 @@ fn updateLazySymbol( errdefer self.freeNavMetadata(elf_file, symbol_index); local_sym.value = 0; - local_sym.flags.needs_zig_got = true; local_esym.st_value = 0; - if (!elf_file.base.isRelocatable()) { - const gop = try local_sym.getOrCreateZigGotEntry(symbol_index, elf_file); - try elf_file.zig_got.writeOne(elf_file, gop.index); - } - const shdr = elf_file.shdrs.items[output_section_index]; const file_offset = shdr.sh_offset + @as(u64, @intCast(atom_ptr.value)); try elf_file.base.file.?.pwriteAll(code, file_offset); @@ -1404,8 +1611,8 @@ pub fn updateNavLineNumber( log.debug("updateNavLineNumber {}({d})", .{ nav.fqn.fmt(ip), nav_index }); - if (self.dwarf) |*dw| { - try dw.updateNavLineNumber(pt.zcu, nav_index); + if (self.dwarf) |*dwarf| { + try dwarf.updateNavLineNumber(pt.zcu, nav_index); } } @@ -1440,11 +1647,57 @@ pub fn getGlobalSymbol(self: *ZigObject, elf_file: *Elf, name: []const u8, lib_n return lookup_gop.value_ptr.*; } +const max_trampoline_len = 12; + +fn trampolineSize(cpu_arch: std.Target.Cpu.Arch) u64 { + const len = switch (cpu_arch) { + .x86_64 => 5, // jmp rel32 + else => @panic("TODO implement trampoline size for this CPU arch"), + }; + comptime assert(len <= max_trampoline_len); + return len; +} + +fn writeTrampoline(tr_sym: Symbol, target: Symbol, elf_file: *Elf) !void { + const atom_ptr = tr_sym.atom(elf_file).?; + const shdr = elf_file.shdrs.items[atom_ptr.output_section_index]; + const fileoff = shdr.sh_offset + @as(u64, @intCast(atom_ptr.value)); + const source_addr = tr_sym.address(.{}, elf_file); + const target_addr = target.address(.{ .trampoline = false }, elf_file); + var buf: [max_trampoline_len]u8 = undefined; + const out = switch (elf_file.getTarget().cpu.arch) { + .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf), + else => @panic("TODO implement write trampoline for this CPU arch"), + }; + try elf_file.base.file.?.pwriteAll(out, fileoff); + + if (elf_file.base.child_pid) |pid| { + switch (builtin.os.tag) { + .linux => { + var local_vec: [1]std.posix.iovec_const = .{.{ + .base = out.ptr, + .len = out.len, + }}; + var remote_vec: [1]std.posix.iovec_const = .{.{ + .base = @as([*]u8, @ptrFromInt(@as(usize, @intCast(source_addr)))), + .len = out.len, + }}; + const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0); + switch (std.os.linux.E.init(rc)) { + .SUCCESS => assert(rc == out.len), + else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}), + } + }, + else => return error.HotSwapUnavailableOnHostOperatingSystem, + } + } +} + pub fn asFile(self: *ZigObject) File { return .{ .zig_object = self }; } -fn addString(self: *ZigObject, allocator: Allocator, string: []const u8) !u32 { +pub fn addString(self: *ZigObject, allocator: Allocator, string: []const u8) !u32 { return self.strtab.insert(allocator, string); } @@ -1662,6 +1915,8 @@ const AvMetadata = struct { symbol_index: Symbol.Index, /// A list of all exports aliases of this Av. exports: std.ArrayListUnmanaged(Symbol.Index) = .{}, + /// Set to true if the AV has been initialized and allocated. + allocated: bool = false, fn @"export"(m: AvMetadata, zig_object: *ZigObject, name: []const u8) ?*u32 { for (m.exports.items) |*exp| { @@ -1672,6 +1927,32 @@ const AvMetadata = struct { } }; +fn checkNavAllocated(pt: Zcu.PerThread, index: InternPool.Nav.Index, meta: AvMetadata) void { + if (!meta.allocated) { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const nav = ip.getNav(index); + log.err("NAV {}({d}) assigned symbol {d} but not allocated!", .{ + nav.fqn.fmt(ip), + index, + meta.symbol_index, + }); + } +} + +fn checkUavAllocated(pt: Zcu.PerThread, index: InternPool.Index, meta: AvMetadata) void { + if (!meta.allocated) { + const zcu = pt.zcu; + const uav = Value.fromInterned(index); + const ty = uav.typeOf(zcu); + log.err("UAV {}({d}) assigned symbol {d} but not allocated!", .{ + ty.fmt(pt), + index, + meta.symbol_index, + }); + } +} + const TlsVariable = struct { symbol_index: Symbol.Index, code: []const u8 = &[0]u8{}, @@ -1682,12 +1963,26 @@ const TlsVariable = struct { }; const AtomList = std.ArrayListUnmanaged(Atom.Index); -const NavTable = std.AutoHashMapUnmanaged(InternPool.Nav.Index, AvMetadata); -const UavTable = std.AutoHashMapUnmanaged(InternPool.Index, AvMetadata); +const NavTable = std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, AvMetadata); +const UavTable = std.AutoArrayHashMapUnmanaged(InternPool.Index, AvMetadata); const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.Index, LazySymbolMetadata); const TlsTable = std.AutoArrayHashMapUnmanaged(Atom.Index, TlsVariable); +const x86_64 = struct { + fn writeTrampolineCode(source_addr: i64, target_addr: i64, buf: *[max_trampoline_len]u8) ![]u8 { + const disp = @as(i64, @intCast(target_addr)) - source_addr - 5; + var bytes = [_]u8{ + 0xe9, 0x00, 0x00, 0x00, 0x00, // jmp rel32 + }; + assert(bytes.len == trampolineSize(.x86_64)); + mem.writeInt(i32, bytes[1..][0..4], @intCast(disp), .little); + @memcpy(buf[0..bytes.len], &bytes); + return buf[0..bytes.len]; + } +}; + const assert = std.debug.assert; +const build_options = @import("build_options"); const builtin = @import("builtin"); const codegen = @import("../../codegen.zig"); const elf = std.elf; diff --git a/zig/src/link/Elf/merge_section.zig b/zig/src/link/Elf/merge_section.zig index baec316c3d..7ffb17e963 100644 --- a/zig/src/link/Elf/merge_section.zig +++ b/zig/src/link/Elf/merge_section.zig @@ -1,4 +1,8 @@ pub const MergeSection = struct { + value: u64 = 0, + size: u64 = 0, + alignment: Atom.Alignment = .@"1", + entsize: u32 = 0, name_offset: u32 = 0, type: u32 = 0, flags: u64 = 0, @@ -26,7 +30,7 @@ pub const MergeSection = struct { pub fn address(msec: MergeSection, elf_file: *Elf) i64 { const shdr = elf_file.shdrs.items[msec.output_section_index]; - return @intCast(shdr.sh_addr); + return @intCast(shdr.sh_addr + msec.value); } const InsertResult = struct { @@ -90,6 +94,29 @@ pub const MergeSection = struct { std.mem.sort(MergeSubsection.Index, msec.finalized_subsections.items, msec, sortFn); } + pub fn updateSize(msec: *MergeSection) void { + for (msec.finalized_subsections.items) |msub_index| { + const msub = msec.mergeSubsection(msub_index); + assert(msub.alive); + const offset = msub.alignment.forward(msec.size); + const padding = offset - msec.size; + msub.value = @intCast(offset); + msec.size += padding + msub.size; + msec.alignment = msec.alignment.max(msub.alignment); + msec.entsize = if (msec.entsize == 0) msub.entsize else @min(msec.entsize, msub.entsize); + } + } + + pub fn initOutputSection(msec: *MergeSection, elf_file: *Elf) !void { + const shndx = elf_file.sectionByName(msec.name(elf_file)) orelse try elf_file.addSection(.{ + .name = msec.name_offset, + .type = msec.type, + .flags = msec.flags, + }); + try elf_file.output_sections.put(elf_file.base.comp.gpa, shndx, .{}); + msec.output_section_index = shndx; + } + pub fn addMergeSubsection(msec: *MergeSection, allocator: Allocator) !MergeSubsection.Index { const index: MergeSubsection.Index = @intCast(msec.subsections.items.len); const msub = try msec.subsections.addOne(allocator); @@ -163,9 +190,12 @@ pub const MergeSection = struct { _ = unused_fmt_string; const msec = ctx.msec; const elf_file = ctx.elf_file; - try writer.print("{s} : @{x} : type({x}) : flags({x})\n", .{ + try writer.print("{s} : @{x} : size({x}) : align({x}) : entsize({x}) : type({x}) : flags({x})\n", .{ msec.name(elf_file), msec.address(elf_file), + msec.size, + msec.alignment.toByteUnits() orelse 0, + msec.entsize, msec.type, msec.flags, }); diff --git a/zig/src/link/Elf/relocatable.zig b/zig/src/link/Elf/relocatable.zig index 92eace8501..50b9b562d1 100644 --- a/zig/src/link/Elf/relocatable.zig +++ b/zig/src/link/Elf/relocatable.zig @@ -42,7 +42,11 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation, module_obj_path: ?[]co try elf_file.finalizeMergeSections(); zig_object.claimUnresolvedObject(elf_file); - try elf_file.initMergeSections(); + for (elf_file.merge_sections.items) |*msec| { + if (msec.finalized_subsections.items.len == 0) continue; + try msec.initOutputSection(elf_file); + } + try elf_file.initSymtab(); try elf_file.initShStrtab(); try elf_file.sortShdrs(); @@ -198,7 +202,6 @@ pub fn flushObject(elf_file: *Elf, comp: *Compilation, module_obj_path: ?[]const claimUnresolved(elf_file); try initSections(elf_file); - try elf_file.initMergeSections(); try elf_file.sortShdrs(); if (elf_file.zigObjectPtr()) |zig_object| { try zig_object.addAtomsToRelaSections(elf_file); @@ -294,6 +297,11 @@ fn initSections(elf_file: *Elf) !void { try object.initRelaSections(elf_file); } + for (elf_file.merge_sections.items) |*msec| { + if (msec.finalized_subsections.items.len == 0) continue; + try msec.initOutputSection(elf_file); + } + const needs_eh_frame = for (elf_file.objects.items) |index| { if (elf_file.file(index).?.object.cies.items.len > 0) break true; } else false; @@ -401,7 +409,7 @@ fn allocateAllocSections(elf_file: *Elf) !void { const needed_size = shdr.sh_size; if (needed_size > elf_file.allocatedSize(shdr.sh_offset)) { shdr.sh_size = 0; - const new_offset = elf_file.findFreeSpace(needed_size, shdr.sh_addralign); + const new_offset = try elf_file.findFreeSpace(needed_size, shdr.sh_addralign); shdr.sh_offset = new_offset; shdr.sh_size = needed_size; } @@ -423,18 +431,21 @@ fn writeAtoms(elf_file: *Elf) !void { // TODO really, really handle debug section separately const base_offset = if (elf_file.isDebugSection(@intCast(shndx))) blk: { - const zig_object = elf_file.zigObjectPtr().?; - if (shndx == elf_file.debug_info_section_index.?) - break :blk zig_object.debug_info_section_zig_size; - if (shndx == elf_file.debug_abbrev_section_index.?) - break :blk zig_object.debug_abbrev_section_zig_size; - if (shndx == elf_file.debug_str_section_index.?) - break :blk zig_object.debug_str_section_zig_size; - if (shndx == elf_file.debug_aranges_section_index.?) - break :blk zig_object.debug_aranges_section_zig_size; - if (shndx == elf_file.debug_line_section_index.?) - break :blk zig_object.debug_line_section_zig_size; - unreachable; + const zo = elf_file.zigObjectPtr().?; + break :blk for ([_]Symbol.Index{ + zo.debug_info_index.?, + zo.debug_abbrev_index.?, + zo.debug_aranges_index.?, + zo.debug_str_index.?, + zo.debug_line_index.?, + zo.debug_line_str_index.?, + zo.debug_loclists_index.?, + zo.debug_rnglists_index.?, + }) |sym_index| { + const sym = zo.symbol(sym_index); + const atom_ptr = sym.atom(elf_file).?; + if (atom_ptr.output_section_index == shndx) break atom_ptr.size; + } else 0; } else 0; const sh_offset = shdr.sh_offset + base_offset; const sh_size = math.cast(usize, shdr.sh_size - base_offset) orelse return error.Overflow; @@ -580,3 +591,4 @@ const Compilation = @import("../../Compilation.zig"); const Elf = @import("../Elf.zig"); const File = @import("file.zig").File; const Object = @import("Object.zig"); +const Symbol = @import("Symbol.zig"); diff --git a/zig/src/link/Elf/relocation.zig b/zig/src/link/Elf/relocation.zig index 5f6810d6f9..d6f8dc5d10 100644 --- a/zig/src/link/Elf/relocation.zig +++ b/zig/src/link/Elf/relocation.zig @@ -91,6 +91,44 @@ pub fn encode(comptime kind: Kind, cpu_arch: std.Target.Cpu.Arch) u32 { }; } +pub const dwarf = struct { + pub fn crossSectionRelocType(format: DW.Format, cpu_arch: std.Target.Cpu.Arch) u32 { + return switch (cpu_arch) { + .x86_64 => @intFromEnum(switch (format) { + .@"32" => elf.R_X86_64.@"32", + .@"64" => .@"64", + }), + .riscv64 => @intFromEnum(switch (format) { + .@"32" => elf.R_RISCV.@"32", + .@"64" => .@"64", + }), + else => @panic("TODO unhandled cpu arch"), + }; + } + + pub fn externalRelocType( + target: Symbol, + address_size: Dwarf.AddressSize, + cpu_arch: std.Target.Cpu.Arch, + ) u32 { + return switch (cpu_arch) { + .x86_64 => @intFromEnum(switch (address_size) { + .@"32" => if (target.flags.is_tls) elf.R_X86_64.DTPOFF32 else .@"32", + .@"64" => if (target.flags.is_tls) elf.R_X86_64.DTPOFF64 else .@"64", + else => unreachable, + }), + .riscv64 => @intFromEnum(switch (address_size) { + .@"32" => elf.R_RISCV.@"32", + .@"64" => elf.R_RISCV.@"64", + else => unreachable, + }), + else => @panic("TODO unhandled cpu arch"), + }; + } + + const DW = std.dwarf; +}; + const FormatRelocTypeCtx = struct { r_type: u32, cpu_arch: std.Target.Cpu.Arch, @@ -112,19 +150,11 @@ fn formatRelocType( _ = unused_fmt_string; _ = options; const r_type = ctx.r_type; - switch (r_type) { - Elf.R_ZIG_GOT32 => try writer.writeAll("R_ZIG_GOT32"), - Elf.R_ZIG_GOTPCREL => try writer.writeAll("R_ZIG_GOTPCREL"), - Elf.R_ZIG_GOT_HI20 => try writer.writeAll("R_ZIG_GOT_HI20"), - Elf.R_ZIG_GOT_LO12 => try writer.writeAll("R_ZIG_GOT_LO12"), - Elf.R_GOT_HI20_STATIC => try writer.writeAll("R_GOT_HI20_STATIC"), - Elf.R_GOT_LO12_I_STATIC => try writer.writeAll("R_GOT_LO12_I_STATIC"), - else => switch (ctx.cpu_arch) { - .x86_64 => try writer.print("R_X86_64_{s}", .{@tagName(@as(elf.R_X86_64, @enumFromInt(r_type)))}), - .aarch64 => try writer.print("R_AARCH64_{s}", .{@tagName(@as(elf.R_AARCH64, @enumFromInt(r_type)))}), - .riscv64 => try writer.print("R_RISCV_{s}", .{@tagName(@as(elf.R_RISCV, @enumFromInt(r_type)))}), - else => unreachable, - }, + switch (ctx.cpu_arch) { + .x86_64 => try writer.print("R_X86_64_{s}", .{@tagName(@as(elf.R_X86_64, @enumFromInt(r_type)))}), + .aarch64 => try writer.print("R_AARCH64_{s}", .{@tagName(@as(elf.R_AARCH64, @enumFromInt(r_type)))}), + .riscv64 => try writer.print("R_RISCV_{s}", .{@tagName(@as(elf.R_RISCV, @enumFromInt(r_type)))}), + else => unreachable, } } @@ -132,4 +162,6 @@ const assert = std.debug.assert; const elf = std.elf; const std = @import("std"); +const Dwarf = @import("../Dwarf.zig"); const Elf = @import("../Elf.zig"); +const Symbol = @import("Symbol.zig"); diff --git a/zig/src/link/Elf/synthetic_sections.zig b/zig/src/link/Elf/synthetic_sections.zig index e1ec90139e..40cb37b967 100644 --- a/zig/src/link/Elf/synthetic_sections.zig +++ b/zig/src/link/Elf/synthetic_sections.zig @@ -223,215 +223,6 @@ pub const DynamicSection = struct { } }; -pub const ZigGotSection = struct { - entries: std.ArrayListUnmanaged(Symbol.Index) = .{}, - output_symtab_ctx: Elf.SymtabCtx = .{}, - flags: Flags = .{}, - - const Flags = packed struct { - needs_rela: bool = false, - dirty: bool = false, - }; - - pub const Index = u32; - - pub fn deinit(zig_got: *ZigGotSection, allocator: Allocator) void { - zig_got.entries.deinit(allocator); - } - - fn allocateEntry(zig_got: *ZigGotSection, allocator: Allocator) !Index { - try zig_got.entries.ensureUnusedCapacity(allocator, 1); - // TODO add free list - const index = @as(Index, @intCast(zig_got.entries.items.len)); - _ = zig_got.entries.addOneAssumeCapacity(); - zig_got.flags.dirty = true; - return index; - } - - pub fn addSymbol(zig_got: *ZigGotSection, sym_index: Symbol.Index, elf_file: *Elf) !Index { - const comp = elf_file.base.comp; - const gpa = comp.gpa; - const zo = elf_file.zigObjectPtr().?; - const index = try zig_got.allocateEntry(gpa); - const entry = &zig_got.entries.items[index]; - entry.* = sym_index; - const symbol = zo.symbol(sym_index); - symbol.flags.has_zig_got = true; - if (elf_file.isEffectivelyDynLib() or (elf_file.base.isExe() and comp.config.pie)) { - zig_got.flags.needs_rela = true; - } - symbol.addExtra(.{ .zig_got = index }, elf_file); - return index; - } - - pub fn entryOffset(zig_got: ZigGotSection, index: Index, elf_file: *Elf) u64 { - _ = zig_got; - const entry_size = elf_file.archPtrWidthBytes(); - const shdr = elf_file.shdrs.items[elf_file.zig_got_section_index.?]; - return shdr.sh_offset + @as(u64, entry_size) * index; - } - - pub fn entryAddress(zig_got: ZigGotSection, index: Index, elf_file: *Elf) i64 { - _ = zig_got; - const entry_size = elf_file.archPtrWidthBytes(); - const shdr = elf_file.shdrs.items[elf_file.zig_got_section_index.?]; - return @as(i64, @intCast(shdr.sh_addr)) + entry_size * index; - } - - pub fn size(zig_got: ZigGotSection, elf_file: *Elf) usize { - return elf_file.archPtrWidthBytes() * zig_got.entries.items.len; - } - - pub fn writeOne(zig_got: *ZigGotSection, elf_file: *Elf, index: Index) !void { - const zo = elf_file.zigObjectPtr().?; - if (zig_got.flags.dirty) { - const needed_size = zig_got.size(elf_file); - try elf_file.growAllocSection(elf_file.zig_got_section_index.?, needed_size); - zig_got.flags.dirty = false; - } - const entry_size: u16 = elf_file.archPtrWidthBytes(); - const target = elf_file.getTarget(); - const endian = target.cpu.arch.endian(); - const off = zig_got.entryOffset(index, elf_file); - const vaddr: u64 = @intCast(zig_got.entryAddress(index, elf_file)); - const entry = zig_got.entries.items[index]; - const value = zo.symbol(entry).address(.{}, elf_file); - switch (entry_size) { - 2 => { - var buf: [2]u8 = undefined; - std.mem.writeInt(u16, &buf, @intCast(value), endian); - try elf_file.base.file.?.pwriteAll(&buf, off); - }, - 4 => { - var buf: [4]u8 = undefined; - std.mem.writeInt(u32, &buf, @intCast(value), endian); - try elf_file.base.file.?.pwriteAll(&buf, off); - }, - 8 => { - var buf: [8]u8 = undefined; - std.mem.writeInt(u64, &buf, @intCast(value), endian); - try elf_file.base.file.?.pwriteAll(&buf, off); - - if (elf_file.base.child_pid) |pid| { - switch (builtin.os.tag) { - .linux => { - var local_vec: [1]std.posix.iovec_const = .{.{ - .base = &buf, - .len = buf.len, - }}; - var remote_vec: [1]std.posix.iovec_const = .{.{ - .base = @as([*]u8, @ptrFromInt(@as(usize, @intCast(vaddr)))), - .len = buf.len, - }}; - const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0); - switch (std.os.linux.E.init(rc)) { - .SUCCESS => assert(rc == buf.len), - else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}), - } - }, - else => return error.HotSwapUnavailableOnHostOperatingSystem, - } - } - }, - else => unreachable, - } - } - - pub fn writeAll(zig_got: ZigGotSection, elf_file: *Elf, writer: anytype) !void { - const zo = elf_file.zigObjectPtr().?; - for (zig_got.entries.items) |entry| { - const symbol = zo.symbol(entry); - const value = symbol.address(.{ .plt = false }, elf_file); - try writeInt(value, elf_file, writer); - } - } - - pub fn numRela(zig_got: ZigGotSection) usize { - return zig_got.entries.items.len; - } - - pub fn addRela(zig_got: ZigGotSection, elf_file: *Elf) !void { - const comp = elf_file.base.comp; - const gpa = comp.gpa; - const cpu_arch = elf_file.getTarget().cpu.arch; - const zo = elf_file.zigObjectPtr().?; - try elf_file.rela_dyn.ensureUnusedCapacity(gpa, zig_got.numRela()); - for (zig_got.entries.items) |entry| { - const symbol = zo.symbol(entry); - const offset = symbol.zigGotAddress(elf_file); - elf_file.addRelaDynAssumeCapacity(.{ - .offset = @intCast(offset), - .type = relocation.encode(.rel, cpu_arch), - .addend = symbol.address(.{ .plt = false }, elf_file), - }); - } - } - - pub fn updateSymtabSize(zig_got: *ZigGotSection, elf_file: *Elf) void { - const zo = elf_file.zigObjectPtr().?; - zig_got.output_symtab_ctx.nlocals = @as(u32, @intCast(zig_got.entries.items.len)); - for (zig_got.entries.items) |entry| { - const name = zo.symbol(entry).name(elf_file); - zig_got.output_symtab_ctx.strsize += @as(u32, @intCast(name.len + "$ziggot".len)) + 1; - } - } - - pub fn writeSymtab(zig_got: ZigGotSection, elf_file: *Elf) void { - const zo = elf_file.zigObjectPtr().?; - for (zig_got.entries.items, zig_got.output_symtab_ctx.ilocal.., 0..) |entry, ilocal, index| { - const symbol = zo.symbol(entry); - const symbol_name = symbol.name(elf_file); - const st_name = @as(u32, @intCast(elf_file.strtab.items.len)); - elf_file.strtab.appendSliceAssumeCapacity(symbol_name); - elf_file.strtab.appendSliceAssumeCapacity("$ziggot"); - elf_file.strtab.appendAssumeCapacity(0); - const st_value = zig_got.entryAddress(@intCast(index), elf_file); - const st_size = elf_file.archPtrWidthBytes(); - elf_file.symtab.items[ilocal] = .{ - .st_name = st_name, - .st_info = elf.STT_OBJECT, - .st_other = 0, - .st_shndx = @intCast(elf_file.zig_got_section_index.?), - .st_value = @intCast(st_value), - .st_size = st_size, - }; - } - } - - const FormatCtx = struct { - zig_got: ZigGotSection, - elf_file: *Elf, - }; - - pub fn fmt(zig_got: ZigGotSection, elf_file: *Elf) std.fmt.Formatter(format2) { - return .{ .data = .{ .zig_got = zig_got, .elf_file = elf_file } }; - } - - pub fn format2( - ctx: FormatCtx, - comptime unused_fmt_string: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = options; - _ = unused_fmt_string; - const zig_got = ctx.zig_got; - const elf_file = ctx.elf_file; - try writer.writeAll(".zig.got\n"); - for (zig_got.entries.items, 0..) |entry, index| { - const zo = elf_file.zigObjectPtr().?; - const symbol = zo.symbol(entry); - try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ - index, - zig_got.entryAddress(@intCast(index), elf_file), - entry, - symbol.address(.{}, elf_file), - symbol.name(elf_file), - }); - } - } -}; - pub const GotSection = struct { entries: std.ArrayListUnmanaged(Entry) = .{}, output_symtab_ctx: Elf.SymtabCtx = .{}, diff --git a/zig/src/link/MachO.zig b/zig/src/link/MachO.zig index 7c0b79a0f1..d99fc2185e 100644 --- a/zig/src/link/MachO.zig +++ b/zig/src/link/MachO.zig @@ -59,7 +59,6 @@ symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, indsymtab: Indsymtab = .{}, got: GotSection = .{}, -zig_got: ZigGotSection = .{}, stubs: StubsSection = .{}, stubs_helper: StubsHelperSection = .{}, objc_stubs: ObjcStubsSection = .{}, @@ -75,14 +74,12 @@ data_in_code: DataInCode = .{}, /// Tracked loadable segments during incremental linking. zig_text_seg_index: ?u8 = null, -zig_got_seg_index: ?u8 = null, zig_const_seg_index: ?u8 = null, zig_data_seg_index: ?u8 = null, zig_bss_seg_index: ?u8 = null, /// Tracked section headers with incremental updates to Zig object. zig_text_sect_index: ?u8 = null, -zig_got_sect_index: ?u8 = null, zig_const_sect_index: ?u8 = null, zig_data_sect_index: ?u8 = null, zig_bss_sect_index: ?u8 = null, @@ -94,6 +91,9 @@ debug_abbrev_sect_index: ?u8 = null, debug_str_sect_index: ?u8 = null, debug_aranges_sect_index: ?u8 = null, debug_line_sect_index: ?u8 = null, +debug_line_str_sect_index: ?u8 = null, +debug_loclists_sect_index: ?u8 = null, +debug_rnglists_sect_index: ?u8 = null, has_tlv: AtomicBool = AtomicBool.init(false), binds_to_weak: AtomicBool = AtomicBool.init(false), @@ -156,7 +156,7 @@ pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void { pub fn createEmpty( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*MachO { const target = comp.root_mod.resolved_target.result; @@ -221,7 +221,7 @@ pub fn createEmpty( } errdefer self.base.destroy(); - self.base.file = try emit.directory.handle.createFile(emit.sub_path, .{ + self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = true, .read = true, .mode = link.File.determineMode(false, output_mode, link_mode), @@ -260,7 +260,7 @@ pub fn createEmpty( pub fn open( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*MachO { // TODO: restore saved linker state, don't truncate the file, and @@ -321,7 +321,6 @@ pub fn deinit(self: *MachO) void { self.symtab.deinit(gpa); self.strtab.deinit(gpa); self.got.deinit(gpa); - self.zig_got.deinit(gpa); self.stubs.deinit(gpa); self.objc_stubs.deinit(gpa); self.tlv_ptr.deinit(gpa); @@ -354,7 +353,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n const sub_prog_node = prog_node.start("MachO Flush", 0); defer sub_prog_node.end(); - const directory = self.base.emit.directory; + const directory = self.base.emit.root_dir; const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: { if (fs.path.dirname(full_out_path)) |dirname| { @@ -587,7 +586,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n if (codesig) |*csig| { try self.writeCodeSignature(csig); // code signing always comes last const emit = self.base.emit; - try invalidateKernelCache(emit.directory.handle, emit.sub_path); + try invalidateKernelCache(emit.root_dir.handle, emit.sub_path); } } @@ -598,7 +597,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const directory = self.base.emit.directory; + const directory = self.base.emit.root_dir; const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: { if (fs.path.dirname(full_out_path)) |dirname| { @@ -1789,12 +1788,41 @@ pub fn sortSections(self: *MachO) !void { self.sections.appendAssumeCapacity(slice.get(sorted.index)); } + for (&[_]*?u8{ + &self.data_sect_index, + &self.got_sect_index, + &self.zig_text_sect_index, + &self.zig_const_sect_index, + &self.zig_data_sect_index, + &self.zig_bss_sect_index, + &self.stubs_sect_index, + &self.stubs_helper_sect_index, + &self.la_symbol_ptr_sect_index, + &self.tlv_ptr_sect_index, + &self.eh_frame_sect_index, + &self.unwind_info_sect_index, + &self.objc_stubs_sect_index, + &self.debug_str_sect_index, + &self.debug_info_sect_index, + &self.debug_abbrev_sect_index, + &self.debug_aranges_sect_index, + &self.debug_line_sect_index, + &self.debug_line_str_sect_index, + &self.debug_loclists_sect_index, + &self.debug_rnglists_sect_index, + }) |maybe_index| { + if (maybe_index.*) |*index| { + index.* = backlinks[index.*]; + } + } + if (self.getZigObject()) |zo| { for (zo.getAtoms()) |atom_index| { const atom = zo.getAtom(atom_index) orelse continue; if (!atom.isAlive()) continue; atom.out_n_sect = backlinks[atom.out_n_sect]; } + if (zo.dwarf) |*dwarf| dwarf.reloadSectionMetadata(); } for (self.objects.items) |index| { @@ -1813,32 +1841,6 @@ pub fn sortSections(self: *MachO) !void { atom.out_n_sect = backlinks[atom.out_n_sect]; } } - - for (&[_]*?u8{ - &self.data_sect_index, - &self.got_sect_index, - &self.zig_text_sect_index, - &self.zig_got_sect_index, - &self.zig_const_sect_index, - &self.zig_data_sect_index, - &self.zig_bss_sect_index, - &self.stubs_sect_index, - &self.stubs_helper_sect_index, - &self.la_symbol_ptr_sect_index, - &self.tlv_ptr_sect_index, - &self.eh_frame_sect_index, - &self.unwind_info_sect_index, - &self.objc_stubs_sect_index, - &self.debug_info_sect_index, - &self.debug_str_sect_index, - &self.debug_line_sect_index, - &self.debug_abbrev_sect_index, - &self.debug_info_sect_index, - }) |maybe_index| { - if (maybe_index.*) |*index| { - index.* = backlinks[index.*]; - } - } } pub fn addAtomsToSections(self: *MachO) !void { @@ -1907,7 +1909,7 @@ fn calcSectionSizes(self: *MachO) !void { } } - // At this point, we can also calculate symtab and data-in-code linkedit section sizes + // At this point, we can also calculate most of the symtab and data-in-code linkedit section sizes if (self.getZigObject()) |zo| { tp.spawnWg(&wg, File.calcSymtabSize, .{ zo.asFile(), self }); } @@ -2107,7 +2109,6 @@ fn initSegments(self: *MachO) !void { &self.text_seg_index, &self.linkedit_seg_index, &self.zig_text_seg_index, - &self.zig_got_seg_index, &self.zig_const_seg_index, &self.zig_data_seg_index, &self.zig_bss_seg_index, @@ -2189,7 +2190,7 @@ fn allocateSections(self: *MachO) !void { header.size = 0; // Must move the entire section. - const new_offset = self.findFreeSpace(existing_size, page_size); + const new_offset = try self.findFreeSpace(existing_size, page_size); log.debug("moving '{s},{s}' from 0x{x} to 0x{x}", .{ header.segName(), @@ -2462,6 +2463,9 @@ fn writeSectionsAndUpdateLinkeditSizes(self: *MachO) !void { if (self.getInternalObject()) |obj| { tp.spawnWg(&wg, File.writeSymtab, .{ obj.asFile(), self, self }); } + if (self.requiresThunks()) for (self.thunks.items) |th| { + tp.spawnWg(&wg, Thunk.writeSymtab, .{ th, self, self }); + }; } if (self.has_errors.swap(false, .seq_cst)) return error.FlushFailure; @@ -2724,6 +2728,14 @@ fn calcSymtabSize(self: *MachO) !void { var nimports: u32 = 0; var strsize: u32 = 1; + if (self.requiresThunks()) for (self.thunks.items) |*th| { + th.output_symtab_ctx.ilocal = nlocals; + th.output_symtab_ctx.stroff = strsize; + th.calcSymtabSize(self); + nlocals += th.output_symtab_ctx.nlocals; + strsize += th.output_symtab_ctx.strsize; + }; + for (files.items) |index| { const file = self.getFile(index).?; const ctx = switch (file) { @@ -3066,32 +3078,36 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { return actual_size +| (actual_size / ideal_factor); } -fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { +fn detectAllocCollision(self: *MachO, start: u64, size: u64) !?u64 { // Conservatively commit one page size as reserved space for the headers as we // expect it to grow and everything else be moved in flush anyhow. const header_size = self.getPageSize(); if (start < header_size) return header_size; + var at_end = true; const end = start + padToIdeal(size); for (self.sections.items(.header)) |header| { if (header.isZerofill()) continue; const increased_size = padToIdeal(header.size); const test_end = header.offset +| increased_size; - if (end > header.offset and start < test_end) { - return test_end; + if (start < test_end) { + if (end > header.offset) return test_end; + if (test_end < std.math.maxInt(u64)) at_end = false; } } for (self.segments.items) |seg| { const increased_size = padToIdeal(seg.filesize); const test_end = seg.fileoff +| increased_size; - if (end > seg.fileoff and start < test_end) { - return test_end; + if (start < test_end) { + if (end > seg.fileoff) return test_end; + if (test_end < std.math.maxInt(u64)) at_end = false; } } + if (at_end) try self.base.file.?.setEndPos(end); return null; } @@ -3159,9 +3175,9 @@ pub fn allocatedSizeVirtual(self: *MachO, start: u64) u64 { return min_pos - start; } -pub fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) u64 { +pub fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) !u64 { var start: u64 = 0; - while (self.detectAllocCollision(start, object_size)) |item_end| { + while (try self.detectAllocCollision(start, object_size)) |item_end| { start = mem.alignForward(u64, item_end, min_alignment); } return start; @@ -3194,7 +3210,7 @@ fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64 } const InitMetadataOptions = struct { - emit: Compilation.Emit, + emit: Path, zo: *ZigObject, symbol_count_hint: u64, program_code_size_hint: u64, @@ -3210,7 +3226,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { { const filesize = options.program_code_size_hint; - const off = self.findFreeSpace(filesize, self.getPageSize()); + const off = try self.findFreeSpace(filesize, self.getPageSize()); self.zig_text_seg_index = try self.addSegment("__TEXT_ZIG", .{ .fileoff = off, .filesize = filesize, @@ -3220,21 +3236,9 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { }); } - { - const filesize = options.symbol_count_hint * @sizeOf(u64); - const off = self.findFreeSpace(filesize, self.getPageSize()); - self.zig_got_seg_index = try self.addSegment("__GOT_ZIG", .{ - .fileoff = off, - .filesize = filesize, - .vmaddr = base_vmaddr + 0x4000000, - .vmsize = filesize, - .prot = macho.PROT.READ | macho.PROT.WRITE, - }); - } - { const filesize: u64 = 1024; - const off = self.findFreeSpace(filesize, self.getPageSize()); + const off = try self.findFreeSpace(filesize, self.getPageSize()); self.zig_const_seg_index = try self.addSegment("__CONST_ZIG", .{ .fileoff = off, .filesize = filesize, @@ -3246,7 +3250,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { { const filesize: u64 = 1024; - const off = self.findFreeSpace(filesize, self.getPageSize()); + const off = try self.findFreeSpace(filesize, self.getPageSize()); self.zig_data_seg_index = try self.addSegment("__DATA_ZIG", .{ .fileoff = off, .filesize = filesize, @@ -3265,7 +3269,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { }); } - if (options.zo.dwarf) |_| { + if (options.zo.dwarf) |*dwarf| { // Create dSYM bundle. log.debug("creating {s}.dSYM bundle", .{options.emit.sub_path}); @@ -3278,7 +3282,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { ); defer gpa.free(d_sym_path); - var d_sym_bundle = try options.emit.directory.handle.makeOpenPath(d_sym_path, .{}); + var d_sym_bundle = try options.emit.root_dir.handle.makeOpenPath(d_sym_path, .{}); defer d_sym_bundle.close(); const d_sym_file = try d_sym_bundle.createFile(options.emit.sub_path, .{ @@ -3288,6 +3292,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { self.d_sym = .{ .allocator = gpa, .file = d_sym_file }; try self.d_sym.?.initMetadata(self); + try dwarf.initMetadata(); } } @@ -3307,7 +3312,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { const sect = &macho_file.sections.items(.header)[sect_id]; const alignment = try math.powi(u32, 2, sect.@"align"); if (!sect.isZerofill()) { - sect.offset = math.cast(u32, macho_file.findFreeSpace(size, alignment)) orelse + sect.offset = math.cast(u32, try macho_file.findFreeSpace(size, alignment)) orelse return error.Overflow; } sect.addr = macho_file.findFreeSpaceVirtual(size, alignment); @@ -3331,13 +3336,6 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { } } - if (!self.base.isRelocatable()) { - self.zig_got_sect_index = try self.addSection("__GOT_ZIG", "__got_zig", .{ - .alignment = 3, - }); - appendSect(self, self.zig_got_sect_index.?, self.zig_got_seg_index.?); - } - { self.zig_const_sect_index = try self.addSection("__CONST_ZIG", "__const_zig", .{}); if (self.base.isRelocatable()) { @@ -3367,43 +3365,34 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { } } - if (self.base.isRelocatable() and options.zo.dwarf != null) { - { - self.debug_str_sect_index = try self.addSection("__DWARF", "__debug_str", .{ - .flags = macho.S_ATTR_DEBUG, - }); - try allocSect(self, self.debug_str_sect_index.?, 200); - } - - { - self.debug_info_sect_index = try self.addSection("__DWARF", "__debug_info", .{ - .flags = macho.S_ATTR_DEBUG, - }); - try allocSect(self, self.debug_info_sect_index.?, 200); - } - - { - self.debug_abbrev_sect_index = try self.addSection("__DWARF", "__debug_abbrev", .{ - .flags = macho.S_ATTR_DEBUG, - }); - try allocSect(self, self.debug_abbrev_sect_index.?, 128); - } - - { - self.debug_aranges_sect_index = try self.addSection("__DWARF", "__debug_aranges", .{ - .alignment = 4, - .flags = macho.S_ATTR_DEBUG, - }); - try allocSect(self, self.debug_aranges_sect_index.?, 160); - } - - { - self.debug_line_sect_index = try self.addSection("__DWARF", "__debug_line", .{ - .flags = macho.S_ATTR_DEBUG, - }); - try allocSect(self, self.debug_line_sect_index.?, 250); - } - } + if (self.base.isRelocatable()) if (options.zo.dwarf) |*dwarf| { + self.debug_str_sect_index = try self.addSection("__DWARF", "__debug_str", .{ + .flags = macho.S_ATTR_DEBUG, + }); + self.debug_info_sect_index = try self.addSection("__DWARF", "__debug_info", .{ + .flags = macho.S_ATTR_DEBUG, + }); + self.debug_abbrev_sect_index = try self.addSection("__DWARF", "__debug_abbrev", .{ + .flags = macho.S_ATTR_DEBUG, + }); + self.debug_aranges_sect_index = try self.addSection("__DWARF", "__debug_aranges", .{ + .alignment = 4, + .flags = macho.S_ATTR_DEBUG, + }); + self.debug_line_sect_index = try self.addSection("__DWARF", "__debug_line", .{ + .flags = macho.S_ATTR_DEBUG, + }); + self.debug_line_str_sect_index = try self.addSection("__DWARF", "__debug_line_str", .{ + .flags = macho.S_ATTR_DEBUG, + }); + self.debug_loclists_sect_index = try self.addSection("__DWARF", "__debug_loclists", .{ + .flags = macho.S_ATTR_DEBUG, + }); + self.debug_rnglists_sect_index = try self.addSection("__DWARF", "__debug_rnglists", .{ + .flags = macho.S_ATTR_DEBUG, + }); + try dwarf.initMetadata(); + }; } pub fn growSection(self: *MachO, sect_index: u8, needed_size: u64) !void { @@ -3417,35 +3406,36 @@ pub fn growSection(self: *MachO, sect_index: u8, needed_size: u64) !void { fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void { const sect = &self.sections.items(.header)[sect_index]; - if (needed_size > self.allocatedSize(sect.offset) and !sect.isZerofill()) { - const existing_size = sect.size; - sect.size = 0; - - // Must move the entire section. - const alignment = self.getPageSize(); - const new_offset = self.findFreeSpace(needed_size, alignment); + const seg_id = self.sections.items(.segment_id)[sect_index]; + const seg = &self.segments.items[seg_id]; - log.debug("moving '{s},{s}' from 0x{x} to 0x{x}", .{ - sect.segName(), - sect.sectName(), - sect.offset, - new_offset, - }); + if (!sect.isZerofill()) { + const allocated_size = self.allocatedSize(sect.offset); + if (sect.offset + allocated_size == std.math.maxInt(u64)) { + try self.base.file.?.setEndPos(sect.offset + needed_size); + } else if (needed_size > allocated_size) { + const existing_size = sect.size; + sect.size = 0; - try self.copyRangeAllZeroOut(sect.offset, new_offset, existing_size); + // Must move the entire section. + const alignment = self.getPageSize(); + const new_offset = try self.findFreeSpace(needed_size, alignment); - sect.offset = @intCast(new_offset); - } + log.debug("moving '{s},{s}' from 0x{x} to 0x{x}", .{ + sect.segName(), + sect.sectName(), + sect.offset, + new_offset, + }); - sect.size = needed_size; + try self.copyRangeAllZeroOut(sect.offset, new_offset, existing_size); - const seg_id = self.sections.items(.segment_id)[sect_index]; - const seg = &self.segments.items[seg_id]; - seg.fileoff = sect.offset; - - if (!sect.isZerofill()) { + sect.offset = @intCast(new_offset); + } seg.filesize = needed_size; } + sect.size = needed_size; + seg.fileoff = sect.offset; const mem_capacity = self.allocatedSizeVirtual(seg.vmaddr); if (needed_size > mem_capacity) { @@ -3464,30 +3454,34 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void { const sect = &self.sections.items(.header)[sect_index]; - if (needed_size > self.allocatedSize(sect.offset) and !sect.isZerofill()) { - const existing_size = sect.size; - sect.size = 0; - - // Must move the entire section. - const alignment = try math.powi(u32, 2, sect.@"align"); - const new_offset = self.findFreeSpace(needed_size, alignment); - const new_addr = self.findFreeSpaceVirtual(needed_size, alignment); - - log.debug("new '{s},{s}' file offset 0x{x} to 0x{x} (0x{x} - 0x{x})", .{ - sect.segName(), - sect.sectName(), - new_offset, - new_offset + existing_size, - new_addr, - new_addr + existing_size, - }); + if (!sect.isZerofill()) { + const allocated_size = self.allocatedSize(sect.offset); + if (sect.offset + allocated_size == std.math.maxInt(u64)) { + try self.base.file.?.setEndPos(sect.offset + needed_size); + } else if (needed_size > allocated_size) { + const existing_size = sect.size; + sect.size = 0; - try self.copyRangeAll(sect.offset, new_offset, existing_size); + // Must move the entire section. + const alignment = try math.powi(u32, 2, sect.@"align"); + const new_offset = try self.findFreeSpace(needed_size, alignment); + const new_addr = self.findFreeSpaceVirtual(needed_size, alignment); - sect.offset = @intCast(new_offset); - sect.addr = new_addr; - } + log.debug("new '{s},{s}' file offset 0x{x} to 0x{x} (0x{x} - 0x{x})", .{ + sect.segName(), + sect.sectName(), + new_offset, + new_offset + existing_size, + new_addr, + new_addr + existing_size, + }); + try self.copyRangeAll(sect.offset, new_offset, existing_size); + + sect.offset = @intCast(new_offset); + sect.addr = new_addr; + } + } sect.size = needed_size; } @@ -3564,7 +3558,6 @@ inline fn requiresThunks(self: MachO) bool { pub fn isZigSegment(self: MachO, seg_id: u8) bool { inline for (&[_]?u8{ self.zig_text_seg_index, - self.zig_got_seg_index, self.zig_const_seg_index, self.zig_data_seg_index, self.zig_bss_seg_index, @@ -3579,7 +3572,6 @@ pub fn isZigSegment(self: MachO, seg_id: u8) bool { pub fn isZigSection(self: MachO, sect_id: u8) bool { inline for (&[_]?u8{ self.zig_text_sect_index, - self.zig_got_sect_index, self.zig_const_sect_index, self.zig_data_sect_index, self.zig_bss_sect_index, @@ -3949,7 +3941,6 @@ fn fmtDumpState( try writer.print("stubs\n{}\n", .{self.stubs.fmt(self)}); try writer.print("objc_stubs\n{}\n", .{self.objc_stubs.fmt(self)}); try writer.print("got\n{}\n", .{self.got.fmt(self)}); - try writer.print("zig_got\n{}\n", .{self.zig_got.fmt(self)}); try writer.print("tlv_ptr\n{}\n", .{self.tlv_ptr.fmt(self)}); try writer.writeByte('\n'); try writer.print("sections\n{}\n", .{self.fmtSections()}); @@ -4591,7 +4582,6 @@ const std = @import("std"); const build_options = @import("build_options"); const builtin = @import("builtin"); const assert = std.debug.assert; -const dwarf = std.dwarf; const fs = std.fs; const log = std.log.scoped(.link); const state_log = std.log.scoped(.link_state); @@ -4624,6 +4614,7 @@ pub const Atom = @import("MachO/Atom.zig"); const AtomicBool = std.atomic.Value(bool); const Bind = bind.Bind; const Cache = std.Build.Cache; +const Path = Cache.Path; const CodeSignature = @import("MachO/CodeSignature.zig"); const Compilation = @import("../Compilation.zig"); const DataInCode = synthetic.DataInCode; @@ -4658,7 +4649,6 @@ const Value = @import("../Value.zig"); const UnwindInfo = @import("MachO/UnwindInfo.zig"); const WaitGroup = std.Thread.WaitGroup; const WeakBind = bind.WeakBind; -const ZigGotSection = synthetic.ZigGotSection; const ZigObject = @import("MachO/ZigObject.zig"); const dev = @import("../dev.zig"); diff --git a/zig/src/link/MachO/Atom.zig b/zig/src/link/MachO/Atom.zig index d0e0fe377d..d2072278a9 100644 --- a/zig/src/link/MachO/Atom.zig +++ b/zig/src/link/MachO/Atom.zig @@ -492,10 +492,6 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { } }, - .zig_got_load => { - assert(rel.getTargetSymbol(self, macho_file).getSectionFlags().has_zig_got); - }, - .got => { rel.getTargetSymbol(self, macho_file).setSectionFlags(.{ .needs_got = true }); }, @@ -652,8 +648,6 @@ fn resolveRelocInner( const G: i64 = @intCast(rel.getGotTargetAddress(self, macho_file)); const TLS = @as(i64, @intCast(macho_file.getTlsAddress())); const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(self, macho_file))) else 0; - // Address of the __got_zig table entry if any. - const ZIG_GOT = @as(i64, @intCast(rel.getZigGotTargetAddress(macho_file))); const divExact = struct { fn divExact(atom: Atom, r: Relocation, num: u12, den: u12, ctx: *MachO) !u12 { @@ -676,13 +670,12 @@ fn resolveRelocInner( S + A - SUB, rel.getTargetAtom(self, macho_file).atom_index, }), - .@"extern" => relocs_log.debug(" {x}<+{d}>: {}: [=> {x}] G({x}) ZG({x}) ({s})", .{ + .@"extern" => relocs_log.debug(" {x}<+{d}>: {}: [=> {x}] G({x}) ({s})", .{ P, rel_offset, rel.fmtPretty(cpu_arch), S + A - SUB, G + A, - ZIG_GOT + A, rel.getTargetSymbol(self, macho_file).getName(macho_file), }), } @@ -745,17 +738,6 @@ fn resolveRelocInner( } }, - .zig_got_load => { - assert(rel.tag == .@"extern"); - assert(rel.meta.length == 2); - assert(rel.meta.pcrel); - switch (cpu_arch) { - .x86_64 => try writer.writeInt(i32, @intCast(ZIG_GOT + A - P), .little), - .aarch64 => @panic("TODO resolve __got_zig indirection reloc"), - else => unreachable, - } - }, - .tlv => { assert(rel.tag == .@"extern"); assert(rel.meta.length == 2); @@ -1065,7 +1047,6 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: []macho.r .subtractor => .ARM64_RELOC_SUBTRACTOR, .unsigned => .ARM64_RELOC_UNSIGNED, - .zig_got_load, .signed, .signed1, .signed2, @@ -1109,7 +1090,6 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: []macho.r .subtractor => .X86_64_RELOC_SUBTRACTOR, .unsigned => .X86_64_RELOC_UNSIGNED, - .zig_got_load, .page, .pageoff, .got_load_page, diff --git a/zig/src/link/MachO/DebugSymbols.zig b/zig/src/link/MachO/DebugSymbols.zig index 52061f325b..15fb9467ad 100644 --- a/zig/src/link/MachO/DebugSymbols.zig +++ b/zig/src/link/MachO/DebugSymbols.zig @@ -15,6 +15,9 @@ debug_abbrev_section_index: ?u8 = null, debug_str_section_index: ?u8 = null, debug_aranges_section_index: ?u8 = null, debug_line_section_index: ?u8 = null, +debug_line_str_section_index: ?u8 = null, +debug_loclists_section_index: ?u8 = null, +debug_rnglists_section_index: ?u8 = null, relocs: std.ArrayListUnmanaged(Reloc) = .{}, @@ -56,13 +59,16 @@ pub fn initMetadata(self: *DebugSymbols, macho_file: *MachO) !void { }); } - self.debug_str_section_index = try self.allocateSection("__debug_str", 200, 0); - self.debug_info_section_index = try self.allocateSection("__debug_info", 200, 0); - self.debug_abbrev_section_index = try self.allocateSection("__debug_abbrev", 128, 0); - self.debug_aranges_section_index = try self.allocateSection("__debug_aranges", 160, 4); - self.debug_line_section_index = try self.allocateSection("__debug_line", 250, 0); + self.debug_str_section_index = try self.createSection("__debug_str", 0); + self.debug_info_section_index = try self.createSection("__debug_info", 0); + self.debug_abbrev_section_index = try self.createSection("__debug_abbrev", 0); + self.debug_aranges_section_index = try self.createSection("__debug_aranges", 4); + self.debug_line_section_index = try self.createSection("__debug_line", 0); + self.debug_line_str_section_index = try self.createSection("__debug_line_str", 0); + self.debug_loclists_section_index = try self.createSection("__debug_loclists", 0); + self.debug_rnglists_section_index = try self.createSection("__debug_rnglists", 0); - self.linkedit_segment_cmd_index = @as(u8, @intCast(self.segments.items.len)); + self.linkedit_segment_cmd_index = @intCast(self.segments.items.len); try self.segments.append(self.allocator, .{ .segname = makeStaticString("__LINKEDIT"), .maxprot = macho.PROT.READ, @@ -71,27 +77,17 @@ pub fn initMetadata(self: *DebugSymbols, macho_file: *MachO) !void { }); } -fn allocateSection(self: *DebugSymbols, sectname: []const u8, size: u64, alignment: u16) !u8 { +fn createSection(self: *DebugSymbols, sectname: []const u8, alignment: u16) !u8 { const segment = self.getDwarfSegmentPtr(); var sect = macho.section_64{ .sectname = makeStaticString(sectname), .segname = segment.segname, - .size = @as(u32, @intCast(size)), .@"align" = alignment, }; - const alignment_pow_2 = try math.powi(u32, 2, alignment); - const off = self.findFreeSpace(size, alignment_pow_2); - - log.debug("found {s},{s} section free space 0x{x} to 0x{x}", .{ - sect.segName(), - sect.sectName(), - off, - off + size, - }); - sect.offset = @as(u32, @intCast(off)); + log.debug("create {s},{s} section", .{ sect.segName(), sect.sectName() }); - const index = @as(u8, @intCast(self.sections.items.len)); + const index: u8 = @intCast(self.sections.items.len); try self.sections.append(self.allocator, sect); segment.cmdsize += @sizeOf(macho.section_64); segment.nsects += 1; @@ -102,16 +98,19 @@ fn allocateSection(self: *DebugSymbols, sectname: []const u8, size: u64, alignme pub fn growSection( self: *DebugSymbols, sect_index: u8, - needed_size: u32, + needed_size: u64, requires_file_copy: bool, macho_file: *MachO, ) !void { const sect = self.getSectionPtr(sect_index); - if (needed_size > self.allocatedSize(sect.offset)) { + const allocated_size = self.allocatedSize(sect.offset); + if (sect.offset + allocated_size == std.math.maxInt(u64)) { + try self.file.setEndPos(sect.offset + needed_size); + } else if (needed_size > allocated_size) { const existing_size = sect.size; sect.size = 0; // free the space - const new_offset = self.findFreeSpace(needed_size, 1); + const new_offset = try self.findFreeSpace(needed_size, 1); log.debug("moving {s} section: {} bytes from 0x{x} to 0x{x}", .{ sect.sectName(), @@ -130,7 +129,7 @@ pub fn growSection( if (amt != existing_size) return error.InputOutput; } - sect.offset = @as(u32, @intCast(new_offset)); + sect.offset = @intCast(new_offset); } sect.size = needed_size; @@ -153,22 +152,27 @@ pub fn markDirty(self: *DebugSymbols, sect_index: u8, macho_file: *MachO) void { } } -fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) ?u64 { +fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 { + var at_end = true; const end = start + padToIdeal(size); + for (self.sections.items) |section| { const increased_size = padToIdeal(section.size); const test_end = section.offset + increased_size; - if (end > section.offset and start < test_end) { - return test_end; + if (start < test_end) { + if (end > section.offset) return test_end; + if (test_end < std.math.maxInt(u64)) at_end = false; } } + + if (at_end) try self.file.setEndPos(end); return null; } -fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) u64 { +fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64 { const segment = self.getDwarfSegmentPtr(); var offset: u64 = segment.fileoff; - while (self.detectAllocCollision(offset, object_size)) |item_end| { + while (try self.detectAllocCollision(offset, object_size)) |item_end| { offset = mem.alignForward(u64, item_end, min_alignment); } return offset; @@ -346,6 +350,7 @@ fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds } fn allocatedSize(self: *DebugSymbols, start: u64) u64 { + if (start == 0) return 0; const seg = self.getDwarfSegmentPtr(); assert(start >= seg.fileoff); var min_pos: u64 = std.math.maxInt(u64); @@ -413,9 +418,9 @@ pub fn writeStrtab(self: *DebugSymbols, off: u32) !u32 { pub fn getSectionIndexes(self: *DebugSymbols, segment_index: u8) struct { start: u8, end: u8 } { var start: u8 = 0; - const nsects = for (self.segments.items, 0..) |seg, i| { - if (i == segment_index) break @as(u8, @intCast(seg.nsects)); - start += @as(u8, @intCast(seg.nsects)); + const nsects: u8 = for (self.segments.items, 0..) |seg, i| { + if (i == segment_index) break @intCast(seg.nsects); + start += @intCast(seg.nsects); } else 0; return .{ .start = start, .end = start + nsects }; } diff --git a/zig/src/link/MachO/Relocation.zig b/zig/src/link/MachO/Relocation.zig index bfb551f224..c732dc3a89 100644 --- a/zig/src/link/MachO/Relocation.zig +++ b/zig/src/link/MachO/Relocation.zig @@ -92,7 +92,6 @@ fn formatPretty( .signed4 => "X86_64_RELOC_SIGNED_4", .got_load => "X86_64_RELOC_GOT_LOAD", .tlv => "X86_64_RELOC_TLV", - .zig_got_load => "ZIG_GOT_LOAD", .page => "ARM64_RELOC_PAGE21", .pageoff => "ARM64_RELOC_PAGEOFF12", .got_load_page => "ARM64_RELOC_GOT_LOAD_PAGE21", @@ -137,8 +136,6 @@ pub const Type = enum { got_load, /// RIP-relative TLV load (X86_64_RELOC_TLV) tlv, - /// Zig-specific __got_zig indirection - zig_got_load, // arm64 /// PC-relative load (distance to page, ARM64_RELOC_PAGE21) diff --git a/zig/src/link/MachO/Symbol.zig b/zig/src/link/MachO/Symbol.zig index 6e5af75fc2..7d8dc9c046 100644 --- a/zig/src/link/MachO/Symbol.zig +++ b/zig/src/link/MachO/Symbol.zig @@ -123,6 +123,7 @@ pub fn getSymbolRank(symbol: Symbol, macho_file: *MachO) u32 { pub fn getAddress(symbol: Symbol, opts: struct { stubs: bool = true, + trampoline: bool = true, }, macho_file: *MachO) u64 { if (opts.stubs) { if (symbol.getSectionFlags().stubs) { @@ -131,6 +132,9 @@ pub fn getAddress(symbol: Symbol, opts: struct { return symbol.getObjcStubsAddress(macho_file); } } + if (symbol.flags.trampoline and opts.trampoline) { + return symbol.getTrampolineAddress(macho_file); + } if (symbol.getAtom(macho_file)) |atom| return atom.getAddress(macho_file) + symbol.value; return symbol.value; } @@ -169,23 +173,11 @@ pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 { return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file); } -const GetOrCreateZigGotEntryResult = struct { - found_existing: bool, - index: ZigGotSection.Index, -}; - -pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, macho_file: *MachO) !GetOrCreateZigGotEntryResult { - assert(!macho_file.base.isRelocatable()); - assert(symbol.getSectionFlags().needs_zig_got); - if (symbol.getSectionFlags().has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).zig_got }; - const index = try macho_file.zig_got.addSymbol(symbol_index, macho_file); - return .{ .found_existing = false, .index = index }; -} - -pub fn getZigGotAddress(symbol: Symbol, macho_file: *MachO) u64 { - if (!symbol.getSectionFlags().has_zig_got) return 0; - const extras = symbol.getExtra(macho_file); - return macho_file.zig_got.entryAddress(extras.zig_got, macho_file); +pub fn getTrampolineAddress(symbol: Symbol, macho_file: *MachO) u64 { + if (!symbol.flags.trampoline) return 0; + const zo = macho_file.getZigObject().?; + const index = symbol.getExtra(macho_file).trampoline; + return zo.symbols.items[index].getAddress(.{}, macho_file); } pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 { @@ -209,12 +201,12 @@ pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 { const AddExtraOpts = struct { got: ?u32 = null, - zig_got: ?u32 = null, stubs: ?u32 = null, objc_stubs: ?u32 = null, objc_selrefs: ?u32 = null, tlv_ptr: ?u32 = null, symtab: ?u32 = null, + trampoline: ?u32 = null, }; pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) void { @@ -393,6 +385,13 @@ pub const Flags = packed struct { /// Whether the symbol makes into the output symtab or not. output_symtab: bool = false, + + /// ZigObject specific flags + /// Whether the symbol has a trampoline + trampoline: bool = false, + + /// Whether the symbol is an extern pointer (as opposed to function). + is_extern_ptr: bool = false, }; pub const SectionFlags = packed struct(u8) { @@ -400,10 +399,6 @@ pub const SectionFlags = packed struct(u8) { needs_got: bool = false, has_got: bool = false, - /// Whether the symbol contains __got_zig indirection. - needs_zig_got: bool = false, - has_zig_got: bool = false, - /// Whether the symbols contains __stubs indirection. stubs: bool = false, @@ -413,7 +408,7 @@ pub const SectionFlags = packed struct(u8) { /// Whether the symbol contains __objc_stubs indirection. objc_stubs: bool = false, - _: u1 = 0, + _: u3 = 0, }; pub const Visibility = enum { @@ -432,12 +427,12 @@ pub const Visibility = enum { pub const Extra = struct { got: u32 = 0, - zig_got: u32 = 0, stubs: u32 = 0, objc_stubs: u32 = 0, objc_selrefs: u32 = 0, tlv_ptr: u32 = 0, symtab: u32 = 0, + trampoline: u32 = 0, }; pub const Index = u32; diff --git a/zig/src/link/MachO/ZigObject.zig b/zig/src/link/MachO/ZigObject.zig index 7d69e4ad76..d6b77243a1 100644 --- a/zig/src/link/MachO/ZigObject.zig +++ b/zig/src/link/MachO/ZigObject.zig @@ -55,8 +55,7 @@ pub fn init(self: *ZigObject, macho_file: *MachO) !void { switch (comp.config.debug_format) { .strip => {}, .dwarf => |v| { - assert(v == .@"32"); - self.dwarf = Dwarf.init(&macho_file.base, .dwarf32); + self.dwarf = Dwarf.init(&macho_file.base, v); self.debug_strtab_dirty = true; self.debug_abbrev_dirty = true; self.debug_aranges_dirty = true; @@ -101,8 +100,8 @@ pub fn deinit(self: *ZigObject, allocator: Allocator) void { } self.tlv_initializers.deinit(allocator); - if (self.dwarf) |*dw| { - dw.deinit(); + if (self.dwarf) |*dwarf| { + dwarf.deinit(); } } @@ -595,56 +594,13 @@ pub fn flushModule(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) if (metadata.const_state != .unused) metadata.const_state = .flushed; } - if (self.dwarf) |*dw| { + if (self.dwarf) |*dwarf| { const pt: Zcu.PerThread = .{ .zcu = macho_file.base.comp.module.?, .tid = tid }; - try dw.flushModule(pt); + try dwarf.flushModule(pt); - if (self.debug_abbrev_dirty) { - try dw.writeDbgAbbrev(); - self.debug_abbrev_dirty = false; - } - - if (self.debug_info_header_dirty) { - // Currently only one compilation unit is supported, so the address range is simply - // identical to the main program header virtual address and memory size. - const text_section = macho_file.sections.items(.header)[macho_file.zig_text_sect_index.?]; - const low_pc = text_section.addr; - const high_pc = text_section.addr + text_section.size; - try dw.writeDbgInfoHeader(pt.zcu, low_pc, high_pc); - self.debug_info_header_dirty = false; - } - - if (self.debug_aranges_dirty) { - // Currently only one compilation unit is supported, so the address range is simply - // identical to the main program header virtual address and memory size. - const text_section = macho_file.sections.items(.header)[macho_file.zig_text_sect_index.?]; - try dw.writeDbgAranges(text_section.addr, text_section.size); - self.debug_aranges_dirty = false; - } - - if (self.debug_line_header_dirty) { - try dw.writeDbgLineHeader(); - self.debug_line_header_dirty = false; - } - - if (!macho_file.base.isRelocatable()) { - const d_sym = macho_file.getDebugSymbols().?; - const sect_index = d_sym.debug_str_section_index.?; - if (self.debug_strtab_dirty or dw.strtab.buffer.items.len != d_sym.getSection(sect_index).size) { - const needed_size = @as(u32, @intCast(dw.strtab.buffer.items.len)); - try d_sym.growSection(sect_index, needed_size, false, macho_file); - try d_sym.file.pwriteAll(dw.strtab.buffer.items, d_sym.getSection(sect_index).offset); - self.debug_strtab_dirty = false; - } - } else { - const sect_index = macho_file.debug_str_sect_index.?; - if (self.debug_strtab_dirty or dw.strtab.buffer.items.len != macho_file.sections.items(.header)[sect_index].size) { - const needed_size = @as(u32, @intCast(dw.strtab.buffer.items.len)); - try macho_file.growSection(sect_index, needed_size); - try macho_file.base.file.?.pwriteAll(dw.strtab.buffer.items, macho_file.sections.items(.header)[sect_index].offset); - self.debug_strtab_dirty = false; - } - } + self.debug_abbrev_dirty = false; + self.debug_aranges_dirty = false; + self.debug_strtab_dirty = false; } // The point of flushModule() is to commit changes, so in theory, nothing should @@ -816,8 +772,8 @@ pub fn updateFunc( var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); - var dwarf_state = if (self.dwarf) |*dw| try dw.initNavState(pt, func.owner_nav) else null; - defer if (dwarf_state) |*ds| ds.deinit(); + var dwarf_wip_nav = if (self.dwarf) |*dwarf| try dwarf.initWipNav(pt, func.owner_nav, sym_index) else null; + defer if (dwarf_wip_nav) |*wip_nav| wip_nav.deinit(); const res = try codegen.generateFunction( &macho_file.base, @@ -827,7 +783,7 @@ pub fn updateFunc( air, liveness, &code_buffer, - if (dwarf_state) |*ds| .{ .dwarf = ds } else .none, + if (dwarf_wip_nav) |*wip_nav| .{ .dwarf = wip_nav } else .none, ); const code = switch (res) { @@ -839,20 +795,67 @@ pub fn updateFunc( }; const sect_index = try self.getNavOutputSection(macho_file, zcu, func.owner_nav, code); + const old_rva, const old_alignment = blk: { + const atom = self.symbols.items[sym_index].getAtom(macho_file).?; + break :blk .{ atom.value, atom.alignment }; + }; try self.updateNavCode(macho_file, pt, func.owner_nav, sym_index, sect_index, code); + const new_rva, const new_alignment = blk: { + const atom = self.symbols.items[sym_index].getAtom(macho_file).?; + break :blk .{ atom.value, atom.alignment }; + }; - if (dwarf_state) |*ds| { + if (dwarf_wip_nav) |*wip_nav| { const sym = self.symbols.items[sym_index]; - try self.dwarf.?.commitNavState( + try self.dwarf.?.finishWipNav( pt, func.owner_nav, - sym.getAddress(.{}, macho_file), - sym.getAtom(macho_file).?.size, - ds, + .{ + .index = sym_index, + .addr = sym.getAddress(.{}, macho_file), + .size = sym.getAtom(macho_file).?.size, + }, + wip_nav, ); } // Exports will be updated by `Zcu.processExports` after the update. + if (old_rva != new_rva and old_rva > 0) { + // If we had to reallocate the function, we re-use the existing slot for a trampoline. + // In the rare case that the function has been further overaligned we skip creating a + // trampoline and update all symbols referring this function. + if (old_alignment.order(new_alignment) == .lt) { + @panic("TODO update all symbols referring this function"); + } + + // Create a trampoline to the new location at `old_rva`. + if (!self.symbols.items[sym_index].flags.trampoline) { + const name = try std.fmt.allocPrint(gpa, "{s}$trampoline", .{ + self.symbols.items[sym_index].getName(macho_file), + }); + defer gpa.free(name); + const name_off = try self.addString(gpa, name); + const tr_size = trampolineSize(macho_file.getTarget().cpu.arch); + const tr_sym_index = try self.newSymbolWithAtom(gpa, name_off, macho_file); + const tr_sym = &self.symbols.items[tr_sym_index]; + tr_sym.out_n_sect = macho_file.zig_text_sect_index.?; + const tr_nlist = &self.symtab.items(.nlist)[tr_sym.nlist_idx]; + tr_nlist.n_sect = macho_file.zig_text_sect_index.? + 1; + const tr_atom = tr_sym.getAtom(macho_file).?; + tr_atom.value = old_rva; + tr_atom.setAlive(true); + tr_atom.alignment = old_alignment; + tr_atom.out_n_sect = macho_file.zig_text_sect_index.?; + tr_atom.size = tr_size; + self.symtab.items(.size)[tr_sym.nlist_idx] = tr_size; + const target_sym = &self.symbols.items[sym_index]; + target_sym.addExtra(.{ .trampoline = tr_sym_index }, macho_file); + target_sym.flags.trampoline = true; + } + const target_sym = self.symbols.items[sym_index]; + const source_sym = self.symbols.items[target_sym.getExtra(macho_file).trampoline]; + try writeTrampoline(source_sym, target_sym, macho_file); + } } pub fn updateNav( @@ -866,9 +869,11 @@ pub fn updateNav( const zcu = pt.zcu; const ip = &zcu.intern_pool; - const nav_val = zcu.navValue(nav_index); - const nav_init = switch (ip.indexToKey(nav_val.toIntern())) { - .variable => |variable| Value.fromInterned(variable.init), + const nav = ip.getNav(nav_index); + + const nav_init = switch (ip.indexToKey(nav.status.resolved.val)) { + .func => .none, + .variable => |variable| variable.init, .@"extern" => |@"extern"| { if (ip.isFunctionType(@"extern".ty)) return; // Extern variable gets a __got entry only @@ -876,54 +881,59 @@ pub fn updateNav( const lib_name = @"extern".lib_name.toSlice(ip); const index = try self.getGlobalSymbol(macho_file, name, lib_name); const sym = &self.symbols.items[index]; - sym.setSectionFlags(.{ .needs_got = true }); + sym.flags.is_extern_ptr = true; return; }, - else => nav_val, + else => nav.status.resolved.val, }; - const sym_index = try self.getOrCreateMetadataForNav(macho_file, nav_index); - self.symbols.items[sym_index].getAtom(macho_file).?.freeRelocs(macho_file); + if (nav_init != .none and Value.fromInterned(nav_init).typeOf(zcu).hasRuntimeBits(pt)) { + const sym_index = try self.getOrCreateMetadataForNav(macho_file, nav_index); + self.symbols.items[sym_index].getAtom(macho_file).?.freeRelocs(macho_file); - var code_buffer = std.ArrayList(u8).init(zcu.gpa); - defer code_buffer.deinit(); + var code_buffer = std.ArrayList(u8).init(zcu.gpa); + defer code_buffer.deinit(); - var nav_state: ?Dwarf.NavState = if (self.dwarf) |*dw| try dw.initNavState(pt, nav_index) else null; - defer if (nav_state) |*ns| ns.deinit(); + var debug_wip_nav = if (self.dwarf) |*dwarf| try dwarf.initWipNav(pt, nav_index, sym_index) else null; + defer if (debug_wip_nav) |*wip_nav| wip_nav.deinit(); - const res = try codegen.generateSymbol( - &macho_file.base, - pt, - zcu.navSrcLoc(nav_index), - nav_init, - &code_buffer, - if (nav_state) |*ns| .{ .dwarf = ns } else .none, - .{ .parent_atom_index = sym_index }, - ); - - const code = switch (res) { - .ok => code_buffer.items, - .fail => |em| { - try zcu.failed_codegen.put(zcu.gpa, nav_index, em); - return; - }, - }; - const sect_index = try self.getNavOutputSection(macho_file, zcu, nav_index, code); - if (isThreadlocal(macho_file, nav_index)) - try self.updateTlv(macho_file, pt, nav_index, sym_index, sect_index, code) - else - try self.updateNavCode(macho_file, pt, nav_index, sym_index, sect_index, code); - - if (nav_state) |*ns| { - const sym = self.symbols.items[sym_index]; - try self.dwarf.?.commitNavState( + const res = try codegen.generateSymbol( + &macho_file.base, pt, - nav_index, - sym.getAddress(.{}, macho_file), - sym.getAtom(macho_file).?.size, - ns, + zcu.navSrcLoc(nav_index), + Value.fromInterned(nav_init), + &code_buffer, + if (debug_wip_nav) |*wip_nav| .{ .dwarf = wip_nav } else .none, + .{ .parent_atom_index = sym_index }, ); - } + + const code = switch (res) { + .ok => code_buffer.items, + .fail => |em| { + try zcu.failed_codegen.put(zcu.gpa, nav_index, em); + return; + }, + }; + const sect_index = try self.getNavOutputSection(macho_file, zcu, nav_index, code); + if (isThreadlocal(macho_file, nav_index)) + try self.updateTlv(macho_file, pt, nav_index, sym_index, sect_index, code) + else + try self.updateNavCode(macho_file, pt, nav_index, sym_index, sect_index, code); + + if (debug_wip_nav) |*wip_nav| { + const sym = self.symbols.items[sym_index]; + try self.dwarf.?.finishWipNav( + pt, + nav_index, + .{ + .index = sym_index, + .addr = sym.getAddress(.{}, macho_file), + .size = sym.getAtom(macho_file).?.size, + }, + wip_nav, + ); + } + } else if (self.dwarf) |*dwarf| try dwarf.updateComptimeNav(pt, nav_index); // Exports will be updated by `Zcu.processExports` after the update. } @@ -981,13 +991,6 @@ fn updateNavCode( if (old_vaddr != atom.value) { sym.value = 0; nlist.n_value = 0; - - if (!macho_file.base.isRelocatable()) { - log.debug(" (updating offset table entry)", .{}); - assert(sym.getSectionFlags().has_zig_got); - const extra = sym.getExtra(macho_file); - try macho_file.zig_got.writeOne(macho_file, extra.zig_got); - } } } else if (code.len < old_size) { atom.shrink(macho_file); @@ -1000,13 +1003,7 @@ fn updateNavCode( errdefer self.freeNavMetadata(macho_file, sym_index); sym.value = 0; - sym.setSectionFlags(.{ .needs_zig_got = true }); nlist.n_value = 0; - - if (!macho_file.base.isRelocatable()) { - const gop = try sym.getOrCreateZigGotEntry(sym_index, macho_file); - try macho_file.zig_got.writeOne(macho_file, gop.index); - } } if (!sect.isZerofill()) { @@ -1416,14 +1413,8 @@ fn updateLazySymbol( errdefer self.freeNavMetadata(macho_file, symbol_index); sym.value = 0; - sym.setSectionFlags(.{ .needs_zig_got = true }); nlist.n_value = 0; - if (!macho_file.base.isRelocatable()) { - const gop = try sym.getOrCreateZigGotEntry(symbol_index, macho_file); - try macho_file.zig_got.writeOne(macho_file, gop.index); - } - const sect = macho_file.sections.items(.header)[output_section_index]; const file_offset = sect.offset + atom.value; try macho_file.base.file.?.pwriteAll(code, file_offset); @@ -1435,8 +1426,8 @@ pub fn updateNavLineNumber( pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, ) !void { - if (self.dwarf) |*dw| { - try dw.updateNavLineNumber(pt.zcu, nav_index); + if (self.dwarf) |*dwarf| { + try dwarf.updateNavLineNumber(pt.zcu, nav_index); } } @@ -1483,6 +1474,31 @@ pub fn getGlobalSymbol(self: *ZigObject, macho_file: *MachO, name: []const u8, l return lookup_gop.value_ptr.*; } +const max_trampoline_len = 12; + +fn trampolineSize(cpu_arch: std.Target.Cpu.Arch) u64 { + const len = switch (cpu_arch) { + .x86_64 => 5, // jmp rel32 + else => @panic("TODO implement trampoline size for this CPU arch"), + }; + comptime assert(len <= max_trampoline_len); + return len; +} + +fn writeTrampoline(tr_sym: Symbol, target: Symbol, macho_file: *MachO) !void { + const atom = tr_sym.getAtom(macho_file).?; + const header = macho_file.sections.items(.header)[atom.out_n_sect]; + const fileoff = header.offset + atom.value; + const source_addr = tr_sym.getAddress(.{}, macho_file); + const target_addr = target.getAddress(.{ .trampoline = false }, macho_file); + var buf: [max_trampoline_len]u8 = undefined; + const out = switch (macho_file.getTarget().cpu.arch) { + .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf), + else => @panic("TODO implement write trampoline for this CPU arch"), + }; + try macho_file.base.file.?.pwriteAll(out, fileoff); +} + pub fn getOrCreateMetadataForNav( self: *ZigObject, macho_file: *MachO, @@ -1495,8 +1511,6 @@ pub fn getOrCreateMetadataForNav( const sym = &self.symbols.items[sym_index]; if (isThreadlocal(macho_file, nav_index)) { sym.flags.tlv = true; - } else { - sym.setSectionFlags(.{ .needs_zig_got = true }); } gop.value_ptr.* = .{ .symbol_index = sym_index }; } @@ -1517,12 +1531,7 @@ pub fn getOrCreateMetadataForLazySymbol( .const_data => .{ &gop.value_ptr.const_symbol_index, &gop.value_ptr.const_state }, }; switch (state_ptr.*) { - .unused => { - const symbol_index = try self.newSymbolWithAtom(pt.zcu.gpa, .{}, macho_file); - const sym = &self.symbols.items[symbol_index]; - sym.setSectionFlags(.{ .needs_zig_got = true }); - symbol_index_ptr.* = symbol_index; - }, + .unused => symbol_index_ptr.* = try self.newSymbolWithAtom(pt.zcu.gpa, .{}, macho_file), .pending_flush => return symbol_index_ptr.*, .flushed => {}, } @@ -1784,6 +1793,19 @@ const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.Index, LazySymb const RelocationTable = std.ArrayListUnmanaged(std.ArrayListUnmanaged(Relocation)); const TlvInitializerTable = std.AutoArrayHashMapUnmanaged(Atom.Index, TlvInitializer); +const x86_64 = struct { + fn writeTrampolineCode(source_addr: u64, target_addr: u64, buf: *[max_trampoline_len]u8) ![]u8 { + const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)) - 5; + var bytes = [_]u8{ + 0xe9, 0x00, 0x00, 0x00, 0x00, // jmp rel32 + }; + assert(bytes.len == trampolineSize(.x86_64)); + mem.writeInt(i32, bytes[1..][0..4], @intCast(disp), .little); + @memcpy(buf[0..bytes.len], &bytes); + return buf[0..bytes.len]; + } +}; + const assert = std.debug.assert; const builtin = @import("builtin"); const codegen = @import("../../codegen.zig"); diff --git a/zig/src/link/MachO/dyld_info/Rebase.zig b/zig/src/link/MachO/dyld_info/Rebase.zig index 8809f130ff..2bee8ad22c 100644 --- a/zig/src/link/MachO/dyld_info/Rebase.zig +++ b/zig/src/link/MachO/dyld_info/Rebase.zig @@ -56,18 +56,6 @@ pub fn updateSize(rebase: *Rebase, macho_file: *MachO) !void { } } - if (macho_file.zig_got_sect_index) |sid| { - const seg_id = macho_file.sections.items(.segment_id)[sid]; - const seg = macho_file.segments.items[seg_id]; - for (0..macho_file.zig_got.entries.items.len) |idx| { - const addr = macho_file.zig_got.entryAddress(@intCast(idx), macho_file); - try rebase.entries.append(gpa, .{ - .offset = addr - seg.vmaddr, - .segment_id = seg_id, - }); - } - } - if (macho_file.got_sect_index) |sid| { const seg_id = macho_file.sections.items(.segment_id)[sid]; const seg = macho_file.segments.items[seg_id]; diff --git a/zig/src/link/MachO/load_commands.zig b/zig/src/link/MachO/load_commands.zig index 74d0c58cbd..a891bedbd6 100644 --- a/zig/src/link/MachO/load_commands.zig +++ b/zig/src/link/MachO/load_commands.zig @@ -53,7 +53,7 @@ pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) !u32 if (macho_file.base.isDynLib()) { const emit = macho_file.base.emit; const install_name = macho_file.install_name orelse - try emit.directory.join(gpa, &.{emit.sub_path}); + try emit.root_dir.join(gpa, &.{emit.sub_path}); defer if (macho_file.install_name == null) gpa.free(install_name); sizeofcmds += calcInstallNameLen( @sizeOf(macho.dylib_command), @@ -237,7 +237,7 @@ pub fn writeDylibIdLC(macho_file: *MachO, writer: anytype) !void { assert(comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic); const emit = macho_file.base.emit; const install_name = macho_file.install_name orelse - try emit.directory.join(gpa, &.{emit.sub_path}); + try emit.root_dir.join(gpa, &.{emit.sub_path}); defer if (macho_file.install_name == null) gpa.free(install_name); const curr = comp.version orelse std.SemanticVersion{ .major = 1, diff --git a/zig/src/link/MachO/relocatable.zig b/zig/src/link/MachO/relocatable.zig index 1ee40921ce..e18442b6c2 100644 --- a/zig/src/link/MachO/relocatable.zig +++ b/zig/src/link/MachO/relocatable.zig @@ -465,7 +465,7 @@ fn allocateSections(macho_file: *MachO) !void { const alignment = try math.powi(u32, 2, header.@"align"); if (!header.isZerofill()) { if (needed_size > macho_file.allocatedSize(header.offset)) { - header.offset = math.cast(u32, macho_file.findFreeSpace(needed_size, alignment)) orelse + header.offset = math.cast(u32, try macho_file.findFreeSpace(needed_size, alignment)) orelse return error.Overflow; } } diff --git a/zig/src/link/MachO/synthetic.zig b/zig/src/link/MachO/synthetic.zig index 5285412bb7..35f9d45536 100644 --- a/zig/src/link/MachO/synthetic.zig +++ b/zig/src/link/MachO/synthetic.zig @@ -1,111 +1,3 @@ -pub const ZigGotSection = struct { - entries: std.ArrayListUnmanaged(Symbol.Index) = .{}, - dirty: bool = false, - - pub const Index = u32; - - pub fn deinit(zig_got: *ZigGotSection, allocator: Allocator) void { - zig_got.entries.deinit(allocator); - } - - fn allocateEntry(zig_got: *ZigGotSection, allocator: Allocator) !Index { - try zig_got.entries.ensureUnusedCapacity(allocator, 1); - // TODO add free list - const index = @as(Index, @intCast(zig_got.entries.items.len)); - _ = zig_got.entries.addOneAssumeCapacity(); - zig_got.dirty = true; - return index; - } - - pub fn addSymbol(zig_got: *ZigGotSection, sym_index: Symbol.Index, macho_file: *MachO) !Index { - const gpa = macho_file.base.comp.gpa; - const zo = macho_file.getZigObject().?; - const index = try zig_got.allocateEntry(gpa); - const entry = &zig_got.entries.items[index]; - entry.* = sym_index; - const symbol = &zo.symbols.items[sym_index]; - assert(symbol.getSectionFlags().needs_zig_got); - symbol.setSectionFlags(.{ .has_zig_got = true }); - symbol.addExtra(.{ .zig_got = index }, macho_file); - return index; - } - - pub fn entryOffset(zig_got: ZigGotSection, index: Index, macho_file: *MachO) u64 { - _ = zig_got; - const sect = macho_file.sections.items(.header)[macho_file.zig_got_sect_index.?]; - return sect.offset + @sizeOf(u64) * index; - } - - pub fn entryAddress(zig_got: ZigGotSection, index: Index, macho_file: *MachO) u64 { - _ = zig_got; - const sect = macho_file.sections.items(.header)[macho_file.zig_got_sect_index.?]; - return sect.addr + @sizeOf(u64) * index; - } - - pub fn size(zig_got: ZigGotSection, macho_file: *MachO) usize { - _ = macho_file; - return @sizeOf(u64) * zig_got.entries.items.len; - } - - pub fn writeOne(zig_got: *ZigGotSection, macho_file: *MachO, index: Index) !void { - if (zig_got.dirty) { - const needed_size = zig_got.size(macho_file); - try macho_file.growSection(macho_file.zig_got_sect_index.?, needed_size); - zig_got.dirty = false; - } - const zo = macho_file.getZigObject().?; - const off = zig_got.entryOffset(index, macho_file); - const entry = zig_got.entries.items[index]; - const value = zo.symbols.items[entry].getAddress(.{ .stubs = false }, macho_file); - - var buf: [8]u8 = undefined; - std.mem.writeInt(u64, &buf, value, .little); - try macho_file.base.file.?.pwriteAll(&buf, off); - } - - pub fn writeAll(zig_got: ZigGotSection, macho_file: *MachO, writer: anytype) !void { - const zo = macho_file.getZigObject().?; - for (zig_got.entries.items) |entry| { - const symbol = zo.symbols.items[entry]; - const value = symbol.address(.{ .stubs = false }, macho_file); - try writer.writeInt(u64, value, .little); - } - } - - const FormatCtx = struct { - zig_got: ZigGotSection, - macho_file: *MachO, - }; - - pub fn fmt(zig_got: ZigGotSection, macho_file: *MachO) std.fmt.Formatter(format2) { - return .{ .data = .{ .zig_got = zig_got, .macho_file = macho_file } }; - } - - pub fn format2( - ctx: FormatCtx, - comptime unused_fmt_string: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = options; - _ = unused_fmt_string; - const zig_got = ctx.zig_got; - const macho_file = ctx.macho_file; - try writer.writeAll("__zig_got\n"); - for (zig_got.entries.items, 0..) |entry, index| { - const zo = macho_file.getZigObject().?; - const symbol = zo.symbols.items[entry]; - try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ - index, - zig_got.entryAddress(@intCast(index), macho_file), - entry, - symbol.getAddress(.{}, macho_file), - symbol.getName(macho_file), - }); - } - } -}; - pub const GotSection = struct { symbols: std.ArrayListUnmanaged(MachO.Ref) = .{}, diff --git a/zig/src/link/MachO/thunks.zig b/zig/src/link/MachO/thunks.zig index 37013c54c4..4248785c54 100644 --- a/zig/src/link/MachO/thunks.zig +++ b/zig/src/link/MachO/thunks.zig @@ -85,6 +85,7 @@ pub const Thunk = struct { value: u64 = 0, out_n_sect: u8 = 0, symbols: std.AutoArrayHashMapUnmanaged(MachO.Ref, void) = .{}, + output_symtab_ctx: MachO.SymtabCtx = .{}, pub fn deinit(thunk: *Thunk, allocator: Allocator) void { thunk.symbols.deinit(allocator); @@ -116,6 +117,34 @@ pub const Thunk = struct { } } + pub fn calcSymtabSize(thunk: *Thunk, macho_file: *MachO) void { + thunk.output_symtab_ctx.nlocals = @as(u32, @intCast(thunk.symbols.keys().len)); + for (thunk.symbols.keys()) |ref| { + const sym = ref.getSymbol(macho_file).?; + thunk.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + "__thunk".len + 1)); + } + } + + pub fn writeSymtab(thunk: Thunk, macho_file: *MachO, ctx: anytype) void { + var n_strx = thunk.output_symtab_ctx.stroff; + for (thunk.symbols.keys(), thunk.output_symtab_ctx.ilocal..) |ref, ilocal| { + const sym = ref.getSymbol(macho_file).?; + const name = sym.getName(macho_file); + const out_sym = &ctx.symtab.items[ilocal]; + out_sym.n_strx = n_strx; + @memcpy(ctx.strtab.items[n_strx..][0..name.len], name); + n_strx += @intCast(name.len); + @memcpy(ctx.strtab.items[n_strx..][0.."__thunk".len], "__thunk"); + n_strx += @intCast("__thunk".len); + ctx.strtab.items[n_strx] = 0; + n_strx += 1; + out_sym.n_type = macho.N_SECT; + out_sym.n_sect = @intCast(thunk.out_n_sect + 1); + out_sym.n_value = @intCast(thunk.getTargetAddress(ref, macho_file)); + out_sym.n_desc = 0; + } + } + pub fn format( thunk: Thunk, comptime unused_fmt_string: []const u8, diff --git a/zig/src/link/NvPtx.zig b/zig/src/link/NvPtx.zig index cb95779d8e..1488f65ff9 100644 --- a/zig/src/link/NvPtx.zig +++ b/zig/src/link/NvPtx.zig @@ -11,6 +11,7 @@ const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const log = std.log.scoped(.link); +const Path = std.Build.Cache.Path; const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); @@ -28,7 +29,7 @@ llvm_object: LlvmObject.Ptr, pub fn createEmpty( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*NvPtx { const target = comp.root_mod.resolved_target.result; @@ -70,7 +71,7 @@ pub fn createEmpty( pub fn open( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*NvPtx { const target = comp.root_mod.resolved_target.result; diff --git a/zig/src/link/Plan9.zig b/zig/src/link/Plan9.zig index afd4c57ff1..27a3bf7bc8 100644 --- a/zig/src/link/Plan9.zig +++ b/zig/src/link/Plan9.zig @@ -23,6 +23,7 @@ const mem = std.mem; const Allocator = std.mem.Allocator; const log = std.log.scoped(.link); const assert = std.debug.assert; +const Path = std.Build.Cache.Path; base: link.File, sixtyfour_bit: bool, @@ -275,7 +276,7 @@ pub fn defaultBaseAddrs(arch: std.Target.Cpu.Arch) Bases { pub fn createEmpty( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*Plan9 { const target = comp.root_mod.resolved_target.result; @@ -447,6 +448,7 @@ pub fn updateNav(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde const nav = ip.getNav(nav_index); const nav_val = zcu.navValue(nav_index); const nav_init = switch (ip.indexToKey(nav_val.toIntern())) { + .func => return, .variable => |variable| Value.fromInterned(variable.init), .@"extern" => { log.debug("found extern decl: {}", .{nav.name.fmt(ip)}); @@ -454,28 +456,31 @@ pub fn updateNav(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde }, else => nav_val, }; - const atom_idx = try self.seeNav(pt, nav_index); - var code_buffer = std.ArrayList(u8).init(gpa); - defer code_buffer.deinit(); - // TODO we need the symbol index for symbol in the table of locals for the containing atom - const res = try codegen.generateSymbol(&self.base, pt, zcu.navSrcLoc(nav_index), nav_init, &code_buffer, .none, .{ - .parent_atom_index = @intCast(atom_idx), - }); - const code = switch (res) { - .ok => code_buffer.items, - .fail => |em| { - try zcu.failed_codegen.put(gpa, nav_index, em); - return; - }, - }; - try self.data_nav_table.ensureUnusedCapacity(gpa, 1); - const duped_code = try gpa.dupe(u8, code); - self.getAtomPtr(self.navs.get(nav_index).?.index).code = .{ .code_ptr = null, .other = .{ .nav_index = nav_index } }; - if (self.data_nav_table.fetchPutAssumeCapacity(nav_index, duped_code)) |old_entry| { - gpa.free(old_entry.value); + if (nav_init.typeOf(zcu).hasRuntimeBits(pt)) { + const atom_idx = try self.seeNav(pt, nav_index); + + var code_buffer = std.ArrayList(u8).init(gpa); + defer code_buffer.deinit(); + // TODO we need the symbol index for symbol in the table of locals for the containing atom + const res = try codegen.generateSymbol(&self.base, pt, zcu.navSrcLoc(nav_index), nav_init, &code_buffer, .none, .{ + .parent_atom_index = @intCast(atom_idx), + }); + const code = switch (res) { + .ok => code_buffer.items, + .fail => |em| { + try zcu.failed_codegen.put(gpa, nav_index, em); + return; + }, + }; + try self.data_nav_table.ensureUnusedCapacity(gpa, 1); + const duped_code = try gpa.dupe(u8, code); + self.getAtomPtr(self.navs.get(nav_index).?.index).code = .{ .code_ptr = null, .other = .{ .nav_index = nav_index } }; + if (self.data_nav_table.fetchPutAssumeCapacity(nav_index, duped_code)) |old_entry| { + gpa.free(old_entry.value); + } + try self.updateFinish(pt, nav_index); } - return self.updateFinish(pt, nav_index); } /// called at the end of update{Decl,Func} @@ -1196,7 +1201,7 @@ pub fn deinit(self: *Plan9) void { pub fn open( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*Plan9 { const target = comp.root_mod.resolved_target.result; @@ -1210,7 +1215,7 @@ pub fn open( const self = try createEmpty(arena, comp, emit, options); errdefer self.base.destroy(); - const file = try emit.directory.handle.createFile(emit.sub_path, .{ + const file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .read = true, .mode = link.File.determineMode( use_lld, diff --git a/zig/src/link/SpirV.zig b/zig/src/link/SpirV.zig index f76ceec2f5..9964c09df0 100644 --- a/zig/src/link/SpirV.zig +++ b/zig/src/link/SpirV.zig @@ -26,6 +26,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const log = std.log.scoped(.link); +const Path = std.Build.Cache.Path; const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); @@ -54,7 +55,7 @@ object: codegen.Object, pub fn createEmpty( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*SpirV { const gpa = comp.gpa; @@ -95,7 +96,7 @@ pub fn createEmpty( pub fn open( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*SpirV { const target = comp.root_mod.resolved_target.result; @@ -110,7 +111,7 @@ pub fn open( errdefer spirv.base.destroy(); // TODO: read the file and keep valid parts instead of truncating - const file = try emit.directory.handle.createFile(emit.sub_path, .{ + const file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = true, .read = true, }); diff --git a/zig/src/link/Wasm.zig b/zig/src/link/Wasm.zig index 87dd8c13f9..2c5fc3fe7a 100644 --- a/zig/src/link/Wasm.zig +++ b/zig/src/link/Wasm.zig @@ -22,6 +22,7 @@ const Air = @import("../Air.zig"); const Allocator = std.mem.Allocator; const Archive = @import("Wasm/Archive.zig"); const Cache = std.Build.Cache; +const Path = Cache.Path; const CodeGen = @import("../arch/wasm/CodeGen.zig"); const Compilation = @import("../Compilation.zig"); const Dwarf = @import("Dwarf.zig"); @@ -346,7 +347,7 @@ pub const StringTable = struct { pub fn open( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*Wasm { // TODO: restore saved linker state, don't truncate the file, and @@ -357,7 +358,7 @@ pub fn open( pub fn createEmpty( arena: Allocator, comp: *Compilation, - emit: Compilation.Emit, + emit: Path, options: link.File.OpenOptions, ) !*Wasm { const gpa = comp.gpa; @@ -430,7 +431,7 @@ pub fn createEmpty( // can be passed to LLD. const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path; - wasm.base.file = try emit.directory.handle.createFile(sub_path, .{ + wasm.base.file = try emit.root_dir.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = if (fs.has_executable_bit) @@ -2496,7 +2497,7 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no const sub_prog_node = prog_node.start("Wasm Flush", 0); defer sub_prog_node.end(); - const directory = wasm.base.emit.directory; // Just an alias to make it shorter to type. + const directory = wasm.base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path}); const module_obj_path: ?[]const u8 = if (wasm.base.zcu_object_sub_path) |path| blk: { if (fs.path.dirname(full_out_path)) |dirname| { @@ -3346,7 +3347,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: const gpa = comp.gpa; - const directory = wasm.base.emit.directory; // Just an alias to make it shorter to type. + const directory = wasm.base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path}); // If there is no Zig code to compile, then we should skip flushing the output file because it diff --git a/zig/src/link/Wasm/ZigObject.zig b/zig/src/link/Wasm/ZigObject.zig index 3424006523..05c15f0732 100644 --- a/zig/src/link/Wasm/ZigObject.zig +++ b/zig/src/link/Wasm/ZigObject.zig @@ -248,46 +248,49 @@ pub fn updateNav( const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - const is_extern, const lib_name, const nav_init = switch (ip.indexToKey(nav.status.resolved.val)) { - .variable => |variable| .{ false, variable.lib_name, variable.init }, + const nav_val = zcu.navValue(nav_index); + const is_extern, const lib_name, const nav_init = switch (ip.indexToKey(nav_val.toIntern())) { + .variable => |variable| .{ false, variable.lib_name, Value.fromInterned(variable.init) }, .func => return, .@"extern" => |@"extern"| if (ip.isFunctionType(nav.typeOf(ip))) return else - .{ true, @"extern".lib_name, nav.status.resolved.val }, - else => .{ false, .none, nav.status.resolved.val }, + .{ true, @"extern".lib_name, nav_val }, + else => .{ false, .none, nav_val }, }; - const gpa = wasm_file.base.comp.gpa; - const atom_index = try zig_object.getOrCreateAtomForNav(wasm_file, pt, nav_index); - const atom = wasm_file.getAtomPtr(atom_index); - atom.clear(); + if (nav_init.typeOf(zcu).hasRuntimeBits(pt)) { + const gpa = wasm_file.base.comp.gpa; + const atom_index = try zig_object.getOrCreateAtomForNav(wasm_file, pt, nav_index); + const atom = wasm_file.getAtomPtr(atom_index); + atom.clear(); - if (is_extern) - return zig_object.addOrUpdateImport(wasm_file, nav.name.toSlice(ip), atom.sym_index, lib_name.toSlice(ip), null); + if (is_extern) + return zig_object.addOrUpdateImport(wasm_file, nav.name.toSlice(ip), atom.sym_index, lib_name.toSlice(ip), null); - var code_writer = std.ArrayList(u8).init(gpa); - defer code_writer.deinit(); + var code_writer = std.ArrayList(u8).init(gpa); + defer code_writer.deinit(); - const res = try codegen.generateSymbol( - &wasm_file.base, - pt, - zcu.navSrcLoc(nav_index), - Value.fromInterned(nav_init), - &code_writer, - .none, - .{ .parent_atom_index = @intFromEnum(atom.sym_index) }, - ); + const res = try codegen.generateSymbol( + &wasm_file.base, + pt, + zcu.navSrcLoc(nav_index), + nav_init, + &code_writer, + .none, + .{ .parent_atom_index = @intFromEnum(atom.sym_index) }, + ); - const code = switch (res) { - .ok => code_writer.items, - .fail => |em| { - try zcu.failed_codegen.put(zcu.gpa, nav_index, em); - return; - }, - }; + const code = switch (res) { + .ok => code_writer.items, + .fail => |em| { + try zcu.failed_codegen.put(zcu.gpa, nav_index, em); + return; + }, + }; - return zig_object.finishUpdateNav(wasm_file, pt, nav_index, code); + try zig_object.finishUpdateNav(wasm_file, pt, nav_index, code); + } } pub fn updateFunc( diff --git a/zig/src/main.zig b/zig/src/main.zig index 62c721f157..226ec79467 100644 --- a/zig/src/main.zig +++ b/zig/src/main.zig @@ -3257,9 +3257,12 @@ fn buildOutputType( else => false, }; + const incremental = opt_incremental orelse false; + const disable_lld_caching = !output_to_cache; const cache_mode: Compilation.CacheMode = b: { + if (incremental) break :b .incremental; if (disable_lld_caching) break :b .incremental; if (!create_module.resolved_options.have_zcu) break :b .whole; @@ -3272,8 +3275,6 @@ fn buildOutputType( break :b .incremental; }; - const incremental = opt_incremental orelse false; - process.raiseFileDescriptorLimit(); var file_system_inputs: std.ArrayListUnmanaged(u8) = .{}; @@ -3518,7 +3519,7 @@ fn buildOutputType( if (test_exec_args.items.len == 0 and target.ofmt == .c) default_exec_args: { // Default to using `zig run` to execute the produced .c code from `zig test`. const c_code_loc = emit_bin_loc orelse break :default_exec_args; - const c_code_directory = c_code_loc.directory orelse comp.bin_file.?.emit.directory; + const c_code_directory = c_code_loc.directory orelse comp.bin_file.?.emit.root_dir; const c_code_path = try fs.path.join(arena, &[_][]const u8{ c_code_directory.path orelse ".", c_code_loc.basename, }); @@ -4141,7 +4142,7 @@ fn serve( if (output.errors.errorMessageCount() != 0) { try server.serveErrorBundle(output.errors); } else { - try server.serveEmitBinPath(output.out_zig_path, .{ + try server.serveEmitDigest(&output.digest, .{ .flags = .{ .cache_hit = output.cache_hit }, }); } @@ -4228,62 +4229,10 @@ fn serveUpdateResults(s: *Server, comp: *Compilation) !void { return; } - // This logic is counter-intuitive because the protocol accounts for each - // emitted artifact possibly being in a different location, which correctly - // matches the behavior of the compiler, however, the build system - // currently always passes flags that makes all build artifacts output to - // the same local cache directory, and relies on them all being in the same - // directory. - // - // So, until the build system and protocol are changed to reflect this, - // this logic must ensure that emit_bin_path is emitted for at least one - // thing, if there are any artifacts. - - switch (comp.cache_use) { - .incremental => if (comp.bin_file) |lf| { - const full_path = try lf.emit.directory.join(gpa, &.{lf.emit.sub_path}); - defer gpa.free(full_path); - try s.serveEmitBinPath(full_path, .{ - .flags = .{ .cache_hit = comp.last_update_was_cache_hit }, - }); - return; - }, - .whole => |whole| if (whole.bin_sub_path) |sub_path| { - const full_path = try comp.local_cache_directory.join(gpa, &.{sub_path}); - defer gpa.free(full_path); - try s.serveEmitBinPath(full_path, .{ - .flags = .{ .cache_hit = comp.last_update_was_cache_hit }, - }); - return; - }, - } - - for ([_]?Compilation.Emit{ - comp.docs_emit, - comp.implib_emit, - }) |opt_emit| { - const emit = opt_emit orelse continue; - const full_path = try emit.directory.join(gpa, &.{emit.sub_path}); - defer gpa.free(full_path); - try s.serveEmitBinPath(full_path, .{ - .flags = .{ .cache_hit = comp.last_update_was_cache_hit }, - }); - return; - } - - for ([_]?Compilation.EmitLoc{ - comp.emit_asm, - comp.emit_llvm_ir, - comp.emit_llvm_bc, - }) |opt_emit_loc| { - const emit_loc = opt_emit_loc orelse continue; - const directory = emit_loc.directory orelse continue; - const full_path = try directory.join(gpa, &.{emit_loc.basename}); - defer gpa.free(full_path); - try s.serveEmitBinPath(full_path, .{ + if (comp.digest) |digest| { + try s.serveEmitDigest(&digest, .{ .flags = .{ .cache_hit = comp.last_update_was_cache_hit }, }); - return; } // Serve empty error bundle to indicate the update is done. @@ -4307,7 +4256,7 @@ fn runOrTest( // A naive `directory.join` here will indeed get the correct path to the binary, // however, in the case of cwd, we actually want `./foo` so that the path can be executed. const exe_path = try fs.path.join(arena, &[_][]const u8{ - lf.emit.directory.path orelse ".", lf.emit.sub_path, + lf.emit.root_dir.path orelse ".", lf.emit.sub_path, }); var argv = std.ArrayList([]const u8).init(gpa); @@ -4419,7 +4368,7 @@ fn runOrTestHotSwap( // tmp zig-cache and use it to spawn the child process. This way we are free to update // the binary with each requested hot update. .windows => blk: { - try lf.emit.directory.handle.copyFile(lf.emit.sub_path, comp.local_cache_directory.handle, lf.emit.sub_path, .{}); + try lf.emit.root_dir.handle.copyFile(lf.emit.sub_path, comp.local_cache_directory.handle, lf.emit.sub_path, .{}); break :blk try fs.path.join(gpa, &[_][]const u8{ comp.local_cache_directory.path orelse ".", lf.emit.sub_path, }); @@ -4428,7 +4377,7 @@ fn runOrTestHotSwap( // A naive `directory.join` here will indeed get the correct path to the binary, // however, in the case of cwd, we actually want `./foo` so that the path can be executed. else => try fs.path.join(gpa, &[_][]const u8{ - lf.emit.directory.path orelse ".", lf.emit.sub_path, + lf.emit.root_dir.path orelse ".", lf.emit.sub_path, }), }; defer gpa.free(exe_path); @@ -4538,9 +4487,11 @@ fn cmdTranslateC( }; if (fancy_output) |p| p.cache_hit = true; - const digest = if (try man.hit()) digest: { + const bin_digest, const hex_digest = if (try man.hit()) digest: { if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf); - break :digest man.final(); + const bin_digest = man.finalBin(); + const hex_digest = Cache.binToHex(bin_digest); + break :digest .{ bin_digest, hex_digest }; } else digest: { if (fancy_output) |p| p.cache_hit = false; var argv = std.ArrayList([]const u8).init(arena); @@ -4575,7 +4526,12 @@ fn cmdTranslateC( Compilation.dump_argv(argv.items); } - const formatted = switch (comp.config.c_frontend) { + const Result = union(enum) { + success: []const u8, + error_bundle: std.zig.ErrorBundle, + }; + + const result: Result = switch (comp.config.c_frontend) { .aro => f: { var stdout: []u8 = undefined; try jitCmd(comp.gpa, arena, argv.items, .{ @@ -4585,7 +4541,7 @@ fn cmdTranslateC( .capture = &stdout, .progress_node = prog_node, }); - break :f stdout; + break :f .{ .success = stdout }; }, .clang => f: { if (!build_options.have_llvm) unreachable; @@ -4613,33 +4569,47 @@ fn cmdTranslateC( c_headers_dir_path_z, ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - error.SemanticAnalyzeFail => { - if (fancy_output) |p| { - p.errors = errors; - return; - } else { - errors.renderToStdErr(color.renderOptions()); - process.exit(1); - } - }, + error.SemanticAnalyzeFail => break :f .{ .error_bundle = errors }, }; defer tree.deinit(comp.gpa); - break :f try tree.render(arena); + break :f .{ .success = try tree.render(arena) }; }, }; - if (out_dep_path) |dep_file_path| { + if (out_dep_path) |dep_file_path| add_deps: { const dep_basename = fs.path.basename(dep_file_path); // Add the files depended on to the cache system. - try man.addDepFilePost(zig_cache_tmp_dir, dep_basename); + man.addDepFilePost(zig_cache_tmp_dir, dep_basename) catch |err| switch (err) { + error.FileNotFound => { + // Clang didn't emit the dep file; nothing to add to the manifest. + break :add_deps; + }, + else => |e| return e, + }; // Just to save disk space, we delete the file because it is never needed again. zig_cache_tmp_dir.deleteFile(dep_basename) catch |err| { warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }); }; } - const digest = man.final(); - const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); + const formatted = switch (result) { + .success => |formatted| formatted, + .error_bundle => |eb| { + if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf); + if (fancy_output) |p| { + p.errors = eb; + return; + } else { + eb.renderToStdErr(color.renderOptions()); + process.exit(1); + } + }, + }; + + const bin_digest = man.finalBin(); + const hex_digest = Cache.binToHex(bin_digest); + + const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &hex_digest }); var o_dir = try comp.local_cache_directory.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); @@ -4655,16 +4625,14 @@ fn cmdTranslateC( if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf); - break :digest digest; + break :digest .{ bin_digest, hex_digest }; }; if (fancy_output) |p| { - p.out_zig_path = try comp.local_cache_directory.join(comp.gpa, &[_][]const u8{ - "o", &digest, translated_zig_basename, - }); + p.digest = bin_digest; p.errors = std.zig.ErrorBundle.empty; } else { - const out_zig_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename }); + const out_zig_path = try fs.path.join(arena, &[_][]const u8{ "o", &hex_digest, translated_zig_basename }); const zig_file = comp.local_cache_directory.handle.openFile(out_zig_path, .{}) catch |err| { const path = comp.local_cache_directory.path orelse "."; fatal("unable to open cached translated zig file '{s}{s}{s}': {s}", .{ path, fs.path.sep_str, out_zig_path, @errorName(err) }); diff --git a/zig/src/print_air.zig b/zig/src/print_air.zig index 137461e6e4..4db66a1d3c 100644 --- a/zig/src/print_air.zig +++ b/zig/src/print_air.zig @@ -283,6 +283,7 @@ const Writer = struct { .dbg_var_ptr, .dbg_var_val, + .dbg_arg_inline, => try w.writeDbgVar(s, inst), .struct_field_ptr => try w.writeStructField(s, inst), @@ -358,10 +359,7 @@ const Writer = struct { try w.writeType(s, arg.ty.toType()); switch (arg.name) { .none => {}, - _ => { - const name = w.air.nullTerminatedString(@intFromEnum(arg.name)); - try s.print(", \"{}\"", .{std.zig.fmtEscapes(name)}); - }, + _ => try s.print(", \"{}\"", .{std.zig.fmtEscapes(arg.name.toSlice(w.air))}), } } @@ -686,8 +684,8 @@ const Writer = struct { fn writeDbgVar(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; try w.writeOperand(s, inst, 0, pl_op.operand); - const name = w.air.nullTerminatedString(pl_op.payload); - try s.print(", \"{}\"", .{std.zig.fmtEscapes(name)}); + const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload); + try s.print(", \"{}\"", .{std.zig.fmtEscapes(name.toSlice(w.air))}); } fn writeCall(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { diff --git a/zig/src/print_zir.zig b/zig/src/print_zir.zig index bd06362e62..774b222626 100644 --- a/zig/src/print_zir.zig +++ b/zig/src/print_zir.zig @@ -615,6 +615,7 @@ const Writer = struct { .restore_err_ret_index => try self.writeRestoreErrRetIndex(stream, extended), .closure_get => try self.writeClosureGet(stream, extended), .field_parent_ptr => try self.writeFieldParentPtr(stream, extended), + .builtin_value => try self.writeBuiltinValue(stream, extended), } } @@ -746,7 +747,7 @@ const Writer = struct { fn writeIntBig(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].str; const byte_count = inst_data.len * @sizeOf(std.math.big.Limb); - const limb_bytes = self.code.nullTerminatedString(inst_data.start)[0..byte_count]; + const limb_bytes = self.code.string_bytes[@intFromEnum(inst_data.start)..][0..byte_count]; // limb_bytes is not aligned properly; we must allocate and copy the bytes // in order to accomplish this. const limbs = try self.gpa.alloc(std.math.big.Limb, inst_data.len); @@ -2782,6 +2783,12 @@ const Writer = struct { try self.writeSrcNode(stream, @bitCast(extended.operand)); } + fn writeBuiltinValue(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const val: Zir.Inst.BuiltinValue = @enumFromInt(extended.small); + try stream.print("{s})) ", .{@tagName(val)}); + try self.writeSrcNode(stream, @bitCast(extended.operand)); + } + fn writeInstRef(self: *Writer, stream: anytype, ref: Zir.Inst.Ref) !void { if (ref == .none) { return stream.writeAll(".none"); diff --git a/zig/src/register_manager.zig b/zig/src/register_manager.zig index 3e75cb152d..7ca117be0c 100644 --- a/zig/src/register_manager.zig +++ b/zig/src/register_manager.zig @@ -10,6 +10,7 @@ const Zcu = @import("Zcu.zig"); const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const expectEqualSlices = std.testing.expectEqualSlices; +const link = @import("link.zig"); const log = std.log.scoped(.register_manager); @@ -25,7 +26,7 @@ pub const AllocateRegistersError = error{ /// Can happen when spilling an instruction triggers a codegen /// error, so we propagate that error CodegenFail, -}; +} || link.File.UpdateDebugInfoError; pub fn RegisterManager( comptime Function: type, @@ -92,6 +93,8 @@ pub fn RegisterManager( comptime set: []const Register, reg: Register, ) ?std.math.IntFittingRange(0, set.len - 1) { + @setEvalBranchQuota(3000); + const Id = @TypeOf(reg.id()); comptime var min_id: Id = std.math.maxInt(Id); comptime var max_id: Id = std.math.minInt(Id); diff --git a/zig/test/behavior/basic.zig b/zig/test/behavior/basic.zig index 90d12e6858..9d34742604 100644 --- a/zig/test/behavior/basic.zig +++ b/zig/test/behavior/basic.zig @@ -733,7 +733,6 @@ test "extern variable with non-pointer opaque type" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; @export(var_to_export, .{ .name = "opaque_extern_var" }); try expect(@as(*align(1) u32, @ptrCast(&opaque_extern_var)).* == 42); diff --git a/zig/test/behavior/export_builtin.zig b/zig/test/behavior/export_builtin.zig index 547a9b990a..25b6e2527e 100644 --- a/zig/test/behavior/export_builtin.zig +++ b/zig/test/behavior/export_builtin.zig @@ -48,7 +48,6 @@ test "exporting using field access" { test "exporting comptime-known value" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and (builtin.target.ofmt != .elf and builtin.target.ofmt != .macho and @@ -68,7 +67,6 @@ test "exporting comptime-known value" { test "exporting comptime var" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and (builtin.target.ofmt != .elf and builtin.target.ofmt != .macho and diff --git a/zig/test/behavior/extern.zig b/zig/test/behavior/extern.zig index cd80c545ce..0ef3e49353 100644 --- a/zig/test/behavior/extern.zig +++ b/zig/test/behavior/extern.zig @@ -7,7 +7,6 @@ test "anyopaque extern symbol" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const a = @extern(*anyopaque, .{ .name = "a_mystery_symbol" }); const b: *i32 = @alignCast(@ptrCast(a)); diff --git a/zig/test/behavior/fn.zig b/zig/test/behavior/fn.zig index 1e4039c1bb..ab7aca6ed6 100644 --- a/zig/test/behavior/fn.zig +++ b/zig/test/behavior/fn.zig @@ -429,7 +429,6 @@ test "implicit cast function to function ptr" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S1 = struct { export fn someFunctionThatReturnsAValue() c_int { diff --git a/zig/test/behavior/maximum_minimum.zig b/zig/test/behavior/maximum_minimum.zig index d08bc82828..ab1803c5b1 100644 --- a/zig/test/behavior/maximum_minimum.zig +++ b/zig/test/behavior/maximum_minimum.zig @@ -146,6 +146,8 @@ test "@min/max for floats" { }; inline for (.{ f16, f32, f64, f80, f128, c_longdouble }) |T| { + if (T == c_longdouble and builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21090 + try S.doTheTest(T); try comptime S.doTheTest(T); } diff --git a/zig/test/behavior/pointers.zig b/zig/test/behavior/pointers.zig index 42e3ea0ae9..f8260d1f0d 100644 --- a/zig/test/behavior/pointers.zig +++ b/zig/test/behavior/pointers.zig @@ -45,7 +45,6 @@ test "pointer-integer arithmetic" { test "pointer subtraction" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; { const a: *u8 = @ptrFromInt(100); diff --git a/zig/test/behavior/vector.zig b/zig/test/behavior/vector.zig index c4e00d9b0c..214fda28d6 100644 --- a/zig/test/behavior/vector.zig +++ b/zig/test/behavior/vector.zig @@ -773,6 +773,7 @@ test "vector reduce operation" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArmOrThumb()) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21091 const S = struct { fn testReduce(comptime op: std.builtin.ReduceOp, x: anytype, expected: anytype) !void { diff --git a/zig/test/cases/compile_errors/stack_usage_in_naked_function.zig b/zig/test/cases/compile_errors/stack_usage_in_naked_function.zig new file mode 100644 index 0000000000..0d6b27abe3 --- /dev/null +++ b/zig/test/cases/compile_errors/stack_usage_in_naked_function.zig @@ -0,0 +1,45 @@ +export fn a() callconv(.Naked) noreturn { + var x: u32 = 10; + _ = &x; + + const y: u32 = x + 10; + _ = y; +} + +export fn b() callconv(.Naked) noreturn { + var x = @as(u32, 10); + _ = &x; + + const y = x; + var z = y; + _ = &z; +} + +export fn c() callconv(.Naked) noreturn { + const Foo = struct { + y: u32, + }; + + var x: Foo = .{ .y = 10 }; + _ = &x; +} + +export fn d() callconv(.Naked) noreturn { + const Foo = struct { + inline fn bar() void { + var x: u32 = 10; + _ = &x; + } + }; + + Foo.bar(); +} + +// error +// backend=stage2 +// +// :2:5: error: local variable in naked function +// :10:5: error: local variable in naked function +// :23:5: error: local variable in naked function +// :30:13: error: local variable in naked function +// :35:12: note: called from here diff --git a/zig/test/incremental/add_decl b/zig/test/incremental/add_decl new file mode 100644 index 0000000000..376a725efc --- /dev/null +++ b/zig/test/incremental/add_decl @@ -0,0 +1,59 @@ +#target=x86_64-linux +#update=initial version +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(foo); +} +const foo = "good morning\n"; +#expect_stdout="good morning\n" + +#update=add new declaration +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(foo); +} +const foo = "good morning\n"; +const bar = "good evening\n"; +#expect_stdout="good morning\n" + +#update=reference new declaration +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(bar); +} +const foo = "good morning\n"; +const bar = "good evening\n"; +#expect_stdout="good evening\n" + +#update=reference missing declaration +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(qux); +} +const foo = "good morning\n"; +const bar = "good evening\n"; +#expect_error=ignored + +#update=add missing declaration +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(qux); +} +const foo = "good morning\n"; +const bar = "good evening\n"; +const qux = "good night\n"; +#expect_stdout="good night\n" + +#update=remove unused declarations +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(qux); +} +const qux = "good night\n"; +#expect_stdout="good night\n" diff --git a/zig/test/incremental/add_decl_namespaced b/zig/test/incremental/add_decl_namespaced new file mode 100644 index 0000000000..43123e0d0c --- /dev/null +++ b/zig/test/incremental/add_decl_namespaced @@ -0,0 +1,59 @@ +#target=x86_64-linux +#update=initial version +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(@This().foo); +} +const foo = "good morning\n"; +#expect_stdout="good morning\n" + +#update=add new declaration +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(@This().foo); +} +const foo = "good morning\n"; +const bar = "good evening\n"; +#expect_stdout="good morning\n" + +#update=reference new declaration +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(@This().bar); +} +const foo = "good morning\n"; +const bar = "good evening\n"; +#expect_stdout="good evening\n" + +#update=reference missing declaration +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(@This().qux); +} +const foo = "good morning\n"; +const bar = "good evening\n"; +#expect_error=ignored + +#update=add missing declaration +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(@This().qux); +} +const foo = "good morning\n"; +const bar = "good evening\n"; +const qux = "good night\n"; +#expect_stdout="good night\n" + +#update=remove unused declarations +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(@This().qux); +} +const qux = "good night\n"; +#expect_stdout="good night\n" diff --git a/zig/test/incremental/delete_comptime_decls b/zig/test/incremental/delete_comptime_decls new file mode 100644 index 0000000000..d0da7110c3 --- /dev/null +++ b/zig/test/incremental/delete_comptime_decls @@ -0,0 +1,38 @@ +#target=x86_64-linux +#update=initial version +#file=main.zig +pub fn main() void {} +comptime { + var array = [_:0]u8{ 1, 2, 3, 4 }; + const src_slice: [:0]u8 = &array; + const slice = src_slice[2..6]; + _ = slice; +} +comptime { + var array = [_:0]u8{ 1, 2, 3, 4 }; + const slice = array[2..6]; + _ = slice; +} +comptime { + var array = [_]u8{ 1, 2, 3, 4 }; + const slice = array[2..5]; + _ = slice; +} +comptime { + var array = [_:0]u8{ 1, 2, 3, 4 }; + const slice = array[3..2]; + _ = slice; +} +#expect_error=ignored + +#update=delete and modify comptime decls +#file=main.zig +pub fn main() void {} +comptime { + const x: [*c]u8 = null; + var runtime_len: usize = undefined; + runtime_len = 0; + const y = x[0..runtime_len]; + _ = y; +} +#expect_error=ignored diff --git a/zig/test/incremental/modify_inline_fn b/zig/test/incremental/modify_inline_fn new file mode 100644 index 0000000000..633e7a0728 --- /dev/null +++ b/zig/test/incremental/modify_inline_fn @@ -0,0 +1,23 @@ +#target=x86_64-linux +#update=initial version +#file=main.zig +const std = @import("std"); +pub fn main() !void { + const str = getStr(); + try std.io.getStdOut().writeAll(str); +} +inline fn getStr() []const u8 { + return "foo\n"; +} +#expect_stdout="foo\n" +#update=change the string +#file=main.zig +const std = @import("std"); +pub fn main() !void { + const str = getStr(); + try std.io.getStdOut().writeAll(str); +} +inline fn getStr() []const u8 { + return "bar\n"; +} +#expect_stdout="bar\n" diff --git a/zig/test/incremental/move_src b/zig/test/incremental/move_src new file mode 100644 index 0000000000..8313bdfac2 --- /dev/null +++ b/zig/test/incremental/move_src @@ -0,0 +1,29 @@ +#target=x86_64-linux +#update=initial version +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writer().print("{d} {d}\n", .{ foo(), bar() }); +} +fn foo() u32 { + return @src().line; +} +fn bar() u32 { + return 123; +} +#expect_stdout="6 123\n" + +#update=add newline +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writer().print("{d} {d}\n", .{ foo(), bar() }); +} + +fn foo() u32 { + return @src().line; +} +fn bar() u32 { + return 123; +} +#expect_stdout="7 123\n" diff --git a/zig/test/incremental/type_becomes_comptime_only b/zig/test/incremental/type_becomes_comptime_only new file mode 100644 index 0000000000..2dfa556e87 --- /dev/null +++ b/zig/test/incremental/type_becomes_comptime_only @@ -0,0 +1,39 @@ +#target=x86_64-linux +#update=initial version +#file=main.zig +const SomeType = u32; +const S = struct { + x: SomeType, + fn foo(_: S) void {} +}; +pub fn main() void { + const s: S = .{ .x = 456 }; + s.foo(); +} +#expect_stdout="" + +#update=make S comptime-only +#file=main.zig +const SomeType = comptime_int; +const S = struct { + x: SomeType, + fn foo(_: S) void {} +}; +pub fn main() void { + const s: S = .{ .x = 456 }; + s.foo(); +} +#expect_stdout="" + +#update=make S runtime again +#file=main.zig +const SomeType = u16; +const S = struct { + x: SomeType, + fn foo(_: S) void {} +}; +pub fn main() void { + const s: S = .{ .x = 456 }; + s.foo(); +} +#expect_stdout="" diff --git a/zig/test/incremental/unreferenced_error b/zig/test/incremental/unreferenced_error new file mode 100644 index 0000000000..e3d67d6ad8 --- /dev/null +++ b/zig/test/incremental/unreferenced_error @@ -0,0 +1,38 @@ +#target=x86_64-linux +#update=initial version +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(a); +} +const a = "Hello, World!\n"; +#expect_stdout="Hello, World!\n" + +#update=introduce compile error +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(a); +} +const a = @compileError("bad a"); +#expect_error=ignored + +#update=remove error reference +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(b); +} +const a = @compileError("bad a"); +const b = "Hi there!\n"; +#expect_stdout="Hi there!\n" + +#update=introduce and remove reference to error +#file=main.zig +const std = @import("std"); +pub fn main() !void { + try std.io.getStdOut().writeAll(a); +} +const a = "Back to a\n"; +const b = @compileError("bad b"); +#expect_stdout="Back to a\n" diff --git a/zig/test/link/elf.zig b/zig/test/link/elf.zig index 71b722a9f9..98253811b2 100644 --- a/zig/test/link/elf.zig +++ b/zig/test/link/elf.zig @@ -1773,25 +1773,41 @@ fn testImportingDataDynamic(b: *Build, opts: Options) *Step { .use_llvm = true, }, .{ .name = "a", - .c_source_bytes = "int foo = 42;", + .c_source_bytes = + \\#include + \\int foo = 42; + \\void printFoo() { fprintf(stderr, "lib foo=%d\n", foo); } + , }); + dso.linkLibC(); const main = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes = + \\const std = @import("std"); \\extern var foo: i32; + \\extern fn printFoo() void; \\pub fn main() void { - \\ @import("std").debug.print("{d}\n", .{foo}); + \\ std.debug.print("exe foo={d}\n", .{foo}); + \\ printFoo(); + \\ foo += 1; + \\ std.debug.print("exe foo={d}\n", .{foo}); + \\ printFoo(); \\} , .strip = true, // TODO temp hack }); main.pie = true; main.linkLibrary(dso); - main.linkLibC(); const run = addRunArtifact(main); - run.expectStdErrEqual("42\n"); + run.expectStdErrEqual( + \\exe foo=42 + \\lib foo=42 + \\exe foo=43 + \\lib foo=43 + \\ + ); test_step.dependOn(&run.step); return test_step; @@ -2957,44 +2973,27 @@ fn testStrip(b: *Build, opts: Options) *Step { fn testThunks(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "thunks", opts); - const src = - \\#include - \\__attribute__((aligned(0x8000000))) int bar() { - \\ return 42; - \\} - \\int foobar(); - \\int foo() { - \\ return bar() - foobar(); - \\} - \\__attribute__((aligned(0x8000000))) int foobar() { - \\ return 42; - \\} - \\int main() { - \\ printf("bar=%d, foo=%d, foobar=%d", bar(), foo(), foobar()); - \\ return foo(); - \\} - ; - - { - const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = src }); - exe.link_function_sections = true; - exe.linkLibC(); - - const run = addRunArtifact(exe); - run.expectStdOutEqual("bar=42, foo=0, foobar=42"); - run.expectExitCode(0); - test_step.dependOn(&run.step); - } - - { - const exe = addExecutable(b, opts, .{ .name = "main2", .c_source_bytes = src }); - exe.linkLibC(); + const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = + \\void foo(); + \\__attribute__((section(".bar"))) void bar() { + \\ return foo(); + \\} + \\__attribute__((section(".foo"))) void foo() { + \\ return bar(); + \\} + \\int main() { + \\ foo(); + \\ bar(); + \\ return 0; + \\} + }); - const run = addRunArtifact(exe); - run.expectStdOutEqual("bar=42, foo=0, foobar=42"); - run.expectExitCode(0); - test_step.dependOn(&run.step); - } + const check = exe.checkObject(); + check.checkInSymtab(); + check.checkContains("foo$thunk"); + check.checkInSymtab(); + check.checkContains("bar$thunk"); + test_step.dependOn(&check.step); return test_step; } diff --git a/zig/test/link/macho.zig b/zig/test/link/macho.zig index 30982e6ba2..730edcf3a9 100644 --- a/zig/test/link/macho.zig +++ b/zig/test/link/macho.zig @@ -2204,25 +2204,28 @@ fn testThunks(b: *Build, opts: Options) *Step { const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = \\#include - \\__attribute__((aligned(0x8000000))) int bar() { - \\ return 42; + \\void bar() { + \\ printf("bar"); \\} - \\int foobar(); - \\int foo() { - \\ return bar() - foobar(); - \\} - \\__attribute__((aligned(0x8000000))) int foobar() { - \\ return 42; + \\void foo() { + \\ fprintf(stdout, "foo"); \\} \\int main() { - \\ printf("bar=%d, foo=%d, foobar=%d", bar(), foo(), foobar()); - \\ return foo(); + \\ foo(); + \\ bar(); + \\ return 0; \\} }); + const check = exe.checkObject(); + check.checkInSymtab(); + check.checkContains("_printf__thunk"); + check.checkInSymtab(); + check.checkContains("_fprintf__thunk"); + test_step.dependOn(&check.step); + const run = addRunArtifact(exe); - run.expectStdOutEqual("bar=42, foo=0, foobar=42"); - run.expectExitCode(0); + run.expectStdOutEqual("foobar"); test_step.dependOn(&run.step); return test_step; diff --git a/zig/test/src/Debugger.zig b/zig/test/src/Debugger.zig new file mode 100644 index 0000000000..718d89dd1c --- /dev/null +++ b/zig/test/src/Debugger.zig @@ -0,0 +1,840 @@ +b: *std.Build, +options: Options, +root_step: *std.Build.Step, + +pub const Options = struct { + test_filters: []const []const u8, + gdb: ?[]const u8, + lldb: ?[]const u8, + optimize_modes: []const std.builtin.OptimizeMode, + skip_single_threaded: bool, + skip_non_native: bool, + skip_libc: bool, +}; + +pub const Target = struct { + resolved: std.Build.ResolvedTarget, + optimize_mode: std.builtin.OptimizeMode = .Debug, + link_libc: ?bool = null, + single_threaded: ?bool = null, + pic: ?bool = null, + test_name_suffix: []const u8, +}; + +pub fn addTestsForTarget(db: *Debugger, target: Target) void { + db.addLldbTest( + "basic", + target, + &.{ + .{ + .path = "basic.zig", + .source = + \\const Basic = struct { + \\ void: void = {}, + \\ bool_false: bool = false, + \\ bool_true: bool = true, + \\ u0_0: u0 = 0, + \\ u1_0: u1 = 0, + \\ u1_1: u1 = 1, + \\ u2_0: u2 = 0, + \\ u2_3: u2 = 3, + \\ u3_0: u3 = 0, + \\ u3_7: u3 = 7, + \\ u4_0: u4 = 0, + \\ u4_15: u4 = 15, + \\ u5_0: u5 = 0, + \\ u5_31: u5 = 31, + \\ u6_0: u6 = 0, + \\ u6_63: u6 = 63, + \\ u7_0: u7 = 0, + \\ u7_127: u7 = 127, + \\ u8_0: u8 = 0, + \\ u8_255: u8 = 255, + \\ u16_0: u16 = 0, + \\ u16_65535: u16 = 65535, + \\ u24_0: u24 = 0, + \\ u24_16777215: u24 = 16777215, + \\ u32_0: u32 = 0, + \\ u32_4294967295: u32 = 4294967295, + \\ i0_0: i0 = 0, + \\ @"i1_-1": i1 = -1, + \\ i1_0: i1 = 0, + \\ @"i2_-2": i2 = -2, + \\ i2_0: i2 = 0, + \\ i2_1: i2 = 1, + \\ @"i3_-4": i3 = -4, + \\ i3_0: i3 = 0, + \\ i3_3: i3 = 3, + \\ @"i4_-8": i4 = -8, + \\ i4_0: i4 = 0, + \\ i4_7: i4 = 7, + \\ @"i5_-16": i5 = -16, + \\ i5_0: i5 = 0, + \\ i5_15: i5 = 15, + \\ @"i6_-32": i6 = -32, + \\ i6_0: i6 = 0, + \\ i6_31: i6 = 31, + \\ @"i7_-64": i7 = -64, + \\ i7_0: i7 = 0, + \\ i7_63: i7 = 63, + \\ @"i8_-128": i8 = -128, + \\ i8_0: i8 = 0, + \\ i8_127: i8 = 127, + \\ @"i16_-32768": i16 = -32768, + \\ i16_0: i16 = 0, + \\ i16_32767: i16 = 32767, + \\ @"i24_-8388608": i24 = -8388608, + \\ i24_0: i24 = 0, + \\ i24_8388607: i24 = 8388607, + \\ @"i32_-2147483648": i32 = -2147483648, + \\ i32_0: i32 = 0, + \\ i32_2147483647: i32 = 2147483647, + \\ @"f16_42.625": f16 = 42.625, + \\ @"f32_-2730.65625": f32 = -2730.65625, + \\ @"f64_357913941.33203125": f64 = 357913941.33203125, + \\ @"f80_-91625968981.3330078125": f80 = -91625968981.3330078125, + \\ @"f128_384307168202282325.333332061767578125": f128 = 384307168202282325.333332061767578125, + \\}; + \\fn testBasic(basic: Basic) void { + \\ _ = basic; + \\} + \\pub fn main() void { + \\ testBasic(.{}); + \\} + \\ + , + }, + }, + \\breakpoint set --file basic.zig --source-pattern-regexp '_ = basic;' + \\process launch + \\frame variable --show-types basic + \\breakpoint delete --force 1 + , + &.{ + \\(lldb) frame variable --show-types basic + \\(root.basic.Basic) basic = { + \\ (void) void = {} + \\ (bool) bool_false = false + \\ (bool) bool_true = true + \\ (u0) u0_0 = 0 + \\ (u1) u1_0 = 0 + \\ (u1) u1_1 = 1 + \\ (u2) u2_0 = 0 + \\ (u2) u2_3 = 3 + \\ (u3) u3_0 = 0 + \\ (u3) u3_7 = 7 + \\ (u4) u4_0 = 0 + \\ (u4) u4_15 = 15 + \\ (u5) u5_0 = 0 + \\ (u5) u5_31 = 31 + \\ (u6) u6_0 = 0 + \\ (u6) u6_63 = 63 + \\ (u7) u7_0 = 0 + \\ (u7) u7_127 = 127 + \\ (u8) u8_0 = 0 + \\ (u8) u8_255 = 255 + \\ (u16) u16_0 = 0 + \\ (u16) u16_65535 = 65535 + \\ (u24) u24_0 = 0 + \\ (u24) u24_16777215 = 16777215 + \\ (u32) u32_0 = 0 + \\ (u32) u32_4294967295 = 4294967295 + \\ (i0) i0_0 = 0 + \\ (i1) i1_-1 = -1 + \\ (i1) i1_0 = 0 + \\ (i2) i2_-2 = -2 + \\ (i2) i2_0 = 0 + \\ (i2) i2_1 = 1 + \\ (i3) i3_-4 = -4 + \\ (i3) i3_0 = 0 + \\ (i3) i3_3 = 3 + \\ (i4) i4_-8 = -8 + \\ (i4) i4_0 = 0 + \\ (i4) i4_7 = 7 + \\ (i5) i5_-16 = -16 + \\ (i5) i5_0 = 0 + \\ (i5) i5_15 = 15 + \\ (i6) i6_-32 = -32 + \\ (i6) i6_0 = 0 + \\ (i6) i6_31 = 31 + \\ (i7) i7_-64 = -64 + \\ (i7) i7_0 = 0 + \\ (i7) i7_63 = 63 + \\ (i8) i8_-128 = -128 + \\ (i8) i8_0 = 0 + \\ (i8) i8_127 = 127 + \\ (i16) i16_-32768 = -32768 + \\ (i16) i16_0 = 0 + \\ (i16) i16_32767 = 32767 + \\ (i24) i24_-8388608 = -8388608 + \\ (i24) i24_0 = 0 + \\ (i24) i24_8388607 = 8388607 + \\ (i32) i32_-2147483648 = -2147483648 + \\ (i32) i32_0 = 0 + \\ (i32) i32_2147483647 = 2147483647 + \\ (f16) f16_42.625 = 42.625 + \\ (f32) f32_-2730.65625 = -2730.65625 + \\ (f64) f64_357913941.33203125 = 357913941.33203125 + \\ (f80) f80_-91625968981.3330078125 = -91625968981.3330078125 + \\ (f128) f128_384307168202282325.333332061767578125 = 384307168202282325.333332061767578125 + \\} + \\(lldb) breakpoint delete --force 1 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + }, + ); + db.addLldbTest( + "pointers", + target, + &.{ + .{ + .path = "pointers.zig", + .source = + \\const Pointers = struct { + \\ var array: [7]u32 = .{ + \\ 3010, + \\ 3014, + \\ 3018, + \\ 3022, + \\ 3026, + \\ 3030, + \\ 3034, + \\ }; + \\ + \\ single: *u32 = @ptrFromInt(0x1010), + \\ single_const: *const u32 = @ptrFromInt(0x1014), + \\ single_volatile: *volatile u32 = @ptrFromInt(0x1018), + \\ single_const_volatile: *const volatile u32 = @ptrFromInt(0x101c), + \\ single_allowzero: *allowzero u32 = @ptrFromInt(0x1020), + \\ single_allowzero_const: *allowzero const u32 = @ptrFromInt(0x1024), + \\ single_allowzero_volatile: *allowzero volatile u32 = @ptrFromInt(0x1028), + \\ single_allowzero_const_volatile: *allowzero const volatile u32 = @ptrFromInt(0x102c), + \\ + \\ many: [*]u32 = @ptrFromInt(0x2010), + \\ many_const: [*]const u32 = @ptrFromInt(0x2014), + \\ many_volatile: [*]volatile u32 = @ptrFromInt(0x2018), + \\ many_const_volatile: [*]const volatile u32 = @ptrFromInt(0x201c), + \\ many_allowzero: [*]allowzero u32 = @ptrFromInt(0x2020), + \\ many_allowzero_const: [*]allowzero const u32 = @ptrFromInt(0x2024), + \\ many_allowzero_volatile: [*]allowzero volatile u32 = @ptrFromInt(0x2028), + \\ many_allowzero_const_volatile: [*]allowzero const volatile u32 = @ptrFromInt(0x202c), + \\ slice: []u32 = array[0..1], + \\ slice_const: []const u32 = array[0..2], + \\ slice_volatile: []volatile u32 = array[0..3], + \\ slice_const_volatile: []const volatile u32 = array[0..4], + \\ slice_allowzero: []allowzero u32 = array[4..4], + \\ slice_allowzero_const: []allowzero const u32 = array[4..5], + \\ slice_allowzero_volatile: []allowzero volatile u32 = array[4..6], + \\ slice_allowzero_const_volatile: []allowzero const volatile u32 = array[4..7], + \\ + \\ c: [*c]u32 = @ptrFromInt(0x4010), + \\ c_const: [*c]const u32 = @ptrFromInt(0x4014), + \\ c_volatile: [*c]volatile u32 = @ptrFromInt(0x4018), + \\ c_const_volatile: [*c]const volatile u32 = @ptrFromInt(0x401c), + \\}; + \\fn testPointers(pointers: Pointers) void { + \\ _ = pointers; + \\} + \\pub fn main() void { + \\ testPointers(.{}); + \\} + \\ + , + }, + }, + \\breakpoint set --file pointers.zig --source-pattern-regexp '_ = pointers;' + \\process launch + \\frame variable --show-types pointers + \\breakpoint delete --force 1 + , + &.{ + \\(lldb) frame variable --show-types pointers + \\(root.pointers.Pointers) pointers = { + \\ (*u32) single = 0x0000000000001010 + \\ (*const u32) single_const = 0x0000000000001014 + \\ (*volatile u32) single_volatile = 0x0000000000001018 + \\ (*const volatile u32) single_const_volatile = 0x000000000000101c + \\ (*allowzero u32) single_allowzero = 0x0000000000001020 + \\ (*allowzero const u32) single_allowzero_const = 0x0000000000001024 + \\ (*allowzero volatile u32) single_allowzero_volatile = 0x0000000000001028 + \\ (*allowzero const volatile u32) single_allowzero_const_volatile = 0x000000000000102c + \\ ([*]u32) many = 0x0000000000002010 + \\ ([*]const u32) many_const = 0x0000000000002014 + \\ ([*]volatile u32) many_volatile = 0x0000000000002018 + \\ ([*]const volatile u32) many_const_volatile = 0x000000000000201c + \\ ([*]allowzero u32) many_allowzero = 0x0000000000002020 + \\ ([*]allowzero const u32) many_allowzero_const = 0x0000000000002024 + \\ ([*]allowzero volatile u32) many_allowzero_volatile = 0x0000000000002028 + \\ ([*]allowzero const volatile u32) many_allowzero_const_volatile = 0x000000000000202c + \\ ([]u32) slice = len=1 { + \\ (u32) [0] = 3010 + \\ } + \\ ([]const u32) slice_const = len=2 { + \\ (u32) [0] = 3010 + \\ (u32) [1] = 3014 + \\ } + \\ ([]volatile u32) slice_volatile = len=3 { + \\ (u32) [0] = 3010 + \\ (u32) [1] = 3014 + \\ (u32) [2] = 3018 + \\ } + \\ ([]const volatile u32) slice_const_volatile = len=4 { + \\ (u32) [0] = 3010 + \\ (u32) [1] = 3014 + \\ (u32) [2] = 3018 + \\ (u32) [3] = 3022 + \\ } + \\ ([]allowzero u32) slice_allowzero = len=0 {} + \\ ([]allowzero const u32) slice_allowzero_const = len=1 { + \\ (u32) [0] = 3026 + \\ } + \\ ([]allowzero volatile u32) slice_allowzero_volatile = len=2 { + \\ (u32) [0] = 3026 + \\ (u32) [1] = 3030 + \\ } + \\ ([]allowzero const volatile u32) slice_allowzero_const_volatile = len=3 { + \\ (u32) [0] = 3026 + \\ (u32) [1] = 3030 + \\ (u32) [2] = 3034 + \\ } + \\ ([*c]u32) c = 0x0000000000004010 + \\ ([*c]const u32) c_const = 0x0000000000004014 + \\ ([*c]volatile u32) c_volatile = 0x0000000000004018 + \\ ([*c]const volatile u32) c_const_volatile = 0x000000000000401c + \\} + \\(lldb) breakpoint delete --force 1 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + }, + ); + db.addLldbTest( + "enums", + target, + &.{ + .{ + .path = "enums.zig", + .source = + \\const Enums = struct { + \\ const Zero = enum(u4) { _ }; + \\ const One = enum { first }; + \\ const Two = enum(i32) { first, second, _ }; + \\ const Three = enum { first, second, third }; + \\ + \\ zero: Zero = @enumFromInt(13), + \\ one: One = .first, + \\ two: Two = @enumFromInt(-1234), + \\ three: Three = .second, + \\}; + \\fn testEnums(enums: Enums) void { + \\ _ = enums; + \\} + \\pub fn main() void { + \\ testEnums(.{}); + \\} + \\ + , + }, + }, + \\breakpoint set --file enums.zig --source-pattern-regexp '_ = enums;' + \\process launch + \\frame variable --show-types enums + \\breakpoint delete --force 1 + , + &.{ + \\(lldb) frame variable --show-types enums + \\(root.enums.Enums) enums = { + \\ (root.enums.Enums.Zero) zero = @enumFromInt(13) + \\ (root.enums.Enums.One) one = .first + \\ (root.enums.Enums.Two) two = @enumFromInt(-1234) + \\ (root.enums.Enums.Three) three = .second + \\} + \\(lldb) breakpoint delete --force 1 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + }, + ); + db.addLldbTest( + "errors", + target, + &.{ + .{ + .path = "errors.zig", + .source = + \\const Errors = struct { + \\ one: error{One} = error.One, + \\ two: error{One,Two} = error.Two, + \\ three: error{One,Two,Three} = error.Three, + \\ any: anyerror = error.Any, + \\ any_void: anyerror!void = error.NotVoid, + \\ any_u32: error{One}!u32 = 42, + \\}; + \\fn testErrors(errors: Errors) void { + \\ _ = errors; + \\} + \\pub fn main() void { + \\ testErrors(.{}); + \\} + \\ + , + }, + }, + \\breakpoint set --file errors.zig --source-pattern-regexp '_ = errors;' + \\process launch + \\frame variable --show-types errors + \\breakpoint delete --force 1 + , + &.{ + \\(lldb) frame variable --show-types errors + \\(root.errors.Errors) errors = { + \\ (error{One}) one = error.One + \\ (error{One,Two}) two = error.Two + \\ (error{One,Two,Three}) three = error.Three + \\ (anyerror) any = error.Any + \\ (anyerror!void) any_void = { + \\ (anyerror) error = error.NotVoid + \\ } + \\ (error{One}!u32) any_u32 = { + \\ (u32) value = 42 + \\ } + \\} + \\(lldb) breakpoint delete --force 1 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + }, + ); + db.addLldbTest( + "optionals", + target, + &.{ + .{ + .path = "optionals.zig", + .source = + \\pub fn main() void { + \\ { + \\ var null_u32: ?u32 = null; + \\ var maybe_u32: ?u32 = null; + \\ var nonnull_u32: ?u32 = 456; + \\ maybe_u32 = 123; + \\ _ = .{ &null_u32, &nonnull_u32 }; + \\ } + \\} + \\ + , + }, + }, + \\breakpoint set --file optionals.zig --source-pattern-regexp 'maybe_u32 = 123;' + \\process launch + \\frame variable null_u32 maybe_u32 nonnull_u32 + \\breakpoint delete --force 1 + \\ + \\breakpoint set --file optionals.zig --source-pattern-regexp '_ = .{ &null_u32, &nonnull_u32 };' + \\process continue + \\frame variable --show-types null_u32 maybe_u32 nonnull_u32 + \\breakpoint delete --force 2 + , + &.{ + \\(lldb) frame variable null_u32 maybe_u32 nonnull_u32 + \\(?u32) null_u32 = null + \\(?u32) maybe_u32 = null + \\(?u32) nonnull_u32 = (nonnull_u32.? = 456) + \\(lldb) breakpoint delete --force 1 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + , + \\(lldb) frame variable --show-types null_u32 maybe_u32 nonnull_u32 + \\(?u32) null_u32 = null + \\(?u32) maybe_u32 = { + \\ (u32) maybe_u32.? = 123 + \\} + \\(?u32) nonnull_u32 = { + \\ (u32) nonnull_u32.? = 456 + \\} + \\(lldb) breakpoint delete --force 2 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + }, + ); + db.addLldbTest( + "unions", + target, + &.{ + .{ + .path = "unions.zig", + .source = + \\const Unions = struct { + \\ const Enum = enum { first, second, third }; + \\ const Untagged = extern union { + \\ u32: u32, + \\ i32: i32, + \\ f32: f32, + \\ }; + \\ const SafetyTagged = union { + \\ void: void, + \\ en: Enum, + \\ eu: error{Error}!Enum, + \\ }; + \\ const Tagged = union(enum) { + \\ void: void, + \\ en: Enum, + \\ eu: error{Error}!Enum, + \\ }; + \\ + \\ untagged: Untagged = .{ .f32 = -1.5 }, + \\ safety_tagged: SafetyTagged = .{ .en = .second }, + \\ tagged: Tagged = .{ .eu = error.Error }, + \\}; + \\fn testUnions(unions: Unions) void { + \\ _ = unions; + \\} + \\pub fn main() void { + \\ testUnions(.{}); + \\} + \\ + , + }, + }, + \\breakpoint set --file unions.zig --source-pattern-regexp '_ = unions;' + \\process launch + \\frame variable --show-types unions + \\breakpoint delete --force 1 + , + &.{ + \\(lldb) frame variable --show-types unions + \\(root.unions.Unions) unions = { + \\ (root.unions.Unions.Untagged) untagged = { + \\ (u32) u32 = 3217031168 + \\ (i32) i32 = -1077936128 + \\ (f32) f32 = -1.5 + \\ } + \\ (root.unions.Unions.SafetyTagged) safety_tagged = { + \\ (root.unions.Unions.Enum) en = .second + \\ } + \\ (root.unions.Unions.Tagged) tagged = { + \\ (error{Error}!root.unions.Unions.Enum) eu = { + \\ (error{Error}) error = error.Error + \\ } + \\ } + \\} + \\(lldb) breakpoint delete --force 1 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + }, + ); + db.addLldbTest( + "storage", + target, + &.{ + .{ + .path = "storage.zig", + .source = + \\const global_const: u64 = 0x19e50dc8d6002077; + \\var global_var: u64 = 0xcc423cec08622e32; + \\threadlocal var global_threadlocal1: u64 = 0xb4d643528c042121; + \\threadlocal var global_threadlocal2: u64 = 0x43faea1cf5ad7a22; + \\fn testStorage( + \\ param1: u64, + \\ param2: u64, + \\ param3: u64, + \\ param4: u64, + \\ param5: u64, + \\ param6: u64, + \\ param7: u64, + \\ param8: u64, + \\) callconv(.C) void { + \\ const local_comptime_val: u64 = global_const *% global_const; + \\ const local_comptime_ptr: struct { u64 } = .{ local_comptime_val *% local_comptime_val }; + \\ const local_const: u64 = global_var ^ global_threadlocal1 ^ global_threadlocal2 ^ + \\ param1 ^ param2 ^ param3 ^ param4 ^ param5 ^ param6 ^ param7 ^ param8; + \\ var local_var: u64 = local_comptime_ptr[0] ^ local_const; + \\ local_var = local_var; + \\} + \\pub fn main() void { + \\ testStorage( + \\ 0x6a607e08125c7e00, + \\ 0x98944cb2a45a8b51, + \\ 0xa320cf10601ee6fb, + \\ 0x691ed3535bad3274, + \\ 0x63690e6867a5799f, + \\ 0x8e163f0ec76067f2, + \\ 0xf9a252c455fb4c06, + \\ 0xc88533722601e481, + \\ ); + \\} + \\ + , + }, + }, + \\breakpoint set --file storage.zig --source-pattern-regexp 'local_var = local_var;' + \\process launch + \\target variable --show-types --format hex global_const global_var global_threadlocal1 global_threadlocal2 + \\frame variable --show-types --format hex param1 param2 param3 param4 param5 param6 param7 param8 local_comptime_val local_comptime_ptr.0 local_const local_var + \\breakpoint delete --force 1 + , + &.{ + \\(lldb) target variable --show-types --format hex global_const global_var global_threadlocal1 global_threadlocal2 + \\(u64) global_const = 0x19e50dc8d6002077 + \\(u64) global_var = 0xcc423cec08622e32 + \\(u64) global_threadlocal1 = 0xb4d643528c042121 + \\(u64) global_threadlocal2 = 0x43faea1cf5ad7a22 + \\(lldb) frame variable --show-types --format hex param1 param2 param3 param4 param5 param6 param7 param8 local_comptime_val local_comptime_ptr.0 local_const local_var + \\(u64) param1 = 0x6a607e08125c7e00 + \\(u64) param2 = 0x98944cb2a45a8b51 + \\(u64) param3 = 0xa320cf10601ee6fb + \\(u64) param4 = 0x691ed3535bad3274 + \\(u64) param5 = 0x63690e6867a5799f + \\(u64) param6 = 0x8e163f0ec76067f2 + \\(u64) param7 = 0xf9a252c455fb4c06 + \\(u64) param8 = 0xc88533722601e481 + \\(u64) local_comptime_val = 0x69490636f81df751 + \\(u64) local_comptime_ptr.0 = 0x82e834dae74767a1 + \\(u64) local_const = 0xdffceb8b2f41e205 + \\(u64) local_var = 0x5d14df51c80685a4 + \\(lldb) breakpoint delete --force 1 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + }, + ); + db.addLldbTest( + "inline_call", + target, + &.{ + .{ + .path = "main.zig", + .source = + \\const module = @import("module"); + \\pub fn main() void { + \\ fa(12); + \\ fb(34); + \\ module.fc(56); + \\ module.fd(78); + \\} + \\fn fa(pa: u32) void { + \\ const la = ~pa; + \\ _ = la; + \\} + \\inline fn fb(pb: u32) void { + \\ const lb = ~pb; + \\ _ = lb; + \\} + \\ + , + }, + .{ + .import = "module", + .path = "module.zig", + .source = + \\pub fn fc(pc: u32) void { + \\ const lc = ~pc; + \\ _ = lc; + \\} + \\pub inline fn fd(pd: u32) void { + \\ const ld = ~pd; + \\ _ = ld; + \\} + \\ + , + }, + }, + \\settings set frame-format 'frame #${frame.index}:{ ${module.file.basename}{\`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${line.file.basename}:${line.number}{:${line.column}}}{${function.is-optimized} [opt]}{${frame.is-artificial} [artificial]}\n' + \\ + \\breakpoint set --file main.zig --source-pattern-regexp '_ = la;' + \\process launch + \\frame variable pa la + \\thread backtrace --count 2 + \\breakpoint delete --force 1 + \\ + \\breakpoint set --file main.zig --source-pattern-regexp '_ = lb;' + \\process continue + \\frame variable pb lb + \\thread backtrace --count 2 + \\breakpoint delete --force 2 + \\ + \\breakpoint set --file module.zig --source-pattern-regexp '_ = lc;' + \\process continue + \\frame variable pc lc + \\thread backtrace --count 2 + \\breakpoint delete --force 3 + \\ + \\breakpoint set --file module.zig --line 7 + \\process continue + \\frame variable pd ld + \\thread backtrace --count 2 + \\breakpoint delete --force 4 + , + &.{ + \\(lldb) frame variable pa la + \\(u32) pa = 12 + \\(u32) la = 4294967283 + \\(lldb) thread backtrace --count 2 + \\* thread #1, name = 'inline_call', stop reason = breakpoint 1.1 + \\ * frame #0: inline_call`main.fa(pa=12) at main.zig:10:5 + \\ frame #1: inline_call`main.main at main.zig:3:7 + \\(lldb) breakpoint delete --force 1 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + , + \\(lldb) frame variable pb lb + \\(u32) pb = 34 + \\(u32) lb = 4294967261 + \\(lldb) thread backtrace --count 2 + \\* thread #1, name = 'inline_call', stop reason = breakpoint 2.1 + \\ * frame #0: inline_call`main.main [inlined] fb(pb=34) at main.zig:14:5 + \\ frame #1: inline_call`main.main at main.zig:4:7 + \\(lldb) breakpoint delete --force 2 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + , + \\(lldb) frame variable pc lc + \\(u32) pc = 56 + \\(u32) lc = 4294967239 + \\(lldb) thread backtrace --count 2 + \\* thread #1, name = 'inline_call', stop reason = breakpoint 3.1 + \\ * frame #0: inline_call`module.fc(pc=56) at module.zig:3:5 + \\ frame #1: inline_call`main.main at main.zig:5:14 + \\(lldb) breakpoint delete --force 3 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + , + \\(lldb) frame variable pd ld + \\(u32) pd = 78 + \\(u32) ld = 4294967217 + \\(lldb) thread backtrace --count 2 + \\* thread #1, name = 'inline_call', stop reason = breakpoint 4.1 + \\ * frame #0: inline_call`main.main [inlined] fd(pd=78) at module.zig:7:5 + \\ frame #1: inline_call`main.main at main.zig:6:14 + \\(lldb) breakpoint delete --force 4 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + }, + ); + db.addLldbTest( + "link_object", + target, + &.{ + .{ + .path = "main.zig", + .source = + \\extern fn fabsf(f32) f32; + \\pub fn main() void { + \\ var x: f32 = -1234.5; + \\ x = fabsf(x); + \\ _ = &x; + \\} + , + }, + }, + \\breakpoint set --file main.zig --source-pattern-regexp 'x = fabsf\(x\);' + \\process launch + \\frame variable x + \\breakpoint delete --force 1 + \\ + \\breakpoint set --file main.zig --source-pattern-regexp '_ = &x;' + \\process continue + \\frame variable x + \\breakpoint delete --force 2 + , + &.{ + \\(lldb) frame variable x + \\(f32) x = -1234.5 + \\(lldb) breakpoint delete --force 1 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + , + \\(lldb) frame variable x + \\(f32) x = 1234.5 + \\(lldb) breakpoint delete --force 2 + \\1 breakpoints deleted; 0 breakpoint locations disabled. + }, + ); +} + +const File = struct { import: ?[]const u8 = null, path: []const u8, source: []const u8 }; + +fn addGdbTest( + db: *Debugger, + name: []const u8, + target: Target, + files: []const File, + commands: []const u8, + expected_output: []const []const u8, +) void { + db.addTest( + name, + target, + files, + &.{ + db.options.gdb orelse return, + "--batch", + "--command", + }, + commands, + &.{ + "--args", + }, + expected_output, + ); +} + +fn addLldbTest( + db: *Debugger, + name: []const u8, + target: Target, + files: []const File, + commands: []const u8, + expected_output: []const []const u8, +) void { + db.addTest( + name, + target, + files, + &.{ + db.options.lldb orelse return, + "--batch", + "--source", + }, + commands, + &.{ + "--", + }, + expected_output, + ); +} + +/// After a failure while running a script, the debugger starts accepting commands from stdin, and +/// because it is empty, the debugger exits normally with status 0. Choose a non-zero status to +/// return from the debugger script instead to detect it running to completion and indicate success. +const success = 99; + +fn addTest( + db: *Debugger, + name: []const u8, + target: Target, + files: []const File, + db_argv1: []const []const u8, + commands: []const u8, + db_argv2: []const []const u8, + expected_output: []const []const u8, +) void { + for (db.options.test_filters) |test_filter| { + if (std.mem.indexOf(u8, name, test_filter)) |_| return; + } + const files_wf = db.b.addWriteFiles(); + const exe = db.b.addExecutable(.{ + .name = name, + .target = target.resolved, + .root_source_file = files_wf.add(files[0].path, files[0].source), + .optimize = target.optimize_mode, + .link_libc = target.link_libc, + .single_threaded = target.single_threaded, + .pic = target.pic, + .strip = false, + .use_llvm = false, + .use_lld = false, + }); + for (files[1..]) |file| { + const path = files_wf.add(file.path, file.source); + if (file.import) |import| exe.root_module.addImport(import, db.b.createModule(.{ + .root_source_file = path, + })); + } + const commands_wf = db.b.addWriteFiles(); + const run = std.Build.Step.Run.create(db.b, db.b.fmt("run {s} {s}", .{ name, target.test_name_suffix })); + run.addArgs(db_argv1); + run.addFileArg(commands_wf.add(db.b.fmt("{s}.cmd", .{name}), db.b.fmt("{s}\n\nquit {d}\n", .{ commands, success }))); + run.addArgs(db_argv2); + run.addArtifactArg(exe); + for (expected_output) |expected| run.addCheck(.{ .expect_stdout_match = db.b.fmt("{s}\n", .{expected}) }); + run.addCheck(.{ .expect_term = .{ .Exited = success } }); + run.setStdIn(.{ .bytes = "" }); + db.root_step.dependOn(&run.step); +} + +const Debugger = @This(); +const std = @import("std"); diff --git a/zig/test/standalone/build.zig.zon b/zig/test/standalone/build.zig.zon index 8e4d727642..30ec07823b 100644 --- a/zig/test/standalone/build.zig.zon +++ b/zig/test/standalone/build.zig.zon @@ -51,14 +51,15 @@ .install_raw_hex = .{ .path = "install_raw_hex", }, - // https://github.com/ziglang/zig/issues/17484 - //.emit_asm_and_bin = .{ - // .path = "emit_asm_and_bin", - //}, - // https://github.com/ziglang/zig/issues/17484 - //.issue_12588 = .{ - // .path = "issue_12588", - //}, + .emit_asm_and_bin = .{ + .path = "emit_asm_and_bin", + }, + .emit_llvm_no_bin = .{ + .path = "emit_llvm_no_bin", + }, + .emit_asm_no_bin = .{ + .path = "emit_asm_no_bin", + }, .child_process = .{ .path = "child_process", }, diff --git a/zig/test/standalone/emit_asm_no_bin/build.zig b/zig/test/standalone/emit_asm_no_bin/build.zig new file mode 100644 index 0000000000..f2f2391bc1 --- /dev/null +++ b/zig/test/standalone/emit_asm_no_bin/build.zig @@ -0,0 +1,19 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + + const obj = b.addObject(.{ + .name = "main", + .root_source_file = b.path("main.zig"), + .optimize = optimize, + .target = b.graph.host, + }); + _ = obj.getEmittedAsm(); + b.default_step.dependOn(&obj.step); + + test_step.dependOn(&obj.step); +} diff --git a/zig/test/standalone/emit_asm_no_bin/main.zig b/zig/test/standalone/emit_asm_no_bin/main.zig new file mode 100644 index 0000000000..902b554db0 --- /dev/null +++ b/zig/test/standalone/emit_asm_no_bin/main.zig @@ -0,0 +1 @@ +pub fn main() void {} diff --git a/zig/test/standalone/issue_12588/build.zig b/zig/test/standalone/emit_llvm_no_bin/build.zig similarity index 100% rename from zig/test/standalone/issue_12588/build.zig rename to zig/test/standalone/emit_llvm_no_bin/build.zig diff --git a/zig/test/standalone/issue_12588/main.zig b/zig/test/standalone/emit_llvm_no_bin/main.zig similarity index 100% rename from zig/test/standalone/issue_12588/main.zig rename to zig/test/standalone/emit_llvm_no_bin/main.zig diff --git a/zig/test/tests.zig b/zig/test/tests.zig index 1b3ef6ad74..73934950db 100644 --- a/zig/test/tests.zig +++ b/zig/test/tests.zig @@ -17,6 +17,7 @@ pub const TranslateCContext = @import("src/TranslateC.zig"); pub const RunTranslatedCContext = @import("src/RunTranslatedC.zig"); pub const CompareOutputContext = @import("src/CompareOutput.zig"); pub const StackTracesContext = @import("src/StackTrace.zig"); +pub const DebuggerContext = @import("src/Debugger.zig"); const TestTarget = struct { target: std.Target.Query = .{}, @@ -27,6 +28,12 @@ const TestTarget = struct { use_lld: ?bool = null, pic: ?bool = null, strip: ?bool = null, + + // This is intended for targets that are known to be slow to compile. These are acceptable to + // run in CI, but should not be run on developer machines by default. As an example, at the time + // of writing, this includes LLVM's MIPS backend which takes upwards of 20 minutes longer to + // compile tests than other backends. + slow_backend: bool = false, }; const test_targets = blk: { @@ -310,8 +317,8 @@ const test_targets = blk: { .os_tag = .linux, .abi = .none, }, + .slow_backend = true, }, - .{ .target = .{ .cpu_arch = .mips, @@ -319,17 +326,17 @@ const test_targets = blk: { .abi = .musl, }, .link_libc = true, + .slow_backend = true, + }, + .{ + .target = .{ + .cpu_arch = .mips, + .os_tag = .linux, + .abi = .gnueabihf, + }, + .link_libc = true, + .slow_backend = true, }, - - // https://github.com/ziglang/zig/issues/4927 - //.{ - // .target = .{ - // .cpu_arch = .mips, - // .os_tag = .linux, - // .abi = .gnueabihf, - // }, - // .link_libc = true, - //}, .{ .target = .{ @@ -337,8 +344,8 @@ const test_targets = blk: { .os_tag = .linux, .abi = .none, }, + .slow_backend = true, }, - .{ .target = .{ .cpu_arch = .mipsel, @@ -346,17 +353,65 @@ const test_targets = blk: { .abi = .musl, }, .link_libc = true, + .slow_backend = true, + }, + .{ + .target = .{ + .cpu_arch = .mipsel, + .os_tag = .linux, + .abi = .gnueabihf, + }, + .link_libc = true, + .slow_backend = true, }, - // https://github.com/ziglang/zig/issues/4927 - //.{ - // .target = .{ - // .cpu_arch = .mipsel, - // .os_tag = .linux, - // .abi = .gnueabihf, - // }, - // .link_libc = true, - //}, + .{ + .target = .{ + .cpu_arch = .mips64, + .os_tag = .linux, + .abi = .none, + }, + }, + .{ + .target = .{ + .cpu_arch = .mips64, + .os_tag = .linux, + .abi = .musl, + }, + .link_libc = true, + }, + .{ + .target = .{ + .cpu_arch = .mips64, + .os_tag = .linux, + .abi = .gnuabi64, + }, + .link_libc = true, + }, + + .{ + .target = .{ + .cpu_arch = .mips64el, + .os_tag = .linux, + .abi = .none, + }, + }, + .{ + .target = .{ + .cpu_arch = .mips64el, + .os_tag = .linux, + .abi = .musl, + }, + .link_libc = true, + }, + .{ + .target = .{ + .cpu_arch = .mips64el, + .os_tag = .linux, + .abi = .gnuabi64, + }, + .link_libc = true, + }, .{ .target = .{ @@ -407,7 +462,8 @@ const test_targets = blk: { .link_libc = true, }, - // Disabled until LLVM fixes their O(N^2) codegen. + // Disabled until LLVM fixes their O(N^2) codegen. Note that this is so bad that we don't + // even want to include this in CI with `slow_backend`. // https://github.com/ziglang/zig/issues/18872 //.{ // .target = .{ @@ -418,7 +474,8 @@ const test_targets = blk: { // .use_llvm = true, //}, - // Disabled until LLVM fixes their O(N^2) codegen. + // Disabled until LLVM fixes their O(N^2) codegen. Note that this is so bad that we don't + // even want to include this in CI with `slow_backend`. // https://github.com/ziglang/zig/issues/18872 //.{ // .target = .{ @@ -971,6 +1028,8 @@ pub fn addRunTranslatedCTests( const ModuleTestOptions = struct { test_filters: []const []const u8, + test_target_filters: []const []const u8, + test_slow_targets: bool, root_src: []const u8, name: []const u8, desc: []const u8, @@ -987,11 +1046,20 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { const step = b.step(b.fmt("test-{s}", .{options.name}), options.desc); for (test_targets) |test_target| { + if (!options.test_slow_targets and test_target.slow_backend) continue; + if (options.skip_non_native and !test_target.target.isNative()) continue; const resolved_target = b.resolveTargetQuery(test_target.target); const target = resolved_target.result; + const triple_txt = target.zigTriple(b.allocator) catch @panic("OOM"); + + if (options.test_target_filters.len > 0) { + for (options.test_target_filters) |filter| { + if (std.mem.indexOf(u8, triple_txt, filter) != null) break; + } else continue; + } if (options.skip_libc and test_target.link_libc == true) continue; @@ -1040,7 +1108,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { if (!want_this_mode) continue; const libc_suffix = if (test_target.link_libc == true) "-libc" else ""; - const triple_txt = target.zigTriple(b.allocator) catch @panic("OOM"); const model_txt = target.cpu.model.name; // wasm32-wasi builds need more RAM, idk why @@ -1265,3 +1332,36 @@ pub fn addCases( test_filters, ); } + +pub fn addDebuggerTests(b: *std.Build, options: DebuggerContext.Options) ?*Step { + const step = b.step("test-debugger", "Run the debugger tests"); + if (options.gdb == null and options.lldb == null) { + step.dependOn(&b.addFail("test-debugger requires -Dgdb and/or -Dlldb").step); + return null; + } + + var context: DebuggerContext = .{ + .b = b, + .options = options, + .root_step = step, + }; + context.addTestsForTarget(.{ + .resolved = b.resolveTargetQuery(.{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, + }), + .pic = false, + .test_name_suffix = "x86_64-linux", + }); + context.addTestsForTarget(.{ + .resolved = b.resolveTargetQuery(.{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, + }), + .pic = true, + .test_name_suffix = "x86_64-linux-pic", + }); + return step; +} diff --git a/zig/tools/incr-check.zig b/zig/tools/incr-check.zig index 56858b88a3..a5c0afc62a 100644 --- a/zig/tools/incr-check.zig +++ b/zig/tools/incr-check.zig @@ -1,15 +1,57 @@ const std = @import("std"); const fatal = std.process.fatal; const Allocator = std.mem.Allocator; +const Cache = std.Build.Cache; + +const usage = "usage: incr-check [--zig-lib-dir lib] [--debug-zcu] [--emit none|bin|c] [--zig-cc-binary /path/to/zig]"; + +const EmitMode = enum { + none, + bin, + c, +}; pub fn main() !void { var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena_instance.deinit(); const arena = arena_instance.allocator(); - const args = try std.process.argsAlloc(arena); - const zig_exe = args[1]; - const input_file_name = args[2]; + var opt_zig_exe: ?[]const u8 = null; + var opt_input_file_name: ?[]const u8 = null; + var opt_lib_dir: ?[]const u8 = null; + var opt_cc_zig: ?[]const u8 = null; + var emit: EmitMode = .bin; + var debug_zcu = false; + + var arg_it = try std.process.argsWithAllocator(arena); + _ = arg_it.skip(); + while (arg_it.next()) |arg| { + if (arg.len > 0 and arg[0] == '-') { + if (std.mem.eql(u8, arg, "--emit")) { + const emit_str = arg_it.next() orelse fatal("expected arg after '--emit'\n{s}", .{usage}); + emit = std.meta.stringToEnum(EmitMode, emit_str) orelse + fatal("invalid emit mode '{s}'\n{s}", .{ emit_str, usage }); + } else if (std.mem.eql(u8, arg, "--zig-lib-dir")) { + opt_lib_dir = arg_it.next() orelse fatal("expected arg after '--zig-lib-dir'\n{s}", .{usage}); + } else if (std.mem.eql(u8, arg, "--debug-zcu")) { + debug_zcu = true; + } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) { + opt_cc_zig = arg_it.next() orelse fatal("expect arg after '--zig-cc-binary'\n{s}", .{usage}); + } else { + fatal("unknown option '{s}'\n{s}", .{ arg, usage }); + } + continue; + } + if (opt_zig_exe == null) { + opt_zig_exe = arg; + } else if (opt_input_file_name == null) { + opt_input_file_name = arg; + } else { + fatal("unknown argument '{s}'\n{s}", .{ arg, usage }); + } + } + const zig_exe = opt_zig_exe orelse fatal("missing path to zig\n{s}", .{usage}); + const input_file_name = opt_input_file_name orelse fatal("missing input file\n{s}", .{usage}); const input_file_bytes = try std.fs.cwd().readFileAlloc(arena, input_file_name, std.math.maxInt(u32)); const case = try Case.parse(arena, input_file_bytes); @@ -24,13 +66,18 @@ pub fn main() !void { const child_prog_node = prog_node.start("zig build-exe", 0); defer child_prog_node.end(); - var child = std.process.Child.init(&.{ - // Convert incr-check-relative path to subprocess-relative path. - try std.fs.path.relative(arena, tmp_dir_path, zig_exe), + // Convert paths to be relative to the cwd of the subprocess. + const resolved_zig_exe = try std.fs.path.relative(arena, tmp_dir_path, zig_exe); + const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir| + try std.fs.path.relative(arena, tmp_dir_path, lib_dir) + else + null; + + var child_args: std.ArrayListUnmanaged([]const u8) = .{}; + try child_args.appendSlice(arena, &.{ + resolved_zig_exe, "build-exe", case.root_source_file, - "-fno-llvm", - "-fno-lld", "-fincremental", "-target", case.target_query, @@ -39,8 +86,20 @@ pub fn main() !void { "--global-cache-dir", ".global_cache", "--listen=-", - }, arena); + }); + if (opt_resolved_lib_dir) |resolved_lib_dir| { + try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir }); + } + switch (emit) { + .bin => try child_args.appendSlice(arena, &.{ "-fno-llvm", "-fno-lld" }), + .none => try child_args.append(arena, "-fno-emit-bin"), + .c => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }), + } + if (debug_zcu) { + try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" }); + } + var child = std.process.Child.init(child_args.items, arena); child.stdin_behavior = .Pipe; child.stdout_behavior = .Pipe; child.stderr_behavior = .Pipe; @@ -48,12 +107,33 @@ pub fn main() !void { child.cwd_dir = tmp_dir; child.cwd = tmp_dir_path; + var cc_child_args: std.ArrayListUnmanaged([]const u8) = .{}; + if (emit == .c) { + const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe| + try std.fs.path.relative(arena, tmp_dir_path, cc_zig_exe) + else + resolved_zig_exe; + + try cc_child_args.appendSlice(arena, &.{ + resolved_cc_zig_exe, + "cc", + "-target", + case.target_query, + "-I", + opt_resolved_lib_dir orelse fatal("'--zig-lib-dir' required when using '--emit c'", .{}), + "-o", + }); + } + var eval: Eval = .{ .arena = arena, .case = case, .tmp_dir = tmp_dir, .tmp_dir_path = tmp_dir_path, .child = &child, + .allow_stderr = debug_zcu, + .emit = emit, + .cc_child_args = &cc_child_args, }; try child.spawn(); @@ -65,9 +145,16 @@ pub fn main() !void { defer poller.deinit(); for (case.updates) |update| { + var update_node = prog_node.start(update.name, 0); + defer update_node.end(); + + if (debug_zcu) { + std.log.info("=== START UPDATE '{s}' ===", .{update.name}); + } + eval.write(update); try eval.requestUpdate(); - try eval.check(&poller, update); + try eval.check(&poller, update, update_node); } try eval.end(&poller); @@ -81,6 +168,11 @@ const Eval = struct { tmp_dir: std.fs.Dir, tmp_dir_path: []const u8, child: *std.process.Child, + allow_stderr: bool, + emit: EmitMode, + /// When `emit == .c`, this contains the first few arguments to `zig cc` to build the generated binary. + /// The arguments `out.c in.c` must be appended before spawning the subprocess. + cc_child_args: *std.ArrayListUnmanaged([]const u8), const StreamEnum = enum { stdout, stderr }; const Poller = std.io.Poller(StreamEnum); @@ -102,7 +194,7 @@ const Eval = struct { } } - fn check(eval: *Eval, poller: *Poller, update: Case.Update) !void { + fn check(eval: *Eval, poller: *Poller, update: Case.Update, prog_node: std.Progress.Node) !void { const arena = eval.arena; const Header = std.zig.Server.Message.Header; const stdout = poller.fifo(.stdout); @@ -136,23 +228,58 @@ const Eval = struct { }; if (stderr.readableLength() > 0) { const stderr_data = try stderr.toOwnedSlice(); - fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data}); + if (eval.allow_stderr) { + std.log.info("error_bundle included stderr:\n{s}", .{stderr_data}); + } else { + fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data}); + } + } + if (result_error_bundle.errorMessageCount() != 0) { + try eval.checkErrorOutcome(update, result_error_bundle); } - try eval.checkErrorOutcome(update, result_error_bundle); // This message indicates the end of the update. stdout.discard(body.len); return; }, - .emit_bin_path => { - const EbpHdr = std.zig.Server.Message.EmitBinPath; + .emit_digest => { + const EbpHdr = std.zig.Server.Message.EmitDigest; const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body)); _ = ebp_hdr; - const result_binary = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); if (stderr.readableLength() > 0) { const stderr_data = try stderr.toOwnedSlice(); - fatal("emit_bin_path included unexpected stderr:\n{s}", .{stderr_data}); + if (eval.allow_stderr) { + std.log.info("emit_digest included stderr:\n{s}", .{stderr_data}); + } else { + fatal("emit_digest included unexpected stderr:\n{s}", .{stderr_data}); + } + } + + if (eval.emit == .none) { + try eval.checkSuccessOutcome(update, null, prog_node); + // This message indicates the end of the update. + stdout.discard(body.len); + return; } - try eval.checkSuccessOutcome(update, result_binary); + + const digest = body[@sizeOf(EbpHdr)..][0..Cache.bin_digest_len]; + const result_dir = ".local-cache" ++ std.fs.path.sep_str ++ "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*); + + const name = std.fs.path.stem(std.fs.path.basename(eval.case.root_source_file)); + const bin_name = try std.zig.binNameAlloc(arena, .{ + .root_name = name, + .target = try std.zig.system.resolveTargetQuery(try std.Build.parseTargetQuery(.{ + .arch_os_abi = eval.case.target_query, + .object_format = switch (eval.emit) { + .none => unreachable, + .bin => null, + .c => "c", + }, + })), + .output_mode = .Exe, + }); + const bin_path = try std.fs.path.join(arena, &.{ result_dir, bin_name }); + + try eval.checkSuccessOutcome(update, bin_path, prog_node); // This message indicates the end of the update. stdout.discard(body.len); return; @@ -166,7 +293,11 @@ const Eval = struct { if (stderr.readableLength() > 0) { const stderr_data = try stderr.toOwnedSlice(); - fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data }); + if (eval.allow_stderr) { + std.log.info("update '{s}' included stderr:\n{s}", .{ update.name, stderr_data }); + } else { + fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data }); + } } waitChild(eval.child); @@ -191,12 +322,28 @@ const Eval = struct { } } - fn checkSuccessOutcome(eval: *Eval, update: Case.Update, binary_path: []const u8) !void { + fn checkSuccessOutcome(eval: *Eval, update: Case.Update, opt_emitted_path: ?[]const u8, prog_node: std.Progress.Node) !void { switch (update.outcome) { .unknown => return, .compile_errors => fatal("expected compile errors but compilation incorrectly succeeded", .{}), .stdout, .exit_code => {}, } + const emitted_path = opt_emitted_path orelse { + std.debug.assert(eval.emit == .none); + return; + }; + + const binary_path = switch (eval.emit) { + .none => unreachable, + .bin => emitted_path, + .c => bin: { + const rand_int = std.crypto.random.int(u64); + const out_bin_name = "./out_" ++ std.fmt.hex(rand_int); + try eval.buildCOutput(update, emitted_path, out_bin_name, prog_node); + break :bin out_bin_name; + }, + }; + const result = std.process.Child.run(.{ .allocator = eval.arena, .argv = &.{binary_path}, @@ -266,6 +413,50 @@ const Eval = struct { fatal("unexpected stderr:\n{s}", .{stderr_data}); } } + + fn buildCOutput(eval: *Eval, update: Case.Update, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void { + std.debug.assert(eval.cc_child_args.items.len > 0); + + const child_prog_node = prog_node.start("build cbe output", 0); + defer child_prog_node.end(); + + try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path }); + defer eval.cc_child_args.items.len -= 2; + + const result = std.process.Child.run(.{ + .allocator = eval.arena, + .argv = eval.cc_child_args.items, + .cwd_dir = eval.tmp_dir, + .cwd = eval.tmp_dir_path, + .progress_node = child_prog_node, + }) catch |err| { + fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{ + update.name, c_path, @errorName(err), + }); + }; + switch (result.term) { + .Exited => |code| if (code != 0) { + if (result.stderr.len != 0) { + std.log.err("update '{s}': zig cc stderr:\n{s}", .{ + update.name, result.stderr, + }); + } + fatal("update '{s}': zig cc for '{s}' failed with code {d}", .{ + update.name, c_path, code, + }); + }, + .Signal, .Stopped, .Unknown => { + if (result.stderr.len != 0) { + std.log.err("update '{s}': zig cc stderr:\n{s}", .{ + update.name, result.stderr, + }); + } + fatal("update '{s}': zig cc for '{s}' terminated unexpectedly", .{ + update.name, c_path, + }); + }, + } + } }; const Case = struct { @@ -357,6 +548,11 @@ const Case = struct { fatal("line {d}: bad string literal: {s}", .{ line_n, @errorName(err) }); }, }; + } else if (std.mem.eql(u8, key, "expect_error")) { + if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n}); + const last_update = &updates.items[updates.items.len - 1]; + if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n}); + last_update.outcome = .{ .compile_errors = &.{} }; } else { fatal("line {d}: unrecognized key '{s}'", .{ line_n, key }); }