[llvm] [Utils] Hoist ValueMapper out of per-instruction remap loops in CloneFunction (PR #216014)

via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 13 05:01:39 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Antoine Bastide (AntoineBastide47)

<details>
<summary>Changes</summary>

I am using LLVM as a backend for a personal project and am trying to make it faster and potentially use less memory, but since I don't know the full scope of the project I tasked an agent to helping me.

I gave it very strict constraints as I didn't want it to just produce slop:
 - Ground all theories in proofs
 - Identical generated code
 - Tests are immutable and the oracle/source of truth
 - Identical diagnostics
 - Should work on all targets
 - Allow memory-for-speed trades only when it does not create a memory blow up

## Change

The free helpers RemapInstruction/MapValue/RemapDbgRecordRange heap-allocate a ~500-byte Mapper per call and the clone paths (CloneFunctionBodyInto, CloneAndPruneIntoFromInst, remapInstructionsInBlocks) call these once per instruction. So instead, since every public ValueMapper method flushes on exit (i.e. reuse behaviour is identical), the changes now construct a single instance of ValueMapper per loop. Also the header itself has a TODO endorsing mapper reuse (ValueMapper.h).

No new tests were added as the existing ones already correctly assert the functional behaviour.

## Measurements

Apple M3 Max (arm64), CTMark at O3, A/B against the same base commit. "hot-77" is the 77 most expensive files from CTMark O3 builds. Instructions retired is used as the speed metric (~±0.05% same-binary noise vs. ±1-3% for wall time on this machine).

| Workload         | Instructions            | Wall          | RSS           | Allocations   | Allocated bytes |
|------------------|-------------------------|---------------|---------------|---------------|-----------------|
| tramp3d-v4 (C++) | -0.56%                  | -1.2%         | +0.4% (noise) | -998K (-5.7%) | -367MB (-7.9%)  |
| sqlite3 (C)      | -0.38%                  | -0.3%         | +1.5% (noise) | -202K (-4.3%) | -74MB (-4.4%)   |
| tramp3d IR path  | -0.71%                  | -0.3%         | -0.5% (noise) |               |                 |
| sqlite IR path   | -0.35%                  | +2.1% (noise) | +1.2% (noise) |               |                 |
| hot-77 corpus    | -0.32% sum / -0.22% geo | -0.36%        | +0.2% (noise) |               |                 |

Notes:
 - RSS is noise since the change reduces allocator work and not peak memory occupancy.
 - Allocation counts are exact (malloc interposition on the cc1 process).

## Validation

- macOS arm64: check-llvm + check-clang clean (0 unexpected failures);
  llvm-test-suite 3523/3524, the one failure pre-exists at the base commit
  and is identical with the baseline compiler.
- Linux aarch64 (container): check-llvm + check-clang and llvm-test-suite
  clean, allocation reductions reproduce (-6.0% count on tramp3d-v4).

This change was developed with AI assistance. I have reviewed, tested, and understand it and take ownership of it.

---
Full diff: https://github.com/llvm/llvm-project/pull/216014.diff


1 Files Affected:

- (modified) llvm/lib/Transforms/Utils/CloneFunction.cpp (+19-24) 


``````````diff
diff --git a/llvm/lib/Transforms/Utils/CloneFunction.cpp b/llvm/lib/Transforms/Utils/CloneFunction.cpp
index 7896b78a2c208..b53226f6974a8 100644
--- a/llvm/lib/Transforms/Utils/CloneFunction.cpp
+++ b/llvm/lib/Transforms/Utils/CloneFunction.cpp
@@ -289,6 +289,7 @@ void llvm::CloneFunctionBodyInto(Function &NewFunc, const Function &OldFunc,
 
   // Loop over all of the instructions in the new function, fixing up operand
   // references as we go. This uses VMap to do all the hard work.
+  ValueMapper Mapper(VMap, RemapFlag, TypeMapper, Materializer, IdentityMD);
   for (Function::iterator
            BB = cast<BasicBlock>(VMap[&OldFunc.front()])->getIterator(),
            BE = NewFunc.end();
@@ -296,10 +297,8 @@ void llvm::CloneFunctionBodyInto(Function &NewFunc, const Function &OldFunc,
     // Loop over all instructions, fixing each one as we find it, and any
     // attached debug-info records.
     for (Instruction &II : *BB) {
-      RemapInstruction(&II, VMap, RemapFlag, TypeMapper, Materializer,
-                       IdentityMD);
-      RemapDbgRecordRange(II.getModule(), II.getDbgRecordRange(), VMap,
-                          RemapFlag, TypeMapper, Materializer, IdentityMD);
+      Mapper.remapInstruction(II);
+      Mapper.remapDbgRecordRange(II.getModule(), II.getDbgRecordRange());
     }
 }
 
@@ -447,6 +446,7 @@ struct PruningFunctionCloner {
   Function *NewFunc;
   const Function *OldFunc;
   ValueToValueMapTy &VMap;
+  ValueMapper &Remapper;
   bool ModuleLevelChanges;
   const char *NameSuffix;
   ClonedCodeInfo &CodeInfo;
@@ -456,9 +456,10 @@ struct PruningFunctionCloner {
 
 public:
   PruningFunctionCloner(Function *newFunc, const Function *oldFunc,
-                        ValueToValueMapTy &valueMap, bool moduleLevelChanges,
-                        const char *nameSuffix, ClonedCodeInfo &codeInfo)
-      : NewFunc(newFunc), OldFunc(oldFunc), VMap(valueMap),
+                        ValueToValueMapTy &valueMap, ValueMapper &remapper,
+                        bool moduleLevelChanges, const char *nameSuffix,
+                        ClonedCodeInfo &codeInfo)
+      : NewFunc(newFunc), OldFunc(oldFunc), VMap(valueMap), Remapper(remapper),
         ModuleLevelChanges(moduleLevelChanges), NameSuffix(nameSuffix),
         CodeInfo(codeInfo) {
     HostFuncIsStrictFP =
@@ -587,8 +588,7 @@ void PruningFunctionCloner::CloneBlock(
     // Eagerly remap operands to the newly cloned instruction, except for PHI
     // nodes for which we defer processing until we update the CFG.
     if (!isa<PHINode>(NewInst)) {
-      RemapInstruction(NewInst, VMap,
-                       ModuleLevelChanges ? RF_None : RF_NoModuleLevelChanges);
+      Remapper.remapInstruction(*NewInst);
 
       // Eagerly constant fold the newly cloned instruction. If successful, add
       // a mapping to the new value. Non-constant operands may be incomplete at
@@ -726,7 +726,10 @@ void llvm::CloneAndPruneIntoFromInst(Function *NewFunc, const Function *OldFunc,
       assert(VMap.count(&II) && "No mapping from source argument specified!");
 #endif
 
-  PruningFunctionCloner PFC(NewFunc, OldFunc, VMap, ModuleLevelChanges,
+  ValueMapper Mapper(VMap,
+                     ModuleLevelChanges ? RF_None : RF_NoModuleLevelChanges,
+                     TypeMapper, Materializer);
+  PruningFunctionCloner PFC(NewFunc, OldFunc, VMap, Mapper, ModuleLevelChanges,
                             NameSuffix, CodeInfo);
   const BasicBlock *StartingBB;
   if (StartingInst)
@@ -773,9 +776,7 @@ void llvm::CloneAndPruneIntoFromInst(Function *NewFunc, const Function *OldFunc,
 
     // Finally, remap the terminator instructions, as those can't be remapped
     // until all BBs are mapped.
-    RemapInstruction(NewBB->getTerminator(), VMap,
-                     ModuleLevelChanges ? RF_None : RF_NoModuleLevelChanges,
-                     TypeMapper, Materializer);
+    Mapper.remapInstruction(*NewBB->getTerminator());
   }
 
   // Defer PHI resolution until rest of function is resolved, PHI resolution
@@ -796,9 +797,7 @@ void llvm::CloneAndPruneIntoFromInst(Function *NewFunc, const Function *OldFunc,
       for (int64_t pred = NumPreds - 1; pred >= 0; --pred) {
         Value *V = VMap.lookup(PN->getIncomingBlock(pred));
         if (BasicBlock *MappedBlock = cast_or_null<BasicBlock>(V)) {
-          Value *InVal =
-              MapValue(PN->getIncomingValue(pred), VMap,
-                       ModuleLevelChanges ? RF_None : RF_NoModuleLevelChanges);
+          Value *InVal = Mapper.mapValue(*PN->getIncomingValue(pred));
           assert(InVal && "Unknown input value?");
           PN->setIncomingValue(pred, InVal);
           PN->setIncomingBlock(pred, MappedBlock);
@@ -904,10 +903,7 @@ void llvm::CloneAndPruneIntoFromInst(Function *NewFunc, const Function *OldFunc,
   Function::iterator Begin = cast<BasicBlock>(VMap[StartingBB])->getIterator();
   for (BasicBlock &BB : make_range(Begin, NewFunc->end())) {
     for (Instruction &I : BB) {
-      RemapDbgRecordRange(I.getModule(), I.getDbgRecordRange(), VMap,
-                          ModuleLevelChanges ? RF_None
-                                             : RF_NoModuleLevelChanges,
-                          TypeMapper, Materializer);
+      Mapper.remapDbgRecordRange(I.getModule(), I.getDbgRecordRange());
     }
   }
 
@@ -1006,12 +1002,11 @@ void llvm::CloneAndPruneFunctionInto(Function *NewFunc, const Function *OldFunc,
 void llvm::remapInstructionsInBlocks(ArrayRef<BasicBlock *> Blocks,
                                      ValueToValueMapTy &VMap) {
   // Rewrite the code to refer to itself.
+  ValueMapper Mapper(VMap, RF_NoModuleLevelChanges | RF_IgnoreMissingLocals);
   for (BasicBlock *BB : Blocks) {
     for (Instruction &Inst : *BB) {
-      RemapDbgRecordRange(Inst.getModule(), Inst.getDbgRecordRange(), VMap,
-                          RF_NoModuleLevelChanges | RF_IgnoreMissingLocals);
-      RemapInstruction(&Inst, VMap,
-                       RF_NoModuleLevelChanges | RF_IgnoreMissingLocals);
+      Mapper.remapDbgRecordRange(Inst.getModule(), Inst.getDbgRecordRange());
+      Mapper.remapInstruction(Inst);
     }
   }
 }

``````````

</details>


https://github.com/llvm/llvm-project/pull/216014


More information about the llvm-commits mailing list