【问题标题】:Overlapping registers error重叠寄存器错误
【发布时间】:2018-07-28 23:38:54
【问题描述】:

内联汇编代码在其中一个 for 循环中替换 C++ 语句。

有时它会神奇地工作并产生正确的结果 - 使用基数排序排序的数组。另一次 Xcode 生成了一个Thread 1: EXC_BAD_ACCESS (code=1, address=0x1eccccccccd) 错误,我使用反汇编视图追溯到incq (%[count], %%rdx, 4) 行。

我的理解

反汇编视图incq (%[count], %%rdx, 4)incq (%rax,%rdx,4)。这可能意味着相同的寄存器用于不同的操作数(%%rax 已在movq (%[array], %%rcx, 4), %%rax 行上使用),问题出在:: [array] "r" (array), [count] "r" (count), "b" (digit), "c" (i)

我不明白的地方

如何管理寄存器,以便我有足够多的寄存器供我使用(分配给输入操作数和稍后的正文代码)并且它们不会同时重叠。我尝试了几种组合,但都没有奏效。

void countingSort(int array[], int length, int digit) {
    int i, count[10] = { };
    int sorted[length];

    // Store number of occurrences in count[].
    // for (i = 0; i < length; i++)
    //     count[ (array[i] / digit) % 10 ]++;

    for (i = 0; i < length; i++)
        asm volatile (
                      "movq (%[array], %%rcx, 4), %%rax \n\t"

                      "xorq %%rdx, %%rdx \n\t"
                      "divq %%rbx \n\t"
                      "movq $10, %%rbx \n\t"
                      "xorq %%rdx, %%rdx \n\t"
                      "divq %%rbx \n\t"

                      "incq (%[count], %%rdx, 4) \n\t"

                      :: [array] "r" (array), [count] "r" (count), "b" (digit), "c" (i)
                      : "memory"
        );

    // ...
}

完整代码:

#include<iostream>
using namespace std;

void print(int array[], int length) {
    for (int i = 0; i < length; i++)
        cout << array[i] << " ";
}

int findMax(int array[], int length) {
    int max = array[0];
    for (int i = 1; i < length; i++)
        if (array[i] > max)
            max = array[i];
    return max;
}

void countingSort(int array[], int length, int digit) {
    int i = 0, count[10] = { };
    int sorted[length];

    // Store number of occurrences in count[].
    // for (i = 0; i < length; i++)
    //     count[ (array[i] / digit) % 10 ]++;

    for (i = 0; i < length; i++)
        asm volatile (
                      "movq (%[array], %%rcx, 4), %%rax \n\t"

                      "xorq %%rdx, %%rdx \n\t"
                      "divq %%rbx \n\t"
                      "movq $10, %%rbx \n\t"
                      "xorq %%rdx, %%rdx \n\t"
                      "divq %%rbx \n\t"

                      "incq (%[count], %%rdx, 4) \n\t"

                      :: [array] "r" (array), [count] "r" (count), "b" (digit), "c" (i)
                      : "memory"
        );

    // Change count[i] so that count[i] now contains actual
    // position of the digit in sorted[].
    // for (i = 1; i < 10; i++)
    //     count[i] += count[i - 1];

    for (i = 1; i < 10; i++)
        count[i] += count[i - 1];

    // Build the sorted array.
    for (i = length - 1; i >= 0; i--) {
        sorted[count[ (array[i] / digit) % 10 ] - 1] = array[i];
        count[ (array[i] / digit) % 10 ]--;
    }

    // Copy the sorted array to array[].
    for (i = 0; i < length; i++)
        array[i] = sorted[i];
}

void radixSort(int array[], int length) {
    // Maximum number helps later when counting number of digits.
    int max = findMax(array, length);

    // Do Counting sort for every digit.
    for (int digit = 1; max / digit > 0; digit *= 10)
        countingSort(array, length, digit);
}

int main() {
    int array[] = { 2, 4928, 48, 72, 280, 4, 66, 3, 1, 0, 4829 };
    int length = sizeof(array) / sizeof(array[0]);

    radixSort(array, length);
    print(array, length);
    return 0;
}

【问题讨论】:

  • 您忘记将被破坏的寄存器添加到被破坏的列表中。
  • @liliscent 是的,你是对的。将"rax", "rdx" 添加到破坏列表修复了错误,但以某种方式改变了循环本身。现在输出是{ 72 4928 4928 4829 4928 4928 0 4928 4829 4928 4928 },而不是排序的{ 0, 1, 2, 3, 4, 48, 66, 72, 280, 4928, 4829 } 数组。您能看出发生这种情况的任何原因吗?
  • 你的数据大小不对,int应该有d后缀,而不是q。尤其是第一个movq,将下一个int移动到rax的高位。
  • 为什么首先要内联 asm?为什么不把它写成 C++ 并让编译器来处理呢?无论如何,它可能会生成一样好或更好的 asm(打开优化时)。
  • @liliscent : MOVSL 是移动长的字符串指令之一。您的意思是MOVSXD 将 32 位值移动到带有符号扩展名的 64 位寄存器吗?或者 MOVL 只是将 32 位数据移动到 32 位寄存器(CPU 自动零将其自动扩展到 64 位寄存器的高 32 位)

标签: sorting assembly x86 g++ inline-assembly


【解决方案1】:

看来这是课堂作业。在现实世界中this would not be done with inline assembly

问题:

  • 当您将 10 移动到 RBX 时,您会通过覆盖关联的寄存器来破坏 digitC++ 编译器不知道这一点,因为您没有提到 RBX 在约束中被修改。编译器可能会假设 RBX 在您的内联汇编之前和之后是相同的。
  • 由于您使用的是 32 位整数,因此您可以使用 long 而不是四字除法。
  • DIV 是无符号除法,IDIV 是有符号除法。您的 C++ 代码对有符号数字进行操作。不过,这真的无关紧要,因为此代码会因数组中的负数而崩溃。如果您使用签名除法,您可以使用 CDQ 将扩展 EAX 签名到 EDX
  • 使用程序集模板通过编译器选择的寄存器传递除数 (10)。
  • 您可以在约束中将i 转换为long 类型。在 64 位代码中,long 是 64 位的,当在程序集模板中引用时,默认情况下会导致编译器使用 64 位寄存器。
  • 整数是 4 字节宽,而不是 8 字节。访问与整数数组关联的内存时,您希望使用读取 4 字节的指令。您使用的 MOVQINCQ 指令移动 8 个字节。您需要考虑 MOVLINCL 或移动 4 个字节的其他等效项。如果要将带符号的 4 字节值从内存移动到 64 位寄存器,可以使用 MOVSXD。在 AT&T 语法中,最好使用 MOVSLQ/MOVSLMOVSXL,因为 CLANG 和 GCC/G++ 都可以理解它们。
  • 尽可能让编译器为输入和输出操作数选择寄存器,而不是在汇编模板中硬编码它们。在您的代码中,唯一被其中一条指令隐式修改且我们无能为力的寄存器是 RDX。将其添加到 clobbers 列表中,以便编译器知道其内容可能会更改。

修改后的代码可能如下所示:

    asm (
             "movslq (%[array], %[index], 4), %%rax \n\t"
             "cdq \n\t"               /* Sign extend eax into edx */
             "idivl %[digit] \n\t"    /* array[i]/digit */
             "cdq \n\t"               /* Sign extend eax into edx */
             "idivl %[divisor] \n\t"  /* (array[i] / digit) mod 10 */
             "incl (%[count], %%rdx, 4)"
              : "=m" (*(int (*)[]) count) /* instead of memory clobber */
              : [divisor] "r" (10), [array] "r" (array), [count] "r" (count),
                [digit] "r" (digit), [index] "r" ((long)i),
                "m" (*(const int (*)[]) array) /* instead of memory clobber */
              : "rax", "rdx", "cc"
    );

我还删除了内存破坏器,并告诉它该数组是一个将被修改的输出内存操作数。这在GCC inline assembly documentation 中进行了讨论。因为除了约束和clobbers中指定的组件模板没有其他副作用,我们不需要使用volatile

您可以删除第一条 MOV 指令并使用中间变量。这将允许您通过 EAX 的约束传递array[i] 的当前值。由于 EAX 现在处于其自己的输入/输出约束中(使用+),我们可以将其从clobbers 中删除。代码可能如下所示:

    int curval;
    asm (
             "cdq \n\t"               /* Sign extend eax into edx */
             "idivl %[digit] \n\t"    /* array[i]/digit */
             "cdq \n\t"               /* Sign extend eax into edx */
             "idivl %[divisor] \n\t"  /* (array[i] / digit) mod 10 */
             "incl (%[count], %%rdx, 4)"
             : "=m" (*(int (*)[]) count), /* instead of memory clobber */
               "+&a" (curval = array[i])  /* Early clobber, we modify it
                                             before all inputs processed */
             : [divisor] "r" (10), [array] "r" (array), [count] "r" (count),
               [digit] "r" (digit), [index] "r" ((long)i)
             : "rdx", "cc"
    );

如果上面的答案过于复杂,并且您想修复代码中的直接问题,那么最少的更改可能如下所示:

    asm volatile (
                  "movl (%[array], %%rcx, 4), %%eax \n\t"
                  "xorq %%rdx, %%rdx \n\t"
                  "divq %%rbx \n\t"
                  "movq $10, %%rsi \n\t"
                  "xorq %%rdx, %%rdx \n\t"
                  "divq %%rsi \n\t"
                  "incl (%[count], %%rdx, 4) \n\t"
                  :: [array] "r" (array), [count] "r" (count), "b" (digit), "c" ((long)i)
                  : "memory", "rax", "rdx", "rsi"
    );
  • 我们修复了修改包含digit 的寄存器,通过使用另一个寄存器来存储10 来进行除法。如果优化编译器假定寄存器的值没有改变,那么修改仅作为输入约束列出的寄存器可能会导致未定义的行为。编译器必须知道发生了什么变化。
  • 由于我们的程序集模板现在修改了 RAXRDXRSI,我们必须将它们添加到 clobber 列表中。如果它们还没有与之关联的输出约束,则它们需要在 clobber 列表中。
  • 我们使用 MOVL 指令将一个 4 字节整数从数组移动到 EAX。当指令的目标寄存器是 32 位寄存器时,CPU 会自动将其零扩展至 64 位寄存器的高 32 位。
  • INCQ 改为 INCL 以更新 4 字节内存地址。

注意事项

  • 在针对 x86 和 x96-64 平台时,C / C++ 有效地忽略了 "cc" 破坏者。如果模板确实破坏了标志,那么将"cc" 指定为破坏者并不是一个坏主意,就像这段代码中的情况一样。如果你曾经在处理器上工作过,如果有任何事情,这是一个好习惯,其中被修改的标志必须在 clobber 列表中明确指定。
  • 如果程序集模板没有输出(或输入/输出)约束,则它是隐式可变的,并且不需要 volatile 修饰符。

【讨论】:

    猜你喜欢
    • 2023-03-19
    • 2015-03-13
    • 2012-07-13
    • 2014-12-24
    • 1970-01-01
    • 1970-01-01
    • 2014-05-12
    • 2015-05-25
    • 2018-12-04
    相关资源
    最近更新 更多