我不确定您的声明:
无论是否提及内联,优化器都会自动“内联”较小的函数...
很明显,用户无法使用关键字inline 对函数“内联”进行任何控制。
我听说编译器可以随意忽略您的 inline 请求,但我认为他们并没有完全无视它。
我查看了 Clang 和 LLVM 的 Github 存储库以找出答案。 (感谢开源软件!)我发现inline 关键字确实使 Clang/LLVM 更有可能内联函数。
搜索
在the Clang repository 中搜索单词inline 会导致令牌说明符kw_inline。看起来 Clang 使用了一个聪明的基于宏的系统来构建词法分析器和其他与关键字相关的函数,因此可以找到像 if (tokenString == "inline") return kw_inline 这样的直接注释。但是Here in ParseDecl.cpp,我们看到kw_inline 导致调用DeclSpec::setFunctionSpecInline()。
case tok::kw_inline:
isInvalid = DS.setFunctionSpecInline(Loc, PrevSpec, DiagID);
break;
Inside that function,如果是重复的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,我们看到它是位域中的一个位,以及it's used in a getter function、isInlineSpecified():
bool isInlineSpecified() const {
return FS_inline_specified | FS_forceinline_specified;
}
搜索isInlineSpecified()的调用点,我们找到the 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);
Clang 到 LLVM
我们已经完成了 C++ 解析阶段。现在,我们的 inline 说明符被转换为与语言无关的 LLVM Function 对象的属性。我们从 Clang 切换到 the LLVM repository。
正在搜索llvm::Attribute::InlineHint yields the method 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;
所以我们已经从优化级别和其他因素中获得了一个基线内联阈值,但如果它低于全局HintThreshold,我们会提高它。 (HintThreshold 可从命令行设置。)
getInlineThreshold() 似乎只有one call site,SimpleInliner 的成员:
InlineCost getInlineCost(CallSite CS) override {
return ICA->getInlineCost(CS, getInlineThreshold(CS));
}
它在其指向InlineCostAnalysis 实例的成员指针上调用一个虚拟方法,也称为getInlineCost。
搜索::getInlineCost() 以查找属于类成员的版本,我们找到一个属于AlwaysInline 的版本 - 这是一种非标准但广泛支持的编译器功能 - 另一个属于InlineCostAnalysis 的成员。它使用它的Threshold 参数here:
CallAnalyzer CA(Callee->getDataLayout(), *TTI, AT, *Callee, Threshold);
bool ShouldInline = CA.analyzeCall(CS);
CallAnalyzer::analyzeCall() 超过 200 行,does the real nitty gritty work of deciding if the function is inlineable。它权衡了许多因素,但是当我们阅读该方法时,我们看到它的所有计算都在操纵Threshold 或Cost。最后:
return Cost < Threshold;
但是名为ShouldInline 的返回值确实是用词不当。其实analyzeCall()的主要目的是在CallAnalyzer对象上设置Cost和Threshold成员变量。返回值仅表示某些其他因素已覆盖成本与阈值分析的情况,as we see here:
// 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 和Threshold 的对象。
return llvm::InlineCost::get(CA.getCost(), CA.getThreshold());
因此,在大多数情况下,我们不会返回是或否的决定。搜索继续!这个getInlineCost()的返回值在哪里使用?
真正的决定
It's found inbool Inliner::shouldInline(CallSite CS)。另一个大功能。它一开始就调用getInlineCost()。
事实证明,getInlineCost 分析了内联函数的内在成本 - 它的参数签名、代码长度、递归、分支、链接等 - 以及一些关于 的汇总信息每个 使用该功能的地方。另一方面,shouldInline() 将此信息与更多关于特定使用该功能的地方的数据结合起来。
在整个方法中都会调用InlineCost::costDelta() - 这将使用analyzeCall() 计算的InlineCosts Threshold 值。最后,我们返回一个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只是一个提示,其他因素可能会超过它。