LLVMチュートリアル(ゼロ)


文章は簡単にLLVM totorialの元のリンクから訳します
このチュートリアルにはC++言語のプログラミングの基礎が必要ですが、コンパイラに関する経験は必要ありません(もっと良いものがあれば)
ここでは、LLVMを使用してコードを生成する具体的な例を示す簡単な言語実装について完全に説明します.
チュートリアルでは、「Kaleidoscope」という簡単な言語を紹介し、次のチュートリアルの章で反復的にこの言語の構築を完了しています.これにより、多くの言語設計とLLVMの技術思想をカバーすることができ、プログラムを構築する過程に従ってプログラムを展示し、実験し、多くの詳細な問題を減らすことができます.
注:このチュートリアルでは、コンパイラテクノロジーとLLVMに焦点を当てるために、ソフトウェアエンジニアリングに関する最適な実装原理はいくつか示されません.たとえば、コードは空を飛ぶグローバル変数を使用し、visitor設計モードを使用しないなどです(これはやはり見なければなりません).
第一章Kaleidoscope言語and Lexer(語法分析器)
この章では、私たちが何をするか、構築する基本的な機能について説明します.通常lexerは言語のparser(解析器)を構築する最初のステップであり、ここでは理解しやすい簡単なC++プログラムで実現されるlexerを使用する.
テキストリンク
このチュートリアルでは、コンパイラの構築プロセスを「Kaleidoscope」というおもちゃの言語で示します.Kaleidoscopeはプロセス言語であり,ユーザはこの言語を用いて関数を定義し,条件判断や数学式などを用いることができる.このチュートリアルでは、if/then/else、forループ、ユーザー定義演算子、コマンドラインを使用したインスタントコンパイル(JIT)、debugなどの機能をサポートできるように、Kaleidoscopeを拡張します.言語を簡単にするために、64ビットの浮動小数点数タイプであるKaleidoscope言語では1つのデータ型しかサポートされていません.これにより、すべての変数が二重精度で表示され、タイプ宣言は必要ありません.これは言語の文法を非常に簡単にします.例えば、次のようにフィボナッチのプログラムを計算します.
def fib(x)
  if x < 3 then
    1
  else
    fib(x-1)+fib(x-2)

# This expression will compute the 40th number.
fib(40)

Kaleidoscope言語は便準ライブラリ関数を呼び出すことができ、LLVMのJITはこの操作を非常に簡単にすることができます.次のようにexternキーを使用して、相互呼び出しおよび再帰的に使用できる関数を定義できます.
extern sin(arg);
extern cos(arg);
extern atan2(arg1, arg2);
atan2(sin(.4), cos(42))

より多くの例は6章で、Mandelbrot Set(マンドブロ集合)を書くなど、小型のKaleidoscopeアプリケーションを書くことができます.
次にKaleidoscope言語の設計段階に入ります.
Laxer
プログラミング言語を実現するには、まずテキストファイルを処理し、何を表現しているのかを識別する必要があります.従来の方法は、「lexer」(scannerとも呼ばれる)を使用して入力データを「token」に分割することです.各tokenがlexerによって返される情報には、token codeおよびいくつかの非表示メタ情報(metadata)が含まれ、例えば、数値の数値)は、まずすべての可能な出現を定義する.
// The lexer returns tokens [0-255] if it is an unknown character, otherwise one
// of these for known things.
enum Token {
     
  tok_eof = -1,

  // commands
  tok_def = -2,
  tok_extern = -3,

  // primary
  tok_identifier = -4,
  tok_number = -5,
};

static std::string IdentifierStr; // Filled in if tok_identifier
static double NumVal;             // Filled in if tok_number

各tokenはlexerによってTokenの列挙値を返すか、未知の文字('+')のascIIコードを返す.現在のtokenが識別子である場合、この識別子文字列のグローバル変数は識別子の名前を保存します.現在のtokenが1.0などの数値である場合、NumValはその値を保存します.実装を簡略化するために、グローバル変数を使用しますが、本当の言語実装プログラムでは、これは良い方法ではありません.
本当にlexerを実行するのはgettokという関数です.この関数は呼び出され、現在の入力情報から次のtokenが返されます.次のように定義します.
/// gettok - Return the next token from standard input.
static int gettok() {
     
  static int LastChar = ' ';

  // Skip any whitespace.
  while (isspace(LastChar))
    LastChar = getchar();

gettok関数は、C言語のgetchar()関数を呼び出して標準の入力から文字を読み出します.これらの文字を識別して記憶し、最後の文字まで読み取るまで、このプロセスは読み取りのみを行い、処理は行わない.まずやるべきことはすべてtokenの間のスペースを取り除きます.この過程は1つのサイクルで解決できる.
次にgettok関数が行うべきことは、表示と特殊なキーワード、例えば「def」を識別します.Kaleidoscope言語は,これらの機能を簡単なループループで実現する.
if (isalpha(LastChar)) {
      // identifier: [a-zA-Z][a-zA-Z0-9]*
  IdentifierStr = LastChar;
  while (isalnum((LastChar = getchar())))
    IdentifierStr += LastChar;

  if (IdentifierStr == "def")
    return tok_def;
  if (IdentifierStr == "extern")
    return tok_extern;
  return tok_identifier;
}

なお、コードでは「IdentifierStr」変数をグローバル変数に設定し、識別子を識別するために使用します.同様に,一つの言語のキーワードも同様にループによってマッチングされ,デジタル変数を識別するのも同様の方法である.
if (isdigit(LastChar) || LastChar == '.') {
        // Number: [0-9.]+
  std::string NumStr;
  do {
     
    NumStr += LastChar;
    LastChar = getchar();
  } while (isdigit(LastChar) || LastChar == '.');

  NumVal = strtod(NumStr.c_str(), 0);
  return tok_number;
}

上記のコードは入力情報を非常に直感的に処理します.数値変数を読み込むと、C言語のstrtod関数を使用して、この数値変数の文字列形式を数値に変換し、NumValに太くします.注意、ここでは十分なエラーチェックが行われておらず、「1.23.56.67」などの不正確な入力情報がある可能性があります.
次に、コメント情報を処理します.
if (LastChar == '#') {
     
  // Comment until end of line.
  do
    LastChar = getchar();
  while (LastChar != EOF && LastChar != '
'
&& LastChar != '\r'); if (LastChar != EOF) return gettok(); }

コメント情報に遭遇したら、ローを直接スキップし、次のtokenに戻ります.最後に、上のいずれかの識別子またはキーワードが一致しておらず、「+」などのオペレータ文字でもなく、ファイルの末尾に読み込まれていない場合は、次のコードで処理します.
  // Check for end of file.  Don't eat the EOF.
  if (LastChar == EOF)
    return tok_eof;

  // Otherwise, just return the character as its ascii value.
  int ThisChar = LastChar;
  LastChar = getchar();
  return ThisChar;
}


上のコードは完全なKaleidoscope言語のlexerで、完全なコードは以下の通りです.
enum Token {
     
  tok_eof = -1,

  // commands
  tok_def = -2,
  tok_extern = -3,

  // primary
  tok_identifier = -4,
  tok_number = -5
};

static std::string IdentifierStr; // Filled in if tok_identifier
static double NumVal;             // Filled in if tok_number

/// gettok - Return the next token from standard input.
static int gettok() {
     
  static int LastChar = ' ';

  // Skip any whitespace.
  while (isspace(LastChar))
    LastChar = getchar();

  if (isalpha(LastChar)) {
      // identifier: [a-zA-Z][a-zA-Z0-9]*
    IdentifierStr = LastChar;
    while (isalnum((LastChar = getchar())))
      IdentifierStr += LastChar;

    if (IdentifierStr == "def")
      return tok_def;
    if (IdentifierStr == "extern")
      return tok_extern;
    return tok_identifier;
  }

  if (isdigit(LastChar) || LastChar == '.') {
      // Number: [0-9.]+
    std::string NumStr;
    do {
     
      NumStr += LastChar;
      LastChar = getchar();
    } while (isdigit(LastChar) || LastChar == '.');

    NumVal = strtod(NumStr.c_str(), nullptr);
    return tok_number;
  }

  if (LastChar == '#') {
     
    // Comment until end of line.
    do
      LastChar = getchar();
    while (LastChar != EOF && LastChar != '
'
&& LastChar != '\r'); if (LastChar != EOF) return gettok(); } // Check for end of file. Don't eat the EOF. if (LastChar == EOF) return tok_eof; // Otherwise, just return the character as its ascii value. int ThisChar = LastChar; LastChar = getchar(); return ThisChar; }

簡単な文字列処理プログラムで、自分で必要なヘッダファイルを付けて、いくつかのテスト用のテキストを入力してこのlexerの効果を試して、どんなバグがあるかを確認することができます(バグが多すぎて、主にlexerの機能を理解するためです)