LLVM Cpu0 新后端3

news2024/10/7 10:23:28

 想好好熟悉一下llvm开发一个新后端都要干什么,于是参考了老师的系列文章:

LLVM 后端实践笔记

代码在这里(还没来得及准备,先用网盘暂存一下):

链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?pwd=vd6s 提取码: vd6s 

这一章主要增加了大量的算数和逻辑指令,第三章的两节我们放在一起分析了,修改的文件如下:

目录

1. Cpu0ISelLowering.cpp/.h

2. Cpu0InstrInfo.td

3. Cpu0RegisterInfo.td

4. Cpu0SEISelDAGToDAG.cpp/.h

5. Cpu0SEInstrInfo.cpp/.h

6. Cpu0Schedule.td

7. Cpu0Subtarget.cpp


1. Cpu0ISelLowering.cpp/.h

这里设计了类型合法化的声明,使用 setOperationAction 函数,指定将 div 和 rem 在对 i32 类型下的操作做 expand,Cpu0 无法处理 sext_inreg,指定对各类型的sext_inreg做扩展,其实就是指定使用 LLVM 内置的函数来展开他们的实现(位于 TargetLowering.cpp 中)。

在这里特殊处理除法和求余运算的 lowering 操作,实现了一个 performDivRemCombine 函数,这是因为在 DAG 中,除法和求余的节点是同一个节点,叫 ISD::SDIVREM,节点中有个值来表示这个节点是计算商还是计算余数,虽然 Cpu0 后端本身并不关心要计算哪个值,因为都是通过 div 来计算的,但 DAG 一级还是会根据 C 代码的逻辑来区分的。当然我们的输出也需要考虑把哪个值(Hi 或 Lo)返回。div 运算会将商放到 Lo 寄存器,将余数放到 Hi 寄存器。

static SDValue performDivRemCombine(SDNode *N, SelectionDAG& DAG,
                                    TargetLowering::DAGCombinerInfo &DCI,
                                    const Cpu0Subtarget &Subtarget) {
  if (DCI.isBeforeLegalizeOps())
    return SDValue();

  EVT Ty = N->getValueType(0);
  unsigned LO = Cpu0::LO;
  unsigned HI = Cpu0::HI;
  unsigned Opc = N->getOpcode() == ISD::SDIVREM ? Cpu0ISD::DivRem :
                                                Cpu0ISD::DivRemU;

  SDLoc DL(N);

  SDValue DivRem = DAG.getNode(Opc, DL, MVT::Glue,
                               N->getOperand(0), N->getOperand(1));
  SDValue InChain = DAG.getEntryNode();
  SDValue InGlue = DivRem;

  // insert MFLO
  if (N->hasAnyUseOfValue(0)) {
    SDValue CopyFromLo = DAG.getCopyFromReg(InChain, DL, LO, Ty, InGlue);
    DAG.ReplaceAllUsesOfValueWith(SDValue(N, 0), CopyFromLo);
    InChain = CopyFromLo.getValue(1);
    InGlue = CopyFromLo.getValue(2);
  }

  // insert MFHI
  if (N->hasAnyUseOfValue(1)) {
    SDValue CopyFromHi = DAG.getCopyFromReg(InChain, DL, HI, Ty, InGlue);
    DAG.ReplaceAllUsesOfValueWith(SDValue(N, 1), CopyFromHi);
  }

  return SDValue();
}

performDivRemCombine 函数内会将架构无关的DAG节点ISD::SDIVREM/ISD::UDIVREM lower成架构相关的Cpu0ISD::DivRem/Cpu0ISD::DivRemU。然后将对应的对于余数的use点都替换成MFLO,将对于商的use点都替换成MFHI。这个过程是在DAGCombine阶段做的。

2. Cpu0InstrInfo.td

这一部分是这一章节的主要的改动点,增加了大量的算数和逻辑指令的定义。

我们加了 -cpu0-enable-overflow 编译选项,它可以让编译器生成 addu 和 subu指令(这两个指令是会截断加减法结果的)或者 add 和 sub(这两个指令是会抛出溢出错误的)。不加该选项(默认是 false),可以查看汇编代码中,生成了 addu 和 subu 指令;然后增加该选项我们能看到对应的指令变成了 add 和 sub 。

这里是LLVM中中端IR到后端的指令转化形式的简易版本,下述是带上一些关键流程的详细版本:

后半部分的流程其实就是我们前几节课完成的,当中的寄存器分配和指令调度优化不在本课程的范围呢,后续有时间再进一步讲解(其实我也还没彻底打通~)。我们今天主要关注前半部分的流程,我们以Cpu0的位移操作的指令转化为例。

LLVM IR转化成架构无关的DAG的操作主要是在SelectionDAGISel::SelectBasicBlock函数内实现的,函数内通过visit的方式来将每一条指令lower成对应的DAG节点。(对于要遍历各种指令实现不同操作的需求来说,LLVM的visit的方式是很方便的,我们也可以继承LLVM的InstVisitor类,来直接复用重写其visit的功能,这是比较方便的一种方式,也可以自己定义visit,SelectionDAGISel类就是全部自己定义的。)然后在SelectionDAGISel::CodeGenAndEmitDAG函数内会进行各种DAG的优化操作,包括combine和legalize等操作,test_rotate_left函数内将shl和srl指令优化成一条rotl指令就是在combine内实现的。然后会运行到SelectionDAGISel::DoInstructionSelection接口执行指令选择的操作,将之前生成的DAG中的节点,转换成我们在Cpu0架构内定义的相应的节点,最后在生成汇编的时候输出相应的汇编指令。

需要说明的一个设计是,在 cpu032I 中使用 cmp 指令完成比较操作,但在 cpu032II 中使用 slt 指令作为替代,slt 指令比 cmp 指令有优势,它使用通用寄存器来代替 $sw 寄存器,能够使用更少的指令来完成比较运算,比较运算 cmp 指令返回的值是 $sw 寄存器编码值,所以要针对我们需要的做一次转换,比如说我们要计算 a < b,指令中是 cmp $sw, a, b,我们要将 $sw 中的值分析出来,并最终将比较结果放到一个新的寄存器中。虽然 slt 指令返回一个普通寄存器的值,但因为它计算的是小于的结果,所以如果我们需要计算 a >= b,那就要对其结果做取反的运算。

针对这些逻辑运算设计了它们的 pattern 等信息。

// Pat - A simple (but common) form of a pattern, which produces a simple result
// not needing a full list.
class Pat<dag pattern, dag result> : Pattern<pattern, [result]>;

multiclass SeteqPatsCmp<RegisterClass RC> {
  // a == b
  def : Pat<(seteq RC:$lhs, RC:$rhs),
            (SHR (ANDi (CMP RC:$lhs, RC:$rhs), 2), 1)>;
  // a != b
  def : Pat<(setne RC:$lhs, RC:$rhs),
            (XORi (SHR (ANDi (CMP RC:$lhs, RC:$rhs), 2), 1), 1)>;
}
multiclass SeteqPatsSlt<RegisterClass RC, Instruction SLTiuOp, Instruction XOROp,
                        Instruction SLTuOp, Register ZEROReg> {
  // a == b
  def : Pat<(seteq RC:$lhs, RC:$rhs),
            (SLTiuOp (XOROp RC:$lhs, RC:$rhs), 1)>;

  // a != b
  def : Pat<(setne RC:$lhs, RC:$rhs),
            (SLTuOp ZEROReg, (XOROp RC:$lhs, RC:$rhs))>;
}

这里的匹配过程是这样的。对于cmp类型的Cpu032I指令,如果匹配到了 seteq+两个寄存器操作数的指令形式(可以看到上边DAG Node有类似指令),那我们就匹配上了,将这条指令替换成cmp+andi+shr三条Cpu032I指令。那么为什么是这三条指令呢?首先,使用 cmp $sw, a, b 将比较结果的 flag 放到 $sw 寄存器中,$sw 寄存器的最低两位分别是 Z (bit 1)和 N (bit 0),如果 a 与 b 相等,那么 Z = 1, N = 0,如果 a 与 b 不相等,那么 Z = 1, N 可为 0 或 1。这样,我们后边只需要对 $sw 寄存器做与 0b10 的与运算,提取这两位,然后右移 1 位拿到 Z 的值,它的值赋给另一个寄存器,这便是 a == b 的结果。

对于slt类型的Cpu032II指令,如果匹配到了 seteq+两个寄存器操作数的指令形式(可以看到上边DAG Node有类似指令),那我们就匹配上了,将这条指令替换成xor+sltiu两条Cpu032II指令。那么为什么是这两条指令呢?如果a与b相等,异或的结果是0,不等的话异或的结果一定是非0,然后我们将其与1进行无符号的小于比较,无符号的小于比较中,只有0小于1,因此替换成这两条指令。

这就是Pat类模式匹配的过程,第一个操作数是要匹配的模式,第二个操作数是模式匹配上的话输出的结果。

将两种比较的方式都实现,并在 def 时使用 HasSlt 和 HasCmp 来选择定义。Cpu032II 中是同时包含有 slt 和 cmp 指令的,但默认是优先选择 slt 指令。其小于运算不需要做这种映射,因为 slt 指令本身就是计算小于结果的。

3. Cpu0RegisterInfo.td

主要实现了 HI 和 LO 寄存器,以及 HILO 寄存器组,当中包括HI和LO,这个语法的使用我们之前介绍过。

4. Cpu0SEISelDAGToDAG.cpp/.h

主要实现了 selectMULT() 函数,用来处理乘法的高低位运算。在 ISD 中的乘法是区分 MUL 和 MULH 的,也就是用两个不同的 Node 来分别处理乘法返回低 32 位和高 32 位。selectMULT() 会放到 trySelect() 接口函数中,专用来处理 MULH 的特殊情况,并将 HI 作为返回值创建新的 Node。

5. Cpu0SEInstrInfo.cpp/.h

主要实现了 copyPhysReg() 函数,用来生成一些寄存器 move 的操作,会根据要移动的寄存器类型,生成不同的指令来处理。这个函数是基类的虚函数,直接覆盖实现,不需要考虑调用问题。如果目的寄存器和源寄存器都是通用寄存器,会使用 addu 来完成,这是一种通用做法。如果源寄存器是 HI 或 LO,会选择生成 mfhi 或 mflo 来处理。反之,如果目的寄存器是 Hi 或 Lo,会选择生成 mthi 和 mtlo 来处理。这里作为最后的指令选择阶段,可以直接使用 BuildMI 生成 MI 结构的指令。

6. Cpu0Schedule.td

实现了乘除法和 HILO 操作的指令行程类。

7. Cpu0Subtarget.cpp

这个文件中新增了一个控制溢出处理方式的命令行选项:-cpu0-enable-overflow,默认是 false,如果在调用 llc 时的命令行中使用这个选项,则为 true。false 时,表示当算术运算出现溢出时,会触发 overflow 异常,true 时,表示算术运算出现溢出时,会截断运算结果。我们将 add 和 sub 指令设计为溢出时触发 overflow 异常,把 addu 和 subu 设计为不会触发异常,而是截断结果。在 subtarget 中,将命令行选项的结果传入 EnableOverflow 类属性。

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

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

相关文章

MacOS中Latex提示没有相关字体怎么办

在使用mactex编译中文的时候&#xff0c;遇到有些中文字体识别不到的情况&#xff0c;例如遇到识别不到Songti.ttc。其实这个时候字体是在系统里面的&#xff0c;但是只不过是latex没有找到正确的字体路径。 本文只针对于系统已经安装了字体库并且能够用find命令搜到&#xff0…

Effective Java 1 用静态工厂方法代替构造器

知识点上本书需要会Java语法和lang、util、io库&#xff0c;涉及concurrent和function包。 内容上主要和设计模式相关&#xff0c;代码风格力求清晰简洁&#xff0c;代码尽量复用&#xff0c;组件尽量少依赖&#xff0c;错误尽早发现。 第1个经验法则&#xff1a;用静态工厂方…

rust学习(字节数组转string)

最新在写数据传输相关的操作&#xff0c;发现string一个有趣的现象&#xff0c;代码如下&#xff1a; fn main() {let mut data:[u8;32] [0;32];data[0] a as u8;let my_str1 String::from_utf8_lossy(&data);let my_str my_str1.trim();println!("my_str len is…

基于JSP技术的社区生活超市管理系统

你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;MyEclipse开发环境、Tomcat服务器 系统展示 首页 管理员功能模块…

两款 IntelliJ IDEA 的 AI 编程插件

介绍两款 IntelliJ IDEA 的 AI 编程插件&#xff1a;通义灵码和 CodeGeeX。 通义灵码 这是由阿里推出的一个基于通义大模型的 AI 编码助手。 它提供了代码智能生成、研发智能问答等功能。通义灵码经过海量优秀开源代码数据训练&#xff0c;可以根据当前代码文件及跨文件的上下…

Spring运维之boo项目表现层测试匹配响应执行状态响应体JSON和响应头

匹配响应执行状态 我们创建了测试环境 而且发送了虚拟的请求 我们接下来要进行验证 验证请求和预期值是否匹配 MVC结果匹配器 匹配上了 匹配失败 package com.example.demo;import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Auto…

Golang | Leetcode Golang题解之第129题求根节点到叶节点数字之和

题目&#xff1a; 题解&#xff1a; type pair struct {node *TreeNodenum int }func sumNumbers(root *TreeNode) (sum int) {if root nil {return}queue : []pair{{root, root.Val}}for len(queue) > 0 {p : queue[0]queue queue[1:]left, right, num : p.node.Left, …

C++使用thread_local实现每个线程下的单例

对于一个类&#xff0c;想要在每个线程种有且只有一个实例对象&#xff0c;且线程之间不共享该实例&#xff0c;可以按照单例模式的写法&#xff0c;同时使用C11提供的thread_local关键字实现。 在单例模式的基础上&#xff0c;使用thread_local关键字修饰单例的instance&…

离散数学答疑 4

知识点&#xff1a;什么是可结合&#xff1f; 举例A选项&#xff1a; 知识点&#xff1a;可交换性? 知识点&#xff1a;什么是阿贝尔群&#xff1f; 可交换->运算表中的元素关于主对角线对称 二阶子群的表达式 二阶子群作为一个群的子群&#xff0c;其本质是一个包含单位元…

【深度学习】温故而知新4-手写体识别-多层感知机+CNN网络-完整代码-可运行

多层感知机版本 import torch import torch.nn as nn import numpy as np import torch.utils from torch.utils.data import DataLoader, Dataset import torchvision from torchvision import transforms import matplotlib.pyplot as plt import matplotlib import os # 前…

SpringBoot+Vue学科竞赛系统(前后端分离)

技术栈 JavaSpringBootMavenMySQLMyBatisVueShiroElement-UI 角色对应功能 学生教师管理员 功能截图

IO流字符流(FileReader与FileWriter)

目录 FileReader 空参read方法 带参read方法&#x1f447; FileWriter void write(intc) 写出一个字符 void write(string str) 写出一个字符串 void write(string str,int off,int len) 写出一个字符串的一部分 void write(char[] cbuf) …

如何学习Golang语言!

第一部分&#xff1a;Go语言概述 起源与设计哲学&#xff1a;Go语言由Robert Griesemer、Rob Pike和Ken Thompson三位Google工程师设计&#xff0c;旨在解决现代编程中的一些常见问题&#xff0c;如编译速度、运行效率和并发编程。主要特点&#xff1a;Go语言的语法简单、编译…

Bootstrap框架集成ECharts教程

最新公司项目要在原有的基础上增加一些饼状图和柱状图来统计一些数据给客户&#xff0c;下面就是集成的一个过程&#xff0c;还是很简单的&#xff0c;分为以下几步 1、引入ECharts的包 2、通过ECharts官网或者菜鸟教程直接拿示例代码过来修修改改直接用就可以了 注意&#xf…

三维地图Cesium,加载一个模型,模型沿着给定的一组经纬度路线移动

目录 实现效果 实现思路 功能点 选择移动路线 加载模型和移动路线 重新运行 指定位置(经纬度点)开始移动 视角切换 到站提示 运行 停止 联动接口 完整代码 html js逻辑 trainOperation.js sourceData.js gitee仓库项目代码 疑问解答 实现效果 三维地图Cesiu…

pyqt QlineEdit内部增加按钮方法

按钮放在QlineEdit内部&#xff0c;界面更紧凑&#xff0c;体现了按钮和文本框的强关联。 def addButton(self,lineEdit):btn QtWidgets.QPushButton("")icon1 QtGui.QIcon()icon1.addPixmap(QtGui.QPixmap(":/image/images/th.png"), QtGui.QIcon.Norm…

C++【STL】改造红黑树简单模拟实现set map(带你了解set map的底层实现结构)

目录 一、学前铺垫&#xff08;泛型编程&#xff09; 二、改造红黑树 1.红黑树节点的改造 2.insert的改造 3.迭代器的实现 4.完整改造代码 三、set的模拟实现封装 四、map的模拟实现封装 五、完结撒❀ 前言&#xff1a; 下面为了简单模拟实现set map所出现的代码是以…

【JsDoc】JsDoc用法 | 巧妙用法

type type {other} other 接收表达式或字符 1、数组代码提示 1、效果图 1、码 /*** type {Array.<play|paush|next>} */ let music []2、字符串提示 2、效果图 2、码 /*** type {a|b|c}*/ let str

minio的一个基础使用案例:用户头像上传

文章目录 一、minio下载安装&#xff08;Windows&#xff09;二、案例需求分析三、后端接口开发 一、minio下载安装&#xff08;Windows&#xff09; 1. 下载minio服务端和客户端 minio下载地址 2. 手动搭建目录 /minio/binmc.exeminio.exe/data/logs手动创建minio应用程序目…

Linux入门学习(2)

1.相关复习新的指令学习 &#xff08;1&#xff09;我们需要自己创建一个用户&#xff0c;这个用户前期可以是一个root用户&#xff0c;后期使用创建的普通用户 &#xff08;2&#xff09;文件等于文件内容加上文件属性,对于文件的操作就包括对于文件内容的操作和文件属性&…