---
# System prepended metadata

title: 2023q1 Homework2 (quiz2)

---

# 2023q1 Homework2 (quiz2)
contributed by < `joshyue` >
## 測驗 `1`
>參照 [你所不知道的 C 語言: bitwise 操作](https://hackmd.io/@sysprog/c-bitwise)，考慮 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 >> 8;
    x |= x >> 16;
    x |= x >> 32;
    return x + 1;
}
```

* 64位元之數值`x`，在此以`x = 0b0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`為例。
* 經過7次`x |= x >> 1`運算，可保證原先最高位的1後面會有6個1，即`x = 0b0111 1111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`。
* 再經過`x |= x >> 8`、`x |= x >> 32`、`x |= x >> 64`，則`x = 0b0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111`，如此便能使一開始最高位的1右邊所有位元皆指派為1。
* 此時`x + 1`才為2的冪，因此最後要`return x + 1`，即能得到`next_pow2`所求。

### 用 __builtin_clzl 改寫
>[__builtin_clzl](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) : Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined. 
```c
uint64_t next_pow2(uint64_t x)
{
    if (！x)
        return 1;
    int clz = __builtin_clzl(x);
    return (1 << (64 - clz));
}
```
* 當`x`不為`0`時，`__builtin_clzl(x)`會回傳最高位開始連續0的個數的功能，假設`x=1024`時，則`clz = 53`。
* 則可以透過求得的`clz`，回傳`(1 << (64 - clz))`，即得題目所求。
### 在 Linux 核心原始程式碼找出類似的使用案例並解釋
* 在<[include/asm-generic/bitops/builtin-fls.h](https://elixir.bootlin.com/linux/latest/source/include/asm-generic/bitops/builtin-fls.h)>中的`fls`函式，有使用到`__builtin_clz`的功能。
```c
static __always_inline int fls(unsigned int x)
{
    return x ? sizeof(x) * 8 - __builtin_clz(x) : 0;
}
```
>fls - find last (most-significant) bit set
>
透過`sizeof(x) * 8`可得到`x`的位元數量，再減去最高位開始連續0的個數，則可以得到`fls`函式所求，即最高位元的1的bit index + 1。
### 上述 clz 內建函式已運用時，編譯器產生之對應x86指令 
* 執行`cc -O2 -std=c99 -S next_pow2.c`，觀察所得`next_pow2.s`
```c
	movl	$1, %eax
	testq	%rdi, %rdi
	je	.L1
	bsrq	%rdi, %rdi
	movl	$64, %ecx
	xorq	$63, %rdi
	subl	%edi, %ecx
	sall	%cl, %eax
	cltq
```

* 觀察`__builtin_clzl(x)`所對應的x86指令，其中 `bsrq	%rdi, %rdi` 會將最高位的1之bit index儲存於rdi暫存器。
* 而後的 `xorq	$63, %rdi` 則是對`63`與最高位的1之bit index作`Exclusive OR`，則等同於`63 - 1之bit index`，因此得到所求，即最高位開始連續0的個數。
---
## 測驗 `2`
>LeetCode [1680. Concatenation of Consecutive Binary Numbers](https://leetcode.com/problems/concatenation-of-consecutive-binary-numbers/) 給定一整數 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 (!(i & (i - 1)))
            len++;
        ans = (i | (ans << len)) % M;
    }
    return ans;
}
```
* 若要判斷`i`是否為2的冪，現假設`i = 8`，則二進位表示`0b1000`，`i - 1`的二進位表示則為`0b0111`，可觀察到兩者依序對應的bit皆相異，因此當`i`為2的冪時，`i & (i - 1)`必等於0。
* 並且題目所述1 到 n 的二進位表示法依序串接，於是我們可以將目前的字串（`ans`）向左shift所需串接的i之位元長度（`len`）後，再與i作`bitwise OR`，最後再mod $10^9+7$，即為題目所求。
### 使用__builtin_{clz,ctz,ffs}改寫，改進mod $10^9+7$ 的運算

```c
int concatenatedBinary(int n)
{
    const int M = 1e9 + 7;
    int len = 0; /* the bit length to be shifted */
    long ans = 0;
    for (int i = 1; i <= n; i++) {
        int ffs = __builtin_ffsl(i);
        if (!(i >> (ffs)))
            len++;
        ans = (i | (ans << len)) % M; 
    }
}
```
>[__builtin_ffsl](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) : Returns one plus the index of the least significant 1-bit of x, or if x is zero, returns zero. 
* 原先使用以上的程式碼，以`__builtin_ffsl`來取得最低位開始第一個1的bit index + 1，`i >> (ffs)`若為0，則表示`i`為2的冪，而後發現此程式碼仍可改進，改進如下。
```c
int concatenatedBinary(int n)
{
    const int M = 1e9 + 7;
    int len = 0; /* the bit length to be shifted */
    long ans = 0;
    for (int i = 1; i <= n; i++){
        ans = (i | (ans << (64 - __builtin_clzl(i)))) % M;
    }
}
```
* 以`__builtin_clzl`得到最高位開始連續0個數後，將i往左shift `64 - 
__builtin_clzl(i)`(其等同先前的程式碼向左shift `len` )，這樣的改進能省去`if`的分支，不用判斷`i`是否為2的冪，使程式碼更加精簡。

---
## 測驗 `3`
>[SIMD within a register](https://en.wikipedia.org/wiki/SWAR) (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 << 3) * (len / 8)  - count;
    count += (len & 7) ? count_utf8((const char *) end, len & 7) : 0;

    return count;
}
```
### 比較 SWAR 和原本的實作效能落差
> [__builtin_popcountll](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) : Returns the number of 1-bits in x.

* 第13行 : 透過`__builtin_popcountll`求得位元組內1的總數，`conut`即代表題目所述continuation bytes（後續位元組）的個數。
* 第16行 : 因為我們在處理一個qword時，qword大小為8byte，`(len / 8)`即表示完整處理的次數(因後續要判斷剩餘未滿1個qword的狀況)，`(1 << 3) * (len / 8)`表示完整處理的次數再乘以8，即已處理字串的長度，並且題目有提到字串長度減去後續位元組，即可確定字元數量，因此我們要再減去第13行得到的`count`，得到字元數量。
* 第17行 : `(len & 7)`等同於`len % 7`，若`len & 7`不為0，表示我們仍有未處理的字元，則呼叫原先的函式`count_utf8`，回傳剩餘的字元數量。

### 在 Linux 核心原始程式碼找出 UTF-8 和 Unicode相關字串處理的程式碼，探討其原理
* 在<[fs/unicode/utf8-core.c](https://elixir.bootlin.com/linux/latest/source/fs/unicode/utf8-core.c#L20)>中，有提供`utf8_strncmp`函式。
*  如果返回值 = 1，則表明兩字串不相等
   如果返回值 = 0，則表明兩字串相等
```c
int utf8_strncmp(const struct unicode_map *um,
		 const struct qstr *s1, const struct qstr *s2)
{
	struct utf8cursor cur1, cur2;
	int c1, c2;

	if (utf8ncursor(&cur1, um, UTF8_NFDI, s1->name, s1->len) < 0)
		return -EINVAL;

	if (utf8ncursor(&cur2, um, UTF8_NFDI, s2->name, s2->len) < 0)
		return -EINVAL;

	do {
		c1 = utf8byte(&cur1);
		c2 = utf8byte(&cur2);

		if (c1 < 0 || c2 < 0)
			return -EINVAL;
		if (c1 != c2)
			return 1;
	} while (c1);

	return 0;
```
* `utf8ncursor`結構表示字串處理的進度，分別透過`c1`及`c2`獲取目前處理字串位置的一個位元組，並轉換此位元組的型態為`unsigned char`，因此若`c1`或`c2`小於0，則表示程式無效，回傳`-EINVAL`，反之的話就是繼續判斷兩個字元是否相等。
---
## 測驗 `4`
>以下程式碼可判定 16 位元無號整數是否符合以下特定樣式 (pattern):

```c
bool is_pattern(uint16_t x)
{
    if (!x)
        return 0;

    for (; x > 0; x <<= 1) {
        if (!(x & 0x8000))
            return false;
    }

    return true;
}
```
* 符合上述程式碼的樣式，如下:
```
pattern: 8000 (32768)
pattern: c000 (49152)
pattern: e000 (57344)
pattern: f000 (61440)
pattern: f800 (63488)
pattern: fc00 (64512)
pattern: fe00 (65024)
pattern: ff00 (65280)
pattern: ff80 (65408)
pattern: ffc0 (65472)
pattern: ffe0 (65504)
pattern: fff0 (65520)
pattern: fff8 (65528)
pattern: fffc (65532)
pattern: fffe (65534)
pattern: ffff (65535)
```
* 改寫上述程式碼，使其等價且更精簡有效，改寫內容如下:
```c
bool is_pattern(uint16_t x)
{
    const uint16_t n = -x;
    return (n ^ x) < x;
}
```
* 透過`x`觀察是否符合程式碼的樣式(pattern)可發現，只要`x`在二進位下符合最高位開始為1的條件，後面依序接著連續的1(可為0個)與連續的0(可為0個)，則符合上述的樣式。
* 上述程式碼的`-x`表示`x`的二補數，以下方圖表為例，若x符合樣式，則`(n ^ x)`必定小於`x`，因此回傳`True`。


| x | n = -x | n ^ x |
| -------- | -------- | -------- |
| (1111 0000 0000 0000)~2~     | (0001 0000 0000 0000)~2~     | (1110 0000 0000 0000)~2~     |
| (1111 0100 0000 0000)~2~     | (0000 1100 0000 0000)~2~     | (1111 1000 0000 0000)~2~     |
| (1111 0000 0000 0001)~2~     | (0000 1111 1111 1111)~2~     | (1111 1111 1111 1110)~2~     |



### 在 Linux 核心原始程式碼找出上述 bitmask 及產生器，探討應用範疇 
