Mercurial > hg > Papers > 2021 > anatofuz-master
view paper/chapter/02-perl.tex @ 17:72bffc43a3a1
...
author | anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Fri, 29 Jan 2021 17:26:43 +0900 |
parents | 69ab7cf9c1a1 |
children | 7431f9572567 |
line wrap: on
line source
\chapter{GearsOSのトランスコンパイラ} GearsOSはCbCで実装を行う。 CbCはC言語よりアセンブラに近い言語であるため、 すべてを純粋なCbCで記述しようとすると記述量が膨大になってしまう。 またノーマルレベルの計算とメタレベルの計算を、全てプログラマが記述する必要が発生してしまう。 メタ計算では値の取り出しなどを行うが、 これはノーマルレベルのCodeGearのAPIが決まれば一意に決定される。 したがってノーマルレベルのみ記述すれば、 機械的にメタ部分の処理は概ね生成可能となる。 また、メタレベルのみ切り替えたいなどの状況が存在する。 ノーマルレベル、メタレベル共に同じコードの場合は記述の変更量が膨大であるが、 メタレベルの作成を分離するとこの問題は解消される。 GearsOSではメタレベルの処理の作成にPerlスクリプトを用いており、 ノーマルレベルで記述されたCbCから、 メタ部分を含むCbCへと変換する。 変換前のCbCをGearsCbCと呼ぶ。 \section{トランスコンパイラ} プログラミング言語から実行可能ファイルやアセンブラを生成する処理系のことを、一般的にコンパイラと呼ぶ。 特定のプログラミング言語から別のプログラミング言語に変換するコンパイラのことを、 トランスコンパイラと呼ぶ。 トランスコンパイラとしてはJavaScriptを古い規格のJavaScriptに変換するBabel\cite{babel}がある。 またトランスコンパイラは、変換先の言語を拡張した言語の実装としても使われる。 JavaScriptに強い型制約をつけた拡張言語であるTypeScriptは、 TypeScriptから純粋なJavaScriptに変換を行うトランスコンパイラである。 すべてのTypeScriptのコードはJavaScriptにコンパイル可能である。 JavaScriptに静的型の機能を取り込みたい場合に使われる言語であり、 JavaScriptの上位の言語と言える。 GearsOSはCbCを拡張した言語となっている。 ただしこの拡張自体はCbCコンパイラであるgcc、 llvm/clangには搭載されていない。 その為GearsOSの拡張部分を、等価な純粋なCbCの記述に変換する必要がある。 現在のGearsOSでは、 CMakeによるコンパイル時にPerlで記述された\texttt{generate\_stub.pl}と\texttt{generate\_context.pl}の2種類のスクリプトで変換される。 \begin{itemize} \item \texttt{generate\_stub.pl} \begin{itemize} \item 各CbCファイルごとに呼び出されるスクリプト \item 対応するメタ計算を導入したCbCファイル(拡張子はc)に変換する \begin{itemize} \item 図\ref{fig:generate_stub_pl_1}に処理の概要を示す \end{itemize} \end{itemize} \item \texttt{generate\_context.pl} \begin{itemize} \item 生成したCbCファイルを解析し、使われているCodeGear、DataGearを確定する \item これらの情報をもとにContext及びContext関係の初期化ルーチン、APIを作成する \end{itemize} \end{itemize} これらのPerlスクリプトはプログラマが自分で動かすことはない。 GearsOSでプログラミングする際は、ビルドしたいプロジェクトをCMakeLists.txtに記述し、 移行はCMakeのビルドフローに従う。 CMakeはMakefileやbuild.ninjaを生成し実際にビルドはmakeやninja-buildが行う。 \begin{figure}[htp] \begin{center} \includegraphics[width=160mm]{drawio/gears_os_build_flow.pdf} \end{center} \caption{generate\_sub.plを利用したクロスコンパイル} \label{fig:generate_stub_pl_1} \end{figure} \section{pmake} GearsOSをビルドする場合は、x86アーキテクチャのマシンからビルドするのが殆どである。 この場合ビルドしたバイナリはx86向けのバイナリとなる。 これはビルドをするホストマシンに導入されているCbCコンパイラがx86アーキテクチャ向けにビルドされたものである為である。 CbCコンパイラはGCCとllvm/clang上に構築した2種類が主力な処理系である。 LVM/clangの場合はLLVM側でターゲットアーキテクチャを選択することが可能である。 GCCの場合は最初からターゲットアーキテクチャを指定してコンパイラをビルドする必要がある。 時にマシンスペックの問題などから、 別のアーキテクチャ向けのバイナリを生成したいケースがある。 教育用マイコンボードであるRaspberry PiはARMアーキテクチャが搭載されている。 Raspberry Pi上でGearsOSのビルドをする場合、 ARM用にビルドされたCbCコンパイラが必要となる。 Raspberry Pi自体は非力なマシンであるため、 GearsOSのビルドはもとよりCbCコンパイラの構築をRaspberry Pi上でするのは困難である。 マシンスペックが高めのx86マシンからARM用のバイナリをビルドして、 Raspberry Piに転送し実行したい。 ホストマシンのアーキテクチャ以外のアーキテクチャ向けにコンパイルすることをクロスコンパイルと呼ぶ。 GearsOSはビルドツールにCMakeを利用しているので、 CMakeでクロスコンパイル出来るように工夫をする必要がある。 CMakeはautomakeなどのMakeファイルを作成するツールに相当するものである。 CMake側の機能でビルドに使用できるコンパイラやリンカを自動探索し、 決定した上でMakefileやNinjaファイルを生成する。 しかしCMakeは今ビルドしようとしている対象が、自分が動作しているアーキテクチャかそうでないか、クロスコンパイラとして使えるかなどはチェックしない。 つまりCMakeが自動でクロスコンパイル対応のGCCコンパイラを探すことはない。 その為そのままビルドするとx86用のバイナリが生成されてしまう。 CMakeを利用してクロスコンパイルする場合、CMakeの実行時に引数でクロスコンパイラを明示的に指定する必要がある。 この場合x86のマシンからARMのバイナリを出力する必要があり、 コンパイラやリンカーなどをARMのクロスコンパイル対応のものに指定する必要がある。 また、 xv6の場合はリンク時に特定のリンカスクリプトを使う必要がある。 これらのリンカスクリプトもCMake側に、 CMakeが提供しているリンカ用の特殊変数を使って自分で組み立てて渡す必要がある。 このようなCMakeの処理を手打ちで行うことは難しいので、 \texttt{pmake.pl}を作成した。 \texttt{pmake.pl}の処理の概要を図\ref{fig:pmake}に示す。 \texttt{pmake.pl}はPerlスクリプトで、 シェルコマンドを内部で実行しクロスコンパイル用のオプションを組み立てる。 \texttt{pmake.pl}を経由してCMakeを実行すると、 makeコマンドに対応するMakefile、 ninja-buildに対応するbuild.ninjaが生成される。 以降はcmakeではなくmakeなどのビルドツールがビルドを行う。 \begin{figure}[htp] \begin{center} \includegraphics[width=160mm]{drawio/pmake.pdf} \end{center} \caption{pmake.plの処理フロー} \label{fig:pmake} \end{figure} \section{GearsCbCのInterfaceの実装時の問題} Interfaceとそれを実装するImplの型が決定すると、最低限満たすべきCodeGearのAPIは一意に決定する。 ここで満たすべきCodeGearは、Interfaceで定義したCodeGearと、 Impl側で定義した privateなCodeGearとなる。 例えばStack Interfaceの実装を考えると、各Implで\texttt{pop}, push, shift, isEmptyなどを実装する必要がある。 従来はプログラマが手作業でヘッダーファイルの定義を参照しながら\texttt{.cbc}ファイルを作成していた。 手作業での実装のため、 コンパイル時に次のような問題点が多発した。 \begin{itemize} \item CodeGearの入力のフォーマットの不一致 \item Interfaceの実装のCodeGearの命名規則の不一致 \item 実装を忘れているCodeGearの発生 \end{itemize} 特にGearsOSの場合はPerlスクリプトによって純粋なCbCに一度変換されてからコンパイルが行われる。 実装の状況とトランスコンパイラの組み合わせによっては、 CbCコンパイラレベルでコンパイルエラーを発生させないケースがある。 この場合は実際に動作させながら、gdb, lldbなどのCデバッガを用いてデバッグをする必要がある。 またCbCコンパイラレベルで検知できても、すでに変換されたコード側でエラーが出てしまうので、トランスコンパイラの挙動をトレースしながらデバッグをする必要がある。 Interfaceの実装が不十分であることのエラーは、 GearsOSレベル、最低でもCbCコンパイラのレベルで完全に検知したい。 \section{Interfaceを満たすコード生成の他言語の対応状況} Interfaceを機能として所持している言語の場合、これらはコンパイルレベルか実行時レベルで検知される。 例えばJavaの場合はInterfaceを満たしていない場合はコンパイルエラーになる。 InterfaceのAPIを完全に実装するのを促す仕組みとして、Interfaceの定義からエディタやツールが満たすべき関数と引数の組を自動生成するツールがある。 Javaでは様々な手法でこのツールを実装している。 Microsoftが提唱しているIDEとプログラミング言語のコンパイラをつなぐプロトコルにLanguage Serverがある。 Language Serverはコーディング中のソースコードをコンパイラ自身でパースし、 型推論やエラーの内容などをIDE側に通知するプロトコルである。 主要なJavaのLanguage Serverの実装であるeclipse.jdt.ls\cite{eclipse.jdt.ls}では、 LanguageServerの機能として未実装のメソッドを検知する機能が実装されている。\cite{eclipse.jdt.pull322} この機能を応用してvscode上から未実装のメソッドを特定し、 雛形を生成する機能がある。 他にもIntelliJ IDEなどの商用IDEでは、 IDEが独自に未実装のメソッドを検知、雛形を生成する機能を実装している。 golangの場合は主に\texttt{josharian/impl}\cite{golang_impl}が使われている。 これはインストールすると\texttt{impl}コマンドが使用可能になり、 実装したいInterfaceの型と、 Interfaceを実装するImplの型(レシーバ)を与えることで雛形が生成される。 主要なエディタであるvscodeのgolangの公式パッケージである\texttt{vscode-go}\cite{vscode-go}でも導入されており、 vscodeから呼び出すことが可能である。 vscode以外にもvimなどのエディタから呼び出すことや、 シェル上で呼び出して標準出力の結果を利用することが可能である。 \section{GearsOSでのInterfaceを満たすCbCの雛形生成} GearsOSでも同様のプログラマ支援ツールを導入したい。 LanguageServerの導入も考えられるが、 今回の場合はC言語のLanguageServerをCbC用にまず改良し、 さらにGearsOS用に書き換える必要がある。 現状のGearsOSが持つシンタックスはCbCのシンタックスを拡張しているものではあるが、これはCbCコンパイラ側には組み込まれていない。 LanguageServerをGearsOSに対応する場合、 CbCコンパイラ側にGearsOSの拡張シンタックスを導入する必要がある。 CbCコンパイラ側への機能の実装は、 比較的難易度が高いと考えらる。 CbCコンパイラ側に手をつけず、 Interfaceの入出力の検査は既存のGearsOSのビルドシステム上に組み込みたい。 対してgolangの\texttt{impl}コマンドのように、 シェルから呼び出し標準出力に結果を書き込む形式も考えられる。 この場合は実装が比較的容易かつ、 コマンドを呼び出して標準出力の結果を使えるシェルやエディタなどの各プラットフォームで使用可能となる。 先行事例を参考に、コマンドを実行して雛形ファイルを生成するスクリプトをGearsOSに導入した。 Interfaceでは入力の引数がImplと揃っている必要があるが、 第一引数は実装自身のインスタンスがくる制約となっている。 実装自身の型は、Interface定義時には不定である。 その為、 GearsOSではInterfaceのAPIの宣言時にデフォルト型変数\texttt{Impl}を実装の型として利用する。 デフォルト型\texttt{Impl}を各実装の型に置換することで自動生成が可能となる。 実装すべきCodeGearはInterfaceとImpl側の型を見れば定義されている。 \texttt{\_\_code}で宣言されているものを逐次生成すればよいが、 継続として呼び出されるCodeGearは具体的な実装を持たない。 GearsOSで使われているInterfaceには概ね次の継続である\texttt{next}が登録されている。 \texttt{next}そのものはInterfaceを呼び出す際に、入力として与える。 その為各Interfaceに入力として与えられた\texttt{next}を保存する場所は存在するが、 nextそのものの独自実装は各Interfaceは所持しない。 したがってこれをInterfaceの実装側で明示的に実装することはできない。 雛形生成の際に、入力として与えられるCodeGearを生成してしまうと、プログラマに混乱をもたらしてしまう。 入力として与えられているCodeGearは、Interfaceに定義されているCodeGearの引数として表現されている。 コードに示す例では、\texttt{whenEmpty}は入力して与えられているCodeGearである。 雛形を生成する場合は、入力として与えられたCodeGearを除外して出力を行う。 順序はInterfaceをまず出力した後に、 Impl側を出力する。 雛形生成では他にコンストラクタの生成も行う。 GearsOSのInterfaceのコンストラクタは、 メモリの確保及び各変数の初期化を行う。 メモリ上に確保するのは主にInterfaceとImplのそれぞれが基本となっている。 Interfaceによっては別のDataGearを内包しているものがある。 その場合は別のDataGearの初期化もコンストラクタ内で行う必要があるが、 自動生成コマンドではそこまでの解析は行わない。 コンストラクタのメンバ変数はデフォルトでは変数は0、ポインタの場合はNULLで初期化するように生成する。 このスクリプトで生成されたコンストラクタを使う場合、 CbCファイルから該当する部分を削除すると、\texttt{generate\_stub.pl}内でも自動的に生成される。 自動生成機能を作成すると1CbCファイルあたりの記述量が減る利点がある。 明示的にコンストラクタが書かれていた場合は、 Perlスクリプト内での自動生成は実行しないように実装した。 これはオブジェクト指向言語のオーバーライドに相当する機能と言える。 現状のGearsOSで使われているコンストラクタは、 基本は\texttt{struct Context*}型の変数のみを引数で要求している。 しかしオブジェクトを識別するためにIDを実装側に埋め込みたい場合など、 コンストラクタ経由で値を代入したいケースが存在する。 この場合はコンストラクタの引数を増やす必要や、 受け取った値をインスタンスのメンバに書き込む必要がある。 具体的にどの値を書き込めば良いのかまではPerlスクリプトでは判定することができない。 このような細かな調整をする場合は、 generate\_stub.pl側での自動生成はせずに、 雛形生成されたコンストラクタを変更すれば良い。 あくまで雛形生成スクリプトはプログラマ支援であるため、 いくつかの手動での実装は許容している。