我不确定您的说法:
无论是否提及内联,优化器都会自动“内联”较小的函数......很明显,用户无法通过使用关键字来控制“内联”函数inline
。
我听说编译器可以随意忽略您的inline
请求,但我认为他们并没有完全无视它。
我查看了 Clang 和 LLVM 的 Github 存储库以找出答案。(谢谢,开源软件!)我发现关键字确实使Clang/LLVM 更有可能内联函数。inline
搜索
inline
在Clang 存储库中搜索单词会导致令牌说明符kw_inline
。看起来 Clang 使用了一个聪明的基于宏的系统来构建词法分析器和其他与关键字相关的功能,因此可以直接if (tokenString == "inline") return kw_inline
找到喜欢的内容。但是在 ParseDecl.cpp 中,我们看到kw_inline
导致调用DeclSpec::setFunctionSpecInline()
.
case tok::kw_inline:
isInvalid = DS.setFunctionSpecInline(Loc, PrevSpec, DiagID);
break;
在该函数中,我们设置了一点,如果它是重复的,则发出警告inline
:
if (FS_inline_specified) {
DiagID = diag::warn_duplicate_declspec;
PrevSpec = "inline";
return true;
}
FS_inline_specified = true;
FS_inlineLoc = Loc;
return false;
在其他地方搜索FS_inline_specified
,我们看到它是位域中的一个位,它用于 getter 函数,isInlineSpecified()
:
bool isInlineSpecified() const {
return FS_inline_specified | FS_forceinline_specified;
}
搜索 的调用站点isInlineSpecified()
,我们找到了 codegen,在这里我们将 C++ 解析树转换为 LLVM 中间表示:
if (!CGM.getCodeGenOpts().NoInline) {
for (auto RI : FD->redecls())
if (RI->isInlineSpecified()) {
Fn->addFnAttr(llvm::Attribute::InlineHint);
break;
}
} else if (!FD->hasAttr<AlwaysInlineAttr>())
Fn->addFnAttr(llvm::Attribute::NoInline);
铿锵到 LLVM
我们完成了 C++ 解析阶段。现在我们的inline
说明符被转换为与语言无关的 LLVMFunction
对象的属性。我们从 Clang 切换到LLVM 存储库。
搜索llvm::Attribute::InlineHint
产生方法 Inliner::getInlineThreshold(CallSite CS)
(带有看起来很吓人的无括号if
块):
// Listen to the inlinehint attribute when it would increase the threshold
// and the caller does not need to minimize its size.
Function *Callee = CS.getCalledFunction();
bool InlineHint = Callee && !Callee->isDeclaration() &&
Callee->getAttributes().hasAttribute(AttributeSet::FunctionIndex,
Attribute::InlineHint);
if (InlineHint && HintThreshold > thres
&& !Caller->getAttributes().hasAttribute(AttributeSet::FunctionIndex,
Attribute::MinSize))
thres = HintThreshold;
因此,我们已经从优化级别和其他因素中获得了基线内联阈值,但如果它低于 global HintThreshold
,我们将其提高。 (HintThreshold 可从命令行设置。)
getInlineThreshold()
似乎只有一个呼叫站点,是以下成员SimpleInliner
:
InlineCost getInlineCost(CallSite CS) override {
return ICA->getInlineCost(CS, getInlineThreshold(CS));
}
getInlineCost
它在其指向 的实例的成员指针上调用一个虚拟方法,也称为InlineCostAnalysis
。
搜索::getInlineCost()
以查找属于类成员的版本,我们找到一个属于 的成员AlwaysInline
- 这是一个非标准但广泛支持的编译器功能 - 另一个属于InlineCostAnalysis
. 它在这里使用它的Threshold
参数:
CallAnalyzer CA(Callee->getDataLayout(), *TTI, AT, *Callee, Threshold);
bool ShouldInline = CA.analyzeCall(CS);
CallAnalyzer::analyzeCall()
超过 200 行,并且做决定函数是否可内联的真正细节工作。它权衡了许多因素,但当我们阅读该方法时,我们看到它的所有计算要么操纵Threshold
要么Cost
。最后:
return Cost < Threshold;
但是命名的返回值ShouldInline
确实是用词不当。其实主要目的analyzeCall()
是在对象上设置Cost
和Threshold
成员变量CallAnalyzer
。返回值仅表示某些其他因素覆盖了成本与阈值分析的情况,如下所示:
// Check if there was a reason to force inlining or no inlining.
if (!ShouldInline && CA.getCost() < CA.getThreshold())
return InlineCost::getNever();
if (ShouldInline && CA.getCost() >= CA.getThreshold())
return InlineCost::getAlways();
否则,我们返回一个存储Cost
and的对象Threshold
。
return llvm::InlineCost::get(CA.getCost(), CA.getThreshold());
因此,在大多数情况下,我们不会返回是或否的决定。搜索继续!这个使用的返回值在哪里getInlineCost()
?
真正的决定
它位于 bool Inliner::shouldInline(CallSite CS)
. 另一个大功能。它在一开始就调用getInlineCost()
。
事实证明,它getInlineCost
分析了内联函数的内在成本——它的参数签名、代码长度、递归、分支、链接等——以及关于函数使用的每个地方的一些聚合信息。另一方面,shouldInline()
将此信息与有关使用该功能的特定位置的更多数据相结合。
在整个方法中,都会调用InlineCost::costDelta()
- 将使用s计算的InlineCost
s值。最后,我们返回一个. 做出决定。在:Threshold
analyzeCall()
bool
Inliner::runOnSCC()
if (!shouldInline(CS)) {
emitOptimizationRemarkMissed(CallerCtx, DEBUG_TYPE, *Caller, DLoc,
Twine(Callee->getName() +
" will not be inlined into " +
Caller->getName()));
continue;
}
// Attempt to inline the function.
if (!InlineCallIfPossible(CS, InlineInfo, InlinedArrayAllocas,
InlineHistoryID, InsertLifetime, DL)) {
emitOptimizationRemarkMissed(CallerCtx, DEBUG_TYPE, *Caller, DLoc,
Twine(Callee->getName() +
" will not be inlined into " +
Caller->getName()));
continue;
}
++NumInlined;
InlineCallIfPossible()
根据shouldInline()
的决定进行内联。
所以Threshold
受inline
关键字影响,最后用来决定是否内联。
因此,您的 Perception B 部分是错误的,因为至少有一个主要编译器会根据inline
关键字更改其优化行为。
但是,我们也可以看出这inline
只是一个提示,其他因素可能会超过它。