LLVM IR 生成的代码到本机代码
LLVM IR generated code to native code
我正在学习编译器的工作原理。为了学习,我使用了几本书和教程,有时偶然发现了这个我无法解决的问题。
找到我遵循的完整教程代码
此代码生成 IR 代码并成功执行。但是,如果我尝试将代码保存为 example.ll
文件并(使用 llc
)编译为本机程序集,则此程序集无法编译为本机可执行文件(使用 nasm 和 ld)。我还尝试将 IR 编译成本机目标文件,然后使用 g++ 编译它(与在教程的 make 文件中编译的解析器相同),这也失败了。我想找到一种方法将我生成的 IR 代码实际编译成可执行二进制文件(至少对于 elf64)。
生成的IR码[example.ll]:
; ModuleID = 'main'
@.str = private constant [4 x i8] c"%d[=10=]A[=10=]"
declare i32 @printf(i8*, ...)
define internal void @echo(i64 %toPrint) {
entry:
%0 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i64 %toPrint)
ret void
}
define internal void @main() {
entry:
%0 = call i64 @do_math(i64 11)
call void @echo(i64 %0)
%1 = call i64 @do_math(i64 12)
call void @echo(i64 %1)
call void @printi(i64 10)
ret void
}
declare void @printi(i64)
define internal i64 @do_math(i64 %a1) {
entry:
%a = alloca i64
store i64 %a1, i64* %a
%x = alloca i64
%0 = load i64* %a
%1 = mul i64 %0, 5
store i64 %1, i64* %x
%2 = load i64* %x
%3 = add i64 %2, 3
ret i64 %3
}
然后通过 asm:
$ llc-3.5 -filetype=asm -x86-asm-syntax=intel -o example.asm example.ll
$ nasm example.asm
example.asm:1: error: attempt to define a local label before any non-local labels
example.asm:2: error: attempt to define a local label before any non-local labels
example.asm:2: error: parser: instruction expected
example.asm:3: error: attempt to define a local label before any non-local labels
example.asm:3: error: parser: instruction expected
example.asm:4: error: attempt to define a local label before any non-local labels
example.asm:4: error: parser: instruction expected
example.asm:5: error: parser: instruction expected
BB#0: # %entry:3: error: parser: instruction expected
BB#0: # %entry:12: error: parser: instruction expected
...
...
<many similar errors here>
或通过 GCC:
$ llc-3.5 -filetype=obj -o example.o example.ll
$ g++ native.o example.o
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status
PS:非常欢迎对存储库做出相应修改代码(使其工作)的贡献!
UPD:按要求
asm代码:
.text
.file "example.ll"
.align 16, 0x90
.type echo,@function
echo: # @echo
.cfi_startproc
# BB#0: # %entry
pushq %rax
.Ltmp0:
.cfi_def_cfa_offset 16
movq %rdi, %rcx
movl $.L.str, %edi
xorl %eax, %eax
movq %rcx, %rsi
callq printf
popq %rax
retq
.Ltmp1:
.size echo, .Ltmp1-echo
.cfi_endproc
.align 16, 0x90
.type main,@function
main: # @main
.cfi_startproc
# BB#0: # %entry
pushq %rax
.Ltmp2:
.cfi_def_cfa_offset 16
movl , %edi
callq do_math
movq %rax, %rdi
callq echo
movl , %edi
callq do_math
movq %rax, %rdi
callq echo
movl , %edi
callq printi
popq %rax
retq
.Ltmp3:
.size main, .Ltmp3-main
.cfi_endproc
.align 16, 0x90
.type do_math,@function
do_math: # @do_math
.cfi_startproc
# BB#0: # %entry
movq %rdi, -8(%rsp)
leaq (%rdi,%rdi,4), %rax
movq %rax, -16(%rsp)
leaq 3(%rdi,%rdi,4), %rax
retq
.Ltmp4:
.size do_math, .Ltmp4-do_math
.cfi_endproc
.type .L.str,@object # @.str
.section .rodata,"a",@progbits
.L.str:
.asciz "%d\n"
.size .L.str, 4
.section ".note.GNU-stack","",@progbits
nm的输出:
$ nm example.o
0000000000000060 t do_math
0000000000000000 t echo
0000000000000020 t main
U printf
U printi
程序集文件
您不能 assemble example.asm
的原因可能是它采用 AT&T 语法,而 nasm
需要 Intel 语法。看来您已要求 llc
输出 Intel 语法,但您弄错了标志。根据 this manual,它是 --x86-asm-syntax
(注意双破折号)。
我怀疑您最好使用 as
(GNU assembler)进行汇编,因为英特尔语法有许多相互不兼容的方言;我不太确定哪个 LLVM 会说话。为此,请使用命令:
$ as example.asm -o example.o
目标文件
你不能 link 你的目标文件与 C 库的原因是你已经声明你的 main
函数有 internal linkage(即 define internal
) .与 C 中的 static
关键字一样,它使符号 "invisible" 位于目标文件之外,如 nm
输出中的小写 't' 所证明。
为 main
创建 LLVM 函数对象时,应将其 linkage 类型设置为 llvm::GlobalValue::ExternalLinkage
。
同样的问题出现在程序集文件中,当然是 - 它缺少 .global main
。
不要认为这意味着你应该给 所有 函数外部 linkage;如果一个函数只在定义它的翻译单元中使用,它真的应该有内部 linkage。 main
.
你做不到
我正在学习编译器的工作原理。为了学习,我使用了几本书和教程,有时偶然发现了这个我无法解决的问题。
找到我遵循的完整教程代码此代码生成 IR 代码并成功执行。但是,如果我尝试将代码保存为 example.ll
文件并(使用 llc
)编译为本机程序集,则此程序集无法编译为本机可执行文件(使用 nasm 和 ld)。我还尝试将 IR 编译成本机目标文件,然后使用 g++ 编译它(与在教程的 make 文件中编译的解析器相同),这也失败了。我想找到一种方法将我生成的 IR 代码实际编译成可执行二进制文件(至少对于 elf64)。
生成的IR码[example.ll]:
; ModuleID = 'main'
@.str = private constant [4 x i8] c"%d[=10=]A[=10=]"
declare i32 @printf(i8*, ...)
define internal void @echo(i64 %toPrint) {
entry:
%0 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i64 %toPrint)
ret void
}
define internal void @main() {
entry:
%0 = call i64 @do_math(i64 11)
call void @echo(i64 %0)
%1 = call i64 @do_math(i64 12)
call void @echo(i64 %1)
call void @printi(i64 10)
ret void
}
declare void @printi(i64)
define internal i64 @do_math(i64 %a1) {
entry:
%a = alloca i64
store i64 %a1, i64* %a
%x = alloca i64
%0 = load i64* %a
%1 = mul i64 %0, 5
store i64 %1, i64* %x
%2 = load i64* %x
%3 = add i64 %2, 3
ret i64 %3
}
然后通过 asm:
$ llc-3.5 -filetype=asm -x86-asm-syntax=intel -o example.asm example.ll
$ nasm example.asm
example.asm:1: error: attempt to define a local label before any non-local labels
example.asm:2: error: attempt to define a local label before any non-local labels
example.asm:2: error: parser: instruction expected
example.asm:3: error: attempt to define a local label before any non-local labels
example.asm:3: error: parser: instruction expected
example.asm:4: error: attempt to define a local label before any non-local labels
example.asm:4: error: parser: instruction expected
example.asm:5: error: parser: instruction expected
BB#0: # %entry:3: error: parser: instruction expected
BB#0: # %entry:12: error: parser: instruction expected
...
...
<many similar errors here>
或通过 GCC:
$ llc-3.5 -filetype=obj -o example.o example.ll
$ g++ native.o example.o
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status
PS:非常欢迎对存储库做出相应修改代码(使其工作)的贡献!
UPD:按要求
asm代码:
.text
.file "example.ll"
.align 16, 0x90
.type echo,@function
echo: # @echo
.cfi_startproc
# BB#0: # %entry
pushq %rax
.Ltmp0:
.cfi_def_cfa_offset 16
movq %rdi, %rcx
movl $.L.str, %edi
xorl %eax, %eax
movq %rcx, %rsi
callq printf
popq %rax
retq
.Ltmp1:
.size echo, .Ltmp1-echo
.cfi_endproc
.align 16, 0x90
.type main,@function
main: # @main
.cfi_startproc
# BB#0: # %entry
pushq %rax
.Ltmp2:
.cfi_def_cfa_offset 16
movl , %edi
callq do_math
movq %rax, %rdi
callq echo
movl , %edi
callq do_math
movq %rax, %rdi
callq echo
movl , %edi
callq printi
popq %rax
retq
.Ltmp3:
.size main, .Ltmp3-main
.cfi_endproc
.align 16, 0x90
.type do_math,@function
do_math: # @do_math
.cfi_startproc
# BB#0: # %entry
movq %rdi, -8(%rsp)
leaq (%rdi,%rdi,4), %rax
movq %rax, -16(%rsp)
leaq 3(%rdi,%rdi,4), %rax
retq
.Ltmp4:
.size do_math, .Ltmp4-do_math
.cfi_endproc
.type .L.str,@object # @.str
.section .rodata,"a",@progbits
.L.str:
.asciz "%d\n"
.size .L.str, 4
.section ".note.GNU-stack","",@progbits
nm的输出:
$ nm example.o
0000000000000060 t do_math
0000000000000000 t echo
0000000000000020 t main
U printf
U printi
程序集文件
您不能 assemble example.asm
的原因可能是它采用 AT&T 语法,而 nasm
需要 Intel 语法。看来您已要求 llc
输出 Intel 语法,但您弄错了标志。根据 this manual,它是 --x86-asm-syntax
(注意双破折号)。
我怀疑您最好使用 as
(GNU assembler)进行汇编,因为英特尔语法有许多相互不兼容的方言;我不太确定哪个 LLVM 会说话。为此,请使用命令:
$ as example.asm -o example.o
目标文件
你不能 link 你的目标文件与 C 库的原因是你已经声明你的 main
函数有 internal linkage(即 define internal
) .与 C 中的 static
关键字一样,它使符号 "invisible" 位于目标文件之外,如 nm
输出中的小写 't' 所证明。
为 main
创建 LLVM 函数对象时,应将其 linkage 类型设置为 llvm::GlobalValue::ExternalLinkage
。
同样的问题出现在程序集文件中,当然是 - 它缺少 .global main
。
不要认为这意味着你应该给 所有 函数外部 linkage;如果一个函数只在定义它的翻译单元中使用,它真的应该有内部 linkage。 main
.