编译为 LLVM IR 的 Haskell 程序缺少 main
Compiled Haskell program to LLVM IR is missing main
以下 关于 Haskell 程序的编译
对于 LLVM IR,我采用了相同的 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
然后我将它转换为位码:
$ 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;dr. 入口点(可能)被命名为 ZCMain_main_closure
,它是一个引用代码块的数据结构,而不是引用块代码本身。尽管如此,它还是被Haskell运行时间解释table,它直接对应于main :: IO ()
中函数Haskell"value"main.hs
程序.
关于 linking 程序,较长的答案涉及的内容比您想知道的要多,但事情就是这样。当你使用 C 程序时:
#include <stdio.h>
int main()
{
printf("I like C!\n");
}
用gcc
将它编译成目标文件:
$ gcc -Wall -c hello.c
并检查目标文件的符号 table:
$ nm hello.o
0000000000000000 T main
U printf
您会看到它包含符号 main
的定义和对外部符号 printf
.
的(未定义)引用
现在,您可能会想象 main
就是这个程序的 "entry point"。哈哈哈哈!你这样想真是天真愚蠢!
事实上,真正的 Linux 专家都知道您的程序的入口点根本不在目标文件 hello.o
中。它在哪里?好吧,它在 "C runtime" 中,当您实际创建 executable:
时,link 被 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
的引用,它将 linked 到您在 [=29 中所谓的入口点=].正是这个小存根定义了 真正的 入口点,即 _start
。你可以看出这是实际的入口点,因为如果你 link 程序进入 executable,你会看到 _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
也就是说,程序的"entry point"其实是一个很复杂的概念。
当您使用 LLVM 工具链而不是 GCC 编译和 运行 C 程序时,情况非常相似。这是为了让一切都与 GCC 兼容而设计的。你的hello.ll
文件中所谓的入口点只是C函数main
,而不是你程序的真正的入口点。这仍然由 crt1.o
存根提供。
现在,如果我们(最终)从讨论 C 切换到讨论 Haskell,Haskell 运行时间显然比C 运行time,但它是建立在 C 运行time 之上的。因此,当您以正常方式编译 Haskell 程序时:
$ ghc main.hs
stack ghc -- main.hs
[1 of 1] Compiling Main ( main.hs, main.o )
Linking main ...
$
你可以看到 executable 有一个名为 _start
:
的入口点
$ nm main | egrep 'T _start'
0000000000406560 T _start
实际上与之前调用 C 入口点的 C 运行时间存根相同:
$ nm main | egrep 'T main'
0000000000406dc4 T main
$
但是这个main
不是你的Haskellmain
。 main
是 GHC 在 link 时动态创建的程序中的 C main
函数。你可以通过运行ning看这样一个程序:
$ ghc -v -keep-tmp-files -fforce-recomp main.hs
并在 /tmp
子目录中的某处查找名为 ghc_4.c
的文件:
$ 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}>
...
现在,它不是 "function"。它是 Haskell 运行 时间系统理解的特殊格式的数据结构(闭包)。上面的 hs_main()
函数(又一个入口点!)是进入 Haskell 运行time:
的主要入口点
$ 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 程序,您可以通过跳转到其入口点以某种方式 运行 直接, 那么我有一些坏消息要告诉你... ;)
以下
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
然后我将它转换为位码:
$ 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;dr. 入口点(可能)被命名为 ZCMain_main_closure
,它是一个引用代码块的数据结构,而不是引用块代码本身。尽管如此,它还是被Haskell运行时间解释table,它直接对应于main :: IO ()
中函数Haskell"value"main.hs
程序.
关于 linking 程序,较长的答案涉及的内容比您想知道的要多,但事情就是这样。当你使用 C 程序时:
#include <stdio.h>
int main()
{
printf("I like C!\n");
}
用gcc
将它编译成目标文件:
$ gcc -Wall -c hello.c
并检查目标文件的符号 table:
$ nm hello.o
0000000000000000 T main
U printf
您会看到它包含符号 main
的定义和对外部符号 printf
.
现在,您可能会想象 main
就是这个程序的 "entry point"。哈哈哈哈!你这样想真是天真愚蠢!
事实上,真正的 Linux 专家都知道您的程序的入口点根本不在目标文件 hello.o
中。它在哪里?好吧,它在 "C runtime" 中,当您实际创建 executable:
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
的引用,它将 linked 到您在 [=29 中所谓的入口点=].正是这个小存根定义了 真正的 入口点,即 _start
。你可以看出这是实际的入口点,因为如果你 link 程序进入 executable,你会看到 _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
也就是说,程序的"entry point"其实是一个很复杂的概念。
当您使用 LLVM 工具链而不是 GCC 编译和 运行 C 程序时,情况非常相似。这是为了让一切都与 GCC 兼容而设计的。你的hello.ll
文件中所谓的入口点只是C函数main
,而不是你程序的真正的入口点。这仍然由 crt1.o
存根提供。
现在,如果我们(最终)从讨论 C 切换到讨论 Haskell,Haskell 运行时间显然比C 运行time,但它是建立在 C 运行time 之上的。因此,当您以正常方式编译 Haskell 程序时:
$ ghc main.hs
stack ghc -- main.hs
[1 of 1] Compiling Main ( main.hs, main.o )
Linking main ...
$
你可以看到 executable 有一个名为 _start
:
$ nm main | egrep 'T _start'
0000000000406560 T _start
实际上与之前调用 C 入口点的 C 运行时间存根相同:
$ nm main | egrep 'T main'
0000000000406dc4 T main
$
但是这个main
不是你的Haskellmain
。 main
是 GHC 在 link 时动态创建的程序中的 C main
函数。你可以通过运行ning看这样一个程序:
$ ghc -v -keep-tmp-files -fforce-recomp main.hs
并在 /tmp
子目录中的某处查找名为 ghc_4.c
的文件:
$ 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}>
...
现在,它不是 "function"。它是 Haskell 运行 时间系统理解的特殊格式的数据结构(闭包)。上面的 hs_main()
函数(又一个入口点!)是进入 Haskell 运行time:
$ 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 程序,您可以通过跳转到其入口点以某种方式 运行 直接, 那么我有一些坏消息要告诉你... ;)