Ну вот прыгнули на базовый блок. А вернуться-то назад как?
дополнительная локальная переменная куда записывается адрес базового блока с которого мы прыгнули
Не обязательно, при трансформаци в машинный код на последнем проходе можно соединить эти две инструкции в одну. В общем то что я описал это просто абстракция как и SSA когда мы сначала трансформируем в промежуточное представление чтобы удобно было на нем проводить оптимизации а потом трансформируем обратно Здесь я вижу удобство в том что у нас нет разделения на inter/intra procedure/module оптимизации - есть просто CFG граф базовых блоков и дальше даже базовые блоки можно разложить на ноды которые через SSA будут связаны одновременно по данным и по control flow и получается один глобальных граф нод-инструкций для всего проекта (без функций, без модулей и даже без базовых блоков) В общем кто-нибудь знает какими терминами называется этот подход и есть ли статьи и доклады об этом?
а насколько вообще дорого делать вызов функции? сколько циклов это отбирает?
со своим стеком могут наверное возникнуть проблемы безопасности, теневой же как раз защищает от подделки адреса возврата
Теневой исходно был для ускорения return, лишь потом на него допфункцию для безопасности навесили
Я просто вижу что вокруг такой темы как инлайнинг функций сильно много шума (оптимизаций, эвристик и т.д) и очень много оптимизаций/проходов зависят от инлайнинга но если подумать то функцию необязательно инлайнить перед оптимизациями точнее мы можем перенести трейдоффы с инлайнингом на другой уровень - если представим вызов функции как прыжок на базовый блок в общем cfg-графе базовых блоков то там уже будут работать локальные оптимизации которые могут инлайнить (дублировать) базовые блоки. И тогда те оптимизации которые ранее работали только внутри функции (и их эффект сильно зависел от того какое количество функций заинлайнилось) теперь начнут работать глобально на уровне всей программы потому что у нас будет общий граф базовых блоков на всю программу без разделения на функции
А какой в этом смысл ? Цель ? Т.е. если код часто переиспользуется, то логичнее делать функцию, если же редко, то выгодно встроить. А мыслить базовыми блоками... можно просто эти базовые блоки завернуть в функции и считать проблему решённой. По сути выбор между функцией/встраиванием это выбор между двумя путями достижения кода - линейный или через движение по коду. Как вы себе представляете вход в базовый блок и выход из него ? Что будет со стеком, как войдя в него вы из него выйдете - по сути в зависимости от ответа на эти вопросы и делается выбор между встройкой и вычленением кода в функцию. Т.е. проблема в том, что функция и есть базовый блок, который можно как встроить, восстановив связи, так и оставить отделённым. И суть в том, что функции и встраивание - это две единственные возможные крайности(за рамками прерываний в железе и ВМ)
Это очень похоже на CPS-преобразования: continuation passing style - когда у вас в коде есть только параметризованные линейные участки, а вызовы функций уже сведены к передачи управления между ними. Но у этого обратная сторона есть - отладка существенно усложняется. Нужно прибегать к инструментализации. И production-код существенно отличается от debug.
От размера кода ещё зависит. Смысл может быть в системах, где поддерживаются хвостовые вызовы. Тогда хвостовой вызов не нужно специально обрабатывать, мы просто видим, что точка выхода из функции остаётся прежней, и просто переходим на линейный блок дальше.
К тому же это мб даже тяжелее вызова функций, ибо вызов функции это по сути запись адреса возврата, сброс кэша и передача параметров, всё это в любом случае будет зашито в параметризованный кусок. Плюс ни каких проблем с отладкой не будет, ибо для этого нужно просто просить компилятор встраивать инструментальные функции в начало и конец такого блока.
Чем это лучше межпроцедурного анализа? Пока что видно только недостатки: теневой стек для возврата из блоков и сложность масштабирования из-за величины графа
Простите, я всё ещё не понимаю, как такой подход рифмуется с проблемкой аллоцировать регистры для всей программы целиком, если в программе, скажем, пол-тыщи строк кода на C?
Так можно для каких-то проходов разворачивать все в граф-суп нод, для каких-то проходов обратно сворачивать в функции и делать оптимизации локально. @true_grue вроде это тоже имел ввиду.
Можно все сделать и на уровне управляющего графа (CFG). Достаточно включить в этот граф функции и связать входы и выходы функций ребрами. Далее можно будет проводить обычный анализ потока данных на уровне совокупности функций.
Но это ведь то, как оно прямо сейчас и делается! 😂
Обсуждают сегодня