23

我最近一直在研究LLVM,我发现它是一个非常有趣的架构。但是,通过本教程和参考资料,我看不到任何关于如何实现字符串数据类型的示例。

有很多关于整数、实数和其他数字类型,甚至数组、函数和结构的文档,但 AFAIK 对字符串一无所知。我是否必须向后端添加新的数据类型?有没有办法使用内置数据类型?任何见解将不胜感激。

4

5 回答 5

26

什么是字符串?字符数组。

什么是性格?一个整数。

因此,虽然我无论如何都不是 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*)
于 2009-06-30T21:43:31.167 回答
16

[要跟进其他解释字符串是什么的答案,这里有一些实现帮助]

使用 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;
}
于 2010-02-11T15:29:41.113 回答
6

对于那些使用 LLVM 的 C++ API 的人,你可以依赖IRBuilder's CreateGlobalStringPtr

Builder.CreateGlobalStringPtr(StringRef("Hello, world!"));

这将i8*在最终的 LLVM IR 中表示。

于 2019-06-30T14:30:58.993 回答
4

使用 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(...)
于 2018-07-15T06:14:22.333 回答
3

想一想字符串在常用语言中是如何表示的:

  • C:指向字符的指针。你不必做任何特别的事情。
  • C++:string是一个具有构造函数、析构函数和复制构造函数的复杂对象。在内部,它通常基本上包含一个 C 字符串。
  • Java/C#/...:字符串是包含字符数组的复杂对象。

LLVM 的名字很容易解释。确实是“低级”。你必须实现你想要的字符串。LLVM 强迫任何人进入特定的实现是愚蠢的。

于 2009-06-30T21:50:31.683 回答