循环“xorl %edx,%eax; shrl $1,%edx”的目的是什么?

2023-12-02

我有以下 x86 汇编代码:

  movl   8(%ebp), %edx  //get an argument from the caller
  movl   $0, %eax
  testl  %edx, %edx
  je     .L1            
.L2:                   // what's the purpose of this loop body?
  xorl   %edx, %eax
  shrl   $1, %edx
  jne    .L2
.L1:
  andl   $1, %eax

课本给出的对应C代码如下

int f1(unsigned x)
{
    int y = 0;
    while(x != 0) {
        __________;
    }
    return __________;
 }

本书要求读者填空并回答“它有什么作用?”的问题。

我无法将循环体合并到一个 C 表达式中。我可以说出循环体的作用,但我不知道它的目的。课本上还说这里的%eax存储的是返回值。那么……目的是什么

andl  $1, %eax

我也不知道。


看起来整个循环的目的是将 32 位参数中的所有位异或在一起。即计算parity.

从最后一条指令开始向后工作(and $1,%eax),我们知道只有结果的低位很重要。

考虑到这一点,xor %edx,%eax变得更清楚:异或当前的低位%edx into %eax。垃圾高了也没关系。

The shr循环直到所有x的位已被移出。我们总是可以循环 32 次来获取所有位,但这比停止一次效率低x是 0。(由于 XOR 的工作原理,我们不需要在 0 位中进行实际的 XOR;这没有效果。)


一旦我们知道了函数的作用,填充 C 就变成了巧妙/紧凑的 C 语法的练习。我一开始以为y ^= (x>>=1);适合循环内部,但这会改变x before第一次使用它。

我认为在一个 C 语句中做到这一点的唯一方法是使用,运算符(它确实引入了序列点,所以阅读是安全的x左侧并修改a的右侧,). So, y ^= x, x>>=1; fits.

或者,为了获得更具可读性的代码,只需作弊并将两个语句放在同一行上;.

int f1(unsigned x) {
    int y = 0;
    while(x != 0) {
        y ^= x;  x>>=1;      
    }
    return y & 1;
 }

这编译为与问题中所示基本相同的 asm, using Godbolt 编译器浏览器上的 gcc5.3 -O3。问题的代码反优化异或归零习惯用法 to a mov $0, %eax,并优化了 gcc 的愚蠢重复ret指示。 (或者可能使用了未执行此操作的早期版本的 gcc。)


循环效率很低:这是一种有效的方法:

我们不需要复杂度为 O(n) 的循环(其中 n 是以位为单位的宽度)x)。相反,我们可以获得 O(log2(n)) 复杂度,并且实际上利用 x86 技巧只执行其中的前 2 个步骤。

我省略了由寄存器确定的指令的操作数大小后缀。 (除了xorw使 16 位异或变得明确。)

#untested
parity:
    # no frame-pointer boilerplate

    xor       %eax,%eax        # zero eax (so the upper 24 bits of the int return value are zeroed).  And yes, this is more efficient than mov $0, %eax
                               # so when we set %al later, the whole of %eax will be good.

    movzwl    4(%esp), %edx      # load low 16 bits of `x`.  (zero-extend into the full %edx is for efficiency.  movw 4(%esp), %dx would work too.
    xorw      6(%esp), %dx       # xor the high 16 bits of `x`
    # Two loads instead of a load + copy + shift is probably a win, because cache is fast.
    xor       %dh, %dl           # xor the two 8 bit halves, setting PF according to the result
    setnp      %al               # get the inverse of the CPU's parity flag.  Remember that the rest of %eax is already zero, so the result is already zero-extended to 32-bits (int return value)
    ret

恩,那就对了,x86 有一个奇偶校验标志(PF)它是从“根据结果设置标志”的每条指令的结果的低 8 位更新的,例如xor.

我们使用np条件因为PF= 1 表示偶校验:所有位的异或 = 0。对于偶校验,我们需要求逆来返回 0。

为了利用它,我们进行了 SIMD 式的水平缩减,将高半部分降低到低半部分并组合,重复两次以将 32 位缩减为 8 位。

在设置标志的指令之前将 eax 归零(使用异或)比设置标志/更有效setp %al / movzbl %al, %eax,正如我在在 x86 汇编中将寄存器设置为零的最佳方法是什么:xor、mov 或 and?.


或者,正如 @EOF 指出的,如果 CPUIDPOPCNT功能位已设置,可以使用popcnt测试低位,看看设置的位数是偶数还是奇数。 (另一种看待这个问题的方法是:异或是不带进位的加法,因此无论您将所有位异或在一起还是将所有位水平相加,低位都是相同的)。

GNU C 也有__builtin_parity and __builtin_popcnt如果您告诉编译器编译目标支持它,则使用硬件指令(使用-march=... or -mpopcnt),但否则编译为目标机器的有效序列。英特尔内在函数始终编译为机器指令,而不是后备序列,并且在没有适当的情况下使用它们会导致编译时错误-mpopcnt目标选项。

不幸的是,gcc 无法将纯 C 循环识别为奇偶校验计算并将其优化为此。一些编译器(例如 clang 和可能的 gcc)可以识别某些类型的 popcount 习惯用法,并将它们优化为popcnt指令,但在这种情况下不会发生这种模式识别。 :(

在 godbolt 上查看这些内容.

int parity_gnuc(unsigned x) {
    return  __builtin_parity(x);
}
    # with -mpopcnt, compiles the same as below
    # without popcnt, compiles to the same upper/lower half XOR algorithm I used, and a setnp
    # using one load and mov/shift for the 32->16 step, and still %dh, %dl for the 16->8 step.

#ifdef __POPCNT__
#include <immintrin.h>
int parity_popcnt(unsigned x) {
    return  _mm_popcnt_u32(x) & 1;
}
#endif

    # gcc does compile this to the optimal code:
    popcnt    4(%esp), %eax
    and       $1, %eax
    ret

另请参阅中的其他链接x86标签维基。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

循环“xorl %edx,%eax; shrl $1,%edx”的目的是什么? 的相关文章

  • 汇编:使用数据段寄存器(DS)

    目前我正在学习 x86 汇编 因为我喜欢微控制器编程 所以我熟悉汇编 目前我一直在到处寻找这个问题的答案 但似乎找不到它 DS寄存器 我知道它应该指向我程序中的全局数据 但我不知道知道它到底是如何工作的 我正在使用 NASM 在大多数简单的
  • 返回地址预测堆栈缓冲区与堆栈存储的返回地址?

    一直在阅读 Agner Fog 的 Intel AMD 和 VIA CPU 的微架构 他在第 34 页描述了 返回地址预测 http www agner org optimize microarchitecture pdf http www
  • 用于计算三角函数、对数或类似函数的算法。仅限加减法

    我正在修复 Ascota 170 古董机械可编程计算机 它已经开始工作了 现在我正在寻找一种算法来展示其功能 例如计算三角或对数表 或类似的东西 不幸的是 从数学运算来看 计算机只能进行整数的加减法 从 1E12到1E12的55个寄存器 甚
  • 段错误...关于你好世界

    这段代码非常简单 但我在 x86 64 Linux 系统上遇到了段错误 这让我很烦恼 刚开始接触asm 请耐心等待 与 NASM 组装nasm f elf64 test asm 与连接ld o test test o SECTION tex
  • 简单内核无法在 GRUB 中启动

    我正在学习一些操作系统开发的知识OSDev org http osdev org 我有一个内核 我正在尝试使用 qemu 在 GRUB Legacy 0 97 中启动 但是 当我输入kernel 200 9 我收到消息 Multiboot
  • 为什么此 NASM 代码会打印我的环境变量?

    本学期我刚刚完成计算机体系结构课程 除其他外 我们一直在涉足 MIPS 汇编并在 MARS 模拟器中运行它 今天 出于好奇 我开始在我的 Ubuntu 机器上摆弄 NASM 基本上只是将教程中的内容拼凑起来 并感受一下 NASM 与 MIP
  • 该程序如何知道该字符串存储的确切位置?

    我用 Radare2 反汇编了一个 C 程序 在这个程序中有很多调用scanf像下面这样 0x000011fe 488d4594 lea rax var 6ch 0x00001202 4889c6 mov rsi rax 0x0000120
  • Biztalk编排逆向工程师

    我的情况是 老开发人员没有留下代码 因此我无法添加任何增强功能或修复错误 我们是否有任何服务 工具可以将编排 映射 反转为原始格式 从 DLL MSI 或运行 BizTalk 应用程序 如果相反的方法不起作用 我希望看到像 Visual s
  • Visual Studio:如何正确构建和指定 x64 和 x86 的配置和平台

    使用 Visual Studio 2012 Professional 和 Ultimate 以及所有最新更新 如何正确指定配置和平台以正确构建 x86 和 x64 当您第一次创建 Winforms 应用程序时 Visual Studio 会
  • 内存映射图形输出

    我正在探索使用内存映射图形绘制像素和线条 我在 Windows 的 Textpad 中使用 TASM 当我单击 运行 时 整个屏幕变成蓝色 就是这样 没有绘制像素 model small stack data saveMode db xVa
  • 在汇编中,指令指定数据类型吗?

    我是汇编语言编程 x86 的初学者 以下说法是否正确 在汇编中 BYTE WORD DWORD 等数据类型分别表示 8 位 16 位和 32 位模式 而不仅仅是整数 它们本身没有意义 它们只是位模式 使用它们的指令赋予了它们意义 汇编 代码
  • 比较已编译的 .NET 程序集?

    有没有什么好的程序可以与编译 NET 程序集进行比较 例如 我有 HelloWorld dll 1 0 0 0 和 HelloWorld dll 2 0 0 0 我想比较差异 我该怎么做 我知道我可以使用 NET Reflector 并使用
  • 就分页分段内存而言的程序寿命

    我对 x86 Linux 机器中的分段和分页过程有一个令人困惑的概念 如果有人能澄清从开始到结束所涉及的所有步骤 我们将很高兴 x86 使用分页分段内存技术进行内存管理 任何人都可以解释一下从可执行的 elf 格式文件从硬盘加载到主内存到它
  • 将 AT&T 语法转换为 INTEL 语法

    我发现这个 GAS 文件包含一些可以从 CD 启动的引导加载程序代码 我想研究它并尝试制作我自己的一个 但唯一的问题是它采用 AT T 语法而不是 Intel 语法 我对 AT T 语法一无所知 我尝试过使用 Intel2gas 转换器 但
  • MFENCE/SFENCE/etc“序列化内存但不序列化指令执行”?

    英特尔系统编程指南第 8 3 节中有关 MFENCE SFENCE LFENCE 的说明 以下指令是内存排序指令 而不是序列化指令 这些指令会耗尽数据内存子系统 它们不序列化指令执行流 我试图弄清楚为什么这很重要 在多线程代码中 对内存的写
  • 汇编PC相对寻址模式

    我正在研究数据路径 并一直在尝试理解分支指令 这就是我的理解 在 MIPS 中 每条指令都是 32 位 这是 4 个字节 所以下一条指令将是四个字节之外 举个例子 我说PC地址是128 我的第一个问题是理解这个128意味着什么 我目前的信念
  • Nasm 点状标签

    我对 TASM 很熟悉 但对 NASM 不太了解 我读过 NASM 允许使用本地标签 这些标签在名称前用点表示 例如 代码 loop some code jmp loop 定义一个名为 loop的局部标号 引用的地址在后面的jmp指令中使用
  • 3 操作数 imul 指令在 ia-32 汇编中到底起什么作用?

    我正在阅读说明 imul 0xffffffd4 ebp ebx 4 eax 我对它到底在做什么感到困惑 我明白那个imul乘法 但我无法弄清楚语法 我知道并且更喜欢 Intel MASM 语法 所以我将使用它 请注意 操作数的顺序在 AT
  • 解释一下 AF 标志在 x86 指令中如何工作?

    我有一个小型 8086 模拟器 并且我已经有一个长期存在的错误了大约 2 年 因为 AF 在 sub 和 add 指令内无法正常运行 我当前计算其值的方法是 8 位数字和减法 uint8 t base subt base base 0xF
  • 了解近调用指令编码

    通过反汇编一些二进制代码 我发现了近调用指令call 0x8ae编码为e8 97 08 00 00 查看指令集参考 我发现这些指令被编码为 call XX XX XX XX lt gt e8 XX XX XX XX being XX XX

随机推荐