From 8133d685e317fc8eec8785d46ddc6e3a5506a028 Mon Sep 17 00:00:00 2001 From: xoxorwr Date: Fri, 12 Jun 2026 16:28:20 +0200 Subject: [PATCH 1/2] Implement -fpatchable-function-entry flag --- compiler/include/dmd/globals.h | 2 ++ compiler/src/dmd/backend/backconfig.d | 4 +++ compiler/src/dmd/backend/cdef.d | 2 ++ compiler/src/dmd/backend/dout.d | 28 +++++++++++++++++ compiler/src/dmd/backend/elfobj.d | 6 ++++ compiler/src/dmd/backend/x86/cgcod.d | 19 ++++++++++++ compiler/src/dmd/dmsc.d | 2 ++ compiler/src/dmd/globals.d | 3 ++ compiler/src/dmd/mars.d | 43 +++++++++++++++++++++++++++ 9 files changed, 109 insertions(+) diff --git a/compiler/include/dmd/globals.h b/compiler/include/dmd/globals.h index 84142e5d6774..45a914adafca 100644 --- a/compiler/include/dmd/globals.h +++ b/compiler/include/dmd/globals.h @@ -291,6 +291,8 @@ struct Param bool timeTrace; uint32_t timeTraceGranularityUs; const char* timeTraceFile; + uint32_t patchableFunctionEntryTotal; + uint32_t patchableFunctionEntryPrefix; }; struct structalign_t diff --git a/compiler/src/dmd/backend/backconfig.d b/compiler/src/dmd/backend/backconfig.d index ec7c44d8991f..d34a60c4e672 100644 --- a/compiler/src/dmd/backend/backconfig.d +++ b/compiler/src/dmd/backend/backconfig.d @@ -88,6 +88,8 @@ void out_config_init( exefmt_t exefmt, bool generatedMain, // a main entrypoint is generated bool dataimports, + uint patchableFunctionEntryTotal, + uint patchableFunctionEntryPrefix, ref GlobalOptimizer go, ErrorCallbackBackend errorCallback) { @@ -97,6 +99,8 @@ void out_config_init( auto cfg = &config; cfg._version = _version; + cfg.patchableFunctionEntryTotal = patchableFunctionEntryTotal; + cfg.patchableFunctionEntryPrefix = patchableFunctionEntryPrefix; if (arm) cfg.target_cpu = TARGET_AArch64; if (!cfg.target_cpu) diff --git a/compiler/src/dmd/backend/cdef.d b/compiler/src/dmd/backend/cdef.d index b40600387ccb..4e41fe0e9407 100644 --- a/compiler/src/dmd/backend/cdef.d +++ b/compiler/src/dmd/backend/cdef.d @@ -532,6 +532,8 @@ struct Config char* csegname; // code segment name char* deflibname; // default library name int errmax; // max error count + uint patchableFunctionEntryTotal; + uint patchableFunctionEntryPrefix; } enum THRESHMAX = 0xFFFF; diff --git a/compiler/src/dmd/backend/dout.d b/compiler/src/dmd/backend/dout.d index af173142245e..8a50408ee1e7 100644 --- a/compiler/src/dmd/backend/dout.d +++ b/compiler/src/dmd/backend/dout.d @@ -1071,6 +1071,10 @@ void writefunc2(Symbol* sfunc, ref GlobalOptimizer go, ref BlockOpt bo) // generate new code segment } cod3_align(cseg); // align start of function + if (config.patchableFunctionEntryPrefix > 0) + { + write_nops(cseg, config.patchableFunctionEntryPrefix); + } objmod.func_start(sfunc); } @@ -1181,6 +1185,30 @@ Ldone: //dfo.dtor(); // save allocation for next time } +@trusted private nothrow +void write_nops(int seg, size_t numNops) +{ + if (config.target_cpu == TARGET_AArch64) + { + foreach (i; 0 .. numNops) + { + // https://developer.arm.com/documentation/ddi0602/2026-03/Base-Instructions/NOP--No-operation-?lang=en + objmod.write_byte(SegData[seg], 0x1F); + objmod.write_byte(SegData[seg], 0x20); + objmod.write_byte(SegData[seg], 0x03); + objmod.write_byte(SegData[seg], 0xD5); + } + } + else + { + foreach (i; 0 .. numNops) + { + // https://www.felixcloutier.com/x86/nop + objmod.write_byte(SegData[seg], 0x90); + } + } +} + /************************* * Align segment offset. * Params: diff --git a/compiler/src/dmd/backend/elfobj.d b/compiler/src/dmd/backend/elfobj.d index 9c030a05f2a0..22dd2e2a7197 100644 --- a/compiler/src/dmd/backend/elfobj.d +++ b/compiler/src/dmd/backend/elfobj.d @@ -2187,9 +2187,15 @@ void ElfObj_func_term(Symbol* sfunc) // fill in the function size if (I64) + { + elfobj.SymbolTable64[sfunc.Sxtrnnum].st_value = sfunc.Soffset; elfobj.SymbolTable64[sfunc.Sxtrnnum].st_size = Offset(cseg) - sfunc.Soffset; + } else + { + elfobj.SymbolTable[sfunc.Sxtrnnum].st_value = cast(uint)sfunc.Soffset; elfobj.SymbolTable[sfunc.Sxtrnnum].st_size = cast(uint)(Offset(cseg) - sfunc.Soffset); + } dwarf_func_term(sfunc); } diff --git a/compiler/src/dmd/backend/x86/cgcod.d b/compiler/src/dmd/backend/x86/cgcod.d index 6f2c7c86b633..9416c9243703 100644 --- a/compiler/src/dmd/backend/x86/cgcod.d +++ b/compiler/src/dmd/backend/x86/cgcod.d @@ -631,6 +631,25 @@ void prolog(ref CGstate cg, ref CodeBuilder cdb) if (config.flags3 & CFG3ibt && !I16) cdb.gen1(I32 ? ENDBR32 : ENDBR64); + // Generate remaining NOPs for patchable-function-entry + if (config.patchableFunctionEntryTotal > config.patchableFunctionEntryPrefix) + { + uint remainingNops = config.patchableFunctionEntryTotal - config.patchableFunctionEntryPrefix; + foreach (i; 0 .. remainingNops) + { + if (cg.AArch64) + { + // https://developer.arm.com/documentation/ddi0602/2026-03/Base-Instructions/NOP--No-operation-?lang=en + cdb.genasm([0x1F, 0x20, 0x03, 0xD5]); + } + else + { + // https://www.felixcloutier.com/x86/nop + cdb.gen1(0x90); + } + } + } + // Special Intel 64 bit ABI prolog setup for variadic functions Symbol* sv64 = null; // set to __va_argsave if (I64 && variadic(funcsym_p.Stype)) diff --git a/compiler/src/dmd/dmsc.d b/compiler/src/dmd/dmsc.d index e90f39dc18cb..73e1c63e12e9 100644 --- a/compiler/src/dmd/dmsc.d +++ b/compiler/src/dmd/dmsc.d @@ -95,6 +95,8 @@ void backend_init(const ref Param params, const ref DMDparams driverParams, cons exfmt, params.addMain, driverParams.symImport != SymImport.none, + params.patchableFunctionEntryTotal, + params.patchableFunctionEntryPrefix, go, // FIXME: casting to @nogc because errors.d is not marked @nogc yet cast(ErrorCallbackBackend) &errorBackend, diff --git a/compiler/src/dmd/globals.d b/compiler/src/dmd/globals.d index 0c4f9eb5417c..054a6ef22706 100644 --- a/compiler/src/dmd/globals.d +++ b/compiler/src/dmd/globals.d @@ -273,6 +273,9 @@ extern (C++) struct Param uint timeTraceGranularityUs = 500; /// In microseconds, minimum event size to report const(char)* timeTraceFile; /// File path of output file + uint patchableFunctionEntryTotal = 0; /// Number of NOPs to pad function entries with + uint patchableFunctionEntryPrefix = 0; /// Number of NOPs before function entry label + /// bool parsingUnittestsRequired() @safe { diff --git a/compiler/src/dmd/mars.d b/compiler/src/dmd/mars.d index d2dbf640be15..ea2839d4c898 100644 --- a/compiler/src/dmd/mars.d +++ b/compiler/src/dmd/mars.d @@ -947,6 +947,49 @@ bool parseCommandLine(const ref Strings arguments, const size_t argc, out Param goto Lnoarg; params.timeTraceFile = mem.xstrdup(tmp); } + else if (startsWith(p + 1, "fpatchable-function-entry=")) + { + enum len = "-fpatchable-function-entry=".length; + const(char)[] val = arg[len .. $]; + int commaIdx = -1; + foreach (j, c; val) + { + if (c == ',') + { + commaIdx = cast(int) j; + break; + } + } + if (commaIdx != -1) + { + const(char)[] totalStr = val[0 .. commaIdx]; + const(char)[] prefixStr = val[commaIdx + 1 .. $]; + uint totalNops, prefixNops; + if (!totalNops.parseDigits(totalStr) || !prefixNops.parseDigits(prefixStr)) + { + error("`-fpatchable-function-entry` requires a non-negative integer parameter", p); + return false; + } + if (prefixNops > totalNops) + { + error("`-fpatchable-function-entry` prefix_nops (%u) must be less than or equal to total_nops (%u)", prefixNops, totalNops); + return false; + } + params.patchableFunctionEntryTotal = totalNops; + params.patchableFunctionEntryPrefix = prefixNops; + } + else + { + uint totalNops; + if (!totalNops.parseDigits(val)) + { + error("`-fpatchable-function-entry` requires a non-negative integer parameter", p); + return false; + } + params.patchableFunctionEntryTotal = totalNops; + params.patchableFunctionEntryPrefix = 0; + } + } else if (arg == "-map") // https://dlang.org/dmd.html#switch-map driverParams.map = true; else if (arg == "-multiobj") From 5ed7d0fd6223514dff70ab99c1f217fdefc5df0c Mon Sep 17 00:00:00 2001 From: xoxorwr Date: Fri, 12 Jun 2026 16:37:39 +0200 Subject: [PATCH 2/2] Add changelog entry --- changelog/dmd.patchable-function-entry.dd | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changelog/dmd.patchable-function-entry.dd diff --git a/changelog/dmd.patchable-function-entry.dd b/changelog/dmd.patchable-function-entry.dd new file mode 100644 index 000000000000..7a699d55ed35 --- /dev/null +++ b/changelog/dmd.patchable-function-entry.dd @@ -0,0 +1,6 @@ +Added the `-fpatchable-function-entry` flag for hotpatching function entries + +The `-fpatchable-function-entry=total_nops[,prefix_nops]` compiler flag enables NOP padding for function entries. +`total_nops` specifies the total number of NOPs to generate, while `prefix_nops` specifies the number of NOPs to generate before the function entry label (defaults to 0). + +Supported on both x86/x86_64 and AArch64 targets.