关于将Haskell程序编译为LLVM IR的this SO post,我采用了相同的Haskell程序并尝试运行其生成的LLVM IR代码:
quicksort [] = []
quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater)
where
lesser = filter (< p) xs
greater = filter (>= p) xs
main = print(quicksort([5,2,1,0,8,3]))
我首先将它编译为LLVM IR
$ ghc -keep-llvm-files main.hs
然后我将其转换为bitcode:
$ llvm-as main.ll
但是,当我尝试使用lli
运行它时,我得到以下关于缺少主要的错误:
$ lli main.bc
'main' function not found in module.
难道我做错了什么?谢谢。
编辑:(来自K. A. Buhr的回答)
$ ls -l main*
main.hs
$ ghc -keep-llvm-files main.hs
[1 of 1] Compiling Main ( main.hs, main.o )
Linking main ...
$ ls -l main*
main
main.hi
main.hs
main.ll
main.o
$ rm main main.hi main.o
$ llvm-as main.ll
$ llc main.bc -filetype=obj -o main.o
$ ghc -o main main.o
$ ./main
[0,1,2,3,5,8]
TL;博士。入口点(可能)名为ZCMain_main_closure
,它是一个引用代码块的数据结构,而不是代码块本身。不过,它可以被Haskell运行时解释,它直接对应于main :: IO ()
程序中main.hs
函数的Haskell“值”。
更长的答案涉及到关于链接程序的比你想知道的更多,但这是交易。当您参加C程序时:
#include <stdio.h>
int main()
{
printf("I like C!\n");
}
使用gcc
将其编译为目标文件:
$ gcc -Wall -c hello.c
并检查目标文件符号表:
$ nm hello.o
0000000000000000 T main
U printf
你会看到它包含符号main
的定义和对外部符号printf
的(未定义)引用。
现在,您可能会想象main
是该计划的“切入点”。哈哈哈哈!对你来说,多么天真和愚蠢的想法!
事实上,真正的Linux专家知道你的程序的入口点根本不在目标文件hello.o
中。它在哪里?好吧,它在"C runtime"中,当你实际创建可执行文件时,gcc
会链接一个小文件:
$ nm /usr/lib/x86_64-linux-gnu/crt1.o
0000000000000000 D __data_start
0000000000000000 W data_start
0000000000000000 R _IO_stdin_used
U __libc_csu_fini
U __libc_csu_init
U __libc_start_main
U main
0000000000000000 T _start
$
请注意,此目标文件具有对main
的未定义引用,该引用将链接到hello.o
中的所谓入口点。这个小小的存根定义了真正的切入点,即_start
。你可以知道这是实际的入口点,因为如果你将程序链接到一个可执行文件,你会看到_start
符号和ELF入口点的位置(这是你内核实际首先转移控制的地址) execve()
你的节目)将重合:
$ gcc -o hello hello.o
$ nm hello | egrep 'T _start'
0000000000400430 T _start
$ readelf -h hello | egrep Entry
Entry point address: 0x400430
这就是说,程序的“切入点”实际上是一个非常复杂的概念。
当您使用LLVM工具链而不是GCC编译和运行C程序时,情况非常相似。这是为了保持一切与GCC兼容的设计。你的hello.ll
文件中的所谓入口点只是C函数main
,它不是你程序的真正入口点。这仍然是由crt1.o
存根提供的。
现在,如果我们(最终)从谈论C转向谈论Haskell,那么Haskell运行时显然比C运行时复杂大约十亿倍,但它是建立在C运行时之上的。因此,当您以正常方式编译Haskell程序时:
$ ghc main.hs
stack ghc -- main.hs
[1 of 1] Compiling Main ( main.hs, main.o )
Linking main ...
$
你可以看到可执行文件有一个名为_start
的入口点:
$ nm main | egrep 'T _start'
0000000000406560 T _start
这实际上是与之前调用C入口点相同的C运行时存根:
$ nm main | egrep 'T main'
0000000000406dc4 T main
$
但这个main
不是你的Haskell main
。这个main
是由GHC在链接时动态创建的程序中的C main
函数。你可以通过运行来查看这样的程序:
$ ghc -v -keep-tmp-files -fforce-recomp main.hs
并在ghc_4.c
子目录中的某个地方搜索一个名为/tmp
的文件:
$ cat /tmp/ghc10915_0/ghc_4.c
#include "Rts.h"
extern StgClosure ZCMain_main_closure;
int main(int argc, char *argv[])
{
RtsConfig __conf = defaultRtsConfig;
__conf.rts_opts_enabled = RtsOptsSafeOnly;
__conf.rts_opts_suggestions = true;
__conf.rts_hs_main = true;
return hs_main(argc,argv,&ZCMain_main_closure,__conf);
}
现在,你看到ZCMain_main_closure
的外部参考?不管你信不信,这是你的程序的Haskell入口点,你应该在main.o
中找到它,无论你使用vanilla GHC管道还是通过LLVM后端编译:
$ egrep ZCMain_main_closure main.ll
%ZCMain_main_closure_struct = type <{i64, i64, i64, i64}>
...
现在,它不是一个“功能”。它是Haskell运行时系统理解的特殊格式的数据结构(闭包)。上面的hs_main()
函数(又一个入口点!)是Haskell运行时的主要入口点:
$ nm ~/.stack/programs/x86_64-linux/ghc-8.4.3/lib/ghc-8.4.3/rts/libHSrts.a | egrep hs_main
0000000000000000 T hs_main
$
并且它接受Haskell主函数的闭包,作为开始执行程序的Haskell入口点。
所以,如果你经历了所有这些麻烦,希望在*.ll
文件中隔离一个Haskell程序,你可以通过跳转到它的入口点直接运行,那么我有一些坏消息给你...;)