我最近一直在研究LLVM,我发现它是一个非常有趣的架构。但是,通过本教程和参考资料,我看不到任何关于如何实现字符串数据类型的示例。
有很多关于整数、实数和其他数字类型,甚至数组、函数和结构的文档,但 AFAIK 对字符串一无所知。我是否必须向后端添加新的数据类型?有没有办法使用内置数据类型?任何见解将不胜感激。
什么是字符串?字符数组。
什么是性格?一个整数。
因此,虽然我无论如何都不是 LLVM 专家,但我猜想,如果你想表示一些 8 位字符集,你会使用一个 i8 数组(8 位整数),或者一个指向i8. 事实上,如果我们有一个简单的 hello world C 程序:
#include <stdio.h>
int main() {
puts("Hello, world!");
return 0;
}
我们使用 llvm-gcc 编译它并转储生成的 LLVM 程序集:
$ llvm-gcc -S -emit-llvm hello.c
$ cat hello.s
; ModuleID = 'hello.c'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128"
target triple = "x86_64-linux-gnu"
@.str = internal constant [14 x i8] c"Hello, world!\00" ; <[14 x i8]*> [#uses=1]
define i32 @main() {
entry:
%retval = alloca i32 ; <i32*> [#uses=2]
%tmp = alloca i32 ; <i32*> [#uses=2]
%"alloca point" = bitcast i32 0 to i32 ; <i32> [#uses=0]
%tmp1 = getelementptr [14 x i8]* @.str, i32 0, i64 0 ; <i8*> [#uses=1]
%tmp2 = call i32 @puts( i8* %tmp1 ) nounwind ; <i32> [#uses=0]
store i32 0, i32* %tmp, align 4
%tmp3 = load i32* %tmp, align 4 ; <i32> [#uses=1]
store i32 %tmp3, i32* %retval, align 4
br label %return
return: ; preds = %entry
%retval4 = load i32* %retval ; <i32> [#uses=1]
ret i32 %retval4
}
declare i32 @puts(i8*)
注意对文件末尾声明的 puts 函数的引用。在 C 中,puts 是
int puts(const char *s)
在 LLVM 中,它是
i32 @puts(i8*)
通信应该是清楚的。
顺便说一句,这里生成的 LLVM 非常冗长,因为我在编译时没有进行优化。如果您打开它们,不必要的说明就会消失:
$ llvm-gcc -O2 -S -emit-llvm hello.c
$ cat hello.s
; ModuleID = 'hello.c'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128"
target triple = "x86_64-linux-gnu"
@.str = internal constant [14 x i8] c"Hello, world!\00" ; <[14 x i8]*> [#uses=1]
define i32 @main() nounwind {
entry:
%tmp2 = tail call i32 @puts( i8* getelementptr ([14 x i8]* @.str, i32 0, i64 0) ) nounwind ; <i32> [#uses=0]
ret i32 0
}
declare i32 @puts(i8*)
[要跟进其他解释字符串是什么的答案,这里有一些实现帮助]
使用 C 接口,您需要的调用类似于:
LLVMValueRef llvmGenLocalStringVar(const char* data, int len)
{
LLVMValueRef glob = LLVMAddGlobal(mod, LLVMArrayType(LLVMInt8Type(), len), "string");
// set as internal linkage and constant
LLVMSetLinkage(glob, LLVMInternalLinkage);
LLVMSetGlobalConstant(glob, TRUE);
// Initialize with string:
LLVMSetInitializer(glob, LLVMConstString(data, len, TRUE));
return glob;
}
对于那些使用 LLVM 的 C++ API 的人,你可以依赖IRBuilder
's CreateGlobalStringPtr
:
Builder.CreateGlobalStringPtr(StringRef("Hello, world!"));
这将i8*
在最终的 LLVM IR 中表示。
使用 C API,而不是使用LLVMConstString
,您可以使用LLVMBuildGlobalString
. 这是我的实现
int main() {
printf("Hello World, %s!\n", "there");
return;
}
使用 C API:
LLVMTypeRef main_type = LLVMFunctionType(LLVMVoidType(), NULL, 0, false);
LLVMValueRef main = LLVMAddFunction(mod, "main", main_type);
LLVMTypeRef param_types[] = { LLVMPointerType(LLVMInt8Type(), 0) };
LLVMTypeRef llvm_printf_type = LLVMFunctionType(LLVMInt32Type(), param_types, 0, true);
LLVMValueRef llvm_printf = LLVMAddFunction(mod, "printf", llvm_printf_type);
LLVMBasicBlockRef entry = LLVMAppendBasicBlock(main, "entry");
LLVMPositionBuilderAtEnd(builder, entry);
LLVMValueRef format = LLVMBuildGlobalStringPtr(builder, "Hello World, %s!\n", "format");
LLVMValueRef value = LLVMBuildGlobalStringPtr(builder, "there", "value");
LLVMValueRef args[] = { format, value };
LLVMBuildCall(builder, llvm_printf, args, 2, "printf");
LLVMBuildRetVoid(builder);
我创建了这样的字符串:
LLVMValueRef format = LLVMBuildGlobalStringPtr(builder, "Hello World, %s!\n", "format");
LLVMValueRef value = LLVMBuildGlobalStringPtr(builder, "there", "value");
生成的 IR 为:
; ModuleID = 'printf.bc'
source_filename = "my_module"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
@format = private unnamed_addr constant [18 x i8] c"Hello World, %s!\0A\00"
@value = private unnamed_addr constant [6 x i8] c"there\00"
define void @main() {
entry:
%printf = call i32 (...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @format, i32 0, i32 0), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @value, i32 0, i32 0))
ret void
}
declare i32 @printf(...)
想一想字符串在常用语言中是如何表示的:
string
是一个具有构造函数、析构函数和复制构造函数的复杂对象。在内部,它通常基本上包含一个 C 字符串。LLVM 的名字很容易解释。确实是“低级”。你必须实现你想要的字符串。LLVM 强迫任何人进入特定的实现是愚蠢的。