Что означает "пара адресов функций GP/Function" в IA-64?

Что означает "пара адресов функций GP/function" в Itanium С++ ABI? Что означает GP?

Ответ 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 для следующего раздела кода.