将 C 函数对齐到 "odd" 地址

Align C function to "odd" address

我从 C Function alignment in GCC 我可以使用

对齐函数
    __attribute__((optimize("align-functions=32")))

现在,如果我希望函数从 "odd" 地址开始,我希望它从 32(2k+1) 形式的地址开始,其中 k是整数吗?

我希望函数从地址(十进制)32、96 或 160 开始,而不是 0、64 或 128。

上下文:我正在做一个关于代码缓存的研究项目,我想要一个函数在一级缓存中对齐但在另一级缓存中未对齐。

我相信 GCC 只能让你对齐 2 的幂

如果你想绕过这个进行测试,你可以使用位置独立代码(-FPIC 或 -FPIE)编译你的函数,然后编写一个单独的加载器,手动将函数复制到一个 MMAP 的区域作为 read/write。然后您可以更改权限以使其可执行。当然,为了进行适当的性能比较,您需要确保要与之进行比较的对齐代码也是使用 FPIC/FPIE.

编译的

如果你需要的话,我可能会给你一些示例代码,请告诉我。

GCC 没有这样做的选项。

相反,编译为 asm 并对该输出进行一些文本操作。例如gcc -O3 -S foo.c 然后 运行 foo.s 上的一些脚本在一些函数标签之前进行奇数对齐,然后使用 gcc -o benchmark foo.s.

编译为最终的可执行文件

一种简单的方法(花费 32 到 95 字节的填充)是这种简单的方法:

 .balign 64        # byte-align by 64
 .space 32         # emit 32 bytes (of zeros)
starts_half_way_into_a_cache_line:
testfunc1:

编译后调整 GCC/clang 输出通常是探索 gcc 应该 做什么的好方法。 函数内外对其他code/data的所有引用都使用符号名称,在您assemble(和link)之前,不依赖于函数之间的相对距离或绝对地址,所以此时编辑 asm 源代码是完全安全的。 (另一个答案建议复制最终机器代码;这非常脆弱,请参阅它下面的评论。)

自动文本处理脚本可以让您 运行 在大量代码上进行实验。可以简单到
awk '/^testfunc.*:/ { print ".p2align 6; .skip 32"; print [=15=] }' foo.s
在每个匹配模式 ^testfunc.* 的标签之前执行此操作。 (假设没有前导下划线名称修饰。)

或者甚至使用 sed,它有一个方便的 -i 选项来完成它 "in-place" 通过在原始文件上重命名输出文件,或者 perl 有类似的东西。 幸运的是,编译器的输出非常公式化,对于给定的编译器来说,它应该是一个非常简单的模式匹配问题。


请记住,代码对齐的效果并不总是纯局部的。一个函数中的分支可以(在分支预测器中)与另一个函数中的分支别名取决于对齐细节。

可能很难确切地知道为什么更改会影响性能,尤其是如果您在函数的早期讨论它会将函数其余部分的分支地址移动几个字节。不过,您并不是在谈论那样的变化,只是在改变整个功能。但它会改变相对于其他函数的对齐方式,因此调用多个函数的测试相互交替,或者如果函数相互调用,可能会受到影响。

对齐的其他影响包括现代 x86 上的 uop-cache 打包,以及获取块。 (除了在 I-cache 行中保留未使用 space 的明显效果)。


理想情况下,您只需插入 0..63 个字节即可到达相对于 64 字节边界的所需位置。本节是使它起作用的失败尝试。

.p2align.balign1 支持可选的第三个 arg,它指定最大填充量,所以我们即将要做它与 GAS 指令。我们也许可以在此基础上通过检查它是否插入任何填充来检测我们是否接近奇数或偶数边界。 (假设我们只讨论 2 种情况,而不是 16 字节相对于 64 字节的 4 种情况。)

# DOESN'T WORK, and maybe not fixable
1:  # local label
 .balign 64,,31     # pad with up to 31 bytes to reach 64-byte alignment
2:
 .balign  32        # byte-align by 32, maybe to the position we want, maybe not
.ifne 2b - 1b
  # there is space between labels 2 and 1 so that balign reached a 64-byte boundary
  .space  32
.endif       # else it was already an odd boundary

但不幸的是这不起作用:Error: non-constant expression in ".if" statement。如果 1:2: 标签之间的代码具有固定大小,例如 .long 0xdeadbeef,那么 assemble 就可以了。因此,显然 GAS 不会让您使用 .if 插入对齐指令的填充量进行查询。

脚注 1:.align.p2align(2 的幂)或 .balign(字节),具体取决于您要组装的目标。我建议始终使用 .p2align.balign 而不是记住哪个是哪个目标,而不是 .align.

由于这个问题被标记为程序集,因此我的 (NASM 8086) 来源中有两个地方 "anti align" 遵循说明和数据。 (这里只是对齐到偶地址,即 2 字节对齐。)两者都是基于 NASM 的对齐宏所做的计算。

https://hg.ulukai.org/ecm/ldebug/file/683a1d8ccef9/source/debug.asm#l1161

        times 1 - (($ - $$) & 1) nop    ; align in-code parameter
        call entry_to_code_sel, exc_code

https://hg.ulukai.org/ecm/ldebug/file/683a1d8ccef9/source/debug.asm#l7062

                ; $ - $$        = offset into section
                ; % 2           = 1 if odd offset, 0 if even
                ; 2 -           = 1 if odd, 2 if even
                ; % 2           = 1 if odd, 0 if even
        ; resb (2 - (($-$$) % 2)) % 2
                ; $ - $$        = offset into section
                ; % 2           = 1 if odd offset, 0 if even
                ; 1 -           = 0 if odd, 1 if even
        resb 1 - (($-$$) % 2)           ; make line_out aligned
trim_overflow:  resb 1                  ; actually part of line_out to avoid overflow of trimputs loop
line_out:       resb 263
                resb 1                  ; reserved for terminating zero
line_out_end:

这里有一个更简单的实现反对齐的方法:

                align 2
                nop

不过这比较浪费,如果在这个序列之前已经满足了目标反对齐,它可能会用完2个字节。我之前的例子不会保留任何超过必要的space。