11

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

使用 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/201...

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

➤➤ 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 命令可以编译汇编:

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

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

➤➤ 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/kal...
另外汇编编译二进制文件的话 nsm as 命令好像都可以, 我不大熟悉...

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

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

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

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

作为脚本语言入门而且心思花在脚本语言上的人, 我很不适应机器的思维方式
虽然理解是理解, 但要写代码生成代码最后能运行, 还真的是两种感觉
IF/ELSE 的控制逻辑转化到低级语言, 相对还好懂一点
http://www.stephendiehl.com/l...
但是看看各种高级一些的抽象怎样被转化到指令就觉得很揪心了
http://llvm.lyngvig.org/Artic...
我后来还想, 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 出来的, 文档挖了两天, 失败了失败了
但是类型推断的话, 以之前写解释器的递归的方案, 还是能推导出一部分的, 比如说

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

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

% a (+ 1 (+ 3 4))

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

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

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

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

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


题叶
17.3k 声望2.6k 粉丝

Calcit 语言作者


引用和评论

0 条评论