[Day2-2] 体验华为方舟编译器:IR测试

上一篇我们讲到完成了方舟编译器的编译过程。目前华为只开源了方舟编译器的IR部分,我们来通过一段简单的代码来测试一下吧。

测试C++代码:

#include <iostream>

using namespace std;

int main() {
    int n;
    cout << "The Answer to Life, the Universe and Everything is:  ";
    cin >> n;
    if (n == 42) {
        cout << "Find highly intelligent lifeform! Start the destruction process!" << endl;
    }
    // 这里为了测试编译器效果
    else if (n != 42) {
        cout << "Yes, you are right!" << endl;
    }
    return 0;
}

文件保存为HelloWorld.cpp。

先用g++测试一下:

g++ HelloUniverse.cpp -o Hello
./Hello

分别测试输入42和10086的情况:

The Answer to Life, the Universe and Everything is:  42
Find highly intelligent lifeform! Start the destruction process!
The Answer to Life, the Universe and Everything is:  42
Find highly intelligent lifeform! Start the destruction process!

看起来是正常运行的。

我们来看一下LLVM下这段代码的IR:

运行

clang -S -emit-llvm HelloUniverse.cpp

输出了ll文件,我们重点看一下cpp代码IR的部分:

; Function Attrs: noinline norecurse optnone uwtable
define dso_local i32 @main() #4 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  %3 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(%"class.std::basic_ostream"* dereferenceable(272) @_ZSt4cout, i8* getelementptr inbounds ([54 x i8], [54 x i8]* @.str, i32 0, i32 0))
  %4 = call dereferenceable(280) %"class.std::basic_istream"* @_ZNSirsERi(%"class.std::basic_istream"* @_ZSt3cin, i32* dereferenceable(4) %2)
  %5 = load i32, i32* %2, align 4
  %6 = icmp eq i32 %5, 42
  br i1 %6, label %7, label %10

; <label>:7:                                      ; preds = %0
  %8 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(%"class.std::basic_ostream"* dereferenceable(272) @_ZSt4cout, i8* getelementptr inbounds ([65 x i8], [65 x i8]* @.str.1, i32 0, i32 0))
  %9 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZNSolsEPFRSoS_E(%"class.std::basic_ostream"* %8, %"class.std::basic_ostream"* (%"class.std::basic_ostream"*)* @_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_)
  br label %17

; <label>:10:                                     ; preds = %0
  %11 = load i32, i32* %2, align 4
  %12 = icmp ne i32 %11, 42
  br i1 %12, label %13, label %16

; <label>:13:                                     ; preds = %10
  %14 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(%"class.std::basic_ostream"* dereferenceable(272) @_ZSt4cout, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.2, i32 0, i32 0))
  %15 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZNSolsEPFRSoS_E(%"class.std::basic_ostream"* %14, %"class.std::basic_ostream"* (%"class.std::basic_ostream"*)* @_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_)
  br label %16

; <label>:16:                                     ; preds = %13, %10
  br label %17

; <label>:17:                                     ; preds = %16, %7
  ret i32 0
}


经过测试,方舟编译器似乎不支持C++,或者是没有开源?我上网查证了一下,少数几篇似乎有意义的文档都在说似乎C++的RT没有放出来(这就很神奇了),所以我们测试一下Java:

以上文件保存为HelloUniverse.java,然后通过OpenJDK编译运行:

javac HelloUniverse.java
java Main

同C++进行测试,输出相同,略过。
然而,使用方舟编译器总是不能成功运行,且输出都是:

Error 40: Init Fail!

有趣的是,如果执行方舟编译器时指定了错误的参数,错误码也是40。

那么今天先到这里辣,我再测试一下!

[Day 2-1]体验华为方舟编译器:开始编译方舟

昨天准备了LLVM,整个编译过程比我想的还要慢,所以就在我睡觉的时候电脑自己跑编译了。

昨天在编译LLVM的时候遇到一个坑,就是在编译到88%的时候,需要编译一个非常复杂的bin(好像是llvm还是clang自身,所以非常大),导致8GB运存和2GB的swap全部吃满,编译失败。遂通过调整swap到32GB(实际上肯定是浪费的,但是毕竟我在睡觉,为了避免过程中再出现问题就设置的很大)。

扯点题外话,随着固态硬盘的发展,实际上从连续读写IO的带宽来看,固态硬盘甚至能达到RAM的低一个数量级,这一事实其实很难令人相信,但是确实是存在的。

目前常见的DDR4带宽在几十GB/s,而最高端NVMe也能够吃满4条PCIe 3.0(当然最近新出的PCIe 4.0暂时不讨论),也就是3500+ MB/s(算上损耗)。这也为swap的性能提供了不少保障。

现在回到话题,我们需要配置llvm,下一步是准备ninja和gn,好在这两者在github上有完整的linux x64使用的release,就不需要自己编译了。让我们开始吧。

配置LLVM

我们需要为方舟编译器指定llvm的路径,因为我不想再把LLVM复制到方舟编译器项目的tools目录下,所以把它指定到我编译的位置。

按照昨天的记录,我们编译后的LLVM应该在~/GitHub/llvm-project/out下,于是在openarkcompiler/build/config/BUILDCONFIG.gn文件中进行配置:

# 为了尽可能避免问题,这里用/home/leon取代~
LLVM_CLANG_ROOT = "/home/leon/GitHub/llvm-project/out"
GN_C_COMPILER = "${LLVM_CLANG_ROOT}/bin/clang"
GN_CXX_COMPILER = "${LLVM_CLANG_ROOT}/bin/clang++"
GN_AR_COMPILER = "${LLVM_CLANG_ROOT}/bin/llvm-ar"

配置Ninja

Ninja的配置比较简单,在Ninja的GitHub官方项目页面:https://github.com/ninja-build/ninja 下载Release里面最新的Linux binary即可(注:Ninja页面截止发稿当日,最新版为1.9.0,这也是方舟编译器的文档所要求的版本)。

然后,将Ninja放入方舟编译器的tools目录下。按照文档的说法,ninja放在了tools/ninja-1.9.0/ninja,但是由于我没有把LLVM一起放进来,实际上tools目录没有这么复杂,所以放在tools/ninja了。

再编辑Makefile,在里面指定Ninja的位置:

NINJA := ${MAPLE_ROOT}/tools/ninja

配置gn

gn的配置和ninja大同小异。

gn在google的gn官网下载binary:https://gn.googlesource.com/gn/

然后,放在tools目录下(同Ninja),在Makefile配置gn的位置:

GN := ${MAPLE_ROOT}/tools/gn

开始编译方舟编译器

现在所有的环境配置工作都完成了,可以开始编译方舟编译器了。进入项目根目录,执行如下命令:

. build/envsetup.sh
make

这样就能编译好方舟编译器的Release版本了。当然,需要Debug版本,可以在make时指定BUILD_TYPE=DEBUG,这里选择RELEASE版。

稍等几分钟,方舟编译器就编译完成了。

编译完成后,在项目根目录的out文件夹内能够看到编译完成后的文件,bin下有一个maple可执行bin,应该就是我们要研究的编译器本体了!

进入out/bin文件夹,执行

./maple --help

输出:

USAGE: maple [options]

 Options:
  -h --help [command]             Print usage and exit.

  --version [command]             Print version and exit.

  --infile file1,file2,file3      Input files.

  --mplt=file1,file2,file3        Import mplt files.

  -O0                             No optimization.

  --save-temps                    Do not delete intermediate files.
                                  --save-temps Save all intermediate files.
                                  --save-temps=file1,file2,file3 Save the
                                  target files.

  --run=cmd1:cmd2                 The name of executables that are going
                                  to execute. IN SEQUENCE.
                                  Separated by ":".Available exe names:
                                  jbc2mpl, me, mpl2mpl, mplcg
                                  Input file must match the tool can
                                  handle

  --option="opt1:opt2"          Options for each executable,
                                  separated by ":".
                                  The sequence must match the sequence in
                                  --run.

  -time-phases                    Timing phases and print percentages

  --genmempl                      Generate me.mpl file

  --genVtableImpl                 Generate VtableImpl.mpl file

  --debug                         Print debug info.

  -h-me --help-me                 Print usage and exit.Available command names:
                                  me

  -h-mpl2mpl --help-mpl2mpl       Print usage and exit.Available command names:
                                  mpl2mpl