From 20fae334acb79dd367e74a3b90af5fa1445364cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 23 Jan 2026 19:42:23 +0100 Subject: [PATCH 1/4] compiler: UEFI does not support dynamic linking --- src/target.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/target.zig b/src/target.zig index 87d0522ce7277549364ee6baf464320216eb11c8..100c0f690ba8fd099b13fa4e53626c8daf0234c7 100644 --- a/src/target.zig +++ b/src/target.zig @@ -12,7 +12,7 @@ pub const default_stack_protector_buffer_size = 4; pub fn cannotDynamicLink(target: *const std.Target) bool { return switch (target.os.tag) { - .freestanding => true, + .freestanding, .uefi => true, else => target.cpu.arch.isSpirV(), }; } -- 2.54.0 From 909159ad8ea9203297c0b670446381c537554525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 23 Jan 2026 19:45:34 +0100 Subject: [PATCH 2/4] compiler: don't enforce PIC for x86-windows and thumb-windows Only x86_64-windows and aarch64-windows actually require PIC. --- src/Compilation/Config.zig | 2 +- src/Package/Module.zig | 2 +- src/Sema.zig | 10 +++++++++- src/target.zig | 33 ++++++++++++++++++++++++++++----- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index 1506a58497805ab61d5a12bb944eb21edb475aa1..e4f85b7a48094d82911c1831ca00e30b5f75c85d 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -255,7 +255,7 @@ pub fn resolve(options: Options) ResolveError!Config { .Exe => true, }; - if (target_util.cannotDynamicLink(target)) { + if (!target_util.canDynamicLink(target)) { if (options.link_mode == .dynamic) return error.TargetCannotDynamicLink; break :b .static; } diff --git a/src/Package/Module.zig b/src/Package/Module.zig index cd7f573046fd41b8dd15beb54e0a3a189400d690..a922af2da5c26e2c2f2001889c1212d3af57893d 100644 --- a/src/Package/Module.zig +++ b/src/Package/Module.zig @@ -178,7 +178,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { return error.PieRequiresPic; break :b true; } - if (options.global.link_mode == .dynamic) { + if (options.global.link_mode == .dynamic and target_util.requiresPicForDynamicLink(target)) { if (options.inherited.pic == false) return error.DynamicLinkingRequiresPic; break :b true; diff --git a/src/Sema.zig b/src/Sema.zig index 65311cafc188da131802235a34bc2582e76444a8..191da7c30dda58400f157c61d5739412cf08b7e0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9180,7 +9180,15 @@ pub fn handleExternLibName( ); break :blk; } - if (!target.cpu.arch.isWasm() and !block.ownerModule().pic) { + if (!target_util.canDynamicLink(target)) { + return sema.fail( + block, + src_loc, + "dependency on dynamic library '{s}' cannot be satisfied because target does not support dynamic linking", + .{lib_name}, + ); + } + if (!block.ownerModule().pic and target_util.requiresPicForDynamicLink(target)) { return sema.fail( block, src_loc, diff --git a/src/target.zig b/src/target.zig index 100c0f690ba8fd099b13fa4e53626c8daf0234c7..fd6edbf0697958c376e82eaa9704459cec37a07e 100644 --- a/src/target.zig +++ b/src/target.zig @@ -10,10 +10,24 @@ const Feature = @import("Zcu.zig").Feature; pub const default_stack_protector_buffer_size = 4; -pub fn cannotDynamicLink(target: *const std.Target) bool { - return switch (target.os.tag) { - .freestanding, .uefi => true, - else => target.cpu.arch.isSpirV(), +pub fn canDynamicLink(target: *const std.Target) bool { + return switch (target.cpu.arch) { + .amdgcn, + .bpfeb, + .bpfel, + .nvptx, + .nvptx64, + .spirv32, + .spirv64, + => false, + .wasm32, + .wasm64, + => true, + else => switch (target.os.tag) { + // This list is likely incomplete. + .freestanding, .uefi => false, + else => true, + }, }; } @@ -41,11 +55,20 @@ pub fn libCxxNeedsLibUnwind(target: *const std.Target) bool { /// This function returns whether non-pic code is completely invalid on the given target. pub fn requiresPIC(target: *const std.Target, linking_libc: bool) bool { return target.abi.isAndroid() or - target.os.tag == .windows or target.os.tag == .uefi or + ((target.os.tag == .windows or target.os.tag == .uefi) and (target.cpu.arch == .aarch64 or target.cpu.arch == .x86_64)) or target.requiresLibC() or (linking_libc and target.isGnuLibC()); } +pub fn requiresPicForDynamicLink(target: *const std.Target) bool { + assert(canDynamicLink(target)); + + return switch (target.os.tag) { + .windows => target.cpu.arch == .aarch64 or target.cpu.arch == .x86_64, + else => !target.cpu.arch.isWasm(), + }; +} + pub fn picLevel(target: *const std.Target) u32 { // MIPS always uses PIC level 1; other platforms vary in their default PIC levels, but they // support both level 1 and 2, in which case we prefer 2. -- 2.54.0 From c699bb81347dc0c9371c7c6c2cddab457cbe02ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 23 Jan 2026 19:45:39 +0100 Subject: [PATCH 3/4] zig cc: don't bother passing -fPIC to Clang for Windows and UEFI targets It's completely ignored anyway, by design, for compatibility reasons. --- src/target.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/target.zig b/src/target.zig index fd6edbf0697958c376e82eaa9704459cec37a07e..f664a354592ddff2d0c8cebbc5b9629d9c4d9c8c 100644 --- a/src/target.zig +++ b/src/target.zig @@ -79,9 +79,7 @@ pub fn picLevel(target: *const std.Target) u32 { /// C compiler argument is valid to Clang. pub fn supports_fpic(target: *const std.Target) bool { return switch (target.os.tag) { - .windows, - .uefi, - => target.abi == .gnu, + .windows, .uefi => false, // Technically allowed for `Abi.gnu`, but completely ignored by Clang (by design) anyway. else => true, }; } -- 2.54.0 From e437efd6015cdc369a2ca632565d105e9f04f20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 23 Jan 2026 19:47:12 +0100 Subject: [PATCH 4/4] test: enable thumb-windows-gnu module tests We use long calls for these just like thumb*-linux-* to prevent range issues as the binaries grow larger over time. We also need function and data sections due to the many __stack_chk_guard references within the std test binary; without these options, the linker is not able to insert range thunks in between functions because the std binary just has one giant .text section that's opaque to the linker. closes https://codeberg.org/ziglang/zig/issues/30923 --- test/tests.zig | 65 +++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index 179f80b3060a48642af431907e220a5966e4887b..ede30a131d804934f8c0b63355965b80c168c35f 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -28,6 +28,8 @@ const TestTarget = struct { use_lld: ?bool = null, pic: ?bool = null, strip: ?bool = null, + function_sections: ?bool = null, + data_sections: ?bool = null, skip_modules: []const []const u8 = &.{}, // This is intended for targets that, for any reason, shouldn't be run as part of a normal test @@ -40,7 +42,7 @@ const test_targets = blk: { // getBaselineCpuFeatures calls populateDependencies which has a O(N ^ 2) algorithm // (where N is roughly 160, which technically makes it O(1), but it adds up to a // lot of branches) - @setEvalBranchQuota(60000); + @setEvalBranchQuota(80_000); break :blk [_]TestTarget{ // Native Targets @@ -1526,36 +1528,43 @@ const test_targets = blk: { }, .{ - .target = .{ - .cpu_arch = .thumb, - .os_tag = .windows, - .abi = .msvc, - }, + .target = std.Target.Query.parse(.{ + .arch_os_abi = "thumb-windows-msvc", + .cpu_features = "baseline+long_calls", + }) catch unreachable, + .pic = false, // Long calls don't work with PIC. + .function_sections = true, + .data_sections = true, }, .{ - .target = .{ - .cpu_arch = .thumb, - .os_tag = .windows, - .abi = .msvc, - }, + .target = std.Target.Query.parse(.{ + .arch_os_abi = "thumb-windows-msvc", + .cpu_features = "baseline+long_calls", + }) catch unreachable, .link_libc = true, + .pic = false, // Long calls don't work with PIC. + .function_sections = true, + .data_sections = true, + }, + .{ + .target = std.Target.Query.parse(.{ + .arch_os_abi = "thumb-windows-gnu", + .cpu_features = "baseline+long_calls", + }) catch unreachable, + .pic = false, // Long calls don't work with PIC. + .function_sections = true, + .data_sections = true, + }, + .{ + .target = std.Target.Query.parse(.{ + .arch_os_abi = "thumb-windows-gnu", + .cpu_features = "baseline+long_calls", + }) catch unreachable, + .link_libc = true, + .pic = false, // Long calls don't work with PIC. + .function_sections = true, + .data_sections = true, }, - // https://github.com/ziglang/zig/issues/24016 - // .{ - // .target = .{ - // .cpu_arch = .thumb, - // .os_tag = .windows, - // .abi = .gnu, - // }, - // }, - // .{ - // .target = .{ - // .cpu_arch = .thumb, - // .os_tag = .windows, - // .abi = .gnu, - // }, - // .link_libc = true, - // }, .{ .target = .{ @@ -2454,6 +2463,8 @@ fn addOneModuleTest( if (options.build_options) |build_options| { these_tests.root_module.addOptions("build_options", build_options); } + if (test_target.function_sections) |fs| these_tests.link_function_sections = fs; + if (test_target.data_sections) |ds| these_tests.link_data_sections = ds; const single_threaded_suffix = if (test_target.single_threaded == true) "-single" else ""; const backend_suffix = if (test_target.use_llvm == true) "-llvm" -- 2.54.0