如何使用内嵌汇编[gcc,intel,c]在操作后检查进位标志是否设置?
sbb %eax,%eax
如果进位标志位被设置,它将在eax中存储-1,如果未设置,则为0。不需要预先清除eax的值; 将eax从自己中减去会为您执行此操作。这种技术非常强大,因为您可以使用结果作为位掩码来修改计算结果,而无需使用条件跳转。
请注意,仅当内联asm块内执行的算术设置了进位标志时,才可以测试进位标志的有效性。您不能测试在C代码中执行的计算的进位,因为编译器可能会优化/重新排列会破坏进位标志的各种方式。
"=@ccc" (cf_output)
语法,用于直接将CF或其他标志条件声明为编译器的输出,而不必在asm模板中将布尔值或整数实现为寄存器。关于您警告不要尝试读取由编译器生成的指令设置的FLAGS:从C程序读取标志寄存器有更多相关信息。 - Peter Cordes通过有条件跳转指令jc
(如果进位则跳转)或jnc
(如果没有进位则跳转)。
或者您可以存储进位标志位,
;; Intel syntax
mov eax, 0
adc eax, 0 ; add with carry
setc AL //will set AL register to 1 or clear to 0 depend on carry flag
or
setc byte ptr [edx] //will set memory byte on location edx depend on carry flag
or even
setc byte ptr [CarryFlagTestByte] //will set memory variable on location CarryFlagTestByte depend on carry flag
procedure TfrmTest.ButtonTestClick(Sender: TObject);
function GetCPUTimeStamp: int64;
asm
rdtsc
end;
var
ii, i: int64;
begin
i := GetCPUTimeStamp;
asm
mov ecx, 1000000
@repeat:
mov al, 0
adc al, 0
mov al, 0
adc al, 0
mov al, 0
adc al, 0
mov al, 0
adc al, 0
loop @repeat
end;
i := GetCPUTimeStamp - i;
ii := GetCPUTimeStamp;
asm
mov ecx, 1000000
@repeat:
setc al
setc al
setc al
setc al
loop @repeat
end;
ii := GetCPUTimeStamp - ii;
caption := IntToStr(i) + ' ' + IntToStr(ii));
end;
使用指令setc的循环(1M次迭代)比使用adc指令的循环快5倍以上。
编辑:添加了第二个测试,测试结果存储在寄存器AL中,并累计在寄存器CL中,以使情况更加真实。
procedure TfrmTestOtlContainers.Button1Click(Sender: TObject);
function GetCPUTimeStamp: int64;
asm
rdtsc
end;
var
ii, i: int64;
begin
i := GetCPUTimeStamp;
asm
xor ecx, ecx
mov edx, $AAAAAAAA
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
end;
i := GetCPUTimeStamp - i;
ii := GetCPUTimeStamp;
asm
xor ecx, ecx
mov edx, $AAAAAAAA
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
end;
ii := GetCPUTimeStamp - ii;
caption := IntToStr(i) + ' ' + IntToStr(ii);
end;
使用SETcc指令的常规部分仍然比较快,快约20%。
xor reg,reg
或在之后执行movzx eax,al
。在某些英特尔CPU上,movzx之后避免了部分寄存器减速。在非英特尔处理器上,xor之前避免了对eax进行错误依赖,并且可以将其从循环中提取出来。在英特尔SnB系列上,sbb解码为2个uops,延迟2个周期。 - Peter Cordes/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_u32(uint32_t a, uint32_t b, uint32_t* r)
{
volatile int no_carry = 1;
volatile uint32_t result = a + b;
asm volatile
(
"jnc 1f ;"
"movl $0, %[xc] ;"
"1: ;"
: [xc] "=m" (no_carry)
);
if(r)
*r = result;
return no_carry;
}
volatile
。请注意,有符号整数的数学运算通过jno
跳转到OF标志。我见过优化器将其更改为基于OF的jnb
。/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_i32(int32_t a, int32_t b, int32_t* r)
{
volatile int no_overflow = 1;
volatile int32_t result = a + b;
asm volatile
(
"jno 1f ;"
"movl $0, %[xo] ;"
"1: ;"
: [xo] "=m" (no_overflow)
);
if(r)
*r = result;
return no_overflow;
}
从大局来看,您可能会按以下方式使用这些功能。在同样的大局中,许多人可能会拒绝额外的工作和美学上的不美观,直到遭受溢出/包装/下溢的影响。
int r, a, b;
...
if(!add_i32(a, b, &r))
abort(); // Integer overflow!!!
...
内联GCC汇编在GCC 3.1及以上版本可用。请参阅带有C表达式操作数的汇编指令,或搜索“GCC扩展汇编”。
最后,在Visual Studio中相同的代码(代码生成方面没有太大区别),但语法更容易,因为MASM允许您跳转到C标签:
/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_i32(__int32 a, __int32 b, __int32* r)
{
volatile int no_overflow = 1;
volatile __int32 result = a + b;
__asm
{
jno NO_OVERFLOW;
mov no_overflow, 0;
NO_OVERFLOW:
}
if(r)
*r = result;
return no_overflow;
}
不足之处在于,上述MASM代码仅适用于x86汇编。对于x64汇编来说,没有内联函数,因此您将需要在汇编中编写代码(在单独的文件中),并使用MASM64进行编译。
gcc 版本是 11.2.1。
$> gcc -Wall -std=c99 -O2 -o uilt uilt.c
代码片段:
size_t i = 0;
int mul = 10;
uint128_t sum = 0;
int int_array[48] = {0};
// fill arr. with ea. str val in argv[1] str. converted to int vals.
while (i < strlen(argv[1])) {
// chk they are digit chars, if not, skip iter
if (isdigit(argv[1][i]) == 0) {
i++;
continue;
}
int_array[i] = (argv[1][i] - 48);
sum = int_array[i] + (sum * mul);
/* check carry flag */
__asm__ goto("jc %l0"
: /* no outputs */
: /* no inputs */
: /* no clobbers */
: carry);
/* no carry */
goto its_good;
carry:
system("clear");
printf("\n\n\tERROR!!!\
\n\n\t!!!!!!! uilt has ABORTED !!!!!!\
\n\tCmdln arg exceeds 2^127 bit limit\
\n\twhen converted from string to 127\
\n\tbit unsigned __int128.\n\n");
exit(1);
its_good:
i++;
}
一些输出:
[jim@nitroII uiltDev]$ ./uilt 1
Dec: 1
Hex: 0x0001
Bin: 0x0001
[jim@nitroII uiltDev]$ ./uilt 255
Dec: 255
Hex: 0x00ff
Bin: 0x0000 1111 1111
[jim@nitroII uiltDev]$ ./uilt 18446744073709551616
Dec: 18446744073709551616
Hex: 0x0001 0000 0000 0000 0000
Bin: 0x0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
[jim@nitroII uiltDev]$ ./uilt 340282366920938463463374607431768211455
Dec: 340282366920938463463374607431768211455
Hex: 0x0000 ffff ffff ffff ffff ffff ffff ffff ffff
Bin: 0x0000 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
Dec: 340282366920938463463374607431768211456
ERROR!!!
!!!!!!! uilt has ABORTED !!!!!!
Cmdln arg exceeds 2^127 bit limit
when converted from string to 127
bit unsigned __int128.
+
之后的CF是asm语句的输入。如果是这种情况,那只是运气好而已。GCC可以轻松地使用LEA或优化SIMD或其他不涉及x86 add
指令的东西。或者在您的asm语句之前运行一些其他指令,例如add rsp,16
(如果有很多参数的函数调用进行了清理)(或者32位模式下只有少数)。 - Peter Cordes00123
总是可以,所以 len < 10
检查仍然可以通过)。否则,与 memchr 类似,但是匹配不等于 0,就像你使用 _mm_cmpeq_epi8
/ _mm_movemask_epi8
(并循环如果全部为 1)/ ~mask
/ bsf
或 tzcnt
找到第一个非 '0'
位置。 - Peter Cordes
+
或<<
操作没有任何明确定义的进位,并且可能会编译为LEA或不涉及标志的其他内容。或者被优化掉。 - Peter Cordes