A Short Survey of Compiler Targets

编译器目标平台简析

作为业余编译器开发者,我经常为一个决定而纠结:选择哪个编译器后端目标。不同于80年代需要直接面向各种机器架构,现在有许多成熟的选择。这是一份简短且不完整的调查,介绍一些流行且有趣的选项。

目录

  1. 机器码/汇编
  2. 中间表示
  3. 其他高级语言
  4. 虚拟机/字节码
  5. WebAssembly
  6. 元追踪与元编译框架
  7. 非传统目标平台
  8. 结论

机器码/汇编

编译器总是可以直接输出针对一种或多种架构的机器码或汇编代码。著名例子是 Tiny C Compiler(TCC),它以快速和小巧著称,可以即时编译运行C代码。另一个例子是 Turbo Pascal。你也可以为自己的编译器这样做,但需要弄清楚每个目标架构的指令集架构(ISA)细节,以及寄存器分配等概念。

大多数现代编译器实际上并不直接生成机器码或汇编。它们先将源代码降维到一种与语言无关的中间表示(IR),然后由此为各大架构(x86-64、ARM64等)生成机器码。

这个领域最突出的工具是LLVM。这是一个庞大的开源编译器库。许多语言的编译器如Rust、Swift、C/C++(通过Clang)和Julia都使用LLVM作为IR来生成机器码。

另一个选择是GNU Compiler Collection(GCC),通过其GIMPLE IR,不过似乎没有编译器直接使用它。GCC可以像LLVM一样作为库使用,通过libgccjit编译代码。它在Emacs中用于即时编译(JIT)Elisp。Cranelift是该领域的另一个新选择,不过它支持的ISA较少。

对于那些觉得LLVM或GCC太大或编译太慢的人,存在极简主义替代品。QBE是一个专注于简洁的小型后端,目标是在"10%的代码量中实现70%的性能"。它被强调快速编译的语言Hare所使用。另一个选择是libFIRM,它使用基于图的SSA表示而非线性IR。

其他高级语言

有时你乐意让其他编译器/运行时来处理繁重工作。你可以将代码转译到另一个成熟的高级语言,并利用该语言的现有编译器/运行时和工具链。

这种情况下常见目标是C语言。由于几乎所有平台都有C编译器,生成C代码使你的语言具有高度可移植性。Chicken SchemeVala采用这种策略。或者你也可以编译到C++,比如Jank(如果你偏好这个)。还有C–,一个被GHCOCaml作为目标的C子集。

另一个无处不在的目标是JavaScript(JS),这是在浏览器或JS运行时(NodeDenoBun)中原生运行代码的两种选择之一(另一种是WebAssembly)。多种语言如TypeScriptPureScriptReasonClojureScriptDartElm都转译到JS。有趣的是,Nim可以转译到C、C++或JS。

另一个类似JS的目标是Lua,一种轻量级且可嵌入的脚本语言,MoonScriptFennel等语言转译到它。

更小众的方法是面向Lisp方言。例如编译到Chez Scheme,让你能利用它的宏系统、运行时和编译器。Idris 2Racket都使用Chez Scheme作为主要后端目标。

虚拟机/字节码

这是应用型语言的常见选择。你编译到虚拟机的可移植字节码。虚拟机通常带有垃圾回收JIT编译和安全沙箱等功能。

Java虚拟机(JVM)可能是最流行的。它是许多语言的目标,包括Java、KotlinScalaGroovyClojure。其主要竞争对手是公共语言运行时(CLR),最初由微软开发,被C#F#Visual Basic.NET等语言作为目标。

另一个值得注意的虚拟机是BEAM,最初为Erlang构建。BEAM VM不是为了原始计算速度,而是为了高并发、容错性和可靠性而设计。最近,ElixirGleam等新语言被创建来面向它。

最后,这一类别还包括MoarVM——Parrot VM的精神继承者,为Raku(原名Perl 6)语言构建。

WebAssembly

WebAssembly(Wasm)是一个相对较新的目标。它是一种可移植的二进制指令格式,专注于安全性和效率。Wasm被所有主流浏览器支持,但不局限于此。WebAssembly系统接口(WASI)标准为在非浏览器和非JS环境中运行Wasm提供了API。现在许多语言如Rust、C/C++、Go、Kotlin、Scala、ZigHaskell都面向Wasm。

元追踪元编译框架是更复杂的类别。这些不是你编译器后端的目标,而是你用它们通过为你的语言指定解释器来构建自定义JIT编译器。

最著名的例子是PyPy,一个使用RPython框架创建的Python实现。另一个这样的框架是GraalVM/Truffle,来自Oracle的多语言VM和元追踪框架。其主要特性是零成本互操作GraalJSTruffleRubyGraalPy的代码都可以在同一个VM上运行,并能直接相互调用。

非传统目标平台

进入非主流领域,你会发现一个充满非传统和深奥编译器目标的世界。开发者选择它们出于学术好奇、艺术表达,或测试可行编译目标的边界。

  • Brainfuck:一种只有八个命令的深奥语言,Brainfuck是图灵完备的,并已成为编译器挑战的目标。人们已经为C、Haskell和Lambda演算编写了编译器。

  • Lambda演算:Lambda演算是一种极简编程语言,仅将计算表示为函数及其应用。由于其简单性,它常被用作教育编译器的目标,以及其与计算本质的联系。Hell,一个Haskell子集,编译到简单类型Lambda演算

  • SKI组合子SKI组合子演算比Lambda演算更极简。SKI演算中的所有程序都可以由仅三个组合子S、K和I组成。MicroHs将Haskell子集编译到SKI演算。

  • JSFuck:你知道可以用仅六个字符[]()!+编写所有可能的JavaScript程序吗?好了,现在你知道了。

  • Postscript:Postscript也是图灵完备编程语言。你的下一个编译器可以面向它!

  • 正则表达式?乐高?元胞自动机?


专有名词解释

术语 英文 解释
编译器后端 Compiler Backend 编译器的后端阶段,负责将中间代码优化并转换为目标平台的机器码或字节码
Tiny C Compiler TCC 极小巧快速的C语言编译器,能即时编译执行C代码
Turbo Pascal - Borland公司开发的Pascal集成开发环境,以编译速度快闻名
指令集架构 ISA (Instruction Set Architecture) 定义处理器支持的所有指令及其编码方式的规范,如x86-64、ARM等
寄存器分配 Register Allocation 编译器优化技术,决定程序变量应存放在CPU寄存器还是内存中,影响执行效率
中间表示 IR (Intermediate Representation) 源代码与目标代码之间的抽象表示形式,用于简化编译器设计与优化
LLVM - 大规模开源编译器基础设施项目,提供模块化编译器组件和工具链
Clang - LLVM项目的C/C++/Objective-C编译器前端
GNU编译器集合 GCC GNU项目的编译器套装,支持多种语言
GIMPLE - GCC使用的中间表示形式
libgccjit - GCC的即时编译库,允许将GCC作为库嵌入其他程序
即时编译 JIT (Just-in-time) 程序运行时动态编译代码的技术,提升执行效率
Cranelift - 用Rust编写的新型代码生成后端,注重快速编译
QBE - 极简主义编译器后端,追求小体积与高性能的平衡
Hare - 注重编译速度和系统编程的现代编程语言
libFIRM - 使用基于图的SSA形式的编译器库
SSA Static Single Assignment 静态单赋值形式,一种常用的中间表示形式,每个变量只赋值一次
转译 Transpile 将一种高级语言源代码转换为另一种高级语言源代码的过程
Chicken Scheme - 将Scheme编译到C的编译器
Vala - 类似C#语法的编程语言,编译到C代码
Jank - Clojure方言,可编译到C++
C– - 面向C的简化中间语言,用于函数式语言编译
GHC Glasgow Haskell Compiler Haskell语言的主要编译器
OCaml - 函数式编程语言
Node/Deno/Bun - JavaScript运行时环境
TypeScript - 微软开发的JavaScript超集,添加静态类型
PureScript - 强类型纯函数式语言,编译到JavaScript
Reason - Facebook开发的编程语言,可编译到JavaScript
ClojureScript - Clojure到JavaScript的编译器
Dart - Google开发的编程语言
Elm - 函数式前端编程语言,编译到JavaScript
Nim - 静态类型的系统编程语言,可编译到多种目标
MoonScript - 编译到Lua的编程语言
Fennel - Lisp方言,编译到Lua
Chez Scheme - 优化编译器,Scheme实现之一
Idris 2 - 支持依赖类型的函数式编程语言
Racket - Lisp方言及通用编程语言平台
字节码 Bytecode 虚拟机执行的中间代码形式,比机器码更抽象
虚拟机 VM (Virtual Machine) 在软件层面模拟的计算机,执行字节码
Java虚拟机 JVM Java平台的核心运行时环境
Kotlin/Scala/Groovy - 运行在JVM上的编程语言
公共语言运行时 CLR .NET平台的运行时环境
C#/F#/VB.NET - .NET生态系统中的编程语言
BEAM - Erlang虚拟机,支持高并发和容错
Erlang - 并发函数式编程语言
Elixir/Gleam - 运行在BEAM虚拟机上的现代编程语言
MoarVM - Rakudo Perl 6的运行时虚拟机
Raku - 原名Perl 6,现代通用编程语言
WebAssembly Wasm 可移植的二进制指令格式,可在浏览器和服务器运行
WebAssembly系统接口 WASI 在非浏览器环境中运行Wasm的系统接口标准
Zig - 现代系统编程语言
元追踪 Meta-tracing 通过追踪解释器执行路径来自动生成JIT编译器的技术
元编译 Metacompilation 编译器生成技术,用高级语言编写解释器来自动获得编译器
PyPy - 使用RPython实现的Python解释器,含JIT
RPython - Python的静态类型子集,用于编写虚拟机
GraalVM/Truffle - Oracle的多语言运行时和元编译框架
GraalJS/TruffleRuby - GraalVM上的JavaScript和Ruby实现
零成本互操作 Zero-cost Interoperability GraalVM中不同语言间无额外开销的互调用能力
Brainfuck - 极简的深奥编程语言,仅8个指令
图灵完备 Turing-complete 计算系统能够模拟任意图灵机,理论上可计算所有可计算问题
Lambda演算 Lambda Calculus 函数式编程的理论基础,用函数抽象表达计算
Hell - Haskell的子集,编译到简单类型Lambda演算
SKI组合子 SKI Combinators 仅含S、K、I三个组合子的极简计算系统
MicroHs - 微型Haskell编译器
JSFuck - 仅用6个字符[]()!+编写JavaScript程序的混淆技术
Postscript - 页面描述语言,同时也是图灵完备编程语言

原帖: