authorgravatar for abbix@riseup.netlg <abbix@riseup.net> 2026-09-03 14:41:10-04:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-09-16 19:21:21+02:00
log39512c3e5547b411294369c8eec8093045f74d64
treeed239d2a11a5fddc1abbf0dc3db3479d7f283658
parent2ec5523d5a70db80bac8563e1f06d3b164f53db3

compiler: add support for patchable functions


17 files changed, 164 insertions(+), 32 deletions(-)

lib/compiler/Maker/Step/Compile.zig+8
......@@ -1279,6 +1279,14 @@ fn appendModuleFlags(
12791279 }
12801280 }
12811281
1282 if (m.patchable_function_entry > 0) {
1283 const count = m.patchable_function_entry;
1284 try zig_args.append(
1285 gpa,
1286 try arena.print("-fpatchable-function-entry={d}", .{count}),
1287 );
1288 }
1289
12821290 if (m.resolved_target.get(conf)) |resolved_target| {
12831291 // Communicate the query via CLI since it's more compact.
12841292 if (resolved_target.unwrapQuery(conf)) |query| {
lib/std/Build/Configuration.zig+1
......@@ -1652,6 +1652,7 @@ pub const Module = struct {
16521652 rpaths: Storage.UnionList(.flags, .rpaths, RPath),
16531653 link_objects: Storage.UnionList(.flags, .link_objects, LinkObject),
16541654 frameworks: Storage.FlagLengthPrefixedList(.flags, .frameworks, Framework),
1655 patchable_function_entry: u32,
16551656
16561657 pub const Optimize = enum(u3) {
16571658 debug,
lib/std/Build/Module.zig+3
......@@ -42,6 +42,7 @@ error_tracing: ?bool,
4242link_libc: ?bool,
4343link_libcpp: ?bool,
4444no_builtin: ?bool,
45patchable_function_entry: u16,
4546
4647/// Symbols to be exported when compiling to WebAssembly.
4748export_symbol_names: []const []const u8 = &.{},
......@@ -230,6 +231,7 @@ pub const CreateOptions = struct {
230231 omit_frame_pointer: ?bool = null,
231232 error_tracing: ?bool = null,
232233 no_builtin: ?bool = null,
234 patchable_function_entry: u16 = 0,
233235};
234236
235237pub const Import = struct {
......@@ -278,6 +280,7 @@ pub fn init(
278280 .error_tracing = options.error_tracing,
279281 .export_symbol_names = &.{},
280282 .no_builtin = options.no_builtin,
283 .patchable_function_entry = options.patchable_function_entry,
281284 };
282285
283286 m.import_table.ensureUnusedCapacity(arena, options.imports.len) catch @panic("OOM");
lib/std/Build/Serialize.zig+1
......@@ -1243,6 +1243,7 @@ fn addModule(s: *Serialize, m: *std.Build.Module) !Configuration.Module.Index {
12431243 .rpaths = .init(rpaths),
12441244 .link_objects = .init(link_objects),
12451245 .frameworks = .{ .slice = frameworks },
1246 .patchable_function_entry = m.patchable_function_entry,
12461247 });
12471248
12481249 // The import table is the only place that modules can form dependency
lib/std/zig.zig+1
......@@ -1202,6 +1202,7 @@ pub const ClangCliParam = struct {
12021202 static,
12031203 dynamic,
12041204 version,
1205 patchable_function_entry,
12051206 };
12061207
12071208 pub fn matchEql(self: @This(), arg: []const u8) u2 {
lib/zig.h+6
......@@ -261,6 +261,12 @@
261261#define zig_no_builtin
262262#endif
263263
264#if zig_has_attribute(patchable_function_entry)
265#define zig_patchable_function_entry(count) __attribute__((patchable_function_entry(count)))
266#else
267#define zig_patchable_function_entry(count) zig_patchable_function_entry_unavailable
268#endif
269
264270#if zig_has_attribute(aligned) || defined(zig_tinyc)
265271#define zig_under_align(alignment) __attribute__((aligned(alignment)))
266272#elif defined(zig_msvc)
src/Compilation.zig+10
......@@ -1306,6 +1306,7 @@ pub const cache_helpers = struct {
13061306 hh.add(mod.fuzz);
13071307 hh.add(mod.unwind_tables);
13081308 hh.add(mod.no_builtin);
1309 hh.add(mod.patchable_function_entry);
13091310 hh.addListOfBytes(mod.cc_argv);
13101311 }
13111312
......@@ -1847,6 +1848,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
18471848 error.PieRequiresPic => unreachable,
18481849 error.DynamicLinkingRequiresPic => unreachable,
18491850 error.TargetHasNoRedZone => unreachable,
1851 error.PatchableFunctionEntryUnsupportedByBackend => unreachable,
18501852 // These are not possible because are explicitly *not* requesting these things.
18511853 error.StackCheckUnsupportedByTarget => unreachable,
18521854 error.StackProtectorUnsupportedByTarget => unreachable,
......@@ -1908,6 +1910,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
19081910 error.StackCheckUnsupportedByTarget => unreachable,
19091911 error.StackProtectorUnsupportedByTarget => unreachable,
19101912 error.StackProtectorUnavailableWithoutLibC => unreachable,
1913 error.PatchableFunctionEntryUnsupportedByBackend => unreachable,
19111914 };
19121915 try options.root_mod.deps.putNoClobber(arena, "ubsan_rt", ubsan_rt_mod);
19131916 }
......@@ -1953,6 +1956,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
19531956 error.StackCheckUnsupportedByTarget => unreachable,
19541957 error.StackProtectorUnsupportedByTarget => unreachable,
19551958 error.StackProtectorUnavailableWithoutLibC => unreachable,
1959 error.PatchableFunctionEntryUnsupportedByBackend => unreachable,
19561960 };
19571961 try options.root_mod.deps.putNoClobber(arena, "zigc", zigc_mod);
19581962 }
......@@ -2085,6 +2089,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
20852089 error.StackCheckUnsupportedByTarget => unreachable,
20862090 error.StackProtectorUnsupportedByTarget => unreachable,
20872091 error.StackProtectorUnavailableWithoutLibC => unreachable,
2092 error.PatchableFunctionEntryUnsupportedByBackend => unreachable,
20882093 };
20892094
20902095 const zcu = try arena.create(Zcu);
......@@ -6639,6 +6644,11 @@ pub fn addCCArgs(
66396644 });
66406645 }
66416646
6647 if (mod.patchable_function_entry > 0) {
6648 const pf = mod.patchable_function_entry;
6649 try argv.append(try std.fmt.allocPrint(arena, "-fpatchable-function-entry={}", .{pf}));
6650 }
6651
66426652 try comp.addCommonCCArgs(arena, argv, ext, out_dep_path, mod, comp.config.c_frontend);
66436653
66446654 // Only assembly files support these flags.
src/Module.zig+39
......@@ -43,6 +43,7 @@ fuzz: bool,
4343unwind_tables: std.lang.UnwindTables,
4444cc_argv: []const []const u8,
4545no_builtin: bool,
46patchable_function_entry: u16,
4647
4748pub const Deps = std.array_hash_map.String(*Module);
4849
......@@ -84,6 +85,7 @@ pub const CreateOptions = struct {
8485 sanitize_thread: ?bool = null,
8586 fuzz: ?bool = null,
8687 no_builtin: ?bool = null,
88 patchable_function_entry: u16 = 0,
8789 };
8890};
8991
......@@ -107,6 +109,7 @@ pub const CreateError = error{
107109 StackCheckUnsupportedByTarget,
108110 StackProtectorUnsupportedByTarget,
109111 StackProtectorUnavailableWithoutLibC,
112 PatchableFunctionEntryUnsupportedByBackend,
110113};
111114
112115/// At least one of `parent` and `resolved_target` must be non-null.
......@@ -324,6 +327,39 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Module {
324327 break :b target.cpu.arch.isBpf();
325328 };
326329
330 const patchable_function_entry = b: {
331 const x = options.inherited.patchable_function_entry;
332 if (x > 0) {
333 switch (zig_backend) {
334 .stage2_llvm,
335 .stage2_x86_64,
336 .stage2_aarch64,
337 .stage2_c,
338 .stage2_riscv64,
339 .stage2_loongarch,
340 => {},
341
342 // Does not apply to this architecture.
343 .stage2_spirv,
344 .zsf_spork8,
345 .stage2_wasm,
346 => return error.PatchableFunctionEntryUnsupportedByBackend,
347
348 // Not implemented.
349 .stage2_sparc64,
350 .stage2_x86,
351 .stage2_arm,
352 .stage2_powerpc,
353 => return error.PatchableFunctionEntryUnsupportedByBackend,
354
355 .other, .stage1, _ => unreachable,
356 }
357 break :b x;
358 }
359 if (options.parent) |p| break :b p.patchable_function_entry;
360 break :b 0;
361 };
362
327363 const llvm_cpu_features: ?[*:0]const u8 = b: {
328364 if (resolved_target.llvm_cpu_features) |x| break :b x;
329365 if (!options.global.use_llvm) break :b null;
......@@ -398,6 +434,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Module {
398434 .unwind_tables = unwind_tables,
399435 .cc_argv = options.cc_argv,
400436 .no_builtin = no_builtin,
437 .patchable_function_entry = patchable_function_entry,
401438 };
402439 return mod;
403440}
......@@ -436,6 +473,7 @@ pub fn createLimited(gpa: Allocator, options: LimitedOptions) Allocator.Error!*M
436473 .unwind_tables = undefined,
437474 .cc_argv = undefined,
438475 .no_builtin = undefined,
476 .patchable_function_entry = undefined,
439477 };
440478 return mod;
441479}
......@@ -474,6 +512,7 @@ pub fn createBuiltin(arena: Allocator, opts: Builtin, dirs: std.zig.Directories)
474512 .red_zone = false,
475513 .sanitize_c = .off,
476514 .no_builtin = false,
515 .patchable_function_entry = 0,
477516 };
478517 return new;
479518}
src/clang_options.zon+6-2
......@@ -3603,7 +3603,11 @@
36033603.{ .name = "fms-compatibility-version=", .syntax = .joined },
36043604.{ .name = "fopenmp-cuda-number-of-sm=", .syntax = .joined },
36053605.{ .name = "foptimization-record-file=", .syntax = .joined },
3606.{ .name = "fpatchable-function-entry=", .syntax = .joined },
3606.{
3607 .name = "fpatchable-function-entry=",
3608 .ze = .patchable_function_entry,
3609 .syntax = .joined,
3610},
36073611.{ .name = "frepack-arrays-contiguity=", .syntax = .joined },
36083612.{ .name = "fsanitize-skip-hot-cutoff=", .syntax = .comma_joined },
36093613.{ .name = "fsave-optimization-record=", .syntax = .joined },
......@@ -5134,4 +5138,4 @@
51345138 .syntax = .joined_or_separate,
51355139},
51365140.{ .name = "y", .syntax = .joined },
5137}
\ No newline at end of file
5141}
src/codegen/aarch64/Select.zig+5
......@@ -7757,6 +7757,11 @@ pub fn layout(
77577757
77587758 {
77597759 wip_mir_log.debug("{f}<prologue>:", .{nav.fqn.fmt(ip)});
7760
7761 for (0..mod.patchable_function_entry) |_| {
7762 try isel.emit(.nop());
7763 }
7764
77607765 var save_index: usize = 0;
77617766 while (save_index < saves.len) if (save_index + 2 <= saves.len and
77627767 saves[save_index + 0].class == saves[save_index + 1].class and
src/codegen/c.zig+8
......@@ -1735,6 +1735,14 @@ pub const DeclGen = struct {
17351735 const fn_ty = fn_val.typeOf(zcu);
17361736
17371737 const fn_info = zcu.typeToFunc(fn_ty).?;
1738
1739 if (kind == .definition) {
1740 if (dg.mod.patchable_function_entry > 0) {
1741 const count = dg.mod.patchable_function_entry;
1742 try w.print("zig_patchable_function_entry({d}) ", .{count});
1743 }
1744 }
1745
17381746 if (fn_info.cc == .naked) {
17391747 switch (kind) {
17401748 .forward_decl => try w.writeAll("zig_naked_decl "),
src/codegen/llvm.zig+9
......@@ -2218,6 +2218,15 @@ pub const Object = struct {
22182218 .value = try o.builder.string(std.mem.span(s)),
22192219 } }, &o.builder);
22202220 }
2221
2222 if (owner_mod.patchable_function_entry > 0) {
2223 const count = owner_mod.patchable_function_entry;
2224 try attributes.addFnAttr(.{ .string = .{
2225 .kind = try o.builder.string("patchable-function-entry"),
2226 .value = try o.builder.fmt("{d}", .{count}),
2227 } }, &o.builder);
2228 }
2229
22212230 if (target.abi.float() == .soft) {
22222231 // `use-soft-float` means "use software routines for floating point computations". In
22232232 // other words, it configures how LLVM lowers basic float instructions like `fcmp`,
src/codegen/loongarch/Select.zig+4-1
......@@ -4756,7 +4756,6 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) codegen.Error!void
47564756/// FP saving/restoring is not yet implemented.
47574757pub fn layout(isel: *Select, cc_it: CallAbiIterator, mod: *const Module) !usize {
47584758 _ = cc_it;
4759 _ = mod;
47604759 const zcu = isel.pt.zcu;
47614760 const ip = &zcu.intern_pool;
47624761 const nav = ip.getNav(isel.nav_index);
......@@ -4879,6 +4878,10 @@ pub fn layout(isel: *Select, cc_it: CallAbiIterator, mod: *const Module) !usize
48794878 .fcc => unreachable,
48804879 }
48814880 }
4881
4882 for (0..mod.patchable_function_entry) |_| {
4883 try isel.emit(.andi(.zero, .zero, 0));
4884 }
48824885 wip_mir_log.debug("{f}<prologue>:", .{nav.fqn.fmt(ip)});
48834886 }
48844887
src/codegen/riscv64/CodeGen.zig+11
......@@ -1126,6 +1126,17 @@ fn gen(func: *Func) !void {
11261126 }
11271127 }
11281128
1129 for (0..func.mod.patchable_function_entry) |_| {
1130 _ = try func.addInst(.{
1131 .tag = .addi,
1132 .data = .{ .i_type = .{
1133 .rd = .zero,
1134 .rs1 = .zero,
1135 .imm12 = Immediate.s(0),
1136 } },
1137 });
1138 }
1139
11291140 if (fn_info.cc != .naked) {
11301141 _ = try func.addPseudo(.pseudo_dbg_prologue_end);
11311142
src/codegen/x86_64/CodeGen.zig+5
......@@ -2067,6 +2067,11 @@ fn gen(
20672067 const pt = self.pt;
20682068 const zcu = pt.zcu;
20692069 const fn_info = zcu.typeToFunc(self.fn_type).?;
2070
2071 for (0..self.mod.patchable_function_entry) |_| {
2072 try self.asmOpOnly(.{ ._, .nop });
2073 }
2074
20702075 if (fn_info.cc != .naked) {
20712076 try self.asmRegister(.{ ._, .push }, .rbp);
20722077 try self.asmPseudoImmediate(.pseudo_cfi_adjust_cfa_offset_i_s, .s(8));
src/main.zig+43-29
......@@ -567,35 +567,36 @@ const compile_usage =
567567 \\ --build-root [path] Override path to project source files
568568 \\
569569 \\Global Compile Options:
570 \\ --name [name] Compilation unit name (not a file path)
571 \\ -M[name][=src] Create a module based on the current per-module settings.
572 \\ The first module is the main module.
573 \\ "std" can be configured by omitting src
574 \\ After a -M argument, per-module settings are reset.
575 \\ --libc [file] Provide a file which specifies libc paths
576 \\ -x [language] Treat subsequent input files as having type <language>
577 \\ --error-limit [num] Set the maximum amount of distinct error values
578 \\ -fllvm Force using LLVM as the codegen backend
579 \\ -fno-llvm Prevent using LLVM as the codegen backend
580 \\ -flibllvm Force using the LLVM API in the codegen backend
581 \\ -fno-libllvm Prevent using the LLVM API in the codegen backend
582 \\ -fclang Force using Clang as the C/C++ compilation backend
583 \\ -fno-clang Prevent using Clang as the C/C++ compilation backend
584 \\ -fPIE Force-enable Position Independent Executable
585 \\ -fno-PIE Force-disable Position Independent Executable
586 \\ -flto Force-enable Link Time Optimization (requires LLVM extensions)
587 \\ -fno-lto Force-disable Link Time Optimization
588 \\ -fdll-export-fns Mark exported functions as DLL exports (Windows)
589 \\ -fno-dll-export-fns Force-disable marking exported functions as DLL exports
590 \\ -freference-trace[=num] Show num lines of reference trace per compile error
591 \\ -fno-reference-trace Disable reference trace
592 \\ -ffunction-sections Places each function in a separate section
593 \\ -fno-function-sections All functions go into same section
594 \\ -fdata-sections Places each data in a separate section
595 \\ -fno-data-sections All data go into same section
596 \\ -mexec-model=[value] (WASI) Execution model
597 \\ -municode (Windows) Use wmain/wWinMain as entry point
598 \\ --time-report Send timing diagnostics to '--listen' clients
570 \\ --name [name] Compilation unit name (not a file path)
571 \\ -M[name][=src] Create a module based on the current per-module settings.
572 \\ The first module is the main module.
573 \\ "std" can be configured by omitting src
574 \\ After a -M argument, per-module settings are reset.
575 \\ --libc [file] Provide a file which specifies libc paths
576 \\ -x [language] Treat subsequent input files as having type <language>
577 \\ --error-limit [num] Set the maximum amount of distinct error values
578 \\ -fllvm Force using LLVM as the codegen backend
579 \\ -fno-llvm Prevent using LLVM as the codegen backend
580 \\ -flibllvm Force using the LLVM API in the codegen backend
581 \\ -fno-libllvm Prevent using the LLVM API in the codegen backend
582 \\ -fclang Force using Clang as the C/C++ compilation backend
583 \\ -fno-clang Prevent using Clang as the C/C++ compilation backend
584 \\ -fPIE Force-enable Position Independent Executable
585 \\ -fno-PIE Force-disable Position Independent Executable
586 \\ -flto Force-enable Link Time Optimization (requires LLVM extensions)
587 \\ -fno-lto Force-disable Link Time Optimization
588 \\ -fdll-export-fns Mark exported functions as DLL exports (Windows)
589 \\ -fno-dll-export-fns Force-disable marking exported functions as DLL exports
590 \\ -freference-trace[=num] Show num lines of reference trace per compile error
591 \\ -fno-reference-trace Disable reference trace
592 \\ -ffunction-sections Places each function in a separate section
593 \\ -fno-function-sections All functions go into same section
594 \\ -fdata-sections Places each data in a separate section
595 \\ -fno-data-sections All data go into same section
596 \\ -fpatchable-function-entry=[num] Add num NOPs of padding in function prologues
597 \\ -mexec-model=[value] (WASI) Execution model
598 \\ -municode (Windows) Use wmain/wWinMain as entry point
599 \\ --time-report Send timing diagnostics to '--listen' clients
599600 \\
600601 \\Per-Module Compile Options:
601602 \\ --dep [[import=]name] Add an entry to the next module's import table
......@@ -1705,6 +1706,10 @@ fn buildOutputType(
17051706 } else if (mem.cutPrefix(u8, arg, "-fopt-bisect-limit=")) |next_arg| {
17061707 llvm_opt_bisect_limit = std.fmt.parseInt(c_int, next_arg, 0) catch |err|
17071708 fatal("unable to parse {q}: {t}", .{ arg, err });
1709 } else if (mem.cutPrefix(u8, arg, "-fpatchable-function-entry=")) |num| {
1710 mod_opts.patchable_function_entry = std.fmt.parseUnsigned(u16, num, 10) catch |err| {
1711 fatal("unable to parse patchable-function-entry count {q}: {t}", .{ num, err });
1712 };
17081713 } else if (mem.eql(u8, arg, "--eh-frame-hdr")) {
17091714 link_eh_frame_hdr = true;
17101715 } else if (mem.eql(u8, arg, "--no-eh-frame-hdr")) {
......@@ -2175,6 +2180,14 @@ fn buildOutputType(
21752180 } else {
21762181 mod_opts.unwind_tables = .sync;
21772182 },
2183 .patchable_function_entry => {
2184 mod_opts.patchable_function_entry =
2185 std.fmt.parseUnsigned(u16, it.only_arg, 10) catch |err| {
2186 fatal("unable to parse patchable function entry count {q}: {t}", .{
2187 it.only_arg, err,
2188 });
2189 };
2190 },
21782191 .nostdlib => {
21792192 create_module.opts.ensure_libc_on_non_freestanding = false;
21802193 create_module.opts.ensure_libcpp_on_non_freestanding = false;
......@@ -4325,6 +4338,7 @@ fn createModule(
43254338 error.StackCheckUnsupportedByTarget => fatal("unable to create module {q}: the selected target does not support stack checking", .{name}),
43264339 error.StackProtectorUnsupportedByTarget => fatal("unable to create module {q}: the selected target does not support stack protection", .{name}),
43274340 error.StackProtectorUnavailableWithoutLibC => fatal("unable to create module {q}: enabling stack protection requires libc", .{name}),
4341 error.PatchableFunctionEntryUnsupportedByBackend => fatal("unable to create module {q}: patchable function entries are unsupported by the selected backend", .{name}),
43284342 error.OutOfMemory => |e| return e,
43294343 };
43304344 cli_mod.resolved = mod;
tools/update_clang_options.zig+4
......@@ -594,6 +594,10 @@ const known_options = [_]KnownOpt{
594594 .name = "version",
595595 .ident = "version",
596596 },
597 .{
598 .name = "fpatchable-function-entry",
599 .ident = "patchable_function_entry",
600 },
597601};
598602
599603const blacklisted_options = [_][]const u8{};