编译原理第二次小班课

news2024/9/25 21:29:23

写给入门者的LLVM介绍 - 知乎 (zhihu.com)
代码优化与LLVM IR pass | Kiprey’s Blog
A Tour to LLVM IR(上) - 知乎 (zhihu.com)
第5章 LLVM中间表示 — Getting Started with LLVM Core Libraries 文档 (getting-started-with-llvm-core-libraries-zh-cn.readthedocs.io)

在这里插入图片描述

第一页-什么是LLVM

LLVM是一个编译器(确切的说是一套框架+基于框架的一些编译器实现)
LLVM IR(Intermediate Representation)是LLVM的一种中间表示,也可以视为中间代码(便于代码优化)
LLVM之所以优秀,在于以下几点:

  • 1、LLVM的中间表达(IR)是可以dump出来成为可阅读的文本形式的(语法有点像汇编),看起来微不足道,但是其他很多编译器却只有内存中的数据结构,使得学习调试难度大增。
  • 2、模块化的设计比较好,吸收了很多前人经验,也和设计者的架构功力息息相关。虽然始于学术项目,但LLVM一直受到工业界的支持(Apple),所以不仅好用,而且开源可定制。避免了在Java中类似面临选择HotSpot和Jikes的困境。

在这里插入图片描述

第二页-为什么关心LLVM

有时候一项工作看起来并不完全是个完整的编译行为,但只要涉及到源码到源码的转换,了解LLVM通常会有所帮助。
以下是一些使用 LLVM 完成并非所有编译操作的研究项目的示例:

  • UIUC 的Virtual Ghost展示了您以使用编译器通道来保护进程免受受损操作系统内核的影响。
  • UW 的CoreDet使多线程程序具有确定性。
  • 在近似计算工作中,使用 LLVM pass 将错误注入程序以模拟容易出错的硬件。
    LLVM 不只是用于实现新的编译器及编译优化!

在这里插入图片描述

第三页-LLVM的基本架构

这张图片显示了 LLVM 架构的主要部件:

  • 前端是clang,获取源代码并将其转换为中间表示或 IR。这种翻译简化了编译器其余部分的工作。
  • IR经过N个pass的优化处理。在一般情况下,pass 通常会优化代码:生成一个 IR 程序作为输出,它与作为输入的 IR 执行相同的操作,只是它更快更优。这是需要拓展定制的地方。使用相关工具可以通过在编译过程中查看和更改 IR 来进行。
  • 后端,生成实际的机器码。很多时候不需要接触这部分。
    尽管这种架构描述了当今大多数编译器,但这里值得注意的是 LLVM 的一个新颖之处:整个编译过程中使用相同的 IR 。在其他编译器中,每次传递都可能以独特的形式生成代码。

在这里插入图片描述

第四页-了解LLVM IR

LLVM IR 具有三种表示形式,这三种中间格式是完全等价的:
在内存中的编译中间语言(我们无法通过文件的形式得到)
在硬盘上存储的二进制中间语言(bitcode形式,格式为 .bc)
人类可读的代码语言(LLVM汇编文件形式,格式为 .ll)

在这里插入图片描述

第五页-汇编形式的LLVM IR

汇编形式的IR是可读的。所以这里用一个简单的例子展示一下汇编形式的IR。首先编写一个简单的c语言函数如下:

// add.cpp
int add(int a, int b) {
    return a + b;
}

使用如下命令可以产生汇编形式的IR:
clang add.cpp -emit-llvm -S -c -o add.ll
具体的汇编IR如下:

; ModuleID = 'add.cpp'
source_filename = "add.cpp"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.15.0"


; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @_Z3addii(i32, i32) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  store i32 %0, i32* %3, align 4
  store i32 %1, i32* %4, align 4
  %5 = load i32, i32* %3, align 4
  %6 = load i32, i32* %4, align 4
  %7 = add nsw i32 %5, %6
  ret i32 %7
}

  • 注释以 分号(;) 开头,分号(;)后面的注释指明了module的标识,source_filename是表明这个module是从什么文件编译得到的(如果你打开main.ll会发现这里的值是main.cpp),如果该modules是通过链接得到的,这里的值就会是llvm-link
  • LLVM IR 是静态类型的(即在编写时每个值都有明确的类型)
  • 局部变量的作用域是单个函数(比如 @main 中的 %1 是一个 i32* 类型的地址,而 @foo 中的 %1 是一个 i32 类型的值)
  • 临时寄存器(或者说临时变量)拥有升序的名字(比如 @main 函数中的 %1%2%3
  • 全局变量与局部变量由前缀区分,全局变量和函数名以 @ 为前缀,局部变量以 % 为前缀
  • 大多数指令与字面含义相同(alloca 分配内存并返回地址,load 从内存读出值,store 向内存存值,add 用于加法等)

在这里插入图片描述

第六页-内存中的IR

Module包含Functions,其中包含BasicBlocks,其中包含Instructions。除了 Module 之外的所有东西都来自Value。
![[Snipaste_2023-11-28_16-19-11.png|125]]
Module类,Module可以理解为一个完整的编译单元。一般来说,这个编译单元就是一个源码文件,比如一个后缀为cpp的源文件。每个module之间相互独立,module主要包含了声明或者定义的函数、全局变量等。
Function类,就是对应一个函数单元。主要包含了大量 BasicBlock 、参数和返回值类型、可变参数列表、函数的attribute和其他函数的基本信息Function由无数个 BasicBlock组成,使用列表存放,有且仅有一个 EntryBlock ,是列表中的第一个 BasicBlock,代码真正执行的时候,就从 EntryBlock开始执行。
BasicBlock类,这个类表示一个基本代码块,“基本代码块”就是一段没有控制流逻辑的基本流程,相当于程序流程图中的基本过程。控制流只能从基本块的第一个指令进入该块。也就是说,没有跳转到基本块中间的或末尾指令的转移指令。除了基本块的最后一个指令,控制流在离开基本块之前不会跳转或停机。
Instruction类,指令类就是LLVM中定义的基本操作,比如加减乘除这种算数指令、函数调用指令、跳转指令、返回指令等等

在这里插入图片描述

第七页-举例

下面,我们通过一个例子来介绍程序的控制流是如何通过基本块与终结指令描述的:
if.c 导出为 LLVM IR

这个程序的控制流如图所示(点一下PPT)

br i1 %9, label %10, label %11 ; A
br label %12 ; B
br label %12 ; C

br指令一共在代码中出现了三次
在这里,我们先介绍一下br指令的用法, br指令的语法为 br + 标志位 + truelabel + falselabel,或者br + label
形如上面代码中 A 用法的转移指令叫做条件转移,如果标志位为1,程序会跳往truelabel标记的basicblock。如果标志位为0,程序会跳往falseblock标记的basicblock。比如,在代码br i1 %9, label %10, label %11中,如果%9的值为1,就会跳转往基本块%10,如果为0,就会跳转往基本块%11

形如上面代码中B,C的用法的转移指令叫做无条件转移,他会在程序运行到此处时无条件跳转到目标基本块。在上面代码中B,C两处的代码都会无条件跳转到基本块%12

如上图所示,%9icmp eq指令(用来判断两个值是否相等,我们会在_推荐使用的指令_一节详细介绍)的结果,如果%7等于%8,那么%9的值就会为1,否则为0。这条指令对应了源代码中的if(a == b)c=5对应了基本块%10c=10对应了基本块%11,这两个基本块运行结束时都需要跳转到目标基本块%12执行后面的语句putint(c)以及return 0

在这里插入图片描述
在这里插入图片描述

第八页-了解常用的代码优化方法

在这里插入图片描述

删除公共子表达式
如果表达式x op y先前已被计算过,并且从先前的计算到现在,x op y中的变量值没有改变,则x op y的这次出现就称为公共子表达式(common subexpression)

删除无用代码
无用代码(Dead-code):其计算结果永远不会被使用的语句

常量合并
如果在编译时刻推导出一个表达式的值是常量,就可以使用该常量来替代这个表达式。该技术被称为 常量合并

代码移动
这个转换的结果是那些 不管循环多少次都得到相同结果的表达式(即循环不变计算,loop-invariant computation),在进入循环之前就对它们进行求值。

强度削弱用较快的操作代替较慢的操作,如用 代替 。(例:2*x ⇒ x+x)

删除归纳变量
对于一个变量x ,如果存在一个正的或负的常数c使得每次x被赋值时它的值总增加c ,那么x就称为归纳变量(Induction Variable)。在沿着循环运行时,如果有一组归纳变量的值的变化保持步调一致,常常可以将这组变量删除为只剩一个

为了优化给定的代码段,我们可以应用几种常见的编译器优化技术。这些包括但不限于常量传播、代数化简、强度削弱和消除冗余代码。下面是按阶段进行的优化步骤,以及每个阶段的代码变化情况:

初始代码

T1 = j - 2;
T2 = 4 * T1;
temp = A[T2];
T3 = j + 2;
T4 = T3 - 2;
T5 = 8 * T4;
T6 = A[T5];

阶段 1: 常量传播和代数化简

  • T1T4 实际上是相同的操作,都是 j - 2
  • T24 * (j - 2),可以直接计算。
  • T3j + 2T4T3 - 2,所以 T4 实际上就是 j
  • T58 * j(因为 T4 现在是 j)。

优化后的代码:

T1 = j - 2; // 或者可以直接在T2中使用j - 2,从而完全省略这一行
T2 = 4 * (j - 2); // 代数化简
temp = A[T2];
// T3 = j + 2; // 不再需要,因为T4和T1相等
// T4 = j; // 不再需要,因为T4和T1相等
T5 = 8 * j; // 代数化简
T6 = A[T5];

阶段 2: 消除冗余代码

  • 因为 T1T4 相等,我们可以消除 T4
  • 直接在 T2T5 的赋值中使用 j - 2j,进一步消除 T1T4

优化后的代码:

T2 = 4 * (j - 2);
temp = A[T2];
T5 = 8 * j;
T6 = A[T5];

阶段 3: 强度削弱

  • 在许多情况下,乘法操作比加法或位移操作更耗时。但在这个例子中,看起来没有直接的机会应用强度削弱。

最终优化后的代码

T2 = 4 * (j - 2);
temp = A[T2];
T5 = 8 * j;
T6 = A[T5];

这个优化过程消除了冗余的计算,简化了表达式,并减少了变量的使用,从而提高了代码的效率。需要注意的是,这些优化假设没有副作用和别名问题(即数组 A 在此期间不会被修改,且 j 是一个不变的值)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1364411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python从入门到网络爬虫(异常处理详解)

前言 异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。一般情况下,在python无法正常处理程序时就会发生一个异常。异常是python对象,表示一个错误。当python脚本发生异常时我们需要捕获处理它,…

【leetcode】力扣热门之反转链表【简单难度】

题目描述 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 用例 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1] 输入:head [1,2] 输出:[2,1] 输入:head [] 输出:[…

软件测试|深入理解SQL FULL JOIN:语法、用法及示例解析

简介 在SQL中,JOIN是一个强大的操作,它允许将两个或多个表中的数据进行关联。SQL提供了多种JOIN类型,其中之一是FULL JOIN。FULL JOIN允许从左表和右表中选择所有记录,并将它们组合在一起。本文将深入探讨SQL FULL JOIN的语法、用…

ASP.NET Core基础之图片文件(二)-WebApi图片文件上传到文件夹

阅读本文你的收获: 了解WebApi项目保存上传图片的三种方式学习在WebApi项目中如何上传图片到指定文件夹中 在ASP.NET Core基础之图片文件(一)-WebApi访问静态图片文章中,学习了如何获取WebApi中的静态图片,本文继续分享如何上传图片。 那么…

单点测距传感器|激光扫描传感器SPR系列安装方法

单点测距传感器|激光扫描传感器可用于对物体进行非接触式距离测量,其十分广泛的应用于工业自动化、生产线、传送带等工业自动化场景中,也可以使用测距传感器进行物体的距离测量和位置检测、AGV和又车的碰撞保护,机器人工作范围的量程检测&…

C++实现单例模式

单例模式: 一种设计模式,它的目的是确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。它适用于需要全局唯一的对象或资源的情况。 23种设计模式种最简单最常见的一种(高频考点) 要求:通过一个…

移动通信原理与关键技术学习(3)

1.什么是相干解调?什么是非相干解调?各自的优缺点是什么? 相干解调需要在接收端有一个与发送端一样的载波(同样的频率和相位),在接收端的载波与发送端载波进行互相关操作,去除载波的影响。相干…

ssm使用web工程的相关知识

不使用框架创建web的两种方式(这里是idea2022.3.2版) 第一种:项目右键点击:add Framwork support选择框架进行创建。 操作步骤: 使用这种方式创建可能会存在的问题: 如果你创建web框架前:在…

【Flutter 开发实战】Dart 基础篇:常见的数据类型

Dart 支持许多数据类型,包括我们常见的 Numbers(数值类型)、Strings(字符串类型)、Booleans(布尔类型),也支持一些包括 Collections(集合类型)、Records&…

嵌入式项目——平衡小车(1)

焊接 驱动板需要焊接的如上图。 陀螺仪8pin排母电机两路排线插口。(个别同学需要焊接)两个电池仓,注意电池仓分正反。 安装 底部电池板 4个 双通尼龙柱M3*224个 尼龙螺钉M3*6电机驱动板

环境搭建 之 Ubuntu 安装

ubuntu-releases-20.04.6安装包下载_开源镜像站-阿里云ubuntu-releases-20.04.6安装包是阿里云官方提供的开源镜像免费下载服务,每天下载量过亿,阿里巴巴开源镜像站为包含ubuntu-releases-20.04.6安装包的几百个操作系统镜像和依赖包镜像进行免费CDN加速…

Matplotlib for C++不完全手册

matplotlib-cpp是Matplotlib(MPL)为C提供的一个用于python的matplotlib绘图库的C包装器。它的构建类似于Matlab和matplotlib使用的绘图API。 However, the function signatures might differ and Matplotlib for C does not support the full functional…

【嵌入式】Makefile 学习笔记记录 | 嵌入式Linux

文章目录 前言一、Makefile的引入——最简单的gcc编译过程二、Makefile的规则三、Makefile的语法3.1、通配符3.2、假想目标 .phony3.3、即时变量 延时变量 四、Makefile的函数4.1、foreach4.2、filter4.3、wildcard4.4、patsubst 五、Makefile升级5.1、包含头文件在内的依赖关系…

商品期货交易中的强行平仓:交易所的规定和风险控制

在商品期货交易中,保证金充足的情况下,一般不会被强行平仓。然而,有几种情况可能会导致强行平仓的发生: 1 持仓超过交易所限仓规定:交易所会设定限仓规定,限制每位投资者的持仓数量。如果超过限仓规定&…

如何使用 CMake 来构建一个共享库(动态库)

tutorial_4/CMakeLists.txt # 声明要求的 cmake 最低版本 cmake_minimum_required( VERSION 2.8 )# 声明一个 cmake 工程 project( HelloSLAM )add_subdirectory(src)tutorial_4/src/CMakeLists.txt #工程添加多个特定的头文件搜索路径 include_directories(include)set(LIBR…

静态路由、代理ARP

目录 静态路由静态路由指明下一跳和指明端口的区别代理ARP 我们知道,跨网络通信需要路由 路由有三种类型: 1.直连路由。 自动产生的路由,当网络设备连接到同一网络时,他们可以自动学习到对方的存在。自动学习相邻网络设备的直连信…

python编程从入门到实践(3+4)操作列表+if语句

文章目录 第四章 列表操作4.1遍历整个列表:可能会发生变化的数值,列表可修改4.1.2遍历中的缩进 4.3创建数值列表4.3.1 使用range()函数range(i,m)输出从i到m-1range(m) 打印从0到m-1 4.3.1 使用…

Java集合框架概念详解

目录 1. 什么是Java集合框架?2. 常用接口介绍3. 常用实现类介绍4. 集合框架的应用场景 前言: Java集合框架是Java编程中最重要的工具之一。它提供了一套强大而灵活的数据结构和算法,用于存储和操作数据。本文将详细介绍Java集合框架的概念、常…

数据结构—环形缓冲区

写在前面,2023年11月开始进入岗位,工作岗位是嵌入式软件工程师。2024年是上班的第一年的,希望今年收获满满,增长见闻。 数据结构—环形缓冲区 为什么要使用环形数组,环形数组比起原来的常规数组的优势是什么&#xf…

CLIP is Also an Efficient Segmenter

表1 复现结果–Seed:70.7245673447014,dCRF:74.85437742935268 误差小于0.5个点,可以接受 表4 复现结果–训练300轮,Val:58.76741354153312,Test:59.18210 感想 VOC全部复现完成&…