Open Izaron opened 2 years ago
Подсказка компилятору о том, что более предпочтительна подстановка кода
Вброшу пару идей:
методы, которые по Стандарту implicit inline functions, как то инстанциации шаблонов
Компиляторы разве не должны их инлайнить по максимуму? Гарантируется, что другие единицы трансляции на них ссылаться не будут, если не сказано extern template
. Про остальное не уверен.
Из соображений симметрии можно добавить атрибут «не инлайнить, экономить размер кода»
С этим всё сложно - атрибутов довольно много, вот где я их смотрел https://github.com/llvm/llvm-project/blob/main/clang/lib/CodeGen/CodeGenModule.cpp#L1829-L1990
Атрибут inlinehint
здесь Attribute::InlintHint
. Атрибут "не инлайнить" (т.е. в смысле вообще никогда) это Attribute::NoInline
. Атрибут "экономить размер кода" это то ли Attribute::OptimizeForSize
, то ли Attribute::MinSize
. И там еще несколько есть.
То что вы предлагаете это "атрибут чтобы не было атрибута inlinehint
", я думаю так не сработает
Компиляторы разве не должны их инлайнить по максимуму?
Шаблонные inline functions или вообще? В общем случае всё что меняется у первоначального LLVM IR, это наличие атрибута linkonce_odr
вместо dso_local
, больше ничего. Частные кейсы надо по Стандартам поискать
Со скобочными атрибутами, если не менять Стандарт в указанном месте, то выйдет наверное как [[noinlinehint]] inline int foo()
(чтобы оставить linkonce
, а не linkonce
+inlinehint
), думаю такое не понравится всем((
Кстати, если даже качественные изменения не примут, всё равно бы наверное хотелось, чтобы понятие inline functions в текущем состоянии переименовали в linkonce functions, потому что дикая путаница в данный момент.
Шаблонные inline functions или вообще?
Шаблоны. Про остальное тоже есть вопросы – например, default конструкторы на то и есть, чтобы они как можно сильнее выродились в memset/memcpy. Но пока не готов подкрепить практикой.
чтобы оставить linkonce, а не linkonce+inlinehint), думаю такое не понравится всем((
Да, в этом контексте абсурдно. Но если inlinehint
по умолчанию навесить всему, что implict inline, то будет осмысленно:
class Bar {
void foo() [[noinline]] {
// very long code
}
};
inlinehint объявляется для всех inline functions независимо от способа, каким они стали inline functions
Но если inlinehint по умолчанию навесить всему, что implict inline
Почитал тред из архивов LLVM, производительность не очень хорошая: https://lists.llvm.org/pipermail/llvm-dev/2015-July/087668.html
В той переписке мне приглянулась мысль, что раз стандарт количественных данных не содержит, то и предлагать оптимизации не может. Поэтому мне показалось правильным, если касательно оптимизаций стандарт даст мотивировочную часть и опишет необходимые условия, но не будет иметь резолютивную часть:
The inline specifier indicates to the implementation that inline substitution of the function body at the point of call
is to be preferred to the usual function call mechanismmust be possible. An implementation is not required to perform this inline substitution at the point of call; however, even if this inline substitution is omitted, the other rules for inline functions specified in this subclause shall still be respected.
В результате:
linkonce
в стандарт:A function declaration ([dcl.fct], [class.mfct], [class.friend]) with an inline specifier declares
an inline functiona linkonce function.[Note 4: A constexpr function is implicitly
inlinelinkonce. In the global module, a function defined within a class definition is implicitlyinlinelinkonce ([class.mfct], [class.friend]). — end note]
Спасибо! Да - в paper надо бы написать, что из возможных исходов нарушение статус-кво повлечет непредсказуемые последствия: из переписки 2015 года стало видно, что где-то ускорился код, где-то бинарник увеличился, где-то и то и другое. На такое пойти опасно, это по эффекту почти как легкий слом ABI.
Я думаю что понятие inline variable (в том же параграфе) тоже надо заменить на linkonce variable. Сейчас слово inline там совершенно точно неправильно. Читатели могут думать что компилятор такие переменные всегда "заинлайнит", но это не так - такие переменные кроме отличающегося ODR ничем не отличаются от "обычных".
Если например какой-то TU возьмет адрес у такой переменной, то компилятор ее не сможет заинлайнить в 100% случаев и поместит ее в секцию .rdata
и будет обращаться к ней читая .rdata
. То есть понятия inlinehint
у переменнных by desing не бывает.
Пониже [dcl.inline].2 можно бы написать [Note 2]
, который явно скажет, что у всех остальных linkonce функций нет рекомендации про inline substitution.
В paper в качестве мотивирующего примера (что текущие понятия запутывают) можно еще положить попытку написания в clang-tidy фиксера "лишних inline", который вы кидали в прошлом issue =) Там тоже далеко не сразу поняли что что-то не так.
P.S. @pavelkryukov , как с вами связаться? =) Если у вас есть желание вместе подготовить paper и послать. Если захотите, то моя почта izaronplatz@gmail.com
.
Так вышло, что сейчас в C++ ключевое слово
inline
для функций (точнее, само понятиеinline
-функции) наделяет их двумяя абсолютно ортогональными друг другу effective смыслами. Понятно, что один смысл вытек из другого, но все равно это раздельные сущности. Это:inlinehint
)linkonce_odr
, для удобства далееlinkonce
)Итого условно у функции есть два атрибута -
inlinehint
иlinkonce
.Проблема в том, что: Стандарт написан так, что
inlinehint
дается только функциям, где явно написан спецификаторinline
.Clang выполняет именно это, т.е.
inlinehint
ставится тогда и только тогда, когда есть спецификаторinline
.То есть получается вот что:
inline int random() { return 4; }
будет Иinlinehint
, Иlinkonce
.А вот методы, которые по Стандарту implicit inline functions, как то:
constexpr
- иconsteval
-функции (на самом деле толькоconstexpr
, т.к.consteval
"испаряется" и в LLVM IR его тупо нету)Они только
linkonce
. Надо все равно писать ключевое словоinline
, чтобы былоlinkonce
+inlinehint
.Отсюда возникают разные неприкольные штуки:
constexpr int foo()
иinline constexpr int foo()
есть, это гига контринтуитивно звучит.linkonce
, но не хотим чтобы она былаinlinehint
(т.е. чтобы компилятор ее заоптимайзил сам по-нормальному). То мы не можем этого сделать, функция по-любому будетlinkonce
+inlinehint
.Методы починки:
inlinehint
объявляется для всех inline functions независимо от способа, каким они стали inline functions (т.е. неважно был ли у них написан спецификаторinline
или нет)linkonce
, понятие inline functions переименовывается в linkonce functions, всё остальное остаётся по-прежнему. Если keywordinline
наделяет метод атрибутамиinlinehint
+linkonce
, то keywordlinkonce
мог бы наделять его только атрибутомlinkonce
.(По методам надо провести голосование и потом доработать paper в сторону выбора)
Ответы на возможные вопросы:
Компиляторы же давно не обращают внимание на "подсказки" от программистов Я тоже так думал, но оказалось что нет, если в 2022 году запустить Clang под флагами без оптимизации LLVM IR, то атрибут
inlinehint
можно увидеть. А в самом LLVM IR он судя по исходникам таки влияет на анализ каких-то костов, т.е. это не дохлый атрибут.Почему один атрибут вытекает из другого, если они ортогональны? Чтобы компилятор мог в translation unit заинлайнить (
inlinehint
) функцию, TU нужно "видеть" исходник этой функции, то есть её тело. В общем случае это невозможно обеспечить, потому что если у нас N штук TU, то будет N определений одной и той же функции и линкер сломается. Поэтому эта функция должна являтьсяlinkonce
, чтобы не нарушился ODR.Такая же логика для
constexpr
-функций - TU должен "видеть" ее, чтобы вычислить в compile-time, поэтому тут уж удобнее сделатьlinkonce
автоматически.Конечно, можно было бы подойти с другого пути и объявлять такие функции
static
, но это все равно немного не то - а если внутриstatic
-функции есть статические переменные, то они будут займут память не 1 раз, а N раз, и будут не "расшариваемы" среди разных TU.