# 2023q1 Homework1 (quiz2) contributed by `JoshuaLee032190` ### 測驗一 * 根據題目的名稱`next_pow2`,我們可以知道這個題目要找出當前整數的下一個2 的冪整數。 * 題目如下: ```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 >> AAAA; x |= x >> 16; x |= x >> BBBB; return CCCC; } ``` * 要回答這個問題首先可以直接看 pattern,可以看到前面總共做了7次 `x |= x >> 1`,而且`AAAA`的下一行為 16,由此我們可以推測 `AAAA` 為 8 * `BBBB` 為 32 * `CCCC` 就要從前面想了,前面的意思為==把這個整數的某一個位元往右全部填滿 1==,那在全部都是1的狀況,我們只需要 `+ 1` 即可得出最終的結果 :::info 我覺得前面右移做了七次有點多此一舉,而測試後,其實可以把code 改成以下的狀況達到同樣的結果 ```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 + 1; } ``` ::: ### 延伸問題 #### 1. 解釋上述程式碼原理,並用 __builtin_clzl 改寫 ```c 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 改寫完成 uint64_t next_pow2_2(uint64_t x) { int hi = __builtin_clzl(x >> 32); int lo = __builtin_clzl(x); uint64_t res = 1; return (hi == 31) ? res << 31 - lo + 1 : res << 64 - hi; } ``` > #### 2. 在 Linux 核心原始程式碼找出類似的使用案例並解釋 * 我先使用 `grep -r "roundup" *` 找出了 pci.h ```c # /include/linux/pci.h static inline int pci_rebar_bytes_to_size(u64 bytes) { bytes = roundup_pow_of_two(bytes); /* Return BAR size as defined in the resizable BAR specification */ return max(ilog2(bytes), 20) - 20; } ``` * 這段程式碼主要是為了要初始化及配置空間,其中`20`的數字代表了 1mb 的大小所以先取 `max` * 可以想像,回傳值是一個大於等於0的常數,如果回傳值為0,那就代表PCI只需要配置1MB的空間,反之,以回傳值來判斷需要多配置多少空間 #### 3. 當上述 clz 內建函式已運用時,編譯器能否產生對應的 x86 指令? > 提示: 可執行 gcc -O2 -std=c99 -S next_pow2.c 並觀察產生的 > next_pow2.s 檔案,確認 bsrq 指令 (表示 “Bit Scan Reverse”) * 有產生bsrq ``` next_pow2_2: .LFB14: .cfi_startproc endbr64 shrq $32, %rdi movl $64, %ecx movl $1, %eax bsrq %rdi, %rdi xorq $63, %rdi subl %edi, %ecx salq %cl, %rax ret .cfi_endproc ``` ## 測驗二 ### 解釋題目 * 由題目敘述得知,這個 function 會回傳1~`輸入`,的二進位連接起來的數字 * 題目如下 ```c int concatenatedBinary(int n) { const int M = 1e9 + 7; int len = 0; /* the bit length to be shifted */ /* use long here as it potentially could overflow for int */ long ans = 0; for (int i = 1; i <= n; i++) { /* removing the rightmost set bit * e.g. 100100 -> 100000 * 000001 -> 000000 * 000000 -> 000000 * after removal, if it is 0, then it means it is power of 2 * as all power of 2 only contains 1 set bit * if it is power of 2, we increase the bit length */ if (!(DDDD)) len++; ans = (i | (EEEE)) % M; } return ans; } ``` * 由題目內的參數名稱以及 `ans = (i | (EEEE)) & M`可知,`len` 為往左移動的距離 * 而只有在一種條件下 `len` 才會需要 `+ 1`,此情況為: ```python # 考慮 1 - 10 的位元如下 1 : 0001 2 : 0010 3 : 0011 4 : 0100 5 : 0101 6 : 0110 7 : 0111 8 : 1000 9 : 1001 10: 1010 ``` * 我們可以仔細觀察位元進位的時刻,狀況為前一個數字和當前的數字做 `AND` 運算得到 0 的狀況 * 所以 `DDDD` 很明顯就是 i & (i - 1) * 而每次都需要更新當前的位元到 `ans` 中,可想而知 `EEEE` 為 `ans << len`,如此一來就可以把運算後的數字放入當前空下來的位元 ### 使用__builtin 改寫 * __builtin_ffs : 用來找出 LSB 的位置 ==從右邊開始數的第一個bit== * __builtin_clz : 用來找出 MSB 的位置 ==從左邊開始數的第一個bit== * __builtin_ctz : 回傳從右邊開始數有幾個0 ```c int concatenatedBinary(int n) { const int M = 1e9 + 7; int len = 0; long ans = 0; for (int i = 1; i <= n; i++) { len = 32 - __builtin_clz(i); ans = (i | (ans << len)) % M; } return ans; } ``` ## 測驗三 * 看不懂 待處理 ## 測驗四 ### 解釋程式碼 * `is_pattern` 主要是在看輸入的無號整數的編碼是否存在特定樣式 * 此特殊樣式為==從MSB開始往LSB存在連續1== * 前面的function 可以看到在迴圈中x 總是跟 0x8000 也就是 `1000000000000000` 做and,當位元還沒有被清空時,只要中間的連續1斷開則回傳`false` > 後面的function 則是找出這個pattern 會發生的事情 ``` # 我們來看一下這個 pattern (這個假設在 bit數為5的狀況) 10000 11000 11100 11110 11111 首先,題目很明顯告訴你有一個 xor 跟比大小;也就是我們必須往比大小的方向思考 那要怎麼樣才可以利用這個 pattern 比大小呢? 我們只需要先取 x 的負數,如此一來可以得到 10000 ==> 10000 11000 ==> 01000 11100 ==> 00100 11110 ==> 00010 11111 ==> 00001 此時 x ^ -x 就會變成 00000 10000 11000 11100 11110 最後再與原本的狀況比大小,此時,如果符合這個 pattern 的話,結果一定會比原本的輸入還要小,此時只要比大小,回傳即可。 ``` `EEEE` 為 `-x` `FFFF` 為 `x` ### 在 linux kernel 中找此 bitmask * 待處理