
みなさん、セキュリティ・キャンプ 2015応募しましたか?
え?年齢制限に達してて応募できない?なんて人も、今年の応募用紙は結構興味があるのではないでしょうか。
http://www.ipa.go.jp/jinzai/camp/2015/zenkoku2015_sheet.html
ところで、僕も今年はセキュリティ・キャンプの講師をやらせて頂くことになったので、実は応募用紙の問題に少し関わっていたりしました。
セキュリティキャンプでは、自作 CPU の開発と、その開発環境 (コンパイラ、アセンブラ) の移植開発に関する講義をやらせて頂きます。
全部話していると時間が足りないので、基本的なところを凝縮して限られた時間の中で伝えることが出来るような講義と開発時間にしようと思っています。
選択問題5はみんな解いてくれましたか?
まあ見たらなんとなくわかるかも知れませんが、これは僕が作問しました。
他の手を動かす系の問題から比べるとかなり簡単だった?と思いますが、解いてくれた人も結構多くて興味深く見させて頂きました。
初めてバイナリやアセンブラに触れた人もいたみたいで、それでこの問題を解いてくれるのかと、驚きでした。ぜひこれからも続けてバイナリ読めるようになって下さい!
ところで、問題の意味や答えが知りたい方もいると思いますし、自由記述の任意選択問題は面白い回答も結構あったので、少し個人的に問題の振り返りを書いてみたいと思います。
ちなみに、完全に正しい答えというのはありませんし、少なくとも僕は皆さんの応募用紙を問題が合っているか合っていないかだけでは評価していませんので、間違っていても安心して下さいね。
なんだかんだで、めちゃくちゃ長いです。
書いている途中で、ちょっとだけ書き始めたことに後悔しました。
もし問題を全部解いてくれたけど、全部見るの大変だ!!という方は、最後の設問5のフィードバックだけ目を通してみて下さい!!
選択問題5の概要
選択問題5は、以下のような問題です。設問1
上記の C 言語のプログラムはどのような動作をしますか。また、この関数を呼び出して利用する main 関数の例を作成してください。正直、このプログラムに意味はありません。書いてあるそのままです。
なので、何かしら利用して使ってくれるものがあれば問題ないです。
ポインタを利用しているので、C のポインタが苦手だなーという人は、この問題を解いてくれないかな、とか思っていました。
function() に確保した配列と要素数を投げて、printf している人が多かったです。
実際に確保されている要素数と n の数が違う場合に問題がある、など指摘してくれた人はとても良い指摘です。
ただ、メモリのポインタだけでは要素数を知ることは不可能なので、まあ C の関数ってだいたい要素数やサイズも一緒に投げるようになっていますよね?という話もあります。
設問2
上記のアセンブリコードを、いくつかのブロックに分割して、おおまかに何をしている部分かを説明してください。提示している objdump の結果は、以下のように生成した結果です。
gcc のバージョンは gcc-5 (Debian 5.1.1-11) 5.1.1 20150616 で、Linux でコンパイルされたものです。
$ gcc -m32 -c -O1 test.c
$ objdump -d test.o
まず、注目すべき点は、このコンパイル結果は最適化されているんですよね。
どうも、最適化されたバイナリを読むのは嫌だ!っていう人もいて、-O0 でコンパイルした Intel Syntax のバイナリを貼り付けてきた人もいくつかありましたね...
例えば、全く最適化をかけないとどうなるか、以下のようなコンパイル結果になります。
https://gist.github.com/techno/5d79de4b4fb6aa8daacf
最適化されていると、なるべくレジスタ内で処理を完結しようと思うので、シンプルなバイナリになって読みやすいと思うのですが、最適化をかけないと変数を毎回スタックへ書き戻そうとします。
デバッグをするときは良いのですが、普段読むのは最適化されたバイナリがほとんどなので、最適化されたバイナリも読めるようになりましょう!
最適化されたバイナリを読む上で、コンパイラの挙動や癖を知っておくと、非常に読みやすいよというのも、この問題の意図のひとつであったりします。
ちなみに、Intel 以外の CPU の objdump 結果を貼り付けて来た猛者は誰もいませんでした...
で、本題のこの objdump の結果を読み解いていきますか。
ただ、コンパイラの最適化というのは、最終的に計算された結果が同じであれば、なるべく少ないコストで CPU が処理できるようにアセンブラの内容を書き換えてしまうのです。
場合によっては、全然違うコードが生成されることもあります。
よって、この命令がどこの C 言語の行に対応するかというのは、厳密に表すことは出来ませんし、C のソースコードの流れとは異なるフローになることもあります。でも、癖はあるので、それがわかると楽だったりします。
0 - 2:
0: 56 push %esi
1: 53 push %ebx
呼出規約に則って、関数内で破壊される esi, ebx レジスタを前もって退避します。
なぜ、eax や ecx, edx も関数内で破壊されるのに、esi と ebx だけを退避してるかというと、そういう決まりだからです。
eax, ecx, edx は、呼出先関数内で破壊しても問題ないのです。逆に、それ以外のレジスタのデータは元に戻さなければいけません。なので退避しています。
詳しくは、cdecl とか、呼出規約とかで検索してみて下さい。
このような、レジスタの退避や、スタックポインタの移動などを行う部分を、通常プロローグと言ったりします。
2 - 6:
2: 8b 5c 24 0c mov 0xc(%esp),%ebx
6: 8b 4c 24 10 mov 0x10(%esp),%ecx
引数を、レジスタに持って来ます。
引数は、スタックに積まれています。これも呼出規約で決まっています。
AT&T Syntax では、レジスタに () 付いている場合、メモリアクセスです。
アドレッシングの記法は様々なものがあるので、検索して調べてみてください。
ここでは (esp + 0xc) の番地に array が、(esp + 0x10) の番地に n が入っています。
なぜ、0xc かというと、上で2つレジスタをスタックに詰んでいるので、esp はこの関数に入った時点より既に 8 増えています。
さらに、その前に関数の戻り先番地が積んであるので、4 足します。よって、8 + 4 = 0xc (12) です。
a - c:
a: 85 c9 test %ecx,%ecx
c: 7e 18 jle 26
この部分では、ecx に格納されている n の値が 0 以下かどうかチェックしています。
最適化によってプログラムの流れを書き換えているので、この部分で躓いた人もいたようです。
test 命令は、オペランド同士の and を取って、その結果を捨ててフラグだけを、フラグレジスタへ反映させる命令です。
jle 命令は、Jump if Less or Equal みたいな意味の命令ですが、この意味は cmp が生成したフラグの時に意味があるので、test では意味合いが変わってきます。
Intel x86 JUMP quick reference
jle 命令が見るのは、ZF (Zero Flag), SF (Sign Flag), OF (Overflow Flag) の3つのフラグです。
jle がジャンプする条件というのは内部的に、(ZF || (SF != OF)) となっています。
TEST の場合、ZF には結果が 0 であれば 1、SF は結果の符号(最上位)ビットが格納され、OF は 0 にクリアされます。
よって、ジャンプするのは ZF か SF が立っている時となるので、n が 0 または 0 未満 (負数) の場合となります。
つまり、配列の要素数を表す n が 0 以下であれば、26 のエピローグにジャンプするので、ほぼ何も処理をしないで関数を脱出します。
なぜ cmp 命令を使わないかというと、test のほうが少ないバイト数で同じことを表現できるからです。この方が効率が良いのです。
e - 15:
e: 89 ce mov %ecx,%esi
10: ba 00 00 00 00 mov $0x0,%edx
15: b8 00 00 00 00 mov $0x0,%eax
この部分は、実際にループの処理に入る前のレジスタの初期化です。
レジスタと変数を1対1で対応させがちですが、最適化がかけられている場合には、基本的にそんなに対応は取れていません。
ループで初期化が行われそうな変数は、i のみですが、アセンブラを見るとそうではないですね。
ecx は、実質 n ですが、ここで esi にも n をコピーしてます。
edx は、とりあえずここではなぞのレジスタということにしておきましょう。
eax も、まだ、なぞのレジスタです。
1a:
1a: 89 14 83 mov %edx,(%ebx,%eax,4)
またメモリアクセスが出てきました。しかも今回は複雑です。
上で、初期化したなぞのレジスタが2つも出てきています。
まず、ここでのメモリアクセスのアドレスオペランドの読み方ですが
(%ebx, %eax, 4) => %ebx + (%eax * 4)
ということです。
なので、ebx が array のポインタなので、そこから eax * 4 をオフセットしたアドレスのメモリにストアしていることが解ります。
4 は、int 型のデータ1つが4バイトということなので、eax はどうも配列の添字っぽいですね。つまり i です。
さて、ストアするデータは、edx ですが、array にストアしていることから、どうも i * n のような気がします。
ただ、ここまで掛け算は一度も出てきていませんし、まだよくわかりません。
1d - 20:
1d: 83 c0 01 add $0x1,%eax
20: 01 f2 add %esi,%edx
次のループへ行くための処理です。
i に相当する eax をインクリメントしています。これは簡単。
ただ、次の edx に esi を足している処理がなぞです。
edx は、i * n っぽいのは上の命令で解りましたが、これは何をしているのでしょうか。
esi は、n です。よって、ループで毎回 n を足しています。
実は、掛け算ではありませんが、この命令が実質 i * n に相当する部分です。なぜなら、ループ毎に毎回 n を足していけば、実質 i * n が生成できるからです。
何故掛け算を使わないかというと、CPU 内部での処理は掛け算より足し算のほうが早いことが多いです。
コンパイラの最適化は、同じ結果が導き出せるのであれば、どんな方法でも利用して CPU にとって最適な命令を出力させようとするのです。
この問題の肝は、この掛け算が足し算に変換されている処理にあったりします。
22 - 24:
22: 39 c1 cmp %eax,%ecx
24: 75 f4 jne 1a
ループの最後に、i (eax) と n (ecx) を比較してます。
jne は Jump if Not Equal なので、i != n の時に 1a の処理に戻って、ループを継続します。
つまり、最適化によってこのプログラムは do { } while(); のようになっているんですね。
0 の時だけ、test 命令で例外的に処理していると。
26 - 28:
26: 5b pop %ebx
27: 5e pop %esi
28: c3 ret
関数のエピローグです。
一番最初に呼出規約で退避したレジスタを、元に戻しています。
また、ret 命令でスタックの一番上に積んである戻り先アドレスへジャンプして、呼び出し元の関数へ戻ります。
この関数は返り値はないので、返り値を格納するべき eax レジスタは何も意味をなしません。
設問3
コンパイラがソースコードの関数を解釈して、ターゲットのアーキテクチャのバイナリを生成するまで、どのように内部で処理を行っていると思いますか。3 はコンパイラの事についてですが、これは結構書いてくれた方が多かったです。
特に、フロントエンド部分の字句解析や構文解析、構文木の構成などについて書いてくれた方が多かったですね。
自分でコンパイラを作った事がある人は、この辺りは専門分野でしょうか。
具体的に、LLVM の実装を取り上げてくれた方もいて、とても興味深く読ませて頂きました。
対して、最適化や、バックエンド部分の実際のアセンブラの出力については、ほとんど詳しく書いてくれた方がいませんでした。ちょっとだけ、悲しいです。
ここで、少し入れ知恵をすると、gcc は以下のような魔法のコマンドを打つと、内部のコンパイルの過程をほとんど全部ダンプすることが出来ます。
$ gcc -fdump-tree-all -fdump-rtl-all -c -O1 test.c
(大量のファイルが生成されるので注意)
GCC は、C のファイルをパースして AST (抽象構文木) を生成した後、GIMPLE という形式に変換し、更にそれを RTL (Register Translation Language) に変換します。
最終的に、バックエンド部分が RTL をターゲットのアセンブリに変換して、アセンブラへ投げます。RTL は、みんな大好き Lisp の S 式風で表現されています。
GIMPLE と RTL の例 (function の結果)
最適化は、GIMPLE ステージの中で行われますが、RTL のステージでも行われてます。ターゲット固有の最適化は、RTL ステージのみで行われるはずです。
また、基本的に GIMPLE のステージはターゲットに非依存で、RTL へ拡張する部分から先がターゲット依存のコードになっています。
具体的に、GIMPLE や RTL の中身を解説すると切りがないので省略しますが、上記のコマンドを打った時に出てくる test.c.000t.xxx というのが GIMPLE で、test.c.000r.xxx というのが RTL です。いくつか、中身を見てみて下さい。
今回の掛け算が足し算に変換される最適化は、GIMPLE のステージで最適化が行われていく様子がひとつひとつ見ていくと、解ると思います。
GIMPLE はかなり、C に近い表現で、途中で SSA (Static Single Assignment) による最適化や CFG (Control Flow Graph) の生成を行ったりします。
RTL では最初の段階では擬似レジスタに割り当てが行われて、実際のレジスタのアロケーションが呼出規約などに基づいて行われます。
最後に、プロローグとエピローグの生成などが行われて、アセンブラが出力されます。
こんな資料を見つけたので、GCC の中に興味がある人はこれをどうぞ
http://www.cse.iitb.ac.in/~uday/courses/cs715-09/gcc-gimple.pdf
LLVM を使ったコンパイラは、LLVM-IR という中間表現を使っていますが、大体これが RTL に対応するといいと思いますが、RTL はターゲットに適切な RTL へ変換していく工程があるので、全ターゲットで共通の LLVM-IR とはかなり違います。
設問4
CPU の内部では、プログラムのバイナリはどのように解釈され実行されていると思いますか。そろそろ長くなってきましたね... でも、問題はまだまだあります。
設問4は、CPU のお話です。
この問題も、キーワードを用意したおかげか、みなさんいろいろと調べて書いてきてくれてありがとうございます。
わかったことは、Wikipedia みんなが大好きだということです。
というのは置いておいて、皆さん基本的なパイプラインの仕組みやそれぞれのステージの役割は、よく書けていたと思います。
ただ、現代の CPU は Wikipedia に書いてあるほどシンプルかつ簡潔ではありません。
https://ja.wikipedia.org/wiki/命令パイプライン
こういう問題を出す側からすると、みんなわかってることを書いてもらっても、おおっとなりづらいのですが、最新の動向とか、具体的な実装の話をすると読んでる側も食いつきやすく、自然に読んでるだけでニヤニヤしてくるので、参考にしましょう。
語り始めるとすごいことになってしまうので、なるべく簡潔に説明しますが、最近の CPU は Wikipedia の項目の複雑化にある通り、非常に複雑化しています。
例えば、デコーダーについて
皆さんが、きっと今も使っている Intel の CPU は、実際のアセンブラがそのまま命令として実行されているわけではありません。
CISC と呼ばれる x86 の命令セットも、内部ではかなり RISC 的な μOps と呼ばれる命令に変換されて、実行されています。
つなり、内部では1つのアセンブラの命令が複数命令になったり、2つのアセンブラ命令の組み合わせが1命令に組み合わされたり (Macro-Ops Fusion) しています。
なので、皆さんが書いてる x86 のアセンブラは、実は CPU から見ると抽象化されたものだったりするのですよね。
もちろん、これは x86 に限った話で、uOps を使ってるのは歴史的理由が大きいです。
なので、変換するという行為は完全に無駄なのですが、それでも Intel の CPU は他社製 CPU に比べて見劣りしないどころか、それを超える性能を出しています。
これは正直いってすごいことです。
また、その変換の無駄を最小限にするために、uOps 変換済みの命令キャッシュを搭載したり、最近の CPU でもこのデコーダ周りの進化はいろいろ行われています。
実行ステージ
命令が実際に実行されるのは、実行ステージなわけですが、実行ステージのことをキーワードに書かなかったせいか、あまり詳しく書いてくれる人いなかったですね。
オペコードが足し算なら、足し算の回路にオペランドを流して、結果をレジスタにライトバックするんでしょ?ぐらいに思っているかも知れません。
何度も言いますが、最近の CPU はみなさんが思い描いているほど単純ではありません。
例えば、最近の Intel の CPU や ARM Cortex-A9 以降のシリーズは、CPU 内部ではアセンブラは書いてある順番では実行されません。
命令の順番を並び替えて、実行できるものからどんどん実行していく、しかも並列に、ということが行われています。(アウトオブオーダー実行)
また、実際のオペランドに書いているレジスタ、実はこのレジスタも内部に eax レジスタというものが実際に存在するわけではありません。(レジスタリネーミング)
レジスタリネーミングが行われている CPU であれば、あの名前はただの同じレジスタを指す仮想的な名前でしかないのです。
なので、依存関係がない命令同士であれば、同じ eax レジスタのオペランドでも、内部は違うレジスタが使われる可能性があります。
最近の Intel CPU の中で、Haswell と言われるものの実行ステージがどうなっているかというと、8個の実行ポートがあって、8命令同時実行できます。
http://www.anandtech.com/show/6355/intels-haswell-architecture/8
一般的に知っていそうな言葉で言うと、スーパースカラーっぽいですが、スーパースカラーという言葉で単純に説明できるほど、現代の CPU は単純なものじゃないのでやめておきます...
ただ、足し算が8命令同時実行出来るかというと、そうではなくて、実行ポートそれぞれに出来ることは決まっていて、足し算は Haswell でも4命令でしか同時に実行できません。
他には、ブランチ (ジャンプ命令) や、メモリのロード・ストアを行うような実行ユニットがあります。
ブランチ命令というのは、パイプラインが使われている CPU だと、一度パイプラインをフラッシュしなければいけないという問題があるのですが、現代の CPU では分岐予測というのをしていて、かなりの確率で分岐する方向を予測して当てるような仕組みがついています。
ここも最近の CPU ではいろいろ改良されているところになっています。
設問4 まとめ
現代の CPU は訳がわからないぐらい複雑な仕組みが、ハードウェアで実行されていることが、少し解りましたか?
もし、興味があればこんな面白いスライド(?)があるので見てみて下さい
http://www.slideshare.net/hktechno/vm1-36900813
ちなみに、問題4に関連するして、コンパイラはアセンブラの命令数が少なくなるということだけではなく、どのような uOp に変換されるかだとか、ここではどの実行ポートが開いてそうだとか、CPU にあわせた最適化では、そういうことも考慮する必要があります。
そして、更に CPU 内部でハードウェア的に命令の並び替えや最適化をやるのはもったいないから、実行ポートなどにあわせた最適化をコンパイラ側でやってしまえ、というのが VLIW (Very Long Instruction Word) という CPU の手法になったりするわけなんですが、これでうまくいった製品というのは、なかなかないのも面白いところです。
設問5
現在の CPU やコンパイラの不満点があれば自由に記述してください。設問5は、とても面白かったです。
自由に書いてくれた人が多く、読んでいて非常に興味深かったです。
なので、問題を答えてくれた皆さんに、フィードバックとして伝えたいことがたくさんあります。
多分、ほとんど全部の不満に答えられていると思うのですが、納得行かないのであれば @hktechno の Twitter やコメント欄からどうぞ...w
コンパイラに対する不満
- Segmentation Fault の一言で、エラーを片付けないで欲しい。エラーの原因を、もっと詳細に表示するべき。
- 個人的には、Segmentation Fault ほど原因が特定しやすく、わかりやすいエラーはないと思います。
- ターミナルに表示されるのは、Segmentation Fault のみですが、実は Segmentation Fault した場合には、簡単にどこでエラーが発生しているかという情報は簡単に特定できます。
- なぜなら、gdb をかければ、一発でどこでエラーが起きたか、どのメモリアクセスでエラーが発生したか特定することができるからです。
- もちろん、それを表示するべきだという意見はありますが、それはコンパイラではなくて OS やシェルの仕事だと思います。
- Intel Syntax と AT&T Syntax の両立は混乱させるだけなので、統一しろ!
- 歴史的理由もあるので、難しいところですね。
- ですが、もしどちらかに統一しましょうという話になると、AT&T Syntax になると思います。
- なんだかんだで AT&T Syntax のほうが多く使われている気がしますね... 例えば、よくある OSS の中で使われているインラインアセンブラは普通 AT&T Syntax です。両方読めて損はないです。
- SIMD をもっと有効的に使って欲しい、intrinsics が長くて覚えられない
- コンパイラの SIMD 関連の Intrinsics は確かに覚えづらいです。
- しかし、そのような事をしなくても、最近の Clang や GCC であれば、ベクタライズに関連する Language Extension を使うと、簡単にベクタライズ出来ます。 しかも、アーキテクチャに依存しません。
- また、OpenCL という手もあります。
- 大規模データ処理では、複数コアを利用した並列化が大変
- 最近の GCC で、OpenMP の一部や CilkPlus がサポートされました。
- Intel のコンパイラでは以前から使えますが、これらの機能を利用すると比較的簡単にマルチコアで並列化させる処理を書くことが出来ます。
- コンパイル時間が長い
- GCC に比べると、Clang はコンパイル時間が比較的短い傾向にあります。 お試し下さい。
- コンパイラが、新しい拡張命令セット (SSE3, SSE4 など) を使用しない事がある。デフォルトで使うべきだ!
- 過去の CPU との互換性を保つためには、デフォルトで利用するのは危険です。
- コンパイルしたホストでしか実行しないのであれば、-march=native 的なオプションがあると思うので、これを使うと良いと思います。
- Clang の最適化済みバイナリが複雑で、バイナリアンとしては苦痛
- Clang だから、最適化が鬼のように効いていて見づらい、という意見だと思うのですが、僕の感覚だと gcc と clang の比較では、吐いたバイナリの実行時間の差はそれほどないか、clang の方が遅いように感じています。
- コンパイラは、人間の気持ちではなく、CPU の気持ちになってバイナリを吐いています。最適化には癖があるので、CPU の気持ちを理解しながら、沢山バイナリを読んで覚えましょう。
- gcc セキュリティ上重要なオプションは、デフォルトで有効にするべき
- SSP などはたしかに有効にするべきだとは思いますが、そのような機能は実行時間やメモリ使用量など、何らかのオーバーヘッドが伴ったりもするので、難しいところですね。
- コンパイラは strcpy, strcat ではなく strncpy, strncat を使うように強制させろ!
- gets() は C11 で消え去ったりしたので、GCC 5.1 以降では -std=gnu11 がデフォルトで使われることもあり、実質使えなくなったりもしました。
- が、strcpy() や strcat() はそれ以上に広範に使われていることもあり、難しいところでしょうね。警告ぐらいはでても良い気はします。
- 現在のコンパイラは、基本的なバグの元を検出できない場合が多い。例えば、この問題の function には n が array の要素数を超えている場合に明らかなバグが発生するにもかかわらず、コンパイルできてしまう
- この function にバグがあるかどうかという問題については、議論の余地があると思います。
- なぜながら、この関数単体だけ見た時に、array のポインタだけでは言語仕様上要素数を判別することは出来ないので、どうしても n が必要になります。また、n を信用せざるを得ないのです。
- バグの元を検出できないことが多いというのは、理解できます。もしなにかアイデアがあれば、gcc や clang など著名なコンパイラへパッチを投げることも検討してみて下さい。
- ソフトウェアを、ソースからビルドするという行為がもっとカジュアルに行われてもいい。そうなると、ポータブルなコンパイラが必要。
- Gentoo 方面からのお客様ですか?
- 確かに、そのような時代が来るとなれば、もっとコンパクトでポータブルなコンパイラが必要でしょう。
- しかし、そうなるとコンパイラの性能をある程度犠牲にする事にもなりますし、それであればポータブルなコンパイラでコンパイルするより、コンパイル済みバイナリを取り寄せたほうが良いとなったりする気もします。
- 現在のコンパイラの多くは、基本的に内部がブラックボックスであり、構文解析や意味解析の結果は コンパイル・バイナリの出力とともに消去されてしまう。Roslyn のようにエディタでもそれを利用できるようにするべきだ。
- Roslyn については詳しく知りませんが、内部的な情報を引っ張りだすライブラリはあるのではないでしょうか。またブラックボックではありません。
- また、バイナリの中にもデバッグ情報の DWARF の中には、一部の解析情報を残していたりもします。
- もしかしたら、現在の gcc や clang にも、コンパイラ内部の情報を引き出せるようなものを作る・または利用すれば、エディタ側でもそれを利用することは出来そうですね。
- バッファオーバーフローをハードウェアで回避出来るような仕組みがあるといい
- Intel MPX と言われる機能が、Skylake から登場します!
- CPU が発熱がすごい、ベンチマーク回すとすごい熱くなるよ!
- 例えば、数世代前の性能で良いのであれば、今の技術であれば低発熱・省電力の CPU を作ることが出来ます。例えば Atom というシリーズはそういう方向で作られてますね。
- ただ、せっかく CPU を作るなら、壊れない限界まで回したほうが処理性能は上がりますよね?なので、ベンチマークを回したりすると、数世代前と変わらないような発熱になるのは当然です。
- それと、近年はただクロック数を上げるだけではなく、1クロックで実行できる命令数を稼ぐ方向に向いているので、クロック数の比較は同世代の CPU 同士では意味があるかも知れませんが、世代が違う CPU のクロック数を比較するのは全く意味のない数字になっています。
- CISC が多く残っているので、RISC のみになってほしい
- 究極の CISC を目指していた日本製チップをご存知でしょうか...
- CISC には、「命令の直交性が高い」と表現されるような、オペランドの自由度が高い特徴があるので、手で書くにはかなり良い特徴であったりもします。
- これらの特徴を知るには、PDP-11 や VAX といった、昔々のアーキテクチャのアセンブラを体験してみると、感心出来るかも知れません。
- 前述したように、Intel の CPU はどうせ中身は RISC だったりもします。
- uOp を直接扱いたい気分もありますが、今の x86 と一緒で、一度決めて公開してしまうと、未来永劫いくらクソであろうが変更ができなくなってしまうので、Intel さんとしては心の中に留めておきたいのでしょう。実際、uOp は世代によってかなり違うみたいです。
- amd64 は複雑化しすぎている
- 歴史的産物なので、しょうがないです。
- Itanium という短命でお亡くなりになった、VLIW プロセッサの墓前で、お祈りを捧げると、気を紛らわせることが出来ると思います。
- AMD と Microsoft に文句を言うのはやめてあげて下さい。
- x86 はリトルエンディアンだから、ネットワークバイトオーダーと逆であり、オーバーヘッドが生じる
- と思われるかも知れませんが、誠に残念ながら、ビッグエンディアンは現在の所 CPU においては着実に死へ向かっております。
- ARM も、POWER も、リトルエンディアンを使うようになってきています。
- リトルエンディアンには、人間に優しくなくても、コンピュータに優しい点がいくつかあるのです。特に、様々なワード長を扱ったりする場合に顕著です。
- 汎用レジスタをたくさん用意して欲しい
- 確かに、x86 の汎用レジスタは少ないですが、これは歴史的な理由だと思います。
- amd64 になって、レジスタはかなり増えました。また、実際に見えてるレジスタが増えなくても、リネーミングで裏にあるレジスタはもっと大量にあるはずなので、速度としてはそれほど影響がないんですよね... (たぶん)
- ちなみに、僕達が作っている MIST32 プロセッサの汎用レジスタは32本あります!
- プロテクトモード、リアルモードの切り替えや CPU の基礎を学ぼうとしても、現代 CPU に関するリファレンスがないので、日本語本を執筆して欲しい
- あります!!おすすめの本は、初めて読む486です。
- ただ、amd64 について解説している本は、なかなかないです。Intel のリファレンスは日本語の本もあるので、これを参考にするしか...
- ムーアの法則が限界を迎えているぞ!!
- はい、単純に待っているだけでは性能は上がらない時代になってしまいました。
- ただ、性能の向上という面で見ると、確かにクロック数の向上は頭打ちになっていますが、実際の性能はかなり向上しています。
- また、Intel も自分自身が提唱したムーアの法則に置いていかれないようにいろいろ頑張っているみたいです。
- マイクロプログラミングなどの衰退するであろう知識を覚えるのには抵抗がある
- 衰退するでしょうか?それは誰にもわかりませんが、基礎的な知識は覚えても無駄になることは少ないと思います。
- それに、マイクロプログラミングとは、マイクロコードのことだと思いますが、場合によっては RISC でも使われます。
- 僕達が作っている、MIST32 はマイクロコードを実装していますし、x86 も内部で利用しています。
- エラッタを直すのは、マイクロコードが最後の手段ですし、複雑な回路をハードで実装する必要がなくなるので、なかなか便利なやつです。
- 関数型言語ををのまま CPU が解釈することができないので、結局手続き型になる。関数型言語の命令を解釈できる CPU があれば面白い。
- LISP マシンをご存知でしょうか...
- 日本でも、一部の機関が LISP マシンを作っていた時期があるようです。
- 何故戻り値はスタックに格納されず、レジスタ戻しなのか
- これは、呼出規約で決まっていることですが、何故かは僕もわかりません...
- 勝手な妄想ですが、引数を push して返すとなると、ret 出来ないので、予め返り値用の領域を確保しておかなければいけないですが、そうなると返り値 void か void でないかによって色々面倒くさい事が起きるので、とか?
- caller-saved なレジスタはどうせ破壊される前提なので、そのうちひとつを返り値にしてしまえば、頭いいよねというのは、まあ当然の発想である気もします。
- 自然言語がコンパイルできるようになる SF 的時代が来て欲しい。
- 夢ですね。
- 量子計算機
- これも、夢ですね。でも、使い方としては、現在のノイマン型のコンピュータを置き換えるように使われるようにはならない気もします。
- Intel の CPU が多すぎて不安になる。もう少し皆が一人一人のアーキテクチャを持てる時代になってほしい。
- すばらしい!自作 CPU、作りましょう!
- 例えば、http://open-arch.org/
- マイ CPU 自慢大会ができるようになったら、素晴らしいですね。
その他気になった点
- ARM は RISC か
- 個人的には、かなり CISC 寄りの RISC だと思っていて、典型的な RISC である ARM といわれても、ちょっとピンときません
- RISC と CISC
- RISC はワイヤードロジックで... とか言われますが、そんなのは大昔の話です。
- いまは、CISC でも一部はワイヤードロジックでしょうし、RISC でもマイクロプログラムを使っていることが多いでしょう。
- つまり、既にあまりその境目というのは曖昧になっていて、お互いにいいところを取り込みつつ、進化しているという感じです。
- Intel は新しい CPU を作らないのか
- 個人的な意見だと、Intel さんは過去の失敗 (故 Itanium 氏) に相当アレルギーを持っているようなので、たぶん今の方向はそれほど変えずに行くのではないかと思っています。
- なにか変わるとしたら、Intel 以外が繁栄する時代が来るか、Intel がアレルギーを克服して新しい物を創りだすか、どちらかのタイミングだと思います。
長かったですが、これで終わりです。
設問5のフィードバックを書くのは、本当に楽しかったです。
選考結果はまだ来ていないと思いますが、みなさんの益々のご活躍をお祈りしています。
また、質問はお気軽に、Twitter やメール、ブログのコメントから、お待ちしています。
ちょっと熱く語りすぎた、疲れた...
0 件のコメント:
コメントを投稿