llvm-project/llvm/lib/CodeGen/WindowsSecureHotPatching.cpp
sivadeilra 8ecbfa66cd
Hot-patch __ref_* variables should be placed in .rdata, not .data (#151008)
This is a refinment of #145565 . That PR added support for "Windows
Secure Hot-patching". In this design, functions that are compiled for
hot-patching need to be modified when they access mutable global
variables. The modification is to insert a level of indirection, the
so-called `__ref_*` variables.

Ref variables are supposed to be inserted into the `.rdata` section, not
`.data`. This provides a degree of protection against modification
(accidental or malicious) of ref variables during program execution.
When the Windows hot-patch subsystem loads a module as a hot-patch, it
finds all ref variables and changes the page protections for the pages
containing them to read/write. Then it sets the ref variables to point
to the real variable locations within the base image. Then it changes
page protections back to read-only.

This relies on the variables being placed in the `.rdata` section, not
`.data`.

However, it is still important that the LLVM `GlobalVariable` that is
created for the ref variable be created with `isConstant = false`. This
prevents LLVM from optimizing accesses to the `GlobalVariable`, i.e.
assuming that the variable can never change and thus inlining its value
into expressions that would ordinarily dereference it. That optimization
would defeat the purpose of hot-patching, so `isConstant = false` is
still the correct value for these ref variables.
2025-07-28 16:15:02 -07:00

631 lines
25 KiB
C++

//===------ WindowsHotPatch.cpp - Support for Windows hotpatching ---------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Provides support for the Windows "Secure Hot-Patching" feature.
//
// Windows contains technology, called "Secure Hot-Patching" (SHP), for securely
// applying hot-patches to a running system. Hot-patches may be applied to the
// kernel, kernel-mode components, device drivers, user-mode system services,
// etc.
//
// SHP relies on integration between many tools, including compiler, linker,
// hot-patch generation tools, and the Windows kernel. This file implements that
// part of the workflow needed in compilers / code generators.
//
// SHP is not intended for productivity scenarios such as Edit-and-Continue or
// interactive development. SHP is intended to minimize downtime during
// installation of Windows OS patches.
//
// In order to work with SHP, LLVM must do all of the following:
//
// * On some architectures (X86, AMD64), the function prolog must begin with
// hot-patchable instructions. This is handled by the MSVC `/hotpatch` option
// and the equivalent `-fms-hotpatch` function. This is necessary because we
// generally cannot anticipate which functions will need to be patched in the
// future. This option ensures that a function can be hot-patched in the
// future, but does not actually generate any hot-patch for it.
//
// * For a selected set of functions that are being hot-patched (which are
// identified using command-line options), LLVM must generate the
// `S_HOTPATCHFUNC` CodeView record (symbol). This record indicates that a
// function was compiled with hot-patching enabled.
//
// This implementation uses the `MarkedForWindowsHotPatching` attribute to
// annotate those functions that were marked for hot-patching by command-line
// parameters. The attribute may be specified by a language front-end by
// setting an attribute when a function is created in LLVM IR, or it may be
// set by passing LLVM arguments.
//
// * For those functions that are hot-patched, LLVM must rewrite references to
// global variables so that they are indirected through a `__ref_*` pointer
// variable. For each global variable, that is accessed by a hot-patched
// function, e.g. `FOO`, a `__ref_FOO` global pointer variable is created and
// all references to the original `FOO` are rewritten as dereferences of the
// `__ref_FOO` pointer.
//
// Some globals do not need `__ref_*` indirection. The pointer indirection
// behavior can be disabled for these globals by marking them with the
// `AllowDirectAccessInHotPatchFunction`.
//
// Rewriting references to global variables has some complexity.
//
// For ordinary instructions that reference GlobalVariables, we rewrite the
// operand of the instruction to a Load of the __ref_* variable.
//
// For constant expressions, we have to convert the constant expression (and
// transitively all constant expressions in its parent chain) to non-constant
// expressions, i.e. to a sequence of instructions.
//
// Pass 1:
// * Enumerate all instructions in all basic blocks.
//
// * If an instruction references a GlobalVariable (and it is not marked
// as being ignored), then we create (if necessary) the __ref_* variable
// for the GlobalVariable reference. However, we do not yet modify the
// Instruction.
//
// * If an instruction has an operand that is a ConstantExpr and the
// ConstantExpression tree contains a reference to a GlobalVariable, then
// we similarly create __ref_*. Similarly, we do not yet modify the
// Instruction or the ConstantExpr tree.
//
// After Pass 1 completes, we will know whether we found any references to
// globals in this pass. If the function does not use any globals (and most
// functions do not use any globals), then we return immediately.
//
// If a function does reference globals, then we iterate the list of globals
// used by this function and we generate Load instructions for each (unique)
// global.
//
// Next, we do another pass over all instructions:
//
// Pass 2:
// * Re-visit the instructions that were found in Pass 1.
//
// * If an instruction operand is a GlobalVariable, then look up the
// replacement
// __ref_* global variable and the Value that came from the Load instruction
// for it. Replace the operand of the GlobalVariable with the Load Value.
//
// * If an instruction operand is a ConstantExpr, then recursively examine the
// operands of all instructions in the ConstantExpr tree. If an operand is
// a GlobalVariable, then replace the operand with the result of the load
// *and* convert the ConstantExpr to a non-constant instruction. This
// instruction will need to be inserted into the BB of the instruction whose
// operand is being modified, ideally immediately before the instruction
// being modified.
//
// Limitations
//
// This feature is not intended to work in every situation. There are many
// legitimate code changes (patches) for which it is not possible to generate
// a hot-patch. Developers who are writing hot-patches are expected to
// understand the limitations.
//
// Tools which generate hot-patch metadata may also check that certain
// variables are upheld, and some of these invariants may be global (may require
// whole-program knowledge, not available in any single compiland). However,
// such tools are not required to be perfect; they are also best-effort.
//
// For these reasons, the hot-patching support implemented in this file is
// "best effort". It does not recognize every possible code pattern that could
// be patched, nor does it generate diagnostics for certain code patterns that
// could result in a binary that does not work with hot-patching. For example,
// const GlobalVariables that point to other non-const GlobalVariables are not
// compatible with hot-patching because they cannot use __ref_*-based
// redirection.
//
// References
//
// * "Hotpatching on Windows":
// https://techcommunity.microsoft.com/blog/windowsosplatform/hotpatching-on-windows/2959541
//
// * "Hotpatch for Windows client now available":
// https://techcommunity.microsoft.com/blog/windows-itpro-blog/hotpatch-for-windows-client-now-available/4399808
//
// * "Get hotpatching for Windows Server":
// https://www.microsoft.com/en-us/windows-server/blog/2025/04/24/tired-of-all-the-restarts-get-hotpatching-for-windows-server/
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/SmallSet.h"
#include "llvm/CodeGen/Passes.h"
#include "llvm/IR/Attributes.h"
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Module.h"
#include "llvm/InitializePasses.h"
#include "llvm/Pass.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/MemoryBuffer.h"
using namespace llvm;
#define DEBUG_TYPE "windows-secure-hot-patch"
// A file containing list of mangled function names to mark for hot patching.
static cl::opt<std::string> LLVMMSSecureHotPatchFunctionsFile(
"ms-secure-hotpatch-functions-file", cl::value_desc("filename"),
cl::desc("A file containing list of mangled function names to mark for "
"Windows Secure Hot-Patching"));
// A list of mangled function names to mark for hot patching.
static cl::list<std::string> LLVMMSSecureHotPatchFunctionsList(
"ms-secure-hotpatch-functions-list", cl::value_desc("list"),
cl::desc("A list of mangled function names to mark for Windows Secure "
"Hot-Patching"),
cl::CommaSeparated);
namespace {
struct GlobalVariableUse {
// GlobalVariable *GV;
Instruction *User;
unsigned Op;
};
class WindowsSecureHotPatching : public ModulePass {
public:
static char ID;
WindowsSecureHotPatching() : ModulePass(ID) {
initializeWindowsSecureHotPatchingPass(*PassRegistry::getPassRegistry());
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.setPreservesCFG();
}
bool doInitialization(Module &) override;
bool runOnModule(Module &M) override { return false; }
private:
bool
runOnFunction(Function &F,
SmallDenseMap<GlobalVariable *, GlobalVariable *> &RefMapping);
};
} // end anonymous namespace
char WindowsSecureHotPatching::ID = 0;
INITIALIZE_PASS(WindowsSecureHotPatching, "windows-secure-hot-patch",
"Mark functions for Windows hot patch support", false, false)
ModulePass *llvm::createWindowsSecureHotPatchingPass() {
return new WindowsSecureHotPatching();
}
// Find functions marked with Attribute::MarkedForWindowsHotPatching and modify
// their code (if necessary) to account for accesses to global variables.
//
// This runs during doInitialization() instead of runOnModule() because it needs
// to run before CodeViewDebug::collectGlobalVariableInfo().
bool WindowsSecureHotPatching::doInitialization(Module &M) {
// The front end may have already marked functions for hot-patching. However,
// we also allow marking functions by passing -ms-hotpatch-functions-file or
// -ms-hotpatch-functions-list directly to LLVM. This allows hot-patching to
// work with languages that have not yet updated their front-ends.
if (!LLVMMSSecureHotPatchFunctionsFile.empty() ||
!LLVMMSSecureHotPatchFunctionsList.empty()) {
std::vector<std::string> HotPatchFunctionsList;
if (!LLVMMSSecureHotPatchFunctionsFile.empty()) {
auto BufOrErr = MemoryBuffer::getFile(LLVMMSSecureHotPatchFunctionsFile);
if (BufOrErr) {
const MemoryBuffer &FileBuffer = **BufOrErr;
for (line_iterator I(FileBuffer.getMemBufferRef(), true), E; I != E;
++I)
HotPatchFunctionsList.push_back(std::string{*I});
} else {
M.getContext().diagnose(DiagnosticInfoGeneric{
Twine("failed to open hotpatch functions file "
"(--ms-hotpatch-functions-file): ") +
LLVMMSSecureHotPatchFunctionsFile + Twine(" : ") +
BufOrErr.getError().message()});
}
}
if (!LLVMMSSecureHotPatchFunctionsList.empty())
for (const auto &FuncName : LLVMMSSecureHotPatchFunctionsList)
HotPatchFunctionsList.push_back(FuncName);
// Build a set for quick lookups. This points into HotPatchFunctionsList, so
// HotPatchFunctionsList must live longer than HotPatchFunctionsSet.
SmallSet<StringRef, 16> HotPatchFunctionsSet;
for (const auto &FuncName : HotPatchFunctionsList)
HotPatchFunctionsSet.insert(StringRef{FuncName});
// Iterate through all of the functions and check whether they need to be
// marked for hotpatching using the list provided directly to LLVM.
for (auto &F : M.functions()) {
// Ignore declarations that are not definitions.
if (F.isDeclarationForLinker())
continue;
if (HotPatchFunctionsSet.contains(F.getName()))
F.addFnAttr("marked_for_windows_hot_patching");
}
}
SmallDenseMap<GlobalVariable *, GlobalVariable *> RefMapping;
bool MadeChanges = false;
for (auto &F : M.functions()) {
if (F.hasFnAttribute("marked_for_windows_hot_patching")) {
if (runOnFunction(F, RefMapping))
MadeChanges = true;
}
}
return MadeChanges;
}
static bool TypeContainsPointers(Type *ty) {
switch (ty->getTypeID()) {
case Type::PointerTyID:
return true;
case Type::ArrayTyID:
return TypeContainsPointers(ty->getArrayElementType());
case Type::StructTyID: {
unsigned NumElements = ty->getStructNumElements();
for (unsigned I = 0; I < NumElements; ++I) {
if (TypeContainsPointers(ty->getStructElementType(I))) {
return true;
}
}
return false;
}
default:
return false;
}
}
// Returns true if GV needs redirection through a __ref_* variable.
static bool globalVariableNeedsRedirect(GlobalVariable *GV) {
// If a global variable is explictly marked as allowing access in hot-patched
// functions, then do not redirect it.
if (GV->hasAttribute("allow_direct_access_in_hot_patch_function"))
return false;
// If the global variable is not a constant, then we want to redirect it.
if (!GV->isConstant()) {
if (GV->getName().starts_with("??_R")) {
// This is the name mangling prefix that MSVC uses for RTTI data.
// Clang is currently generating RTTI data that is marked non-constant.
// We override that and treat it like it is constant.
return false;
}
// In general, if a global variable is not a constant, then redirect it.
return true;
}
// If the type of GV cannot contain pointers, then it cannot point to
// other global variables. In this case, there is no need for redirects.
// For example, string literals do not contain pointers.
return TypeContainsPointers(GV->getValueType());
}
// Get or create a new global variable that points to the old one and whose
// name begins with `__ref_`.
//
// In hot-patched images, the __ref_* variables point to global variables in
// the original (unpatched) image. Hot-patched functions in the hot-patch
// image use these __ref_* variables to access global variables. This ensures
// that all code (both unpatched and patched) is using the same instances of
// global variables.
//
// The Windows hot-patch infrastructure handles modifying these __ref_*
// variables. By default, they are initialized with pointers to the equivalent
// global variables, so when a hot-patch module is loaded *as* a base image
// (such as after a system reboot), hot-patch functions will access the
// instances of global variables that are compiled into the hot-patch image.
// This is the desired outcome, since in this situation (normal boot) the
// hot-patch image *is* the base image.
//
// When we create the GlobalVariable for the __ref_* variable, we must create
// it as a *non-constant* global variable. The __ref_* pointers will not change
// during the runtime of the program, so it is tempting to think that they
// should be constant. However, they still need to be updateable by the
// hot-patching infrastructure. Also, if the GlobalVariable is created as a
// constant, then the LLVM optimizer will assume that it can dereference the
// definition of the __ref_* variable at compile time, which defeats the
// purpose of the indirection (pointer).
//
// The RefMapping table spans the entire module, not just a single function.
static GlobalVariable *getOrCreateRefVariable(
Function &F, SmallDenseMap<GlobalVariable *, GlobalVariable *> &RefMapping,
GlobalVariable *GV) {
GlobalVariable *&ReplaceWithRefGV = RefMapping.try_emplace(GV).first->second;
if (ReplaceWithRefGV != nullptr) {
// We have already created a __ref_* pointer for this GlobalVariable.
return ReplaceWithRefGV;
}
Module *M = F.getParent();
const DISubprogram *Subprogram = F.getSubprogram();
DICompileUnit *Unit = Subprogram != nullptr ? Subprogram->getUnit() : nullptr;
DIFile *File = Subprogram != nullptr ? Subprogram->getFile() : nullptr;
DIBuilder DebugInfo{*F.getParent(), true, Unit};
auto PtrTy = PointerType::get(M->getContext(), 0);
Constant *AddrOfOldGV =
ConstantExpr::getGetElementPtr(PtrTy, GV, ArrayRef<Value *>{});
GlobalVariable *RefGV =
new GlobalVariable(*M, PtrTy, false, GlobalValue::LinkOnceAnyLinkage,
AddrOfOldGV, Twine("__ref_").concat(GV->getName()),
nullptr, GlobalVariable::NotThreadLocal);
// RefGV is created with isConstant = false, but we want to place RefGV into
// .rdata, not .data. It is important that the GlobalVariable be mutable
// from the compiler's point of view, so that the optimizer does not remove
// the global variable entirely and replace all references to it with its
// initial value.
//
// When the Windows hot-patch loader applies a hot-patch, it maps the
// pages of .rdata as read/write so that it can set each __ref_* variable
// to point to the original variable in the base image. Afterward, pages in
// .rdata are remapped as read-only. This protects the __ref_* variables from
// being overwritten during execution.
RefGV->setSection(".rdata");
// Create debug info for the replacement global variable.
DataLayout Layout = M->getDataLayout();
DIType *DebugType = DebugInfo.createPointerType(
nullptr, Layout.getTypeSizeInBits(GV->getValueType()));
DIGlobalVariableExpression *GVE = DebugInfo.createGlobalVariableExpression(
Unit, RefGV->getName(), StringRef{}, File,
/*LineNo*/ 0, DebugType,
/*IsLocalToUnit*/ false);
RefGV->addDebugInfo(GVE);
// Store the __ref_* in RefMapping so that future calls use the same RefGV.
ReplaceWithRefGV = RefGV;
return RefGV;
}
// Given a ConstantExpr, this searches for GlobalVariable references within
// the expression tree. If found, it will generate instructions and will
// return a non-null Value* that points to the new root instruction.
//
// If C does not contain any GlobalVariable references, this returns nullptr.
//
// If this function creates new instructions, then it will insert them
// before InsertionPoint.
static Value *rewriteGlobalVariablesInConstant(
Constant *C, SmallDenseMap<GlobalVariable *, Value *> &GVLoadMap,
IRBuilder<> &IRBuilderAtEntry) {
if (C->getValueID() == Value::GlobalVariableVal) {
GlobalVariable *GV = cast<GlobalVariable>(C);
if (globalVariableNeedsRedirect(GV)) {
return GVLoadMap.at(GV);
} else {
return nullptr;
}
}
// Scan the operands of this expression.
SmallVector<Value *, 8> ReplacedValues;
bool ReplacedAnyOperands = false;
unsigned NumOperands = C->getNumOperands();
for (unsigned OpIndex = 0; OpIndex < NumOperands; ++OpIndex) {
Value *OldValue = C->getOperand(OpIndex);
Value *ReplacedValue = nullptr;
if (Constant *OldConstant = dyn_cast<Constant>(OldValue)) {
ReplacedValue = rewriteGlobalVariablesInConstant(OldConstant, GVLoadMap,
IRBuilderAtEntry);
}
// Do not use short-circuiting, here. We need to traverse the whole tree.
ReplacedAnyOperands |= ReplacedValue != nullptr;
ReplacedValues.push_back(ReplacedValue);
}
// If none of our operands were replaced, then don't rewrite this expression.
if (!ReplacedAnyOperands) {
return nullptr;
}
// We need to rewrite this expression. Convert this constant expression
// to an instruction, then replace any operands as needed.
Instruction *NewInst = cast<ConstantExpr>(C)->getAsInstruction();
for (unsigned OpIndex = 0; OpIndex < NumOperands; ++OpIndex) {
Value *ReplacedValue = ReplacedValues[OpIndex];
if (ReplacedValue != nullptr) {
NewInst->setOperand(OpIndex, ReplacedValue);
}
}
// Insert the new instruction before the reference instruction.
IRBuilderAtEntry.Insert(NewInst);
return NewInst;
}
static bool searchConstantExprForGlobalVariables(
Value *V, SmallDenseMap<GlobalVariable *, Value *> &GVLoadMap,
SmallVector<GlobalVariableUse> &GVUses) {
SmallVector<Value *, 8> ReplacedOperands;
if (GlobalVariable *GV = dyn_cast<GlobalVariable>(V)) {
if (globalVariableNeedsRedirect(GV)) {
GVLoadMap[GV] = nullptr;
return true;
} else {
return false;
}
}
if (User *U = dyn_cast<User>(V)) {
unsigned NumOperands = U->getNumOperands();
bool FoundAny = false;
for (unsigned OpIndex = 0; OpIndex < NumOperands; ++OpIndex) {
Value *Op = U->getOperand(OpIndex);
// Do not use short-circuiting, here. We need to traverse the whole tree.
FoundAny |= searchConstantExprForGlobalVariables(Op, GVLoadMap, GVUses);
}
return FoundAny;
} else {
return false;
}
}
// Processes a function that is marked for hot-patching.
//
// If a function is marked for hot-patching, we generate an S_HOTPATCHFUNC
// CodeView debug symbol. Tools that generate hot-patches look for
// S_HOTPATCHFUNC in final PDBs so that they can find functions that have been
// hot-patched and so that they can distinguish hot-patched functions from
// non-hot-patched functions.
//
// Also, in functions that are hot-patched, we must indirect all access to
// (mutable) global variables through a pointer. This pointer may point into the
// unpatched ("base") binary or may point into the patched image, depending on
// whether a hot-patch was loaded as a patch or as a base image. These
// indirections go through a new global variable, named `__ref_<Foo>` where
// `<Foo>` is the original symbol name of the global variable.
//
// This function handles rewriting accesses to global variables, but the
// generation of S_HOTPATCHFUNC occurs in
// CodeViewDebug::emitHotPatchInformation().
//
// Returns true if any global variable references were found and rewritten.
bool WindowsSecureHotPatching::runOnFunction(
Function &F,
SmallDenseMap<GlobalVariable *, GlobalVariable *> &RefMapping) {
// Scan the function for references to global variables. If we find such a
// reference, create (if necessary) the __ref_* variable, then add an entry
// to the GVUses table.
//
// We ignore references to global variables if the variable is marked with
// AllowDirectAccessInHotPatchFunction.
SmallDenseMap<GlobalVariable *, Value *> GVLoadMap;
SmallVector<GlobalVariableUse> GVUses;
for (auto &I : instructions(F)) {
unsigned NumOperands = I.getNumOperands();
for (unsigned OpIndex = 0; OpIndex < NumOperands; ++OpIndex) {
Value *V = I.getOperand(OpIndex);
bool FoundAnyGVUses = false;
switch (V->getValueID()) {
case Value::GlobalVariableVal: {
// Discover all uses of GlobalVariable, these will need to be replaced.
GlobalVariable *GV = cast<GlobalVariable>(V);
if (globalVariableNeedsRedirect(GV)) {
GVLoadMap.insert(std::make_pair(GV, nullptr));
FoundAnyGVUses = true;
}
break;
}
case Value::ConstantExprVal: {
ConstantExpr *CE = cast<ConstantExpr>(V);
if (searchConstantExprForGlobalVariables(CE, GVLoadMap, GVUses)) {
FoundAnyGVUses = true;
}
break;
}
default:
break;
}
if (FoundAnyGVUses) {
GVUses.push_back(GlobalVariableUse{&I, OpIndex});
}
}
}
// If this function did not reference any global variables then we have no
// work to do. Most functions do not access global variables.
if (GVUses.empty()) {
return false;
}
// We know that there is at least one instruction that needs to be rewritten.
// Generate a Load instruction for each unique GlobalVariable used by this
// function. The Load instructions are inserted at the beginning of the
// entry block. Since entry blocks cannot contain PHI instructions, there is
// no need to skip PHI instructions.
// We use a single IRBuilder for inserting Load instructions as well as the
// constants that we convert to instructions. Because constants do not
// depend on any dynamic values (they're constant, after all!), it is safe
// to move them to the start of entry BB.
auto &EntryBlock = F.getEntryBlock();
IRBuilder<> IRBuilderAtEntry(&EntryBlock, EntryBlock.begin());
for (auto &[GV, LoadValue] : GVLoadMap) {
assert(LoadValue == nullptr);
GlobalVariable *RefGV = getOrCreateRefVariable(F, RefMapping, GV);
LoadValue = IRBuilderAtEntry.CreateLoad(RefGV->getValueType(), RefGV);
}
const DISubprogram *Subprogram = F.getSubprogram();
DICompileUnit *Unit = Subprogram != nullptr ? Subprogram->getUnit() : nullptr;
DIBuilder DebugInfo{*F.getParent(), true, Unit};
// Go back to the instructions and rewrite their uses of GlobalVariable.
// Because a ConstantExpr can be a tree, it may reference more than one
// GlobalVariable.
for (auto &GVUse : GVUses) {
Value *OldOperandValue = GVUse.User->getOperand(GVUse.Op);
Value *NewOperandValue;
switch (OldOperandValue->getValueID()) {
case Value::GlobalVariableVal: {
// This is easy. Look up the replacement value and store the operand.
Value *OperandValue = GVUse.User->getOperand(GVUse.Op);
GlobalVariable *GV = cast<GlobalVariable>(OperandValue);
NewOperandValue = GVLoadMap.at(GV);
break;
}
case Value::ConstantExprVal: {
// Walk the recursive tree of the ConstantExpr. If we find a
// GlobalVariable then replace it with the loaded value and rewrite
// the ConstantExpr to an Instruction and insert it before the
// current instruction.
Value *OperandValue = GVUse.User->getOperand(GVUse.Op);
ConstantExpr *CE = cast<ConstantExpr>(OperandValue);
NewOperandValue =
rewriteGlobalVariablesInConstant(CE, GVLoadMap, IRBuilderAtEntry);
assert(NewOperandValue != nullptr);
break;
}
default:
// We should only ever get here because a GVUse was created in the first
// pass, and this only happens for GlobalVariableVal and ConstantExprVal.
llvm_unreachable_internal(
"unexpected Value in second pass of hot-patching");
break;
}
GVUse.User->setOperand(GVUse.Op, NewOperandValue);
}
return true;
}