chibiccを読む~Cコンパイラコードリーディング~ ステップ20, 21, 22

トップページ
jupiteroak.hatenablog.com
「低レイヤを知りたい人のためのCコンパイラ作成入門」のCコンパイラを読んでいきます。
www.sigbus.info
ステップ21に該当
github.com
ステップ22に該当
github.com
ステップ20に該当
github.com

今回作成するコンパイラ

配列を実装する(ステップ21、配列を扱えるコンパイラを作成する)

配列の添字を実装する(ステップ22、配列の添字を扱えるコンパイラを作成する)

sizeof演算子(ステップ20、sizeof演算子を扱えるコンパイラを作成する)

追加・修正されたコンパイラソースコード(ステップ21、配列を扱えるコンパイラを実装する)

main関数

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR17
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/main.c#L17

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) {
      Var *var = vl->var;
      offset += size_of(var->ty);
      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();
型付けを行う(変更なし)
  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) {
      Var *var = vl->var;
      offset += size_of(var->ty);
      var->offset = offset;
    }
    fn->stack_size = offset;
  }

引数やローカル変数に割り当てるアドレス(スタックのベースアドレスからのオフセットアドレス)を8バイトからsize_of(var->ty)バイト(型のバイトサイズ)へ変更します。

アセンブリコードを生成する(変更なし)
  // Traverse the AST to emit assembly.
  codegen(prog);

  return 0;

tokenize関数

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R173
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/tokenize.c#L173

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");
  }
トークン列の終端を表すトークンを生成する(変更なし)
new_token(TK_EOF, cur, p, 0);
連結リストの先頭トークンを戻り値としてリターンする(変更なし)
  return head.next;

declaration関数

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR155
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/parse.c#L155

// declaration = basetype ident ("[" num "]")* ("=" expr) ";"
Node *declaration() {
  Token *tok = token;
  Type *ty = basetype();
  char *name = expect_ident();
  ty = read_type_suffix(ty);
  Var *var = push_var(name, 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 ("[" num "]")* ("=" expr)? ";" に基づいて、抽象構文木のノードを生成します。

basetype(変更なし)
  Type *ty = basetype();
ident
  char *name = expect_ident();

この時点では、トークンから読み取った識別子が変数名か配列名かわからないので、expect_ident関数を呼び出し名前を保存しておきます。

「"["とnumと"]"」を0回以上
  ty = read_type_suffix(ty);
  Var *var = push_var(name, ty);

read_type_suffix関数を呼び出して、トークンを読み取りながら、データ型が配列型がそれ以外の型かを決定します。
その後、配列名かローカル変数名を表現するVar構造体を生成します。

「"="とexpr」を0回か1回(変更なし)
  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);

read_func_param関数

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR105
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/parse.c#L105

VarList *read_func_param() {
  Type *ty = basetype();
  char *name = expect_ident();
  ty = read_type_suffix(ty);

  VarList *vl = calloc(1, sizeof(VarList));
  vl->var = push_var(name, ty);
  return vl;
}
Type構造体を生成する(変更なし)
Type *ty = basetype();
変数名か配列名になる識別子を取得する
char *name = expect_ident();

この時点では、トークンから読み取った識別子が変数名か配列名かわからないので、expect_ident関数を呼び出し名前を保存しておきます。

Type構造体を更新する
ty = read_type_suffix(ty);

read_type_suffix関数を呼び出して、トークンを読み取りながら、データ型が配列型がそれ以外の型かを決定します。

VarList構造体を生成する(変更なし)
  VarList *vl = calloc(1, sizeof(VarList));
Var構造体を生成する(変更なし)
  vl->var = push_var(name, ty);

read_type_suffix関数

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR94
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/parse.c#L94

Type *read_type_suffix(Type *base) {
  if (!consume("["))
    return base;
  int sz = expect_number();
  expect("]");
  base = read_type_suffix(base);
  return array_of(base, sz);
}

read_type_suffix関数は、トークンを読み取って、データ型が配列型か配列型以外かを決定します。

!consume("[") が真となる場合 → 現在着目しているトークンが"["ではなかった場合は、Type構造体baseを戻り値としてリターンします。

"["の次のトークンは、配列のサイズを指定する整数値であることが期待されているので、expect_number関数を呼び出して、その整数値を取得します。

多次元配列に対応するために、read_type_suffix関数を再帰的に呼び出し、baseを更新します。

最後に、array_of関数を呼び出して、配列型を表現するType構造体を生成します。

array_of関数

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R16
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/type.c#L16

Type *array_of(Type *base, int size) {
  Type *ty = calloc(1, sizeof(Type));
  ty->kind = TY_ARRAY;
  ty->base = base;
  ty->array_size = size;
  return ty;
}

array_of関数は、配列型を表現するType構造体を生成します。

size_of関数

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R24
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/type.c#L24

int size_of(Type *ty) {
  if (ty->kind == TY_INT || ty->kind == TY_PTR)
    return 8;
  assert(ty->kind == TY_ARRAY);
  return size_of(ty->base) * ty->array_size;
}

sizet_of関数は、引数で指定されType構造体が表現する型に応じて、その型のサイズを取得します。

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->base) {
      Node *tmp = node->lhs;
      node->lhs = node->rhs;
      node->rhs = tmp;
    }
    if (node->rhs->ty->base)
      error_tok(node->tok, "invalid pointer arithmetic operands");
    node->ty = node->lhs->ty;
    return;
  case ND_SUB:
    if (node->rhs->ty->base)
      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:
    if (node->lhs->ty->kind == TY_ARRAY)
      node->ty = pointer_to(node->lhs->ty->base);
    else
      node->ty = pointer_to(node->lhs->ty);
    return;
  case ND_DEREF:
    if (!node->lhs->ty->base)
      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、変更なし)
  case ND_VAR:
    node->ty = node->var->ty;
    return;
ノード型に応じて型付け処理を行う(ND_ADD)

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R63
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/type.c#L63

  case ND_ADD:
     if (node->rhs->ty->base) {
      Node *tmp = node->lhs;
      node->lhs = node->rhs;
      node->rhs = tmp;
    }
    if (node->rhs->ty->base)
      error_tok(node->tok, "invalid pointer arithmetic operands");
    node->ty = node->lhs->ty;
    return;

子ノードrhsの型が「~へのポインタ」型であるかどうかを、子ノードが持つType構造体がさらにType構造体を参照しているかどうかで、判断するようにします。

ノード型に応じて型付け処理を行う(ND_SUB)

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R73
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/type.c#L73

  case ND_SUB:
    if (node->rhs->ty->base)
      error_tok(node->tok, "invalid pointer arithmetic operands");
    node->ty = node->lhs->ty;
    return;

子ノードrhsの型が「~へのポインタ」型であるかどうかを、子ノードが持つType構造体がさらにType構造体を参照しているかどうかで、判断するようにします。

ノード型に応じて型付け処理を行う(ND_ASSIGN、変更なし)
  case ND_ASSIGN:
    node->ty = node->lhs->ty;
    return;
ノード型に応じて型付け処理を行う(ND_ADDR)

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R81
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/type.c#L81

  case ND_ADDR:
    if (node->lhs->ty->kind == TY_ARRAY)
      node->ty = pointer_to(node->lhs->ty->base);
    else
      node->ty = pointer_to(node->lhs->ty);
    return;
  }

子ノードlhsの持つType構造体が配列型を表現している場合は(子ノードlhsの持つ型が配列型の場合は)、その配列型を表現しているType構造体が参照しているType構造体を用いてpointer_to関数を呼び出し、親ノードが持つ型(Type構造体)を「base型へのポインタ」型にします。

ノード型に応じて型付け処理を行う(ND_DEREF)

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R87
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/type.c#L87

  case ND_DEREF:
    if (!node->lhs->ty->base)
      error_tok(node->tok, "invalid pointer dereference");
    node->ty = node->lhs->ty->base;
    return;

子ノードlhsの型が「~へのポインタ」型であるかどうかを、子ノードが持つType構造体がさらにType構造体を参照しているかどうかで、判断するようにします。
また、!node->lhs->ty->baseが真となる場合 → 子ノードが持つType構造体がさらにType構造体を参照していなかった場合 → 子ノードlhsの型が「~へのポインタ」型でなかった場合は、 error_tok関数を呼び出してエラーメッセージを出力します。

gen関数

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);
    if (node->ty->kind != TY_ARRAY)
      load();
    return;
  case ND_ASSIGN:
    gen_lval(node->lhs);
    gen(node->rhs);
    store();
    return;
  case ND_ADDR:
    gen_addr(node->lhs);
    return;
  case ND_DEREF:
    gen(node->lhs);
    if (node->ty->kind != TY_ARRAY)
      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->base)
      printf("  imul rdi, %d\n", size_of(node->ty->base));
    printf("  add rax, rdi\n");
    break;
  case ND_SUB:
    if (node->ty->base)
      printf("  imul rdi, %d\n", size_of(node->ty->base));
    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_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);
    if (node->ty->kind != TY_ARRAY)
      load();
    return;
  case ND_ASSIGN:
    gen_lval(node->lhs);
    gen(node->rhs);
    store();
    return;
  case ND_ADDR:
    gen_addr(node->lhs);
    return;
  case ND_DEREF:
    gen(node->lhs);
    if (node->ty->kind != TY_ARRAY)
      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_VARの場合は(ノードがローカル変数に対応している場合は)、ノードの持つType構造体が配列型を表現していないことを確認してから、load関数を呼び出します。
https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR58
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/codegen.c#L58

  case ND_VAR:
    gen_addr(node);
    if (node->ty->kind != TY_ARRAY)
      load();
    return;

ノード型がND_ASSIGNの場合(ノードが等号記号に対応している場合)の処理において、gen_addr(node->lhs)からgen_lval(node->lhsへ変更します。
https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR62
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/codegen.c#L62

  case ND_ASSIGN:
    gen_lval(node->lhs);
    gen(node->rhs);
    store();
    return;

ノード型がND_DEREFの場合は(ノードが 間接参照演算子"*" に対応している場合は)、ノードの持つType構造体が配列型を表現していないことを確認してから、load関数を呼び出します。
https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR71
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/codegen.c#L71

  case ND_DEREF:
    gen(node->lhs);
    if (node->ty->kind != TY_ARRAY)
      load();
    return;
二項演算の対象となる値を得るためのアセンブリコードを生成する(変更なし)
  gen(node->lhs);
  gen(node->rhs);

  printf("  pop rdi\n");
  printf("  pop rax\n");
二項演算を行うアセンブリコードを生成する
  switch (node->kind) {
  case ND_ADD:
    if (node->ty->base)
      printf("  imul rdi, %d\n", size_of(node->ty->base));
    printf("  add rax, rdi\n");
    break;
  case ND_SUB:
    if (node->ty->base)
      printf("  imul rdi, %d\n", size_of(node->ty->base));
    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の場合(加算を行う場合)における処理の改修です。
ノードの型が「~へのポインタ」型であるかどうかを、ノードが持つType構造体がさらにType構造体を参照しているかどうかで、判断するようにします。
ノードの型が「~へのポインタ」型である場合は、右オペランドの値をsize_of(node->ty->base)倍する必要があるので、加算を行うアセンブリコード"add rax, rdi"を生成する前に、アセンブリコード" imul rdi, (「~へのポインタ」型のサイズ)"を生成するようにします。
https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR174
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/codegen.c#L174

  case ND_ADD:
    if (node->ty->base)
      printf("  imul rdi, %d\n", size_of(node->ty->base));
    printf("  add rax, rdi\n");
    break;

ノード型がND_SUBの場合(減算を行う場合)における処理の改修です。
ノードの型が「~へのポインタ」型であるかどうかを、ノードが持つType構造体がさらにType構造体を参照しているかどうかで、判断するようにします。
ノードの型が「~へのポインタ」型である場合は、右オペランドの値をsize_of(node->ty->base)倍する必要があるので、減算を行うアセンブリコード"sub rax, rdi"を生成する前に、アセンブリコード" imul rdi, (「~へのポインタ」型のサイズ)"を生成するようにします。
https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR179
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/codegen.c#L179

  case ND_SUB:
    if (node->ty->base)
      printf("  imul rdi, %d\n", size_of(node->ty->base));
    printf("  sub rax, rdi\n");
    break;

gen_lval関数

https://github.com/rui314/chibicc/commit/f5536961e8182951376e320fafe4340c3a8e12b3#diff-629fe11334ae1d560032cdb6cc6f9a4fbb0f5b1365894b6b648d6ee4d5a654beR25
https://github.com/rui314/chibicc/blob/f5536961e8182951376e320fafe4340c3a8e12b3/codegen.c#L25

void gen_lval(Node *node) {
  if (node->ty->kind == TY_ARRAY)
    error_tok(node->tok, "not an lvalue");
  gen_addr(node);
}

gen_lval関数は、ノードが持つType構造体が配列型を表現していない場合にのみ、gen_addr関数を呼び出し左辺値を取得するためのアセンブリコードを生成するようにします。

追加・修正されたコンパイラソースコード(ステップ22、配列の添字を扱えるコンパイラを作成する)

unary関数

https://github.com/rui314/chibicc/commit/c97aa80173b949104eb0a3e376bbf6309ce90d55#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR348
https://github.com/rui314/chibicc/blob/c97aa80173b949104eb0a3e376bbf6309ce90d55/parse.c#L348

// unary = ("+" | "-" | "*" | "&")? unary
//       | postfix
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 postfix();
}

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);
postfix
 return postfix();

primary関数からpostfix関数へ変更します。

postfix関数

https://github.com/rui314/chibicc/commit/c97aa80173b949104eb0a3e376bbf6309ce90d55#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR351
https://github.com/rui314/chibicc/blob/c97aa80173b949104eb0a3e376bbf6309ce90d55/parse.c#L351

// postfix = primary ("[" expr "]")*
Node *postfix() {
  Node *node = primary();
  Token *tok;

  while (tok = consume("[")) {
    // x[y] is short for *(x+y)
    Node *exp = new_binary(ND_ADD, node, expr(), tok);
    expect("]");
    node = new_unary(ND_DEREF, exp, tok);
  }
  return node;
}

postfix関数は、生成規則 postfix = primary ("[" expr "]")* に基づいて、抽象構文木のノードを生成します。

primary
  Node *node = primary();

primary関数を呼び出して、抽象構文木のノードを生成します。

「"["、expr、"]"」を0回以上
  Token *tok;

  while (tok = consume("[")) {
    // x[y] is short for *(x+y)
    Node *exp = new_binary(ND_ADD, node, expr(), tok);
    expect("]");
    node = new_unary(ND_DEREF, exp, tok);
  }
  return node;

tok = consume("[")が真となる場合 → 現在読み取っているトークンが"["の場合は、以降のトークンの並びが、"配列のインデックスとなるexpr"、"]"となる想定でパースを進めていきます。その際、x[y]を*(x+y)と読み替えて、抽象構文木のノードを生成します。

追加・修正されたコンパイラソースコード(ステップ20、sizeof演算子を扱えるコンパイラを作成する)

starts_with_reserved関数

https://github.com/rui314/chibicc/commit/30d530831fa2312ed6f2e73520c1525202745d88#diff-289479d6df6940b25dd31a6f2da4881331f916ec642bd1ae47d4ff0a365d8e88R131
https://github.com/rui314/chibicc/blob/30d530831fa2312ed6f2e73520c1525202745d88/tokenize.c#L131

char *starts_with_reserved(char *p) {
  // Keyword
  static char *kw[] = {"return", "if", "else", "while", "for", "int", "sizeof"};

  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", "sizeof"};

  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に"sizeof"を追加し、"sizeof"をキーワードとして扱えるようにします。

複数文字の記号を取得する(変更なし)
  // 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/30d530831fa2312ed6f2e73520c1525202745d88#diff-d06dbb7ef5899cdf50b340464444680b13aded45363e7aba944dc3551fdf6334R85
https://github.com/rui314/chibicc/blob/30d530831fa2312ed6f2e73520c1525202745d88/chibicc.h#L85

// 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_SIZEOF,    // "sizeof"
  ND_BLOCK,     // { ... }
  ND_FUNCALL,   // Function call
  ND_EXPR_STMT, // Expression statement
  ND_VAR,       // Variable
  ND_NUM,       // Integer
  ND_NULL,      // Empty statement
} NodeKind;

キーワード"sizeof"を表現するノード型ND_SIZEOFを追加します。

primary関数

https://github.com/rui314/chibicc/commit/30d530831fa2312ed6f2e73520c1525202745d88#diff-a07721cd062be25900bddb926de15fc103cf32ea2726d1fea286f6548b810c6aR390
https://github.com/rui314/chibicc/blob/30d530831fa2312ed6f2e73520c1525202745d88/parse.c#L390

// primary = "(" expr ")" | "sizeof" unary | ident func-args? | num
Node *primary() {
  Token *tok;

  if (consume("(")) {
    Node *node = expr();
    expect(")");
    return node;
  }

  if (tok = consume("sizeof"))
    return new_unary(ND_SIZEOF, unary(), 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);
}

primary関数は、生成規則 primary = "(" expr ")" | "sizeof" unary | ident func-args? | num に基づいて、抽象構文木のノードを生成します。

"("、expr、")"(変更なし)
  if (consume("(")) {
    Node *node = expr();
    expect(")");
    return node;
  }
sizeof、unary
  if (tok = consume("sizeof"))
    return new_unary(ND_SIZEOF, unary(), tok);

tok = consume("sizeof")が真となる場合 → 現在着目トークンが"sizeof"の場合は、new_unary関数を呼び出し"sizeof"に対応するノードを生成します。
また、new_unary関数の第二引数にunary関数の戻り値を指定し、unary関数によって生成された抽象構文木のルートノードがsizeof演算子オペランドに対応する子ノードとなるようにします。

ident、func-argsを0回か1回(変更なし)
  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);
  }
num(変更なし)
  tok = token;
  if (tok->kind != TK_NUM)
    error_tok(tok, "expected expression");
  return new_num(expect_number(), tok);

visit関数

https://github.com/rui314/chibicc/commit/30d530831fa2312ed6f2e73520c1525202745d88#diff-76824d44018db869c0d65b9ae798b19e16fca3176fd792fbc855fe74da032e32R91
https://github.com/rui314/chibicc/blob/30d530831fa2312ed6f2e73520c1525202745d88/type.c#L91

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->base) {
      Node *tmp = node->lhs;
      node->lhs = node->rhs;
      node->rhs = tmp;
    }
    if (node->rhs->ty->base)
      error_tok(node->tok, "invalid pointer arithmetic operands");
    node->ty = node->lhs->ty;
    return;
  case ND_SUB:
    if (node->rhs->ty->base)
      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:
    if (node->lhs->ty->kind == TY_ARRAY)
      node->ty = pointer_to(node->lhs->ty->base);
    else
      node->ty = pointer_to(node->lhs->ty);
    return;
  case ND_DEREF:
    if (!node->lhs->ty->base)
      error_tok(node->tok, "invalid pointer dereference");
    node->ty = node->lhs->ty->base;
    return;
  case ND_SIZEOF:
    node->kind = ND_NUM;
    node->ty = int_type();
    node->val = size_of(node->lhs->ty);
    node->lhs = NULL;
    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、変更なし)
  case ND_VAR:
    node->ty = node->var->ty;
    return;
ノード型に応じて型付け処理を行う(ND_ADD、変更なし)
  case ND_ADD:
     if (node->rhs->ty->base) {
      Node *tmp = node->lhs;
      node->lhs = node->rhs;
      node->rhs = tmp;
    }
    if (node->rhs->ty->base)
      error_tok(node->tok, "invalid pointer arithmetic operands");
    node->ty = node->lhs->ty;
    return;
ノード型に応じて型付け処理を行う(ND_SUB、変更なし)
  case ND_SUB:
    if (node->rhs->ty->base)
      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:
    if (node->lhs->ty->kind == TY_ARRAY)
      node->ty = pointer_to(node->lhs->ty->base);
    else
      node->ty = pointer_to(node->lhs->ty);
    return;
ノード型に応じて型付け処理を行う(ND_DEREF、変更なし)
  case ND_DEREF:
    if (!node->lhs->ty->base)
      error_tok(node->tok, "invalid pointer dereference");
    node->ty = node->lhs->ty->base;
    return;
ノード型に応じて型付け処理を行う(ND_SIZEOF)
  case ND_SIZEOF:
    node->kind = ND_NUM;
    node->ty = int_type();
    node->val = size_of(node->lhs->ty);
    node->lhs = NULL;
    return;

nodeのノード型がND_SIZEOFの場合は、nodeをsizeof演算子の評価値に対応するノードへ変更します。
nodeのノード型をND_NUMに変更し、nodeの持つ型をint型にするためにint_type関数を呼び出します。
nodeの持つ値は、子ノードnode->lhsの持つ型のサイズとなります。
最後に、子ノードへの参照をなくしておきます。

テストコード

https://github.com/rui314/chibicc/commit/30d530831fa2312ed6f2e73520c1525202745d88#diff-3722d9ba8feb2d3feac8ce71a209a638d4b404e1c53f937188761181594023e2R136
https://github.com/rui314/chibicc/blob/30d530831fa2312ed6f2e73520c1525202745d88/test.sh#L136

#!/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; }'

assert 3 'int main() { int x[2]; int *y=&x; *y=3; return *x; }'

assert 3 'int main() { int x[3]; *x=3; *(x+1)=4; *(x+2)=5; return *x; }'
assert 4 'int main() { int x[3]; *x=3; *(x+1)=4; *(x+2)=5; return *(x+1); }'
assert 5 'int main() { int x[3]; *x=3; *(x+1)=4; *(x+2)=5; return *(x+2); }'

assert 0 'int main() { int x[2][3]; int *y=x; *y=0; return **x; }'
assert 1 'int main() { int x[2][3]; int *y=x; *(y+1)=1; return *(*x+1); }'
assert 2 'int main() { int x[2][3]; int *y=x; *(y+2)=2; return *(*x+2); }'
assert 3 'int main() { int x[2][3]; int *y=x; *(y+3)=3; return **(x+1); }'
assert 4 'int main() { int x[2][3]; int *y=x; *(y+4)=4; return *(*(x+1)+1); }'
assert 5 'int main() { int x[2][3]; int *y=x; *(y+5)=5; return *(*(x+1)+2); }'
assert 6 'int main() { int x[2][3]; int *y=x; *(y+6)=6; return **(x+2); }'

assert 3 'int main() { int x[3]; *x=3; x[1]=4; x[2]=5; return *x; }'
assert 4 'int main() { int x[3]; *x=3; x[1]=4; x[2]=5; return *(x+1); }'
assert 5 'int main() { int x[3]; *x=3; x[1]=4; x[2]=5; return *(x+2); }'
assert 5 'int main() { int x[3]; *x=3; x[1]=4; x[2]=5; return *(x+2); }'
assert 5 'int main() { int x[3]; *x=3; x[1]=4; 2[x]=5; return *(x+2); }'

assert 0 'int main() { int x[2][3]; int *y=x; y[0]=0; return x[0][0]; }'
assert 1 'int main() { int x[2][3]; int *y=x; y[1]=1; return x[0][1]; }'
assert 2 'int main() { int x[2][3]; int *y=x; y[2]=2; return x[0][2]; }'
assert 3 'int main() { int x[2][3]; int *y=x; y[3]=3; return x[1][0]; }'
assert 4 'int main() { int x[2][3]; int *y=x; y[4]=4; return x[1][1]; }'
assert 5 'int main() { int x[2][3]; int *y=x; y[5]=5; return x[1][2]; }'
assert 6 'int main() { int x[2][3]; int *y=x; y[6]=6; return x[2][0]; }'

assert 8 'int main() { int x; return sizeof(x); }'
assert 8 'int main() { int x; return sizeof x; }'
assert 8 'int main() { int *x; return sizeof(x); }'
assert 32 'int main() { int x[4]; return sizeof(x); }'
assert 96 'int main() { int x[3][4]; return sizeof(x); }'
assert 32 'int main() { int x[3][4]; return sizeof(*x); }'
assert 8 'int main() { int x[3][4]; return sizeof(**x); }'
assert 9 'int main() { int x[3][4]; return sizeof(**x) + 1; }'
assert 9 'int main() { int x[3][4]; return sizeof **x + 1; }'
assert 8 'int main() { int x[3][4]; return sizeof(**x + 1); }'

echo OK

Makefile

https://github.com/rui314/chibicc/blob/30d530831fa2312ed6f2e73520c1525202745d88/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