Подтвердить что ты не робот

Как я могу реализовать строковый тип данных в LLVM?

В последнее время я смотрю LLVM, и я считаю, что это довольно интересная архитектура. Однако, просматривая учебное пособие и справочный материал, я не вижу никаких примеров того, как я мог бы использовать тип данных string.

Существует много документации о целых числах, действиях и других типах номеров и даже массивах, функциях и структурах, но AFAIK ничего о строках. Должен ли я добавить новый тип данных в бэкэнд? Есть ли способ использовать встроенные типы данных? Любое понимание было бы оценено.

4b9b3361

Ответ 1

Что такое строка? Массив символов.

Что такое персонаж? Целое число.

Так что, хотя я не эксперт 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*)

Ответ 2

[Чтобы следить за другими ответами, объясняющими, что такое строки, вот некоторые сведения об использовании)

Используя интерфейс 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;
}

Ответ 3

Подумайте, как строка представлена ​​на общих языках:

  • C: указатель на символ. Вам не нужно ничего особенного.
  • С++: string - это сложный объект с конструктором, деструктором и конструктором копирования. Внутри он обычно содержит по существу строку C.
  • Java/С#/...: строка представляет собой сложный объект, содержащий массив символов.

Название LLVM очень понятно. Это действительно "низкий уровень". Вы должны реализовать строки, как вы хотите, чтобы они были. Было бы глупо для LLVM принуждать кого-либо к конкретной реализации.

Ответ 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(...)

Ответ 5

Для тех, кто использует C++ API LLVM, вы можете положиться на IRBuilder CreateGlobalStringPtr:

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

Это будет представлено как i8* в финальном IR LLVM.