Skip to content

Commit d6f994a

Browse files
[InlineCost] Check for conflicting target attributes early
When we inline a callee into a caller, the compiler needs to make sure that the caller supports a superset of instruction sets that the callee is allowed to use. Normally, we check for the compatibility of target features via functionsHaveCompatibleAttributes, but that happens after we decide to honor call site attribute Attribute::AlwaysInline. If the caller contains a call marked with Attribute::AlwaysInline, which can happen with __attribute__((flatten)) placed on the caller, the caller could end up with code that cannot be lowered to assembly code. This patch fixes the problem by checking the target feature compatibility before we honor Attribute::AlwaysInline. Fixes #62664 Differential Revision: https://reviews.llvm.org/D150396
1 parent 14c44df commit d6f994a

File tree

2 files changed

+50
-6
lines changed

2 files changed

+50
-6
lines changed

llvm/lib/Analysis/InlineCost.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2801,16 +2801,14 @@ LLVM_DUMP_METHOD void InlineCostCallAnalyzer::dump() { print(dbgs()); }
28012801
/// Test that there are no attribute conflicts between Caller and Callee
28022802
/// that prevent inlining.
28032803
static bool functionsHaveCompatibleAttributes(
2804-
Function *Caller, Function *Callee, TargetTransformInfo &TTI,
2804+
Function *Caller, Function *Callee,
28052805
function_ref<const TargetLibraryInfo &(Function &)> &GetTLI) {
28062806
// Note that CalleeTLI must be a copy not a reference. The legacy pass manager
28072807
// caches the most recently created TLI in the TargetLibraryInfoWrapperPass
28082808
// object, and always returns the same object (which is overwritten on each
28092809
// GetTLI call). Therefore we copy the first result.
28102810
auto CalleeTLI = GetTLI(*Callee);
2811-
return (IgnoreTTIInlineCompatible ||
2812-
TTI.areInlineCompatible(Caller, Callee)) &&
2813-
GetTLI(*Caller).areInlineCompatible(CalleeTLI,
2811+
return GetTLI(*Caller).areInlineCompatible(CalleeTLI,
28142812
InlineCallerSupersetNoBuiltin) &&
28152813
AttributeFuncs::areInlineCompatible(*Caller, *Callee);
28162814
}
@@ -2926,6 +2924,12 @@ std::optional<InlineResult> llvm::getAttributeBasedInliningDecision(
29262924
" address space");
29272925
}
29282926

2927+
// Never inline functions with conflicting target attributes.
2928+
Function *Caller = Call.getCaller();
2929+
if (!IgnoreTTIInlineCompatible &&
2930+
!CalleeTTI.areInlineCompatible(Caller, Callee))
2931+
return InlineResult::failure("conflicting target attributes");
2932+
29292933
// Calls to functions with always-inline attributes should be inlined
29302934
// whenever possible.
29312935
if (Call.hasFnAttr(Attribute::AlwaysInline)) {
@@ -2940,8 +2944,12 @@ std::optional<InlineResult> llvm::getAttributeBasedInliningDecision(
29402944

29412945
// Never inline functions with conflicting attributes (unless callee has
29422946
// always-inline attribute).
2943-
Function *Caller = Call.getCaller();
2944-
if (!functionsHaveCompatibleAttributes(Caller, Callee, CalleeTTI, GetTLI))
2947+
// FIXME: functionsHaveCompatibleAttributes below checks for compatibilities
2948+
// of different kinds of function attributes -- sanitizer-related ones,
2949+
// checkDenormMode, no-builtin-memcpy, etc. It's unclear if we really want
2950+
// the always-inline attribute to take precedence over these different types
2951+
// of function attributes.
2952+
if (!functionsHaveCompatibleAttributes(Caller, Callee, GetTLI))
29452953
return InlineResult::failure("conflicting attributes");
29462954

29472955
// Don't inline this call if the caller has the optnone attribute.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
; RUN: opt < %s -passes=inline -pass-remarks-missed=inline -S 2>&1 | FileCheck %s
2+
3+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
4+
target triple = "x86_64-unknown-linux-gnu"
5+
6+
; Make sure that we do not inline callee into caller. If we inline
7+
; callee into caller, caller would end pu with AVX512 intrinsics even
8+
; though it is not allowed to use AVX512 instructions.
9+
; CHECK: remark: [[MSG:.*]] because it should never be inlined (cost=never): conflicting target attributes
10+
11+
define void @caller(ptr %0) {
12+
; CHECK-LABEL: define void @caller
13+
; CHECK-SAME: (ptr [[TMP0:%.*]]) {
14+
; CHECK-NEXT: call void @callee(ptr [[TMP0]], i64 0, i32 0) #[[ATTR2:[0-9]+]]
15+
; CHECK-NEXT: ret void
16+
;
17+
call void @callee(ptr %0, i64 0, i32 0) #1
18+
ret void
19+
}
20+
21+
define available_externally void @callee(ptr %0, i64 %1, i32 %2) #0 {
22+
; CHECK-LABEL: define available_externally void @callee
23+
; CHECK-SAME: (ptr [[TMP0:%.*]], i64 [[TMP1:%.*]], i32 [[TMP2:%.*]]) #[[ATTR0:[0-9]+]] {
24+
; CHECK-NEXT: [[TMP4:%.*]] = call <16 x float> @llvm.x86.avx512.min.ps.512(<16 x float> zeroinitializer, <16 x float> zeroinitializer, i32 0)
25+
; CHECK-NEXT: store <16 x float> [[TMP4]], ptr [[TMP0]], align 1
26+
; CHECK-NEXT: ret void
27+
;
28+
%4 = call <16 x float> @llvm.x86.avx512.min.ps.512(<16 x float> zeroinitializer, <16 x float> zeroinitializer, i32 0)
29+
store <16 x float> %4, ptr %0, align 1
30+
ret void
31+
}
32+
33+
declare <16 x float> @llvm.x86.avx512.min.ps.512(<16 x float>, <16 x float>, i32 immarg)
34+
35+
attributes #0 = { "target-features"="+aes,+avx,+avx2,+avx512bw,+avx512dq,+avx512f,+avx512vl,+bmi,+bmi2,+crc32,+cx16,+cx8,+f16c,+fma,+fsgsbase,+fxsr,+invpcid,+lzcnt,+mmx,+movbe,+pclmul,+popcnt,+rdrnd,+sahf,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87,+xsave,+xsaveopt" }
36+
attributes #1 = { alwaysinline }

0 commit comments

Comments
 (0)