[llvm] [StructuralHash] Global Variable (PR #118412)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Dec 2 17:50:29 PST 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-aarch64
Author: Kyungwoo Lee (kyulee-com)
<details>
<summary>Changes</summary>
This update enhances the implementation of structural hashing for global variables, using their initial contents. Private global variables or constants are often used for metadata, where their names are not unique. This can lead to the creation of different hash results although they could be merged by the linker as they are effectively identical.
- Refine the hashing of GlobalVariables for strings or certain Objective-C metadata cases that have section names. This can be further extended to other scenarios.
- Expose StructuralHash for GlobalVariable so that this API can be utilized by MachineStableHashing, which is also employed in the global function outliner.
This change significantly improves size reduction by an additional 1% on the LLD binary when the global function outliner and merger are enabled together. As discussed in the RFC, if we disable or relocate the LOH pass, the size impact could increase to 4%.
---
Full diff: https://github.com/llvm/llvm-project/pull/118412.diff
5 Files Affected:
- (modified) llvm/include/llvm/IR/StructuralHash.h (+3)
- (modified) llvm/lib/CodeGen/MachineStableHash.cpp (+10-3)
- (modified) llvm/lib/IR/StructuralHash.cpp (+44-7)
- (added) llvm/test/CodeGen/AArch64/cgdata-merge-gvar.ll (+91)
- (added) llvm/test/CodeGen/AArch64/cgdata-outline-gvar.ll (+52)
``````````diff
diff --git a/llvm/include/llvm/IR/StructuralHash.h b/llvm/include/llvm/IR/StructuralHash.h
index 071575137ff572..514dd6f174b903 100644
--- a/llvm/include/llvm/IR/StructuralHash.h
+++ b/llvm/include/llvm/IR/StructuralHash.h
@@ -31,6 +31,9 @@ class Module;
/// to true includes instruction and operand type information.
stable_hash StructuralHash(const Function &F, bool DetailedHash = false);
+/// Returns a hash of the global variable \p G.
+stable_hash StructuralHash(const GlobalVariable &G);
+
/// Returns a hash of the module \p M by hashing all functions and global
/// variables contained within. \param M The module to hash. \param DetailedHash
/// Whether or not to encode additional information in the function hashes that
diff --git a/llvm/lib/CodeGen/MachineStableHash.cpp b/llvm/lib/CodeGen/MachineStableHash.cpp
index facda7a59e2f86..09a81cb318ecb7 100644
--- a/llvm/lib/CodeGen/MachineStableHash.cpp
+++ b/llvm/lib/CodeGen/MachineStableHash.cpp
@@ -27,6 +27,8 @@
#include "llvm/CodeGen/Register.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/IR/Constants.h"
+#include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/StructuralHash.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/Support/Alignment.h"
#include "llvm/Support/ErrorHandling.h"
@@ -97,9 +99,14 @@ stable_hash llvm::stableHashValue(const MachineOperand &MO) {
++StableHashBailingGlobalAddress;
return 0;
}
- auto Name = GV->getName();
- return stable_hash_combine(MO.getType(), MO.getTargetFlags(),
- stable_hash_name(Name), MO.getOffset());
+ stable_hash GVHash = 0;
+ if (auto *GVar = dyn_cast<GlobalVariable>(GV))
+ GVHash = StructuralHash(*GVar);
+ if (!GVHash)
+ GVHash = stable_hash_name(GV->getName());
+
+ return stable_hash_combine(MO.getType(), MO.getTargetFlags(), GVHash,
+ MO.getOffset());
}
case MachineOperand::MO_TargetIndex: {
diff --git a/llvm/lib/IR/StructuralHash.cpp b/llvm/lib/IR/StructuralHash.cpp
index ccc534a8904191..de883f81a8e4a5 100644
--- a/llvm/lib/IR/StructuralHash.cpp
+++ b/llvm/lib/IR/StructuralHash.cpp
@@ -46,7 +46,7 @@ class StructuralHashImpl {
/// Assign a unique ID to each Value in the order they are first seen.
DenseMap<const Value *, int> ValueToId;
- stable_hash hashType(Type *ValueType) {
+ static stable_hash hashType(Type *ValueType) {
SmallVector<stable_hash> Hashes;
Hashes.emplace_back(ValueType->getTypeID());
if (ValueType->isIntegerTy())
@@ -65,7 +65,7 @@ class StructuralHashImpl {
}
}
- stable_hash hashAPInt(const APInt &I) {
+ static stable_hash hashAPInt(const APInt &I) {
SmallVector<stable_hash> Hashes;
Hashes.emplace_back(I.getBitWidth());
auto RawVals = ArrayRef<uint64_t>(I.getRawData(), I.getNumWords());
@@ -73,11 +73,36 @@ class StructuralHashImpl {
return stable_hash_combine(Hashes);
}
- stable_hash hashAPFloat(const APFloat &F) {
+ static stable_hash hashAPFloat(const APFloat &F) {
return hashAPInt(F.bitcastToAPInt());
}
- stable_hash hashGlobalValue(const GlobalValue *GV) {
+ static stable_hash hashGlobalVariable(const GlobalVariable &GVar) {
+ if (!GVar.hasInitializer())
+ return hashGlobalValue(&GVar);
+
+ // Hash the contents of a string.
+ if (GVar.getName().starts_with(".str"))
+ return hashConstant(GVar.getInitializer());
+
+ // Hash structural contents of Objective-C metadata in specific sections.
+ // This can be extended to other metadata if needed.
+ static constexpr const char *SectionNames[] = {
+ "__cfstring", "__cstring", "__objc_classrefs",
+ "__objc_methname", "__objc_selrefs",
+ };
+ if (GVar.hasSection()) {
+ StringRef SectionName = GVar.getSection();
+ for (const char *Name : SectionNames) {
+ if (SectionName.contains(Name))
+ return hashConstant(GVar.getInitializer());
+ }
+ }
+
+ return hashGlobalValue(&GVar);
+ }
+
+ static stable_hash hashGlobalValue(const GlobalValue *GV) {
if (!GV->hasName())
return 0;
return stable_hash_name(GV->getName());
@@ -87,7 +112,7 @@ class StructuralHashImpl {
// FunctionComparator::cmpConstants() in FunctionComparator.cpp, but here
// we're interested in computing a hash rather than comparing two Constants.
// Some of the logic is simplified, e.g, we don't expand GEPOperator.
- stable_hash hashConstant(Constant *C) {
+ static stable_hash hashConstant(const Constant *C) {
SmallVector<stable_hash> Hashes;
Type *Ty = C->getType();
@@ -98,14 +123,21 @@ class StructuralHashImpl {
return stable_hash_combine(Hashes);
}
+ if (auto *GVar = dyn_cast<GlobalVariable>(C)) {
+ Hashes.emplace_back(hashGlobalVariable(*GVar));
+ return stable_hash_combine(Hashes);
+ }
+
if (auto *G = dyn_cast<GlobalValue>(C)) {
Hashes.emplace_back(hashGlobalValue(G));
return stable_hash_combine(Hashes);
}
if (const auto *Seq = dyn_cast<ConstantDataSequential>(C)) {
- Hashes.emplace_back(xxh3_64bits(Seq->getRawDataValues()));
- return stable_hash_combine(Hashes);
+ if (Seq->isString()) {
+ Hashes.emplace_back(stable_hash_name(Seq->getAsString()));
+ return stable_hash_combine(Hashes);
+ }
}
switch (C->getValueID()) {
@@ -266,6 +298,7 @@ class StructuralHashImpl {
Hashes.emplace_back(Hash);
Hashes.emplace_back(GlobalHeaderHash);
Hashes.emplace_back(GV.getValueType()->getTypeID());
+ Hashes.emplace_back(hashGlobalVariable(GV));
// Update the combined hash in place.
Hash = stable_hash_combine(Hashes);
@@ -297,6 +330,10 @@ stable_hash llvm::StructuralHash(const Function &F, bool DetailedHash) {
return H.getHash();
}
+stable_hash llvm::StructuralHash(const GlobalVariable &GVar) {
+ return StructuralHashImpl::hashGlobalVariable(GVar);
+}
+
stable_hash llvm::StructuralHash(const Module &M, bool DetailedHash) {
StructuralHashImpl H(DetailedHash);
H.update(M);
diff --git a/llvm/test/CodeGen/AArch64/cgdata-merge-gvar.ll b/llvm/test/CodeGen/AArch64/cgdata-merge-gvar.ll
new file mode 100644
index 00000000000000..f1f5209abe3507
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/cgdata-merge-gvar.ll
@@ -0,0 +1,91 @@
+; This test verifies that global variables are hashed based on their initial contents,
+; allowing them to be merged even if they appear different due to their names.
+; Now they become identical functions that can be merged without creating a paramter.
+
+; RUN: rm -rf %t && split-file %s %t
+
+; RUN: llc -mtriple=arm64-apple-darwin -enable-global-merge-func=true -global-merging-skip-no-params=false < %t/string.ll | FileCheck %s
+; RUN: llc -mtriple=arm64-apple-darwin -enable-global-merge-func=true -global-merging-skip-no-params=false < %t/ns-const.ll | FileCheck %s
+; RUN: llc -mtriple=arm64-apple-darwin -enable-global-merge-func=true -global-merging-skip-no-params=false < %t/objc-ref.ll | FileCheck %s
+
+; CHECK: _f1.Tgm
+; CHECK: _f2.Tgm
+
+;--- string.ll
+
+ at .str = private unnamed_addr constant [6 x i8] c"hello\00", align 1
+ at .str.1 = private unnamed_addr constant [6 x i8] c"hello\00", align 1
+
+declare noundef i32 @goo(ptr noundef)
+
+define i32 @f1() {
+entry:
+ %call = tail call noundef i32 @goo(ptr noundef nonnull @.str)
+ %add = add nsw i32 %call, 1
+ ret i32 %add
+}
+
+define i32 @f2() {
+entry:
+ %call = tail call noundef i32 @goo(ptr noundef nonnull @.str.1)
+ %add = add nsw i32 %call, 1
+ ret i32 %add
+}
+
+;--- ns-const.ll
+
+%struct.__NSConstantString_tag = type { ptr, i32, ptr, i64 }
+ at __CFConstantStringClassReference = external global [0 x i32]
+ at .str.2 = private unnamed_addr constant [9 x i8] c"cfstring\00", section "__TEXT,__cstring,cstring_literals", align 1
+ at _unnamed_cfstring_ = private global %struct.__NSConstantString_tag { ptr @__CFConstantStringClassReference, i32 1992, ptr @.str.2, i64 8 }, section "__DATA,__cfstring", align 8
+
+ at .str.3 = private unnamed_addr constant [9 x i8] c"cfstring\00", section "__TEXT,__cstring,cstring_literals", align 1
+ at _unnamed_cfstring_.2 = private global %struct.__NSConstantString_tag { ptr @__CFConstantStringClassReference, i32 1992, ptr @.str.3, i64 8 }, section "__DATA,__cfstring", align 8
+
+declare noundef i32 @hoo(ptr noundef)
+
+define i32 @f1() {
+entry:
+ %call = tail call i32 @hoo(ptr noundef nonnull @_unnamed_cfstring_)
+ %add = sub nsw i32 %call, 1
+ ret i32 %add
+}
+
+define i32 @f2() {
+entry:
+ %call = tail call i32 @hoo(ptr noundef nonnull @_unnamed_cfstring_.2)
+ %add = sub nsw i32 %call, 1
+ ret i32 %add
+}
+
+;--- objc-ref.ll
+
+%struct._class_t = type { ptr, ptr, ptr, ptr, ptr }
+
+@"OBJC_CLASS_$_MyClass" = external global %struct._class_t
+@"OBJC_CLASSLIST_REFERENCES_$_" = internal global ptr @"OBJC_CLASS_$_MyClass", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8
+@"OBJC_CLASSLIST_REFERENCES_$_.1" = internal global ptr @"OBJC_CLASS_$_MyClass", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8
+
+ at OBJC_METH_VAR_NAME_ = private unnamed_addr constant [6 x i8] c"hello\00", section "__TEXT,__objc_methname,cstring_literals", align 1
+ at OBJC_METH_VAR_NAME_.1 = private unnamed_addr constant [6 x i8] c"hello\00", section "__TEXT,__objc_methname,cstring_literals", align 1
+
+ at OBJC_SELECTOR_REFERENCES_ = internal externally_initialized global ptr @OBJC_METH_VAR_NAME_, section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
+ at OBJC_SELECTOR_REFERENCES_.1 = internal externally_initialized global ptr @OBJC_METH_VAR_NAME_.1, section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
+
+define i32 @f1() {
+entry:
+ %0 = load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$_", align 8
+ %1 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_, align 8
+ %call = tail call noundef i32 @objc_msgSend(ptr noundef %0, ptr noundef %1)
+ ret i32 %call
+}
+
+declare ptr @objc_msgSend(ptr, ptr, ...)
+
+define i32 @f2() {
+entry:
+ %0 = load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$_.1", align 8
+ %1 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_.1, align 8
+ %call = tail call noundef i32 @objc_msgSend(ptr noundef %0, ptr noundef %1)
+ ret i32 %call
+}
diff --git a/llvm/test/CodeGen/AArch64/cgdata-outline-gvar.ll b/llvm/test/CodeGen/AArch64/cgdata-outline-gvar.ll
new file mode 100644
index 00000000000000..447928dfa07245
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/cgdata-outline-gvar.ll
@@ -0,0 +1,52 @@
+; This test verifies that global variables are hashed based on their initial contents,
+; allowing them to be outlined even if they appear different due to their names.
+
+; RUN: split-file %s %t
+
+; Check if the outlined function is created locally.
+; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -aarch64-enable-collect-loh=false -filetype=obj %t/local-two.ll -o %t_write_base
+; RUN: llvm-objdump -d %t_write_base | FileCheck %s
+
+; RUN: llvm-cgdata --merge %t_write_base -o %t_cgdata_base
+
+; Read the cgdata in the machine outliner for optimistically outlining in local-one.ll.
+; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata_base -aarch64-enable-collect-loh=false -append-content-hash-outlined-name=false -filetype=obj %t/local-one.ll -o %t_read_base
+; RUN: llvm-objdump -d %t_read_base | FileCheck %s
+
+; The names of globals `.str` and `.str.4` are different, but their initial contents are identical.
+; The outlined function now starts with a reference to that global ("hello\00").
+; CHECK: _OUTLINED_FUNCTION_{{.*}}:
+; CHECK-NEXT: adrp x1
+; CHECK-NEXT: add x1, x1
+; CHECK-NEXT: mov w2
+; CHECK-NEXT: mov w3
+; CHECK-NEXT: mov w4
+; CHECK-NEXT: b
+
+;--- local-two.ll
+ at .str = private unnamed_addr constant [6 x i8] c"hello\00", align 1
+ at .str.1 = private unnamed_addr constant [3 x i8] c"f1\00", align 1
+ at .str.2 = private unnamed_addr constant [3 x i8] c"f2\00", align 1
+
+declare noundef i32 @goo(ptr noundef, ptr noundef, i32, i32, i32)
+define i32 @f1() minsize {
+entry:
+ %call = tail call noundef i32 @goo(ptr noundef nonnull @.str.1, ptr noundef nonnull @.str, i32 1, i32 2, i32 3)
+ ret i32 %call
+}
+define i32 @f2() minsize {
+entry:
+ %call = tail call noundef i32 @goo(ptr noundef nonnull @.str.2, ptr noundef nonnull @.str, i32 1, i32 2, i32 3)
+ ret i32 %call
+}
+
+;--- local-one.ll
+ at .str.3 = private unnamed_addr constant [3 x i8] c"f3\00", align 1
+ at .str.4 = private unnamed_addr constant [6 x i8] c"hello\00", align 1
+
+declare noundef i32 @goo(ptr noundef, ptr noundef, i32, i32, i32)
+define i32 @f1() minsize {
+entry:
+ %call = tail call noundef i32 @goo(ptr noundef nonnull @.str.3, ptr noundef nonnull @.str.4, i32 1, i32 2, i32 3)
+ ret i32 %call
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/118412
More information about the llvm-commits
mailing list