Что означает "пара адресов функций GP/function" в Itanium С++ ABI? Что означает GP?
Что означает "пара адресов функций GP/Function" в IA-64?
Ответ 1
Краткое объяснение: gp
, по всем практическим меркам, является скрытым параметром для всех функций, соответствующих Itanium ABI. Это своего рода указатель this
для глобальных переменных, которые использует функция. Насколько я знаю, никакая основная ОС не делает этого больше.
GP означает "указатель globals". Это базовый адрес для данных, статически выделяемых исполняемыми файлами, а для архитектуры Itanium для этого есть регистр.
Например, если у вас были эти глобальные переменные и эта функция в вашей программе:
int foo;
int bar;
int baz;
int func()
{
foo++;
bar += foo;
baz *= bar / foo;
return foo + bar + baz;
}
Пара gp/function концептуально будет &foo, &func
. Код, сгенерированный для func
, будет ссылаться на gp
, чтобы найти, где находятся глобальные блоки. Компилятор знает, что foo
можно найти в gp
, bar
можно найти в gp + 4
и baz
можно найти в gp + 8
.
Предполагая, что func
определяется во внешней библиотеке, если вы вызываете его из своей программы, компилятор будет использовать последовательность инструкций, подобных этой:
- сохранить текущее значение gp в стеке;
- введите код нагрузки из пары для
func
в некоторый регистр; - загрузить значение gp из той же пары в GP;
- выполнить косвенный вызов в регистре, где мы сохранили адрес кода;
- восстановить прежнее значение gp, которое мы сохранили в стек раньше, возобновить функцию вызова.
Это делает исполняемые файлы полностью независимыми от позиции, поскольку они никогда не сохраняют абсолютные адреса в символах данных и, следовательно, позволяют поддерживать только один экземпляр любого исполняемого файла в памяти, независимо от того, сколько его использует процесс (вы могли бы даже загружать один и тот же исполняемый файл несколько раз в течение одного процесса и по-прежнему иметь только одну копию исполняемого кода в системе), за счет того, что указатели функций немного странные. С помощью Itanium ABI указатель на функцию не является кодовым адресом (например, с "регулярными" x86 ABI): это адрес для значения gp и адрес кода, поскольку этот код может не стоить многого, если он может " t получить доступ к своим глобальным переменным, точно так же, как метод, возможно, не сможет многое сделать, если у него нет указателя this
.
Единственный другой ABI, который я знаю, использует эту концепцию Mac OS Classic PowerPC ABI. Они называли эти пары "переходными векторами".
Так как x86_64 поддерживает RIP-относительную адресацию (x86 не имеет эквивалентной EIP-относительной адресации), теперь довольно легко создать независимый от позиции код без необходимости использовать дополнительный регистр или использовать "расширенные" указатели на функции. Код и данные просто должны храниться при постоянных смещениях. Таким образом, эта часть Itanium ABI, вероятно, ушла на пользу на платформах Intel.
Из Соглашения о регистрации Itanium:
8.2 Регистр gp
Каждая процедура, которая ссылается на статически распределенные данные или вызывает другую процедуру, требует указатель на свой сегмент данных в регистре gp, чтобы он мог получить доступ к своим статическим данным и их таблицам связей. Каждый загрузочный модуль имеет свой собственный сегмент данных, а регистр gp должен быть установлен правильно до вызова любой точки входа в этом загрузочном модуле.
Соглашения о привязке требуют, чтобы каждый модуль загрузки определял ровно одно значение gp, чтобы ссылаться на местоположение в его коротком сегменте данных. Ожидается, что это местоположение будет выбрано так, чтобы максимизировать полезность немедленных инструкций короткого перемещения для адресации скаляров и записей таблицы привязки. DLL-загрузчик будет определять абсолютное значение регистра gp для каждого загрузочного модуля после загрузки его сегмента данных в память.
Для вызовов внутри модуля загрузки регистр gp останется без изменений, поэтому вызовы, известные как локальные, могут быть оптимизированы соответствующим образом.
Для вызовов между модулями нагрузки регистр gp должен быть инициализирован с правильным значением gp для нового модуля загрузки, а вызывающая функция должна гарантировать, что его собственное значение gp будет сохранено и восстановлено.
Ответ 2
Просто комментарий об этой цитате из другого ответа:
It is expected that this location will be chosen to maximize the usefulness of short-displacement immediate instructions for addressing scalars and linkage table entries.
О чем это говорит: Itanium имеет три разных способа поместить значение в регистр (где "немедленный" здесь означает "смещение от базы" ). Вы можете поддерживать полное 64-битное смещение из любого места, но оно принимает две инструкции:
// r34 has base address
movl r33 = <my immediate>
;;
add r35 = r34, r35
;;
Это не только два отдельных такта, он занимает 3 слота для инструкций по двум пучкам, чтобы это произошло.
Существуют две более короткие версии: add14
(также adds
) и add22
(также addl
). Разница заключалась в непосредственном размере, с которым каждый мог справиться. Каждый из них взял один слот "A" iirc и был завершен за один такт.
add14
может использовать любой регистр в качестве источника и цели, но может обрабатывать только до 14 бит.
add22
может использовать любой регистр в качестве цели, но для источника выделяются только два бита. Таким образом, вы можете использовать r0
, r1
, r2
, r3
в качестве исходных рег. r0
не является реальным регистром - он подключен к 0. Но использование одного из других 3 в качестве локальных регистров стека означает, что вы можете адресовать 256 раз памяти с помощью простых смещений, по сравнению с использованием локальных регистров стека. Поэтому, если вы поместите свой глобальный базовый адрес в r1
(соглашение), вы можете получить доступ к гораздо более локальным смещениям, прежде чем выполнять отдельный movl и/или модифицировать gp для следующего раздела кода.