Creating an LLVM Backend for the Cpu0 Architecture
Backend structure
• TargetMachine structure
• Add AsmPrinter
• Add Cpu0DAGToDAGISel class
• Handle return register $lr
• Add Prologue/Epilogue functions
o Concept
o Prologue and Epilogue functions
o Handle stack slot for local variables
o Large stack
• Data operands DAGs
• Summary of this Chapter

Fig. 14 Cpu0 backend class access link
图 14 Cpu0 后端类访问链接
添加了大多数 Cpu0 后端类,代码可以概括为图14。类 Cpu0Subtarget 提供接口 getInstrInfo(),getFrameLowering(),…,获取其它 Cpu0 类。大多数类(如 Cpu0InstrInfo,Cpu0RegisterInfo 等),都有 Subtarget 引用成员,允许通过 Cpu0Subtarget 接口,访问其它类。如果后端模块没有 Subtarget 引用,这些类仍然可以通过 static_cast<Cpu0TargetMachine &>™.getSubtargetImpl(),通过 Cpu0TargetMachine(通常使用 TM 作为符号)访问 Subtarget 类。一旦获取到 Subtarget 类,后端代码就可以访问其它类。对于 Cpu0SExx 类的名称,表示标准32 位类。遵循 llvm 3.5 Mips 后端风格。Mips 后端使用 Mips16,MipsSE 和 Mips64 文件/类名称,分别为 16,32 和 64 位架构定义类。
图15显示了 Cpu0 TableGen 的继承关系。后端类可以包含 TableGen 生成的类并从中继承。Cpu0后端的所有TableGen生成的类,都在build/lib/Target/Cpu0/*.inc中。通过 C++ 继承机制,TableGen 为后端程序员,提供了一种灵活的方式,使用生成的代码。如果需要,程序员有机会覆盖此功能。

图 15继承自 TableGen 生成文件的 Cpu0 类
Fig. 15 Cpu0 classes inherited from TableGen generated files
由于llvm有很深的继承树,这里就不深挖了。受益于继承树结构,不需要在指令,帧/堆栈和选择 DAG 类中,实现太多代码,很多代码是由父类实现的。llvm-tblgen 根据Cpu0InstrInfo.td 的信息,生成 Cpu0GenInstrInfo.inc。Cpu0InstrInfo.h 通过定义“#define GET_INSTRINFO_HEADER”,从 Cpu0GenInstrInfo.inc 中,提取需要的代码。使用TabelGen,通过编译器开发的模式匹配理论,减少了后端的代码量。这在 “DAG”和“指令选择”中,都有解释。
To make the registration clearly, summary as the following diagram, Fig. 16.

图 16 Tblgen 为 Cpu0 后端生成文件
Fig. 16 Tblgen generate files for Cpu0 backend
createCpu0MCAsmInfo() 为目标 TheCpu0Target 和 TheCpu0elTarget,注册了类 Cpu0MCAsmInfo 的对象。TheCpu0Target 用于大端,TheCpu0elTarget 用于小端。Cpu0MCAsmInfo 派生自 MCAsmInfo,一个 llvm 内置类。大多数代码在父级中实现,后端通过继承重用这些代码。
createCpu0MCInstrInfo() 实例化 MCInstrInfo 对象X,通过 InitCpu0MCInstrInfo(X) ,进行初始化。由于 InitCpu0MCInstrInfo(X) 是在 Cpu0GenInstrInfo.inc 中定义的,所以这个函数会添加指定的 Cpu0InstrInfo.td 中的信息。
createCpu0MCInstPrinter() 实例化 Cpu0InstPrinter,支持打印功能的说明。
createCpu0MCRegisterInfo()类似于“MC指令信息的注册函数”,初始化了Cpu0RegisterInfo.td中,指定的寄存器信息。共享来自指令/寄存器 td 描述的一些值,如果与 td 描述文件一致,无需在 Initialize 例程中,再次指定。
createCpu0MCSubtargetInfo() 实例化 MCSubtargetInfo 对象,使用 Cpu0.td 信息,进行初始化。
根据“目标注册部分” ,可以通过动态注册机制,在 LLVMInitializeCpu0TargetMC() 按需注册 Cpu0 后端类,如上述函数 LLVMInitializeCpu0TargetMC()。
现在,可以使用 AsmPrinter,如下所示,
Summary above translation into Table: Chapter 3 .bc IR instructions.
Table 10 Chapter 3 .bc IR instructions
以上摘要翻译成表:第 3 章 .bc IR 指令。
表 10第 3 章 .bc IR 指令

• 下层:初始选择 DAG(Cpu0ISelLowering.cpp,LowerReturn(…))
• ISel:指令选择
• RVR:重写虚拟寄存器,删除 CopyToReg
• AsmP:Cpu0 Asm 打印
• Post-RA:Post-RA 伪指令扩展pass
从上面的llc -print-before-all -print-after-all显示,ret在stage Optimized legalized selection DAG中,翻译成 Cpu0ISD::Ret,最后翻译成Cpu0指令ret。由于 ret 使用常量 0(在此示例中为ret i32 0),因此常量 0通过Cpu0InstrInfo.td 中定义的以下模式,转换为“addiu $2, $zero, 0”。
Cpu0ISelLowering.cpp 的函数LowerReturn() 正确处理返回变量。Chapter3_4/Cpu0ISelLowering.cpp在LowerReturn()中创建Cpu0ISD::Ret节点,当llvm系统遇到C的return关键字时调用。创建 DAG(Cpu0ISD::Ret (CopyToReg %X, %V0, %Y), %V0, Flag)。由于 V0 寄存器,在 CopyToReg 中分配,Cpu0ISD::Ret 使用 V0,带有 V0 寄存器的 CopyToReg,继续存在,不会在任何后续优化步骤中删除。如果使用“return DAG.getNode(Cpu0ISD::Ret, DL, MVT::Other, Chain, DAG.getRegister(Cpu0::LR, MVT::i32));”,不是“返回 DAG.getNode (Cpu0ISD::Ret, DL, MVT::Other, &RetOps[0], RetOps.size());”,V0 寄存器将不会生效,DAG(CopyToReg %X, %V0, %Y)将在以后的优化步骤中删除。
添加序言/尾声功能
概念
以下来自 tricore_llvm.pdf 部分“4.4.2 非静态寄存器信息”。
对于某些目标架构,目标架构的寄存器集的某些方面,取决于可变因素,必须在运行时确定。不能从 TableGen,描述静态生成——尽管在 TriCore 后端,大部分是可能的。有以下几点:
• 调用者保存的寄存器。通常,ABI 指定一组寄存器,如果内容在执行期间可能被修改,函数必须在进入时,保存这些寄存器,在返回时,恢复这些寄存器。
• 保留寄存器。尽管 TableGen 文件中,已经定义了一组不可用的寄存器,TriCoreRegisterInfo 包含一个方法,用于在位向量中,标记所有不可分配的寄存器编号。
实现了以下方法:
• emitPrologue() 在函数的开头,插入序言代码。由于 TriCore 的上下文模型,这是一项微不足道的任务,不需要手动保存任何寄存器。唯一需要做的,通过递减堆栈指针,为函数的堆栈帧保留空间。如果函数需要一个帧指针,帧寄存器 %a14 被预先设置为堆栈指针的旧值。
• emitEpilogue() 旨在发出指令,在从函数返回之前,销毁堆栈帧,恢复所有先前保存的寄存器。由于 %a10(堆栈指针),%a11(返回地址)和 %a14(帧指针,如果有),都是上层上下文的一部分,根本不需要结尾代码。所有清理操作,都由 ret 指令隐式执行。
• 对于引用堆栈槽中,一个数据字的每条指令,都调用消除帧索引()。代码生成器之前的所有过程,都通过抽象帧索引和立即偏移量,寻址堆栈槽。此函数的目的,将这样的引用转换为寄存器-偏移对。根据包含指令的机器函数,是否具有固定或可变堆栈帧,使用堆栈指针 %a10,或帧指针 %a14,作为基址寄存器。相应计算偏移量。图 17展示了两种情况下,堆栈槽的寻址方式。
如果受影响指令的寻址模式,由于偏移量太大,无法处理该地址(偏移字段对于 BO 寻址模式,有 10 位,对于 BOL 模式,有 16 位),发出一系列指令,显式计算有效地址。临时结果,放入一个未使用的地址寄存器。如果没有可用的,清除已占用的地址寄存器。LLVM 的框架提供了一个名为 RegScavenger 的类,负责处理所有细节。
able 11 Handle return register lr
表 11处理返回寄存器 lr

图 17位于堆栈上的变量 a 的寻址。如果堆栈帧具有可变大小,必须相对于帧指针寻址槽
Fig. 17 Addressing of a variable a located on the stack. If the stack frame has a variable size, slot must be addressed relative to the frame pointer

Table 12 Backend functions called in PrologEpilogInserter.cpp
表 12 PrologEpilogInserter.cpp 中调用的后端函数

File PrologEpilogInserter.cpp includes the calling of backend functions spillCalleeSavedRegisters(), emitProlog(), emitEpilog() and eliminateFrameIndex() as follows,
文件 PrologEpilogInserter.cpp,包括调用后端函数,spillCalleeSavedRegisters(), emitProlog(), emitEpilog() ,eliminateFrameIndex()。
Table 13 Cpu0 stack adjustment instructions before replace addiu and shl with lui instruction

Cpu0AnalyzeImmediate.cpp递归方式编写,逻辑上有点复杂。不过前端编译,用到了递归技巧。不跟踪代码,列出“表:用lui指令替换addiu和shl之前的Cpu0堆栈,调整指令”和“表:用lui指令替换addiu和shl之后的Cpu0堆栈,调整指令”中的堆栈大小和指令。
表13用lui指令,替换addiu和shl前的cpu0栈调整指令。


Table 14 Cpu0 stack adjustment instructions after replace addiu and shl with lui instruction

由于 Cpu0 堆栈是 8 字节对齐,从 0x7ff9 到 0x7fff 的地址,不可能存在的。
假设 sp = 0xa0008000,stack size = 0x90008000, (0xa0008000 - 0x90008000) => 0x10000000。使用 Cpu0 Prologue 说明,进行验证,如下所示,

  1. “addiu $1, $zero, -9” => ($1 = 0 + 0xfffffff7) => $1 = 0xfffffff7.
  2. “shl $1, $1, 28;” => $1 = 0x70000000.
  3. “addiu $1, $1, -32768” => $1 = (0x70000000 + 0xffff8000) => $1 = 0x6fff8000.
  4. “addu $sp, $sp, $1” => $sp = (0xa0008000 + 0x6fff8000) => $sp = 0x10000000.
    使用 sp = 0x10000000,堆栈大小stack size = 0x90008000 的 Cpu0 Epilogue 指令,进行验证。
  5. “addiu $1, $zero, -28671” => ($1 = 0 + 0xffff9001) => $1 = 0xffff9001.
  6. “shl $1, $1, 16;” => $1 = 0x90010000.
  7. “addiu $1, $1, -32768” => $1 = (0x90010000 + 0xffff8000) => $1 = 0x90008000.
  8. “addu $sp, $sp, $1” => $sp = (0x10000000 + 0x90008000) => $sp = 0xa0008000.
    Cpu0AnalyzeImmediate::GetShortestSeq() ,将调用 Cpu0AnalyzeImmediate:: ReplaceADDiuSHLWithLUi() ,仅用单个指令 lui,替换 addiu 和 shl。
    假设 sp = 0xa0008000 和堆栈大小 = 0x90008000,那么 (0xa0008000 - 0x90008000) => 0x10000000。使用 Cpu0 Prologue 说明进行验证,如下所示,
  9. “lui $1, 28671” => $1 = 0x6fff0000。
  10. “ori $1, $1, 32768” => $1 = (0x6fff0000 + 0x00008000) => $1 = 0x6fff8000。
  11. “addu $sp, $sp, $1” => $sp = (0xa0008000 + 0x6fff8000) => $sp = 0x10000000。
    使用 sp = 0x10000000 和堆栈大小 = 0x90008000 的 Cpu0 Epilogue 指令进行验证,如下所示,
  12. “lui $1, 36865” => $1 = 0x90010000。
  13. “addiu $1, $1, -32768” => $1 = (0x90010000 + 0xffff8000) => $1 = 0x90008000。
  14. “addu $sp, $sp, $1” => $sp = (0x10000000 + 0x90008000) => $sp = 0xa0008000。
    表 15 llvm 后端阶段的函数
    able 15 Functions for llvm backend stages

在“添加 Cpu0DAGToDAGISel 类”部分的指令,添加了一个pass。可以将代码嵌入到其它类似的pass中。有关信息,请查看 CodeGen/Passes.h。根据llc -debug-pass=Structure 指示的功能单元,调用pass。
已经完成了一个简单的 cpu0 编译器,只支持ld, st,addiu,ori,lui,addu,shl和ret 8 条指令。
可能会想“在编写了这么多代码之后,只需得到这 8 条指令!”。重点是已经为 Cpu0 目标机,创建了一个框架(llvm 后端结构类继承树)。有超过 3000 行带有注释的源代码,包括文件 .cpp,.h,.td 和 CMakeLists.txt。可以通过命令计数wc find dir -name *.cpp对于文件 .cpp,.h,.td,*.txt。LLVM 前端,总共有 700 行源代码,没有注释。实际上,编写后端是启动缓慢,但运行很快。Clang 在 clang/lib 目录中,有超过 500,000 行,带有注释的源代码,包括 C++ 和 Obj C 支持。llvm 3.1 的 Mips 后端,只有 15,000 行,带有注释。即使是复杂的X86 CPU,外有CISC,内有RISC(微指令),在llvm 3.1中,也只有45000行注释。

Fig. 20 Code generation and execution flow
图20的上半部分,生成和执行计算机程序,工作流程和软件包。IR代表中间表示。中间部分是工作流程。除了clang,其它块都需要扩展,进行新的后端开发(许多后端也扩展clang,Cpu0后端没有这个需求)。实现了黄框部分。该图的绿色部分,用于 Cpu0 后端的 lld 和 elf2hex,可以在http://jonathan2251.github.io/lbt/index.html上找到 。十六进制是 ascii 文件格式,使用“0”到“9”和“a”到“f”,表示十六进制值,因为 Verilog 语言机器,用作输入文件。

参考链接:
http://jonathan2251.github.io/lbd/ctrlflow.html

CPU架构的llvm后端相关推荐

  1. 从LLVM说起,有关CPU架构,JIT等

    文章目录 LLVM CPU架构 PowerPC ARM X86 MIPS 编译执行和解释执行 JIT编译 LLVM LLVM(Low Level Virtual Machine,不过官方为了避免混淆已 ...

  2. Tencent JDK 国产化CPU架构支持分享

    GIAC(GLOBAL INTERNET ARCHITECTURE CONFERENCE)是长期关注互联网技术与架构的高可用架构技术社区和msup推出的,面向架构师.技术负责人及高端技术从业人员的年度 ...

  3. 【大话存储】多CPU架构变迁, SMP,NUMA,MPP

    多CPU架构演进 对称多处理器结构:(SMP,Symmetric Multi-Processor) 服务器最开始的时候是单CPU,然后才进化到了双CPU甚至多CPU的SMP架构.所谓SMP架构指的是多 ...

  4. 从CPU架构--x86架构和arm架构处理器--功耗

    目录: 1.两种cpu架构:冯洛伊曼和哈佛 2.x86架构和arm架构分析 3.x86架构和arm架构功耗探究 一.两种cpu架构: 目前主流的cpu 处理器都采用了冯洛伊曼架构或者哈佛架构,那么这和 ...

  5. Firefox 的User Agent 将移除 CPU 架构信息

    Mozilla 计划从 Firefox 的 User Agent(用户代理)和几个支持的 API 中移除 CPU 架构信息,以减少 Firefox 用户的"数字指纹".Web 浏览 ...

  6. (转)从CPU架构和技术的演变看GPU未来发展

    泡泡网显卡频道5月28日 自从AMD提出Fusion(融聚)的概念.NVIDIA加大力度推广GPU通用计算.Intel率先将CPU和GPU整合在一起之后,大家就会发现CPU和GPU从没如此亲密无间过, ...

  7. MongoDB的NUMA CPU架构问题

    NUMA是多核心CPU架构中的一种,其全称为Non-Uniform MemoryAccess,简单来说就是在多核心CPU中,机器的物理内存是分配给各个核的. 在NUMA架构的机子上启动mongodb进 ...

  8. bad cpu type in executable_【简讯】Intel将每5年重新开发一次CPU架构;华为EMUI 11曝光…...

    Intel将每5年重新开发一次CPU架构 日前,Intel TSCG高级副总裁.硅工程总经理.CPU大牛Jim Keller在视频采访中谈到了CPU研发的问题,之前Intel虽然有过Tick-Tock ...

  9. 如何查询当前手机的cpu架构,so库导入工程又出异常了?

    如何查询当前手机的cpu架构,so库导入工程又出异常了? 参考文章: (1)如何查询当前手机的cpu架构,so库导入工程又出异常了? (2)https://www.cnblogs.com/woaixi ...

最新文章

  1. C++——构造函数(拷贝构造,拷贝复制),析构函数,操作符重载
  2. 机器人时代的资本主义:21世纪的工作,收入和财富
  3. 功能接口简介–在Java 8中重新创建的概念
  4. 输出 Hello World 混乱C语言的源代码
  5. @RequiresPermissions 注解说明
  6. oracle增加字段为主键自增_在 Oracle 中设置自增列
  7. day11_用例执行顺序、跳过用例skip用法
  8. mysql where substr_mysql – 在WHERE子句中使用substr的SELECT语句
  9. 分布式应用CAP理论
  10. 收获,不止SQL优化——抓住SQL的本质--第十一章
  11. 第2节 storm实时看板案例:12、实时看板综合案例代码完善;13、今日课程总结...
  12. RAC性能分析 - gc buffer busy acquire 等待事件
  13. 【Redis】Redis中使用Lua脚本
  14. 郑州市城市建成区20年时空变化特征提取与分析
  15. 简单的在线出入库管理用哪个系统好
  16. 英雄联盟加载的时候特卡,排查记录:win10那些破玩意
  17. 【wps】wps怎样删除中间的一页?
  18. 详细了解 Android 巧用 flexboxLayout 布局
  19. 亿美软通一键登录升级:扩大适用范围、更高安全保障
  20. Android 渲染机制——SurfaceFlinger

热门文章

  1. 设置linux初始root密码
  2. Jquery DIV滚动至浏览器顶部后固定不动代码
  3. 2022-2028年中国瓷砖粘结剂行业市场研究及前瞻分析报告
  4. docker通过镜像方式安装tomcat
  5. Python 中 (,|)和(and,or)之间的区别 逻辑判断
  6. virtualenv创建虚拟环境为主_多版本
  7. 嵌入式开发在过去20年中是如何演变的
  8. 道路场景语义分割算法
  9. Centos7 下安装python3及卸载
  10. Python案例:使用XPath的爬虫