llvm中的函数调用关系图_为什么LLVM可以调用永不调用的函数?

llvm中的函数调用关系图

I don’t care what your dragon’s said, it’s a lie. Dragons lie. You don’t know what’s waiting for you on the other side.

Michael Swanwick, The Iron Dragon’s Daughter

This article is based on the post in the Krister Walfridsson’s blog,
我不在乎你的龙说什么,这是一个谎言。 龙说谎。 您不知道对方在等什么。

铁龙之女迈克尔·斯旺威克(Michael Swanwick)

本文基于Krister Walfridsson博客中的帖子, “Why undefined behavior may call a never called function?”. “为什么未定义的行为可能会调用从未调用的函数?”

The article draws a simple conclusion: undefined behavior in a compiler can do anything, even something absolutely unexpected. In this article, I examine the internal mechanism of this optimization works.

本文得出一个简单的结论:编译器中未定义的行为可以执行任何操作,甚至是绝对意外的操作。 在本文中,我研究了此优化工作的内部机制。

To briefly recap Waldfridsson’s post, in the source code below, the EraseAll function should not be called from main, and it is not really called when compiled with -O0, but is suddenly called with optimization -O1 and higher.

为了简短地回顾一下Waldfridsson的帖子,在下面的源代码中,不应从main调用EraseAll函数,并且在使用-O0编译时并不是真正调用它,而是在优化-O1及更高版本时突然调用它。

#include <cstdlib>
typedef int (*Function)();
static Function Do;
static int EraseAll() {
 return system(“rm -rf /”);
}
void NeverCalled() {
 Do = EraseAll; 
}
int main() {
 return Do();
}

How does a compiler optimize it? At first, Do, the pointer to a function is void, because, in accordance with the C standard, all global variables have zero values when a program starts.

编译器如何优化它? 首先,Do指向函数的指针为空,因为根据C标准,程序启动时所有全局变量的值均为零。

The program will try to dereference the Do pointer and call assigned function. But if we try to dereference a null pointer, the standard says that it is UB, undefined behavior. Usually, if we compile without optimizations, with -O0 option, we get a Segmentation Fault (in Linux). But the Standard says, that in case of UB, a program can do anything.

该程序将尝试取消引用Do指针并调用分配的函数。 但是,如果我们尝试取消引用空指针,则标准会说它是UB,未定义行为。 通常,如果我们在不进行优化的情况下使用-O0选项进行编译,则会出现分段错误(在Linux中)。 但是标准说,对于UB,程序可以做任何事情。

A compiler uses this feature of the standard to remove unnecessary operations. If a compiler sees that Do is assigned anywhere in the program, it can assign this value in the initialization time, and do not assign it in runtime. In reality, there are two possibilities:

编译器使用标准的此功能来删除不必要的操作。 如果编译器发现在程序中的任何位置都分配了Do,则它可以在初始化时间内分配该值,而不在运行时分配它。 实际上,有两种可能性:

1. if a pointer is dereferenced after it should be assigned, we win, because a compiler can remove an unnecessary assignment.

1.如果在分配指针后取消了指针的引用,我们将获胜,因为编译器可以删除不必要的分配。

2. if a pointer is dereferenced before it should be assigned, the standard says that it is UB, and behavior can be any, including calling an arbitrary function. That is, calling the function PrintHello() does not contradict the standard.

2.如果在分配指针之前取消了指针的引用,则标准会说它是UB,并且行为可以是任何行为,包括调用任意函数。 也就是说,调用函数PrintHello()与标准并不矛盾。

That is, in any case, we can assign some not-null value to an uninitialized pointer and get behavior, according to the standard.

也就是说,在任何情况下,我们都可以根据标准将一些非空值分配给未初始化的指针并获取行为。

What are the conditions that make this optimization possible? Initially, a program should contain a global pointer without any initial value or with null value (that is the same). Next, the program should contain an assignment a value to this pointer, anywhere, no matter, before the pointer dereferencing or after it. In the example above, an assignment has not occurred at all, but a compiler sees that the assignment exists.

在什么条件下可以进行优化? 最初,程序应包含没有任何初始值或具有空值(相同)的全局指针。 接下来,程序应在指针解引用之前或之后的任何位置(无论在任何地方)为该指针分配一个值。 在上面的示例中,根本没有发生分配,但是编译器看到该分配存在。

If these conditions are met, a compiler can remove the assignment and change it into the initial value of the pointer.

如果满足这些条件,则编译器可以删除分配并将其更改为指针的初始值。

In the given code the variable Do is a pointer to a function, and it has the initial value null. When we try to call a function on the null pointer, the behavior of the program is undefined (undefined behavior, UB) and the compiler has right to optimize the UB as it wants. In this case, the compiler immediately executed the Do = EraseAll assignment.

在给定的代码中,变量Do是指向函数的指针,并且其初始值为null。 当我们尝试在空指针上调用函数时,程序的行为是不确定的(未定义行为,UB),并且编译器有权根据需要优化UB。 在这种情况下,编译器立即执行Do = EraseAll分配。

Why does this happen? In the rest of the text, LLVM and Clang version 5.0.0 are used as a compiler. Code examples are runnable for you to practice yourself.

为什么会这样? 在本文的其余部分,LLVM和Clang版本5.0.0用作编译器。 代码示例可运行,供您练习。

To begin with, let’s look at the IR code when optimizing with -O0 and -O1. Let’s change the source code slightly to make it less dramatic:

首先,让我们看一下使用-O0和-O1优化时的IR代码。 让我们稍微更改一下源代码以使其不那么生动:

#include <cstdlib>
typedef int (*Function)();
static Function Do;
static int PrintHello() {
  return printf("hello world\n");
}
void NeverCalled() {
  Do = PrintHello;  
}
int main() {
  return Do();
}

And we compile the IR code with -O0 (the debugging information is omitted for clarity):

然后,我们用-O0编译IR代码(为清晰起见,省略了调试信息):

; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
@Do = internal global i32 (...)* null, align 8
@.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00", align 1
; Function Attrs: noinline nounwind optnone uwtable
define void @NeverCalled() #0 {
entry:
  store i32 (...)* bitcast (i32 ()* @PrintHello to i32 (...)*), i32 (...)** @Do, align 8
  ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define i32 @main() #0 {
entry:
  %retval = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  %0 = load i32 (...)*, i32 (...)** @Do, align 8
  %call = call i32 (...) %0()
  ret i32 %call
}
; Function Attrs: noinline nounwind optnone uwtable
define internal i32 @PrintHello() #0 {
entry:
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i32 0, i32 0))
  ret i32 %call
}
declare i32 @printf(i8*, ...) #1
And with -O1:
; ModuleID = 'test.ll'
source_filename = "test.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
@.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00", align 1
; Function Attrs: noinline nounwind optnone uwtable
define void @NeverCalled() local_unnamed_addr #0 {
entry:
  ret void
}
; Function Attrs: noinline nounwind optnone uwtable
define i32 @main() local_unnamed_addr #0 {
entry:
  %retval = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  %call = call i32 (...) bitcast (i32 ()* @PrintHello to i32 (...)*)()
  ret i32 %call
}
; Function Attrs: noinline nounwind optnone uwtable
define internal i32 @PrintHello() unnamed_addr #0 {
entry:
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i32 0, i32 0))
  ret i32 %call
}
declare i32 @printf(i8*, ...) local_unnamed_addr #1

If you compile the executables, you will confirm that in the first case, a segmentation error occurs, and in the second case, “hello world” is displayed. With other optimization options, the result is the same as for -O1.

如果编译可执行文件,将确认在第一种情况下发生分段错误,在第二种情况下显示“ hello world”。 使用其他优化选项,结果与-O1相同。

Now find the part of the compiler code that performs this optimization. The architecture of LLVM the frontend does not deal with optimizations itself, i.e. cfe (Clang Frontend) always generates the code without optimizations, which we see in the version for -O0, and all optimizations are performed by the utility opt:

现在,找到执行此优化的编译器代码部分。 LLVM前端的体系结构本身不处理优化,即cfe(Clang前端)始终生成没有优化的代码,我们在-O0的版本中看到该代码,所有优化均由实用程序opt执行:

With -O1, 186 optimization passes are performed.

使用-O1,执行186次优化遍。

Turning off the passes one after another, we find what we are looking for: the globalopt pass. We can leave only this optimization pass, and make sure that it, and no one else, generates the code we need. The source is in the file /lib/Transforms/IPO/GlobalOpt.cpp. You can see the source code in the LLVM repository. For brevity, I have only provided functions important for understanding how it works.

一遍又一遍地关闭通行证,我们找到了我们想要的东西: globalopt通行证。 我们只能保留此优化过程,并确保它(没有其他过程)生成我们需要的代码。 源在文件/lib/Transforms/IPO/GlobalOpt.cpp中。 您可以在LLVM存储库中查看源代码。 为简洁起见,我仅提供了对于理解其工作原理很重要的功能。

This picture represents a structure of the IR representation. A code in LLVM IR representation has hierarchical levels: a module represents the highest level of a hierarchy, and includes all function and global objects, such as global variables. A function is the most important level of IR representation and most of passes work on this level. A basic block is one is the most important concept of a compiler theory. A basic block consists of instructions, which can not make jumps from a middle of a basic block or inside a basic block. All transitions between basic block are possible only from an end of a basic block and to a begin of a basic block, and any jumps from or to a middle of a basic block are never possible. An instruction level represents an LLVM IR code instruction. It is not a processor’s instruction, it’s an instruction of some very generalized virtual machine with an infinite number of registers.

该图片表示IR表示的结构。 LLVM IR表示中的代码具有层次结构级别:模块代表层次结构的最高级别,并且包括所有函数和全局对象,例如全局变量。 功能是IR表示的最重要级别,大多数过程都在此级别上进行。 基本块是编译器理论中最重要的概念。 基本块由指令组成,这些指令不能从基本块的中间或基本块内部进行跳转。 基本块之间的所有过渡都只能从基本块的末尾到基本块的开始,并且从基本块到中间块的任何跳转都是不可能的。 指令级别代表LLVM IR代码指令。 它不是处理器的指令,它是某些非常通用的具有无限数量寄存器的虚拟机的指令。

This picture shows a hierarchy of LLVM passes. On the left passes working on LLVM IR code are shown, on the right side passes working with target’s instructions are shown.

此图显示了LLVM传递的层次结构。 左侧显示了处理LLVM IR代码的过程,右侧显示了处理目标指令的过程。

Initially, it implements the runOnModule method, i.e. when working, it sees and optimizes the entire module (which, of course, is reasonable in this case). The function which performs the optimization is optimizeGlobalsInModule:

最初,它实现runOnModule方法,即在工作时,它会看到并优化整个模块(在这种情况下,当然是合理的)。 执行优化的函数是optimizeGlobalsInModule:

static bool optimizeGlobalsInModule(
    Module &M, const DataLayout &DL, TargetLibraryInfo *TLI,
    function_ref<dominatortree> LookupDomTree) {
  SmallSet<const comdat="Comdat" 8="8"> NotDiscardableComdats;
  bool Changed = false;
  bool LocalChange = true;
  while (LocalChange) {
    LocalChange = false;
NotDiscardableComdats.clear();
    for (const GlobalVariable &GV : M.globals())
      if (const Comdat *C = GV.getComdat())
        if (!GV.isDiscardableIfUnused() || !GV.use_empty())
          NotDiscardableComdats.insert(C);
    for (Function &F : M)
      if (const Comdat *C = F.getComdat())
        if (!F.isDefTriviallyDead())
          NotDiscardableComdats.insert(C);
    for (GlobalAlias &GA : M.aliases())
      if (const Comdat *C = GA.getComdat())
        if (!GA.isDiscardableIfUnused() || !GA.use_empty())
          NotDiscardableComdats.insert(C);
// Delete functions that are trivially dead, ccc -> fastcc
    LocalChange |=
        OptimizeFunctions(M, TLI, LookupDomTree, NotDiscardableComdats);
// Optimize global_ctors list.
    LocalChange |= optimizeGlobalCtorsList(M, [&](Function *F) {
      return EvaluateStaticConstructor(F, DL, TLI);
    });
// Optimize non-address-taken globals.
    LocalChange |= OptimizeGlobalVars(M, TLI, LookupDomTree,
                                      NotDiscardableComdats);
// Resolve aliases, when possible.
    LocalChange |= OptimizeGlobalAliases(M, NotDiscardableComdats);
// Try to remove trivial global destructors if they are not removed
    // already.
    Function *CXAAtExitFn = FindCXAAtExit(M, TLI);
    if (CXAAtExitFn)
      LocalChange |= OptimizeEmptyGlobalCXXDtors(CXAAtExitFn);
Changed |= LocalChange;
  }
// TODO: Move all global ctors functions to the end of the module for code
  // layout.
return Changed;
}

Let’s try to describe in words what this function does. For each global variable in the module, it requests a Comdat object.

让我们尝试用语言来描述此功能的作用。 对于模块中的每个全局变量,它都请求一个Comdat对象。

What is a Comdat object?

什么是Comdat对象?

A Comdat section is a section in the object file, in which objects are placed, which can be duplicated in other object files. Each object has information for the linker, indicating what it must do when duplicates are detected. The options can be: Any — do anything, ExactMatch — duplicates must completely match, otherwise an error occurs, Largest — take the object with the largest value, NoDublicates — there should not be a duplicate, SameSize — duplicates must have the same size, otherwise an error occurs.

Comdat节是目标文件中放置对象的节,可以在其他目标文件中复制这些对象。 每个对象都有链接器的信息,指示链接器在检测到重复项时必须执行的操作。 选项可以是:任何-做任何事情,ExactMatch-重复项必须完全匹配,否则会发生错误,最大-取值最大的对象NoDublicates-不应重复,SameSize-重复项必须具有相同的大小,否则会发生错误。

In LLVM, Comdat data is represented by an enumeration:

在LLVM中,Comdat数据由枚举表示:

enum SelectionKind {
    Any,          ///< The linker may choose any COMDAT.
    ExactMatch,   ///< The data referenced by the COMDAT must be the same.
    Largest,      ///< The linker will choose the largest COMDAT.
    NoDuplicates, ///< No other Module may specify this COMDAT.
    SameSize,     ///< The data referenced by the COMDAT must be the same size.
  };

and the class Comdat actually represents a pair (Name, SelectionKind). (In fact, everything is more complicated.) All variables that for some reason cannot be deleted are placed in a set of NotDiscardableComdats. With functions and global aliases, we do the same — something that can not be deleted is placed in NotDiscardableComdats. Then, separate optimization functions for global constructors, global functions, global variables, global aliases, and global destructors are called. Optimizations continue in the loop until no optimization is performed. At each iteration of the loop, the set of NotDiscardableComdats is set to zero.

而Comdat类实际上代表一对(名称,SelectionKind)。 (实际上,一切都更加复杂。)由于某种原因而无法删除的所有变量都放在一组NotDiscardableComdats中。 使用函数和全局别名,我们可以进行相同的操作-无法删除的内容放置在NotDiscardableComdats中。 然后,为全局构造函数,全局函数,全局变量,全局别名和全局析构函数调用单独的优化函数。 优化将继续循环,直到没有执行优化为止。 在循环的每次迭代中,NotDiscardableComdats的集合都设置为零。

Let’s see what objects of the listed our test source contains.

让我们看看测试源中列出的对象是什么。

Global variables:

全局变量:

1. @Do = internal global i32 (...)* null, align 8
2. @.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00", align 1

(a little looking ahead, I can say that the first variable will be deleted by the optimizer at the first iteration).

(稍微向前看,我可以说第一个变量将在第一次迭代时由优化器删除)。

Functions:

功能:

define void @NeverCalled()
define i32 @main()
define internal i32 @PrintHello()
declare i32 @printf(i8*, ...)

Note that printf is only declared, but not defined.

请注意,仅声明了printf,但未定义。

There are no global aliases.

没有全局别名。

Let’s look at the example of this optimization pass and consider how this result turned out. Of course, to analyze all the optimization variants even in one pass is a very big task, because it involves many different special cases of optimizations. We will concentrate on our example, considering those functions and data structures that are important for understanding the work of this optimization pass.

让我们看一下优化过程的示例,并考虑一下结果如何。 当然,即使一次分析所有优化变量也是一项艰巨的任务,因为它涉及许多不同的优化特殊情况。 我们将集中在我们的示例上,考虑对理解此优化过程的工作很重要的那些功能和数据结构。

Initially, the optimizer does various uninteresting checks in this case, and calls the processInternalGlobal function, which tries to optimize global variables. This function is also quite complex and does a lot of different things, but we are interested in one thing:

最初,在这种情况下,优化器执行各种无用的检查,并调用processInternalGlobal函数,该函数尝试优化全局变量。 这个函数也很复杂,可以做很多不同的事情,但是我们对一件事很感兴趣:

if (GS.StoredType == GlobalStatus::StoredOnce && GS.StoredOnceValue) {
...
// We are trying to optimize global variables, about which it is known that they are assigned a value only once, except the initializing value.
    if (optimizeOnceStoredGlobal(GV, GS.StoredOnceValue, GS.Ordering, DL, TLI))
      return true;
...
}

The information that the global variable is assigned the value one and only once is extracted from the GS structure (GlobalStatus). This structure is populated in the calling function:

从GS结构(GlobalStatus)中提取了为全局变量分配的值一次且仅一次的信息。 此结构填充在调用函数中:

static bool
processGlobal(GlobalValue &GV, TargetLibraryInfo *TLI,
              function_ref<dominatortree> LookupDomTree) {
  if (GV.getName().startswith("llvm."))
    return false;
GlobalStatus GS;
if (GlobalStatus::analyzeGlobal(&GV, GS))
    return false;
...

Here we see one more interesting fact: objects whose names begin with “llvm.” are not subject to optimization (since they are system calls for llvm runtime). And, just in case, the names of variables in LLVM IR can contain points (and even consist of one point with the prefix @ or %). The function analyzeGlobal is a call to the LLVM API and we will not consider its internal work. The structure of GlobalStatus should be viewed in details since it contains very important information for optimization passes.

在这里,我们看到了另一个有趣的事实:名称以“ llvm”开头的对象。 不受优化(因为它们是llvm运行时的系统调用)。 而且,以防万一,LLVM IR中的变量名称可以包含点(甚至包含一个前缀为@或%的点)。 函数analyzerGlobal是对LLVM API的调用,我们将不考虑其内部工作。 应该详细查看GlobalStatus的结构,因为它包含有关优化过程的非常重要的信息。

/// As we analyze each global, keep track of some information about it.  If we
/// find out that the address of the global is taken, none of this info will be
/// accurate.
struct GlobalStatus {
  /// True if the global's address is used in a comparison.
  bool IsCompared = false;
/// True if the global is ever loaded.  If the global isn't ever loaded it
  /// can be deleted.
  bool IsLoaded = false;
/// Keep track of what stores to the global look like.
  enum StoredType {
    /// There is no store to this global.  It can thus be marked constant.
    NotStored,
/// This global is stored to, but the only thing stored is the constant it
    /// was initialized with. This is only tracked for scalar globals.
    InitializerStored,
/// This global is stored to, but only its initializer and one other value
    /// is ever stored to it.  If this global isStoredOnce, we track the value
    /// stored to it in StoredOnceValue below.  This is only tracked for scalar
    /// globals.
    StoredOnce,
/// This global is stored to by multiple values or something else that we
    /// cannot track.
    Stored
  } StoredType = NotStored;
/// If only one value (besides the initializer constant) is ever stored to
  /// this global, keep track of what value it is.
  Value *StoredOnceValue = nullptr;
...
};

It is worth to explain why “If we find out that the address of the global is taken, none of this info will be accurate.” In fact, if we take the address of a global variable, and then write something at this address, not by name, then it will be extremely difficult to track this, and it is better to leave such variables as is, without trying to optimize.

值得解释为什么“如果我们发现全局地址已被使用,则此信息将不准确。” 实际上,如果我们采用全局变量的地址,然后在该地址上写一些东西,而不是按名称写东西,那么跟踪它将非常困难,最好不进行优化就直接保留这些变量。 。

So, we get into the function optimizeOnceStoredGlobal, to which the variable (GV) and the stored value (StoredOnceVal) are passed. Here they are:

因此,我们进入了函数optimizeOnceStoredGlobal,变量(GV)和存储值(StoredOnceVal)传递给该函数。 他们来了:

@Do = internal unnamed_addr global i32 (...)* null, align 8 // the variable
i32 (...)* bitcast (i32 ()* @PrintHello to i32 (...)*) // the value

Next, for the value, the insignificant bitcast is deleted, and for the variable the following condition is checked:

接下来,对于该值,删除无关紧要的位广播,对于变量,检查以下条件:

if (GV->getInitializer()->getType()->isPointerTy() &&
      GV->getInitializer()->isNullValue()) {
...

that is, the variable must be initialized with a null pointer. If this is the case, then we create a new SOVC variable corresponding to the value of StoredOnceVal cast to the GV type:

也就是说,必须使用空指针初始化变量。 如果是这种情况,那么我们将创建一个新的SOVC变量,该变量对应于强制转换为GV类型的StoredOnceVal的值:

if (Constant *SOVC = dyn_cast<constant>(StoredOnceVal)) {
      if (GV->getInitializer()->getType() != SOVC->getType())
        SOVC = ConstantExpr::getBitCast(SOVC, GV->getInitializer()->getType());

Here, getBitCast is the method that returns the bitcast command, which types the types in the LLVM IR language.

在这里,getBitCast是返回bitcast命令的方法,该命令以LLVM IR语言键入类型。

After that, the function OptimizeAwayTrappingUsesOfLoads is called. It transfers the global variable GV and the constant LV.

之后,将调用函数OptimizeAwayTrappingUsesOfLoads。 它传输全局变量GV和常数LV。

Direct optimization is performed by the function OptimizeAwayTrappingUsesOfValue (Value * V, Constant * NewV).

直接优化由功能OptimizeAwayTrappingUsesOfValue(值* V,常数* NewV)执行。

For each use of a variable:

对于变量的每次使用:

for (auto UI = V->user_begin(), E = V->user_end(); UI != E; ) {
    Instruction *I = cast<instruction>(*UI++);

if this is a Load command, replace its operand with a new value:

如果这是一个Load命令,则将其操作数替换为新值:

if (LoadInst *LI = dyn_cast<loadinst>(I)) {
      LI->setOperand(0, NewV);
      Changed = true;
    }

If the variable is used in the function call or invoke (which is exactly what happens in our example), create a new function, replacing its argument with a new value:

如果在函数调用或调用中使用了变量(这正是我们的示例中发生的情况),请创建一个新函数,将其参数替换为新值:

if (isa<callinst>(I) || isa<invokeinst>(I)) {
      CallSite CS(I);
      if (CS.getCalledValue() == V) {
        // Calling through the pointer!  Turn into a direct call, but be careful
        // that the pointer is not also being passed as an argument.
        CS.setCalledFunction(NewV);
        Changed = true;
        bool PassedAsArg = false;
        for (unsigned i = 0, e = CS.arg_size(); i != e; ++i)
          if (CS.getArgument(i) == V) {
            PassedAsArg = true;
            CS.setArgument(i, NewV);
          }

All other arguments to the function are simply copied.

该函数的所有其他参数都将被简单复制。

Also, similar replacement algorithms are provided for the Cast and GEP instructions, but in our case this does not happen.

同样,为Cast和GEP指令提供了类似的替换算法,但在我们这种情况下不会发生。

The further actions are as follows: we look through all uses of a global variable, trying to delete everything, except assignment of value. If this is successful, then we can delete the Do variable.

进一步的操作如下:我们遍历全局变量的所有使用,尝试删除除赋值外的所有内容。 如果成功,那么我们可以删除Do变量。

So, we briefly reviewed the work of the optimization pass LLVM on a specific example. In principle, nothing super complicated is here, but more careful programming is required to provide for all possible combinations of commands and variable types. Of course, all this must be covered by tests. Learning the source code of LLVM optimizers will help you write your optimizations, allowing you to improve the code for some specific cases.

因此,我们在一个特定示例上简要回顾了优化过程LLVM的工作。 原则上,这里没有什么超级复杂的,但是需要更仔细的编程才能提供命令和变量类型的所有可能组合。 当然,所有这些都必须包含在测试中。 学习LLVM优化器的源代码将帮助您编写优化程序,从而使您可以针对某些特定情况改进代码。

翻译自: https://habr.com/en/post/458442/

llvm中的函数调用关系图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值