chibiccを読む~Cコンパイラコードリーディング~ ステップ12
トップページ
jupiteroak.hatenablog.com
「低レイヤを知りたい人のためのCコンパイラ作成入門」のCコンパイラを読んでいきます。
www.sigbus.info
ステップ12に該当
github.com
ステップ12に該当
github.com
ステップ12に該当
github.com
- 今回作成するコンパイラ
- 追加・修正されたコンパイラのソースコード(if文を扱えるコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード(while文を扱えるコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード(for文を扱えるコンパイラを作成する)
- テストコード
- Makefile
追加・修正されたコンパイラのソースコード(if文を扱えるコンパイラを作成する)
tokenize関数
https://github.com/rui314/chibicc/commit/ead8dc6996514b7a9b0c82e662bb8dccc830883e#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R131
https://github.com/rui314/chibicc/blob/ead8dc6996514b7a9b0c82e662bb8dccc830883e/tokenize.c#L131
Token *tokenize() { char *p = user_input; Token head; head.next = NULL; Token *cur = &head; while (*p) { // Skip whitespace characters. if (isspace(*p)) { p++; continue; } // Keyword or multi-letter punctuator char *kw = starts_with_reserved(p); if (kw) { int len = strlen(kw); cur = new_token(TK_RESERVED, cur, p, len); p += len; continue; } // Single-letter punctuator if (strchr("+-*/()<>;=", *p)) { cur = new_token(TK_RESERVED, cur, p++, 1); continue; } // Identifier if (is_alpha(*p)) { char *q = p++; while (is_alnum(*p)) p++; cur = new_token(TK_IDENT, cur, q, p - q); continue; } // Integer literal if (isdigit(*p)) { cur = new_token(TK_NUM, cur, p, 0); char *q = p; cur->val = strtol(p, &p, 10); cur->len = p - q; continue; } error_at(p, "invalid token"); } new_token(TK_EOF, cur, p, 0); return head.next; }
文字列の先頭アドレスを取得する(変更なし)
char *p = user_input;
トークンからなる連結リストのヘッダーを作成する(変更なし)
Token head; head.next = NULL; Token *cur = &head;
空白文字の場合(変更なし)
// Skip whitespace characters. if (isspace(*p)) { p++; continue; }
キーワードの場合
// Keyword or multi-letter punctuator char *kw = starts_with_reserved(p); if (kw) { int len = strlen(kw); cur = new_token(TK_RESERVED, cur, p, len); p += len; continue; }
starts_with_reserved関数を呼び出して、文字p*を含んだ文字列 が キーワード や 複数文字の記号 であるかを確認します。
starts_with_reserved関数の戻り値kwがキーワードや複数文字の記号である場合は(文字p*を含んだ文字列がキーワードや複数文字の記号である場合は)、 new_token関数を呼び出して、キーワードを表現するトークンを生成しトークンの連結リストを作成します。変数curには新規に生成されたトークン(連結リストの終端要素)のアドレスが格納されます。
次の文字を取得するためにchar型へのポインタpをlenバイト分(キーワードの長さ文)インクリメントしてからwhile文のループを継続します。
1文字の記号の場合(変更なし)
// Single-letter punctuator if (strchr("+-*/()<>;=", *p)) { cur = new_token(TK_RESERVED, cur, p++, 1); continue; }
識別子の場合(変更なし)
// Identifier if (is_alpha(*p)) { char *q = p++; while (is_alnum(*p)) p++; cur = new_token(TK_IDENT, cur, q, p - q); continue; }
数字の場合(変更なし)
// Integer literal if (isdigit(*p)) { cur = new_token(TK_NUM, cur, p, 0); char *q = p; cur->val = strtol(p, &p, 10); cur->len = p - q; continue; }
その他の場合(変更なし)
error_at(p, "invalid token");
連結リストの先頭トークンを戻り値としてリターンする(変更なし)
return head.next;
starts_with_reserved関数
https://github.com/rui314/chibicc/commit/ead8dc6996514b7a9b0c82e662bb8dccc830883e#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R97
https://github.com/rui314/chibicc/blob/ead8dc6996514b7a9b0c82e662bb8dccc830883e/tokenize.c#L97
char *starts_with_reserved(char *p) { // Keyword static char *kw[] = {"return", "if", "else"}; for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) { int len = strlen(kw[i]); if (startswith(p, kw[i]) && !is_alnum(p[len])) return kw[i]; } // Multi-letter punctuator static char *ops[] = {"==", "!=", "<=", ">="}; for (int i = 0; i < sizeof(ops) / sizeof(*ops); i++) if (startswith(p, ops[i])) return ops[i]; return NULL; }
starts_with_reserved関数は、引数pの指定する文字を含む文字列がキーワードや複数文字の記号であるかを判定します。
キーワードを取得する
// Keyword static char *kw[] = {"return", "if", "else"}; for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) { int len = strlen(kw[i]); if (startswith(p, kw[i]) && !is_alnum(p[len])) return kw[i]; }
for文を使って文字p*を含んだ文字列とキーワード(kw配列の要素)を比較します。
sizeof(kw)はkw配列全体のサイズの値(3バイト)、sizeof(*kw)はkw配列要素のサイズの値(1バイト)、となるので、sizeof(kw) / sizeof(*kw)は配列の要素数(3)となります。
startswith(p, kw[i]) && !is_alnum(p[len])が真となる場合 → startswith関数の戻り値がtrue、かつ、is_alnum関数の戻り値がfalseとなる場合 → 文字p*を含んだ文字列とキーワード(kw配列の要素)が一致し、かつ、キーワードの次の文字が英数字ではない場合は、文字p*を含んだ文字列はキーワードとみなせるので、そのキーワードを戻り値としてリターンします。
複数文字の記号を取得する
// Multi-letter punctuator static char *ops[] = {"==", "!=", "<=", ">="}; for (int i = 0; i < sizeof(ops) / sizeof(*ops); i++) if (startswith(p, ops[i])) return ops[i];
for文を使って文字p*を含んだ文字列と複数文字の記号(ops配列の要素)を比較します。
startswith(p, ops[i])が真となる場合 → startswith関数の戻り値がtrueとなる場合 → 文字p*を含んだ文字列と複数文字の記号(ops配列の要素)が一致する場合は、その文字列を戻り値としてリターンします。
NodeKind
https://github.com/rui314/chibicc/commit/ead8dc6996514b7a9b0c82e662bb8dccc830883e#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R68
https://github.com/rui314/chibicc/blob/ead8dc6996514b7a9b0c82e662bb8dccc830883e/chibicc.h#L68
// AST node typedef enum { ND_ADD, // + ND_SUB, // - ND_MUL, // * ND_DIV, // / ND_EQ, // == ND_NE, // != ND_LT, // < ND_LE, // <= ND_ASSIGN, // = ND_RETURN, // "return" ND_IF, // "if" ND_EXPR_STMT, // Expression statement ND_VAR, // Variable ND_NUM, // Integer } NodeKind;
キーワードifを表現するノード型ND_IFを追加します。
Node構造体
https://github.com/rui314/chibicc/commit/ead8dc6996514b7a9b0c82e662bb8dccc830883e#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R83
https://github.com/rui314/chibicc/blob/ead8dc6996514b7a9b0c82e662bb8dccc830883e/chibicc.h#L83
// AST node type typedef struct Node Node; struct Node { NodeKind kind; // Node kind Node *next; // Next node Node *lhs; // Left-hand side Node *rhs; // Right-hand side // "if" statement Node *cond; Node *then; Node *els; Var *var; // Used if kind == ND_VAR int val; // Used if kind == ND_NUM };
if文をパースする際に使用する子ノードcond、then、elsを追加します。
stmt関数
https://github.com/rui314/chibicc/commit/ead8dc6996514b7a9b0c82e662bb8dccc830883e#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR95
https://github.com/rui314/chibicc/blob/ead8dc6996514b7a9b0c82e662bb8dccc830883e/parse.c#L95
// stmt = "return" expr ";" // | "if" "(" expr ")" stmt ("else" stmt)? // | expr ";" Node *stmt() { if (consume("return")) { Node *node = new_unary(ND_RETURN, expr()); expect(";"); return node; } if (consume("if")) { Node *node = new_node(ND_IF); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); if (consume("else")) node->els = stmt(); return node; } Node *node = read_expr_stmt(); expect(";"); return node; }
stmt関数は、生成規則 stmt = "return" expr ";" | "if" "(" expr ")" stmt ("else" stmt)? | expr ";" に基づいて、抽象構文木のノードを生成します。
"return"とexprと";"(変更なし)
if (consume("return")) { Node *node = new_unary(ND_RETURN, expr()); expect(";"); return node; }
"if"、"("、expr、")"、stmt 、「"else" と stmt」を0回か1回
if (consume("if")) { Node *node = new_node(ND_IF); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); if (consume("else")) node->els = stmt(); return node; }
consume("if")の戻り値がtrueとなる場合→着目しているトークンが"if"の場合は、new_node関数を呼び出してキーワードifを表現するノードを生成します。
ifの次のトークンは"("であることが期待されているのでexpect("(")を呼び出しトークン列を1つ読み進めます。
expr()を呼び出して、条件式に対応する抽象構文木を生成し、その抽象構文木のルートノードを子ノードcond(ifを表現するノードの子ノード)として登録します。
exprの次のトークンは")"であることが期待されているのでexpect(")")を呼び出しトークン列を1つ読み進めます。
stmt()を呼び出して、真文に対応する抽象構文木を生成し、その抽象構文木のルートノードを子ノードthen(ifを表現するノードの子ノード)として登録します。
consume("else")の戻り値がtrueとなる場合→着目しているトークンが"else"の場合は、stmt()を呼び出して、偽文に相当する抽象構文木を生成し、その抽象構文木のルートノードを子ノードels(ifを表現するノードの子ノード)として登録します。
exprと";"
Node *node = read_expr_stmt(); expect(";"); return node;
new_unary(ND_EXPR_STMT, expr())をread_expr_stmt()に変更します。
read_expr_stmt関数
https://github.com/rui314/chibicc/commit/ead8dc6996514b7a9b0c82e662bb8dccc830883e#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR81
https://github.com/rui314/chibicc/blob/ead8dc6996514b7a9b0c82e662bb8dccc830883e/parse.c#L81
Node *read_expr_stmt() { return new_unary(ND_EXPR_STMT, expr()); }
read_expr_stmt関数は、new_unary関数を呼び出して式文を表現するノードを生成します。
gen関数
https://github.com/rui314/chibicc/commit/ead8dc6996514b7a9b0c82e662bb8dccc830883e#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR48
https://github.com/rui314/chibicc/blob/ead8dc6996514b7a9b0c82e662bb8dccc830883e/codegen.c#L48
void gen(Node *node) { switch (node->kind) { case ND_NUM: printf(" push %d\n", node->val); return; case ND_EXPR_STMT: gen(node->lhs); printf(" add rsp, 8\n"); return; case ND_VAR: gen_addr(node); load(); return; case ND_ASSIGN: gen_addr(node->lhs); gen(node->rhs); store(); return; case ND_IF: { int seq = labelseq++; if (node->els) { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lelse%d\n", seq); gen(node->then); printf(" jmp .Lend%d\n", seq); printf(".Lelse%d:\n", seq); gen(node->els); printf(".Lend%d:\n", seq); } else { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); gen(node->then); printf(".Lend%d:\n", seq); } return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; } gen(node->lhs); gen(node->rhs); printf(" pop rdi\n"); printf(" pop rax\n"); switch (node->kind) { case ND_ADD: printf(" add rax, rdi\n"); break; case ND_SUB: printf(" sub rax, rdi\n"); break; case ND_MUL: printf(" imul rax, rdi\n"); break; case ND_DIV: printf(" cqo\n"); printf(" idiv rdi\n"); break; case ND_EQ: printf(" cmp rax, rdi\n"); printf(" sete al\n"); printf(" movzb rax, al\n"); break; case ND_NE: printf(" cmp rax, rdi\n"); printf(" setne al\n"); printf(" movzb rax, al\n"); break; case ND_LT: printf(" cmp rax, rdi\n"); printf(" setl al\n"); printf(" movzb rax, al\n"); break; case ND_LE: printf(" cmp rax, rdi\n"); printf(" setle al\n"); printf(" movzb rax, al\n"); break; } printf(" push rax\n"); }
二項演算以外を行うアセンブリコードを生成する
switch (node->kind) { case ND_NUM: printf(" push %d\n", node->val); return; case ND_EXPR_STMT: gen(node->lhs); printf(" add rsp, 8\n"); return; case ND_VAR: gen_addr(node); load(); return; case ND_ASSIGN: gen_addr(node->lhs); gen(node->rhs); store(); return; case ND_IF: { int seq = labelseq++; if (node->els) { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lelse%d\n", seq); gen(node->then); printf(" jmp .Lend%d\n", seq); printf(".Lelse%d:\n", seq); gen(node->els); printf(".Lend%d:\n", seq); } else { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); gen(node->then); printf(".Lend%d:\n", seq); } return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; }
ノードの型がND_IFの場合(ノードの種類がifの場合)における処理を追加します。
node->elsが真となる場合(else文がある場合)
①gen(node->cond)
条件式の評価値(node->condに対応する値)を得るために必要となるアセンブリコードを生成します。
②printf(" pop rax\n");
条件式の評価値(node->condに対応する値)をスタックから取得するアセンブリコードを生成します。
③printf(" cmp rax, 0\n");printf(" je .Lelse%d\n", seq);
条件式の評価値(node->condに対応する値)が0の場合に、 偽文へジャンプするアセンブリコードを生成します。
④gen(node->then);
真文(node->thenに対応する式文)となるアセンブリコードを生成します。
⑤printf(" jmp .Lend%d\n", seq);
真文を実行した後にジャンプするアセンブリコードを生成します。
⑥printf(" je .Lend%d\n", seq);
偽文が配置されるアドレスに付けるラベルのアセンブリコードを生成します。
⑦gen(node->els);
偽文(node->thenに対応する式文)となるアセンブリコードを生成します。
⑧printf(".Lend%d:\n", seq);
真文を実行した後のジャンプ先となるラベルのアセンブリコードを生成します。
node->elsが偽となる場合(else文がない場合)
①gen(node->cond)
条件式の評価値(node->condに対応する値)を得るために必要となるアセンブリコードを生成します。
②printf(" pop rax\n");
条件式の評価値(node->condに対応する値)をスタックから取得するアセンブリコードを生成します。
③printf(" cmp rax, 0\n");printf(" je .Lend%d\n", seq);
条件式の評価値(node->condに対応する値)が0の場合に、 ジャンプするアセンブリコードを生成します。
④gen(node->then);
真文(node->thenに対応する式文)となるアセンブリコードを生成します。
⑤printf(".Lend%d:\n", seq);
ジャンプ先ラベルのアセンブリコードを生成します。
アセンブリコード内で複数のif文が生成される場合を考慮して、ラベルの識別番号となる変数を定義しておきます。
https://github.com/rui314/chibicc/commit/ead8dc6996514b7a9b0c82e662bb8dccc830883e#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR3
https://github.com/rui314/chibicc/blob/ead8dc6996514b7a9b0c82e662bb8dccc830883e/codegen.c#L3
int labelseq = 0;
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
gen(node->lhs); gen(node->rhs); printf(" pop rdi\n"); printf(" pop rax\n");
二項演算を行うアセンブリコードを生成する(変更なし)
switch (node->kind) { case ND_ADD: printf(" add rax, rdi\n"); break; case ND_SUB: printf(" sub rax, rdi\n"); break; case ND_MUL: printf(" imul rax, rdi\n"); break; case ND_DIV: printf(" cqo\n"); printf(" idiv rdi\n"); break; case ND_EQ: printf(" cmp rax, rdi\n"); printf(" sete al\n"); printf(" movzb rax, al\n"); break; case ND_NE: printf(" cmp rax, rdi\n"); printf(" setne al\n"); printf(" movzb rax, al\n"); break; case ND_LT: printf(" cmp rax, rdi\n"); printf(" setl al\n"); printf(" movzb rax, al\n"); break; case ND_LE: printf(" cmp rax, rdi\n"); printf(" setle al\n"); printf(" movzb rax, al\n"); break; } printf(" push rax\n");
追加・修正されたコンパイラのソースコード(while文を扱えるコンパイラを作成する)
starts_with_reserved関数
https://github.com/rui314/chibicc/commit/a072d39871dbade251c7b9d1d93ecf38c35f0055#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R103
https://github.com/rui314/chibicc/blob/a072d39871dbade251c7b9d1d93ecf38c35f0055/tokenize.c#L103
char *starts_with_reserved(char *p) { // Keyword static char *kw[] = {"return", "if", "else", "while"}; for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) { int len = strlen(kw[i]); if (startswith(p, kw[i]) && !is_alnum(p[len])) return kw[i]; } // Multi-letter punctuator static char *ops[] = {"==", "!=", "<=", ">="}; for (int i = 0; i < sizeof(ops) / sizeof(*ops); i++) if (startswith(p, ops[i])) return ops[i]; return NULL; }
キーワードを取得する
// Keyword static char *kw[] = {"return", "if", "else", "while"}; for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) { int len = strlen(kw[i]); if (startswith(p, kw[i]) && !is_alnum(p[len])) return kw[i]; }
配列kwにキーワード"while"を追加します。
複数文字の記号を取得する(変更なし)
// Multi-letter punctuator static char *ops[] = {"==", "!=", "<=", ">="}; for (int i = 0; i < sizeof(ops) / sizeof(*ops); i++) if (startswith(p, ops[i])) return ops[i]; return NULL;
NodeKind
https://github.com/rui314/chibicc/commit/a072d39871dbade251c7b9d1d93ecf38c35f0055#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R69
https://github.com/rui314/chibicc/blob/a072d39871dbade251c7b9d1d93ecf38c35f0055/chibicc.h#L69
// AST node typedef enum { ND_ADD, // + ND_SUB, // - ND_MUL, // * ND_DIV, // / ND_EQ, // == ND_NE, // != ND_LT, // < ND_LE, // <= ND_ASSIGN, // = ND_RETURN, // "return" ND_IF, // "if" ND_WHILE, // "while" ND_EXPR_STMT, // Expression statement ND_VAR, // Variable ND_NUM, // Integer } NodeKind;
キーワードwhileを表現するノード型ND_WHILEを追加します。
stmt関数
https://github.com/rui314/chibicc/commit/a072d39871dbade251c7b9d1d93ecf38c35f0055#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR107
https://github.com/rui314/chibicc/blob/a072d39871dbade251c7b9d1d93ecf38c35f0055/parse.c#L107
Node *stmt() { if (consume("return")) { Node *node = new_unary(ND_RETURN, expr()); expect(";"); return node; } if (consume("if")) { Node *node = new_node(ND_IF); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); if (consume("else")) node->els = stmt(); return node; } if (consume("while")) { Node *node = new_node(ND_WHILE); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); return node; } Node *node = read_expr_stmt(); expect(";"); return node; }
stmt関数は、生成規則 stmt = "return" expr ";" | "if" "(" expr ")" stmt ("else" stmt)? | "while" "(" expr ")" stmt | expr ";" に基づいて、抽象構文木のノードを生成します。
"return"とexprと";"(変更なし)
if (consume("return")) { Node *node = new_unary(ND_RETURN, expr()); expect(";"); return node; }
"if"、"("、expr、")"、stmt 、「"else" と stmt」を0回か1回(変更なし)
if (consume("if")) { Node *node = new_node(ND_IF); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); if (consume("else")) node->els = stmt(); return node; }
"while"と"("とexprと ")"と stmt
if (consume("while")) { Node *node = new_node(ND_WHILE); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); return node; }
consume("while")の戻り値がtrueとなる場合→着目しているトークンが"while"の場合は、new_node関数を呼び出してキーワードwhileを表現するノードを生成します。
whileの次のトークンは"("であることが期待されているのでexpect("(")を呼び出しトークン列を1つ読み進めます。
expr()を呼び出して、条件式に対応する抽象構文木を生成し、その抽象構文木のルートノードを子ノードcond(whileを表現するノードの子ノード)として登録します。
exprの次のトークンは")"であることが期待されているのでexpect(")")を呼び出しトークン列を1つ読み進めます。
stmt()を呼び出して、真文に対応する抽象構文木を生成し、その抽象構文木のルートノードを子ノードthen(whileを表現するノードの子ノード)として登録します。
exprと";"(変更なし)
Node *node = read_expr_stmt(); expect(";"); return node;
gen関数
https://github.com/rui314/chibicc/commit/a072d39871dbade251c7b9d1d93ecf38c35f0055#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR70
https://github.com/rui314/chibicc/blob/a072d39871dbade251c7b9d1d93ecf38c35f0055/codegen.c#L70
void gen(Node *node) { switch (node->kind) { case ND_NUM: printf(" push %d\n", node->val); return; case ND_EXPR_STMT: gen(node->lhs); printf(" add rsp, 8\n"); return; case ND_VAR: gen_addr(node); load(); return; case ND_ASSIGN: gen_addr(node->lhs); gen(node->rhs); store(); return; case ND_IF: { int seq = labelseq++; if (node->els) { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lelse%d\n", seq); gen(node->then); printf(" jmp .Lend%d\n", seq); printf(".Lelse%d:\n", seq); gen(node->els); printf(".Lend%d:\n", seq); } else { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); gen(node->then); printf(".Lend%d:\n", seq); } return; } case ND_WHILE: { int seq = labelseq++; printf(".Lbegin%d:\n", seq); gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); gen(node->then); printf(" jmp .Lbegin%d\n", seq); printf(".Lend%d:\n", seq); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; } gen(node->lhs); gen(node->rhs); printf(" pop rdi\n"); printf(" pop rax\n"); switch (node->kind) { case ND_ADD: printf(" add rax, rdi\n"); break; case ND_SUB: printf(" sub rax, rdi\n"); break; case ND_MUL: printf(" imul rax, rdi\n"); break; case ND_DIV: printf(" cqo\n"); printf(" idiv rdi\n"); break; case ND_EQ: printf(" cmp rax, rdi\n"); printf(" sete al\n"); printf(" movzb rax, al\n"); break; case ND_NE: printf(" cmp rax, rdi\n"); printf(" setne al\n"); printf(" movzb rax, al\n"); break; case ND_LT: printf(" cmp rax, rdi\n"); printf(" setl al\n"); printf(" movzb rax, al\n"); break; case ND_LE: printf(" cmp rax, rdi\n"); printf(" setle al\n"); printf(" movzb rax, al\n"); break; } printf(" push rax\n"); }
二項演算以外を行うアセンブリコードを生成する
switch (node->kind) { case ND_NUM: printf(" push %d\n", node->val); return; case ND_EXPR_STMT: gen(node->lhs); printf(" add rsp, 8\n"); return; case ND_VAR: gen_addr(node); load(); return; case ND_ASSIGN: gen_addr(node->lhs); gen(node->rhs); store(); return; case ND_IF: { int seq = labelseq++; if (node->els) { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lelse%d\n", seq); gen(node->then); printf(" jmp .Lend%d\n", seq); printf(".Lelse%d:\n", seq); gen(node->els); printf(".Lend%d:\n", seq); } else { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); gen(node->then); printf(".Lend%d:\n", seq); } return; } case ND_WHILE: { int seq = labelseq++; printf(".Lbegin%d:\n", seq); gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); gen(node->then); printf(" jmp .Lbegin%d\n", seq); printf(".Lend%d:\n", seq); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; }
ノードの型がND_WHILEの場合(ノードの種類がwhileの場合)における処理を追加します。
①printf(".Lbegin%d:\n", seq);
先頭へジャンプする際に使用される(while文を継続する際に使用される)ラベルとなるアセンブリコードを生成します。
②gen(node->cond);
条件式の評価値(node->condの結果)を得るために必要となるアセンブリコードを生成します。
③printf(" pop rax\n");
条件式の評価値(node->condの結果)をスタックから取得するアセンブリコードを生成します。
④printf(" cmp rax, 0\n");printf(" je .Lend%d\n", seq);
条件式の評価値(node->condの結果)が0の場合に、 後尾へジャンプする(while文から脱出する)アセンブリコードを生成します。
⑤gen(node->then);
真文(node->thenに対応する式文)となるアセンブリコードを生成します。
⑥printf(" jmp .Lbegin%d\n", seq);
先頭へジャンプする(while文を継続する)アセンブリコードを生成します。
⑦printf(".Lend%d:\n", seq);
後尾へジャンプする際に使用される(while文から脱出する際に使用される)ラベルとなるアセンブリコードを生成します。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
gen(node->lhs); gen(node->rhs); printf(" pop rdi\n"); printf(" pop rax\n");
二項演算を行うアセンブリコードを生成する(変更なし)
switch (node->kind) { case ND_ADD: printf(" add rax, rdi\n"); break; case ND_SUB: printf(" sub rax, rdi\n"); break; case ND_MUL: printf(" imul rax, rdi\n"); break; case ND_DIV: printf(" cqo\n"); printf(" idiv rdi\n"); break; case ND_EQ: printf(" cmp rax, rdi\n"); printf(" sete al\n"); printf(" movzb rax, al\n"); break; case ND_NE: printf(" cmp rax, rdi\n"); printf(" setne al\n"); printf(" movzb rax, al\n"); break; case ND_LT: printf(" cmp rax, rdi\n"); printf(" setl al\n"); printf(" movzb rax, al\n"); break; case ND_LE: printf(" cmp rax, rdi\n"); printf(" setle al\n"); printf(" movzb rax, al\n"); break; } printf(" push rax\n");
追加・修正されたコンパイラのソースコード(for文を扱えるコンパイラを作成する)
starts_with_reserved関数
https://github.com/rui314/chibicc/commit/801ee65d8190d69ace65000763a93629a911c14b#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R99
https://github.com/rui314/chibicc/blob/801ee65d8190d69ace65000763a93629a911c14b/tokenize.c#L99
char *starts_with_reserved(char *p) { // Keyword static char *kw[] = {"return", "if", "else", "while", "for"}; for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) { int len = strlen(kw[i]); if (startswith(p, kw[i]) && !is_alnum(p[len])) return kw[i]; } // Multi-letter punctuator static char *ops[] = {"==", "!=", "<=", ">="}; for (int i = 0; i < sizeof(ops) / sizeof(*ops); i++) if (startswith(p, ops[i])) return ops[i]; return NULL; }
キーワードを取得する
// Keyword static char *kw[] = {"return", "if", "else", "while", "for"}; for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) { int len = strlen(kw[i]); if (startswith(p, kw[i]) && !is_alnum(p[len])) return kw[i]; }
配列kwにキーワード"for"を追加します。
複数文字の記号を取得する(変更なし)
// Multi-letter punctuator static char *ops[] = {"==", "!=", "<=", ">="}; for (int i = 0; i < sizeof(ops) / sizeof(*ops); i++) if (startswith(p, ops[i])) return ops[i]; return NULL;
NodeKind
https://github.com/rui314/chibicc/commit/801ee65d8190d69ace65000763a93629a911c14b#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R70
https://github.com/rui314/chibicc/blob/801ee65d8190d69ace65000763a93629a911c14b/chibicc.h#L70
// AST node typedef enum { ND_ADD, // + ND_SUB, // - ND_MUL, // * ND_DIV, // / ND_EQ, // == ND_NE, // != ND_LT, // < ND_LE, // <= ND_ASSIGN, // = ND_RETURN, // "return" ND_IF, // "if" ND_WHILE, // "while" ND_FOR, // "for" ND_EXPR_STMT, // Expression statement ND_VAR, // Variable ND_NUM, // Integer } NodeKind;
キーワードforを表現するノード型ND_FORを追加します。
Node構造体
https://github.com/rui314/chibicc/commit/801ee65d8190d69ace65000763a93629a911c14b#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R85
https://github.com/rui314/chibicc/blob/801ee65d8190d69ace65000763a93629a911c14b/chibicc.h#L85
// AST node type typedef struct Node Node; struct Node { NodeKind kind; // Node kind Node *next; // Next node Node *lhs; // Left-hand side Node *rhs; // Right-hand side // "if, "while" or "for" statement Node *cond; Node *then; Node *els; Node *init; Node *inc; Var *var; // Used if kind == ND_VAR int val; // Used if kind == ND_NUM };
for文をパースする際に使用する子ノードinit、incを追加します。
stmt関数
https://github.com/rui314/chibicc/commit/801ee65d8190d69ace65000763a93629a911c14b#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR117
https://github.com/rui314/chibicc/blob/801ee65d8190d69ace65000763a93629a911c14b/parse.c#L117
Node *stmt() { if (consume("return")) { Node *node = new_unary(ND_RETURN, expr()); expect(";"); return node; } if (consume("if")) { Node *node = new_node(ND_IF); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); if (consume("else")) node->els = stmt(); return node; } if (consume("while")) { Node *node = new_node(ND_WHILE); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); return node; } if (consume("for")) { Node *node = new_node(ND_FOR); expect("("); if (!consume(";")) { node->init = read_expr_stmt(); expect(";"); } if (!consume(";")) { node->cond = expr(); expect(";"); } if (!consume(")")) { node->inc = read_expr_stmt(); expect(")"); } node->then = stmt(); return node; } Node *node = read_expr_stmt(); expect(";"); return node; }
stmt関数は、生成規則 stmt = "return" expr ";" | "if" "(" expr ")" stmt ("else" stmt)? | "while" "(" expr ")" stmt | "for" "(" expr? ";" expr? ";" expr? ")" stmt | expr ";" に基づいて、抽象構文木のノードを生成します。
"return"とexprと";"(変更なし)
if (consume("return")) { Node *node = new_unary(ND_RETURN, expr()); expect(";"); return node; }
"if"、"("、expr、")"、stmt 、「"else" と stmt」を0回か1回(変更なし)
if (consume("if")) { Node *node = new_node(ND_IF); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); if (consume("else")) node->els = stmt(); return node; }
"while"と"("とexprと ")"と stmt(変更なし)
if (consume("while")) { Node *node = new_node(ND_WHILE); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); return node; }
"for"、"("、exprを0回か1回、 ";"、exprを0回か1回、";"、exprを0回か1回、")"、stmt
if (consume("for")) { Node *node = new_node(ND_FOR); expect("("); if (!consume(";")) { node->init = read_expr_stmt(); expect(";"); } if (!consume(";")) { node->cond = expr(); expect(";"); } if (!consume(")")) { node->inc = read_expr_stmt(); expect(")"); } node->then = stmt(); return node; }
consume("for")の戻り値がtrueとなる場合→着目しているトークンが"for"の場合は、new_node関数を呼び出してキーワードforを表現するノードを生成します。
forの次のトークンは"("であることが期待されているのでexpect("(")を呼び出しトークン列を1つ読み進めます。
consume(";")の戻り値がfalseとなる場合→初期設定式のトークン列がある場合は、read_expr_stmt関数を呼び出して式文(初期設定式)に対応する抽象構文木を生成し、その抽象構文木のルートノードを子ノードinit(forを表現するノードの子ノード)として登録します。
初期設定式のトークン列の次のトークンは")"であることが期待されているのでexpect(")")を呼び出しトークン列を1つ読み進めます。
consume(";")の戻り値がfalseとなる場合→継続条件式のトークン列がある場合は、expr関数を呼び出して式(継続条件式)に対応する抽象構文木を生成し、その抽象構文木のルートノードを子ノードcond(forを表現するノードの子ノード)として登録します。
継続条件式のトークン列の次のトークンは")"であることが期待されているのでexpect(")")を呼び出しトークン列を1つ読み進めます。
consume(";")の戻り値がfalseとなる場合→再設定式のトークン列がある場合は、read_expr_stmt関数を呼び出して式文(再設定式)に対応する抽象構文木を生成し、その抽象構文木のルートノードを子ノードinc(forを表現するノードの子ノード)として登録します。
再設定式のトークン列の次のトークンは")"であることが期待されているのでexpect(")")を呼び出しトークン列を1つ読み進めます。
最後に、stmt()を呼び出して真文に対応する抽象構文木を生成し、その抽象構文木のルートノードを子ノードthen(forを表現するノードの子ノード)として登録します。
exprと";"(変更なし)
Node *node = read_expr_stmt(); expect(";"); return node;
gen関数
https://github.com/rui314/chibicc/commit/801ee65d8190d69ace65000763a93629a911c14b#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR82
https://github.com/rui314/chibicc/blob/801ee65d8190d69ace65000763a93629a911c14b/codegen.c#L82
void gen(Node *node) { switch (node->kind) { case ND_NUM: printf(" push %d\n", node->val); return; case ND_EXPR_STMT: gen(node->lhs); printf(" add rsp, 8\n"); return; case ND_VAR: gen_addr(node); load(); return; case ND_ASSIGN: gen_addr(node->lhs); gen(node->rhs); store(); return; case ND_IF: { int seq = labelseq++; if (node->els) { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lelse%d\n", seq); gen(node->then); printf(" jmp .Lend%d\n", seq); printf(".Lelse%d:\n", seq); gen(node->els); printf(".Lend%d:\n", seq); } else { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); gen(node->then); printf(".Lend%d:\n", seq); } return; } case ND_WHILE: { int seq = labelseq++; printf(".Lbegin%d:\n", seq); gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); gen(node->then); printf(" jmp .Lbegin%d\n", seq); printf(".Lend%d:\n", seq); return; } case ND_FOR: { int seq = labelseq++; if (node->init) gen(node->init); printf(".Lbegin%d:\n", seq); if (node->cond) { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); } gen(node->then); if (node->inc) gen(node->inc); printf(" jmp .Lbegin%d\n", seq); printf(".Lend%d:\n", seq); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; } gen(node->lhs); gen(node->rhs); printf(" pop rdi\n"); printf(" pop rax\n"); switch (node->kind) { case ND_ADD: printf(" add rax, rdi\n"); break; case ND_SUB: printf(" sub rax, rdi\n"); break; case ND_MUL: printf(" imul rax, rdi\n"); break; case ND_DIV: printf(" cqo\n"); printf(" idiv rdi\n"); break; case ND_EQ: printf(" cmp rax, rdi\n"); printf(" sete al\n"); printf(" movzb rax, al\n"); break; case ND_NE: printf(" cmp rax, rdi\n"); printf(" setne al\n"); printf(" movzb rax, al\n"); break; case ND_LT: printf(" cmp rax, rdi\n"); printf(" setl al\n"); printf(" movzb rax, al\n"); break; case ND_LE: printf(" cmp rax, rdi\n"); printf(" setle al\n"); printf(" movzb rax, al\n"); break; } printf(" push rax\n"); }
二項演算以外を行うアセンブリコードを生成する
switch (node->kind) { case ND_NUM: printf(" push %d\n", node->val); return; case ND_EXPR_STMT: gen(node->lhs); printf(" add rsp, 8\n"); return; case ND_VAR: gen_addr(node); load(); return; case ND_ASSIGN: gen_addr(node->lhs); gen(node->rhs); store(); return; case ND_IF: { int seq = labelseq++; if (node->els) { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lelse%d\n", seq); gen(node->then); printf(" jmp .Lend%d\n", seq); printf(".Lelse%d:\n", seq); gen(node->els); printf(".Lend%d:\n", seq); } else { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); gen(node->then); printf(".Lend%d:\n", seq); } return; } case ND_WHILE: { int seq = labelseq++; printf(".Lbegin%d:\n", seq); gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); gen(node->then); printf(" jmp .Lbegin%d\n", seq); printf(".Lend%d:\n", seq); return; } case ND_FOR: { int seq = labelseq++; if (node->init) gen(node->init); printf(".Lbegin%d:\n", seq); if (node->cond) { gen(node->cond); printf(" pop rax\n"); printf(" cmp rax, 0\n"); printf(" je .Lend%d\n", seq); } gen(node->then); if (node->inc) gen(node->inc); printf(" jmp .Lbegin%d\n", seq); printf(".Lend%d:\n", seq); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; }
ノードの型がND_FORの場合(ノードの種類がforの場合)における処理を追加します。
①gen(node->init);
node->initが真となる場合 → 初期設定式に対応する抽象構文木がある場合は、gen関数を呼び出して初期設定式(node->initに対応する式文)となるアセンブリコードを生成します。
②printf(".Lbegin%d:\n", seq);
先頭へジャンプする際に使用される(for文を継続する際に使用される)ラベルを生成します。
③gen(node->cond);
node->condが真となる場合 → 継続条件式に対応する抽象構文木がある場合は、gen関数を呼び出して継続条件式の評価値(node->condの結果)を得るために必要なアセンブリコードを生成します。
④printf(" pop rax\n");
継続条件式の評価値(node->condの結果)をスタックから得るためのアセンブリコードを生成します。
⑤printf(" cmp rax, 0\n");printf(" je .Lend%d\n", seq);
継続条件式の評価値(node->condの結果)が0の場合に、後尾へジャンプする(for文から脱出する)アセンブリコードを生成します。
⑥gen(node->then);
真文(node->thenに対応する式文)となるアセンブリコードを生成します。
⑦gen(node->inc);
node->incが真となる場合 → 再設定式に対応する抽象構文木がある場合は、gen関数を呼び出して再設定式(node->incに対応する式文)となるアセンブリコードを生成します。
⑧printf(" jmp .Lbegin%d\n", seq);
先頭へジャンプする(for文を継続する)アセンブリコードを生成します。
⑨printf(".Lend%d:\n", seq);
後尾へジャンプする際に使用される(for文を脱出する際に使用される)ラベルを生成します。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
gen(node->lhs); gen(node->rhs); printf(" pop rdi\n"); printf(" pop rax\n");
二項演算を行うアセンブリコードを生成する(変更なし)
switch (node->kind) { case ND_ADD: printf(" add rax, rdi\n"); break; case ND_SUB: printf(" sub rax, rdi\n"); break; case ND_MUL: printf(" imul rax, rdi\n"); break; case ND_DIV: printf(" cqo\n"); printf(" idiv rdi\n"); break; case ND_EQ: printf(" cmp rax, rdi\n"); printf(" sete al\n"); printf(" movzb rax, al\n"); break; case ND_NE: printf(" cmp rax, rdi\n"); printf(" setne al\n"); printf(" movzb rax, al\n"); break; case ND_LT: printf(" cmp rax, rdi\n"); printf(" setl al\n"); printf(" movzb rax, al\n"); break; case ND_LE: printf(" cmp rax, rdi\n"); printf(" setle al\n"); printf(" movzb rax, al\n"); break; } printf(" push rax\n"); }
テストコード
https://github.com/rui314/chibicc/blob/801ee65d8190d69ace65000763a93629a911c14b/test.sh
#!/bin/bash assert() { expected="$1" input="$2" ./chibicc "$input" > tmp.s gcc -static -o tmp tmp.s ./tmp actual="$?" if [ "$actual" = "$expected" ]; then echo "$input => $actual" else echo "$input => $expected expected, but got $actual" exit 1 fi } assert 0 'return 0;' assert 42 'return 42;' assert 21 'return 5+20-4;' assert 41 'return 12 + 34 - 5 ;' assert 47 'return 5+6*7;' assert 15 'return 5*(9-6);' assert 4 'return (3+5)/2;' assert 10 'return -10+20;' assert 10 'return - -10;' assert 10 'return - - +10;' assert 0 'return 0==1;' assert 1 'return 42==42;' assert 1 'return 0!=1;' assert 0 'return 42!=42;' assert 1 'return 0<1;' assert 0 'return 1<1;' assert 0 'return 2<1;' assert 1 'return 0<=1;' assert 1 'return 1<=1;' assert 0 'return 2<=1;' assert 1 'return 1>0;' assert 0 'return 1>1;' assert 0 'return 1>2;' assert 1 'return 1>=0;' assert 1 'return 1>=1;' assert 0 'return 1>=2;' assert 3 'a=3; return a;' assert 8 'a=3; z=5; return a+z;' assert 1 'return 1; 2; 3;' assert 2 '1; return 2; 3;' assert 3 '1; 2; return 3;' assert 3 'foo=3; return foo;' assert 8 'foo123=3; bar=5; return foo123+bar;' assert 3 'if (0) return 2; return 3;' assert 3 'if (1-1) return 2; return 3;' assert 2 'if (1) return 2; return 3;' assert 2 'if (2-1) return 2; return 3;' assert 10 'i=0; while(i<10) i=i+1; return i;' assert 55 'i=0; j=0; for (i=0; i<=10; i=i+1) j=i+j; return j;' assert 3 'for (;;) return 3; return 5;' echo OK
Makefile
https://github.com/rui314/chibicc/blob/801ee65d8190d69ace65000763a93629a911c14b/Makefile
CFLAGS=-std=c11 -g -static SRCS=$(wildcard *.c) OBJS=$(SRCS:.c=.o) chibicc: $(OBJS) $(CC) -o $@ $(OBJS) $(LDFLAGS) $(OBJS): chibicc.h test: chibicc ./test.sh clean: rm -f chibicc *.o *~ tmp* .PHONY: test clean