Skip to content

[NFC][KeyInstr] Add Atom Group (re)mapping #133479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
[NFC][KeyInstr] Add Atom Group (re)mapping
Add:
   mapAtomInstance - map the atom group number to a new group.
   RemapSourceAtom - apply the mapped atom group number to this instruction.

Modify:
  CloneBasicBlock - Call mapAtomInstance on cloned instruction's DebugLocs
                    if MapAtoms is true (default). Setting to false could
		    lead to a degraded debugging experience. See code comment.

Optimisations like loop unroll that duplicate instructions need to remap source
atom groups so that each duplicated source construct instance is considered
distinct when determining is_stmt locations.

This commit adds the remapping functionality and a unittest.
  • Loading branch information
OCHyams committed May 6, 2025
commit c5829702f686b69f727f1718fcb2524675f4bdbd
5 changes: 5 additions & 0 deletions llvm/include/llvm/IR/ValueMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ class ValueMap {
using ValueMapCVH = ValueMapCallbackVH<KeyT, ValueT, Config>;
using MapT = DenseMap<ValueMapCVH, ValueT, DenseMapInfo<ValueMapCVH>>;
using MDMapT = DenseMap<const Metadata *, TrackingMDRef>;
/// Map {(InlinedAt, old atom number) -> new atom number}.
using DMAtomT = DenseMap<std::pair<Metadata *, uint64_t>, uint64_t>;
Copy link
Member

Choose a reason for hiding this comment

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

Consider using SmallDenseMap simply to reduce the initial allocations in the non-debug-info codepath?

using ExtraData = typename Config::ExtraData;

MapT Map;
Expand Down Expand Up @@ -117,6 +119,8 @@ class ValueMap {
return *MDMap;
}
std::optional<MDMapT> &getMDMap() { return MDMap; }
/// Map {(InlinedAt, old atom number) -> new atom number}.
DMAtomT AtomMap;

/// Get the mapped metadata, if it's in the map.
std::optional<Metadata *> getMappedMD(const Metadata *MD) const {
Expand Down Expand Up @@ -145,6 +149,7 @@ class ValueMap {
void clear() {
Map.clear();
MDMap.reset();
AtomMap.clear();
}

/// Return 1 if the specified key is in the map, 0 otherwise.
Expand Down
15 changes: 14 additions & 1 deletion llvm/include/llvm/Transforms/Utils/Cloning.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "llvm/Analysis/AssumptionCache.h"
#include "llvm/Analysis/InlineCost.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/DebugLoc.h"
#include "llvm/IR/ValueHandle.h"
#include "llvm/Transforms/Utils/ValueMapper.h"
#include <functional>
Expand Down Expand Up @@ -117,9 +118,21 @@ struct ClonedCodeInfo {
/// If you would like to collect additional information about the cloned
/// function, you can specify a ClonedCodeInfo object with the optional fifth
/// parameter.
///
/// Set \p MapAtoms to false to skip mapping source atoms for later remapping.
Copy link
Member

Choose a reason for hiding this comment

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

IMO "source-location atoms" to make it even clearer that this is a debugging feature.

Also IMO it's better to discuss when this flag is necessary instead of when it's not necessary, as it'll enlighten the reader what it's for. AFAIUI, something like "Must be true when you duplicate a code path and a source line is intended to appear twice in the generated instructions. Can be set to false if you are transplanting code from one place to another".

/// Incorrectly setting false may harm the debugging experience. It's safe to
/// set false if the cloned basic block is destined for a different function or
/// if the original block is deleted. Setting true (default) is always safe
/// (correct) but sometimes unecessary; this option reduces the compile-time
/// impact of Key Instruction in such cases.
BasicBlock *CloneBasicBlock(const BasicBlock *BB, ValueToValueMapTy &VMap,
const Twine &NameSuffix = "", Function *F = nullptr,
ClonedCodeInfo *CodeInfo = nullptr);
ClonedCodeInfo *CodeInfo = nullptr,
bool MapAtoms = true);

/// Mark a cloned instruction as a new instance so that its source loc can
/// be updated when remapped.
void mapAtomInstance(const DebugLoc &DL, ValueToValueMapTy &VMap);

/// Return a copy of the specified function and add it to that
/// function's module. Also, any references specified in the VMap are changed
Expand Down
10 changes: 10 additions & 0 deletions llvm/include/llvm/Transforms/Utils/ValueMapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ enum RemapFlags {
/// Any global values not in value map are mapped to null instead of mapping
/// to self. Illegal if RF_IgnoreMissingLocals is also set.
RF_NullMapMissingGlobalValues = 8,

/// Do not remap atom instances. Only safe if to do this if the cloned
Copy link
Member

Choose a reason for hiding this comment

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

IMO needs "source location atom" instead of just "atom" to ensure the random reader knows it's about debug-info.

/// instructions being remapped are inserted into a new function, or an
/// existing function where the inlined-at fields are updated. If in doubt,
/// don't use this flag. It's used for compiler performance reasons rather
/// than correctness.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// don't use this flag. It's used for compiler performance reasons rather
/// than correctness.
/// don't use this flag. It's used when remapping is known to be un-necessary
/// to save some compile-time.

Copy link
Member

Choose a reason for hiding this comment

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

IMO suggesting that it's not related to correctness is misleading, because the presence/absence of the flag can lead to correctness issues. Better to just avoid saying that and say something else as suggested.

RF_DoNotRemapAtoms = 16,
};

inline RemapFlags operator|(RemapFlags LHS, RemapFlags RHS) {
Expand Down Expand Up @@ -284,6 +291,9 @@ inline void RemapInstruction(Instruction *I, ValueToValueMapTy &VM,
.remapInstruction(*I);
}

/// Remap source atom. Called by RemapInstruction.
Copy link
Member

Choose a reason for hiding this comment

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

IMO too terse; needs some purpose and context.

void RemapSourceAtom(Instruction *I, ValueToValueMapTy &VM);

/// Remap the Values used in the DbgRecord \a DR using the value map \a
/// VM.
inline void RemapDbgRecord(Module *M, DbgRecord *DR, ValueToValueMapTy &VM,
Expand Down
29 changes: 28 additions & 1 deletion llvm/lib/Transforms/Utils/CloneFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//

#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Analysis/ConstantFolding.h"
#include "llvm/Analysis/DomTreeUpdater.h"
#include "llvm/Analysis/InstructionSimplify.h"
Expand Down Expand Up @@ -40,6 +41,27 @@ using namespace llvm;

#define DEBUG_TYPE "clone-function"

STATISTIC(RemappedAtomMax, "Highest global NextAtomGroup (after mapping)");

void llvm::mapAtomInstance(const DebugLoc &DL, ValueToValueMapTy &VMap) {
auto CurGroup = DL.get()->getAtomGroup();
if (!CurGroup)
return;

// Try inserting a new entry. If there's already a mapping for this atom
// then there's nothing to do.
auto [It, Inserted] = VMap.AtomMap.insert({{DL.getInlinedAt(), CurGroup}, 0});
if (!Inserted)
return;

// Map entry to a new atom group.
uint64_t NewGroup = DL->getContext().incNextAtomGroup();
assert(NewGroup > CurGroup && "Next should always be greater than current");
It->second = NewGroup;

RemappedAtomMax = std::max<uint64_t>(NewGroup, RemappedAtomMax);
}

namespace {
void collectDebugInfoFromInstructions(const Function &F,
DebugInfoFinder &DIFinder) {
Expand Down Expand Up @@ -79,7 +101,7 @@ MetadataPredicate createIdentityMDPredicate(const Function &F,
/// See comments in Cloning.h.
BasicBlock *llvm::CloneBasicBlock(const BasicBlock *BB, ValueToValueMapTy &VMap,
const Twine &NameSuffix, Function *F,
ClonedCodeInfo *CodeInfo) {
ClonedCodeInfo *CodeInfo, bool MapAtoms) {
BasicBlock *NewBB = BasicBlock::Create(BB->getContext(), "", F);
NewBB->IsNewDbgInfoFormat = BB->IsNewDbgInfoFormat;
if (BB->hasName())
Expand All @@ -98,6 +120,11 @@ BasicBlock *llvm::CloneBasicBlock(const BasicBlock *BB, ValueToValueMapTy &VMap,

VMap[&I] = NewInst; // Add instruction map to value.

if (MapAtoms) {
if (const DebugLoc &DL = NewInst->getDebugLoc())
mapAtomInstance(DL.get(), VMap);
}

if (isa<CallInst>(I) && !I.isDebugOrPseudoInst()) {
hasCalls = true;
hasMemProfMetadata |= I.hasMetadata(LLVMContext::MD_memprof);
Expand Down
26 changes: 26 additions & 0 deletions llvm/lib/Transforms/Utils/ValueMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "llvm/IR/Type.h"
#include "llvm/IR/Value.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include <cassert>
#include <limits>
Expand Down Expand Up @@ -1010,6 +1011,10 @@ void Mapper::remapInstruction(Instruction *I) {
I->setMetadata(MI.first, New);
}

// Remap source location atom instance.
if (!(Flags & RF_DoNotRemapAtoms))
RemapSourceAtom(I, getVM());

if (!TypeMapper)
return;

Expand Down Expand Up @@ -1292,3 +1297,24 @@ void ValueMapper::scheduleMapGlobalIFunc(GlobalIFunc &GI, Constant &Resolver,
void ValueMapper::scheduleRemapFunction(Function &F, unsigned MCID) {
getAsMapper(pImpl)->scheduleRemapFunction(F, MCID);
}

void llvm::RemapSourceAtom(Instruction *I, ValueToValueMapTy &VM) {
const DebugLoc &DL = I->getDebugLoc();
if (!DL)
return;

auto AtomGroup = DL->getAtomGroup();
if (!AtomGroup)
return;

auto R = VM.AtomMap.find({DL->getInlinedAt(), AtomGroup});
if (R == VM.AtomMap.end())
return;
AtomGroup = R->second;

// Remap the atom group and copy all other fields.
DILocation *New = DILocation::get(
I->getContext(), DL.getLine(), DL.getCol(), DL.getScope(),
DL.getInlinedAt(), DL.isImplicitCode(), AtomGroup, DL->getAtomRank());
I->setDebugLoc(New);
}
104 changes: 104 additions & 0 deletions llvm/unittests/Transforms/Utils/CloningTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/AsmParser/Parser.h"
#include "llvm/IR/Argument.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constant.h"
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Transforms/Utils/ValueMapper.h"
#include "gtest/gtest.h"

using namespace llvm;
Expand Down Expand Up @@ -1162,4 +1165,105 @@ declare i64 @foo(i32 noundef) local_unnamed_addr
auto NewM = llvm::CloneModule(*M);
EXPECT_FALSE(verifyModule(*NewM, &errs()));
}

TEST_F(CloneInstruction, cloneKeyInstructions) {
LLVMContext Context;

std::unique_ptr<Module> M = parseIR(Context, R"(
define void @test(ptr align 4 %dst) !dbg !3 {
store i64 1, ptr %dst, align 4, !dbg !6
store i64 2, ptr %dst, align 4, !dbg !7
store i64 3, ptr %dst, align 4, !dbg !8
ret void, !dbg !9
}

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2}
!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1)
!1 = !DIFile(filename: "test.cpp", directory: "")
!2 = !{i32 1, !"Debug Info Version", i32 3}
!3 = distinct !DISubprogram(name: "test", scope: !0, unit: !0)
!4 = distinct !DISubprogram(name: "inlined", scope: !0, unit: !0, retainedNodes: !{!5})
!5 = !DILocalVariable(name: "awaitables", scope: !4)
!6 = !DILocation(line: 1, scope: !4, inlinedAt: !8, atomGroup: 1, atomRank: 1)
!7 = !DILocation(line: 2, scope: !3, atomGroup: 1, atomRank: 1)
!8 = !DILocation(line: 3, scope: !3, atomGroup: 1, atomRank: 1)
!9 = !DILocation(line: 4, scope: !3, atomGroup: 2, atomRank: 1)
)");

ASSERT_FALSE(verifyModule(*M, &errs()));

#ifdef EXPERIMENTAL_KEY_INSTRUCTIONS
#define EXPECT_ATOM(Inst, G) \
EXPECT_TRUE(Inst->getDebugLoc()); \
EXPECT_EQ(Inst->getDebugLoc()->getAtomGroup(), uint64_t(G));
#else
#define EXPECT_ATOM(Inst, G) (void)Inst;
#endif

Function *F = M->getFunction("test");
BasicBlock *BB = &*F->begin();
ASSERT_TRUE(F);
Instruction *Store1 = &*BB->begin();
Instruction *Store2 = Store1->getNextNode();
Instruction *Store3 = Store2->getNextNode();
Instruction *Ret = Store3->getNextNode();

// Test the remapping works as expected outside of cloning.
ValueToValueMapTy VM;
// Store1 and Store2 have the same atomGroup number, but have different
// inlining scopes, so only Store1 should change group.
mapAtomInstance(Store1->getDebugLoc(), VM);
for (Instruction &I : *BB)
RemapSourceAtom(&I, VM);
EXPECT_ATOM(Store1, 3);
EXPECT_ATOM(Store2, 1);
EXPECT_ATOM(Store3, 1);
EXPECT_ATOM(Ret, 2);
VM.clear();

// Store2 and Store3 have the same group number; both should get remapped.
mapAtomInstance(Store2->getDebugLoc(), VM);
for (Instruction &I : *BB)
RemapSourceAtom(&I, VM);
EXPECT_ATOM(Store1, 3);
EXPECT_ATOM(Store2, 4);
EXPECT_ATOM(Store3, 4);
EXPECT_ATOM(Ret, 2);
VM.clear();

// Cloning BB with MapAtoms=false should clone the atom numbers.
BasicBlock *BB2 =
CloneBasicBlock(BB, VM, "", nullptr, nullptr, /*MapAtoms*/ false);
for (Instruction &I : *BB2)
RemapSourceAtom(&I, VM);
Store1 = &*BB2->begin();
Store2 = Store1->getNextNode();
Store3 = Store2->getNextNode();
Ret = Store3->getNextNode();
EXPECT_ATOM(Store1, 3);
EXPECT_ATOM(Store2, 4);
EXPECT_ATOM(Store3, 4);
EXPECT_ATOM(Ret, 2);
VM.clear();
delete BB2;

// Cloning BB with MapAtoms=true should map the atom numbers.
BasicBlock *BB3 =
CloneBasicBlock(BB, VM, "", nullptr, nullptr, /*MapAtoms*/ true);
for (Instruction &I : *BB3)
RemapSourceAtom(&I, VM);
Store1 = &*BB3->begin();
Store2 = Store1->getNextNode();
Store3 = Store2->getNextNode();
Ret = Store3->getNextNode();
EXPECT_ATOM(Store1, 5);
EXPECT_ATOM(Store2, 6);
EXPECT_ATOM(Store3, 6);
EXPECT_ATOM(Ret, 7);
VM.clear();
delete BB3;
#undef EXPECT_ATOM
}

} // namespace