/* * x in %rdi, y in %rsi, z in %rdx * subq %rdx, %rsi // y - z ==> y * imulq %rsi, %rdi // x * y ==> x * movq %rsi, %rax // y ==> %rax * salq $63, %rax // << 63 * sarq $63, %rax // >> 63 * xorq %rdi, %rax // 這個時候的%rdi已是x*y ^ %rax * 所以能夠得出結論 (x*y) ^ ((y-z) << 63 >> 63) */ long decode2(long x, long y, long z) { return (x * y) ^ ((y - z) << 63 >> 63); }
/* 根據提示: x = 2^64 * x_h + x_l (x_h表示x的高64位,x_l表示x的低64位) y = 2^64 * y_h + y_l (y_h表示y的高64位,y_l表示x的低64位) x * y = (2^64 * x_h + x_l) * (2^64 * y_h + y_l) = 2^64 * x_h * 2^64 * y_h + 2^64 * x_h * y_l + x_l * 2^64 * y_h + x_l * y_l 在上邊這個表達式中2^64 * x_h * 2^64 * y_h明顯已經越界,所以捨去, x * y = 2^64(x_h * y_l + x_l * y_h) + (x_l * y_l) 上邊的公式很重要,它表達的就是x*y的乘積的樣式,根據p = 2^64 *p_h + p_l 再結合上邊的公式 咱們得出的結論是: 2^64(x_h * y_l + x_l * y_h) + (x_l * y_l) = 2^64 *p_h + p_l 那麼2^64 *p_h = 2^64(x_h * y_l + x_l * y_h) + (x_l * y_l) - p_l p_h = (x_h * y_l + x_l * y_h) + (x_l * y_l)/2^64 - p_l/2^64 (x_l * y_l)/2^64 表示相乘後右移64位正好是他們相乘後的高64位的值 p_l/2^64 則爲0 所以咱們就把任務簡化了,咱們接下來看彙編 dest in %rdi, x in %rsi, y in %rdx stroe_prod: movq %rdx, %rax // %rax = y, 此時y_l = %rax cqto // 該命令的做用是把%rax中的符號位擴展到%rdx中,此時y_h = %rdx movq %rsi, %rcx // 這行命令的做用是配合下一行獲取x高64位的值 sarq $63, %rcx // 獲取x的高64的值x_h = %rcx imulq %rax, %rcx // 計算y_l * x_h = %rax * %rcx imulq %rsi, %rdx // 計算y_h * x_l = %rdx * %rsi addq %rdx, %rcx // 計算x_h * y_l + x_l * y_h的值 mulq %rsi // 該命令是計算%rax * %rsi的值,也就是x_l * y_l的值 addq %rcx, %rdx // 根據上邊咱們得出的結論,進行相加處理 */
/* 咱們先寫出彙編的註釋: x in %rdi, n in %esi loop: movl %esi, %ecx // %ecx = n movl $1, %edx // %edx = 1 movl $0, %eax // %eax = 0 jmp .L2 // 跳轉到L2 .L3: movq %rdi, %r8 // %r8 = x andq %rdx, %r8 // %r8 &= %rdx orq %r8, %rax // %rax |= %r8 salq %c1, %rdx // %rdx <<= %cl .L2: testq %rdx, %rdx // %rdx & %rdx jne .L3 // if != jump to .L3 根據.L2咱們能夠得出的結論是若是%rdx的值爲0 就繼續循環 .L3中作了什麼事呢? 咱們知道%rdx的初始值爲1,返回值%rax的值爲0,那麼.L3中的解釋爲: 1. x &= %rdx 2. %rax |= x 3. %rdx << n的低8位的值,也是爲了保護位移 經過分析,咱們就能夠得出結論,該函數的目的是得出x中n的倍數的位掩碼 答案: A: x --> %rdi n --> %esi result --> %rax mask --> %rdx B: result = 0 mask = 1 C: mask != 0 D: mask <<= n E: result |= (x & mask) F: 以下函數 */ long loop(long x, int n) { long result = 0; long mask; for (mask = 1; mask != 0; mask = mask << n) { result |= (x & mask); } return result; }
long cread(long *xp) { return (xp ? *xp : 0); } long cread_alt(long *xp) { return (!xp ? 0 : *xp); }
typedef enum {MODE_A, MODE_B, MODE_C, MODE_D, MODE_E} mode_t; long switch3(long *p1, long *p2, mode_t action) { long result = 0; switch(action) { case MODE_A: result = *p2; *p2 = *p1; break; case MODE_B: result = *p1 + *p2; *p1 = result; break; case MODE_C: *p1 = 59; result = *p2; break; case MODE_D: result = *p2; *p1 = result; result = 27; break; case MODE_E: result = 27; break; default: result = 12; } return result; }
/* sub $0x3c, %rsi // %rsi = n - 60 cmp $0x5, %rsi // 比較%rsi : 5 ja 4005c3 // 大於就跳轉 jmpq *0x4006f8(,%rsi,8) // 這一行的目的是直接在跳轉表中獲取地址而後跳轉 // 所以下邊這些彙編代碼就是對應跳轉表中的地址 4005a1對應的index爲0和2: lea 0x0(,%rdi,8), %rax // result = 8x 4005c3對應的index爲1,也就是case 1,經過觀察,它用的就是default的指令 因此case 1 在switch中是缺失的 4005aa對應的index爲3: mov %rdi,%rax // result = x sar $0x3,%rax // result >>= 3 也就是result = x / 8 4005b2對應的index爲4: mov %rdi,%rax // result = x shl $0x4,%rax // result <<= 4 sub %rdi,%rax // result -= x mov %rax,%rdi // x = result 也就是result = x * 15; x = result 4005bf對應的index爲5: imul %rdi,%rdi // x *= x lea 0x4b(%rdi), %rax // result = 75 + x 通過上邊的分析,就很容易得出結論了,可是別忘了要把index加上60 */ long switch_prob(long x, long n) { long result = x; switch(n) { case 60: case 62: result = 8 * x; break; case 63: result = x / 8; break; case 64: result = 15 * x; x = result; case 65: x *= x; default: result = 75 + x; } return result; }
設L爲數組元素的大小,X_a表示數據的起始地址 &A[i][j][k] = X_a + L(i * S * T + j * T + k) 咱們再進一步分析彙編代碼: i in %rdi, j in %rsi, k in %rdx, dest in %rcx leaq (%rsi,%rsi,2), %rax // %rax = 3j leaq (%rsi,%rax,4), %rax // %rax = 13j movq %rdi, %rsi // %rsi = i salq $6, %rsi // 結合上一條指令,%rsi = i << 6 addq %rsi, %rdi // %rdi = 65i addq %rax, %rdi // %rdi = 65i + 13j addq %rdi, %rdx // %rdx = 65i + 13j + k movq A(,%rdx,8), %rax // %rax = *(A + 8(65i + 13j + k)) movq %rax, (%rcx) // *dest = *(A + 8(65i + 13j + k)) movl $3640, %eax // %rax = 3640 使用A + 8(65i + 13j + k)和最上邊的公式對比後發現: L: 8 T: 13 S: 5 要求出R還必須用到3640這個值 R * T * S * L = 3640 R = 3640 / 8 / 13 / 5 = 7 R: 7
咱們先假設M爲4,咱們假設矩陣A爲: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 那麼在用函數transpose處理以後,矩陣變成了 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16 能夠看出對矩陣沿着對角線進行了轉換。咱們繼續看彙編代碼 下邊的彙編代碼只是函數中內循環中的代碼 .L6: movq (%rdx), %rcx // %rcx = A[i][j] movq (%rax), %rsi // %rsi = A[j][i] movq %rsi, (%rdx) // A[i][j] = A[j][i] movq %rcx, (%rax) // A[j][i] = A[i][j] addq $8, %rdx // %rdx += 8 addq $120, %rax // %rax += 120 cmpq %rdi, %rax // jne .L6 // 咱們很容易就發現,指向A[i][j]的寄存器爲%rdx,指向A[j][i]的寄存器爲%rax 求M最關鍵的是找出%rax寄存器移動的規律,由於%rdx也就是A[i][j] + 8 就表示右移一位 而%rax則要移動M * 8位 所以M = 120 / 8 = 15 上邊的寄存器%rdi應該放的就是i == j時的A[i][j]的地址
首先咱們寫出彙編代碼的註釋: n in %rdi, A in %rsi, j in %rdx sum_col: leaq 1(,%rdi,4), %r8 // %r8 = 1 + 4n leaq (%rdi,%rdi,2), %rax // %rax = 3n movq %rax, %rdi // %rdi = 3n testq %rax, %rax // 3n & 3n jle .L4 // if <= 0 .L4 salq $3, %r8 // %r8 = (1 + 4n) << 3 leaq (%rsi,%rdx,8), %rcx // %rcx = 8j + A movl $0, %eax // %rax = 0 movl $0, %edx // %rdx = 0 .L3: addq (%rcx), %rax // %rax += *%rcx addq $1, %rdx // %rdx += 1 addq %r8, %rcx // %rcx += (1 + 4n) << 3 cmpq %rdi, %rdx // %rdx : 3n jne .L3 rep; ret .L4: movl $0, %eax // %rax = 0 ret 很明顯,.L3上邊的代碼都是爲循環準備數據的 若是n = 0 那麼就直接返回result = 0 而後初始化局部變量%rdx保存i的值,%rax保存result的值,%rcx保存每一行j的地址, 而後進入循環體.L3 由%rdx : 3n能夠看出打破循環的條件是 i == 3n 推導出:NR(n) = 3n 由%rcx += (1 + 4n) << 3能夠看出,%rcx每次都移動了一行的寬度,也就是NC(n) = (1 + 4n) << 3 答案是: NR(n) = 3n NC(n) = (1 + 4n) << 3
咱們先給彙編代碼添加註釋: x in %rdi, y in %rsi, z in %rdx eval: subq $104, %rsp // 給棧分配了104個字節的空間 movq %rdx, 24(%rsp) // 把z的值保存在偏移量爲24的位置 leaq 24(%rsp), %rax // %rax保存了z的指針 movq %rdi, (%rsp) // 把x的值保存在偏移量爲0的位置 movq %rsi, 8(%rsp) // 把y的值保存在偏移量爲8的位置 movq %rax, 16(%rsp) // 把z的指針值保存在偏移量爲16的位置 leaq 64(%rsp), %rdi // 把偏移量爲64的指針賦值給%rdi,當作參數傳遞給後邊的函數 call process movq 72(%rsp), %rax // 取出偏移量爲72的值賦值給%rax addq 64(%rsp), %rax // + addq 80(%rsp), %rax // + addq $104, %rsp // 恢復棧頂指針 ret process: movq %rdi, %rax // 把參數保存到%rax movq 24(%rsp), %rdx // %rdx = &z 這裏有點意思,當調用call後會把函數下邊代碼的地址壓入棧中 movq (%rdx), %rdx // %rdx = z movq 16(%rsp), %rcx // %rcx = y movq %rcx, (%rdi) // 把y保存到偏移量爲64 + 8 = 72的位置 movq 8(%rsp), %rcx // %rcx = x movq %rcx, 8(%rdi) // 把x保存到偏移量爲64 + 8 + 8 = 80的位置 movq %rdx, 16(%rdi) // 把z保存到偏移量爲64 + 8 + 16 = 88的位置 ret 經過上邊的註釋,下邊的問題就很清楚了 A: ----------- <-- 108 z ----------- <-- 24 &z ----------- <-- 16 y ----------- <-- 8 x ----------- <-- %rsp B: 傳遞了一個相對於%rsp偏移量爲64的指針 C: 直接使用偏移量來訪問的s的元素 D: 直接設置偏移量 E: ----------- <-- 108 ----------- <-- 88 z ----------- <-- 80 x ----------- <-- 72 y ----------- <-- 64 --- %rax ----------- <-- 32 z ----------- <-- 24 &z ----------- <-- 16 y ----------- <-- 8 x ----------- <-- %rsp F: 經過這個例子,咱們可以發現,若是把結構做爲參數,那麼實際傳遞的會是一個空的位置指針,函數把數據 存儲在這個位置上,同時返回值也是這個指針。
p in %rdi, q in %rsi setVal: movslq 8(%rsi), %rax // %rax = *(8 + q) addq 32(%rsi), %rax // %rax += *(32 + q) movq %rax, 184(%rdi) // 這個問題算是很是簡單的,由最後一條代碼再加上str的結構,咱們能夠得出這樣一個等式 4 * A * B + space = 184 因爲對齊原則是保證8的倍數,分別假設space爲7和0 ==> 44 < A * B <= 46 %rax = *(8 + q) 能夠推斷出char array[B] 應該總共使用8個字節 由於須要考慮對齊原則,因此先得出 B <= 8 short s[A] %rax += *(32 + q) 咱們t佔用4個字節 ==> 4 + A * 2 <= 32 - 8 <= 24 因而咱們有三個公式來作判斷: 44 < A * B <= 46 B <= 8 A <= 10 那麼A * B 的值只能是45 組合就是 5 * 9 因爲 B <= 8 所以 B = 5 A = 9 咱們再驗證一番,short s[A] 因爲對齊原則 佔用了20個字節,跟彙編代碼一致 答案: A = 9 B = 5
i in %rdi, bp in %rsi test: mov 0x120(%rsi), %ecx // %rcx = *(288 + bp) add (%rsi), %ecx // %rcx = *(288 + bp) + *bp lea (%rdi,%rdi,4), %rax // %rax = 5 * i lea (%rsi,%rax,8), %rax // %rax = 5 * i * 8 + bp mov 0x8(%rax), %rdx // %rdx = *((5 * i * 8 + bp) + 8) movslq %ecx, %rcx mov %rcx, 0x10(%rax,%rdx,8) // &(16 + %rax + 8 * %rdx) = %rcx retq 由 %rdx = (5 * i * 8 + bp) + 8 能夠推導出 a_struct a[CNT] 每一個元素佔40個字節,first佔8個字節 ==> CNT = (288 - 8) / 40 ==> CNT = 7 本題重點理解%rax 和 %rdx中保存的是什麼的值, %rax中保存的是ap的值,而%rdx中保存的是ap->idx的值,理解了這一層接下來就簡單了 說明ap->idx保存的是8字節的值,根據 &(16 + %rax + 8 * %rdx) = %rcx 能夠得出idx應該是結構體的第一個變量long idx 若是結構體佔用了40個字節 , 那麼數組x應該佔用 40 - 8 也就是32個字節,每一個元素佔8個,能夠容納4個元素 typedef struct { long idx; long x[4]; }a_struct; 這個題目最重要的地方是理解mov 0x8(%rax), %rdx 這段代碼,它是求ap->idx的值。
A: 0 8 0 8 B: e1最多須要16個字節 e2最多須要16個字節 所以 總共須要16個字節 C: up in %rdi proc: movq 8(%rdi), %rax // %rax = *(8 + up) 取出偏移量爲8的地址 movq (%rax), %rdx // %rdx = *%rax 取出該地址中的值 movq (%rdx), %rdx // 取出指針指向的值 subq 8(%rax), %rdx // 用該值減去 *(%rax + 8) movq %rdx, (%rdi) // ret 通常來講 若是一個寄存器,好比說%rax 在下邊的使用中用到了(%rax),咱們就認定該寄存器保存的值爲指針 movq 8(%rdi), %rax %rax保存了up偏移量爲8的指針值,在該函數中偏移量爲8仍是指針的只能是e2的next ==> %rax = up -> e2.next movq (%rax), %rdx %rdx 一樣保存的是指針,對(%rax)取值獲得的是up下一個unio的指針 ==> %rdx = *(up -> e2.next) movq (%rdx), %rdx 這行代碼事後,%rdx就再也不是指針了,是一個值,但運行以前,%rdx是個指針 ==> %rdx = *(*(up -> e2.next) -> e2.p) subq 8(%rax), %rdx 咱們知道%rax是個指針 指向next +8後 ==> 8(%rax) = *(up -> e2.next) -> e1.y 答案: up -> e2.x = *(*(up -> e2.next) -> e2.p) - *(up -> e2.next) -> e1.y;
#include <stdio.h> #include <assert.h> #define BUF_SIZE 12 void good_echo(void) { char buf[BUF_SIZE]; while(1) { /* function fgets is interesting */ char* p = fgets(buf, BUF_SIZE, stdin); if (p == NULL) { break; } printf("%s", p); } return; } int main(int argc, char* argv[]) { good_echo(); return 0; }
咱們先畫一畫棧圖: ---------- ---------- <-- %rbp 0 ---------- s1 <-- -16 e1 ---------- p ---------- p e2 ---------- s2 A: s2 = %rsp - 16 - (-16 & (8n + 30)) 因爲s2 = %rsp - 16 因此 s2 = s1 - (-16 & (8n + 30)) 這裏的-16的十六進制表示爲0xfffffff0,之因此用& 就是爲了求16的整數倍 B: p = (s2 + 15) & 0xfffffff0 C: s2 = s1 - (0xfffffff0 & (8n + 30)) 根據這個公式 當n是偶數的時候,咱們能夠把式子簡化爲 s2 = s1 - (8 * n + 16) 當n是奇數的時候,咱們能夠把式子簡化爲 s2 = s1 - (8 * n + 24) 先求e1最小的狀況 e1和e2是對立的關係,要想e1最小,那麼e2就要最大,e2最大也就是15, n是偶數的時候,e1 = 16 - 15 = 1 這個時候s1 % 16 == 1 e1最大的狀況: e2 == 0 時 e1最大, 當n是奇數的時候,e1 == 24 這個時候s1 % 16 == 0(p中多處了一個8字節) D: s2 確保可以容納足夠的p, p可以保證自身16對齊
#include <stdio.h> #include <assert.h> typedef enum {NEG, ZERO, POS, OTHER} range_t; range_t find_range(float x) { __asm__( "vxorps %xmm1, %xmm1, %xmm1\n\t" "vucomiss %xmm1, %xmm0\n\t" "jp .P\n\t" "ja .A\n\t" "jb .B\n\t" "je .E\n\t" ".A:\n\t" "movl $2, %eax\n\t" "jmp .Done\n\t" ".B:\n\t" "movl $0, %eax\n\t" "jmp .Done\n\t" ".E:\n\t" "movl $1, %eax\n\t" "jmp .Done\n\t" ".P:\n\t" "movl $3, %eax\n\t" ".Done:\n\t" ); } int main(int argc, char* argv[]) { range_t n = NEG, z = ZERO, p = POS, o = OTHER; assert(o == find_range(0.0/0.0)); assert(n == find_range(-2.3)); assert(z == find_range(0.0)); assert(p == find_range(3.33)); return 0; }
#include <stdio.h> #include <assert.h> typedef enum {NEG, ZERO, POS, OTHER} range_t; range_t find_range(float x) { __asm__( "vxorps %xmm1, %xmm1, %xmm1\n\t" "movq $1, %rax\n\t" "movq $2, %r8\n\t" "movq $0, %r9\n\t" "movq $3, %r10\n\t" "vucomiss %xmm1, %xmm0\n\t" "cmovg %r8, %rax\n\t" "cmove %r9, %rax\n\t" "cmovpq %r10, %rax\n\t" ); } int main(int argc, char* argv[]) { range_t n = NEG, z = ZERO, p = POS, o = OTHER; assert(o == find_range(0.0/0.0)); assert(n == find_range(-2.3)); assert(z == find_range(0.0)); assert(p == find_range(3.33)); return 0; }
這個題考察的是複數的概念 複數 = 實數 + 虛數 傳參的時候,有這樣的規律 (複數1, 複數2, 複數3...) 對應的浮點寄存器就會是: %xmm0, %xmm1, %xmm2, %x
看本章的過程中,彷彿回到了大學時光,在讀的的過程當中,書本上的練習題作的還能夠,可是感受不少前邊講過的東西仍是不太清楚,因而在讀完後又從新讀了一遍,在閱讀第二遍的過程當中, 注意到了不少細節,好比以前push 和 pop 有點迷惑,如今就很是清晰了html
要想記住書本中的內容,看來仍是要多讀幾遍。我感受在該章中學到最多的是理解了c語言在機器代碼級別的表示,對數據在內存中的操做更加了解了,不得不感慨編譯器的強大,如今還感受不出這些東西在實際工做中的用處,但對運行時棧的理解仍是頗有用的。git
我已經把答案上傳到了個人github中深刻理解計算機系統(第三版)做業題答案(第三章)github
有錯誤的地方能夠直接指出,歡迎討論。app