# 2018q3 Homework4 (assessment)
<contributed by < [`siahuat0727`](https://github.com/siahuat0727) >
[作業 E05: assessment](https://hackmd.io/s/HJ236XI57)
### [第 4 週測驗題 (上)](https://hackmd.io/s/HyyxpJE5X)
考慮以下求絕對值的程式碼:
```C
#include <stdint.h>
int64_t abs64(int x) {
if (x < 0) return -x;
return x;
}
```
Q:移除分支並善用[二補數](https://en.wikipedia.org/wiki/Two%27s_complement)特性,改寫為下方程式碼:
```C
#include <stdint.h>
int64_t abs64(int64_t x) {
int64_t y = x A1 (A2 - 1);
return (x A3 y) - y;
}
```
A:
```C
#include <stdint.h>
int64_t abs64(int64_t x) {
int64_t y = x >> (64 - 1);
return (x ^ y) - y;
}
```
這裡假設了 C 語言的 `>>` 是 [arithmetic right shift](https://en.wikipedia.org/wiki/Arithmetic_shift)。(C 語言標準沒有明確定義對於有符號數應該使用哪種右移,然而實際上幾乎所有編輯器都對其使用 arithmetic right shift)
#### 分析
+ 若 `x` 爲非負,則 `y` 爲 `0`,
`(x ^ y) - y`
= `(x ^ 0) - 0`
= `(x) - 0`
= `x`
+ 若 `x` 爲負,則 `y` 在二進制表示時全爲 `1`,或者十進制的 `-1`,
`(x ^ y) - y`
= `(~x) - (-1)`
= `~x + 1`
= `x` 的一補數 + `1`
= `x` 的二補數
= `-x`
#### overflow/underflow 議題
當 `x` 爲最小值 $-2^{-63}$ 時,`x` 沒有對應的正值表示,無論用 `-x` 或是 `x` 的二補數的方法都會得到 `x` 自己。
#### 撰寫 `abs64` 的測試程式
我想測試應該是針對例外(`x` 爲最小值)以外的情況。
要測試 $2^{64}$ 種情況太久,如 CSAPP 練習題 2.35,我們可以測試 `abs8` 來取代測試 `abs64` 。
```C
#include <stdio.h>
#include <stdint.h>
int8_t abs8_true(int x) {
if (x < 0) return -x;
return x;
}
int8_t abs8(int8_t x) {
int8_t y = x >> (8 - 1);
return (x ^ y) - y;
}
int main()
{
for (int i = -0x80; i <= 0x7F; ++i) {
if (abs8_true(i) != abs8(i)) {
fprintf(stderr, "wrong when calling abs8(%d)\n", i);
}
}
return 0;
}
```
:::info
不太懂 PRNG 是不是要以隨機取樣的方式來測試,如果真的有例外情況感覺被測到的機率很小?
:::
### [第 4 週測驗題 (中)](https://hackmd.io/s/Syl6me49Q)
`void *__builtin_return_address(unsigned int level)`
+ 若 level 爲 0,取得這個 function 的 return address
+ 若 level 爲 1,取得上一層 function 的 return address
+ 以此類推...
這裡以比較 caller(first_xxx)和 callee(second_xxx)的 return address,來判斷是否發生 TCO。(若兩者相同則表示呼叫 function 時沒有創建新的 stack frame => 發生 TCO)
---
1. :heavy_check_mark: TCO
```C
void second_zero (void) {
second_ra = __builtin_return_address(0);
}
void first_zero (void) {
first_ra = __builtin_return_address(0);
second_zero();
}
```
TCO 之前:(optimization flag `-O`)
```
<first_zero>:
sub $0x8,%rsp
mov 0x8(%rsp),%rax
mov %rax,0x20083d(%rip) # 201018 <first_ra>
callq 88e <second_zero>
add $0x8,%rsp
retq
```
可以看到 function call 之後就是 pop stack frame(`add $0x8, %rsp`) 然後 return (`retq`),因此若重用 stack frame(只存了 return address),結果如下:(optimization flag `-O2`)
```
<first_zero>:
mov (%rsp),%rax
mov %rax,0x2007ad(%rip) # 201018 <first_ra>
jmpq 910 <second_zero>
```
---
2. :heavy_check_mark: TCO
```C
void second_one (int a) {
second_ra = __builtin_return_address(0);
}
void first_one (int a) {
first_ra = __builtin_return_address(0);
second_one(0);
}
```
```
<first_one>:
mov (%rsp),%rax
xor %edi,%edi
mov %rax,0x20079b(%rip) # 201018 <first_ra>
jmpq 920 <second_one>
```
---
3. :heavy_check_mark: TCO
```C
void second_zero_one (int a) {
second_ra = __builtin_return_address(0);
}
void first_zero_one (void) {
first_ra = __builtin_return_address(0);
second_zero_one(0);
}
```
```
<first_zero_one>:
mov (%rsp),%rax
xor %edi,%edi
mov %rax,0x20077b(%rip) # 201018 <first_ra>
jmpq 930 <second_zero_one>
```
---
4. :heavy_check_mark: TCO
```C
void second_one_zero (void) {
second_ra = __builtin_return_address(0);
}
void first_one_zero (int a) {
first_ra = __builtin_return_address(0);
second_one_zero ();
}
```
```
<first_one_zero>:
mov (%rsp),%rax
mov %rax,0x20075d(%rip) # 201018 <first_ra>
jmpq 940 <second_one_zero>
```
---
5. :x: TCO
```C
char second_ret_int_char (void) {
second_ra = __builtin_return_address(0);
return 0;
}
int first_ret_int_char (void) {
first_ra = __builtin_return_address(0);
return second_ret_int_char ();
}
```
```
<first_ret_int_char>:
sub $0x8,%rsp
mov 0x8(%rsp),%rax
mov %rax,0x200748(%rip) # 201018 <first_ra>
callq 950 <second_ret_int_char>
add $0x8,%rsp
movsbl %al,%eax
retq
```
這裡因爲 return type 的不同,導致 function call 之後不只是 pop stack frame 和 return,還有轉型 `movsbl %al,%eax` (根據 signed bit 設置 `%eax` 的高 24 位),所以沒辦法 TCO。
這是因爲若直接 `jmpq <second_ret_int_char>`,`second_ret_int_char` 只會設定 `%eax` 的低 8 位 `%al`,但 `first_ret_int_char` 的 caller 可以使用整個 `%eax`。
---
6. :x: TCO :question:
```C
int second_ret_char_int (void) {
second_ra = __builtin_return_address(0);
return 0;
}
char first_ret_char_int (void) {
first_ra = __builtin_return_address(0);
return second_ret_char_int ();
}
```
```
<first_ret_char_int>:
sub $0x8,%rsp
mov 0x8(%rsp),%rax
mov %rax,0x200728(%rip) # 201018 <first_ra>
callq 960 <second_ret_char_int>
add $0x8,%rsp
retq
```
:::info
這裡是 int 轉 char,不需要額外的指令轉型(直接讀 `%eax` 的低 8 位 `%al`),所以我想其實是可以進行 TCO 的?
:::
---
7. :heavy_check_mark: TCO
```C
int second_ret_void_int (void) {
second_ra = __builtin_return_address(0);
return 0;
}
void first_ret_void_int (void) {
first_ra = __builtin_return_address(0);
second_ret_void_int ();
}
```
```
<first_ret_void_int>:
mov (%rsp),%rax
mov %rax,0x20070d(%rip) # 201018 <first_ra>
jmpq 970 <second_ret_void_int>
```
`first_ret_void_int` return type 爲 `void`,所以可以 TCO。(無視 `second_ret_void_int` 的 return value)
---
```
int *global_var;
void f(void)
{
int x = 3;
global_var = &x;
...
/* Can the compiler perform TCO here? */
g();
}
```
[Tail Calls and C](https://david.wragg.org/blog/2014/02/c-tail-calls-1.html)