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

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

今回作成するコンパイラ

加減算のできるコンパイラの作成(「加減算を含む式」を入力から読んで「その加減算を実行時に行うアセンブリ」を出力するコンパイラを作成する)

コンパイラソースコード

main.c
https://github.com/rui314/chibicc/commit/afc9e8f05faddf051aa3a578520d6484ab451282#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR10
https://github.com/rui314/chibicc/blob/afc9e8f05faddf051aa3a578520d6484ab451282/main.c#L10

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr, "%s: invalid number of arguments\n", argv[0]);
    return 1;
  }

  char *p = argv[1];

  printf(".intel_syntax noprefix\n");
  printf(".global main\n");
  printf("main:\n");
  printf("  mov rax, %ld\n", strtol(p, &p, 10));

  while (*p) {
    if (*p == '+') {
      p++;
      printf("  add rax, %ld\n", strtol(p, &p, 10));
      continue;
    }

    if (*p == '-') {
      p++;
      printf("  sub rax, %ld\n", strtol(p, &p, 10));
      continue;
    }

    fprintf(stderr, "unexpected character: '%c'\n", *p);
    return 1;
  }

  printf("  ret\n");
  return 0;
}

コマンドライン引数の個数をチェックする(変更なし)

  if (argc != 2) {
    fprintf(stderr, "%s: invalid number of arguments\n", argv[0]);
    return 1;
  }

アセンブリコードを生成する

  char *p = argv[1];

  printf(".intel_syntax noprefix\n");
  printf(".global main\n");
  printf("main:\n");
  printf("  mov rax, %ld\n", strtol(p, &p, 10));

  while (*p) {
    if (*p == '+') {
      p++;
      printf("  add rax, %ld\n", strtol(p, &p, 10));
      continue;
    }

    if (*p == '-') {
      p++;
      printf("  sub rax, %ld\n", strtol(p, &p, 10));
      continue;
    }

    fprintf(stderr, "unexpected character: '%c'\n", *p);
    return 1;
  }

  printf("  ret\n");
  return 0;


strtol関数について
strtol関数は、第一引数で指定された文字列の先頭部分にある数字を、第三引数で指定された基数で、取得します。
第二引数のアドレスが指定するポインタには、第一引数で指定された文字列の先頭部分にある数字の次にある文字を指定するアドレスが格納されます。

char *p = "5+20-4";
strtol(p, &p, 10));

例えば上記のようなプログラムの場合、第一引数pで指定された文字列"5+20-4"の先頭部分にある数字"5"を、第三引数で指定された基数(10進数)で、取得します(strtol関数の戻り値が5になります)。
第二引数のアドレス&pが指定するポインタpには、第一引数で指定された文字列"5+20-4"の先頭部分にある数字"5"の次にある文字"+"を指定するアドレスが格納されます。

コンパイラが出力するアセンブリコード

「5+20-4」を入力から読んだ場合

.intel_syntax noprefix
.globl main

main:
        mov rax, 5
        add rax, 20
        sub rax, 4
        ret

テストコード

test.sh
https://github.com/rui314/chibicc/commit/afc9e8f05faddf051aa3a578520d6484ab451282#diff-3722d9ba8feb2d3feac8ce71a209a638d4b404e1c53f937188761181594023e2R21
https://github.com/rui314/chibicc/blob/afc9e8f05faddf051aa3a578520d6484ab451282/test.sh#L21

#!/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 0
assert 42 42
assert 21 '5+20-4'

echo OK

Makefile

https://github.com/rui314/chibicc/blob/afc9e8f05faddf051aa3a578520d6484ab451282/Makefile

CFLAGS=-std=c11 -g -static

chibicc: main.o
	$(CC) -o $@ $? $(LDFLAGS)

test: chibicc
	./test.sh

clean:
	rm -f chibicc *.o *~ tmp*

.PHONY: test clean