llvm如何使用vc编译器_使用LLVM框架创建可用的编译器,第1部分

llvm如何使用vc编译器

LLVM(以前称为低级虚拟机)是一个功能非常强大的编译器基础结构框架,旨在对使用您喜欢的编程语言编写的程序进行编译时,链接时和运行时优化。 LLVM在几种不同的平台上工作,其成名的主要主张是生成可快速运行的代码。

LLVM框架是围绕一个记录良好的代码中间表示(IR)构建的。 本文是由两部分组成的系列文章的第一篇,深入研究了LLVM IR的基础知识及其一些精妙之处。 从那里,您将构建一个代码生成器,该代码生成器可以自动为您生成LLVM IR。 拥有LLVM IR生成器意味着您所需要的只是用于插入自己喜欢的语言的前端,并且拥有完整的流程(前端解析器+ IR生成器+ LLVM后端)。 创建自定义编译器变得简单了。

LLVM入门

在开始之前,必须在开发计算机上编译LLVM(请参阅参考资料中的链接)。 本文中的示例基于LLVM 3.0版。 LLVM代码的后构建和安装的两个最重要的工具是llclli

llc和lli

因为LLVM是虚拟机(VM),所以它可能应该具有自己的中间字节代码表示形式,对吗? 最终,您需要将LLVM字节代码编译为特定于平台的汇编语言。 然后,您可以通过本机汇编器和链接器运行汇编代码以生成可执行文件,共享库等。 您可以使用llc将LLVM字节代码转换为特定于平台的汇编代码(请参阅参考资料 ,以获取有关此工具的更多信息的链接)。 要直接执行LLVM字节代码的某些部分,请不要等到本机可执行文件崩溃后才知道您的程序中存在一两个错误。 这是lli派上用场的地方,因为它可以直接执行字节码。 lli通过解释器或在lli使用即时(JIT)编译器执行此任务。 请参阅相关信息的链接,有关详细信息, lli

llvm-gcc

llvm-gcc是GNU编译器集合(gcc)的修改版本,当与-S -emit-llvm选项一起运行时,可以生成LLVM字节代码。 然后,您可以使用lli执行此生成的字节码(也称为LLVM汇编 )。 有关LLVM-GCC的详细信息,请参阅相关主题 。 如果您的系统上没有预装llvm-gcc,则应该能够从源代码构建它。 请参阅参考资料 ,以获取分步指南的链接。

LLVM的Hello World

为了更好地理解LLVM,您必须学习LLVM IR及其特质。 此过程类似于学习另一种编程语言。 但是,如果您已经经历过CC++及其怪癖,那么在LLVM IR中就不会有什么可以阻止您的。 清单1显示了您的第一个程序,该程序在控制台输出中显示“ Hello World”。 要编译此代码,请使用llvm-gcc。

清单1.外观熟悉的Hello World程序
#include <stdio.h>
int main( )
{ 
  printf("Hello World!\n");
}

要编译代码,请输入以下命令:

Tintin.local# llvm-gcc helloworld.cpp -S -emit-llvm

编译后,llvm-gcc会生成文件helloworld.s,您可以使用lli执行该文件以将消息打印到控制台。 lli用法是:

Tintin.local# lli helloworld.s
Hello, World

现在,首先看一下LLVM组件。 清单2显示了代码。

清单2. Hello World程序的LLVM字节代码
@.str = private constant [13 x i8] c"Hello World!\00", align 1 ;

define i32 @main() ssp {
entry:
  %retval = alloca i32
  %0 = alloca i32
  %"alloca point" = bitcast i32 0 to i32
  %1 = call i32 @puts(i8* getelementptr inbounds ([13 x i8]* @.str, i64 0, i64 0))
  store i32 0, i32* %0, align 4
  %2 = load i32* %0, align 4
  store i32 %2, i32* %retval, align 4
  br label %return
return:
  %retval1 = load i32* %retval
  ret i32 %retval1
}

declare i32 @puts(i8*)

了解LLVM IR

LLVM带有详细的汇编语言表示(请参阅参考资料中的链接)。 在尝试制作自己之前讨论过的Hello World程序版本之前,这是必知事项:

  • LLVM汇编中的注释以分号( ; )开头,一直到该行的末尾。
  • 全局标识符以at( @ )字符开头。 所有函数名称和全局变量也必须以@开头。
  • LLVM中的本地标识符以百分比符号( % )开头。 标识符的典型正则表达式为[%@][a-zA-Z$._][a-zA-Z$._0-9]*
  • LLVM具有强大的类型系统,并且它是最重要的功能之一。 LLVM将整数类型定义为i N ,其中N是该整数将占用的位数。 您可以指定1到223-1之间的任何位宽。
  • 您将向量或数组类型声明为[no. of elements X size of each element] [no. of elements X size of each element] 。 对于字符串“ Hello World!” 这使得类型为[13 x i8] ,假设每个字符为1个字节,并为NULL字符增加一个额外的字节。
  • 您可以为hello-world字符串声明一个全局字符串常量,如下所示: @hello = constant [13 x i8] c"Hello World!\00" 。 使用constant关键字声明一个常量,后跟类型和值。 类型已经讨论过,所以让我们看一下值:首先使用c然后使用双引号将整个字符串括起来,包括\0并以0结尾。 不幸的是,LLVM文档没有提供任何解释为什么需要使用c前缀声明字符串,并在末尾同时包含NULL字符和0。 请参阅相关信息的链接,语法文件,如果你有兴趣探索更LLVM怪癖。
  • LLVM允许您声明和定义函数。 而不是浏览LLVM函数的整个功能列表,而是专注于裸露的骨骼。 首先是define关键字,然后是返回类型,然后是函数名称。 一个简单的main定义,它返回类似于以下内容的32位整数: define i32 @main() { ; some LLVM assembly code that returns i32 } define i32 @main() { ; some LLVM assembly code that returns i32 }
  • 函数定义和定义一样,有很多缺点。 这是puts方法的最简单声明,它与printf等效于LLVM: declare i32 puts(i8*) 。 您可以使用declare关键字,然后是返回类型,函数名称以及该函数的可选参数列表,来进行声明。 该声明必须在全局范围内。
  • 每个函数都以return语句结尾。 return语句有两种形式: ret <type> <value>ret void 。 对于您简单的主例程, ret i32 0就足够了。
  • 使用call <function return type> <function name> <optional function arguments>来调用函数。 请注意,每个函数参数必须在其类型之前。 返回6位整数并接受36位整数的函数测试的语法为: call i6 @test( i36 %arg1 )

一开始就是这样。 您需要定义一个主例程,一个用于容纳字符串的常量以及一个用于处理实际打印的puts方法的声明。 清单3显示了第一次尝试。

清单3.首次尝试创建手工制作的Hello World程序
declare  i32 @puts(i8*) 
@global_str = constant [13 x i8] c"Hello World!\00"
define i32 @main { 
  call i32 @puts( [13 x i8] @global_str )
  ret i32 0 
}

这是lli的日志:

lli: test.s:5:29: error: global variable reference must have pointer type
  call i32 @puts( [13 x i8] @global_str )
                            ^

糟糕,这没有按预期进行。 刚才发生了什么? 如前所述,LLVM具有强大的类型系统。 因为puts期望指向i8的指针,并且您传递了i8的向量,所以lli很快指出了错误。 来自C编程背景的一个明显的解决方法是类型转换。 这将带您进入LLVM指令getelementptr 。 请注意,您必须puts 清单3中puts调用修改为类似于call i32 @puts(i8* %t) ,其中%t的类型为i8*并且是从[13 x i8] to i8*的类型转换的结果。 (请参阅参考资料中有关 getelementptr的详细描述的getelementptr 。)在进一步介绍之前, 清单4提供了有效的代码。

清单4.使用getelementptr正确将类型转换为指针
declare i32 @puts (i8*)
@global_str = constant [13 x i8] c"Hello World!\00"

define i32 @main() {
  %temp = getelementptr [13 x i8]*  @global_str, i64 0, i64 0
  call i32 @puts(i8* %temp)
  ret i32 0
}

getelementptr的第一个参数是指向全局字符串变量的指针。 需要第一个索引i64 0才能越过指向全局变量的指针。 因为getelementptr指令的第一个参数必须始终是pointer类型的值,所以第一个索引将遍历该指针。 值为0表示从该指针偏移0个元素。 我的开发计算机运行的是64位Linux®,因此指针为8个字节。 第二个索引i64 0用于选择字符串的第0个元素,该元素作为puts的参数puts

创建自定义LLVM IR代码生成器

了解LLVM IR很好,但是您需要一个自动代码生成系统来转储LLVM程序集。 值得庆幸的是,LLVM确实具有足够的应用程序编程接口(API)支持,可以帮助您完成这项工作(请参阅参考资料中的程序员手册链接)。 在开发计算机上查找文件LLVMContext.h; 如果该文件丢失,则说明您安装LLVM的方式可能有问题。

现在,让我们创建一个为上面讨论的Hello World程序生成LLVM IR的程序。 该程序不会在此处处理整个LLVM API,但是下面的代码示例应证明LLVM API相当直观且易于使用。

LLVM的带有一个极好的工具称为llvm-config (参见相关主题 )。 运行llvm-config –cxxflags得到编译标志需要被传递到G ++, llvm-config –ldflags的连接选项,以及llvm-config –libs反对权LLVM库链接。 在清单5的示例中,所有选项都需要传递给g ++。

清单5.使用llvm-config通过LLVM API构建代码
tintin# llvm-config --cxxflags --ldflags --libs \
-I/usr/include  -DNDEBUG -D_GNU_SOURCE \
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS \
-D__STDC_LIMIT_MACROS -O3  -fno-exceptions -fno-rtti -fno-common \
-Woverloaded-virtual -Wcast-qual \
-L/usr/lib  -lpthread -lm \
-lLLVMXCoreCodeGen -lLLVMTableGen -lLLVMSystemZCodeGen \
-lLLVMSparcCodeGen -lLLVMPTXCodeGen \
-lLLVMPowerPCCodeGen -lLLVMMSP430CodeGen -lLLVMMipsCodeGen \
-lLLVMMCJIT -lLLVMRuntimeDyld \
-lLLVMObject -lLLVMMCDisassembler -lLLVMXCoreDesc -lLLVMXCoreInfo \
-lLLVMSystemZDesc -lLLVMSystemZInfo \
-lLLVMSparcDesc -lLLVMSparcInfo -lLLVMPowerPCDesc -lLLVMPowerPCInfo \
-lLLVMPowerPCAsmPrinter \
-lLLVMPTXDesc -lLLVMPTXInfo -lLLVMPTXAsmPrinter -lLLVMMipsDesc \
-lLLVMMipsInfo -lLLVMMipsAsmPrinter \
-lLLVMMSP430Desc -lLLVMMSP430Info -lLLVMMSP430AsmPrinter \
-lLLVMMBlazeDisassembler -lLLVMMBlazeAsmParser \
-lLLVMMBlazeCodeGen -lLLVMMBlazeDesc -lLLVMMBlazeAsmPrinter \
-lLLVMMBlazeInfo -lLLVMLinker -lLLVMipo \
-lLLVMInterpreter -lLLVMInstrumentation -lLLVMJIT -lLLVMExecutionEngine \
-lLLVMDebugInfo -lLLVMCppBackend \
-lLLVMCppBackendInfo -lLLVMCellSPUCodeGen -lLLVMCellSPUDesc \
-lLLVMCellSPUInfo -lLLVMCBackend \
-lLLVMCBackendInfo -lLLVMBlackfinCodeGen -lLLVMBlackfinDesc \
-lLLVMBlackfinInfo -lLLVMBitWriter \
-lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen \
-lLLVMX86Desc -lLLVMX86AsmPrinter -lLLVMX86Utils \
-lLLVMX86Info -lLLVMAsmParser -lLLVMARMDisassembler -lLLVMARMAsmParser \
-lLLVMARMCodeGen -lLLVMARMDesc \
-lLLVMARMAsmPrinter -lLLVMARMInfo -lLLVMArchive -lLLVMBitReader \
-lLLVMAlphaCodeGen -lLLVMSelectionDAG \
-lLLVMAsmPrinter -lLLVMMCParser -lLLVMCodeGen -lLLVMScalarOpts \
-lLLVMInstCombine -lLLVMTransformUtils \
-lLLVMipa -lLLVMAnalysis -lLLVMTarget -lLLVMCore -lLLVMAlphaDesc \
-lLLVMAlphaInfo -lLLVMMC -lLLVMSupport

LLVM模块,上下文等

LLVM模块类是所有其他LLVM IR对象的顶级容器。 LLVM模块类可以包含全局变量,函数,该模块所依赖的其他模块,符号表等的列表。 这是LLVM模块的构造函数:

explicit Module(StringRef ModuleID, LLVMContext& C);

您必须通过创建LLVM模块来开始程序。 第一个参数是模块的名称,可以是任何虚拟字符串。 第二个参数是称为LLVMContext东西。 LLVMContext类有点不透明,但是足以理解它提供了一个在其中创建变量等的上下文。 在多个线程的上下文中,此类很重要,您可能希望在每个线程中创建一个本地上下文,并且每个线程完全独​​立于任何其他上下文运行。 现在,使用LLVM提供的默认全局上下文句柄。 这是创建模块的代码:

llvm::LLVMContext& context = llvm::getGlobalContext();

llvm::Module* module = new llvm::Module("top", context);

下一个要学习的重要类是实际提供API的方法,该类可创建LLVM指令并将其插入基本块: IRBuilder类。 IRBuilder带有很多IRBuilder ,但是我选择了一种最简单的构造方法-通过将全局上下文和代码一起传递给它:

llvm::LLVMContext& context = llvm::getGlobalContext();

llvm::Module* module = new llvm::Module("top", context);

llvm::IRBuilder<> builder(context);

当LLVM对象模型准备就绪时,可以通过调用模块的dump方法来转储其内容。 清单6显示了代码。

清单6.创建一个虚拟模块
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module* module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

  module->dump( );
}

在运行清单6中的代码之后,这将打印到控制台:

; ModuleID = 'top'

接下来,您需要创建main方法。 LLVM提供了llvm::Function类来创建函数,并提供llvm::FunctionType来关联llvm::FunctionType的返回类型。 另外,请记住main方法必须是模块的一部分。 清单7显示了代码。

清单7.将main方法添加到顶部模块
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

  llvm::FunctionType *funcType = 
      llvm::FunctionType::get(builder.getInt32Ty(), false);
  llvm::Function *mainFunc = 
      llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", module);

  module->dump( );
}

请注意,您希望main返回void ,这就是为什么您调用builder.getVoidTy() ; 如果main返回i32 ,则调用为builder.getInt32Ty() 。 编译并运行清单7中的代码之后,结果是:

; ModuleID = 'top'
declare void @main()

您尚未定义main应该执行的指令集。 为此,您必须定义一个基本块并将其与main方法相关联。 一个基本块是LLVM IR中指令的集合,可以选择将标签(类似于C标签)定义为其构造函数的一部分。 builder.setInsertPoint告诉LLVM引擎接下来在何处插入指令。 清单8显示了代码。

清单8.在main中添加一个基本块
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

  llvm::FunctionType *funcType = 
      llvm::FunctionType::get(builder.getInt32Ty(), false);
  llvm::Function *mainFunc = 
      llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", module);

  llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunc);
  builder.SetInsertPoint(entry);

  module->dump( );
}

这是清单8的输出。 请注意,因为现在已经定义了main的基本块,所以LLVM转储现在将main视为方法定义,而不是声明。 很酷的东西!

; ModuleID = 'top'
define void @main() { 
entrypoint: 
}

现在,将全局hello-world字符串添加到代码中。 清单9显示了代码。

清单9.将全局字符串添加到LLVM模块
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

  llvm::FunctionType *funcType = 
      llvm::FunctionType::get(builder.getVoidTy(), false);
  llvm::Function *mainFunc = 
      llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", module);

  llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunc);
  builder.SetInsertPoint(entry);

  llvm::Value *helloWorld = builder.CreateGlobalStringPtr("hello world!\n");

  module->dump( );
}

清单9的输出中,请注意LLVM引擎如何转储字符串:

; ModuleID = 'top'
@0 = internal unnamed_addr constant [14 x i8] c"hello world!\0A\00"
define void @main() {
entrypoint:
}

您现在需要的是声明puts方法并对其进行调用。 要声明puts方法,必须创建适当的FunctionType* 。 从原始的Hello World代码中,您知道puts返回i32并接受i8*作为输入参数。 清单10显示了为puts创建正确类型的代码。

清单10.声明puts方法的代码
std::vector<llvm::Type *> putsArgs;
  putsArgs.push_back(builder.getInt8Ty()->getPointerTo());
  llvm::ArrayRef<llvm::Type*>  argsRef(putsArgs);

  llvm::FunctionType *putsType = 
    llvm::FunctionType::get(builder.getInt32Ty(), argsRef, false);
  llvm::Constant *putsFunc = module->getOrInsertFunction("puts", putsType);

FunctionType::get的第一个参数是返回类型; 第二个参数是LLVM::ArrayRef结构,最后一个false表示后面没有可变数量的参数。 ArrayRef结构类似于矢量,不同之处在于它不包含任何基础数据,并且主要用于包装数组和矢量之类的数据块。 进行此更改后,输出将显示在清单11中

清单11.声明puts方法
; ModuleID = 'top'
@0 = internal unnamed_addr constant [14 x i8] c"hello world!\0A\00"
define void @main() {
entrypoint:
}
declare i32 @puts(i8*)

剩下的就是在main内部调用puts方法并从main返回。 LLVM API负责转换,其余所有工作:调用puts只是调用builder.CreateCall 。 最后,要创建return语句,请调用builder.CreateRetVoid清单12提供了完整的工作代码。

清单12.打印Hello World的完整代码
#include "llvm/ADT/ArrayRef.h"
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Function.h"
#include "llvm/BasicBlock.h"
#include "llvm/Support/IRBuilder.h"
#include <vector>
#include <string>

int main()
{
  llvm::LLVMContext & context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("asdf", context);
  llvm::IRBuilder<> builder(context);

  llvm::FunctionType *funcType = llvm::FunctionType::get(builder.getVoidTy(), false);
  llvm::Function *mainFunc = 
    llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", module);
  llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunc);
  builder.SetInsertPoint(entry);

  llvm::Value *helloWorld = builder.CreateGlobalStringPtr("hello world!\n");

  std::vector<llvm::Type *> putsArgs;
  putsArgs.push_back(builder.getInt8Ty()->getPointerTo());
  llvm::ArrayRef<llvm::Type*>  argsRef(putsArgs);

  llvm::FunctionType *putsType = 
    llvm::FunctionType::get(builder.getInt32Ty(), argsRef, false);
  llvm::Constant *putsFunc = module->getOrInsertFunction("puts", putsType);

  builder.CreateCall(putsFunc, helloWorld);
  builder.CreateRetVoid();
  module->dump();
}

结论

在对LLVM的初步研究中,您了解了诸如llillvm-config类的LLVM工具,并挖掘了LLVM中间代码,并使用LLVM API为您生成了中间代码。 本系列的第二部分也是最后一部分,将探讨您可以使用LLVM进行的另一项任务-花费最少的精力添加额外的编译过程。


翻译自: https://www.ibm.com/developerworks/opensource/library/os-createcompilerllvm1/index.html

llvm如何使用vc编译器

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值