01-LLVM概述及简单使用

LLVM是构架编译器(compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。是模块化和可重用的编译器和工具链技术的集合.

本来只是想写一个底层的虚拟机(low level virtual machine),但做出来后实际上跟虚拟机几乎没有关系。LLVM就是这个项目的全名
本文只是学习并记录笔记,如有错误或不足请谅解指正,谢谢!


LLVM简介

传统GCC编译器架构

传统静态编译器主要流行分为三个阶段:前端(词法分析、语法分析、语义分析、生成中间代码)、优化器(中间代码优化)和后端(生成机器码)

  • 前端解析源代码,检查错误,并构建特定语言的抽象语法树 (AST)
  • 优化器负责进行各种转换以尝试提高代码的运行时间,例如消除冗余计算
  • 后端将代码映射到目标指令集,编译器后端的常见部分包括指令选择、寄存器分配和指令调度

这种GCC“一条龙”服务的好处是:不会暴露中间接口来给你操作它的IR,是强耦合的;但缺点是,对于不同的、新的语言,需要完成全部的从前端、优化器到后端的流程设计。

LLVM架构

目的:支持多种源语言或目标架构(三阶段设计)

LLVM与传统GCC编译器的主要不同就是:对于不同的语言它都提供了同一种中间表示。优化器只对中间表示IR操作,通过一系列的pass对IR做优化。使用这种设计,移植编译器以支持新的源语言需要实现新的前端,但可以重用现有的优化器和后端。

优点:LLVM由于共享优化器的中转,不同的前端语言最终都转换成同一种的IR,不仅仅支持一种源语言和一个目标

LLVM IR


官方文档:https://llvm.org/docs/Reference.html#llvm-ir

LLVM IR(Intermediate Representation)是LLVM的代码表示,是用于在编译器中表示代码的形式。旨在轻量级和低级,同时具有表现力、类型和可扩展性。旨在成为某种“通用 IR”,通过处于足够低的级别,可以将高级想法清晰地映射到它。

LLVM IR有三种形式:①内存中的编译中间表示 ②磁盘上的二进制码 bitcode ③可读汇编文本

这三种形式之间是等价的。

结构:

  • Module,可以被视为一个.c文件的 IR 表示,每个 Module都是相对独立的东西,主要包含了声明或者定义的函数、声明或定义的全局变量、当前 Module的基本信息
  • Function,是 Module中以List的方式存放的。函数内的第一个基本块叫做入口基本块。无法单独存在,必须是在某个 Module里,包含了大量 BasicBlock 、参数和返回值类型、可变参数列表、函数的attribute和其他函数的基本信息
  • BasicBlock,是 Function 中以List方式存放。包含了大量的 Instruction ,前驱、后继的 BasicBlock,以及一些基本信息,每个 BasicBlock都可以视为一段顺序执行的代码
  • Instruction,是 BasicBlock 中以List方式存放的,LLVM IR中的最小可执行单位,每一条指令都单占一行

LLVM简单使用


#include <stdio.h>

int main() {
printf("hello LLVM\n");
return 0;
}
  • 将C文件编译为LLVM IR文件
clang -emit-llvm -S hello_llvm.c -o hello_llvm.ll

查看hello_llvm.ll

; ModuleID = 'hello_llvm.c'
source_filename = "hello_llvm.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

@.str = private unnamed_addr constant [12 x i8] c"hello LLVM\0A\00", align 1

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
%1 = alloca i32, align 4
store i32 0, ptr %1, align 4
%2 = call i32 (ptr, ...) @printf(ptr noundef @.str)
ret i32 0
}

declare i32 @printf(ptr noundef, ...) #1

attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"PIC Level", i32 2}
!2 = !{i32 7, !"PIE Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 2}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"clang version 15.0.0 (https://github.com/llvm/llvm-project.git e300682597470ffc88b59a6187cdd763f1595d3a)"}
  • 将C文件编译为LLVM bitcode文件
clang -emit-llvm -c hello_llvm.c -o hello_llvm.bc
  • 查看bitcode文件对应的LLVM 程序集
llvm-dis < hello_llvm.bc

与上面生成的IR文件内容一致

  • 可以通过lli指令运行上面生成的.ll.bc文件
$ lli hello_llvm.bc       
hello LLVM

$ lli hello_llvm.ll
hello LLVM

附LLVM常用命令,详细信息可以查看官方命令指南

llvm-ar:存档器生成一个包含给定 LLVM 位码文件的存档,可选地带有一个索引以便更快地查找
llvm-as:汇编器将可读的 LLVM 程序集转换为 LLVM bitcode
llvm-dis:反汇编器将 LLVM bitcode转换为可读的 LLVM 程序集
llvm-link:将多个 LLVM 模块链接到一个程序中
lli:LLVM解释器(LLVM interpreter),可以直接执行LLVM bitcode
llc:LLVM 后端编译器,它将 LLVM bitcode转换为本机代码汇编文件
opt:读取 LLVM 位码,将一系列 LLVM 应用于 LLVM 转换,并输出结果位码;opt -help获取LLVM 中可用的程序转换列表。opt还可以对输入的 LLVM 位码文件运行特定分析并打印结果。主要用于调试分析或熟悉分析的作用。
文章作者: HotSpurzzZ
文章链接: http://example.com/2022/05/05/01-LLVM概述及IR简单使用/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 HotSpurzzZ