コンパイラはどのようにしてそれ自体をコンパイルできますか?


168

私はウェブサイトhttp://coffeescript.org/でCoffeeScriptを研究しています、そしてそれはテキストを持っています

CoffeeScriptコンパイラ自体はCoffeeScriptで記述されています

コンパイラはそれ自体をどのようにコンパイルできますか、またはこのステートメントはどういう意味ですか?


14
自分自身をコンパイルできるコンパイラの別の用語はself-hostingコンパイラです。参照してくださいprogrammers.stackexchange.com/q/263651/6221
oɔɯǝɹ

37
コンパイラがそれ自体をコンパイルできないのはなぜですか?
user253751

48
関連するコンパイラのコピーが少なくとも2つあります。既存のものは新しいコピーをコンパイルします。新しいものは古いものと同じかもしれませんし、そうでないかもしれません。
bdsl 2016年

12
Gitにも興味があるかもしれません。そのソースコードはもちろんGitリポジトリで追跡されます。
Greg d'Eon

7
これは、「Xeroxプリンタが回路図を自分自身に印刷する方法を教えてください」と尋ねるようなものです。コンパイラーはテキストをバイトコードにコンパイルします。コンパイラーが使用可能なバイトコードにコンパイルできる場合は、それぞれの言語でコンパイラーコードを記述し、そのコードをコンパイラーに渡して出力を生成できます。
RLH 2016年

回答:


219

コンパイラーの最初のエディションは、それに固有のプログラミング言語から機械で生成することはできません。あなたの混乱は理解できます。より多くの言語機能を備えた新しいバージョンのコンパイラー(ソースが新しい言語の最初のバージョンで書き直されている)は、最初のコンパイラーによってビルドできます。その後、そのバージョンは次のコンパイラーなどをコンパイルします。次に例を示します。

  1. 最初のCoffeeScriptコンパイラーはRubyで作成され、CoffeeScriptのバージョン1を生成します
  2. CSコンパイラのソースコードがCoffeeScriptで書き直されました1
  3. 元のCSコンパイラは、新しいコード(CS 1で記述)をバージョン2のコンパイラにコンパイルします。
  4. コンパイラのソースコードに変更が加えられ、新しい言語機能が追加されます
  5. 2番目のCSコンパイラ(CSで最初に記述されたもの)は、改訂された新しいソースコードをコンパイラのバージョン3にコンパイルします。
  6. 反復ごとに手順4と5を繰り返します。

注:CoffeeScriptのバージョンがどのように番号付けされているか正確にはわかりません。これは単なる例です。

このプロセスは通常、ブートストラップと呼ばれます。ブートストラップコンパイラのもう1つの例はrustcRust言語のコンパイラです。


5
コンパイラーをブートストラップするもう1つの方法は、言語の(サブセット)のインタープリターを作成することです。
アロン

別の言語で書かれたコンパイラーまたはインタープリターでブートストラップするもう1つの方法として、非常に古い方法は、コンパイラーのソースを手動で組み立てることです。Chuck Mooreは、問題指向言語のプログラミングweb.archive.org/web/20160327044521/www.colorforth.com/POLの最後に、第9章「ブートストラップするプログラム」でForthインタプリタに対してこれを行う方法を説明しています。.htm)、以前に手動で2回行ったことに基づいています。ここでのコード入力は、ビットのトグルスイッチによって制御されるメモリアドレスに値を直接格納できるようにするフロントパネルを介して行われます。
ジェレミーW.シャーマン

59

論文で の信頼信託の反省、ケン・トンプソン、Unixのの創始者の一つは、どのようにCコンパイラのコンパイル自体の魅力的な(そして読みやすい)の概要を書き込みます。同様の概念は、CoffeeScriptやその他の言語にも適用できます。

独自のコードをコンパイルするコンパイラのアイデアは、クイン:ソースコードを実行するときに元のソースコードを出力として生成するソースコードに漠然と似ています。 以下は、CoffeeScript quineの一例です。トンプソンは、このC羽の例を挙げました。

char s[] = {
    '\t',
    '0',
    '\n',
    '}',
    ';',
    '\n',
    '\n',
    '/',
    '*',
    '\n',
    … 213 lines omitted …
    0
};

/*
 * The string s is a representation of the body
 * of this program from '0'
 * to the end.
 */

main()
{
    int i;

    printf("char\ts[] = {\n");
    for(i = 0; s[i]; i++)
        printf("\t%d,\n", s[i]);
    printf("%s", s);
}

次に、'\n'ASCIIコード10を表すようなエスケープシーケンスがコンパイラにどのように教えられるのか不思議に思うかもしれません。答えは、Cコンパイラのどこかに、バックスラッシュシーケンスを認識する次のような条件を含む文字リテラルを解釈するルーチンがあるということです。

…
c = next();
if (c != '\\') return c;        /* A normal character */
c = next();
if (c == '\\') return '\\';     /* Two backslashes in the code means one backslash */
if (c == 'r')  return '\r';     /* '\r' is a carriage return */
…

したがって、上のコードに条件を1つ追加できます…

if (c == 'n')  return 10;       /* '\n' is a newline */

'\n'ASCII 10 を表すことを知っているコンパイラを生成する。興味深いことに、そのコンパイラと、それによってコンパイルされた後続のすべてのコンパイラそのマッピングを「知っている」ので、次世代のソースコードでは、最後の行を次のように変更できます。

if (c == 'n')  return '\n';

…そしてそれは正しいことをします!の10コンパイラから来ていない、もはや明示的にコンパイラのソースコードで定義する必要があります。1

これは、Cコードで実装されたC言語機能の1つの例です。ここで、すべての単一言語機能についてそのプロセスを繰り返します。これで、「セルフホスティング」コンパイラー(Cで作成されたCコンパイラー)ができました。


1論文で説明されているプロットのひねりは、コンパイラがこのような事実を「教える」ことができるため、検出が困難な方法でトロイの木馬を実行した実行ファイルを生成することを誤って教えられる可能性があり、そのような妨害行為が持続する可能性があることです。汚染されたコンパイラによって生成されたすべてのコンパイラ。


7
これは興味深い情報ですが、質問の答えになるとは思いません。あなたの例は、あなたがすでにブートストラップされたコンパイラを持っていると仮定します、さもなければ、Cコンパイラはどの言語で書かれていますか?
Arturo TorresSánchez2016年

9
@ArturoTorresSánchezさまざまな説明がさまざまな人に役立ちます。私は他の答えで言われたことを繰り返すことを目指していません。むしろ、私は他の答えが私が考えているよりも高いレベルで話すのを見つけます。私は個人的には、1つの機能がどのように追加されるかを具体的な図で示し、浅い概要ではなく、読者がそれから推定できるようにします。
200_success

5
OK、私はあなたの視点を理解しています。問題は、「コンパイラーをコンパイルするコンパイラーが存在しない場合、コンパイラーはどのようにしてそれ自体をコンパイルできるか」であり、「ブートストラップされたコンパイラーに新しい機能を追加する方法」ではないということだけです。
Arturo TorresSánchez16年

17
質問自体はあいまいで、自由回答です。一部の人々は、「CoffeeScriptコンパイラーはどのようにしてそれ自体をコンパイルできるのか」を意味すると解釈しているようです。コメントで与えられたフリッパーの応答は、「コードをコンパイルするのと同じように、なぜそれ自体をコンパイルできないのか」です。私はそれを「セルフホスティングコンパイラーがどのようにして生まれるか」を意味すると解釈し、コンパイラーが独自の言語機能の1つについてどのように教えることができるかを示しました。実装方法の低レベルの図を提供することにより、別の方法で質問に答えます。
200_success

1
@ArturoTorresSánchez: "[I] n Cコンパイラはどの言語で書かれていますか?" ずっと前に、古いK&R付録(IBM 360用の付録)に記載された元のCコンパイラを維持しました。多くの人は、最初にBCPL、次にB、CがBの改良バージョンであることを知っています。まだBで記述されていて、Cに書き直されていなかった古いコンパイラーの一部。変数は単一の文字/数字の形式であり、ポインター演算は自動的にスケーリングされるとは想定されていませんでした。その古いコードは、 BからCへブートストラップ最初の「C」コンパイラがBに書かれた
Eliyahu Skoczylas

29

あなたはすでに非常に良い答えを得ていますが、私はあなたに啓蒙となる別の視点を提供したいと思います。まず、2人が同意できる2つの事実を確認しましょう。

  1. CoffeeScriptコンパイラは、CoffeeScriptで書かれたプログラムをコンパイルできるプログラムです。
  2. CoffeeScriptコンパイラはCoffeeScriptで書かれたプログラムです。

#1と#2の両方が真であることに同意できると思います。次に、2つのステートメントを見てください。CoffeeScriptコンパイラーがCoffeeScriptコンパイラーをコンパイルできることは完全に正常であることがわかりましたか?

コンパイラは、をコンパイルするかを気にしません。CoffeeScriptで書かれたプログラムであれば、コンパイルできます。そして、CoffeeScriptコンパイラ自体は、たまたまそのようなプログラムです。CoffeeScriptコンパイラーは、それがコンパイルしているCoffeeScriptコンパイラー自体であることを気にしません。表示されるのは、CoffeeScriptコードです。限目。

コンパイラはそれ自体をどのようにコンパイルできますか、またはこのステートメントはどういう意味ですか?

はい、それがまさにその発言の意味です。そして、その発言がどのように真実であるかを今ご覧いただければ幸いです。


2
私はコーヒースクリプトについてはあまり詳しくありませんが、ポイント2はコーヒースクリプトで記述されていたが、コンパイルされてからマシンコードであると説明することで明確にできます。とにかく、鶏と卵の問題について説明していただけますか。コンパイラーがまだコンパイラーが作成されていない言語で作成された場合、コンパイラーはどのように実行またはコンパイルできますか?
barlop

6
あなたの声明2は不完全/不正確で、非常に誤解を招くものです。最初の答えが言うように、最初はコーヒーのスクリプトで書かれていなかったので..それは彼の質問にとても関連しています。そして、「コンパイラーはどのようにしてそれ自体をコンパイルできるのか、またはこのステートメントは何を意味するのか」についてです。あなたは「はい」と言っています(私の心は少し小さいですが)と思いますが、それ自体ではなく、以前のバージョンのコンパイルに使用されているのがわかります。しかし、それ自体をコンパイルするためにも使用されますか?それは無意味だろうと思いました。
barlop

2
@barlop:ステートメント2を「今日、CoffeeScriptコンパイラーはCoffeeScriptで書かれたプログラムです」に変更します。それはあなたがそれをよりよく理解するのに役立ちますか?コンパイラは、入力(コード)を出力(プログラム)に変換するプログラムの「単なる」です。したがって、言語Fooのコンパイラがある場合、Fooコンパイラのソースコードを言語Foo自体で記述し、そのソースを最初のFooコンパイラにフィードすると、2番目のFooコンパイラが出力として得られます。これは多くの言語で行われます(たとえば、私が知っているすべてのCコンパイラはCで書かれています)。
DarkDust 2016年

3
コンパイラはそれ自体をコンパイルできません。出力ファイルは、出力ファイルを生成するコンパイラーと同じインスタンスではありません。そのステートメントがどのように間違っているか、今ご覧いただければ幸いです。
パブラム

3
@pabramsなぜあなたはそれを仮定するのですか?出力は、それを生成するために使用されたコンパイラーとまったく同じである可能性があります。たとえば、GCC 6.1でGCC 6.1をコンパイルすると、GCC 6.1でコンパイルされたバージョンのGCC 6.1が表示されます。そして、それを使用してGCC 6.1をコンパイルする場合、GCC 6.1でコンパイルされたGCC 6.1のバージョンも取得します。これは、(タイムスタンプなどを無視して)同一である必要があります。
user253751 2016年

9

コンパイラはそれ自体をどのようにコンパイルできますか、またはこのステートメントはどういう意味ですか?

それはまさにそれを意味します。まず第一に、考慮すべきいくつかの事柄。確認する必要がある4つのオブジェクトがあります。

  • 任意のCoffeScriptプログラムのソースコード
  • 任意のCoffeScriptプログラムの(生成された)アセンブリ
  • CoffeScriptコンパイラのソースコード
  • CoffeScriptコンパイラーの(生成された)アセンブリ

これで、CoffeScriptコンパイラの生成されたアセンブリ(実行可能ファイル)を使用して任意のCoffeScriptプログラムをコンパイルし、そのプログラムのアセンブリを生成できることは明らかです。

現在、CoffeScriptコンパイラー自体は単なる任意のCoffeScriptプログラムであるため、CoffeScriptコンパイラーでコンパイルできます。

あなたの混乱は、あなたが自分自身の新しい言語を作成するとき、あなたはしていないという事実から生じると思われていコンパイラをまだあなたのコンパイラをコンパイルするために使用することができます。これは確かに鶏卵問題のようですね。

ブートストラップと呼ばれるプロセスを紹介します。

  1. 新しい言語のサブセットをコンパイルできる既存の言語(CoffeScriptの場合、元のコンパイラはRubyで書かれた)でコンパイラを作成します。
  2. 新しい言語自体で、新しい言語のサブセットをコンパイルできるコンパイラを記述します。上記のステップのコンパイラーがコンパイルできる言語機能のみを使用できます。
  3. ステップ1のコンパイラーを使用して、ステップ2のコンパイラーをコンパイルします。これにより、元は新しい言語のサブセットで作成されたアセンブリが残り、新しい言語のサブセットをコンパイルできます。

次に、新しい機能を追加する必要があります。while-loops だけを実装したが、-loopsも必要forだとします。これは問題ではありません。for-loopになるように-loopを書き換えることができるからですwhile。つまりwhile、手元にあるアセンブリでコンパイルできるのは、コンパイラのソースコードでのみ-loopを使用できるということです。ただし、コンパイラー内で関数を作成forして、それを使用してループを配置およびコンパイルできます。次に、既存のアセンブリを使用して、新しいコンパイラバージョンをコンパイルします。これで、forループの解析とコンパイルもできるコンパイラのアセンブリができました!コンパイラのソースファイルに戻り、while不要な-loopsを-loopsに書き換えますfor

必要なすべての言語機能をコンパイラーでコンパイルできるようになるまで、すすぎおよび繰り返します。

whileそして、for明らかに唯一の例でしたが、これはあなたが望む任意の新しい言語機能のために動作します。そして、あなたはCoffeScriptが今ある状況にあります:コンパイラはそれ自身をコンパイルします。

そこにはたくさんの文学があります。信頼についての考察信頼は、そのトピックに関心のあるすべての人が少なくとも一度は読むべき古典です。


5
(「CoffeeScriptコンパイラ自体はCoffeeScriptで書かれています」という文は真ですが、「コンパイラはそれ自体をコンパイルできます」は偽です。)
pabrams

4
いいえ、その通りです。コンパイラそれ自体コンパイルできます。それだけでは意味がありません。言語のバージョンXをコンパイルできる実行可能ファイルがあるとします。バージョンX + 1をコンパイルできるコンパイラーを作成し、使用しているコンパイラー(バージョンX)でコンパイルします。言語のバージョンX + 1をコンパイルできる実行可能ファイルになります。これで、その新しい実行可能ファイルを使用してコンパイラを再コンパイルできます。しかし、何のために?あなたはあなたがしたいことをする実行ファイルをすでに持っています。コンパイラは、コンパイルすることができます任意の有効なプログラムを、それは完全に自分自身をコンパイルすることができます!
Polygnome

1
実際、かなりの回数ビルドすることは前代未聞ではありません。iircmodern freepascalはコンパイラを合計5回ビルドします。
プラグウォッシュ

1
@pabrams「Do not touch」と「Hot object。Do not touch」を書いても、意図したフレーズのメッセージに違いはありません。メッセージの対象読者(プログラマー)が、フレーズの意図したメッセージを理解している限り(コンパイラーのビルドはそのソースをコンパイルできます)、どのように記述されていても、この議論は無意味です。現在のところ、あなたの主張は無効です。メッセージの対象読者がプログラマーではないことを示すことができない場合を除いて、あなたは正しいです。
DarkDestry

2
@pabrams「グッドイングリッシュ」は、意図した聴衆にアイデアを明確に伝え、作家や話者が意図した方法で伝える英語です。対象読者がプログラマーであり、プログラマーがそれを理解している場合は、英語が上手です。「光は粒子と波の両方として存在する」と言うことは、「光は光子と電磁波の両方として存在する」と基本的に同等です。物理学者にとって、それらは文字通り同じことを意味します。つまり、常により長く、より明確な文法を使用する必要があるということですか?番号!対象読者にとって意味がすでに明確であると、読書が複雑になるからです。
DarkDestry

7

小さいが重要な説明

ここで、コンパイラという用語は、2つのファイルが関係しているという事実を無視しています。1つは、CoffeScriptで記述された入力ファイルを取り、その出力ファイルとして別の実行可能ファイル、リンク可能なオブジェクトファイル、または共有ライブラリを生成する実行可能ファイルです。もう1つはCoffeeScriptソースファイルで、CoffeeScriptのコンパイル手順をたまたま説明しているだけです。

最初のファイルを2番目のファイルに適用し、最初のファイルと同じコンパイル動作を実行できる3番目のファイルを生成します(2番目のファイルが最初のファイルによって実装されていない機能を定義している場合はさらに多くの可能性があります)。とても欲望。


4
  1. CoffeeScriptコンパイラは、最初はRubyで作成されました。
  2. CoffeeScriptコンパイラは、CoffeeScriptで書き直されました。

CoffeeScriptコンパイラーのRubyバージョンは既に存在しているため、CoffeeScriptコンパイラーのCoffeeScriptバージョンを作成するために使用されました。

ここに画像の説明を入力してください これは、セルフホスティングコンパイラと呼ばれます。

これは非常に一般的であり、通常、自分の言語を使用してその言語の成長を維持したいという作者の欲求に起因します。


3

ここでのコンパイラの問題ではなく、言語の表現力の問題です。コンパイラは、ある言語で書かれたプログラムにすぎないからです。

「言語が作成/実装されている」とは、実際にはその言語のコンパイラまたはインタープリタが実装されていることを意味します。言語を実装するプログラムを作成できるプログラミング言語があります(同じ言語のコンパイラー/インタープリターです)。これらの言語は、ユニバーサル言語と呼ばれます

これを理解できるようにするために、金属旋盤について考えてみましょう。金属を成形するための道具です。そのツールだけを使用して、パーツを作成することにより、別の同じツールを作成することができます。したがって、そのツールはユニバーサルマシンです。もちろん、最初の方法は他の手段(他のツール)を使用して作成されたため、おそらく品質が低くなっていました。しかし、最初のものは、より高い精度で新しいものを構築するために使用されました。

3Dプリンターはほとんど普遍的な機械です。3Dプリンターを使用して、3Dプリンター全体を印刷できます(プラスチックを溶かすチップを作成することはできません)。


旋盤のアナロジーが好きです。ただし、旋盤のアナロジーとは異なり、コンパイラーの最初の反復における欠陥は、後続のすべてのコンパイラーに渡されます。たとえば、上記の回答は、元のコンパイラがwhileループのみを使用するforループ機能の追加について言及しています。出力はforループを理解しますが、実装はwhileループで行われます。元のwhileループの実装に欠陥があるか非効率的である場合は、常にそうなります。

単に間違っている@ Physics-Compute。悪意がない場合、コンパイラをコンパイルするときに、通常、欠陥は伝播しません。
プラグウォッシュ2016年

アセンブリの変換は、アセンブリの変換が修正されるまで、繰り返し実行されます。古い機能を基にした新しい機能は、基礎となる実装を変更しません。しばらく考えてみてください。

@plugwash Ken Thompsonによる「信頼を信頼する上での反射」を参照してください-ece.cmu.edu/~ganger/712.fall02/papers/p761-thompson.pdf

3

誘導による証明

誘導ステップ

コンパイラのn + 1番目のバージョンはXで書かれています。

したがって、n番目のバージョンのコンパイラー(Xでも記述)でコンパイルできます。

規範事例

ただし、Xで書かれたコンパイラの最初のバージョンは、X以外の言語で書かれたXのコンパイラでコンパイルする必要があります。この手順は、コンパイラのブートストラップと呼ばれます。


1
言語Xの最初のコンパイラコンパイラは、Xで簡単に記述できます。これが可能なのは、この最初のコンパイラを解釈できるからです。(X以外の言語で書かれたXインタープリターによる)。
Kaz

0

コンパイラーは高レベルの仕様を取り、それをハードウェアで実行できるような低レベルの実装に変えます。したがって、対象となる言語のセマンティクス以外に、仕様の形式と実際の実行との間には関係はありません。

クロスコンパイラーは、あるシステムから別のシステムに移動し、クロス言語コンパイラーは、ある言語仕様を別の言語仕様にコンパイルします。

基本的にコンパイルは単なる翻訳であり、レベルは通常、言語の上位レベルから下位レベルまでですが、多くのバリエーションがあります。

もちろん、ブートストラップコンパイラは、記述された言語をコンパイルするため、最も混乱を招きます。実行可能な最低限の既存のバージョンを必要とするブートストラップの最初のステップを忘れないでください。多くのブートストラップコンパイラは、最初にプログラミング言語の最小限の機能を処理し、前の機能を使用して新しい機能を表現できる限り、今後さらに複雑な言語機能を追加します。そうでない場合は、「コンパイラ」のその部分を別の言語で事前に開発しておく必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.