周末花了点时间看 LLVM IR, 闲扯几句

某个大神真是牛逼得不行了, 连发文章用 LLVM 造语言
一篇是 Haskell, 一篇是 Python, 看得我都飘飘然了, 以为好简单
http://www.stephendiehl.com/llvm/
http://dev.stephendiehl.com/numpile/
总体上说, LLVM 已经是非常棒的平台了, 各种语言都能编译到他
比如说 Rust, Julia, 两个语言基本上就是靠着 LLVM 强大功能强大起来的
另外 Clang 跟 Swift 是苹果自家就不消说了
我注意到 Haskell 居然也支持选项开启使用 LLVM 作为编译后端
没看到完整列表记录有哪些语言, 但是逛 Wiki 提到的已经很不少了
http://en.wikipedia.org/wiki/LLVM

使用 LLVM 的话, 编译的流程就清晰多了

源码 -> AST -> LLVM IR -> LLVM Bytecode -> ASM -> Native

一般 LLVM IR 的文件后缀是 .ll, Bytecode 文件后缀是 .bc
其中 IR 是可读的, 比汇编好懂一些, .bc 文件我没怎么接触过了

要看具体 IR 长什么样子的话, 其实用 Clang 可以把 C 代码一步步编译过去
从 C 编译到 LLVM IR, 然后 IR 编译到汇编, 汇编生成机器码
http://ellcc.org/demo/index.cgi
https://idea.popcount.org/2013-07-24-ir-is-better-than-assembly/

我自己花了点时间照着试过, 比如说保存一个 IR 文件, 看看怎么编译.

text➤➤ cat hello.ll
declare i32 @putchar(i32)

define i32 @add(i32 %a, i32 %b) {
  %1 = add i32 %a, %b
  ret i32 %1
}

define void @main() {
  %1 = call i32 @add(i32 0, i32 97)
  call i32 @putchar(i32 %1)
  ret void
}

LLVM 的 lli 命令可以直接运行 .ll 文件, llc 命令可以编译汇编:

text➤➤ lli hello.ll
a➤➤ llc hello.ll
➤➤ l
hello.ll  hello.s

然后汇编可以用 clang 直接编译到二进制文件的,
只是很奇怪在 OS X 会多出一行来报错, 去掉这一行然后编译就通过了:

text➤➤ clang hello.s -o hello.native
hello.s:2:2: error: unknown directive
 .macosx_version_min 14, 3
 ^
➤➤ vim he
hello.ll  hello.s
➤➤ vim hello.s
➤➤ clang hello.s -o hello.native

这个问题前面写教程的大神也没给解决, 估计大神不用 Mac...
https://github.com/sdiehl/kaleidoscope/issues/19
另外汇编编译二进制文件的话 nsm as 命令好像都可以, 我不大熟悉...

只是编译 IR 的话, 我搜到了还有另一篇给了另一种途径, 先编译对象文件:

text$ llc -filetype=obj hello_world.ll
$ gcc hello_world.o -o hello_world
$ ./hello_world
Hello World!

http://www.wilfred.me.uk/blog/2015/02/21/my-first-llvm-compiler/
这个我不大了解, 但是 Anyway 对象文件也是语言编译的一个环节没错

要看生成代码的话还有个很方便的办法就是看 Julia 生成的代码
神奇的 Julia 把 JIT 当作静态编译在用, 结果是在 REPL 当中直接看编译结果
文章里列举的, 从 AST, 到 LLVM IR, 到 ASM 全都能打印, 我就说逆天...
http://blog.leahhanson.us/julia-introspects.html

作为脚本语言入门而且心思花在脚本语言上的人, 我很不适应机器的思维方式
虽然理解是理解, 但要写代码生成代码最后能运行, 还真的是两种感觉
IF/ELSE 的控制逻辑转化到低级语言, 相对还好懂一点
http://www.stephendiehl.com/llvm/#chapter-5-control-flow
但是看看各种高级一些的抽象怎样被转化到指令就觉得很揪心了
http://llvm.lyngvig.org/Articles/Mapping-High-Level-Constructs-to-LLVM...
我后来还想, Haskell 那样的高阶函数怎样编译为指令呢? 想不出来傻掉了

大致接触下来 LLVM IR 似乎有几点跟汇编之类的东西不同的
LLVM IR 假定寄存器有无限个, 所以变量名随便用, 不怕用完
LLVM IR 的语句都是 Static Single Assignment, 大概意思就是只能赋值一次吧
而且作为低级语言, 是没有嵌套的, 是带类型的, 这个在编译过程都要做
另外我后来才注意到基础类型当中是没有 String 的, 就像 C 一样几乎是 Int 模拟的
我对于底层的硬件这些细节太缺乏了解了, 现在我真的太佩服 Haskell 了
C 这种语言完全是编译器开发者偷懒, 听说还是直译汇编的, 这叫什么高级语言啊

我前些天还在看 Babel 怎么编译 ES6 到 ES5, 怎么搞尾递归啊参数 Spread 啦
现在想想动态语言当中字符串编译还是比 LLVM IR 温柔不少的
而且能在 AST 基础上进行操作, 已经比起 IR 有天壤之别了
我想起来 Go 语言也是把 AST 暴露出来给开发者操作的, 真是明智啊
https://github.com/jcla1/gisp
https://github.com/DAddYE/igo/
不过 Go, Lua 这些虽然赞, 但是返回多参数, 完全是冲着实用设计的语言, 不能多想

LLVM 目前的绑定支持的语言不少 C++, OCaml, Haskell, Python, Go, Ruby, Node.
官方的 Kaleidoscope 目前也有多语言的版本 C++, OCaml, Haskell, Python
但官方文档里就 C++ 跟 OCaml, 而且 C++ 不知道比 OCaml 长多少...
我今天尝试过安装 Haskell, Go, Node 的版本.
Haskell 的缺依赖没安装成功, Go 的 llvm.org 仓库证书失败 GitHub 版本也没装成功.
Node 这边的 node-llvm 跟 llvm-x 没装成功, 刚出来个 llvm2 装了却不会用
不管怎么说 llvm2 是我今天唯一一个安装成功的版本, 借以安慰一下...
https://github.com/dirk/llvm2 第一个 commit 还是 7 天前的..

本来我是想装逼写个编译的 Demo 出来的, 文档挖了两天, 失败了失败了
但是类型推断的话, 以之前写解释器的递归的方案, 还是能推导出一部分的, 比如说

cirrutype-define funcName retType (arg1Type arg2Type)
define funcName (arg1 arg2)
  ret r1

这样的写法, 我用类型声明的方式, 可以先明确定义函数的类型是什么
然后如果是嵌套的话, 比如定义变量 a(1 + (3 + 4)),

cirru% a (+ 1 (+ 3 4))

只有从内部往外一层层计算, 最终 a 的类型其实可以推出来(假如这里只用 float)
而且, 嵌套表达式要转换 SSA 那样简单的赋值指令的话, 可以修改表达式返回的数据结构,
这样 (+ 1 x) 不再返回一个值, 而是对象, 有依赖代码, 类型, 生成代码, 这些东西..

cirruobject
  :depend $ array ":+ 3 4"
  :text + 1 %b
  :type :float

我只是想说, 中间有一部分还是不难的, 但是对于高阶函数真的要傻了
然后呢, 还有复杂的数据结果, 代数数据结构怎么办? 天晓得 Haskell 是怎么实现的..
原来有一门高级语言可以用是多么不容易的事情, 真的应该知道满足啊

吐槽到此结束, 我还是有那种想要自己发明编程语言的...维护自尊心之类的想法吧
只是从实际的复杂度上看, 我没有足够的精力跟资源可以投入在里边
不过 LLVM 真的已经把语言编译的门槛拉低累非常之多, 很多语言借此冒出来了
LLVM 的文章有大神翻译了几章, 不过确实比不上直接海翻英文资源...
http://kaleidoscope-llvm-tutorial-zh-cn.readthedocs.org/zh_CN/latest/

又想起要吐槽的事情了.. 前端那群都在搞些什么啊, 一天到晚发明新的框架新的工具
就不能学学编译这边发点 Paper 把底层原理搞清楚, 然后放点心思好好设计下平台
V8, CommonJS, AMD, React, Webpack, 这些确实算得上深思熟虑,
但是其他随处抢山头的技术也太多了吧, 打包工具满地都是, MVC 框架满地都是
好吧这么说确实有点过, 微型框架独到之处确实不少. 但是 Paper 跟平台什么真心少啊.

阅读 18.5k

推荐阅读
题叶
用户专栏

ClojureScript 爱好者.

496 人关注
249 篇文章
专栏主页