Выдача байт-кода llvm из атрибута clang: 'byval' для передачи объектов с нетривиальным деструктором в функцию - PullRequest
6 голосов
/ 01 июля 2011

У меня есть исходный код C ++, который я анализирую с помощью clang, создавая байт-код llvm. С этого момента я хочу обработать файл сам ... Однако я допустил проблему. Рассмотрим следующий сценарий: - Я создаю класс с нетривиальным деструктором или конструктором копирования. - Я определяю функцию, в которой объект этого класса передается в качестве параметра по значению (без ссылки или указателя).

В полученном байт-коде вместо этого я получаю указатель. Для классов без деструктора параметр аннотируется как «byval», но в данном случае это не так. В результате я не могу различить, передается ли параметр по значению или действительно по указателю.

Рассмотрим следующий пример:

Входной файл - cpass.cpp:

class C {
  public:
  int x;
  ~C() {}
};

void set(C val, int x) {val.x=x;};

void set(C *ptr, int x) {ptr->x=x;}

Командная строка компиляции:

clang++ -c cpass.cpp -emit-llvm -o cpass.bc; llvm-dis cpass.bc

Произведенный выходной файл (cpass.ll):

; ModuleID = 'cpass.bc'
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-n8:16:32:64"
target triple = "x86_64-unknown-linux-gnu"

%class.C = type { i32 }

define void @_Z3set1Ci(%class.C* %val, i32 %x) nounwind {
  %1 = alloca i32, align 4
  store i32 %x, i32* %1, align 4
  %2 = load i32* %1, align 4
  %3 = getelementptr inbounds %class.C* %val, i32 0, i32 0
  store i32 %2, i32* %3, align 4
  ret void
}

define void @_Z3setP1Ci(%class.C* %ptr, i32 %x) nounwind {
  %1 = alloca %class.C*, align 8
  %2 = alloca i32, align 4
  store %class.C* %ptr, %class.C** %1, align 8
  store i32 %x, i32* %2, align 4
  %3 = load i32* %2, align 4
  %4 = load %class.C** %1, align 8
  %5 = getelementptr inbounds %class.C* %4, i32 0, i32 0
  store i32 %3, i32* %5, align 4
  ret void
}

Как видите, параметры обеих функций set выглядят одинаково. Так как я могу сказать, что первая функция должна была принимать параметр по значению вместо указателя?

Одним из решений может быть как-то разобрать искаженное имя функции, но оно не всегда может быть жизнеспособным. Что если кто-то поставит extern "C" перед функцией?

Есть ли способ указать clang оставить аннотацию byval или создать дополнительную аннотацию для каждого параметра функции, переданного значением?

Антон Коробейников предлагает мне разобраться с ИК-излучением LLVM от Clang. К сожалению, я почти ничего не знаю о внутренностях clang, документация довольно скудная. Руководство по внутреннему оборудованию от Clang не говорит об инфракрасном излучении. Так что я не знаю, с чего начать, куда идти, чтобы решить проблему, надеюсь, без фактического прохождения всего исходного кода Clang. Есть указатели? Советы? Дальнейшее чтение?


В ответе Антона Коробейникова:

Я более или менее знаю, как выглядит C ++ ABI в отношении передачи параметров. Нашел хорошее чтение здесь: http://agner.org./optimize/calling_conventions.pdf. Но это очень зависит от платформы! Этот подход может оказаться невозможным на разных архитектурах или в некоторых особых обстоятельствах.

В моем случае, например, функция будет работать на другом устройстве, а не на том, откуда она вызывается. Два устройства не разделяют память, поэтому они даже не делят стек. Если пользователь не передает указатель (в этом случае мы предполагаем, что он знает, что он делает), объект всегда должен передаваться в сообщении с параметрами функции. Если он имеет нетривиальный конструктор копирования, он должен быть выполнен вызывающей стороной, но объект также должен быть создан в области параметров.

Итак, я хотел бы как-то переопределить ABI в clang, не слишком вмешиваясь в их исходный код. Или, может быть, добавьте некоторую дополнительную аннотацию, которая будет игнорироваться в обычном конвейере компиляции, но я мог обнаружить это при разборе файла .bc / .ll. Или как-то иначе реконструировать сигнатуру функции.


1 Ответ

5 голосов
/ 02 июля 2011

К сожалению, «byval» - это не просто «аннотация», это атрибут параметра, который означает много для оптимизаторов и бэкэндов.По сути, правила передачи небольших структур / классов с нетривиальными функциями и без них регулируются платформой C ++ ABI, поэтому здесь не всегда можно использовать byval.

Фактически, byval здесь является просто результатомнебольшая оптимизация на уровне интерфейса.Когда вы передаете вещи по значению, тогда временный объект должен быть построен в стеке (через ctor копирования по умолчанию).Если у вас есть класс, похожий на POD, то clang может определить, что копия ctor будет тривиальной и оптимизирует пару ctor / dtor, передавая только «содержимое».классы (как в вашем случае) clang не может выполнить такую ​​оптимизацию и имеют для вызова как ctor, так и dtor.Таким образом, вы видите, что указатель на временный объект создан.

Попробуйте вызвать ваши функции set (), и вы увидите, что там происходит.

...