现如今,我们使用的计算机几乎都是 X86 架构的,Intel 和 AMD 几乎是普通消费者购买新计算机时的唯二选择,可能会有少数的 Apple 用户使用了配备 Apple Silicon 处理器的 Arm Mac,除此之外,似乎就没有什么别的体系架构可供选择了。但实际上,无论是历史还是现在,指令集的江湖都不是如此平静的,过去有 MIPS 和 PowerPC 等等在游戏机和服务器等领域挑战 X86 的地位,近有 RISCV 和 LoongArch 等试图在嵌入式领域开辟新的领域,而这些挑战者们都有一个共同点——都是 RISC(精简指令集架构)处理器。
何谓 RISC?
要了解什么是 RISC,首先就要清楚什么是 CISC,因为 RISC 的概念正是从 CISC 延申出来的。上世纪 70 年代,Intel 推出了第一颗 X86 处理器——Intel 8086,作为 X86 系列的始祖,它的减配版本(地址线位数砍半)8088 被 IBM 选中,作为 IBM PC 的处理器,从此开启了个人电脑和兼容机的传奇,同一时期,Apple 也选中了摩托罗拉的 68000 CPU 作为第一台 Macintosh 的处理器,用来支持它引入的图形界面。
8086 与 68000 看似大相径庭,但两者的共同点就在于它们都是复杂指令集处理器(CISC),由于当时储存器的价格普遍较高,编译器也还不够成熟,因此当时的软件开发者往往倾向于手工编写汇编指令,受此影响早期处理器的指令设计普遍比较复杂,并且采用了变长指令字的设计,用以支持各种复杂的操作,这样可以减小程序的体积和程序员编写指令的工作量,例如在 X86 中,就可以直接使用loop
指令编写循环,而无需手动编写跳转等指令。
然而,随着时间踏入 90 年代,CISC 处理器暴露出了各种问题:一方面,过于复杂的指令集不利于引入流水线的设计,造成性能受到影响;另一方面,程序规模不断增大,已经很难靠程序员去编写所有汇编代码了,编译器也逐渐走向成熟,然而它们还是很难利用上 CISC 指令集上各种复杂的指令。受此启发,人们搞出了精简指令处理器(RISC),正如其名,它的指令相比 CISC 要简单很多,一般有如下的特点:
- 指令字定长,通常是 32 位长,方便解码器的设计
- 通用寄存器较多,例如 ARM 有 16 个寄存器,而 MIPS 有 32 个寄存器,减少访存带来的性能瓶颈
- 访存由单独的指令完成,普通指令只会对寄存器和立即数进行操作,简化了处理器的设计
- 指令数量较少,语义单一,方便编译器的实现
这些特点都给当时的 RISC 处理器带来了巨幅的性能提升,RISC 处理器一时风光无两,在游戏机和服务器等各种领域全面开花,就连身为 Wintel 联盟一员的微软都为 MIPS 处理器推出了 Windows NT for RISC 用来兼容当时在高端工作站领域攻城略地的 MIPS 处理器。
不过,正如《计算机体系结构》一书中所言,如今 RISC 与 CISC 的区别已经日渐消弭了,甚至很多 RISC 架构的指令集都不比早期 CISC 更简单了:例如 PowerPC 的寄存器窗口和 MIPS 的延迟槽设计,有些甚至成为了性能提升的瓶颈,而传统的 CISC 指令集则通过引入 uop 的方式在执行阶段 RISC 化,话虽如此,新的指令集还是不断出现,并且往往还是会采用 RISC 简化的思想——例如 RISC-V 还有笔者本次要介绍的 LoongArch。
LoongArch 简介
龙芯架构 LoongArch 是一种精简指令集计算机(Reduced Instruction Set Computing,简称 RISC)风格的指令系统架构。
这是《龙芯架构参考手册 卷一:基础架构》中对 LoongArch 给出的定义,LoongArch 是龙芯中科公司设计的一种 CPU 指令集架构,2020 年对外公开其存在,2021 年起公开出货,在其 3A5000 CPU 产品开始搭载。龙芯之前的产品采用的是 MIPS 指令集,而随着时代的变迁,MIPS 指令集的实控人几经易手,就连 MIPS 公司自己都在 2020 年宣布放弃 MIPS,转投 RISC-V 阵营,而龙芯的选择则是另起炉灶——独立开发 ISA。
当然,任何新指令集逃不过的一个问题就是生态,毕竟 X86 指令集能够在今天仍然保持第一的占有量,靠的就是其他指令集无法比拟的生态鸿沟,无数的软件都是为了 X86 编写的,并且很难简单移植到其他的平台上,更遑论无数的闭源商业软件了。而新架构要加强自身的生态,基本只有两条路可以走:模拟(兼容层)和移植,笔者目前手头就有一台 3A5000 开发机,并且正在从事有关软件移植的工作。
移植指北
那么,要如何让软件跑在 LoongArch 架构上呢?实际上,对于大多数软件而言,并不需要做任何修改,在 C 编译器,JRE 运行时,.NET 环境等基础软件基本完成适配之后,只需要将代码重新编译(Java 等甚至不需要编译)运行即可,但对于一些依赖于底层汇编实现的软件,则需要手动进行移植。
笔者在今年年初完成了对 Haskell GHC 编译器向 LoongArch 架构的移植,并在 LLVM 和 GHC 开源社区中顺利将移植代码合并进了主线(虽然还差点闹出点乱子),下面就以我当时的移植过程为例,展示将一个依赖于底层体系架构细节的应用移植到新架构上,都需要做哪些改动。
不过要注意的是,随着 LLVM16 和 GHC 9.6.1 的发布,相关代码已经集成进了主线源码中,因此无需修改源码即可直接进行编译。
获取源码
首先,使用以下命令将 GHC LoongArch 分支克隆至本地
git clone -b loongarch-patch --recurse-submodules https://gitlab.haskell.org/lrzlin/ghc.git
GHC Unregisterised 版本编译
根据 CLFS 文档中的指南配置好交叉工具链后,找到源码目录中的ghc/m4/ghc_unregisteried.m4
文件并将其中的loongarch64
字段删除。
将ghc/libffi-tarballs
和ghc/libraries/ghc-bignum/gmp/gmp-tarballs
中libffi
和gmp
的压缩包替换为支持 LoongArch 的版本。
之后回到GHC根目录,输入:
./boot
./configure CC=$SYSDIR/cross-tools/bin/loongarch64-unknown-linux-gnu-gcc --target=loongarch64-unknown-linux-gnu LD=$SYSDIR/cross-tools/bin/loongarch64-unknown-linux-gnu-ld
等待配置完成后,输入如下命令进行编译。
hadrian/build -j4
如果在编译中碰到缺少ncurses
库的报错,请自行编译并将其安装至交叉编译工具链中。
编译完成后,输入:
hadrian/build binary-dist
打包二进制分发包,至此 GHC Unregisterised 版本编译完成,可以将其拷贝至 LoongArch 架构机器安装使用。
GHC Registerised 编译
GHC Unregistered 即可满足大多数情况下的使用需要,但如果需要更高的运行效率、更完善的库支持和更完整的功能,则需要编译 GHC Registerised 版本,由于该版本需要 LLVM 后端的支持,因此需要首先编译支持 LoongArch 架构的 LLVM16。
注:以下操作均在 LoongArch 机器上完成。
首先需要安装之前编译完成的 GHC Unregisterised 版本,将 tar 包加压缩之后,进入目录并输入如下指令安装:
./configure
hadrian/build install
之后在 LLVM 官方网站或 Github Release 页面下载 LLVM16 源码,解压至本地之后使用如下命令编译安装:
cmake .. -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS:BOOL=OFF -DLLVM_ENABLE_LIBCXX:BOOL=OFF \
-DLLVM_LIBDIR_SUFFIX=64 \
-DCMAKE_C_FLAGS="-DNDEBUG" -DCMAKE_CXX_FLAGS="-DNDEBUG" \
-DLLVM_BUILD_RUNTIME:BOOL=ON -DLLVM_ENABLE_RTTI:BOOL=ON \
-DLLVM_ENABLE_ZLIB:BOOL=ON -DLLVM_ENABLE_FFI:BOOL=ON \
-DLLVM_ENABLE_TERMINFO:BOOL=OFF \
-DLLVM_BUILD_LLVM_DYLIB:BOOL=ON \
-DLLVM_LINK_LLVM_DYLIB:BOOL=ON -DLLVM_BUILD_EXTERNAL_COMPILER_RT:BOOL=ON \
-DLLVM_INSTALL_TOOLCHAIN_ONLY:BOOL=OFF \
-DLLVM_TARGET_ARCH=LoongArch -DLLVM_DEFAULT_TARGET_TRIPLE=loongarch64-unknown-linux-gnu
ninja && ninja install
由于 GHC 尚不支持最新的 LLVM 16 版本 因此需要对进行如下修改:
- 将 ghc/llvm-passes 的内容修改为
[ (0, "-passes=module(default<O0>,function(mem2reg),globalopt,function(lower-expect))"), (1, "-passes=module(default<O1>,globalopt)"), (2, "-passes=module(default<O2>)") ]
- 将 ghc/compiler/GHC/Driver/Pipeline/Execute.hs 第 902 行中的
-tbaa
删除 - 在 ghc/utils/genapply/Main.hs 中添加
#undef UnregisterisedCompiler
由于 GHC 在最新的主线移除了对 make 工具的支持,因此我们还需要编译并安装 cabal-install。请自行参考 Cabal Bootstrap 指南进行编译,完成编译后,请注意需要执行一次cabal update
之后即可进入 GHC 根目录并使用以下指令编译安装 GHC Registerised 版:
./boot
./configure
hadrian/build -j4
hadrian/build install
以上,就是一次“简单”的移植过程。
参考文章:
https://bbs.loongarch.org/d/150-ghc-loongarch
https://blog.xen0n.name/posts/tinkering/loongarch-faq/