Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog/dmd.patchable-function-entry.dd
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions compiler/include/dmd/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ struct Param
bool timeTrace;
uint32_t timeTraceGranularityUs;
const char* timeTraceFile;
uint32_t patchableFunctionEntryTotal;
uint32_t patchableFunctionEntryPrefix;
};

struct structalign_t
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dmd/backend/backconfig.d
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dmd/backend/cdef.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 28 additions & 0 deletions compiler/src/dmd/backend/dout.d
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dmd/backend/elfobj.d
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
19 changes: 19 additions & 0 deletions compiler/src/dmd/backend/x86/cgcod.d
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dmd/dmsc.d
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dmd/globals.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +276 to +277

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this in dmdparams as it's not used by the front-end.


///
bool parsingUnittestsRequired() @safe
{
Expand Down
43 changes: 43 additions & 0 deletions compiler/src/dmd/mars.d
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading