資料搬移與算術運算 (Data Movement & Arithmetic)

Overview Table

主題 小節 (書頁) 核心重點
整數暫存器 3.4 (p.215-216) 16 個 64-bit 通用暫存器,可以 1/2/4/8 bytes 存取低位部分
Operand 形式 3.4.1 (p.216-218) Immediate / Register / Memory;最一般化定址 Imm(rb,ri,s)
mov 家族 3.4.2 (p.218-222) movb/w/l/qmovabsqmovz*(零延伸)、movs*(符號延伸)、cltq
搬移範例 3.4.3 (p.222-225) pointer = address;dereference = 把位址放暫存器再做記憶體參照
Push / Pop 3.4.4 (p.225-227) stack 向低位址成長;pushq = 先減 %rsp 再寫入;popq 相反
leaq 3.5.1 (p.227-229) 只算 effective address 不碰記憶體;編譯器拿來做 a + b*s + Imm 算術
一元/二元運算 3.5.2 (p.230) inc/dec/neg/not;add/sub/imul/xor/or/and(D ← D op S)
位移 3.5.3 (p.230-231) sal=shl;sar 算術右移 vs shr 邏輯右移;移位量取 %cl 低 m 位
討論 3.5.4 (p.232) 多數指令 signed/unsigned 通用;只有右移需要區分
特殊算術 3.5.5 (p.233-236) 單運算元 mulq/imulq 產生 128-bit 積;cqto + idivq / 清零 %rdx + divq

3.4 存取資訊:16 個整數暫存器 (p.215-216)

x86-64 CPU 有 16 個 64-bit 通用暫存器,名稱皆以 %r 開頭,存放整數與指標。歷史演進:8086 的 8 個 16-bit 暫存器(%ax%bp)→ IA32 擴為 32-bit(%eax%ebp)→ x86-64 擴為 64-bit(%rax%rbp)並新增 %r8%r15。指令可操作各暫存器的低位 1、2、4、8 bytes

 63              31          15      7      0
 +---------------+-----------+-------+------+
 |            %rax            (64-bit)      |   <- movq 存取全部
 |               |        %eax (32-bit)     |   <- movl(高 4 bytes 清 0!)
 |               |           |  %ax (16-bit)|   <- movw(其餘 bytes 不變)
 |               |           |       | %al  |   <- movb(其餘 bytes 不變)
 +---------------+-----------+-------+------+
64-bit 32-bit 16-bit 8-bit 慣例角色
%rax %eax %ax %al 回傳值
%rbx %ebx %bx %bl Callee saved
%rcx %ecx %cx %cl 第 4 參數
%rdx %edx %dx %dl 第 3 參數
%rsi %esi %si %sil 第 2 參數
%rdi %edi %di %dil 第 1 參數
%rbp %ebp %bp %bpl Callee saved
%rsp %esp %sp %spl Stack pointer
%r8 / %r9 %r8d / %r9d %r8w / %r9w %r8b / %r9b 第 5 / 6 參數
%r10 / %r11 %r10d / %r11d %r10w / %r11w %r10b / %r11b Caller saved
%r12%r15 %r12d%r15d %r12w%r15w %r12b%r15b Callee saved
產生小於 8 bytes 結果時的兩種慣例(p.215)

  • 產生 1 或 2 bytes 的指令:目的暫存器其餘 bytes 保持不變
  • 產生 4 bytes 的指令:高 4 bytes 一律清為 0(x86-64 從 IA32 擴充時採用的慣例)。

3.4.1 Operand Specifiers:三類運算元與定址模式 (p.216-218)

運算元(operand)有三型:immediate(常數,ATT 格式寫 $Imm,如 $-577$0x1F;不同指令允許不同範圍,assembler 自動選最精簡編碼)、register(R[ra],把暫存器組視為以識別碼索引的陣列 R)、memory reference(依計算出的 effective address 存取記憶體,記為 M[Addr];b-byte 值寫作 Mb[Addr],通常省略下標 b)。

Type Form Operand value 名稱
Immediate $Imm Imm Immediate
Register ra R[ra] Register
Memory Imm M[Imm] Absolute
Memory (ra) M[R[ra]] Indirect
Memory Imm(rb) M[Imm + R[rb]] Base + displacement
Memory (rb,ri) M[R[rb] + R[ri]] Indexed
Memory Imm(rb,ri) M[Imm + R[rb] + R[ri]] Indexed
Memory (,ri,s) M[R[ri]*s] Scaled indexed
Memory Imm(,ri,s) M[Imm + R[ri]*s] Scaled indexed
Memory (rb,ri,s) M[R[rb] + R[ri]*s] Scaled indexed
Memory Imm(rb,ri,s) M[Imm + R[rb] + R[ri]*s] Scaled indexed

3.4.2 資料搬移指令 (p.218-222)

mov class 是最常用的指令類:movb(1B)、movw(2B)、movl(4B)、movq(8B),把資料由來源複製到目的地,不做任何轉換。來源可為 immediate / register / memory;目的可為 register / memory。

 來源 -> 目的 的 5 種合法組合:
 Imm --> Reg     movl $0x4050,%eax
 Imm --> Mem     movb $-17,(%rsp)
 Reg --> Reg     movw %bp,%sp
 Reg --> Mem     movq %rax,-12(%rbp)
 Mem --> Reg     movb (%rdi,%rcx),%al
 Mem -X-> Mem    禁止!需經暫存器分兩步:load -> store
三大限制(常考)

  1. 兩個 operand 不可同時是 memory;memory-to-memory 複製需兩條指令(先 load 進暫存器再 store)。
  2. 暫存器 operand 的大小必須與指令字尾(b/w/l/q)一致。
  3. movq 的 immediate 來源只能是 32-bit two's-complement,會符號延伸成 64-bit;任意 64-bit immediate 要用 movabsq I, R(目的地只能是暫存器)。

movl 例外:目的地為暫存器時,movl 會把高 4 bytes 設為 0(p.219)。示範(p.220 Aside):

movabsq $0x0011223344556677, %rax    %rax = 0011223344556677
movb    $-1, %al                     %rax = 00112233445566FF   (其餘不變)
movw    $-1, %ax                     %rax = 001122334455FFFF   (其餘不變)
movl    $-1, %eax                    %rax = 00000000FFFFFFFF   (高 4B 清零!)
movq    $-1, %rax                    %rax = FFFFFFFFFFFFFFFF

零延伸與符號延伸(小 → 大複製)

Class Effect 成員 來源 → 目的
movz S, R R ← ZeroExtend(S) movzbw, movzbl, movzwl, movzbq, movzwq reg/mem → reg
movs S, R R ← SignExtend(S) movsbw, movsbl, movswl, movsbq, movswq, movslq reg/mem → reg
cltq %rax ← SignExtend(%eax) 無 operand 等價 movslq %eax,%rax 但編碼更短

3.4.3 資料搬移範例 (p.222-225)

long exchange(long *xp, long y) {      exchange:                  xp in %rdi, y in %rsi
    long x = *xp;                        movq (%rdi), %rax        取 *xp,設為回傳值
    *xp = y;                             movq %rsi, (%rdi)        把 y 寫入 xp 指向處
    return x;                            ret
}

3.4.4 Push 與 Pop (p.225-227)

Stack 是 LIFO(last-in, first-out)結構:push 加入、pop 移除,pop 出的一定是最近 push 且仍在 stack 上的值。x86-64 的 program stack 存於記憶體某區域,向低位址成長——stack 頂端元素的位址是所有元素中最低的;%rsp 保存頂端元素位址。習慣上把 stack 圖畫成上下顛倒(top 在下)。

指令 Effect 說明
pushq S R[%rsp] ← R[%rsp] − 8; M[R[%rsp]] ← S 先減 8 再寫入
popq D D ← M[R[%rsp]]; R[%rsp] ← R[%rsp] + 8 先讀出再加 8
     初始                pushq %rax             popq %rdx
  %rax 0x123           %rax 0x123             %rax 0x123
  %rdx 0               %rdx 0                 %rdx 0x123
  %rsp 0x108           %rsp 0x100             %rsp 0x108

  高位址 (stack bottom)
      |    ...    |       |    ...    |          |    ...    |
0x108 |___________|<-rsp  |___________|    0x108 |___________|<-rsp
      (top)         0x100 |__0x123____|<-rsp     |__0x123____|  <- 值仍留在記憶體,
  低位址 (成長方向 ↓)        (new top)                但已不屬於 stack

3.5 算術與邏輯運算總表 (p.227-228)

分四組:load effective address、unary、binary、shift。除 leaq 外皆為指令類(各有 b/w/l/q 四種大小,如 add 類 = addb/addw/addl/addq)。

指令 Effect 說明
leaq S, D D ← &S Load effective address(無其他大小變體)
inc D / dec D D ← D+1 / D ← D−1 遞增 / 遞減
neg D / not D D ← -D / D ← ~D 取負 / 位元反相
add S, D D ← D + S
sub S, D D ← D − S 從 D 減去 S(注意順序)
imul S, D D ← D * S 乘(截斷成 operand 大小)
xor / or / and S, D D ← D ^ / | / & S 位元運算
sal k, D / shl k, D D ← D << k 左移(兩者相同,右側補 0)
sar k, D D ← D >>A k 算術右移(補符號位)
shr k, D D ← D >>L k 邏輯右移(補 0)
表中這些整數算術與邏輯運算會設定 condition codes,是 3.6 條件控制的基礎,見 03-Machine-Level-Programs/03-Control-Flow。但下一節的 leaqmov 類指令不會改動 condition codes(leaq 專用於位址計算,p.238)。

3.5.1 Load Effective Address:leaq (p.227-229)

leaq 形式上像「從記憶體讀到暫存器」的 movq 變體,但完全不存取記憶體——它把第一運算元的 effective address 直接複製到目的地(即 C 的 &S)。用途:(1) 產生之後要用的指標;(2) 精簡表達算術:一條指令算出 Imm+rb+ris

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
看到 leaq 別想成記憶體讀取——先把它翻譯成 Imm + base + index*s 的算術式再對照 C 程式。

3.5.2 一元與二元運算 (p.230)

3.5.3 位移運算 (p.230-231)

移位量在前、被移值在後。移位量可為 immediate單一 byte 暫存器 %cl——這是少數只允許特定暫存器當 operand 的指令。目的 operand 可為 register 或 memory。

移位量公式:對 w-bit 資料位移,實際移位量取 %cl低 m 位,其中 2m=w;更高位一律忽略。

%cl = 0xFF 時:salb 移 7、salw 移 15、sall 移 31、salq 移 63。

3.5.4 討論:signed / unsigned 通用性 (p.232)

Figure 3.10 中絕大多數指令對 unsigned 與 two's-complement 運算通用,只有右移需要區分 signed(sar)/ unsigned(shr)版本——這正是 two's-complement 成為 signed 整數首選實作方式的原因之一(呼應 02-Information-Representation/03-Integer-Arithmetic)。

範例 arith(p.232):C 的 z*48 被編譯成 leaq (%rdx,%rdx,2),%rax(×3)+ salq $4,%rax(×16);同一暫存器 %rax 依序承載 3*z48*zt4——編譯器會讓單一暫存器輪流存放多個程式值,並在暫存器間搬移。

xorq %rcx,%rcx 是慣用「清零」寫法(x ^ x = 0),等效 movq $0,%rcx 但編碼更短(Practice Problem 3.11)。

3.5.5 特殊算術:128-bit 乘除 (p.233-236)

兩個 64-bit 數相乘可能需 128 bits 表示(見 2.3)。Intel 稱 16-byte 量為 oct word;x86-64 以暫存器對 %rdx(高 64 bits):%rax(低 64 bits) 合成一個 128-bit 值,提供有限的 128-bit 支援。

指令 Effect 說明
imulq S(單運算元) R[%rdx]:R[%rax] ← S × R[%rax] Signed 全乘法(128-bit 積)
mulq S R[%rdx]:R[%rax] ← S × R[%rax] Unsigned 全乘法
cqto R[%rdx]:R[%rax] ← SignExtend(R[%rax]) 轉成 oct word;Intel 文件稱 cqo(ATT 與 Intel 名稱少見的不一致)
idivq S R[%rax] ← R[%rdx]:R[%rax] ÷ S; R[%rdx] ← R[%rdx]:R[%rax] mod S Signed 除法:商 → %rax、餘數 → %rdx
divq S 同上 Unsigned 除法
 signed 除法 x / y 的資料流(remdiv, p.236):
        %rdx (高 64)      %rax (低 64)
 cqto:  [sign bits of x][      x      ]   <- 128-bit dividend
                   |  idivq S(除數 = S)
                   v
        %rdx = 餘數 (x mod S)    %rax = 商 (x ÷ S)

Exam/Test Patterns

情境 / 關鍵字 答案
給定 reg/mem 值,求 operand 值(PP 3.1) 先算 EA = Imm + R[rb] + R[ri]·s 再查 M[EA];$ 是常數本身、無括號純數字是 Absolute 位址
mov 該用哪個字尾?(PP 3.2) 暫存器 operand 的大小:%eax→l、%dx→w、%bl→b、%rax→q;memory operand 不決定大小
組譯錯誤判斷(PP 3.3) 常見錯:兩 operand 皆 memory、字尾與暫存器大小不符、目的地是 immediate、32-bit 暫存器當定址 base
movl 寫入暫存器後高 4 bytes? 一律變 0;movb / movw 則保留原值
4-byte → 8-byte 零延伸 不存在 movzlq,直接用 movl reg,reg
*dp = (dest_t)*sp 選指令(PP 3.4) 小→大:signed 來源用 movs、unsigned 來源用 movz;大→小:mov 低位部分;先改大小再改 signedness
leaq k(%ra,%rb,s), %rax 求運算式(PP 3.6/3.7) 值 = k + ra + rb·s(純算術,不讀記憶體)
subq %rax,%rdx 的效果 %rdx = %rdx − %rax(source 在前、destination 在後)
%cl = 0xFFsalq 移幾位?(3.5.3) 63(取低 6 位,因 2^6 = 64)
xorq %rcx,%rcx 的目的(PP 3.11) 清零(x^x=0),比 movq $0,%rcx 編碼短
signed 除法指令序列(PP 3.12 對照) movq x,%raxcqtoidivq y;unsigned 版改為 %rdx 清 0 → divq
128-bit 積如何存放 %rdx(高 64):%rax(低 64);存入記憶體時低 8B 在低位址(little-endian)
pushq / popq%rsp? push 減 8、pop 加 8(stack 向低位址成長);pushq 等價 sub+mov 但僅 1 byte 編碼