diff --git a/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp b/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp --- a/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp +++ b/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp @@ -48,6 +48,7 @@ #include "llvm/Analysis/MemoryLocation.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/TargetTransformInfo.h" +#include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/Argument.h" #include "llvm/IR/Attributes.h" #include "llvm/IR/BasicBlock.h" @@ -312,10 +313,34 @@ LoadInst *newLoad = IRB.CreateLoad(OrigLoad->getType(), V, V->getName() + ".val"); newLoad->setAlignment(OrigLoad->getAlign()); - // Transfer the AA info too. - AAMDNodes AAInfo; - OrigLoad->getAAMetadata(AAInfo); - newLoad->setAAMetadata(AAInfo); + newLoad->copyMetadata(*OrigLoad, {LLVMContext::MD_align}); + // The more aggressive assumptions made by the other metadatas may + // only hold when the control flow actually reaches the load instr; so + // they should only be copied if the load is guaranteed to execute. + BasicBlock *BB = OrigLoad->getParent(); + if (BB == &BB->getParent()->getEntryBlock()) { + bool IsOrigLoadGuaranteedToExecute = true; + for (const Instruction &II : *BB) { + if (!IsOrigLoadGuaranteedToExecute || &II == OrigLoad) { + break; + } + IsOrigLoadGuaranteedToExecute = + llvm::isGuaranteedToTransferExecutionToSuccessor(&II); + } + + if (IsOrigLoadGuaranteedToExecute) { + // Transfer the AA info too. + AAMDNodes AAInfo; + OrigLoad->getAAMetadata(AAInfo); + newLoad->setAAMetadata(AAInfo); + // And other metadata. + newLoad->copyMetadata(*OrigLoad, {LLVMContext::MD_nontemporal, + LLVMContext::MD_nonnull, + LLVMContext::MD_dereferenceable, + LLVMContext::MD_noundef, + LLVMContext::MD_range}); + } + } Args.push_back(newLoad); ArgAttrVec.push_back(AttributeSet()); diff --git a/llvm/test/Transforms/ArgumentPromotion/metadata.ll b/llvm/test/Transforms/ArgumentPromotion/metadata.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/ArgumentPromotion/metadata.ll @@ -0,0 +1,105 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes +; RUN: opt < %s -argpromotion -S | FileCheck %s + +define i32 @should_copy_range(i32* %x) { +; CHECK-LABEL: define {{[^@]+}}@should_copy_range +; CHECK-SAME: (i32* [[X:%.*]]) { +; CHECK-NEXT: [[X_VAL:%.*]] = load i32, i32* [[X]], align 4, !range !0 +; CHECK-NEXT: [[TMP1:%.*]] = call i32 @f_load_range(i32 [[X_VAL]]) +; CHECK-NEXT: ret i32 [[TMP1]] +; + %1 = call i32 @f_load_range(i32* %x) + ret i32 %1 +} + +define internal i32 @f_load_range(i32* %v) { +; CHECK-LABEL: define {{[^@]+}}@f_load_range +; CHECK-SAME: (i32 [[V_VAL:%.*]]) { +; CHECK-NEXT: ret i32 [[V_VAL]] +; + %1 = load i32, i32* %v, align 4, !range !0 + ret i32 %1 +} + +define i32* @should_copy_nonnull(i32** %x) { +; CHECK-LABEL: define {{[^@]+}}@should_copy_nonnull +; CHECK-SAME: (i32** [[X:%.*]]) { +; CHECK-NEXT: [[X_VAL:%.*]] = load i32*, i32** [[X]], align 4, !nonnull !1 +; CHECK-NEXT: [[TMP1:%.*]] = call i32* @f_load_nonnull(i32* [[X_VAL]]) +; CHECK-NEXT: ret i32* [[TMP1]] +; + %1 = call i32* @f_load_nonnull(i32** %x) + ret i32* %1 +} + +define internal i32* @f_load_nonnull(i32** %v) { +; CHECK-LABEL: define {{[^@]+}}@f_load_nonnull +; CHECK-SAME: (i32* [[V_VAL:%.*]]) { +; CHECK-NEXT: ret i32* [[V_VAL]] +; + %1 = load i32*, i32** %v, align 4, !nonnull !1 + ret i32* %1 +} + +define i32* @should_copy_dereferenceable(i32** %x) { +; CHECK-LABEL: define {{[^@]+}}@should_copy_dereferenceable +; CHECK-SAME: (i32** [[X:%.*]]) { +; CHECK-NEXT: [[X_VAL:%.*]] = load i32*, i32** [[X]], align 4, !dereferenceable !2 +; CHECK-NEXT: [[TMP1:%.*]] = call i32* @f_load_dereferenceable(i32* [[X_VAL]]) +; CHECK-NEXT: ret i32* [[TMP1]] +; + %1 = call i32* @f_load_dereferenceable(i32** %x) + ret i32* %1 +} + +define internal i32* @f_load_dereferenceable(i32** %v) { +; CHECK-LABEL: define {{[^@]+}}@f_load_dereferenceable +; CHECK-SAME: (i32* [[V_VAL:%.*]]) { +; CHECK-NEXT: ret i32* [[V_VAL]] +; + %1 = load i32*, i32** %v, align 4, !dereferenceable !2 + ret i32* %1 +} + +define i32* @no_copy_if_not_always_executed( +; CHECK-LABEL: define {{[^@]+}}@no_copy_if_not_always_executed +; CHECK-SAME: (i1 [[C:%.*]], i32** dereferenceable(8) [[V:%.*]]) { +; CHECK-NEXT: [[V_VAL:%.*]] = load i32*, i32** [[V]], align 1, !align !3 +; CHECK-NEXT: [[TMP1:%.*]] = call i32* @f_load_not_always_executed(i1 [[C]], i32* [[V_VAL]]) +; CHECK-NEXT: ret i32* [[TMP1]] +; + i1 %c, + i32** dereferenceable(8) %v) { + %1 = call i32* @f_load_not_always_executed(i1 %c, i32** dereferenceable(8) %v) + ret i32* %1 +} + +define internal i32* @f_load_not_always_executed( +; CHECK-LABEL: define {{[^@]+}}@f_load_not_always_executed +; CHECK-SAME: (i1 [[C:%.*]], i32* [[V_VAL:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[ELSE:%.*]] +; CHECK: if: +; CHECK-NEXT: ret i32* [[V_VAL]] +; CHECK: else: +; CHECK-NEXT: ret i32* null +; + i1 %c, + i32** dereferenceable(8) %v) { + br i1 %c, label %if, label %else + +if: + %1 = load i32*, i32** %v, align 1, !nonnull !1, !align !3 + ret i32* %1 + +else: + ret i32* null +} + +; CHECK: !0 = !{i32 0, i32 4} +; CHECK-NEXT: !1 = !{} +; CHECK-NEXT: !2 = !{i64 42} +; +!0 = !{i32 0, i32 4} +!1 = !{} +!2 = !{i64 42} +!3 = !{i64 1}