chibiccを読む~Cコンパイラコードリーディング~ ステップ17, 18, 19
トップページ
jupiteroak.hatenablog.com
「低レイヤを知りたい人のためのCコンパイラ作成入門」のCコンパイラを読んでいきます。
www.sigbus.info
ステップ18, 19に該当
github.com
ステップ17に該当
github.com
- 今回作成するコンパイラ
- 追加・修正されたコンパイラのソースコード(ステップ18・19、ポインタを扱えるコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード(ステップ17、int型を扱えるコンパイラを作成する)
- テストコード
- Makefile
今回作成するコンパイラ
ポインタ型を導入する・ポインタの加算と減算を実装する(ステップ18・19、ポインタを扱えるコンパイラを作成する)
↓
暗黙の変数定義を廃止して、intというキーワードを導入する(ステップ17、int型を扱えるコンパイラを作成する)
追加・修正されたコンパイラのソースコード(ステップ18・19、ポインタを扱えるコンパイラを作成する)
main関数
https://github.com/rui314/chibicc/commit/1813fe470e4774c8d52b867649545d93f677323c#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR11
https://github.com/rui314/chibicc/blob/1813fe470e4774c8d52b867649545d93f677323c/main.c#L11
int main(int argc, char **argv) { if (argc != 2) error("%s: invalid number of arguments", argv[0]); // Tokenize and parse. user_input = argv[1]; token = tokenize(); Function *prog = program(); add_type(prog); // Assign offsets to local variables. for (Function *fn = prog; fn; fn = fn->next) { int offset = 0; for (VarList *vl = fn->locals; vl; vl = vl->next) { offset += 8; vl->var->offset = offset; } fn->stack_size = offset; } // Traverse the AST to emit assembly. codegen(prog); return 0; }
コマンドライン引数の個数をチェックする(変更なし)
if (argc != 2) error("%s: invalid number of arguments", argv[0]);
トークナイズを行う(変更なし)
user_input = argv[1]; token = tokenize();
パースを行う(変更なし)
Function *prog = program();
引数・ローカル変数のアドレスとスタックサイズを定める(変更なし)
// Assign offsets to local variables. for (Function *fn = prog; fn; fn = fn->next) { int offset = 0; for (VarList *vl = fn->locals; vl; vl = vl->next) { offset += 8; vl->var->offset = offset; } fn->stack_size = offset; }
アセンブリコードを生成する(変更なし)
// Traverse the AST to emit assembly. codegen(prog); return 0;
TypeKind
https://github.com/rui314/chibicc/commit/1813fe470e4774c8d52b867649545d93f677323c#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R135
https://github.com/rui314/chibicc/blob/1813fe470e4774c8d52b867649545d93f677323c/chibicc.h#L135
typedef enum { TY_INT, TY_PTR } TypeKind;
型の種類を表現する名前付き整数定数を定め、TypeKind型を定義します。
TY_INTは int型 を、TY_PTRは ~へのポインタ型 を表現しています。
Type構造体
https://github.com/rui314/chibicc/commit/1813fe470e4774c8d52b867649545d93f677323c#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R137
https://github.com/rui314/chibicc/blob/1813fe470e4774c8d52b867649545d93f677323c/chibicc.h#L137
https://github.com/rui314/chibicc/commit/1813fe470e4774c8d52b867649545d93f677323c#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R8
https://github.com/rui314/chibicc/blob/1813fe470e4774c8d52b867649545d93f677323c/chibicc.h#L8
struct Type { TypeKind kind; Type *base; }; typedef struct Type Type;
型を表現するデータ構造であるType構造体を定義し、さらに、Type型として定義します。
Node構造体
https://github.com/rui314/chibicc/commit/1813fe470e4774c8d52b867649545d93f677323c#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R94
https://github.com/rui314/chibicc/blob/1813fe470e4774c8d52b867649545d93f677323c/chibicc.h#L94
// AST node type typedef struct Node Node; struct Node { NodeKind kind; // Node kind Node *next; // Next node Type *ty; // Type, e.g. int or pointer to int
型付けを行うために、Node構造体にtyメンバを追加します。
add_type関数
https://github.com/rui314/chibicc/commit/1813fe470e4774c8d52b867649545d93f677323c#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R75
https://github.com/rui314/chibicc/blob/1813fe470e4774c8d52b867649545d93f677323c/type.c#L75
void add_type(Function *prog) { for (Function *fn = prog; fn; fn = fn->next) for (Node *node = fn->node; node; node = node->next) visit(node); }
add_type関数は、プログラム内にある全ての抽象構文木に対して型付け処理を行います。
visit関数
https://github.com/rui314/chibicc/commit/1813fe470e4774c8d52b867649545d93f677323c#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R16
https://github.com/rui314/chibicc/blob/1813fe470e4774c8d52b867649545d93f677323c/type.c#L16
void visit(Node *node) { if (!node) return; visit(node->lhs); visit(node->rhs); visit(node->cond); visit(node->then); visit(node->els); visit(node->init); visit(node->inc); for (Node *n = node->body; n; n = n->next) visit(n); for (Node *n = node->args; n; n = n->next) visit(n); switch (node->kind) { case ND_MUL: case ND_DIV: case ND_EQ: case ND_NE: case ND_LT: case ND_LE: case ND_VAR: case ND_FUNCALL: case ND_NUM: node->ty = int_type(); return; case ND_ADD: if (node->rhs->ty->kind == TY_PTR) { Node *tmp = node->lhs; node->lhs = node->rhs; node->rhs = tmp; } if (node->rhs->ty->kind == TY_PTR) error_tok(node->tok, "invalid pointer arithmetic operands"); node->ty = node->lhs->ty; return; case ND_SUB: if (node->rhs->ty->kind == TY_PTR) error_tok(node->tok, "invalid pointer arithmetic operands"); node->ty = node->lhs->ty; return; case ND_ASSIGN: node->ty = node->lhs->ty; return; case ND_ADDR: node->ty = pointer_to(node->lhs->ty); return; case ND_DEREF: if (node->lhs->ty->kind == TY_PTR) node->ty = node->lhs->ty->base; else node->ty = int_type(); return; } }
visit関数は、抽象構文木が持つ全てのノードに対して型付け処理を行います。
ノードがセットされていない場合
if (!node) return;
引数nodeにアドレスがセットされていない場合は、何もせずリターンします。
全ての子のノードに対してvisit関数を再帰的に呼び出す
visit(node->lhs); visit(node->rhs); visit(node->cond); visit(node->then); visit(node->els); visit(node->init); visit(node->inc); for (Node *n = node->body; n; n = n->next) visit(n); for (Node *n = node->args; n; n = n->next) visit(n);
全ての子ノードに対してvisit関数を再帰的に呼び出します。
子ノードnode->bodyと子ノードnode->argsは抽象構文木のルートノードからなる連結リストなので、連結リストの全ての要素に対してもvisit関数を呼び出します。
ノード型に応じて型付け処理を行う(int型を付ける)
switch (node->kind) { case ND_MUL: case ND_DIV: case ND_EQ: case ND_NE: case ND_LT: case ND_LE: case ND_VAR: case ND_FUNCALL: case ND_NUM: node->ty = int_type(); return;
ノードの型が、ND_MUL( * )、ND_DIV( / )、ND_EQ(=)、ND_NE(!=)、ND_LT(<)、ND_LE(<=)、ND_VAR(ローカル変数)、ND_FUNCALL(関数呼び出し)、ND_NUM(数値)の場合は、int_type関数を呼び出してint型を表現したType構造体を生成し、そのアドレスをtyメンバにセットします。
ノード型に応じて型付け処理を行う(ND_ADD)
case ND_ADD: if (node->rhs->ty->kind == TY_PTR) { Node *tmp = node->lhs; node->lhs = node->rhs; node->rhs = tmp; } if (node->rhs->ty->kind == TY_PTR) error_tok(node->tok, "invalid pointer arithmetic operands"); node->ty = node->lhs->ty; return;
ノード型がND_ADD(+)の場合は、左右両方のオペランドが「~へのポインタ」型の場合、左右どちらか一方のオペランドが「~へのポインタ」型の場合、左右両方のオペランドが「~へのポインタ」型ではない場合、を考慮した処理を行います。
node->rhs->ty->kind == TY_PTR が真となる場合 → 右オペランドが「~へのポインタ」型の場合は、子ノードを入れ替えて左オペランドが「~へのポインタ」型になるようにします。
(右オペランドに「~へのポインタ」型を持つノードが対応する場合は、子ノードを入れ替えて、左オペランドに「~へのポインタ」型を持つノードが対応するようにします。)
node->rhs->ty->kind == TY_PTR が真となる場合 → 右オペランドが「~へのポインタ」型の場合 → 左右両方のオペランドが「~へのポインタ」型だった場合は、error_tok関数を呼び出してエラーメッセージを出力します。
node->rhs->ty->kind == TY_PTR が偽となる場合 → 右オペランドが「~へのポインタ」型ではない場合 → 左右どちらか一方のオペランドが「~へのポインタ」型だった、または、左右両方のオペランドが「~へのポインタ」型ではなかった場合は、ノードnodeの型の種類を左オペランドの型と同じ種類にします。
ノード型に応じて型付け処理を行う(ND_SUB)
case ND_SUB: if (node->rhs->ty->kind == TY_PTR) error_tok(node->tok, "invalid pointer arithmetic operands"); node->ty = node->lhs->ty; return;
ノード型がND_SUB( - )の場合は、右オペランドの型に注意して型付け処理を行います。
node->rhs->ty->kind == TY_PTR が真となる場合 → 右オペランドが「~へのポインタ」型の場合は、error_tok関数を呼び出してエラーメッセージを出力します。
node->rhs->ty->kind == TY_PTR が偽となる場合 → 右オペランドが「~へのポインタ」型ではない場合は、ノードnodeの型の種類を左オペランドの型と同じ種類にします。
ノード型に応じて型付け処理を行う(ND_ASSIGN)
case ND_ASSIGN: node->ty = node->lhs->ty; return;
ノード型がND_ASSIGN(=)の場合は、ノードnodeの型の種類を左オペランドの型と同じ種類にします。
(ノード型がND_ASSIGN(=)の場合は、親ノードnodeが持つ型を左オペランドに対応するノードが持つ型と同じ種類にします。)
ノード型に応じて型付け処理を行う(ND_ADDR)
case ND_ADDR: node->ty = pointer_to(node->lhs->ty); return;
ノード型が ND_ADDR(&)の場合は、pointer_to関数を呼び出して、「"子ノードnode->lhsが持つ型"へのポインタ」型を表現するType構造体を生成し、nodeのtyメンバにセットします(nodeの持つ型を「"子ノードnode->lhsが持つ型"へのポインタ」型にします)。
ノード型に応じて型付け処理を行う(ND_DEREF)
case ND_DEREF: if (node->lhs->ty->kind == TY_PTR) node->ty = node->lhs->ty->base; else node->ty = int_type(); return; }
ノード型が ND_DEREF( * )の場合は、オペランドの型が「~へのポインタ」型である場合と「~へのポインタ」型ではない場合を考慮して型付け処理を行います。
node->lhs->ty->kind == TY_PTR が真となる場合 → オペランドの型が「~へのポインタ」型の場合は、親ノードnodeが持つ型を、子ノードnode->lhs(オペランド)が持つ型が参照している型と、同じ種類の型にします。
node->lhs->ty->kind == TY_PTR が偽となる場合 → オペランドの型が「~へのポインタ」型ではない場合は、int_type関数を呼び出して親ノードnodeの型をint型にします(int_type関数を呼び出してint型を表現したType構造体を生成し、そのアドレスをtyメンバにセットします)。
int_type関数
https://github.com/rui314/chibicc/commit/1813fe470e4774c8d52b867649545d93f677323c#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R3
https://github.com/rui314/chibicc/blob/1813fe470e4774c8d52b867649545d93f677323c/type.c#L3
Type *int_type() { Type *ty = calloc(1, sizeof(Type)); ty->kind = TY_INT; return ty; }
int_type関数は、int型を表現するType構造体を生成します。
pointer_to関数
https://github.com/rui314/chibicc/commit/1813fe470e4774c8d52b867649545d93f677323c#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R9
https://github.com/rui314/chibicc/blob/1813fe470e4774c8d52b867649545d93f677323c/type.c#L9
Type *pointer_to(Type *base) { Type *ty = calloc(1, sizeof(Type)); ty->kind = TY_PTR; ty->base = base; return ty; }
pointer_to関数は、base型へのポインタを表現するType構造体を生成します。
gen関数
https://github.com/rui314/chibicc/commit/1813fe470e4774c8d52b867649545d93f677323c#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR164
https://github.com/rui314/chibicc/blob/1813fe470e4774c8d52b867649545d93f677323c/codegen.c#L164
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_ADDR: gen_addr(node->lhs); return; case ND_DEREF: gen(node->lhs); load(); 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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); // We need to align RSP to a 16 byte boundary before // calling a function because it is an ABI requirement. // RAX is set to 0 for variadic function. int seq = labelseq++; printf(" mov rax, rsp\n"); printf(" and rax, 15\n"); printf(" jnz .Lcall%d\n", seq); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" jmp .Lend%d\n", seq); printf(".Lcall%d:\n", seq); printf(" sub rsp, 8\n"); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" add rsp, 8\n"); printf(".Lend%d:\n", seq); printf(" push rax\n"); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn.%s\n", funcname); return; } gen(node->lhs); gen(node->rhs); printf(" pop rdi\n"); printf(" pop rax\n"); switch (node->kind) { case ND_ADD: if (node->ty->kind == TY_PTR) printf(" imul rdi, 8\n"); printf(" add rax, rdi\n"); break; case ND_SUB: if (node->ty->kind == TY_PTR) printf(" imul rdi, 8\n"); 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_ADDR: gen_addr(node->lhs); return; case ND_DEREF: gen(node->lhs); load(); 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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); // We need to align RSP to a 16 byte boundary before // calling a function because it is an ABI requirement. // RAX is set to 0 for variadic function. int seq = labelseq++; printf(" mov rax, rsp\n"); printf(" and rax, 15\n"); printf(" jnz .Lcall%d\n", seq); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" jmp .Lend%d\n", seq); printf(".Lcall%d:\n", seq); printf(" sub rsp, 8\n"); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" add rsp, 8\n"); printf(".Lend%d:\n", seq); printf(" push rax\n"); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn.%s\n", funcname); return; }
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
gen(node->lhs); gen(node->rhs); printf(" pop rdi\n"); printf(" pop rax\n");
二項演算を行うアセンブリコードを生成する
switch (node->kind) { case ND_ADD: if (node->ty->kind == TY_PTR) printf(" imul rdi, 8\n"); printf(" add rax, rdi\n"); break; case ND_SUB: if (node->ty->kind == TY_PTR) printf(" imul rdi, 8\n"); 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"); }
ノード型がND_ADDの場合(加算を行う場合)とノード型がND_SUBの場合(減算を行う場合)の処理を改修します。
node->ty->kind == TY_PTR が真となる場合 → ノードの型が「~へのポインタ」型である場合は、visit関数で見たように左オペランドが「~へのポインタ」型、右オペランドが整数値となるので、右オペランドの値を8(ポインタのサイズ)倍するために、アセンブリコード" imul rdi, 8"を生成しておきます。
(chibccでは、ポインタのサイズは8バイトとなっています)
追加・修正されたコンパイラのソースコード(ステップ17、int型を扱えるコンパイラを作成する)
starts_with_reserved
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R131
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/tokenize.c#L131
char *starts_with_reserved(char *p) { // Keyword static char *kw[] = {"return", "if", "else", "while", "for", "int"}; 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"}; 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の要素としてintを追加し、intをキーワードとして扱えるようにします。
複数文字の記号を取得する(変更なし)
// 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;
peek関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R52
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/tokenize.c#L52
// Returns true if the current token matches a given string. Token *peek(char *s) { if (token->kind != TK_RESERVED || strlen(s) != token->len || memcmp(token->str, s, token->len)) return NULL; return token; }
peek関数は、現在着目しているトークンが引数sが指定する文字列と一致する場合に、そのトークンを戻り値として取得します。
consume関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R61
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/tokenize.c#L61
// Consumes the current token if it matches a given string. Token *consume(char *s) { if (!peek(s)) return NULL; Token *t = token; token = token->next; return t; }
トークンを戻り値としてリターンする(変更なし)
return t;
expect関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R78
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/tokenize.c#L78
// Ensure that the current token is a given string void expect(char *s) { if (!peek(s)) error_tok(token, "expected \"%s\"", s); token = token->next; }
NodeKind
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R89
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/chibicc.h#L89
typedef enum { ND_ADD, // + ND_SUB, // - ND_MUL, // * ND_DIV, // / ND_EQ, // == ND_NE, // != ND_LT, // < ND_LE, // <= ND_ASSIGN, // = ND_ADDR, // unary & ND_DEREF, // unary * ND_RETURN, // "return" ND_IF, // "if" ND_WHILE, // "while" ND_FOR, // "for" ND_BLOCK, // { ... } ND_FUNCALL, // Function call ND_EXPR_STMT, // Expression statement ND_VAR, // Variable ND_NUM, // Integer ND_NULL, // Empty statement } NodeKind;
アセンブリコードの生成で使用されないノードであることを示すノード型ND_NULLを追加します。
Var構造体
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R57
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/chibicc.h#L57
// Local variable typedef struct Var Var; struct Var { char *name; // Variable name Type *ty; // Type int offset; // Offset from RBP };
ローカル変数の型付けを行うために、Var構造体にtyメンバを追加します。
function関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR117
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/parse.c#L117
Function *function() { locals = NULL; Function *fn = calloc(1, sizeof(Function)); basetype(); fn->name = expect_ident(); expect("("); fn->params = read_func_params(); expect("{"); Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; } fn->node = head.next; fn->locals = locals; return fn; }
Function構造体を生成する
Function *fn = calloc(1, sizeof(Function));
calloc関数を呼び出してFunction構造体を生成します。
basetype、ident、 "("、 paramsを0回か1回、 ")"
basetype(); fn->name = expect_ident(); expect("("); fn->params = read_func_params();
basetype関数を呼び出して、戻り値の型を表したトークンを読み取ってType構造体を生成します。
expect_ident関数を呼び出して、戻り値の型を表したトークンの次にあるトークンが関数名(識別子)であることを確認し、その関数名をFuction構造体のnameメンバに記録します。
expect関数を呼び出して、関数名(識別子)を表したトークンの次にあるトークンが "(" であることを確認します。
read_func_params関数を呼び出して、引数を管理しているValist構造体の連結リストを作成し、その連結リストの先頭アドレスをFuction構造体のparamsメンバに記録します。
(read_func_param関数内で呼び出されるpush_var関数によって、引数を管理しているValist構造体の連結リストはローカル変数を管理しているVarList構造体の連結リストLocalsにも追加されています。)
"{"、stmtを0回以上、 "}"(変更なし)
expect("{"); Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; }
Function構造体に値を設定する
fn->node = head.next; fn->locals = locals; return fn;
実装に対応する抽象構文木の(ルートノードからなる)連結リストのアドレス、ローカル変数を管理しているVarList構造体の連結リストのアドレスを記録します。
read_func_params関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR105
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/parse.c#L105
VarList *read_func_params() { if (consume(")")) return NULL; VarList *head = read_func_param(); VarList *cur = head; while (!consume(")")) { expect(","); cur->next = read_func_param(); cur = cur->next; } return head; }
引数の有無を確認する(変更なし)
if (consume(")")) return NULL;
VarList構造体からなる連結リストの先頭要素を生成する
VarList *head = read_func_param(); VarList *cur = head;
Valist構造体の生成・push_var関数によるVar構造体の生成をread_func_param関数で置き換えます。
VarList構造体からなる連結リストを作成する
while (!consume(")")) { expect(","); cur->next = read_func_param(); cur = cur->next; }
Valist構造体の生成・push_var関数によるVar構造体の生成をread_func_param関数で置き換えます。
VarList構造体をリターンする(変更なし)
return head;
read_func_param関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR94
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/parse.c#L94
VarList *read_func_param() { VarList *vl = calloc(1, sizeof(VarList)); Type *ty = basetype(); vl->var = push_var(expect_ident(), ty); return vl; }
read_func_param関数は、1つの引数を管理しているVarList構造体と1つの引数を表現しているVar構造体を生成します。
VarList構造体を生成する
VarList *vl = calloc(1, sizeof(VarList));
calloc関数を呼び出して、引数を管理するVarList構造体を生成します。
Var構造体を生成する
vl->var = push_var(expect_ident(), ty);
push_var関数を呼び出して、引数を表現しているVar構造体を生成し、VarList構造体のvarメンバにセットします。
VarList構造体を戻り値としてリターンする
return vl;
stmt関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR234
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/parse.c#L234
Node *stmt() { Token *tok; if (tok = consume("return")) { Node *node = new_unary(ND_RETURN, expr(), tok); expect(";"); return node; } if (tok = consume("if")) { Node *node = new_node(ND_IF, tok); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); if (consume("else")) node->els = stmt(); return node; } if (tok = consume("while")) { Node *node = new_node(ND_WHILE, tok); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); return node; } if (tok = consume("for")) { Node *node = new_node(ND_FOR, tok); 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; } if (tok = consume("{")) { Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; } Node *node = new_node(ND_BLOCK, tok); node->body = head.next; return node; } if (tok = peek("int")) return declaration(); Node *node = read_expr_stmt(); expect(";"); return node; }
"return"、expr、";"(変更なし)
if (tok = consume("return")) { Node *node = new_unary(ND_RETURN, expr(), tok); expect(";"); return node; }
"if"、"("、expr、")"、stmt 、「"else" と stmt」を0回か1回(変更なし)
if (tok = consume("if")) { Node *node = new_node(ND_IF, tok); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); if (consume("else")) node->els = stmt(); return node; }
"while"、"("、expr、")"、stmt(変更なし)
if (tok = consume("while")) { Node *node = new_node(ND_WHILE, tok); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); return node; }
"for"、"("、exprを0回か1回、 ";"、exprを0回か1回、";"、exprを0回か1回、")"、stmt(変更なし)
if (tok = consume("for")) { Node *node = new_node(ND_FOR, tok); 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; }
"{"、stmtを0回以上、"}"(変更なし)
if (tok = consume("{")) { Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; } Node *node = new_node(ND_BLOCK, tok); node->body = head.next; return node; }
declaration
if (tok = peek("int")) return declaration();
tok = peek("int")が真となる場合 → 現在着目しているトークンがintの場合は、declarationを呼び出して抽象構文木のノードを生成します。
expr、";"(変更なし)
Node *node = read_expr_stmt(); expect(";"); return node; }
primary関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR369
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/parse.c#L369
Node *primary() { if (consume("(")) { Node *node = expr(); expect(")"); return node; } Token *tok; if (tok = consume_ident()) { if (consume("(")) { Node *node = new_node(ND_FUNCALL, tok); node->funcname = strndup(tok->str, tok->len); node->args = func_args(); return node; } Var *var = find_var(tok); if (!var) error_tok(tok, "undefined variable"); return new_var(var, tok); } tok = token; if (tok->kind != TK_NUM) error_tok(tok, "expected expression"); return new_num(expect_number(), tok); }
"("、expr、")"(変更なし)
if (consume("(")) { Node *node = expr(); expect(")"); return node; }
ident、func-argsを0回か1回
Token *tok; if (tok = consume_ident()) { if (consume("(")) { Node *node = new_node(ND_FUNCALL, tok); node->funcname = strndup(tok->str, tok->len); node->args = func_args(); return node; } Var *var = find_var(tok); if (!var) error_tok(tok, "undefined variable"); return new_var(var, tok); }
find_var関数を呼び出してVar構造体を取得できなかった場合 → 現在着目しているトークンが持つ変数名が宣言がされていないものだった場合は、error_tok関数を呼び出してエラーメッセージを出力します。
num(変更なし)
tok = token; if (tok->kind != TK_NUM) error_tok(tok, "expected expression"); return new_num(expect_number(), tok); }
declaration関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR143
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/parse.c#L143
Node *declaration() { Token *tok = token; Type *ty = basetype(); Var *var = push_var(expect_ident(), ty); if (consume(";")) return new_node(ND_NULL, tok); expect("="); Node *lhs = new_var(var, tok); Node *rhs = expr(); expect(";"); Node *node = new_binary(ND_ASSIGN, lhs, rhs, tok); return new_unary(ND_EXPR_STMT, node, tok); }
declaration関数は、生成規則 declaration = basetype ident ("=" expr)? ";" に基づいて、抽象構文木のノードを生成します。
ident
Var *var = push_var(expect_ident(), ty); if (consume(";")) return new_node(ND_NULL, tok);
expect_ident関数を呼び出して取得した変数名(識別子)を用いて、push_var関数を呼び出しVar構造体を生成します。
また、consume(";")が真となる場合 → 現在着目しているトークンが" ; "だった場合 → 変数の宣言時に初期化を行っていない場合は、new_node関数を呼び出してND_NULL型のノードを生成します。
basetype関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR85
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/parse.c#L85
// basetype = "int" "*"* Type *basetype() { expect("int"); Type *ty = int_type(); while (consume("*")) ty = pointer_to(ty); return ty; }
basetype関数は、型を表すトークンを読み取り、その型を表現したType構造体を生成します。
int型のキーワードを表現したType構造体を生成する
expect("int"); Type *ty = int_type();
expect関数を呼び出して、現在着目しているトークンがintであることを確認してから、int_type関数を呼び出しint型を表現したType構造体を生成します。
ポインタを表現したType構造体を生成する
while (consume("*")) ty = pointer_to(ty); return ty;
consume("*")を呼び出してトークンを取得できた場合は、そのたびにpointer_to関数を呼び出して、「~へのポインタ」型を表現したType構造体を生成します。
push_var関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR47
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/parse.c#L47
Var *push_var(char *name, Type *ty) { Var *var = calloc(1, sizeof(Var)); var->name = name; var->ty = ty; VarList *vl = calloc(1, sizeof(VarList)); vl->var = var; vl->next = locals; locals = vl; return var; }
Var構造体を生成する
Var *var = calloc(1, sizeof(Var)); var->name = name; var->ty = ty;
push_var関数内でVar構造体のtyメンバを設定できるようにします。
(push_var関数内でローカル変数の型付けを行えるようにします)
変数を管理しているValist構造体を生成する(変更なし)
VarList *vl = calloc(1, sizeof(VarList)); vl->var = var; vl->next = locals; locals = vl;
生成したVar構造体をリターンする(変更なし)
return var;
visit関数
void visit(Node *node) { if (!node) return; visit(node->lhs); visit(node->rhs); visit(node->cond); visit(node->then); visit(node->els); visit(node->init); visit(node->inc); for (Node *n = node->body; n; n = n->next) visit(n); for (Node *n = node->args; n; n = n->next) visit(n); switch (node->kind) { case ND_MUL: case ND_DIV: case ND_EQ: case ND_NE: case ND_LT: case ND_LE: case ND_FUNCALL: case ND_NUM: node->ty = int_type(); return; case ND_VAR: node->ty = node->var->ty; return; case ND_ADD: if (node->rhs->ty->kind == TY_PTR) { Node *tmp = node->lhs; node->lhs = node->rhs; node->rhs = tmp; } if (node->rhs->ty->kind == TY_PTR) error_tok(node->tok, "invalid pointer arithmetic operands"); node->ty = node->lhs->ty; return; case ND_SUB: if (node->rhs->ty->kind == TY_PTR) error_tok(node->tok, "invalid pointer arithmetic operands"); node->ty = node->lhs->ty; return; case ND_ASSIGN: node->ty = node->lhs->ty; return; case ND_ADDR: node->ty = pointer_to(node->lhs->ty); return; case ND_DEREF: if (node->lhs->ty->kind != TY_PTR) error_tok(node->tok, "invalid pointer dereference"); node->ty = node->lhs->ty->base; return; } }
ノードがセットされていない場合(変更なし)
if (!node) return;
全ての子のノードに対してvisit関数を再帰的に呼び出す(変更なし)
visit(node->lhs); visit(node->rhs); visit(node->cond); visit(node->then); visit(node->els); visit(node->init); visit(node->inc); for (Node *n = node->body; n; n = n->next) visit(n); for (Node *n = node->args; n; n = n->next) visit(n);
ノード型に応じて型付け処理を行う(int型を付ける、変更なし)
switch (node->kind) { case ND_MUL: case ND_DIV: case ND_EQ: case ND_NE: case ND_LT: case ND_LE: case ND_FUNCALL: case ND_NUM: node->ty = int_type(); return; }
ノード型に応じて型付け処理を行う(ND_VAR)
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R44
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/type.c#L44
case ND_VAR: node->ty = node->var->ty; return;
ノード型がND_VARの場合は、そのノードが持っているVar構造体のtyメンバを用いて、そのノードの型付けを行います。
ノード型に応じて型付け処理を行う(ND_ADD、変更なし)
case ND_ADD: if (node->rhs->ty->kind == TY_PTR) { Node *tmp = node->lhs; node->lhs = node->rhs; node->rhs = tmp; } if (node->rhs->ty->kind == TY_PTR) error_tok(node->tok, "invalid pointer arithmetic operands"); node->ty = node->lhs->ty; return;
ノード型に応じて型付け処理を行う(ND_SUB、変更なし)
case ND_SUB: if (node->rhs->ty->kind == TY_PTR) error_tok(node->tok, "invalid pointer arithmetic operands"); node->ty = node->lhs->ty; return;
ノード型に応じて型付け処理を行う(ND_ASSIGN、変更なし)
case ND_ASSIGN: node->ty = node->lhs->ty; return;
ノード型に応じて型付け処理を行う(ND_ADDR、変更なし)
case ND_ADDR: node->ty = pointer_to(node->lhs->ty); return;
ノード型に応じて型付け処理を行う(ND_DEREF)
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R69
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/type.c#L69
case ND_DEREF: if (node->lhs->ty->kind != TY_PTR) error_tok(node->tok, "invalid pointer dereference"); node->ty = node->lhs->ty->base; return; }
宣言されていない変数に型付けを行わないようにします。
node->lhs->ty->kind != TY_PTRが真となる場合 → 親ノードがND_DEREFにもかかわらず、子ノードの持つ型が「~へのポインタ」型ではない場合は、error_tok関数を呼び出してエラーメッセージを出力します。
gen関数
https://github.com/rui314/chibicc/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR41
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/codegen.c#L41
void gen(Node *node) { switch (node->kind) { case ND_NULL: return; 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_ADDR: gen_addr(node->lhs); return; case ND_DEREF: gen(node->lhs); load(); 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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); // We need to align RSP to a 16 byte boundary before // calling a function because it is an ABI requirement. // RAX is set to 0 for variadic function. int seq = labelseq++; printf(" mov rax, rsp\n"); printf(" and rax, 15\n"); printf(" jnz .Lcall%d\n", seq); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" jmp .Lend%d\n", seq); printf(".Lcall%d:\n", seq); printf(" sub rsp, 8\n"); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" add rsp, 8\n"); printf(".Lend%d:\n", seq); printf(" push rax\n"); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn.%s\n", funcname); return; } gen(node->lhs); gen(node->rhs); printf(" pop rdi\n"); printf(" pop rax\n"); switch (node->kind) { case ND_ADD: if (node->ty->kind == TY_PTR) printf(" imul rdi, 8\n"); printf(" add rax, rdi\n"); break; case ND_SUB: if (node->ty->kind == TY_PTR) printf(" imul rdi, 8\n"); 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"); }
二項演算以外を行うアセンブリコードを生成する
void gen(Node *node) { switch (node->kind) { case ND_NULL: return; 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_ADDR: gen_addr(node->lhs); return; case ND_DEREF: gen(node->lhs); load(); 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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); // We need to align RSP to a 16 byte boundary before // calling a function because it is an ABI requirement. // RAX is set to 0 for variadic function. int seq = labelseq++; printf(" mov rax, rsp\n"); printf(" and rax, 15\n"); printf(" jnz .Lcall%d\n", seq); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" jmp .Lend%d\n", seq); printf(".Lcall%d:\n", seq); printf(" sub rsp, 8\n"); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" add rsp, 8\n"); printf(".Lend%d:\n", seq); printf(" push rax\n"); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn.%s\n", funcname); return; }
ノード型がND_NULLの場合は、何もせずリターンするようにします。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
gen(node->lhs); gen(node->rhs); printf(" pop rdi\n"); printf(" pop rax\n");
二項演算を行うアセンブリコードを生成する(変更なし)
switch (node->kind) { case ND_ADD: if (node->ty->kind == TY_PTR) printf(" imul rdi, 8\n"); printf(" add rax, rdi\n"); break; case ND_SUB: if (node->ty->kind == TY_PTR) printf(" imul rdi, 8\n"); 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/commit/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a#diff-3722d9ba8feb2d3feac8ce71a209a638d4b404e1c53f937188761181594023e2R30
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/test.sh#L30
#!/bin/bash cat <<EOF | gcc -xc -c -o tmp2.o - int ret3() { return 3; } int ret5() { return 5; } int add(int x, int y) { return x+y; } int sub(int x, int y) { return x-y; } int add6(int a, int b, int c, int d, int e, int f) { return a+b+c+d+e+f; } EOF assert() { expected="$1" input="$2" ./chibicc "$input" > tmp.s gcc -static -o tmp tmp.s tmp2.o ./tmp actual="$?" if [ "$actual" = "$expected" ]; then echo "$input => $actual" else echo "$input => $expected expected, but got $actual" exit 1 fi } assert 0 'int main() { return 0; }' assert 42 'int main() { return 42; }' assert 21 'int main() { return 5+20-4; }' assert 41 'int main() { return 12 + 34 - 5 ; }' assert 47 'int main() { return 5+6*7; }' assert 15 'int main() { return 5*(9-6); }' assert 4 'int main() { return (3+5)/2; }' assert 10 'int main() { return -10+20; }' assert 10 'int main() { return - -10; }' assert 10 'int main() { return - - +10; }' assert 0 'int main() { return 0==1; }' assert 1 'int main() { return 42==42; }' assert 1 'int main() { return 0!=1; }' assert 0 'int main() { return 42!=42; }' assert 1 'int main() { return 0<1; }' assert 0 'int main() { return 1<1; }' assert 0 'int main() { return 2<1; }' assert 1 'int main() { return 0<=1; }' assert 1 'int main() { return 1<=1; }' assert 0 'int main() { return 2<=1; }' assert 1 'int main() { return 1>0; }' assert 0 'int main() { return 1>1; }' assert 0 'int main() { return 1>2; }' assert 1 'int main() { return 1>=0; }' assert 1 'int main() { return 1>=1; }' assert 0 'int main() { return 1>=2; }' assert 3 'int main() { int a; a=3; return a; }' assert 8 'int main() { int a; int z; a=3; z=5; return a+z; }' assert 3 'int main() { int a=3; return a; }' assert 8 'int main() { int a=3; int z=5; return a+z; }' assert 1 'int main() { return 1; 2; 3; }' assert 2 'int main() { 1; return 2; 3; }' assert 3 'int main() { 1; 2; return 3; }' assert 3 'int main() { int foo=3; return foo; }' assert 8 'int main() { int foo123=3; int bar=5; return foo123+bar; }' assert 3 'int main() { if (0) return 2; return 3; }' assert 3 'int main() { if (1-1) return 2; return 3; }' assert 2 'int main() { if (1) return 2; return 3; }' assert 2 'int main() { if (2-1) return 2; return 3; }' assert 3 'int main() { {1; {2;} return 3;} }' assert 10 'int main() { int i=0; i=0; while(i<10) i=i+1; return i; }' assert 55 'int main() { int i=0; int j=0; while(i<=10) {j=i+j; i=i+1;} return j; }' assert 55 'int main() { int i=0; int j=0; for (i=0; i<=10; i=i+1) j=i+j; return j; }' assert 3 'int main() { for (;;) return 3; return 5; }' assert 3 'int main() { return ret3(); }' assert 5 'int main() { return ret5(); }' assert 8 'int main() { return add(3, 5); }' assert 2 'int main() { return sub(5, 3); }' assert 21 'int main() { return add6(1,2,3,4,5,6); }' assert 32 'int main() { return ret32(); } int ret32() { return 32; }' assert 7 'int main() { return add2(3,4); } int add2(int x, int y) { return x+y; }' assert 1 'int main() { return sub2(4,3); } int sub2(int x, int y) { return x-y; }' assert 55 'int main() { return fib(9); } int fib(int x) { if (x<=1) return 1; return fib(x-1) + fib(x-2); }' assert 3 'int main() { int x=3; return *&x; }' assert 3 'int main() { int x=3; int *y=&x; int **z=&y; return **z; }' assert 5 'int main() { int x=3; int y=5; return *(&x+1); }' assert 5 'int main() { int x=3; int y=5; return *(1+&x); }' assert 3 'int main() { int x=3; int y=5; return *(&y-1); }' assert 5 'int main() { int x=3; int y=5; int *z=&x; return *(z+1); }' assert 3 'int main() { int x=3; int y=5; int *z=&y; return *(z-1); }' assert 5 'int main() { int x=3; int *y=&x; *y=5; return x; }' assert 7 'int main() { int x=3; int y=5; *(&x+1)=7; return y; }' assert 7 'int main() { int x=3; int y=5; *(&y-1)=7; return x; }' assert 8 'int main() { int x=3; int y=5; return foo(&x, y); } int foo(int *x, int y) { return *x + y; }' echo OK
Makefile
https://github.com/rui314/chibicc/blob/0d0358fc85f2a74f6e37f5ca21afd57af3eaee6a/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
chibiccを読む~Cコンパイラコードリーディング~ ステップ16
トップページ
jupiteroak.hatenablog.com
「低レイヤを知りたい人のためのCコンパイラ作成入門」のCコンパイラを読んでいきます。
www.sigbus.info
ステップ16に該当
github.com
追加・修正されたコンパイラのソースコード
tokenize関数
https://github.com/rui314/chibicc/commit/80ed7c425842319de78031c20fa0b04bb33fc840#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R167
https://github.com/rui314/chibicc/blob/80ed7c425842319de78031c20fa0b04bb33fc840/tokenize.c#L167
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;
空白文字の場合(変更なし)
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; }
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;
NodeKind
https://github.com/rui314/chibicc/commit/80ed7c425842319de78031c20fa0b04bb33fc840#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R74
https://github.com/rui314/chibicc/blob/80ed7c425842319de78031c20fa0b04bb33fc840/chibicc.h#L74
// AST node typedef enum { ND_ADD, // + ND_SUB, // - ND_MUL, // * ND_DIV, // / ND_EQ, // == ND_NE, // != ND_LT, // < ND_LE, // <= ND_ASSIGN, // = ND_ADDR, // unary & ND_DEREF, // unary * ND_RETURN, // "return" ND_IF, // "if" ND_WHILE, // "while" ND_FOR, // "for" ND_BLOCK, // { ... } ND_FUNCALL, // Function call ND_EXPR_STMT, // Expression statement ND_VAR, // Variable ND_NUM, // Integer } NodeKind;
"&"を表現したノード型 ND_ADDRと”*”を表現したノード型ND_DEREFを追加します。
unary関数
https://github.com/rui314/chibicc/commit/80ed7c425842319de78031c20fa0b04bb33fc840#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR288
https://github.com/rui314/chibicc/blob/80ed7c425842319de78031c20fa0b04bb33fc840/parse.c#L288
// unary = ("+" | "-" | "*" | "&")? unary // | primary Node *unary() { Token *tok; if (consume("+")) return unary(); if (tok = consume("-")) return new_binary(ND_SUB, new_num(0, tok), unary(), tok); if (tok = consume("&")) return new_unary(ND_ADDR, unary(), tok); if (tok = consume("*")) return new_unary(ND_DEREF, unary(), tok); return primary(); }
unary関数は、生成規則 unary = ("+" | "-" | "*" | "&")? unary に基づいて、抽象構文木のノードを生成します。
「"+"または"-"または"&"または"*"を0回か1回」とunaray
Token *tok; if (consume("+")) return unary(); if (tok = consume("-")) return new_binary(ND_SUB, new_num(0, tok), unary(), tok); if (tok = consume("&")) return new_unary(ND_ADDR, unary(), tok); if (tok = consume("*")) return new_unary(ND_DEREF, unary(), tok);
現在着目しているトークンが"&"や"*"の場合の処理を追加します。
consume("&")の戻り値がtrueとなる場合→着目しているトークンが "&" の場合は、new_unary関数を呼び出して、"&"を表現するノードを生成します。この"&"を表現するノードは、第二引数のunaryで生成されたノードを子ノードとして持ちます。
consume("*")の戻り値がtrueとなる場合→着目しているトークンが "*" の場合は、new_unary関数を呼び出して、"*"を表現するノードを生成します。この"*"を表現するノードは、第二引数のunaryで生成されたノードを子ノードとして持ちます。
primary(変更なし)
return primary();
gen関数
https://github.com/rui314/chibicc/commit/80ed7c425842319de78031c20fa0b04bb33fc840#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR57
https://github.com/rui314/chibicc/blob/80ed7c425842319de78031c20fa0b04bb33fc840/codegen.c#L57
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_ADDR: gen_addr(node->lhs); return; case ND_DEREF: gen(node->lhs); load(); 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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); // We need to align RSP to a 16 byte boundary before // calling a function because it is an ABI requirement. // RAX is set to 0 for variadic function. int seq = labelseq++; printf(" mov rax, rsp\n"); printf(" and rax, 15\n"); printf(" jnz .Lcall%d\n", seq); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" jmp .Lend%d\n", seq); printf(".Lcall%d:\n", seq); printf(" sub rsp, 8\n"); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" add rsp, 8\n"); printf(".Lend%d:\n", seq); printf(" push rax\n"); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn.%s\n", funcname); 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_ADDR: gen_addr(node->lhs); return; case ND_DEREF: gen(node->lhs); load(); 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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); // We need to align RSP to a 16 byte boundary before // calling a function because it is an ABI requirement. // RAX is set to 0 for variadic function. int seq = labelseq++; printf(" mov rax, rsp\n"); printf(" and rax, 15\n"); printf(" jnz .Lcall%d\n", seq); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" jmp .Lend%d\n", seq); printf(".Lcall%d:\n", seq); printf(" sub rsp, 8\n"); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" add rsp, 8\n"); printf(".Lend%d:\n", seq); printf(" push rax\n"); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn.%s\n", funcname); return; }
ノード型がND_ADDRの場合の処理(ノードの種類が"&"の場合の処理)とノード型がND_DEREFの場合の処理(ノードの種類が"*"の場合の処理)を追加します。
ノード型がND_ADDRの場合は、その子ノードnode->lhsを用いてgen_addr関数を呼び出します。子ノードnode->lhsはローカル変数を表現するノードであることを前提としているので、gen_addr関数によって、子ノードnode->lhsに対応するローカル変数のアドレスを取得するためのアセンブリコードが生成されます。
ノード型がND_DEREFの場合は、その子ノードnode->lhsを用いてgen関数を呼び出します。
gen関数によってアドレス(ノードnode->lhsの結果)を取得するためのアセンブリコードが生成されます。
そのアドレス(左辺値)を使って、アドレス(左辺値)が指定するメモリ領域から値(右辺値)を取得するアセンブリコードを生成するために、load関数を呼び出します。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
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");
gen_addr関数
https://github.com/rui314/chibicc/commit/80ed7c425842319de78031c20fa0b04bb33fc840#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR12
https://github.com/rui314/chibicc/blob/80ed7c425842319de78031c20fa0b04bb33fc840/codegen.c#L12
// Pushes the given node's address to the stack. void gen_addr(Node *node) { switch (node->kind) { case ND_VAR: printf(" lea rax, [rbp-%d]\n", node->var->offset); printf(" push rax\n"); return; case ND_DEREF: gen(node->lhs); return; } error_tok(node->tok, "not an lvalue"); }
左辺値を取得するためのアセンブリコードを生成する
switch (node->kind) { case ND_VAR: printf(" lea rax, [rbp-%d]\n", node->var->offset); printf(" push rax\n"); return; case ND_DEREF: gen(node->lhs); return; }
引数で与えられたノードのノード型がND_DEREFの場合の処理(ノードの種類が"*"の場合の処理)を追加します。
引数で与えられたノードのノード型がND_DEREFの場合、その子ノードnode->lhsを用いてgen関数を呼び出し、アドレス値(子ノードnode->lhsの結果)を取得するためのアセンブリコードを生成します(生成されたアセンブリコードの実行時は、アドレス値(子ノードnode->lhsの結果)はスタックに退避されている状態です)。
エラーメッセージを表示する(変更なし)
error_tok(node->tok, "not an lvalue");
テストコード
https://github.com/rui314/chibicc/commit/80ed7c425842319de78031c20fa0b04bb33fc840#diff-3722d9ba8feb2d3feac8ce71a209a638d4b404e1c53f937188761181594023e2R94
https://github.com/rui314/chibicc/blob/80ed7c425842319de78031c20fa0b04bb33fc840/test.sh#L94
#!/bin/bash cat <<EOF | gcc -xc -c -o tmp2.o - int ret3() { return 3; } int ret5() { return 5; } int add(int x, int y) { return x+y; } int sub(int x, int y) { return x-y; } int add6(int a, int b, int c, int d, int e, int f) { return a+b+c+d+e+f; } EOF assert() { expected="$1" input="$2" ./chibicc "$input" > tmp.s gcc -static -o tmp tmp.s tmp2.o ./tmp actual="$?" if [ "$actual" = "$expected" ]; then echo "$input => $actual" else echo "$input => $expected expected, but got $actual" exit 1 fi } assert 0 'main() { return 0; }' assert 42 'main() { return 42; }' assert 21 'main() { return 5+20-4; }' assert 41 'main() { return 12 + 34 - 5 ; }' assert 47 'main() { return 5+6*7; }' assert 15 'main() { return 5*(9-6); }' assert 4 'main() { return (3+5)/2; }' assert 10 'main() { return -10+20; }' assert 10 'main() { return - -10; }' assert 10 'main() { return - - +10; }' assert 0 'main() { return 0==1; }' assert 1 'main() { return 42==42; }' assert 1 'main() { return 0!=1; }' assert 0 'main() { return 42!=42; }' assert 1 'main() { return 0<1; }' assert 0 'main() { return 1<1; }' assert 0 'main() { return 2<1; }' assert 1 'main() { return 0<=1; }' assert 1 'main() { return 1<=1; }' assert 0 'main() { return 2<=1; }' assert 1 'main() { return 1>0; }' assert 0 'main() { return 1>1; }' assert 0 'main() { return 1>2; }' assert 1 'main() { return 1>=0; }' assert 1 'main() { return 1>=1; }' assert 0 'main() { return 1>=2; }' assert 3 'main() { a=3; return a; }' assert 8 'main() { a=3; z=5; return a+z; }' assert 1 'main() { return 1; 2; 3; }' assert 2 'main() { 1; return 2; 3; }' assert 3 'main() { 1; 2; return 3; }' assert 3 'main() { foo=3; return foo; }' assert 8 'main() { foo123=3; bar=5; return foo123+bar; }' assert 3 'main() { if (0) return 2; return 3; }' assert 3 'main() { if (1-1) return 2; return 3; }' assert 2 'main() { if (1) return 2; return 3; }' assert 2 'main() { if (2-1) return 2; return 3; }' assert 3 'main() { {1; {2;} return 3;} }' assert 10 'main() { i=0; while(i<10) i=i+1; return i; }' assert 55 'main() { i=0; j=0; while(i<=10) {j=i+j; i=i+1;} return j; }' assert 55 'main() { i=0; j=0; for (i=0; i<=10; i=i+1) j=i+j; return j; }' assert 3 'main() { for (;;) return 3; return 5; }' assert 3 'main() { return ret3(); }' assert 5 'main() { return ret5(); }' assert 8 'main() { return add(3, 5); }' assert 2 'main() { return sub(5, 3); }' assert 21 'main() { return add6(1,2,3,4,5,6); }' assert 32 'main() { return ret32(); } ret32() { return 32; }' assert 7 'main() { return add2(3,4); } add2(x,y) { return x+y; }' assert 1 'main() { return sub2(4,3); } sub2(x,y) { return x-y; }' assert 55 'main() { return fib(9); } fib(x) { if (x<=1) return 1; return fib(x-1) + fib(x-2); }' assert 3 'main() { x=3; return *&x; }' assert 3 'main() { x=3; y=&x; z=&y; return **z; }' assert 5 'main() { x=3; y=5; return *(&x+8); }' assert 3 'main() { x=3; y=5; return *(&y-8); }' assert 5 'main() { x=3; y=&x; *y=5; return x; }' assert 7 'main() { x=3; y=5; *(&x+8)=7; return y; }' assert 7 'main() { x=3; y=5; *(&y-8)=7; return x; }' echo OK
Makefile
https://github.com/rui314/chibicc/blob/80ed7c425842319de78031c20fa0b04bb33fc840/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
chibiccを読む~Cコンパイラコードリーディング~ ステップ15
トップページ
jupiteroak.hatenablog.com
「低レイヤを知りたい人のためのCコンパイラ作成入門」のCコンパイラを読んでいきます。
www.sigbus.info
ステップ15に該当
github.com
ステップ15に該当
github.com
該当ステップなし
github.com
- 今回作成するコンパイラ
- 追加・修正されたコンパイラのソースコード(引数なしの関数の定義に対応したコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード(最大引数6つの関数の定義に対応したコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード!(エラーメッセージ改良のために、ノードが生成された時のトークン位置を参照できるようにしたコンパイラを作成する)
- テストコード
- Makefile
今回作成するコンパイラ
関数の定義に対応する(引数なしの関数の定義に対応したコンパイラ、最大引数6つの関数定義に対応したコンパイラ、エラーメッセージ改良のためにノードが生成された時のトークン位置を参照できるようにしたコンパイラを作成する)
追加・修正されたコンパイラのソースコード(引数なしの関数の定義に対応したコンパイラを作成する)
main関数
https://github.com/rui314/chibicc/commit/004b0fd8d230352fda871fe5badd80ff92c4068c#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR10
https://github.com/rui314/chibicc/blob/004b0fd8d230352fda871fe5badd80ff92c4068c/main.c#L10
int main(int argc, char **argv) { if (argc != 2) error("%s: invalid number of arguments", argv[0]); // Tokenize and parse. user_input = argv[1]; token = tokenize(); Function *prog = program(); // Assign offsets to local variables. for (Function *fn = prog; fn; fn = fn->next) { int offset = 0; for (Var *var = prog->locals; var; var = var->next) { offset += 8; var->offset = offset; } fn->stack_size = offset; } // Traverse the AST to emit assembly. codegen(prog); return 0; }
コマンドライン引数の個数をチェックする(変更なし)
if (argc != 2) error("%s: invalid number of arguments", argv[0]);
トークナイズを行う(変更なし)
// Tokenize and parse. user_input = argv[1]; token = tokenize();
パースを行う
Function *prog = program();
戻り値の型をFunction構造体に変更します。
ローカル変数のアドレスとスタックを用意する
// Assign offsets to local variables. for (Function *fn = prog; fn; fn = fn->next) { int offset = 0; for (Var *var = prog->locals; var; var = var->next) { offset += 8; var->offset = offset; } fn->stack_size = offset; }
全てのコードをなんらかの関数内に記述する形式にしているので、関数ごとにローカル変数のアドレスとスタックを用意するようにします。
アセンブリコードを生成する(変更なし)
// Traverse the AST to emit assembly. codegen(prog); return 0;
expect_ident関数
https://github.com/rui314/chibicc/commit/004b0fd8d230352fda871fe5badd80ff92c4068c#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R71
https://github.com/rui314/chibicc/blob/004b0fd8d230352fda871fe5badd80ff92c4068c/tokenize.c#L71
// Ensure that the current token is TK_IDENT. char *expect_ident() { if (token->kind != TK_IDENT) error_at(token->str, "expected an identifier"); char *s = strndup(token->str, token->len); token = token->next; return s; }
expect_ident関数は、現在着目しているトークンの種類が識別子である場合に、そのトークンの文字列を戻り値として取得し、トークン列を1つ読み進めます。
Function構造体
https://github.com/rui314/chibicc/commit/004b0fd8d230352fda871fe5badd80ff92c4068c#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R106
https://github.com/rui314/chibicc/blob/004b0fd8d230352fda871fe5badd80ff92c4068c/chibicc.h#L106
typedef struct Function Function; struct Function { Function *next; char *name; Node *node; Var *locals; int stack_size; };
Program構造体をFunction構造体に変更し、全てのコードをなんらかの関数内に記述する形式にします。
program関数
https://github.com/rui314/chibicc/commit/004b0fd8d230352fda871fe5badd80ff92c4068c#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR63
https://github.com/rui314/chibicc/blob/004b0fd8d230352fda871fe5badd80ff92c4068c/parse.c#L63
Function *program() { Function head; head.next = NULL; Function *cur = &head; while (!at_eof()) { cur->next = function(); cur = cur->next; } return head.next; }
program関数は、Function構造体(関数定義に対応する抽象構文木を含む)の連結リストを作成します。
Function構造体からなる連結リストのヘッダーを作成する
Function head; head.next = NULL; Function *cur = &head;
Fuction構造体headを定義し、これから作成する連結リスト(Function構造体からなる連結リスト)のヘッダーとします。
nextメンバの初期値はNULL、連結リストの終端をcurで表現します。
Function構造体からなる連結リストを作成する
while (!at_eof()) { cur->next = function(); cur = cur->next; }
at_eof()関数の戻り値がtrueになるまで→着目しているトークンがトークン列の終端を表すトークンになるまで、while文のループを継続します。
function関数を呼び出してを Function構造体生成し、 Function構造体のアドレスを戻り値として取得します。
戻り値として取得した Function構造体のアドレスを連結リストの終端要素のnextメンバに格納し、連結リストの終端要素を表すcurを更新します。
Function構造体をリターンする
return head.next;
連結リストの先頭ノード(連結リストのヘッダーの次にあるノード)のアドレスを戻り値としてリターンします。
function関数
https://github.com/rui314/chibicc/commit/004b0fd8d230352fda871fe5badd80ff92c4068c#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR76
https://github.com/rui314/chibicc/blob/004b0fd8d230352fda871fe5badd80ff92c4068c/parse.c#L76
// function = ident "(" ")" "{" stmt* "}" Function *function() { locals = NULL; char *name = expect_ident(); expect("("); expect(")"); expect("{"); Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; } Function *fn = calloc(1, sizeof( Function)); fn->name = name; fn->node = head.next; fn->locals = locals; return fn; }
function関数は、生成規則 function = ident "(" ")" "{" stmt* "}" に基づいて抽象構文木のノードを生成し、Function構造体(関数定義に対応する抽象構文木を含む)を生成します。
ident、"("、")"
char *name = expect_ident(); expect("("); expect(")");
最初のトークンは識別子(関数名)であることが期待されているので(全てのコードをなんらかの関数内に記述する形式にしているので)、expect_ident関数を呼び出して関数名を取得しトークン列を1つ読み進めます。
識別子(関数名)の次は ”(” と ”)” が続いていることが期待されているので(この時点では引数なしの関数定義にのみ対応しているので)、 expect("(")とexpect(")")を呼び出してトークン列を読み進めます。
"{"、stmtを0回以上、 "}"
expect("{"); Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; }
関数の実装に対応する抽象構文木を生成していきます(1つの文に対して1つの抽象構文木を生成します)。
ブロックの最初のトークンは "{" であることが期待されているので、expect("{")を呼び出してトークン列を1つ読み進めます。
ノード構造体headを定義し、これから作成する連結リスト(ノード構造体からなる連結リスト)のヘッダーとします。
nextメンバの初期値はNULL、連結リストの終端をcurで表現します。
consume("}")の戻り値がtrueになるまで→着目しているトークンが”}”を表すトークンになるまで、while文のループを継続します。
stmt関数を呼び出して抽象構文木を生成し、生成された抽象構文木のルートノードのアドレスを戻り値として取得します。
戻り値として取得した抽象構文木のルートノードのアドレスを連結リストの終端要素のnextメンバに格納し、連結リストの終端要素を表すcurを更新します。
Function構造体を生成する
Function *fn = calloc(1, sizeof( Function)); fn->name = name; fn->node = head.next; fn->locals = locals; return fn;
calloc関数を呼び出してFunction構造体を生成し、関数名、実装に対応する抽象構文木の(ルートノードからなる)連結リスト、Var構造体の連結リストを記録します。
codegen関数
https://github.com/rui314/chibicc/commit/004b0fd8d230352fda871fe5badd80ff92c4068c#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR188
https://github.com/rui314/chibicc/blob/004b0fd8d230352fda871fe5badd80ff92c4068c/codegen.c#L188
void codegen(Function *prog) { printf(".intel_syntax noprefix\n"); for (Function *fn = prog; fn; fn = fn->next) { printf(".global %s\n", fn->name); printf("%s:\n", fn->name); funcname = fn->name; // Prologue printf(" push rbp\n"); printf(" mov rbp, rsp\n"); printf(" sub rsp, %d\n", fn->stack_size); // Emit code for (Node *node = fn->node; node; node = node->next) gen(node); // Epilogue printf(".Lreturn.%s:\n", funcname); printf(" mov rsp, rbp\n"); printf(" pop rbp\n"); printf(" ret\n"); } }
部分的にアセンブリコードを生成する①
printf(".intel_syntax noprefix\n");
部分的にアセンブリコードを生成する②
for (Function *fn = prog; fn; fn = fn->next) { printf(".global %s\n", fn->name); printf("%s:\n", fn->name); funcname = fn->name;
.globalディレクティブと関数名のラベルを生成します。
部分的にアセンブリコードを生成する③
// Prologue printf(" push rbp\n"); printf(" mov rbp, rsp\n"); printf(" sub rsp, %d\n", fn->stack_size);
関数のプロローグとなるアセンブリコードを生成します。
gen関数
https://github.com/rui314/chibicc/commit/004b0fd8d230352fda871fe5badd80ff92c4068c#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR139
https://github.com/rui314/chibicc/blob/004b0fd8d230352fda871fe5badd80ff92c4068c/codegen.c#L139
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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); // We need to align RSP to a 16 byte boundary before // calling a function because it is an ABI requirement. // RAX is set to 0 for variadic function. int seq = labelseq++; printf(" mov rax, rsp\n"); printf(" and rax, 15\n"); printf(" jnz .Lcall%d\n", seq); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" jmp .Lend%d\n", seq); printf(".Lcall%d:\n", seq); printf(" sub rsp, 8\n"); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" add rsp, 8\n"); printf(".Lend%d:\n", seq); printf(" push rax\n"); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn.%s\n", funcname); 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"); }
二項演算以外を行うアセンブリコードを生成する
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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); // We need to align RSP to a 16 byte boundary before // calling a function because it is an ABI requirement. // RAX is set to 0 for variadic function. int seq = labelseq++; printf(" mov rax, rsp\n"); printf(" and rax, 15\n"); printf(" jnz .Lcall%d\n", seq); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" jmp .Lend%d\n", seq); printf(".Lcall%d:\n", seq); printf(" sub rsp, 8\n"); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" add rsp, 8\n"); printf(".Lend%d:\n", seq); printf(" push rax\n"); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn.%s\n", funcname); return; }
ノードの型がND_RETURNの場合の処理を修正します。
全てのコードをなんらかの関数内に記述する形式にしているので、関数ごとに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"); }
追加・修正されたコンパイラのソースコード(最大引数6つの関数の定義に対応したコンパイラを作成する)
main関数
https://github.com/rui314/chibicc/commit/3973698139945efa05228416a133ec27fd296639#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR15
https://github.com/rui314/chibicc/blob/3973698139945efa05228416a133ec27fd296639/main.c#L15
int main(int argc, char **argv) { if (argc != 2) error("%s: invalid number of arguments", argv[0]); // Tokenize and parse. user_input = argv[1]; token = tokenize(); Function *prog = program(); // Assign offsets to local variables. for (Function *fn = prog; fn; fn = fn->next) { int offset = 0; for (VarList *vl = fn->locals; vl; vl = vl->next) { offset += 8; vl->var->offset = offset; } fn->stack_size = offset; } // Traverse the AST to emit assembly. codegen(prog); return 0; }
コマンドライン引数の個数をチェックする(変更なし)
if (argc != 2) error("%s: invalid number of arguments", argv[0]);
トークナイズを行う(変更なし)
// Tokenize and parse. user_input = argv[1]; token = tokenize();
パースを行う(変更なし)
Function *prog = program();
引数・ローカル変数のアドレスとスタックサイズを定める
// Assign offsets to local variables. for (Function *fn = prog; fn; fn = fn->next) { int offset = 0; for (VarList *vl = fn->locals; vl; vl = vl->next) { offset += 8; vl->var->offset = offset; } fn->stack_size = offset; }
VarList構造体からVar構造体を取得するようにします。
アセンブリコードを生成する(変更なし)
// Traverse the AST to emit assembly. codegen(prog); return 0;
Var構造体
https://github.com/rui314/chibicc/commit/3973698139945efa05228416a133ec27fd296639#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334L52
https://github.com/rui314/chibicc/blob/3973698139945efa05228416a133ec27fd296639/chibicc.h#L50
typedef struct Var Var; struct Var { char *name; // Variable name int offset; // Offset from RBP };
nextメンバを削除します。
VarList構造体
https://github.com/rui314/chibicc/commit/3973698139945efa05228416a133ec27fd296639#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R56
https://github.com/rui314/chibicc/blob/3973698139945efa05228416a133ec27fd296639/chibicc.h#L56
typedef struct VarList VarList; struct VarList { VarList *next; Var *var; };
関数の引数やローカル変数(どちらもVar構造体で表現される)を管理するためにVarList構造体を定義します。
locals変数
https://github.com/rui314/chibicc/commit/3973698139945efa05228416a133ec27fd296639#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR3
https://github.com/rui314/chibicc/blob/3973698139945efa05228416a133ec27fd296639/parse.c#L3
VarList *locals;
グローバル変数localsの型をVarList型に変更し、Valist構造体の連結リスト(引数とローカル変数を管理するリスト)の先頭要素を指定するようにします。
Function構造体
https://github.com/rui314/chibicc/commit/3973698139945efa05228416a133ec27fd296639#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R115
https://github.com/rui314/chibicc/blob/3973698139945efa05228416a133ec27fd296639/chibicc.h#L115
typedef struct Function Function; struct Function { Function *next; char *name; VarList *params; Node *node; VarList *locals; int stack_size; };
paramsメンバを追加します。
function関数
https://github.com/rui314/chibicc/commit/3973698139945efa05228416a133ec27fd296639#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR104
https://github.com/rui314/chibicc/blob/3973698139945efa05228416a133ec27fd296639/parse.c#L104
// function = ident "(" params? ")" "{" stmt* "}" // params = ident ("," ident)* Function *function() { locals = NULL; Function *fn = calloc(1, sizeof(Function)); fn->name = expect_ident(); expect("("); fn->params = read_func_params(); expect("{"); Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; } fn->node = head.next; fn->locals = locals; return fn; }
Function構造体を生成する
Function *fn = calloc(1, sizeof(Function));
calloc関数を呼び出してFunction構造体を生成します。
関数名、(、引数、)、をパースする
fn->name = expect_ident(); expect("("); fn->params = read_func_params();
最初のトークンは識別子(関数名)であることが期待されているので(全てのコードをなんらかの関数内に記述する形式にしているので)、expect_ident関数を呼び出して関数名を取得しトークン列を1つ読み進めます。
識別子(関数名)の次のトークンは ”(” であることが期待されているので、expect("(")を呼び出してトークン列を1つ読み進めます。
read_func_params関数を呼び出して、引数に対応するVar構造体を含んだVarList構造体の連結リストを作成し、その連結リストの先頭アドレスをparamsメンバにセットします(引数の次のトークンである ")" は、read_func_params関数内で読み取られます)。
関数の実装に対応する抽象構文木を生成する
expect("{"); Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; }
関数の実装に対応する抽象構文木を生成していきます(1つの文に対して1つの抽象構文木を生成します)。。
ブロックの最初のトークンは "{" であることが期待されているので、expect("{")を呼び出してトークン列を1つ読み進めます。
ノード構造体headを定義し、これから作成する連結リスト(ノード構造体からなる連結リスト)のヘッダーとします。
nextメンバの初期値はNULL、連結リストの終端をcurで表現します。
consume("}")の戻り値がtrueになるまで→着目しているトークンが”}”を表すトークンになるまで、while文のループを継続します。
stmt関数を呼び出して抽象構文木を生成し、生成された抽象構文木のルートノードのアドレスを戻り値として取得します。
戻り値として取得した抽象構文木のルートノードのアドレスを連結リストの終端要素のnextメンバに格納し、連結リストの終端要素を表すcurを更新します。
Function構造体に値を設定する
fn->node = head.next; fn->locals = locals; return fn;
関数の実装に対応する抽象構文木の(ルートノードからなる)連結リストの先頭アドレス、VarList構造体の連結リストの先頭アドレス(read_func_params関数内で生成されたVarList構造体・Var構造体も、この連結リストに追加されています)を記録します。
read_func_params関数
https://github.com/rui314/chibicc/commit/3973698139945efa05228416a133ec27fd296639#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR81
https://github.com/rui314/chibicc/blob/3973698139945efa05228416a133ec27fd296639/parse.c#L81
VarList *read_func_params() { if (consume(")")) return NULL; VarList *head = calloc(1, sizeof(VarList)); head->var = push_var(expect_ident()); VarList *cur = head; while (!consume(")")) { expect(","); cur->next = calloc(1, sizeof(VarList)); cur->next->var = push_var(expect_ident()); cur = cur->next; } return head; }
read_func_params関数は、引数を管理するVarList構造体の連結リストを作成します。
引数の有無を確認する
if (consume(")")) return NULL;
consume(")")の戻り値がtrueとなる場合 → "関数名("の次のトークンが”)”の場合は、引数なしの関数になるので、戻り値をNULLにしてリターンします。
VarList構造体からなる連結リストの先頭要素を生成する
VarList *head = calloc(1, sizeof(VarList)); head->var = push_var(expect_ident()); VarList *cur = head;
calloc関数を呼び出して、VarList構造体へのポインタheadにメモリを割り当てます。
トークン列では次のトークンは引数名(識別子)であることが期待されているので、expect_ident関数を呼び出して引数名(識別子)を取得し、取得した引数名(識別子)を用いてpush_var関数を呼び出し、引数に対応するVar構造体を生成します。
また、連結リストの終端をcurで表現します。
VarList構造体からなる連結リストを作成する
while (!consume(")")) { expect(","); cur->next = calloc(1, sizeof(VarList)); cur->next->var = push_var(expect_ident()); cur = cur->next; }
consume("}")の戻り値がtrueになるまで→着目しているトークンが”}”を表すトークンになるまで、while文のループを継続します。
トークン列では次のトークンは” , ”であることが期待されているので、expect(",")を呼びだしてトークン列を1つ読み進めます。
calloc関数を呼び出してVaList構造体を生成し、生成されたVaList構造体を連結リストの終端要素として追加します。
トークン列では次のトークンは引数名(識別子)であることが期待されているので、expect_ident関数を呼び出して引数名(識別子)を取得し、取得した引数名(識別子)を用いてpush_var関数を呼び出し、引数に対応するVar構造体を生成し、生成したVar構造体を連結リストの終端要素のnextメンバに格納します。
最後に連結リストの終端要素を表すcurを更新します。
VarList構造体をリターンする
return head;
連結リストの先頭要素のアドレスを戻り値としてリターンします。
push_var関数
https://github.com/rui314/chibicc/commit/3973698139945efa05228416a133ec27fd296639#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR50
https://github.com/rui314/chibicc/blob/3973698139945efa05228416a133ec27fd296639/parse.c#L50
Var *push_var(char *name) { Var *var = calloc(1, sizeof(Var)); var->name = name; VarList *vl = calloc(1, sizeof(VarList)); vl->var = var; vl->next = locals; locals = vl; return var; }
push_var関数は、変数を表現しているVar構造体、Var構想体を管理しているVarlist構造体、を生成し、生成したVarlist構造体を連結リストに追加します。
Var構造体を生成する
Var *var = calloc(1, sizeof(Var)); var->name = name;
calloc関数を呼び出して、Var構造体を生成します。
生成したVar構造体に変数名nameをセットします。
変数を管理しているValist構造体を生成する
VarList *vl = calloc(1, sizeof(VarList)); vl->var = var; vl->next = locals; locals = vl;
calloc関数を呼び出して、Varlist構造体を生成します。
生成したVarlist構造体にVar構造体をセットします。
生成したVarlist構造体を新しい先頭要素として、localsが指定する連結リストに追加します。
生成したVar構造体をリターンする
return var;
find_var関数
https://github.com/rui314/chibicc/commit/3973698139945efa05228416a133ec27fd296639#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR7
https://github.com/rui314/chibicc/blob/3973698139945efa05228416a133ec27fd296639/parse.c#L7
// Find a local variable by name. Var *find_var(Token *tok) { for (VarList *vl = locals; vl; vl = vl->next) { Var *var = vl->var; if (strlen(var->name) == tok->len && !memcmp(tok->str, var->name, tok->len)) return var; } return NULL; }
Varlist構造体の連結リストから、トークンの文字列と同じ名前を持つvar構造体を探索するようにします(今まで定義されたローカル変数からトークンの文字列と同じ名前を持つローカル変数を探索します)。
codegen関数
https://github.com/rui314/chibicc/commit/3973698139945efa05228416a133ec27fd296639#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR201
https://github.com/rui314/chibicc/blob/3973698139945efa05228416a133ec27fd296639/codegen.c#L201
void codegen(Function *prog) { printf(".intel_syntax noprefix\n"); for (Function *fn = prog; fn; fn = fn->next) { printf(".global %s\n", fn->name); printf("%s:\n", fn->name); funcname = fn->name; // Prologue printf(" push rbp\n"); printf(" mov rbp, rsp\n"); printf(" sub rsp, %d\n", fn->stack_size); // Push arguments to the stack int i = 0; for (VarList *vl = fn->params; vl; vl = vl->next) { Var *var = vl->var; printf(" mov [rbp-%d], %s\n", var->offset, argreg[i++]); } // Emit code for (Node *node = fn->node; node; node = node->next) gen(node); // Epilogue printf(".Lreturn.%s:\n", funcname); printf(" mov rsp, rbp\n"); printf(" pop rbp\n"); printf(" ret\n"); } }
部分的にアセンブリコードを生成する①(変更なし)
printf(".intel_syntax noprefix\n");
部分的にアセンブリコードを生成する②(変更なし)
for (Function *fn = prog; fn; fn = fn->next) { printf(".global %s\n", fn->name); printf("%s:\n", fn->name); funcname = fn->name;
部分的にアセンブリコードを生成する③
// Prologue printf(" push rbp\n"); printf(" mov rbp, rsp\n"); printf(" sub rsp, %d\n", fn->stack_size);
部分的にアセンブリコードを生成する④
// Push arguments to the stack int i = 0; for (VarList *vl = fn->params; vl; vl = vl->next) { Var *var = vl->var; printf(" mov [rbp-%d], %s\n", var->offset, argreg[i++]); } // Emit code for (Node *node = fn->node; node; node = node->next) gen(node);
Function構造体ごとに(関数ごとに)用意されたVarList構造体へのポインタparamsから引数に対応するVar構造体を取り出し、引数の値(レジスタにセットされた値)をスタック領域のアドレス(RBPの値にvar->offsetを減算した値)に退避させるアセンブリコードを生成するようにします。
部分的にアセンブリコードを生成する⑤(変更なし)
// Epilogue printf(".Lreturn.%s:\n", funcname); printf(" mov rsp, rbp\n"); printf(" pop rbp\n"); printf(" ret\n");
追加・修正されたコンパイラのソースコード!(エラーメッセージ改良のために、ノードが生成された時のトークン位置を参照できるようにしたコンパイラを作成する)
consume関数
Token *consume(char *op) { if (token->kind != TK_RESERVED || strlen(op) != token->len || memcmp(token->str, op, token->len)) return NULL; Token *t = token; token = token->next; return t; }
expect関数
void expect(char *op) { if (token->kind != TK_RESERVED || strlen(op) != token->len || memcmp(token->str, op, token->len)) error_tok(token, "expected \"%s\"", op); token = token->next; }
expect_number関数
int expect_number() { if (token->kind != TK_NUM) error_tok(token, "expected a number"); int val = token->val; token = token->next; return val; }
expect_ident関数
char *expect_ident() { if (token->kind != TK_IDENT) error_tok(token, "expected an identifier"); char *s = strndup(token->str, token->len); token = token->next; return s; }
error_tok関数
void error_tok(Token *tok, char *fmt, ...) { va_list ap; va_start(ap, fmt); if (tok) verror_at(tok->str, fmt, ap); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); exit(1); }
error_at関数
void error_at(char *loc, char *fmt, ...) { va_list ap; va_start(ap, fmt); verror_at(loc, fmt, ap); }
verror_at関数
void verror_at(char *loc, char *fmt, va_list ap) { int pos = loc - user_input; fprintf(stderr, "%s\n", user_input); fprintf(stderr, "%*s", pos, ""); // print pos spaces. fprintf(stderr, "^ "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); exit(1); }
new_node関数
Node *new_node(NodeKind kind, Token *tok) { Node *node = calloc(1, sizeof(Node)); node->kind = kind; node->tok = tok; return node; }
new_binary関数
Node *new_binary(NodeKind kind, Node *lhs, Node *rhs, Token *tok) { Node *node = new_node(kind, tok); node->lhs = lhs; node->rhs = rhs; return node; }
new_unary関数
Node *new_unary(NodeKind kind, Node *expr, Token *tok) { Node *node = new_node(kind, tok); node->lhs = expr; return node; }
new_num関数
Node *new_num(int val, Token *tok) { Node *node = new_node(ND_NUM, tok); node->val = val; return node; }
new_var関数
Node *new_var(Var *var, Token *tok) { Node *node = new_node(ND_VAR, tok); node->var = var; return node; }
stmt関数
Node *stmt() { Token *tok; if (tok = consume("return")) { Node *node = new_unary(ND_RETURN, expr(), tok); expect(";"); return node; } if (tok = consume("if")) { Node *node = new_node(ND_IF, tok); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); if (consume("else")) node->els = stmt(); return node; } if (tok = consume("while")) { Node *node = new_node(ND_WHILE, tok); expect("("); node->cond = expr(); expect(")"); node->then = stmt(); return node; } if (tok = consume("for")) { Node *node = new_node(ND_FOR, tok); 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; } if (tok = consume("{")) { Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; } Node *node = new_node(ND_BLOCK, tok); node->body = head.next; return node; } Node *node = read_expr_stmt(); expect(";"); return node; }
read_expr_stmt関数
Node *read_expr_stmt() { Token *tok = token; return new_unary(ND_EXPR_STMT, expr(), tok); }
assign関数
Node *assign() { Node *node = equality(); Token *tok; if (tok = consume("=")) node = new_binary(ND_ASSIGN, node, assign(), tok); return node; }
equality関数
Node *equality() { Node *node = relational(); Token *tok; for (;;) { if (tok = consume("==")) node = new_binary(ND_EQ, node, relational(), tok); else if (tok = consume("!=")) node = new_binary(ND_NE, node, relational(), tok); else return node; } }
relational関数
Node *relational() { Node *node = add(); Token *tok; for (;;) { if (tok = consume("<")) node = new_binary(ND_LT, node, add(), tok); else if (tok = consume("<=")) node = new_binary(ND_LE, node, add(), tok); else if (tok = consume(">")) node = new_binary(ND_LT, add(), node, tok); else if (tok = consume(">=")) node = new_binary(ND_LE, add(), node, tok); else return node; } }
add関数
Node *add() { Node *node = mul(); Token *tok; for (;;) { if (tok = consume("+")) node = new_binary(ND_ADD, node, mul(), tok); else if (tok = consume("-")) node = new_binary(ND_SUB, node, mul(), tok); else return node; } }
mul関数
Node *mul() { Node *node = unary(); Token *tok; for (;;) { if (tok = consume("*")) node = new_binary(ND_MUL, node, unary(), tok); else if (tok = consume("/")) node = new_binary(ND_DIV, node, unary(), tok); else return node; } }
unary関数
Node *unary() { Token *tok; if (consume("+")) return unary(); if (tok = consume("-")) return new_binary(ND_SUB, new_num(0, tok), unary(), tok); return primary(); }
primary関数
Node *primary() { if (consume("(")) { Node *node = expr(); expect(")"); return node; } Token *tok; if (tok = consume_ident()) { if (consume("(")) { Node *node = new_node(ND_FUNCALL, tok); node->funcname = strndup(tok->str, tok->len); node->args = func_args(); return node; } Var *var = find_var(tok); if (!var) var = push_var(strndup(tok->str, tok->len)); return new_var(var, tok); } tok = token; if (tok->kind != TK_NUM) error_tok(tok, "expected expression"); return new_num(expect_number(), tok); }
gen_addr関数
void gen_addr(Node *node) { if (node->kind == ND_VAR) { printf(" lea rax, [rbp-%d]\n", node->var->offset); printf(" push rax\n"); return; } error_tok(node->tok, "not an lvalue"); }
テストコード
chibiccを読む~Cコンパイラコードリーディング~ ステップ14
トップページ
jupiteroak.hatenablog.com
「低レイヤを知りたい人のためのCコンパイラ作成入門」のCコンパイラを読んでいきます。
www.sigbus.info
ステップ14に該当
github.com
ステップ14に該当
github.com
ステップ14に該当
github.com
- 今回作成するコンパイラ
- 追加・修正されたコンパイラのソースコード(引数なしの関数を呼び出せるコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード(最大引数6つの関数を呼び出せるコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード(関数呼び出しの前にRSPが16の倍数になっているコンパイラを作成する)
- テストコード
- Makefile
追加・修正されたコンパイラのソースコード(引数なしの関数を呼び出せるコンパイラを作成する)
NodeKind
https://github.com/rui314/chibicc/commit/f5540b578e4bafa57d7ff8d94f4a0a46c95ede12#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R72
https://github.com/rui314/chibicc/blob/f5540b578e4bafa57d7ff8d94f4a0a46c95ede12/chibicc.h#L72
// 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_BLOCK, // { ... } ND_FUNCALL, // Function call ND_EXPR_STMT, // Expression statement ND_VAR, // Variable ND_NUM, // Integer } NodeKind;
関数呼び出しを表現するノード型 ND_FUNCALLを追加します。
Node構造体
https://github.com/rui314/chibicc/commit/f5540b578e4bafa57d7ff8d94f4a0a46c95ede12#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R98
https://github.com/rui314/chibicc/blob/f5540b578e4bafa57d7ff8d94f4a0a46c95ede12/chibicc.h#L98
// 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; // Block Node *body; // Function call char *funcname; Var *var; // Used if kind == ND_VAR int val; // Used if kind == ND_NUM };
関数名を保存するメンバfuncnameを追加します。
primary関数
https://github.com/rui314/chibicc/commit/f5540b578e4bafa57d7ff8d94f4a0a46c95ede12#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR251
https://github.com/rui314/chibicc/blob/f5540b578e4bafa57d7ff8d94f4a0a46c95ede12/parse.c#L251
Node *primary() { if (consume("(")) { Node *node = expr(); expect(")"); return node; } Token *tok = consume_ident(); if (tok) { if (consume("(")) { expect(")"); Node *node = new_node(ND_FUNCALL); node->funcname = strndup(tok->str, tok->len); return node; } Var *var = find_var(tok); if (!var) var = push_var(strndup(tok->str, tok->len)); return new_var(var); } return new_num(expect_number()); }
primary関数は、生成規則 primary = "(" expr ")" | ident args? | num に基づいて、抽象構文木のノードを生成します。
(argsの生成規則は args = "(" ")" です。)
"("、expr、")"(変更なし)
if (consume("(")) { Node *node = expr(); expect(")"); return node; }
ident、argsを0回か1回
Token *tok = consume_ident(); if (tok) { if (consume("(")) { expect(")"); Node *node = new_node(ND_FUNCALL); node->funcname = strndup(tok->str, tok->len); return node; } Var *var = find_var(tok); if (!var) var = push_var(strndup(tok->str, tok->len)); return new_var(var); }
consume_ident関数を呼び出してtokが真となる場合 → 識別子(ローカル変数、または、関数名)を表現するトークンを取得できた場合の処理です。
consume("(")の戻り値がtrueとなる場合 → 識別子の次のトークンが"("の場合 → 識別子が関数名の場合は、expect(")")を呼び出してトークン列を1つ読み進めます。
new_node関数を呼び出して、関数呼び出しを表すノードを生成し、生成したノードに関数名を記録します。
識別子がローカル変数の場合は、find_var関数を呼び出して、Var構造体からなる連結リストからtokが持つ文字列と同じ名前を持つvar構造体変数を取得します。
!varが真となる場合→tokが持つ文字列と同じ名前を持つvar構造体変数を取得できなかった場合は、push_var関数を呼び出して、var構造体を新規に生成しvar構造体の連結リストに追加します。
最後に、new_var関数を呼び出して、ローカル変数に対応するノードを新規に生成します。
num(変更なし)
return new_num(expect_number());
gen関数
https://github.com/rui314/chibicc/commit/f5540b578e4bafa57d7ff8d94f4a0a46c95ede12#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR104
https://github.com/rui314/chibicc/blob/f5540b578e4bafa57d7ff8d94f4a0a46c95ede12/codegen.c#L104
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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: printf(" call %s\n", node->funcname); printf(" push rax\n"); 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"); }
二項演算以外を行うアセンブリコードを生成する
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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: printf(" call %s\n", node->funcname); printf(" push rax\n"); return; case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; }
ノード型がND_FUNCALL(ノードの種類が関数呼び出し)の場合における処理を追加します。
ノード型がND_FUNCALLの場合は、関数呼び出しを行うアセンブリコード”call 関数名”を生成します。
最後に、その関数の戻り値をスタックに退避させるアセンブリコード"push rax"を生成します。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
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"); }
追加・修正されたコンパイラのソースコード(最大引数6つの関数を呼び出せるコンパイラを作成する)
tokenize関数
https://github.com/rui314/chibicc/commit/5dea368205321bdd55722b7be88efd0bd41b7fb4#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R145
https://github.com/rui314/chibicc/blob/5dea368205321bdd55722b7be88efd0bd41b7fb4/tokenize.c#L145
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; }
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;
Node構造体
https://github.com/rui314/chibicc/commit/5dea368205321bdd55722b7be88efd0bd41b7fb4#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R99
https://github.com/rui314/chibicc/blob/5dea368205321bdd55722b7be88efd0bd41b7fb4/chibicc.h#L99
// 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; // Block Node *body; // Function call char *funcname; Node *args; Var *var; // Used if kind == ND_VAR int val; // Used if kind == ND_NUM };
関数呼び出しのパースの際に使用される子ノードargsを追加します。
primary関数
https://github.com/rui314/chibicc/commit/5dea368205321bdd55722b7be88efd0bd41b7fb4#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR268
https://github.com/rui314/chibicc/blob/5dea368205321bdd55722b7be88efd0bd41b7fb4/parse.c#L268
Node *primary() { if (consume("(")) { Node *node = expr(); expect(")"); return node; } Token *tok = consume_ident(); if (tok) { if (consume("(")) { Node *node = new_node(ND_FUNCALL); node->funcname = strndup(tok->str, tok->len); node->args = func_args(); return node; } Var *var = find_var(tok); if (!var) var = push_var(strndup(tok->str, tok->len)); return new_var(var); } return new_num(expect_number()); }
primary関数は、生成規則 primary = "(" expr ")" | ident func-args? | num に基づいて、抽象構文木のノードを生成します。
"("、expr、")"(変更なし)
if (consume("(")) { Node *node = expr(); expect(")"); return node; }
ident、func-argsを0回か1回
Token *tok = consume_ident(); if (tok) { if (consume("(")) { Node *node = new_node(ND_FUNCALL); node->funcname = strndup(tok->str, tok->len); node->args = func_args(); return node; } Var *var = find_var(tok); if (!var) var = push_var(strndup(tok->str, tok->len)); return new_var(var); }
consume_ident関数を呼び出してtokが真となる場合 → 識別子(ローカル変数、または、関数名)を表現するトークンを取得できた場合の処理です。
consume("(")の戻り値がtrueとなる場合 → 識別子の次のトークンが"("の場合 → 識別子が関数名の場合は、new_node関数を呼び出して関数を呼び出しを表すノードを生成し、生成したノードに関数名を記録します。
最後に、func_args関数を呼び出して抽象構文木を生成し、その抽象構文木の(ルートノードからなる)連結リストを子ノードargsとして登録します。
識別子がローカル変数の場合は、find_var関数を呼び出して、Var構造体からなる連結リストからtokが持つ文字列と同じ名前を持つvar構造体変数を取得します。
!varが真となる場合→tokが持つ文字列と同じ名前を持つvar構造体変数を取得できなかった場合は、push_var関数を呼び出して、var構造体を新規に生成しvar構造体の連結リストに追加します。
最後に、new_var関数を呼び出して、ローカル変数に対応するノードを新規に生成します。
num(変更なし)
return new_num(expect_number());
func_args関数
https://github.com/rui314/chibicc/commit/5dea368205321bdd55722b7be88efd0bd41b7fb4#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR241
https://github.com/rui314/chibicc/blob/5dea368205321bdd55722b7be88efd0bd41b7fb4/parse.c#L241
Node *func_args() { if (consume(")")) return NULL; Node *head = assign(); Node *cur = head; while (consume(",")) { cur->next = assign(); cur = cur->next; } expect(")"); return head; }
func_args関数は、生成規則 func-args = "(" (assign ("," assign)*)? ")" に基づいて、抽象構文木のノードを生成します。
"("
if (consume(")")) return NULL;
consume(")")の戻り値がtrueとなる場合 → "関数名("の次のトークンが”)”の場合は、引数なしの関数呼び出しになってしまうので、戻り値をNULLにして処理を終了します。
「assign、『"," assign』を0回以上」を0回か1回
Node *head = assign(); Node *cur = head; while (consume(",")) { cur->next = assign(); cur = cur->next; }
assign関数を呼び出して抽象構文木を生成し、その抽象構文木のルートノードをこれから作成する連結リスト(ノード構造体からなる連結リスト)のヘッダーheadとします。
consume(",")の戻り値がfalseになるまで→”,”を表すトークンが出現するまで、while文のループを継続します。
assign関数を呼び出して抽象構文木を生成し、生成された抽象構文木のルートノードのアドレスを戻り値として取得します。
戻り値として取得した抽象構文木のルートノードのアドレスを連結リストの終端要素のnextメンバに格納し、連結リストの終端要素を表すcurを更新します。
gen関数
https://github.com/rui314/chibicc/commit/5dea368205321bdd55722b7be88efd0bd41b7fb4#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR105
https://github.com/rui314/chibicc/blob/5dea368205321bdd55722b7be88efd0bd41b7fb4/codegen.c#L105
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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); printf(" call %s\n", node->funcname); printf(" push rax\n"); 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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); printf(" call %s\n", node->funcname); printf(" push rax\n"); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; }
ノードの型がND_FUNCALL(ノードの種類が関数呼び出し)の場合における処理を修正します。
抽象構文木のルートノードからなる連結リストargsを用いてgen関数を呼び出し、引数値(ノードargの結果)を得るために必要なアセンブリコードを生成します。
この時、連結リストargsの最後の要素を指定するインデックスを取得するために、変数nargsをインクリメントしておきます。
生成されたアセンブリコードの実行時を考慮すると、関数で使用される引数値はスタックに退避されている状態なので、インデックスnargs、配列argreg、for文を使って、引数の個数分だけアセンブリコード" pop レジスタ名"を生成します。
argregはレジスタ名の要素からなる配列で、何番目の引数値をどのレジスタにセットするかはABIの一部である関数呼び出し規約によって定められています。
https://github.com/rui314/chibicc/commit/5dea368205321bdd55722b7be88efd0bd41b7fb4#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR4
https://github.com/rui314/chibicc/blob/5dea368205321bdd55722b7be88efd0bd41b7fb4/codegen.c#L4
char *argreg[] = {"rdi", "rsi", "rdx", "rcx", "r8", "r9"};
最後に、関数呼び出しを行うアセンブリコード”call 関数名”とその関数の戻り値をスタックに退避させるアセンブリコード"push rax"を生成します。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
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");
追加・修正されたコンパイラのソースコード(関数呼び出しの前にRSPが16の倍数になっているコンパイラを作成する)
gen関数
https://github.com/rui314/chibicc/commit/aedbf56c3af4914e3f183223ff879734683bec73#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR115
https://github.com/rui314/chibicc/blob/aedbf56c3af4914e3f183223ff879734683bec73/codegen.c#L115
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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); // We need to align RSP to a 16 byte boundary before // calling a function because it is an ABI requirement. // RAX is set to 0 for variadic function. int seq = labelseq++; printf(" mov rax, rsp\n"); printf(" and rax, 15\n"); printf(" jnz .Lcall%d\n", seq); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" jmp .Lend%d\n", seq); printf(".Lcall%d:\n", seq); printf(" sub rsp, 8\n"); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" add rsp, 8\n"); printf(".Lend%d:\n", seq); printf(" push rax\n"); 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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_FUNCALL: { int nargs = 0; for (Node *arg = node->args; arg; arg = arg->next) { gen(arg); nargs++; } for (int i = nargs - 1; i >= 0; i--) printf(" pop %s\n", argreg[i]); // We need to align RSP to a 16 byte boundary before // calling a function because it is an ABI requirement. // RAX is set to 0 for variadic function. int seq = labelseq++; printf(" mov rax, rsp\n"); printf(" and rax, 15\n"); printf(" jnz .Lcall%d\n", seq); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" jmp .Lend%d\n", seq); printf(".Lcall%d:\n", seq); printf(" sub rsp, 8\n"); printf(" mov rax, 0\n"); printf(" call %s\n", node->funcname); printf(" add rsp, 8\n"); printf(".Lend%d:\n", seq); printf(" push rax\n"); return; } case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; }
ノードの型がND_FUNCALL(ノードの種類が関数呼び出し)の場合における処理に、RSPの値を16バイト境界にする処理を追加します。
RSPの値が16バイト境界であるかを判定する処理
①printf(" mov rax, rsp\n");
RSPの値をRAXにコピーするアセンブリコードを生成します。
②printf(" and rax, 15\n");
RAXの値(RSPの値)と0x0000 0000 0000 000Fの各bitをAND演算し、その演算結果をRAXにセットします。
push命令やpop命令はRSPを8バイト単位で変更するので、RSPの下位8bitは 0x0 か 0x8 のどちらかの値となります(RSPの値は 0x???? ???? ???? ???0 か 0x???? ???? ???? ???8 のどちらかの値となります)。
演算結果が0ではない時、RSPの下位8bitは0x8(RSPの値は 0x???? ???? ???? ???8) → RSPの値は16バイト境界ではありません。
演算結果が0の時、RSPの下位8bitは0x0(RSPの値は 0x???? ???? ???? ???0) → RSPの値は16バイト境界となります。
③printf(" jnz .Lcall%d\n", seq);
演算結果が0ではない時(RSPの値が16バイト境界ではない時)にジャンプするアセンブリコードを生成します。
RSPの値が16バイト境界である場合の処理
④printf(" mov rax, 0\n");
関数呼び出しが終わった後、RAXにはその関数の戻り値がセットされるので、RAXを0で初期化するアセンブリコードを生成します。
⑤printf(" call %s\n", node->funcname);
関数呼び出しを行うアセンブリコードを生成します。
⑥printf(" jmp .Lend%d\n", seq);
戻り値をスタックへ退避させる処理へジャンプするアセンブリコードを生成します。
RSPの値が16バイト境界ではない場合の処理
⑦printf(".Lcall%d:\n", seq);
RSPの値が16バイト境界ではない場合に使用されるラベルを生成します。
⑧printf(" sub rsp, 8\n");
RSPから8を減算して(スタックのトップアドレスを下位方向へ8バイト移動させて)、RSPの値を16バイト境界にするアセンブリコードを生成します。
⑨printf(" mov rax, 0\n");
関数呼び出しが終わった後、RAXにはその関数の戻り値がセットされるので、RAXを0で初期化するアセンブリコードを生成します。
⑩printf(" call %s\n", node->funcname);
関数呼び出しを行うアセンブリコードを生成します。
⑪printf(" add rsp, 8\n");
RSPに8を加算して(スタックのトップアドレスを上位方向へ8バイト移動させて)、RSPの値を元の状態に戻すアセンブリコードを生成します。
戻り値をスタックへ退避させる処理
⑫printf(".Lend%d:\n", seq);
戻り値をスタックへ退避させる処理へジャンプする時に使用されるラベルを生成します。
⑬printf(" push rax\n");
戻り値をスタックへ退避させるアセンブリコードを生成します。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
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/aedbf56c3af4914e3f183223ff879734683bec73/test.sh
#!/bin/bash cat <<EOF | gcc -xc -c -o tmp2.o - int ret3() { return 3; } int ret5() { return 5; } int add(int x, int y) { return x+y; } int sub(int x, int y) { return x-y; } int add6(int a, int b, int c, int d, int e, int f) { return a+b+c+d+e+f; } EOF assert() { expected="$1" input="$2" ./chibicc "$input" > tmp.s gcc -static -o tmp tmp.s tmp2.o ./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 3 '{1; {2;} return 3;}' assert 10 'i=0; while(i<10) i=i+1; return i;' assert 55 'i=0; j=0; while(i<=10) {j=i+j; i=i+1;} return j;' 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;' assert 3 'return ret3();' assert 5 'return ret5();' assert 8 'return add(3, 5);' assert 2 'return sub(5, 3);' assert 21 'return add6(1,2,3,4,5,6);' echo OK
Makefile
https://github.com/rui314/chibicc/blob/aedbf56c3af4914e3f183223ff879734683bec73/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
chibiccを読む~Cコンパイラコードリーディング~ ステップ13
トップページ
jupiteroak.hatenablog.com
「低レイヤを知りたい人のためのCコンパイラ作成入門」のCコンパイラを読んでいきます。
www.sigbus.info
ステップ13に該当
github.com
追加・修正されたコンパイラのソースコード
tokenize関数
https://github.com/rui314/chibicc/commit/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R141
https://github.com/rui314/chibicc/blob/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04/tokenize.c#L141
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; }
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;
NodeKind
https://github.com/rui314/chibicc/commit/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R71
https://github.com/rui314/chibicc/blob/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04/chibicc.h#L71
// 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_BLOCK, // { ... } ND_EXPR_STMT, // Expression statement ND_VAR, // Variable ND_NUM, // Integer } NodeKind;
ブロックを表現するノード型ND_BLOCKを追加します。
Node構造体
https://github.com/rui314/chibicc/commit/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R94
https://github.com/rui314/chibicc/blob/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04/chibicc.h#L94
// 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; // Block Node *body; Var *var; // Used if kind == ND_VAR int val; // Used if kind == ND_NUM };
ブロックをパースする際に使用する子ノードbodyを追加します。
stmt関数
https://github.com/rui314/chibicc/commit/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR137
https://github.com/rui314/chibicc/blob/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04/parse.c#L137
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; } if (consume("{")) { Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; } Node *node = new_node(ND_BLOCK); node->body = head.next; 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 | "{" 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; }
"{"、stmtを0回以上、"}"
if (consume("{")) { Node head; head.next = NULL; Node *cur = &head; while (!consume("}")) { cur->next = stmt(); cur = cur->next; } Node *node = new_node(ND_BLOCK); node->body = head.next; return node; }
consume("{")の戻り値がtrueとなる場合→着目しているトークンが"{"の場合は、ブロック(複文)の抽象構文木を生成する処理を行います。
ノード構造体headを定義し、これから作成する連結リスト(ノード構造体からなる連結リスト)のヘッダーとします。
nextメンバの初期値はNULL、連結リストの終端をcurで表現します。
consume("}")の戻り値がtrueになるまで→着目しているトークンが”}”を表すトークンになるまで、while文のループを継続します。
stmt関数を呼び出して抽象構文木を生成し、生成された抽象構文木のルートノードのアドレスを戻り値として取得します。
戻り値として取得した抽象構文木のルートノードのアドレスを連結リストの終端要素のnextメンバに格納し、連結リストの終端要素を表すcurを更新します。
最後に、new_node関数を呼び出してブロックを表すノードを生成し、連結リストの先頭ノード(連結リストのヘッダーの次にあるノード)を子ノードbodyとして登録します。
expr、";"(変更なし)
Node *node = read_expr_stmt(); expect(";"); return node;
gen関数
https://github.com/rui314/chibicc/commit/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR100
https://github.com/rui314/chibicc/blob/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04/codegen.c#L100
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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); 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_BLOCK: for (Node *n = node->body; n; n = n->next) gen(n); return; case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; }
ノードの型がND_BLOCKの場合の処理を追加します。
ノードの型がND_BLOCKの場合は、抽象構文木のルートノードからなる連結リストbodyを用いてgen関数を呼び出し、ブロック内の式文に対応するアセンブリコードを生成します。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
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/commit/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04#diff-3722d9ba8feb2d3feac8ce71a209a638d4b404e1c53f937188761181594023e2R64
https://github.com/rui314/chibicc/blob/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04/test.sh#L64
#!/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 3 '{1; {2;} return 3;}' assert 10 'i=0; while(i<10) i=i+1; return i;' assert 55 'i=0; j=0; while(i<=10) {j=i+j; i=i+1;} return j;' 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/8eea2ec3a8d9a828d2ddf85cf3f46c303f937a04/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
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
chibiccを読む~Cコンパイラコードリーディング~ ステップ9,10,11
トップページ
jupiteroak.hatenablog.com
「低レイヤを知りたい人のためのCコンパイラ作成入門」のCコンパイラを読んでいきます。
www.sigbus.info
ステップ9に該当
github.com
ステップ11に該当
github.com
該当ステップなし
github.com
ステップ9に該当
github.com
ステップ10に該当
github.com
- 今回作成するコンパイラ
- 追加・修正されたコンパイラのソースコード(ステップ9、;を使って複数の文を扱えるコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード(ステップ11、return文を扱えるコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード(該当ステップなし、式文を表現するノード追加に伴う修正を行ったコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード(ステップ9、1文字のローカル変数を扱えるコンパイラを作成する)
- 追加・修正されたコンパイラのソースコード(ステップ10、複数文字のローカル変数を扱えるコンパイラを作成する)
- テストコード
- Makefile
今回作成するコンパイラ
1文字のローカル変数(ステップ9、;を使って複数の文を扱えるコンパイラを作成する)
↓
return文(ステップ11、return文を扱えるコンパイラを作成する)
↓
タイトルなし(該当ステップなし、式文を表現するノード追加に伴う修正を行ったコンパイラを作成する)
↓
1文字のローカル変数(ステップ9、1文字のローカル変数を扱えるコンパイラを作成する)
↓
複数文字のローカル変数(ステップ10、複数文字のローカル変数を扱えるコンパイラを作成する)
追加・修正されたコンパイラのソースコード(ステップ9、;を使って複数の文を扱えるコンパイラを作成する)
main関数
https://github.com/rui314/chibicc/commit/b4ff70045f63ff6cff3acb63412002ef3eec78b5#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR10
https://github.com/rui314/chibicc/blob/b4ff70045f63ff6cff3acb63412002ef3eec78b5/main.c#L10
#include "chibicc.h" int main(int argc, char **argv) { if (argc != 2) error("%s: invalid number of arguments", argv[0]); // Tokenize and parse. user_input = argv[1]; token = tokenize(); Node *node = program(); // Traverse the AST to emit assembly. codegen(node); return 0; }
コマンドライン引数の個数をチェックする(変更なし)
if (argc != 2) error("%s: invalid number of arguments", argv[0]);
トークナイズを行う(変更なし)
// Tokenize and parse. user_input = argv[1]; token = tokenize();
tokenize関数
https://github.com/rui314/chibicc/commit/b4ff70045f63ff6cff3acb63412002ef3eec78b5#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R96
https://github.com/rui314/chibicc/blob/b4ff70045f63ff6cff3acb63412002ef3eec78b5/tokenize.c#L96
Token *tokenize() { char *p = user_input; Token head; head.next = NULL; Token *cur = &head; while (*p) { // Skip whitespace characters. if (isspace(*p)) { p++; continue; } // Multi-letter punctuator if (startswith(p, "==") || startswith(p, "!=") || startswith(p, "<=") || startswith(p, ">=")) { cur = new_token(TK_RESERVED, cur, p, 2); p += 2; continue; } // Single-letter punctuator if (strchr("+-*/()<>;", *p)) { cur = new_token(TK_RESERVED, cur, p++, 1); 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; }
複数文字の記号の場合(変更なし)
// Multi-letter punctuator if (startswith(p, "==") || startswith(p, "!=") || startswith(p, "<=") || startswith(p, ">=")) { cur = new_token(TK_RESERVED, cur, p, 2); p += 2; continue; }
1文字の記号の場合
// Single-letter punctuator if (strchr("+-*/()<>;", *p)) { cur = new_token(TK_RESERVED, cur, p++, 1); 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;
Node構造体
https://github.com/rui314/chibicc/commit/b4ff70045f63ff6cff3acb63412002ef3eec78b5#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R60
https://github.com/rui314/chibicc/blob/b4ff70045f63ff6cff3acb63412002ef3eec78b5/chibicc.h#L60
typedef struct Node Node; struct Node { NodeKind kind; // Node kind Node *next; // Next node Node *lhs; // Left-hand side Node *rhs; // Right-hand side int val; // Used if kind == ND_NUM };
C言語の1つの文( ; が終端にある文字列)に1つの抽象構文木(抽象構文木のルートノード)が対応するので、次の文に対応する抽象構文木(抽象構文木のノード)のアドレスの保存先となるnextメンバを追加します。
program関数
https://github.com/rui314/chibicc/commit/b4ff70045f63ff6cff3acb63412002ef3eec78b5#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR31
https://github.com/rui314/chibicc/blob/b4ff70045f63ff6cff3acb63412002ef3eec78b5/parse.c#L31
// program = stmt* Node *program() { Node head; head.next = NULL; Node *cur = &head; while (!at_eof()) { cur->next = stmt(); cur = cur->next; } return head.next; }
prgram関数は、抽象構文木のルートノードからなる連結リストを作成します。
抽象構文木のルートノードからなる連結リストのヘッダーを作成する
Node head; head.next = NULL; Node *cur = &head;
ノード構造体headを定義し、これから作成する連結リスト(ノード構造体からなる連結リスト)のヘッダーとします。
nextメンバの初期値はNULL、連結リストの終端をcurで表現します。
抽象構文木のルートノードからなる連結リストを作成する
while (!at_eof()) { cur->next = stmt(); cur = cur->next; }
at_eof()関数の戻り値がtrueになるまで→着目しているトークンがトークン列の終端を表すトークンになるまで、while文のループを継続します。
stmt関数を呼び出して抽象構文木を生成し、生成された抽象構文木のルートノードのアドレスを戻り値として取得します。
戻り値として取得した抽象構文木のルートノードのアドレスを連結リストの終端要素のnextメンバに格納し、連結リストの終端要素を表すcurを更新します。
連結リストの先頭ノードを戻り値としてリターンする
return head.next;
連結リストの先頭ノード(連結リストのヘッダーの次にあるノード)のアドレスを戻り値としてリターンします。
stmt関数
https://github.com/rui314/chibicc/commit/b4ff70045f63ff6cff3acb63412002ef3eec78b5#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR44
https://github.com/rui314/chibicc/blob/b4ff70045f63ff6cff3acb63412002ef3eec78b5/parse.c#L44
// stmt = expr ";" Node *stmt() { Node *node = expr(); expect(";"); return node; }
stmt関数は、生成規則 stmt = expr ";" に基づいて、抽象構文木のノードを生成します。
codegen関数
https://github.com/rui314/chibicc/commit/b4ff70045f63ff6cff3acb63412002ef3eec78b5#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR59
https://github.com/rui314/chibicc/blob/b4ff70045f63ff6cff3acb63412002ef3eec78b5/codegen.c#L59
void codegen(Node *node) { printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n"); for (Node *n = node; n; n = n->next) { gen(n); printf(" pop rax\n"); } printf(" ret\n"); }
codegen関数は、アセンブリコードを生成する処理を行います。
部分的にアセンブリコードを生成する①
printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n");
序盤部分のアセンブリコードを生成します。
部分的にアセンブリコードを生成する②
for (Node *n = node; n; n = n->next) { gen(n); printf(" pop rax\n"); }
抽象構文木を用いて、アセンブリコードを生成します。
1つの文が1つ抽象構文木(のルートノード)に対応しており、次の文に対応している抽象構文木(のルートノード)はnextから参照します。
gen関数によって生成されたアセンブリコードの実行時を考慮すると、抽象構文木の結果を表す値はスタックに退避されているので、その抽象構文木の結果を表す値を式文の評価値として取得するために、アセンブリコード"pop rax"を生成しています。
低レイヤを知りたい人のためのCコンパイラ作成入門では、連結リストではなく、配列(Node *code[100])を用いてノードを管理しています。
追加・修正されたコンパイラのソースコード(ステップ11、return文を扱えるコンパイラを作成する)
tokenize関数
https://github.com/rui314/chibicc/commit/5124ded8ea4020da1aae237aeb25247f806089ce#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R95
https://github.com/rui314/chibicc/blob/5124ded8ea4020da1aae237aeb25247f806089ce/tokenize.c#L95
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 if (startswith(p, "return") && !is_alnum(p[6])) { cur = new_token(TK_RESERVED, cur, p, 6); p += 6; continue; } // Multi-letter punctuator if (startswith(p, "==") || startswith(p, "!=") || startswith(p, "<=") || startswith(p, ">=")) { cur = new_token(TK_RESERVED, cur, p, 2); p += 2; continue; } // Single-letter punctuator if (strchr("+-*/()<>;", *p)) { cur = new_token(TK_RESERVED, cur, p++, 1); 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 if (startswith(p, "return") && !is_alnum(p[6])) { cur = new_token(TK_RESERVED, cur, p, 6); p += 6; continue; }
startwith関数の戻り値がtrue、かつ、is_alnum関数の戻値がfalseの場合→文字p*を含む文字列が"return"、かつ、”return”の次の文字p[6]が英数字やアンダーバーではない場合は、キーワードを表現するトークンを生成し、トークンの連結リストを作成します。
変数curには、新規に生成されたトークン(連結リストの終端要素)のアドレスが格納されます。
次の文字を取得するためにchar型へのポインタpを6バイト分("return"の6文字分)インクリメントしてからwhile文のループを継続します。
低レイヤを知りたい人のためのCコンパイラ作成入門では、引数のトークンの型がTK_RESERVEDではなく、TK_RETURNとなっています。
複数文字の記号の場合(変更なし)
// Multi-letter punctuator if (startswith(p, "==") || startswith(p, "!=") || startswith(p, "<=") || startswith(p, ">=")) { cur = new_token(TK_RESERVED, cur, p, 2); p += 2; continue; }
1文字の記号の場合(変更なし)
// Single-letter punctuator if (strchr("+-*/()<>;", *p)) { cur = new_token(TK_RESERVED, cur, p++, 1); 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;
is_alnum関数
https://github.com/rui314/chibicc/commit/5124ded8ea4020da1aae237aeb25247f806089ce#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R77
https://github.com/rui314/chibicc/blob/5124ded8ea4020da1aae237aeb25247f806089ce/tokenize.c#L77
bool is_alnum(char c) { return is_alpha(c) || ('0' <= c && c <= '9'); }
alnum関数は、数cで指定された文字が英数字やアンダーバーであるかを判定します。
is_alpha関数
https://github.com/rui314/chibicc/commit/5124ded8ea4020da1aae237aeb25247f806089ce#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R73
https://github.com/rui314/chibicc/blob/5124ded8ea4020da1aae237aeb25247f806089ce/tokenize.c#L73
bool is_alpha(char c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; }
alpha関数は、引数cで指定された文字が英字やアンダーバーであるかを判定します。
NodeKind
https://github.com/rui314/chibicc/commit/5124ded8ea4020da1aae237aeb25247f806089ce#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R45
https://github.com/rui314/chibicc/blob/5124ded8ea4020da1aae237aeb25247f806089ce/chibicc.h#L45
typedef enum { ND_ADD, // + ND_SUB, // - ND_MUL, // * ND_DIV, // / ND_EQ, // == ND_NE, // != ND_LT, // < ND_LE, // <= ND_RETURN, // "return" ND_NUM, // Integer } NodeKind;
キーワードreturnを表現するノードの型を追加します。
stmt関数
https://github.com/rui314/chibicc/commit/5124ded8ea4020da1aae237aeb25247f806089ce#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR50
https://github.com/rui314/chibicc/blob/5124ded8ea4020da1aae237aeb25247f806089ce/parse.c#L50
// stmt = "return" expr ";" // | expr ";" Node *stmt() { if (consume("return")) { Node *node = new_unary(ND_RETURN, expr()); expect(";"); return node; } Node *node = expr(); expect(";"); return node; }
stmt関数は、生成規則 stmt = "return" expr ";" | expr ";" に基づいて、抽象構文木のノードを生成します。
"return"とexprと";"
if (consume("return")) { Node *node = new_unary(ND_RETURN, expr()); expect(";"); return node; }
consume("return")の戻り値がtrueとなる場合→着目しているトークンが"return"の場合は、new_unary関数を呼び出して、キーワードreturnを表現するノードを生成します。キーワードreturnを表現するノードは、第二引数のexpr関数で生成されたノードを子ノードとして持ちます。
new_unary関数内でトークン列の読み進めや抽象構文木のノード生成が終わった後は、着目しているトークンが ";" であることが期待されているので、expect(";")を呼び出します。最後に、new_unary関数で生成されたノードのアドレスを戻り値としてリターンします。
new_unary関数
https://github.com/rui314/chibicc/commit/5124ded8ea4020da1aae237aeb25247f806089ce#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR16
https://github.com/rui314/chibicc/blob/5124ded8ea4020da1aae237aeb25247f806089ce/parse.c#L16
Node *new_unary(NodeKind kind, Node *expr) { Node *node = new_node(kind); node->lhs = expr; return node; }
new_unary関数は、抽象構文木のノード(子ノードを1つ持つ)を生成します。
生成されたノードに子ノードを登録する
node->lhs = expr;
引数exprの値(子ノードを指定するアドレス)を生成されるノード構造体のメンバに設定します。
生成されたノードを戻り値としてリターンする
return node;
生成されたノード構造体のアドレスを戻り値としてリターンします。
gen関数
https://github.com/rui314/chibicc/commit/5124ded8ea4020da1aae237aeb25247f806089ce#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR4
https://github.com/rui314/chibicc/blob/5124ded8ea4020da1aae237aeb25247f806089ce/codegen.c#L4
#include "chibicc.h" void gen(Node *node) { switch (node->kind) { case ND_NUM: printf(" push %d\n", node->val); return; case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" ret\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_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" ret\n"); return; }
ノードの型がND_RETURNの場合(ノードの種類がreturnの場合)における処理を追加します。
ノードの型がND_RETURNの場合は、gen関数を呼び出し、returnを表現するノードの子ノードnode->lhsを用いてアセンブリコードを生成します。
gen(node->lhs)で生成されたアセンブリコードの実行時を考慮すると、ノードnode->lhsの結果に対応する値がスタックに退避されているので、このノードnode->lhsの結果に対応する値を取得するために(expr関数で生成された抽象構文木に対応する式文の評価値を取得するために)、アセンブリコード"pop rax"を生成します。
最後に、returnの処理に相当するアセンブリコード”ret”を生成します。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
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");
追加・修正されたコンパイラのソースコード(該当ステップなし、式文を表現するノード追加に伴う修正を行ったコンパイラを作成する)
NodeKind
https://github.com/rui314/chibicc/commit/74a759540c71db4529ff689b2245fd2566eb6aa3#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R54
https://github.com/rui314/chibicc/blob/74a759540c71db4529ff689b2245fd2566eb6aa3/chibicc.h#L54
typedef enum { ND_ADD, // + ND_SUB, // - ND_MUL, // * ND_DIV, // / ND_EQ, // == ND_NE, // != ND_LT, // < ND_LE, // <= ND_RETURN, // "return" ND_EXPR_STMT, // Expression statement ND_NUM, // Integer } NodeKind;
式文を表現するノードの型ND_EXPR_STMTを追加します。
stmt関数
https://github.com/rui314/chibicc/commit/74a759540c71db4529ff689b2245fd2566eb6aa3#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR59
https://github.com/rui314/chibicc/blob/74a759540c71db4529ff689b2245fd2566eb6aa3/parse.c#L59
// stmt = "return" expr ";" // | expr ";" Node *stmt() { if (consume("return")) { Node *node = new_unary(ND_RETURN, expr()); expect(";"); return node; } Node *node = new_unary(ND_EXPR_STMT, expr()); expect(";"); return node; }
"return"とexprと";"(変更なし)
if (consume("return")) { Node *node = new_unary(ND_RETURN, expr()); expect(";"); return node; }
exprと";"
Node *node = new_unary(ND_EXPR_STMT, expr()); expect(";"); return node;
expr()からnew_unary(ND_EXPR_STMT, expr())に変更します。
codegen関数
https://github.com/rui314/chibicc/commit/74a759540c71db4529ff689b2245fd2566eb6aa3#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR69
https://github.com/rui314/chibicc/blob/74a759540c71db4529ff689b2245fd2566eb6aa3/codegen.c#L69
void codegen(Node *node) { printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n"); for (Node *n = node; n; n = n->next) gen(n); printf(" ret\n"); }
部分的にアセンブリコードを生成する①(変更なし)
printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n");
部分的にアセンブリコードを生成する②
for (Node *n = node; n; n = n->next) gen(n);
gen関数の後にあったprintf(" pop rax\n")を削除します。
部分的にアセンブリコードを生成する③(変更なし)
printf(" ret\n");
gen関数
https://github.com/rui314/chibicc/commit/74a759540c71db4529ff689b2245fd2566eb6aa3#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR8
https://github.com/rui314/chibicc/blob/74a759540c71db4529ff689b2245fd2566eb6aa3/codegen.c#L8
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_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" ret\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"); }
二項演算以外を行うアセンブリコードを生成する
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_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" ret\n"); return; }
ノードの型がND_EXPR_STMTの場合(ノードの種類が式文の場合)における処理を追加します。
ノードの型がND_EXPR_STMTの場合は、gen関数を呼び出し、式文を表現するノードの子ノードnode->lhsを用いてアセンブリコードを生成します。
(gen(node->lhs)の後に、なぜアセンブリ命令"add rsp, 8"を生成する必要があるのかがわからない、、、)
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
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"); }
追加・修正されたコンパイラのソースコード(ステップ9、1文字のローカル変数を扱えるコンパイラを作成する)
TokenKind
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R14
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/chibicc.h#L14
typedef enum { TK_RESERVED, // Keywords or punctuators TK_IDENT, // Identifiers TK_NUM, // Integer literals TK_EOF, // End-of-file markers } TokenKind;
識別子を表現するトークン型TK_IDENTを追加します。
tokenize関数
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 if (startswith(p, "return") && !is_alnum(p[6])) { cur = new_token(TK_RESERVED, cur, p, 6); p += 6; continue; } // Multi-letter punctuator if (startswith(p, "==") || startswith(p, "!=") || startswith(p, "<=") || startswith(p, ">=")) { cur = new_token(TK_RESERVED, cur, p, 2); p += 2; continue; } // Single-letter punctuator if (strchr("+-*/()<>;=", *p)) { cur = new_token(TK_RESERVED, cur, p++, 1); continue; } // Identifier if ('a' <= *p && *p <= 'z') { cur = new_token(TK_IDENT, cur, p++, 1); 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 if (startswith(p, "return") && !is_alnum(p[6])) { cur = new_token(TK_RESERVED, cur, p, 6); p += 6; continue; }
複数文字の記号の場合(変更なし)
// Multi-letter punctuator if (startswith(p, "==") || startswith(p, "!=") || startswith(p, "<=") || startswith(p, ">=")) { cur = new_token(TK_RESERVED, cur, p, 2); p += 2; continue; }
1文字の記号の場合
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R120
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/tokenize.c#L120
// Single-letter punctuator if (strchr("+-*/()<>;=", *p)) { cur = new_token(TK_RESERVED, cur, p++, 1); continue; }
第一引数の文字列に = を追加して、= をトークンとして読み込めるようにしています。
識別子の場合
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R125
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/tokenize.c#L125
// Identifier if ('a' <= *p && *p <= 'z') { cur = new_token(TK_IDENT, cur, p++, 1); continue; }
文字*pが小文字の英数字の場合は、new_token関数を呼び出して、識別子を表現するトークンを生成しトークンの連結リストを作成します。
変数curには、新規に生成されたトークン(連結リストの終端要素)のアドレスが格納されます。
次の文字を取得するためにchar型へのポインタpをインクリメントしてからwhile文のループを継続します(char型へのポインタpを後置インクリメントしているので、評価前のpの値が引数として渡されます)。
数字の場合(変更なし)
// 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;
consume_ident関数
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R38
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/tokenize.c#L38
// Consumes the current token if it is an identifier. Token *consume_ident() { if (token->kind != TK_IDENT) return NULL; Token *t = token; token = token->next; return t; }
consume_ident関数は、現在着目しているトークンの種類が識別子である場合に、そのトークンを戻り値として取得し、トークンを1つ読み進めます。
NodeKind
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R55
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/chibicc.h#L55
typedef enum { ND_ADD, // + ND_SUB, // - ND_MUL, // * ND_DIV, // / ND_EQ, // == ND_NE, // != ND_LT, // < ND_LE, // <= ND_ASSIGN, // = ND_RETURN, // "return" ND_EXPR_STMT, // Expression statement ND_LVAR, // Local variable ND_NUM, // Integer } NodeKind;
等号記号を表現するノードの型ND_ASSIGNと、ローカル変数を表現するノードの型ND_LVARを追加します。
Node構造体
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R69
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/chibicc.h#L69
// 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 char name; // Used if kind == ND_LVAR int val; // Used if kind == ND_NUM };
ローカル変数(1文字)の名前を保存するnameメンバを追加します。
低レイヤを知りたい人のためのCコンパイラ作成入門ではローカル変数のアドレス値を算出するのに必要となるoffsetメンバを用意していますが、referenceブランチのコミットではgen_addr関数内でローカル変数のアドレス値を算出しています。
expr関数
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR71
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/parse.c#L71
// expr = assign Node *expr() { return assign(); }
expr関数は、生成規則 expr = assign に基づいて、抽象構文木のノードを生成します。
assign関数
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR76
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/parse.c#L76
// assign = equality ("=" assign)? Node *assign() { Node *node = equality(); if (consume("=")) node = new_binary(ND_ASSIGN, node, assign()); return node; }
assign関数は、生成規則 assign = equality ("=" assign)? に基づいて、抽象構文木のノードを生成します。
「"="とassign」を0回か1回
if (consume("=")) node = new_binary(ND_ASSIGN, node, assign()); return node;
consume(”=”)の戻り値がtrueとなる場合→着目しているトークンが ”=” の場合は、new_binary関数を呼び出して、等号記号を表現するノードを生成します。この等号記号を表現するノードは、第二引数のnodeが指定するノード(先ほどのequality関数)と第三引数のassign関数で生成されたノードを子ノードとして持ちます。
primary関数
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR154
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/parse.c#L154
// primary = "(" expr ")" | ident | num Node *primary() { if (consume("(")) { Node *node = expr(); expect(")"); return node; } Token *tok = consume_ident(); if (tok) return new_lvar(*tok->str); return new_num(expect_number()); }
primary関数は、生成規則 primary = "(" expr ")" | ident | num に基づいて、抽象構文木のノードを生成します。
"("とexprと")"(変更なし)
if (consume("(")) { Node *node = expr(); expect(")"); return node; }
ident
Token *tok = consume_ident(); if (tok) return new_lvar(*tok->str);
consume_ident関数を呼び出して、現在着目しているトークンが識別子であることを確認します。
consume_ident関数の戻り値がNULLではない場合→現在着目しているトークンが識別子だった場合は、new_lvar関数を呼び出して、ローカル変数を表現したノードを生成します。
num(変更なし)
return new_num(expect_number());
new_lvar関数
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR28
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/parse.c#L28
Node *new_lvar(char name) { Node *node = new_node(ND_LVAR); node->name = name; return node; }
new_lvar関数は、抽象構文木のノード(ローカル変数に対応する外部ノード)を新規に生成します。
生成されたノードのメンバに値を設定する
node->name = name;
引数で指定されたnameの値(ローカル変数の名前)を生成されるノード構造体のメンバに設定します。
生成されたノードを戻り値としてリターンする
return node;
生成されたノード構造体のアドレスを戻り値としてリターンします。
codegen関数
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR104
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/codegen.c#L104
void codegen(Node *node) { printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n"); // Prologue printf(" push rbp\n"); printf(" mov rbp, rsp\n"); printf(" sub rsp, 208\n"); for (Node *n = node; n; n = n->next) gen(n); // Epilogue printf(".Lreturn:\n"); printf(" mov rsp, rbp\n"); printf(" pop rbp\n"); printf(" ret\n"); }
部分的にアセンブリコードを生成する①(変更なし)
printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n");
部分的にアセンブリコードを生成する②
// Prologue printf(" push rbp\n"); printf(" mov rbp, rsp\n"); printf(" sub rsp, 208\n");
mainルーチンのプロローグとなるアセンブリコードを生成します。
ローカル変数26個分のスタック領域(26×8=208バイト)をあらかじめ用意しておきます。
部分的にアセンブリコードを生成する③
for (Node *n = node; n; n = n->next) gen(n);
gen関数
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_LVAR: gen_addr(node); load(); return; case ND_ASSIGN: gen_addr(node->lhs); gen(node->rhs); store(); 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_LVAR: gen_addr(node); load(); return; case ND_ASSIGN: gen_addr(node->lhs); gen(node->rhs); store(); return; case ND_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; }
ノードの型がND_LVARの場合(ノードの種類がローカル変数の場合)、ノードの型がND_ASSIGNの場合(ノードの種類が等号記号の場合)、ノードの型がND_RETURNの場合(ノードの種類がreturnの場合)、における処理をそれぞれ追加します。
ノードの型がND_LVARの場合
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR38
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/codegen.c#L38
ノードの型がND_LVARの場合は、gen_addr関数を呼び出して、ノードに対応するローカル変数の左辺値(アドレス値)を取得するためのアセンブリコードを生成します。その後、load関数を呼び出して、ローカル変数の左辺値(アドレス値)が指定するメモリ領域から右辺値を取得するアセンブリコードを生成します。
ノードの型がND_ASSIGNの場合
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR42
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/codegen.c#L42
ノードの型がND_ASSIGNの場合は、gen_addr(node->lhs)関数を呼び出して、ノードlhsに対応するローカル変数の左辺値(等号記号の左辺にあるローカル変数の左辺値)を取得するためのアセンブリコードを生成します。その後、gen(node->rhs)関数を呼び出して、node->rhsに対応する右辺値を取得するためのアセンブリコードを生成します。最後に、store関数を呼び出して、ノードlhsに対応する左辺値が指定するメモリ領域に、node->rhsに対応する右辺値を保存するアセンブリコードを生成します。
ノードの型がND_RETURNの場合
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR50
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/codegen.c#L50
生成されたアセンブリコードにおいてreturnを実行した時に、mainルーチンへ制御が移るようにアセンブリコード"jmp .Lreturn"を生成するようにします("ret"は削除します)。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
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"); }
gen_addr
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR3
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/codegen.c#L3
// Pushes the given node's address to the stack. void gen_addr(Node *node) { if (node->kind == ND_LVAR) { int offset = (node->name - 'a' + 1) * 8; printf(" lea rax, [rbp-%d]\n", offset); printf(" push rax\n"); return; } error("not an lvalue"); }
gen_addr関数は、ノードに対応するローカル変数の左辺値(アドレス値)を取得するためのアセンブリコードを生成します。
左辺値を取得するためのアセンブリコードを生成する
if (node->kind == ND_LVAR) { int offset = (node->name - 'a' + 1) * 8; printf(" lea rax, [rbp-%d]\n", offset); printf(" push rax\n"); return; }
ノードの型がND_LVARの場合は(ノードの種類がローカル変数の場合は)、そのローカル変数に応じたアドレスを取得するためのアセンブリコードを生成します。
ローカル変数名(node->nameの値)は小文字の英数字a~zのいずれかなので、aのオフセットは8、bのオフセットは16、cのオフセット24...となるように、文字コードの差name - 'a'を用いてオフセット値を算出します。
エラーメッセージを表示する
error("not an lvalue");
error関数を呼び出し、メッセージを出力してからプログラムを終了します。
load関数
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR15
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/codegen.c#L15
void load() { printf(" pop rax\n"); printf(" mov rax, [rax]\n"); printf(" push rax\n"); }
load関数は、左辺値が指定するメモリ領域から右辺値を取得するアセンブリコードを生成します。
store関数
https://github.com/rui314/chibicc/commit/97a255d2d33e76c0be672cc4ecd32f9035e833ca#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR21
https://github.com/rui314/chibicc/blob/97a255d2d33e76c0be672cc4ecd32f9035e833ca/codegen.c#L21
void store() { printf(" pop rdi\n"); printf(" pop rax\n"); printf(" mov [rax], rdi\n"); printf(" push rdi\n"); }
store関数は、左辺値が指定するメモリ領域に右辺値を保存するアセンブリコードを生成します。
追加・修正されたコンパイラのソースコード(ステップ10、複数文字のローカル変数を扱えるコンパイラを作成する)
main関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR10
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/main.c#L10
int main(int argc, char **argv) { if (argc != 2) error("%s: invalid number of arguments", argv[0]); // Tokenize and parse. user_input = argv[1]; token = tokenize(); Program *prog = program(); // Assign offsets to local variables. int offset = 0; for (Var *var = prog->locals; var; var = var->next) { offset += 8; var->offset = offset; } prog->stack_size = offset; // Traverse the AST to emit assembly. codegen(prog); return 0; }
コマンドライン引数の個数をチェックする(変更なし)
if (argc != 2) error("%s: invalid number of arguments", argv[0]);
トークナイズを行う(変更なし)
// Tokenize and parse. user_input = argv[1]; token = tokenize();
パースを行う
Program *prog = program();
program関数の戻り値をnodeからprogへ変更します。program関数で生成された抽象構文木(のルートノードからなる連結リスト)は、Program構造体変数progのnodeメンバから参照することができます。
ローカル変数のアドレスとスタックを用意する
// Assign offsets to local variables. int offset = 0; for (Var *var = prog->locals; var; var = var->next) { offset += 8; var->offset = offset; } prog->stack_size = offset;
ローカル変数を表現したvra構造体のそれぞれが持つoffsetメンバに値をセットします → 各ローカル変数にオフセットを割り当てます。
オフセットは、スタックにおいてベースアドレスからローカル変数が配置されるアドレスまでの差分となる値(バイト単位)で、8、16、24、、、の8バイト境界となる値(0を除く)をとります。そのオフセットの最大値を必要となるスタックサイズとしてprog構造体のstack_sizeメンバに保存します。
tokenize関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R133
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/tokenize.c#L133
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 if (startswith(p, "return") && !is_alnum(p[6])) { cur = new_token(TK_RESERVED, cur, p, 6); p += 6; continue; } // Multi-letter punctuator if (startswith(p, "==") || startswith(p, "!=") || startswith(p, "<=") || startswith(p, ">=")) { cur = new_token(TK_RESERVED, cur, p, 2); p += 2; 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 if (startswith(p, "return") && !is_alnum(p[6])) { cur = new_token(TK_RESERVED, cur, p, 6); p += 6; continue; }
複数文字の記号の場合(変更なし)
// Multi-letter punctuator if (startswith(p, "==") || startswith(p, "!=") || startswith(p, "<=") || startswith(p, ">=")) { cur = new_token(TK_RESERVED, cur, p, 2); p += 2; continue; }
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; }
is_alpha関数の戻り値がtrueとなる場合→文字列の先頭文字が*pが英字の場合は、識別子を表現したトークンを生成します。
次の文字を取得するためにchar型へのポインタpを後置インクリメントし(ポインタqに値を保存してからpをインクリメントし)、is_alnum関数の戻り値がtrueになった回数分だけ(文字*pが英数字やアンダーバーである回数分だけ)pをインクリメントします。その後、new_token関数を呼び出して、識別子を表現するトークンを生成しトークンの連結リストを作成します。
変数curには、新規に生成されたトークン(連結リストの終端要素)のアドレスが格納されます。
数字の場合(変更なし)
// 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;
NodeKind
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R69
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/chibicc.h#L69
typedef enum { ND_ADD, // + ND_SUB, // - ND_MUL, // * ND_DIV, // / ND_EQ, // == ND_NE, // != ND_LT, // < ND_LE, // <= ND_ASSIGN, // = ND_RETURN, // "return" ND_EXPR_STMT, // Expression statement ND_LVAR, // Local variable ND_VAR, // Variable ND_NUM, // Integer } NodeKind;
ローカル変数を表現するノード型ND_LVARを表現するノード型ND_VARに変更します。
Var構造体
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R48
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/chibicc.h#L48
// Local variable typedef struct Var Var; struct Var { Var *next; char *name; // Variable name int offset; // Offset from RBP };
変数を表現し管理するためのデータ構造であるVar構造体を定義します。
locals変数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR3
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/parse.c#L3
Var *locals;
ローカル変数はVar構造体の連結リストとして管理するので、その連結リストの先頭を示すグローバル変数localsを定義します。
Node構造体
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R80
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/chibicc.h#L80
// 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 Var *var; // Used if kind == ND_VAR int val; // Used if kind == ND_NUM };
var構造体へのポインタvarをメンバとして追加します。
new_var関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR38
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/parse.c#L38
Node *new_var(Var *var) { Node *node = new_node(ND_VAR); node->var = var; return node; }
new_var関数は、抽象構文木の外部ノード(ローカル変数に対応するノード)を新規に生成します。
生成されたノードのメンバに値を設定する
node->var = var;
引数varで指定されたvar構造体変数のアドレスを生成されるノード構造体のvarメンバに設定します
生成されたノードを戻り値としてリターンする
return node;
生成されたノード構造体のアドレスを戻り値としてリターンします。
Program構造体
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R84
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/chibicc.h#L84
typedef struct { Node *node; Var *locals; int stack_size; } Program;
生成されるアセンブリコードの抽象構文木、ローカル変数、スタックサイズを管理するためのデータ構造であるProgram構造体を定義します。
program関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR63
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/parse.c#L63
Program *program() { locals = NULL; Node head; head.next = NULL; Node *cur = &head; while (!at_eof()) { cur->next = stmt(); cur = cur->next; } Program *prog = calloc(1, sizeof(Program)); prog->node = head.next; prog->locals = locals; return prog; }
抽象構文木のルートノードからなる連結リストのヘッダーを作成する(変更なし)
Node head; head.next = NULL; Node *cur = &head;
抽象構文木のルートノードからなる連結リストを作成する(変更なし)
while (!at_eof()) { cur->next = stmt(); cur = cur->next; }
プログラム構造体をリターンする
Program *prog = calloc(1, sizeof(Program)); prog->node = head.next; prog->locals = locals; return prog;
calloc関数を呼び出して、Program構造体を生成するのに必要なメモリ領域を割り当てます。
nodeメンバに抽象構文木のルートノードのからなる連結リストの先頭要素のアドレス、localsメンバにVar構造体からなる連結リストの先頭要素のアドレス、を格納してから、Program構造体のアドレスを戻り値としてリターンします。
primary関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR187
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/parse.c#L187
Node *primary() { if (consume("(")) { Node *node = expr(); expect(")"); return node; } Token *tok = consume_ident(); if (tok) { Var *var = find_var(tok); if (!var) var = push_var(strndup(tok->str, tok->len)); return new_var(var); } return new_num(expect_number()); }
"("とexprと")"(変更なし)
if (consume("(")) { Node *node = expr(); expect(")"); return node; }
ident
Token *tok = consume_ident(); if (tok) { Var *var = find_var(tok); if (!var) var = push_var(strndup(tok->str, tok->len)); return new_var(var); }
consume_ident関数を呼び出してtokが真となる場合 → 識別子(ローカル変数)を表現するトークンを取得できた場合は、find_var関数を呼び出して、Var構造体からなる連結リストから、tokが持つ文字列と同じ名前を持つvar構造体変数を取得します。
!varが真となる場合→tokが持つ文字列と同じ名前を持つvar構造体変数を取得できなかった場合は、push_var関数を呼び出して、var構造体を新規に生成しvar構造体の連結リストに追加します。
最後に、new_var関数を呼び出して、ローカル変数に対応するノードを新規に生成します。
num(変更なし)
return new_num(expect_number());
find_var関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR5
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/parse.c#L5
// Find a local variable by name. Var *find_var(Token *tok) { for (Var *var = locals; var; var = var->next) if (strlen(var->name) == tok->len && !memcmp(tok->str, var->name, tok->len)) return var; return NULL; }
find_var関数は、var構造体からなる連結リストからトークンの文字列と同じ名前を持つvar構造体を探索します(今まで定義されたローカル変数からトークンの文字列と同じ名前を持つローカル変数を探索します)。
for文の初期設定式でグローバル変数localsから連結リストにある先頭要素のアドレスを取得します。
strlen関数の戻り値とtok->lenの値が等しい、かつ、memcmp関数の戻り値が0となる場合 → var->nameの長さとtok->lenの長さが同じ、かつ、var->nameの値とtok->strの値が同じ場合 → 今まで定義されたローカル変数名の長さと引数で指定されたトークン文字列の長さが同じ、かつ、今まで定義されたローカル変数名と指定されたトークン文字列が同じ場合は、そのVar構造体変数のアドレスを戻り値としてリターンします。
Var構造体からなる連結リストにトークンの文字列と同じ名前を持つVar構造体がなかった場合は、NULLを戻り値としてリターンします。
push_var関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR44
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/parse.c#L44
Var *push_var(char *name) { Var *var = calloc(1, sizeof(Var)); var->next = locals; var->name = name; locals = var; return var; }
push_var関数は、ローカル変数を表現しているvar構造体を新規に生成し、localsが指定するvar構造体の連結リストに追加します。
strndup関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R29
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/tokenize.c#L29
char *strndup(char *p, int len) { char *buf = malloc(len + 1); strncpy(buf, p, len); buf[len] = '\0'; return buf; }
strndup関数は、引数pが指定する文字列から、長さlen+1となるヌル終端文字列を取得します。
char *buf = malloc(len + 1);
malloc関数を呼び出して、文字列を格納するために必要なメモリ領域を割り当てます。
割り当てるメモリ領域のサイズは、引数で指定されたlen+1バイトです(文字列+ヌル文字)。
割り当てたメモリ領域の先頭アドレスを戻り値として取得します。
strncpy(buf, p, len);
strncp関数は、第二引数pのアドレスから、第一引数bufのアドレスへ、第三引数lenで指定されたサイズ分の文字データをコピーします。
buf[len] = '\0';
メモリ領域の最後尾1バイトにヌル文字を格納します。
return buf;
割り当てたメモリ領域の先頭アドレスを戻り値としてリターンします。
codegen関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR98
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/codegen.c#L98
void codegen(Program *prog) { printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n"); // Prologue printf(" push rbp\n"); printf(" mov rbp, rsp\n"); printf(" sub rsp, %d\n", prog->stack_size); // Emit code for (Node *node = prog->node; node; node = node->next) gen(node); // Epilogue printf(".Lreturn:\n"); printf(" mov rsp, rbp\n"); printf(" pop rbp\n"); printf(" ret\n"); }
部分的にアセンブリコードを生成する①(変更なし)
printf(".intel_syntax noprefix\n"); printf(".global main\n"); printf("main:\n");
部分的にアセンブリコードを生成する②
// Prologue printf(" push rbp\n"); printf(" mov rbp, rsp\n"); printf(" sub rsp, %d\n", prog->stack_size);
printf(" sub rsp, 208\n");を削除し、printf(" sub rsp, %d\n", prog->stack_size);を追加します。
prog->stack_sizeで指定された値の分だけ、ローカル変数を退避させるスタック領域を用意しておきます。
部分的にアセンブリコードを生成する③
// Emit code for (Node *node = prog->node; node; node = node->next) gen(node);
prog構造体変数からnodeを取得するように変更します。
部分的にアセンブリコードを生成する④(変更なし)
// Epilogue printf(".Lreturn:\n"); printf(" mov rsp, rbp\n"); printf(" pop rbp\n"); printf(" ret\n");
gen関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR37
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/codegen.c#L37
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_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_RETURN: gen(node->lhs); printf(" pop rax\n"); printf(" jmp .Lreturn\n"); return; }
ローカル変数を表現するノード型をND_LVARからND_VARへ変更します。
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
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");
gen_addr関数
https://github.com/rui314/chibicc/commit/8a8a35b9241dcbdc39926e5e64f6f22e8174418f#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR5
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/codegen.c#L5
void gen_addr(Node *node) { if (node->kind == ND_VAR) { printf(" lea rax, [rbp-%d]\n", node->var->offset); printf(" push rax\n"); return; } error("not an lvalue"); }
左辺値を取得するためのアセンブリコードを生成する
if (node->kind == ND_VAR) { printf(" lea rax, [rbp-%d]\n", node->var->offset); printf(" push rax\n"); return; }
ローカル変数を表現するノード型をND_LVARからND_VARへ変更します。
gen_addr関数内でオフセットを計算するのではなく、node->var->offsetの値をオフセットとして使用するようにします。
エラーメッセージを表示する(変更なし)
error("not an lvalue");
テストコード
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/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;' echo OK
Makefile
https://github.com/rui314/chibicc/blob/8a8a35b9241dcbdc39926e5e64f6f22e8174418f/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