LLVM 将 va_list 传递给另一个函数
LLVM pass va_list to another function
我使用LLVM已经有一段时间了,我自己通过搜索和使用clang输出生成的代码解决了问题,但我一直无法解决这个问题。我目前正在开发一个编译器,使用我的自定义前端和 LLVM 作为后端。
由于我想要一个与 printf 函数完全一样的自定义 println 函数(varadic 参数格式),我尝试使用 instrisic @llvm.va_start 和 @llvm.va_end.
来实现它
现在的问题是一切都编译得很好,但是当我 运行 程序时,它显示奇怪的数字而不是使用的实际参数,例如:
输入:
println("Hello World %i, %i?", 1, 2)
输出:
Hello World 1447122753, 1280590165?
值得注意的是,即使程序再次 运行,数字也不会改变。
一些系统信息和使用的库:
- LLVM-10
- Ubuntu 18.04.4 LTS
- 英特尔(R) 酷睿(TM) i5-8400 CPU
我的编译器在 LLVM-IR 中的程序链接输出:
; ModuleID = 'merged.bc'
source_filename = "ld-temp.o"
target triple = "x86_64-unknown-linux-gnu"
%0 = type { i32, i32, i8*, i8* }
@0 = private unnamed_addr constant [20 x i8] c"Hello World %i, %i?[=13=]", align 1
@1 = private unnamed_addr constant [2 x i8] c"[=13=]A[=13=]", align 1
define i32 @main() {
%1 = alloca i32, align 4
%2 = call i32 (i8*, ...) @println(i8* getelementptr inbounds ([20 x i8], [20 x i8]* @0, i32 0, i32 0), i32 1, i32 2)
store i32 0, i32* %1, align 4
br label %3
3: ; preds = %0
%4 = load i32, i32* %1, align 4
ret i32 %4
}
define internal i32 @println(i8* %0, ...) {
%2 = alloca i32, align 4
%3 = call %0* @va_start()
%4 = alloca %0*
store %0* %3, %0** %4
%5 = alloca i8*, align 1
store i8* %0, i8** %5, align 1
%6 = load i8*, i8** %5, align 1
%7 = load %0*, %0** %4
%8 = call i32 @vprintf(i8* %6, %0* %7)
%9 = alloca i32, align 4
store i32 %8, i32* %9, align 4
%10 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @1, i32 0, i32 0))
%11 = load %0*, %0** %4
call void @va_end(%0* %11)
%12 = load i32, i32* %9, align 4
store i32 %12, i32* %2, align 4
br label %13
13: ; preds = %1
%14 = load i32, i32* %2, align 4
ret i32 %14
}
declare i32 @vprintf(i8*, %0*)
declare i32 @printf(i8*, ...)
define internal %0* @va_start() {
%1 = alloca %0, align 8
%2 = bitcast %0* %1 to i8*
call void @llvm.va_start(i8* %2)
ret %0* %1
}
; Function Attrs: nounwind
declare void @llvm.va_start(i8*) #0
define internal void @va_end(%0* %0) {
%2 = bitcast %0* %0 to i8*
call void @llvm.va_end(i8* %2)
ret void
}
; Function Attrs: nounwind
declare void @llvm.va_end(i8*) #0
attributes #0 = { nounwind }
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"LTOPostLink", i32 1}
变化:
- 不再使用外部函数,而是在调用时直接生成 IR(这会导致问题,因为您将获得 va_start() 函数的参数,该函数没有任何参数)。现在的工作 IR 是:
; ModuleID = 'merged.bc'
source_filename = "ld-temp.o"
target triple = "x86_64-unknown-linux-gnu"
%0 = type { i32, i32, i8*, i8* }
@0 = private unnamed_addr constant [20 x i8] c"Hello World %i, %i?[=14=]", align 1
@1 = private unnamed_addr constant [2 x i8] c"[=14=]A[=14=]", align 1
define i32 @main() {
%1 = alloca i32, align 4
%2 = call i32 (i8*, ...) @println(i8* getelementptr inbounds ([20 x i8], [20 x i8]* @0, i32 0, i32 0), i32 1, i32 2)
store i32 0, i32* %1, align 4
br label %3
3: ; preds = %0
%4 = load i32, i32* %1, align 4
ret i32 %4
}
define internal i32 @println(i8* %0, ...) {
%2 = alloca i32, align 4
%3 = alloca %0, align 8
%4 = bitcast %0* %3 to i8*
call void @llvm.va_start(i8* %4)
%5 = load %0, %0* %3, align 8
%6 = alloca %0
store %0 %5, %0* %6
%7 = alloca i8*, align 1
store i8* %0, i8** %7, align 1
%8 = load i8*, i8** %7, align 1
%9 = getelementptr %0, %0* %6
%10 = call i32 @vprintf(i8* %8, %0* %9)
%11 = alloca i32, align 4
store i32 %10, i32* %11, align 4
%12 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @1, i32 0, i32 0))
%13 = getelementptr %0, %0* %6
%14 = getelementptr %0, %0* %13
%15 = bitcast %0* %14 to i8*
call void @llvm.va_end(i8* %15)
store i32 0, i32* %2, align 4
br label %16
16: ; preds = %1
%17 = load i32, i32* %2, align 4
ret i32 %17
}
; Function Attrs: nounwind
declare void @llvm.va_start(i8*) #0
declare i32 @vprintf(i8*, %0*)
declare i32 @printf(i8*, ...)
; Function Attrs: nounwind
declare void @llvm.va_end(i8*) #0
attributes #0 = { nounwind }
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"LTOPostLink", i32 1}
您的主要问题似乎是您要从 @va_start
返回指向 alloca
分配的内存(即本地内存)的指针。您应该让它像 @llvm.va_start
那样将指针作为参数,或者完全摆脱该函数并直接从 @println
.
调用 @llvm.va_start
PS:我不明白你的%0
类型是什么意思。如果它应该代表一个参数列表,为什么不直接使用 i8*
(这是 LLVM vararg 函数所期望的)而不是对其进行位转换。
PPS:源代码中的格式字符串和参数数量与生成的 LLVM 中的不匹配。我猜 LLVM 实际上是从不同的源代码生成的(特别是 println("Hello World %i?", 1)
)。
我使用LLVM已经有一段时间了,我自己通过搜索和使用clang输出生成的代码解决了问题,但我一直无法解决这个问题。我目前正在开发一个编译器,使用我的自定义前端和 LLVM 作为后端。 由于我想要一个与 printf 函数完全一样的自定义 println 函数(varadic 参数格式),我尝试使用 instrisic @llvm.va_start 和 @llvm.va_end.
来实现它现在的问题是一切都编译得很好,但是当我 运行 程序时,它显示奇怪的数字而不是使用的实际参数,例如:
输入:
println("Hello World %i, %i?", 1, 2)
输出:
Hello World 1447122753, 1280590165?
值得注意的是,即使程序再次 运行,数字也不会改变。
一些系统信息和使用的库:
- LLVM-10
- Ubuntu 18.04.4 LTS
- 英特尔(R) 酷睿(TM) i5-8400 CPU
我的编译器在 LLVM-IR 中的程序链接输出:
; ModuleID = 'merged.bc'
source_filename = "ld-temp.o"
target triple = "x86_64-unknown-linux-gnu"
%0 = type { i32, i32, i8*, i8* }
@0 = private unnamed_addr constant [20 x i8] c"Hello World %i, %i?[=13=]", align 1
@1 = private unnamed_addr constant [2 x i8] c"[=13=]A[=13=]", align 1
define i32 @main() {
%1 = alloca i32, align 4
%2 = call i32 (i8*, ...) @println(i8* getelementptr inbounds ([20 x i8], [20 x i8]* @0, i32 0, i32 0), i32 1, i32 2)
store i32 0, i32* %1, align 4
br label %3
3: ; preds = %0
%4 = load i32, i32* %1, align 4
ret i32 %4
}
define internal i32 @println(i8* %0, ...) {
%2 = alloca i32, align 4
%3 = call %0* @va_start()
%4 = alloca %0*
store %0* %3, %0** %4
%5 = alloca i8*, align 1
store i8* %0, i8** %5, align 1
%6 = load i8*, i8** %5, align 1
%7 = load %0*, %0** %4
%8 = call i32 @vprintf(i8* %6, %0* %7)
%9 = alloca i32, align 4
store i32 %8, i32* %9, align 4
%10 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @1, i32 0, i32 0))
%11 = load %0*, %0** %4
call void @va_end(%0* %11)
%12 = load i32, i32* %9, align 4
store i32 %12, i32* %2, align 4
br label %13
13: ; preds = %1
%14 = load i32, i32* %2, align 4
ret i32 %14
}
declare i32 @vprintf(i8*, %0*)
declare i32 @printf(i8*, ...)
define internal %0* @va_start() {
%1 = alloca %0, align 8
%2 = bitcast %0* %1 to i8*
call void @llvm.va_start(i8* %2)
ret %0* %1
}
; Function Attrs: nounwind
declare void @llvm.va_start(i8*) #0
define internal void @va_end(%0* %0) {
%2 = bitcast %0* %0 to i8*
call void @llvm.va_end(i8* %2)
ret void
}
; Function Attrs: nounwind
declare void @llvm.va_end(i8*) #0
attributes #0 = { nounwind }
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"LTOPostLink", i32 1}
变化:
- 不再使用外部函数,而是在调用时直接生成 IR(这会导致问题,因为您将获得 va_start() 函数的参数,该函数没有任何参数)。现在的工作 IR 是:
; ModuleID = 'merged.bc'
source_filename = "ld-temp.o"
target triple = "x86_64-unknown-linux-gnu"
%0 = type { i32, i32, i8*, i8* }
@0 = private unnamed_addr constant [20 x i8] c"Hello World %i, %i?[=14=]", align 1
@1 = private unnamed_addr constant [2 x i8] c"[=14=]A[=14=]", align 1
define i32 @main() {
%1 = alloca i32, align 4
%2 = call i32 (i8*, ...) @println(i8* getelementptr inbounds ([20 x i8], [20 x i8]* @0, i32 0, i32 0), i32 1, i32 2)
store i32 0, i32* %1, align 4
br label %3
3: ; preds = %0
%4 = load i32, i32* %1, align 4
ret i32 %4
}
define internal i32 @println(i8* %0, ...) {
%2 = alloca i32, align 4
%3 = alloca %0, align 8
%4 = bitcast %0* %3 to i8*
call void @llvm.va_start(i8* %4)
%5 = load %0, %0* %3, align 8
%6 = alloca %0
store %0 %5, %0* %6
%7 = alloca i8*, align 1
store i8* %0, i8** %7, align 1
%8 = load i8*, i8** %7, align 1
%9 = getelementptr %0, %0* %6
%10 = call i32 @vprintf(i8* %8, %0* %9)
%11 = alloca i32, align 4
store i32 %10, i32* %11, align 4
%12 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @1, i32 0, i32 0))
%13 = getelementptr %0, %0* %6
%14 = getelementptr %0, %0* %13
%15 = bitcast %0* %14 to i8*
call void @llvm.va_end(i8* %15)
store i32 0, i32* %2, align 4
br label %16
16: ; preds = %1
%17 = load i32, i32* %2, align 4
ret i32 %17
}
; Function Attrs: nounwind
declare void @llvm.va_start(i8*) #0
declare i32 @vprintf(i8*, %0*)
declare i32 @printf(i8*, ...)
; Function Attrs: nounwind
declare void @llvm.va_end(i8*) #0
attributes #0 = { nounwind }
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"LTOPostLink", i32 1}
您的主要问题似乎是您要从 @va_start
返回指向 alloca
分配的内存(即本地内存)的指针。您应该让它像 @llvm.va_start
那样将指针作为参数,或者完全摆脱该函数并直接从 @println
.
@llvm.va_start
PS:我不明白你的%0
类型是什么意思。如果它应该代表一个参数列表,为什么不直接使用 i8*
(这是 LLVM vararg 函数所期望的)而不是对其进行位转换。
PPS:源代码中的格式字符串和参数数量与生成的 LLVM 中的不匹配。我猜 LLVM 实际上是从不同的源代码生成的(特别是 println("Hello World %i?", 1)
)。