基于LLVM的Fortran编译器分析
简介
本文内容基于LLVM 13.0.0。
目前基于LLVM的Fortran编译器(或者驱动)有3种,分别是flang、f18和flang-new。
flang是pgfortran的开源版本,基于PGI/NVIDIA的商业Fortran 编译器,它并不从属于LLVM项目。NVIDIA团队在2018年宣布了Fortran的新前端——f18,f18是使用现代 C++ 从头开始编写的,它将与 LLVM 最佳实践紧密结合,并以 LLVM 和 clang 的风格编写,f18已经被纳入为LLVM子项目。flang-new是一款新的flang驱动,在未来将会取代f18驱动。flang-new目前没有实现Fortran程序从源码生成.out的完整过程,f18可以生成.out,但是是借助外部编译器来完成的,默认外部编译器为gfortran。
以下表格展示了f18和flang-new在编译器驱动和前端驱动的细分,编译器驱动程序将允许您控制所有编译阶段(即预处理、前端代码生成、中间/后端代码优化和降级、链接),前端驱动程序将所有前端库粘合在一起,并为前端提供易于使用且直观的接口。
Compiler driver |
Frontend driver |
|
f18 |
f18 |
f18 |
flang-new |
flang-new |
flang-new -fc1 |
在前端驱动方面,flang-new -fc1和f18完全兼容,在编译器驱动方面,flang-new尚不支持代码生成(code-generate),f18调用一个独立的外部Fortran编译器来生成代码。
编译安装
flang
根据github上的介绍,安装flang需要下载flang项目(https://github.com/flang-compiler/flang)和定制的LLVM项目(https://github.com/flang-compiler/classic-flang-llvm-project)。
编译时,先用gcc编译安装定制的LLVM,再用生成的clang编译安装libpgmath库和flang。编译LLVM时,需要把clang和openmp都装上,因为flang需要使用到openmp库。
编译安装成功后,flang相关的可执行文件也放在LLVM的bin目录下,其中flang1负责输出PGI IR,flang2负责输出LLVM IR。
f18&flang-new
宏FLANG_BUILD_NEW_DRIVER控制是否需要安装flang-new,默认为on,flang-new依赖于clang驱动,所以在安装flang-new的时候也需要安装clang,通过LLVM_ENABLE_PROJECTS把clang设置上,如果FLANG_BUILD_NEW_DRIVER设置为off,则不需要安装clang。
在f18被移除之后,宏FLANG_BUILD_NEW_DRIVER也会被一并删除,这意味着接下来LLVM中flang驱动对clang的依赖将是必须和永久的。
cmake -G "Unix Makefiles"
-DLLVM_ENABLE_PROJECTS='clang;flang'
-DCMAKE_INSTALL_PREFIX=../x86_tools
-DCMAKE_BUILD_TYPE=Release
-DLLVM_TARGETS_TO_BUILD=X86
../llvm
编译安装成功后在bin目录下生成了以下两个flang驱动——f18和flang-new:
图中的flang并不是独立的可执行文件,不能通过gdb被调试,目前在使用时会被扩展为f18,在未来会根据FLANG_BUILD_NEW_DRIVER的设置被扩展为flang-new,同样地,flang_fc1被扩展为flang-new -fc1。
原理介绍
flang
flang在PGI Fortran编译器的基础之上,新增了将PGI中间表示转换为LLVM中间表示的能力,并提供了PGI Fortran的运行时,有了LLVM中间表示后就可以利用起LLVM的后端功能,从而进一步生成二进制文件。可以说,flang也是借助了外部编译器来完成Fortran的编译。
如前文所述,flang1负责输出PGI IR,flang2负责输出LLVM IR。flang1包含以下阶段:
1.扫描提取文本token
2.创建语法树和符号表
3.转换ASTcanonical为AST
4.将一般AST转换为优化的AST
5.创建AST ILM文件,即PGI IR1
flang2包含以下阶段:
1.ILM扩展为ILI文件,即PGI IR2
2.优化ILI文件
3.将ILI优化为LLVM IR
flang-new
编译阶段说明
官方文档表明flang编译分为以下8个阶段:
1.预扫描和预处理
flang-new -fc1 -E src.f90
这一阶段与一般的编译器的预处理阶段是一致的,操作包括宏替换、删除空格和注释等。
2.解析
flang-new -fc1 -fdebug-dump-parse-tree src.f90
将第1步中的输出转储为解析树
flang-new -fc1 -fdebug-unparse src.f90
将解析树转换为标准的Fortran源码
3.验证标签并规范化Do语句
4.解析名称
flang-new -fc1 -fdebug-dump-symbols src.f90
5.检查DO CONCURRENT约束
6.编写模块文件
7.分析表达式和任务
8.生成中间表示
实际上,由于开发尚未完成,目前的f18和flang-new还不能生成LLVM中间表示。
编译器驱动
flang-new的编译器驱动的主入口点的实现在flang/tools/flang-driver/driver.cpp中,它是基于clang的驱动库来实现的,这样的好处在于以下2点:
1. 受益于clang对各种目标、平台和操作系统的支持
2. 利用clang驱动LLVM中各种后端以及链接器、汇编器能力,所有的flang驱动器选项和clang的选项都定义在clang/include/clang/Driver/Options.td里,对于两者通用的选项,定义是同等共享的。
基于clangDriver的编译器驱动通过创建跟大量编译阶段相关的动作(action)来工作,比如clang::driver::Action::ActionClass枚举里定义的 PreprocessJobClass
, CompileJobClass
, BackendJobClass
和LinkJobClass
和LinkJobClass
,以及一些比较特殊的不直接映射到常见编译步骤的动作,比如MigrateJobClass
和InputClass
。具体运行哪个动作,由编译选项决定,比如:
- -E表示PreprocessJobClass
- -c表示CompileJobClass
在大多数情况下,驱动会创建一个关于动作(action)/任务(job)/阶段(phase)的链(chain)来串起整个流程,可以使用-ccc-print-phases选项打印出驱动器为当前编译所生成的序列:
flang-new -ccc-print-phases -c file.f +- 0: input, "file.f", f95-cpp-input +- 1: preprocessor, {0}, f95 +- 2: compiler, {1}, ir +- 3: backend, {2}, assembler 4: assembler, {3}, object |
前端驱动
flang-new的前端驱动程序是用户和flang前端之间的主要接口,主入口点fc1_main在flang/tools/flang-driver/driver.cpp里实现,通过flang-new -fc1访问。前端驱动程序一次只会运行一个动作(action),如果指定多个操作选项,则仅最后一个有效。
源码分析
flang
在编译安装后的bin目录中可以看到flang可执行文件是指向clang的,所以当执行flang时,main函数进的是clang的driver.cpp(clang/tools/driver/driver.cpp),通过以下代码解析当前需要使用flang:
auto TargetAndMode = ToolChain::getTargetAndModeFromProgramName(argv[0]);
此时的TargetAndMode打印出来为:
$1 = {TargetPrefix = "", ModeSuffix = "flang", DriverMode = 0x118ea194 "--driver-mode=flang", TargetIsValid = false}
与常规clang驱动流程的不同之处在于,flang会使用ClassicFlang.cpp中的ConstructJob()函数来组装flang的任务,该函数中指定了一系列调flang1和flang2所需要的参数和宏等内容,然后添加任务。
……const char *UpperExec = Args.MakeArgString(getToolChain().GetProgramPath("flang1"));……C.addCommand(std::make_unique<Command>(JA, *this, UpperExec, UpperCmdArgs, Inputs));const char *UpperExec = Args.MakeArgString(getToolChain().GetProgramPath("flang2"));……C.addCommand(std::make_unique<Command>(JA, *this, UpperExec, UpperCmdArgs, Inputs));
有了上面两个任务,Driver会调到以下函数逐个执行:
void Compilation::ExecuteJobs(const JobList &Jobs,FailingCommandList &FailingCommands) const {// According to UNIX standard, driver need to continue compiling all the// inputs on the command line even one of them failed.// In all but CLMode, execute all the jobs unless the necessary inputs for the// job is missing due to previous failures.for (const auto &Job : Jobs) {if (!InputsOk(Job, FailingCommands))continue;const Command *FailingCommand = nullptr;if (int Res = ExecuteCommand(Job, FailingCommand)) {FailingCommands.push_back(std::make_pair(Res, FailingCommand));// Bail as soon as one command fails in cl driver mode.if (TheDriver.IsCLMode())return;}}}
最终会传递给llvm::sys::ExecuteAndWait()函数。
return llvm::sys::ExecuteAndWait(Executable, Args, Env, Redirects,/*secondsToWait*/ 0,/*memoryLimit*/ 0, ErrMsg, ExecutionFailed);
完整调用栈如下图所示,flang1和flang2是完全一致的。
f18
f18的main函数在flang/tools/f18/f18.cpp中,在main函数中通过F18_FC环境变量确定了外部Fortran编译器,对参数做了一系列准备后,通过Link()->Exec()最终调到llvm/lib/Support/Program.cpp中的llvm::sys::ExecuteAndWait()函数实现程序的执行。未来f18.cpp将会被移除,与之相关的代码也将被更新或删除。
int main(int argc, char *const argv[]) {atexit(CleanUpAtExit);DriverOptions driver;const char *F18_FC{getenv("F18_FC")};driver.F18_FCArgs.push_back(F18_FC ? F18_FC : "gfortran");bool isPGF90{driver.F18_FCArgs.back().rfind("pgf90") != std::string::npos};……if (!driver.compileOnly && !objlist.empty()) {Link(liblist, objlist, driver);}return exitStatus;}
flang-new
flang-new没有调用外部Fortran编译器,而是自己组织action,同样也是调到llvm::sys::ExecuteAndWait()函数实现程序的执行,但由于尚未开发完成,在ExecuteAction()的时候直接报错。
void EmitObjAction::ExecuteAction() {CompilerInstance &ci = this->instance();unsigned DiagID = ci.diagnostics().getCustomDiagID(clang::DiagnosticsEngine::Error, "code-generation is not available yet");ci.diagnostics().Report(DiagID);}
基于LLVM的Fortran编译器分析相关推荐
- 基于LLVM编译器的IDA自动结构体分析插件
引用 这篇文章旨在介绍一款对基于LLVM的retdec开源反编译器工具进行二次开发的IDA自动结构体识别插件实现原理分析 文章目录 引用 简介 源码分析 LLVM编译器简介 Retdec源码分析 Kl ...
- 基于LLVM的编译原理简明教程 (1) - 写编译器越来越容易了
基于LLVM的编译原理简明教程 (1) - 写编译器越来越容易了 进入21世纪,新的编程语言如雨后春笋一样不停地冒出来.需求当然是重要的驱动力量,但是在其中起了重要作用的就是工具链的改善. 2000年 ...
- 非线性有限元:基本理论与算法及基于Python、Fortran程序实现与案例分析实践技术
有限单元法在岩土工程问题中应用非常广泛,很多商业软件如Plaxis/Abaqus/Comsol等都采用有限单元解法.尽管各类商业软件使用方便,但其使用对用户来说往往是一个"黑箱子" ...
- 非线性有限元:基本理论与算法及基于Python、Fortran程序实现与案例分析
非线性有限元:基本理论与算法及基于Python.Fortran程序实现与案例分析 (qq.com) 有限单元法在岩土工程问题中应用非常广泛,很多商业软件如Plaxis/Abaqus/Comsol等都采 ...
- 岩土工程--非线性有限元:基本理论与算法及基于Python、Fortran程序实现与案例分析
非线性有限元:基本理论与算法及基于Python.Fortran程序实现与案例分析实践技术 有限单元法在岩土工程问题中应用非常广泛,很多商业软件如Plaxis/Abaqus/Comsol等都采用有限单元 ...
- 转:GCC,LLVM,Clang编译器对比
GCC,LLVM,Clang编译器对比 转自: http://www.cnblogs.com/qoakzmxncb/archive/2013/04/18/3029105.html 在XCode中,我们 ...
- 【转载】基于LLVM Pass实现控制流平坦化
基于LLVM Pass实现控制流平坦化 文章目录 基于LLVM Pass实现控制流平坦化 0x00. 什么是LLVM和LLVM Pass 0x01. 首先写一个能跑起来的LLVM Pass 0x02. ...
- C语言头文件下载迅雷,LLVM汇编|clang llvm(C语言编译器)下载v3.4 免费版 - 欧普软件下载...
LLVM汇编是一款免费的构架编译器框架系统,采用C++编写而来,软件采用现代化的设计,和语言无关的中间代码,方便的进行编程语言的优化编译工作.此外小编还提供了LLVM安装教程,有需要的朋友赶快下载吧! ...
- 开源的fortran编译器LFortran
基于LLVM,现在是pre alpha.很快就有发布版 LFortran 是一款现代开源 (BSD 许可) 交互式fortran编译器,建在 LLVM 之上.它可以以交互式身份执行用户代码,以便进行探 ...
最新文章
- Google综述:细数Transformer模型的17大高效变种
- CA ARCserve Backup系列(3)—安装代理(Linux篇)
- 面向程序员的数据库访问性能优化法则
- 模拟浏览器自动化测试工具Selenium之六设置代理篇
- 在 Java 中,为什么需要创建内部类对象之前需要先创建外部类对象
- 通用业务流水号功能设计
- 新电子书:解决生产中Java应用程序错误的完整指南
- [css] 举例说明如何从html元素继承box-sizing?
- Reason: image not found
- Windows坐标系统
- html之文档的头部和元数据定义(上)
- AD下安装Exchange及简单收发邮件【视频】
- Maximum Submatrix Largest Rectangle
- zabbix client安装配置执行
- Atitit 高性能架构法艾提拉著作 目录 1. 前期可以立即使用的技术	2 2. 分离法	3 2.1. Web db分离	3 2.2. 读写分离	4 2.3. CDN加速技术	4 2.4. 动静分
- Octotree插件安装
- Java并发之-队列同步器AQS
- 「缠师课后回复精选」第14课: 喝茅台的高潮程序!
- 怎么样才能查看别人的IP地址
- **旅行-interveiw