Web備忘録

雑多に書いています

「コンピュータシステムの理論と実装」を読んだ

半年ほどかけて、やっとのことで読み終えた。

結論からいうと、難しくはありつつも、面白かった。「コンパイラアセンブラってなに?メモリ操作って具体的に何をしているの?CPUとは?」そのレベル感で、コンピューターサイエンスを体系的に学んでいない自分が読み進めたが、多くの内容が新鮮で(そしてとても難しく)、興味深かった。トランジスタが実現するゼロイチの世界から簡易的なCPUまで作る経験は、コンピュータというものが「一体なんなのか」という点に関して、確かな輪郭を形づくってくれたという実感がある。

( ただ、後半の4章(コンパイラ実装、簡易OS実装)は自前では実装していない。コンパイラについては、こちらの

低レイヤを知りたい人のためのCコンパイラ作成入門で学ぼうと思っていて、OSについては、汎用的な知識が身につくというより、「この本の中で紹介されているJackという言語仕様に特化したもの」になりそうな気配があったので、一旦パスした。ここについては、折をみてUnixLinuxを中心に学ぼうと思っている。 )

本題に戻る。内容がとても面白かった一方で、書籍内で文意を汲み取れず自分が理解できない箇所について、「知らないから理解できないのか(そもそも現状の理解度ではいくら考えても理解できないのか)」、「既知の理解を組み合わせれば解けるのか」といった点が、すぐには掴みづらい所もある、というのが悩みのタネだった。

考えることで解ける問題は楽しいけれど、いくら考えてもわからない問題は辛くなる。

そこで、この記事では、自分が詰まったところや理解したと思えるところについて文章にして残し、後にこの本に取り組む人や、あるいはコンピュータサイエンスを体系的に学んでいない人はこういうところでつまりがち、といった点の参考なればと思い、書くことにした。

記事内では正確性も出来る限り担保したいとは思っているが、自分なりの解釈や理解を言葉にしている箇所(つまり「感想ベース」の意見)も多くなっているとは思うので、そこはご了承いただければと思う……。

記事を分けることはせず、この記事で1冊を完結させた内容にしようとは思っている。

途中経過については、github上でも残している。コード自体はむちゃくちゃで設計はいまいちだけど、一応動くものではある。

GitHub - fujiten/Nand2Tetris

前準備

本書は、前準備としてわりと必要なものが多い。そして、その必要なものがそこまで明示的に表されていない。これが第一の難関である。

僕なりに必要なものをまとめた。

  • ハードウェアシュミレーターチュートリアル https://b1391bd6-da3d-477d-8c01-38cdf774495a.filesusr.com/ugd/44046b_bfd91435260748439493a60a8044ade6.pdf

  • エディタ

    • (VScode を使っている場合は、nand2tetrisという拡張機能があり、それを使うと本書のためのコードを書くためのハイライトなどがよい感じになるのでおすすめ。)
  • プロジェクトで使うtool、ファイル一式

    • 以下のページで、zip形式で落としてこれる。これをさがすのが大変だった。本書で明確に参照されていないが、これを落としてこないと何もできない。

    • Software | nand2tetris

  • Java環境

    • (ハードウェアシュミレーターという、Javaで書かれたデスクトップアプリをshで実行するさいに、Java環境を要求される。Javaをここ以外で使う予定がないのでローカルに落としたくなかったが、このためだけ立てたDockerコンテナでうまくデスクトップアプリケーションを起動することができず(Dockerむずい)、brew java で落としたと思う)

以上である。

また、本書は、「1章ごとに表題にあることについて解説をし、そしてその解説にそぐうような回路を作成する」といった内容になっている。章の終盤で「この章での君のプロジェクト(作るもの)はこれだ」と言われ、そこで「あ、これって何かを作るための解説だったのか」と気づく構成になっているので、もし最初から心の準備ができていれば有利だと思う。

まえがきを読んで

この本の対象読者は「情報工学コンピュータサイエンス)系の学生・大学院生」であるといった説明のがはじめにある。

この本に取り組むタイミングとして推奨されているのが、最初のプログラミング言語入門の次、または「すべての授業の最後に実践的なまとめ」らしい。恐らく、具体的な時期としては「大学1年生の夏」または「大学4年生の春」頃に、「これから大学で学ぶことについてはまずは手を動かしながら実践してみよう or 今まで学んだことについて、手を動かしながら実践してみよう」といったものなのだろう。

1章

1章では、ブール論理、つまり、0と1との組み合わせが表す論理関係と、その関係性を物理的に実現する回路の作り方について学ぶ。

ABの2つの入力値の組み合わせによって定義されるブール関数は16個ある。

f:id:fujiten3:20201101093912p:plain
bool

本書では、Nandゲートから他の回路(ブール関数)を作成していく。概念自体がとても難しいという章ではなかった。ただ、頭の中で、実際の電気回路をイメージし、入力と出力を線で結んでいくというイメージが最初は思いつかず、ただ「文法ルールに基づいてNAND論理からANDやORを作っていく」というふうに作業を進めてしまったので、そこがすこしもったいなかったかもしれない。

またMuxなどの実装については、プログラミングの考えを素直に適応しても実装できないな、という感がある。

if True:
  return a
else:
  return b

を表す処理を、NotやAnd、Orの論理組み合わせだけで表現するというのは、最初は手こずるかもしれない。自分は手こずった。

2章

今まで作成した論理ゲートを利用ながら「算術計算」を行なうことの出来るハードウェアを作成する。ハードウェアといっても、コードを記述し、それに則ったシュミレーションが正しく動くかどうか試すという形なので、ソフトウェア的な開発ではある。

2進数における足し算や負の数を、1章で実装した回路を使って表す。

3章

クロック(tick/tockのようなラベル付けがされる、時間の経過を表現するための継続的に変化する信号)によって担保されるメモリーを自前で実装していく。1Bitを記録できる論理から16bitを記憶するレジスターを作り、そのレジスターを組み合わせてRAMを作成していく。

この本で細部まで説明されない要素に「コンピューター内における時間経過(フリップフロップ)」がある。ここについての細部の実装はググってみたけれど、かなり難しかったため、本書においては一旦無視していいと思う。

load のフラグを立てると、メモリに「書き込み」をする挙動をみてテレビゲームのローディング中などの表示でよくある「now loading」って、読み込み中って考え方だけじゃなくて、RAMに情報を書き込み中、みたいな捉え方でも見れるんだなあ、と思った。

4章

機械語を自分で書いていく章。前章に比べて命令の抽象度が上がるため、自分の書いた機械語が「実際にメモリ、CPUに対してどのようなことを行わせているのか」を意識しないと全く理解できなくなる章である。

機械語を書いていく上での基本的な流れの一例は「メモリアドレスの指定 → 指定したアドレスのメモリに格納されている値をCPUが使用するためのDレジスタに格納 → 新たにメモリアドレスを指定し、そのアドレスに格納されている値と、先程Dレジスタに格納した値を2章で作成した演算装置にて演算 → 結果をメモリに保存」といったものである。

アセンブラを動かし、自分の書いた機械語が「0000001000001010」といった16bitのバイナリで表現されているのを確認し、そのバイナリが「正しい命令になっているか確かめる(バイナリをどう読めばいいかの説明は本書にある)」という流れで進めていくのが基本だと思う。

5章

4章における「機械語がバイナリ命令にアセンブリされる」流れを踏まえて、「バイナリ命令(ROM)とデータメモリ出力(RAM)を受けて、データメモリへの書き込みや次の命令のアドレスを指定するCPU」を作成する、という章である。

どの出力のフラグをどの回路に結びつければいいか、の概要図は示されるものの、今までの章を理解していないと確実に解けない。

自分の場合は、ある程度は算段がたったものの、実際の記述法がなかなか思いつかず(回路に対する命令を行うHDLという言語の仕様が難しい)、ネットでちょっと解法を探した。

6章

アセンブラの作成である。「@7」といった形で記述されている機械語を、バイナリに変換するプログラムを自分で書く。言語指定はないので、自分はPythonで書いた。

7章 ~ 8章

VM言語(中間言語)を機械語に変換する)コンパイラの作成である。VM言語とは、ある言語のコンパイラが、直接機械語を作る前に、まず一段階経由する言語ということのようである。たとえばJavaは、直接機械語を生成せず、まず中間言語を作成する仕様のようであるが、ただ、Go言語のように、中間言語を介さず直接機械語を生成できる言語もある。

スタックと呼ばれるシンプルかつ応用性が高い抽象データ型を利用した演算や、サブルーチン(関数)を呼び出すさいのスコープだったりを設定するためのメモリ操作について、コンパイラを作りながら勉強する。

正直、かなり難しく、8章の応用問題は解けなかった…。複数ファイルを名前競合しないようにコンパイルしつつ、再帰構造を含む各関数を適切に呼び出さなければならないが、メモリへの命令をどう書けばいいのか頭で処理できなくなったので飛ばした。

誰か、凄い人に解説してもらいたい…。

9 ~ 12章

ここについては、一番はじめに述べたとおり、他の教材で詳しく学ぼうと思ったので、演習問題は飛ばし、内容を読むだけにした。

最後に

コンパイラアセンブラってなに?メモリ操作って具体的に何をしているの?CPUとは?」そういった疑問に対して、言葉上の意味だけでなく「こういった挙動をするものなんだ」ということが、身にしみてわかったのでよかったと思う(小並感)。

これらの知識がすぐに実務に活きるかというとそうではないとは思うけれど、新しい技術に触れたり、自分で低水準の操作を行う必要があった場合は、役に立ってくる知識じゃないだろうか。

なので、もし低水準に興味があり、ただどうやって学べばいいか悩んでいる人の入門的位置づけの本としては、とてもよいと思います。(入門本だけど簡単ではないので注意! 自分は8章までやるだけでも50時間はかかった気がする。)