[llvm] 1afb81d - [StructuralHash] Global Variable (#118412)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Dec 3 16:01:54 PST 2024
Author: Kyungwoo Lee
Date: 2024-12-03T16:01:50-08:00
New Revision: 1afb81dfaf902c1c42bd91fec1a7385e6e1529d3
URL: https://github.com/llvm/llvm-project/commit/1afb81dfaf902c1c42bd91fec1a7385e6e1529d3
DIFF: https://github.com/llvm/llvm-project/commit/1afb81dfaf902c1c42bd91fec1a7385e6e1529d3.diff
LOG: [StructuralHash] Global Variable (#118412)
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
https://discourse.llvm.org/t/loh-conflicting-with-machineoutliner/83279/8?u=kyulee-com,
if we disable or relocate the LOH pass, the size impact could increase
to 4%.
Added:
llvm/test/CodeGen/AArch64/cgdata-merge-gvar-nsconst.ll
llvm/test/CodeGen/AArch64/cgdata-merge-gvar-objc.ll
llvm/test/CodeGen/AArch64/cgdata-merge-gvar-string.ll
llvm/test/CodeGen/AArch64/cgdata-outline-gvar.ll
Modified:
llvm/include/llvm/IR/StructuralHash.h
llvm/lib/CodeGen/MachineStableHash.cpp
llvm/lib/IR/StructuralHash.cpp
Removed:
################################################################################
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..5ab589acee4135 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"
@@ -93,13 +95,19 @@ stable_hash llvm::stableHashValue(const MachineOperand &MO) {
return 0;
case MachineOperand::MO_GlobalAddress: {
const GlobalValue *GV = MO.getGlobal();
- if (!GV->hasName()) {
- ++StableHashBailingGlobalAddress;
- return 0;
+ stable_hash GVHash = 0;
+ if (auto *GVar = dyn_cast<GlobalVariable>(GV))
+ GVHash = StructuralHash(*GVar);
+ if (!GVHash) {
+ if (!GV->hasName()) {
+ ++StableHashBailingGlobalAddress;
+ return 0;
+ }
+ GVHash = stable_hash_name(GV->getName());
}
- auto Name = GV->getName();
- return stable_hash_combine(MO.getType(), MO.getTargetFlags(),
- stable_hash_name(Name), MO.getOffset());
+
+ 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..56b925626d8450 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,39 @@ 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")) {
+ auto *C = GVar.getInitializer();
+ if (const auto *Seq = dyn_cast<ConstantDataSequential>(C))
+ if (Seq->isString())
+ return stable_hash_name(Seq->getAsString());
+ }
+
+ // 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 +115,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 +126,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 +301,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 +333,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-nsconst.ll b/llvm/test/CodeGen/AArch64/cgdata-merge-gvar-nsconst.ll
new file mode 100644
index 00000000000000..490a778f69e263
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/cgdata-merge-gvar-nsconst.ll
@@ -0,0 +1,32 @@
+; This test verifies that global variables (ns constant) are hashed based on their initial contents,
+; allowing them to be merged even if they appear
diff erent due to their names.
+; Now they become identical functions that can be merged without creating a parameter
+
+; RUN: llc -mtriple=arm64-apple-darwin -enable-global-merge-func=true -global-merging-skip-no-params=false < %s | FileCheck %s
+
+; CHECK: _f1.Tgm
+; CHECK: _f2.Tgm
+
+%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 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
+}
diff --git a/llvm/test/CodeGen/AArch64/cgdata-merge-gvar-objc.ll b/llvm/test/CodeGen/AArch64/cgdata-merge-gvar-objc.ll
new file mode 100644
index 00000000000000..0073114941501a
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/cgdata-merge-gvar-objc.ll
@@ -0,0 +1,38 @@
+; This test verifies that global variables (objc metadata) are hashed based on their initial contents,
+; allowing them to be merged even if they appear
diff erent due to their names.
+; Now they become identical functions that can be merged without creating a parameter
+
+; RUN: llc -mtriple=arm64-apple-darwin -enable-global-merge-func=true -global-merging-skip-no-params=false < %s | FileCheck %s
+
+; CHECK: _f1.Tgm
+; CHECK: _f2.Tgm
+
+%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
+
+declare ptr @objc_msgSend(ptr, ptr, ...)
+
+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 i32 @objc_msgSend(ptr noundef %0, ptr noundef %1)
+ ret i32 %call
+}
+
+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 i32 @objc_msgSend(ptr noundef %0, ptr noundef %1)
+ ret i32 %call
+}
diff --git a/llvm/test/CodeGen/AArch64/cgdata-merge-gvar-string.ll b/llvm/test/CodeGen/AArch64/cgdata-merge-gvar-string.ll
new file mode 100644
index 00000000000000..1e67425f0b8475
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/cgdata-merge-gvar-string.ll
@@ -0,0 +1,46 @@
+; This test verifies that global variables (string) are hashed based on their initial contents,
+; allowing them to be merged even if they appear
diff erent due to their names.
+; Now they become identical functions that can be merged without creating a parameter.
+
+; RUN: llc -mtriple=arm64-apple-darwin -enable-global-merge-func=true -global-merging-skip-no-params=false < %s | FileCheck %s
+
+; CHECK: _f1.Tgm
+; CHECK: _f2.Tgm
+; CHECK-NOT: _f3.Tgm
+; CHECK-NOT: _f4.Tgm
+
+; The initial contents of `.str` and `.str.1` are identical, but not with those of `.str.2` and `.str.3`.
+ 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
+ at .str.2 = private unnamed_addr constant [6 x i8] c"
diff 2\00", align 1
+ at .str.3 = private unnamed_addr constant [6 x i8] c"
diff 3\00", align 1
+
+declare i32 @goo(ptr noundef)
+
+define i32 @f1() {
+entry:
+ %call = tail call i32 @goo(ptr noundef nonnull @.str)
+ %add = add nsw i32 %call, 1
+ ret i32 %add
+}
+
+define i32 @f2() {
+entry:
+ %call = tail call i32 @goo(ptr noundef nonnull @.str.1)
+ %add = add nsw i32 %call, 1
+ ret i32 %add
+}
+
+define i32 @f3() {
+entry:
+ %call = tail call noundef i32 @goo(ptr noundef nonnull @.str.2)
+ %add = sub nsw i32 %call, 1
+ ret i32 %add
+}
+
+define i32 @f4() {
+entry:
+ %call = tail call noundef i32 @goo(ptr noundef nonnull @.str.3)
+ %add = sub nsw i32 %call, 1
+ ret i32 %add
+}
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..63ba1d491f9c73
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/cgdata-outline-gvar.ll
@@ -0,0 +1,64 @@
+; This test verifies that global variables are hashed based on their initial contents,
+; allowing them to be outlined even if they appear
diff erent due to their names.
+
+; RUN: split-file %s %t
+
+; The outlined function is created locally.
+; Note that `.str.3` is commonly used in both `f1()` and `f2()`.
+; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate -aarch64-enable-collect-loh=false \
+; RUN: %t/local-two.ll -o - | FileCheck %s --check-prefix=WRITE
+
+; WRITE-LABEL: _OUTLINED_FUNCTION_{{.*}}:
+; WRITE: adrp x1, l_.str.3
+; WRITE-NEXT: add x1, x1, l_.str.3
+; WRITE-NEXT: mov w2
+; WRITE-NEXT: mov w3
+; WRITE-NEXT: mov w4
+; WRITE-NEXT: b
+
+; Create an object file and merge it into the cgdata.
+; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate -aarch64-enable-collect-loh=false \
+; RUN: -filetype=obj %t/local-two.ll -o %t_write_base
+; 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.
+; Note that the hash of `.str.5` in local-one.ll matches that of `.str.3` in an outlined tree in the cgdata.
+
+; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata_base -aarch64-enable-collect-loh=false \
+; RUN: %t/local-one.ll -o - | FileCheck %s --check-prefix=READ
+
+; READ-LABEL: _OUTLINED_FUNCTION_{{.*}}:
+; READ: adrp x1, l_.str.5
+; READ-NEXT: add x1, x1, l_.str.5
+; READ-NEXT: mov w2
+; READ-NEXT: mov w3
+; READ-NEXT: mov w4
+; READ-NEXT: b
+
+;--- local-two.ll
+ 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
+ at .str.3 = 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.1, ptr noundef nonnull @.str.3, 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.3, i32 1, i32 2, i32 3)
+ ret i32 %call
+}
+
+;--- local-one.ll
+ at .str.4 = private unnamed_addr constant [3 x i8] c"f3\00", align 1
+ at .str.5 = 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.4, ptr noundef nonnull @.str.5, i32 1, i32 2, i32 3)
+ ret i32 %call
+}
More information about the llvm-commits
mailing list