Я тут обнаружил забавную историю, о которой не так много говорят. В Itanium ABI -- у Unix* подобных системах есть пункт про конструкторы и деструкторы классов, чтобы они возвращали void. Оно логично, конструкторы и деструкторы классов ничего не возвращают, зачем им вообще возвращаемые значения.



К сожалению, это приводит к достаточно интересным проблемам при конструировании базовых/деконструировании классов



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