From 91dba79c48f0a309e014062f2fe311a5c46b1084 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 26 Aug 2022 21:01:05 +0200 Subject: [PATCH 01/40] wasm: fix abi size of c_longdouble According to https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md the size of c's long double is 16 bytes for Wasm, rather than 8 bytes which was the value previously in the compiler. This ensures we not only pass the correct value, but also creates the correct function signature needed to pass the Wasm validator. This also adds an additional test case in c_abi tests. --- src/type.zig | 4 ++++ test/c_abi/cfuncs.c | 6 ++++++ test/c_abi/main.zig | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/src/type.zig b/src/type.zig index cc6e5706ee..215011df8d 100644 --- a/src/type.zig +++ b/src/type.zig @@ -6592,6 +6592,8 @@ pub const CType = enum { .powerpcle, .powerpc64, .powerpc64le, + .wasm32, + .wasm64, => return 128, else => return 64, @@ -6640,6 +6642,8 @@ pub const CType = enum { .powerpcle, .powerpc64, .powerpc64le, + .wasm32, + .wasm64, => return 128, else => return 64, diff --git a/test/c_abi/cfuncs.c b/test/c_abi/cfuncs.c index f5c90adba0..391e87fc67 100644 --- a/test/c_abi/cfuncs.c +++ b/test/c_abi/cfuncs.c @@ -33,6 +33,7 @@ void zig_five_integers(int32_t, int32_t, int32_t, int32_t, int32_t); void zig_f32(float); void zig_f64(double); +void zig_longdouble(long double); void zig_five_floats(float, float, float, float, float); bool zig_ret_bool(); @@ -157,6 +158,7 @@ void run_c_tests(void) { zig_f32(12.34f); zig_f64(56.78); + zig_longdouble(12.34l); zig_five_floats(1.0f, 2.0f, 3.0f, 4.0f, 5.0f); zig_ptr((void*)0xdeadbeefL); @@ -271,6 +273,10 @@ void c_f64(double x) { assert_or_panic(x == 56.78); } +void c_long_double(long double x) { + assert_or_panic(x == 12.34l); +} + void c_ptr(void *x) { assert_or_panic(x == (void*)0xdeadbeefL); } diff --git a/test/c_abi/main.zig b/test/c_abi/main.zig index 71a53bedea..8450d19a99 100644 --- a/test/c_abi/main.zig +++ b/test/c_abi/main.zig @@ -89,6 +89,7 @@ export fn zig_struct_u128(a: U128) void { extern fn c_f32(f32) void; extern fn c_f64(f64) void; +extern fn c_long_double(c_longdouble) void; // On windows x64, the first 4 are passed via registers, others on the stack. extern fn c_five_floats(f32, f32, f32, f32, f32) void; @@ -105,6 +106,7 @@ test "C ABI floats" { c_f32(12.34); c_f64(56.78); c_five_floats(1.0, 2.0, 3.0, 4.0, 5.0); + c_long_double(12.34); } export fn zig_f32(x: f32) void { @@ -113,6 +115,9 @@ export fn zig_f32(x: f32) void { export fn zig_f64(x: f64) void { expect(x == 56.78) catch @panic("test failure: zig_f64"); } +export fn zig_longdouble(x: c_longdouble) void { + expect(x == 12.34) catch @panic("test failure: zig_longdouble"); +} extern fn c_ptr(*anyopaque) void; From 3860e664c596bb4c0425de58768d63ee95fe36ef Mon Sep 17 00:00:00 2001 From: William Sengir Date: Sun, 28 Aug 2022 03:10:39 -0700 Subject: [PATCH 02/40] coff: fix reading COFF header offset --- lib/std/coff.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index f9de318e7a..a6d95735cd 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -831,7 +831,7 @@ pub const Coff = struct { var stream = std.io.fixedBufferStream(self.data); const reader = stream.reader(); try stream.seekTo(pe_pointer_offset); - const coff_header_offset = try reader.readByte(); + const coff_header_offset = try reader.readIntLittle(u32); try stream.seekTo(coff_header_offset); var buf: [4]u8 = undefined; try reader.readNoEof(&buf); From 91b9f295d3ad74ac0ba9d1329bf5c581a45f6e89 Mon Sep 17 00:00:00 2001 From: William Sengir Date: Sun, 28 Aug 2022 03:14:15 -0700 Subject: [PATCH 03/40] coff: publicize and flesh out more image constants --- lib/std/coff.zig | 95 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 15 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index a6d95735cd..47ec155df8 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -256,15 +256,53 @@ pub const OptionalHeaderPE64 = extern struct { number_of_rva_and_sizes: u32, }; -pub const DebugDirectoryEntry = extern struct { - characteristiccs: u32, - time_date_stamp: u32, - major_version: u16, - minor_version: u16, - @"type": u32, - size_of_data: u32, - address_of_raw_data: u32, - pointer_to_raw_data: u32, +pub const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; + +pub const DirectoryEntry = enum(u16) { + /// Export Directory + EXPORT = 0, + + /// Import Directory + IMPORT = 1, + + /// Resource Directory + RESOURCE = 2, + + /// Exception Directory + EXCEPTION = 3, + + /// Security Directory + SECURITY = 4, + + /// Base Relocation Table + BASERELOC = 5, + + /// Debug Directory + DEBUG = 6, + + /// Architecture Specific Data + ARCHITECTURE = 7, + + /// RVA of GP + GLOBALPTR = 8, + + /// TLS Directory + TLS = 9, + + /// Load Configuration Directory + LOAD_CONFIG = 10, + + /// Bound Import Directory in headers + BOUND_IMPORT = 11, + + /// Import Address Table + IAT = 12, + + /// Delay Load Import Descriptors + DELAY_IMPORT = 13, + + /// COM Runtime descriptor + COM_DESCRIPTOR = 14, }; pub const ImageDataDirectory = extern struct { @@ -272,6 +310,37 @@ pub const ImageDataDirectory = extern struct { size: u32, }; +pub const DebugDirectoryEntry = extern struct { + characteristics: u32, + time_date_stamp: u32, + major_version: u16, + minor_version: u16, + @"type": DebugType, + size_of_data: u32, + address_of_raw_data: u32, + pointer_to_raw_data: u32, +}; + +pub const DebugType = enum(u32) { + UNKNOWN = 0, + COFF = 1, + CODEVIEW = 2, + FPO = 3, + MISC = 4, + EXCEPTION = 5, + FIXUP = 6, + OMAP_TO_SRC = 7, + OMAP_FROM_SRC = 8, + BORLAND = 9, + RESERVED10 = 10, + VC_FEATURE = 12, + POGO = 13, + ILTCG = 14, + MPX = 15, + REPRO = 16, + EX_DLLCHARACTERISTICS = 20, +}; + pub const SectionHeader = extern struct { name: [8]u8, virtual_size: u32, @@ -794,10 +863,6 @@ pub const MachineType = enum(u16) { } }; -const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; -const IMAGE_DEBUG_TYPE_CODEVIEW = 2; -const DEBUG_DIRECTORY = 6; - pub const CoffError = error{ InvalidPEMagic, InvalidPEHeader, @@ -862,7 +927,7 @@ pub const Coff = struct { }; const data_dirs = self.getDataDirectories(); - const debug_dir = data_dirs[DEBUG_DIRECTORY]; + const debug_dir = data_dirs[@enumToInt(DirectoryEntry.DEBUG)]; const file_offset = debug_dir.virtual_address - header.virtual_address + header.pointer_to_raw_data; var stream = std.io.fixedBufferStream(self.data); @@ -875,7 +940,7 @@ pub const Coff = struct { var i: u32 = 0; blk: while (i < debug_dir_entry_count) : (i += 1) { const debug_dir_entry = try reader.readStruct(DebugDirectoryEntry); - if (debug_dir_entry.type == IMAGE_DEBUG_TYPE_CODEVIEW) { + if (debug_dir_entry.type == .CODEVIEW) { for (self.getSectionHeaders()) |*section| { const section_start = section.virtual_address; const section_size = section.virtual_size; From 1eb22e7ad6f700a72d34cdb36e614c95bdab0464 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 26 Aug 2022 22:00:22 +0200 Subject: [PATCH 04/40] wasm: skip unimplemented behavior test Since now the size of a c_longdouble is correctly 16 bytes, the test is no longer passing. It was previously accidentally passing due to incorrect sizing and it not being larger than the size of a f64. disable long_double test for windows --- test/behavior/cast.zig | 1 + test/c_abi/main.zig | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index fa0877258c..4c6dab2dbb 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -1430,6 +1430,7 @@ test "coerce between pointers of compatible differently-named floats" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.os.tag == .windows) { // https://github.com/ziglang/zig/issues/12396 diff --git a/test/c_abi/main.zig b/test/c_abi/main.zig index 8450d19a99..145bbc384a 100644 --- a/test/c_abi/main.zig +++ b/test/c_abi/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const print = std.debug.print; const expect = std.testing.expect; @@ -106,6 +107,10 @@ test "C ABI floats" { c_f32(12.34); c_f64(56.78); c_five_floats(1.0, 2.0, 3.0, 4.0, 5.0); +} + +test "C ABI long double" { + if (!builtin.cpu.arch.isWasm()) return error.SkipZigTest; c_long_double(12.34); } @@ -116,6 +121,7 @@ export fn zig_f64(x: f64) void { expect(x == 56.78) catch @panic("test failure: zig_f64"); } export fn zig_longdouble(x: c_longdouble) void { + if (!builtin.cpu.arch.isWasm()) return; // waiting for #1481 expect(x == 12.34) catch @panic("test failure: zig_longdouble"); } From fffece1533d37b917c6e301c984c1cb3eb0040e4 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Tue, 23 Aug 2022 12:45:12 +0200 Subject: [PATCH 05/40] wasm-lld: set stack size to 1MB by default Regardless of the build mode (build-exe, build-lib), always set the default stack size to 1MB. Previously, this was only done when using build-exe, making the inconsistancy confusing. The user can still override this behavior by providing the `--stack ` flag. --- src/link/Wasm.zig | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 727f329f4b..e20703cb2b 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2836,24 +2836,19 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) ! try argv.append(entry); } - if (self.base.options.output_mode == .Exe) { - // Increase the default stack size to a more reasonable value of 1MB instead of - // the default of 1 Wasm page being 64KB, unless overridden by the user. - try argv.append("-z"); - const stack_size = self.base.options.stack_size_override orelse 1048576; - const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); - try argv.append(arg); + // Increase the default stack size to a more reasonable value of 1MB instead of + // the default of 1 Wasm page being 64KB, unless overridden by the user. + try argv.append("-z"); + const stack_size = self.base.options.stack_size_override orelse wasm.page_size * 16; + const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); + try argv.append(arg); + if (self.base.options.output_mode == .Exe) { if (self.base.options.wasi_exec_model == .reactor) { // Reactor execution model does not have _start so lld doesn't look for it. try argv.append("--no-entry"); } - } else { - if (self.base.options.stack_size_override) |stack_size| { - try argv.append("-z"); - const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); - try argv.append(arg); - } + } else if (self.base.options.entry == null) { try argv.append("--no-entry"); // So lld doesn't look for _start. } try argv.appendSlice(&[_][]const u8{ From 56bdd0127f806da8b617eb82be444a4624aa5685 Mon Sep 17 00:00:00 2001 From: Meghan Date: Mon, 29 Aug 2022 16:40:37 -0700 Subject: [PATCH 06/40] implement cache environment variables for `zig build` --- src/main.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.zig b/src/main.zig index dd3a7e797b..7be177708f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3735,8 +3735,8 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi var build_file: ?[]const u8 = null; var override_lib_dir: ?[]const u8 = null; - var override_global_cache_dir: ?[]const u8 = null; - var override_local_cache_dir: ?[]const u8 = null; + var override_global_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_GLOBAL_CACHE_DIR"); + var override_local_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LOCAL_CACHE_DIR"); var child_argv = std.ArrayList([]const u8).init(arena); const argv_index_exe = child_argv.items.len; From 6f9b7c8ceebf9276892b2185e517400f5b729b80 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 29 Aug 2022 23:35:07 -0700 Subject: [PATCH 07/40] make 'zig build' respect ZIG_LIB_DIR follow-up to 56bdd0127f806da8b617eb82be444a4624aa5685 --- src/main.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.zig b/src/main.zig index 7be177708f..e8a16e194a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3734,7 +3734,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi const self_exe_path = try introspect.findZigExePath(arena); var build_file: ?[]const u8 = null; - var override_lib_dir: ?[]const u8 = null; + var override_lib_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LIB_DIR"); var override_global_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_GLOBAL_CACHE_DIR"); var override_local_cache_dir: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LOCAL_CACHE_DIR"); var child_argv = std.ArrayList([]const u8).init(arena); From 75d5a4b9a255895246762f27399e14821bc0e768 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 29 Aug 2022 23:37:31 -0700 Subject: [PATCH 08/40] CI: pass explicit zig lib dir in lots of places see #12684 for motivation --- ci/azure/macos_script | 1 + ci/azure/pipelines.yml | 2 ++ ci/drone/test_linux_behavior | 8 ++++---- ci/drone/test_linux_cases | 5 +++-- ci/drone/test_linux_misc | 14 +++++++------- ci/drone/test_linux_std_Debug | 7 ++++++- ci/drone/test_linux_std_ReleaseFast | 8 +++++++- ci/drone/test_linux_std_ReleaseSafe | 8 +++++++- ci/drone/test_linux_std_ReleaseSmall | 4 ++-- ci/srht/freebsd_script | 1 + ci/zinc/linux_test_stage3_debug | 3 ++- ci/zinc/linux_test_stage3_release | 6 ++++-- 12 files changed, 46 insertions(+), 21 deletions(-) diff --git a/ci/azure/macos_script b/ci/azure/macos_script index 4bb083e5e2..c9adb84d49 100755 --- a/ci/azure/macos_script +++ b/ci/azure/macos_script @@ -49,6 +49,7 @@ unset CXX make $JOBS install stage3-release/bin/zig build test docs \ + --zig-lib-dir ../lib \ -Denable-macos-sdk \ -Dstatic-llvm \ --search-prefix "$PREFIX" diff --git a/ci/azure/pipelines.yml b/ci/azure/pipelines.yml index 45504c5b6e..172336423d 100644 --- a/ci/azure/pipelines.yml +++ b/ci/azure/pipelines.yml @@ -60,6 +60,7 @@ jobs: displayName: 'Build' - pwsh: | + Set-Variable -Name ZIGLIBDIR -Value "$(Get-Location)\lib" Set-Variable -Name ZIGINSTALLDIR -Value "$(Get-Location)\stage3-release" function CheckLastExitCode { @@ -71,6 +72,7 @@ jobs: & "$ZIGINSTALLDIR\bin\zig.exe" build test docs ` --search-prefix "$ZIGPREFIXPATH" ` + --zig-lib-dir "$ZIGLIBDIR" ` -Dstatic-llvm ` -Dskip-non-native ` -Dskip-stage2-tests diff --git a/ci/drone/test_linux_behavior b/ci/drone/test_linux_behavior index fb5b1fa9e4..52ce24d689 100755 --- a/ci/drone/test_linux_behavior +++ b/ci/drone/test_linux_behavior @@ -7,7 +7,7 @@ INSTALL_PREFIX="$DRONE_WORKSPACE/stage3-release" ZIG="$INSTALL_PREFIX/bin/zig" export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" -$ZIG build test-behavior -Dskip-non-native -$ZIG build test-compiler-rt -Dskip-non-native -$ZIG build test-fmt -$ZIG build docs +$ZIG build test-behavior -Dskip-non-native --zig-lib-dir lib +$ZIG build test-compiler-rt -Dskip-non-native --zig-lib-dir lib +$ZIG build test-fmt --zig-lib-dir lib +$ZIG build docs --zig-lib-dir lib diff --git a/ci/drone/test_linux_cases b/ci/drone/test_linux_cases index 383ddf7b37..0b80345f84 100755 --- a/ci/drone/test_linux_cases +++ b/ci/drone/test_linux_cases @@ -7,5 +7,6 @@ INSTALL_PREFIX="$DRONE_WORKSPACE/stage3-release" ZIG="$INSTALL_PREFIX/bin/zig" export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" -$ZIG build -Dskip-non-native # test building self-hosted without LLVM -$ZIG build -Dskip-non-native test-cases +# test building self-hosted without LLVM +$ZIG build -Dskip-non-native --zig-lib-dir lib +$ZIG build test-cases -Dskip-non-native --zig-lib-dir lib diff --git a/ci/drone/test_linux_misc b/ci/drone/test_linux_misc index fc3dfcf4d4..ef34f6d3d8 100755 --- a/ci/drone/test_linux_misc +++ b/ci/drone/test_linux_misc @@ -7,10 +7,10 @@ INSTALL_PREFIX="$DRONE_WORKSPACE/stage3-release" ZIG="$INSTALL_PREFIX/bin/zig" export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" -$ZIG build test-universal-libc -Dskip-non-native -$ZIG build test-compare-output -Dskip-non-native -$ZIG build test-standalone -Dskip-non-native -Dskip-release-safe -$ZIG build test-stack-traces -Dskip-non-native -$ZIG build test-cli -Dskip-non-native -$ZIG build test-asm-link -Dskip-non-native -$ZIG build test-translate-c -Dskip-non-native +$ZIG build test-universal-libc -Dskip-non-native --zig-lib-dir lib +$ZIG build test-compare-output -Dskip-non-native --zig-lib-dir lib +$ZIG build test-standalone -Dskip-non-native --zig-lib-dir lib -Dskip-release-safe +$ZIG build test-stack-traces -Dskip-non-native --zig-lib-dir lib +$ZIG build test-cli -Dskip-non-native --zig-lib-dir lib +$ZIG build test-asm-link -Dskip-non-native --zig-lib-dir lib +$ZIG build test-translate-c -Dskip-non-native --zig-lib-dir lib diff --git a/ci/drone/test_linux_std_Debug b/ci/drone/test_linux_std_Debug index d05554d5a5..39558e19d1 100755 --- a/ci/drone/test_linux_std_Debug +++ b/ci/drone/test_linux_std_Debug @@ -7,4 +7,9 @@ INSTALL_PREFIX="$DRONE_WORKSPACE/stage3-release" ZIG="$INSTALL_PREFIX/bin/zig" export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" -$ZIG build test-std -Dskip-release-safe -Dskip-release-fast -Dskip-release-small -Dskip-non-native +$ZIG build test-std \ + --zig-lib-dir lib \ + -Dskip-release-safe \ + -Dskip-release-fast \ + -Dskip-release-small \ + -Dskip-non-native diff --git a/ci/drone/test_linux_std_ReleaseFast b/ci/drone/test_linux_std_ReleaseFast index 7e117b313a..e42f4b6247 100755 --- a/ci/drone/test_linux_std_ReleaseFast +++ b/ci/drone/test_linux_std_ReleaseFast @@ -7,4 +7,10 @@ INSTALL_PREFIX="$DRONE_WORKSPACE/stage3-release" ZIG="$INSTALL_PREFIX/bin/zig" export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" -$ZIG build test-std -Dskip-debug -Dskip-release-safe -Dskip-release-small -Dskip-non-native -Dskip-single-threaded +$ZIG build test-std \ + --zig-lib-dir lib \ + -Dskip-debug \ + -Dskip-release-safe \ + -Dskip-release-small \ + -Dskip-non-native \ + -Dskip-single-threaded diff --git a/ci/drone/test_linux_std_ReleaseSafe b/ci/drone/test_linux_std_ReleaseSafe index b494ea6e47..fe8021827d 100755 --- a/ci/drone/test_linux_std_ReleaseSafe +++ b/ci/drone/test_linux_std_ReleaseSafe @@ -7,4 +7,10 @@ INSTALL_PREFIX="$DRONE_WORKSPACE/stage3-release" ZIG="$INSTALL_PREFIX/bin/zig" export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" -$ZIG build test-std -Dskip-debug -Dskip-release-fast -Dskip-release-small -Dskip-non-native -Dskip-single-threaded +$ZIG build test-std \ + --zig-lib-dir lib \ + -Dskip-debug \ + -Dskip-release-fast \ + -Dskip-release-small \ + -Dskip-non-native \ + -Dskip-single-threaded diff --git a/ci/drone/test_linux_std_ReleaseSmall b/ci/drone/test_linux_std_ReleaseSmall index ffd366d454..5b7821b4d9 100755 --- a/ci/drone/test_linux_std_ReleaseSmall +++ b/ci/drone/test_linux_std_ReleaseSmall @@ -12,5 +12,5 @@ export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" # of ReleaseSmall std lib tests. # $ZIG build test-std -Dskip-debug -Dskip-release-safe -Dskip-release-fast -Dskip-non-native -$ZIG test lib/std/std.zig -OReleaseSmall -$ZIG test lib/std/std.zig -OReleaseSmall -lc +$ZIG test lib/std/std.zig -OReleaseSmall --zig-lib-dir lib +$ZIG test lib/std/std.zig -OReleaseSmall -lc --zig-lib-dir lib diff --git a/ci/srht/freebsd_script b/ci/srht/freebsd_script index 326e3cadf5..cf1d00d22c 100755 --- a/ci/srht/freebsd_script +++ b/ci/srht/freebsd_script @@ -56,6 +56,7 @@ ZIG_LIBC="$ZIG_LIBC_TXT" samu install # Here we skip some tests to save time. stage3/bin/zig build test docs \ + --zig-lib-dir ../lib \ -Dstatic-llvm \ --search-prefix "$PREFIX" \ -Dskip-stage1 \ diff --git a/ci/zinc/linux_test_stage3_debug b/ci/zinc/linux_test_stage3_debug index 074e80d19b..7f871e3958 100755 --- a/ci/zinc/linux_test_stage3_debug +++ b/ci/zinc/linux_test_stage3_debug @@ -55,7 +55,8 @@ stage3/bin/zig build test \ -fwasmtime \ -Dstatic-llvm \ -Dtarget=native-native-musl \ - --search-prefix "$DEPS_LOCAL" + --search-prefix "$DEPS_LOCAL" \ + --zig-lib-dir ../lib # Explicit exit helps show last command duration. exit diff --git a/ci/zinc/linux_test_stage3_release b/ci/zinc/linux_test_stage3_release index 24bdde17d0..d450d7bc79 100755 --- a/ci/zinc/linux_test_stage3_release +++ b/ci/zinc/linux_test_stage3_release @@ -40,13 +40,15 @@ ninja install -fwasmtime \ -Dstatic-llvm \ -Dtarget=native-native-musl \ - --search-prefix "$DEPS_LOCAL" + --search-prefix "$DEPS_LOCAL" \ + --zig-lib-dir ../lib # Produce the experimental std lib documentation. mkdir -p "$RELEASE_STAGING/docs/std" "$RELEASE_STAGING/bin/zig" test ../lib/std/std.zig \ -femit-docs=$RELEASE_STAGING/docs/std \ - -fno-emit-bin + -fno-emit-bin \ + --zig-lib-dir ../lib cp ../LICENSE $RELEASE_STAGING/ cp ../zig-cache/langref.html $RELEASE_STAGING/docs/ From 527055a8215b23a13fb407bdeba2908b8206f6f0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 30 Aug 2022 00:33:21 -0700 Subject: [PATCH 09/40] CI: use absolute path for zig lib dir Workaround for #12685 --- ci/azure/macos_script | 2 +- ci/azure/pipelines.yml | 2 -- ci/srht/freebsd_script | 2 +- ci/zinc/linux_test_stage3_debug | 2 +- ci/zinc/linux_test_stage3_release | 4 ++-- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/ci/azure/macos_script b/ci/azure/macos_script index c9adb84d49..2bc0e75e61 100755 --- a/ci/azure/macos_script +++ b/ci/azure/macos_script @@ -49,7 +49,7 @@ unset CXX make $JOBS install stage3-release/bin/zig build test docs \ - --zig-lib-dir ../lib \ + --zig-lib-dir "$(pwd)/../lib" \ -Denable-macos-sdk \ -Dstatic-llvm \ --search-prefix "$PREFIX" diff --git a/ci/azure/pipelines.yml b/ci/azure/pipelines.yml index 172336423d..45504c5b6e 100644 --- a/ci/azure/pipelines.yml +++ b/ci/azure/pipelines.yml @@ -60,7 +60,6 @@ jobs: displayName: 'Build' - pwsh: | - Set-Variable -Name ZIGLIBDIR -Value "$(Get-Location)\lib" Set-Variable -Name ZIGINSTALLDIR -Value "$(Get-Location)\stage3-release" function CheckLastExitCode { @@ -72,7 +71,6 @@ jobs: & "$ZIGINSTALLDIR\bin\zig.exe" build test docs ` --search-prefix "$ZIGPREFIXPATH" ` - --zig-lib-dir "$ZIGLIBDIR" ` -Dstatic-llvm ` -Dskip-non-native ` -Dskip-stage2-tests diff --git a/ci/srht/freebsd_script b/ci/srht/freebsd_script index cf1d00d22c..d54f13d03e 100755 --- a/ci/srht/freebsd_script +++ b/ci/srht/freebsd_script @@ -56,7 +56,7 @@ ZIG_LIBC="$ZIG_LIBC_TXT" samu install # Here we skip some tests to save time. stage3/bin/zig build test docs \ - --zig-lib-dir ../lib \ + --zig-lib-dir "$(pwd)/../lib" \ -Dstatic-llvm \ --search-prefix "$PREFIX" \ -Dskip-stage1 \ diff --git a/ci/zinc/linux_test_stage3_debug b/ci/zinc/linux_test_stage3_debug index 7f871e3958..401b225f3d 100755 --- a/ci/zinc/linux_test_stage3_debug +++ b/ci/zinc/linux_test_stage3_debug @@ -56,7 +56,7 @@ stage3/bin/zig build test \ -Dstatic-llvm \ -Dtarget=native-native-musl \ --search-prefix "$DEPS_LOCAL" \ - --zig-lib-dir ../lib + --zig-lib-dir "$(pwd)/../lib" # Explicit exit helps show last command duration. exit diff --git a/ci/zinc/linux_test_stage3_release b/ci/zinc/linux_test_stage3_release index d450d7bc79..63883a3948 100755 --- a/ci/zinc/linux_test_stage3_release +++ b/ci/zinc/linux_test_stage3_release @@ -41,14 +41,14 @@ ninja install -Dstatic-llvm \ -Dtarget=native-native-musl \ --search-prefix "$DEPS_LOCAL" \ - --zig-lib-dir ../lib + --zig-lib-dir "$(pwd)/../lib" # Produce the experimental std lib documentation. mkdir -p "$RELEASE_STAGING/docs/std" "$RELEASE_STAGING/bin/zig" test ../lib/std/std.zig \ -femit-docs=$RELEASE_STAGING/docs/std \ -fno-emit-bin \ - --zig-lib-dir ../lib + --zig-lib-dir "$(pwd)/../lib" cp ../LICENSE $RELEASE_STAGING/ cp ../zig-cache/langref.html $RELEASE_STAGING/docs/ From 06371950cf0a75f7b2e602381f5250fa2419d25a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 21 Aug 2022 09:55:26 +0200 Subject: [PATCH 10/40] start: allow for explicitly defined entry point for Win --- lib/std/start.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index e6a7b7991a..e7056a69d0 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -37,7 +37,9 @@ comptime { @export(main2, .{ .name = "main" }); } } else if (builtin.os.tag == .windows) { - @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); + if (!@hasDecl(root, "wWinMainCRTStartup")) { + @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); + } } else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) { @export(wasiMain2, .{ .name = "_start" }); } else { From 3c10221030c47a68c17b4e037abd8afcdfd08486 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 21 Aug 2022 09:56:55 +0200 Subject: [PATCH 11/40] coff: move header writing logic into flush --- src/link/Coff.zig | 501 +++++++++++++++++++++++----------------------- 1 file changed, 253 insertions(+), 248 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index e4f0227aec..d7e6ebe162 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -144,39 +144,10 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }); self.base.file = file; - // TODO Write object specific relocations, COFF symbol table, then enable object file output. - switch (options.output_mode) { - .Exe => {}, - .Obj => return error.TODOImplementWritingObjFiles, - .Lib => return error.TODOImplementWritingLibFiles, - } - - var coff_file_header_offset: u32 = 0; - if (options.output_mode == .Exe) { - // Write the MS-DOS stub and the PE signature - try self.base.file.?.pwriteAll(msdos_stub ++ "PE\x00\x00", 0); - coff_file_header_offset = msdos_stub.len + 4; - } - - // COFF file header + const coff_file_header_offset: u32 = if (options.output_mode == .Exe) msdos_stub.len + 4 else 0; + const default_offset_table_size = file_alignment; const data_directory_count = 0; - var hdr_data: [112 + data_directory_count * 8 + section_table_size]u8 = undefined; - var index: usize = 0; - - const machine = self.base.options.target.cpu.arch.toCoffMachine(); - if (machine == .Unknown) { - return error.UnsupportedCOFFArchitecture; - } - mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine)); - index += 2; - - // Number of sections (we only use .got, .text) - mem.writeIntLittle(u16, hdr_data[index..][0..2], 2); - index += 2; - // TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - + const default_size_of_code = 0; const optional_header_size = switch (options.output_mode) { .Exe => data_directory_count * 8 + switch (self.ptr_width) { .p32 => @as(u16, 96), @@ -184,218 +155,15 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }, else => 0, }; - const section_table_offset = coff_file_header_offset + 20 + optional_header_size; - const default_offset_table_size = file_alignment; - const default_size_of_code = 0; - - self.section_data_offset = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, file_alignment); - const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, section_alignment); + self.section_data_offset = mem.alignForwardGeneric(u32, section_table_offset + section_table_size, file_alignment); + const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, section_table_offset + section_table_size, section_alignment); self.offset_table_virtual_address = default_image_base + section_data_relative_virtual_address; self.offset_table_size = default_offset_table_size; self.section_table_offset = section_table_offset; self.text_section_virtual_address = default_image_base + section_data_relative_virtual_address + section_alignment; self.text_section_size = default_size_of_code; - // Size of file when loaded in memory - const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + default_size_of_code, section_alignment); - - mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); - index += 2; - - // Characteristics - var characteristics: std.coff.CoffHeaderFlags = .{ - .DEBUG_STRIPPED = 1, // TODO remove debug info stripped flag when necessary - .RELOCS_STRIPPED = 1, - }; - if (options.output_mode == .Exe) { - characteristics.EXECUTABLE_IMAGE = 1; - } - switch (self.ptr_width) { - .p32 => characteristics.@"32BIT_MACHINE" = 1, - .p64 => characteristics.LARGE_ADDRESS_AWARE = 1, - } - mem.writeIntLittle(u16, hdr_data[index..][0..2], @bitCast(u16, characteristics)); - index += 2; - - assert(index == 20); - try self.base.file.?.pwriteAll(hdr_data[0..index], coff_file_header_offset); - - if (options.output_mode == .Exe) { - self.optional_header_offset = coff_file_header_offset + 20; - // Optional header - index = 0; - mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) { - .p32 => @as(u16, 0x10b), - .p64 => 0x20b, - }); - index += 2; - - // Linker version (u8 + u8) - mem.set(u8, hdr_data[index..][0..2], 0); - index += 2; - - // SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32) - mem.set(u8, hdr_data[index..][0..20], 0); - index += 20; - - if (self.ptr_width == .p32) { - // Base of data relative to the image base (UNUSED) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Image base address - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_image_base); - index += 4; - } else { - // Image base address - mem.writeIntLittle(u64, hdr_data[index..][0..8], default_image_base); - index += 8; - } - - // Section alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment); - index += 4; - // File alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment); - index += 4; - // Required OS version, 6.0 is vista - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Image version - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Required subsystem version, same as OS version - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Reserved zeroes (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // CheckSum (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Subsystem, TODO: Let users specify the subsystem, always CUI for now - mem.writeIntLittle(u16, hdr_data[index..][0..2], 3); - index += 2; - // DLL characteristics - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0); - index += 2; - - switch (self.ptr_width) { - .p32 => { - // Size of stack reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - // Size of heap reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - }, - .p64 => { - // Size of stack reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - // Size of heap reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - }, - } - - // Reserved zeroes - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Number of data directories - mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count); - index += 4; - // Initialize data directories to zero - mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0); - index += data_directory_count * 8; - - assert(index == optional_header_size); - } - - // Write section table. - // First, the .got section - hdr_data[index..][0..8].* = ".got\x00\x00\x00\x00".*; - index += 8; - if (options.output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_INITIALIZED_DATA = 1, - .MEM_READ = 1, - })); - index += 4; - // Then, the .text section - hdr_data[index..][0..8].* = ".text\x00\x00\x00".*; - index += 8; - if (options.output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset + default_offset_table_size); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_CODE = 1, - .MEM_EXECUTE = 1, - .MEM_READ = 1, - .MEM_WRITE = 1, - })); - index += 4; - - assert(index == optional_header_size + section_table_size); - try self.base.file.?.pwriteAll(hdr_data[0..index], self.optional_header_offset); - try self.base.file.?.setEndPos(self.section_data_offset + default_offset_table_size + default_size_of_code); - return self; } @@ -728,6 +496,12 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! if (decl.val.tag() == .extern_fn) { return; // TODO Should we do more when front-end analyzed extern decl? } + if (decl.val.castTag(.variable)) |payload| { + const variable = payload.data; + if (variable.is_extern) { + return; // TODO Should we do more when front-end analyzed extern decl? + } + } // TODO COFF/PE debug information // TODO Implement exports @@ -735,9 +509,10 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); + const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val; const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{ .ty = decl.ty, - .val = decl.val, + .val = decl_val, }, &code_buffer, .none, .{ .parent_atom_index = 0, }); @@ -862,15 +637,14 @@ pub fn updateDeclExports( continue; } } - if (mem.eql(u8, exp.options.name, "_start")) { + if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { self.entry_addr = decl.link.coff.getVAddr(self.*) - default_image_base; } else { try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than '_start'", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than 'wWinMainCRTStartup'", .{}), ); - continue; } } } @@ -884,14 +658,13 @@ pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !vo } return; } - if (build_options.have_llvm and self.base.options.use_lld) { + const use_lld = build_options.have_llvm and self.base.options.use_lld; + if (use_lld) { return self.linkWithLLD(comp, prog_node); - } else { - switch (self.base.options.effectiveOutputMode()) { - .Exe, .Obj => {}, - .Lib => return error.TODOImplementWritingLibFiles, - } - return self.flushModule(comp, prog_node); + } + switch (self.base.options.output_mode) { + .Exe, .Obj => return self.flushModule(comp, prog_node), + .Lib => return error.TODOImplementWritingLibFiles, } } @@ -909,6 +682,238 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + const output_mode = self.base.options.output_mode; + log.debug("in flushModule with {}", .{output_mode}); + + var coff_file_header_offset: u32 = 0; + if (output_mode == .Exe) { + // Write the MS-DOS stub and the PE signature + try self.base.file.?.pwriteAll(msdos_stub ++ "PE\x00\x00", 0); + coff_file_header_offset = msdos_stub.len + 4; + } + + // COFF file header + const data_directory_count = 0; + var hdr_data: [112 + data_directory_count * 8 + section_table_size]u8 = undefined; + var index: usize = 0; + + const machine = self.base.options.target.cpu.arch.toCoffMachine(); + if (machine == .Unknown) { + return error.UnsupportedCOFFArchitecture; + } + mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine)); + index += 2; + + // Number of sections (we only use .got, .text) + mem.writeIntLittle(u16, hdr_data[index..][0..2], 2); + index += 2; + // TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32) + mem.set(u8, hdr_data[index..][0..12], 0); + index += 12; + + const optional_header_size = switch (output_mode) { + .Exe => data_directory_count * 8 + switch (self.ptr_width) { + .p32 => @as(u16, 96), + .p64 => 112, + }, + else => 0, + }; + + const default_offset_table_size = file_alignment; + const default_size_of_code = 0; + + // Size of file when loaded in memory + const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + default_size_of_code, section_alignment); + + mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); + index += 2; + + // Characteristics + var characteristics: u16 = std.coff.IMAGE_FILE_DEBUG_STRIPPED | std.coff.IMAGE_FILE_RELOCS_STRIPPED; // TODO Remove debug info stripped flag when necessary + if (output_mode == .Exe) { + characteristics |= std.coff.IMAGE_FILE_EXECUTABLE_IMAGE; + } + switch (self.ptr_width) { + .p32 => characteristics |= std.coff.IMAGE_FILE_32BIT_MACHINE, + .p64 => characteristics |= std.coff.IMAGE_FILE_LARGE_ADDRESS_AWARE, + } + mem.writeIntLittle(u16, hdr_data[index..][0..2], characteristics); + index += 2; + + assert(index == 20); + try self.base.file.?.pwriteAll(hdr_data[0..index], coff_file_header_offset); + + if (output_mode == .Exe) { + self.optional_header_offset = coff_file_header_offset + 20; + // Optional header + index = 0; + mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) { + .p32 => @as(u16, 0x10b), + .p64 => 0x20b, + }); + index += 2; + + // Linker version (u8 + u8) + mem.set(u8, hdr_data[index..][0..2], 0); + index += 2; + + // SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32) + mem.set(u8, hdr_data[index..][0..20], 0); + index += 20; + + if (self.ptr_width == .p32) { + // Base of data relative to the image base (UNUSED) + mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + + // Image base address + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_image_base); + index += 4; + } else { + // Image base address + mem.writeIntLittle(u64, hdr_data[index..][0..8], default_image_base); + index += 8; + } + + // Section alignment + mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment); + index += 4; + // File alignment + mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment); + index += 4; + // Required OS version, 6.0 is vista + mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); + index += 2; + mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); + index += 2; + // Image version + mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + // Required subsystem version, same as OS version + mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); + index += 2; + mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); + index += 2; + // Reserved zeroes (u32) + mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image); + index += 4; + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); + index += 4; + // CheckSum (u32) + mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + // Subsystem, TODO: Let users specify the subsystem, always CUI for now + mem.writeIntLittle(u16, hdr_data[index..][0..2], 3); + index += 2; + // DLL characteristics + mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0); + index += 2; + + switch (self.ptr_width) { + .p32 => { + // Size of stack reserve + commit + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000); + index += 4; + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); + index += 4; + // Size of heap reserve + commit + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000); + index += 4; + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); + index += 4; + }, + .p64 => { + // Size of stack reserve + commit + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000); + index += 8; + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); + index += 8; + // Size of heap reserve + commit + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000); + index += 8; + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); + index += 8; + }, + } + + // Reserved zeroes + mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + + // Number of data directories + mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count); + index += 4; + // Initialize data directories to zero + mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0); + index += data_directory_count * 8; + + assert(index == optional_header_size); + } + + // Write section table. + // First, the .got section + hdr_data[index..][0..8].* = ".got\x00\x00\x00\x00".*; + index += 8; + if (output_mode == .Exe) { + // Virtual size (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); + index += 4; + // Virtual address (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - default_image_base); + index += 4; + } else { + mem.set(u8, hdr_data[index..][0..8], 0); + index += 8; + } + // Size of raw data (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); + index += 4; + // File pointer to the start of the section + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); + index += 4; + // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) + mem.set(u8, hdr_data[index..][0..12], 0); + index += 12; + // Section flags + mem.writeIntLittle(u32, hdr_data[index..][0..4], std.coff.IMAGE_SCN_CNT_INITIALIZED_DATA | std.coff.IMAGE_SCN_MEM_READ); + index += 4; + // Then, the .text section + hdr_data[index..][0..8].* = ".text\x00\x00\x00".*; + index += 8; + if (output_mode == .Exe) { + // Virtual size (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); + index += 4; + // Virtual address (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - default_image_base); + index += 4; + } else { + mem.set(u8, hdr_data[index..][0..8], 0); + index += 8; + } + // Size of raw data (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); + index += 4; + // File pointer to the start of the section + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset + default_offset_table_size); + index += 4; + // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) + mem.set(u8, hdr_data[index..][0..12], 0); + index += 12; + // Section flags + mem.writeIntLittle( + u32, + hdr_data[index..][0..4], + std.coff.IMAGE_SCN_CNT_CODE | std.coff.IMAGE_SCN_MEM_EXECUTE | std.coff.IMAGE_SCN_MEM_READ | std.coff.IMAGE_SCN_MEM_WRITE, + ); + index += 4; + + assert(index == optional_header_size + section_table_size); + try self.base.file.?.pwriteAll(hdr_data[0..index], self.optional_header_offset); + try self.base.file.?.setEndPos(self.section_data_offset + default_offset_table_size + default_size_of_code); + if (self.text_section_size_dirty) { // Write the new raw size in the .text header var buf: [4]u8 = undefined; From 580bfe01c89c4e95cfb341b73514b8ec769ce635 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 26 Aug 2022 10:28:33 +0200 Subject: [PATCH 12/40] coff: fix after rebase --- src/link/Coff.zig | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index d7e6ebe162..adfb843e04 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -729,15 +729,19 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod index += 2; // Characteristics - var characteristics: u16 = std.coff.IMAGE_FILE_DEBUG_STRIPPED | std.coff.IMAGE_FILE_RELOCS_STRIPPED; // TODO Remove debug info stripped flag when necessary + var flags: std.coff.CoffHeaderFlags = .{ + // TODO Remove debug info stripped flag when necessary + .DEBUG_STRIPPED = 1, + .RELOCS_STRIPPED = 1, + }; if (output_mode == .Exe) { - characteristics |= std.coff.IMAGE_FILE_EXECUTABLE_IMAGE; + flags.EXECUTABLE_IMAGE = 1; } switch (self.ptr_width) { - .p32 => characteristics |= std.coff.IMAGE_FILE_32BIT_MACHINE, - .p64 => characteristics |= std.coff.IMAGE_FILE_LARGE_ADDRESS_AWARE, + .p32 => flags.@"32BIT_MACHINE" = 1, + .p64 => flags.LARGE_ADDRESS_AWARE = 1, } - mem.writeIntLittle(u16, hdr_data[index..][0..2], characteristics); + mem.writeIntLittle(u16, hdr_data[index..][0..2], @bitCast(u16, flags)); index += 2; assert(index == 20); @@ -877,7 +881,10 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod mem.set(u8, hdr_data[index..][0..12], 0); index += 12; // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], std.coff.IMAGE_SCN_CNT_INITIALIZED_DATA | std.coff.IMAGE_SCN_MEM_READ); + mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + })); index += 4; // Then, the .text section hdr_data[index..][0..8].* = ".text\x00\x00\x00".*; @@ -903,11 +910,12 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod mem.set(u8, hdr_data[index..][0..12], 0); index += 12; // Section flags - mem.writeIntLittle( - u32, - hdr_data[index..][0..4], - std.coff.IMAGE_SCN_CNT_CODE | std.coff.IMAGE_SCN_MEM_EXECUTE | std.coff.IMAGE_SCN_MEM_READ | std.coff.IMAGE_SCN_MEM_WRITE, - ); + mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ + .CNT_CODE = 1, + .MEM_EXECUTE = 1, + .MEM_READ = 1, + .MEM_WRITE = 1, + })); index += 4; assert(index == optional_header_size + section_table_size); From 90b3599c6846b29a6162a670783d6c2763683f36 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 26 Aug 2022 14:11:14 +0200 Subject: [PATCH 13/40] coff: reorganize the linker --- CMakeLists.txt | 2 + lib/std/coff.zig | 9 + src/Module.zig | 8 +- src/Sema.zig | 2 +- src/arch/aarch64/CodeGen.zig | 14 +- src/arch/arm/CodeGen.zig | 14 +- src/arch/riscv64/CodeGen.zig | 14 +- src/arch/x86_64/CodeGen.zig | 14 +- src/link.zig | 6 +- src/link/Coff.zig | 1609 ++++++++----------------------- src/link/Coff/Atom.zig | 85 ++ src/link/Coff/lld.zig | 602 ++++++++++++ src/link/MachO.zig | 3 +- src/link/MachO/DebugSymbols.zig | 1 - 14 files changed, 1121 insertions(+), 1262 deletions(-) create mode 100644 src/link/Coff/Atom.zig create mode 100644 src/link/Coff/lld.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 835f3c4d62..372338bb7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -753,6 +753,8 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link.zig" "${CMAKE_SOURCE_DIR}/src/link/C.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff.zig" + "${CMAKE_SOURCE_DIR}/src/link/Coff/Atom.zig" + "${CMAKE_SOURCE_DIR}/src/link/Coff/lld.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig" diff --git a/lib/std/coff.zig b/lib/std/coff.zig index f9de318e7a..1640d44fb5 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -303,6 +303,15 @@ pub const SectionHeader = extern struct { return std.math.powi(u16, 2, self.flags.ALIGN - 1) catch unreachable; } + pub fn setAlignment(self: *SectionHeader, new_alignment: u16) void { + assert(new_alignment > 0 and new_alignment <= 8192); + self.flags.ALIGN = std.math.log2(new_alignment); + } + + pub fn isCode(self: SectionHeader) bool { + return self.flags.CNT_CODE == 0b1; + } + pub fn isComdat(self: SectionHeader) bool { return self.flags.LNK_COMDAT == 0b1; } diff --git a/src/Module.zig b/src/Module.zig index a92849e127..706ab5ff17 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -5259,9 +5259,9 @@ pub fn clearDecl( // TODO instead of a union, put this memory trailing Decl objects, // and allow it to be variably sized. decl.link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = link.File.Coff.TextBlock.empty }, + .coff => .{ .coff = link.File.Coff.Atom.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, - .macho => .{ .macho = link.File.MachO.TextBlock.empty }, + .macho => .{ .macho = link.File.MachO.Atom.empty }, .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, @@ -5680,9 +5680,9 @@ pub fn allocateNewDecl( .zir_decl_index = 0, .src_scope = src_scope, .link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = link.File.Coff.TextBlock.empty }, + .coff => .{ .coff = link.File.Coff.Atom.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, - .macho => .{ .macho = link.File.MachO.TextBlock.empty }, + .macho => .{ .macho = link.File.MachO.Atom.empty }, .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, diff --git a/src/Sema.zig b/src/Sema.zig index f884684d73..a4e2106afb 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5076,7 +5076,7 @@ pub fn analyzeExport( }, .src = src, .link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = {} }, + .coff => .{ .coff = .{} }, .elf => .{ .elf = .{} }, .macho => .{ .macho = .{} }, .plan9 => .{ .plan9 = null }, diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index d256f9a558..98f778ef35 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -3475,10 +3475,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk got_sym.value; + } else unreachable; try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = got_addr }); @@ -5110,8 +5111,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index e8f0507614..0f796c530d 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -3709,10 +3709,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk @intCast(u32, got_sym.value); + } else unreachable; try self.genSetReg(Type.initTag(.usize), .lr, .{ .memory = got_addr }); } else if (func_value.castTag(.extern_fn)) |_| { @@ -5549,8 +5550,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne } else if (self.bin_file.cast(link.File.MachO)) |_| { unreachable; // unsupported architecture for MachO } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 06adcff6d4..2bb68086d8 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1755,10 +1755,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk got_sym.value; + } else unreachable; try self.genSetReg(Type.initTag(.usize), .ra, .{ .memory = got_addr }); _ = try self.addInst(.{ @@ -2592,8 +2593,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // index to the GOT target symbol index. return MCValue{ .memory = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 106d2feec0..d7263523c2 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -3971,10 +3971,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - @intCast(u32, coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes) - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk got_sym.value; + } else unreachable; _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), @@ -6847,8 +6848,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/link.zig b/src/link.zig index 60c6b24640..421188bd47 100644 --- a/src/link.zig +++ b/src/link.zig @@ -245,8 +245,8 @@ pub const File = struct { pub const LinkBlock = union { elf: Elf.TextBlock, - coff: Coff.TextBlock, - macho: MachO.TextBlock, + coff: Coff.Atom, + macho: MachO.Atom, plan9: Plan9.DeclBlock, c: void, wasm: Wasm.DeclBlock, @@ -267,7 +267,7 @@ pub const File = struct { pub const Export = union { elf: Elf.Export, - coff: void, + coff: Coff.Export, macho: MachO.Export, plan9: Plan9.Export, c: void, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index adfb843e04..67172abcd4 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1,39 +1,30 @@ const Coff = @This(); const std = @import("std"); +const build_options = @import("build_options"); const builtin = @import("builtin"); -const log = std.log.scoped(.link); -const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const fs = std.fs; -const allocPrint = std.fmt.allocPrint; +const coff = std.coff; +const log = std.log.scoped(.link); +const math = std.math; const mem = std.mem; -const lldMain = @import("../main.zig").lldMain; -const trace = @import("../tracy.zig").trace; -const Module = @import("../Module.zig"); -const Compilation = @import("../Compilation.zig"); +const Allocator = std.mem.Allocator; + const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); -const build_options = @import("build_options"); -const Cache = @import("../Cache.zig"); -const mingw = @import("../mingw.zig"); +const lld = @import("Coff/lld.zig"); +const trace = @import("../tracy.zig").trace; + const Air = @import("../Air.zig"); +pub const Atom = @import("Coff/Atom.zig"); +const Compilation = @import("../Compilation.zig"); const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; +const Module = @import("../Module.zig"); +const StringTable = @import("strtab.zig").StringTable; const TypedValue = @import("../TypedValue.zig"); -const allocation_padding = 4 / 3; -const minimum_text_block_size = 64 * allocation_padding; - -const section_alignment = 4096; -const file_alignment = 512; -const default_image_base = 0x400_000; -const section_table_size = 2 * 40; -comptime { - assert(mem.isAligned(default_image_base, section_alignment)); -} - pub const base_tag: link.File.Tag = .coff; const msdos_stub = @embedFile("msdos-stub.bin"); @@ -42,91 +33,94 @@ const msdos_stub = @embedFile("msdos-stub.bin"); llvm_object: ?*LlvmObject = null, base: link.File, -ptr_width: PtrWidth, error_flags: link.File.ErrorFlags = .{}, -text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{}, -last_text_block: ?*TextBlock = null, +ptr_width: PtrWidth, -/// Section table file pointer. -section_table_offset: u32 = 0, -/// Section data file pointer. -section_data_offset: u32 = 0, -/// Optional header file pointer. -optional_header_offset: u32 = 0, +sections: std.MultiArrayList(Section) = .{}, -/// Absolute virtual address of the offset table when the executable is loaded in memory. -offset_table_virtual_address: u32 = 0, -/// Current size of the offset table on disk, must be a multiple of `file_alignment` -offset_table_size: u32 = 0, -/// Contains absolute virtual addresses -offset_table: std.ArrayListUnmanaged(u64) = .{}, -/// Free list of offset table indices -offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, +text_section_index: ?u16 = null, +got_section_index: ?u16 = null, + +locals: std.ArrayListUnmanaged(coff.Symbol) = .{}, +globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{}, + +locals_free_list: std.ArrayListUnmanaged(u32) = .{}, + +strtab: StringTable(.strtab) = .{}, + +got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, +got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, /// Virtual address of the entry point procedure relative to image base. -entry_addr: ?u32 = null, +entry_addr: ?u64 = null, -/// Absolute virtual address of the text section when the executable is loaded in memory. -text_section_virtual_address: u32 = 0, -/// Current size of the `.text` section on disk, must be a multiple of `file_alignment` -text_section_size: u32 = 0, +/// Table of Decls that are currently alive. +/// We store them here so that we can properly dispose of any allocated +/// memory within the atom in the incremental linker. +/// TODO consolidate this. +decls: std.AutoHashMapUnmanaged(Module.Decl.Index, ?u16) = .{}, -offset_table_size_dirty: bool = false, -text_section_size_dirty: bool = false, -/// This flag is set when the virtual size of the whole image file when loaded in memory has changed -/// and needs to be updated in the optional header. -size_of_image_dirty: bool = false, +/// List of atoms that are either synthetic or map directly to the Zig source program. +managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, -pub const PtrWidth = enum { p32, p64 }; +/// Table of atoms indexed by the symbol index. +atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, -pub const TextBlock = struct { - /// Offset of the code relative to the start of the text section - text_offset: u32, - /// Used size of the text block - size: u32, - /// This field is undefined for symbols with size = 0. - offset_table_index: u32, - /// Points to the previous and next neighbors, based on the `text_offset`. - /// This can be used to find, for example, the capacity of this `TextBlock`. - prev: ?*TextBlock, - next: ?*TextBlock, +const page_size: u16 = 0x1000; - pub const empty = TextBlock{ - .text_offset = 0, - .size = 0, - .offset_table_index = undefined, - .prev = null, - .next = null, - }; +const Section = struct { + header: coff.SectionHeader, - /// Returns how much room there is to grow in virtual address space. - fn capacity(self: TextBlock) u64 { - if (self.next) |next| { - return next.text_offset - self.text_offset; - } - // This is the last block, the capacity is only limited by the address space. - return std.math.maxInt(u32) - self.text_offset; - } + last_atom: ?*Atom = null, - fn freeListEligible(self: TextBlock) bool { - // No need to keep a free list node for the last block. - const next = self.next orelse return false; - const cap = next.text_offset - self.text_offset; - const ideal_cap = self.size * allocation_padding; - if (cap <= ideal_cap) return false; - const surplus = cap - ideal_cap; - return surplus >= minimum_text_block_size; - } - - /// Absolute virtual address of the text block when the file is loaded in memory. - fn getVAddr(self: TextBlock, coff: Coff) u32 { - return coff.text_section_virtual_address + self.text_offset; - } + /// A list of atoms that have surplus capacity. This list can have false + /// positives, as functions grow and shrink over time, only sometimes being added + /// or removed from the freelist. + /// + /// An atom has surplus capacity when its overcapacity value is greater than + /// padToIdeal(minimum_atom_size). That is, when it has so + /// much extra capacity, that we could fit a small new symbol in it, itself with + /// ideal_capacity or more. + /// + /// Ideal capacity is defined by size + (size / ideal_factor). + /// + /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that + /// overcapacity can be negative. A simple way to have negative overcapacity is to + /// allocate a fresh atom, which will have ideal capacity, and then grow it + /// by 1 byte. It will then have -1 overcapacity. + free_list: std.ArrayListUnmanaged(*Atom) = .{}, }; +pub const PtrWidth = enum { p32, p64 }; pub const SrcFn = void; +pub const Export = struct { + sym_index: ?u32 = null, +}; + +pub const SymbolWithLoc = struct { + // Index into the respective symbol table. + sym_index: u32, + + // null means it's a synthetic global or Zig source. + file: ?u32 = null, +}; + +/// When allocating, the ideal_capacity is calculated by +/// actual_capacity + (actual_capacity / ideal_factor) +const ideal_factor = 3; + +/// In order for a slice of bytes to be considered eligible to keep metadata pointing at +/// it as a possible place to put new symbols, it must have enough room for this many bytes +/// (plus extra for reserved capacity). +const minimum_text_block_size = 64; +pub const min_text_capacity = padToIdeal(minimum_text_block_size); + +/// We commit 0x1000 = 4096 bytes of space to the headers. +/// This should be plenty for any potential future extensions. +const default_headerpad_size: u32 = 0x1000; + pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Coff { assert(options.target.ofmt == .coff); @@ -144,25 +138,18 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }); self.base.file = file; - const coff_file_header_offset: u32 = if (options.output_mode == .Exe) msdos_stub.len + 4 else 0; - const default_offset_table_size = file_alignment; - const data_directory_count = 0; - const default_size_of_code = 0; - const optional_header_size = switch (options.output_mode) { - .Exe => data_directory_count * 8 + switch (self.ptr_width) { - .p32 => @as(u16, 96), - .p64 => 112, - }, - else => 0, - }; - const section_table_offset = coff_file_header_offset + 20 + optional_header_size; - self.section_data_offset = mem.alignForwardGeneric(u32, section_table_offset + section_table_size, file_alignment); - const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, section_table_offset + section_table_size, section_alignment); - self.offset_table_virtual_address = default_image_base + section_data_relative_virtual_address; - self.offset_table_size = default_offset_table_size; - self.section_table_offset = section_table_offset; - self.text_section_virtual_address = default_image_base + section_data_relative_virtual_address + section_alignment; - self.text_section_size = default_size_of_code; + // Index 0 is always a null symbol. + try self.locals.append(allocator, .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }); + try self.strtab.buffer.append(allocator, 0); + + try self.populateMissingMetadata(); return self; } @@ -193,245 +180,259 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { return self; } -pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { - if (self.llvm_object) |_| return; +pub fn deinit(self: *Coff) void { + const gpa = self.base.allocator; - try self.offset_table.ensureUnusedCapacity(self.base.allocator, 1); - - const decl = self.base.options.module.?.declPtr(decl_index); - if (self.offset_table_free_list.popOrNull()) |i| { - decl.link.coff.offset_table_index = i; - } else { - decl.link.coff.offset_table_index = @intCast(u32, self.offset_table.items.len); - _ = self.offset_table.addOneAssumeCapacity(); - - const entry_size = self.base.options.target.cpu.arch.ptrBitWidth() / 8; - if (self.offset_table.items.len > self.offset_table_size / entry_size) { - self.offset_table_size_dirty = true; - } + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa); } - self.offset_table.items[decl.link.coff.offset_table_index] = 0; + for (self.sections.items(.free_list)) |*free_list| { + free_list.deinit(gpa); + } + self.sections.deinit(gpa); + + for (self.managed_atoms.items) |atom| { + gpa.destroy(atom); + } + self.managed_atoms.deinit(gpa); + + self.locals.deinit(gpa); + self.globals.deinit(gpa); + self.locals_free_list.deinit(gpa); + self.strtab.deinit(gpa); + self.got_entries.deinit(gpa); + self.got_entries_free_list.deinit(gpa); + self.decls.deinit(gpa); + self.atom_by_index_table.deinit(gpa); } -fn allocateTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const new_block_min_capacity = new_block_size * allocation_padding; +fn populateMissingMetadata(self: *Coff) !void { + _ = self; + @panic("TODO populateMissingMetadata"); +} - // We use these to indicate our intention to update metadata, placing the new block, +pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { + if (self.llvm_object) |_| return; + const decl = self.base.options.module.?.declPtr(decl_index); + if (decl.link.coff.sym_index != 0) return; + decl.link.coff.sym_index = try self.allocateSymbol(); + const gpa = self.base.allocator; + try self.atom_by_index_table.putNoClobber(gpa, decl.link.coff.sym_index, &decl.link.coff); + try self.decls.putNoClobber(gpa, decl_index, null); +} + +fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, sect_id: u16) !u64 { + const tracy = trace(@src()); + defer tracy.end(); + + const header = &self.sections.items(.header)[sect_id]; + const free_list = &self.sections.items(.free_list)[sect_id]; + const maybe_last_atom = &self.sections.items(.last_atom)[sect_id]; + const new_atom_ideal_capacity = if (header.isCode()) padToIdeal(new_atom_size) else new_atom_size; + + // We use these to indicate our intention to update metadata, placing the new atom, // and possibly removing a free list node. // It would be simpler to do it inside the for loop below, but that would cause a // problem if an error was returned later in the function. So this action // is actually carried out at the end of the function, when errors are no longer possible. - var block_placement: ?*TextBlock = null; + var atom_placement: ?*Atom = null; var free_list_removal: ?usize = null; - const vaddr = blk: { + // First we look for an appropriately sized free list node. + // The list is unordered. We'll just take the first thing that works. + var vaddr = blk: { var i: usize = 0; - while (i < self.text_block_free_list.items.len) { - const free_block = self.text_block_free_list.items[i]; - - const next_block_text_offset = free_block.text_offset + free_block.capacity(); - const new_block_text_offset = mem.alignForwardGeneric(u64, free_block.getVAddr(self.*) + free_block.size, alignment) - self.text_section_virtual_address; - if (new_block_text_offset < next_block_text_offset and next_block_text_offset - new_block_text_offset >= new_block_min_capacity) { - block_placement = free_block; - - const remaining_capacity = next_block_text_offset - new_block_text_offset - new_block_min_capacity; - if (remaining_capacity < minimum_text_block_size) { - free_list_removal = i; - } - - break :blk new_block_text_offset + self.text_section_virtual_address; - } else { - if (!free_block.freeListEligible()) { - _ = self.text_block_free_list.swapRemove(i); + while (i < free_list.items.len) { + const big_atom = free_list.items[i]; + // We now have a pointer to a live atom that has too much capacity. + // Is it enough that we could fit this new atom? + const sym = big_atom.getSymbol(self); + const capacity = big_atom.capacity(self); + const ideal_capacity = if (header.isCode()) padToIdeal(capacity) else capacity; + const ideal_capacity_end_vaddr = math.add(u64, sym.n_value, ideal_capacity) catch ideal_capacity; + const capacity_end_vaddr = sym.n_value + capacity; + const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity; + const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment); + if (new_start_vaddr < ideal_capacity_end_vaddr) { + // Additional bookkeeping here to notice if this free list node + // should be deleted because the atom that it points to has grown to take up + // more of the extra capacity. + if (!big_atom.freeListEligible(self)) { + _ = free_list.swapRemove(i); } else { i += 1; } continue; } - } else if (self.last_text_block) |last| { - const new_block_vaddr = mem.alignForwardGeneric(u64, last.getVAddr(self.*) + last.size, alignment); - block_placement = last; - break :blk new_block_vaddr; + // At this point we know that we will place the new atom here. But the + // remaining question is whether there is still yet enough capacity left + // over for there to still be a free list node. + const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr; + const keep_free_list_node = remaining_capacity >= min_text_capacity; + + // Set up the metadata to be updated, after errors are no longer possible. + atom_placement = big_atom; + if (!keep_free_list_node) { + free_list_removal = i; + } + break :blk new_start_vaddr; + } else if (maybe_last_atom.*) |last| { + const last_symbol = last.getSymbol(self); + const ideal_capacity = if (header.isCode()) padToIdeal(last.size) else last.size; + const ideal_capacity_end_vaddr = last_symbol.n_value + ideal_capacity; + const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment); + atom_placement = last; + break :blk new_start_vaddr; } else { - break :blk self.text_section_virtual_address; + break :blk mem.alignForwardGeneric(u64, header.addr, alignment); } }; - const expand_text_section = block_placement == null or block_placement.?.next == null; - if (expand_text_section) { - const needed_size = @intCast(u32, mem.alignForwardGeneric(u64, vaddr + new_block_size - self.text_section_virtual_address, file_alignment)); - if (needed_size > self.text_section_size) { - const current_text_section_virtual_size = mem.alignForwardGeneric(u32, self.text_section_size, section_alignment); - const new_text_section_virtual_size = mem.alignForwardGeneric(u32, needed_size, section_alignment); - if (current_text_section_virtual_size != new_text_section_virtual_size) { - self.size_of_image_dirty = true; - // Write new virtual size - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, new_text_section_virtual_size); - try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 8); - } - - self.text_section_size = needed_size; - self.text_section_size_dirty = true; - } - self.last_text_block = text_block; - } - text_block.text_offset = @intCast(u32, vaddr - self.text_section_virtual_address); - text_block.size = @intCast(u32, new_block_size); - - // This function can also reallocate a text block. - // In this case we need to "unplug" it from its previous location before - // plugging it in to its new location. - if (text_block.prev) |prev| { - prev.next = text_block.next; - } - if (text_block.next) |next| { - next.prev = text_block.prev; + const expand_section = atom_placement == null or atom_placement.?.next == null; + if (expand_section) { + @panic("TODO expand section in allocateAtom"); } - if (block_placement) |big_block| { - text_block.prev = big_block; - text_block.next = big_block.next; - big_block.next = text_block; + if (header.getAlignment() < alignment) { + header.setAlignment(alignment); + } + atom.size = new_atom_size; + atom.alignment = alignment; + + if (atom.prev) |prev| { + prev.next = atom.next; + } + if (atom.next) |next| { + next.prev = atom.prev; + } + + if (atom_placement) |big_atom| { + atom.prev = big_atom; + atom.next = big_atom.next; + big_atom.next = atom; } else { - text_block.prev = null; - text_block.next = null; + atom.prev = null; + atom.next = null; } if (free_list_removal) |i| { - _ = self.text_block_free_list.swapRemove(i); + _ = free_list.swapRemove(i); } + return vaddr; } -fn growTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const block_vaddr = text_block.getVAddr(self.*); - const align_ok = mem.alignBackwardGeneric(u64, block_vaddr, alignment) == block_vaddr; - const need_realloc = !align_ok or new_block_size > text_block.capacity(); - if (!need_realloc) return @as(u64, block_vaddr); - return self.allocateTextBlock(text_block, new_block_size, alignment); +fn allocateSymbol(self: *Coff) !u32 { + const gpa = self.base.allocator; + try self.locals.ensureUnusedCapacity(gpa, 1); + + const index = blk: { + if (self.locals_free_list.popOrNull()) |index| { + log.debug(" (reusing symbol index {d})", .{index}); + break :blk index; + } else { + log.debug(" (allocating symbol index {d})", .{self.locals.items.len}); + const index = @intCast(u32, self.locals.items.len); + _ = self.locals.addOneAssumeCapacity(); + break :blk index; + } + }; + + self.locals.items[index] = .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }; + + return index; } -fn shrinkTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64) void { - text_block.size = @intCast(u32, new_block_size); - if (text_block.capacity() - text_block.size >= minimum_text_block_size) { - self.text_block_free_list.append(self.base.allocator, text_block) catch {}; +pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 { + const gpa = self.base.allocator; + try self.got_entries.ensureUnusedCapacity(gpa, 1); + if (self.got_entries_free_list.popOrNull()) |index| { + log.debug(" (reusing GOT entry index {d})", .{index}); + if (self.got_entries.getIndex(target)) |existing| { + assert(existing == index); + } + self.got_entries.keys()[index] = target; + return index; + } else { + log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len}); + const index = @intCast(u32, self.got_entries.keys().len); + try self.got_entries.putAssumeCapacityNoClobber(target, 0); + return index; } } -fn freeTextBlock(self: *Coff, text_block: *TextBlock) void { +fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, sect_id: u16) !u64 { + const sym = atom.getSymbol(self); + const align_ok = mem.alignBackwardGeneric(u64, sym.value, alignment) == sym.value; + const need_realloc = !align_ok or new_atom_size > atom.capacity(self); + if (!need_realloc) return sym.value; + return self.allocateAtom(atom, new_atom_size, alignment, sect_id); +} + +fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u64, sect_id: u16) void { + _ = self; + _ = atom; + _ = new_block_size; + _ = sect_id; + // TODO check the new capacity, and if it crosses the size threshold into a big enough + // capacity, insert a free list node for it. +} + +fn freeAtom(self: *Coff, atom: *Atom, sect_id: u16) void { + log.debug("freeAtom {*}", .{atom}); + + const free_list = &self.sections.items(.free_list)[sect_id]; var already_have_free_list_node = false; { var i: usize = 0; - // TODO turn text_block_free_list into a hash map - while (i < self.text_block_free_list.items.len) { - if (self.text_block_free_list.items[i] == text_block) { - _ = self.text_block_free_list.swapRemove(i); + // TODO turn free_list into a hash map + while (i < free_list.items.len) { + if (free_list.items[i] == atom) { + _ = free_list.swapRemove(i); continue; } - if (self.text_block_free_list.items[i] == text_block.prev) { + if (free_list.items[i] == atom.prev) { already_have_free_list_node = true; } i += 1; } } - if (self.last_text_block == text_block) { - self.last_text_block = text_block.prev; - } - if (text_block.prev) |prev| { - prev.next = text_block.next; - if (!already_have_free_list_node and prev.freeListEligible()) { - // The free list is heuristics, it doesn't have to be perfect, so we can - // ignore the OOM here. - self.text_block_free_list.append(self.base.allocator, prev) catch {}; - } - } - - if (text_block.next) |next| { - next.prev = text_block.prev; - } -} - -fn writeOffsetTableEntry(self: *Coff, index: usize) !void { - const entry_size = self.base.options.target.cpu.arch.ptrBitWidth() / 8; - const endian = self.base.options.target.cpu.arch.endian(); - - const offset_table_start = self.section_data_offset; - if (self.offset_table_size_dirty) { - const current_raw_size = self.offset_table_size; - const new_raw_size = self.offset_table_size * 2; - log.debug("growing offset table from raw size {} to {}\n", .{ current_raw_size, new_raw_size }); - - // Move the text section to a new place in the executable - const current_text_section_start = self.section_data_offset + current_raw_size; - const new_text_section_start = self.section_data_offset + new_raw_size; - - const amt = try self.base.file.?.copyRangeAll(current_text_section_start, self.base.file.?, new_text_section_start, self.text_section_size); - if (amt != self.text_section_size) return error.InputOutput; - - // Write the new raw size in the .got header - var buf: [8]u8 = undefined; - mem.writeIntLittle(u32, buf[0..4], new_raw_size); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 16); - // Write the new .text section file offset in the .text section header - mem.writeIntLittle(u32, buf[0..4], new_text_section_start); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 20); - - const current_virtual_size = mem.alignForwardGeneric(u32, self.offset_table_size, section_alignment); - const new_virtual_size = mem.alignForwardGeneric(u32, new_raw_size, section_alignment); - // If we had to move in the virtual address space, we need to fix the VAs in the offset table, as well as the virtual address of the `.text` section - // and the virtual size of the `.got` section - - if (new_virtual_size != current_virtual_size) { - log.debug("growing offset table from virtual size {} to {}\n", .{ current_virtual_size, new_virtual_size }); - self.size_of_image_dirty = true; - const va_offset = new_virtual_size - current_virtual_size; - - // Write .got virtual size - mem.writeIntLittle(u32, buf[0..4], new_virtual_size); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 8); - - // Write .text new virtual address - self.text_section_virtual_address = self.text_section_virtual_address + va_offset; - mem.writeIntLittle(u32, buf[0..4], self.text_section_virtual_address - default_image_base); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 12); - - // Fix the VAs in the offset table - for (self.offset_table.items) |*va, idx| { - if (va.* != 0) { - va.* += va_offset; - - switch (entry_size) { - 4 => { - mem.writeInt(u32, buf[0..4], @intCast(u32, va.*), endian); - try self.base.file.?.pwriteAll(buf[0..4], offset_table_start + idx * entry_size); - }, - 8 => { - mem.writeInt(u64, &buf, va.*, endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + idx * entry_size); - }, - else => unreachable, - } - } + const maybe_last_atom = &self.sections.items(.last_atom)[sect_id]; + if (maybe_last_atom.*) |last_atom| { + if (last_atom == atom) { + if (atom.prev) |prev| { + // TODO shrink the section size here + maybe_last_atom.* = prev; + } else { + maybe_last_atom.* = null; } } - self.offset_table_size = new_raw_size; - self.offset_table_size_dirty = false; } - // Write the new entry - switch (entry_size) { - 4 => { - var buf: [4]u8 = undefined; - mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size); - }, - 8 => { - var buf: [8]u8 = undefined; - mem.writeInt(u64, &buf, self.offset_table.items[index], endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size); - }, - else => unreachable, + + if (atom.prev) |prev| { + prev.next = atom.next; + + if (!already_have_free_list_node and prev.freeListEligible(self)) { + // The free list is heuristics, it doesn't have to be perfect, so we can + // ignore the OOM here. + free_list.append(self.base.allocator, prev) catch {}; + } + } else { + atom.prev = null; + } + + if (atom.next) |next| { + next.prev = atom.prev; + } else { + atom.next = null; } } @@ -470,15 +471,19 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live }, }; - return self.finishUpdateDecl(module, func.owner_decl, code); + const sym = try self.updateDeclCode(decl_index, code); + log.debug("updated decl code has sym {}", .{sym}); + + // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. + const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; + return self.updateDeclExports(module, decl_index, decl_exports); } pub fn lowerUnnamedConst(self: *Coff, tv: TypedValue, decl_index: Module.Decl.Index) !u32 { _ = self; _ = tv; _ = decl_index; - log.debug("TODO lowerUnnamedConst for Coff", .{}); - return error.AnalysisFail; + @panic("TODO lowerUnnamedConst"); } pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) !void { @@ -503,9 +508,6 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! } } - // TODO COFF/PE debug information - // TODO Implement exports - var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); @@ -526,49 +528,21 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! }, }; - return self.finishUpdateDecl(module, decl_index, code); -} - -fn finishUpdateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index, code: []const u8) !void { - const decl = module.declPtr(decl_index); - const required_alignment = decl.ty.abiAlignment(self.base.options.target); - const curr_size = decl.link.coff.size; - if (curr_size != 0) { - const capacity = decl.link.coff.capacity(); - const need_realloc = code.len > capacity or - !mem.isAlignedGeneric(u32, decl.link.coff.text_offset, required_alignment); - if (need_realloc) { - const curr_vaddr = self.text_section_virtual_address + decl.link.coff.text_offset; - const vaddr = try self.growTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("growing {s} from 0x{x} to 0x{x}\n", .{ decl.name, curr_vaddr, vaddr }); - if (vaddr != curr_vaddr) { - log.debug(" (writing new offset table entry)\n", .{}); - self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; - try self.writeOffsetTableEntry(decl.link.coff.offset_table_index); - } - } else if (code.len < curr_size) { - self.shrinkTextBlock(&decl.link.coff, code.len); - } - } else { - const vaddr = try self.allocateTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("allocated text block for {s} at 0x{x} (size: {Bi})\n", .{ - mem.sliceTo(decl.name, 0), - vaddr, - std.fmt.fmtIntSizeDec(code.len), - }); - errdefer self.freeTextBlock(&decl.link.coff); - self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; - try self.writeOffsetTableEntry(decl.link.coff.offset_table_index); - } - - // Write the code into the file - try self.base.file.?.pwriteAll(code, self.section_data_offset + self.offset_table_size + decl.link.coff.text_offset); + const sym = try self.updateDeclCode(decl_index, code); + log.debug("updated decl code for {}", .{sym}); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; return self.updateDeclExports(module, decl_index, decl_exports); } +fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) !*coff.Symbol { + _ = self; + _ = decl_index; + _ = code; + @panic("TODO updateDeclCode"); +} + pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { if (build_options.have_llvm) { if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index); @@ -577,9 +551,31 @@ pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); + log.debug("freeDecl {*}", .{decl}); + + const kv = self.decls.fetchRemove(decl_index); + if (kv.?.value) |index| { + self.freeAtom(&decl.link.coff, index); + } + // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. - self.freeTextBlock(&decl.link.coff); - self.offset_table_free_list.append(self.base.allocator, decl.link.coff.offset_table_index) catch {}; + const gpa = self.base.allocator; + const sym_index = decl.link.coff.sym_index; + if (sym_index != 0) { + self.locals_free_list.append(gpa, sym_index) catch {}; + + // Try freeing GOT atom if this decl had one + const got_target = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + if (self.got_entries.getIndex(got_target)) |got_index| { + self.got_entries_free_list.append(gpa, @intCast(u32, got_index)) catch {}; + self.got_entries.values()[got_index] = 0; + log.debug(" adding GOT index {d} to free list (target local@{d})", .{ got_index, sym_index }); + } + + self.locals.items[sym_index].section_number = @intToEnum(coff.SectionNumber, 0); + _ = self.atom_by_index_table.remove(sym_index); + decl.link.coff.sym_index = 0; + } } pub fn updateDeclExports( @@ -625,28 +621,7 @@ pub fn updateDeclExports( if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports); } - const decl = module.declPtr(decl_index); - for (exports) |exp| { - if (exp.options.section) |section_name| { - if (!mem.eql(u8, section_name, ".text")) { - try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); - module.failed_exports.putAssumeCapacityNoClobber( - exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), - ); - continue; - } - } - if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { - self.entry_addr = decl.link.coff.getVAddr(self.*) - default_image_base; - } else { - try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); - module.failed_exports.putAssumeCapacityNoClobber( - exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than 'wWinMainCRTStartup'", .{}), - ); - } - } + @panic("TODO updateDeclExports"); } pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -660,7 +635,7 @@ pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !vo } const use_lld = build_options.have_llvm and self.base.options.use_lld; if (use_lld) { - return self.linkWithLLD(comp, prog_node); + return lld.linkWithLLD(self, comp, prog_node); } switch (self.base.options.output_mode) { .Exe, .Obj => return self.flushModule(comp, prog_node), @@ -682,888 +657,68 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); - const output_mode = self.base.options.output_mode; - log.debug("in flushModule with {}", .{output_mode}); - - var coff_file_header_offset: u32 = 0; - if (output_mode == .Exe) { - // Write the MS-DOS stub and the PE signature - try self.base.file.?.pwriteAll(msdos_stub ++ "PE\x00\x00", 0); - coff_file_header_offset = msdos_stub.len + 4; - } - - // COFF file header - const data_directory_count = 0; - var hdr_data: [112 + data_directory_count * 8 + section_table_size]u8 = undefined; - var index: usize = 0; - - const machine = self.base.options.target.cpu.arch.toCoffMachine(); - if (machine == .Unknown) { - return error.UnsupportedCOFFArchitecture; - } - mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine)); - index += 2; - - // Number of sections (we only use .got, .text) - mem.writeIntLittle(u16, hdr_data[index..][0..2], 2); - index += 2; - // TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - - const optional_header_size = switch (output_mode) { - .Exe => data_directory_count * 8 + switch (self.ptr_width) { - .p32 => @as(u16, 96), - .p64 => 112, - }, - else => 0, - }; - - const default_offset_table_size = file_alignment; - const default_size_of_code = 0; - - // Size of file when loaded in memory - const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + default_size_of_code, section_alignment); - - mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); - index += 2; - - // Characteristics - var flags: std.coff.CoffHeaderFlags = .{ - // TODO Remove debug info stripped flag when necessary - .DEBUG_STRIPPED = 1, - .RELOCS_STRIPPED = 1, - }; - if (output_mode == .Exe) { - flags.EXECUTABLE_IMAGE = 1; - } - switch (self.ptr_width) { - .p32 => flags.@"32BIT_MACHINE" = 1, - .p64 => flags.LARGE_ADDRESS_AWARE = 1, - } - mem.writeIntLittle(u16, hdr_data[index..][0..2], @bitCast(u16, flags)); - index += 2; - - assert(index == 20); - try self.base.file.?.pwriteAll(hdr_data[0..index], coff_file_header_offset); - - if (output_mode == .Exe) { - self.optional_header_offset = coff_file_header_offset + 20; - // Optional header - index = 0; - mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) { - .p32 => @as(u16, 0x10b), - .p64 => 0x20b, - }); - index += 2; - - // Linker version (u8 + u8) - mem.set(u8, hdr_data[index..][0..2], 0); - index += 2; - - // SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32) - mem.set(u8, hdr_data[index..][0..20], 0); - index += 20; - - if (self.ptr_width == .p32) { - // Base of data relative to the image base (UNUSED) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Image base address - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_image_base); - index += 4; - } else { - // Image base address - mem.writeIntLittle(u64, hdr_data[index..][0..8], default_image_base); - index += 8; - } - - // Section alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment); - index += 4; - // File alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment); - index += 4; - // Required OS version, 6.0 is vista - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Image version - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Required subsystem version, same as OS version - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Reserved zeroes (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // CheckSum (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Subsystem, TODO: Let users specify the subsystem, always CUI for now - mem.writeIntLittle(u16, hdr_data[index..][0..2], 3); - index += 2; - // DLL characteristics - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0); - index += 2; - - switch (self.ptr_width) { - .p32 => { - // Size of stack reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - // Size of heap reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - }, - .p64 => { - // Size of stack reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - // Size of heap reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - }, - } - - // Reserved zeroes - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Number of data directories - mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count); - index += 4; - // Initialize data directories to zero - mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0); - index += data_directory_count * 8; - - assert(index == optional_header_size); - } - - // Write section table. - // First, the .got section - hdr_data[index..][0..8].* = ".got\x00\x00\x00\x00".*; - index += 8; - if (output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_INITIALIZED_DATA = 1, - .MEM_READ = 1, - })); - index += 4; - // Then, the .text section - hdr_data[index..][0..8].* = ".text\x00\x00\x00".*; - index += 8; - if (output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset + default_offset_table_size); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_CODE = 1, - .MEM_EXECUTE = 1, - .MEM_READ = 1, - .MEM_WRITE = 1, - })); - index += 4; - - assert(index == optional_header_size + section_table_size); - try self.base.file.?.pwriteAll(hdr_data[0..index], self.optional_header_offset); - try self.base.file.?.setEndPos(self.section_data_offset + default_offset_table_size + default_size_of_code); - - if (self.text_section_size_dirty) { - // Write the new raw size in the .text header - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, self.text_section_size); - try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 16); - try self.base.file.?.setEndPos(self.section_data_offset + self.offset_table_size + self.text_section_size); - self.text_section_size_dirty = false; - } - - if (self.base.options.output_mode == .Exe and self.size_of_image_dirty) { - const new_size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + self.text_section_size, section_alignment); - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, new_size_of_image); - try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 56); - self.size_of_image_dirty = false; - } - if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; } else { log.debug("flushing. no_entry_point_found = false\n", .{}); self.error_flags.no_entry_point_found = false; - - if (self.base.options.output_mode == .Exe) { - // Write AddressOfEntryPoint - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, self.entry_addr.?); - try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 16); - } } } -fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { - const tracy = trace(@src()); - defer tracy.end(); - - var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { - const use_stage1 = build_options.have_stage1 and self.base.options.use_stage1; - if (use_stage1) { - const obj_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = self.base.options.root_name, - .target = self.base.options.target, - .output_mode = .Obj, - }); - switch (self.base.options.cache_mode) { - .incremental => break :blk try module.zig_cache_artifact_directory.join( - arena, - &[_][]const u8{obj_basename}, - ), - .whole => break :blk try fs.path.join(arena, &.{ - fs.path.dirname(full_out_path).?, obj_basename, - }), - } - } - - try self.flushModule(comp, prog_node); - - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? }); - } else { - break :blk self.base.intermediary_basename.?; - } - } else null; - - var sub_prog_node = prog_node.start("LLD Link", 0); - sub_prog_node.activate(); - sub_prog_node.context.refresh(); - defer sub_prog_node.end(); - - const is_lib = self.base.options.output_mode == .Lib; - const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; - const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; - const link_in_crt = self.base.options.link_libc and is_exe_or_dyn_lib; - const target = self.base.options.target; - - // See link/Elf.zig for comments on how this mechanism works. - const id_symlink_basename = "lld.id"; - - var man: Cache.Manifest = undefined; - defer if (!self.base.options.disable_lld_caching) man.deinit(); - - var digest: [Cache.hex_digest_len]u8 = undefined; - - if (!self.base.options.disable_lld_caching) { - man = comp.cache_parent.obtain(); - self.base.releaseLock(); - - comptime assert(Compilation.link_hash_implementation_version == 7); - - for (self.base.options.objects) |obj| { - _ = try man.addFile(obj.path, null); - man.hash.add(obj.must_link); - } - for (comp.c_object_table.keys()) |key| { - _ = try man.addFile(key.status.success.object_path, null); - } - try man.addOptionalFile(module_obj_path); - man.hash.addOptionalBytes(self.base.options.entry); - man.hash.addOptional(self.base.options.stack_size_override); - man.hash.addOptional(self.base.options.image_base_override); - man.hash.addListOfBytes(self.base.options.lib_dirs); - man.hash.add(self.base.options.skip_linker_dependencies); - if (self.base.options.link_libc) { - man.hash.add(self.base.options.libc_installation != null); - if (self.base.options.libc_installation) |libc_installation| { - man.hash.addBytes(libc_installation.crt_dir.?); - if (target.abi == .msvc) { - man.hash.addBytes(libc_installation.msvc_lib_dir.?); - man.hash.addBytes(libc_installation.kernel32_lib_dir.?); - } - } - } - link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); - man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys()); - man.hash.addOptional(self.base.options.subsystem); - man.hash.add(self.base.options.is_test); - man.hash.add(self.base.options.tsaware); - man.hash.add(self.base.options.nxcompat); - man.hash.add(self.base.options.dynamicbase); - // strip does not need to go into the linker hash because it is part of the hash namespace - man.hash.addOptional(self.base.options.major_subsystem_version); - man.hash.addOptional(self.base.options.minor_subsystem_version); - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| blk: { - log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - // Hot diggity dog! The output binary is already there. - self.base.lock = man.toOwnedLock(); - return; - } - log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; - } - - if (self.base.options.output_mode == .Obj) { - // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy - // here. TODO: think carefully about how we can avoid this redundant operation when doing - // build-obj. See also the corresponding TODO in linkAsArchive. - const the_object_path = blk: { - if (self.base.options.objects.len != 0) - break :blk self.base.options.objects[0].path; - - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; - - if (module_obj_path) |p| - break :blk p; - - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - // This can happen when using --enable-cache and using the stage1 backend. In this case - // we can skip the file copy. - if (!mem.eql(u8, the_object_path, full_out_path)) { - try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); - } - } else { - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(self.base.allocator); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" }); - - try argv.append("-ERRORLIMIT:0"); - try argv.append("-NOLOGO"); - if (!self.base.options.strip) { - try argv.append("-DEBUG"); - } - if (self.base.options.lto) { - switch (self.base.options.optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("-OPT:lldlto=2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), - } - } - if (self.base.options.output_mode == .Exe) { - const stack_size = self.base.options.stack_size_override orelse 16777216; - try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); - } - if (self.base.options.image_base_override) |image_base| { - try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base})); - } - - if (target.cpu.arch == .i386) { - try argv.append("-MACHINE:X86"); - } else if (target.cpu.arch == .x86_64) { - try argv.append("-MACHINE:X64"); - } else if (target.cpu.arch.isARM()) { - if (target.cpu.arch.ptrBitWidth() == 32) { - try argv.append("-MACHINE:ARM"); - } else { - try argv.append("-MACHINE:ARM64"); - } - } - - for (self.base.options.force_undefined_symbols.keys()) |symbol| { - try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); - } - - if (is_dyn_lib) { - try argv.append("-DLL"); - } - - if (self.base.options.entry) |entry| { - try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry})); - } - - if (self.base.options.tsaware) { - try argv.append("-tsaware"); - } - if (self.base.options.nxcompat) { - try argv.append("-nxcompat"); - } - if (self.base.options.dynamicbase) { - try argv.append("-dynamicbase"); - } - - try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); - - if (self.base.options.implib_emit) |emit| { - const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); - try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); - } - - if (self.base.options.link_libc) { - if (self.base.options.libc_installation) |libc_installation| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); - - if (target.abi == .msvc) { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); - } - } - } - - for (self.base.options.lib_dirs) |lib_dir| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir})); - } - - try argv.ensureUnusedCapacity(self.base.options.objects.len); - for (self.base.options.objects) |obj| { - if (obj.must_link) { - argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{s}", .{obj.path})); - } else { - argv.appendAssumeCapacity(obj.path); - } - } - - for (comp.c_object_table.keys()) |key| { - try argv.append(key.status.success.object_path); - } - - if (module_obj_path) |p| { - try argv.append(p); - } - - const resolved_subsystem: ?std.Target.SubSystem = blk: { - if (self.base.options.subsystem) |explicit| break :blk explicit; - switch (target.os.tag) { - .windows => { - if (self.base.options.module) |module| { - if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib) - break :blk null; - if (module.stage1_flags.have_c_main or self.base.options.is_test or - module.stage1_flags.have_winmain_crt_startup or - module.stage1_flags.have_wwinmain_crt_startup) - { - break :blk .Console; - } - if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain) - break :blk .Windows; - } - }, - .uefi => break :blk .EfiApplication, - else => {}, - } - break :blk null; - }; - - const Mode = enum { uefi, win32 }; - const mode: Mode = mode: { - if (resolved_subsystem) |subsystem| { - const subsystem_suffix = ss: { - if (self.base.options.major_subsystem_version) |major| { - if (self.base.options.minor_subsystem_version) |minor| { - break :ss try allocPrint(arena, ",{d}.{d}", .{ major, minor }); - } else { - break :ss try allocPrint(arena, ",{d}", .{major}); - } - } - break :ss ""; - }; - - switch (subsystem) { - .Console => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .EfiApplication => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiBootServiceDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRom => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRuntimeDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .Native => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Posix => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Windows => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - } - } else if (target.os.tag == .uefi) { - break :mode .uefi; - } else { - break :mode .win32; - } - }; - - switch (mode) { - .uefi => try argv.appendSlice(&[_][]const u8{ - "-BASE:0", - "-ENTRY:EfiMain", - "-OPT:REF", - "-SAFESEH:NO", - "-MERGE:.rdata=.data", - "-ALIGN:32", - "-NODEFAULTLIB", - "-SECTION:.xdata,D", - }), - .win32 => { - if (link_in_crt) { - if (target.abi.isGnu()) { - try argv.append("-lldmingw"); - - if (target.cpu.arch == .i386) { - try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); - } else { - try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); - } - - if (is_dyn_lib) { - try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.obj")); - if (target.cpu.arch == .i386) { - try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); - } else { - try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); - } - } else { - try argv.append(try comp.get_libc_crt_file(arena, "crt2.obj")); - } - - try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib")); - try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib")); - try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib")); - - for (mingw.always_link_libs) |name| { - if (!self.base.options.system_libs.contains(name)) { - const lib_basename = try allocPrint(arena, "{s}.lib", .{name}); - try argv.append(try comp.get_libc_crt_file(arena, lib_basename)); - } - } - } else { - const lib_str = switch (self.base.options.link_mode) { - .Dynamic => "", - .Static => "lib", - }; - const d_str = switch (self.base.options.optimize_mode) { - .Debug => "d", - else => "", - }; - switch (self.base.options.link_mode) { - .Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})), - .Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})), - } - - try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str })); - try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str })); - - //Visual C++ 2015 Conformance Changes - //https://msdn.microsoft.com/en-us/library/bb531344.aspx - try argv.append("legacy_stdio_definitions.lib"); - - // msvcrt depends on kernel32 and ntdll - try argv.append("kernel32.lib"); - try argv.append("ntdll.lib"); - } - } else { - try argv.append("-NODEFAULTLIB"); - if (!is_lib) { - if (self.base.options.module) |module| { - if (module.stage1_flags.have_winmain_crt_startup) { - try argv.append("-ENTRY:WinMainCRTStartup"); - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } - } - }, - } - - // libc++ dep - if (self.base.options.link_libcpp) { - try argv.append(comp.libcxxabi_static_lib.?.full_object_path); - try argv.append(comp.libcxx_static_lib.?.full_object_path); - } - - // libunwind dep - if (self.base.options.link_libunwind) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - } - - if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) { - if (!self.base.options.link_libc) { - if (comp.libc_static_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - // MinGW doesn't provide libssp symbols - if (target.abi.isGnu()) { - if (comp.libssp_static_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - // MSVC compiler_rt is missing some stuff, so we build it unconditionally but - // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. - if (comp.compiler_rt_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - - try argv.ensureUnusedCapacity(self.base.options.system_libs.count()); - for (self.base.options.system_libs.keys()) |key| { - const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); - if (comp.crt_files.get(lib_basename)) |crt_file| { - argv.appendAssumeCapacity(crt_file.full_object_path); - continue; - } - if (try self.findLib(arena, lib_basename)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - if (target.abi.isGnu()) { - const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); - if (try self.findLib(arena, fallback_name)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - } - log.err("DLL import library for -l{s} not found", .{key}); - return error.DllImportLibraryNotFound; - } - - if (self.base.options.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv.items[1..]); - } - - if (std.process.can_spawn) { - // If possible, we run LLD as a child process because it does not always - // behave properly as a library, unfortunately. - // https://github.com/ziglang/zig/issues/3825 - var child = std.ChildProcess.init(argv.items, arena); - if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; - - const term = child.spawnAndWait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - switch (term) { - .Exited => |code| { - if (code != 0) { - std.process.exit(code); - } - }, - else => std.process.abort(), - } - } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); - - const term = child.wait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO parse this output and surface with the Compilation API rather than - // directly outputting to stderr here. - std.debug.print("{s}", .{stderr}); - return error.LLDReportedFailure; - } - }, - else => { - log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); - return error.LLDCrashed; - }, - } - - if (stderr.len != 0) { - log.warn("unexpected LLD stderr:\n{s}", .{stderr}); - } - } - } else { - const exit_code = try lldMain(arena, argv.items, false); - if (exit_code != 0) { - if (comp.clang_passthrough_mode) { - std.process.exit(exit_code); - } else { - return error.LLDReportedFailure; - } - } - } - } - - if (!self.base.options.disable_lld_caching) { - // Update the file with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - self.base.lock = man.toOwnedLock(); - } -} - -fn findLib(self: *Coff, arena: Allocator, name: []const u8) !?[]const u8 { - for (self.base.options.lib_dirs) |lib_dir| { - const full_path = try fs.path.join(arena, &.{ lib_dir, name }); - fs.cwd().access(full_path, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| return e, - }; - return full_path; - } - return null; -} - pub fn getDeclVAddr( self: *Coff, decl_index: Module.Decl.Index, reloc_info: link.File.RelocInfo, ) !u64 { + _ = self; + _ = decl_index; _ = reloc_info; - const mod = self.base.options.module.?; - const decl = mod.declPtr(decl_index); - assert(self.llvm_object == null); - return self.text_section_virtual_address + decl.link.coff.text_offset; + @panic("TODO getDeclVAddr"); } pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void { _ = self; _ = module; _ = decl; - // TODO Implement this + log.debug("TODO implement updateDeclLineNumber", .{}); } -pub fn deinit(self: *Coff) void { - if (build_options.have_llvm) { - if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); - } - - self.text_block_free_list.deinit(self.base.allocator); - self.offset_table.deinit(self.base.allocator); - self.offset_table_free_list.deinit(self.base.allocator); +pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { + // TODO https://github.com/ziglang/zig/issues/1284 + return math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch + math.maxInt(@TypeOf(actual_size)); +} + +/// Returns pointer-to-symbol described by `sym_with_loc` descriptor. +pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol { + assert(sym_loc.file == null); // TODO linking object files + return &self.locals.items[sym_loc.sym_index]; +} + +/// Returns symbol described by `sym_with_loc` descriptor. +pub fn getSymbol(self: *Coff, sym_loc: SymbolWithLoc) coff.Symbol { + return self.getSymbolPtr(sym_loc).*; +} + +/// Returns name of the symbol described by `sym_with_loc` descriptor. +pub fn getSymbolName(self: *Coff, sym_loc: SymbolWithLoc) []const u8 { + assert(sym_loc.file == null); // TODO linking object files + const sym = self.locals.items[sym_loc.sym_index]; + const offset = sym.getNameOffset() orelse return sym.getName().?; + return self.strtab.get(offset).?; +} + +/// Returns atom if there is an atom referenced by the symbol described by `sym_with_loc` descriptor. +/// Returns null on failure. +pub fn getAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { + assert(sym_loc.file == null); // TODO linking with object files + return self.atom_by_index_table.get(sym_loc.sym_index); +} + +/// Returns GOT atom that references `sym_with_loc` if one exists. +/// Returns null otherwise. +pub fn getGotAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { + const got_index = self.got_entries.get(sym_loc) orelse return null; + return self.atom_by_index_table.get(got_index); } diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig new file mode 100644 index 0000000000..b61e77f53e --- /dev/null +++ b/src/link/Coff/Atom.zig @@ -0,0 +1,85 @@ +const Atom = @This(); + +const std = @import("std"); +const coff = std.coff; + +const Allocator = std.mem.Allocator; + +const Coff = @import("../Coff.zig"); +const SymbolWithLoc = Coff.SymbolWithLoc; + +/// Each decl always gets a local symbol with the fully qualified name. +/// The vaddr and size are found here directly. +/// The file offset is found by computing the vaddr offset from the section vaddr +/// the symbol references, and adding that to the file offset of the section. +/// If this field is 0, it means the codegen size = 0 and there is no symbol or +/// offset table entry. +sym_index: u32, + +/// null means symbol defined by Zig source. +file: ?u32, + +/// Used size of the atom +size: u64, + +/// Alignment of the atom +alignment: u32, + +/// Points to the previous and next neighbors, based on the `text_offset`. +/// This can be used to find, for example, the capacity of this `Atom`. +prev: ?*Atom, +next: ?*Atom, + +pub const empty = Atom{ + .sym_index = 0, + .file = null, + .size = 0, + .alignment = 0, + .prev = null, + .next = null, +}; + +pub fn deinit(self: *Atom, gpa: Allocator) void { + _ = self; + _ = gpa; +} + +pub fn getSymbol(self: Atom, coff_file: *Coff) coff.Symbol { + return self.getSymbolPtr(coff_file).*; +} + +pub fn getSymbolPtr(self: Atom, coff_file: *Coff) *coff.Symbol { + return coff_file.getSymbolPtr(.{ + .sym_index = self.sym_index, + .file = self.file, + }); +} + +pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc { + return .{ .sym_index = self.sym_index, .file = self.file }; +} + +/// Returns how much room there is to grow in virtual address space. +pub fn capacity(self: Atom, coff_file: *Coff) u64 { + const self_sym = self.getSymbol(coff_file); + if (self.next) |next| { + const next_sym = next.getSymbol(coff_file); + return next_sym.value - self_sym.value; + } else { + // We are the last atom. + // The capacity is limited only by virtual address space. + return std.math.maxInt(u64) - self_sym.value; + } +} + +pub fn freeListEligible(self: Atom, coff_file: *Coff) bool { + // No need to keep a free list node for the last atom. + const next = self.next orelse return false; + const self_sym = self.getSymbol(coff_file); + const next_sym = next.getSymbol(coff_file); + const cap = next_sym.value - self_sym.value; + const ideal_cap = Coff.padToIdeal(self.size); + if (cap <= ideal_cap) return false; + const surplus = cap - ideal_cap; + return surplus >= Coff.min_text_capacity; +} diff --git a/src/link/Coff/lld.zig b/src/link/Coff/lld.zig new file mode 100644 index 0000000000..feae0134e9 --- /dev/null +++ b/src/link/Coff/lld.zig @@ -0,0 +1,602 @@ +const std = @import("std"); +const build_options = @import("build_options"); +const allocPrint = std.fmt.allocPrint; +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.link); +const mem = std.mem; + +const mingw = @import("../../mingw.zig"); +const link = @import("../../link.zig"); +const lldMain = @import("../../main.zig").lldMain; +const trace = @import("../../tracy.zig").trace; + +const Allocator = mem.Allocator; + +const Cache = @import("../../Cache.zig"); +const Coff = @import("../Coff.zig"); +const Compilation = @import("../../Compilation.zig"); + +pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { + const tracy = trace(@src()); + defer tracy.end(); + + var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { + const use_stage1 = build_options.have_stage1 and self.base.options.use_stage1; + if (use_stage1) { + const obj_basename = try std.zig.binNameAlloc(arena, .{ + .root_name = self.base.options.root_name, + .target = self.base.options.target, + .output_mode = .Obj, + }); + switch (self.base.options.cache_mode) { + .incremental => break :blk try module.zig_cache_artifact_directory.join( + arena, + &[_][]const u8{obj_basename}, + ), + .whole => break :blk try fs.path.join(arena, &.{ + fs.path.dirname(full_out_path).?, obj_basename, + }), + } + } + + try self.flushModule(comp, prog_node); + + if (fs.path.dirname(full_out_path)) |dirname| { + break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? }); + } else { + break :blk self.base.intermediary_basename.?; + } + } else null; + + var sub_prog_node = prog_node.start("LLD Link", 0); + sub_prog_node.activate(); + sub_prog_node.context.refresh(); + defer sub_prog_node.end(); + + const is_lib = self.base.options.output_mode == .Lib; + const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; + const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; + const link_in_crt = self.base.options.link_libc and is_exe_or_dyn_lib; + const target = self.base.options.target; + + // See link/Elf.zig for comments on how this mechanism works. + const id_symlink_basename = "lld.id"; + + var man: Cache.Manifest = undefined; + defer if (!self.base.options.disable_lld_caching) man.deinit(); + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!self.base.options.disable_lld_caching) { + man = comp.cache_parent.obtain(); + self.base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 7); + + for (self.base.options.objects) |obj| { + _ = try man.addFile(obj.path, null); + man.hash.add(obj.must_link); + } + for (comp.c_object_table.keys()) |key| { + _ = try man.addFile(key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + man.hash.addOptionalBytes(self.base.options.entry); + man.hash.addOptional(self.base.options.stack_size_override); + man.hash.addOptional(self.base.options.image_base_override); + man.hash.addListOfBytes(self.base.options.lib_dirs); + man.hash.add(self.base.options.skip_linker_dependencies); + if (self.base.options.link_libc) { + man.hash.add(self.base.options.libc_installation != null); + if (self.base.options.libc_installation) |libc_installation| { + man.hash.addBytes(libc_installation.crt_dir.?); + if (target.abi == .msvc) { + man.hash.addBytes(libc_installation.msvc_lib_dir.?); + man.hash.addBytes(libc_installation.kernel32_lib_dir.?); + } + } + } + link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); + man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys()); + man.hash.addOptional(self.base.options.subsystem); + man.hash.add(self.base.options.is_test); + man.hash.add(self.base.options.tsaware); + man.hash.add(self.base.options.nxcompat); + man.hash.add(self.base.options.dynamicbase); + // strip does not need to go into the linker hash because it is part of the hash namespace + man.hash.addOptional(self.base.options.major_subsystem_version); + man.hash.addOptional(self.base.options.minor_subsystem_version); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + self.base.lock = man.toOwnedLock(); + return; + } + log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + if (self.base.options.output_mode == .Obj) { + // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (self.base.options.objects.len != 0) + break :blk self.base.options.objects[0].path; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.keys()[0].status.success.object_path; + + if (module_obj_path) |p| + break :blk p; + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + // This can happen when using --enable-cache and using the stage1 backend. In this case + // we can skip the file copy. + if (!mem.eql(u8, the_object_path, full_out_path)) { + try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); + } + } else { + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" }); + + try argv.append("-ERRORLIMIT:0"); + try argv.append("-NOLOGO"); + if (!self.base.options.strip) { + try argv.append("-DEBUG"); + } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-OPT:lldlto=2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), + } + } + if (self.base.options.output_mode == .Exe) { + const stack_size = self.base.options.stack_size_override orelse 16777216; + try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); + } + if (self.base.options.image_base_override) |image_base| { + try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base})); + } + + if (target.cpu.arch == .i386) { + try argv.append("-MACHINE:X86"); + } else if (target.cpu.arch == .x86_64) { + try argv.append("-MACHINE:X64"); + } else if (target.cpu.arch.isARM()) { + if (target.cpu.arch.ptrBitWidth() == 32) { + try argv.append("-MACHINE:ARM"); + } else { + try argv.append("-MACHINE:ARM64"); + } + } + + for (self.base.options.force_undefined_symbols.keys()) |symbol| { + try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); + } + + if (is_dyn_lib) { + try argv.append("-DLL"); + } + + if (self.base.options.entry) |entry| { + try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry})); + } + + if (self.base.options.tsaware) { + try argv.append("-tsaware"); + } + if (self.base.options.nxcompat) { + try argv.append("-nxcompat"); + } + if (self.base.options.dynamicbase) { + try argv.append("-dynamicbase"); + } + + try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); + + if (self.base.options.implib_emit) |emit| { + const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); + try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + } + + if (self.base.options.link_libc) { + if (self.base.options.libc_installation) |libc_installation| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); + + if (target.abi == .msvc) { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); + } + } + } + + for (self.base.options.lib_dirs) |lib_dir| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir})); + } + + try argv.ensureUnusedCapacity(self.base.options.objects.len); + for (self.base.options.objects) |obj| { + if (obj.must_link) { + argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{s}", .{obj.path})); + } else { + argv.appendAssumeCapacity(obj.path); + } + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(key.status.success.object_path); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + + const resolved_subsystem: ?std.Target.SubSystem = blk: { + if (self.base.options.subsystem) |explicit| break :blk explicit; + switch (target.os.tag) { + .windows => { + if (self.base.options.module) |module| { + if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib) + break :blk null; + if (module.stage1_flags.have_c_main or self.base.options.is_test or + module.stage1_flags.have_winmain_crt_startup or + module.stage1_flags.have_wwinmain_crt_startup) + { + break :blk .Console; + } + if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain) + break :blk .Windows; + } + }, + .uefi => break :blk .EfiApplication, + else => {}, + } + break :blk null; + }; + + const Mode = enum { uefi, win32 }; + const mode: Mode = mode: { + if (resolved_subsystem) |subsystem| { + const subsystem_suffix = ss: { + if (self.base.options.major_subsystem_version) |major| { + if (self.base.options.minor_subsystem_version) |minor| { + break :ss try allocPrint(arena, ",{d}.{d}", .{ major, minor }); + } else { + break :ss try allocPrint(arena, ",{d}", .{major}); + } + } + break :ss ""; + }; + + switch (subsystem) { + .Console => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .EfiApplication => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiBootServiceDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRom => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRuntimeDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .Native => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Posix => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Windows => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + } + } else if (target.os.tag == .uefi) { + break :mode .uefi; + } else { + break :mode .win32; + } + }; + + switch (mode) { + .uefi => try argv.appendSlice(&[_][]const u8{ + "-BASE:0", + "-ENTRY:EfiMain", + "-OPT:REF", + "-SAFESEH:NO", + "-MERGE:.rdata=.data", + "-ALIGN:32", + "-NODEFAULTLIB", + "-SECTION:.xdata,D", + }), + .win32 => { + if (link_in_crt) { + if (target.abi.isGnu()) { + try argv.append("-lldmingw"); + + if (target.cpu.arch == .i386) { + try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); + } else { + try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); + } + + if (is_dyn_lib) { + try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.obj")); + if (target.cpu.arch == .i386) { + try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); + } else { + try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); + } + } else { + try argv.append(try comp.get_libc_crt_file(arena, "crt2.obj")); + } + + try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib")); + try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib")); + try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib")); + + for (mingw.always_link_libs) |name| { + if (!self.base.options.system_libs.contains(name)) { + const lib_basename = try allocPrint(arena, "{s}.lib", .{name}); + try argv.append(try comp.get_libc_crt_file(arena, lib_basename)); + } + } + } else { + const lib_str = switch (self.base.options.link_mode) { + .Dynamic => "", + .Static => "lib", + }; + const d_str = switch (self.base.options.optimize_mode) { + .Debug => "d", + else => "", + }; + switch (self.base.options.link_mode) { + .Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})), + .Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})), + } + + try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str })); + try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str })); + + //Visual C++ 2015 Conformance Changes + //https://msdn.microsoft.com/en-us/library/bb531344.aspx + try argv.append("legacy_stdio_definitions.lib"); + + // msvcrt depends on kernel32 and ntdll + try argv.append("kernel32.lib"); + try argv.append("ntdll.lib"); + } + } else { + try argv.append("-NODEFAULTLIB"); + if (!is_lib) { + if (self.base.options.module) |module| { + if (module.stage1_flags.have_winmain_crt_startup) { + try argv.append("-ENTRY:WinMainCRTStartup"); + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } + } + }, + } + + // libc++ dep + if (self.base.options.link_libcpp) { + try argv.append(comp.libcxxabi_static_lib.?.full_object_path); + try argv.append(comp.libcxx_static_lib.?.full_object_path); + } + + // libunwind dep + if (self.base.options.link_libunwind) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + } + + if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) { + if (!self.base.options.link_libc) { + if (comp.libc_static_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + // MinGW doesn't provide libssp symbols + if (target.abi.isGnu()) { + if (comp.libssp_static_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + // MSVC compiler_rt is missing some stuff, so we build it unconditionally but + // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. + if (comp.compiler_rt_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + + try argv.ensureUnusedCapacity(self.base.options.system_libs.count()); + for (self.base.options.system_libs.keys()) |key| { + const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); + if (comp.crt_files.get(lib_basename)) |crt_file| { + argv.appendAssumeCapacity(crt_file.full_object_path); + continue; + } + if (try findLib(arena, lib_basename, self.base.options.lib_dirs)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + if (target.abi.isGnu()) { + const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); + if (try findLib(arena, fallback_name, self.base.options.lib_dirs)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + } + log.err("DLL import library for -l{s} not found", .{key}); + return error.DllImportLibraryNotFound; + } + + if (self.base.options.verbose_link) { + // Skip over our own name so that the LLD linker name is the first argv item. + Compilation.dump_argv(argv.items[1..]); + } + + if (std.process.can_spawn) { + // If possible, we run LLD as a child process because it does not always + // behave properly as a library, unfortunately. + // https://github.com/ziglang/zig/issues/3825 + var child = std.ChildProcess.init(argv.items, arena); + if (comp.clang_passthrough_mode) { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + const term = child.spawnAndWait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + switch (term) { + .Exited => |code| { + if (code != 0) { + std.process.exit(code); + } + }, + else => std.process.abort(), + } + } else { + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Pipe; + + try child.spawn(); + + const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); + + const term = child.wait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO parse this output and surface with the Compilation API rather than + // directly outputting to stderr here. + std.debug.print("{s}", .{stderr}); + return error.LLDReportedFailure; + } + }, + else => { + log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); + return error.LLDCrashed; + }, + } + + if (stderr.len != 0) { + log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + } + } + } else { + const exit_code = try lldMain(arena, argv.items, false); + if (exit_code != 0) { + if (comp.clang_passthrough_mode) { + std.process.exit(exit_code); + } else { + return error.LLDReportedFailure; + } + } + } + } + + if (!self.base.options.disable_lld_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + self.base.lock = man.toOwnedLock(); + } +} + +fn findLib(arena: Allocator, name: []const u8, lib_dirs: []const []const u8) !?[]const u8 { + for (lib_dirs) |lib_dir| { + const full_path = try fs.path.join(arena, &.{ lib_dir, name }); + fs.cwd().access(full_path, .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + }; + return full_path; + } + return null; +} diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 764e4e71b2..af25441066 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -26,7 +26,7 @@ const trace = @import("../tracy.zig").trace; const Air = @import("../Air.zig"); const Allocator = mem.Allocator; const Archive = @import("MachO/Archive.zig"); -const Atom = @import("MachO/Atom.zig"); +pub const Atom = @import("MachO/Atom.zig"); const Cache = @import("../Cache.zig"); const CodeSignature = @import("MachO/CodeSignature.zig"); const Compilation = @import("../Compilation.zig"); @@ -44,7 +44,6 @@ const Type = @import("../type.zig").Type; const TypedValue = @import("../TypedValue.zig"); const Value = @import("../value.zig").Value; -pub const TextBlock = Atom; pub const DebugSymbols = @import("MachO/DebugSymbols.zig"); pub const base_tag: File.Tag = File.Tag.macho; diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index c2aa562db5..a7dc6391c2 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -18,7 +18,6 @@ const Dwarf = @import("../Dwarf.zig"); const MachO = @import("../MachO.zig"); const Module = @import("../../Module.zig"); const StringTable = @import("../strtab.zig").StringTable; -const TextBlock = MachO.TextBlock; const Type = @import("../../type.zig").Type; base: *MachO, From ed481e3837218659ab1c18d9d4b836cf656307b9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 26 Aug 2022 18:02:12 +0200 Subject: [PATCH 14/40] coff: write headers to file --- lib/std/coff.zig | 15 +++ src/link/Coff.zig | 238 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 244 insertions(+), 9 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 1640d44fb5..08f8e7e8a3 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -787,6 +787,21 @@ pub const MachineType = enum(u16) { /// MIPS little-endian WCE v2 WCEMIPSV2 = 0x169, + pub fn fromTargetCpuArch(arch: std.Target.Cpu.Arch) MachineType { + return switch (arch) { + .arm => .ARM, + .powerpc => .POWERPC, + .riscv32 => .RISCV32, + .thumb => .Thumb, + .i386 => .I386, + .aarch64 => .ARM64, + .riscv64 => .RISCV64, + .x86_64 => .X64, + // 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, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 67172abcd4..a270843fdd 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -49,11 +49,13 @@ locals_free_list: std.ArrayListUnmanaged(u32) = .{}, strtab: StringTable(.strtab) = .{}, +symtab_offset: ?u32 = null, + got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, /// Virtual address of the entry point procedure relative to image base. -entry_addr: ?u64 = null, +entry_addr: ?u32 = null, /// Table of Decls that are currently alive. /// We store them here so that we can properly dispose of any allocated @@ -117,10 +119,6 @@ const ideal_factor = 3; const minimum_text_block_size = 64; pub const min_text_capacity = padToIdeal(minimum_text_block_size); -/// We commit 0x1000 = 4096 bytes of space to the headers. -/// This should be plenty for any potential future extensions. -const default_headerpad_size: u32 = 0x1000; - pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Coff { assert(options.target.ofmt == .coff); @@ -208,8 +206,11 @@ pub fn deinit(self: *Coff) void { } fn populateMissingMetadata(self: *Coff) !void { - _ = self; - @panic("TODO populateMissingMetadata"); + log.debug("{x}", .{msdos_stub.len}); + + if (self.text_section_index == null) {} + if (self.got_section_index == null) {} + if (self.symtab_offset == null) {} } pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { @@ -540,7 +541,8 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) _ = self; _ = decl_index; _ = code; - @panic("TODO updateDeclCode"); + log.debug("TODO updateDeclCode", .{}); + return &self.locals.items[0]; } pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { @@ -621,7 +623,7 @@ pub fn updateDeclExports( if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports); } - @panic("TODO updateDeclExports"); + log.debug("TODO updateDeclExports", .{}); } pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -657,6 +659,8 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + try self.writeHeader(); + if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; @@ -664,6 +668,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod log.debug("flushing. no_entry_point_found = false\n", .{}); self.error_flags.no_entry_point_found = false; } + self.error_flags.no_entry_point_found = false; } pub fn getDeclVAddr( @@ -684,12 +689,227 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v log.debug("TODO implement updateDeclLineNumber", .{}); } +fn writeHeader(self: *Coff) !void { + const gpa = self.base.allocator; + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); + const writer = buffer.writer(); + + try buffer.ensureUnusedCapacity(msdos_stub.len + 8); + writer.writeAll(msdos_stub) catch unreachable; + writer.writeByteNTimes(0, 4) catch unreachable; // align to 8 bytes + writer.writeAll("PE\x00\x00") catch unreachable; + buffer.items[0x3c] = @intCast(u8, msdos_stub.len + 4); + + var num_data_directories: u32 = 1; + var size_of_optional_header = switch (self.ptr_width) { + .p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)), + .p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)), + } + num_data_directories * @sizeOf(coff.ImageDataDirectory); + + var flags = coff.CoffHeaderFlags{ + .EXECUTABLE_IMAGE = 1, + .DEBUG_STRIPPED = 1, // TODO + }; + switch (self.ptr_width) { + .p32 => flags.@"32BIT_MACHINE" = 1, + .p64 => flags.LARGE_ADDRESS_AWARE = 1, + } + if (self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic) { + flags.DLL = 1; + } + + var coff_header = coff.CoffHeader{ + .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), + .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section + .time_date_stamp = 0, // TODO + .pointer_to_symbol_table = self.symtab_offset orelse 0, + .number_of_symbols = if (self.symtab_offset) |_| self.getTotalNumberOfSymbols() else 0, + .size_of_optional_header = @intCast(u16, size_of_optional_header), + .flags = flags, + }; + + try buffer.ensureUnusedCapacity(@sizeOf(coff.CoffHeader)); + writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; + + const subsystem: coff.Subsystem = .WINDOWS_CUI; + switch (self.ptr_width) { + .p32 => { + try buffer.ensureUnusedCapacity(@sizeOf(coff.OptionalHeaderPE32)); + var opt_header = coff.OptionalHeaderPE32{ + .magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC, + .major_linker_version = 0, + .minor_linker_version = 0, + .size_of_code = 0, + .size_of_initialized_data = 0, + .size_of_uninitialized_data = 0, + .address_of_entry_point = self.entry_addr orelse 0, + .base_of_code = 0, + .base_of_data = 0, + .image_base = 0, + .section_alignment = 0, + .file_alignment = 0, + .major_operating_system_version = 0, + .minor_operating_system_version = 0, + .major_image_version = 0, + .minor_image_version = 0, + .major_subsystem_version = 0, + .minor_subsystem_version = 0, + .win32_version_value = 0, + .size_of_image = 0, + .size_of_headers = 0, + .checksum = 0, + .subsystem = subsystem, + .dll_flags = .{}, + .size_of_stack_reserve = 0, + .size_of_stack_commit = 0, + .size_of_heap_reserve = 0, + .size_of_heap_commit = 0, + .loader_flags = 0, + .number_of_rva_and_sizes = num_data_directories, + }; + writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; + }, + .p64 => { + try buffer.ensureUnusedCapacity(@sizeOf(coff.OptionalHeaderPE64)); + var opt_header = coff.OptionalHeaderPE64{ + .magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC, + .major_linker_version = 0, + .minor_linker_version = 0, + .size_of_code = 0, + .size_of_initialized_data = 0, + .size_of_uninitialized_data = 0, + .address_of_entry_point = self.entry_addr orelse 0, + .base_of_code = 0, + .image_base = 0, + .section_alignment = 0, + .file_alignment = 0, + .major_operating_system_version = 0, + .minor_operating_system_version = 0, + .major_image_version = 0, + .minor_image_version = 0, + .major_subsystem_version = 0, + .minor_subsystem_version = 0, + .win32_version_value = 0, + .size_of_image = 0, + .size_of_headers = 0, + .checksum = 0, + .subsystem = subsystem, + .dll_flags = .{}, + .size_of_stack_reserve = 0, + .size_of_stack_commit = 0, + .size_of_heap_reserve = 0, + .size_of_heap_commit = 0, + .loader_flags = 0, + .number_of_rva_and_sizes = num_data_directories, + }; + writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; + }, + } + + try buffer.ensureUnusedCapacity(num_data_directories * @sizeOf(coff.ImageDataDirectory)); + writer.writeAll(mem.asBytes(&coff.ImageDataDirectory{ + .virtual_address = 0, + .size = 0, + })) catch unreachable; + + try self.base.file.?.pwriteAll(buffer.items, 0); +} + +fn getTotalNumberOfSymbols(self: Coff) u32 { + _ = self; + // TODO + return 0; +} + pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { // TODO https://github.com/ziglang/zig/issues/1284 return math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch math.maxInt(@TypeOf(actual_size)); } +// fn detectAllocCollision(self: *Coff, 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 < default_header_size) +// return ehdr_size; + +// const end = start + padToIdeal(size); + +// if (self.shdr_table_offset) |off| { +// const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr); +// const tight_size = self.sections.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 (self.phdr_table_offset) |off| { +// const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr); +// const tight_size = self.sections.items.len * phdr_size; +// const increased_size = padToIdeal(tight_size); +// const test_end = off + increased_size; +// if (end > off and start < test_end) { +// return test_end; +// } +// } + +// for (self.sections.items) |section| { +// const increased_size = padToIdeal(section.sh_size); +// const test_end = section.sh_offset + increased_size; +// if (end > section.sh_offset and start < test_end) { +// return test_end; +// } +// } +// for (self.program_headers.items) |program_header| { +// const increased_size = padToIdeal(program_header.p_filesz); +// const test_end = program_header.p_offset + increased_size; +// if (end > program_header.p_offset and start < test_end) { +// return test_end; +// } +// } +// return null; +// } + +// pub fn allocatedSize(self: *Coff, start: u64) u64 { +// if (start == 0) +// return 0; +// var min_pos: u64 = std.math.maxInt(u64); +// if (self.shdr_table_offset) |off| { +// if (off > start and off < min_pos) min_pos = off; +// } +// if (self.phdr_table_offset) |off| { +// if (off > start and off < min_pos) min_pos = off; +// } +// for (self.sections.items) |section| { +// if (section.sh_offset <= start) continue; +// if (section.sh_offset < min_pos) min_pos = section.sh_offset; +// } +// for (self.program_headers.items) |program_header| { +// if (program_header.p_offset <= start) continue; +// if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; +// } +// return min_pos - start; +// } + +// pub fn findFreeSpace(self: *Coff, object_size: u64, min_alignment: u32) u64 { +// var start: u64 = 0; +// while (self.detectAllocCollision(start, object_size)) |item_end| { +// start = mem.alignForwardGeneric(u64, item_end, min_alignment); +// } +// return start; +// } + +fn getMaxOptionalHeaderSize(self: Coff) usize { + const hdr_size: usize = switch (self.ptr_width) { + .p32 => @sizeOf(coff.OptionalHeaderPE32), + .p64 => @sizeOf(coff.OptionalHeaderPE64), + }; + return hdr_size + @sizeOf(coff.ImageDataDirectory) * 16; +} + /// Returns pointer-to-symbol described by `sym_with_loc` descriptor. pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol { assert(sym_loc.file == null); // TODO linking object files From 93127a615be716efa0a74ca08e0a17fda2f4074f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 27 Aug 2022 15:05:43 +0200 Subject: [PATCH 15/40] coff: set some defaults for PE headers --- src/Compilation.zig | 1 - src/link/Coff.zig | 71 +++++++++++++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 32d06e5907..8e4b322230 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1127,7 +1127,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { link_eh_frame_hdr or options.link_emit_relocs or options.output_mode == .Lib or - options.image_base_override != null or options.linker_script != null or options.version_script != null or options.emit_implib != null or build_id) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a270843fdd..61868221d7 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -69,7 +69,10 @@ managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, /// Table of atoms indexed by the symbol index. atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, -const page_size: u16 = 0x1000; +const default_section_alignment: u16 = 0x1000; +const default_file_alignment: u16 = 0x200; +const default_image_base_dll: u64 = 0x10000000; +const default_image_base_exe: u64 = 0x10000; const Section = struct { header: coff.SectionHeader, @@ -701,7 +704,7 @@ fn writeHeader(self: *Coff) !void { writer.writeAll("PE\x00\x00") catch unreachable; buffer.items[0x3c] = @intCast(u8, msdos_stub.len + 4); - var num_data_directories: u32 = 1; + var num_data_directories: u32 = 0; var size_of_optional_header = switch (self.ptr_width) { .p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)), .p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)), @@ -732,7 +735,25 @@ fn writeHeader(self: *Coff) !void { try buffer.ensureUnusedCapacity(@sizeOf(coff.CoffHeader)); writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; + const dll_flags: coff.DllFlags = .{ + .HIGH_ENTROPY_VA = 0, // TODO handle ASLR + .DYNAMIC_BASE = 0, // TODO handle ASLR + .TERMINAL_SERVER_AWARE = 1, // We are not a legacy app + .NX_COMPAT = 1, // We are compatible with Data Execution Prevention + }; const subsystem: coff.Subsystem = .WINDOWS_CUI; + const size_of_headers: u32 = @intCast( + u32, + msdos_stub.len + 8 + size_of_optional_header + self.sections.slice().len * @sizeOf(coff.SectionHeader), + ); + const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_section_alignment); + const size_of_headers_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_file_alignment); + const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { + .Exe => default_image_base_exe, + .Lib => default_image_base_dll, + else => unreachable, + }; + switch (self.ptr_width) { .p32 => { try buffer.ensureUnusedCapacity(@sizeOf(coff.OptionalHeaderPE32)); @@ -746,21 +767,21 @@ fn writeHeader(self: *Coff) !void { .address_of_entry_point = self.entry_addr orelse 0, .base_of_code = 0, .base_of_data = 0, - .image_base = 0, - .section_alignment = 0, - .file_alignment = 0, - .major_operating_system_version = 0, + .image_base = @intCast(u32, image_base), + .section_alignment = default_section_alignment, + .file_alignment = default_file_alignment, + .major_operating_system_version = 6, .minor_operating_system_version = 0, .major_image_version = 0, .minor_image_version = 0, - .major_subsystem_version = 0, + .major_subsystem_version = 6, .minor_subsystem_version = 0, .win32_version_value = 0, - .size_of_image = 0, - .size_of_headers = 0, + .size_of_image = size_of_image_aligned, + .size_of_headers = size_of_headers_aligned, .checksum = 0, .subsystem = subsystem, - .dll_flags = .{}, + .dll_flags = dll_flags, .size_of_stack_reserve = 0, .size_of_stack_commit = 0, .size_of_heap_reserve = 0, @@ -781,21 +802,21 @@ fn writeHeader(self: *Coff) !void { .size_of_uninitialized_data = 0, .address_of_entry_point = self.entry_addr orelse 0, .base_of_code = 0, - .image_base = 0, - .section_alignment = 0, - .file_alignment = 0, - .major_operating_system_version = 0, + .image_base = image_base, + .section_alignment = default_section_alignment, + .file_alignment = default_file_alignment, + .major_operating_system_version = 6, .minor_operating_system_version = 0, .major_image_version = 0, .minor_image_version = 0, - .major_subsystem_version = 0, + .major_subsystem_version = 6, .minor_subsystem_version = 0, .win32_version_value = 0, - .size_of_image = 0, - .size_of_headers = 0, + .size_of_image = size_of_image_aligned, + .size_of_headers = size_of_headers_aligned, .checksum = 0, .subsystem = subsystem, - .dll_flags = .{}, + .dll_flags = dll_flags, .size_of_stack_reserve = 0, .size_of_stack_commit = 0, .size_of_heap_reserve = 0, @@ -808,11 +829,17 @@ fn writeHeader(self: *Coff) !void { } try buffer.ensureUnusedCapacity(num_data_directories * @sizeOf(coff.ImageDataDirectory)); - writer.writeAll(mem.asBytes(&coff.ImageDataDirectory{ - .virtual_address = 0, - .size = 0, - })) catch unreachable; + { + var i: usize = 0; + while (i < num_data_directories) : (i += 1) { + writer.writeAll(mem.asBytes(&coff.ImageDataDirectory{ + .virtual_address = 0, + .size = 0, + })) catch unreachable; + } + } + try self.base.file.?.pwriteAll(&[_]u8{0}, size_of_headers_aligned); try self.base.file.?.pwriteAll(buffer.items, 0); } From 9fc6933418be00df54db79aa8eb3e2c759a2c038 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 27 Aug 2022 18:10:29 +0200 Subject: [PATCH 16/40] coff: write data directory and section headers to file --- src/link/Coff.zig | 141 ++++++++++++++++++++++++---------------------- 1 file changed, 73 insertions(+), 68 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 61868221d7..2fda648263 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -38,6 +38,7 @@ error_flags: link.File.ErrorFlags = .{}, ptr_width: PtrWidth, sections: std.MultiArrayList(Section) = .{}, +data_directories: std.ArrayListUnmanaged(coff.ImageDataDirectory) = .{}, text_section_index: ?u16 = null, got_section_index: ?u16 = null, @@ -49,8 +50,6 @@ locals_free_list: std.ArrayListUnmanaged(u32) = .{}, strtab: StringTable(.strtab) = .{}, -symtab_offset: ?u32 = null, - got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, @@ -73,6 +72,7 @@ const default_section_alignment: u16 = 0x1000; const default_file_alignment: u16 = 0x200; const default_image_base_dll: u64 = 0x10000000; const default_image_base_exe: u64 = 0x10000; +const default_header_size: u64 = default_section_alignment; const Section = struct { header: coff.SectionHeader, @@ -192,6 +192,7 @@ pub fn deinit(self: *Coff) void { free_list.deinit(gpa); } self.sections.deinit(gpa); + self.data_directories.deinit(gpa); for (self.managed_atoms.items) |atom| { gpa.destroy(atom); @@ -209,11 +210,8 @@ pub fn deinit(self: *Coff) void { } fn populateMissingMetadata(self: *Coff) !void { - log.debug("{x}", .{msdos_stub.len}); - if (self.text_section_index == null) {} if (self.got_section_index == null) {} - if (self.symtab_offset == null) {} } pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { @@ -662,6 +660,8 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + try self.writeDataDirectoriesHeaders(); + try self.writeSectionHeaders(); try self.writeHeader(); if (self.entry_addr == null and self.base.options.output_mode == .Exe) { @@ -692,24 +692,28 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v log.debug("TODO implement updateDeclLineNumber", .{}); } +fn writeSectionHeaders(self: *Coff) !void { + const offset = self.getSectionHeadersOffset(); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items(.header)), offset); +} + +fn writeDataDirectoriesHeaders(self: *Coff) !void { + const offset = self.getDataDirectoryHeadersOffset(); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.data_directories.items), offset); +} + fn writeHeader(self: *Coff) !void { const gpa = self.base.allocator; var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); const writer = buffer.writer(); - try buffer.ensureUnusedCapacity(msdos_stub.len + 8); + try buffer.ensureTotalCapacity(self.getSizeOfHeaders()); writer.writeAll(msdos_stub) catch unreachable; writer.writeByteNTimes(0, 4) catch unreachable; // align to 8 bytes writer.writeAll("PE\x00\x00") catch unreachable; buffer.items[0x3c] = @intCast(u8, msdos_stub.len + 4); - var num_data_directories: u32 = 0; - var size_of_optional_header = switch (self.ptr_width) { - .p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)), - .p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)), - } + num_data_directories * @sizeOf(coff.ImageDataDirectory); - var flags = coff.CoffHeaderFlags{ .EXECUTABLE_IMAGE = 1, .DEBUG_STRIPPED = 1, // TODO @@ -722,17 +726,20 @@ fn writeHeader(self: *Coff) !void { flags.DLL = 1; } + const size_of_optional_header = @intCast( + u16, + self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize(), + ); var coff_header = coff.CoffHeader{ .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section .time_date_stamp = 0, // TODO - .pointer_to_symbol_table = self.symtab_offset orelse 0, - .number_of_symbols = if (self.symtab_offset) |_| self.getTotalNumberOfSymbols() else 0, - .size_of_optional_header = @intCast(u16, size_of_optional_header), + .pointer_to_symbol_table = 0, + .number_of_symbols = 0, + .size_of_optional_header = size_of_optional_header, .flags = flags, }; - try buffer.ensureUnusedCapacity(@sizeOf(coff.CoffHeader)); writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; const dll_flags: coff.DllFlags = .{ @@ -742,10 +749,7 @@ fn writeHeader(self: *Coff) !void { .NX_COMPAT = 1, // We are compatible with Data Execution Prevention }; const subsystem: coff.Subsystem = .WINDOWS_CUI; - const size_of_headers: u32 = @intCast( - u32, - msdos_stub.len + 8 + size_of_optional_header + self.sections.slice().len * @sizeOf(coff.SectionHeader), - ); + const size_of_headers: u32 = @intCast(u32, self.getSizeOfHeaders()); const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_section_alignment); const size_of_headers_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_file_alignment); const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { @@ -756,7 +760,6 @@ fn writeHeader(self: *Coff) !void { switch (self.ptr_width) { .p32 => { - try buffer.ensureUnusedCapacity(@sizeOf(coff.OptionalHeaderPE32)); var opt_header = coff.OptionalHeaderPE32{ .magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC, .major_linker_version = 0, @@ -787,12 +790,11 @@ fn writeHeader(self: *Coff) !void { .size_of_heap_reserve = 0, .size_of_heap_commit = 0, .loader_flags = 0, - .number_of_rva_and_sizes = num_data_directories, + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.items.len), }; writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; }, .p64 => { - try buffer.ensureUnusedCapacity(@sizeOf(coff.OptionalHeaderPE64)); var opt_header = coff.OptionalHeaderPE64{ .magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC, .major_linker_version = 0, @@ -822,33 +824,16 @@ fn writeHeader(self: *Coff) !void { .size_of_heap_reserve = 0, .size_of_heap_commit = 0, .loader_flags = 0, - .number_of_rva_and_sizes = num_data_directories, + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.items.len), }; writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; }, } - try buffer.ensureUnusedCapacity(num_data_directories * @sizeOf(coff.ImageDataDirectory)); - { - var i: usize = 0; - while (i < num_data_directories) : (i += 1) { - writer.writeAll(mem.asBytes(&coff.ImageDataDirectory{ - .virtual_address = 0, - .size = 0, - })) catch unreachable; - } - } - try self.base.file.?.pwriteAll(&[_]u8{0}, size_of_headers_aligned); try self.base.file.?.pwriteAll(buffer.items, 0); } -fn getTotalNumberOfSymbols(self: Coff) u32 { - _ = self; - // TODO - return 0; -} - pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { // TODO https://github.com/ziglang/zig/issues/1284 return math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch @@ -856,14 +841,12 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { } // fn detectAllocCollision(self: *Coff, 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 < default_header_size) -// return ehdr_size; +// return default_header_size; // const end = start + padToIdeal(size); -// if (self.shdr_table_offset) |off| { +// if (self.symtab_offset) |off| { // const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr); // const tight_size = self.sections.items.len * shdr_size; // const increased_size = padToIdeal(tight_size); @@ -900,26 +883,26 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { // return null; // } -// pub fn allocatedSize(self: *Coff, start: u64) u64 { -// if (start == 0) -// return 0; -// var min_pos: u64 = std.math.maxInt(u64); -// if (self.shdr_table_offset) |off| { -// if (off > start and off < min_pos) min_pos = off; -// } -// if (self.phdr_table_offset) |off| { -// if (off > start and off < min_pos) min_pos = off; -// } -// for (self.sections.items) |section| { -// if (section.sh_offset <= start) continue; -// if (section.sh_offset < min_pos) min_pos = section.sh_offset; -// } -// for (self.program_headers.items) |program_header| { -// if (program_header.p_offset <= start) continue; -// if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; -// } -// return min_pos - start; -// } +// // pub fn allocatedSize(self: *Coff, start: u64) u64 { +// // if (start == 0) +// // return 0; +// // var min_pos: u64 = std.math.maxInt(u64); +// // if (self.shdr_table_offset) |off| { +// // if (off > start and off < min_pos) min_pos = off; +// // } +// // if (self.phdr_table_offset) |off| { +// // if (off > start and off < min_pos) min_pos = off; +// // } +// // for (self.sections.items) |section| { +// // if (section.sh_offset <= start) continue; +// // if (section.sh_offset < min_pos) min_pos = section.sh_offset; +// // } +// // for (self.program_headers.items) |program_header| { +// // if (program_header.p_offset <= start) continue; +// // if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; +// // } +// // return min_pos - start; +// // } // pub fn findFreeSpace(self: *Coff, object_size: u64, min_alignment: u32) u64 { // var start: u64 = 0; @@ -929,12 +912,34 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { // return start; // } -fn getMaxOptionalHeaderSize(self: Coff) usize { - const hdr_size: usize = switch (self.ptr_width) { +inline fn getSizeOfHeaders(self: Coff) usize { + const msdos_hdr_size = msdos_stub.len + 8; + return msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() + + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize(); +} + +inline fn getOptionalHeaderSize(self: Coff) usize { + return switch (self.ptr_width) { .p32 => @sizeOf(coff.OptionalHeaderPE32), .p64 => @sizeOf(coff.OptionalHeaderPE64), }; - return hdr_size + @sizeOf(coff.ImageDataDirectory) * 16; +} + +inline fn getDataDirectoryHeadersSize(self: Coff) usize { + return self.data_directories.items.len * @sizeOf(coff.ImageDataDirectory); +} + +inline fn getSectionHeadersSize(self: Coff) usize { + return self.sections.slice().len * @sizeOf(coff.SectionHeader); +} + +inline fn getDataDirectoryHeadersOffset(self: Coff) usize { + const msdos_hdr_size = msdos_stub.len + 8; + return msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize(); +} + +inline fn getSectionHeadersOffset(self: Coff) usize { + return self.getDataDirectoryHeadersOffset() + self.getDataDirectoryHeadersSize(); } /// Returns pointer-to-symbol described by `sym_with_loc` descriptor. From da00e6dd596685e8f0ad9650145309c00485da6f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 08:31:13 +0200 Subject: [PATCH 17/40] coff: always write all data directory headers to file Maximum number is always 16, and this also unbreaks `dumpbin.exe` run on a simple section-less PE image created with our linker. --- src/link/Coff.zig | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 2fda648263..517ee6a3c0 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -38,7 +38,7 @@ error_flags: link.File.ErrorFlags = .{}, ptr_width: PtrWidth, sections: std.MultiArrayList(Section) = .{}, -data_directories: std.ArrayListUnmanaged(coff.ImageDataDirectory) = .{}, +data_directories: [16]coff.ImageDataDirectory, text_section_index: ?u16 = null, got_section_index: ?u16 = null, @@ -72,7 +72,6 @@ const default_section_alignment: u16 = 0x1000; const default_file_alignment: u16 = 0x200; const default_image_base_dll: u64 = 0x10000000; const default_image_base_exe: u64 = 0x10000; -const default_header_size: u64 = default_section_alignment; const Section = struct { header: coff.SectionHeader, @@ -171,6 +170,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { .file = null, }, .ptr_width = ptr_width, + .data_directories = comptime mem.zeroes([16]coff.ImageDataDirectory), }; const use_llvm = build_options.have_llvm and options.use_llvm; @@ -192,7 +192,6 @@ pub fn deinit(self: *Coff) void { free_list.deinit(gpa); } self.sections.deinit(gpa); - self.data_directories.deinit(gpa); for (self.managed_atoms.items) |atom| { gpa.destroy(atom); @@ -699,7 +698,7 @@ fn writeSectionHeaders(self: *Coff) !void { fn writeDataDirectoriesHeaders(self: *Coff) !void { const offset = self.getDataDirectoryHeadersOffset(); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.data_directories.items), offset); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(&self.data_directories), offset); } fn writeHeader(self: *Coff) !void { @@ -790,7 +789,7 @@ fn writeHeader(self: *Coff) !void { .size_of_heap_reserve = 0, .size_of_heap_commit = 0, .loader_flags = 0, - .number_of_rva_and_sizes = @intCast(u32, self.data_directories.items.len), + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), }; writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; }, @@ -824,7 +823,7 @@ fn writeHeader(self: *Coff) !void { .size_of_heap_reserve = 0, .size_of_heap_commit = 0, .loader_flags = 0, - .number_of_rva_and_sizes = @intCast(u32, self.data_directories.items.len), + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), }; writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; }, @@ -841,32 +840,13 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { } // fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { -// if (start < default_header_size) -// return default_header_size; +// const headers_size = self.getSizeOfHeaders(); +// if (start < headers_size) +// return headers_size; // const end = start + padToIdeal(size); -// if (self.symtab_offset) |off| { -// const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr); -// const tight_size = self.sections.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 (self.phdr_table_offset) |off| { -// const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr); -// const tight_size = self.sections.items.len * phdr_size; -// const increased_size = padToIdeal(tight_size); -// const test_end = off + increased_size; -// if (end > off and start < test_end) { -// return test_end; -// } -// } - -// for (self.sections.items) |section| { +// for (self.sections.items(.header)) |header| { // const increased_size = padToIdeal(section.sh_size); // const test_end = section.sh_offset + increased_size; // if (end > section.sh_offset and start < test_end) { @@ -926,7 +906,7 @@ inline fn getOptionalHeaderSize(self: Coff) usize { } inline fn getDataDirectoryHeadersSize(self: Coff) usize { - return self.data_directories.items.len * @sizeOf(coff.ImageDataDirectory); + return self.data_directories.len * @sizeOf(coff.ImageDataDirectory); } inline fn getSectionHeadersSize(self: Coff) usize { From 3aa99f45b8d9b22a2d2d09457f19ca3ef20764f6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 09:14:18 +0200 Subject: [PATCH 18/40] coff: initial implementation of incremental file allocs --- src/link/Coff.zig | 80 +++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 48 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 517ee6a3c0..a2542bdbac 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -839,58 +839,42 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { math.maxInt(@TypeOf(actual_size)); } -// fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { -// const headers_size = self.getSizeOfHeaders(); -// if (start < headers_size) -// return headers_size; +fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { + const headers_size = self.getSizeOfHeaders(); + if (start < headers_size) + return headers_size; -// const end = start + padToIdeal(size); + const end = start + padToIdeal(size); -// for (self.sections.items(.header)) |header| { -// const increased_size = padToIdeal(section.sh_size); -// const test_end = section.sh_offset + increased_size; -// if (end > section.sh_offset and start < test_end) { -// return test_end; -// } -// } -// for (self.program_headers.items) |program_header| { -// const increased_size = padToIdeal(program_header.p_filesz); -// const test_end = program_header.p_offset + increased_size; -// if (end > program_header.p_offset and start < test_end) { -// return test_end; -// } -// } -// return null; -// } + for (self.sections.items(.header)) |header| { + const increased_size = padToIdeal(header.size_of_raw_data); + const test_end = header.pointer_to_raw_data + increased_size; + if (end > header.pointer_to_raw_data and start < test_end) { + return test_end; + } + } -// // pub fn allocatedSize(self: *Coff, start: u64) u64 { -// // if (start == 0) -// // return 0; -// // var min_pos: u64 = std.math.maxInt(u64); -// // if (self.shdr_table_offset) |off| { -// // if (off > start and off < min_pos) min_pos = off; -// // } -// // if (self.phdr_table_offset) |off| { -// // if (off > start and off < min_pos) min_pos = off; -// // } -// // for (self.sections.items) |section| { -// // if (section.sh_offset <= start) continue; -// // if (section.sh_offset < min_pos) min_pos = section.sh_offset; -// // } -// // for (self.program_headers.items) |program_header| { -// // if (program_header.p_offset <= start) continue; -// // if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; -// // } -// // return min_pos - start; -// // } + return null; +} -// pub fn findFreeSpace(self: *Coff, object_size: u64, min_alignment: u32) u64 { -// var start: u64 = 0; -// while (self.detectAllocCollision(start, object_size)) |item_end| { -// start = mem.alignForwardGeneric(u64, item_end, min_alignment); -// } -// return start; -// } +pub fn allocatedSize(self: *Coff, start: u64) u64 { + if (start == 0) + return 0; + var min_pos: u64 = std.math.maxInt(u64); + for (self.sections.items(.header)) |header| { + if (header.pointer_to_raw_data <= start) continue; + if (header.pointer_to_raw_data < min_pos) min_pos = header.pointer_to_raw_data; + } + return min_pos - start; +} + +pub fn findFreeSpace(self: *Coff, object_size: u64, min_alignment: u32) u64 { + var start: u64 = 0; + while (self.detectAllocCollision(start, object_size)) |item_end| { + start = mem.alignForwardGeneric(u64, item_end, min_alignment); + } + return start; +} inline fn getSizeOfHeaders(self: Coff) usize { const msdos_hdr_size = msdos_stub.len + 8; From ff0abad2a9b9701287818684bb60d638a97172f2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 09:38:58 +0200 Subject: [PATCH 19/40] coff: allow for strtab in final PE image I believe this is going to be vital for section headers having names that require the use of a string table. --- src/link/Coff.zig | 42 ++++++++++++++++++++++++++++++------------ src/link/strtab.zig | 4 ++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a2542bdbac..7a68852782 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -49,6 +49,7 @@ globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{}, locals_free_list: std.ArrayListUnmanaged(u32) = .{}, strtab: StringTable(.strtab) = .{}, +strtab_offset: ?u32 = null, got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, @@ -138,17 +139,6 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }); self.base.file = file; - // Index 0 is always a null symbol. - try self.locals.append(allocator, .{ - .name = [_]u8{0} ** 8, - .value = 0, - .section_number = @intToEnum(coff.SectionNumber, 0), - .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, - .storage_class = .NULL, - .number_of_aux_symbols = 0, - }); - try self.strtab.buffer.append(allocator, 0); - try self.populateMissingMetadata(); return self; @@ -209,8 +199,25 @@ pub fn deinit(self: *Coff) void { } fn populateMissingMetadata(self: *Coff) !void { + const gpa = self.base.allocator; + if (self.text_section_index == null) {} + if (self.got_section_index == null) {} + + if (self.strtab_offset == null) { + try self.strtab.buffer.append(gpa, 0); + } + + // Index 0 is always a null symbol. + try self.locals.append(gpa, .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }); } pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { @@ -733,7 +740,7 @@ fn writeHeader(self: *Coff) !void { .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section .time_date_stamp = 0, // TODO - .pointer_to_symbol_table = 0, + .pointer_to_symbol_table = self.strtab_offset orelse 0, .number_of_symbols = 0, .size_of_optional_header = size_of_optional_header, .flags = flags, @@ -846,6 +853,14 @@ fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { const end = start + padToIdeal(size); + if (self.strtab_offset) |off| { + const increased_size = padToIdeal(self.strtab.len()); + const test_end = off + increased_size; + if (end > off and start < test_end) { + return test_end; + } + } + for (self.sections.items(.header)) |header| { const increased_size = padToIdeal(header.size_of_raw_data); const test_end = header.pointer_to_raw_data + increased_size; @@ -861,6 +876,9 @@ pub fn allocatedSize(self: *Coff, start: u64) u64 { if (start == 0) return 0; var min_pos: u64 = std.math.maxInt(u64); + if (self.strtab_offset) |off| { + if (off > start and off < min_pos) min_pos = off; + } for (self.sections.items(.header)) |header| { if (header.pointer_to_raw_data <= start) continue; if (header.pointer_to_raw_data < min_pos) min_pos = header.pointer_to_raw_data; diff --git a/src/link/strtab.zig b/src/link/strtab.zig index ae9b00027e..8e314f189f 100644 --- a/src/link/strtab.zig +++ b/src/link/strtab.zig @@ -109,5 +109,9 @@ pub fn StringTable(comptime log_scope: @Type(.EnumLiteral)) type { pub fn getAssumeExists(self: Self, off: u32) []const u8 { return self.get(off) orelse unreachable; } + + pub fn len(self: Self) usize { + return self.buffer.items.len; + } }; } From f36029a3851756d6e98eb486c89dc741db79752e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 10:15:27 +0200 Subject: [PATCH 20/40] coff: add helpers for setting section/symbol names --- src/link/Coff.zig | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 7a68852782..0d591e504f 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -5,6 +5,7 @@ const build_options = @import("build_options"); const builtin = @import("builtin"); const assert = std.debug.assert; const coff = std.coff; +const fmt = std.fmt; const log = std.log.scoped(.link); const math = std.math; const mem = std.mem; @@ -36,6 +37,7 @@ base: link.File, error_flags: link.File.ErrorFlags = .{}, ptr_width: PtrWidth, +page_size: u32, sections: std.MultiArrayList(Section) = .{}, data_directories: [16]coff.ImageDataDirectory, @@ -69,7 +71,6 @@ managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, /// Table of atoms indexed by the symbol index. atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, -const default_section_alignment: u16 = 0x1000; const default_file_alignment: u16 = 0x200; const default_image_base_dll: u64 = 0x10000000; const default_image_base_exe: u64 = 0x10000; @@ -150,6 +151,9 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { 33...64 => .p64, else => return error.UnsupportedCOFFArchitecture, }; + const page_size: u32 = switch (options.target.cpu.arch) { + else => 0x1000, + }; const self = try gpa.create(Coff); errdefer gpa.destroy(self); self.* = .{ @@ -160,6 +164,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { .file = null, }, .ptr_width = ptr_width, + .page_size = page_size, .data_directories = comptime mem.zeroes([16]coff.ImageDataDirectory), }; @@ -199,9 +204,15 @@ pub fn deinit(self: *Coff) void { } fn populateMissingMetadata(self: *Coff) !void { + assert(self.llvm_object == null); const gpa = self.base.allocator; - if (self.text_section_index == null) {} + if (self.text_section_index == null) { + self.text_section_index = @intCast(u16, self.sections.slice().len); + const file_size = self.base.options.program_code_size_hint; + const off = self.findFreeSpace(file_size, self.page_size); // TODO we are over-aligning in file; we should track both in file and in memory pointers + log.debug("found .text free space 0x{x} to 0x{x}", .{ off, off + file_size }); + } if (self.got_section_index == null) {} @@ -756,7 +767,7 @@ fn writeHeader(self: *Coff) !void { }; const subsystem: coff.Subsystem = .WINDOWS_CUI; const size_of_headers: u32 = @intCast(u32, self.getSizeOfHeaders()); - const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_section_alignment); + const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, self.page_size); const size_of_headers_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_file_alignment); const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { .Exe => default_image_base_exe, @@ -777,7 +788,7 @@ fn writeHeader(self: *Coff) !void { .base_of_code = 0, .base_of_data = 0, .image_base = @intCast(u32, image_base), - .section_alignment = default_section_alignment, + .section_alignment = self.page_size, .file_alignment = default_file_alignment, .major_operating_system_version = 6, .minor_operating_system_version = 0, @@ -811,7 +822,7 @@ fn writeHeader(self: *Coff) !void { .address_of_entry_point = self.entry_addr orelse 0, .base_of_code = 0, .image_base = image_base, - .section_alignment = default_section_alignment, + .section_alignment = self.page_size, .file_alignment = default_file_alignment, .major_operating_system_version = 6, .minor_operating_system_version = 0, @@ -956,3 +967,23 @@ pub fn getGotAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { const got_index = self.got_entries.get(sym_loc) orelse return null; return self.atom_by_index_table.get(got_index); } + +fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !void { + if (name.len <= 8) { + mem.copy(u8, &header.name, name); + return; + } + const offset = try self.strtab.insert(self.base.allocator, name); + const name_offset = try fmt.bufPrint(&header.name, "/{d}", .{offset}); + mem.set(u8, header.name[name_offset.len..], 0); +} + +fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { + if (name.len <= 8) { + mem.copy(u8, &symbol.name, name); + return; + } + const offset = try self.strtab.insert(self.base.allocator, name); + mem.copy(u8, symbol.name[0..4], 0); + _ = try fmt.bufPrint(symbol.name[4..], "{d}", .{offset}); +} From 2a994ba4a713f0bd469265ea3c55e335048d4c91 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 14:25:58 +0200 Subject: [PATCH 21/40] coff: populate missing section metadata --- src/link/Coff.zig | 139 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 13 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 0d591e504f..74a0a5720d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -43,7 +43,9 @@ sections: std.MultiArrayList(Section) = .{}, data_directories: [16]coff.ImageDataDirectory, text_section_index: ?u16 = null, -got_section_index: ?u16 = null, +rdata_section_index: ?u16 = null, +pdata_section_index: ?u16 = null, +data_section_index: ?u16 = null, locals: std.ArrayListUnmanaged(coff.Symbol) = .{}, globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{}, @@ -212,12 +214,103 @@ fn populateMissingMetadata(self: *Coff) !void { const file_size = self.base.options.program_code_size_hint; const off = self.findFreeSpace(file_size, self.page_size); // TODO we are over-aligning in file; we should track both in file and in memory pointers log.debug("found .text free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = @intCast(u32, file_size), + .virtual_address = @intCast(u32, off), + .size_of_raw_data = @intCast(u32, file_size), + .pointer_to_raw_data = @intCast(u32, off), + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_CODE = 1, + .MEM_EXECUTE = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".text"); + try self.sections.append(gpa, .{ .header = header }); } - if (self.got_section_index == null) {} + if (self.pdata_section_index == null) { + self.pdata_section_index = @intCast(u16, self.sections.slice().len); + const file_size = self.base.options.symbol_count_hint; + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .pdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = @intCast(u32, file_size), + .virtual_address = @intCast(u32, off), + .size_of_raw_data = @intCast(u32, file_size), + .pointer_to_raw_data = @intCast(u32, off), + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".pdata"); + try self.sections.append(gpa, .{ .header = header }); + } + + if (self.rdata_section_index == null) { + self.rdata_section_index = @intCast(u16, self.sections.slice().len); + const file_size = 1024; + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .rdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = @intCast(u32, file_size), + .virtual_address = @intCast(u32, off), + .size_of_raw_data = @intCast(u32, file_size), + .pointer_to_raw_data = @intCast(u32, off), + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".rdata"); + try self.sections.append(gpa, .{ .header = header }); + } + + if (self.data_section_index == null) { + self.data_section_index = @intCast(u16, self.sections.slice().len); + const file_size = 1024; + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .data free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = @intCast(u32, file_size), + .virtual_address = @intCast(u32, off), + .size_of_raw_data = @intCast(u32, file_size), + .pointer_to_raw_data = @intCast(u32, off), + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + .MEM_WRITE = 1, + }, + }; + try self.setSectionName(&header, ".data"); + try self.sections.append(gpa, .{ .header = header }); + } if (self.strtab_offset == null) { try self.strtab.buffer.append(gpa, 0); + self.strtab_offset = @intCast(u32, self.findFreeSpace(self.strtab.len(), 1)); + log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + self.strtab.len() }); } // Index 0 is always a null symbol. @@ -677,6 +770,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + try self.writeStrtab(); try self.writeDataDirectoriesHeaders(); try self.writeSectionHeaders(); try self.writeHeader(); @@ -709,6 +803,19 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v log.debug("TODO implement updateDeclLineNumber", .{}); } +fn writeStrtab(self: *Coff) !void { + const allocated_size = self.allocatedSize(self.strtab_offset.?); + const needed_size = self.strtab.len(); + + if (needed_size > allocated_size) { + self.strtab_offset = null; + self.strtab_offset = @intCast(u32, self.findFreeSpace(needed_size, 1)); + } + + log.debug("writing strtab from 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + needed_size }); + try self.base.file.?.pwriteAll(self.strtab.buffer.items, self.strtab_offset.?); +} + fn writeSectionHeaders(self: *Coff) !void { const offset = self.getSectionHeadersOffset(); try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items(.header)), offset); @@ -729,7 +836,7 @@ fn writeHeader(self: *Coff) !void { writer.writeAll(msdos_stub) catch unreachable; writer.writeByteNTimes(0, 4) catch unreachable; // align to 8 bytes writer.writeAll("PE\x00\x00") catch unreachable; - buffer.items[0x3c] = @intCast(u8, msdos_stub.len + 4); + mem.writeIntLittle(u32, buffer.items[0x3c..][0..4], msdos_stub.len + 4); var flags = coff.CoffHeaderFlags{ .EXECUTABLE_IMAGE = 1, @@ -743,10 +850,7 @@ fn writeHeader(self: *Coff) !void { flags.DLL = 1; } - const size_of_optional_header = @intCast( - u16, - self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize(), - ); + const size_of_optional_header = @intCast(u16, self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize()); var coff_header = coff.CoffHeader{ .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section @@ -774,6 +878,13 @@ fn writeHeader(self: *Coff) !void { .Lib => default_image_base_dll, else => unreachable, }; + const text_section = self.sections.get(self.text_section_index.?).header; + + var size_of_initialized_data: u32 = 0; + for (self.sections.items(.header)) |header| { + if (header.flags.CNT_INITIALIZED_DATA == 0) continue; + size_of_initialized_data += header.virtual_size; + } switch (self.ptr_width) { .p32 => { @@ -781,11 +892,11 @@ fn writeHeader(self: *Coff) !void { .magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC, .major_linker_version = 0, .minor_linker_version = 0, - .size_of_code = 0, - .size_of_initialized_data = 0, + .size_of_code = text_section.virtual_size, + .size_of_initialized_data = size_of_initialized_data, .size_of_uninitialized_data = 0, .address_of_entry_point = self.entry_addr orelse 0, - .base_of_code = 0, + .base_of_code = text_section.virtual_address, .base_of_data = 0, .image_base = @intCast(u32, image_base), .section_alignment = self.page_size, @@ -816,11 +927,11 @@ fn writeHeader(self: *Coff) !void { .magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC, .major_linker_version = 0, .minor_linker_version = 0, - .size_of_code = 0, - .size_of_initialized_data = 0, + .size_of_code = text_section.virtual_size, + .size_of_initialized_data = size_of_initialized_data, .size_of_uninitialized_data = 0, .address_of_entry_point = self.entry_addr orelse 0, - .base_of_code = 0, + .base_of_code = text_section.virtual_address, .image_base = image_base, .section_alignment = self.page_size, .file_alignment = default_file_alignment, @@ -971,6 +1082,7 @@ pub fn getGotAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !void { if (name.len <= 8) { mem.copy(u8, &header.name, name); + mem.set(u8, header.name[name.len..], 0); return; } const offset = try self.strtab.insert(self.base.allocator, name); @@ -981,6 +1093,7 @@ fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !v fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { if (name.len <= 8) { mem.copy(u8, &symbol.name, name); + mem.set(u8, symbol.name[name.len..], 0); return; } const offset = try self.strtab.insert(self.base.allocator, name); From e5b8a1ac27367402c703a25774bc228499cfeb37 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 16:10:02 +0200 Subject: [PATCH 22/40] coff: allocate and write atoms to file --- src/link/Coff.zig | 445 +++++++++++++++++++++++++++++++---------- src/link/Coff/Atom.zig | 16 +- 2 files changed, 348 insertions(+), 113 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 74a0a5720d..a92c751ca7 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -23,6 +23,7 @@ const Compilation = @import("../Compilation.zig"); const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; const Module = @import("../Module.zig"); +const Object = @import("Coff/Object.zig"); const StringTable = @import("strtab.zig").StringTable; const TypedValue = @import("../TypedValue.zig"); @@ -39,6 +40,8 @@ error_flags: link.File.ErrorFlags = .{}, ptr_width: PtrWidth, page_size: u32, +objects: std.ArrayListUnmanaged(Object) = .{}, + sections: std.MultiArrayList(Section) = .{}, data_directories: [16]coff.ImageDataDirectory, @@ -185,6 +188,11 @@ pub fn deinit(self: *Coff) void { if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa); } + for (self.objects.items) |*object| { + object.deinit(gpa); + } + self.objects.deinit(gpa); + for (self.sections.items(.free_list)) |*free_list| { free_list.deinit(gpa); } @@ -211,15 +219,15 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.text_section_index == null) { self.text_section_index = @intCast(u16, self.sections.slice().len); - const file_size = self.base.options.program_code_size_hint; + const file_size = @intCast(u32, self.base.options.program_code_size_hint); const off = self.findFreeSpace(file_size, self.page_size); // TODO we are over-aligning in file; we should track both in file and in memory pointers log.debug("found .text free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ .name = undefined, - .virtual_size = @intCast(u32, file_size), - .virtual_address = @intCast(u32, off), - .size_of_raw_data = @intCast(u32, file_size), - .pointer_to_raw_data = @intCast(u32, off), + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, .pointer_to_relocations = 0, .pointer_to_linenumbers = 0, .number_of_relocations = 0, @@ -236,15 +244,15 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.pdata_section_index == null) { self.pdata_section_index = @intCast(u16, self.sections.slice().len); - const file_size = self.base.options.symbol_count_hint; + const file_size = @intCast(u32, self.base.options.symbol_count_hint); const off = self.findFreeSpace(file_size, self.page_size); log.debug("found .pdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ .name = undefined, - .virtual_size = @intCast(u32, file_size), - .virtual_address = @intCast(u32, off), - .size_of_raw_data = @intCast(u32, file_size), - .pointer_to_raw_data = @intCast(u32, off), + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, .pointer_to_relocations = 0, .pointer_to_linenumbers = 0, .number_of_relocations = 0, @@ -260,15 +268,15 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.rdata_section_index == null) { self.rdata_section_index = @intCast(u16, self.sections.slice().len); - const file_size = 1024; + const file_size: u32 = 1024; const off = self.findFreeSpace(file_size, self.page_size); log.debug("found .rdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ .name = undefined, - .virtual_size = @intCast(u32, file_size), - .virtual_address = @intCast(u32, off), - .size_of_raw_data = @intCast(u32, file_size), - .pointer_to_raw_data = @intCast(u32, off), + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, .pointer_to_relocations = 0, .pointer_to_linenumbers = 0, .number_of_relocations = 0, @@ -284,15 +292,15 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.data_section_index == null) { self.data_section_index = @intCast(u16, self.sections.slice().len); - const file_size = 1024; + const file_size: u32 = 1024; const off = self.findFreeSpace(file_size, self.page_size); log.debug("found .data free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ .name = undefined, - .virtual_size = @intCast(u32, file_size), - .virtual_address = @intCast(u32, off), - .size_of_raw_data = @intCast(u32, file_size), - .pointer_to_raw_data = @intCast(u32, off), + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, .pointer_to_relocations = 0, .pointer_to_linenumbers = 0, .number_of_relocations = 0, @@ -309,7 +317,7 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.strtab_offset == null) { try self.strtab.buffer.append(gpa, 0); - self.strtab_offset = @intCast(u32, self.findFreeSpace(self.strtab.len(), 1)); + self.strtab_offset = self.findFreeSpace(@intCast(u32, self.strtab.len()), 1); log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + self.strtab.len() }); } @@ -334,7 +342,7 @@ pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { try self.decls.putNoClobber(gpa, decl_index, null); } -fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, sect_id: u16) !u64 { +fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, sect_id: u16) !u32 { const tracy = trace(@src()); defer tracy.end(); @@ -362,10 +370,10 @@ fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, se const sym = big_atom.getSymbol(self); const capacity = big_atom.capacity(self); const ideal_capacity = if (header.isCode()) padToIdeal(capacity) else capacity; - const ideal_capacity_end_vaddr = math.add(u64, sym.n_value, ideal_capacity) catch ideal_capacity; - const capacity_end_vaddr = sym.n_value + capacity; + const ideal_capacity_end_vaddr = math.add(u32, sym.value, ideal_capacity) catch ideal_capacity; + const capacity_end_vaddr = sym.value + capacity; const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity; - const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment); + const new_start_vaddr = mem.alignBackwardGeneric(u32, new_start_vaddr_unaligned, alignment); if (new_start_vaddr < ideal_capacity_end_vaddr) { // Additional bookkeeping here to notice if this free list node // should be deleted because the atom that it points to has grown to take up @@ -392,23 +400,30 @@ fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, se } else if (maybe_last_atom.*) |last| { const last_symbol = last.getSymbol(self); const ideal_capacity = if (header.isCode()) padToIdeal(last.size) else last.size; - const ideal_capacity_end_vaddr = last_symbol.n_value + ideal_capacity; - const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment); + const ideal_capacity_end_vaddr = last_symbol.value + ideal_capacity; + const new_start_vaddr = mem.alignForwardGeneric(u32, ideal_capacity_end_vaddr, alignment); atom_placement = last; break :blk new_start_vaddr; } else { - break :blk mem.alignForwardGeneric(u64, header.addr, alignment); + break :blk mem.alignForwardGeneric(u32, header.virtual_address, alignment); } }; const expand_section = atom_placement == null or atom_placement.?.next == null; if (expand_section) { - @panic("TODO expand section in allocateAtom"); + const sect_capacity = self.allocatedSize(header.pointer_to_raw_data); + const needed_size: u32 = (vaddr + new_atom_size) - header.virtual_address; + if (needed_size > sect_capacity) { + @panic("TODO move section"); + } + maybe_last_atom.* = atom; + header.virtual_size = needed_size; + header.size_of_raw_data = needed_size; } - if (header.getAlignment() < alignment) { - header.setAlignment(alignment); - } + // if (header.getAlignment().? < alignment) { + // header.setAlignment(alignment); + // } atom.size = new_atom_size; atom.alignment = alignment; @@ -465,30 +480,39 @@ fn allocateSymbol(self: *Coff) !u32 { pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 { const gpa = self.base.allocator; try self.got_entries.ensureUnusedCapacity(gpa, 1); - if (self.got_entries_free_list.popOrNull()) |index| { - log.debug(" (reusing GOT entry index {d})", .{index}); - if (self.got_entries.getIndex(target)) |existing| { - assert(existing == index); + const index: u32 = blk: { + if (self.got_entries_free_list.popOrNull()) |index| { + log.debug(" (reusing GOT entry index {d})", .{index}); + if (self.got_entries.getIndex(target)) |existing| { + assert(existing == index); + } + break :blk index; + } else { + log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len}); + const index = @intCast(u32, self.got_entries.keys().len); + self.got_entries.putAssumeCapacityNoClobber(target, 0); + break :blk index; } - self.got_entries.keys()[index] = target; - return index; - } else { - log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len}); - const index = @intCast(u32, self.got_entries.keys().len); - try self.got_entries.putAssumeCapacityNoClobber(target, 0); - return index; - } + }; + self.got_entries.keys()[index] = target; + return index; } -fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, sect_id: u16) !u64 { +fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { + _ = self; + _ = target; + @panic("TODO createGotAtom"); +} + +fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, sect_id: u16) !u32 { const sym = atom.getSymbol(self); - const align_ok = mem.alignBackwardGeneric(u64, sym.value, alignment) == sym.value; + const align_ok = mem.alignBackwardGeneric(u32, sym.value, alignment) == sym.value; const need_realloc = !align_ok or new_atom_size > atom.capacity(self); if (!need_realloc) return sym.value; return self.allocateAtom(atom, new_atom_size, alignment, sect_id); } -fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u64, sect_id: u16) void { +fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u32, sect_id: u16) void { _ = self; _ = atom; _ = new_block_size; @@ -497,6 +521,22 @@ fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u64, sect_id: u16) void // capacity, insert a free list node for it. } +fn writeAtom(self: *Coff, atom: *Atom, code: []const u8, sect_id: u16) !void { + const section = self.sections.get(sect_id); + const sym = atom.getSymbol(self); + const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address; + try self.resolveRelocs(atom, code); + log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset }); + try self.base.file.?.pwriteAll(code, file_offset); +} + +fn resolveRelocs(self: *Coff, atom: *Atom, code: []const u8) !void { + _ = self; + _ = atom; + _ = code; + log.debug("TODO resolveRelocs", .{}); +} + fn freeAtom(self: *Coff, atom: *Atom, sect_id: u16) void { log.debug("freeAtom {*}", .{atom}); @@ -583,8 +623,7 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live }, }; - const sym = try self.updateDeclCode(decl_index, code); - log.debug("updated decl code has sym {}", .{sym}); + try self.updateDeclCode(decl_index, code); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; @@ -640,20 +679,104 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! }, }; - const sym = try self.updateDeclCode(decl_index, code); - log.debug("updated decl code for {}", .{sym}); + try self.updateDeclCode(decl_index, code); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; return self.updateDeclExports(module, decl_index, decl_exports); } -fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) !*coff.Symbol { - _ = self; - _ = decl_index; - _ = code; - log.debug("TODO updateDeclCode", .{}); - return &self.locals.items[0]; +fn getDeclOutputSection(self: *Coff, decl: *Module.Decl) u16 { + const ty = decl.ty; + const zig_ty = ty.zigTypeTag(); + const val = decl.val; + const index: u16 = blk: { + if (val.isUndefDeep()) { + // TODO in release-fast and release-small, we should put undef in .bss + break :blk self.data_section_index.?; + } + + switch (zig_ty) { + .Fn => break :blk self.text_section_index.?, + else => { + if (val.castTag(.variable)) |_| { + break :blk self.data_section_index.?; + } + break :blk self.rdata_section_index.?; + }, + } + }; + return index; +} + +fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) !void { + const gpa = self.base.allocator; + const mod = self.base.options.module.?; + const decl = mod.declPtr(decl_index); + + const decl_name = try decl.getFullyQualifiedName(mod); + defer gpa.free(decl_name); + + log.debug("updateDeclCode {s}{*}", .{ decl_name, decl }); + const required_alignment = decl.getAlignment(self.base.options.target); + + const decl_ptr = self.decls.getPtr(decl_index).?; + if (decl_ptr.* == null) { + decl_ptr.* = self.getDeclOutputSection(decl); + } + const sect_index = decl_ptr.*.?; + + const code_len = @intCast(u32, code.len); + const atom = &decl.link.coff; + assert(atom.sym_index != 0); // Caller forgot to allocateDeclIndexes() + if (atom.size != 0) { + const sym = atom.getSymbolPtr(self); + const capacity = atom.capacity(self); + const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, sym.value, required_alignment); + + if (need_realloc) { + const vaddr = try self.growAtom(atom, code_len, required_alignment, sect_index); + log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, sym.value, vaddr }); + log.debug(" (required alignment 0x{x}", .{required_alignment}); + + if (vaddr != sym.value) { + sym.value = vaddr; + log.debug(" (updating GOT entry)", .{}); + var buffer: [@sizeOf(u64)]u8 = undefined; + const got_atom = self.getGotAtomForSymbol(.{ .sym_index = atom.sym_index, .file = null }).?; + try self.writeAtom(got_atom, &buffer, self.pdata_section_index.?); + } + } else if (code_len < atom.size) { + self.shrinkAtom(atom, code_len, sect_index); + } + atom.size = code_len; + try self.setSymbolName(sym, decl_name); + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; + sym.storage_class = .NULL; + } else { + const sym = atom.getSymbolPtr(self); + try self.setSymbolName(sym, decl_name); + const vaddr = try self.allocateAtom(atom, code_len, required_alignment, sect_index); + errdefer self.freeAtom(atom, sect_index); + + log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, vaddr }); + + atom.size = code_len; + sym.value = vaddr; + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; + sym.storage_class = .NULL; + + const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null }; + _ = try self.allocateGotEntry(got_target); + const got_atom = try self.createGotAtom(got_target); + + var buffer: [@sizeOf(u64)]u8 = undefined; + try self.writeAtom(got_atom, &buffer, self.pdata_section_index.?); + } + + try self.writeAtom(atom, code, sect_index); } pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { @@ -701,40 +824,142 @@ pub fn updateDeclExports( @panic("Attempted to compile for object format that was disabled by build configuration"); } - // Even in the case of LLVM, we need to notice certain exported symbols in order to - // detect the default subsystem. - for (exports) |exp| { - const exported_decl = module.declPtr(exp.exported_decl); - if (exported_decl.getFunction() == null) continue; - const winapi_cc = switch (self.base.options.target.cpu.arch) { - .i386 => std.builtin.CallingConvention.Stdcall, - else => std.builtin.CallingConvention.C, - }; - const decl_cc = exported_decl.ty.fnCallingConvention(); - if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and - self.base.options.link_libc) - { - module.stage1_flags.have_c_main = true; - } else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) { - if (mem.eql(u8, exp.options.name, "WinMain")) { - module.stage1_flags.have_winmain = true; - } else if (mem.eql(u8, exp.options.name, "wWinMain")) { - module.stage1_flags.have_wwinmain = true; - } else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) { - module.stage1_flags.have_winmain_crt_startup = true; - } else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { - module.stage1_flags.have_wwinmain_crt_startup = true; - } else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) { - module.stage1_flags.have_dllmain_crt_startup = true; + if (build_options.have_llvm) { + // Even in the case of LLVM, we need to notice certain exported symbols in order to + // detect the default subsystem. + for (exports) |exp| { + const exported_decl = module.declPtr(exp.exported_decl); + if (exported_decl.getFunction() == null) continue; + const winapi_cc = switch (self.base.options.target.cpu.arch) { + .i386 => std.builtin.CallingConvention.Stdcall, + else => std.builtin.CallingConvention.C, + }; + const decl_cc = exported_decl.ty.fnCallingConvention(); + if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and + self.base.options.link_libc) + { + module.stage1_flags.have_c_main = true; + } else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) { + if (mem.eql(u8, exp.options.name, "WinMain")) { + module.stage1_flags.have_winmain = true; + } else if (mem.eql(u8, exp.options.name, "wWinMain")) { + module.stage1_flags.have_wwinmain = true; + } else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) { + module.stage1_flags.have_winmain_crt_startup = true; + } else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { + module.stage1_flags.have_wwinmain_crt_startup = true; + } else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) { + module.stage1_flags.have_dllmain_crt_startup = true; + } } } - } - if (build_options.have_llvm) { if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports); } - log.debug("TODO updateDeclExports", .{}); + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.allocator; + + const decl = module.declPtr(decl_index); + const atom = &decl.link.coff; + if (atom.sym_index == 0) return; + const decl_sym = atom.getSymbol(self); + + for (exports) |exp| { + log.debug("adding new export '{s}'", .{exp.options.name}); + + if (exp.options.section) |section_name| { + if (!mem.eql(u8, section_name, ".text")) { + try module.failed_exports.putNoClobber( + module.gpa, + exp, + try Module.ErrorMsg.create( + gpa, + decl.srcLoc(), + "Unimplemented: ExportOptions.section", + .{}, + ), + ); + continue; + } + } + + if (exp.options.linkage == .LinkOnce) { + try module.failed_exports.putNoClobber( + module.gpa, + exp, + try Module.ErrorMsg.create( + gpa, + decl.srcLoc(), + "Unimplemented: GlobalLinkage.LinkOnce", + .{}, + ), + ); + continue; + } + + const sym_index = exp.link.macho.sym_index orelse blk: { + const sym_index = try self.allocateSymbol(); + exp.link.coff.sym_index = sym_index; + break :blk sym_index; + }; + const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + const sym = self.getSymbolPtr(sym_loc); + try self.setSymbolName(sym, exp.options.name); + sym.value = decl_sym.value; + sym.section_number = @intToEnum(coff.SectionNumber, self.text_section_index.? + 1); + sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; + + switch (exp.options.linkage) { + .Strong => { + sym.storage_class = .EXTERNAL; + }, + .Internal => @panic("TODO Internal"), + .Weak => @panic("TODO WeakExternal"), + else => unreachable, + } + + self.resolveGlobalSymbol(sym_loc) catch |err| switch (err) { + error.MultipleSymbolDefinitions => { + const global = self.globals.get(exp.options.name).?; + if (sym_loc.sym_index != global.sym_index and global.file != null) { + _ = try module.failed_exports.put(module.gpa, exp, try Module.ErrorMsg.create( + gpa, + decl.srcLoc(), + \\LinkError: symbol '{s}' defined multiple times + \\ first definition in '{s}' + , + .{ exp.options.name, self.objects.items[global.file.?].name }, + )); + } + }, + else => |e| return e, + }; + } +} + +fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void { + const gpa = self.base.allocator; + const sym = self.getSymbol(current); + _ = sym; + const sym_name = self.getSymbolName(current); + + const name = try gpa.dupe(u8, sym_name); + const global_index = @intCast(u32, self.globals.values().len); + _ = global_index; + const gop = try self.globals.getOrPut(gpa, name); + defer if (gop.found_existing) gpa.free(name); + + if (!gop.found_existing) { + gop.value_ptr.* = current; + // TODO undef + tentative + return; + } + + log.debug("TODO finish resolveGlobalSymbols implementation", .{}); + return error.MultipleSymbolDefinitions; } pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -805,7 +1030,7 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v fn writeStrtab(self: *Coff) !void { const allocated_size = self.allocatedSize(self.strtab_offset.?); - const needed_size = self.strtab.len(); + const needed_size = @intCast(u32, self.strtab.len()); if (needed_size > allocated_size) { self.strtab_offset = null; @@ -870,7 +1095,7 @@ fn writeHeader(self: *Coff) !void { .NX_COMPAT = 1, // We are compatible with Data Execution Prevention }; const subsystem: coff.Subsystem = .WINDOWS_CUI; - const size_of_headers: u32 = @intCast(u32, self.getSizeOfHeaders()); + const size_of_headers: u32 = self.getSizeOfHeaders(); const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, self.page_size); const size_of_headers_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_file_alignment); const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { @@ -968,7 +1193,7 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { math.maxInt(@TypeOf(actual_size)); } -fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { +fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 { const headers_size = self.getSizeOfHeaders(); if (start < headers_size) return headers_size; @@ -976,7 +1201,7 @@ fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { const end = start + padToIdeal(size); if (self.strtab_offset) |off| { - const increased_size = padToIdeal(self.strtab.len()); + const increased_size = padToIdeal(@intCast(u32, self.strtab.len())); const test_end = off + increased_size; if (end > off and start < test_end) { return test_end; @@ -994,10 +1219,10 @@ fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { return null; } -pub fn allocatedSize(self: *Coff, start: u64) u64 { +pub fn allocatedSize(self: *Coff, start: u32) u32 { if (start == 0) return 0; - var min_pos: u64 = std.math.maxInt(u64); + var min_pos: u32 = std.math.maxInt(u32); if (self.strtab_offset) |off| { if (off > start and off < min_pos) min_pos = off; } @@ -1008,41 +1233,41 @@ pub fn allocatedSize(self: *Coff, start: u64) u64 { return min_pos - start; } -pub fn findFreeSpace(self: *Coff, object_size: u64, min_alignment: u32) u64 { - var start: u64 = 0; +pub fn findFreeSpace(self: *Coff, object_size: u32, min_alignment: u32) u32 { + var start: u32 = 0; while (self.detectAllocCollision(start, object_size)) |item_end| { - start = mem.alignForwardGeneric(u64, item_end, min_alignment); + start = mem.alignForwardGeneric(u32, item_end, min_alignment); } return start; } -inline fn getSizeOfHeaders(self: Coff) usize { +inline fn getSizeOfHeaders(self: Coff) u32 { const msdos_hdr_size = msdos_stub.len + 8; - return msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() + - self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize(); + return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() + + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize()); } -inline fn getOptionalHeaderSize(self: Coff) usize { +inline fn getOptionalHeaderSize(self: Coff) u32 { return switch (self.ptr_width) { - .p32 => @sizeOf(coff.OptionalHeaderPE32), - .p64 => @sizeOf(coff.OptionalHeaderPE64), + .p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)), + .p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)), }; } -inline fn getDataDirectoryHeadersSize(self: Coff) usize { - return self.data_directories.len * @sizeOf(coff.ImageDataDirectory); +inline fn getDataDirectoryHeadersSize(self: Coff) u32 { + return @intCast(u32, self.data_directories.len * @sizeOf(coff.ImageDataDirectory)); } -inline fn getSectionHeadersSize(self: Coff) usize { - return self.sections.slice().len * @sizeOf(coff.SectionHeader); +inline fn getSectionHeadersSize(self: Coff) u32 { + return @intCast(u32, self.sections.slice().len * @sizeOf(coff.SectionHeader)); } -inline fn getDataDirectoryHeadersOffset(self: Coff) usize { +inline fn getDataDirectoryHeadersOffset(self: Coff) u32 { const msdos_hdr_size = msdos_stub.len + 8; - return msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize(); + return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize()); } -inline fn getSectionHeadersOffset(self: Coff) usize { +inline fn getSectionHeadersOffset(self: Coff) u32 { return self.getDataDirectoryHeadersOffset() + self.getDataDirectoryHeadersSize(); } @@ -1086,7 +1311,7 @@ fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !v return; } const offset = try self.strtab.insert(self.base.allocator, name); - const name_offset = try fmt.bufPrint(&header.name, "/{d}", .{offset}); + const name_offset = fmt.bufPrint(&header.name, "/{d}", .{offset}) catch unreachable; mem.set(u8, header.name[name_offset.len..], 0); } @@ -1097,6 +1322,6 @@ fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { return; } const offset = try self.strtab.insert(self.base.allocator, name); - mem.copy(u8, symbol.name[0..4], 0); - _ = try fmt.bufPrint(symbol.name[4..], "{d}", .{offset}); + mem.set(u8, symbol.name[0..4], 0); + _ = fmt.bufPrint(symbol.name[4..], "{d}", .{offset}) catch unreachable; } diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig index b61e77f53e..d8420c8850 100644 --- a/src/link/Coff/Atom.zig +++ b/src/link/Coff/Atom.zig @@ -20,7 +20,7 @@ sym_index: u32, file: ?u32, /// Used size of the atom -size: u64, +size: u32, /// Alignment of the atom alignment: u32, @@ -44,10 +44,12 @@ pub fn deinit(self: *Atom, gpa: Allocator) void { _ = gpa; } +/// Returns symbol referencing this atom. pub fn getSymbol(self: Atom, coff_file: *Coff) coff.Symbol { return self.getSymbolPtr(coff_file).*; } +/// Returns pointer-to-symbol referencing this atom. pub fn getSymbolPtr(self: Atom, coff_file: *Coff) *coff.Symbol { return coff_file.getSymbolPtr(.{ .sym_index = self.sym_index, @@ -59,8 +61,16 @@ pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc { return .{ .sym_index = self.sym_index, .file = self.file }; } +/// Returns the name of this atom. +pub fn getName(self: Atom, coff_file: *Coff) []const u8 { + return coff_file.getSymbolName(.{ + .sym_index = self.sym_index, + .file = self.file, + }); +} + /// Returns how much room there is to grow in virtual address space. -pub fn capacity(self: Atom, coff_file: *Coff) u64 { +pub fn capacity(self: Atom, coff_file: *Coff) u32 { const self_sym = self.getSymbol(coff_file); if (self.next) |next| { const next_sym = next.getSymbol(coff_file); @@ -68,7 +78,7 @@ pub fn capacity(self: Atom, coff_file: *Coff) u64 { } else { // We are the last atom. // The capacity is limited only by virtual address space. - return std.math.maxInt(u64) - self_sym.value; + return std.math.maxInt(u32) - self_sym.value; } } From 30baba899cd20c6bc2224f3713f58ba40bbd8709 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Aug 2022 08:50:39 +0200 Subject: [PATCH 23/40] coff: add missing bits required for minimal PE example --- lib/std/start.zig | 2 +- src/Module.zig | 3 + src/link/Coff.zig | 315 +++++++++++++++++++++++++++++++++++----------- 3 files changed, 243 insertions(+), 77 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index e7056a69d0..9f70cce1ea 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -37,7 +37,7 @@ comptime { @export(main2, .{ .name = "main" }); } } else if (builtin.os.tag == .windows) { - if (!@hasDecl(root, "wWinMainCRTStartup")) { + if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) { @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); } } else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) { diff --git a/src/Module.zig b/src/Module.zig index 706ab5ff17..66c36b939b 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -5391,6 +5391,9 @@ fn deleteDeclExports(mod: *Module, decl_index: Decl.Index) void { if (mod.comp.bin_file.cast(link.File.Wasm)) |wasm| { wasm.deleteExport(exp.link.wasm); } + if (mod.comp.bin_file.cast(link.File.Coff)) |coff| { + coff.deleteExport(exp.link.coff); + } if (mod.failed_exports.fetchSwapRemove(exp)) |failed_kv| { failed_kv.value.destroy(mod.gpa); } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a92c751ca7..ba66676706 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -46,8 +46,8 @@ sections: std.MultiArrayList(Section) = .{}, data_directories: [16]coff.ImageDataDirectory, text_section_index: ?u16 = null, +got_section_index: ?u16 = null, rdata_section_index: ?u16 = null, -pdata_section_index: ?u16 = null, data_section_index: ?u16 = null, locals: std.ArrayListUnmanaged(coff.Symbol) = .{}, @@ -76,9 +76,49 @@ managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, /// Table of atoms indexed by the symbol index. atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, +/// Table of unnamed constants associated with a parent `Decl`. +/// We store them here so that we can free the constants whenever the `Decl` +/// needs updating or is freed. +/// +/// For example, +/// +/// ```zig +/// const Foo = struct{ +/// a: u8, +/// }; +/// +/// pub fn main() void { +/// var foo = Foo{ .a = 1 }; +/// _ = foo; +/// } +/// ``` +/// +/// value assigned to label `foo` is an unnamed constant belonging/associated +/// with `Decl` `main`, and lives as long as that `Decl`. +unnamed_const_atoms: UnnamedConstTable = .{}, + +/// A table of relocations indexed by the owning them `TextBlock`. +/// Note that once we refactor `TextBlock`'s lifetime and ownership rules, +/// this will be a table indexed by index into the list of Atoms. +relocs: RelocTable = .{}, + +const Reloc = struct { + target: SymbolWithLoc, + offset: u32, + addend: u32, + prev_vaddr: u32, +}; + +const RelocTable = std.AutoHashMapUnmanaged(*Atom, std.ArrayListUnmanaged(Reloc)); +const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(*Atom)); + const default_file_alignment: u16 = 0x200; const default_image_base_dll: u64 = 0x10000000; const default_image_base_exe: u64 = 0x10000; +const default_size_of_stack_reserve: u32 = 0x1000000; +const default_size_of_stack_commit: u32 = 0x1000; +const default_size_of_heap_reserve: u32 = 0x100000; +const default_size_of_heap_commit: u32 = 0x1000; const Section = struct { header: coff.SectionHeader, @@ -211,6 +251,22 @@ pub fn deinit(self: *Coff) void { self.got_entries_free_list.deinit(gpa); self.decls.deinit(gpa); self.atom_by_index_table.deinit(gpa); + + { + var it = self.unnamed_const_atoms.valueIterator(); + while (it.next()) |atoms| { + atoms.deinit(gpa); + } + self.unnamed_const_atoms.deinit(gpa); + } + + { + var it = self.relocs.valueIterator(); + while (it.next()) |relocs| { + relocs.deinit(gpa); + } + self.relocs.deinit(gpa); + } } fn populateMissingMetadata(self: *Coff) !void { @@ -242,11 +298,11 @@ fn populateMissingMetadata(self: *Coff) !void { try self.sections.append(gpa, .{ .header = header }); } - if (self.pdata_section_index == null) { - self.pdata_section_index = @intCast(u16, self.sections.slice().len); + if (self.got_section_index == null) { + self.got_section_index = @intCast(u16, self.sections.slice().len); const file_size = @intCast(u32, self.base.options.symbol_count_hint); const off = self.findFreeSpace(file_size, self.page_size); - log.debug("found .pdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); + log.debug("found .got free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ .name = undefined, .virtual_size = file_size, @@ -262,7 +318,7 @@ fn populateMissingMetadata(self: *Coff) !void { .MEM_READ = 1, }, }; - try self.setSectionName(&header, ".pdata"); + try self.setSectionName(&header, ".got"); try self.sections.append(gpa, .{ .header = header }); } @@ -330,6 +386,20 @@ fn populateMissingMetadata(self: *Coff) !void { .storage_class = .NULL, .number_of_aux_symbols = 0, }); + + { + // We need to find out what the max file offset is according to section headers. + // Otherwise, we may end up with an COFF binary with file size not matching the final section's + // offset + it's filesize. + // TODO I don't like this here one bit + var max_file_offset: u64 = 0; + for (self.sections.items(.header)) |header| { + if (header.pointer_to_raw_data + header.size_of_raw_data > max_file_offset) { + max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data; + } + } + try self.base.file.?.pwriteAll(&[_]u8{0}, max_file_offset); + } } pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { @@ -418,7 +488,7 @@ fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, se } maybe_last_atom.* = atom; header.virtual_size = needed_size; - header.size_of_raw_data = needed_size; + header.size_of_raw_data = mem.alignForwardGeneric(u32, needed_size, default_file_alignment); } // if (header.getAlignment().? < alignment) { @@ -499,9 +569,35 @@ pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 { } fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { - _ = self; - _ = target; - @panic("TODO createGotAtom"); + const gpa = self.base.allocator; + const atom = try gpa.create(Atom); + errdefer gpa.destroy(atom); + atom.* = Atom.empty; + atom.sym_index = try self.allocateSymbol(); + atom.size = @sizeOf(u64); + atom.alignment = @alignOf(u64); + + try self.managed_atoms.append(gpa, atom); + try self.atom_by_index_table.putNoClobber(gpa, atom.sym_index, atom); + + const sym = atom.getSymbolPtr(self); + sym.value = try self.allocateAtom(atom, atom.size, atom.alignment, self.got_section_index.?); + sym.section_number = @intToEnum(coff.SectionNumber, self.got_section_index.? + 1); + + log.debug("allocated {s} atom at 0x{x}", .{ atom.getName(self), sym.value }); + + const gop_relocs = try self.relocs.getOrPut(gpa, atom); + if (!gop_relocs.found_existing) { + gop_relocs.value_ptr.* = .{}; + } + try gop_relocs.value_ptr.append(gpa, .{ + .target = target, + .offset = 0, + .addend = 0, + .prev_vaddr = sym.value, + }); + + return atom; } fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, sect_id: u16) !u32 { @@ -525,16 +621,46 @@ fn writeAtom(self: *Coff, atom: *Atom, code: []const u8, sect_id: u16) !void { const section = self.sections.get(sect_id); const sym = atom.getSymbol(self); const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address; - try self.resolveRelocs(atom, code); + const resolved = try self.resolveRelocs(atom, code); + defer self.base.allocator.free(resolved); log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset }); - try self.base.file.?.pwriteAll(code, file_offset); + try self.base.file.?.pwriteAll(resolved, file_offset); } -fn resolveRelocs(self: *Coff, atom: *Atom, code: []const u8) !void { - _ = self; - _ = atom; - _ = code; - log.debug("TODO resolveRelocs", .{}); +fn writeGotAtom(self: *Coff, atom: *Atom) !void { + switch (self.ptr_width) { + .p32 => { + var buffer: [@sizeOf(u32)]u8 = [_]u8{0} ** @sizeOf(u32); + try self.writeAtom(atom, &buffer, self.got_section_index.?); + }, + .p64 => { + var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64); + try self.writeAtom(atom, &buffer, self.got_section_index.?); + }, + } +} + +fn resolveRelocs(self: *Coff, atom: *Atom, code: []const u8) ![]const u8 { + const gpa = self.base.allocator; + const resolved = try gpa.dupe(u8, code); + const relocs = self.relocs.get(atom) orelse return resolved; + + for (relocs.items) |*reloc| { + const target_sym = self.getSymbol(reloc.target); + const target_vaddr = target_sym.value + reloc.addend; + if (target_vaddr == reloc.prev_vaddr) continue; + + log.debug(" ({x}: [() => 0x{x} ({s}))", .{ reloc.offset, target_vaddr, self.getSymbolName(reloc.target) }); + + switch (self.ptr_width) { + .p32 => mem.writeIntLittle(u32, resolved[reloc.offset..][0..4], @intCast(u32, target_vaddr)), + .p64 => mem.writeIntLittle(u64, resolved[reloc.offset..][0..8], target_vaddr), + } + + reloc.prev_vaddr = target_vaddr; + } + + return resolved; } fn freeAtom(self: *Coff, atom: *Atom, sect_id: u16) void { @@ -623,7 +749,7 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live }, }; - try self.updateDeclCode(decl_index, code); + try self.updateDeclCode(decl_index, code, .FUNCTION); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; @@ -679,7 +805,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! }, }; - try self.updateDeclCode(decl_index, code); + try self.updateDeclCode(decl_index, code, .NULL); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; @@ -709,7 +835,7 @@ fn getDeclOutputSection(self: *Coff, decl: *Module.Decl) u16 { return index; } -fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) !void { +fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8, complex_type: coff.ComplexType) !void { const gpa = self.base.allocator; const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); @@ -742,9 +868,8 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) if (vaddr != sym.value) { sym.value = vaddr; log.debug(" (updating GOT entry)", .{}); - var buffer: [@sizeOf(u64)]u8 = undefined; const got_atom = self.getGotAtomForSymbol(.{ .sym_index = atom.sym_index, .file = null }).?; - try self.writeAtom(got_atom, &buffer, self.pdata_section_index.?); + try self.writeGotAtom(got_atom); } } else if (code_len < atom.size) { self.shrinkAtom(atom, code_len, sect_index); @@ -752,8 +877,7 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) atom.size = code_len; try self.setSymbolName(sym, decl_name); sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); - sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; - sym.storage_class = .NULL; + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; } else { const sym = atom.getSymbolPtr(self); try self.setSymbolName(sym, decl_name); @@ -765,15 +889,12 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) atom.size = code_len; sym.value = vaddr; sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); - sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; - sym.storage_class = .NULL; + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null }; _ = try self.allocateGotEntry(got_target); const got_atom = try self.createGotAtom(got_target); - - var buffer: [@sizeOf(u64)]u8 = undefined; - try self.writeAtom(got_atom, &buffer, self.pdata_section_index.?); + try self.writeGotAtom(got_atom); } try self.writeAtom(atom, code, sect_index); @@ -900,7 +1021,7 @@ pub fn updateDeclExports( continue; } - const sym_index = exp.link.macho.sym_index orelse blk: { + const sym_index = exp.link.coff.sym_index orelse blk: { const sym_index = try self.allocateSymbol(); exp.link.coff.sym_index = sym_index; break :blk sym_index; @@ -921,22 +1042,36 @@ pub fn updateDeclExports( else => unreachable, } - self.resolveGlobalSymbol(sym_loc) catch |err| switch (err) { - error.MultipleSymbolDefinitions => { - const global = self.globals.get(exp.options.name).?; - if (sym_loc.sym_index != global.sym_index and global.file != null) { - _ = try module.failed_exports.put(module.gpa, exp, try Module.ErrorMsg.create( - gpa, - decl.srcLoc(), - \\LinkError: symbol '{s}' defined multiple times - \\ first definition in '{s}' - , - .{ exp.options.name, self.objects.items[global.file.?].name }, - )); - } - }, - else => |e| return e, - }; + try self.resolveGlobalSymbol(sym_loc); + } +} + +pub fn deleteExport(self: *Coff, exp: Export) void { + if (self.llvm_object) |_| return; + const sym_index = exp.sym_index orelse return; + + const gpa = self.base.allocator; + + const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + const sym = self.getSymbolPtr(sym_loc); + const sym_name = self.getSymbolName(sym_loc); + log.debug("deleting export '{s}'", .{sym_name}); + assert(sym.storage_class == .EXTERNAL); + sym.* = .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }; + self.locals_free_list.append(gpa, sym_index) catch {}; + + if (self.globals.get(sym_name)) |global| blk: { + if (global.sym_index != sym_index) break :blk; + if (global.file != null) break :blk; + const kv = self.globals.fetchSwapRemove(sym_name); + gpa.free(kv.?.key); } } @@ -959,7 +1094,6 @@ fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void { } log.debug("TODO finish resolveGlobalSymbols implementation", .{}); - return error.MultipleSymbolDefinitions; } pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -995,10 +1129,13 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + if (self.getEntryPoint()) |entry_sym_loc| { + self.entry_addr = self.getSymbol(entry_sym_loc).value; + } + try self.writeStrtab(); try self.writeDataDirectoriesHeaders(); try self.writeSectionHeaders(); - try self.writeHeader(); if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); @@ -1006,8 +1143,8 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod } else { log.debug("flushing. no_entry_point_found = false\n", .{}); self.error_flags.no_entry_point_found = false; + try self.writeHeader(); } - self.error_flags.no_entry_point_found = false; } pub fn getDeclVAddr( @@ -1075,11 +1212,12 @@ fn writeHeader(self: *Coff) !void { flags.DLL = 1; } + const timestamp = std.time.timestamp(); const size_of_optional_header = @intCast(u16, self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize()); var coff_header = coff.CoffHeader{ .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section - .time_date_stamp = 0, // TODO + .time_date_stamp = @truncate(u32, @bitCast(u64, timestamp)), .pointer_to_symbol_table = self.strtab_offset orelse 0, .number_of_symbols = 0, .size_of_optional_header = size_of_optional_header, @@ -1095,20 +1233,30 @@ fn writeHeader(self: *Coff) !void { .NX_COMPAT = 1, // We are compatible with Data Execution Prevention }; const subsystem: coff.Subsystem = .WINDOWS_CUI; - const size_of_headers: u32 = self.getSizeOfHeaders(); - const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, self.page_size); - const size_of_headers_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_file_alignment); + const size_of_image: u32 = self.getSizeOfImage(); + const size_of_headers: u32 = mem.alignForwardGeneric(u32, self.getSizeOfHeaders(), default_file_alignment); const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { .Exe => default_image_base_exe, .Lib => default_image_base_dll, else => unreachable, }; - const text_section = self.sections.get(self.text_section_index.?).header; + const base_of_code = self.sections.get(self.text_section_index.?).header.virtual_address; + const base_of_data = self.sections.get(self.data_section_index.?).header.virtual_address; + + var size_of_code: u32 = 0; var size_of_initialized_data: u32 = 0; + var size_of_uninitialized_data: u32 = 0; for (self.sections.items(.header)) |header| { - if (header.flags.CNT_INITIALIZED_DATA == 0) continue; - size_of_initialized_data += header.virtual_size; + if (header.flags.CNT_CODE == 1) { + size_of_code += header.size_of_raw_data; + } + if (header.flags.CNT_INITIALIZED_DATA == 1) { + size_of_initialized_data += header.size_of_raw_data; + } + if (header.flags.CNT_UNINITIALIZED_DATA == 1) { + size_of_uninitialized_data += header.size_of_raw_data; + } } switch (self.ptr_width) { @@ -1117,12 +1265,12 @@ fn writeHeader(self: *Coff) !void { .magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC, .major_linker_version = 0, .minor_linker_version = 0, - .size_of_code = text_section.virtual_size, + .size_of_code = size_of_code, .size_of_initialized_data = size_of_initialized_data, - .size_of_uninitialized_data = 0, + .size_of_uninitialized_data = size_of_uninitialized_data, .address_of_entry_point = self.entry_addr orelse 0, - .base_of_code = text_section.virtual_address, - .base_of_data = 0, + .base_of_code = base_of_code, + .base_of_data = base_of_data, .image_base = @intCast(u32, image_base), .section_alignment = self.page_size, .file_alignment = default_file_alignment, @@ -1133,15 +1281,15 @@ fn writeHeader(self: *Coff) !void { .major_subsystem_version = 6, .minor_subsystem_version = 0, .win32_version_value = 0, - .size_of_image = size_of_image_aligned, - .size_of_headers = size_of_headers_aligned, + .size_of_image = size_of_image, + .size_of_headers = size_of_headers, .checksum = 0, .subsystem = subsystem, .dll_flags = dll_flags, - .size_of_stack_reserve = 0, - .size_of_stack_commit = 0, - .size_of_heap_reserve = 0, - .size_of_heap_commit = 0, + .size_of_stack_reserve = default_size_of_stack_reserve, + .size_of_stack_commit = default_size_of_stack_commit, + .size_of_heap_reserve = default_size_of_heap_reserve, + .size_of_heap_commit = default_size_of_heap_commit, .loader_flags = 0, .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), }; @@ -1152,11 +1300,11 @@ fn writeHeader(self: *Coff) !void { .magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC, .major_linker_version = 0, .minor_linker_version = 0, - .size_of_code = text_section.virtual_size, + .size_of_code = size_of_code, .size_of_initialized_data = size_of_initialized_data, - .size_of_uninitialized_data = 0, + .size_of_uninitialized_data = size_of_uninitialized_data, .address_of_entry_point = self.entry_addr orelse 0, - .base_of_code = text_section.virtual_address, + .base_of_code = base_of_code, .image_base = image_base, .section_alignment = self.page_size, .file_alignment = default_file_alignment, @@ -1167,15 +1315,15 @@ fn writeHeader(self: *Coff) !void { .major_subsystem_version = 6, .minor_subsystem_version = 0, .win32_version_value = 0, - .size_of_image = size_of_image_aligned, - .size_of_headers = size_of_headers_aligned, + .size_of_image = size_of_image, + .size_of_headers = size_of_headers, .checksum = 0, .subsystem = subsystem, .dll_flags = dll_flags, - .size_of_stack_reserve = 0, - .size_of_stack_commit = 0, - .size_of_heap_reserve = 0, - .size_of_heap_commit = 0, + .size_of_stack_reserve = default_size_of_stack_reserve, + .size_of_stack_commit = default_size_of_stack_commit, + .size_of_heap_reserve = default_size_of_heap_reserve, + .size_of_heap_commit = default_size_of_heap_commit, .loader_flags = 0, .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), }; @@ -1183,7 +1331,6 @@ fn writeHeader(self: *Coff) !void { }, } - try self.base.file.?.pwriteAll(&[_]u8{0}, size_of_headers_aligned); try self.base.file.?.pwriteAll(buffer.items, 0); } @@ -1271,6 +1418,22 @@ inline fn getSectionHeadersOffset(self: Coff) u32 { return self.getDataDirectoryHeadersOffset() + self.getDataDirectoryHeadersSize(); } +inline fn getSizeOfImage(self: Coff) u32 { + var max_image_size: u32 = 0; + for (self.sections.items(.header)) |header| { + if (header.virtual_address + header.virtual_size > max_image_size) { + max_image_size = header.virtual_address + header.virtual_size; + } + } + return mem.alignForwardGeneric(u32, @maximum(max_image_size, self.getSizeOfHeaders()), self.page_size); +} + +/// Returns symbol location corresponding to the set entrypoint (if any). +pub fn getEntryPoint(self: Coff) ?SymbolWithLoc { + const entry_name = self.base.options.entry orelse "mainCRTStartup"; // TODO this is incomplete + return self.globals.get(entry_name); +} + /// Returns pointer-to-symbol described by `sym_with_loc` descriptor. pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol { assert(sym_loc.file == null); // TODO linking object files @@ -1323,5 +1486,5 @@ fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { } const offset = try self.strtab.insert(self.base.allocator, name); mem.set(u8, symbol.name[0..4], 0); - _ = fmt.bufPrint(symbol.name[4..], "{d}", .{offset}) catch unreachable; + mem.writeIntLittle(u32, symbol.name[4..8], offset); } From b4e3b87a526378c33d4158ccc622a5183eadfb3f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Aug 2022 13:21:32 +0200 Subject: [PATCH 24/40] coff: ...and lift-off! --- src/link/Coff.zig | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index ba66676706..bbe5e89a3d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -30,6 +30,7 @@ const TypedValue = @import("../TypedValue.zig"); pub const base_tag: link.File.Tag = .coff; const msdos_stub = @embedFile("msdos-stub.bin"); +const N_DATA_DIRS: u5 = 16; /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. llvm_object: ?*LlvmObject = null, @@ -43,7 +44,7 @@ page_size: u32, objects: std.ArrayListUnmanaged(Object) = .{}, sections: std.MultiArrayList(Section) = .{}, -data_directories: [16]coff.ImageDataDirectory, +data_directories: [N_DATA_DIRS]coff.ImageDataDirectory, text_section_index: ?u16 = null, got_section_index: ?u16 = null, @@ -114,7 +115,7 @@ const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayL const default_file_alignment: u16 = 0x200; const default_image_base_dll: u64 = 0x10000000; -const default_image_base_exe: u64 = 0x10000; +const default_image_base_exe: u64 = 0x400000; const default_size_of_stack_reserve: u32 = 0x1000000; const default_size_of_stack_commit: u32 = 0x1000; const default_size_of_heap_reserve: u32 = 0x100000; @@ -210,7 +211,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { }, .ptr_width = ptr_width, .page_size = page_size, - .data_directories = comptime mem.zeroes([16]coff.ImageDataDirectory), + .data_directories = comptime mem.zeroes([N_DATA_DIRS]coff.ImageDataDirectory), }; const use_llvm = build_options.have_llvm and options.use_llvm; @@ -487,8 +488,8 @@ fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, se @panic("TODO move section"); } maybe_last_atom.* = atom; - header.virtual_size = needed_size; - header.size_of_raw_data = mem.alignForwardGeneric(u32, needed_size, default_file_alignment); + // header.virtual_size = needed_size; + // header.size_of_raw_data = mem.alignForwardGeneric(u32, needed_size, default_file_alignment); } // if (header.getAlignment().? < alignment) { @@ -1196,10 +1197,9 @@ fn writeHeader(self: *Coff) !void { try buffer.ensureTotalCapacity(self.getSizeOfHeaders()); writer.writeAll(msdos_stub) catch unreachable; - writer.writeByteNTimes(0, 4) catch unreachable; // align to 8 bytes - writer.writeAll("PE\x00\x00") catch unreachable; - mem.writeIntLittle(u32, buffer.items[0x3c..][0..4], msdos_stub.len + 4); + mem.writeIntLittle(u32, buffer.items[0x3c..][0..4], msdos_stub.len); + writer.writeAll("PE\x00\x00") catch unreachable; var flags = coff.CoffHeaderFlags{ .EXECUTABLE_IMAGE = 1, .DEBUG_STRIPPED = 1, // TODO @@ -1345,10 +1345,10 @@ fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 { if (start < headers_size) return headers_size; - const end = start + padToIdeal(size); + const end = start + size; if (self.strtab_offset) |off| { - const increased_size = padToIdeal(@intCast(u32, self.strtab.len())); + const increased_size = @intCast(u32, self.strtab.len()); const test_end = off + increased_size; if (end > off and start < test_end) { return test_end; @@ -1356,7 +1356,7 @@ fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 { } for (self.sections.items(.header)) |header| { - const increased_size = padToIdeal(header.size_of_raw_data); + const increased_size = header.size_of_raw_data; const test_end = header.pointer_to_raw_data + increased_size; if (end > header.pointer_to_raw_data and start < test_end) { return test_end; @@ -1389,7 +1389,7 @@ pub fn findFreeSpace(self: *Coff, object_size: u32, min_alignment: u32) u32 { } inline fn getSizeOfHeaders(self: Coff) u32 { - const msdos_hdr_size = msdos_stub.len + 8; + const msdos_hdr_size = msdos_stub.len + 4; return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize()); } @@ -1410,7 +1410,7 @@ inline fn getSectionHeadersSize(self: Coff) u32 { } inline fn getDataDirectoryHeadersOffset(self: Coff) u32 { - const msdos_hdr_size = msdos_stub.len + 8; + const msdos_hdr_size = msdos_stub.len + 4; return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize()); } @@ -1419,13 +1419,11 @@ inline fn getSectionHeadersOffset(self: Coff) u32 { } inline fn getSizeOfImage(self: Coff) u32 { - var max_image_size: u32 = 0; + var image_size: u32 = mem.alignForwardGeneric(u32, self.getSizeOfHeaders(), self.page_size); for (self.sections.items(.header)) |header| { - if (header.virtual_address + header.virtual_size > max_image_size) { - max_image_size = header.virtual_address + header.virtual_size; - } + image_size += mem.alignForwardGeneric(u32, header.virtual_size, self.page_size); } - return mem.alignForwardGeneric(u32, @maximum(max_image_size, self.getSizeOfHeaders()), self.page_size); + return image_size; } /// Returns symbol location corresponding to the set entrypoint (if any). From db1a3bb0e70338dc5261adf148284c1408d3df87 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Aug 2022 14:12:39 +0200 Subject: [PATCH 25/40] coff: fallback to _start as default entry point for now This is not technically correct, but given that we are not yet able to link against the CRT, it's a good default until then. Add basic logging of generated symbol table in the linker. --- lib/std/start.zig | 4 -- src/link/Coff.zig | 93 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 9f70cce1ea..49094ab02d 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -36,10 +36,6 @@ comptime { if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) { @export(main2, .{ .name = "main" }); } - } else if (builtin.os.tag == .windows) { - if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) { - @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); - } } else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) { @export(wasiMain2, .{ .name = "_start" }); } else { diff --git a/src/link/Coff.zig b/src/link/Coff.zig index bbe5e89a3d..a90d323482 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1130,6 +1130,10 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + if (build_options.enable_logging) { + self.logSymtab(); + } + if (self.getEntryPoint()) |entry_sym_loc| { self.entry_addr = self.getSymbol(entry_sym_loc).value; } @@ -1428,7 +1432,7 @@ inline fn getSizeOfImage(self: Coff) u32 { /// Returns symbol location corresponding to the set entrypoint (if any). pub fn getEntryPoint(self: Coff) ?SymbolWithLoc { - const entry_name = self.base.options.entry orelse "mainCRTStartup"; // TODO this is incomplete + const entry_name = self.base.options.entry orelse "_start"; // TODO this is incomplete return self.globals.get(entry_name); } @@ -1439,14 +1443,15 @@ pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol { } /// Returns symbol described by `sym_with_loc` descriptor. -pub fn getSymbol(self: *Coff, sym_loc: SymbolWithLoc) coff.Symbol { - return self.getSymbolPtr(sym_loc).*; +pub fn getSymbol(self: *const Coff, sym_loc: SymbolWithLoc) *const coff.Symbol { + assert(sym_loc.file == null); // TODO linking object files + return &self.locals.items[sym_loc.sym_index]; } /// Returns name of the symbol described by `sym_with_loc` descriptor. -pub fn getSymbolName(self: *Coff, sym_loc: SymbolWithLoc) []const u8 { +pub fn getSymbolName(self: *const Coff, sym_loc: SymbolWithLoc) []const u8 { assert(sym_loc.file == null); // TODO linking object files - const sym = self.locals.items[sym_loc.sym_index]; + const sym = self.getSymbol(sym_loc); const offset = sym.getNameOffset() orelse return sym.getName().?; return self.strtab.get(offset).?; } @@ -1486,3 +1491,81 @@ fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { mem.set(u8, symbol.name[0..4], 0); mem.writeIntLittle(u32, symbol.name[4..8], offset); } + +fn logSymAttributes(sym: *const coff.Symbol, buf: *[4]u8) []const u8 { + mem.set(u8, buf[0..4], '_'); + switch (sym.section_number) { + .UNDEFINED => { + buf[3] = 'u'; + switch (sym.storage_class) { + .EXTERNAL => buf[1] = 'e', + .WEAK_EXTERNAL => buf[1] = 'w', + .NULL => {}, + else => unreachable, + } + }, + .ABSOLUTE => unreachable, // handle ABSOLUTE + .DEBUG => unreachable, + else => { + buf[0] = 's'; + switch (sym.storage_class) { + .EXTERNAL => buf[1] = 'e', + .WEAK_EXTERNAL => buf[1] = 'w', + .NULL => {}, + else => unreachable, + } + }, + } + return buf[0..]; +} + +fn logSymtab(self: *Coff) void { + var buf: [4]u8 = undefined; + + log.debug("symtab:", .{}); + log.debug(" object(null)", .{}); + for (self.locals.items) |*sym, sym_id| { + const where = if (sym.section_number == .UNDEFINED) "ord" else "sect"; + const def_index: u16 = switch (sym.section_number) { + .UNDEFINED => 0, // TODO + .ABSOLUTE => unreachable, // TODO + .DEBUG => unreachable, // TODO + else => @enumToInt(sym.section_number), + }; + log.debug(" %{d}: {?s} @{x} in {s}({d}), {s}", .{ + sym_id, + self.getSymbolName(.{ .sym_index = @intCast(u32, sym_id), .file = null }), + sym.value, + where, + def_index, + logSymAttributes(sym, &buf), + }); + } + + log.debug("globals table:", .{}); + for (self.globals.keys()) |name, id| { + const value = self.globals.values()[id]; + log.debug(" {s} => %{d} in object({?d})", .{ name, value.sym_index, value.file }); + } + + log.debug("GOT entries:", .{}); + for (self.got_entries.keys()) |target, i| { + const got_sym = self.getSymbol(.{ .sym_index = self.got_entries.values()[i], .file = null }); + const target_sym = self.getSymbol(target); + if (target_sym.section_number == .UNDEFINED) { + log.debug(" {d}@{x} => import('{s}')", .{ + i, + got_sym.value, + self.getSymbolName(target), + }); + } else { + log.debug(" {d}@{x} => local(%{d}) in object({?d}) {s}", .{ + i, + got_sym.value, + target.sym_index, + target.file, + logSymAttributes(target_sym, &buf), + }); + } + } +} From f0d4ce4494f910508a127aad83cfbc557ac4aef9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Aug 2022 18:15:08 +0200 Subject: [PATCH 26/40] coff: add basic handling of GOT PC relative indirection --- src/arch/x86_64/CodeGen.zig | 62 ++++++++++++----- src/arch/x86_64/Emit.zig | 21 +++++- src/link/Coff.zig | 129 +++++++++++++++++++++++------------- src/link/Coff/Atom.zig | 13 ++-- 4 files changed, 159 insertions(+), 66 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index d7263523c2..60ac26ecc6 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2664,6 +2664,10 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue }; const mod = self.bin_file.options.module.?; const fn_owner_decl = mod.declPtr(self.mod_fn.owner_decl); + const atom_index = if (self.bin_file.tag == link.File.MachO.base_tag) + fn_owner_decl.link.macho.sym_index + else + fn_owner_decl.link.coff.sym_index; _ = try self.addInst(.{ .tag = .lea_pie, .ops = Mir.Inst.Ops.encode(.{ @@ -2672,7 +2676,7 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue }), .data = .{ .relocation = .{ - .atom_index = fn_owner_decl.link.macho.sym_index, + .atom_index = atom_index, .sym_index = sym_index, }, }, @@ -3961,21 +3965,17 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // Due to incremental compilation, how function calls are generated depends // on linking. const mod = self.bin_file.options.module.?; - if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) { + if (self.bin_file.cast(link.File.Elf)) |elf_file| { if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); const fn_owner_decl = mod.declPtr(func.owner_decl); - const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got_addr = blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - break :blk got_sym.value; - } else unreachable; + }; _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), @@ -3999,14 +3999,47 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. .data = undefined, }); } + } else if (self.bin_file.cast(link.File.Coff)) |_| { + if (self.air.value(callee)) |func_value| { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; + const fn_owner_decl = mod.declPtr(func.owner_decl); + const sym_index = fn_owner_decl.link.coff.sym_index; + try self.genSetReg(Type.initTag(.usize), .rax, .{ .got_load = sym_index }); + // callq *%rax + _ = try self.addInst(.{ + .tag = .call, + .ops = Mir.Inst.Ops.encode(.{ + .reg1 = .rax, + .flags = 0b01, + }), + .data = undefined, + }); + } else if (func_value.castTag(.extern_fn)) |_| { + return self.fail("TODO implement calling extern functions", .{}); + } else { + return self.fail("TODO implement calling bitcasted functions", .{}); + } + } else { + assert(ty.zigTypeTag() == .Pointer); + const mcv = try self.resolveInst(callee); + try self.genSetReg(Type.initTag(.usize), .rax, mcv); + _ = try self.addInst(.{ + .tag = .call, + .ops = Mir.Inst.Ops.encode(.{ + .reg1 = .rax, + .flags = 0b01, + }), + .data = undefined, + }); + } } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const fn_owner_decl = mod.declPtr(func.owner_decl); - try self.genSetReg(Type.initTag(.usize), .rax, .{ - .got_load = fn_owner_decl.link.macho.sym_index, - }); + const sym_index = fn_owner_decl.link.macho.sym_index; + try self.genSetReg(Type.initTag(.usize), .rax, .{ .got_load = sym_index }); // callq *%rax _ = try self.addInst(.{ .tag = .call, @@ -6847,10 +6880,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // the linker has enough info to perform relocations. assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - return MCValue{ .memory = got_sym.value }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + assert(decl.link.coff.sym_index != 0); + return MCValue{ .got_load = decl.link.coff.sym_index }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 8b61c6077c..9cf75ab7e3 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -994,6 +994,7 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { ); const end_offset = emit.code.items.len; + const gpa = emit.bin_file.allocator; if (emit.bin_file.cast(link.File.MachO)) |macho_file| { const reloc_type = switch (ops.flags) { @@ -1003,7 +1004,7 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { }; const atom = macho_file.atom_by_index_table.get(relocation.atom_index).?; log.debug("adding reloc of type {} to local @{d}", .{ reloc_type, relocation.sym_index }); - try atom.relocs.append(emit.bin_file.allocator, .{ + try atom.relocs.append(gpa, .{ .offset = @intCast(u32, end_offset - 4), .target = .{ .sym_index = relocation.sym_index, .file = null }, .addend = 0, @@ -1012,6 +1013,24 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .length = 2, .@"type" = reloc_type, }); + } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { + const atom = coff_file.atom_by_index_table.get(relocation.atom_index).?; + log.debug("adding reloc to local @{d}", .{relocation.sym_index}); + const gop = try coff_file.relocs.getOrPut(gpa, atom); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + try gop.value_ptr.append(gpa, .{ + .@"type" = switch (ops.flags) { + 0b00 => .got_pcrel, + 0b01 => .direct, + else => return emit.fail("TODO unused LEA PIE variants 0b10 and 0b11", .{}), + }, + .target = .{ .sym_index = relocation.sym_index, .file = null }, + .offset = @intCast(u32, end_offset - 4), + .addend = 0, + .prev_vaddr = atom.getSymbol(coff_file).value, + }); } else { return emit.fail( "TODO implement lea reg, [rip + reloc] for linking backends different than MachO", diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a90d323482..0c3fbfd6a0 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -104,6 +104,10 @@ unnamed_const_atoms: UnnamedConstTable = .{}, relocs: RelocTable = .{}, const Reloc = struct { + @"type": enum { + got_pcrel, + direct, + }, target: SymbolWithLoc, offset: u32, addend: u32, @@ -413,10 +417,11 @@ pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { try self.decls.putNoClobber(gpa, decl_index, null); } -fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, sect_id: u16) !u32 { +fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 { const tracy = trace(@src()); defer tracy.end(); + const sect_id = @enumToInt(atom.getSymbol(self).section_number) - 1; const header = &self.sections.items(.header)[sect_id]; const free_list = &self.sections.items(.free_list)[sect_id]; const maybe_last_atom = &self.sections.items(.last_atom)[sect_id]; @@ -580,18 +585,20 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { try self.managed_atoms.append(gpa, atom); try self.atom_by_index_table.putNoClobber(gpa, atom.sym_index, atom); + self.got_entries.getPtr(target).?.* = atom.sym_index; const sym = atom.getSymbolPtr(self); - sym.value = try self.allocateAtom(atom, atom.size, atom.alignment, self.got_section_index.?); sym.section_number = @intToEnum(coff.SectionNumber, self.got_section_index.? + 1); + sym.value = try self.allocateAtom(atom, atom.size, atom.alignment); - log.debug("allocated {s} atom at 0x{x}", .{ atom.getName(self), sym.value }); + log.debug("allocated GOT atom at 0x{x}", .{sym.value}); const gop_relocs = try self.relocs.getOrPut(gpa, atom); if (!gop_relocs.found_existing) { gop_relocs.value_ptr.* = .{}; } try gop_relocs.value_ptr.append(gpa, .{ + .@"type" = .direct, .target = target, .offset = 0, .addend = 0, @@ -601,72 +608,98 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { return atom; } -fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, sect_id: u16) !u32 { +fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 { const sym = atom.getSymbol(self); const align_ok = mem.alignBackwardGeneric(u32, sym.value, alignment) == sym.value; const need_realloc = !align_ok or new_atom_size > atom.capacity(self); if (!need_realloc) return sym.value; - return self.allocateAtom(atom, new_atom_size, alignment, sect_id); + return self.allocateAtom(atom, new_atom_size, alignment); } -fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u32, sect_id: u16) void { +fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u32) void { _ = self; _ = atom; _ = new_block_size; - _ = sect_id; // TODO check the new capacity, and if it crosses the size threshold into a big enough // capacity, insert a free list node for it. } -fn writeAtom(self: *Coff, atom: *Atom, code: []const u8, sect_id: u16) !void { - const section = self.sections.get(sect_id); +fn writeAtom(self: *Coff, atom: *Atom, code: []const u8) !void { const sym = atom.getSymbol(self); + const section = self.sections.get(@enumToInt(sym.section_number) - 1); const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address; - const resolved = try self.resolveRelocs(atom, code); - defer self.base.allocator.free(resolved); log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset }); - try self.base.file.?.pwriteAll(resolved, file_offset); + try self.base.file.?.pwriteAll(code, file_offset); + try self.resolveRelocs(atom); } fn writeGotAtom(self: *Coff, atom: *Atom) !void { switch (self.ptr_width) { .p32 => { var buffer: [@sizeOf(u32)]u8 = [_]u8{0} ** @sizeOf(u32); - try self.writeAtom(atom, &buffer, self.got_section_index.?); + try self.writeAtom(atom, &buffer); }, .p64 => { var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64); - try self.writeAtom(atom, &buffer, self.got_section_index.?); + try self.writeAtom(atom, &buffer); }, } } -fn resolveRelocs(self: *Coff, atom: *Atom, code: []const u8) ![]const u8 { - const gpa = self.base.allocator; - const resolved = try gpa.dupe(u8, code); - const relocs = self.relocs.get(atom) orelse return resolved; +fn resolveRelocs(self: *Coff, atom: *Atom) !void { + const relocs = self.relocs.get(atom) orelse return; + const source_sym = atom.getSymbol(self); + const source_section = self.sections.get(@enumToInt(source_sym.section_number) - 1).header; + const file_offset = source_section.pointer_to_raw_data + source_sym.value - source_section.virtual_address; + + log.debug("relocating '{s}'", .{atom.getName(self)}); for (relocs.items) |*reloc| { - const target_sym = self.getSymbol(reloc.target); - const target_vaddr = target_sym.value + reloc.addend; - if (target_vaddr == reloc.prev_vaddr) continue; + const target_vaddr = switch (reloc.@"type") { + .got_pcrel => blk: { + const got_atom = self.getGotAtomForSymbol(reloc.target) orelse continue; + break :blk got_atom.getSymbol(self).value; + }, + .direct => self.getSymbol(reloc.target).value, + }; + const target_vaddr_with_addend = target_vaddr + reloc.addend; - log.debug(" ({x}: [() => 0x{x} ({s}))", .{ reloc.offset, target_vaddr, self.getSymbolName(reloc.target) }); + if (target_vaddr_with_addend == reloc.prev_vaddr) continue; - switch (self.ptr_width) { - .p32 => mem.writeIntLittle(u32, resolved[reloc.offset..][0..4], @intCast(u32, target_vaddr)), - .p64 => mem.writeIntLittle(u64, resolved[reloc.offset..][0..8], target_vaddr), + log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{ + reloc.offset, + target_vaddr_with_addend, + self.getSymbolName(reloc.target), + @tagName(reloc.@"type"), + }); + + switch (reloc.@"type") { + .got_pcrel => { + const source_vaddr = source_sym.value + reloc.offset; + const disp = target_vaddr_with_addend - source_vaddr - 4; + try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, disp)), file_offset + reloc.offset); + }, + .direct => switch (self.ptr_width) { + .p32 => try self.base.file.?.pwriteAll( + mem.asBytes(&@intCast(u32, target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + .p64 => try self.base.file.?.pwriteAll( + mem.asBytes(&(target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + }, } - reloc.prev_vaddr = target_vaddr; + reloc.prev_vaddr = target_vaddr_with_addend; } - - return resolved; } -fn freeAtom(self: *Coff, atom: *Atom, sect_id: u16) void { +fn freeAtom(self: *Coff, atom: *Atom) void { log.debug("freeAtom {*}", .{atom}); + const sym = atom.getSymbol(self); + const sect_id = @enumToInt(sym.section_number) - 1; const free_list = &self.sections.items(.free_list)[sect_id]; var already_have_free_list_node = false; { @@ -858,11 +891,14 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8, assert(atom.sym_index != 0); // Caller forgot to allocateDeclIndexes() if (atom.size != 0) { const sym = atom.getSymbolPtr(self); + try self.setSymbolName(sym, decl_name); + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; + const capacity = atom.capacity(self); const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, sym.value, required_alignment); - if (need_realloc) { - const vaddr = try self.growAtom(atom, code_len, required_alignment, sect_index); + const vaddr = try self.growAtom(atom, code_len, required_alignment); log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, sym.value, vaddr }); log.debug(" (required alignment 0x{x}", .{required_alignment}); @@ -873,32 +909,28 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8, try self.writeGotAtom(got_atom); } } else if (code_len < atom.size) { - self.shrinkAtom(atom, code_len, sect_index); + self.shrinkAtom(atom, code_len); } atom.size = code_len; - try self.setSymbolName(sym, decl_name); - sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); - sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; } else { const sym = atom.getSymbolPtr(self); try self.setSymbolName(sym, decl_name); - const vaddr = try self.allocateAtom(atom, code_len, required_alignment, sect_index); - errdefer self.freeAtom(atom, sect_index); - - log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, vaddr }); - - atom.size = code_len; - sym.value = vaddr; sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; + const vaddr = try self.allocateAtom(atom, code_len, required_alignment); + errdefer self.freeAtom(atom); + log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, vaddr }); + atom.size = code_len; + sym.value = vaddr; + const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null }; _ = try self.allocateGotEntry(got_target); const got_atom = try self.createGotAtom(got_target); try self.writeGotAtom(got_atom); } - try self.writeAtom(atom, code, sect_index); + try self.writeAtom(atom, code); } pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { @@ -912,8 +944,8 @@ pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { log.debug("freeDecl {*}", .{decl}); const kv = self.decls.fetchRemove(decl_index); - if (kv.?.value) |index| { - self.freeAtom(&decl.link.coff, index); + if (kv.?.value) |_| { + self.freeAtom(&decl.link.coff); } // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. @@ -1134,6 +1166,13 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod self.logSymtab(); } + { + var it = self.relocs.keyIterator(); + while (it.next()) |atom| { + try self.resolveRelocs(atom.*); + } + } + if (self.getEntryPoint()) |entry_sym_loc| { self.entry_addr = self.getSymbol(entry_sym_loc).value; } diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig index d8420c8850..2e59187d2b 100644 --- a/src/link/Coff/Atom.zig +++ b/src/link/Coff/Atom.zig @@ -45,8 +45,11 @@ pub fn deinit(self: *Atom, gpa: Allocator) void { } /// Returns symbol referencing this atom. -pub fn getSymbol(self: Atom, coff_file: *Coff) coff.Symbol { - return self.getSymbolPtr(coff_file).*; +pub fn getSymbol(self: Atom, coff_file: *const Coff) *const coff.Symbol { + return coff_file.getSymbol(.{ + .sym_index = self.sym_index, + .file = self.file, + }); } /// Returns pointer-to-symbol referencing this atom. @@ -62,7 +65,7 @@ pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc { } /// Returns the name of this atom. -pub fn getName(self: Atom, coff_file: *Coff) []const u8 { +pub fn getName(self: Atom, coff_file: *const Coff) []const u8 { return coff_file.getSymbolName(.{ .sym_index = self.sym_index, .file = self.file, @@ -70,7 +73,7 @@ pub fn getName(self: Atom, coff_file: *Coff) []const u8 { } /// Returns how much room there is to grow in virtual address space. -pub fn capacity(self: Atom, coff_file: *Coff) u32 { +pub fn capacity(self: Atom, coff_file: *const Coff) u32 { const self_sym = self.getSymbol(coff_file); if (self.next) |next| { const next_sym = next.getSymbol(coff_file); @@ -82,7 +85,7 @@ pub fn capacity(self: Atom, coff_file: *Coff) u32 { } } -pub fn freeListEligible(self: Atom, coff_file: *Coff) bool { +pub fn freeListEligible(self: Atom, coff_file: *const Coff) bool { // No need to keep a free list node for the last atom. const next = self.next orelse return false; const self_sym = self.getSymbol(coff_file); From ebdb2867363256a265646f2a1b66d3762a51b7ba Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Aug 2022 18:57:02 +0200 Subject: [PATCH 27/40] coff: commit missing Object.zig placeholder --- src/link/Coff/Object.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/link/Coff/Object.zig diff --git a/src/link/Coff/Object.zig b/src/link/Coff/Object.zig new file mode 100644 index 0000000000..63bbd07463 --- /dev/null +++ b/src/link/Coff/Object.zig @@ -0,0 +1,12 @@ +const Object = @This(); + +const std = @import("std"); +const mem = std.mem; + +const Allocator = mem.Allocator; + +name: []const u8, + +pub fn deinit(self: *Object, gpa: Allocator) void { + gpa.free(self.name); +} From 601f2147e0344621d238e5209e0d36c69be3cc03 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 30 Aug 2022 00:35:14 +0200 Subject: [PATCH 28/40] coff: cleanup relocations; remove COFF support from other backends Given that COFF will want to support PIC from ground-up, there is no point in leaving outdated code for COFF in other backends such as arm or aarch64. Instead, when we are ready to look into those, we can start figuring out what to add and where. --- src/arch/aarch64/CodeGen.zig | 18 ++++++-------- src/arch/arm/CodeGen.zig | 14 +++-------- src/arch/riscv64/CodeGen.zig | 18 ++++++-------- src/arch/x86_64/CodeGen.zig | 20 +++++++-------- src/arch/x86_64/Emit.zig | 35 +++++++++++++-------------- src/arch/x86_64/Mir.zig | 13 +++++----- src/link/Coff.zig | 47 +++++++++++++++++++++--------------- src/link/Coff/Atom.zig | 12 +++++++++ 8 files changed, 89 insertions(+), 88 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 98f778ef35..884fd68d55 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -3466,20 +3466,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // on linking. const mod = self.bin_file.options.module.?; if (self.air.value(callee)) |func_value| { - if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) { + if (self.bin_file.cast(link.File.Elf)) |elf_file| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); const fn_owner_decl = mod.declPtr(func.owner_decl); - const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got_addr = blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - break :blk got_sym.value; - } else unreachable; + }; try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = got_addr }); @@ -3547,6 +3543,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. } else { return self.fail("TODO implement calling bitcasted functions", .{}); } + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO implement calling in COFF for {}", .{self.target.cpu.arch}); } else unreachable; } else { assert(ty.zigTypeTag() == .Pointer); @@ -5110,10 +5108,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // the linker has enough info to perform relocations. assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - return MCValue{ .memory = got_sym.value }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO codegen COFF const Decl pointer", .{}); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 0f796c530d..cefcf3b114 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -3698,7 +3698,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // Due to incremental compilation, how function calls are generated depends // on linking. switch (self.bin_file.tag) { - .elf, .coff => { + .elf => { if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; @@ -3709,12 +3709,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - break :blk @intCast(u32, got_sym.value); } else unreachable; - try self.genSetReg(Type.initTag(.usize), .lr, .{ .memory = got_addr }); } else if (func_value.castTag(.extern_fn)) |_| { return self.fail("TODO implement calling extern functions", .{}); @@ -3752,6 +3747,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. } }, .macho => unreachable, // unsupported architecture for MachO + .coff => return self.fail("TODO implement call in COFF for {}", .{self.target.cpu.arch}), .plan9 => return self.fail("TODO implement call on plan9 for {}", .{self.target.cpu.arch}), else => unreachable, } @@ -5549,10 +5545,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.MachO)) |_| { unreachable; // unsupported architecture for MachO - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - return MCValue{ .memory = got_sym.value }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO codegen COFF const Decl pointer", .{}); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 2bb68086d8..cd1d0e4050 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1718,7 +1718,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // Due to incremental compilation, how function calls are generated depends // on linking. - if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) { + if (self.bin_file.cast(link.File.Elf)) |elf_file| { for (info.args) |mc_arg, arg_i| { const arg = args[arg_i]; const arg_ty = self.air.typeOf(arg); @@ -1752,14 +1752,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const ptr_bytes: u64 = @divExact(ptr_bits, 8); const mod = self.bin_file.options.module.?; const fn_owner_decl = mod.declPtr(func.owner_decl); - const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got_addr = blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - break :blk got_sym.value; - } else unreachable; + }; try self.genSetReg(Type.initTag(.usize), .ra, .{ .memory = got_addr }); _ = try self.addInst(.{ @@ -1778,6 +1774,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. } else { return self.fail("TODO implement calling runtime known function pointer", .{}); } + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO implement calling in COFF for {}", .{self.target.cpu.arch}); } else if (self.bin_file.cast(link.File.MachO)) |_| { unreachable; // unsupported architecture for MachO } else if (self.bin_file.cast(link.File.Plan9)) |_| { @@ -2592,10 +2590,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // TODO I'm hacking my way through here by repurposing .memory for storing // index to the GOT target symbol index. return MCValue{ .memory = decl.link.macho.sym_index }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - return MCValue{ .memory = got_sym.value }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO codegen COFF const Decl pointer", .{}); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 60ac26ecc6..e5d47e589a 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2657,19 +2657,19 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue .direct_load, => |sym_index| { const abi_size = @intCast(u32, ptr_ty.abiSize(self.target.*)); - const flags: u2 = switch (ptr) { - .got_load => 0b00, - .direct_load => 0b01, - else => unreachable, - }; const mod = self.bin_file.options.module.?; const fn_owner_decl = mod.declPtr(self.mod_fn.owner_decl); const atom_index = if (self.bin_file.tag == link.File.MachO.base_tag) fn_owner_decl.link.macho.sym_index else fn_owner_decl.link.coff.sym_index; + const flags: u2 = switch (ptr) { + .got_load => 0b00, + .direct_load => 0b01, + else => unreachable, + }; _ = try self.addInst(.{ - .tag = .lea_pie, + .tag = .lea_pic, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size), .flags = flags, @@ -4004,9 +4004,9 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const fn_owner_decl = mod.declPtr(func.owner_decl); - const sym_index = fn_owner_decl.link.coff.sym_index; - try self.genSetReg(Type.initTag(.usize), .rax, .{ .got_load = sym_index }); - // callq *%rax + try self.genSetReg(Type.initTag(.usize), .rax, .{ + .got_load = fn_owner_decl.link.coff.sym_index, + }); _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ @@ -6876,8 +6876,6 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.MachO)) |_| { - // Because MachO is PIE-always-on, we defer memory address resolution until - // the linker has enough info to perform relocations. assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |_| { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 9cf75ab7e3..12f3e9118f 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -137,7 +137,7 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .fld => try emit.mirFld(inst), .lea => try emit.mirLea(inst), - .lea_pie => try emit.mirLeaPie(inst), + .lea_pic => try emit.mirLeaPic(inst), .shl => try emit.mirShift(.shl, inst), .sal => try emit.mirShift(.sal, inst), @@ -338,7 +338,7 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { .base = ops.reg1, }), emit.code); }, - 0b11 => return emit.fail("TODO unused JMP/CALL variant 0b11", .{}), + 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), } } @@ -784,7 +784,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { // FD return lowerToFdEnc(.mov, ops.reg1, imm, emit.code); }, - else => return emit.fail("TODO unused variant: movabs 0b{b}", .{ops.flags}), + else => return emit.fail("TODO unused movabs variant", .{}), } } @@ -978,12 +978,17 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .lea_pie); + assert(tag == .lea_pic); const ops = emit.mir.instructions.items(.ops)[inst].decode(); const relocation = emit.mir.instructions.items(.data)[inst].relocation; + switch (ops.flags) { + 0b00, 0b01 => {}, + else => return emit.fail("TODO unused LEA PIC variants 0b10 and 0b11", .{}), + } + // lea reg1, [rip + reloc] // RM try lowerToRmEnc( @@ -1000,7 +1005,7 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const reloc_type = switch (ops.flags) { 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), - else => return emit.fail("TODO unused LEA PIE variants 0b10 and 0b11", .{}), + else => unreachable, }; const atom = macho_file.atom_by_index_table.get(relocation.atom_index).?; log.debug("adding reloc of type {} to local @{d}", .{ reloc_type, relocation.sym_index }); @@ -1015,27 +1020,21 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { }); } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { const atom = coff_file.atom_by_index_table.get(relocation.atom_index).?; - log.debug("adding reloc to local @{d}", .{relocation.sym_index}); - const gop = try coff_file.relocs.getOrPut(gpa, atom); - if (!gop.found_existing) { - gop.value_ptr.* = .{}; - } - try gop.value_ptr.append(gpa, .{ + try atom.addRelocation(coff_file, .{ .@"type" = switch (ops.flags) { - 0b00 => .got_pcrel, + 0b00 => .got, 0b01 => .direct, - else => return emit.fail("TODO unused LEA PIE variants 0b10 and 0b11", .{}), + else => unreachable, }, .target = .{ .sym_index = relocation.sym_index, .file = null }, .offset = @intCast(u32, end_offset - 4), .addend = 0, + .pcrel = true, + .length = 2, .prev_vaddr = atom.getSymbol(coff_file).value, }); } else { - return emit.fail( - "TODO implement lea reg, [rip + reloc] for linking backends different than MachO", - .{}, - ); + return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); } } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index f67b48a271..71aecc5e85 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -178,11 +178,11 @@ pub const Inst = struct { lea, /// ops flags: form: - /// 0b00 reg1, [rip + reloc] // via GOT emits X86_64_RELOC_GOT relocation - /// 0b01 reg1, [rip + reloc] // direct load emits X86_64_RELOC_SIGNED relocation + /// 0b00 reg1, [rip + reloc] // via GOT PIC + /// 0b01 reg1, [rip + reloc] // direct load PIC /// Notes: /// * `Data` contains `relocation` - lea_pie, + lea_pic, /// ops flags: form: /// 0b00 reg1, 1 @@ -242,15 +242,14 @@ pub const Inst = struct { imul_complex, /// ops flags: form: - /// 0bX0 reg1, imm64 - /// 0bX1 rax, moffs64 + /// 0b00 reg1, imm64 + /// 0b01 rax, moffs64 /// Notes: /// * If reg1 is 64-bit, the immediate is 64-bit and stored /// within extra data `Imm64`. - /// * For 0bX1, reg1 (or reg2) need to be + /// * For 0b01, reg1 (or reg2) need to be /// a version of rax. If reg1 == .none, then reg2 == .rax, /// or vice versa. - /// TODO handle scaling movabs, /// ops flags: form: diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 0c3fbfd6a0..36ddfc4e2a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -103,14 +103,16 @@ unnamed_const_atoms: UnnamedConstTable = .{}, /// this will be a table indexed by index into the list of Atoms. relocs: RelocTable = .{}, -const Reloc = struct { +pub const Reloc = struct { @"type": enum { - got_pcrel, + got, direct, }, target: SymbolWithLoc, offset: u32, addend: u32, + pcrel: bool, + length: u2, prev_vaddr: u32, }; @@ -593,15 +595,13 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { log.debug("allocated GOT atom at 0x{x}", .{sym.value}); - const gop_relocs = try self.relocs.getOrPut(gpa, atom); - if (!gop_relocs.found_existing) { - gop_relocs.value_ptr.* = .{}; - } - try gop_relocs.value_ptr.append(gpa, .{ + try atom.addRelocation(self, .{ .@"type" = .direct, .target = target, .offset = 0, .addend = 0, + .pcrel = false, + .length = 3, .prev_vaddr = sym.value, }); @@ -656,7 +656,7 @@ fn resolveRelocs(self: *Coff, atom: *Atom) !void { for (relocs.items) |*reloc| { const target_vaddr = switch (reloc.@"type") { - .got_pcrel => blk: { + .got => blk: { const got_atom = self.getGotAtomForSymbol(reloc.target) orelse continue; break :blk got_atom.getSymbol(self).value; }, @@ -673,21 +673,28 @@ fn resolveRelocs(self: *Coff, atom: *Atom) !void { @tagName(reloc.@"type"), }); - switch (reloc.@"type") { - .got_pcrel => { - const source_vaddr = source_sym.value + reloc.offset; - const disp = target_vaddr_with_addend - source_vaddr - 4; - try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, disp)), file_offset + reloc.offset); - }, - .direct => switch (self.ptr_width) { - .p32 => try self.base.file.?.pwriteAll( - mem.asBytes(&@intCast(u32, target_vaddr_with_addend + default_image_base_exe)), + if (reloc.pcrel) { + const source_vaddr = source_sym.value + reloc.offset; + const disp = target_vaddr_with_addend - source_vaddr - 4; + try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, disp)), file_offset + reloc.offset); + return; + } + + switch (self.ptr_width) { + .p32 => try self.base.file.?.pwriteAll( + mem.asBytes(&@intCast(u32, target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + .p64 => switch (reloc.length) { + 2 => try self.base.file.?.pwriteAll( + mem.asBytes(&@truncate(u32, target_vaddr_with_addend + default_image_base_exe)), file_offset + reloc.offset, ), - .p64 => try self.base.file.?.pwriteAll( + 3 => try self.base.file.?.pwriteAll( mem.asBytes(&(target_vaddr_with_addend + default_image_base_exe)), file_offset + reloc.offset, ), + else => unreachable, }, } @@ -1270,8 +1277,8 @@ fn writeHeader(self: *Coff) !void { writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; const dll_flags: coff.DllFlags = .{ - .HIGH_ENTROPY_VA = 0, // TODO handle ASLR - .DYNAMIC_BASE = 0, // TODO handle ASLR + .HIGH_ENTROPY_VA = 0, //@boolToInt(self.base.options.pie), + .DYNAMIC_BASE = 0, .TERMINAL_SERVER_AWARE = 1, // We are not a legacy app .NX_COMPAT = 1, // We are compatible with Data Execution Prevention }; diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig index 2e59187d2b..6c085a8f58 100644 --- a/src/link/Coff/Atom.zig +++ b/src/link/Coff/Atom.zig @@ -6,6 +6,7 @@ const coff = std.coff; const Allocator = std.mem.Allocator; const Coff = @import("../Coff.zig"); +const Reloc = Coff.Reloc; const SymbolWithLoc = Coff.SymbolWithLoc; /// Each decl always gets a local symbol with the fully qualified name. @@ -96,3 +97,14 @@ pub fn freeListEligible(self: Atom, coff_file: *const Coff) bool { const surplus = cap - ideal_cap; return surplus >= Coff.min_text_capacity; } + +pub fn addRelocation(self: *Atom, coff_file: *Coff, reloc: Reloc) !void { + const gpa = coff_file.base.allocator; + // TODO causes a segfault on Windows + // log.debug("adding reloc of type {s} to target %{d}", .{ @tagName(reloc.@"type"), reloc.target.sym_index }); + const gop = try coff_file.relocs.getOrPut(gpa, self); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + try gop.value_ptr.append(gpa, reloc); +} From 241e1ac030432b309fcfebf2c3f0279875b2c5aa Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 30 Aug 2022 00:45:01 +0200 Subject: [PATCH 29/40] cmake: add runaway Coff/Object.zig path --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 372338bb7e..7a278ad3b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -754,6 +754,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/C.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff/Atom.zig" + "${CMAKE_SOURCE_DIR}/src/link/Coff/Object.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff/lld.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" From e57fbe8069e672483b0c9ae1fa28c00812596306 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 30 Aug 2022 11:01:08 +0200 Subject: [PATCH 30/40] test-cases: fix compiler error locations for hello world with updates --- test/cases/aarch64-macos/hello_world_with_updates.0.zig | 2 +- test/cases/x86_64-linux/hello_world_with_updates.0.zig | 2 +- test/cases/x86_64-macos/hello_world_with_updates.0.zig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/cases/aarch64-macos/hello_world_with_updates.0.zig b/test/cases/aarch64-macos/hello_world_with_updates.0.zig index 0de742bdec..dc65cd8279 100644 --- a/test/cases/aarch64-macos/hello_world_with_updates.0.zig +++ b/test/cases/aarch64-macos/hello_world_with_updates.0.zig @@ -2,5 +2,5 @@ // output_mode=Exe // target=aarch64-macos // -// :107:9: error: struct 'tmp.tmp' has no member named 'main' +// :105:9: error: struct 'tmp.tmp' has no member named 'main' // :7:1: note: struct declared here diff --git a/test/cases/x86_64-linux/hello_world_with_updates.0.zig b/test/cases/x86_64-linux/hello_world_with_updates.0.zig index 4816ec1b26..795f4f2991 100644 --- a/test/cases/x86_64-linux/hello_world_with_updates.0.zig +++ b/test/cases/x86_64-linux/hello_world_with_updates.0.zig @@ -2,5 +2,5 @@ // output_mode=Exe // target=x86_64-linux // -// :107:9: error: struct 'tmp.tmp' has no member named 'main' +// :105:9: error: struct 'tmp.tmp' has no member named 'main' // :7:1: note: struct declared here diff --git a/test/cases/x86_64-macos/hello_world_with_updates.0.zig b/test/cases/x86_64-macos/hello_world_with_updates.0.zig index 998b2f13eb..9839371e31 100644 --- a/test/cases/x86_64-macos/hello_world_with_updates.0.zig +++ b/test/cases/x86_64-macos/hello_world_with_updates.0.zig @@ -2,5 +2,5 @@ // output_mode=Exe // target=x86_64-macos // -// :107:9: error: struct 'tmp.tmp' has no member named 'main' +// :105:9: error: struct 'tmp.tmp' has no member named 'main' // :7:1: note: struct declared here From e6be6d97683d5443f48ce8b6d86fdf27f0f1d556 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 13:02:17 +0300 Subject: [PATCH 31/40] std.rand: make weightedIndex proportions param a const slice The function does not mutate the proportions and the signature should reflect that. --- lib/std/rand.zig | 2 +- lib/std/rand/test.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/rand.zig b/lib/std/rand.zig index c13d2895c9..e94578c119 100644 --- a/lib/std/rand.zig +++ b/lib/std/rand.zig @@ -343,7 +343,7 @@ pub const Random = struct { /// /// This is useful for selecting an item from a slice where weights are not equal. /// `T` must be a numeric type capable of holding the sum of `proportions`. - pub fn weightedIndex(r: std.rand.Random, comptime T: type, proportions: []T) usize { + pub fn weightedIndex(r: std.rand.Random, comptime T: type, proportions: []const T) usize { // This implementation works by summing the proportions and picking a random // point in [0, sum). We then loop over the proportions, accumulating // until our accumulator is greater than the random point. diff --git a/lib/std/rand/test.zig b/lib/std/rand/test.zig index cae77d6e37..1ad9adbeb8 100644 --- a/lib/std/rand/test.zig +++ b/lib/std/rand/test.zig @@ -452,7 +452,7 @@ test "Random weightedIndex" { var prng = DefaultPrng.init(0); const random = prng.random(); - var proportions = [_]T{ 2, 1, 1, 2 }; + const proportions = [_]T{ 2, 1, 1, 2 }; var counts = [_]f64{ 0, 0, 0, 0 }; const n_trials: u64 = 10_000; From b64e4c5bf28286091ff97245e61f08b897e3eb5e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 30 Aug 2022 14:29:06 +0200 Subject: [PATCH 32/40] fix aarch64-macos CI build script --- ci/zinc/build_aarch64_macos | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/zinc/build_aarch64_macos b/ci/zinc/build_aarch64_macos index 5e97ccc913..00a1a8f971 100755 --- a/ci/zinc/build_aarch64_macos +++ b/ci/zinc/build_aarch64_macos @@ -16,5 +16,5 @@ SEARCH_PREFIX="/deps/$TARGET" -Drelease \ -Dstrip \ -Dtarget="$TARGET" \ - -Dmcpu="$MCPU" \ + -Dcpu="$MCPU" \ -Denable-stage1 From aa5568beb6931679dfa8eb498140a6a7f8858ae7 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 30 Aug 2022 15:51:46 +0200 Subject: [PATCH 33/40] coff: move Symtab and Strtab out of coff.Coff This should ease interfacing with different std.coff functionalities. --- lib/std/coff.zig | 242 +++++++++++++++++++++++------------------------ 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 66f057f0b9..e822416f70 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -1100,132 +1100,132 @@ pub const Coff = struct { mem.copy(u8, out_buff, self.data[sec.pointer_to_raw_data..][0..sec.virtual_size]); return out_buff; } +}; - pub const Symtab = struct { +pub const Symtab = struct { + buffer: []const u8, + + pub fn len(self: Symtab) usize { + return @divExact(self.buffer.len, Symbol.sizeOf()); + } + + pub const Tag = enum { + symbol, + func_def, + debug_info, + weak_ext, + file_def, + sect_def, + }; + + pub const Record = union(Tag) { + symbol: Symbol, + debug_info: DebugInfoDefinition, + func_def: FunctionDefinition, + weak_ext: WeakExternalDefinition, + file_def: FileDefinition, + sect_def: SectionDefinition, + }; + + /// Lives as long as Symtab instance. + pub fn at(self: Symtab, index: usize, tag: Tag) Record { + const offset = index * Symbol.sizeOf(); + const raw = self.buffer[offset..][0..Symbol.sizeOf()]; + return switch (tag) { + .symbol => .{ .symbol = asSymbol(raw) }, + .debug_info => .{ .debug_info = asDebugInfo(raw) }, + .func_def => .{ .func_def = asFuncDef(raw) }, + .weak_ext => .{ .weak_ext = asWeakExtDef(raw) }, + .file_def => .{ .file_def = asFileDef(raw) }, + .sect_def => .{ .sect_def = asSectDef(raw) }, + }; + } + + fn asSymbol(raw: []const u8) Symbol { + return .{ + .name = raw[0..8].*, + .value = mem.readIntLittle(u32, raw[8..12]), + .section_number = @intToEnum(SectionNumber, mem.readIntLittle(u16, raw[12..14])), + .@"type" = @bitCast(SymType, mem.readIntLittle(u16, raw[14..16])), + .storage_class = @intToEnum(StorageClass, raw[16]), + .number_of_aux_symbols = raw[17], + }; + } + + fn asDebugInfo(raw: []const u8) DebugInfoDefinition { + return .{ + .unused_1 = raw[0..4].*, + .linenumber = mem.readIntLittle(u16, raw[4..6]), + .unused_2 = raw[6..12].*, + .pointer_to_next_function = mem.readIntLittle(u32, raw[12..16]), + .unused_3 = raw[16..18].*, + }; + } + + fn asFuncDef(raw: []const u8) FunctionDefinition { + return .{ + .tag_index = mem.readIntLittle(u32, raw[0..4]), + .total_size = mem.readIntLittle(u32, raw[4..8]), + .pointer_to_linenumber = mem.readIntLittle(u32, raw[8..12]), + .pointer_to_next_function = mem.readIntLittle(u32, raw[12..16]), + .unused = raw[16..18].*, + }; + } + + fn asWeakExtDef(raw: []const u8) WeakExternalDefinition { + return .{ + .tag_index = mem.readIntLittle(u32, raw[0..4]), + .flag = @intToEnum(WeakExternalFlag, mem.readIntLittle(u32, raw[4..8])), + .unused = raw[8..18].*, + }; + } + + fn asFileDef(raw: []const u8) FileDefinition { + return .{ + .file_name = raw[0..18].*, + }; + } + + fn asSectDef(raw: []const u8) SectionDefinition { + return .{ + .length = mem.readIntLittle(u32, raw[0..4]), + .number_of_relocations = mem.readIntLittle(u16, raw[4..6]), + .number_of_linenumbers = mem.readIntLittle(u16, raw[6..8]), + .checksum = mem.readIntLittle(u32, raw[8..12]), + .number = mem.readIntLittle(u16, raw[12..14]), + .selection = @intToEnum(ComdatSelection, raw[14]), + .unused = raw[15..18].*, + }; + } + + pub const Slice = struct { buffer: []const u8, - - fn len(self: Symtab) usize { - return @divExact(self.buffer.len, Symbol.sizeOf()); - } - - const Tag = enum { - symbol, - func_def, - debug_info, - weak_ext, - file_def, - sect_def, - }; - - const Record = union(Tag) { - symbol: Symbol, - debug_info: DebugInfoDefinition, - func_def: FunctionDefinition, - weak_ext: WeakExternalDefinition, - file_def: FileDefinition, - sect_def: SectionDefinition, - }; + num: usize, + count: usize = 0, /// Lives as long as Symtab instance. - fn at(self: Symtab, index: usize, tag: Tag) Record { - const offset = index * Symbol.sizeOf(); - const raw = self.buffer[offset..][0..Symbol.sizeOf()]; - return switch (tag) { - .symbol => .{ .symbol = asSymbol(raw) }, - .debug_info => .{ .debug_info = asDebugInfo(raw) }, - .func_def => .{ .func_def = asFuncDef(raw) }, - .weak_ext => .{ .weak_ext = asWeakExtDef(raw) }, - .file_def => .{ .file_def = asFileDef(raw) }, - .sect_def => .{ .sect_def = asSectDef(raw) }, - }; - } - - fn asSymbol(raw: []const u8) Symbol { - return .{ - .name = raw[0..8].*, - .value = mem.readIntLittle(u32, raw[8..12]), - .section_number = @intToEnum(SectionNumber, mem.readIntLittle(u16, raw[12..14])), - .@"type" = @bitCast(SymType, mem.readIntLittle(u16, raw[14..16])), - .storage_class = @intToEnum(StorageClass, raw[16]), - .number_of_aux_symbols = raw[17], - }; - } - - fn asDebugInfo(raw: []const u8) DebugInfoDefinition { - return .{ - .unused_1 = raw[0..4].*, - .linenumber = mem.readIntLittle(u16, raw[4..6]), - .unused_2 = raw[6..12].*, - .pointer_to_next_function = mem.readIntLittle(u32, raw[12..16]), - .unused_3 = raw[16..18].*, - }; - } - - fn asFuncDef(raw: []const u8) FunctionDefinition { - return .{ - .tag_index = mem.readIntLittle(u32, raw[0..4]), - .total_size = mem.readIntLittle(u32, raw[4..8]), - .pointer_to_linenumber = mem.readIntLittle(u32, raw[8..12]), - .pointer_to_next_function = mem.readIntLittle(u32, raw[12..16]), - .unused = raw[16..18].*, - }; - } - - fn asWeakExtDef(raw: []const u8) WeakExternalDefinition { - return .{ - .tag_index = mem.readIntLittle(u32, raw[0..4]), - .flag = @intToEnum(WeakExternalFlag, mem.readIntLittle(u32, raw[4..8])), - .unused = raw[8..18].*, - }; - } - - fn asFileDef(raw: []const u8) FileDefinition { - return .{ - .file_name = raw[0..18].*, - }; - } - - fn asSectDef(raw: []const u8) SectionDefinition { - return .{ - .length = mem.readIntLittle(u32, raw[0..4]), - .number_of_relocations = mem.readIntLittle(u16, raw[4..6]), - .number_of_linenumbers = mem.readIntLittle(u16, raw[6..8]), - .checksum = mem.readIntLittle(u32, raw[8..12]), - .number = mem.readIntLittle(u16, raw[12..14]), - .selection = @intToEnum(ComdatSelection, raw[14]), - .unused = raw[15..18].*, - }; - } - - const Slice = struct { - buffer: []const u8, - num: usize, - count: usize = 0, - - /// Lives as long as Symtab instance. - fn next(self: *Slice) ?Symbol { - if (self.count >= self.num) return null; - const sym = asSymbol(self.buffer[0..Symbol.sizeOf()]); - self.count += 1; - self.buffer = self.buffer[Symbol.sizeOf()..]; - return sym; - } - }; - - fn slice(self: Symtab, start: usize, end: ?usize) Slice { - const offset = start * Symbol.sizeOf(); - const llen = if (end) |e| e * Symbol.sizeOf() else self.buffer.len; - const num = @divExact(llen - offset, Symbol.sizeOf()); - return Slice{ .buffer = self.buffer[offset..][0..llen], .num = num }; + pub fn next(self: *Slice) ?Symbol { + if (self.count >= self.num) return null; + const sym = asSymbol(self.buffer[0..Symbol.sizeOf()]); + self.count += 1; + self.buffer = self.buffer[Symbol.sizeOf()..]; + return sym; } }; - pub const Strtab = struct { - buffer: []const u8, - - fn get(self: Strtab, off: u32) []const u8 { - assert(off < self.buffer.len); - return mem.sliceTo(@ptrCast([*:0]const u8, self.buffer.ptr + off), 0); - } - }; + pub fn slice(self: Symtab, start: usize, end: ?usize) Slice { + const offset = start * Symbol.sizeOf(); + const llen = if (end) |e| e * Symbol.sizeOf() else self.buffer.len; + const num = @divExact(llen - offset, Symbol.sizeOf()); + return Slice{ .buffer = self.buffer[offset..][0..llen], .num = num }; + } +}; + +pub const Strtab = struct { + buffer: []const u8, + + pub fn get(self: Strtab, off: u32) []const u8 { + assert(off < self.buffer.len); + return mem.sliceTo(@ptrCast([*:0]const u8, self.buffer.ptr + off), 0); + } }; From 0a42602418dcaf08f13b4220b6c216356f87cbfc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 30 Aug 2022 12:02:26 -0700 Subject: [PATCH 34/40] CI: drone: disable failing tests See tracking issue #12689 --- ci/drone/test_linux_behavior | 3 ++- ci/drone/test_linux_misc | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ci/drone/test_linux_behavior b/ci/drone/test_linux_behavior index 52ce24d689..53c9d94a1e 100755 --- a/ci/drone/test_linux_behavior +++ b/ci/drone/test_linux_behavior @@ -7,7 +7,8 @@ INSTALL_PREFIX="$DRONE_WORKSPACE/stage3-release" ZIG="$INSTALL_PREFIX/bin/zig" export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" -$ZIG build test-behavior -Dskip-non-native --zig-lib-dir lib +# https://github.com/ziglang/zig/issues/12689 +# $ZIG build test-behavior -Dskip-non-native --zig-lib-dir lib $ZIG build test-compiler-rt -Dskip-non-native --zig-lib-dir lib $ZIG build test-fmt --zig-lib-dir lib $ZIG build docs --zig-lib-dir lib diff --git a/ci/drone/test_linux_misc b/ci/drone/test_linux_misc index ef34f6d3d8..a64edf19b8 100755 --- a/ci/drone/test_linux_misc +++ b/ci/drone/test_linux_misc @@ -8,7 +8,8 @@ ZIG="$INSTALL_PREFIX/bin/zig" export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" $ZIG build test-universal-libc -Dskip-non-native --zig-lib-dir lib -$ZIG build test-compare-output -Dskip-non-native --zig-lib-dir lib +# https://github.com/ziglang/zig/issues/12689 +# $ZIG build test-compare-output -Dskip-non-native --zig-lib-dir lib $ZIG build test-standalone -Dskip-non-native --zig-lib-dir lib -Dskip-release-safe $ZIG build test-stack-traces -Dskip-non-native --zig-lib-dir lib $ZIG build test-cli -Dskip-non-native --zig-lib-dir lib From 67a44211f7a442d33096cc0dfff059eee9315bc6 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 14:04:13 +0300 Subject: [PATCH 35/40] Sema: improve handling of always_tail call modifier Closes #4301 Closes #5692 Closes #6281 Closes #10786 Closes #11149 Closes #11776 --- src/Sema.zig | 34 ++++++++++-- src/codegen/llvm.zig | 2 +- test/behavior/call.zig | 54 +++++++++++++++++++ .../compile_errors/invalid_tail_call.zig | 12 +++++ test/cases/taill_call_noreturn.zig | 18 +++++++ 5 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 test/cases/compile_errors/invalid_tail_call.zig create mode 100644 test/cases/taill_call_noreturn.zig diff --git a/src/Sema.zig b/src/Sema.zig index a4e2106afb..823ae9baf2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6152,9 +6152,23 @@ fn analyzeCall( if (ensure_result_used) { try sema.ensureResultUsed(block, result, call_src); } + if (call_tag == .call_always_tail) { + return sema.handleTailCall(block, call_src, func_ty, result); + } return result; } +fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref { + const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl); + if (!func_ty.eql(func_decl.ty, sema.mod)) { + return sema.fail(block, call_src, "unable to perform tail call: type of function being called '{}' does not match type of calling function '{}'", .{ + func_ty.fmt(sema.mod), func_decl.ty.fmt(sema.mod), + }); + } + _ = try block.addUnOp(.ret, result); + return Air.Inst.Ref.unreachable_value; +} + fn analyzeInlineCallArg( sema: *Sema, arg_block: *Block, @@ -6670,7 +6684,8 @@ fn instantiateGenericCall( try sema.requireFunctionBlock(block, call_src); const comptime_args = callee.comptime_args.?; - const new_fn_info = mod.declPtr(callee.owner_decl).ty.fnInfo(); + const func_ty = mod.declPtr(callee.owner_decl).ty; + const new_fn_info = func_ty.fnInfo(); const runtime_args_len = @intCast(u32, new_fn_info.param_types.len); const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len); { @@ -6717,7 +6732,7 @@ fn instantiateGenericCall( try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len + runtime_args_len); - const func_inst = try block.addInst(.{ + const result = try block.addInst(.{ .tag = call_tag, .data = .{ .pl_op = .{ .operand = callee_inst, @@ -6729,9 +6744,12 @@ fn instantiateGenericCall( sema.appendRefsAssumeCapacity(runtime_args); if (ensure_result_used) { - try sema.ensureResultUsed(block, func_inst, call_src); + try sema.ensureResultUsed(block, result, call_src); } - return func_inst; + if (call_tag == .call_always_tail) { + return sema.handleTailCall(block, call_src, func_ty, result); + } + return result; } fn emitDbgInline( @@ -19262,7 +19280,7 @@ fn resolveCallOptions( return wanted_modifier; }, // These can be upgraded to comptime. nosuspend bit can be safely ignored. - .always_tail, .always_inline, .compile_time => { + .always_inline, .compile_time => { _ = (try sema.resolveDefinedValue(block, func_src, func)) orelse { return sema.fail(block, func_src, "modifier '{s}' requires a comptime-known function", .{@tagName(wanted_modifier)}); }; @@ -19272,6 +19290,12 @@ fn resolveCallOptions( } return wanted_modifier; }, + .always_tail => { + if (is_comptime) { + return .compile_time; + } + return wanted_modifier; + }, .async_kw => { if (is_nosuspend) { return sema.fail(block, modifier_src, "modifier 'async_kw' cannot be used inside nosuspend block", .{}); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d1c68b430c..a431c14d5a 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4522,7 +4522,7 @@ pub const FuncGen = struct { "", ); - if (return_type.isNoReturn()) { + if (return_type.isNoReturn() and attr != .AlwaysTail) { _ = self.builder.buildUnreachable(); return null; } diff --git a/test/behavior/call.zig b/test/behavior/call.zig index eafd2ef4e9..4c697ed542 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -261,3 +261,57 @@ test "arguments to comptime parameters generated in comptime blocks" { }; S.foo(S.fortyTwo()); } + +test "forced tail call" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + + const S = struct { + fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 { + if (n == 0) return a; + if (n == 1) return b; + return @call( + .{ .modifier = .always_tail }, + fibonacciTailInternal, + .{ n - 1, b, a + b }, + ); + } + + fn fibonacciTail(n: u16) u16 { + return fibonacciTailInternal(n, 0, 1); + } + }; + try expect(S.fibonacciTail(10) == 55); +} + +test "inline call preserves tail call" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + + const max = std.math.maxInt(u16); + const S = struct { + var a: u16 = 0; + fn foo() void { + return bar(); + } + + inline fn bar() void { + if (a == max) return; + // Stack overflow if not tail called + var buf: [max]u16 = undefined; + buf[a] = a; + a += 1; + return @call(.{ .modifier = .always_tail }, foo, .{}); + } + }; + S.foo(); + try expect(S.a == std.math.maxInt(u16)); +} diff --git a/test/cases/compile_errors/invalid_tail_call.zig b/test/cases/compile_errors/invalid_tail_call.zig new file mode 100644 index 0000000000..cdeb9df930 --- /dev/null +++ b/test/cases/compile_errors/invalid_tail_call.zig @@ -0,0 +1,12 @@ +fn myFn(_: usize) void { + return; +} +pub export fn entry() void { + @call(.{ .modifier = .always_tail }, myFn, .{0}); +} + +// error +// backend=llvm +// target=native +// +// :5:5: error: unable to perform tail call: type of function being called 'fn(usize) void' does not match type of calling function 'fn() callconv(.C) void' diff --git a/test/cases/taill_call_noreturn.zig b/test/cases/taill_call_noreturn.zig new file mode 100644 index 0000000000..0c2497d6ce --- /dev/null +++ b/test/cases/taill_call_noreturn.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const builtin = std.builtin; +pub fn foo(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn { + @call(.{ .modifier = .always_tail }, bar, .{ message, stack_trace }); +} +pub fn bar(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn { + _ = message; + _ = stack_trace; + std.process.exit(0); +} + +pub fn main() void { + foo("foo", null); +} + +// run +// backend=llvm +// target=native From 01d19a8d3cdd52ad50f45bfb8666b56ccf8d3a22 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 14:40:48 +0300 Subject: [PATCH 36/40] Sema: do not emit generic poison for non generic parameters Closes #12679 --- src/Sema.zig | 18 ++++++++++++++++-- ...n-comptime-parameter-used-as-array-size.zig | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 test/cases/compile_errors/non-comptime-parameter-used-as-array-size.zig diff --git a/src/Sema.zig b/src/Sema.zig index 823ae9baf2..bf147a7cb4 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8195,8 +8195,22 @@ fn zirParam( .is_comptime = comptime_syntax, .name = param_name, }); - const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); - try sema.inst_map.putNoClobber(sema.gpa, inst, result); + + if (is_comptime) { + // If this is a comptime parameter we can add a constant generic_poison + // since this is also a generic parameter. + const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); + try sema.inst_map.putNoClobber(sema.gpa, inst, result); + } else { + // Otherwise we need a dummy runtime instruction. + const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len); + try sema.air_instructions.append(sema.gpa, .{ + .tag = .alloc, + .data = .{ .ty = param_ty }, + }); + const result = Air.indexToRef(result_index); + try sema.inst_map.putNoClobber(sema.gpa, inst, result); + } } fn zirParamAnytype( diff --git a/test/cases/compile_errors/non-comptime-parameter-used-as-array-size.zig b/test/cases/compile_errors/non-comptime-parameter-used-as-array-size.zig new file mode 100644 index 0000000000..b5495480ed --- /dev/null +++ b/test/cases/compile_errors/non-comptime-parameter-used-as-array-size.zig @@ -0,0 +1,16 @@ +export fn entry() void { + const llamas1 = makeLlamas(5); + const llamas2 = makeLlamas(5); + _ = llamas1; + _ = llamas2; +} + +fn makeLlamas(count: usize) [count]u8 { + _ = count; +} + +// error +// target=native +// +// :8:30: error: unable to resolve comptime value +// :8:30: note: array length must be comptime known From d3b4b2edf140c002da4c9c1396c26e0f66835eb0 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 15:21:50 +0300 Subject: [PATCH 37/40] Sema: shift of comptime int with runtime value Closes #12290 --- src/AstGen.zig | 33 ++++-- src/Autodoc.zig | 2 +- src/Sema.zig | 102 +++++++++++++----- src/Zir.zig | 15 +-- src/print_zir.zig | 11 +- .../runtime_to_comptime_num.zig | 31 ++++++ ...ing_without_int_type_or_comptime_known.zig | 23 ++++ ...ing_without_int_type_or_comptime_known.zig | 9 -- 8 files changed, 167 insertions(+), 59 deletions(-) create mode 100644 test/cases/compile_errors/runtime_to_comptime_num.zig create mode 100644 test/cases/compile_errors/shifting_without_int_type_or_comptime_known.zig delete mode 100644 test/cases/compile_errors/stage1/obj/shifting_without_int_type_or_comptime_known.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 943d0aad08..79e5ad963e 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -226,6 +226,8 @@ pub const ResultLoc = union(enum) { ref, /// The expression will be coerced into this type, but it will be evaluated as an rvalue. ty: Zir.Inst.Ref, + /// Same as `ty` but for shift operands. + ty_shift_operand: Zir.Inst.Ref, /// Same as `ty` but it is guaranteed that Sema will additionally perform the coercion, /// so no `as` instruction needs to be emitted. coerced_ty: Zir.Inst.Ref, @@ -259,7 +261,7 @@ pub const ResultLoc = union(enum) { fn strategy(rl: ResultLoc, block_scope: *GenZir) Strategy { switch (rl) { // In this branch there will not be any store_to_block_ptr instructions. - .none, .ty, .coerced_ty, .ref => return .{ + .none, .ty, .ty_shift_operand, .coerced_ty, .ref => return .{ .tag = .break_operand, .elide_store_to_block_ptr_instructions = false, }, @@ -302,6 +304,14 @@ pub const ResultLoc = union(enum) { else => rl, }; } + + fn zirTag(rl: ResultLoc) Zir.Inst.Tag { + return switch (rl) { + .ty => .as_node, + .ty_shift_operand => .as_shift_operand, + else => unreachable, + }; + } }; pub const align_rl: ResultLoc = .{ .ty = .u29_type }; @@ -1385,7 +1395,7 @@ fn arrayInitExpr( const tag: Zir.Inst.Tag = if (types.array != .none) .array_init else .array_init_anon; return arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, tag); }, - .ty, .coerced_ty => { + .ty, .ty_shift_operand, .coerced_ty => { const tag: Zir.Inst.Tag = if (types.array != .none) .array_init else .array_init_anon; const result = try arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, tag); return rvalue(gz, rl, result, node); @@ -1631,7 +1641,7 @@ fn structInitExpr( return structInitExprRlNone(gz, scope, node, struct_init, .none, .struct_init_anon); } }, - .ty, .coerced_ty => |ty_inst| { + .ty, .ty_shift_operand, .coerced_ty => |ty_inst| { if (struct_init.ast.type_expr == 0) { const result = try structInitExprRlNone(gz, scope, node, struct_init, ty_inst, .struct_init_anon); return rvalue(gz, rl, result, node); @@ -2327,6 +2337,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .anyframe_type, .as, .as_node, + .as_shift_operand, .bit_and, .bitcast, .bit_or, @@ -2497,7 +2508,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .field_parent_ptr, .maximum, .minimum, - .builtin_async_call, .c_import, .@"resume", .@"await", @@ -7278,7 +7288,7 @@ fn as( ) InnerError!Zir.Inst.Ref { const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { - .none, .discard, .ref, .ty, .coerced_ty => { + .none, .discard, .ref, .ty, .ty_shift_operand, .coerced_ty => { const result = try reachableExpr(gz, scope, .{ .ty = dest_type }, rhs, node); return rvalue(gz, rl, result, node); }, @@ -7959,7 +7969,8 @@ fn builtinCall( return rvalue(gz, rl, result, node); }, .async_call => { - const result = try gz.addPlNode(.builtin_async_call, node, Zir.Inst.AsyncCall{ + const result = try gz.addExtendedPayload(.builtin_async_call, Zir.Inst.AsyncCall{ + .node = gz.nodeIndexToRelative(node), .frame_buffer = try expr(gz, scope, .none, params[0]), .result_ptr = try expr(gz, scope, .none, params[1]), .fn_ptr = try expr(gz, scope, .none, params[2]), @@ -8178,7 +8189,7 @@ fn shiftOp( ) InnerError!Zir.Inst.Ref { const lhs = try expr(gz, scope, .none, lhs_node); const log2_int_type = try gz.addUnNode(.typeof_log2_int_type, lhs, lhs_node); - const rhs = try expr(gz, scope, .{ .ty = log2_int_type }, rhs_node); + const rhs = try expr(gz, scope, .{ .ty_shift_operand = log2_int_type }, rhs_node); const result = try gz.addPlNode(tag, node, Zir.Inst.Bin{ .lhs = lhs, .rhs = rhs, @@ -9409,7 +9420,7 @@ fn rvalue( } return indexToRef(gop.value_ptr.*); }, - .ty => |ty_inst| { + .ty, .ty_shift_operand => |ty_inst| { // Quickly eliminate some common, unnecessary type coercion. const as_ty = @as(u64, @enumToInt(Zir.Inst.Ref.type_type)) << 32; const as_comptime_int = @as(u64, @enumToInt(Zir.Inst.Ref.comptime_int_type)) << 32; @@ -9470,7 +9481,7 @@ fn rvalue( => return result, // type of result is already correct // Need an explicit type coercion instruction. - else => return gz.addPlNode(.as_node, src_node, Zir.Inst.As{ + else => return gz.addPlNode(rl.zirTag(), src_node, Zir.Inst.As{ .dest_type = ty_inst, .operand = result, }), @@ -10350,7 +10361,7 @@ const GenZir = struct { // we emit ZIR for the block break instructions to have the result values, // and then rvalue() on that to pass the value to the result location. switch (parent_rl) { - .ty, .coerced_ty => |ty_inst| { + .ty, .ty_shift_operand, .coerced_ty => |ty_inst| { gz.rl_ty_inst = ty_inst; gz.break_result_loc = parent_rl; }, @@ -11506,7 +11517,7 @@ const GenZir = struct { fn addRet(gz: *GenZir, rl: ResultLoc, operand: Zir.Inst.Ref, node: Ast.Node.Index) !void { switch (rl) { .ptr => |ret_ptr| _ = try gz.addUnNode(.ret_load, ret_ptr, node), - .ty => _ = try gz.addUnNode(.ret_node, operand, node), + .ty, .ty_shift_operand => _ = try gz.addUnNode(.ret_node, operand, node), else => unreachable, } } diff --git a/src/Autodoc.zig b/src/Autodoc.zig index db681157ae..0e056c093f 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -1888,7 +1888,7 @@ fn walkInstruction( .expr = .{ .typeInfo = operand_index }, }; }, - .as_node => { + .as_node, .as_shift_operand => { const pl_node = data[inst_index].pl_node; const extra = file.zir.extraData(Zir.Inst.As, pl_node.payload_index); const dest_type_walk = try self.walkRef( diff --git a/src/Sema.zig b/src/Sema.zig index bf147a7cb4..1b35f52437 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -712,6 +712,7 @@ fn analyzeBodyInner( .vector_type => try sema.zirVectorType(block, inst), .as => try sema.zirAs(block, inst), .as_node => try sema.zirAsNode(block, inst), + .as_shift_operand => try sema.zirAsShiftOperand(block, inst), .bit_and => try sema.zirBitwise(block, inst, .bit_and), .bit_not => try sema.zirBitNot(block, inst), .bit_or => try sema.zirBitwise(block, inst, .bit_or), @@ -848,7 +849,6 @@ fn analyzeBodyInner( .mul_add => try sema.zirMulAdd(block, inst), .builtin_call => try sema.zirBuiltinCall(block, inst), .field_parent_ptr => try sema.zirFieldParentPtr(block, inst), - .builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst), .@"resume" => try sema.zirResume(block, inst), .@"await" => try sema.zirAwait(block, inst), .array_base_ptr => try sema.zirArrayBasePtr(block, inst), @@ -956,6 +956,7 @@ fn analyzeBodyInner( .error_to_int => try sema.zirErrorToInt( block, extended), .int_to_error => try sema.zirIntToError( block, extended), .reify => try sema.zirReify( block, extended, inst), + .builtin_async_call => try sema.zirBuiltinAsyncCall( block, extended), // zig fmt: on .fence => { try sema.zirFence(block, extended); @@ -8257,7 +8258,7 @@ fn zirAs(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst defer tracy.end(); const bin_inst = sema.code.instructions.items(.data)[inst].bin; - return sema.analyzeAs(block, sema.src, bin_inst.lhs, bin_inst.rhs); + return sema.analyzeAs(block, sema.src, bin_inst.lhs, bin_inst.rhs, false); } fn zirAsNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8267,7 +8268,17 @@ fn zirAsNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const extra = sema.code.extraData(Zir.Inst.As, inst_data.payload_index).data; - return sema.analyzeAs(block, src, extra.dest_type, extra.operand); + return sema.analyzeAs(block, src, extra.dest_type, extra.operand, false); +} + +fn zirAsShiftOperand(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const tracy = trace(@src()); + defer tracy.end(); + + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.As, inst_data.payload_index).data; + return sema.analyzeAs(block, src, extra.dest_type, extra.operand, true); } fn analyzeAs( @@ -8276,6 +8287,7 @@ fn analyzeAs( src: LazySrcLoc, zir_dest_type: Zir.Inst.Ref, zir_operand: Zir.Inst.Ref, + no_cast_to_comptime_int: bool, ) CompileError!Air.Inst.Ref { const is_ret = if (Zir.refToIndex(zir_dest_type)) |ptr_index| sema.code.instructions.items(.tag)[ptr_index] == .ret_type @@ -8287,7 +8299,7 @@ fn analyzeAs( if (dest_ty.zigTypeTag() == .NoReturn) { return sema.fail(block, src, "cannot cast to noreturn", .{}); } - return sema.coerceExtra(block, dest_ty, operand, src, true, is_ret) catch |err| switch (err) { + return sema.coerceExtra(block, dest_ty, operand, src, .{ .is_ret = is_ret, .no_cast_to_comptime_int = no_cast_to_comptime_int }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -10491,7 +10503,12 @@ fn zirShl( const runtime_src = if (maybe_lhs_val) |lhs_val| rs: { if (lhs_val.isUndef()) return sema.addConstUndef(lhs_ty); - const rhs_val = maybe_rhs_val orelse break :rs rhs_src; + const rhs_val = maybe_rhs_val orelse { + if (scalar_ty.zigTypeTag() == .ComptimeInt) { + return sema.fail(block, src, "LHS of shift must be a fixed-width integer type, or RHS must be a comptime known", .{}); + } + break :rs rhs_src; + }; const val = switch (air_tag) { .shl_exact => val: { @@ -10615,7 +10632,10 @@ fn zirShr( const target = sema.mod.getTarget(); const scalar_ty = lhs_ty.scalarType(); - const runtime_src = if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| rs: { + const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs); + + const runtime_src = if (maybe_rhs_val) |rhs_val| rs: { if (rhs_val.isUndef()) { return sema.addConstUndef(lhs_ty); } @@ -10647,7 +10667,7 @@ fn zirShr( }); } } - if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { + if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { return sema.addConstUndef(lhs_ty); } @@ -10665,6 +10685,10 @@ fn zirShr( } } else rhs_src; + if (maybe_rhs_val == null and scalar_ty.zigTypeTag() == .ComptimeInt) { + return sema.fail(block, src, "LHS of shift must be a fixed-width integer type, or RHS must be a comptime known", .{}); + } + try sema.requireRuntimeBlock(block, src, runtime_src); const result = try block.addBinOp(air_tag, lhs, rhs); if (block.wantSafety()) { @@ -15385,7 +15409,7 @@ fn analyzeRet( if (sema.fn_ret_ty.zigTypeTag() == .ErrorUnion) { try sema.addToInferredErrorSet(uncasted_operand); } - const operand = sema.coerceExtra(block, sema.fn_ret_ty, uncasted_operand, src, true, true) catch |err| switch (err) { + const operand = sema.coerceExtra(block, sema.fn_ret_ty, uncasted_operand, src, .{ .is_ret = true }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -19652,9 +19676,9 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void }); } -fn zirBuiltinAsyncCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); +fn zirBuiltinAsyncCall(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { + const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; + const src = LazySrcLoc.nodeOffset(extra.node); return sema.failWithUseOfAsync(block, src); } @@ -22542,7 +22566,7 @@ fn coerce( inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, true, false) catch |err| switch (err) { + return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, .{}) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -22554,14 +22578,22 @@ const CoersionError = CompileError || error{ NotCoercible, }; +const CoerceOpts = struct { + /// Should coerceExtra emit error messages. + report_err: bool = true, + /// Ignored if `report_err == false`. + is_ret: bool = false, + /// Should coercion to comptime_int ermit an error message. + no_cast_to_comptime_int: bool = false, +}; + fn coerceExtra( sema: *Sema, block: *Block, dest_ty_unresolved: Type, inst: Air.Inst.Ref, inst_src: LazySrcLoc, - report_err: bool, - is_ret: bool, + opts: CoerceOpts, ) CoersionError!Air.Inst.Ref { switch (dest_ty_unresolved.tag()) { .var_args_param => return sema.coerceVarArgParam(block, inst, inst_src), @@ -22613,7 +22645,7 @@ fn coerceExtra( // T to ?T const child_type = try dest_ty.optionalChildAlloc(sema.arena); - const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, false, is_ret) catch |err| switch (err) { + const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { error.NotCoercible => { if (in_memory_result == .no_match) { // Try to give more useful notes @@ -22729,7 +22761,7 @@ fn coerceExtra( return sema.addConstant(dest_ty, Value.@"null"); }, .ComptimeInt => { - const addr = sema.coerceExtra(block, Type.usize, inst, inst_src, false, is_ret) catch |err| switch (err) { + const addr = sema.coerceExtra(block, Type.usize, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { error.NotCoercible => break :pointer, else => |e| return e, }; @@ -22740,7 +22772,7 @@ fn coerceExtra( .signed => Type.isize, .unsigned => Type.usize, }; - const addr = sema.coerceExtra(block, ptr_size_ty, inst, inst_src, false, is_ret) catch |err| switch (err) { + const addr = sema.coerceExtra(block, ptr_size_ty, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { error.NotCoercible => { // Try to give more useful notes in_memory_result = try sema.coerceInMemoryAllowed(block, ptr_size_ty, inst_ty, false, target, dest_ty_src, inst_src); @@ -22866,7 +22898,13 @@ fn coerceExtra( }, .Int, .ComptimeInt => switch (inst_ty.zigTypeTag()) { .Float, .ComptimeFloat => float: { - const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse break :float; + const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse { + if (dest_ty.zigTypeTag() == .ComptimeInt) { + if (!opts.report_err) return error.NotCoercible; + return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_int' must be comptime known"); + } + break :float; + }; if (val.floatHasFraction()) { return sema.fail( @@ -22883,11 +22921,16 @@ fn coerceExtra( if (try sema.resolveDefinedValue(block, inst_src, inst)) |val| { // comptime known integer to other number if (!(try sema.intFitsInType(block, inst_src, val, dest_ty, null))) { - if (!report_err) return error.NotCoercible; + if (!opts.report_err) return error.NotCoercible; return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(sema.mod), val.fmtValue(inst_ty, sema.mod) }); } return try sema.addConstant(dest_ty, val); } + if (dest_ty.zigTypeTag() == .ComptimeInt) { + if (!opts.report_err) return error.NotCoercible; + if (opts.no_cast_to_comptime_int) return inst; + return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_int' must be comptime known"); + } // integer widening const dst_info = dest_ty.intInfo(target); @@ -22924,6 +22967,7 @@ fn coerceExtra( } return try sema.addConstant(dest_ty, result_val); } else if (dest_ty.zigTypeTag() == .ComptimeFloat) { + if (!opts.report_err) return error.NotCoercible; return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_float' must be comptime known"); } @@ -22936,7 +22980,13 @@ fn coerceExtra( } }, .Int, .ComptimeInt => int: { - const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse break :int; + const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse { + if (dest_ty.zigTypeTag() == .ComptimeFloat) { + if (!opts.report_err) return error.NotCoercible; + return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_float' must be comptime known"); + } + break :int; + }; const result_val = try val.intToFloat(sema.arena, inst_ty, dest_ty, target); // TODO implement this compile error //const int_again_val = try result_val.floatToInt(sema.arena, inst_ty); @@ -23088,9 +23138,9 @@ fn coerceExtra( return sema.addConstUndef(dest_ty); } - if (!report_err) return error.NotCoercible; + if (!opts.report_err) return error.NotCoercible; - if (is_ret and dest_ty.zigTypeTag() == .NoReturn) { + if (opts.is_ret and dest_ty.zigTypeTag() == .NoReturn) { const msg = msg: { const msg = try sema.errMsg(block, inst_src, "function declared 'noreturn' returns", .{}); errdefer msg.destroy(sema.gpa); @@ -23127,7 +23177,7 @@ fn coerceExtra( try in_memory_result.report(sema, block, inst_src, msg); // Add notes about function return type - if (is_ret and sema.mod.test_functions.get(sema.func.?.owner_decl) == null) { + if (opts.is_ret and sema.mod.test_functions.get(sema.func.?.owner_decl) == null) { const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = 0 }; const src_decl = sema.mod.declPtr(sema.func.?.owner_decl); if (inst_ty.isError() and !dest_ty.isError()) { @@ -24088,7 +24138,7 @@ fn storePtr2( // https://github.com/ziglang/zig/issues/11154 if (sema.obtainBitCastedVectorPtr(ptr)) |vector_ptr| { const vector_ty = sema.typeOf(vector_ptr).childType(); - const vector = sema.coerceExtra(block, vector_ty, uncasted_operand, operand_src, true, is_ret) catch |err| switch (err) { + const vector = sema.coerceExtra(block, vector_ty, uncasted_operand, operand_src, .{ .is_ret = is_ret }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -24096,7 +24146,7 @@ fn storePtr2( return; } - const operand = sema.coerceExtra(block, elem_ty, uncasted_operand, operand_src, true, is_ret) catch |err| switch (err) { + const operand = sema.coerceExtra(block, elem_ty, uncasted_operand, operand_src, .{ .is_ret = is_ret }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -26831,7 +26881,7 @@ fn wrapErrorUnionPayload( inst_src: LazySrcLoc, ) !Air.Inst.Ref { const dest_payload_ty = dest_ty.errorUnionPayload(); - const coerced = try sema.coerceExtra(block, dest_payload_ty, inst, inst_src, false, false); + const coerced = try sema.coerceExtra(block, dest_payload_ty, inst, inst_src, .{ .report_err = false }); if (try sema.resolveMaybeUndefVal(block, inst_src, coerced)) |val| { return sema.addConstant(dest_ty, try Value.Tag.eu_payload.create(sema.arena, val)); } diff --git a/src/Zir.zig b/src/Zir.zig index ec9ddfcffb..3ce086e10b 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -242,6 +242,8 @@ pub const Inst = struct { /// Type coercion to the function's return type. /// Uses the `pl_node` field. Payload is `As`. AST node could be many things. as_node, + /// Same as `as_node` but ignores runtime to comptime int error. + as_shift_operand, /// Bitwise AND. `&` bit_and, /// Reinterpret the memory representation of a value as a different type. @@ -942,9 +944,6 @@ pub const Inst = struct { /// Implements the `@maximum` builtin. /// Uses the `pl_node` union field with payload `Bin` maximum, - /// Implements the `@asyncCall` builtin. - /// Uses the `pl_node` union field with payload `AsyncCall`. - builtin_async_call, /// Implements the `@cImport` builtin. /// Uses the `pl_node` union field with payload `Block`. c_import, @@ -1029,6 +1028,7 @@ pub const Inst = struct { .anyframe_type, .as, .as_node, + .as_shift_operand, .bit_and, .bitcast, .bit_or, @@ -1231,7 +1231,6 @@ pub const Inst = struct { .memcpy, .memset, .minimum, - .builtin_async_call, .c_import, .@"resume", .@"await", @@ -1339,6 +1338,7 @@ pub const Inst = struct { .anyframe_type, .as, .as_node, + .as_shift_operand, .bit_and, .bitcast, .bit_or, @@ -1513,7 +1513,6 @@ pub const Inst = struct { .field_parent_ptr, .maximum, .minimum, - .builtin_async_call, .c_import, .@"resume", .@"await", @@ -1577,6 +1576,7 @@ pub const Inst = struct { .anyframe_type = .un_node, .as = .bin, .as_node = .pl_node, + .as_shift_operand = .pl_node, .bit_and = .pl_node, .bitcast = .pl_node, .bit_not = .un_node, @@ -1801,7 +1801,6 @@ pub const Inst = struct { .memcpy = .pl_node, .memset = .pl_node, .minimum = .pl_node, - .builtin_async_call = .pl_node, .c_import = .pl_node, .alloc = .un_node, @@ -1972,6 +1971,9 @@ pub const Inst = struct { /// `operand` is payload index to `UnNode`. /// `small` contains `NameStrategy reify, + /// Implements the `@asyncCall` builtin. + /// `operand` is payload index to `AsyncCall`. + builtin_async_call, pub const InstData = struct { opcode: Extended, @@ -3454,6 +3456,7 @@ pub const Inst = struct { }; pub const AsyncCall = struct { + node: i32, frame_buffer: Ref, result_ptr: Ref, fn_ptr: Ref, diff --git a/src/print_zir.zig b/src/print_zir.zig index f315d7f014..976d012138 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -283,7 +283,6 @@ const Writer = struct { .mul_add => try self.writeMulAdd(stream, inst), .field_parent_ptr => try self.writeFieldParentPtr(stream, inst), .builtin_call => try self.writeBuiltinCall(stream, inst), - .builtin_async_call => try self.writeBuiltinAsyncCall(stream, inst), .struct_init_anon, .struct_init_anon_ref, @@ -397,7 +396,7 @@ const Writer = struct { .field_val_named, => try self.writePlNodeFieldNamed(stream, inst), - .as_node => try self.writeAs(stream, inst), + .as_node, .as_shift_operand => try self.writeAs(stream, inst), .repeat, .repeat_inline, @@ -531,6 +530,7 @@ const Writer = struct { try stream.writeAll(") "); try self.writeSrc(stream, src); }, + .builtin_async_call => try self.writeBuiltinAsyncCall(stream, extended), } } @@ -814,9 +814,8 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } - fn writeBuiltinAsyncCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Zir.Inst.AsyncCall, inst_data.payload_index).data; + fn writeBuiltinAsyncCall(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.AsyncCall, extended.operand).data; try self.writeInstRef(stream, extra.frame_buffer); try stream.writeAll(", "); try self.writeInstRef(stream, extra.result_ptr); @@ -825,7 +824,7 @@ const Writer = struct { try stream.writeAll(", "); try self.writeInstRef(stream, extra.args); try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); + try self.writeSrc(stream, LazySrcLoc.nodeOffset(extra.node)); } fn writeParam(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { diff --git a/test/cases/compile_errors/runtime_to_comptime_num.zig b/test/cases/compile_errors/runtime_to_comptime_num.zig new file mode 100644 index 0000000000..2275e35c43 --- /dev/null +++ b/test/cases/compile_errors/runtime_to_comptime_num.zig @@ -0,0 +1,31 @@ +pub export fn entry() void { + var a: u32 = 0; + _ = @as(comptime_int, a); +} +pub export fn entry2() void{ + var a: u32 = 0; + _ = @as(comptime_float, a); +} +pub export fn entry3() void{ + comptime var aa: comptime_float = 0.0; + var a: f32 = 4; + aa = a; +} +pub export fn entry4() void{ + comptime var aa: comptime_int = 0.0; + var a: f32 = 4; + aa = a; +} + +// error +// backend=stage2 +// target=native +// +// :3:27: error: unable to resolve comptime value +// :3:27: note: value being casted to 'comptime_int' must be comptime known +// :7:29: error: unable to resolve comptime value +// :7:29: note: value being casted to 'comptime_float' must be comptime known +// :12:10: error: unable to resolve comptime value +// :12:10: note: value being casted to 'comptime_float' must be comptime known +// :17:10: error: unable to resolve comptime value +// :17:10: note: value being casted to 'comptime_int' must be comptime known diff --git a/test/cases/compile_errors/shifting_without_int_type_or_comptime_known.zig b/test/cases/compile_errors/shifting_without_int_type_or_comptime_known.zig new file mode 100644 index 0000000000..7e68baed62 --- /dev/null +++ b/test/cases/compile_errors/shifting_without_int_type_or_comptime_known.zig @@ -0,0 +1,23 @@ +export fn entry(x: u8) u8 { + return 0x11 << x; +} +export fn entry1(x: u8) u8 { + return 0x11 >> x; +} +export fn entry2() void { + var x: u5 = 1; + _ = @shlExact(12345, x); +} +export fn entry3() void { + var x: u5 = 1; + _ = @shrExact(12345, x); +} + +// error +// backend=stage2 +// target=native +// +// :2:17: error: LHS of shift must be a fixed-width integer type, or RHS must be a comptime known +// :5:17: error: LHS of shift must be a fixed-width integer type, or RHS must be a comptime known +// :9:9: error: LHS of shift must be a fixed-width integer type, or RHS must be a comptime known +// :13:9: error: LHS of shift must be a fixed-width integer type, or RHS must be a comptime known diff --git a/test/cases/compile_errors/stage1/obj/shifting_without_int_type_or_comptime_known.zig b/test/cases/compile_errors/stage1/obj/shifting_without_int_type_or_comptime_known.zig deleted file mode 100644 index 4d875575d0..0000000000 --- a/test/cases/compile_errors/stage1/obj/shifting_without_int_type_or_comptime_known.zig +++ /dev/null @@ -1,9 +0,0 @@ -export fn entry(x: u8) u8 { - return 0x11 << x; -} - -// error -// backend=stage1 -// target=native -// -// tmp.zig:2:17: error: LHS of shift must be a fixed-width integer type, or RHS must be compile-time known From c558de6655e2e9b72c5733b2d477ff18520d1c6b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 16:01:28 +0300 Subject: [PATCH 38/40] stage2 llvm: use tag value instead of field index in airUnionInit Closes #12656 --- src/Sema.zig | 12 ++++++------ src/codegen/llvm.zig | 19 ++++++++++++++++--- test/behavior/union.zig | 28 ++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 1b35f52437..74f19e5b93 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -21933,6 +21933,7 @@ fn unionFieldPtr( .mutable = union_ptr_ty.ptrIsMutable(), .@"addrspace" = union_ptr_ty.ptrAddressSpace(), }); + const enum_field_index = @intCast(u32, union_obj.tag_ty.enumFieldIndex(field_name).?); if (initializing and field.ty.zigTypeTag() == .NoReturn) { const msg = msg: { @@ -21954,11 +21955,10 @@ fn unionFieldPtr( if (union_val.isUndef()) { return sema.failWithUseOfUndef(block, src); } - const enum_field_index = union_obj.tag_ty.enumFieldIndex(field_name).?; const tag_and_val = union_val.castTag(.@"union").?.data; var field_tag_buf: Value.Payload.U32 = .{ .base = .{ .tag = .enum_field_index }, - .data = @intCast(u32, enum_field_index), + .data = enum_field_index, }; const field_tag = Value.initPayload(&field_tag_buf.base); const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, sema.mod); @@ -21990,7 +21990,7 @@ fn unionFieldPtr( if (!initializing and union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null and union_obj.fields.count() > 1) { - const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); const wanted_tag = try sema.addConstant(union_obj.tag_ty, wanted_tag_val); // TODO would it be better if get_union_tag supported pointers to unions? const union_val = try block.addTyOp(.load, union_ty, union_ptr); @@ -22020,15 +22020,15 @@ fn unionFieldVal( const union_obj = union_ty.cast(Type.Payload.Union).?.data; const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_name_src); const field = union_obj.fields.values()[field_index]; + const enum_field_index = @intCast(u32, union_obj.tag_ty.enumFieldIndex(field_name).?); if (try sema.resolveMaybeUndefVal(block, src, union_byval)) |union_val| { if (union_val.isUndef()) return sema.addConstUndef(field.ty); const tag_and_val = union_val.castTag(.@"union").?.data; - const enum_field_index = union_obj.tag_ty.enumFieldIndex(field_name).?; var field_tag_buf: Value.Payload.U32 = .{ .base = .{ .tag = .enum_field_index }, - .data = @intCast(u32, enum_field_index), + .data = enum_field_index, }; const field_tag = Value.initPayload(&field_tag_buf.base); const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, sema.mod); @@ -22064,7 +22064,7 @@ fn unionFieldVal( if (union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null and union_obj.fields.count() > 1) { - const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); const wanted_tag = try sema.addConstant(union_obj.tag_ty, wanted_tag_val); const active_tag = try block.addTyOp(.get_union_tag, union_obj.tag_ty, union_byval); const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index a431c14d5a..93d2eaa9df 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -8527,12 +8527,26 @@ pub const FuncGen = struct { const union_llvm_ty = try self.dg.lowerType(union_ty); const target = self.dg.module.getTarget(); const layout = union_ty.unionGetLayout(target); + const union_obj = union_ty.cast(Type.Payload.Union).?.data; + const tag_int = blk: { + const tag_ty = union_ty.unionTagTypeHypothetical(); + const union_field_name = union_obj.fields.keys()[extra.field_index]; + const enum_field_index = tag_ty.enumFieldIndex(union_field_name).?; + var tag_val_payload: Value.Payload.U32 = .{ + .base = .{ .tag = .enum_field_index }, + .data = @intCast(u32, enum_field_index), + }; + const tag_val = Value.initPayload(&tag_val_payload.base); + var int_payload: Value.Payload.U64 = undefined; + const tag_int_val = tag_val.enumToInt(tag_ty, &int_payload); + break :blk tag_int_val.toUnsignedInt(target); + }; if (layout.payload_size == 0) { if (layout.tag_size == 0) { return null; } assert(!isByRef(union_ty)); - return union_llvm_ty.constInt(extra.field_index, .False); + return union_llvm_ty.constInt(tag_int, .False); } assert(isByRef(union_ty)); // The llvm type of the alloca will the the named LLVM union type, which will not @@ -8541,7 +8555,6 @@ pub const FuncGen = struct { // then set the fields appropriately. const result_ptr = self.buildAlloca(union_llvm_ty); const llvm_payload = try self.resolveInst(extra.init); - const union_obj = union_ty.cast(Type.Payload.Union).?.data; assert(union_obj.haveFieldTypes()); const field = union_obj.fields.values()[extra.field_index]; const field_llvm_ty = try self.dg.lowerType(field.ty); @@ -8625,7 +8638,7 @@ pub const FuncGen = struct { }; const field_ptr = self.builder.buildInBoundsGEP(casted_ptr, &indices, indices.len, ""); const tag_llvm_ty = try self.dg.lowerType(union_obj.tag_ty); - const llvm_tag = tag_llvm_ty.constInt(extra.field_index, .False); + const llvm_tag = tag_llvm_ty.constInt(tag_int, .False); const store_inst = self.builder.buildStore(llvm_tag, field_ptr); store_inst.setAlignment(union_obj.tag_ty.abiAlignment(target)); } diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 5d6b084be5..79bc1861e4 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1324,3 +1324,31 @@ test "union and enum field order doesn't match" { x = .b; try expect(x == .b); } + +test "@unionInit uses tag value instead of field index" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + const E = enum(u8) { + b = 255, + a = 3, + }; + const U = union(E) { + a: usize, + b: isize, + }; + var i: isize = -1; + var u = @unionInit(U, "b", i); + { + var a = u.b; + try expect(a == i); + } + { + var a = &u.b; + try expect(a.* == i); + } + try expect(@enumToInt(u) == 255); +} From 65d37239682b6418b6dd25f07187ae99088e67f0 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 17:13:35 +0300 Subject: [PATCH 39/40] Sema: check that target supports tail calls --- lib/std/target.zig | 10 ++++++++++ src/Sema.zig | 4 ++++ test/behavior/call.zig | 4 ++++ test/cases/taill_call_noreturn.zig | 2 +- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 64f9f97809..7b4a468a61 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1440,6 +1440,16 @@ pub const Target = struct { return !self.cpu.arch.isWasm(); } + pub fn supportsTailCall(self: Target) bool { + switch (self.cpu.arch) { + .wasm32, .wasm64 => return wasm.featureSetHas(self.cpu.features, .tail_call), + // TODO these might not be true but LLVM doesn't seem to be able to handle them + .mips, .mipsel, .mips64, .mips64el => return false, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => return false, + else => return true, + } + } + pub const FloatAbi = enum { hard, soft, diff --git a/src/Sema.zig b/src/Sema.zig index 74f19e5b93..8cde9d7e12 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6160,6 +6160,10 @@ fn analyzeCall( } fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref { + const target = sema.mod.getTarget(); + if (!target.supportsTailCall()) { + return sema.fail(block, call_src, "unable to perform tail call: target does not support tail calls", .{}); + } const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl); if (!func_ty.eql(func_decl.ty, sema.mod)) { return sema.fail(block, call_src, "unable to perform tail call: type of function being called '{}' does not match type of calling function '{}'", .{ diff --git a/test/behavior/call.zig b/test/behavior/call.zig index 4c697ed542..4a4ff4fb78 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -270,6 +270,8 @@ test "forced tail call" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest; + const S = struct { fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 { if (n == 0) return a; @@ -296,6 +298,8 @@ test "inline call preserves tail call" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest; + const max = std.math.maxInt(u16); const S = struct { var a: u16 = 0; diff --git a/test/cases/taill_call_noreturn.zig b/test/cases/taill_call_noreturn.zig index 0c2497d6ce..fabb9e729b 100644 --- a/test/cases/taill_call_noreturn.zig +++ b/test/cases/taill_call_noreturn.zig @@ -15,4 +15,4 @@ pub fn main() void { // run // backend=llvm -// target=native +// target=x86_64-linux,x86_64-macos,aarch64-linux,aarch64-macos From 7377dce368090e3c49a15d8996cc812adadd3d43 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 30 Aug 2022 12:43:10 -0700 Subject: [PATCH 40/40] avoid exposing supportsTailCall in the standard library This is problematic because in practice it depends on whether the compiler backend supports it too, as evidenced by the TODO comment about LLVM not supporting some architectures that in fact do support tail calls. Instead this logic is organized strategically in src/target.zig, part of the internal compiler source code, and the behavior tests in question duplicate some logic for deciding whether to proceed with the test. The proper place to expose this flag is in `@import("builtin")` - the generated source file - so that third party compilers can advertise whether they support tail calls. --- lib/std/target.zig | 10 ---- src/Compilation.zig | 36 +++++++------- src/Sema.zig | 7 ++- src/codegen/llvm.zig | 110 +++++++++++++++++++++++++++++++++++++++++ src/link.zig | 7 +-- src/mingw.zig | 8 +-- src/target.zig | 108 +++------------------------------------- test/behavior/call.zig | 14 +++++- 8 files changed, 161 insertions(+), 139 deletions(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 7b4a468a61..64f9f97809 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1440,16 +1440,6 @@ pub const Target = struct { return !self.cpu.arch.isWasm(); } - pub fn supportsTailCall(self: Target) bool { - switch (self.cpu.arch) { - .wasm32, .wasm64 => return wasm.featureSetHas(self.cpu.features, .tail_call), - // TODO these might not be true but LLVM doesn't seem to be able to handle them - .mips, .mipsel, .mips64, .mips64el => return false, - .powerpc, .powerpcle, .powerpc64, .powerpc64le => return false, - else => return true, - } - } - pub const FloatAbi = enum { hard, soft, diff --git a/src/Compilation.zig b/src/Compilation.zig index 8e4b322230..c1321e40cf 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4766,6 +4766,24 @@ pub fn dump_argv(argv: []const []const u8) void { std.debug.print("{s}\n", .{argv[argv.len - 1]}); } +pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend { + const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1; + if (use_stage1) return .stage1; + if (build_options.have_llvm and comp.bin_file.options.use_llvm) return .stage2_llvm; + const target = comp.bin_file.options.target; + if (target.ofmt == .c) return .stage2_c; + return switch (target.cpu.arch) { + .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm, + .arm, .armeb, .thumb, .thumbeb => .stage2_arm, + .x86_64 => .stage2_x86_64, + .i386 => .stage2_x86, + .aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64, + .riscv64 => .stage2_riscv64, + .sparc64 => .stage2_sparc64, + else => .other, + }; +} + pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Allocator.Error![:0]u8 { const tracy_trace = trace(@src()); defer tracy_trace.end(); @@ -4775,23 +4793,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca const target = comp.getTarget(); const generic_arch_name = target.cpu.arch.genericName(); - const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1; - - const zig_backend: std.builtin.CompilerBackend = blk: { - if (use_stage1) break :blk .stage1; - if (build_options.have_llvm and comp.bin_file.options.use_llvm) break :blk .stage2_llvm; - if (target.ofmt == .c) break :blk .stage2_c; - break :blk switch (target.cpu.arch) { - .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm, - .arm, .armeb, .thumb, .thumbeb => .stage2_arm, - .x86_64 => .stage2_x86_64, - .i386 => .stage2_x86, - .aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64, - .riscv64 => .stage2_riscv64, - .sparc64 => .stage2_sparc64, - else => .other, - }; - }; + const zig_backend = comp.getZigBackend(); @setEvalBranchQuota(4000); try buffer.writer().print( diff --git a/src/Sema.zig b/src/Sema.zig index 8cde9d7e12..0626fd30ee 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6161,8 +6161,11 @@ fn analyzeCall( fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref { const target = sema.mod.getTarget(); - if (!target.supportsTailCall()) { - return sema.fail(block, call_src, "unable to perform tail call: target does not support tail calls", .{}); + const backend = sema.mod.comp.getZigBackend(); + if (!target_util.supportsTailCall(target, backend)) { + return sema.fail(block, call_src, "unable to perform tail call: compiler backend '{s}' does not support tail calls on target architecture '{s}' with the selected CPU feature flags", .{ + @tagName(backend), @tagName(target.cpu.arch), + }); } const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl); if (!func_ty.eql(func_decl.ty, sema.mod)) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 93d2eaa9df..45426c5ee0 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -177,6 +177,116 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 { return llvm_triple.toOwnedSliceSentinel(0); } +pub fn targetOs(os_tag: std.Target.Os.Tag) llvm.OSType { + return switch (os_tag) { + .freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS, + .windows, .uefi => .Win32, + .ananas => .Ananas, + .cloudabi => .CloudABI, + .dragonfly => .DragonFly, + .freebsd => .FreeBSD, + .fuchsia => .Fuchsia, + .ios => .IOS, + .kfreebsd => .KFreeBSD, + .linux => .Linux, + .lv2 => .Lv2, + .macos => .MacOSX, + .netbsd => .NetBSD, + .openbsd => .OpenBSD, + .solaris => .Solaris, + .zos => .ZOS, + .haiku => .Haiku, + .minix => .Minix, + .rtems => .RTEMS, + .nacl => .NaCl, + .aix => .AIX, + .cuda => .CUDA, + .nvcl => .NVCL, + .amdhsa => .AMDHSA, + .ps4 => .PS4, + .elfiamcu => .ELFIAMCU, + .tvos => .TvOS, + .watchos => .WatchOS, + .mesa3d => .Mesa3D, + .contiki => .Contiki, + .amdpal => .AMDPAL, + .hermit => .HermitCore, + .hurd => .Hurd, + .wasi => .WASI, + .emscripten => .Emscripten, + }; +} + +pub fn targetArch(arch_tag: std.Target.Cpu.Arch) llvm.ArchType { + return switch (arch_tag) { + .arm => .arm, + .armeb => .armeb, + .aarch64 => .aarch64, + .aarch64_be => .aarch64_be, + .aarch64_32 => .aarch64_32, + .arc => .arc, + .avr => .avr, + .bpfel => .bpfel, + .bpfeb => .bpfeb, + .csky => .csky, + .hexagon => .hexagon, + .m68k => .m68k, + .mips => .mips, + .mipsel => .mipsel, + .mips64 => .mips64, + .mips64el => .mips64el, + .msp430 => .msp430, + .powerpc => .ppc, + .powerpcle => .ppcle, + .powerpc64 => .ppc64, + .powerpc64le => .ppc64le, + .r600 => .r600, + .amdgcn => .amdgcn, + .riscv32 => .riscv32, + .riscv64 => .riscv64, + .sparc => .sparc, + .sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9. + .sparcel => .sparcel, + .s390x => .systemz, + .tce => .tce, + .tcele => .tcele, + .thumb => .thumb, + .thumbeb => .thumbeb, + .i386 => .x86, + .x86_64 => .x86_64, + .xcore => .xcore, + .nvptx => .nvptx, + .nvptx64 => .nvptx64, + .le32 => .le32, + .le64 => .le64, + .amdil => .amdil, + .amdil64 => .amdil64, + .hsail => .hsail, + .hsail64 => .hsail64, + .spir => .spir, + .spir64 => .spir64, + .kalimba => .kalimba, + .shave => .shave, + .lanai => .lanai, + .wasm32 => .wasm32, + .wasm64 => .wasm64, + .renderscript32 => .renderscript32, + .renderscript64 => .renderscript64, + .ve => .ve, + .spu_2, .spirv32, .spirv64 => .UnknownArch, + }; +} + +pub fn supportsTailCall(target: std.Target) bool { + switch (target.cpu.arch) { + .wasm32, .wasm64 => return std.Target.wasm.featureSetHas(target.cpu.features, .tail_call), + // Although these ISAs support tail calls, LLVM does not support tail calls on them. + .mips, .mipsel, .mips64, .mips64el => return false, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => return false, + else => return true, + } +} + pub const Object = struct { gpa: Allocator, module: *Module, diff --git a/src/link.zig b/src/link.zig index 421188bd47..a8845a0d57 100644 --- a/src/link.zig +++ b/src/link.zig @@ -931,9 +931,10 @@ pub const File = struct { std.debug.print("\n", .{}); } - const llvm = @import("codegen/llvm/bindings.zig"); - const os_type = @import("target.zig").osToLLVM(base.options.target.os.tag); - const bad = llvm.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_type); + const llvm_bindings = @import("codegen/llvm/bindings.zig"); + const llvm = @import("codegen/llvm.zig"); + const os_tag = llvm.targetOs(base.options.target.os.tag); + const bad = llvm_bindings.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_tag); if (bad) return error.UnableToWriteArchive; if (!base.options.disable_lld_caching) { diff --git a/src/mingw.zig b/src/mingw.zig index b50cc4b009..23035fe72b 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -6,7 +6,6 @@ const assert = std.debug.assert; const log = std.log.scoped(.mingw); const builtin = @import("builtin"); -const target_util = @import("target.zig"); const Compilation = @import("Compilation.zig"); const build_options = @import("build_options"); const Cache = @import("Cache.zig"); @@ -404,11 +403,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { }); errdefer comp.gpa.free(lib_final_path); - const llvm = @import("codegen/llvm/bindings.zig"); - const arch_type = target_util.archToLLVM(target.cpu.arch); + const llvm_bindings = @import("codegen/llvm/bindings.zig"); + const llvm = @import("codegen/llvm.zig"); + const arch_tag = llvm.targetArch(target.cpu.arch); const def_final_path_z = try arena.dupeZ(u8, def_final_path); const lib_final_path_z = try arena.dupeZ(u8, lib_final_path); - if (llvm.WriteImportLibrary(def_final_path_z.ptr, arch_type, lib_final_path_z.ptr, true)) { + if (llvm_bindings.WriteImportLibrary(def_final_path_z.ptr, arch_tag, lib_final_path_z.ptr, true)) { // TODO surface a proper error here log.err("unable to turn {s}.def into {s}.lib", .{ lib_name, lib_name }); return error.WritingImportLibFailed; diff --git a/src/target.zig b/src/target.zig index 405a7fe2bf..f8b44bac0e 100644 --- a/src/target.zig +++ b/src/target.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const llvm = @import("codegen/llvm/bindings.zig"); const Type = @import("type.zig").Type; pub const ArchOsAbi = struct { @@ -317,106 +316,6 @@ pub fn supportsReturnAddress(target: std.Target) bool { }; } -pub fn osToLLVM(os_tag: std.Target.Os.Tag) llvm.OSType { - return switch (os_tag) { - .freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS, - .windows, .uefi => .Win32, - .ananas => .Ananas, - .cloudabi => .CloudABI, - .dragonfly => .DragonFly, - .freebsd => .FreeBSD, - .fuchsia => .Fuchsia, - .ios => .IOS, - .kfreebsd => .KFreeBSD, - .linux => .Linux, - .lv2 => .Lv2, - .macos => .MacOSX, - .netbsd => .NetBSD, - .openbsd => .OpenBSD, - .solaris => .Solaris, - .zos => .ZOS, - .haiku => .Haiku, - .minix => .Minix, - .rtems => .RTEMS, - .nacl => .NaCl, - .aix => .AIX, - .cuda => .CUDA, - .nvcl => .NVCL, - .amdhsa => .AMDHSA, - .ps4 => .PS4, - .elfiamcu => .ELFIAMCU, - .tvos => .TvOS, - .watchos => .WatchOS, - .mesa3d => .Mesa3D, - .contiki => .Contiki, - .amdpal => .AMDPAL, - .hermit => .HermitCore, - .hurd => .Hurd, - .wasi => .WASI, - .emscripten => .Emscripten, - }; -} - -pub fn archToLLVM(arch_tag: std.Target.Cpu.Arch) llvm.ArchType { - return switch (arch_tag) { - .arm => .arm, - .armeb => .armeb, - .aarch64 => .aarch64, - .aarch64_be => .aarch64_be, - .aarch64_32 => .aarch64_32, - .arc => .arc, - .avr => .avr, - .bpfel => .bpfel, - .bpfeb => .bpfeb, - .csky => .csky, - .hexagon => .hexagon, - .m68k => .m68k, - .mips => .mips, - .mipsel => .mipsel, - .mips64 => .mips64, - .mips64el => .mips64el, - .msp430 => .msp430, - .powerpc => .ppc, - .powerpcle => .ppcle, - .powerpc64 => .ppc64, - .powerpc64le => .ppc64le, - .r600 => .r600, - .amdgcn => .amdgcn, - .riscv32 => .riscv32, - .riscv64 => .riscv64, - .sparc => .sparc, - .sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9. - .sparcel => .sparcel, - .s390x => .systemz, - .tce => .tce, - .tcele => .tcele, - .thumb => .thumb, - .thumbeb => .thumbeb, - .i386 => .x86, - .x86_64 => .x86_64, - .xcore => .xcore, - .nvptx => .nvptx, - .nvptx64 => .nvptx64, - .le32 => .le32, - .le64 => .le64, - .amdil => .amdil, - .amdil64 => .amdil64, - .hsail => .hsail, - .hsail64 => .hsail64, - .spir => .spir, - .spir64 => .spir64, - .kalimba => .kalimba, - .shave => .shave, - .lanai => .lanai, - .wasm32 => .wasm32, - .wasm64 => .wasm64, - .renderscript32 => .renderscript32, - .renderscript64 => .renderscript64, - .ve => .ve, - .spu_2, .spirv32, .spirv64 => .UnknownArch, - }; -} - fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool { if (ignore_case) { return std.ascii.eqlIgnoreCase(a, b); @@ -770,3 +669,10 @@ pub fn supportsFunctionAlignment(target: std.Target) bool { else => true, }; } + +pub fn supportsTailCall(target: std.Target, backend: std.builtin.CompilerBackend) bool { + switch (backend) { + .stage1, .stage2_llvm => return @import("codegen/llvm.zig").supportsTailCall(target), + else => return false, + } +} diff --git a/test/behavior/call.zig b/test/behavior/call.zig index 4a4ff4fb78..37c6ce3691 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -270,7 +270,12 @@ test "forced tail call" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_llvm) { + // Only attempt this test on targets we know have tail call support in LLVM. + if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) { + return error.SkipZigTest; + } + } const S = struct { fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 { @@ -298,7 +303,12 @@ test "inline call preserves tail call" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_llvm) { + // Only attempt this test on targets we know have tail call support in LLVM. + if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) { + return error.SkipZigTest; + } + } const max = std.math.maxInt(u16); const S = struct {