Assembly 在入口点标签前带有说明的函数是否会导致任何问题(链接)?

Assembly 在入口点标签前带有说明的函数是否会导致任何问题(链接)?,assembly,linker,object-files,Assembly,Linker,Object Files,这实际上是一个链接器/对象文件的问题,但是使用汇编标记,因为编译器从来不会这样做。(尽管也许他们可以!) 考虑这个函数,我想处理一个特殊情况,其中一个代码块与函数入口点位于同一个I-cache行中。为了避免在通常的快速路径中跳过它,将它的代码放在函数的全局符号之前是否安全(wrt.linking/shared libraries/other tools,我没有想到过)呢? 我知道这很愚蠢/过分,见下文。大部分时候我只是好奇。不管这种技术是否有助于使代码在实践中运行得更快,我认为这是一个有趣的问题

这实际上是一个链接器/对象文件的问题,但是使用汇编标记,因为编译器从来不会这样做。(尽管也许他们可以!)

考虑这个函数,我想处理一个特殊情况,其中一个代码块与函数入口点位于同一个I-cache行中。为了避免在通常的快速路径中跳过它,将它的代码放在函数的全局符号之前是否安全(wrt.linking/shared libraries/other tools,我没有想到过)呢?

我知道这很愚蠢/过分,见下文。大部分时候我只是好奇。不管这种技术是否有助于使代码在实践中运行得更快,我认为这是一个有趣的问题

.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
(顺便说一句,我对asm很感兴趣,因为我很好奇glibc是如何实现它的。结果是编译成了一些非常糟糕的代码,有大量的分支。例如,检查NaN的两个输入应该用FP比较来完成,因为这非常快,特别是在非NaN的情况下。)


在反汇编输出中,标签前的指令分组在前一个函数的指令之后。e、 g

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
                ...
0000000000 400AD0:
...
400af0:5d pop%rbp
400af1:e9 7a ff ff jmpq 400a70
400af6:66 2e 0f 1f 84 00 nopw%cs:0x0(%rax,%rax,1)
上午4时00分
400b00:7a 56 jp 400b58
400b02:0f 28 c1移动路径%xmm1,%xmm0
400b05:c3 retq
0000000000 400B06:
400b06:66 0f 2e c1 ucomisd%xmm1,%xmm0
400b0a:74 f4 je 400b00
400b0c:0f 57 db xorps%xmm3,%xmm3
400b0f:66 0f二层c3 comisd%xmm3%xmm0
400b13:74 4b je 400b60
400b15:66 48 0f 7e c0 movq%xmm0%rax
...
请注意,主体中的第四条指令,
comisd
,从
400b0f
开始(并没有完全包含在包含函数入口点的第一个16B对齐块中)。因此,对于无分支快速路径来说,以这种方式进行指令获取和解码可能不是最佳选择。不过,这只是一个例子

因此,即使在文件的开头,这似乎也是可行的。它确实混淆了
objdump
,在
gdb
中并不理想(但这不是一个大问题)。ELF对象文件不会记录符号大小,因此
nm--print size
不会做任何事情。(还有试图计算符号大小的
nm--size sort--print size
,奇怪的是它没有包含我的函数。)

我对Windows对象文件了解不多。那里有更糟糕的事情发生吗

我有点担心这里的正确性:是否有任何东西试图通过将单个函数的符号地址中的字节复制到下面的符号地址来将其从对象文件中复制出来?普通的库存档(
ar
用于静态库)和链接器复制整个对象文件,对吗?否则,他们无法确定是否复制了所有必要的静态数据


此函数可能很少调用,我们希望最小化缓存污染(I$、uop缓存、分支预测器)。如果有的话,请使用冷分支预测器针对未缓存的情况进行优化

这可能很愚蠢,因为未缓存的情况只会很少发生。但是,如果许多函数都以这种方式进行优化,那么总的缓存占用空间将减少,并且可能它们都适合缓存

请注意,最近的Intel CPU根本不做静态分支预测,因此没有理由支持通常不执行分支的前向分支


对于不在BHT中的“未知”分支,我的理解是它们不检查分支是否为“新”分支,而不是默认为向后分支/不向前分支。他们只是使用BHT中已经存在的任何条目,而不清除它

有一种简单的方法可以让它看起来完全正常:在代码前面放一个非全局标签。这使它看起来像(或实际上是)一个
静态
辅助函数

非全局函数可以使用它们想要的任何调用约定相互调用。C编译器甚至可以通过链接时间/整个程序优化来生成这样的代码,甚至可以只优化编译单元中的
静态
函数。跳转(而不是调用)到另一个函数已经用于尾部调用优化

“helper function”代码可以在入口点以外的其他位置跳转到主函数中。我相信这对链接者来说不是问题。只有当链接器改变了辅助对象和主函数之间的距离(通过在它们之间插入一些东西),而没有调整跨越其加宽的间隙的相对跳跃时,这才会中断。我不认为任何链接器一开始会以这种方式插入任何内容,而且这样做而不修复任何分支显然是一个bug

我不确定在生成
.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\u或\u无序
块放在其他地方,而无需将其重新命名为
.L
并更改所有指向它的跳转。)
下一个之后的
将作为一个单独的名称使用。

函数大小对于共享库来说很重要,并且有一个