2016-02-17 3 views
2

Это действительно вопрос с компоновщиком/объектным файлом, но пометка с сборкой, поскольку компиляторы никогда этого не делают. (Хотя возможно, они могли бы!)Выполняет ли функция с инструкциями перед меткой точки входа проблемы для чего-либо (связывание)?

Рассмотрите эту функцию, где я хочу обрабатывать один специальный случай с блоком кода, который находится в той же строке I-cache, что и точка входа функции. Чтобы избежать перескакивания по нему в обычном быстром пути, безопасно (например, ссылки/общие библиотеки/другие инструменты, о которых я не думал), чтобы поставить код для него перед глобальным символом функции?

Я знаю, что это глупо/overkill, см. Ниже. В основном мне было просто любопытно. Независимо от того, полезен ли этот метод для создания кода, который на самом деле работает быстрее на практике, я думаю, что это интересный вопрос.

.globl __nextafter_pjc  // double __nextafter_pjc(double x, double y) 
.p2align 6 // unrealistic 64B alignment, just for the sake of argument 

// GNU as local labels have the form .L... 
.Lequal_or_unordered: 
    jp .Lunordered 
    movaps %xmm1, %xmm0 # ISO C11 requires returning y, not x. (matters for -0.0 == +0.0) 
    ret 

######### Function entry point/global symbol here #############  
// .p2align something // tuning for Sandybridge, maybe best to just leave this unaligned, since it's only 6B from the alignment boundary 
nextafter_pjc: 
    ucomisd %xmm1, %xmm0 
    je .Lequal_or_unordered 

    xorps %xmm3, %xmm3 
    comisd %xmm3, %xmm0 // x==+/0.0 can be a special case: the sign bit may change 
    je .Lx_zero 

    movq %xmm0, %rax 
    ... // some mostly-branchless bit-ninjutsu that I have no idea how I'd get gcc to emit from C 

    ret 

.Lx_zero: 
    ... 
    ret 
.Lunordered: 
    ... 
    ret 

(BTW, я возиться с ассемблером для nextafter, потому что мне было интересно, как Glibc реализовала. Оказывается current implementation компилирует некоторые действительно неприятный код с тонной ветвей. Например, проверкой оба входа для NaN должно быть сделано с FP сравнить, потому что это супер-быстрый особ. в не-NaN случае.)


в разборке выходе, инструкции перед этикетке сгруппированы после инструкции предыдущей функции. например

0000000000400ad0 <frame_dummy>: 
       ... 
    400af0:  5d      pop %rbp 
    400af1:  e9 7a ff ff ff   jmpq 400a70 <register_tm_clones> 
    400af6:  66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 
    400afd:  00 00 00 
    400b00:  7a 56     jp  400b58 <__nextafter_pjc+0x52> 
    400b02:  0f 28 c1    movaps %xmm1,%xmm0 
    400b05:  c3      retq 

0000000000400b06 <__nextafter_pjc>: 
    400b06:  66 0f 2e c1    ucomisd %xmm1,%xmm0 
    400b0a:  74 f4     je  400b00 <frame_dummy+0x30> 
    400b0c:  0f 57 db    xorps %xmm3,%xmm3 
    400b0f:  66 0f 2f c3    comisd %xmm3,%xmm0 
    400b13:  74 4b     je  400b60 <__nextafter_pjc+0x5a> 
    400b15:  66 48 0f 7e c0   movq %xmm0,%rax 
       ... 

Обратите внимание, что 4-й команд в основном корпусе, comisd, начинается в 400b0f (и не полностью содержится в первом 16B-выровненного блока, который содержит функцию точку входа). Таким образом, это может быть не совсем оптимально для команды-выборки и декодирования для быстрого пути, который не был принят, чтобы сделать это именно таким образом. Это всего лишь пример.

Значит, появляется, чтобы работать, даже в начале файла. Он путает objdump и не идеален в gdb (но это не большая проблема). Объектные файлы ELF в любом случае не записывают размеры символов, поэтому nm --print-size ничего не делает. (И nm --size-sort --print-size, который пытается рассчитать размеры символов, странно не включил мою функцию.)

Я мало знаю о объектных файлах Windows. Что-нибудь хуже происходит?

Я немного обеспокоен правильностью здесь: что-нибудь пытается скопировать отдельные функции из объектных файлов, взяв байты с их символьного адреса на следующий адрес символа? Обычные библиотечные архивы (ar для статических библиотек) и компоновщики копируют целые файлы объектов, верно? В противном случае они не были уверены, что они копируют все необходимые статические данные.


Эта функция, вероятно, называется нечасто, и мы хотим, чтобы свести к минимуму загрязнение кэша (I $, УОП-кэш, филиал-предикторы). И если что-нибудь, оптимизируйте для случая без кэширования с предикторами холодной ветви.

Это, вероятно, глупо, потому что случай без кэширования может произойти нечасто. Однако, если многие функции все оптимизированы таким образом, общий размер кеша уменьшится, и, возможно, они будут все вписываются в кеш.

Обратите внимание, что последние процессоры Intel не выполняют статического предсказания ветвлений вообще, поэтому нет причин поощрять ветви вперед для обычно не принятых ветвей.

Вместо того, чтобы не принимать во внимание отстающие ветви/не приняты для пересылки для «неизвестных» ветвей, которые не находятся в BHT, мое понимание Agner Fog's microarch doc (the branch prediction chapter) заключается в том, что они не проверяют, является ли ветка «новой» или не. Они просто используют любую запись уже в BHT, не очищая ее. This may not be exactly true, though, for Nehalem.

+1

Размер функции важен для общих библиотек, хотя есть и директива '.size' в' gas', которую 'gcc' действительно использует, и' nm' показывает. Я забыл, для чего это фактически используется. Или, может быть, для функций только символ «.type» важен, а размер имеет значение для не-функций. – Jester

ответ

1

Существует простой способ сделать это абсолютно нормальным: поставьте неглобальную метку перед кодом. Это делает его похожим (или фактически be) a static вспомогательная функция.

Неглобальные функции могут звонить друг другу с любым соглашением о вызовах, которое они хотят. Компиляторы C могут даже сделать такой код с оптимизацией link-time/whole-program или даже просто оптимизировать функции static в компиляторе. Переходы (вместо вызовов) на другую функцию уже используются для оптимизации хвостового вызова.

Код «вспомогательная функция» может перейти в основную функцию где-то, кроме точки входа. Я уверен, что это не проблема для линкеров. Это сломалось бы только тогда, когда линкер изменил расстояние между помощником и основной функцией (вставив что-то между ними) без корректировки относительных прыжков, которые пересекают промежуток, который он расширил. Я не думаю, что какой-либо компоновщик будет вставить что-нибудь таким образом, в первую очередь, и делать это без фиксации каких-либо ветвей явно ошибка.

Я не уверен, есть ли какие-либо подводные камни при создании .size ELF метаданных. Я думаю, что я читал, что это важно для функций, которые будут связаны с разделяемыми библиотеками.

Следующие должен работать с любым инструментом, который имеет дело с объектными файлами:

.globl __nextafter_pjc  // double __nextafter_pjc(double x, double y) 
.p2align 6 // unrealistic 64B alignment, just for the sake of argument 


nextafter_helper: # not a local label, but not .globl either 
.Lequal_or_unordered: 
    jp .Lunordered 
    movaps %xmm1, %xmm0 # ISO C11 requires returning y, not x. (matters for -0.0 == +0.0) 
    ret 

######### Function entry point/global symbol here #############  
// .p2align something? 
__nextafter_pjc: 
    ucomisd %xmm1, %xmm0 
    je .Lequal_or_unordered 

    ...  
    ret 

Мы не необходимости простой ярлык и «местный» ярлык, но с использованием различных этикеток для различных целей средства при переустановке вещей требуется меньше изменений. (например, вы можете поместить блок .Lequal_or_unordered в другое место, не переименовав его обратно в .L и изменив все переходы, которые его нацелили.) nextafter_equal_or_unordered будет работать как одно имя.

 Смежные вопросы

  • Нет связанных вопросов^_^