From 39512c3e5547b411294369c8eec8093045f74d64 Mon Sep 17 00:00:00 2001 From: lg Date: Thu, 3 Sep 2026 14:41:10 -0400 Subject: [PATCH] compiler: add support for patchable functions --- lib/compiler/Maker/Step/Compile.zig | 8 ++++ lib/std/Build/Configuration.zig | 1 + lib/std/Build/Module.zig | 3 ++ lib/std/Build/Serialize.zig | 1 + lib/std/zig.zig | 1 + lib/zig.h | 6 +++ src/Compilation.zig | 10 ++++ src/Module.zig | 39 ++++++++++++++++ src/clang_options.zon | 8 +++- src/codegen/aarch64/Select.zig | 5 ++ src/codegen/c.zig | 8 ++++ src/codegen/llvm.zig | 9 ++++ src/codegen/loongarch/Select.zig | 5 +- src/codegen/riscv64/CodeGen.zig | 11 +++++ src/codegen/x86_64/CodeGen.zig | 5 ++ src/main.zig | 72 +++++++++++++++++------------ tools/update_clang_options.zig | 4 ++ 17 files changed, 164 insertions(+), 32 deletions(-) diff --git a/lib/compiler/Maker/Step/Compile.zig b/lib/compiler/Maker/Step/Compile.zig index da5b70b062f10be5560950bc4e7f55f1cf0be5cc..cd144b077fbc250136b75b9b4511cbf110bd5c16 100644 --- a/lib/compiler/Maker/Step/Compile.zig +++ b/lib/compiler/Maker/Step/Compile.zig @@ -1279,6 +1279,14 @@ fn appendModuleFlags( } } + if (m.patchable_function_entry > 0) { + const count = m.patchable_function_entry; + try zig_args.append( + gpa, + try arena.print("-fpatchable-function-entry={d}", .{count}), + ); + } + if (m.resolved_target.get(conf)) |resolved_target| { // Communicate the query via CLI since it's more compact. if (resolved_target.unwrapQuery(conf)) |query| { diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index 651c7c2da4572c5d20245ae9ea48ccd9272968f6..e216a272e95bd69e462d494123bfcf3c7083669c 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -1652,6 +1652,7 @@ pub const Module = struct { rpaths: Storage.UnionList(.flags, .rpaths, RPath), link_objects: Storage.UnionList(.flags, .link_objects, LinkObject), frameworks: Storage.FlagLengthPrefixedList(.flags, .frameworks, Framework), + patchable_function_entry: u32, pub const Optimize = enum(u3) { debug, diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index cf25d2f73ffb175ee929c5d42e7e770b7298e040..3877790c7df015d5c41c6a34891d97bb04f898d2 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -42,6 +42,7 @@ error_tracing: ?bool, link_libc: ?bool, link_libcpp: ?bool, no_builtin: ?bool, +patchable_function_entry: u16, /// Symbols to be exported when compiling to WebAssembly. export_symbol_names: []const []const u8 = &.{}, @@ -230,6 +231,7 @@ pub const CreateOptions = struct { omit_frame_pointer: ?bool = null, error_tracing: ?bool = null, no_builtin: ?bool = null, + patchable_function_entry: u16 = 0, }; pub const Import = struct { @@ -278,6 +280,7 @@ pub fn init( .error_tracing = options.error_tracing, .export_symbol_names = &.{}, .no_builtin = options.no_builtin, + .patchable_function_entry = options.patchable_function_entry, }; m.import_table.ensureUnusedCapacity(arena, options.imports.len) catch @panic("OOM"); diff --git a/lib/std/Build/Serialize.zig b/lib/std/Build/Serialize.zig index e75dd2d5b10baa05d175d21f6c63c664ead99ad5..7582df0a43f1ef3264da6e7332c4fab8bf87d108 100644 --- a/lib/std/Build/Serialize.zig +++ b/lib/std/Build/Serialize.zig @@ -1243,6 +1243,7 @@ fn addModule(s: *Serialize, m: *std.Build.Module) !Configuration.Module.Index { .rpaths = .init(rpaths), .link_objects = .init(link_objects), .frameworks = .{ .slice = frameworks }, + .patchable_function_entry = m.patchable_function_entry, }); // The import table is the only place that modules can form dependency diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 32d71776c227b56c4be7bfac1268959c0ddfca79..46556a225263153d2b404f283d2ae80d1a64eee1 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -1202,6 +1202,7 @@ pub const ClangCliParam = struct { static, dynamic, version, + patchable_function_entry, }; pub fn matchEql(self: @This(), arg: []const u8) u2 { diff --git a/lib/zig.h b/lib/zig.h index ad4156b64a1abf3503a3d55eee385690e6b59a54..7f64d4c7d9095b448b9f23e3c689cad1874349b3 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -261,6 +261,12 @@ #define zig_no_builtin #endif +#if zig_has_attribute(patchable_function_entry) +#define zig_patchable_function_entry(count) __attribute__((patchable_function_entry(count))) +#else +#define zig_patchable_function_entry(count) zig_patchable_function_entry_unavailable +#endif + #if zig_has_attribute(aligned) || defined(zig_tinyc) #define zig_under_align(alignment) __attribute__((aligned(alignment))) #elif defined(zig_msvc) diff --git a/src/Compilation.zig b/src/Compilation.zig index d1a95dd5f2ac14df15161547b5d3f1351bd10a8e..1663f740c6e14d370c4e8c4da8865cdf125223a1 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1306,6 +1306,7 @@ pub const cache_helpers = struct { hh.add(mod.fuzz); hh.add(mod.unwind_tables); hh.add(mod.no_builtin); + hh.add(mod.patchable_function_entry); hh.addListOfBytes(mod.cc_argv); } @@ -1847,6 +1848,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, error.PieRequiresPic => unreachable, error.DynamicLinkingRequiresPic => unreachable, error.TargetHasNoRedZone => unreachable, + error.PatchableFunctionEntryUnsupportedByBackend => unreachable, // These are not possible because are explicitly *not* requesting these things. error.StackCheckUnsupportedByTarget => unreachable, error.StackProtectorUnsupportedByTarget => unreachable, @@ -1908,6 +1910,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, error.StackCheckUnsupportedByTarget => unreachable, error.StackProtectorUnsupportedByTarget => unreachable, error.StackProtectorUnavailableWithoutLibC => unreachable, + error.PatchableFunctionEntryUnsupportedByBackend => unreachable, }; try options.root_mod.deps.putNoClobber(arena, "ubsan_rt", ubsan_rt_mod); } @@ -1953,6 +1956,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, error.StackCheckUnsupportedByTarget => unreachable, error.StackProtectorUnsupportedByTarget => unreachable, error.StackProtectorUnavailableWithoutLibC => unreachable, + error.PatchableFunctionEntryUnsupportedByBackend => unreachable, }; try options.root_mod.deps.putNoClobber(arena, "zigc", zigc_mod); } @@ -2085,6 +2089,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, error.StackCheckUnsupportedByTarget => unreachable, error.StackProtectorUnsupportedByTarget => unreachable, error.StackProtectorUnavailableWithoutLibC => unreachable, + error.PatchableFunctionEntryUnsupportedByBackend => unreachable, }; const zcu = try arena.create(Zcu); @@ -6639,6 +6644,11 @@ pub fn addCCArgs( }); } + if (mod.patchable_function_entry > 0) { + const pf = mod.patchable_function_entry; + try argv.append(try std.fmt.allocPrint(arena, "-fpatchable-function-entry={}", .{pf})); + } + try comp.addCommonCCArgs(arena, argv, ext, out_dep_path, mod, comp.config.c_frontend); // Only assembly files support these flags. diff --git a/src/Module.zig b/src/Module.zig index 0567c07a6021f74c13a04c8ff74c6cd509d83633..e66982fa3af97ce9d4ff70522d8bd2073560ed51 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -43,6 +43,7 @@ fuzz: bool, unwind_tables: std.lang.UnwindTables, cc_argv: []const []const u8, no_builtin: bool, +patchable_function_entry: u16, pub const Deps = std.array_hash_map.String(*Module); @@ -84,6 +85,7 @@ pub const CreateOptions = struct { sanitize_thread: ?bool = null, fuzz: ?bool = null, no_builtin: ?bool = null, + patchable_function_entry: u16 = 0, }; }; @@ -107,6 +109,7 @@ pub const CreateError = error{ StackCheckUnsupportedByTarget, StackProtectorUnsupportedByTarget, StackProtectorUnavailableWithoutLibC, + PatchableFunctionEntryUnsupportedByBackend, }; /// At least one of `parent` and `resolved_target` must be non-null. @@ -324,6 +327,39 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Module { break :b target.cpu.arch.isBpf(); }; + const patchable_function_entry = b: { + const x = options.inherited.patchable_function_entry; + if (x > 0) { + switch (zig_backend) { + .stage2_llvm, + .stage2_x86_64, + .stage2_aarch64, + .stage2_c, + .stage2_riscv64, + .stage2_loongarch, + => {}, + + // Does not apply to this architecture. + .stage2_spirv, + .zsf_spork8, + .stage2_wasm, + => return error.PatchableFunctionEntryUnsupportedByBackend, + + // Not implemented. + .stage2_sparc64, + .stage2_x86, + .stage2_arm, + .stage2_powerpc, + => return error.PatchableFunctionEntryUnsupportedByBackend, + + .other, .stage1, _ => unreachable, + } + break :b x; + } + if (options.parent) |p| break :b p.patchable_function_entry; + break :b 0; + }; + const llvm_cpu_features: ?[*:0]const u8 = b: { if (resolved_target.llvm_cpu_features) |x| break :b x; if (!options.global.use_llvm) break :b null; @@ -398,6 +434,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Module { .unwind_tables = unwind_tables, .cc_argv = options.cc_argv, .no_builtin = no_builtin, + .patchable_function_entry = patchable_function_entry, }; return mod; } @@ -436,6 +473,7 @@ pub fn createLimited(gpa: Allocator, options: LimitedOptions) Allocator.Error!*M .unwind_tables = undefined, .cc_argv = undefined, .no_builtin = undefined, + .patchable_function_entry = undefined, }; return mod; } @@ -474,6 +512,7 @@ pub fn createBuiltin(arena: Allocator, opts: Builtin, dirs: std.zig.Directories) .red_zone = false, .sanitize_c = .off, .no_builtin = false, + .patchable_function_entry = 0, }; return new; } diff --git a/src/clang_options.zon b/src/clang_options.zon index 15b49f82107b39aab694c9b4ed3341a2230da2d0..c51e8742e082435821e4f19114da2184e022a5ca 100644 --- a/src/clang_options.zon +++ b/src/clang_options.zon @@ -3603,7 +3603,11 @@ .{ .name = "fms-compatibility-version=", .syntax = .joined }, .{ .name = "fopenmp-cuda-number-of-sm=", .syntax = .joined }, .{ .name = "foptimization-record-file=", .syntax = .joined }, -.{ .name = "fpatchable-function-entry=", .syntax = .joined }, +.{ + .name = "fpatchable-function-entry=", + .ze = .patchable_function_entry, + .syntax = .joined, +}, .{ .name = "frepack-arrays-contiguity=", .syntax = .joined }, .{ .name = "fsanitize-skip-hot-cutoff=", .syntax = .comma_joined }, .{ .name = "fsave-optimization-record=", .syntax = .joined }, @@ -5134,4 +5138,4 @@ .syntax = .joined_or_separate, }, .{ .name = "y", .syntax = .joined }, -} \ No newline at end of file +} diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig index 81216ea24a0d5f05cecb1d1663cb4f6ff822be4a..642c8ba765e21bac786b6e7d27addb24224c24a7 100644 --- a/src/codegen/aarch64/Select.zig +++ b/src/codegen/aarch64/Select.zig @@ -7757,6 +7757,11 @@ pub fn layout( { wip_mir_log.debug("{f}:", .{nav.fqn.fmt(ip)}); + + for (0..mod.patchable_function_entry) |_| { + try isel.emit(.nop()); + } + var save_index: usize = 0; while (save_index < saves.len) if (save_index + 2 <= saves.len and saves[save_index + 0].class == saves[save_index + 1].class and diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 5b1b451d6ff1feeb8d5a4addd6e615cf6f240b99..e795e5aabf10fb2a2cd54a411da64b069b04a683 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1735,6 +1735,14 @@ pub const DeclGen = struct { const fn_ty = fn_val.typeOf(zcu); const fn_info = zcu.typeToFunc(fn_ty).?; + + if (kind == .definition) { + if (dg.mod.patchable_function_entry > 0) { + const count = dg.mod.patchable_function_entry; + try w.print("zig_patchable_function_entry({d}) ", .{count}); + } + } + if (fn_info.cc == .naked) { switch (kind) { .forward_decl => try w.writeAll("zig_naked_decl "), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index a146fed57f4d9a3ee582639d4f036ae30466ed88..004fb58fdb6ef3f146818dad88c349ea64c2f61a 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2218,6 +2218,15 @@ pub const Object = struct { .value = try o.builder.string(std.mem.span(s)), } }, &o.builder); } + + if (owner_mod.patchable_function_entry > 0) { + const count = owner_mod.patchable_function_entry; + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("patchable-function-entry"), + .value = try o.builder.fmt("{d}", .{count}), + } }, &o.builder); + } + if (target.abi.float() == .soft) { // `use-soft-float` means "use software routines for floating point computations". In // other words, it configures how LLVM lowers basic float instructions like `fcmp`, diff --git a/src/codegen/loongarch/Select.zig b/src/codegen/loongarch/Select.zig index 49e9fb09702b345c28d0058f2a48b353414edebc..278ca9dfc37ffddcefb3c84fcfd8a031e6c0837a 100644 --- a/src/codegen/loongarch/Select.zig +++ b/src/codegen/loongarch/Select.zig @@ -4756,7 +4756,6 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) codegen.Error!void /// FP saving/restoring is not yet implemented. pub fn layout(isel: *Select, cc_it: CallAbiIterator, mod: *const Module) !usize { _ = cc_it; - _ = mod; const zcu = isel.pt.zcu; const ip = &zcu.intern_pool; const nav = ip.getNav(isel.nav_index); @@ -4879,6 +4878,10 @@ pub fn layout(isel: *Select, cc_it: CallAbiIterator, mod: *const Module) !usize .fcc => unreachable, } } + + for (0..mod.patchable_function_entry) |_| { + try isel.emit(.andi(.zero, .zero, 0)); + } wip_mir_log.debug("{f}:", .{nav.fqn.fmt(ip)}); } diff --git a/src/codegen/riscv64/CodeGen.zig b/src/codegen/riscv64/CodeGen.zig index 9f971cddf23f8a15cf0abcbb17fae110c41066c2..1db4dde0627e2ee00b6f65f9c48db0b53b29f8c2 100644 --- a/src/codegen/riscv64/CodeGen.zig +++ b/src/codegen/riscv64/CodeGen.zig @@ -1126,6 +1126,17 @@ fn gen(func: *Func) !void { } } + for (0..func.mod.patchable_function_entry) |_| { + _ = try func.addInst(.{ + .tag = .addi, + .data = .{ .i_type = .{ + .rd = .zero, + .rs1 = .zero, + .imm12 = Immediate.s(0), + } }, + }); + } + if (fn_info.cc != .naked) { _ = try func.addPseudo(.pseudo_dbg_prologue_end); diff --git a/src/codegen/x86_64/CodeGen.zig b/src/codegen/x86_64/CodeGen.zig index e59e520c5cb4ee44ef14062bc030266a776711ae..5b06cd9eaaa85c30538d19af5304383836b39e0c 100644 --- a/src/codegen/x86_64/CodeGen.zig +++ b/src/codegen/x86_64/CodeGen.zig @@ -2067,6 +2067,11 @@ fn gen( const pt = self.pt; const zcu = pt.zcu; const fn_info = zcu.typeToFunc(self.fn_type).?; + + for (0..self.mod.patchable_function_entry) |_| { + try self.asmOpOnly(.{ ._, .nop }); + } + if (fn_info.cc != .naked) { try self.asmRegister(.{ ._, .push }, .rbp); try self.asmPseudoImmediate(.pseudo_cfi_adjust_cfa_offset_i_s, .s(8)); diff --git a/src/main.zig b/src/main.zig index be23af69ab220c3dba9c3e4763569047c5ac4873..a2218a480f8f38b30c4963402e8bfe413df2b8bd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -567,35 +567,36 @@ const compile_usage = \\ --build-root [path] Override path to project source files \\ \\Global Compile Options: - \\ --name [name] Compilation unit name (not a file path) - \\ -M[name][=src] Create a module based on the current per-module settings. - \\ The first module is the main module. - \\ "std" can be configured by omitting src - \\ After a -M argument, per-module settings are reset. - \\ --libc [file] Provide a file which specifies libc paths - \\ -x [language] Treat subsequent input files as having type - \\ --error-limit [num] Set the maximum amount of distinct error values - \\ -fllvm Force using LLVM as the codegen backend - \\ -fno-llvm Prevent using LLVM as the codegen backend - \\ -flibllvm Force using the LLVM API in the codegen backend - \\ -fno-libllvm Prevent using the LLVM API in the codegen backend - \\ -fclang Force using Clang as the C/C++ compilation backend - \\ -fno-clang Prevent using Clang as the C/C++ compilation backend - \\ -fPIE Force-enable Position Independent Executable - \\ -fno-PIE Force-disable Position Independent Executable - \\ -flto Force-enable Link Time Optimization (requires LLVM extensions) - \\ -fno-lto Force-disable Link Time Optimization - \\ -fdll-export-fns Mark exported functions as DLL exports (Windows) - \\ -fno-dll-export-fns Force-disable marking exported functions as DLL exports - \\ -freference-trace[=num] Show num lines of reference trace per compile error - \\ -fno-reference-trace Disable reference trace - \\ -ffunction-sections Places each function in a separate section - \\ -fno-function-sections All functions go into same section - \\ -fdata-sections Places each data in a separate section - \\ -fno-data-sections All data go into same section - \\ -mexec-model=[value] (WASI) Execution model - \\ -municode (Windows) Use wmain/wWinMain as entry point - \\ --time-report Send timing diagnostics to '--listen' clients + \\ --name [name] Compilation unit name (not a file path) + \\ -M[name][=src] Create a module based on the current per-module settings. + \\ The first module is the main module. + \\ "std" can be configured by omitting src + \\ After a -M argument, per-module settings are reset. + \\ --libc [file] Provide a file which specifies libc paths + \\ -x [language] Treat subsequent input files as having type + \\ --error-limit [num] Set the maximum amount of distinct error values + \\ -fllvm Force using LLVM as the codegen backend + \\ -fno-llvm Prevent using LLVM as the codegen backend + \\ -flibllvm Force using the LLVM API in the codegen backend + \\ -fno-libllvm Prevent using the LLVM API in the codegen backend + \\ -fclang Force using Clang as the C/C++ compilation backend + \\ -fno-clang Prevent using Clang as the C/C++ compilation backend + \\ -fPIE Force-enable Position Independent Executable + \\ -fno-PIE Force-disable Position Independent Executable + \\ -flto Force-enable Link Time Optimization (requires LLVM extensions) + \\ -fno-lto Force-disable Link Time Optimization + \\ -fdll-export-fns Mark exported functions as DLL exports (Windows) + \\ -fno-dll-export-fns Force-disable marking exported functions as DLL exports + \\ -freference-trace[=num] Show num lines of reference trace per compile error + \\ -fno-reference-trace Disable reference trace + \\ -ffunction-sections Places each function in a separate section + \\ -fno-function-sections All functions go into same section + \\ -fdata-sections Places each data in a separate section + \\ -fno-data-sections All data go into same section + \\ -fpatchable-function-entry=[num] Add num NOPs of padding in function prologues + \\ -mexec-model=[value] (WASI) Execution model + \\ -municode (Windows) Use wmain/wWinMain as entry point + \\ --time-report Send timing diagnostics to '--listen' clients \\ \\Per-Module Compile Options: \\ --dep [[import=]name] Add an entry to the next module's import table @@ -1705,6 +1706,10 @@ fn buildOutputType( } else if (mem.cutPrefix(u8, arg, "-fopt-bisect-limit=")) |next_arg| { llvm_opt_bisect_limit = std.fmt.parseInt(c_int, next_arg, 0) catch |err| fatal("unable to parse {q}: {t}", .{ arg, err }); + } else if (mem.cutPrefix(u8, arg, "-fpatchable-function-entry=")) |num| { + mod_opts.patchable_function_entry = std.fmt.parseUnsigned(u16, num, 10) catch |err| { + fatal("unable to parse patchable-function-entry count {q}: {t}", .{ num, err }); + }; } else if (mem.eql(u8, arg, "--eh-frame-hdr")) { link_eh_frame_hdr = true; } else if (mem.eql(u8, arg, "--no-eh-frame-hdr")) { @@ -2175,6 +2180,14 @@ fn buildOutputType( } else { mod_opts.unwind_tables = .sync; }, + .patchable_function_entry => { + mod_opts.patchable_function_entry = + std.fmt.parseUnsigned(u16, it.only_arg, 10) catch |err| { + fatal("unable to parse patchable function entry count {q}: {t}", .{ + it.only_arg, err, + }); + }; + }, .nostdlib => { create_module.opts.ensure_libc_on_non_freestanding = false; create_module.opts.ensure_libcpp_on_non_freestanding = false; @@ -4325,6 +4338,7 @@ fn createModule( error.StackCheckUnsupportedByTarget => fatal("unable to create module {q}: the selected target does not support stack checking", .{name}), error.StackProtectorUnsupportedByTarget => fatal("unable to create module {q}: the selected target does not support stack protection", .{name}), error.StackProtectorUnavailableWithoutLibC => fatal("unable to create module {q}: enabling stack protection requires libc", .{name}), + error.PatchableFunctionEntryUnsupportedByBackend => fatal("unable to create module {q}: patchable function entries are unsupported by the selected backend", .{name}), error.OutOfMemory => |e| return e, }; cli_mod.resolved = mod; diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index a89ec3724ee72099aabe7f5ff50e47625ec1bc75..ca45110a87c0185a002e7f3148fca487448dce7f 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -594,6 +594,10 @@ const known_options = [_]KnownOpt{ .name = "version", .ident = "version", }, + .{ + .name = "fpatchable-function-entry", + .ident = "patchable_function_entry", + }, }; const blacklisted_options = [_][]const u8{}; -- 2.54.0