# 2023q1 Homework2 (quiz2)
contributed by < `koty6069` >
[2023q1 第 2 週測驗題](https://hackmd.io/@sysprog/linux2023-quiz2)
## 測驗 `1`
### 程式碼解析
原文有提到我們可改動原本數值 `x` ,從其原本有數值的最高位元至最低位元皆設成 `1` 。
>最初 x 是 0010000000000000,經過一系列操作後,成為 0011111111111111,亦即設定 (set,即指派為 1) 自原本最高位元到最低位元中,所有的位元。
因此程式碼除了 `return CCCC` 以外,皆是在做設定為 `1` 的動作。
我們可以上面引文之數值 `0010000000000000` 進行操作,觀察其結果。
```c
x |= x >> 1; //0011000000000000
x |= x >> 1; //0011100000000000
x |= x >> 1; //0011110000000000
x |= x >> 1; //0011111000000000
x |= x >> 1; //0011111100000000
x |= x >> 1; //0011111110000000
x |= x >> 1; //0011111111000000
```
以上進行了七次 `x |= x >> 1`,將最高位的 `1` 其右方七個位元都設成 `1` ,也就是目前從原先的最高位 `1` 開始有連續 8 個位元為 `1` ,接下來可直接 shift 8 個位元,再將右方 8 個位元皆設成 `1` ,後續再 shift 16 及 32 個位元,即可將全部 64 個位元皆覆蓋到,因此 `AAAA` 為 `8` , `BBBB` 為 `32`。
```c
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
```
只要將上述連續的 `1` 再加上 `1` 即可得到 「最接近且大於等於的 2 的冪的值」,因此最後的 `CCCC` 為 `x + 1` 。
```c
return x + 1;
```
### 使用 `__builtin_clzl` 改寫
`__builtin_clzl` 會回傳最高位的 `1` 之前 0 的數量,使用 64 減去此值會得到最高位的 `1` 所在位置,因此將 1 向左移動此數值(`64 - shift`)就能得到「最接近且大於等於的 2 的冪的值」。
```c
uint64_t next_pow2(uint64_t x)
{
int shift = __builtin_clzl(x);
return 1UL << (64 - shift);
}
```
### 當上述 clz 內建函式已運用時,編譯器能否產生對應的 x86 指令?
可確認到 `bsrq` 指令。
```
next_pow2:
.LFB13:
.cfi_startproc
endbr64
bsrq %rdi, %rdi
movl $64, %ecx
movl $1, %eax
xorq $63, %rdi
subl %edi, %ecx
salq %cl, %rax
ret
.cfi_endproc
```
## 測驗 `2`
### 程式碼解析
根據註解,每次迴圈會將最右方的 `1` 移除,若移除後數值為 0 ,代表此數為 2 的冪,此時需要將長度增加,原因是二進位制在遇到 2 的冪時會使得紀錄需要的位元數增加。
>舉例來說,3 的二進位為 `11` ,需要兩個位元作紀錄, 4 的二進位則為 `100` 需要三個位元。
因此 `if` 需要在移除 `1` 後數值為 0 的情況下將 `len` 增加,`DDDD` 為 `i & (i - 1)`,將 `i` 減一時會與最右方的 `1` 做借位,接著進行 `&` 操作會將剛剛操作後產生的 `1` 和原本最右方的 `1` 都消除,此時若數值為 0 則代表此數為 2 的冪。
```c
if (!(i & (i - 1)))
len++;
```
答案需要我們將二進制全部串接,每次迴圈需要將二進制的 `i` 串接到目前的答案尾端,因此我們需要將目前的答案向左 shift ,再透過 `&` 操作將 `i` 串接上去, `EEEE` 為 `ans << len` 。
```c
ans = (i | (ans << len)) % M;
```
## 測驗 `3`
### 程式碼解析
由於後續使用迴圈一次處理 8 bytes (一個位元組),所以一開始先透過 `len >> 3` (也就是 `len / 8`)來計算總共有多少位元組。
```c
const uint64_t *qword = (const uint64_t *) buf;
const uint64_t *end = qword + len >> 3;
```
迴圈即是使用 SWAR 一次檢測一個位元組,也就是原文有解釋過的 `not bit6 and bit7` 操作,並使用 `count` 紀錄有幾個 byte 符合 `10xx.xxxx` 的形式(也就是有多少 `continuation byte`)。
```c
size_t count = 0;
for (; qword != end; qword++) {
const uint64_t t0 = *qword;
const uint64_t t1 = ~t0;
const uint64_t t2 = t1 & 0x04040404040404040llu;
const uint64_t t3 = t2 + t2;
const uint64_t t4 = t0 & t3;
count += __builtin_popcountll(t4);
}
```
得到 `continuation byte` 的數量後,根據原文說明,需要將此數量從總字串長度中減去。
> 若輸入的字串是一個有效的 UTF-8 序列,則計算其中的後續位元組數量,並將此數字從總字串長度中減去,即可確定字元數量。
這邊先計算整除 8 的部分長度為何,透過 `(1 << 3) * (len / 8)` 將最低位 3 bits 設為 0 ,得到長度後將其減去 `count` ,因此 `AAAA` 為 `3`。
```c
count = (1 << 3) * (len / 8) - count;
```
最後計算未滿 8 bytes 的部分中有多少 `continuation byte` ,並將其補上。
```c
count += (len & 7) ? count_utf8((const char *) end, len & 7) : 0;
```
`len & 7` 可得知未滿 8 bytes 的長度為何,將此長度的字元使用 `count_utf8` 逐字元檢查是否為 `continuation byte` 並加回 `count`,因此 `BBBB` 和 `CCCC` 皆為 `7` ,若剩餘的長度為 0 ,代表不須多做檢測,將 `count` 加上 0 即可,因此 `DDDD` 為 0 。
## 測驗 `4`
### 程式碼解析
原始程式碼中,每次會將 `x` 向左 shift 一位元,再與 `0x8000` 做 `&` ,用意是檢測當前的 MSB 是否為 1 ,若非 1 則不符合樣式,再對照下方提供的範例樣式,可得知要尋找的樣式為從 MSB 開始有連續位元為 1 的數。
```c
for (; x > 0; x <<= 1) {
if (!(x & 0x8000)) //0x8000 => 1000 0000 0000 0000
return false;
}
```
```
pattern: 8000 (32768) => 1000 0000 0000 0000
pattern: c000 (49152) => 1100 0000 0000 0000
pattern: e000 (57344) => 1110 0000 0000 0000
pattern: f000 (61440) => 1111 0000 0000 0000
pattern: f800 (63488) => 1111 1000 0000 0000
pattern: fc00 (64512) => 1111 1100 0000 0000
```
後續的改進中,首先可以先觀察將 `x` 取 `-x` 也就是二補數後的結果,我們使用上面的範例來觀察。
```
c000: 1100 0000 0000 0000
-c000: 0100 0000 0000 0000
==========================
f800: 1111 1000 0000 0000
-f800: 0000 1000 0000 0000
```
可發現取 `-x` 後只會保留最右方的 `1` ,這時與原數做 `XOR` 操作,會將原數值中最右的的 `1` 消除,因此符合樣式的值在 `XOR` 後應會比原數值小。
```
1100 0000 0000 0000
^ 0100 0000 0000 0000
-------------------------
1000 0000 0000 0000
```
```
1111 1000 0000 0000
^ 0000 1000 0000 0000
-------------------------
1111 0000 0000 0000
```
因此可推測答案 `EEEE` 為 `-x` , `FFFF` 為 `x` 。
```c
const uint16_t n = -x;
return (n ^ x) < x;
```
我們可再取一不符合樣式的值做檢測,會發現不符合樣式的值在 `XOR` 後會比原數大,因此會返回 `false` 符合上述推測。
```
d000: 1101 0000 0000 0000
-d000: 0010 0000 0000 0001
```
```
1101 0000 0000 0000
^ 0010 0000 0000 0001
-------------------------
1111 0000 0000 0001
```