Я тут обнаружил забавную историю, о которой не так много говорят. В Itanium ABI -- у Unix* подобных системах есть пункт про конструкторы и деструкторы классов, чтобы они возвращали void. Оно логично, конструкторы и деструкторы классов ничего не возвращают, зачем им вообще возвращаемые значения.
К сожалению, это приводит к достаточно интересным проблемам при конструировании базовых/деконструировании классов
Выглядит как:
Remember pointer to another register
call destructor of TObject
Move pointer to %rdi
call free memory
В итоге мы лишний раз вызываем сохранение указателя на объект, чтобы удалить память.
Вот в ARM и (внимание!) Fuchsia, WebAssembly ABI практически такой же как и Itanium кроме того факта, что конструкторы и деструкторы возвращают указатель на объект. Из-за этого можно делать tail call оптимизации:
call destructor of TObject
Move return value to %rdi
call free memory
И не надо сохранять никаких объектов до этого
Последняя операция возможна, так как регистры уже подготовлены и все объекты, память готовы к использованию. В первом случае такая операция невозможна, так как ABI не ждёт возврата. Это экономит код, немного перформанса.
С конструкторами чуть интереснее: оно позволяет экономить на перекладывания регистров в случае наследований
Fuchsia ABI позволит делать tail call из B сразу в A, так как все регистры уже подготовлены и указатель на память на начало B тот же, что и на начало A.
В clang вы можете включить опцию
Поэтому, я конечно же, предлагаю пропатчить clang одной строчкой здесь, чтобы разрешить использовать Fuchsia ABI. (просто return true;)
А сам код максимально приятный.
Итог: бинарь кликхауса в релиз режиме был 539854128, стал 520550564 байт, 3.6% размера. Запустился и вроде даже какие-то тесты прошёл.
Перф тесты надо бы запустить, но компилятор патчить и вставлять для этого пока лень. clang на 0.5-1% по тестам стал быстрее компилировать всё после его же бутстрапа.
Какие у этого проблемы? Если вы используете динамическую линковку с C++ библиотеками, будут проблемы, поэтому это работает только если вы контролируете статическую сборку.
Даже в ARM через gdb видны эти вещи
Такие вот проблемы с Itanium ABI, которые мы навряд ли уберем из-за legacy :)
[1] ARM ABI constructor/destructor return values
[2] Itanium ABI constructor/destructor return values note
К сожалению, это приводит к достаточно интересным проблемам при конструировании базовых/деконструировании классов
void destroy(TObject* object) {
delete object;
}
Выглядит как:
Remember pointer to another register
call destructor of TObject
Move pointer to %rdi
call free memory
В итоге мы лишний раз вызываем сохранение указателя на объект, чтобы удалить память.
Вот в ARM и (внимание!) Fuchsia, WebAssembly ABI практически такой же как и Itanium кроме того факта, что конструкторы и деструкторы возвращают указатель на объект. Из-за этого можно делать tail call оптимизации:
call destructor of TObject
Move return value to %rdi
call free memory
И не надо сохранять никаких объектов до этого
Последняя операция возможна, так как регистры уже подготовлены и все объекты, память готовы к использованию. В первом случае такая операция невозможна, так как ABI не ждёт возврата. Это экономит код, немного перформанса.
С конструкторами чуть интереснее: оно позволяет экономить на перекладывания регистров в случае наследований
class B { B(int) {...} }
class A : public class B { A(int x) : B(x) {...} }
Fuchsia ABI позволит делать tail call из B сразу в A, так как все регистры уже подготовлены и указатель на память на начало B тот же, что и на начало A.
В clang вы можете включить опцию
-fc++-abi=fuchsia -target x86_64-unknown-fuchsia
, но будут проблемы, чтобы скомпилировать что-то большое.Поэтому, я конечно же, предлагаю пропатчить clang одной строчкой здесь, чтобы разрешить использовать Fuchsia ABI. (просто return true;)
А сам код максимально приятный.
class FuchsiaCXXABI final : public ItaniumCXXABI {
public:
explicit FuchsiaCXXABI(CodeGen::CodeGenModule &CGM)
: ItaniumCXXABI(CGM) {}
private:
bool HasThisReturn(GlobalDecl GD) const override {
return isa<CXXConstructorDecl>(GD.getDecl()) ||
(isa<CXXDestructorDecl>(GD.getDecl()) &&
GD.getDtorType() != Dtor_Deleting);
}
};
Итог: бинарь кликхауса в релиз режиме был 539854128, стал 520550564 байт, 3.6% размера. Запустился и вроде даже какие-то тесты прошёл.
Перф тесты надо бы запустить, но компилятор патчить и вставлять для этого пока лень. clang на 0.5-1% по тестам стал быстрее компилировать всё после его же бутстрапа.
Какие у этого проблемы? Если вы используете динамическую линковку с C++ библиотеками, будут проблемы, поэтому это работает только если вы контролируете статическую сборку.
Даже в ARM через gdb видны эти вещи
class A {
public:
A() {}
~A() {}
};
(gdb) ptype A::A
type = class A {
public:
A(void);
~A(int);
} *(A * const)
(gdb) ptype A::~A
type = void *(A * const)
Такие вот проблемы с Itanium ABI, которые мы навряд ли уберем из-за legacy :)
[1] ARM ABI constructor/destructor return values
[2] Itanium ABI constructor/destructor return values note