GCC 16 вышел с важным изменением: вложенная функция, которая не обращается к переменным родительской функции, больше не требует trampoline. Раньше так было только при включённой оптимизации, теперь это гарантируется и без неё, и поведение задокументировано. Такую функцию можно безопасно возвращать из родителя. Если же функция захватывает локальный контекст, компилятор в простых случаях предупредит об этом. При этом вложенная функция без захвата может пользоваться статическими переменными, именованными константами и типами — это удобно для колбэков.
Зачем это нужно: когда берётся адрес вложенной функции, GCC создаёт trampoline и кладёт его на стек. Стек приходится делать исполняемым, а это плохо для безопасности. Опция класть trampoline в кучу лучше, но аллокация дороже, а при longjmp он может утечь.
Следующий шаг — патч, который влили в ветку GCC 17. Он добавляет две новые встроенные функции, которые вместе с существующей __builtin_call_with_static_chain позволяют писать колбэки без trampoline. Работает это так: вместо обычного указателя на функцию передаётся её адрес через __builtin_call_code_address и указатель на захваченные данные через __builtin_call_static_chain. По сути GCC сам превращает такой код в рукописный вариант с передачей структуры данных, только указатель идёт не аргументом, а в специальном регистре static chain. Этот регистр уже зарезервирован ABI именно для таких целей — его используют многие языки программирования.
Без полноценной языковой поддержки пример можно сделать красивее: завести generic-тип wide pointer и обернуть встроенные функции в макросы CALL и CLOSURE. Пример из заметки компилируется в одну инструкцию lea eax, [rdi+rdi*2] — эффективное умножение на три.
Дальше в заметках — про использование вложенных функций в clang и GCC и хак, который позволяет избегать trampoline даже на старых версиях GCC.