This patch adds support for the MSVC /HOTPATCH flag: https://docs.microsoft.com/sv-se/cpp/build/reference/hotpatch-create-hotpatchable-image?view=msvc-170&viewFallbackFrom=vs-2019
The flag is translated to a new -fms-hotpatch flag, which in turn adds a 'patchable-function' attribute for each function in the TU. This is then picked up by the PatchableFunction pass which would generate a TargetOpcode::PATCHABLE_OP of minsize = 2 (which means the target instruction must resolve to at least two bytes). TargetOpcode::PATCHABLE_OP is only implemented for x86/x64. When targetting ARM/ARM64, /HOTPATCH isn't required (instructions are always 2/4 bytes and suitable for hotpatching).
Additionally, when using /Z7, we generate a 'hot patchable' flag in the CodeView debug stream, in the S_COMPILE3 record. This flag is then picked up by LLD (or link.exe) and is used in conjunction with the linker /FUNCTIONPADMIN flag to generate extra space before each function, to accommodate for live patching long jumps. Please see: https://github.com/llvm/llvm-project/blob/
d703b922961e0d02a5effdd4bfbb23ad50a3cc9f/lld/COFF/Writer.cpp#L1298
The outcome is that we can finally use Live++ or Recode along with clang-cl.
NOTE: It seems that MSVC cl.exe always enables /HOTPATCH on x64 by default, although if we did the same I thought we might generate sub-optimal code (if this flag was active by default). Additionally, MSVC always generates a .debug$S section and a S_COMPILE3 record, which Clang doesn't do without /Z7. Therefore, the following MSVC command-line "cl /c file.cpp" would have to be written with Clang such as "clang-cl /c file.cpp /HOTPATCH /Z7" in order to obtain the same result.
Depends on D43002, D80833 and D81301 for the full feature.
Differential Revision: https://reviews.llvm.org/D116511
or pass ``/permissive`` to disable C++ operator names altogether. See
`PR42427 <https://llvm.org/pr42427>` for more info.
+- Add support for MSVC-compatible ``/hotpatch`` flag in clang-cl, and equivalent
+ -cc1 flag ``-fms-hotpatch``. Along with the linker flag ``/functionpadmin``
+ this creates executable images suitable for runtime code patching. This flag
+ is only required for x86/x64 targets; ARM/ARM64 simply needs the linker
+ ``/functionpadmin``.
+
+ With this addition, clang-cl can be used in live code patching scenarios,
+ along with tools such as Live++ or Recode. Microsoft Edit and Continue isn't
+ currently supported.
+
C Language Changes in Clang
---------------------------
VALUE_CODEGENOPT(PatchableFunctionEntryCount , 32, 0) ///< Number of NOPs at function entry
VALUE_CODEGENOPT(PatchableFunctionEntryOffset , 32, 0)
+CODEGENOPT(HotPatch, 1, 0) ///< Supports the Microsoft /HOTPATCH flag and
+ ///< generates a 'patchable-function' attribute.
+
CODEGENOPT(InstrumentForProfiling , 1, 0) ///< Set when -pg is enabled.
CODEGENOPT(CallFEntry , 1, 0) ///< Set when -mfentry is enabled.
CODEGENOPT(MNopMCount , 1, 0) ///< Set when -mnop-mcount is enabled.
def fpatchable_function_entry_EQ : Joined<["-"], "fpatchable-function-entry=">, Group<f_Group>, Flags<[CC1Option]>,
MetaVarName<"<N,M>">, HelpText<"Generate M NOPs before function entry and N-M NOPs after function entry">,
MarshallingInfoInt<CodeGenOpts<"PatchableFunctionEntryCount">>;
+def fms_hotpatch : Flag<["-"], "fms-hotpatch">, Group<f_Group>, Flags<[CC1Option, CoreOption]>,
+ HelpText<"Ensure that all functions can be hotpatched at runtime">,
+ MarshallingInfoFlag<CodeGenOpts<"HotPatch">>;
def fpcc_struct_return : Flag<["-"], "fpcc-struct-return">, Group<f_Group>, Flags<[CC1Option]>,
HelpText<"Override the default ABI to return all structs on the stack">;
def fpch_preprocess : Flag<["-"], "fpch-preprocess">, Group<f_Group>;
def _SLASH_help : CLFlag<"help">, Alias<help>,
HelpText<"Display available options">;
def _SLASH_HELP : CLFlag<"HELP">, Alias<help>;
+def _SLASH_hotpatch : CLFlag<"hotpatch">, Alias<fms_hotpatch>,
+ HelpText<"Create hotpatchable image">;
def _SLASH_I : CLJoinedOrSeparate<"I">,
HelpText<"Add directory to include search path">, MetaVarName<"<dir>">,
Alias<I>;
def _SLASH_headerUnitAngle : CLJoinedOrSeparate<"headerUnit:angle">;
def _SLASH_headerUnitQuote : CLJoinedOrSeparate<"headerUnit:quote">;
def _SLASH_homeparams : CLFlag<"homeparams">;
-def _SLASH_hotpatch : CLFlag<"hotpatch">;
def _SLASH_kernel : CLFlag<"kernel">;
def _SLASH_LN : CLFlag<"LN">;
def _SLASH_MP : CLJoined<"MP">;
Options.MCOptions.CommandLineArgs = CodeGenOpts.CommandLineArgs;
Options.DebugStrictDwarf = CodeGenOpts.DebugStrictDwarf;
Options.ObjectFilenameForDebug = CodeGenOpts.ObjectFilenameForDebug;
+ Options.Hotpatch = CodeGenOpts.HotPatch;
return true;
}
if (Offset)
Fn->addFnAttr("patchable-function-prefix", std::to_string(Offset));
}
+ // Instruct that functions for COFF/CodeView targets should start with a
+ // patchable instruction, but only on x86/x64. Don't forward this to ARM/ARM64
+ // backends as they don't need it -- instructions on these architectures are
+ // always atomically patchable at runtime.
+ if (CGM.getCodeGenOpts().HotPatch &&
+ getContext().getTargetInfo().getTriple().isX86())
+ Fn->addFnAttr("patchable-function", "prologue-short-redirect");
// Add no-jump-tables value.
if (CGM.getCodeGenOpts().NoUseJumpTables)
}
}
+ Args.AddLastArg(CmdArgs, options::OPT_fms_hotpatch);
+
if (TC.SupportsProfiling()) {
Args.AddLastArg(CmdArgs, options::OPT_pg);
if (Args.hasArg(options::OPT_g_Group, options::OPT__SLASH_Z7))
CmdArgs.push_back("-debug");
+ // If we specify /hotpatch, let the linker add padding in front of each
+ // function, like MSVC does.
+ if (Args.hasArg(options::OPT_fms_hotpatch, options::OPT__SLASH_hotpatch))
+ CmdArgs.push_back("-functionpadmin");
+
// Pass on /Brepro if it was passed to the compiler.
// Note that /Brepro maps to -mno-incremental-linker-compatible.
bool DefaultIncrementalLinkerCompatible =
// RUN: %clang_cc1 -triple aarch64 -emit-llvm %s -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64 -emit-llvm %s -fpatchable-function-entry=1 -o - | FileCheck --check-prefixes=CHECK,OPT %s
+// RUN: %clang_cc1 -triple x86_64 -emit-llvm %s -fms-hotpatch -o - | FileCheck --check-prefixes=HOTPATCH %s
// CHECK: define{{.*}} void @f0() #0
__attribute__((patchable_function_entry(0))) void f0() {}
// CHECK: attributes #2 = { {{.*}} "patchable-function-entry"="0" "patchable-function-prefix"="4"
// CHECK: attributes #3 = { {{.*}} "patchable-function-entry"="3" "patchable-function-prefix"="2"
// OPT: attributes #4 = { {{.*}} "patchable-function-entry"="1"
+// HOTPATCH: attributes #0 = { {{.*}} "patchable-function"="prologue-short-redirect"
+// HOTPATCH: attributes #1 = { {{.*}} "patchable-function"="prologue-short-redirect"
+// HOTPATCH: attributes #2 = { {{.*}} "patchable-function"="prologue-short-redirect"
+// HOTPATCH: attributes #3 = { {{.*}} "patchable-function"="prologue-short-redirect"
--- /dev/null
+// REQUIRES: aarch64-registered-target || arm-registered-target
+///
+/// Check that using /hotpatch doesn't generate an error.
+/// Binaries are always hotpatchable on ARM/ARM64.
+///
+// RUN: %clang_cl --target=arm-pc-windows-msvc /c /hotpatch /Z7 -- %s 2>&1
+// RUN: %clang_cl --target=aarch64-pc-windows-msvc /c /hotpatch /Z7 -- %s 2>&1
+///
+/// Ensure that we set the hotpatchable flag in the debug information.
+///
+// RUN: %clang_cl --target=arm-pc-windows-msvc /c /Z7 -o %t.obj -- %s
+// RUN: llvm-pdbutil dump -symbols %t.obj | FileCheck %s --check-prefix=HOTPATCH
+// RUN: %clang_cl --target=aarch64-pc-windows-msvc /c /Z7 -o %t.obj -- %s
+// RUN: llvm-pdbutil dump -symbols %t.obj | FileCheck %s --check-prefix=HOTPATCH
+// HOTPATCH: S_COMPILE3 [size = [[#]]]
+// HOTPATCH: flags = hot patchable
+///
+/// Unfortunately we need /Z7, Clang does not systematically generate S_COMPILE3.
+///
+// RUN: %clang_cl --target=aarch64-pc-windows-msvc /c -o %t.obj -- %s
+// RUN: llvm-pdbutil dump -symbols %t.obj | FileCheck %s --check-prefix=NO-HOTPATCH
+// NO-HOTPATCH-NOT: flags = hot patchable
+
+int main() {
+ return 0;
+}
--- /dev/null
+// REQUIRES: x86-registered-target
+///
+// RUN: %clang_cl --target=x86_64-windows-msvc /c /hotpatch /Z7 -o %t.obj -- %s
+// RUN: llvm-pdbutil dump -symbols %t.obj | FileCheck %s --check-prefix=HOTPATCH
+// HOTPATCH: S_COMPILE3 [size = [[#]]]
+// HOTPATCH: flags = hot patchable
+///
+// RUN: %clang_cl --target=x86_64-windows-msvc /c /Z7 -o %t.obj -- %s
+// RUN: llvm-pdbutil dump -symbols %t.obj | FileCheck %s --check-prefix=NO-HOTPATCH
+// NO-HOTPATCH-NOT: flags = hot patchable
+///
+// RUN: %clang_cl --target=x86_64-windows-msvc /hotpatch -### -- %s 2>&1 \
+// RUN: | FileCheck %s --check-prefix=FUNCTIONPADMIN
+// FUNCTIONPADMIN: clang{{.*}}
+// FUNCTIONPADMIN: {{link.*"}}
+// FUNCTIONPADMIN: -functionpadmin
+
+int main() {
+ return 0;
+}
// RUN: %clang_cl /Gw /Gw- -### -- %s 2>&1 | FileCheck -check-prefix=Gw_ %s
// Gw_-NOT: -fdata-sections
+// RUN: %clang_cl /hotpatch -### -- %s 2>&1 | FileCheck -check-prefix=hotpatch %s
+// hotpatch: -fms-hotpatch
+
// RUN: %clang_cl /Imyincludedir -### -- %s 2>&1 | FileCheck -check-prefix=SLASH_I %s
// RUN: %clang_cl /I myincludedir -### -- %s 2>&1 | FileCheck -check-prefix=SLASH_I %s
// SLASH_I: "-I" "myincludedir"
// RUN: /GZ \
// RUN: /H \
// RUN: /homeparams \
-// RUN: /hotpatch \
// RUN: /JMC \
// RUN: /kernel \
// RUN: /LN \
EnableMachineFunctionSplitter(false), SupportsDefaultOutlining(false),
EmitAddrsig(false), EmitCallSiteInfo(false),
SupportsDebugEntryValues(false), EnableDebugEntryValues(false),
- ValueTrackingVariableLocations(false),
- ForceDwarfFrameSection(false), XRayOmitFunctionIndex(false),
- DebugStrictDwarf(false),
+ ValueTrackingVariableLocations(false), ForceDwarfFrameSection(false),
+ XRayOmitFunctionIndex(false), DebugStrictDwarf(false),
+ Hotpatch(false),
FPDenormalMode(DenormalMode::IEEE, DenormalMode::IEEE) {}
/// DisableFramePointerElim - This returns true if frame pointer elimination
/// By default, it is set to false.
unsigned DebugStrictDwarf : 1;
+ /// Emit the hotpatch flag in CodeView debug.
+ unsigned Hotpatch : 1;
+
/// Name of the stack usage file (i.e., .su file) if user passes
/// -fstack-usage. If empty, it can be implied that -fstack-usage is not
/// passed on the command line.
if (MMI->getModule()->getProfileSummary(/*IsCS*/ false) != nullptr) {
Flags |= static_cast<uint32_t>(CompileSym3Flags::PGO);
}
+ using ArchType = llvm::Triple::ArchType;
+ ArchType Arch = Triple(MMI->getModule()->getTargetTriple()).getArch();
+ if (Asm->TM.Options.Hotpatch || Arch == ArchType::thumb ||
+ Arch == ArchType::aarch64) {
+ Flags |= static_cast<uint32_t>(CompileSym3Flags::HotPatch);
+ }
OS.AddComment("Flags and language");
OS.emitInt32(Flags);
; OBJ: Compile3Sym {
; OBJ-NEXT: Kind: S_COMPILE3 (0x113C)
; OBJ-NEXT: Language: C (0x0)
-; OBJ-NEXT: Flags [ (0x0)
+; OBJ-NEXT: Flags [ (0x4000)
+; OBJ-NEXT: HotPatch (0x4000)
; OBJ-NEXT: ]
; OBJ-NEXT: Machine: ARMNT (0xF4)
; CHECK: Compile3Sym {
; CHECK: Kind: S_COMPILE3 (0x113C)
; CHECK: Language: C (0x0)
-; CHECK: Flags [ (0x0)
+; CHECK: Flags [ (0x4000
+; CHECK: HotPatch (0x4000)
; CHECK: ]
; CHECK: }
; CHECK: ]