【汇编语言与计算机系统结构笔记07】条件码,比较、测试、条件跳转与条件转移指令,结合微体系结构与流水的说明

本次笔记内容:
08.控制流-1

文章目录

本次笔记还是从C语言入手,来对应汇编语言。本次内容为Control Flow,即跳转。

条件码

基于add的CF, ZF, SF, OF

后文条件码由算数指令隐含设置:

  • addl Src, Dest
  • 或addq Src, Dest
  • 类似的C语言表达式t = a + b (a = Src, b = Dest)
条件码 全程 意义
CF Carry(进位) Flag 可用于检测无符号整数运算的溢出
ZF Zero Flag set if t == 0
SF Sign Flag set if t < 0
OF Overflow Flag set if 补码运算溢出(即带符号整数运算)

对于OF,在t = a + b中,如果满足如下两种条件中的一种,OF置为1,否则为0:

  • a > 0 && b > 0 && t < 0
  • a < 0 && b< 0 && t >= 0

比较(Compare)指令

  • cmpl Src2, Src1
  • cmpq Src2, Src1

cmpl b, a类似于计算a-b(但是不改变目的操作数)

  • CF set if carry out from most significant bit,可用于无符号数的比较
  • ZF set if a == b
  • SF set if (a-b)< 0
  • OF set if two’s complement overflow: (a>0 && b<0 && (a-b)<0) || (a<0 && b>0 && (a-b)>0)

测试(Test)指令

  • testl Src2, Src1
  • testq Src2, src1

计算Src1 & Src2并设置相应的条件码,但是不改变目的操作数。

  • ZF set when a & b == 0
  • SF set when a&b < 0

读取条件码(SetX)指令

读取当前的条件码(或者某些条件码的组合),并存入目的字节寄存器。

SetX Condition Description
sete ZF Equal / Zero
setne ~ZF Not Equal / Not Zero
sets SF Negative
setns ~SF Nongegative
setq ~(SF^OF) & ~ZF Greater (Signed)
setge ~(SF^OF) Greater of Equal (Signed)
setl (SF^OF) Less (Signed)
setle (SF^OF) ZF
seta ~CF & ~ZF Above (unsigned)
setb CF Below (unsigned)
例子

读取当前的条件码(或者某些条件码的组合)并存入目的“字节”寄存器:

  • 余下的三个字节不会被修改
  • 通常使用“movzbl”指令对目的寄存器进行“0”扩展。
int gt (int x, int y)
{
	return x > y;
}

其汇编的Body如下:

movl 12(%ebp), %eax		# eax = y
cmpl %eax, 8(%ebp)		# Compare x : y
setq %al				# al = x > y
movzbl %al, %eax		# Zero rest of %eax
# mov,z表示0扩展,为b to l

寄存器结构:
【汇编语言与计算机系统结构笔记07】条件码,比较、测试、条件跳转与条件转移指令,结合微体系结构与流水的说明

如果同样的代码放在x86-64下编译:

int gt (long x, long y)
{
	return x > y;
}

long gt (long x, long y)
{
	return x > y;
}

x86-64下函数参数:

  • x in %rdi
  • y in %rsi

Body(same for both):

xorl %eax, %eax		# eax = 0 使用异或赋0速度快
cmpq %rsi, %rdi		# Compare x : y
setq %al			# al = x > y

为什么long编译出来,还是放在32位的%eax中,而非64位的%rax中呢?

拓展:流水设计与微体系结构

这与流水设计、微体系结构背景有关:

因特尔手册:32-bit operands generate a 32-bit result. zero-extended to a 64-bit result in the destination general-purpose register.

为什么?因为部分原因来自于微体系结构(即处理器)内部实现的效率方面的考虑,的是为了消除“部分数据依赖”。

解释如下:

现在的处理器,是基于流水操作的。流水线,让吞吐率变高,虽然总时间会升高(多了交接时间)。

【汇编语言与计算机系统结构笔记07】条件码,比较、测试、条件跳转与条件转移指令,结合微体系结构与流水的说明

如果流水线很深,有两条指令:读和写,前面的“读”指令不执行结束,后面的“写”指令就无法开始,二者有数据依赖。

这使指令的效率降低。

如果在64位模式做32位操作,对于64位的rax,修改了其低32位eax,而为了不使高32位产生数据依赖(被其他指令依赖),则强制把高32位置为0。

例子如下:

addw %bx, %ax
movl %eax, %ecx

可见,对%ax产生依赖(第一天指令执行完%ax,第二条才能去读%eax)。这种指令有优化的可能性,比如在两条指令间增加有用的其他指令,这样在addw执行同时,先去执行其他指令,而不会触发stop,使movl的流水线等待addw的流水线。

即尽量不用两个数据相关的指令挨在一起。


跳转指令

依赖当前条件码选择下一条执行语句(是否顺序执行)。

jX Condition Description
jmp 1 Unconditional
je ZF Equal / Zero
jne ~ZF Not Equal / Not Zero
js SF Negative
jns ~SF Nongegative
jg ~(SF^OF) & ~ZF Greater (Signed)
jge ~(SF^OF) Greater of Equal (Signed)
jl (SF^OF) Less (Signed)
jle (SF^OF) ZF
ja ~CF & ~ZF Above (unsigned)
jb CF Below (unsigned)
条件跳转指令实例
int absdiff(int x, int y) {
	int result;
	if (x > y) {
		result = x - y;
	} else {
		result = y - x;
	}
	return result;
}
absdiff:
	pushl %ebp
	movl %esp, %ebp
	movl 8(%ebp), %edx
	movl 12(%ebp), %eax
	# above are Set Up
	cmpl %eax, %edx
	jle .L7
	subl %eax, %edx
	movl %edx, %eax
	# above are Body1
.L8:
	leave
	ret
	# Finish
.L7:
	subl %edx, %eax
	jmp .L8
	# above are Body2
条件表达式(条件转移指令)

val = Test ? Then - Expr : Else - Expr;

val = x > y ? x - y : y - x;

Goto Verson:

	nt = !Test;
	if (nt) goto Else;
	val = Then Expr:
Done:
	...
Else:
	val = Elase - Expr;
	goto Done;

为什么引入条件表达式呢?

在x86-64下编译:

int absdiff(int x, int y) {
	int result;
	if (x > y) {
		result = x - y;
	} else {
		result = y - x;
	}
	return result;
}

64位编译后:

absdiff: # x in %edi, y in %esi
	movl %edi, %eax		# v = x
	movl %esi, %edx		# ve = y
	subl %esi, %eax		# v -= y
	subl %edi, %edx		# ve -= x
	cmpl %esi, %edi		# x : y
	cmovle %edx, %eax	# v = ve if <= ret
	ret

较新的32位Gcc也可以编译出类似的代码(-march=i686)

条件传送指令:

  • cmovC src, dest
  • 如果条件C成立,将数据从src传送至dest
  • 从执行角度来看,比一般的条件跳转指令的效率高(因为其控制流可预测,即条件C是已知的)

在32位使用-march=i686编译后:

pushl %ebp
movl %esp, %ebp
pushl %ebx

movl 8(%ebp), %ecx
movl 12(%ebp), %edx
movl %ecx, %ebx
subl %edx, %ebx
movl %edx, %eax
subl %ecx, %eax
cmpl %edx, %ecx
cmovg %ebx, %eax

popl %ebx
popl %ebp
ret

微体系结构(关于跳转指令)

处理器流水线(五级流水示例):

  • Instruction Fetch (IF)
  • Read Registers (RD)
  • Arithmetic Operation (ALU)
  • Memory Access (MEM)
  • Write Back (WB)

【汇编语言与计算机系统结构笔记07】条件码,比较、测试、条件跳转与条件转移指令,结合微体系结构与流水的说明

条件跳转指令往往会引起一定的性能损失,因此需要尽量消除。

因此说,引入cmovle可以一定程度取代条件跳转指令,提升性能。

条件转移指令的局限性

int xgty = 0; xltey = 0;

int absdiff_se(int x, int y) {
	int result;
	if (x > y) {
		xgty++;
		result = x - y;
	} else {
		xltey++;
		result = y - x;
	}
}

条件执行的语句中,改变了全局变量,因此不能使用条件转移指令。

条件转移指令限制使用的场合:

  • Then-Expr或Else-Expr表达式有“副作用”;
  • Then-Expr或Else-Expr表达式的计算量较大。

更通俗一些的解释:

条件转移指令相当于C中的:

condition ? exp1 : exp2

意义为,先把exp1和exp2都算一遍,然后根据condition决定要谁。

但是,如果exp1和exp2中都包含对全局变量的操作,最后选了exp1,没有选exp2,难道还要把exp2的全局变量的操作倒回去么?这显然是不合理的,所以说,条件转移指令有“副作用”。

另外,如果exp1和exp2都过于复杂,则两个都进行运算效率低下,还不如使用条件转移指令。

上一篇:微前端与微前端实践


下一篇:MultipartFile后台调用文件上传接口