Я думаю, вы должны определить значение константы в отдельном файле asm или C .Это гарантированный способ остановить компиляцию значения в любом месте, даже с оптимизацией во время компоновки, не используя ничего столь же неэффективного, как volatile
.то есть компилятор Ada никогда не видит значение константы вообще в любом исходном файле.
Я не знаю Ada, но эквивалент C будет extern const int my_const;
, а затем вотдельный constant.S
файл использует .section .rodata
/ my_const: .long 0x12345
.Или используйте пользовательский раздел и скрипт компоновщика, чтобы ваши изменяемые константы были размещены в определенном месте вашего двоичного файла.
Подробнее о взаимодействии Ada с C или asm см. https://gcc.gnu.org/onlinedocs/gcc-7.3.0/gnat_ugn/Interfacing-to-C.html. В ней есть примеры импортаи экспорт определений символов в / из C. (А сопоставление между C и asm очень простое, поэтому вы можете просто сказать Ada, что это C, даже если вы создаете объектные файлы с использованием asm.)
My_Num : Integer;
pragma Import (C, My_Num, "Ada_my_num");
В идеале вы можете объявить My_Num
так, чтобы компилятор Ada знал, что это константа. Это объявляет его как обычный глобальный (с внешним определением, использующим имя символа Ada_my_num
C).
Это очень похоже на то, что предлагает ответ @ Jose, за исключением использования стандартногоодин asm, чтобы скрыть значение от компилятора.
Вы хотите, чтобы компилятор мог максимально оптимизировать .то есть предположить, что значение этой «переменной» не изменяется в течение времени жизни программы, поэтому она может загрузить ее в регистр при запуске функции и предположить, что вызовы не встроенных функций не могут ее изменить,Или чтобы избежать повторных вычислений с этим ( CSE ), поэтому, если ваш исходный код имеет
a * my_const
как до, так и после вызова функции, он может сохранить результат в регистре вместо перезагрузки константыиз памяти и повторного умножения после вызова функции.
Этого не может быть, если компилятор считает, что это обычная глобальная переменная с неизвестным значением;нужно было бы предположить, что вызов функции мог изменить значение любой глобальной переменной.
Но если вы использовали обычную глобальную переменную и присваивали ей значение везде, где может видеть компилятор Ada, тогда вся программаОптимизация времени соединения может распространить это значение в других местах или даже превратить его в другие константы.(например, если вы когда-либо сделаете a += 2 * my_constant
, вы могли бы жестко кодировать 2*my_constant
где-нибудь в своем выводе asm).
(например, если вы скомпилируете + ссылку с -flto
напусть компилятор оптимизирует между модулями компиляции IDK, если GNAT может сделать это так же, как это делает интерфейс C, но, надеюсь, может.)
Почему компилятор делает это: потому что этоконечно, более эффективный!
Загрузка значения из статических данных в памяти обычно требует нескольких инструкций для генерации 32-битного адреса (на ISA с фиксированной шириной инструкции, такой как SPARC);с таким же количеством инструкций вы могли бы создать произвольную 32-битную константу непосредственно в регистре.Инструкции ALU обычно дешевле загрузок и не могут отсутствовать в кэше.
Небольшие константы еще более эффективны и могут использоваться в качестве единственного непосредственного операнда для add
, or
, and
или что-то еще.
Постоянное свертывание и постоянное распространение после встраивания - это основной способ, с помощью которого ассемблерная версия программы может выполнять меньше работы, чем исходная.Например, 5 * my_const
может быть сделано во время компиляции, если компилятору известно значение my_const
.(Таким образом, он может генерировать , что в регистре напрямую, при необходимости, вместо загрузки my_const
и использования shift / add.)
Некоторая универсальная функция может проверять if(x>0)
, ноКомпилятор может доказать, что это всегда так в одном месте, где встроена функция, если он знает что-то о значениях констант.(Это оптимизация диапазона значений).
Отказ вашему компилятору в значении константы определенно может сделать ваш код менее эффективным, в зависимости от того, как вы используете константу.
Пример вывода компилятора .(Я предполагаю, что вы можете написать эквивалентный Ada, который серверная часть SPARC для GNAT / gcc будет оптимизировать аналогично тому, что делает clang / LLVM -target sparc
).Суть этого в том, чтобы проиллюстрировать разницу между константой неизвестного значения и известной константой:
( From clang6.0 -O3 -fomit-frame-pointer -target sparc
в проводнике компилятора Godbolt )
const int my_const1, my_const2;
static const int my_static = 123; // a small constant that works as an immediate
int foo(int x) {
return x + my_static;
}
retl
add %o0, 123, %o0 # branch-delay slot
int one_constant(int x) {
return x + my_const1;
}
sethi %hi(my_const1), %o1
ld [%o1+%lo(my_const1)], %o1
retl
add %o1, %o0, %o0
Последний, очевидно, менее эффективен.Кажется, что clang не знает, совпадают ли / когда %hi(my_const1)
и %hi(my_const2)
, поэтому он использует другой sethi
для каждого статического местоположения.В идеале компилятор мог бы использовать одну и ту же контрольную точку для множественного доступа внутри большой функции, но это не относится к clang / LLVM.У Godbolt нет SPARC GCC, поэтому я не мог попробовать это легко.