# [2019q3](http://wiki.csie.ncku.edu.tw/sysprog/schedule) 第 7 週測驗題
:::info
目的: 檢驗學員對 bitwise 與 floating point 內部表示法的認知
:::
---
### 測驗 `1`
考慮貌似簡單卻蘊含實作深度的 [FizzBuzz](https://en.wikipedia.org/wiki/Fizz_buzz) 題目:
* 從 1 數到 n,如果是 3的倍數,印出 "Fizz"
* 如果是 5 的倍數,印出 "Buzz"
* 如果是 15 的倍數,印出 "FizzBuzz"
* 如果都不是,就印出數字本身
直覺的實作程式碼如下: (`naive.c`)
```cpp
#include <stdio.h>
int main() {
for (unsigned int i = 1; i < 100; i++) {
if (i % 3 == 0) printf("Fizz");
if (i % 5 == 0) printf("Buzz");
if (i % 15 == 0) printf("FizzBuzz");
if ((i % 3) && (i % 5)) printf("%u", i);
printf("\n");
}
return 0;
}
```
觀察 `printf` 的(格式)字串,可分類為以下三種:
1. 整數格式字串 `"%d"` : 長度為 2 B
2. "Fizz" 或 "Buzz" : 長度為 4 B
3. "FizzBuzz" : 長度為 8 B
考慮下方程式碼:
```cpp
#define MSG_LEN 8
char fmt[MSG_LEN + 1];
strncpy(fmt, &"FizzBuzz%u"[start], length);
fmt[length] = '\0';
printf(fmt, i);
printf("\n");
```
我們若能精準從給定輸入 `i` 的規律去控制 `start` 及 `length`,即可符合 FizzBuzz 題意:
```cpp
string literal: "fizzbuzz%u"
offset: 0 4 8
```
以下是利用 bit-wise 和上述技巧實作的 FizzBuzz 程式碼: (`bitwise.c`)
```cpp
#define MSG_LEN 8
static inline bool is_divisible(uint32_t n, uint64_t M) {
return n * M <= M - 1;
}
static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1;
static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1;
int main(int argc, char **argv) {
for (size_t i = 1; i <= 100; i++) {
uint8_t div3 = is_divisible(i, M3);
uint8_t div5 = is_divisible(i, M5);
unsigned int length = (2 << div3) << div5;
char fmt[MSG_LEN + 1];
strncpy(fmt, &"FizzBuzz%u"[(MSG_LEN >> KK1) >> (KK2 << KK3)], length);
fmt[length] = '\0';
printf(fmt, i);
printf("\n");
}
return 0;
}
```
其中 `is_divisible` 函式技巧來自 [Faster remainders when the divisor is a constant: beating compilers and libdivide](https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide/),甚至 gcc-9 還內建了 [FizzBuzz optimization](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82853) (Bug 82853 - Optimize x % 3 == 0 without modulo)。
請補完。
==作答區==
KK1 = ?
* `(a)` 0
* `(b)` 1
* `(c)` 2
* `(d)` div3
* `(e)` div5
KK2 = ?
* `(a)` 0
* `(b)` 1
* `(c)` 2
* `(d)` div3
* `(e)` div5
KK3 = ?
* `(a)` 0
* `(b)` 1
* `(c)` 2
* `(d)` div3
* `(e)` div5
:::success
延伸問題:
1. 解釋上述程式運作原理並評估 `naive.c` 和 `bitwise.c` 效能落差
* 避免 stream I/O 帶來的影響,可將 `printf` 更換為 `sprintf`
2. 分析 [Faster remainders when the divisor is a constant: beating compilers and libdivide](https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide/) 一文的想法 (可參照同一篇網誌下方的評論),並設計另一種 bitmask,如「可被 3 整除則末位設為 1」「可被 5 整除則倒數第二位設定為 1」,然後再改寫 `bitwise.c` 程式碼,試圖運用更少的指令來實作出 branchless;
* 參照 [fastmod: A header file for fast 32-bit division remainders on 64-bit hardware](https://github.com/lemire/fastmod)
3. `printf` 本身就是個直譯器,甚至符合 [Turing complete](https://en.wikipedia.org/wiki/Turing_completeness),這意味著你可以用 `printf` 的字串處理來實作出各式程式,包含實作出另一個 [Turing complete](https://en.wikipedia.org/wiki/Turing_completeness) 的程式語言,例如 Brainf*ck: [printbf -- Brainfuck interpreter in printf](https://github.com/HexHive/printbf)
* 閱讀簡報 [Memory corruption: why we can't have nice things](https://github.com/HexHive/printbf/blob/master/BalCCon-presentation.pdf)
* 對照 [CS:APP 第 3 版](https://hackmd.io/@sysprog/S1vGugaDQ) 一書的第 3 章研讀
* 記錄你的認知並重現實驗
:::
---
### 測驗 `2`
[CS:APP 第 3 版](https://hackmd.io/@sysprog/S1vGugaDQ) 一書的第 2 章家庭作業 `2.97` (Page 97) 題目:
> 依據浮點數位元編碼規則,實作出符合以下原型定義和作用的函式: (`float-i2f.c`)
```cpp
/* compute (float) i */
float_bits float_i2f(int i);
```
> 給定整數 `i`, 此函式計算 (float) i 逐位元的表示法。
> 其中
```cpp
typedef unsigned float_bits;
```
> 並假設 `int` 長度為 32-bit 且在 little endian 機器上執行。
可用的工具函式:
```cpp
/* Assuming i > 0, calculate an integer's bit length */
/* * e.g. 0x3 => 2, 0xFF => 8 */
static inline int bits_length(int i) { return 32 - __builtin_clz(i); }
/* generating bitmask. e.g. 3 => 0x00000007, 16 => 0x0000FFFF */
static inline unsigned bits_mask(int l) { return (unsigned) -1 >> (32 - l); }
```
==本題採問答形式進行==
---
### 測驗 `3`
[CS:APP 第 3 版](https://hackmd.io/@sysprog/S1vGugaDQ) 一書的第 3 章家庭作業 `3.70` (Page 224) 題目:
> 考慮到以下 union 宣告
```cpp
union ele {
struct {
long *p, y;
} e1;
struct {
long x;
union ele *next;
} e2;
};
```
> 編譯器產生以下組合語言程式碼:
```cpp
proc:
movq 8(%rdi), %rdx
movq (%rdx), %rax
movq (%rax), %rax
subq 8(%rdx), %rax
movq %rax, (%rdi)
ret
```
對應的 C 語言程式如下:
```cpp
void proc(union ele *ptr) {
ptr->AAA.x = *(ptr->BBB.CCC->DDD.p) - (ptr->EEE.FFF->GGG.HHH);
}
```
提示: 可用 `gcc -Os -S` 輸出來檢驗
請補完程式碼。
==作答區==
AAA = ?
* `(a)` e1
* `(b)` e2
* `(c)` p
* `(d)` y
* `(e)` x
* `(f)` next
BBB = ?
* `(a)` e1
* `(b)` e2
* `(c)` p
* `(d)` y
* `(e)` x
* `(f)` next
CCC = ?
* `(a)` e1
* `(b)` e2
* `(c)` p
* `(d)` y
* `(e)` x
* `(f)` next
next
DDD = ?
* `(a)` e1
* `(b)` e2
* `(c)` p
* `(d)` y
* `(e)` x
* `(f)` next
EEE = ?
* `(a)` e1
* `(b)` e2
* `(c)` p
* `(d)` y
* `(e)` x
* `(f)` next
FFF = ?
* `(a)` e1
* `(b)` e2
* `(c)` p
* `(d)` y
* `(e)` x
* `(f)` next
GGG = ?
* `(a)` e1
* `(b)` e2
* `(c)` p
* `(d)` y
* `(e)` x
* `(f)` next
HHH = ?
* `(a)` e1
* `(b)` e2
* `(c)` p
* `(d)` y
* `(e)` x
* `(f)` next
:::success
延伸問題:
1. 解釋你反組譯的過程中,是如何答題
2. 在 `gcc -Os -S` 的輸出中,可見 `.cfi_startproc` 和 `.cfi_endproc` 這兩道假指令 ([pseudo instruction](https://nasm.us/doc/nasmdoc3.html)),其作用為何?gcc 程式碼產生器透過這樣的假指令要達到什麼效果?
3. 在 GitHub 的 [組合語言版本 linked list 實作](https://github.com/search?l=assembly&p=2&q=linked+list&type=Repositories) 中挑一個為基礎,改寫為有效的 x86_64 組合語言程式碼,並確保其具備針對 singly-linked 的 list push, pop, print 等功能
:::
---