---
# System prepended metadata

title: 2023q1 Homework2 (quiz2)

---

# 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`
:::