CSPP学习笔记-Ch3.5 算术和逻辑操作

3.5 算术和逻辑操作

给出的每个指令类都有四种不同大小数据的指令,这些操作被分为四组:加载有效地址、一元操作、二元操作和移位。

3.5.1 加载有效地址(leaq)

指令效果描述
leaq S,D D ← & S D←\&S D&S加载有效地址

加载有效地址(load effective address)指令 leaq 实际上是 movq 指令的变形。

它的指令形式是从内存读数据到寄存器,但实际上它根本就没有引用内存。

  • 第一个操作数 S看上去是一个内存引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数 D。这条指令可以为后面的内存引用产生指针。
  • 目的操作数 D 必须是一个寄存器。

另外,它还可以简洁地描述普通的算术操作。例如,如果寄存器 %rdx 的值为 x,那么指令
leaq 7(%rdx, %rdx,4), %rax 将设置寄存器 %rax 的值为 5x+7

编译器经常发现 leaq 的一些灵活用法,根本就与上述的有效地址计算无关。

【例子】

long scale(long x,long y, long z){
	long t = x+4*y+12*z;
	return t;
}
scale:							# x in %rdi, y in %rsi, z in %rdx
	leaq  (%rdi,%rsi,4), %rax	# x + 4*y
	leaq  (%rdx,%rdx,2), %rdx	# z + 2*z = 3*z
	leaq  (%rax,%rdx,4), %rax	# (x+4*y) + 4*(3*z) = x + 4*y + 12*z
	ret

3.5.2 一元操作

只有一个操作数,既是源又是目的。可以是一个寄存器,也可以是一个内存位置。

指令效果描述
INC D D ← D + 1 D←D+1 DD+1加1
DEC D D ← D − 1 D←D-1 DD1减1
NEG D D ← − D D←-D DD取其相反数
NOT D D ← D← D​ ~ D D D​​取其补码

3.5.3 移位操作

指令效果描述
SAL k,D D ← D < < k D←D<<k DD<<k左移
SHL k,D D ← D < < k D←D<<k DD<<k左移
SAR k,D D ← D > > A k D←D>>_Ak DD>>Ak算术右移
SHR k,D D ← D > > L k D←D>>_Lk DD>>Lk逻辑右移

移位量 k 可以是一个立即数,或者放在单字节寄存器 %cl(不可以是其他寄存器)。

在x86-64中,移位操作对 w w w​ 位长的数据值进行操作,移位量是由寄存器 %cl 的低 m m m​​ 位决定的,这里 2 m = w 2^m=w 2m=w 。高位会被忽略。

移位操作的**目的操作数 D **可以是一个寄存器,或是一个内存位置。

long shift_left4_rightn(long x, long n){
	x <<= 4;
    x >>= n;	// 使用算术右移
    return x;
}
shift_left4_rightn:		# x in %rdi, n in %rsi
	movq  %rdi, %rax	# get x
	salq  $4, %rax		# x <<= 4
	movl  %esi,%ecx		# get n (只取4个字节长的)
	sarq  %cl,%rax		# x >>= n
    ret

3.5.4 二元操作——算术操作

第一个操作数可以是立即数、寄存器或是内存位置;

第二个操作数可以是寄存器或是内存位置。

  • 当第二个操作数为内存地址时,处理器必须从内存读出值,执行操作,再把结果写回内存。
指令效果描述
ADD S,D D ← D + S D←D+S DD+S
SUM S,D D ← D − S D←D-S DDS
IMUL S,D D ← D ∗ S D←D*S DDS
XOR S,D D ← D D←D DD ^ S S S异或
OR S,D$D←DS$
AND S,D D ← D & S D←D\&S DD&S

【举例🌰】

1ong arith(1ong x,1ong y, 1ong z)
{
    long t1 = x^y;
    1ong t2 = z*48;
    long t3 = t1 & 0x0F0F0F0F;
    long t4 = t2-t3;
    return t4;
}
arith:							# x in %rdi, y in %rsi, z in %rdx
    xorq %rsi,%rdi				# t1=x^y
    leaq (%rdx,%rdx,2),%rax		# 3*z
    salq $4,%rax				# t2=16*(3*z)=48*z
    andl $252645135,%edi		# t3 = t1 & 0x0FOFOFOF
    subq %rdi,%rax				# Return t2-t3
	ret

第 2 行计算 x^y 的值。
第 3、4 行用 leaq 和移位指令的组合来实现表达式 z*48
第 5 行计算 t10x0F0F0F0F 的 AND 值。
第 6 行计算最后的减法。由于减法的目的寄存器是 %rax,函数会返回这个值。

通常,编译器产生的代码中,会用一个寄存器存放多个程序值,还会在寄存器之间传送程序值。

3.5.5 特殊的算术操作

两个 64 位有符号或无符号整数相乘得到的乘积需要128位来表示。

Intel 把 16 字节的数称为八字(oct word)。

x86-64 指令集支持产生两个 64 位数字的全 128 位乘积以及整数除法的指令

指令效果描述
imulq SR[%rdx]:R[%rax]←S×R[%rax]有符号乘法
mulq SR[%rdx]:R[%rax]←S×R[%rax]无符号乘法
cqtoR[%rdx]:R[%rax]←符号扩展(R[%rax])转换为 8 字
idivq SR[%rdx]←R[%rdx]:R[%rax] mod S
R[%rax]←R[%rdx]:R[%rax] ➗ S
有符号除法
divq SR[%rdx]←R[%rdx]:R[%rax] mod S
R[%rax]←R[%rdx]:R[%rax] ➗ S
无符号除法
  • R[%rdx]:R[%rax]表示一对寄存器 %rdx%rax 组成一个128位的八字
  • imulq 有两种指令语法
    • 双操作数imulq,两个 64 位操作数产生一个 64 位结果。
    • 单操作数:其实也是双的,只是一个隐含在寄存器 %rax 中,而另一个作为指令的源操作数给出,即 S。其结果存放在寄存器 %rdx%rax 中,其中高64位放在 %rdx 中,低64位放在 %rax 中。
      • 有符号数乘法:mulq
      • 补码乘法:imulq

1、64 位乘法

#include <inttypes.h>
    typedef unsigned __int128 uint128_t;
    void store_uprod(uint128_t *dest, uint64_t x, uint64_t y) {
    *dest = x*(uint128_t)y;
}
store_uprod: 			# dest in %rdi, x in %rsi, y in %rdx
    movq %rsi, %rax		# Copy x to multiplicand
    mulq %rdx 			# Multiply by y
    movq %rax, (%rdi)	# Store lower 8 bytes at dest
    movq %rdx, 8(%rdi)	# Store upper 8 bytes at dest+8
    ret

可以观察到,存储乘积需要两个 movq 指令:一个存储低8个字节(第4行),一个存储高8个字节(第5行)。

2、64 位除法

上一节中没有给出除法求模运算——这些操作是由单操作数除法指令来提供的,类似于单操作数乘法指令。

  • 有符号除法指令 idivl 将寄存器 %rdx(高64位)和 %rax(低64位)中的128位数作为被除数,而除数作为指令的操作数给出。
  • 指令将存储在寄存器 %rax 中,将余数存储在寄存器 %rdx 中。

对于大多数 64 位除法应用来说,除数也常常是一个 64 位的值,也应该存放在 %rax 中,%rdx 的位应该设置为全 0 (无符号运算)或者 %rax 的符号位(有符号运算)。后面这个操作可以用指令 cqto 来完成。

  • 指令 cqto 不需要操作数——它隐含读出 %rax 的符号位,并将它复制到 %rdx 的所有位。

【有符号🌰】

void remdiv(long x, long y, long *qp, long *rp){
	long q = x/y;
    long r = x%y;
    *qp = q;
    *rp = r;
}
remdiv:					# x in %rdi, y in %rsi ,qp in %rdx, rp in %rcx
	movq  %rdx, %r8		# Copy qp
	movq  %rdi, %rax	# Move x to lower 8 bytes of dividend
	cqto				# 符号扩展到高8字节
	idivq %rsi			# 除以y
	movq  %rax, (%r8)	# 商存储在qp
	movq  %rdx, (%rcx)	# 余数存储在rp
	ret

在上述代码中,必须首先把参数 qp (也即商要存放的内存的地址)保存到另一个寄存器中(第 2 行),因为除法操作要使用参数寄存器 %rdx。接下来,第 3~4 行准备被除数,复制并符号扩展x。除法之后,寄存器 %rax 中的商被保存在qp (第 6 行),而寄存器 %rdx 中的余数被保存在 rp (第 7 行)。

【无符号🌰】

无符号除法使用 divq 指令。通常,寄存器 %rdx 会事先设置为0。

void uremdiv(unsigned long x, unsigned long y, 
             unsigned long *qp, unsigned long *rp){
	unsigned long q = x/y;
    unsigned long r = x%y;
    *qp = q;
    *rp = r;
}
remdiv:					# x in %rdi, y in %rsi ,qp in %rdx, rp in %rcx
	movq  %rdx, %r8		# Copy qp
	movq  %rdi, %rax	# Move x to lower 8 bytes of dividend
	movl  $0, %edx		# 设置高8字节为0(零扩展)
	divq  rsi			# 除以y
	movq  %rax, (%r8)	# 商存储在qp
	movq  %rdx, (%rcx)	# 余数存储在rp
	ret

3.5.6 浮点运算操作

【执行算术运算的标量 AVX2 浮点指令】
【格式】指令 S1,S2,D
【S1】第一操作数:可以是 XMM 寄存器,可以是内存;
【S2】第二操作数:只能是 XMM 寄存器;
【D】目的操作数:只能是 XMM 寄存器。

单精度双精度效果描述
vaddssvaddsd D ← S 2 + S 1 D ← S_2 +S_1 DS2+S1浮点数加
vsubssvsubsd D ← S 2 − S 1 D ← S_2 -S_1 DS2S1浮点数减
vmulssvmulsd D ← S 2 × S 1 D ← S_2 × S_1 DS2×S1浮点数乘
vdivssvdivsd D ← S 2 / S 1 D ← S_2 /S_1 DS2/S1浮点数除
vmaxssvmaxsd$D ← max(S_2 , S_1 ) $浮点数最大值
vminssvminsd D ← m i n ( S 2 , S 1 ) D ←min(S_2 , S_1 ) Dmin(S2,S1)浮点数最小值
sqrtsssqrtsd D ← S 1 D←\sqrt{S_1} DS1 浮点数平方根

🌰
三个浮点参数 axb 通过 XMM 寄存器 %xmm0 ~ %xmm2 传递,整数参数通过寄存器 %edi 传递。
标准的双指令序列用以将参数 x 转换为双精度类型。
另一条转换指令用来将参数 i 转换为双精度类型。
函数的值通过寄存器 %xmm0 返回。

double funct(double a, float x, double b, int i)
{
	return a*x - b/i;
}
# double funct(double a, float x, double b, int i)
# a in %xmm0, x in %xmm1, b in %xmm2, i in %edi
funct:
	# The following two instructions convert x to double
	vunpcklps %xmm1, %xmm1, %xmm1
	vcvtps2pd %xmm1, %xmm1
	vmulsd %xmm0, %xmm1, %xmm0 			# Multiply a by x
	vcvtsi2sd %edi, %xmm1, %xmm1 		# Convert i to double
	vdivsd %xmm1, %xmm2, %xmm2 			# Compute b/i
	vsubsd %smm2, %xmm0, %xmm0			# Subtract from a*x
	ret									# Return

3.5.7 浮点的位级运算

👇指令对寄存器的所有位进行指定的位级运算。
【格式】指令 S1,S2,D

单精度双精度效果描述
vxorpsxorpd D ← S 2 D ← S_2 DS2^ S 1 S_1 S1位级异或(EXCLUSIVE-OR)
vandpsandpd$D ← S_2 & S_1 $​位级与(AND)

🌰

	vmovsd .LC1(%rip), %xmm1
	vandpd %xmm1, %xmm0, %xmm0	### 求绝对值(.LC1的高65位都是0,低63位都是1)
.LC1: # 因为XMM是128位的,所以vmovsd会读取16个字节传送过去
	.long 42949672955 # 0xffffffffh
	.long 2147483647  # 0x7fffffffh
	.long 0
	.long 0						
	vxorpd %xmm0, %xmm0, %xmm0	### 自己和自己异或,产生浮点常数0.0的方法
	vmovsd .LC2(%rip), %xmm1
	vxorpd %xmm1, %xmm0, %xmm0	### 计算相反数
.LC2: # 因为XMM是128位的,所以vmovsd会读取16个字节传送过去
	.long 0
	.long -2147483648 # 0x80000000h
	.long 0
	.long 0

3.5.8 定义和使用浮点常数

和整数运算操作不同, AVX 浮点操作不能使用立即数作为操作数。
对于浮点常数,编译器需要为其分配和初始化存储空间,然后代码把这些值从内存读入。

🌰

doule cel2fahr(double temp)
{
	return 1.8*temp+32.0;	
}
# double cel2fahr(double temp)
# temp in %xmm0
cel2fahr:
	vmulsd	.LC2(%rip), %xmm0, %xmm0	# Multiply by 1.8
	vaddsd	.LC3(%rip), %xmm0, %xmm0	# Add 32.0
	ret
.LC2:
	.long	3435973837		# 浮点数1.8的低4字节
	.long	1073532108		# 浮点数1.8的高4字节
.LC3:
	.long	0				# 浮点数32.0的低4字节
	.long	1077936128		# 浮点数32.0的高4字节

【浮点数 1.8 的编码方式】
x86-64 使用的是小端法字节顺序。
long 型整数 (3435973837)d = (0xcccccccc)h——低位 4 字节;
long 型整数 (1073532108)d = (0x3ffccccc)h——高位 4 字节;
组合成 64 位数表示为 (0x3ffccccccccccccc)h——double 型;
根据浮点数表示: V = ( − 1 ) s × M × 2 E V=(-1)^s×M×2^E V=(1)s×M×2E (详见 2.4.2 IEEE浮点表示)
符号位 s:最高比特位,十六进制表示的最高位 0x3→(0011)b,符号位为 0,正数;
阶码 E:double 型有 11 11 11 位阶码位,即 0x3ff→(0011 1111 1111)b 中的 (011 1111 1111)b→(1023)d E = e − B i a s = ( 1023 ) d − ( 2 k − 1 − 1 ) = 1023 − 1023 = 0 E=e-Bias=(1023)d-(2^{k-1}-1)=1023-1023=0 E=eBias=(1023)d(2k11)=10231023=0
尾数 M:阶码 E E E 11 11 11 位不全为 0 或 1, M = 1 + f = 1. f 51 ⋅ ⋅ ⋅ f 1 f 0 = 1.8 M=1+f=1.f_{51}···f_1f_0=1.8 M=1+f=1.f51f1f0=1.8 f i f_i fi 是尾数 M M M 的位值)。
V = ( − 1 ) s × M × 2 E = ( − 1 ) 0 × 1.8 × 2 0 = 1.8 V = (-1)^s×M×2^E=(-1)^0×1.8×2^0=1.8 V=(1)s×M×2E=(1)0×1.8×20=1.8

【浮点数 32.0 的编码方式】
long 型整数 (0)d = (0x0)h——低位 4 字节;
long 型整数 (1077936128)d = (0x40400000)h——高位 4 字节;
组合成 64 位数表示为 (0x4040000000000000)h——double 型;
根据浮点数表示: V = ( − 1 ) s × M × 2 E V=(-1)^s×M×2^E V=(1)s×M×2E​​​​ (详见 2.4.2 IEEE浮点表示)
符号位 s:最高比特位,十六进制表示的最高位 0x4→(0100)b,符号位为 0,正数;
阶码 E:double 型有 11 11 11​​​​ 位阶码位,即 0x404→(0100 1111 0100)b 中的 (0100 1111 0100)b→(1028)d E = e − B i a s = ( 1028 ) d − ( 2 k − 1 − 1 ) = 1028 − 1023 = 5 E=e-Bias=(1028)d-(2^{k-1}-1)=1028-1023=5 E=eBias=(1028)d(2k11)=10281023=5​​​​ ;
尾数 M:阶码 E E E​​​​ 的 11 11 11​​​​ 位不全为 0 或 1, M = 1 + f = 1. f 51 ⋅ ⋅ ⋅ f 1 f 0 = 1.0 M=1+f=1.f_{51}···f_1f_0=1.0 M=1+f=1.f51f1f0=1.0​​​​ ( f i f_i fi​​​​ 是尾数 M M M​​​​ 的位值)。
V = ( − 1 ) s × M × 2 E = ( − 1 ) 0 × 1.0 × 2 5 = 32.0 V = (-1)^s×M×2^E=(-1)^0×1.0×2^5=32.0 V=(1)s×M×2E=(1)0×1.0×25=32.0

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
select * from (select t1.[id] as t1_id,t1.[requestId] as t1_requestId,t1.[htqsrq] as t1_htqsrq,t1.[htjzrq] as t1_htjzrq,t1.[htbh] as t1_htbh,t1.[gf] as t1_gf,t1.[xf] as t1_xf,t1.[rq] as t1_rq,t1.[fkfs] as t1_fkfs,t1.[formmodeid] as t1_formmodeid,t1.[modedatacreater] as t1_modedatacreater,t1.[modedatacreatertype] as t1_modedatacreatertype,t1.[modedatacreatedate] as t1_modedatacreatedate,t1.[modedatacreatetime] as t1_modedatacreatetime,t1.[modedatamodifier] as t1_modedatamodifier,t1.[modedatamodifydatetime] as t1_modedatamodifydatetime,t1.[form_biz_id] as t1_form_biz_id,t1.[MODEUUID] as t1_MODEUUID,t1.[htfj] as t1_htfj,t1.[zje] as t1_zje,t1.[ds] as t1_ds,t1.[zjedx] as t1_zjedx,t1.[cspp] as t1_cspp,t1.[yfk] as t1_yfk,t1.[gxid] as t1_gxid,t1.[bz] as t1_bz,t1.[gfqymc] as t1_gfqymc,t1.[gfjc] as t1_gfjc,t1.[bh] as t1_bh,t1.[jylx] as t1_jylx,t1.[cght] as t1_cght,t1.[yf] as t1_yf,t1.[yfk1] as t1_yfk1,t1.[yf11] as t1_yf11,t1.[nf] as t1_nf,t1.[rksj] as t1_rksj,t1.[cclx] as t1_cclx,t1.[cgbt] as t1_cgbt,t1.[yfk2] as t1_yfk2,t1.[sywf] as t1_sywf,t1.[yfbl] as t1_yfbl,t1.[fhbl] as t1_fhbl,t1.[yfh] as t1_yfh,t1.[sykf] as t1_sykf,t1.[hzsdlqys] as t1_hzsdlqys,t1.[sys_workflowid] as t1_sys_workflowid,t1.[cgqzyz] as t1_cgqzyz,t1.[htwjpdf] as t1_htwjpdf,t1.[cghtlc] as t1_cghtlc,t1.[htzt] as t1_htzt,t1.[qzfs] as t1_qzfs,t1.[htwjtp] as t1_htwjtp,t1.[cgqzlc] as t1_cgqzlc,t1.[sjfk] as t1_sjfk,t1.[ydkds] as t1_ydkds,t1.[chpt] as t1_chpt,t1.[lxdhchr] as t1_lxdhchr,t1.[gxsjkx] as t1_gxsjkx,t1.[hkzt] as t1_hkzt,t1.[lcfkd] as t1_lcfkd,t1.[fkzlcid] as t1_fkzlcid,t1.[mode_top_4] as t1_mode_top_4,t1.[cgdj] as t1_cgdj,t1.[mode_top_22] as t1_mode_top_22,t2.[id] as t2_id,t2.[mainid] as t2_mainid,t2.[sld] as t2_sld,t2.[ppcj] as t2_ppcj,t2.[hsdj] as t2_hsdj,t2.[bz] as t2_bz,t2.[je] as t2_je,t2.[xhggyt] as t2_xhggyt,t2.[mxgxid] as t2_mxgxid,t2.[dqkckc] as t2_dqkckc,t2.[rkhkc] as t2_rkhkc,t2.[yf] as t2_yf,t2.[yldjbhyf] as t2_yldjbhyf,SELECT year(rksj) as 年 FROM uf_gfht as cus_年年 from uf_gfht t1 INNER join uf_gfht_dt1 t2 on t1.id = t2.mainid) tmp1错在哪里
05-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值