正确地从内联汇编中收集 return 个值

Properly gathering return value(s) from inline assembly

我想使用 inline assemblyPowerPC 32-bit 架构上执行 syscall。在执行 syscall 之后,我还想 return 通过获取 r3r4 的值并放入 syscall 的 return 值他们变成了long。我的函数如下所示:

constexpr auto maximum_syscall_parameter_count = 8;

long execute_system_call_with_arguments(short value, const int parameters_array[maximum_syscall_parameter_count]) {    
    char return_value_buffer[sizeof(long)];

    // syscall value
    asm volatile("mr 0, %0" : : "r" (value));

    // Pass the parameters
    asm volatile("mr 3, %0" : : "r" (parameters_array[0]));
    asm volatile("mr 4, %0" : : "r" (parameters_array[1]));
    asm volatile("mr 5, %0" : : "r" (parameters_array[2]));
    asm volatile("mr 6, %0" : : "r" (parameters_array[3]));
    asm volatile("mr 7, %0" : : "r" (parameters_array[4]));
    asm volatile("mr 8, %0" : : "r" (parameters_array[5]));
    asm volatile("mr 9, %0" : : "r" (parameters_array[6]));
    asm volatile("mr 10, %0" : : "r" (parameters_array[7]));

    // Execute the syscall
    asm volatile ("sc");

    // Retrieve the return value
    asm volatile ("mr %0, 3" : "=r" (*(int *) &return_value_buffer));
    asm volatile ("mr %0, 4" : "=r" (*(int *) &return_value_buffer[sizeof(int)]));

    return *(long *) &return_value_buffer;
}

这似乎生成了正确的代码,但感觉很糟糕,生成了 2 条冗余指令:

mr        r0, r30
lwz       r9, 0(r31)
mr        r3, r9
lwz       r9, 4(r31)
mr        r4, r9
lwz       r9, 8(r31)
mr        r5, r9
lwz       r9, 0xC(r31)
mr        r6, r9
lwz       r9, 0x10(r31)
mr        r7, r9
lwz       r9, 0x14(r31)
mr        r8, r9
lwz       r9, 0x18(r31)
mr        r9, r9
lwz       r9, 0x1C(r31)
mr        r10, r9
sc
mr        r3, r3 # Redundant
mr        r9, r4 # Redundant
blr

我的目标是通过 sc 指令设置 return 和 r3r4,但删除 return 值或最后 2 个内联源代码中的汇编指令会破坏函数,使其在 return 或 return 0.

上崩溃

让我首先重申我上面所说的:我不会说 PPC asm,而且我没有 PPC 来 运行 这段代码。因此,尽管我相信通常这是您应该继续前进的方向,但不要将此代码视为福音。

接下来,Jester 和我建议使用 local register variables is that it results in better (and arguably more readable/maintainable) code. The reason for that is this line in the gcc docs 的原因:

GCC 本身不解析汇编指令,不知道它们的含义,甚至不知道它们是否是有效的汇编输入。

考虑到这一点,当您使用上面的代码并使用如下代码调用例程时会发生什么:

int parameters_array[maximum_syscall_parameter_count] = {1, 2, 3, 4, 5, 6, 7};

long a = execute_system_call_with_arguments(9, parameters_array);

由于编译器不知道该 asm 块内会发生什么,它必须 将所有内容写入内存,然后 asm 块将其从内存读回寄存器。在使用如下代码时,编译器可以足够聪明地跳过分配内存并直接加载寄存器。如果您使用(基本上)相同的参数多次调用 execute_system_call_with_arguments,这会更有用。

constexpr auto maximum_syscall_parameter_count = 7;

long execute_system_call_with_arguments(const int value, const int parameters_array[maximum_syscall_parameter_count]) {    
    int return_value_buffer[2];

    register int foo0 asm("0") = value;

    register int foo1 asm("3") = parameters_array[0];
    register int foo2 asm("4") = parameters_array[1];
    register int foo3 asm("5") = parameters_array[2];
    register int foo4 asm("6") = parameters_array[3];
    register int foo5 asm("7") = parameters_array[4];
    register int foo6 asm("8") = parameters_array[5];
    register int foo7 asm("9") = parameters_array[6];

    // Execute the syscall
    asm volatile ("sc"
    : "+r"(foo3), "+r"(foo4)
    : "r"(foo0), "r"(foo1), "r"(foo2), "r"(foo5), "r"(foo6), "r"(foo7)
    );

    return_value_buffer[0] = foo3;
    return_value_buffer[1] = foo4;

    return *(long *) &return_value_buffer;
}

当用上面的例子调用时产生:

.L.main:
    li 0,9
    li 3,1
    li 4,2
    li 5,3
    li 6,4
    li 7,5
    li 8,6
    li 9,7
    sc
    extsw 3,6
    blr

将尽可能多的代码保留在外部 asm 模板(考虑约束"outside")允许 gcc 的优化器做各种有用的事情。

其他几点:

  1. 如果 parameters_array 中的任何项目是(或可能是)指针,您将需要添加 memory clobber。这确保在执行 asm 指令之前,可能存储在寄存器中的任何值都被刷新到内存中。如果不需要,添加内存破坏器(可能)会通过几条指令减慢执行速度。如果需要,省略它可能会导致读取不正确的数据。
  2. 如果 sc 修改了此处未列出的任何寄存器,您必须将它们列为 clobbers。如果此处列出的任何寄存器(foo3 和 foo4 除外)发生变化,您也必须使它们成为输入+输出(sc 是否在 foo0 中放置了 return 代码?)。即使您在 asm 调用之后 "don't use them",如果它们发生变化,您也必须通知编译器。作为 gcc 文档 explicitly warn:

不要修改仅输入操作数的内容(与输出相关的输入除外)。编译器假定在退出 asm 语句时,这些操作数包含与执行语句之前相同的值。

如果不注意这个警告,可能会导致代码在某天似乎运行良好,然后在 asm 块之后(有时是很久之后)的某个时刻突然导致奇怪的故障。这个 "works and then suddenly doesn't" 是我建议你 don't use inline asm 的原因之一,但是如果你 必须 (你有点这样做如果您需要直接调用 sc),请尽量保持小。

  1. 我通过将 maximum_syscall_parameter_count 更改为 7 来作弊。显然 godbolt 的 gcc 没有使用更多参数优化此代码。如果有必要,可能有解决此问题的方法,但您需要比我更好的 PPC 专家来定义它。