ARM与x86指令集体系
一、概述
本节重点探讨嵌入式领域主导的ARM指令集(ARMv7 32位、ARMv8 64位)以及个人计算机主流的x86指令集(以80386为核心),并对比其与MIPS的设计。ARM和x86在市场中占据重要地位:
- ARM:2011年,全球超90亿设备使用ARM处理器,年增长20亿,广泛应用于嵌入式和移动设备。
- x86:自1978年Intel 8086发布,凭借兼容性和市场优势,主宰PC领域,年产量约3.5亿片。
通过分析指令集设计、寻址模式、寄存器结构、指令编码及性能优劣,揭示ARM和x86的特点,并总结常见谬误与陷阱,强调汇编编程与现代编译器的关系。
二、ARM指令集
1. ARMv7(32位)
背景:
- 1985年发布,与MIPS同期,遵循RISC(精简指令集)设计哲学,强调简洁高效。
- 最初为Acorn RISC Machine,后改为Advanced RISC Machine。
与MIPS比较:
- 相同点:
- 发布年份:1985年。
- 指令大小:32位。
- 寻址空间:32位平坦。
- 数据对齐:对齐。
- 存储器映射:支持。
- 不同点:
- 寄存器数量:MIPS有31个通用寄存器,ARMv7有15个。
- 寻址模式:ARMv7提供9种复杂寻址模式,MIPS仅3种简单模式。
核心指令集:
- 算术逻辑指令:
- 加法:
add
(等价MIPSaddu/addiu
),支持溢出捕获(adds/swivs
)。 - 减法:
sub
(等价MIPSsubu
),支持溢出捕获。 - 乘法:
mul
(等价MIPSmull/multu
)。 - 逻辑操作:
and
、orr
(或)、eor
(异或)。 - 移位:逻辑左移(
lsl
)、右移(lsr
)、算术右移(asr
)。 - 比较:
cmp
(减法设置条件码)、cmn
(加法设置条件码)、tst
(逻辑与)、teq
(异或)。
- 加法:
- 数据传输指令:
- 加载:字节(
ldrsb/ldrb
)、半字(ldrsh/ldrh
)、字(ldr
)。 - 存储:字节(
strb
)、半字(strh
)、字(str
)。 - 特殊寄存器:
mrs/msr
(读/写)。 - 原子交换:
swp/swpb
(类似MIPSll/sc
)。
- 加载:字节(
- 特点:
- 无除法指令,移位操作融入数据指令(如
lsl^1
为move
变种)。 - 每条指令可附带移位操作,增加灵活性。
- 无除法指令,移位操作融入数据指令(如
寻址模式:
- 支持9种模式,远超MIPS的3种:
- 寄存器操作数、立即数操作数。
- 寄存器+偏移(基址寻址)。
- 寄存器+寄存器(下标寻址)。
- 寄存器+寄存器倍乘(比例寻址)。
- 寄存器+偏移/寄存器+寄存器并更新寄存器。
- 自增/自减。
- PC相对数据寻址。
- 特点:
- 寄存器间接寻址与寄存器+偏移分开,支持偏移左移1或2位以扩展范围。
- 复杂模式(如寄存器移位后加另一寄存器)支持灵活地址计算。
条件分支:
- 使用4位条件码(负值、零、进位、溢出),由算术/逻辑指令可选设置。
- 条件执行:
- 每条指令前4位字段决定是否执行(基于条件码),可替代短分支,节省代码空间和时间。
- 示例:条件执行可将分支优化为单指令。
独特指令:
- 立即数加载:
mov Rd, Imm
(12位立即数扩展为32位,循环右移)。 - 取反:
mvn Rd, -(Rsl)
。 - 循环右移:
ror Rd, Rsl >> i
。 - 位清除:
bic Rd, Rsl & -(Rs2)
。 - 反向减:
rsb Rd, Rs2 - Rsl
。 - 多字运算:
adcs/sbcs
(支持进位)。 - 块加载/存储:
- 单指令操作任意寄存器组合(16位掩码控制)。
- 用途:保存/恢复寄存器、内存块复制。
指令格式:
- 包含4位条件执行字段,寄存器字段较小(16个寄存器)。
- 数据传输:12位立即数;分支/跳转:24位常量。
2. ARMv8(64位)
背景:
- 2007年开始设计,2013年完成,应对32位地址空间限制。
- 完全改进,而非简单扩展(如x86从32位到64位)。
与ARMv7的差异:
- 移除特性:
- 无条件执行字段(v7每条指令支持)。
- 立即数字段简化为12位常量(v7为复杂函数)。
- 移除块加载/存储指令。
- PC不再作为寄存器,防止意外分支。
- 新增特性:
- 32个通用寄存器(类似MIPS),包括恒零寄存器。
- 统一字长寻址模式。
- 添加除法指令。
- 支持相等/不等条件分支(类似MIPS
beq/bne
)。
与MIPS的相似性:
- 指令集更接近MIPS,简化设计,适合编译器优化。
- 主要区别仅在名称,ARMv8可视为MIPS的64位变种。
三、x86指令集
1. 历史演进
- 1978:8086:16位体系结构,专用寄存器,非通用寄存器。
- 1980:8087:添加60条浮点指令,使用栈操作。
- 1982:80286:扩展到24位地址,增加内存保护和指令。
- 1985:80386:32位地址/寄存器,新增寻址模式、页面支持,接近通用寄存器。
- 1989-1995:80486、Pentium、Pentium Pro,新增4条指令(多处理、条件传送)。
- 1997:MMX:57条多媒体指令,基于浮点栈,SIMD方式。
- 1999:SSE:70条指令,新增128位寄存器,支持单精度浮点并行操作。
- 2001:SSE2:144条指令,新增双精度浮点,优化编译器浮点操作。
- 2003:AMD64:64位地址/寄存器,新增16个寄存器,长模式支持。
- 2004:EM64T/SSE3:Intel采纳AMD64,新增13条复杂算术指令。
- 2006:SSE4:54条指令,优化数组操作和虚拟机支持。
- 2007:SSE5:AMD新增170条指令,包括3操作数版本。
- 2011:AVX:扩展SSE寄存器到256位,新增128条指令。
特点:
- 35年持续扩展,平均每月新增1条指令(图2-43)。
- 兼容性驱动发展,新增特性不破坏已有软件。
- 复杂性高,指令集难以统一描述,开发分散。
2. 80386寄存器与寻址模式
寄存器:
- 8个32位通用寄存器(EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI)。
- 专用寄存器:代码段、堆栈指针、数据段、指令指针(EIP)、条件码(EFLAGS)。
- 对比:MIPS有31个通用寄存器,ARMv7有15个,x86寄存器数量少,增加分配压力。
寻址模式:
- 寄存器间接:地址在寄存器中(不可用ESP/EBP)。
- 基址+偏移:基址寄存器+8/32位偏移(不可用ESP)。
- 基址+比例下标:基址+2^比例*下标(比例0/1/2/3,下标不可用ESP)。
- 基址+比例下标+偏移:组合模式。
- 特点:
- 支持存储器操作数,指令可直接访问内存(不同于MIPS/ARMv7需显式加载)。
- 比例因子避免下标乘4(字节地址转换),优化数组访问。
- 等价MIPS代码需额外指令(如
mul
、lui
)。
数据类型:
- 支持8位(字节)、16位(字)、32位(双字)。
- 默认数据长度由代码段寄存器指定,可用前缀切换(如8位前缀更改默认32位)。
3. 整数操作
分类:
- 数据传送:
move
、push
、pop
、les
(加载段和寄存器)。 - 算术逻辑:
add
、sub
、cmp
、shl
、shr
、test
、inc
、dec
、or
、xor
。 - 控制流:
jnz
、jz
、jmp
、call
、ret
、loop
。 - 字符串:
movs
(复制)、lods
(加载到EAX)。
特点:
- 算术/逻辑指令:一个操作数兼作源和目的(如
add EAX, 6765
修改EAX)。 - 存储器操作:支持寄存器-存储器格式,增加灵活性但复杂化实现。
- 条件分支:基于条件码,PC相对寻址以字节为单位(非固定4字节指令)。
- 字符串指令:效率低于等效软件例程,现代程序少用。
4. 指令编码
复杂性:
- 指令长度1-15字节,取决于操作数、前缀、寻址模式。
- 操作码包含位宽标志(
w
位,8/32位)、寻址模式、寄存器信息。
格式:
- 示例:
JE
(条件跳转,8位偏移)、CALL
(32位位移)、MOV EBX, [EDI+45]
(后置字节+偏移)。 - 后置字节(
mod, reg, r/m
):指定模式、寄存器、寄存器/存储器。 - 比例下标模式:需额外字节(
sc, index, base
)。
编码规则:
mod
:0(无偏移)、1(8位偏移)、2(16/32位偏移)、3(寄存器)。r/m
:指定寄存器或寻址模式,32位模式下r/m=4
使用比例下标。- 复杂编码增加编译器和硬件设计难度。
5. 总结
- 优势:
- 兼容性确保软件生态延续,市场主导PC领域。
- 复杂寻址模式和存储器操作减少指令数。
- 劣势:
- 指令集复杂,硬件实现成本高。
- 寄存器数量少,增加分配压力。
- 在移动设备领域竞争力弱(后PC时代)。
四、谬误与陷阱
1. 谬误:更强大指令意味着更高性能
- 例:x86重复前缀+
move
指令传输内存数据,性能低于标准寄存器操作(约慢1.5倍)或浮点寄存器操作(慢2倍)。 - 原因:复杂指令增加时钟周期或执行时间,简单指令序列更高效。
2. 谬误:汇编语言编程获得最高性能
- 现状:现代编译器优化(如寄存器分配)接近或超越手写汇编。
- 风险:
- 汇编代码开发/调试时间长,可移植性差,维护困难。
- 高级语言便于跨平台和未来机器适配。
- 例:C编译器忽略手动寄存器分配提示,自动优化更优。
3. 谬误:二进制兼容性阻止指令集变化
- 反例:x86在35年新增大量指令(每月约1条),兼容性未限制扩展。
- 启示:指令集可通过前缀、模式等扩展功能。
4. 陷阱:字寻址误以为地址差1
- 错误:汇编程序员常误认为连续字地址差1,而非4字节(MIPS字寻址)。
- 解决:明确字长,地址计算加4。
5. 陷阱:指针指向已释放的局部变量
- 错误:返回指向局部数组的指针,栈回收后指针失效。
- 解决:遵循栈规则(图2-12),避免外部使用自动变量指针。
五、扩展与总结
1. 性能优化
- 编译器:
- 利用流水线(第4章)和存储器层次(第5章)优化指令序列。
- 自动寄存器分配减少手动干预。
- 指令集设计:
- RISC(如ARM/MIPS):简单指令,易于流水化和优化。
- CISC(如x86):复杂指令减少代码量,但硬件实现复杂。
- ARMv8:向MIPS靠拢,简化条件执行,增加寄存器,适合现代编译器。
2. 市场与应用
- ARM:
- 嵌入式和移动设备霸主,低功耗、高集成度。
- ARMv8扩展64位,增强服务器和桌面潜力。
- x86:
- PC和高性能计算核心,兼容性驱动市场。
- 移动领域受限,需优化功耗和复杂性。
3. 未来趋势
- ARM:64位普及,挑战x86在服务器领域的地位。
- x86:通过AVX等扩展支持AI和高性能计算,需简化指令集以适应移动设备。
- 编译器:持续缩小汇编与高级语言性能差距,降低汇编编程需求。
4. 总结
- ARMv7:灵活寻址和条件执行,适合嵌入式,复杂性略高。
- ARMv8:简化设计,接近MIPS,优化64位场景。
- x86:兼容性强,指令复杂,适合PC但移动领域受限。
- 关键启示:
- 指令集设计需平衡简单性与功能,RISC更易优化。
- 现代编译器减少汇编需求,高级语言提升开发效率。
- 地址空间扩展(如64位)是指令集长期发展的关键。