diff --git a/.github/workflows/deployment.yaml b/.github/workflows/deployment.yaml new file mode 100644 index 0000000..cd86532 --- /dev/null +++ b/.github/workflows/deployment.yaml @@ -0,0 +1,52 @@ +name: Update submodule in mikiken/mikiken.net + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + update_submodules: + name: Update submodules + runs-on: ubuntu-20.04 + defaults: + run: + working-directory: /home/runner/work/compiler_log/mikiken.net + + steps: + - name: Clone repository + run: | + git clone https://github.com/mikiken/mikiken.net.git + cd mikiken.net + git init + pwd + ls -la + working-directory: /home/runner/work/compiler_log/ + + - name: Configure git + run: | + pwd + git config --local user.name "mikiken" + git config --local user.email "mikiken.dev@gmail.com" + + - name: Change revision of submodule + run: | + pwd + cd compiler_log + pwd + git fetch + git reset --hard origin/main + git status + + - name: Add and commit files + run: | + pwd + git status + git add . + git commit -m "Update submodule via github-actions" + + - name: Push changes + run : git push origin main \ No newline at end of file diff --git a/index.html b/index.html index e72fc34..c2a25a4 100644 --- a/index.html +++ b/index.html @@ -1,11 +1,943 @@ -メモ帳 - 作業ログ
Guild icon
メモ帳
seccamp2022 / 作業ログ
-
+ + -
Avatar
関数定義 (edited)
12:01
typedef struct Function Function; + + メモ帳 - 作業ログ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Guild icon
+
+
メモ帳
+
seccamp2022 / 作業ログ
+
+
+
+ +
+
+
+
Avatar
+
+ +
関数定義 (edited)
+
+
+
+
+
+
+
12:01
+
+
+
typedef struct Function Function; struct Function { Function *next; @@ -14,51 +946,201 @@ Lvar params_head; // 引数リストの先頭 Node *body; // statement Lvar *locals; // ローカル変数のリスト -}; (edited)
-
Avatar
1.整数を返すだけの関数が定義できるように
-
Avatar
offset計算するところでセグフォしてる
-
Avatar
struct Expr typecheck_expression(struct AnalyzerState *ptr_ps, - const struct UntypedExpr *ref_uexpr)
-
Avatar
hsjoihs「なので、それに一つずつ型情報を付与していく必要があります」 -hsjoihs「やり方は二通りあって、一つは、いまある Node を書き換えて、型情報を書き込んでいく方法」 -hsjoihs「もう一つは、今ある AST を見て、再帰的に『その AST の、型がついたバージョン』を作っていく方法」
- -
Avatar
問題点 -そもそも関数名とか型の情報とか渡ってなくね? -引数としてlistを受け取ってるけど結局使ってなくね?
-
Avatar
TODO(対応済み) -変数のサイズによってレジスタの名前が変わるので、それに対応できるようにcodegen.c を書き換える (edited)
- -
Avatar
Step 21-2 配列からポインタへの暗黙の型変換を実装する -・右辺に配列型変数が現れた場合 a&a のようにキャストする -・明示的に&a と書いたときは、上記のキャストを行わないようにする -・*a = 1; みたいな式が書けるように(次のステップでa[i]*(a+i) に読み替えるための準備) -・sizeof(a)(1つ分のサイズ) * (要素数) を返す (edited)
-
Avatar
新たに処理を書き足す必要がある(と思われる)演算子 -・+ - (済) -・== != < <= > >= → そもそもポインタに対しても対応していないので後回し -・= (済) -・* (dereference) オペランドとして直接配列が来た場合に対応する必要あり (済) -・sizeof (済) -※& について 明示的に& を書いているからキャストする必要ないはず) (edited)
-
Avatar
ポインタの加算・減算のうち -・lhs が数字/rhs がポインタのものは実装していないはず
-
Avatar
代入式の左辺(lhs )に直接配列が来ることはありえない
18:52
#include <stdio.h> +}; (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
1.整数を返すだけの関数が定義できるように
+
+
+
+
+
+
+
+
Avatar
+
+ +
offset計算するところでセグフォしてる
+
+
+
+
+
+
+
+
Avatar
+
+ +
struct Expr typecheck_expression(struct AnalyzerState *ptr_ps, + const struct UntypedExpr *ref_uexpr)
+
+
+
+
+
+
+
+
Avatar
+
+ +
hsjoihs「なので、それに一つずつ型情報を付与していく必要があります」 + hsjoihs「やり方は二通りあって、一つは、いまある Node を書き換えて、型情報を書き込んでいく方法」 + hsjoihs「もう一つは、今ある AST を見て、再帰的に『その AST の、型がついたバージョン』を作っていく方法」
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+
+
+
Avatar
+
+ +
問題点 + そもそも関数名とか型の情報とか渡ってなくね? + 引数としてlistを受け取ってるけど結局使ってなくね?
+
+
+
+
+
+
+
+
Avatar
+
+ +
TODO(対応済み) + 変数のサイズによってレジスタの名前が変わるので、それに対応できるようにcodegen.c を書き換える (edited)
+
+
+
+
+ +
+
+
+
Avatar
+
+ +
Step 21-2 配列からポインタへの暗黙の型変換を実装する + ・右辺に配列型変数が現れた場合 a&a のようにキャストする + ・明示的に&a と書いたときは、上記のキャストを行わないようにする + ・*a = 1; みたいな式が書けるように(次のステップでa[i]*(a+i) に読み替えるための準備) + ・sizeof(a)(1つ分のサイズ) * (要素数) を返す (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
新たに処理を書き足す必要がある(と思われる)演算子 + ・+ - (済) + ・== != < <= > >= → そもそもポインタに対しても対応していないので後回し + ・= (済) + ・* (dereference) オペランドとして直接配列が来た場合に対応する必要あり (済) + ・sizeof (済) + ※& について 明示的に& を書いているからキャストする必要ないはず) (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
ポインタの加算・減算のうち + ・lhs が数字/rhs がポインタのものは実装していないはず
+
+
+
+
+
+
+
+
Avatar
+
+ +
代入式の左辺(lhs )に直接配列が来ることはありえない
+
+
+
+
+
+
+
18:52
+
+
+
#include <stdio.h> int main(char *argv[], int argc) { int a[2]; a = 80; printf("%d", a[0]); return 0; } -./Main.c: In function ‘main’: + ./Main.c: In function ‘main’: ./Main.c:4:5: error: assignment to expression with array type 4 | a = 80; - | ^ (edited)
-
Avatar
TODO -・int を4byteに -・(任意の型)* を8byteに -・関数呼び出しの際、rspを16byte境界にalignする
-
Avatar
void alloc4(int **p, int a, int b, int c, int d) { + | ^ (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
TODO + ・int を4byteに + ・(任意の型)* を8byteに + ・関数呼び出しの際、rspを16byte境界にalignする
+
+
+
+
+
+
+
+
Avatar
+
+ +
void alloc4(int **p, int a, int b, int c, int d) { *p = malloc(sizeof(int) * 5); (*p)[0] = a; (*p)[1] = b; @@ -66,25 +1148,180 @@ (*p)[3] = d; (*p)[4] = 100; } -int alloc4(); int main(){int *p; alloc4(&p, 1, 2, 4, 8); return *(p+2);} => 100 -これはgccがint を4byteとして扱っている一方、9ccでは8byteとして扱っているため (edited)
-
Avatar
int main() {int a; a = 4; int b[2]; int *p; p = b; *p = 1; *(p+1) = 2; return a + *p + *(p+1);} => 7 expected, but got 5 -となるが、 -int main() {int a; a = 4; int b[2]; int *p; p = b; *p = 1; *(p-1) = 2; return a + *p + *(p-1);} => 7 -とすると上手くいく
-
Avatar
PUSH copies the specified register, memory location, or immediate value to the top of stack. This instruction decrements the stack pointer by 2, 4, or 8, depending on the operand size, and then copies the operand into the memory location pointed to by SS:rSP.
(edited)
08:34
POP copies a word, doubleword, or quadword from the memory location pointed to by the SS:rSP registers (the top of stack) to a specified register or memory location. Then, the rSP register is incremented by 2, 4, or 8. After the POP operation, rSP points to the new top of stack.
-
Avatar
For addition, either both operands shall have arithmetic type, or one operand shall be a pointer to a complete object type and the other shall have integer type. (Incrementing is equivalent to adding 1.)
21:12
For subtraction, one of the following shall hold: -・both operands have arithmetic type; -・both operands are pointers to qualified or unqualified versions of compatible complete object types; or -・the left operand is a pointer to a complete object type and the right operand has integer type. -(Decrementing is equivalent to subtracting 1.) -
ってなってるけど、スタック操作(popとかpush)を行うときは8byteレジスタしか引数に取れない?(あまりよく分かってない)
(edited)
-
Avatar
もしかしたらローカル変数のメモリ上での配置の仕方がgccとかと違うかも(おそらく逆順) (edited)
17:00
そもそもこういうテストケース -int main() {int a; int b; a = 2; b = 8; int *p; int *q; p = &a; q = &b; return q - p;} -に対して1が帰ってくる保証はないのでは? -(int aint b が連続して確保される保証はないため) (edited)
-
Avatar
2つのポインタを減算する場合において、その両方のポインタが同じ配列オブジェクトの要素か、その配列オブジェクトの最後の要素を一つ越えたところを指していない場合。(6.5.6)
-
Avatar
Step 25 文字列リテラルを実装する
17:40
tokenizerはこういうコードを追加すればいいはず
17:41
    // 文字列リテラルの場合 + int alloc4(); int main(){int *p; alloc4(&p, 1, 2, 4, 8); return *(p+2);} => 100 + これはgccがint を4byteとして扱っている一方、9ccでは8byteとして扱っているため (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
int main() {int a; a = 4; int b[2]; int *p; p = b; *p = 1; *(p+1) = 2; return a + *p + *(p+1);} => 7 expected, but got 5 + となるが、 + int main() {int a; a = 4; int b[2]; int *p; p = b; *p = 1; *(p-1) = 2; return a + *p + *(p-1);} => 7 + とすると上手くいく
+
+
+
+
+
+
+
+
Avatar
+
+ +
+
+
+
PUSH copies the specified register, memory location, or immediate value to the top of stack. This instruction decrements the stack pointer by 2, 4, or 8, depending on the operand size, and then copies the operand into the memory location pointed to by SS:rSP.
+
+
(edited)
+
+
+
+
+
+
+
08:34
+
+
+
+
+
+
POP copies a word, doubleword, or quadword from the memory location pointed to by the SS:rSP registers (the top of stack) to a specified register or memory location. Then, the rSP register is incremented by 2, 4, or 8. After the POP operation, rSP points to the new top of stack.
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
+
+
+
For addition, either both operands shall have arithmetic type, or one operand shall be a pointer to a complete object type and the other shall have integer type. (Incrementing is equivalent to adding 1.)
+
+
+
+
+
+
+
+
+
21:12
+
+
+
+
+
+
For subtraction, one of the following shall hold: + ・both operands have arithmetic type; + ・both operands are pointers to qualified or unqualified versions of compatible complete object types; or + ・the left operand is a pointer to a complete object type and the right operand has integer type. + (Decrementing is equivalent to subtracting 1.) +
+
ってなってるけど、スタック操作(popとかpush)を行うときは8byteレジスタしか引数に取れない?(あまりよく分かってない) +
(edited)
+
+
+
+ +
+
+
+
+
Avatar
+
+ +
もしかしたらローカル変数のメモリ上での配置の仕方がgccとかと違うかも(おそらく逆順) (edited)
+
+
+
+
+
+
+
17:00
+
+
+
そもそもこういうテストケース + int main() {int a; int b; a = 2; b = 8; int *p; int *q; p = &a; q = &b; return q - p;} + に対して1が帰ってくる保証はないのでは? + (int aint b が連続して確保される保証はないため) (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
+
+
+
2つのポインタを減算する場合において、その両方のポインタが同じ配列オブジェクトの要素か、その配列オブジェクトの最後の要素を一つ越えたところを指していない場合。(6.5.6)
+
+
+
+
+
+ +
+
+
+
+
Avatar
+
+ +
Step 25 文字列リテラルを実装する
+
+
+
+
+
+
+
17:40
+
+
+
tokenizerはこういうコードを追加すればいいはず
+
+
+
+
+
+
+
17:41
+
+
+
    // 文字列リテラルの場合     if (startswith(p, "\"")) {       char *start = ++p; // ダブルクオートを読み飛ばす       while (*p != '\"') @@ -92,9 +1329,29 @@       cur = new_token(TK_STR, cur, start, p - 1);       p++; // ダブルクオートを読み飛ばす       continue; -     }
17:44
launch.jsonargsにテストケースを渡すとき、ダブルクオーテーションを\\\"とエスケープしないといけない
-
Avatar
文字列リテラルを変更しようとするとセグフォするのを実験 -#include <stdio.h> +     }
+
+
+
+
+
+
+
17:44
+
+
+
launch.jsonargsにテストケースを渡すとき、ダブルクオーテーションを\\\"とエスケープしないといけない
+
+
+
+
+
+
+
+
Avatar
+
+ +
文字列リテラルを変更しようとするとセグフォするのを実験 + #include <stdio.h> char *str = "Hello, implementation-defined behavior!"; int main(int argc, int **argv) { @@ -102,47 +1359,316 @@ str[5] = '!'; // ここでセグフォする(けどコンパイル時にwarningさえ出ない) printf("%s\n", str); return 0; -}
23:09
文字列リテラルがcharの配列として扱われるのは、例外的な場合のみ -char str[] = "Good bye"; // 初期化式だからcharの配列として書ける -str[5] = 'B';
23:10
なのでこの段階では気にする必要はなく、文字列リテラルはすべてグローバルに確保して、その領域へのアドレスを返すようにすればOKなはず
-
Avatar
既存のコンパイラの出力を観察してみる -int main() { char *str = "abc"; return str[0]; } -に対する出力
02:26
x86-64 clang 15.0.0 -https://godbolt.org/z/dYWx1zchq
int main() { char *str = "abc"; return str[0]; }
02:26
int main() { char *str = "abc"; return str[0]; }
-
Avatar
エスケープシーケンスを実装する -
Escape sequences are defined using themselves here. E.g. '\n' is implemented using '\n'. This tautological definition works because the compiler that compiles our compiler knows what '\n' actually is. In other words, we "inherit" the ASCII code of '\n' from the compiler that compiles our compiler, so we don't have to teach the actual code here. This fact has huge implications not only for the correctness of the compiler but also for the security of the generated code. For more info, read "Reflections on Trusting Trust" by Ken Thompson. -https://github.com/rui314/chibicc/wiki/thompson1984.pdf
(edited)
-
Avatar
・最後の"を認識して、文字列リテラルの文字数分のcharの配列を確保する -・一文字ずつ読んでいって、エスケープシーケンスじゃなかったらそのまま配列のi文字目に書き込む -・エスケープシーケンスの場合はうまく扱えるように文字を置き換えて配列のi文字目に格納する (edited)
-
Avatar
Step27 行コメントとブロックコメントを実装する -たぶんtokenize.cに少し処理足せば一瞬でできるので、とりあえず後回しにして、Step28に進む(今のままだとテストが書きにくいので) (edited)
-
Avatar
Step28 テストをCで書き直す (edited)
-
Avatar
printfを使うにあたって -・フォーマット指定子(%dなど)については、とりあえず引数さえちゃんと渡せば大丈夫なはず -・可変長引数を取る関数を呼ぶので、call命令の前で浮動小数点数引数の個数をalに入れておく必要あり (edited)
-
Avatar
int printf(); -int exit(); - -int assert(int expected, int actual, char *code) { - if (expected == actual) { - printf("%s => %d\n", code, actual); - } - else { - printf("%s => %d expected, but got %d\n", code, expected, actual); - exit(1); - } - return 0; -} +}
+
+
+
+
+
+
+
23:09
+
+
+
文字列リテラルがcharの配列として扱われるのは、例外的な場合のみ + char str[] = "Good bye"; // 初期化式だからcharの配列として書ける +str[5] = 'B';
+
+
+
+
+
+
+
23:10
+
+
+
なのでこの段階では気にする必要はなく、文字列リテラルはすべてグローバルに確保して、その領域へのアドレスを返すようにすればOKなはず
+
+
+
+
+
+
+
+
Avatar
+
+ +
既存のコンパイラの出力を観察してみる + int main() { char *str = "abc"; return str[0]; } + に対する出力
+
+
+
+
+
+
+
02:26
+
+
+
x86-64 clang 15.0.0 + https://godbolt.org/z/dYWx1zchq
+
+
+
+
+
+ +
+
int main() { char *str = "abc"; return str[0]; }
+
+
+ +
+
+
+
+
+
+
+
+
+
02:26
+
+
+ +
+
+
+
+
+ +
+
int main() { char *str = "abc"; return str[0]; }
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
エスケープシーケンスを実装する +
+
+
Escape sequences are defined using themselves here. E.g. '\n' is implemented using '\n'. This tautological definition works because the compiler that compiles our compiler knows what '\n' actually is. In other words, we "inherit" the ASCII code of '\n' from the compiler that compiles our compiler, so we don't have to teach the actual code here. This fact has huge implications not only for the correctness of the compiler but also for the security of the generated code. For more info, read "Reflections on Trusting Trust" by Ken Thompson. + https://github.com/rui314/chibicc/wiki/thompson1984.pdf +
+
+
(edited)
+
+
+
+
+
+
+
06:59
+
+
+ +
+
+
+
+
+ +
+
A small C compiler. Contribute to rui314/chibicc development by creating an account on GitHub.
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
・最後の"を認識して、文字列リテラルの文字数分のcharの配列を確保する + ・一文字ずつ読んでいって、エスケープシーケンスじゃなかったらそのまま配列のi文字目に書き込む + ・エスケープシーケンスの場合はうまく扱えるように文字を置き換えて配列のi文字目に格納する (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
Step27 行コメントとブロックコメントを実装する + たぶんtokenize.cに少し処理足せば一瞬でできるので、とりあえず後回しにして、Step28に進む(今のままだとテストが書きにくいので) (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
Step28 テストをCで書き直す (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
printfを使うにあたって + ・フォーマット指定子(%dなど)については、とりあえず引数さえちゃんと渡せば大丈夫なはず + ・可変長引数を取る関数を呼ぶので、call命令の前で浮動小数点数引数の個数をalに入れておく必要あり (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+ +
+
int printf(); + int exit(); -int main() { - return 0; -}
-
Avatar
実装するときに考えてたことのメモ -何したらいいか分かりづらいので、とりあえず、test.c1ファイルに全ての処理をベタ書きしてみる (edited)
19:12
まずは整数1つを返すテストケースを通せるようにする
19:12
これが通ったので、あとはいい感じに拡張すればいけそう
19:13
関数の引数に渡せるのはexpressionであってstatementは渡せないので、statementを渡したいときは、別途関数を作って、引数としてその関数(の返り値)を渡せば良さそう
19:14
全てのテストケースをtest.cに移すと800行とかになったので、いくつかのファイルに分割した方が良さそう
-
Avatar
適当に7ファイルくらいに分割した
02:38
make cleanが、サブディレクトリに対してもいい感じに作用してほしい
02:38
# ccに渡すコンパイラオプションを指定 + int assert(int expected, int actual, char *code) { + if (expected == actual) { + printf("%s => %d\n", code, actual); + } + else { + printf("%s => %d expected, but got %d\n", code, expected, actual); + exit(1); + } + return 0; + } + + int main() { + return 0; + }
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
実装するときに考えてたことのメモ + 何したらいいか分かりづらいので、とりあえず、test.c1ファイルに全ての処理をベタ書きしてみる (edited)
+
+
+
+
+
+
+
19:12
+
+
+
まずは整数1つを返すテストケースを通せるようにする
+
+
+
+
+
+
+
19:12
+
+
+
これが通ったので、あとはいい感じに拡張すればいけそう
+
+
+
+
+
+
+
19:13
+
+
+
関数の引数に渡せるのはexpressionであってstatementは渡せないので、statementを渡したいときは、別途関数を作って、引数としてその関数(の返り値)を渡せば良さそう
+
+
+
+
+
+
+
19:14
+
+
+
全てのテストケースをtest.cに移すと800行とかになったので、いくつかのファイルに分割した方が良さそう
+
+
+
+
+
+
+
+
Avatar
+
+ +
適当に7ファイルくらいに分割した
+
+
+
+
+
+
+
02:38
+
+
+
make cleanが、サブディレクトリに対してもいい感じに作用してほしい
+
+
+
+
+
+
+
02:38
+
+
+
# ccに渡すコンパイラオプションを指定 CFLAGS=-std=c11 -g -static # src/*.cをソースとして指定 SRCS=$(wildcard src/*.c) @@ -167,11 +1693,92 @@ rm -f 9cc src/*.o test/*.s tmp* .gdb_history peda-session-*.txt src/peda-session-*.txt .vscode/peda-session-*.txt # testとcleanをダミーターゲット(実際に存在しないファイル)に指定 -.PHONY: test clean
02:39
SUBDIR的な変数を用意して、ゴニャゴニャしたらうまくいきそう(?)
-
Avatar
%演算子を実装する
18:00
Each of the operands shall have arithmetic type. The operands of the % operator shall have integer type.
-
Avatar
seccamp2018 c compiler. Contribute to hsjoihs/c-compiler development by creating an account on GitHub.
23:41
フォーマットしたやつ -int putchar(); +.PHONY: test clean
+
+
+
+
+
+
+
02:39
+
+
+
SUBDIR的な変数を用意して、ゴニャゴニャしたらうまくいきそう(?)
+
+
+
+
+
+
+
+
Avatar
+
+ +
%演算子を実装する
+
+
+
+ +
+
+
+
18:00
+
+
+
+
+
+
Each of the operands shall have arithmetic type. The operands of the % operator shall have integer type.
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+ +
+
seccamp2018 c compiler. Contribute to hsjoihs/c-compiler development by creating an account on GitHub.
+
+
+ +
+
+
+
+
+
+
+
+
+
23:41
+
+
+
フォーマットしたやつ + int putchar(); void *memset(); int m(int a, int b) { @@ -226,29 +1833,366 @@ usleep(50000); } return 0; -}
23:42
これ見ると、 -・初期化式全般 -・void型 -&&演算子(というか論理演算子全般) -・インクリメント++とデクリメント--演算子 -あたりが足りなさそう (edited)
23:44
ということで今週はこの辺を追加していきたい
23:45
あとポインタを返り値とするようなテストケースが全然ない気がするので、それも追加したい
-
Avatar
あとsizeof(int)みたいな記法が未実装
- -
Avatar
未初期化のポインタを参照するのは未定義動作だが、その場合も考慮した実装 -https://github.com/karintou8710/kcc/commit/8d56aea5861c72eae36a723aa662123268d67c2c (edited)
19:41
(左辺で未定義動作踏んだらセグフォするとは思うけど)
19:42
とりあえず未定義動作踏まなければ大丈夫なはずなので、一旦これでいく
-
Avatar
BNFの修正点 -・関数宣言を追加 -・Func_name(void) みたいな記法に対応 -・関数のパラメータあたりのBNFがいろいろアレ (edited)
-
Avatar
関数宣言に今のところパラメータが書けない (対応済) (edited)
-
Avatar
Step ?? カンマ演算子を追加する
-
15:18
こんな感じの構文木にパースしないといけないはず
-
Avatar
例のドーナツを動かす
16:22
-
Avatar
破滅の刃() ~無限デバッグ編~ (edited)
16:37
原因調査
16:37
まず、z[o]のところで落ちてそう
16:38
oの値を調べてみると、定義した配列の要素数を大幅に上回っているので、これのせいで変なメモリアクセスが発生し、セグフォしてるっぽい
16:39
ということで各種変数の値について、ccの出力と9ccの出力を比較してみると、2ステップ目で既にNの値が異なっていることが分かる(上記画像参照) (edited)
16:39
(恐らく)Nの値がバグってることで、後々oも変な値になってる
16:40
ということで -N = 8 * (m(m(l, r) - m(m(w, q), p), u) - m(m(w, r), p) - m(l, q) - m(m(e, v), p)) / s; -の各オペランドごとに値を出力してみた
16:41
16:42
op1の値がバグってる
-
Avatar
さらにop1を分解
16:55
16:56
ちなみにここまで、こんな感じで展開している - printf("<< %d >>\n", ++count); +}
+
+
+
+
+
+
+
23:42
+
+
+
これ見ると、 + ・初期化式全般 + ・void型 + &&演算子(というか論理演算子全般) + ・インクリメント++とデクリメント--演算子 + あたりが足りなさそう (edited)
+
+
+
+
+
+
+
23:44
+
+
+
ということで今週はこの辺を追加していきたい
+
+
+
+
+
+
+
23:45
+
+
+
あとポインタを返り値とするようなテストケースが全然ない気がするので、それも追加したい
+
+
+
+
+
+
+
+
Avatar
+
+ +
あとsizeof(int)みたいな記法が未実装
+
+
+
+
+ +
+
+
+
Avatar
+
+ +
未初期化のポインタを参照するのは未定義動作だが、その場合も考慮した実装 + https://github.com/karintou8710/kcc/commit/8d56aea5861c72eae36a723aa662123268d67c2c (edited)
+ +
+
+
+ +
+
+
+
19:41
+
+
+
(左辺で未定義動作踏んだらセグフォするとは思うけど)
+
+
+
+
+
+
+
19:42
+
+
+
とりあえず未定義動作踏まなければ大丈夫なはずなので、一旦これでいく
+
+
+
+
+
+
+
+
Avatar
+
+ +
BNFの修正点 + ・関数宣言を追加 + ・Func_name(void) みたいな記法に対応 + ・関数のパラメータあたりのBNFがいろいろアレ (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
関数宣言に今のところパラメータが書けない (対応済) (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
Step ?? カンマ演算子を追加する
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+
+
15:18
+
+
+
こんな感じの構文木にパースしないといけないはず
+
+
+
+
+
+
+
+
Avatar
+
+ +
例のドーナツを動かす
+
+
+
+
+
+
+
16:22
+
+
+ +
+
+
+
+
+
+
+
Avatar
+
+ +
破滅の刃() ~無限デバッグ編~ (edited)
+
+
+
+
+
+
+
16:37
+
+
+
原因調査
+
+
+
+
+
+
+
16:37
+
+
+
まず、z[o]のところで落ちてそう
+
+
+
+
+
+
+
16:38
+
+
+
oの値を調べてみると、定義した配列の要素数を大幅に上回っているので、これのせいで変なメモリアクセスが発生し、セグフォしてるっぽい
+
+
+
+
+
+
+
16:39
+
+
+
ということで各種変数の値について、ccの出力と9ccの出力を比較してみると、2ステップ目で既にNの値が異なっていることが分かる(上記画像参照) (edited)
+
+
+
+
+
+
+
16:39
+
+
+
(恐らく)Nの値がバグってることで、後々oも変な値になってる
+
+
+
+
+
+
+
16:40
+
+
+
ということで + N = 8 * (m(m(l, r) - m(m(w, q), p), u) - m(m(w, r), p) - m(l, q) - m(m(e, v), p)) / s; + の各オペランドごとに値を出力してみた
+
+
+
+
+
+
+
16:41
+
+
+
+
+ + + +
14.84 KB
+
+
+
+
+
+
+
+
+
16:42
+
+
+
op1の値がバグってる
+
+
+
+
+
+
+
+
Avatar
+
+ +
さらにop1を分解
+
+
+
+
+
+
+
16:55
+
+
+
+
+ + + +
16.53 KB
+
+
+
+
+
+
+
+
+
16:56
+
+
+
ちなみにここまで、こんな感じで展開している + printf("<< %d >>\n", ++count); if (count >= 90) return 0; @@ -292,8 +2236,28 @@ printf("y = %d\n", y); printf("o = %d\n", o); printf("N = %d\n\n", N); - */
16:59
関数int m(int a, int b);が返している値がおかしいと考えられる
-
Avatar
テストコードint m(int a, int b) { + */
+
+
+
+
+
+
+
16:59
+
+
+
関数int m(int a, int b);が返している値がおかしいと考えられる
+
+
+
+
+
+
+
+
Avatar
+
+ +
テストコードint m(int a, int b) { return (a * b + 5000) / 10000; } @@ -313,8 +2277,18 @@ printf("m(-1989, 10000) = %d\n", m(-1989, 10000)); return 0; -} (edited)
17:25
出力結果 -m( 0, 10000) = 0 m( 0, 10000) = 0 +} (edited)
+
+
+
+
+
+
+
17:25
+
+
+
出力結果 + m( 0, 10000) = 0 m( 0, 10000) = 0 m(-200, 10000) = 429297 | m(-200, 10000) = -199 m(-400, 10000) = 429097 | m(-400, 10000) = -399 m(-600, 10000) = 428897 | m(-600, 10000) = -599 @@ -324,9 +2298,19 @@ m(-1397, 10000) = 428100 | m(-1397, 10000) = -1396 m(-1595, 10000) = 427902 | m(-1595, 10000) = -1594 m(-1792, 10000) = 427705 | m(-1792, 10000) = -1791 -m(-1989, 10000) = 427508 | m(-1989, 10000) = -1988
-
Avatar
mのどこで事故ってるか調査 -int printf(); +m(-1989, 10000) = 427508 | m(-1989, 10000) = -1988
+
+
+
+
+
+
+
+
Avatar
+
+ +
mのどこで事故ってるか調査 + int printf(); int m(int a, int b) { printf("a = %d\n", a); @@ -364,9 +2348,19 @@ */ return 0; -}
-
Avatar
mのどこで事故ってるか調査 -int printf(); +}
+
+
+
+
+
+
+
+
Avatar
+
+ +
mのどこで事故ってるか調査 + int printf(); int m(int a, int b) { printf("a = %d\n", a); @@ -404,21 +2398,247 @@ */ return 0; -}
18:51
符号拡張を型のサイズによって変える必要があるっぽい
18:52
@mikikeen これ僕もバグらせたんですが、ログを「割り算」などで検索すると当時の会話でそのことについて話してます。https://t.co/pCdLWPAyRm
18:53
型のサイズに応じて符号拡張する関数書いたら、ドーナツ出た -(ただ、まだセグフォする) -https://twitter.com/mikikeen/status/1598726498948591616 (edited)
自作コンパイラ、伝家の宝刀†printfデバッグ†してたら、printfの有無で挙動変わって頭抱えてる
Likes
149
-
Avatar
久々にデバッグの続きをやっていく~~
19:51
とりあえず -・符号拡張直したやつ -・配列をポインタにキャストし忘れてたやつを追加 -をcommitした
19:52
それで何がどうなっていたか、忘れてたのでMakeFileの差分もdiscardした
19:52
codegen.cのコメントアウトでいい感じにnodeの種類を表示するやつはそのまま置いてある
19:54
donutifyされてるやつを普通に戻して初期化式を排除して、下のメッセージを消去したdonut.cがこれ
19:55
19:55
とりあえず9ccでコンパイルしてみる
19:55
usleepでセグフォしてる
19:56
こういうの -void usleep(); +}
+
+
+
+
+
+
+
18:51
+
+
+
符号拡張を型のサイズによって変える必要があるっぽい
+
+
+
+ +
+
+
+
18:52
+
+
+ +
+
+
+
+
+ +
+
@mikikeen これ僕もバグらせたんですが、ログを「割り算」などで検索すると当時の会話でそのことについて話してます。https://t.co/pCdLWPAyRm
+
+
+
+ +
+
+
+
+
+
+
+
+
18:53
+
+
+
型のサイズに応じて符号拡張する関数書いたら、ドーナツ出た + (ただ、まだセグフォする) + https://twitter.com/mikikeen/status/1598726498948591616 (edited)
+ +
+
+
+
+
+ +
+
自作コンパイラ、伝家の宝刀†printfデバッグ†してたら、printfの有無で挙動変わって頭抱えてる
+
+
+
+
+
Likes
+
+
+
149
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
久々にデバッグの続きをやっていく~~
+
+
+
+
+
+
+
19:51
+
+
+
とりあえず + ・符号拡張直したやつ + ・配列をポインタにキャストし忘れてたやつを追加 + をcommitした
+
+
+
+
+
+
+
19:52
+
+
+
それで何がどうなっていたか、忘れてたのでMakeFileの差分もdiscardした
+
+
+
+
+
+
+
19:52
+
+
+
codegen.cのコメントアウトでいい感じにnodeの種類を表示するやつはそのまま置いてある
+
+
+
+
+
+
+
19:53
+
+
+ +
+
+
+
+
+ +
+
seccamp2018 c compiler. Contribute to hsjoihs/c-compiler development by creating an account on GitHub.
+
+
+ +
+
+
+
+
+
+
+
+
+
19:54
+
+
+
donutifyされてるやつを普通に戻して初期化式を排除して、下のメッセージを消去したdonut.cがこれ
+
+
+
+
+
+
+
19:55
+
+
+
+
+ + + +
1.54 KB
+
+
+
+
+
+
+
+
+
19:55
+
+
+
とりあえず9ccでコンパイルしてみる
+
+
+
+
+
+
+
19:55
+
+
+
usleepでセグフォしてる
+
+
+
+
+
+
+
19:56
+
+
+
こういうの + void usleep(); int printf(); int main() { usleep(2000000); printf("Now testing usleep() calling.\n"); -}
19:56
がコンパイルできるか確認したところ、これは通る
-
Avatar
Program received signal SIGSEGV, Segmentation fault. +}
+
+
+
+
+
+
+
19:56
+
+
+
がコンパイルできるか確認したところ、これは通る
+
+
+
+
+
+
+
+
Avatar
+
+ +
Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] RAX: 0x7fffff7feffc RBX: 0x2 @@ -454,8 +2674,28 @@ Stopped reason: SIGSEGV m () at tmp.s:18 18 mov DWORD PTR [rax], edi -gdb-peda$ q
-
Avatar
ワンチャン原因これか?
13:15
mikiken@DESKTOP-CM4259U:~/compiler/9cc$ ulimit -a +gdb-peda$ q
+
+
+
+
+
+
+
+
Avatar
+
+ +
ワンチャン原因これか?
+
+
+
+
+
+
+
13:15
+
+
+
mikiken@DESKTOP-CM4259U:~/compiler/9cc$ ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 @@ -471,10 +2711,102 @@ cpu time (seconds, -t) unlimited max user processes (-u) 31257 virtual memory (kbytes, -v) unlimited -file locks (-x) unlimited
13:18
13:18
はい正解~~~
-
Avatar
顛末自作のmodintライブラリのテストがてら、( 10^6 \times 10 )サイズの配列を宣言してみたらsegmentation faultが出た。計算途中にオーバーフローを起こすような処理(例えばmodを取る前の値がlong longのmaxを超えているとか)を行っているわけではないのでおそらくメモリ関連のエラーであると推測する。同様のサイズの配列をWindows環境でMinGW G++を用いてコンパイルして実行しても正常に通るのでおそらくLinux(Ubuntu on WSL...
-
Avatar
なんかusleep.cがないって言われてるっぽい? (edited)
17:37
-
Avatar
スタックの16byte-alignmentが崩れている
00:39
.L.end.3: +file locks (-x) unlimited
+
+
+
+
+
+
+
13:18
+
+
+ +
+
+
+
+
+
+
13:18
+
+
+
はい正解~~~
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+ +
+
顛末自作のmodintライブラリのテストがてら、( 10^6 \times 10 )サイズの配列を宣言してみたらsegmentation faultが出た。計算途中にオーバーフローを起こすような処理(例えばmodを取る前の値がlong longのmaxを超えているとか)を行っているわけではないのでおそらくメモリ関連のエラーであると推測する。同様のサイズの配列をWindows環境でMinGW G++を用いてコンパイルして実行しても正常に通るのでおそらくLinux(Ubuntu on WSL...
+
+
+
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
なんかusleep.cがないって言われてるっぽい? (edited)
+
+
+
+
+
+
+
17:37
+
+
+
+
+ + + +
7.35 KB
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
スタックの16byte-alignmentが崩れている
+
+
+
+
+
+
+
00:39
+
+
+
.L.end.3: lea rdi, [rbp-8844] push rdi lea rdi, [rbp-8844] @@ -521,19 +2853,136 @@ call a push rax jmp .L.begin.2 -.L.end.2:
00:41
最初におかしくなってるのが、tmp.sの1133行目で、これに対応するのはdonut.cの50行目の - for (j = 0; j < 314; j++, a(&e, &w, s - 2, 200)) { -関数aを呼び出す部分だと考えられる
-
Avatar
~1ヶ月経過😇~ -for(expr; expr; expr)exprの値がpushされたままになってたのが原因
- -
Avatar
再び1ヶ月経過
18:38
1ヶ月ぶりに自作コンパイラのコード見てるけど、何をしようとしてたか忘れた
18:39
ローカルな配列の初期化式を実装しようとしてたっぽい
18:39
というわけでやっていき
18:40
こんな感じのコードを追加し、 -Node *array_init(Function *func, Token *ident, Token *tok) { +.L.end.2:
+
+
+
+
+
+
+
00:41
+
+
+
最初におかしくなってるのが、tmp.sの1133行目で、これに対応するのはdonut.cの50行目の + for (j = 0; j < 314; j++, a(&e, &w, s - 2, 200)) { + 関数aを呼び出す部分だと考えられる
+
+
+
+
+
+
+
+
Avatar
+
+ +
~1ヶ月経過😇~ + for(expr; expr; expr)exprの値がpushされたままになってたのが原因
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+
+
01:30
+
+
+ +
+
+
+
+
+
+
+
Avatar
+
+ +
再び1ヶ月経過
+
+
+
+
+
+
+
18:38
+
+
+ +
+
+
+
+
+ +
+
1ヶ月ぶりに自作コンパイラのコード見てるけど、何をしようとしてたか忘れた
+
+
+
+ +
+
+
+
+
+
+
+
+
18:39
+
+
+ +
+
+
+
+
+ +
+
ローカルな配列の初期化式を実装しようとしてたっぽい
+
+
+
+ +
+
+
+
+
+
+
+
+
18:39
+
+
+
というわけでやっていき
+
+
+
+
+
+
+
18:40
+
+
+
こんな感じのコードを追加し、 + Node *array_init(Function *func, Token *ident, Token *tok) { Node head; Node *cur = &head; for (int offset = 0; !consume(tok, TK_RIGHT_BRACE); offset++) { @@ -546,31 +2995,283 @@ expect(tok, TK_COMMA); } return head.next; -}
18:40
こういうコードが通るようになった -int array_test13() { +}
+
+
+
+
+
+
+
18:40
+
+
+
こういうコードが通るようになった + int array_test13() { int a[5] = {1, 2, 3, 4, 5}; int sum = 0; for (int i = 0; i < 5; i++) sum += a[i]; return sum; -}
18:40
ケツカンマがあっても通る実装になってる
18:41
あと実装しないといけない仕様としては、
18:41
配列の長さを明示的に書かずに初期化するやつ -int arr[] = {1,2,3}; (edited)
18:43
あとは初期化式が一部しか与えられてない場合に、残りの要素を0埋めするやつとか -int x[5] = {1, 2, 3}; // これは下と等価 -int x[5] = {1, 2, 3, 0, 0};
18:45
そもそも、ただ配列を宣言しただけのときって、初期化されたっけ? -int arr[3]; // これの初期値どうなってる?
18:48
あとは、定義したデカさ以上の要素を参照しようとしたときに警告出すとか -(gccで試したらwarningになったので、未定義動作っぽい?)←確認が必要
-
Avatar
ポインタの配列のテストケース入れてないな、そういえば
-
Avatar
とりあえず配列の長さを明示的に書かないやつを実装していく
-
Avatar
適当にコード足したら、変数のオフセットの処理書き忘れたっぽくて、出力したアセンブリがセグフォする
-
Avatar
テストでコケても、他のテストケースも流せるようにしたいかも
-
Avatar
ND_LVARっていうNodekind、実質使ってないから抹消したさあるな -(追記) int a[3];みたいな感じで宣言のみを行う場合、木構造の葉にあたるnodeがなくなってしまうので、あった方が便利 (edited)
-
Avatar
本来出力されるべきアセンブリと現状出力されているものとの差分
17:12
-
21:20
あと初期化の要素が配列の長さより短い場合に、残りの要素に0を代入するやつを実装した -https://github.com/mikiken/9cc/commit/e45a6b45485e02bdcf6d5855b96f2641cfb337c3 (edited)
if not all elements are given at initalizer
21:21
char str[] = "hoge"; みたいな構文を実装しようとしたが、文字リテラルをまだ実装していなかった -(文字列リテラルはある)
21:21
というわけで、文字リテラルを実装していく
21:22
大体できたけど、エスケープシーケンスが2文字と解釈されてエラーでる(それはそう)
21:23
こんな感じのコードを追加し、tok->valの値とすることで、ひとまず動くようにはなった -char read_escape_char(char *p) { +}
+
+
+
+
+
+
+
18:40
+
+
+
ケツカンマがあっても通る実装になってる
+
+
+
+
+
+
+
18:41
+
+
+
あと実装しないといけない仕様としては、
+
+
+
+
+
+
+
18:41
+
+
+
配列の長さを明示的に書かずに初期化するやつ + int arr[] = {1,2,3}; (edited)
+
+
+
+
+
+
+
18:43
+
+
+
あとは初期化式が一部しか与えられてない場合に、残りの要素を0埋めするやつとか + int x[5] = {1, 2, 3}; // これは下と等価 +int x[5] = {1, 2, 3, 0, 0};
+
+
+
+
+
+
+
18:45
+
+
+
そもそも、ただ配列を宣言しただけのときって、初期化されたっけ? + int arr[3]; // これの初期値どうなってる?
+
+
+
+
+
+
+
18:48
+
+
+
あとは、定義したデカさ以上の要素を参照しようとしたときに警告出すとか + (gccで試したらwarningになったので、未定義動作っぽい?)←確認が必要
+
+
+
+
+
+
+
+
Avatar
+
+ +
ポインタの配列のテストケース入れてないな、そういえば
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず配列の長さを明示的に書かないやつを実装していく
+
+
+
+
+
+
+
+
Avatar
+
+ +
適当にコード足したら、変数のオフセットの処理書き忘れたっぽくて、出力したアセンブリがセグフォする
+
+
+
+
+
+
+
+
Avatar
+
+ +
テストでコケても、他のテストケースも流せるようにしたいかも
+
+
+
+
+
+
+
+
Avatar
+
+ +
ND_LVARっていうNodekind、実質使ってないから抹消したさあるな + (追記) int a[3];みたいな感じで宣言のみを行う場合、木構造の葉にあたるnodeがなくなってしまうので、あった方が便利 (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
本来出力されるべきアセンブリと現状出力されているものとの差分
+
+
+
+
+
+
+
17:12
+
+
+
+
+ + + +
7.91 KB
+
+
+
+
+
+
+
+ +
+
+
+
21:20
+
+
+
あと初期化の要素が配列の長さより短い場合に、残りの要素に0を代入するやつを実装した + https://github.com/mikiken/9cc/commit/e45a6b45485e02bdcf6d5855b96f2641cfb337c3 (edited)
+
+
+
+
+
+ +
+
if not all elements are given at initalizer
+
+
+ +
+
+
+
+
+
+
+
+
+
21:21
+
+
+ +
+
+
+
+
+ +
+
char str[] = "hoge"; みたいな構文を実装しようとしたが、文字リテラルをまだ実装していなかった + (文字列リテラルはある)
+
+
+
+ +
+
+
+
+
+
+
+
+
21:21
+
+
+
というわけで、文字リテラルを実装していく
+
+
+
+
+
+
+
21:22
+
+
+
大体できたけど、エスケープシーケンスが2文字と解釈されてエラーでる(それはそう)
+
+
+
+
+
+
+
21:23
+
+
+
こんな感じのコードを追加し、tok->valの値とすることで、ひとまず動くようにはなった + char read_escape_char(char *p) { switch (*p) { case 'a': return 7; @@ -597,19 +3298,93 @@ default: return *p; } -}
21:24
文字列リテラルでも似たようなコードを書いてしまってるのをどうにかしたい
-
Avatar
実は文字列リテラル実装するとき、Compiler Explorerの出力を見ようみまねで真似ただけで、いまいち仕組みが分かっていない😇 (edited)
-
Avatar
えーとchibiccでは、文字列リテラルはdataセクションにcharの配列として記録されているっぽい (edited)
-
Avatar
一方、自分のやつの場合、.stringという記法を使ってお茶を濁している (edited)
16:34
このディレクティブあたりの話は、以下のURL見ると良さそう -http://www.swlab.cs.okayama-u.ac.jp/~nom/lect/p3/what-is-directive.html -https://suu-g.hateblo.jp/entry/20080510/1210408956 (edited)
アセンブリコードをアセンブルすると機械語になる。こういう説明が世の中じゃされてるけど、でもそれは半分ウソ。アセンブリコードの中でも機械語に直接対応しないものがある。それがディレクティブと呼ばれているもので、ドットから始まる命令がこれにあたる。 疑似命令 (psuedo-ops) とも呼ばれるこのアセンブラディレクティブは、機械語への直接の対応ではなくて、アセンブラ(GNU as)に対する命令。 前回の記事で説明したのはセクションについてだけだったけれども、ディレクティブにはもっといろいろな意味がある。 以下、その具体的な例を挙げていくよ。 文字列を配置する [.ascii .asciz .st…
-
Avatar
現在のfind_string_literal_end()の実装 -char *find_string_literal_end(char *start) { +}
+
+
+
+
+
+
+
21:24
+
+
+
文字列リテラルでも似たようなコードを書いてしまってるのをどうにかしたい
+
+
+
+
+
+
+
+
Avatar
+
+ +
実は文字列リテラル実装するとき、Compiler Explorerの出力を見ようみまねで真似ただけで、いまいち仕組みが分かっていない😇 (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
えーとchibiccでは、文字列リテラルはdataセクションにcharの配列として記録されているっぽい (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
一方、自分のやつの場合、.stringという記法を使ってお茶を濁している (edited)
+
+
+
+
+
+
+
16:34
+
+
+
このディレクティブあたりの話は、以下のURL見ると良さそう + http://www.swlab.cs.okayama-u.ac.jp/~nom/lect/p3/what-is-directive.html + https://suu-g.hateblo.jp/entry/20080510/1210408956 (edited)
+
+
+
+
+
+ + +
+
アセンブリコードをアセンブルすると機械語になる。こういう説明が世の中じゃされてるけど、でもそれは半分ウソ。アセンブリコードの中でも機械語に直接対応しないものがある。それがディレクティブと呼ばれているもので、ドットから始まる命令がこれにあたる。 疑似命令 (psuedo-ops) とも呼ばれるこのアセンブラディレクティブは、機械語への直接の対応ではなくて、アセンブラ(GNU as)に対する命令。 前回の記事で説明したのはセクションについてだけだったけれども、ディレクティブにはもっといろいろな意味がある。 以下、その具体的な例を挙げていくよ。 文字列を配置する [.ascii .asciz .st…
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
現在のfind_string_literal_end()の実装 + char *find_string_literal_end(char *start) { char *p; for (p = start; *p != '\"'; p++) { if (*p == '\0' || *p == '\n') @@ -618,21 +3393,81 @@ p++; } return p; -}
16:58
ちょっと実験 -int main() { +}
+
+
+
+
+
+
+
16:58
+
+
+
ちょっと実験 + int main() { char c = '"'; // => Program returned: 34 return c; -}
16:58
int main() { +}
+
+
+
+
+
+
+
16:58
+
+
+
int main() { char c = '\"'; // => Program returned: 34 return c; -}
17:00
int main() { +}
+
+
+
+
+
+
+
17:00
+
+
+
int main() { // char c = '\'; // => コンパイルエラー (2個目のシングルクオートがエスケープされている) char c = '\\'; // => Program returned: 92 return c; -}
-
22:34
find_string_literal_end()関数は、for文の更新式は条件判定の前に評価されることを上手く利用している (edited)
-
Avatar
文字列リテラルの実装をとりあえずこうしてみた - // 文字列リテラルの場合 +}
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+
+
22:34
+
+
+
find_string_literal_end()関数は、for文の更新式は条件判定の前に評価されることを上手く利用している (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
文字列リテラルの実装をとりあえずこうしてみた + // 文字列リテラルの場合 if (startswith(p, "\"")) { char *start = ++p; // ダブルクオートを読み飛ばす char *end = find_string_literal_end(p); @@ -660,54 +3495,281 @@ cur = new_token(TK_STR, cur, buf, buf + len - 1); p++; // ダブルクオートを読み飛ばす continue; - }
21:49
\aとか\eが動かないのは、.stringディレクティブがこれらのエスケープシーケンスに対応していないっぽい
21:50
http://web.mit.edu/gnu/doc/html/as_7.html#SEC120 -
.string "str" -Copy the characters in str to the object file. You may specify more than one string to copy, separated by commas. Unless otherwise specified for a particular machine, the assembler marks the end of each string with a 0 byte. You can use any of the escape sequences described in section Strings.
(edited)
21:51
Stringsのsectionを見てみると、
21:55
Strings -A string is written between double-quotes. It may contain double-quotes or null characters. The way to get special characters into a string is to escape these characters: precede them with a backslash \ character. For example \\ represents one backslash: the first \ is an escape which tells as to interpret the second character literally as a backslash (which prevents as from recognizing the second \ as an escape character). The complete list of escapes follows.
(edited)
21:55
\b -Mnemonic for backspace; for ASCII this is octal code 010. -\f -Mnemonic for FormFeed; for ASCII this is octal code 014. -\n -Mnemonic for newline; for ASCII this is octal code 012. -\r -Mnemonic for carriage-Return; for ASCII this is octal code 015. -\t -Mnemonic for horizontal Tab; for ASCII this is octal code 011. -\ digit digit digit -An octal character code. The numeric code is 3 octal digits. For compatibility with other Unix systems, 8 and 9 are accepted as digits: for example, \008 has the value 010, and \009 the value 011. -\x hex-digit hex-digit -A hex character code. The numeric code is 2 hexadecimal digits. Either upper or lower case x works. -\\ -Represents one \ character. -\" -Represents one " character. Needed in strings to represent this character, because an unescaped " would end the string. -\ anything-else -Any other character when escaped by \ gives a warning, but assembles as if the \ was not present. The idea is that if you used an escape sequence you clearly didn't want the literal interpretation of the following character. However as has no other interpretation, so as knows it is giving you the wrong code and warns you of the fact. -Which characters are escapable, and what those escapes represent, varies widely among assemblers. The current set is what we think the BSD 4.2 assembler recognizes, and is a subset of what most C compilers recognize. If you are in doubt, do not use an escape sequence.
(edited)
-
Avatar
gccは上記に記載されていないエスケープシーケンスの場合(例えば\a)、\007のように8進コードポイントを使って表現している
-
Avatar
コンパイラのソースには書いていないのにバイナリだけで代々伝わっていく情報というのがあって、それはコンピュータのセキュリティに大きく関わっている。ここではそれについて書いてみよう。 僕は8ccというCコンパイラをスクラッチから書いたことがあるのだけど、8ccには文字列を読む部分で、""の後に"n"がきたら"\n"という文字(改行文字)を読んだことにするという箇所がある。これはよく考えてみれば自己言及的になっていて、ソースコードの中に、コンパイラが実際に必要とする改行文字のASCIIコードの情報が含まれていない。しかしコンパイラをコンパイルするコンパイラからその情報が受け継がれるので、
-
Avatar
そういえば、gen_data_section()のコードを書き換えてたときに、printfとかputcharの出力がすぐには表示されなかったが、これは一旦出力がストリームにバッファリングされるのが原因 -(必要な場合はfflush関数を用いることで、即座にバッファを吐き出すことができる)
-
Avatar
とりあえず、文字列リテラル内のエスケープシーケンスがちゃんと出力されるようになった -https://github.com/mikiken/9cc/commit/613ade31af9bbad8bfef1d13cbc9dc0ae5f57593 (edited)
16:20
かなり横道に逸れたけど、なにをしていたかというと、 -char str[] = "hoge"; -みたいな構文を書けるようにしようとしていたはず
16:23
char *str = "hoge"; -とは違って、上記の構文はちゃんとスタック領域に確保されたcharの配列として扱わないとだめ
- -
Avatar
続いてグローバル変数の初期化式を実装していく -(追記)一旦保留 (edited)
-
Avatar
とりあえず文字列リテラルの初期化式は後回しにして、ただのグローバル変数の初期化式とグローバルの配列の初期化式を実装していく
-
Avatar
ローカル変数の初期化式は、単に宣言の式と代入の式に分解してパースすれば良いだけだったけど、グローバル変数の場合はそうはいかない
-
Avatar
例えば、トップレベルに -int x = 3; -と書いた場合、 -x: + }
+
+
+
+
+
+
+
21:49
+
+
+
\aとか\eが動かないのは、.stringディレクティブがこれらのエスケープシーケンスに対応していないっぽい
+
+
+
+
+
+
+
21:50
+
+
+
http://web.mit.edu/gnu/doc/html/as_7.html#SEC120 +
+
+
.string "str" + Copy the characters in str to the object file. You may specify more than one string to copy, separated by commas. Unless otherwise specified for a particular machine, the assembler marks the end of each string with a 0 byte. You can use any of the escape sequences described in section Strings.
+
+
(edited)
+
+
+
+
+
+
+
21:51
+
+
+
Stringsのsectionを見てみると、
+
+
+
+
+
+
+
21:55
+
+
+
+
+
+
Strings + A string is written between double-quotes. It may contain double-quotes or null characters. The way to get special characters into a string is to escape these characters: precede them with a backslash \ character. For example \\ represents one backslash: the first \ is an escape which tells as to interpret the second character literally as a backslash (which prevents as from recognizing the second \ as an escape character). The complete list of escapes follows.
+
+
(edited)
+
+
+
+
+
+
+
21:55
+
+
+
+
+
+
\b + Mnemonic for backspace; for ASCII this is octal code 010. + \f + Mnemonic for FormFeed; for ASCII this is octal code 014. + \n + Mnemonic for newline; for ASCII this is octal code 012. + \r + Mnemonic for carriage-Return; for ASCII this is octal code 015. + \t + Mnemonic for horizontal Tab; for ASCII this is octal code 011. + \ digit digit digit + An octal character code. The numeric code is 3 octal digits. For compatibility with other Unix systems, 8 and 9 are accepted as digits: for example, \008 has the value 010, and \009 the value 011. + \x hex-digit hex-digit + A hex character code. The numeric code is 2 hexadecimal digits. Either upper or lower case x works. + \\ + Represents one \ character. + \" + Represents one " character. Needed in strings to represent this character, because an unescaped " would end the string. + \ anything-else + Any other character when escaped by \ gives a warning, but assembles as if the \ was not present. The idea is that if you used an escape sequence you clearly didn't want the literal interpretation of the following character. However as has no other interpretation, so as knows it is giving you the wrong code and warns you of the fact. + Which characters are escapable, and what those escapes represent, varies widely among assemblers. The current set is what we think the BSD 4.2 assembler recognizes, and is a subset of what most C compilers recognize. If you are in doubt, do not use an escape sequence. +
+
+
(edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
gccは上記に記載されていないエスケープシーケンスの場合(例えば\a)、\007のように8進コードポイントを使って表現している
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+ +
+
コンパイラのソースには書いていないのにバイナリだけで代々伝わっていく情報というのがあって、それはコンピュータのセキュリティに大きく関わっている。ここではそれについて書いてみよう。 僕は8ccというCコンパイラをスクラッチから書いたことがあるのだけど、8ccには文字列を読む部分で、""の後に"n"がきたら"\n"という文字(改行文字)を読んだことにするという箇所がある。これはよく考えてみれば自己言及的になっていて、ソースコードの中に、コンパイラが実際に必要とする改行文字のASCIIコードの情報が含まれていない。しかしコンパイラをコンパイルするコンパイラからその情報が受け継がれるので、
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
そういえば、gen_data_section()のコードを書き換えてたときに、printfとかputcharの出力がすぐには表示されなかったが、これは一旦出力がストリームにバッファリングされるのが原因 + (必要な場合はfflush関数を用いることで、即座にバッファを吐き出すことができる)
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず、文字列リテラル内のエスケープシーケンスがちゃんと出力されるようになった + https://github.com/mikiken/9cc/commit/613ade31af9bbad8bfef1d13cbc9dc0ae5f57593 (edited)
+ +
+
+
+
+
+
+
16:20
+
+
+
かなり横道に逸れたけど、なにをしていたかというと、 + char str[] = "hoge"; + みたいな構文を書けるようにしようとしていたはず
+
+
+
+
+
+
+
16:23
+
+
+
char *str = "hoge"; + とは違って、上記の構文はちゃんとスタック領域に確保されたcharの配列として扱わないとだめ
+
+
+
+
+ +
+
+
+
Avatar
+
+ +
続いてグローバル変数の初期化式を実装していく + (追記)一旦保留 (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず文字列リテラルの初期化式は後回しにして、ただのグローバル変数の初期化式とグローバルの配列の初期化式を実装していく
+
+
+
+
+
+
+
+
Avatar
+
+ +
ローカル変数の初期化式は、単に宣言の式と代入の式に分解してパースすれば良いだけだったけど、グローバル変数の場合はそうはいかない
+
+
+
+
+
+
+
+
Avatar
+
+ +
例えば、トップレベルに + int x = 3; + と書いた場合、 + x: .long 3 -とコンパイルしないといけない (edited)
-
Avatar
現状、 -codegen.c -void gen_data_secton(Obj *gvar_list) { + とコンパイルしないといけない (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
現状、 + codegen.c + void gen_data_secton(Obj *gvar_list) { printf(".data\n"); for (Obj *obj = gvar_list; obj->next != NULL; obj = obj->next) { // 文字列リテラルの場合 @@ -721,8 +3783,18 @@ } printf("\n"); } -となっているので、 (edited)
12:29
Objinit_exprみたいなメンバを追加するのが良さそう -typedef struct Obj Obj; + となっているので、 (edited)
+
+
+
+
+
+
+
12:29
+
+
+
Objinit_exprみたいなメンバを追加するのが良さそう + typedef struct Obj Obj; struct Obj { Obj *next; // 次のオブジェクトまたはNULL @@ -736,17 +3808,183 @@ // 文字列リテラル char *init_data; int str_id; -}; (edited)
-
Avatar
と思ったけど、めんどくさそうなので、グローバル変数の初期化式は、一旦後回しにしようかな
23:11
例えば、 -int x = 3 + 2 * 4; -みたいな式もトップレベルに書けるはずだが、これをコンパイルしようとすると、静的解析の時点で値を計算しておかないといけない気がする(?)
23:12
とりあえず、単に定数を代入する場合のみ書けるようにしてもいいけど、そもそも今の実装でグローバル変数を0以外で初期化してるところってそんなにないと思うから、とりあえず後に回そうという判断
23:15
現時点でのparse.cを保存しておく
23:15
26.75 KB
23:16
ということで、次に実装したいと思ってた多次元配列を実装していく
-
Avatar
初めに 「配列編」と銘打っていますが、続編が投稿される保証はありません。 そうそう この記事は言語実装 Advent Calendar 2018とC言語 Advent Calendar 2018の17日目の記事です。 想定している読者層 (どういう読者層を想定しているんだろう、書いていて自分でもよく分からなくなった)(Cコンパイラ書いていて「配列の配列(いわゆる二次元配列)がなんかバグるなぁ」となった人のための記事かもなぁ)(というか、多分バグらせていた当時の自分への手紙) 本題に入ろう Cコンパイラを書く上で微妙にハマった、配列へのポインタの話、それに付随して構造体を実装する際の話について軽…
-
Avatar
たぶん配列全体へのポインタさえ実装できれば、割とすぐに実装できそう
-
Avatar
int arr[3][5];という宣言に対して、arr[2][4]*(*(arr+2)+4)としてパースすればOK
-
Avatar
parse_type()declaration()で型のパースを(実質)2回やってるの、設計悪そう
12:51
識別子だけ保存しといて、parse_type()でトークンを読み進めておけばよさそう?
-
Avatar
とりあえず、parse_type()parse_base_type()parse_variable_type()に分割し、各関数の中でトークンを読み進めるようにした
16:23
declaration()の中がまだ直せてないので、直す (edited)
-
Avatar
一旦メモ - // 配列の場合 +}; (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
と思ったけど、めんどくさそうなので、グローバル変数の初期化式は、一旦後回しにしようかな
+
+
+
+
+
+
+
23:11
+
+
+
例えば、 + int x = 3 + 2 * 4; + みたいな式もトップレベルに書けるはずだが、これをコンパイルしようとすると、静的解析の時点で値を計算しておかないといけない気がする(?)
+
+
+
+
+
+
+
23:12
+
+
+
とりあえず、単に定数を代入する場合のみ書けるようにしてもいいけど、そもそも今の実装でグローバル変数を0以外で初期化してるところってそんなにないと思うから、とりあえず後に回そうという判断
+
+
+
+
+
+
+
23:15
+
+
+
現時点でのparse.cを保存しておく
+
+
+
+
+
+
+
23:15
+
+
+
+
+ + + +
26.75 KB
+
+
+
+
+
+
+
+
+
23:16
+
+
+
ということで、次に実装したいと思ってた多次元配列を実装していく
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+ + +
+
初めに 「配列編」と銘打っていますが、続編が投稿される保証はありません。 そうそう この記事は言語実装 Advent Calendar 2018とC言語 Advent Calendar 2018の17日目の記事です。 想定している読者層 (どういう読者層を想定しているんだろう、書いていて自分でもよく分からなくなった)(Cコンパイラ書いていて「配列の配列(いわゆる二次元配列)がなんかバグるなぁ」となった人のための記事かもなぁ)(というか、多分バグらせていた当時の自分への手紙) 本題に入ろう Cコンパイラを書く上で微妙にハマった、配列へのポインタの話、それに付随して構造体を実装する際の話について軽…
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
たぶん配列全体へのポインタさえ実装できれば、割とすぐに実装できそう
+
+
+
+
+
+
+
+
Avatar
+
+ +
int arr[3][5];という宣言に対して、arr[2][4]*(*(arr+2)+4)としてパースすればOK
+
+
+
+
+
+
+
+
Avatar
+
+ +
parse_type()declaration()で型のパースを(実質)2回やってるの、設計悪そう
+
+
+
+
+
+
+
12:51
+
+
+
識別子だけ保存しといて、parse_type()でトークンを読み進めておけばよさそう?
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず、parse_type()parse_base_type()parse_variable_type()に分割し、各関数の中でトークンを読み進めるようにした
+
+
+
+
+
+
+
16:23
+
+
+
declaration()の中がまだ直せてないので、直す (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
一旦メモ + // 配列の場合 if (dec_type->kind == TYPE_ARRAY) { while (dec_type->ptr_to) { // 配列の要素数が明示されている場合 @@ -787,18 +4025,102 @@ return node; } } - }
-
Avatar
初期化式が存在するときに配列宣言の要素数を省略できる記法は、難しそうなので、一旦消してから後で復活させるか (edited)
-
Avatar
semantic_analysis.cで、 -
不正な加算を行うことはできません -
というエラーが出たので、原因を調べる
(edited)
01:09
ここで、配列全体へのポインタを実装する必要が出てくるのだと思われる
-
23:15
int arr[2][3]int [3]の要素数2の配列型であることに注意する必要がある -(直感に反する)
-
Avatar
多次元配列のパースの仕方をミスってたので、直す -順方向に伸びるLinked Listとして実装すればOKなはず (edited)
-
Avatar
直せた
-
Avatar
今までは、一次元配列であることを(暗黙に)仮定してたので、 - case ND_ADD: { + }
+
+
+
+
+
+
+
+
Avatar
+
+ +
初期化式が存在するときに配列宣言の要素数を省略できる記法は、難しそうなので、一旦消してから後で復活させるか (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
semantic_analysis.cで、 +
+
+
不正な加算を行うことはできません +
+
というエラーが出たので、原因を調べる +
(edited)
+
+
+
+
+
+
+
01:09
+
+
+
ここで、配列全体へのポインタを実装する必要が出てくるのだと思われる
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+
+
23:15
+
+
+
int arr[2][3]int [3]の要素数2の配列型であることに注意する必要がある + (直感に反する)
+
+
+
+
+
+
+
+
Avatar
+
+ +
多次元配列のパースの仕方をミスってたので、直す + 順方向に伸びるLinked Listとして実装すればOKなはず (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
直せた
+
+
+
+
+
+
+
+
Avatar
+
+ +
今までは、一次元配列であることを(暗黙に)仮定してたので、 + case ND_ADD: { Node *lhs = add_type_to_node(lvar_list, node->lhs); Node *rhs = add_type_to_node(lvar_list, node->rhs); // 左辺が配列の場合、ポインタにキャストする @@ -830,24 +4152,177 @@ else { error("不正な加算を行うことはできません"); } - }
01:00
の中で、 - // 左辺がポインタ型、右辺がint型の場合 + }
+
+
+
+
+
+
+
01:00
+
+
+
の中で、 + // 左辺がポインタ型、右辺がint型の場合 if (lhs->type->kind == TYPE_PTR && rhs->type->kind == TYPE_INT) { Node *size = new_size_node(lhs->type->ptr_to->kind); Node *mul_scaling = new_typed_binary(new_typed_node(new_type(TYPE_INT), new_node(ND_MUL)), size, rhs); return new_typed_binary(new_typed_node(lhs->type, node), lhs, mul_scaling); } -のようにキャストすることで、上手くいっていた
01:01
しかし、多次元配列の場合は、lhs->type->ptr_tointのような基本型が来るとは限らない -実際、二次元配列の場合は、lhs->type->ptr_toTYPE_ARRAYが来ている (edited)
01:03
add_type_to_nodeを再帰的に呼び出す際に、型情報も、1段深いものを渡す必要がある(?)
01:08
話が戻るけど、グローバル変数の初期化式、定数をベタ書きする記法だけサポートしておくのはありかもしれない
-
Avatar
やっていき
11:40
int arr[2][3]という宣言があったとして、 -
1回目の間接参照では、ND_DEREFの型はint [3]であるべきであり、 -2回目の間接参照では、ND_DEREFの型はintであるべきである -
ってことになるはず
(edited)
-
Avatar
低レイヤを知りたい人のためのCコンパイラ作成入門を、Rustでコツコツとやっている。 www.sigbus.info 今まで苦労しながらもgithubのソースを見ながらまっすぐに来れたが、二次元配列の所で躓いたので整理してみた。 動かしたいソース まだここまでは動かないけど、やりたいことはこのCソースの演算部分を動かす事。二次元配列の宣言と、ポインターへの変換、デリファレンス。 流石にCの動きが分からないと言うことはないが、ちゃんと動くアセンブラを自力で出力するのには難しい。 #include int main() { int x [2][3]; int *y = x; i…
-
Avatar
int x[2][3];という宣言に対して、int (*x_ptr)[3] = &x[2]みたいなコードも動かないといけないはず(要検証)
-
Avatar
とりあえず、add_type_to_nodeND_DEREFlhsの型を見て、その型が配列型である場合は、ptr_toに入っている型情報を与えた上で、add_type_to_nodeを再度呼び出せばOKそう? (edited)
-
Avatar
数日経過
09:40
そもそも、add_type_to_nodeがデカすぎる説はあるな
09:43
この関数は、ただ機械的に型をつけるだけにして、その上で、後から型を修正したり、ポインタ演算用にnodeを追加した方が見通し良さそう (edited)
-
Avatar
// ND_DEREFで間接参照を行った場合、そのnodeの以下の型はnode->type->ptr_toとなるので、型を付け直す + のようにキャストすることで、上手くいっていた
+
+
+
+
+
+
+
01:01
+
+
+
しかし、多次元配列の場合は、lhs->type->ptr_tointのような基本型が来るとは限らない + 実際、二次元配列の場合は、lhs->type->ptr_toTYPE_ARRAYが来ている (edited)
+
+
+
+
+
+
+
01:03
+
+
+
add_type_to_nodeを再帰的に呼び出す際に、型情報も、1段深いものを渡す必要がある(?)
+
+
+
+
+
+
+
01:08
+
+
+
話が戻るけど、グローバル変数の初期化式、定数をベタ書きする記法だけサポートしておくのはありかもしれない
+
+
+
+
+
+
+
+
Avatar
+
+ +
やっていき
+
+
+
+
+
+
+
11:40
+
+
+
int arr[2][3]という宣言があったとして、 +
+
+
1回目の間接参照では、ND_DEREFの型はint [3]であるべきであり、 + 2回目の間接参照では、ND_DEREFの型はintであるべきである +
+
ってことになるはず +
(edited)
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+ + +
+
低レイヤを知りたい人のためのCコンパイラ作成入門を、Rustでコツコツとやっている。 www.sigbus.info 今まで苦労しながらもgithubのソースを見ながらまっすぐに来れたが、二次元配列の所で躓いたので整理してみた。 動かしたいソース まだここまでは動かないけど、やりたいことはこのCソースの演算部分を動かす事。二次元配列の宣言と、ポインターへの変換、デリファレンス。 流石にCの動きが分からないと言うことはないが、ちゃんと動くアセンブラを自力で出力するのには難しい。 #include int main() { int x [2][3]; int *y = x; i…
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
int x[2][3];という宣言に対して、int (*x_ptr)[3] = &x[2]みたいなコードも動かないといけないはず(要検証)
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず、add_type_to_nodeND_DEREFlhsの型を見て、その型が配列型である場合は、ptr_toに入っている型情報を与えた上で、add_type_to_nodeを再度呼び出せばOKそう? (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
数日経過
+
+
+
+
+
+
+
09:40
+
+
+
そもそも、add_type_to_nodeがデカすぎる説はあるな
+
+
+
+
+
+
+
09:43
+
+
+
この関数は、ただ機械的に型をつけるだけにして、その上で、後から型を修正したり、ポインタ演算用にnodeを追加した方が見通し良さそう (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
// ND_DEREFで間接参照を行った場合、そのnodeの以下の型はnode->type->ptr_toとなるので、型を付け直す Node *fix_node_type_to_ptr_to(Node *node, Type *node_type) { switch (node->kind) { case ND_DEREF: @@ -856,9 +4331,19 @@ default: return node; } -}
-
Avatar
テンプレを書いた -Node *add_type_to_node(Node *node) { +}
+
+
+
+
+
+
+
+
Avatar
+
+ +
テンプレを書いた + Node *add_type_to_node(Node *node) { switch (node->kind) { case ND_STMT: return; @@ -919,50 +4404,432 @@ case ND_SIZEOF: return; } -}
-
Avatar
(作り直し中の)add_type_to_nodeND_GVARがあるが、これはあくまで関数のスコープの中でグローバル変数を読んだ場合であって、トップレベルにグローバル変数に宣言を書いた場合ではない
21:46
(そもそもグローバル変数の初期化式を実装していない以上、それはそう)
-
Avatar
さらに数日経過
16:38
とりあえず、add_type_to_nodeでは機械的に型を付けるだけにした
16:39
配列をその先頭要素へのポインタにキャストする処理や、ポインタの加算減算で、オフセットをsizeof(lhs->type->ptr_to)倍する処理は、別の関数で行うことにした
16:41
なんか、改めて意味解析の処理を見返すと、型の扱い方合ってる?って思う箇所があるけど、とりあえず気にせずに分割していく (edited)
-
Avatar
とりあえず関数を分割したが、semantic_analysis() : 代入式の両辺の型が異なりますが出るな
16:47
どこでエラーが出てるかというと、test/array.carray_test1の代入式*p=1の部分 (edited)
16:49
int array_test1() { +}
+
+
+
+
+
+
+
+
Avatar
+
+ +
(作り直し中の)add_type_to_nodeND_GVARがあるが、これはあくまで関数のスコープの中でグローバル変数を読んだ場合であって、トップレベルにグローバル変数に宣言を書いた場合ではない
+
+
+
+
+
+
+
21:46
+
+
+
(そもそもグローバル変数の初期化式を実装していない以上、それはそう)
+
+
+
+
+
+
+
+
Avatar
+
+ +
さらに数日経過
+
+
+
+
+
+
+
16:38
+
+
+
とりあえず、add_type_to_nodeでは機械的に型を付けるだけにした
+
+
+
+
+
+
+
16:39
+
+
+
配列をその先頭要素へのポインタにキャストする処理や、ポインタの加算減算で、オフセットをsizeof(lhs->type->ptr_to)倍する処理は、別の関数で行うことにした
+
+
+
+
+
+
+
16:41
+
+
+
なんか、改めて意味解析の処理を見返すと、型の扱い方合ってる?って思う箇所があるけど、とりあえず気にせずに分割していく (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず関数を分割したが、semantic_analysis() : 代入式の両辺の型が異なりますが出るな
+
+
+
+
+
+
+
16:47
+
+
+
どこでエラーが出てるかというと、test/array.carray_test1の代入式*p=1の部分 (edited)
+
+
+
+
+
+
+
16:49
+
+
+
int array_test1() { int a[2]; int *p; p = a; *p = 1; *(p + 1) = 2; return *p + *(p + 1); -}
16:51
ND_DEREF自体の型は、lhs->type->ptr_toにしないといけないのに、そうなってなかったので修正した (edited)
-
Avatar
それはそうとして、大量のSegmentation faultsemantic_analysis() : 代入式の両辺の型が異なりますが出ているのが気になる
17:24
arith.cですらセグフォしてるので原因を調査する
17:29
codegen.cで関数呼び出しの引数の型を参照しようとしたところ、ND_FUNCALLbody->typeに型情報が入っておらずエラーになっているらしい
17:32
semantic_analysis()を確認したところ、普通にcase ND_FUNCALL:の実装忘れてたわ😇 (edited)
-
Avatar
実装した
-
Avatar
arith.cを9ccでコンパイルすると、 assert(21, 5 + 20 - 4, "5 + 20 - 4");のテストケースでコケてる
18:07
そもそもND_NUMに型情報が入っていないっぽい
-
Avatar
add_type_to_nodesemantic_analysisで、for文でarg->bodyに対して処理を行うべきところがargに対して処理を行ってたのが原因
22:43
これで、arith.cのコンパイルは通るようになった
22:43
次に、array.cのコンパイルを試みると、やはりセグフォ
22:49
とりあえず、代入式に型情報が入っていないことが分かる
22:53
確かにsemantic_analysis()ND_ASSIGNのところを見ると、node自体の型を指定し忘れてるな
-
Avatar
左辺の型に合わせるようにした
-
Avatar
なんかcodegen.cでメモリ上の値をレジスタにセットしようとしたときに配列型が登場してエラーになってる
12:25
どのテストケースでコケてるんだろ (edited)
-
Avatar
構文木はこんな感じ
15:38
-
Avatar
該当するテストケース無くね? -(追記) callstackを確認したらarray_test13()でコケてることが判明 (edited)
16:06
parse.cは特に触ってないから、意味解析のところで構文木を構築するのをミスってそう
-
Avatar
現時点で -arith.c +}
+
+
+
+
+
+
+
16:51
+
+
+
ND_DEREF自体の型は、lhs->type->ptr_toにしないといけないのに、そうなってなかったので修正した (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
それはそうとして、大量のSegmentation faultsemantic_analysis() : 代入式の両辺の型が異なりますが出ているのが気になる
+
+
+
+
+
+
+
17:24
+
+
+
arith.cですらセグフォしてるので原因を調査する
+
+
+
+
+
+
+
17:29
+
+
+
codegen.cで関数呼び出しの引数の型を参照しようとしたところ、ND_FUNCALLbody->typeに型情報が入っておらずエラーになっているらしい
+
+
+
+
+
+
+
17:32
+
+
+
semantic_analysis()を確認したところ、普通にcase ND_FUNCALL:の実装忘れてたわ😇 (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
実装した
+
+
+
+
+
+
+
+
Avatar
+
+ +
arith.cを9ccでコンパイルすると、 assert(21, 5 + 20 - 4, "5 + 20 - 4");のテストケースでコケてる
+
+
+
+
+
+
+
18:07
+
+
+
そもそもND_NUMに型情報が入っていないっぽい
+
+
+
+
+
+
+
+
Avatar
+
+ +
add_type_to_nodesemantic_analysisで、for文でarg->bodyに対して処理を行うべきところがargに対して処理を行ってたのが原因
+
+
+
+
+
+
+
22:43
+
+
+
これで、arith.cのコンパイルは通るようになった
+
+
+
+
+
+
+
22:43
+
+
+
次に、array.cのコンパイルを試みると、やはりセグフォ
+
+
+
+
+
+
+
22:47
+
+
+ +
+
+
+
+
+
+
22:49
+
+
+
とりあえず、代入式に型情報が入っていないことが分かる
+
+
+
+
+
+
+
22:53
+
+
+
確かにsemantic_analysis()ND_ASSIGNのところを見ると、node自体の型を指定し忘れてるな
+
+
+
+
+
+
+
+
Avatar
+
+ +
左辺の型に合わせるようにした
+
+
+
+
+
+
+
+
Avatar
+
+ +
なんかcodegen.cでメモリ上の値をレジスタにセットしようとしたときに配列型が登場してエラーになってる
+
+
+
+
+
+
+
12:25
+
+
+
どのテストケースでコケてるんだろ (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
構文木はこんな感じ
+
+
+
+
+
+
+
15:38
+
+
+ +
+
+
+
+
+
+
+
Avatar
+
+ +
該当するテストケース無くね? + (追記) callstackを確認したらarray_test13()でコケてることが判明 (edited)
+
+
+
+
+
+
+
16:06
+
+
+
parse.cは特に触ってないから、意味解析のところで構文木を構築するのをミスってそう
+
+
+
+
+
+
+
+
Avatar
+
+ +
現時点で + arith.c array.c control_statement.c function.c string.c test.c variable.c -はコンパイル可能 (edited)
16:12
一方で、pointer.cはコンパイルエラーになる (edited)
-
Avatar
Avatar
mikiken
該当するテストケース無くね? -(追記) callstackを確認したらarray_test13()でコケてることが判明 (edited)
VSCodeのgdb拡張(?)、めっちゃ便利 (edited)
16:39
int array_test13() { + はコンパイル可能 (edited)
+
+
+
+
+
+
+
16:12
+
+
+
一方で、pointer.cはコンパイルエラーになる (edited)
+
+
+
+
+
+
+
+
+
Avatar +
+
+
Avatar +
mikiken
+
該当するテストケース無くね? + (追記) callstackを確認したらarray_test13()でコケてることが判明 (edited)
+
+ +
VSCodeのgdb拡張(?)、めっちゃ便利 (edited)
+ +
+
+
+
+
+
+
16:39
+
+
+
int array_test13() { int a[5] = {1, 2, 3, 4, 5}; int sum = 0; for (int i = 0; i < 5; i++) sum += a[i]; return sum; -}
16:42
この関数のsum += a[i]のところで型の不一致が起きてる
16:46
右辺の配列aがポインタにキャストされてないのが原因っぽい
-
Avatar
semantic_analysis()ND_FORnode->thenの構文木の意味解析をし忘れてるのが原因
17:01
case ND_FOR: +}
+
+
+
+
+
+
+
16:42
+
+
+
この関数のsum += a[i]のところで型の不一致が起きてる
+
+
+
+
+
+
+
16:46
+
+
+
右辺の配列aがポインタにキャストされてないのが原因っぽい
+
+
+
+
+
+
+
+
Avatar
+
+ +
semantic_analysis()ND_FORnode->thenの構文木の意味解析をし忘れてるのが原因
+
+
+
+
+
+
+
17:01
+
+
+
case ND_FOR: if (node->init) semantic_analysis(node->init); if (node->cond) @@ -975,15 +4842,65 @@ if (node->type->kind == TYPE_NULL) return; else - error("semantic_analysis() : if文の意味解析を行うことができませんでした");
17:01
真ん中に1行追加したらarray.cのコンパイルが通るようになった (edited)
17:05
あとpointer.cを通せば、移植完了しそう
17:06
pointer.cpointer_test12()でコケてる
-
Avatar
int pointer_test12() { + error("semantic_analysis() : if文の意味解析を行うことができませんでした");
+
+
+
+
+
+
+
17:01
+
+
+
真ん中に1行追加したらarray.cのコンパイルが通るようになった (edited)
+
+
+
+
+
+
+
17:05
+
+
+
あとpointer.cを通せば、移植完了しそう
+
+
+
+
+
+
+
17:06
+
+
+
pointer.cpointer_test12()でコケてる
+
+
+
+
+
+
+
+
Avatar
+
+ +
int pointer_test12() { int a[2]; int k; k = &a[1] - &a[0]; return k; -}
-
Avatar
ND_SUBでポインタ同士の引き算をいい感じに意味解析するところのコード - // 両辺がポインタ型の場合 +}
+
+
+
+
+
+
+
+
Avatar
+
+ +
ND_SUBでポインタ同士の引き算をいい感じに意味解析するところのコード + // 両辺がポインタ型の場合 else if (is_ptr_type(node->lhs->type) && is_ptr_type(node->rhs->type)) { if (node->lhs->type->ptr_to->kind != node->rhs->type->ptr_to->kind) error("semantic_analysis() : 異なる型へのポインタ同士で減算を行うことはできません"); @@ -991,14 +4908,169 @@ Node *diff_addr = new_typed_binary(new_typed_node(node->lhs->type, new_node(ND_SUB)), node->lhs, node->rhs); *node = *new_typed_binary(new_typed_node(new_type(TYPE_INT), new_node(ND_DIV)), diff_addr, size); return; - } (edited)
18:04
node = new_typed_binary(~);となっていたが、これではND_ASSIGNnode->rhsの参照先は古い構文木のままになってしまう。そこで、*node = *new_typed_binary(~);として、Node *nodeの参照先自体を新しい構文木に書き換える必要がある。 (edited)
-
Avatar
これで、pointer.cのコンパイルも通るようになった
12:50
make testすると、string.cでエラーが出るな
12:51
[FAIL] {char x[3]; x[0] = -1; x[1] = 2; int y; y = 4; return x[0] + y;} => 3 expected, but got -253
-
Avatar
gdbでchar_type_test1()からreturnした直後のraxの値を観察すると何か分かりそう
-
Avatar
gdbで調べた感じこんな感じっぽい
-
Avatar
たぶん元々raxのビットがすべて立ってる状態0xffffffffから、raxの下位1byteのみ書き換えられた結果、0xffffff03というビットパターンになった (edited)
16:09
なのでraxのビットパターンの下位1byteのみを読むと、ちゃんと3という値になる
16:10
一方で、raxのビットパターンを(signed) intとして読むと、0xff03となるので、-253という値になる
16:10
ってことだと思う
-
Avatar
とりあえず、古い実装が吐くアセンブリと新しい実装が吐くアセンブリの差分を取ってみた
17:24
17:29
少なくとも、表面的に出力されるアセンブリを比較すると、古い方ではきちんとrdiの下位1byteを読んでいる一方、新しい方ではrdiの下位4byteを読んでしまっていることが分かる
-
Avatar
テストケースがデカいので、バグが再現するもう少し小さいテストケースを考える -int printf(); + } (edited)
+
+
+
+
+
+
+
18:04
+
+
+
node = new_typed_binary(~);となっていたが、これではND_ASSIGNnode->rhsの参照先は古い構文木のままになってしまう。そこで、*node = *new_typed_binary(~);として、Node *nodeの参照先自体を新しい構文木に書き換える必要がある。 (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
これで、pointer.cのコンパイルも通るようになった
+
+
+
+
+
+
+
12:50
+
+
+
make testすると、string.cでエラーが出るな
+
+
+
+
+
+
+
12:51
+
+
+
[FAIL] {char x[3]; x[0] = -1; x[1] = 2; int y; y = 4; return x[0] + y;} => 3 expected, but got -253
+
+
+
+
+
+
+
+
Avatar
+
+ +
gdbでchar_type_test1()からreturnした直後のraxの値を観察すると何か分かりそう
+
+
+
+
+
+
+
+
Avatar
+
+ +
gdbで調べた感じこんな感じっぽい
+
+
+ + + +
6.41 KB
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
たぶん元々raxのビットがすべて立ってる状態0xffffffffから、raxの下位1byteのみ書き換えられた結果、0xffffff03というビットパターンになった (edited)
+
+
+
+
+
+
+
16:09
+
+
+
なのでraxのビットパターンの下位1byteのみを読むと、ちゃんと3という値になる
+
+
+
+
+
+
+
16:10
+
+
+
一方で、raxのビットパターンを(signed) intとして読むと、0xff03となるので、-253という値になる
+
+
+
+
+
+
+
16:10
+
+
+
ってことだと思う
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず、古い実装が吐くアセンブリと新しい実装が吐くアセンブリの差分を取ってみた
+
+
+
+
+
+
+
17:24
+
+
+
+
+ + + +
8.9 KB
+
+
+
+
+
+
+
+
+
17:29
+
+
+
少なくとも、表面的に出力されるアセンブリを比較すると、古い方ではきちんとrdiの下位1byteを読んでいる一方、新しい方ではrdiの下位4byteを読んでしまっていることが分かる
+
+
+
+
+
+
+
+
Avatar
+
+ +
テストケースがデカいので、バグが再現するもう少し小さいテストケースを考える + int printf(); char char_type_test1() { char x = -1; @@ -1009,10 +5081,87 @@ int main() { printf("%d\n", char_type_test1()); return 0; -}
18:22
-
Avatar
とりあえず、算術型の加減算のnode自体の型を全てintにすることで、ひとまず動くようになった
-
Avatar
origin/mastermasterの乖離がすごいことに
19:13
19:13
さすがにコミットログが汚いので、きれいにしたい
-
Avatar
git rebase -iしてたらconflict起きた
23:52
mikiken@DESKTOP-CM4259U:~/compiler/9cc$ git rebase -i HEAD~25 +}
+
+
+
+
+
+
+
18:22
+
+
+
+
+ + + +
6.56 KB
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず、算術型の加減算のnode自体の型を全てintにすることで、ひとまず動くようになった
+
+
+
+
+
+
+
+
Avatar
+
+ +
origin/mastermasterの乖離がすごいことに
+
+
+
+
+
+
+
19:13
+
+
+ +
+
+
+
+
+
+
19:13
+
+
+
さすがにコミットログが汚いので、きれいにしたい
+
+
+
+
+
+
+
+
Avatar
+
+ +
git rebase -iしてたらconflict起きた
+
+
+
+
+
+
+
23:52
+
+
+
mikiken@DESKTOP-CM4259U:~/compiler/9cc$ git rebase -i HEAD~25 Auto-merging src/semantic_analysis.c CONFLICT (content): Merge conflict in src/semantic_analysis.c error: could not apply 59f3168... `ND_ADD`,`ND_SUB`でnodeそのものの型をちゃんとした @@ -1020,43 +5169,282 @@ "git add/rm <conflicted_files>", then run "git rebase --continue". You can instead skip this commit: run "git rebase --skip". To abort and get back to the state before "git rebase", run "git rebase --abort". -Could not apply 59f3168... `ND_ADD`,`ND_SUB`でnodeそのものの型をちゃんとした
-
Avatar
やったーきれいになったぞー
-
Avatar
というわけでようやく多次元配列の実装に着手していく
17:51
int arr[3][5];みたいな宣言に対して、arr[2][4] = 3;と書いたら、*(*(arr + 2) + 4) = 3;とパースするようには既になってるはず
17:52
とりあえずコンパイルを試みると、semantic_analysis() : 不正な加算を行うことはできませんというエラーが出るので、semantic_analysis()ND_ADDで両辺の型の不一致が起こっていそう
- -
Avatar
まず試しているテストケースはこれ -int main() { +Could not apply 59f3168... `ND_ADD`,`ND_SUB`でnodeそのものの型をちゃんとした
+
+
+
+
+
+
+
+
Avatar
+
+ +
やったーきれいになったぞー
+ +
+
+
+
+
+
+
+
Avatar
+
+ +
というわけでようやく多次元配列の実装に着手していく
+
+
+
+
+
+
+
17:51
+
+
+
int arr[3][5];みたいな宣言に対して、arr[2][4] = 3;と書いたら、*(*(arr + 2) + 4) = 3;とパースするようには既になってるはず
+
+
+
+
+
+
+
17:52
+
+
+
とりあえずコンパイルを試みると、semantic_analysis() : 不正な加算を行うことはできませんというエラーが出るので、semantic_analysis()ND_ADDで両辺の型の不一致が起こっていそう
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+
+
+
Avatar
+
+ +
まず試しているテストケースはこれ + int main() { int arr[3][5]; *(*(arr + 2) + 4) = 3; return *(*(arr + 2) + 4); -}
15:27
何も考えずに、./9cc a.cしたところ、semantic_analysis() : 不正な加算を行うことはできませんが出た
15:28
それはそう -(semantic_analysis()において、左辺に配列型、右辺がint型の場合の処理が入っていないので) (edited)
15:30
ということで、ND_ADDの処理で、左辺に配列型、右辺にint型が来た場合、サイズ調整用のnodeを付加するようなコードを追加した - // 左辺が配列型、右辺がint型の場合 +}
+
+
+
+
+
+
+
15:27
+
+
+
何も考えずに、./9cc a.cしたところ、semantic_analysis() : 不正な加算を行うことはできませんが出た
+
+
+
+
+
+
+
15:28
+
+
+
それはそう + (semantic_analysis()において、左辺に配列型、右辺がint型の場合の処理が入っていないので) (edited)
+
+
+
+
+
+
+
15:30
+
+
+
ということで、ND_ADDの処理で、左辺に配列型、右辺にint型が来た場合、サイズ調整用のnodeを付加するようなコードを追加した + // 左辺が配列型、右辺がint型の場合 if (is_array_type(node->lhs->type) && node->rhs->type->kind == TYPE_INT) { // node自体の型は左辺の型に合わせる node->type = node->lhs->type; Node *size_node = new_size_node(node->lhs->type->ptr_to); node->rhs = new_typed_binary(new_typed_node(new_type(TYPE_INT), new_node(ND_MUL)), size_node, node->rhs); return; - }
15:30
再び./9cc a.cすると、semantic_analysis() : ポインタでないものを間接参照することはできませんが出た
15:32
今までは1次元配列しかなかったので、ND_DEREFlhsには必ず(配列型がキャストされたことで生じた)ポインタ型が来ていたが、その前提が崩れた
15:32
ということで、ひとまずコメントアウトしといた
15:33
再びコンパイルを試みると、一部アセンブリが出力されるものの、途中でcodegen.cにおいてレジスタに値をセットできませんでしたが出てるな
-
Avatar
とりあえずgdbを走らせて確認したところ、内側のND_DEREFでレジスタに入っているアドレスを参照しようとしたところ、mov_memory_to_registerに配列型の扱いがないため、エラーが出ているっぽい - case ND_DEREF: + }
+
+
+
+
+
+
+
15:30
+
+
+
再び./9cc a.cすると、semantic_analysis() : ポインタでないものを間接参照することはできませんが出た
+
+
+
+
+
+
+
15:32
+
+
+
今までは1次元配列しかなかったので、ND_DEREFlhsには必ず(配列型がキャストされたことで生じた)ポインタ型が来ていたが、その前提が崩れた
+
+
+
+
+
+
+
15:32
+
+
+
ということで、ひとまずコメントアウトしといた
+
+
+
+
+
+
+
15:33
+
+
+
再びコンパイルを試みると、一部アセンブリが出力されるものの、途中でcodegen.cにおいてレジスタに値をセットできませんでしたが出てるな
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえずgdbを走らせて確認したところ、内側のND_DEREFでレジスタに入っているアドレスを参照しようとしたところ、mov_memory_to_registerに配列型の扱いがないため、エラーが出ているっぽい + case ND_DEREF: gen_expr(node->lhs); pop_addr(RDI); mov_memory_to_register(node->type, RAX, RDI); // この関数で「レジスタに値をセットできませんでした」が出てる push(node->type, RAX); - return;
19:50
ND_DEREFlhsの型が、配列型(=ポインタ型ではない)である場合、gen_expr(node->lhs);で生成されたアドレスの中身を参照する処理を飛ばさなければいけないのでは? (edited)
-
Avatar
現状出力されているアセンブリを手動で直したら動いた -そのアセンブリとの差分がこれ
14:29
現在は、例えばND_DEREFなどで左辺値と右辺値を区別せずにコード生成をしているが、左辺値としてアドレスを生成した場合は、アドレスの指す先をメモリから読む処理を飛ばすようにしないとダメそう (edited)
14:29
左辺値と右辺値で関数分けた方がいいかもしれない
-
Avatar
ということで、関数gen_expr()の分割を開始 (edited)
17:38
まずは二項演算子のコード生成の処理をgen_binary_operator_expr()という別関数に切り出した、これは簡単
17:39
そういえば、READMEのBNF全然更新してないなあ
17:42
a = b = 3;みたいな式を書いた場合、 - = + return;
+
+
+
+
+
+
+
19:50
+
+
+
ND_DEREFlhsの型が、配列型(=ポインタ型ではない)である場合、gen_expr(node->lhs);で生成されたアドレスの中身を参照する処理を飛ばさなければいけないのでは? (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
現状出力されているアセンブリを手動で直したら動いた + そのアセンブリとの差分がこれ
+
+
+ + + +
5.87 KB
+
+
+
+
+
+
+
+
+
14:29
+
+
+
現在は、例えばND_DEREFなどで左辺値と右辺値を区別せずにコード生成をしているが、左辺値としてアドレスを生成した場合は、アドレスの指す先をメモリから読む処理を飛ばすようにしないとダメそう (edited)
+
+
+
+
+
+
+
14:29
+
+
+
左辺値と右辺値で関数分けた方がいいかもしれない
+
+
+
+
+
+
+
+
Avatar
+
+ +
ということで、関数gen_expr()の分割を開始 (edited)
+
+
+
+
+
+
+
17:38
+
+
+
まずは二項演算子のコード生成の処理をgen_binary_operator_expr()という別関数に切り出した、これは簡単
+
+
+
+
+
+
+
17:39
+
+
+
そういえば、READMEのBNF全然更新してないなあ
+
+
+
+
+
+
+
17:42
+
+
+
a = b = 3;みたいな式を書いた場合、 + = / \ a = / \ b 3 -という構文木になる
-
Avatar
まず、gen_expr()をこんな感じにした -void gen_expr(Node *node) { + という構文木になる
+
+
+
+
+
+
+
+
Avatar
+
+ +
まず、gen_expr()をこんな感じにした + void gen_expr(Node *node) { switch (node->kind) { case ND_ASSIGN: gen_addr(node->lhs); // 左辺のアドレスをスタックにpush @@ -1070,8 +5458,28 @@ gen_expr_as_rvalue(node); return; } -}
18:36
ND_ASSIGNの場合は特別視し、それ以外は全て右辺値のコード生成として扱う
18:37
そして、gen_addr()がこんな感じになってる -void gen_addr(Node *node) { +}
+
+
+
+
+
+
+
18:36
+
+
+
ND_ASSIGNの場合は特別視し、それ以外は全て右辺値のコード生成として扱う
+
+
+
+
+
+
+
18:37
+
+
+
そして、gen_addr()がこんな感じになってる + void gen_addr(Node *node) { switch (node->kind) { case ND_LVAR: printf(" lea rdi, [rbp-%d]\n", node->offset); // アドレスは8byte @@ -1092,19 +5500,89 @@ error("nodeのアドレスを生成することができません"); return; } -} (edited)
18:38
これで、gen_expr_as_lvalueの中を書いていけば、いい感じになりそう
18:42
現状gen_expr_as_lvalue()のcallerはND_DEREFのはずなので、例えばND_NUMの処理は削っても大丈夫そう(?) (edited)
-
Avatar
メモ gen_expr_as_lvalueの中身を検討
-
Avatar
こういうコードは合法 -int main() { +} (edited)
+
+
+
+
+
+
+
18:38
+
+
+
これで、gen_expr_as_lvalueの中を書いていけば、いい感じになりそう
+
+
+
+
+
+
+
18:42
+
+
+
現状gen_expr_as_lvalue()のcallerはND_DEREFのはずなので、例えばND_NUMの処理は削っても大丈夫そう(?) (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
メモ gen_expr_as_lvalueの中身を検討
+
+
+
+
+
+
+
+
Avatar
+
+ +
こういうコードは合法 + int main() { int arr[2]; int *p; *(p = arr) = 3; return arr[0]; -}
-
Avatar
とりあえず、いい感じに関数分割できたっぽいんだが、make testしたら -[FAIL] {int x; int *y; x=3; y=&x; *y=5; return x; } => 5 expected, but got 3 -になった
-
Avatar
gdbで見た感じ、*y = 5;の処理がうまくいってない
07:31
gdb-peda$ c +}
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず、いい感じに関数分割できたっぽいんだが、make testしたら + [FAIL] {int x; int *y; x=3; y=&x; *y=5; return x; } => 5 expected, but got 3 + になった
+
+
+
+
+
+
+
+
Avatar
+
+ +
gdbで見た感じ、*y = 5;の処理がうまくいってない
+
+
+
+
+
+
+
07:31
+
+
+
gdb-peda$ c Continuing. [----------------------------------registers-----------------------------------] RAX: 0x5 @@ -1149,9 +5627,39 @@ Breakpoint 2, main () at tmp.s:37 37 mov DWORD PTR [rdi], eax # ここでおかしくなった gdb-peda$ p/d (int)*0x7fffffffdc6c -$1 = 3
07:32
この状態で、本来ならrdiにxのアドレス(0x7fffffffdc6c)がストアされてないとおかしいはず
07:33
おそらく間接参照のコードがうまく生成できてない
-
Avatar
gen_addr()ND_DEREFのところに数行追加したら動いた -void gen_addr(Node *node) { +$1 = 3
+
+
+
+
+
+
+
07:32
+
+
+
この状態で、本来ならrdiにxのアドレス(0x7fffffffdc6c)がストアされてないとおかしいはず
+
+
+
+
+
+
+
07:33
+
+
+
おそらく間接参照のコードがうまく生成できてない
+
+
+
+
+
+
+
+
Avatar
+
+ +
gen_addr()ND_DEREFのところに数行追加したら動いた + void gen_addr(Node *node) { switch (node->kind) { case ND_LVAR: printf(" lea rdi, [rbp-%d]\n", node->offset); // アドレスは8byte @@ -1175,31 +5683,274 @@ error("nodeのアドレスを生成することができません"); return; } -} (edited)
-
Avatar
続いてmake testすると、このテストケースでセグフォした -int global_variable1; +} (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
続いてmake testすると、このテストケースでセグフォした + int global_variable1; int global_variable2[20]; int global_variable_test3() { global_variable1 = 3; global_variable2[2] = 2; return global_variable1 + global_variable2[2]; -}
-
Avatar
グローバル変数の実装も、gccの出力を真似してるだけで中身理解してないので、今調べよう
11:42
グローバル変数を扱うアセンブリを書いているときに、最初よく分からずに非PIEでのコードを書いてしまっていた。 -最近のLinuxディストリビューションにインストールされているGCCはデフォルトでPIEとしてリンクしようとするため、非PI...
-
Avatar
.LC0: - .string "hello" +}
+
+
+
+
+
+
+
+
Avatar
+
+ +
グローバル変数の実装も、gccの出力を真似してるだけで中身理解してないので、今調べよう
+
+
+
+
+
+
+
11:42
+
+
+ +
+
+
+
+
+ +
+
グローバル変数を扱うアセンブリを書いているときに、最初よく分からずに非PIEでのコードを書いてしまっていた。 + 最近のLinuxディストリビューションにインストールされているGCCはデフォルトでPIEとしてリンクしようとするため、非PI...
+
+
+
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+ +
+
.LC0: + .string "hello" -main: - 〜〜〜 - lea rax, .LC0[rip] + main: + 〜〜〜 + lea rax, .LC0[rip] -が上手く行かなくて死亡してる
12:30
int x;みたいな宣言に対して、従来はmov rax, OFFSET FLAT:xのように書くことでグローバル変数のアドレスをレジスタにストアしていた
-
Avatar
このように変数のアドレスを決め打ちする記法は、プログラムの移植性・セキュリティ上の問題から、現在ではあまり使われなくなっている (edited)
-
Avatar
そこで、アドレスを決め打ちせず、プログラムがメモリ上のどのアドレスに配置されても動作するような実行形式として、Position-Independent Executable という形式が使われている
12:55
このページにある図 -https://tanakamura.github.io/pllp/docs/pic_pie.html (edited)
12:57
13:02
このようにプログラムカウンタからのオフセットでアドレスを指定する方式を、PC相対アドレッシングという
13:03
さっきの例でいくと、lea rdi, x[rip]のように書くとリンカがグローバル変数xripとのオフセットを自動計算してくれるということらしい (edited)
13:07
ということでセグフォの原因を探っていく
13:07
上のテストケースで、配列の要素に代入しているところglobal_variable2[2] = 2;でセグフォしてる
13:08
さっきからの流れでいくと、間接参照の実装がまずそう
-
Avatar
さっきgen_addr()に追加した処理によって、node->lhsが変数以外の場合も、アドレスの指す先を参照しようとした結果セグフォしてるっぽい
13:41
なので、さっき追加した処理が発動するのを、node->lhsに変数が来た場合に限定すれば良さそう
-
Avatar
このテストケースは通るようになった
15:12
次は、このテストケースでセグフォしてる
15:13
int alloc4_array[5]; + が上手く行かなくて死亡してる
+
+
+
+ +
+
+
+
+
+
+
+
+
12:30
+
+
+
int x;みたいな宣言に対して、従来はmov rax, OFFSET FLAT:xのように書くことでグローバル変数のアドレスをレジスタにストアしていた
+
+
+
+
+
+
+
+
Avatar
+
+ +
このように変数のアドレスを決め打ちする記法は、プログラムの移植性・セキュリティ上の問題から、現在ではあまり使われなくなっている (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
そこで、アドレスを決め打ちせず、プログラムがメモリ上のどのアドレスに配置されても動作するような実行形式として、Position-Independent Executable という形式が使われている
+
+
+
+ +
+
+
+
12:55
+
+
+
このページにある図 + https://tanakamura.github.io/pllp/docs/pic_pie.html (edited)
+
+
+
+
+
+
+
12:57
+
+
+ +
+
+
+
+
+
+
13:02
+
+
+
このようにプログラムカウンタからのオフセットでアドレスを指定する方式を、PC相対アドレッシングという
+
+
+
+
+
+
+
13:03
+
+
+
さっきの例でいくと、lea rdi, x[rip]のように書くとリンカがグローバル変数xripとのオフセットを自動計算してくれるということらしい (edited)
+
+
+
+
+
+
+
13:07
+
+
+
ということでセグフォの原因を探っていく
+
+
+
+
+
+
+
13:07
+
+
+
上のテストケースで、配列の要素に代入しているところglobal_variable2[2] = 2;でセグフォしてる
+
+
+
+
+
+
+
13:08
+
+
+
さっきからの流れでいくと、間接参照の実装がまずそう
+
+
+
+
+
+
+
+
Avatar
+
+ +
さっきgen_addr()に追加した処理によって、node->lhsが変数以外の場合も、アドレスの指す先を参照しようとした結果セグフォしてるっぽい
+
+
+
+
+
+
+
13:41
+
+
+
なので、さっき追加した処理が発動するのを、node->lhsに変数が来た場合に限定すれば良さそう
+
+
+
+
+
+
+
+
Avatar
+
+ +
このテストケースは通るようになった
+
+
+
+
+
+
+
15:12
+
+
+
次は、このテストケースでセグフォしてる
+
+
+
+
+
+
+
15:13
+
+
+
int alloc4_array[5]; int alloc4(int **p, int a, int b, int c, int d) { *p = alloc4_array; @@ -1217,7 +5968,37 @@ int *q; q = p + 2; return *q; -}
15:13
gdbを走らせたが、どこでセグフォしたかが検出できない
15:13
ということで、バグが再現しそうな小さいテストケースを作った
15:14
int arr[3]; +}
+
+
+
+
+
+
+
15:13
+
+
+
gdbを走らせたが、どこでセグフォしたかが検出できない
+
+
+
+
+
+
+
15:13
+
+
+
ということで、バグが再現しそうな小さいテストケースを作った
+
+
+
+
+
+
+
15:14
+
+
+
int arr[3]; int alloc(int **p) { *p = arr; @@ -1228,9 +6009,49 @@ int main() { int *p; return alloc(&p); -}
15:14
ちゃんとセグフォしてくれた
-
23:01
(*p)[1] = 3;のコード生成がうまくいってない
23:02
該当するアセンブリ - lea rdi, [rbp-8] +}
+
+
+
+
+
+
+
15:14
+
+
+
ちゃんとセグフォしてくれた
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+
+
23:01
+
+
+
(*p)[1] = 3;のコード生成がうまくいってない
+
+
+
+
+
+
+
23:02
+
+
+
該当するアセンブリ + lea rdi, [rbp-8] push rdi push 4 push 1 @@ -1247,41 +6068,320 @@ pop rdi mov DWORD PTR [rdi], eax push rax - pop rax (edited)
23:03
ここで本来は、オフセットを加算する前に0x55~8010のアドレスがスタックに積まれていないといけないはず
23:05
-
Avatar
代入式でnode->lhs->kind == ND_DEREFの場合、gen_exprgen_addrgen_expr_as_lvalueの経路で呼ばれるが、これがまずそう
23:27
gen_addrは単に、変数や文字列リテラルのアドレスを生成する処理に集中したほうが良さそう
23:28
直そうとしたら、nodeのアドレスを生成することができませんが出た
23:29
pointer.cのコード生成でエラーが出てる
-
Avatar
とりあえず、コンパイルは通るようになった
23:41
以前、同じテストケースでセグフォしてる
23:41
ただ、セグフォする位置が変わった
-
Avatar
もう一回分間接参照する必要があるが、そのコードが生成されていない -(具体的には、0x~dc98のアドレスはスタックに積まれているが、それを間接参照して0x~8010のアドレスをスタックに積む必要がある) (edited)
- -
Avatar
混乱しているので一旦整理
15:03
int x = 2; + pop rax (edited)
+
+
+
+
+
+
+
23:03
+
+
+
ここで本来は、オフセットを加算する前に0x55~8010のアドレスがスタックに積まれていないといけないはず
+
+
+
+
+
+
+
23:05
+
+
+ +
+
+
+
+
+
+
+
Avatar
+
+ +
代入式でnode->lhs->kind == ND_DEREFの場合、gen_exprgen_addrgen_expr_as_lvalueの経路で呼ばれるが、これがまずそう
+
+
+
+
+
+
+
23:27
+
+
+
gen_addrは単に、変数や文字列リテラルのアドレスを生成する処理に集中したほうが良さそう
+
+
+
+
+
+
+
23:28
+
+
+
直そうとしたら、nodeのアドレスを生成することができませんが出た
+
+
+
+
+
+
+
23:29
+
+
+
pointer.cのコード生成でエラーが出てる
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず、コンパイルは通るようになった
+
+
+
+
+
+
+
23:41
+
+
+
以前、同じテストケースでセグフォしてる
+
+
+
+
+
+
+
23:41
+
+
+
ただ、セグフォする位置が変わった
+
+
+
+
+
+
+
+
Avatar
+
+ +
もう一回分間接参照する必要があるが、そのコードが生成されていない + (具体的には、0x~dc98のアドレスはスタックに積まれているが、それを間接参照して0x~8010のアドレスをスタックに積む必要がある) (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ + +
+
+
+
+
+
+
+
Avatar
+
+ +
混乱しているので一旦整理
+
+
+
+
+
+
+
15:03
+
+
+
int x = 2; int *p; p = &x; // 1 int y = *p; // 2 -*p = 3; // 3
15:04
1については、 -
1. pそのもののアドレスを生成 -
し、そのアドレスに&xをストアすればいい
(edited)
15:04
2については、 -
1. pそのもののアドレスを生成 -2. pにストアされているアドレス(=xのアドレス)を生成 -3. pにストアされているアドレスの中身(=xの値)を生成 -
の3ステップを行い、その値をyにストアすればいい
(edited)
15:08
3については、 -
1. pそのもののアドレスを生成 -2. pにストアされてるアドレス(=xのアドレス)を生成 -し、そのアドレスに3をストアすればいい
-
Avatar
そして解決へ -完全制覇p.195にめっちゃいい説明あった (edited)
16:47
多次元配列のように見えるものは「配列の配列」です。 -この「多次元配列」(もどき)は、通常hoge[i][j]のようにアクセスするわけですが、このときに何が起きているかを以下で説明します。 -
int hoge[3][5]; -
という「配列の配列」があり、それをhoge[i][j]という形でアクセスするとします(FIg. 3-15参照)。 -① hogeの型は「intの配列(要素数5)の配列(要素数3)」である。 -② だが、式の中なので、配列はポインタに読み替えられる。よって、hogeの型は「intの配列(要素数5)へのポインタ」となる。 -③ hoge[i]は、*(hoge + i)のシンタックスシュガーである。 -③-① ポインタにi加算することは、そのポインタが指す型のサイズ×iだけ、ポインタを進めることを意味する。hogeの指す先の型は「intの配列(要素数5)」であるから、hoge + iでは、sizeof(int [5]) * iだけ進む。 -③-② *(hoge + i)*により、ポインタが1つ剥がされる。*(hoge + i)の型は「intの配列(要素数5)」となる。 -③-③ が、式の中なので、配列がポインタに読み替えられる。*(hoge + i)の最終的な型は「intへのポインタ」となる。 -④ (*(hoge + i))[j]は、*(*(hoge + i) + j)に等しい。したがって、(*(hoge + i))[j]は「intへのポインタにjだけ加算したアドレスの内容」であり、型はintである。
(edited)
-
Avatar
この説明を読んで、コード生成で配列型が出てきている時点で間違っている(ポインタにキャストされているべき)ことが分かった
23:59
cast_array_to_pointer()が、node->kindND_LVARND_GVARじゃないと発動しないようになっていたので、この条件を削除した
00:00
すると、多次元配列のコードが動くようになった
🎉 1
-
Avatar
多次元配列が動くようになったので、ライフゲームをコンパイルしてみたいな
16:13
GitHub Gist: instantly share code, notes, and snippets.
16:14
二次元配列の初期化がまだ書けない - int grid[SIZE][SIZE] = { +*p = 3; // 3
+
+
+
+
+
+
+
15:04
+
+
+
1については、 +
+
+
1. pそのもののアドレスを生成 +
+
し、そのアドレスに&xをストアすればいい +
(edited)
+
+
+
+
+
+
+
15:04
+
+
+
2については、 +
+
+
1. pそのもののアドレスを生成 + 2. pにストアされているアドレス(=xのアドレス)を生成 + 3. pにストアされているアドレスの中身(=xの値)を生成 +
+
の3ステップを行い、その値をyにストアすればいい +
(edited)
+
+
+
+
+
+
+
15:08
+
+
+
3については、 +
+
+
1. pそのもののアドレスを生成 + 2. pにストアされてるアドレス(=xのアドレス)を生成 + し、そのアドレスに3をストアすればいい
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
そして解決へ + 完全制覇p.195にめっちゃいい説明あった (edited)
+
+
+
+
+
+
+
16:47
+
+
+
+
+
+
多次元配列のように見えるものは「配列の配列」です。 + この「多次元配列」(もどき)は、通常hoge[i][j]のようにアクセスするわけですが、このときに何が起きているかを以下で説明します。 +
+
int hoge[3][5]; +
+
+
という「配列の配列」があり、それをhoge[i][j]という形でアクセスするとします(FIg. 3-15参照)。 + ① hogeの型は「intの配列(要素数5)の配列(要素数3)」である。 + ② だが、式の中なので、配列はポインタに読み替えられる。よって、hogeの型は「intの配列(要素数5)へのポインタ」となる。 + ③ hoge[i]は、*(hoge + i)のシンタックスシュガーである。 + ③-① ポインタにi加算することは、そのポインタが指す型のサイズ×iだけ、ポインタを進めることを意味する。hogeの指す先の型は「intの配列(要素数5)」であるから、hoge + iでは、sizeof(int [5]) * iだけ進む。 + ③-② *(hoge + i)*により、ポインタが1つ剥がされる。*(hoge + i)の型は「intの配列(要素数5)」となる。 + ③-③ が、式の中なので、配列がポインタに読み替えられる。*(hoge + i)の最終的な型は「intへのポインタ」となる。 + ④ (*(hoge + i))[j]は、*(*(hoge + i) + j)に等しい。したがって、(*(hoge + i))[j]は「intへのポインタにjだけ加算したアドレスの内容」であり、型はintである。
+
+
(edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
この説明を読んで、コード生成で配列型が出てきている時点で間違っている(ポインタにキャストされているべき)ことが分かった
+
+
+
+
+
+
+
23:59
+
+
+
cast_array_to_pointer()が、node->kindND_LVARND_GVARじゃないと発動しないようになっていたので、この条件を削除した
+
+
+
+
+
+
+
00:00
+
+
+
すると、多次元配列のコードが動くようになった
+
+
🎉 1
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
多次元配列が動くようになったので、ライフゲームをコンパイルしてみたいな
+
+
+
+
+
+
+
16:13
+
+
+ +
+
+
+
+
+ +
+
GitHub Gist: instantly share code, notes, and snippets.
+
+
+ +
+
+
+
+
+
+
+
+
+
16:14
+
+
+
二次元配列の初期化がまだ書けない + int grid[SIZE][SIZE] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, @@ -1301,8 +6401,18 @@ {0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
16:15
ということで、for文で代用しよう - // 上のような初期化式がまだ書けないので、for文で代用 + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
+
+
+
+
+
+
+
16:15
+
+
+
ということで、for文で代用しよう + // 上のような初期化式がまだ書けないので、for文で代用 int grid[20][20]; for (int a = 0; a < 20; a++) for (int b = 0; b < 20; b++) @@ -1316,36 +6426,384 @@ grid[15][5] = 1; grid[16][5] = 1; grid[17][1] = 1; - grid[17][4] = 1;
16:17
関数定義の引数にまだ配列が書けなかった気がする -int count_nbr(int grid[20][20], int i, int j, int size) { + grid[17][4] = 1;
+
+
+
+
+
+
+
16:17
+
+
+
関数定義の引数にまだ配列が書けなかった気がする + int count_nbr(int grid[20][20], int i, int j, int size) { …(中略)… -}
16:17
これをコンパイルできるようにしなければ
-
Avatar
えーと、Cにおいて関数定義の引数に配列型を書いた場合にどうなるかというと、 (edited)
23:23
型分類としての配列が、ポインタに読み替えられる
23:23
ただし、このルールが適用されるのは最外周の配列(型分類としての配列)のみであることに注意
23:25
例えば、 -void func(int a[]) {}は -void func(int *a) {}に読み替えられる
23:28
二次元配列の場合、 -void func(int a[][5]) {}とか -void func(int a[3][5]) {}とかは、 -void func((*a)[5]) {}に読み替えられる
23:28
多次元配列の場合も同様
23:29
なので、とりあえず -void func((*a)[5]) {} -のような型宣言をパースできるようにする必要がある
-
Avatar
いよいよCの✝️ 型宣言✝️ のパースに着手していく
-
Avatar
たぶん関数ポインタも、この際パースできるようにした方が見通しは良いんだろうなあ
-
Avatar
とりあえず型宣言のパースの処理をまるごと書き直している
19:10
TypeKindTYPE_FUNCというのを追加したので、変数と関数の型を統一的に扱えるようになりそう
19:11
ただ、そのためにはstruct Typeに対してstruct Functionとをマッピングできるような仕組みを用意する必要がありそう
-
Avatar
やっぱり、identifierの前後に情報が分散しているのがかなりめんどい😇
14:03
とりあえず、構造体Typeが関数の引数リストを持てるようにしたほうが良さそう (edited)
-
Avatar
型宣言で、引数の名前を省略できる記法はとりあえずサポートしないことにする
17:56
これやりだすと、さすがに頭爆発する
-
Avatar
とりあえず、これをパースできるようにしたい -void (*signal(int sig, void (*func)(int)))(int);
-
18:36
これを見ると分かるけど、何重にネストしていたとしても識別子の直後を見れば、(型の図における)最も外側の型が分かるというのが、ポイントになりそう (edited)
18:36
例えば、int (*x())() // func() * func() int (edited)
18:37
int (*x[])() // [] * func() int (edited)
18:37
int (**x)() // * * func() int (edited)
18:37
なので、とにかく識別子を見たら大枠の型を確定させるように処理を組んでいく必要がある
18:38
なので、parse_nested_typeの処理を書いていく上では、識別子の処理を最初に書いていくのが近道だと思う
-
Avatar
たぶんここを起点に処理を考えて、それに合うように周辺の処理を書き直す方がいい
-
Avatar
型がネストしている以上、nested_typeptr_toが存在するはず
-
Avatar
一応型宣言のparserができた
10:21
pushした
10:23
あとは、parser側の実装を型宣言のparserに合わせる必要がある
-
Avatar
コンパイルエラーが出ている箇所については、型宣言のparserのインターフェースに合わせた
16:48
い つ も の -mikiken@DESKTOP-CM4259U:~/compiler/9cc$ make test +}
+
+
+
+
+
+
+
16:17
+
+
+
これをコンパイルできるようにしなければ
+
+
+
+
+
+
+
+
Avatar
+
+ +
えーと、Cにおいて関数定義の引数に配列型を書いた場合にどうなるかというと、 (edited)
+
+
+
+
+
+
+
23:23
+
+
+
型分類としての配列が、ポインタに読み替えられる
+
+
+
+
+
+
+
23:23
+
+
+
ただし、このルールが適用されるのは最外周の配列(型分類としての配列)のみであることに注意
+
+
+
+
+
+
+
23:25
+
+
+
例えば、 + void func(int a[]) {}は + void func(int *a) {}に読み替えられる
+
+
+
+
+
+
+
23:28
+
+
+
二次元配列の場合、 + void func(int a[][5]) {}とか + void func(int a[3][5]) {}とかは、 + void func((*a)[5]) {}に読み替えられる
+
+
+
+
+
+
+
23:28
+
+
+
多次元配列の場合も同様
+
+
+
+
+
+
+
23:29
+
+
+
なので、とりあえず + void func((*a)[5]) {} + のような型宣言をパースできるようにする必要がある
+
+
+
+
+
+
+
+
Avatar
+
+ +
いよいよCの✝️ 型宣言✝️ のパースに着手していく
+
+
+
+
+
+
+
+
Avatar
+
+ +
たぶん関数ポインタも、この際パースできるようにした方が見通しは良いんだろうなあ
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず型宣言のパースの処理をまるごと書き直している
+
+
+
+
+
+
+
19:10
+
+
+
TypeKindTYPE_FUNCというのを追加したので、変数と関数の型を統一的に扱えるようになりそう
+
+
+
+
+
+
+
19:11
+
+
+
ただ、そのためにはstruct Typeに対してstruct Functionとをマッピングできるような仕組みを用意する必要がありそう
+
+
+
+
+
+
+
+
Avatar
+
+ +
やっぱり、identifierの前後に情報が分散しているのがかなりめんどい😇
+
+
+
+
+
+
+
14:03
+
+
+
とりあえず、構造体Typeが関数の引数リストを持てるようにしたほうが良さそう (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
型宣言で、引数の名前を省略できる記法はとりあえずサポートしないことにする
+
+
+
+
+
+
+
17:56
+
+
+
これやりだすと、さすがに頭爆発する
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず、これをパースできるようにしたい + void (*signal(int sig, void (*func)(int)))(int);
+
+
+
+
+
+ +
+
+
+
18:36
+
+
+
これを見ると分かるけど、何重にネストしていたとしても識別子の直後を見れば、(型の図における)最も外側の型が分かるというのが、ポイントになりそう (edited)
+
+
+
+
+
+
+
18:36
+
+
+
例えば、int (*x())() // func() * func() int (edited)
+
+
+
+
+
+
+
18:37
+
+
+
int (*x[])() // [] * func() int (edited)
+
+
+
+
+
+
+
18:37
+
+
+
int (**x)() // * * func() int (edited)
+
+
+
+
+
+
+
18:37
+
+
+
なので、とにかく識別子を見たら大枠の型を確定させるように処理を組んでいく必要がある
+
+
+
+
+
+
+
18:38
+
+
+
なので、parse_nested_typeの処理を書いていく上では、識別子の処理を最初に書いていくのが近道だと思う
+
+
+
+
+
+
+
+
Avatar
+
+ +
たぶんここを起点に処理を考えて、それに合うように周辺の処理を書き直す方がいい
+
+
+
+
+
+
+
+
Avatar
+
+ +
型がネストしている以上、nested_typeptr_toが存在するはず
+
+
+
+
+
+
+
+
Avatar
+
+ +
一応型宣言のparserができた
+
+
+
+
+
+
+
10:21
+
+
+
pushした
+
+
+
+ +
+
+
+
10:23
+
+
+
あとは、parser側の実装を型宣言のparserに合わせる必要がある
+
+
+
+
+
+
+
+
Avatar
+
+ +
コンパイルエラーが出ている箇所については、型宣言のparserのインターフェースに合わせた
+
+
+
+
+
+
+
16:48
+
+
+
い つ も の + mikiken@DESKTOP-CM4259U:~/compiler/9cc$ make test ./test/test_driver.sh ./test/test_driver.sh: line 3: 25339 Segmentation fault ../9cc ${file%.*}.c > ${file%.*}.s ./test/test_driver.sh: line 3: 25342 Segmentation fault ../9cc ${file%.*}.c > ${file%.*}.s @@ -1360,12 +6818,102 @@ collect2: error: ld returned 1 exit status ./test/test_driver.sh: line 9: ./tmp: No such file or directory rm: cannot remove 'tmp': No such file or directory -make: *** [Makefile:17: test] Error 1
16:53
セグフォ解消まつりを開催
16:56
とりあえず、型宣言のパースでバグってるので直す (edited)
16:58
そもそも型宣言のパーサーのテスト書いた方が絶対いいんよな
-
Avatar
Token *identkindTK_SEMICOLONで草
17:06
-
Avatar
例によってshallow copyとdeep copyを間違えてた
17:15
Token *ident = tok; -↓ -Token ident = *tok;
-
Avatar
FuncDeclarationを確保したら、引数のfunc_typeが書き換わるとかいう謎の現象発生した
17:28
void new_func_declaration(Type *func_type) { +make: *** [Makefile:17: test] Error 1
+
+
+
+
+
+
+
16:53
+
+
+
セグフォ解消まつりを開催
+
+
+
+
+
+
+
16:56
+
+
+
とりあえず、型宣言のパースでバグってるので直す (edited)
+
+
+
+
+
+
+
16:58
+
+
+
そもそも型宣言のパーサーのテスト書いた方が絶対いいんよな
+
+
+
+
+
+
+
+
Avatar
+
+ +
Token *identkindTK_SEMICOLONで草
+
+
+
+
+
+
+
17:06
+
+
+ +
+
+
+
+
+
+
+
Avatar
+
+ +
例によってshallow copyとdeep copyを間違えてた
+
+
+
+
+
+
+
17:15
+
+
+
Token *ident = tok; + ↓ + Token ident = *tok;
+
+
+
+
+
+
+
+
Avatar
+
+ +
FuncDeclarationを確保したら、引数のfunc_typeが書き換わるとかいう謎の現象発生した
+
+
+
+
+
+
+
17:28
+
+
+
void new_func_declaration(Type *func_type) { FuncDeclaration *func_dec = calloc(1, sizeof(FuncDeclaration)); func_dec->ret_type = func_type->return_type; func_dec->name = calloc(func_type->ident->len + 1, sizeof(char)); @@ -1373,17 +6921,83 @@ func_dec->len = func_type->ident->len; func_dec->next = func_declaration_list; func_declaration_list = func_dec; -}
-
Avatar
Avatar
mikiken
FuncDeclarationを確保したら、引数のfunc_typeが書き換わるとかいう謎の現象発生した
デバッガの挙動的に、またshallow copyとdeep copyの間違いっぽい? (edited)
-
Avatar
あ〜、なるほど - // 識別子を記録 +}
+
+
+
+
+
+
+
+
+
Avatar +
+
+
Avatar +
mikiken
+
FuncDeclarationを確保したら、引数のfunc_typeが書き換わるとかいう謎の現象発生した
+
+ +
デバッガの挙動的に、またshallow copyとdeep copyの間違いっぽい? (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
あ〜、なるほど + // 識別子を記録 Token ident = *tok; -identがスタック領域に確保されてるので、関数を抜けると消滅してしまい、上のような謎挙動を引き起こしている (edited)
20:26
ということでidentcallocすれば大丈夫なはず - // 識別子を記録 + identがスタック領域に確保されてるので、関数を抜けると消滅してしまい、上のような謎挙動を引き起こしている (edited)
+
+
+
+
+
+
+
20:26
+
+
+
ということでidentcallocすれば大丈夫なはず + // 識別子を記録 Token *ident = calloc(1, sizeof(Token)); - *ident = *tok; (edited)
20:27
お、セグフォが消えた
-
Avatar
コンパイルが通るように色々修正
23:56
簡単なテストケース -int printf(); + *ident = *tok; (edited)
+
+
+
+
+
+
+
20:27
+
+
+
お、セグフォが消えた
+
+
+
+
+
+
+
+
Avatar
+
+ +
コンパイルが通るように色々修正
+
+
+
+
+
+
+
23:56
+
+
+
簡単なテストケース + int printf(); int func(int x) { return x; @@ -1391,7 +7005,47 @@ int main() { return func(3); -}
23:56
とりあえず、これのコンパイルが通るようになった
23:56
ただ、実行結果がおかしい
23:57
正しい出力との差分をとってみた
23:57
.intel_syntax noprefix .intel_syntax noprefix +}
+
+
+
+
+
+
+
23:56
+
+
+
とりあえず、これのコンパイルが通るようになった
+
+
+
+
+
+
+
23:56
+
+
+
ただ、実行結果がおかしい
+
+
+
+
+
+
+
23:57
+
+
+
正しい出力との差分をとってみた
+
+
+
+
+
+
+
23:57
+
+
+
.intel_syntax noprefix .intel_syntax noprefix .data .data @@ -1414,24 +7068,194 @@ mov rsp, rbp mov rsp, rbp pop rbp pop rbp ret ret -(以下略)
23:58
引数をローカル変数にコピーする処理が抜けてそう
00:02
いや、むしろそれはできてて、引数リストの中身が入ってない
-
Avatar
いろいろ実装が噛み合ってないところを修正
-
Avatar
いろいろ変えたら今度はローカル変数のパースで事故ってる
12:24
ローカル変数のoffsetを計算しようとしてるのに、lvar_list_tailがないって言われるっぽい
-
Avatar
arith.c +(以下略)
+
+
+
+
+
+
+
23:58
+
+
+
引数をローカル変数にコピーする処理が抜けてそう
+
+
+
+
+
+
+
00:02
+
+
+
いや、むしろそれはできてて、引数リストの中身が入ってない
+
+
+
+
+
+
+
+
Avatar
+
+ +
いろいろ実装が噛み合ってないところを修正
+
+
+
+
+
+
+
+
Avatar
+
+ +
いろいろ変えたら今度はローカル変数のパースで事故ってる
+
+
+
+
+
+
+
12:24
+
+
+
ローカル変数のoffsetを計算しようとしてるのに、lvar_list_tailがないって言われるっぽい
+
+
+
+
+
+
+
+
Avatar
+
+ +
arith.c control_statement.c pointer.c string.c variable.c test.c -のコンパイルは通るようになった (edited)
16:14
次にarray.cを試す
16:15
セグフォ(定期)
16:15
配列型をパースするときに、構造体Typeに識別子を記録紙忘れてた
16:16
次、意味解析でsemantic_analysis(): ポインタでないものを間接参照することはできませんが出た
16:16
例の多次元配列のテストケースでコケてる -int array_test17() { + のコンパイルは通るようになった (edited)
+
+
+
+
+
+
+
16:14
+
+
+
次にarray.cを試す
+
+
+
+
+
+
+
16:15
+
+
+
セグフォ(定期)
+
+
+
+
+
+
+
16:15
+
+
+
配列型をパースするときに、構造体Typeに識別子を記録紙忘れてた
+
+
+
+
+
+
+
16:16
+
+
+
次、意味解析でsemantic_analysis(): ポインタでないものを間接参照することはできませんが出た
+
+
+
+
+
+
+
16:16
+
+
+
例の多次元配列のテストケースでコケてる + int array_test17() { int arr[3][5]; arr[2][4] = 21; return arr[2][4]; -}
16:18
とりあえず型宣言のパースのところをステップ実行してみて、思った通りに多次元配列がパースできてるか確認した方がよさそう
-
Avatar
なんか多次元配列の型宣言が上手くパースできてなくて、配列の配列が表現できていない
-
Avatar
parse_suffix_type()parse_outermost_type()の実装がまずかったので修正
19:58
function.c以外はコンパイルできるようになった
20:02
function.cの関数ポインタの宣言のテストケースをコメントアウトしたら、"コンパイルは通った"
20:02
なお実行結果 -mikiken@DESKTOP-CM4259U:~/compiler/9cc$ make test +}
+
+
+
+
+
+
+
16:18
+
+
+
とりあえず型宣言のパースのところをステップ実行してみて、思った通りに多次元配列がパースできてるか確認した方がよさそう
+
+
+
+
+
+
+
+
Avatar
+
+ +
なんか多次元配列の型宣言が上手くパースできてなくて、配列の配列が表現できていない
+
+
+
+
+
+
+
+
Avatar
+
+ +
parse_suffix_type()parse_outermost_type()の実装がまずかったので修正
+
+
+
+
+
+
+
19:58
+
+
+
function.c以外はコンパイルできるようになった
+
+
+
+
+
+
+
20:02
+
+
+
function.cの関数ポインタの宣言のテストケースをコメントアウトしたら、"コンパイルは通った"
+
+
+
+
+
+
+
20:02
+
+
+
なお実行結果 + mikiken@DESKTOP-CM4259U:~/compiler/9cc$ make test ./test/test_driver.sh [PASS] 0 => 21993 [PASS] 42 => 21993 @@ -1478,24 +7302,170 @@ [PASS] comma_opetator_test1((1, 2), (3, 4)) => 21993 All arithmetical test cases have passed. -./test/test_driver.sh: line 9: 3231 Segmentation fault ./tmp
-
Avatar
正しく動いていた段階のコミットに戻して、差分をとってみた (edited)
10:29
10:33
ローカル変数のoffsetの計算が壊れているのが原因っぽい (edited)
-
Avatar
calc_lvar_offsetで直前のローカル変数をうまく渡せてなさそう
-
Avatar
引数をローカル変数のリストにコピーする処理を書き忘れたりしていたのを修正
17:35
ひとまず、全てのテストが通るようになった
17:35
関数ポインタを含むテストケースが正しくパースできていない疑惑がある (edited)
-
Avatar
型宣言の各関数の責務を明確にしたほうが良さそう -Type *parse_base_type(Token *tok); +./test/test_driver.sh: line 9: 3231 Segmentation fault ./tmp
+
+
+
+
+
+
+
20:02
+
+
+ +
+
+
+
+
+ +
+
今日も一日
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
正しく動いていた段階のコミットに戻して、差分をとってみた (edited)
+
+
+
+
+
+
+
10:29
+
+
+
+
+ + + +
15.7 KB
+
+
+
+
+
+
+
+
+
10:33
+
+
+
ローカル変数のoffsetの計算が壊れているのが原因っぽい (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
calc_lvar_offsetで直前のローカル変数をうまく渡せてなさそう
+
+
+
+
+
+
+
+
Avatar
+
+ +
引数をローカル変数のリストにコピーする処理を書き忘れたりしていたのを修正
+
+
+
+
+
+
+
17:35
+
+
+
ひとまず、全てのテストが通るようになった
+
+
+
+
+
+
+
17:35
+
+
+
関数ポインタを含むテストケースが正しくパースできていない疑惑がある (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
型宣言の各関数の責務を明確にしたほうが良さそう + Type *parse_base_type(Token *tok); Type *parse_pointer_type(Type *base_type, Token *tok); Type *parse_nested_type(Type *pointed_type, Token *tok); Type *parse_outermost_type(Type *pointed_type, Token *tok); Type *parse_suffix_type(Type *pointer_type, Token *tok); -Type *parse_declaration_type(Token *tok); (edited)
-
Avatar
とりあえずいろいろ変更
12:56
diff --git a/src/parse.c b/src/parse.c +Type *parse_declaration_type(Token *tok); (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえずいろいろ変更
+
+
+
+
+
+
+
12:56
+
+
+
diff --git a/src/parse.c b/src/parse.c index 5e48914..b7abb73 100644 --- a/src/parse.c +++ b/src/parse.c @@ -209,50 +209,35 @@ Type *parse_pointer_type(Type *base_type, Token *tok) { return base_type; - } (edited)
12:57
-Type *parse_nested_type(Type *pointed_type, Token *tok) { + } (edited)
+
+
+
+
+
+
+
12:57
+
+
+
-Type *parse_nested_type(Type *pointed_type, Token *tok) { - Type *pointer_type = parse_pointer_type(pointed_type, tok); +Type *parse_nested_type(Type *pointer_type, Token *tok) { if (!pointer_type) @@ -1549,7 +7519,17 @@ + Type *outermost_pointer_type = parse_pointer_type(inner_type, tok); + return parse_outermost_type(outermost_pointer_type, tok); + } - } (edited)
12:57
// 識別子のレベル(型の図において最も外側の型)をパース + } (edited)
+
+
+
+
+
+
+
12:57
+
+
+
// 識別子のレベル(型の図において最も外側の型)をパース -Type *parse_outermost_type(Type *pointed_type, Token *tok) { - Type *pointer_type = parse_pointer_type(pointed_type, tok); +Type *parse_outermost_type(Type *pointer_type, Token *tok) { @@ -1578,57 +7558,604 @@ + } } // 関数の引数が (void)である場合 - else if (pointer_type->kind == TYPE_VOID) {
12:59
結構いい線いってるっぽいが、 -// 配列型・関数型の場合 + else if (pointer_type->kind == TYPE_VOID) {
+
+
+
+
+
+
+
12:59
+
+
+
結構いい線いってるっぽいが、 + // 配列型・関数型の場合 if (suffix_type) - *inner_type = *suffix_type;
12:59
コピーの仕方がまずくて、↑の処理を行った瞬間に、ポインタが循環参照になって型情報が壊れる
13:00
どこかしらでシャローコピーしてるのが悪さしてそうなんよなあ (edited)
-
Avatar
pointer_typeparse_suffix_type()で再び参照しているのがまずそう
16:11
そこで、以下のようにpointer_typeを(ディープ)コピーしてから用いるようにした
16:12
Type *prefix_type = new_type(TYPE_NULL); - *prefix_type = *pointer_type;
16:12
ネストした型がパースできるようになった
16:13
それによって、ライフゲームのサンプルが動くようになった
16:13
-
Avatar
さすがに構造体を足したい
23:43
structというキーワードを足した
23:44
方針としては、各要素の型が異なる配列みたいな感じで扱う感じになりそう?
23:45
別の表現をすれば、複数の型を1つの型としてまとめて扱えるようにするという感じ
23:45
とりあえず、TokenKindTK_STRUCTを追加
23:48
(多次元)配列と違うのは、各要素(すなわちメンバ)に名前がついており、各要素の型(データサイズ)が異なる点
23:49
それを踏まえると、Memberみたいな構造体を用意する必要がありそう
23:52
Memberという構造体に必要そうなメンバは、 -・型情報 -・メンバの名前 -・(先頭のアドレスからの)オフセット
23:57
ある構造体に対して、複数のメンバの情報を持つとき、Linked Listで実装してもいいが、正直適当なデカさの配列で持っておいてもいい気はする
23:57
まあLinked Listでいくか
- -
Avatar
関数に構造体の宣言の情報を記録しておく必要がある?
-
Avatar
とりあえず、Struct *struct_listみたいなやつを生やして対応
22:37
そのうちローカル変数の処理とかと一本化したい
-
Avatar
int main() { + *inner_type = *suffix_type;
+
+
+
+
+
+
+
12:59
+
+
+
コピーの仕方がまずくて、↑の処理を行った瞬間に、ポインタが循環参照になって型情報が壊れる
+
+
+
+
+
+
+
13:00
+
+
+
どこかしらでシャローコピーしてるのが悪さしてそうなんよなあ (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
pointer_typeparse_suffix_type()で再び参照しているのがまずそう
+
+
+
+
+
+
+
16:11
+
+
+
そこで、以下のようにpointer_typeを(ディープ)コピーしてから用いるようにした
+
+
+
+
+
+
+
16:12
+
+
+
Type *prefix_type = new_type(TYPE_NULL); + *prefix_type = *pointer_type;
+
+
+
+
+
+
+
16:12
+
+
+
ネストした型がパースできるようになった
+
+
+
+ +
+
+
+
16:13
+
+
+
それによって、ライフゲームのサンプルが動くようになった
+
+
+
+
+
+
+
16:13
+
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
さすがに構造体を足したい
+
+
+
+
+
+
+
23:43
+
+
+
structというキーワードを足した
+
+
+
+
+
+
+
23:44
+
+
+
方針としては、各要素の型が異なる配列みたいな感じで扱う感じになりそう?
+
+
+
+
+
+
+
23:45
+
+
+
別の表現をすれば、複数の型を1つの型としてまとめて扱えるようにするという感じ
+
+
+
+
+
+
+
23:45
+
+
+
とりあえず、TokenKindTK_STRUCTを追加
+
+
+
+
+
+
+
23:48
+
+
+
(多次元)配列と違うのは、各要素(すなわちメンバ)に名前がついており、各要素の型(データサイズ)が異なる点
+
+
+
+
+
+
+
23:49
+
+
+
それを踏まえると、Memberみたいな構造体を用意する必要がありそう
+
+
+
+
+
+
+
23:52
+
+
+
Memberという構造体に必要そうなメンバは、 + ・型情報 + ・メンバの名前 + ・(先頭のアドレスからの)オフセット
+
+
+
+
+
+
+
23:57
+
+
+
ある構造体に対して、複数のメンバの情報を持つとき、Linked Listで実装してもいいが、正直適当なデカさの配列で持っておいてもいい気はする
+
+
+
+
+
+
+
23:57
+
+
+
まあLinked Listでいくか
+
+
+
+
+ +
+
+
+
Avatar
+
+ +
関数に構造体の宣言の情報を記録しておく必要がある?
+
+
+
+
+
+
+
+
Avatar
+
+ +
とりあえず、Struct *struct_listみたいなやつを生やして対応
+
+
+
+
+
+
+
22:37
+
+
+
そのうちローカル変数の処理とかと一本化したい
+
+
+
+
+
+
+
+
Avatar
+
+ +
int main() { struct position { int x; int y; }; struct position p; -}
-
Avatar
構造体のメンバのアライメントに気をつけないといけないみたいな話あった気がしてきた -例えばこういうの: https://godbolt.org/z/vbb3nf7fE (edited)
int main () { - struct foo{ - int x; - char y; - }; - struct foo p; - p.x = 3; - p.y = 5; - printf("sizeof : %lu\n", sizeof(struct foo)); - printf("_Alignof : %lu\n", _Alignof(struct foo)); - return 0; -}
-
Avatar
久々にやっていく
17:43
何も覚えていないので、一旦全部取り消して一から構造体を実装していく (edited)
17:44
まず、構造体定義を表す構造体を作成した -struct StructDef { +}
+
+
+
+
+
+
+
+
Avatar
+
+ +
構造体のメンバのアライメントに気をつけないといけないみたいな話あった気がしてきた + 例えばこういうの: https://godbolt.org/z/vbb3nf7fE (edited)
+
+
+
+
+
+ +
+
int main () { + struct foo{ + int x; + char y; + }; + struct foo p; + p.x = 3; + p.y = 5; + printf("sizeof : %lu\n", sizeof(struct foo)); + printf("_Alignof : %lu\n", _Alignof(struct foo)); + return 0; + }
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
久々にやっていく
+
+
+
+
+
+
+
17:43
+
+
+
何も覚えていないので、一旦全部取り消して一から構造体を実装していく (edited)
+
+
+
+
+
+
+
17:44
+
+
+
まず、構造体定義を表す構造体を作成した + struct StructDef { Token *tag; Member members; -};
17:44
この構造体は、構造体のタグ(struct positionpositionの部分)と、構造体のメンバのリストを持つ
17:45
構造体メンバのリストは、次のような構造体で表される -struct Member { +};
+
+
+
+
+
+
+
17:44
+
+
+
この構造体は、構造体のタグ(struct positionpositionの部分)と、構造体のメンバのリストを持つ
+
+
+
+
+
+
+
17:45
+
+
+
構造体メンバのリストは、次のような構造体で表される + struct Member { Member *next; Type *type; -};
17:45
次のメンバへのポインタと、メンバ自体の型情報を持つ
17:46
Typeという構造体の中に、変数名などの情報も含まれているので、struct Memberのメンバとしてはこの2種類で足りるはず
17:47
そして、Typeという構造体に、今書いたStructDefへのポインタを追加した
17:47
// 構造体型 - StructDef *struct_def;
17:48
構造体の定義リスト自体は、トップレベルに書く場合、関数の中で書く場合など、スコープごとに持っておく必要がある
17:49
その構造体の定義リストの中の、構造体定義を上記のstruct_defが指し示す
17:50
なお、メンバのリストは、struct StructDef自体がその実体を持つ
17:50
とりあえず、まずはトップレベルと関数のリストの中に、構造体定義のリストを追加する必要がある (edited)
✅ 1
-
Avatar
構造体定義と、構造体変数の定義を同時に行う記法は現時点ではサポートしない
-
Avatar
タグなし構造体って書けるんや(無知)
23:21
使わん気がするのでサポートしない
23:26
構造体できたら、全体的にリファクタリングしたいな
23:27
さすがに適当な順番でいろんな機能を生やしているのでコードがぐちゃぐちゃになってる
-
Avatar
変数宣言など、「何もしない」ことを表すNodeKindがあってもいいかも
23:41
雑な改修の結果、とりあえず構造体定義はパースできるようになったと思われる
23:41
なおsamantic_analysis.cではセグフォしてる
23:41
これは、NodeをNULLのまま返してるので switch(node->kind)がNULLになってるから (edited)
23:45
こういうのもできるらしい -struct position { +};
+
+
+
+
+
+
+
17:45
+
+
+
次のメンバへのポインタと、メンバ自体の型情報を持つ
+
+
+
+
+
+
+
17:46
+
+
+
Typeという構造体の中に、変数名などの情報も含まれているので、struct Memberのメンバとしてはこの2種類で足りるはず
+
+
+
+
+
+
+
17:47
+
+
+
そして、Typeという構造体に、今書いたStructDefへのポインタを追加した
+
+
+
+
+
+
+
17:47
+
+
+
// 構造体型 + StructDef *struct_def;
+
+
+
+
+
+
+
17:48
+
+
+
構造体の定義リスト自体は、トップレベルに書く場合、関数の中で書く場合など、スコープごとに持っておく必要がある
+
+
+
+
+
+
+
17:49
+
+
+
その構造体の定義リストの中の、構造体定義を上記のstruct_defが指し示す
+
+
+
+
+
+
+
17:50
+
+
+
なお、メンバのリストは、struct StructDef自体がその実体を持つ
+
+
+
+
+
+
+
17:50
+
+
+
とりあえず、まずはトップレベルと関数のリストの中に、構造体定義のリストを追加する必要がある (edited)
+
+
✅ 1
+
+
+
+
+
+
+
+
+
Avatar
+
+ +
構造体定義と、構造体変数の定義を同時に行う記法は現時点ではサポートしない
+
+
+
+
+
+
+
+
Avatar
+
+ +
タグなし構造体って書けるんや(無知)
+
+
+
+
+
+
+
23:21
+
+
+
使わん気がするのでサポートしない
+
+
+
+
+
+
+
23:26
+
+
+
構造体できたら、全体的にリファクタリングしたいな
+
+
+
+
+
+
+
23:27
+
+
+
さすがに適当な順番でいろんな機能を生やしているのでコードがぐちゃぐちゃになってる
+
+
+
+
+
+
+
+
Avatar
+
+ +
変数宣言など、「何もしない」ことを表すNodeKindがあってもいいかも
+
+
+
+
+
+
+
23:41
+
+
+
雑な改修の結果、とりあえず構造体定義はパースできるようになったと思われる
+
+
+
+
+
+
+
23:41
+
+
+
なおsamantic_analysis.cではセグフォしてる
+
+
+
+
+
+
+
23:41
+
+
+
これは、NodeをNULLのまま返してるので switch(node->kind)がNULLになってるから (edited)
+
+
+
+
+
+
+
23:45
+
+
+
こういうのもできるらしい + struct position { int x; int y; -} pos[30];
23:46
まあとりあえずは、こいつを通せるようにやっていく -int main() { +} pos[30];
+
+
+
+
+
+
+
23:46
+
+
+
まあとりあえずは、こいつを通せるようにやっていく + int main() { struct position { int x; int y; @@ -1639,14 +8166,104 @@ p.y = 3; return p.x + p.y; -}
-
Avatar
struct Memberがoffsetを持つようにした -offsetの計算の際に、メンバのアライメントを考えないといけないはず -でも、これはローカル変数のoffsetの計算と同じようにできるはずなので、既に作成しているはず -結局、構造体は、変数のリストをネストさせたものみたいに理解すれば良さそう(な気がしている) (edited)
00:51
TK_DOTをトークナイズできるようにした
00:51
次のステップは構造体変数を定義できるようにすること
00:52
その次に.演算子の実装
-
Avatar
まず、構造体変数を定義できるようにしていく
14:50
新たな構造体の定義時に、def_listへの追加を忘れてたので修正
14:53
変数のリストに、構造体変数を追加できるようにする
-
Avatar
構造体変数のオフセットを計算できるようにする
-
Avatar
while (true) { +}
+
+
+
+
+
+
+
+
Avatar
+
+ +
struct Memberがoffsetを持つようにした + offsetの計算の際に、メンバのアライメントを考えないといけないはず + でも、これはローカル変数のoffsetの計算と同じようにできるはずなので、既に作成しているはず + 結局、構造体は、変数のリストをネストさせたものみたいに理解すれば良さそう(な気がしている) (edited)
+
+
+
+
+
+
+
00:51
+
+
+
TK_DOTをトークナイズできるようにした
+
+
+
+
+
+
+
00:51
+
+
+
次のステップは構造体変数を定義できるようにすること
+
+
+
+
+
+
+
00:52
+
+
+
その次に.演算子の実装
+
+
+
+
+
+
+
+
Avatar
+
+ +
まず、構造体変数を定義できるようにしていく
+
+
+
+
+
+
+
14:50
+
+
+
新たな構造体の定義時に、def_listへの追加を忘れてたので修正
+
+
+
+
+
+
+
14:53
+
+
+
変数のリストに、構造体変数を追加できるようにする
+
+
+
+
+
+
+
+
Avatar
+
+ +
構造体変数のオフセットを計算できるようにする
+
+
+
+
+
+
+
+
Avatar
+
+ +
while (true) { if (consume(tok, TK_LEFT_BRACKET)) { node = new_binary_node(ND_DEREF, new_binary_node(ND_ADD, node, expr(func, tok)), NULL); expect(tok, TK_RIGHT_BRACKET); @@ -1663,13 +8280,113 @@ } return node; } -現時点だと、ローカル変数はとnodeはoffsetの情報のみでマッピングしてるので、parseする段階でnodeに型情報を入れておく必要がある
-
Avatar
型情報足したら、多分parseは通るようになって、codegenでアドレスが生成できないとか言われてる -良さそう
12:09
gen_addr()において、.が来た場合は、rbp - 構造体変数のoffset - メンバのoffset のアドレスを生成すればいけそう
12:09
これは、p.x = 2;みたいに左辺値として.演算子が使われた場合の話
12:10
一方で、return p.x;みたいな感じで、右辺値として使われた場合のコードもgen_expr()とかに足す必要があるはず
-
Avatar
メモ -・トップレベルに構造体を定義できるようにする -・構造体をネストできるようにする (edited)
-
Avatar
また1ヶ月くらい放置した
22:20
何やってたっけ
22:20
構造体を実装してて、.演算子が一応追加されてたはず
22:21
ただ一部のテストがまだバグってたはず
22:21
int main() { + 現時点だと、ローカル変数はとnodeはoffsetの情報のみでマッピングしてるので、parseする段階でnodeに型情報を入れておく必要がある
+
+
+
+
+
+
+
+
Avatar
+
+ +
型情報足したら、多分parseは通るようになって、codegenでアドレスが生成できないとか言われてる + 良さそう
+
+
+
+
+
+
+
12:09
+
+
+
gen_addr()において、.が来た場合は、rbp - 構造体変数のoffset - メンバのoffset のアドレスを生成すればいけそう
+
+
+
+
+
+
+
12:09
+
+
+
これは、p.x = 2;みたいに左辺値として.演算子が使われた場合の話
+
+
+
+
+
+
+
12:10
+
+
+
一方で、return p.x;みたいな感じで、右辺値として使われた場合のコードもgen_expr()とかに足す必要があるはず
+
+
+
+
+
+
+
+
Avatar
+
+ +
メモ + ・トップレベルに構造体を定義できるようにする + ・構造体をネストできるようにする (edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
また1ヶ月くらい放置した
+
+
+
+
+
+
+
22:20
+
+
+
何やってたっけ
+
+
+
+
+
+
+
22:20
+
+
+
構造体を実装してて、.演算子が一応追加されてたはず
+
+
+
+
+
+
+
22:21
+
+
+
ただ一部のテストがまだバグってたはず
+
+
+
+
+
+
+
22:21
+
+
+
int main() { struct position { int x; int y; @@ -1681,10 +8398,60 @@ p.ptr = &hoge; return *p.ptr; // => 7 -}
22:21
これはちゃんとコンパイルできる
22:22
struct positionのメンバにint zを追加すると、returnされる値がバグる
-
Avatar
アセンブリ読んだ感じだとoffsetの計算が間違っていそう
23:30
というか、挙動からエスパーしても、こういうパターンは大体offsetの計算間違えてる(偏見)
-
Avatar
出力されるアセンブリを手で書き換えたらうまく動いた -.intel_syntax noprefix +}
+
+
+
+
+
+
+
22:21
+
+
+
これはちゃんとコンパイルできる
+
+
+
+
+
+
+
22:22
+
+
+
struct positionのメンバにint zを追加すると、returnされる値がバグる
+
+
+
+
+
+
+
+
Avatar
+
+ +
アセンブリ読んだ感じだとoffsetの計算が間違っていそう
+
+
+
+
+
+
+
23:30
+
+
+
というか、挙動からエスパーしても、こういうパターンは大体offsetの計算間違えてる(偏見)
+
+
+
+
+
+
+
+
Avatar
+
+ +
出力されるアセンブリを手で書き換えたらうまく動いた + .intel_syntax noprefix .data @@ -1729,8 +8496,28 @@ .L.return.main: mov rsp, rbp pop rbp - ret
09:11
配列のアドレス出力するのと同様の計算を行う必要がありそう
-
Avatar
(← higer address ↑) + ret
+
+
+
+
+
+
+
09:11
+
+
+
配列のアドレス出力するのと同様の計算を行う必要がありそう
+
+
+
+
+
+
+
+
Avatar
+
+ +
(← higer address ↑) 0 4 8 +----------------+----------------+ |/// (int x) ////|/// (int y) ////| @@ -1739,8 +8526,76 @@ +----------------+----------------| |////////// (int *ptr) ///////////| +----------------+----------------| -(lower address) (edited)
-
Avatar
配列のメモリ配置がどうだったか記憶が薄れてきたので、念のため確認
09:45
先頭のアドレスが下位アドレスにあって、そこにoffset * sizeof(int)分のアドレスを加算することで、各要素のアドレスを算出してる
09:50
一方、構造体の場合はこんな感じ
09:50
やはり、先に書いたメンバが下位アドレスから上位アドレスに向かって順次配置されてそう
09:56
たぶん今の実装だと、構造体変数全体の大きさをrbpから引いて、さらにoffsetの値をそこから引くようになってるけど、これはどうみてもおかしいので直す必要がありそう
09:56
さらに先に書いたメンバが上位アドレス側に配置されているので、これも修正する必要がある
+(lower address)
(edited)
+
+
+
+
+
+
+
+
Avatar
+
+ +
配列のメモリ配置がどうだったか記憶が薄れてきたので、念のため確認
+ +
+
+
+
+
+
+
09:45
+
+
+
先頭のアドレスが下位アドレスにあって、そこにoffset * sizeof(int)分のアドレスを加算することで、各要素のアドレスを算出してる
+
+
+
+
+
+
+
09:50
+
+
+
一方、構造体の場合はこんな感じ
+ +
+
+
+
+
+
+
09:50
+
+
+
やはり、先に書いたメンバが下位アドレスから上位アドレスに向かって順次配置されてそう
+
+
+
+
+
+
+
09:56
+
+
+
たぶん今の実装だと、構造体変数全体の大きさをrbpから引いて、さらにoffsetの値をそこから引くようになってるけど、これはどうみてもおかしいので直す必要がありそう
+
+
+
+
+
+
+
09:56
+
+
+
さらに先に書いたメンバが上位アドレス側に配置されているので、これも修正する必要がある
+
+
+
+
-
-
Exported 527 message(s)
+
+
+
Exported 527 message(s)
+
\ No newline at end of file