Почему этот код LLVM IR дает неожиданные результаты? - PullRequest
0 голосов
/ 24 марта 2019

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

В настоящее время я создаю свой собственный язык программирования и в настоящее время пытаюсь реализовать перечисления и операторы сопоставления, которые соответствуют значению с регистром перечисления, и выполняет соответствующий оператор, но я получаю неожиданные результаты и ошибки segfaults здесь и там.

Вот один фрагмент кода моего языка, который работает (lli), но дает неожиданные результаты иногда (печатает 1, а не 3 по некоторым причинам):

class Node {
    fld value: int;
    fld next: OptionalNode;

    new(_value: int, _next: OptionalNode) {
        value = _value;
        next = _next;
    }
}

enum OptionalNode {
    val nil;
    val some(Node);
}

fun main(): int {
    var s: OptionalNode = OptionalNode.some(new Node(3, OptionalNode.nil));

    match s {
        OptionalNode.some(n) => print n.value;
    }

    var r: int = 0;

    ret r;
}

Это соответствующий IR LLVM, который генерирует мой компилятор:

; ModuleID = 'test.bc'
source_filename = "test"

%test.Node = type { i32, %test.OptionalNode }
%test.OptionalNode = type { i8, [8 x i8] }
%test.OptionalNode.nil = type { i8 }
%test.OptionalNode.some = type { i8, %test.Node* }

@str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1

declare i32 @printf(i8*, ...)

define void @"test.Node.!ctor$[test.Node]i[test.OptionalNode]"(%test.Node* %this, i32 %_value, %test.OptionalNode %_next) {
entry:
  %arg0 = alloca %test.Node*, align 8
  store %test.Node* %this, %test.Node** %arg0
  %arg1 = alloca i32, align 4
  store i32 %_value, i32* %arg1
  %arg2 = alloca %test.OptionalNode, align 16
  store %test.OptionalNode %_next, %test.OptionalNode* %arg2
  %ldarg1 = load i32, i32* %arg1
  %tmpld_cls = load %test.Node*, %test.Node** %arg0
  %tmpfld = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls, i32 0, i32 0
  store i32 %ldarg1, i32* %tmpfld
  %ldarg2 = load %test.OptionalNode, %test.OptionalNode* %arg2
  %tmpld_cls1 = load %test.Node*, %test.Node** %arg0
  %tmpfld2 = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls1, i32 0, i32 1
  store %test.OptionalNode %ldarg2, %test.OptionalNode* %tmpfld2
  ret void
}

define i32 @"test.main$v"() {
entry:
  %s = alloca %test.OptionalNode, align 16
  %enm = alloca %test.OptionalNode
  %0 = bitcast %test.OptionalNode* %enm to %test.OptionalNode.nil*
  %1 = getelementptr inbounds %test.OptionalNode.nil, %test.OptionalNode.nil* %0, i32 0, i32 0
  store i8 0, i8* %1
  %2 = load %test.OptionalNode, %test.OptionalNode* %enm
  %tmpalloc = alloca %test.Node
  call void @"test.Node.!ctor$[test.Node]i[test.OptionalNode]"(%test.Node* %tmpalloc, i32 3, %test.OptionalNode %2)
  %enm1 = alloca %test.OptionalNode
  %3 = bitcast %test.OptionalNode* %enm1 to %test.OptionalNode.some*
  %4 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %3, i32 0, i32 0
  store i8 1, i8* %4
  %5 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %3, i32 0, i32 1
  store %test.Node* %tmpalloc, %test.Node** %5
  %6 = load %test.OptionalNode, %test.OptionalNode* %enm1
  store %test.OptionalNode %6, %test.OptionalNode* %s
  %7 = getelementptr inbounds %test.OptionalNode, %test.OptionalNode* %s, i32 0, i32 0
  %8 = load i8, i8* %7
  switch i8 %8, label %match_end [
    i8 1, label %case1
  ]

case1:                                            ; preds = %entry
  %n = alloca %test.Node*, align 8
  %9 = bitcast %test.OptionalNode* %s to %test.OptionalNode.some*
  %10 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %9, i32 0, i32 1
  %11 = load %test.Node*, %test.Node** %10
  store %test.Node* %11, %test.Node** %n
  %tmpld_cls = load %test.Node*, %test.Node** %n
  %tmpgetfldgep = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls, i32 0, i32 0
  %tmpgetfldld = load i32, i32* %tmpgetfldgep
  %print_i = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @str, i32 0, i32 0), i32 %tmpgetfldld)
  br label %match_end

match_end:                                        ; preds = %case1, %entry
  %r = alloca i32, align 4
  store i32 0, i32* %r
  %tmploadlocal = load i32, i32* %r
  ret i32 %tmploadlocal
}

define i32 @main() {
entry:
  %call = tail call i32 @"test.main$v"()
  ret i32 %call
}

Теперь, как я сказал, это компилируется и запускается полностью, но по какой-то причине иногда печатается 1 вместо 3, что я совсем не понимаю . Я понятия не имею, как отлаживать код llvm ir, и применение прохода отладки с помощью opt приводит к неправильным исходным строкам (все с переменным смещением), что также приводит к NO SENSE (я использую llvm 8, но llvm 6.0.1 который я использовал раньше, показал те же результаты).

Затем, если я перемещу определение переменной r до выражения match, внезапно я получу сегфоут, положение которого я не могу точно определить из-за смещенных исходных линий ir, о которых я упоминал ранее.

Вот соответствующий код и ir для этого:

class Node {
    fld value: int;
    fld next: OptionalNode;

    new(_value: int, _next: OptionalNode) {
        value = _value;
        next = _next;
    }
}

enum OptionalNode {
    val nil;
    val some(Node);
}

fun main(): int {
    var s: OptionalNode = OptionalNode.some(new Node(3, OptionalNode.nil));

    var r: int = 0;

    match s {
        OptionalNode.some(n) => print n.value;
    }

    ret r;
}
; ModuleID = 'test.bc'
source_filename = "test"

%test.Node = type { i32, %test.OptionalNode }
%test.OptionalNode = type { i8, [8 x i8] }
%test.OptionalNode.nil = type { i8 }
%test.OptionalNode.some = type { i8, %test.Node* }

@str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1

declare i32 @printf(i8*, ...)

define void @"test.Node.!ctor$[test.Node]i[test.OptionalNode]"(%test.Node* %this, i32 %_value, %test.OptionalNode %_next) {
entry:
  %arg0 = alloca %test.Node*, align 8
  store %test.Node* %this, %test.Node** %arg0
  %arg1 = alloca i32, align 4
  store i32 %_value, i32* %arg1
  %arg2 = alloca %test.OptionalNode, align 16
  store %test.OptionalNode %_next, %test.OptionalNode* %arg2
  %ldarg1 = load i32, i32* %arg1
  %tmpld_cls = load %test.Node*, %test.Node** %arg0
  %tmpfld = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls, i32 0, i32 0
  store i32 %ldarg1, i32* %tmpfld
  %ldarg2 = load %test.OptionalNode, %test.OptionalNode* %arg2
  %tmpld_cls1 = load %test.Node*, %test.Node** %arg0
  %tmpfld2 = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls1, i32 0, i32 1
  store %test.OptionalNode %ldarg2, %test.OptionalNode* %tmpfld2
  ret void
}

define i32 @"test.main$v"() {
entry:
  %s = alloca %test.OptionalNode, align 16
  %enm = alloca %test.OptionalNode
  %0 = bitcast %test.OptionalNode* %enm to %test.OptionalNode.nil*
  %1 = getelementptr inbounds %test.OptionalNode.nil, %test.OptionalNode.nil* %0, i32 0, i32 0
  store i8 0, i8* %1
  %2 = load %test.OptionalNode, %test.OptionalNode* %enm
  %tmpalloc = alloca %test.Node
  call void @"test.Node.!ctor$[test.Node]i[test.OptionalNode]"(%test.Node* %tmpalloc, i32 3, %test.OptionalNode %2)
  %enm1 = alloca %test.OptionalNode
  %3 = bitcast %test.OptionalNode* %enm1 to %test.OptionalNode.some*
  %4 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %3, i32 0, i32 0
  store i8 1, i8* %4
  %5 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %3, i32 0, i32 1
  store %test.Node* %tmpalloc, %test.Node** %5
  %6 = load %test.OptionalNode, %test.OptionalNode* %enm1
  store %test.OptionalNode %6, %test.OptionalNode* %s
  %r = alloca i32, align 4
  store i32 0, i32* %r
  %7 = getelementptr inbounds %test.OptionalNode, %test.OptionalNode* %s, i32 0, i32 0
  %8 = load i8, i8* %7
  switch i8 %8, label %match_end [
    i8 1, label %case1
  ]

case1:                                            ; preds = %entry
  %n = alloca %test.Node*, align 8
  %9 = bitcast %test.OptionalNode* %s to %test.OptionalNode.some*
  %10 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %9, i32 0, i32 1
  %11 = load %test.Node*, %test.Node** %10
  store %test.Node* %11, %test.Node** %n
  %tmpld_cls = load %test.Node*, %test.Node** %n
  %tmpgetfldgep = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls, i32 0, i32 0
  %tmpgetfldld = load i32, i32* %tmpgetfldgep
  %print_i = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @str, i32 0, i32 0), i32 %tmpgetfldld)
  br label %match_end

match_end:                                        ; preds = %case1, %entry
  %tmploadlocal = load i32, i32* %r
  ret i32 %tmploadlocal
}

define i32 @main() {
entry:
  %call = tail call i32 @"test.main$v"()
  ret i32 %call
}

Я знаю, что такого рода вопросы действительно плохие, и я, вероятно, нарушаю некоторые правила, просто вставляя свой код сюда, но если кто-то пожертвует часть своего времени, чтобы помочь некоторым действительно расстроенным и близким к тому, чтобы бросить парня, я Буду очень благодарен.

1 Ответ

1 голос
/ 26 марта 2019

Это, конечно, выглядит сложно. Я думаю, что у меня есть ответ на ваш вопрос.

Сегфоут возникает при попытке печати % tmpgetfldld . Если вы попытаетесь скомпилировать с помощью clang, а затем выполните его, вы не получите segfault. Это не означает, что это ошибка lli, потому что даже в этом случае вы получите неправильный вывод, потому что вы обращаетесь к недопустимому пространству памяти. Позвольте мне объяснить, как это происходит.

% tmpgetfldld (что недопустимо) - это i32, который изначально был извлечен из адреса памяти, указанного % n , на 3 строки выше:

%tmpld_cls = load %test.Node*, %test.Node** %n

Если значение % tmpgetfldld недопустимо, это означает, что % 11 , который был сохранен в % n , недопустим. Причина в этой инструкции:

%9 = bitcast %test.OptionalNode* %s to %test.OptionalNode.some*

В начале вашей программы вы выделили указателю % s размер, равный размеру % test.OptionalNode объекта, который составляет 9 байт (1 байт и другой). 8 байтов для массива). Затем вы назначаете для регистрации % 9 битовую передачу % s для типа % test.OptionalNode.some . Этот тип имеет общий размер 1 + 4 + 1 + 8 * 1 = 14 байтов. На данный момент в вашей программе все в порядке, и % 9 указывает на тот же адрес, который % s сделал, но вы воспринимаете его только как % test.OptionalNode.some . Однако в этом пространстве памяти вы выделили 9 байтов, и теперь с помощью инструкций 'getelementptr' или 'extractvalue' вы получаете доступ к 14 байтам. Чтение после 9-го байта может вызвать ошибку. Действительно, с помощью этих инструкций:

%10 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %9, i32 0, i32 1
%11 = load %test.Node*, %test.Node** %10

Вы получаете указатель, указывающий на байты с 1 по 13 (считая от индекса 0). Этот указатель затем сохраняется ниже и загружается снова, и вы получите segfault только при попытке доступа к значению, что происходит при доступе к % tmpgetfldld .

Чтобы устранить ошибку, вам нужно как-то предупредить компилятор, что при выделении % s или любого другого % test.OptionalNode , у вас есть как минимум 9 байт, но вы можете ожидать, что вам понадобится больше, если, например, вы поместили бит в структуру с большим размером. На самом деле, именно так LLVM обрабатывает виртуальные классы и полиморфизм, когда подклассы имеют члены переменного размера, но все же должны быть каким-то образом привязаны к родительскому классу. Поэтому, если вы измените объявление % test.OptionalNode struct на это, вы решите segfault:

%test.OptionalNode = type { i8, [8 x i8], i8(...)** }

Последний тип - это указатель на функцию, указывающий, что вы ожидаете переменное число i8 (байт). Проверьте также здесь: LLVM, что означает i32 (...) ** в определении типа?

Если вы сделаете это изменение, вы избавитесь от segfault, но вы заметите, что не полностью решили свою проблему. Иногда вы можете получить 3 в качестве вывода, иногда что-то еще, что-то вроде неопределенного поведения. Это связано с тем, что даже если вы объявили i8 (...) ** для объяснения дополнительных байтов структурного типа с битовой передачей, типы данных, которые находятся в общих областях памяти между двумя структурными типами, плохо выровнены. Вы заметите, что их различие начинается со второго байта: в % test.OptionalNode начинается массив i8, тогда как в % test.OptionalNode.some , есть i32, затем i8, а затем тот же массив i8. Чтобы решить эту проблему, вы должны изменить свои определения структуры так:

%test.OptionalNode = type { i8, [8 x i8], i8(...)** }
%test.OptionalNode.some = type { i8, [8 x i8], %test.Node* }

или что:

%test.OptionalNode = type { i8, i8(...)** }
%test.OptionalNode.some = type { i8, %test.Node* }

Зависит от того, нужен ли вам этот массив [8 x i8] или нет в вашем дизайне. Теперь ваш результат постоянно равен 3, и ваша проблема исчезла. Я думаю, что это решение также охватывает ваш предыдущий вопрос ( Как исправить ошибку сегментации в генерируемом байт-коде llvm? ).

Извините за длинный ответ. Надеюсь, это поможет.

...