--- title: 2023q1 Homework2 (quiz2) tags: linux 2023 --- # 2023q1 Homework2 (quiz2) contributed by < `randyuncle` > ## 測驗一 ```c uint64_t next_pow2(uint64_t x) { x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 8; x |= x >> 16; x |= x >> 32; return ++x; } ``` * 此程式碼主要的目標是找出變數 `x` 最接近且大於等於 2 的冪次。例如說代入 `x = 42`,回傳的值會是 64,也就是 $2^{6}$。 * 達成以上目標的原理:將輸入的變數 `x` 在做 right shift 後產生的值和原本的 `x` 做 63 次的 OR 運算,及可將第一個 1 以後全部的 0 皆設定成 1。如 `x = 42` 代入並經過前述之運算後,會變成 63 (如果以十六進位的觀點來看的話,就是從 `x = 0x002A` 變成 `0x003F`)。最後,於函數的尾端將 `x` 的值加一,使 `x` 的值進位,從而得到答案。 不過,如果要再精簡此程式碼的話,可以將其簡化為以下: ```c uint64_t next_pow2(uint64_t x) { x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; x |= x >> 32; return ++x; } ``` ### 運用 `__builtin_clzl` 改寫程式碼 首先,先看 `__builtin_clzl` 的定義 > int __builtin_clz (unsigned int x) Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined. int __builtin_clzl (unsigned long) Similar to __builtin_clz, except the argument type is unsigned long. 從上面的定義可知道,`__builtin_clzl` 是用來計算並回傳整數中的最高位元位之前的 0 位元的數量。所以,我們也可以用`__builtin_clzl` 去做變數 `x` 的下一個 2 的冪次方的計算。要特別注意的是 `__builtin_clzl` 和 `__builtin_clz` 一樣,在輸入為`x = 0` 時是未定義操作,所以在實作此函數時要做相應的排除。 除此之外,有一點要注意的事,`__builtin_clzl` 只能支援 `unsigned long` 的資料型態,也就是說它的輸入最大為 32 位元的值。所以在實作此改寫方案時要將變數 `x` 切成兩個 32 位元的數值來進行。 因此,程式可被改寫為以下: ```c uint64_t next_pow2(uint64_t x) { if(!x) return 1; uint32_t front = (uint32_t)(x >> 32), back = (uint32_t)x; int n = 0; if(front) n = 64 - __builtin_clzl(front); else n = 32 - __builtin_clzl(back); return 1 << n; } ``` ### `clz` 內建函式已運用時,編譯器能否產生對應的 x86 指令? 執行 `cc -O2 -std=c99 -S next_pow2.c` 後,可以在產生的 `next_pow2.s` 檔案中,發現到裡面誕生了 `bsrq` 指令。因此,可說明上面實作的 `clz` 內建函式在運用後,編譯器能產生對應的 x86 指令。 ``` ... next_pow2: .LFB0: .cfi_startproc endbr64 movl $1, %eax testq %rdi, %rdi je .L1 movq %rdi, %rax shrq $32, %rax jne .L8 bsrq %rdi, %rdi movl $32, %ecx xorq $63, %rdi subl %edi, %ecx ... ``` ## 測驗二