Скомпилированная программа на Haskell для LLVM IR отсутствует 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.
Я делаю что-то неправильно? Благодарю.
РЕДАКТИРОВАТЬ: (из ответа К. А. Бура)
$ 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]
1 ответ
ТЛ; др. Точка входа (вероятно) названа ZCMain_main_closure
и это структура данных, которая ссылается на блок кода, а не на сам код. Тем не менее, он интерпретируется средой выполнения Haskell и напрямую соответствует "значению" функции Haskell. main :: IO ()
в вашем main.hs
программа.
Более длинный ответ включает в себя больше, чем вы когда-либо хотели знать о связывании программ, но вот в чем дело. Когда вы принимаете программу на Си, как:
#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
Все это означает, что "точка входа" программы на самом деле является довольно сложной концепцией.
Когда вы компилируете и запускаете программу на C с помощью цепочки инструментов LLVM вместо GCC, ситуация довольно похожа. Это сделано для того, чтобы все было совместимо с 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
это не твой Хаскель main
, это main
это С main
Функция в программе, динамически создаваемой GHC во время соединения. Вы можете посмотреть на такую программу, запустив:
$ 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
независимо от того, скомпилированы ли вы с использованием ванильного конвейера 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, чтобы начать выполнение вашей программы.
Итак, если вы прошли через все эти неприятности в надежде изолировать программу на Haskell в *.ll
файл, который вы могли бы как-то запустить напрямую, перепрыгивая через точку входа, тогда у меня для вас плохие новости...;)