# 2023q1 Homework2 (quiz2)
contributed by < [cin-cout](https://github.com/cin-cout) >
[題目](https://hackmd.io/@sysprog/linux2023-quiz2)
## 測驗一
### 延伸問題:
1. 解釋上述程式碼原理,並用 __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.
2. 在 Linux 核心原始程式碼找出類似的使用案例並解釋
3. 當上述 clz 內建函式已運用時,編譯器能否產生對應的 x86 指令?
提示: 可執行 cc -O2 -std=c99 -S next_pow2.c 並觀察產生的 next_pow2.s 檔案,確認 bsrq 指令 (表示 “Bit Scan Reverse”)
### Question
考慮 next_pow2 可針對給定無號 64 位元數值 x,找出最接近且大於等於 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;
}
```
要找出最接近且大於等於 2 的冪的值,意思即為其最高位後全部填一後再加一(即進一位)。
以測驗中的例子為例:
```
x = 0010000000000000
x = 0011000000000000
x = 0011110000000000
x = 0011111111000000
x = 0011111111111111
```
最後再將 x + 1 ,使其進一位其找出最接近且大於等於 2 的冪的值。
#### AAAA
因為在 AAAA 之前已經右移過 7 次,加上原本就存在的最高位,可以理解為最高位後的 7 位都已經置換為 1 ,加上原本的最高位,最高 8 位已經都為 1 。
接下來就是持續將低位數值換為 1 ,因為已經確定最高 8 位為 1 ,我們可以直接右移 8 位,使 9~16 位為 1 ,最後再將其跟原本的 x or ,使最高位元(含)後 16 位皆為 1 。
故 AAAA 為 8。
#### BBBB
BBBB 也是同樣道理,在 BBBB 之前已經將最高位元(含)後 32 位都改為 1 了。
接下來就是持續將低位數值換為 1 ,因為已經確定最高 32 位為 1 ,我們可以直接右移 32 位,使 33~64 位為 1 ,最後再將其跟原本的 x or ,使最高位元(含)後 64 位皆為 1 ,若最高位後少於 64 位,則會因為右移後,則是將剩餘位元數全改為1,不會對結果產生影響。
故 BBBB 為 32。
#### CCCC
如同前面前面範例所述,此法的最終結果須 + 1 才是正確答案。
故 CCCC 為 x + 1。
### 補充
因為在第一次位移一位時就可以確保數值最高非0位數的後,兩位(含)為 1 了,所以在下次位移可以直接右移 2,以此類推。
故題目程式可改進如下。
```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;
}
```
### Answer
:::success
AAAAA = `8`
BBBB = `32`
CCCC = `x + 1`
:::
## 測驗二
### 延伸問題:
1. 解釋上述程式碼運作原理
2. 嘗試使用 __builtin_{clz,ctz,ffs} 改寫,並改進 mod $10^9+7$的運算
### Question
給定一整數 n ,回傳將 1 到 n 的二進位表示法依序串接在一起所得到的二進位字串,其所代表的十進位數字 mod $10^9+7$ 之值。
```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;
}
```
#### 程式碼解說
整體的邏輯為 1 到 n 一項一項的確認,若數值 2 進位的位數增加了即更新 len,最後在將其接在原本 ans 的後面。
一開始的 for 迴圈為從 1 到 n 一個數字一個數字處理。
##### DDDD
if 是在判斷數值的 2 進位位數是否增加了,位數增加的時機即為數值為 2 冪次的數值。
若 i & (i - 1) == 0 則代表其為 2 冪次,原因如下:
以 4 為例
i = 100
i-1 = 011
i & (i - 1) == 0
可以想像成因為 2 冪次的數值只會有一個 1 ,所以當你減 1 時,結果即為其 1 後面所以位數皆為 1 ,但原本 1 的位變為 0。與原本的 i 做 & 後自然為 0。
題目中註解也是在描述一樣的道理,只是用不同方式切入, i & (i - 1) 本身可以看做將最右邊的 1 後(含)全部換為 0 的運算。
題目在 DDDD 前有個 !,所以就不需要增加 == 0 的條件了。
故 DDDD = i & (i - 1)。
##### EEEE
EEEE 此行的功用只在於把每個數字的結果 1 個 1 個接起來而已,所以即是把 ans<<len 因為要空出新的數值長度的位置,再用 | 將 i 給放入空出來的位置。
EEEE = ans<<len。
### Answer
:::success
DDDD = `i & (i - 1)`
EEEE = `ans<<len`
:::
## 測驗三
### 延伸問題:
1. 解釋上述程式碼運作原理,比較 SWAR 和原本的實作效能落差
2. 在 Linux 核心原始程式碼找出 UTF-8 和 Unicode 相關字串處理的程式碼,探討其原理,並指出可能的改進空間
### Question
SWAR 實作如下:
```c
size_t swar_count_utf8(const char *buf, size_t len)
{
const uint64_t *qword = (const uint64_t *) buf;
const uint64_t *end = qword + len >> 3;
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);
}
count = (1 << AAAA) * (len / 8) - count;
count += (len & BBBB) ? count_utf8((const char *) end, len & CCCC) : DDDD;
return count;
}
```
請補完程式碼,使其運作符合預期。
#### 程式碼解說
先儲存 buf 中的前 8 bytes ,而 end 則是用來紀錄最後剩下無法被分成 8 bytes 的頭。算法為 qword + 總共有幾個 8 bytes , 將 len >> 3 就是除8的意思。
```c
const uint64_t *qword = (const uint64_t *) buf;
const uint64_t *end = qword + len >> 3;
```
for 迴圈就是一個 8 bytes 一個 8 bytes 的做,直到最後剩下的 bytes ,意及剩下無法被分成 8 bytes 的 bytes。
而 for 迴圈中的內容是老師[上課講義](https://hackmd.io/@sysprog/linux2023-quiz2)中的 not 6 and 7 的運算,處理過後總共有幾個 1 就代表有幾個 continuation bytes。
```c
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);
}
```
最後這段就是配合原本實做 count_utf8 來計算剩下的 bytes 的非 continuation bytes 數量。
```c
count = (1 << AAAA) * (len / 8) - count;
count += (len & BBBB) ? count_utf8((const char *) end, len & CCCC) : DDDD;
```
補:count_utf8 函式用來計算剩下的 bytes 的非 continuation bytes 數量,詳細解釋見[上課講義](https://hackmd.io/@sysprog/linux2023-quiz2)。
```c
#include <stddef.h>
#include <stdint.h>
size_t count_utf8(const char *buf, size_t len)
{
const int8_t *p = (const int8_t *) buf;
size_t counter = 0;
for (size_t i = 0; i < len; i++) {
/* -65 is 0b10111111, anything larger in two-complement's should start
* new code point.
*/
if (p[i] > -65)
counter++;
}
return counter;
}
```
##### AAAA
因原本的結果為紀錄 continuation bytes 數量,我們要求的是非 continuation bytes 的數量,所以要先將 count 轉換,轉換方法為 `已經計算過的 bytes - continuation bytes 的數量 = 非 continuation bytes 的數量。`
已經計算過的 bytes = 8 * (len / 8)
因為 c 的 len/8 會取 floor ,所以即代表有幾個 8 bytes,最後 x8 就可以知道有幾個 bytes。
```c
count = (1 << AAAA) * (len / 8) - count;
```
故 AAAA 為 3。
##### BBBB CCCC DDDD
最後則是將剩下無法分成 8 bytes 的 bytes 做處理,若(len & 0b111)為則代表 len 為 0 ,直接將 count +0 即可。
故 BBBB 為 7 、 DDDD 為 0。
若 len 不為 0,則用原本實做的函式計算剩餘的非 continuation bytes 數量, count_utf8 第二個參數為 bytes 數量,故為 len % 8,意及 len & 0b111。
故 CCCC 為 0。
```c
count += (len & BBBB) ? count_utf8((const char *) end, len & CCCC) : DDDD;
```
### Answer
:::success
AAAA = `3`
BBBB = `7`
CCCC = `7`
DDDD = `0`
:::
## 測驗四
### 延伸問題:
1. 解釋上述程式碼運作原理
1. 在 Linux 核心原始程式碼找出上述 bitmask 及產生器,探討應用範疇參見 Data Structures in the Linux Kernel
### Question
以下程式碼可判定 16 位元無號整數是否符合特定樣式 (pattern):
```c
#include <stdint.h>
#include <stdbool.h>
bool is_pattern(uint16_t x)
{
if (!x)
return 0;
for (; x > 0; x <<= 1) {
if (!(x & 0x8000))
return false;
}
return true;
}
```
改寫上述程式碼,使其達到等價行為,但更精簡有效。
```c
bool is_pattern(uint16_t x)
{
const uint16_t n = EEEE;
return (n ^ x) < FFFF;
}
```
#### 程式碼解說
對於原本的程式碼,符合的結果如下:
```
1000000000000000
1100000000000000
1110000000000000
.
.
.
1111111111111111
```
##### EEEE
EEEE 結果為 ~x + 1 原因如下:
~x 會將 0 轉換為 1,1 轉換為 0。
在符合的狀況中,會有以下的結果:
~x 必定為 $0^+1^+$ 的狀態。
~x + 1 會使 1 的部分進位。
~x + 1 必定只會有一個 1 且為原本 x 的 LSB。
在 false 的狀況中,~x + 1 的 LSB 一樣會是 1 , LSB 之上的 bit 即為原本的 ~x。
##### FFFF
FFFF 結果為 x 原因如下:
(~x + 1 ^ x) 在 true 的狀況中即為 x 將 LSB set 為 0,所以一定會比原本的 x 小,故結果為 true。
而反之在 false 的情況中 (x 的 LSB 之上一定會有 0 ), (~x + 1 ^ x) 的結果同樣會將 x 的 LSB set 為 0,但是因為 ~x + 1 與 x 在 LSB 之上的 bits 會完全相反,所以會全部被 set 為 1 ,所以一定會比原 x 大,故結果為 false 。
### Answer
:::success
EEEE = `~x + 1`
FFFF = `x`
:::