編纂者:hsjoihs
2022年セキュリティ・キャンプL3(Cコンパイラゼミ)は基本的に Discord で行われた。したがって、テキストチャットとボイスチャットが混在する形態となったが、講師である hsjoihs がゼミ中の会話を全て手動でテキスト起こしした。
OS自作ゼミ・Cコンパイラゼミは、もとから自分が興味を持っていて、かつDiscordでやってることが見えやすかったのでよかったです。特にCコンパイラゼミは会話がまるごとテキストに起こされてチャンネルに流されていたので、マジで良かったです。これ絶対大変だと思うんですけど、めちゃくちゃ助かりました。 セキュリティ・キャンプを終えて(感想とか) - わたすけのへや
セキュリティ・キャンプCコンパイラゼミの創設者である@rui314さんの以下の思想に基づき、Cコンパイラゼミの会話のうち、発言者からの公開許可を頂けた発言を一般公開する。会話のテキスト起こしも Discord に最初からテキストとして書かれたものも区別せずに表示している。
セキュキャンは参加者はお金を払わないし、物理的制約で選考というものがある中で通るか否かいうのは時の運によるものが多いので(年齢制限もあるし)、行ったかどうかであまり差がつくというのは望ましくないと思う。なので可能な限り教材をやれば誰でもできるというようにしたいとは思います。
— Rui Ueyama (@rui314) April 15, 2019
セキュキャンに来なくて大丈夫なようにコースの詳細な資料を全公開するとなぜか逆に応募が増えるという。でもインターネットってそういうものかも。
— Rui Ueyama (@rui314) May 28, 2019
なお、ここで言及されている「コースの詳細な資料」とは「低レイヤを知りたい人のためのCコンパイラ作成入門」のことである。今年の C コンパイラゼミでは、これに加えて前橋和弥「新・標準プログラマーズライブラリ C言語 ポインタ完全制覇」も用いた。
assignment-expression:
conditional-expression
unary-expression assignment-operator assignment-expression
「試しにunary-expressionを読んでみて、その直後にassignment-operatorが来ていなかった場合はその情報を破棄し、conditional-expressionとして読む」と実装しなければならない。しかし、現状ではparseしたら同時にコード生成もしてしまう。
make test1 => 第1世代に対するテスト
make test2 => 第2世代 ...
make test3 => 第3世代 ...
make testall => test1, test2, test3を実行
make diff => selfhost/gen2/*.sとselfhost/gen3/*.sを比較する。
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
.globl init_array8
init_array8:
push rbp
mov rbp, rsp
sub rsp, 20
mov rax, 0
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
mov rax, 1
push rax
pop rdi
pop rax
mov [rax], edi
push rdi
pop rax
mov rax, 1
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
mov rax, 2
push rax
pop rdi
pop rax
mov [rax], edi
push rdi
pop rax
push rax
pop rax
mov rax, 0
push rax
pop rax
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
pop rax
mov eax, [rax]
cdqe
push rax
mov rax, 4
push rax
pop rax
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
pop rax
mov eax, [rax]
cdqe
push rax
pop rdi
pop rax
add rax, rdi
push rax
pop rax
push rax
pop rdi
movsx rax, edi
jmp .L.return.init_array8
pop rax
push rax
pop rax
.L.return.init_array8:
mov rsp, rbp
pop rbp
ret
.globl init_array8
init_array8:
push rbp
mov rbp, rsp
sub rsp, 20
mov rax, 0
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
mov rax, 1
push rax
pop rdi
pop rax
mov [rax], edi
push rdi
pop rax
mov rax, 1
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
mov rax, 2
push rax
pop rdi
pop rax
mov [rax], edi
push rdi
pop rax
mov rax, 2
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
mov rax, 3
push rax
pop rdi
pop rax
mov [rax], edi
push rdi
pop rax
mov rax, 3
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
mov rax, 4
push rax
pop rdi
pop rax
mov [rax], edi
push rdi
pop rax
mov rax, 4
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
mov rax, 5
push rax
pop rdi
pop rax
mov [rax], edi
push rdi
pop rax
push rax
pop rax
mov rax, 0
push rax
pop rax
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
pop rax
mov eax, [rax]
cdqe
push rax
mov rax, 4
push rax
pop rax
push rax
mov rax, 4
push rax
pop rdi
pop rax
imul rax, rdi
push rax
mov rax, rbp
sub rax, 20
push rax
pop rax
push rax
pop rdi
pop rax
add rax, rdi
push rax
pop rax
mov eax, [rax]
cdqe
push rax
pop rdi
pop rax
add rax, rdi
push rax
pop rax
push rax
pop rdi
movsx rax, edi
jmp .L.return.init_array8
pop rax
push rax
pop rax
.L.return.init_array8:
mov rsp, rbp
pop rbp
ret
test9:
.long 1
.long 2
.long 3
test9:
.long 1
.long 2
| children_cap | test9 | init_array8 |
|--------------|-------|-------------|
| 1 | fail | fail |
| 2 | fail | fail |
| 3 | ok | fail |
| 4 | ok | fail |
| 5 | ok | ok |
int test9[] = {1, 2, 3};
int init_array8() {
int a[] = {1, 2, 3, 4, 5};
return a[0] + a[4];
}
initialize_array initialize2 initialize_array initialize2 initialize
test_gcc_and_clang.sh: kcc1_gcc start compileing tests/init2.c before: 0x56156ec602d0, 2 after : 0x56156ec60450, 4 before: 0x56156ec60450, 4 after : 0x56156ec60620, 8 final len: 5 init->children: 0x56156ec60620 init->len: 5 init->children: (nil) init->children: (nil) init->children: (nil) init->children: (nil) init->children: (nil) before: 0x56156ec630d0, 2 after : 0x56156ec63250, 4 final len: 3 init->children: 0x56156ec63250 init->len: 3 init->children: (nil) init->children: (nil) init->children: (nil) test_gcc_and_clang.sh: kcc1_clang start compileing tests/init2.c before: 0x607000000020, 2 after : 0x60e000000040, 4 before: 0x60e000000040, 4 after : 0x612000000040, 8 final len: 5 init->children: 0x612000000040 init->len: 5 init->children: (nil) init->children: (nil) init->children: 0xbebebebebebebebe init->len: -1094795586 init->children: 0xbebebebebebebebe init->len: -1094795586 init->children: 0xbebebebebebebebe init->len: -1094795586 before: 0x607000000090, 2 after : 0x60e000000120, 4 final len: 3 init->children: 0x60e000000120 init->len: 3 init->children: (nil) init->children: (nil) init->children: 0xbebebebebebebebe init->len: -1094795586
① このコンパイラでは、メモリ確保は memory_alloc という関数を使って行っているが、こいつは裏で calloc を呼び出しているので、必ずゼロ埋めされたメモリ領域が返ってくる ② しかし、メモリ再確保は libc の realloc をそのまま呼んでおり、こいつはメモリをゼロ埋めするとは限らないし、なんなら clang のサニタイザは意図的に 0xBEBEBEBE で埋めた領域を返してくる ③ で、未初期化のフィールドを読んでるので、ヌルポインタが入ってるところが期待されるべき場所で 0xBEBEBEBE を読んで、コンパイラがバグっている ④ 解決策としては、以下の二つのどちらか(あるいは両方)が考えられる。 A.「このコンパイラでは、ヒープ領域のうち代入してないフィールドは常に 0 かヌルポインタであることを仮定する」という暗黙の規約を守るために、realloc(ソースコード中で 2 回登場)した領域を必ずゼロクリアする B. ヒープ上に確保した構造体のありとあらゆるフィールドに 0 を入れて回る
2018年8月31日 (Day 50) セルフホストに向けて 構造体を関数に渡すのは放棄しよう。スタック使いたくないし。 ということで構造体を関数で渡している部分をポインタ渡しにするようにしたので、 codegen_expression.c をセルフコンパイルできた。 グローバル変数へのアクセスを全部GOTPCREL経由にしたところ、stderrがちゃんと使えるようになった。よって std.c と error.c をセルフコンパイル。 「今構造体周りってどこまでできてたっけ?」と思ったので実験をしている。a.b.cとか普通にできるのか。
# uname -a
Linux uint 5.13.0-51-generic #58~20.04.1-Ubuntu SMP Tue Jun 14 11:29:12 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
# gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# uname -a
Linux LAPTOP-BKHPSENK 4.19.128-microsoft-standard #1 SMP Tue Jun 23 12:58:10 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
# gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ uname -a
Linux KARINTO 4.19.128-microsoft-standard #1 SMP Tue Jun 23 12:58:10 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
AddressSanitizer:DEADLYSIGNAL
=================================================================
==27109==ERROR: AddressSanitizer: SEGV on unknown address (pc 0x0001089add29 bp 0x7ffee7250dd0 sp 0x7ffee7250da8 T0)
==27109==The signal is caused by a READ memory access.
==27109==Hint: this fault was caused by a dereference of a high value address (see register values below). Dissassemble the provided pc to learn which register was used.
#0 0x1089add29 in .Lbegin8+0x55 (prpr:x86_64+0x100001d29)
#1 0x1089aea01 in .Lswitch1+0x16 (prpr:x86_64+0x100002a01)
#2 0x1089ae908 in .Lend35+0x1d (prpr:x86_64+0x100002908)
#3 0x1089aeaac in .Lswitch6+0x41 (prpr:x86_64+0x100002aac)
#4 0x1089ae391 in .Lend26+0x7f (prpr:x86_64+0x100002391)
#5 0x1089ae4e9 in include+0x14a (prpr:x86_64+0x1000024e9)
#6 0x1089aea21 in .Lswitch2+0x16 (prpr:x86_64+0x100002a21)
#7 0x1089aeef0 in main+0x3a0 (prpr:x86_64+0x100002ef0)
#8 0x7fff71a72cc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)
==27109==Register values:
rax = 0x02ffffff00000002 rbx = 0x00007ffee7253640 rcx = 0x0000000117b3de6c rdx = 0x0000000000000000
rdi = 0x0000000000000000 rsi = 0x00000000000120a8 rbp = 0x00007ffee7250dd0 rsp = 0x00007ffee7250da8
r8 = 0x00000000000130a8 r9 = 0x0000000000000000 r10 = 0x00007ffee7250dc4 r11 = 0x00007fff9859abf0
r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (prpr:x86_64+0x100001d29) in .Lbegin8+0x55
==27109==ABORTING
$ gdb prpr/prpr
@(gdb) run (適当なcファイル)
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7f0af0b in ?? () from /usr/lib/libc.so.6
@(gdb) backtrace
#0 0x00007ffff7f0af0b in ?? () from /usr/lib/libc.so.6
#1 0x0000555555555636 in read_file (name=0x5555d2a0 <error: Cannot access memory at address 0x5555d2a0>)
at fileutil.c:16
#2 0x00005555555563ba in main (argc=2, argv=0x7fffffffe6f8) at main.c:33
このように、Cは、現場の人間が、自分たちの用途のために、必要に応じて作成した言語です。その後もCは(中略)かなり行き当たりばったりに機能拡張を繰り返してきました。
Cは、プログラマーは全知全能であるという理念の元に設計されています。Cの設計で優先されたのは、 ・いかにコンパイラを簡単に実装できるか(Cを使う人が、如何に簡単にプログラムを実装できるか、ではない) ・いかに高速な実行コードを吐くソースが書けるか(いかにコンパイラで最適化して、高速な実行コードを吐くか、ではない)
hsjoihs 試しに実測してみました。 https://en.wikipedia.org/wiki/Serif の本文で 4分半ぐらいですかね 16:04 えーと 2800 単語を 270 秒で読んでるので、1 秒に 10 単語強ですかねぇ 16:10 https://irisreading.com/what-is-the-average-reading-speed/ これの本文が2分半。1525 単語あるっぽいので、やっぱり 1 秒に 10 単語強ですね
struct Expr {
enum ExprCategory category;
union {
struct {
enum UnaryOp op;
struct Expr* ptr1;
} unary;
struct {
enum BinaryOp op;
struct Expr* ptr1;
struct Expr* ptr2;
} binary;
};
};
switch(expr.category) {
case UNARY_EXPR: // expr.unary.opとか
case BINARY_EXPR: // expr.binary.opとか
}
// expr.category == UNARY_EXPRのときにexpr.binaryにアクセスできてしまう
case IF_STATEMENT: {
int label1 = get_new_label_name(ptr_prs);
int label2 = get_new_label_name(ptr_prs);
const struct Expr expr = ref_sta->expr1;
print_expression(ptr_prs, &expr);
gen_if_zero_jmp_nbyte(
size_of_basic(&expr.details.type, "condition of `if`"), label1, 0);
gen_discard();
print_statement(ptr_prs, ref_sta->inner_statement);
gen_jump(label2, "if statement");
gen_label(label1);
gen_discard();
gen_label(label2);
return;
}
When compiling in ISO C mode by GCC or Clang (e.g. with option -std=c11), __asm__ must be used instead of asm.
7.1.3 Reserved identifiers 1 Each header declares or defines all identifiers listed in its associated subclause, and optionally declares or defines identifiers listed in its associated future library directions subclause and identifiers which are always reserved either for any use or for use as file scope identifiers. All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use. All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces. Each macro name in any of the following subclauses (including the future library directions) is reserved for use as specified if any of its associated headers is included; unless explicitly stated otherwise (see 7.1.4). All identifiers with external linkage in any of the following subclauses (including the future library directions) and errno are always reserved for use as identifiers with external linkage.184) Each identifier with file scope listed in any of the following subclauses (including the future library directions) is reserved for use as a macro name and as an identifier with file scope in the same name space if any of its associated headers is included.
J.5 Common extensions 1 The following extensions are widely used in many systems, but are not portable to all implementations. The inclusion of any extension that may cause a strictly conforming program to become invalid renders an implementation nonconforming. Examples of such extensions are new keywords, extra library functions declared in standard headers, or predefined macros with names that do not begin with an underscore.
A preprocessing directive of the form # include <h-char-sequence> new-line searches a sequence of implementation-defined places for a header identified uniquely by the specified sequence between the < and > delimiters, and causes the replacement of that directive by the entire contents of the header. How the places are specified or the header identified is implementation-defined.
#if VERSION == 1
#define INCFILE "vers1.h"
#elif VERSION == 2
#define INCFILE "vers2.h" // and so on
#else
#define INCFILE "versN.h"
#endif
#include INCFILE
init.map_or(Ok(None), |expr| self.down_expr(expr, lvar_map).map(|expr| Some(expr)))?
./test.sh
a=3; if(a==1) return 1; if(a==2) return 2; if(a==3) return 3; => 3 expected, but got 179
make: *** [Makefile:11: test] Error 1
.intel_syntax noprefix
.globl main
main:
push rbp
mov rbp, rsp
sub rsp, 8
mov rax, rbp
sub rax, 8
push rax
push 3
pop rdi
pop rax
mov [rax], rdi
push rdi
pop rax
mov rax, rbp
sub rax, 8
push rax
pop rax
mov rax, [rax]
push rax
push 1
pop rdi
pop rax
cmp rax, rdi
sete al
movzb rax, al
push rax
pop rax
cmp rax, 0
je .L.end.0
push 1
pop rax
mov rsp, rbp
pop rbp
ret
.L.end.0:
pop rax
mov rax, rbp
sub rax, 8
push rax
pop rax
mov rax, [rax]
push rax
push 2
pop rdi
pop rax
cmp rax, rdi
sete al
movzb rax, al
push rax
pop rax
cmp rax, 0
je .L.end.1
push 2
pop rax
mov rsp, rbp
pop rbp
ret
.L.end.1:
pop rax
mov rax, rbp
sub rax, 8
push rax
pop rax
mov rax, [rax]
push rax
push 3
pop rdi
pop rax
cmp rax, rdi
sete al
movzb rax, al
push rax
pop rax
cmp rax, 0
je .L.end.2
push 3
pop rax
mov rsp, rbp
pop rbp
ret
.L.end.2:
pop rax
mov rsp, rbp
pop rbp
ret
スカラとは、char, int, double, 列挙型などの算術型、およびポインタを指します。(中略) 初期の C では、一度に扱えるのはスカラだけでした。 (中略) 初期の C では、一度にできることといえば、スカラという「小さな」型を、右から左に動かしたり、スカラ同士で演算したり、スカラ同士で比較したりすることだけでした。 C はそういう言語です。入出力はおろか、配列や構造体すら、言語自体の機能でまとめて扱うことを放棄した言語なのです。 ただし、ANSI C では、以下の点で、集成体型をまとめて扱うことが可能になっています。 ・構造体の一括代入 ・構造体を関数の引数として渡す ・構造体を関数の戻り値として返す ・auto 変数の初期化
C では、すべての変数は、一般的に使う前に、普通は関数の始めの実行可能分の前のところで宣言しておかなければならない。
p.297 compound-statement: { declaration-list_opt statement-list_opt }
On the MS/DOS 16 bit compilers, you had different "modes", and data pointers weren't necessarily the same size as function pointers. But at least on the ones I used, all data pointers (including void*) always had the same size. (Of course, you couldn't convert a function pointer to void*, since void* might be smaller. But according to the standard, you can't do that today, either.)
You don't even have to have a Harvard architecture to have code and data pointers using different address spaces - the old DOS "Small" memory model did this (near pointers with CS != DS)
For those who remember MS-DOS, Windows 3.1 and older the answer is quite easy. All of these used to support several different memory models, with varying combinations of characteristics for code and data pointers. So for instance for the Compact model (small code, large data): sizeof(void *) > sizeof(void(*)()) and conversely in the Medium model (large code, small data): sizeof(void *) < sizeof(void(*)()) In this case you didn't have separate storage for code and date but still couldn't convert between the two pointers (short of using non-standard __near and __far modifiers).