Mercurial > hg > Papers > 2021 > anatofuz-master
changeset 58:b1e2bcdd5191
fix order
author | anatofuz <anatofuz@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Tue, 02 Feb 2021 15:48:04 +0900 |
parents | 69e341226e5a |
children | 23d1cff7c260 |
files | paper/chapter/02-cbc.tex paper/chapter/03-gears.tex paper/chapter/04-interface.tex paper/chapter/04-perl.tex paper/chapter/abstract.tex paper/master_paper.pdf paper/master_paper.tex |
diffstat | 7 files changed, 292 insertions(+), 286 deletions(-) [+] |
line wrap: on
line diff
--- a/paper/chapter/02-cbc.tex Tue Feb 02 14:41:18 2021 +0900 +++ b/paper/chapter/02-cbc.tex Tue Feb 02 15:48:04 2021 +0900 @@ -15,11 +15,11 @@ 入力のDataGearをInputDataGearと呼び、 出力のDataGearをOutputDataGearと呼ぶ。 CodeGearがアクセスできるDataGearは、 InputDataGearとOutputDataGearに限定される。 -CodeGearは関数呼び出し時のスタックを持たない為、一度あるCodeGearに遷移してしまうと元の処理に戻ってくることができない。 +CodeGearは関数呼び出し時のスタックを持たない為、一度あるCodeGearに遷移すると元の処理に戻ってこれない。 しかしCodeGearを呼び出す直前のスタックは保存される。 部分的にCbCを適用する場合はCodeGearを呼び出す\texttt{void}型などの関数を経由することで呼び出しが可能となる。 -この他にCbCからCへ復帰する為のAPIとして、 環境付きgotoという機能がある。 +この他にCbCからCへ復帰する為のAPIとして、 環境付きgotoがある。 これは呼び出し元の関数を次のCodeGearの継続対象として設定するものである。 これはGCCでは内部コードを生成を行う。 LLVM/clangでは\texttt{setjmp}と\texttt{longjmp}を使い実装している。
--- a/paper/chapter/03-gears.tex Tue Feb 02 14:41:18 2021 +0900 +++ b/paper/chapter/03-gears.tex Tue Feb 02 15:48:04 2021 +0900 @@ -55,7 +55,7 @@ ホストマシンのアーキテクチャ以外のアーキテクチャ向けにコンパイルすることをクロスコンパイルと呼ぶ。 -GearsOSはビルドツールにCMakeを利用しているので、 CMakeでクロスコンパイル出来るように工夫をする必要がある。 +GearsOSはビルドツールにCMakeを利用しているので、 CMakeでクロスコンパイル可能に工夫をしなければならない。 ビルドに使用するコンパイラやリンカはCMakeが自動探索し、 決定した上でMakefileやbuild.ninjaファイルを生成する。 しかしCMakeは今ビルドしようとしている対象が、自分が動作しているアーキテクチャかそうでないか、クロスコンパイラとして使えるかなどはチェックしない。 つまりCMakeが自動でクロスコンパイル対応のGCCコンパイラを探すことはない。
--- a/paper/chapter/04-interface.tex Tue Feb 02 14:41:18 2021 +0900 +++ b/paper/chapter/04-interface.tex Tue Feb 02 15:48:04 2021 +0900 @@ -8,7 +8,7 @@ \lstinputlisting[label=src:old-stack, caption=従来のStack Interface]{src/old-stack.h} 従来の分離している記法の場合、 このDataGearの宣言が一致していないケースが多々発生した。 -またInterfaceの入力としてのDataGearではなく、 フィールド変数としてDataGearを使うようなプログラミングスタイルを取ってしまうケースも見られた。 +またInterfaceの入力としてのDataGearではなく、 フィールド変数としてDataGearを使うプログラミングスタイルを取るケースも見られた。 GearsOSでは、 DataGearやフィールド変数をオブジェクトに格納したい場合、 Interface側ではなくImpl側に変数を保存する必要がある。 Interface側に記述してしまう原因は複数考えられる。 GearsOSのプログラミングスタイルに慣れていないことも考えられるが、構文によるところも考えられる。 @@ -32,7 +32,7 @@ \lstinputlisting[label=src:stack, caption=変更後のStack Interface]{src/stack.h} -従来のInterfaceでは\texttt{<Type, Impl>}という記述があった。 +従来のInterfaceでは\texttt{<Type, Impl>}キーワードが含まれていた。 これはジェネリクスの機能を意識して導入された構文である。 \texttt{Impl}キーワードは実装自身の型を示す型変換として使われていた。 しかし基本Interfaceの定義を行う際にGearsOSのシステム上、CodeGearの第一引数は\texttt{Impl}型のポインタが来る。 @@ -56,12 +56,13 @@ 例えばStack Interfaceの実装はSingleLinkedStackであり、 Queueの実装はSingleLinkedQueueやSynchronizedQueueが存在する。 このSynchronizedQueueはGearsOSではDataGearとして扱われる。 -このDataGearの定義は、 Interfaceの定義のように型定義ファイルが存在するわけではなかった。 +Interfaceの定義と同等な型定義ファイルが、 実装の型については存在しなかった。 従来はcontext.hのDataGearの宣言部分に、構造体の形式で表現したものを手で記述していた。(ソースコード\ref{src:singleContext.h}) \lstinputlisting[label=src:singleContext.h, caption=cotnext.hに直接書かれた型定義]{src/singleContext.h} -CbCファイルからはcontext.hをインクルードすることで問題なく型を使うことが可能であるが、 型定義ファイルの存在の有無がInterfaceと実装で異なってしまっていた。 +CbCファイルからはcontext.hをインクルードすることで問題なく型の使用は可能である。 Perlのトランスコンパイラであるgenerate\_stub.plはInterfaceの型定義ファイルをパースしていた。 +しかし型定義ファイルの存在の有無がInterfaceと実装で異なっている為に、 generate\_stub.plでImplementの型に関する操作ができない。 Implementの型も同様に定義ファイルを作製すれば、generate\_stub.plで型定義を用いた様々な処理が可能となり、ビルドシステムが柔軟な挙動が可能となる。 また型定義は一貫して\texttt{*.h}に記述すれば良くなるため、 プログラマの見通しも良くなる。 本研究では新たにImplementの型定義ファイルを考案する。 @@ -99,204 +100,120 @@ Interfaceで定義したCodeGearと対応しているImplementのCodeGearの場合はこのデータの取り出し方で問題はない。 しかしImplementのCodeGearから内部でgotoするCodeGearの場合は事情が異なる。 -内部でgotoするCodeGearは、 Javaなどのプライベートメソッドのように使うことを想定している。 +内部でgotoするCodeGearは、 Javaなどのプライベートメソッドとして使用できる。 このCodeGearのことをprivate CodeGearと呼ぶ。 privateCodeGearにgotoする場合、 goto元のCodeGearからは\texttt{goto meta}経由で遷移する。 -goto metaが発行されるとStub Code Gearに遷移するが、現在のシステムではInterfaceから値をとってくることになってしまう。 - -\section{context.hの自動生成} -GearsOSのContextの定義はcontext.hにある。 -ContextはGearsOSの計算で使用されるすべてのCodeGear、 DataGearの情報を持っている。 -context.hではDataGearに対応する\texttt{union Data}型の定義も行っている。 -Data型はCの共用体であり、 Dataを構成する要素として各DataGearがある。 -各DataGearは構造体の形で表現されている。 -各DataGear自体の定義もcontext.hのunion Dataの定義の中で行われている。 - -DataGearの定義はInterfaceファイルで行っていた。 -InterfaceファイルはGearsOS用に拡張されたシンタックスのヘッダファイルを使っており、 直接CbCからロードすることができない。 -その為従来はプログラマが静的にInterfaceファイルをCbCの文脈に変換し、 context.hに構造体に変換したものを書いていた。 -この手法では手書きでの構築のために自由度は高かったが、 GearsOSの例題によっては使わないDataGearも、 context.hから削除しない限りcontextに含んでしまう問題があった。 -さらにInterfaceファイルで定義した型をcontext.hに転記し、それをもとにImplの型を考えてCbCファイルを作製する必要があった。 -これらをすべてユーザーが行うと、ファイルごとに微妙な差異が発生したりとかなり煩雑な実装を要求されてしまう。 -DataGearの定義はInterfaceファイルを作製した段階で決まり、 使用しているDataGear、CodeGearはコンパイル時に確定するはずである。 -使用している各Gearがコンパイル時に確定するならば、 コンパイルの直前に実行されるPerlトランスコンパイラでもGearの確定ができるはずである。 -ここからcontext.hをコンパイルタイミングでPerlスクリプト経由で生成する手法を考案した。 - -\subsection{context.hの作製フロー} -GearsCbCからメタ計算を含むCbCファイルに変換するgenerate\_stub.plは各CbCファイルを1つ1つ呼び出していた。 -context.hを生成しようとする場合、 プロジェクトで利用する全CbCファイルを扱う必要がある。 - -Contextの初期化ルーチンを作製するgenerate\_context.plは、その特性上すべてのCbCファイルをロードしていた。 -したがってcontext.hを作製する場合はこのスクリプトで行うのが良い。 - -Perlのモジュールとして\texttt{Gears::Template::Context}を作製した。 -xv6プロジェクトの場合は一部ヘッダファイルに含める情報が異なる。 - -派生モジュールとして\texttt{Gears::Template::Context::XV6}も実装している。 -これらのテンプレートモジュールはgenerate\_context.plの実行時のオプションで選択可能とした - -\section{メタ計算部分の入れ替え} -GearsOSでは次のCodeGearに移行する前のMetaCodeGearとして、 デフォルトでは\texttt{\_\_code meta}が使われている。 -\texttt{\_\_code meta}はcontextに含まれているCodeGearの関数ポインタを、 enumからディスパッチして次のStub CodeGearに継続するものである。 - -例えばモデル検査をGearsOSで実行する場合、 通常のStub CodeGearのほかに状態の保存などを行う必要がある。 -この状態の保存に関する一連の処理は明らかにメタ計算であるので、 ノーマルレベルのCodeGearではない箇所で行いたい。 -ノーマルレベル以外のCodeGearで実行する場合は、 通常のコード生成だとStubCodeGearの中で行うことになる。 -StubCodeGearは自動生成されてしまうため、 値の取り出し以外のことを行う場合は自分で実装する必要がある。 -しかしモデル検査に関する処理は様々なCodeGearの後に行う必要があるため、 すべてのCodeGearのStubを静的に実装するのは煩雑である。 - -ノーマルレベルのCodeGearの処理の後に、StubCodeGear以外のMeta Code Gearを実行したい。 -Stub Code Gearに直ちに遷移してしまう\texttt{\_\_code meta}以外のMeta CodeGearに、 特定のCodeGearの計算が終わったら遷移したい。 -このためには、特定のCodeGearの遷移先のMetaCodeGearをユーザーが定義できるAPIが必要となる。 -このAPIを実装すると、ユーザーが柔軟にメタ計算を選択することが可能となる。 - -GearsOSのビルドシステムのAPIとして\texttt{meta.pm}を作製した。 -これはPerlのモジュールファイルとして実装した。 -meta.pmはPerlで実装されたGearsOSのトランスコンパイラであるgenerate\_stub.plから呼び出される。 -meta.pmの中のサブルーチンである\texttt{replaceMeta}に変更対象のCodeGearと変更先のMetaCodeGearへのgotoを記述する。 -ユーザーはmeta.pmのPerlファイルをAPIとしてGearsOSのトランスコンパイラにアクセスすることが可能となる。 - -具体的な使用例をコード\ref{src:metapm}に示す。 -meta.pmはサブルーチン\texttt{replaceMeta}が返すリストの中に、特定のパターンで配列を設定する。 -各配列の0番目には、goto metaを置換したいCodeGearの名前を示すPerl正規表現リテラルを入れる。 -コード\ref{src:metapm}の例では、\texttt{PhilsImpl}が名前に含まれるCodeGearを指定している。 -すべてのCodeGearのgotoの先を切り替える場合は\texttt{qr/.*\//}などの正規表現を指定する。 - -\lstinputlisting[label=src:metapm, caption=meta.pm]{src/meta.pm} - -generate\_stub.plはGears CbCファイルの変換時に、 CbCファイルがあるディレクトリにmeta.pmがあるかを確認する。 -meta.pmがある場合はモジュールロードを行う。 -meta.pmがない場合はmeta Code Gearにgotoするものをデフォルト設定として使う。 -各Gode Gearが\texttt{goto文}を呼び出したタイミングでreplaceMetaを呼び出し、 ルールにしたがってgoto文を書き換える。 -変換するCodeGearがルールになかった場合は、 デフォルト設定が呼び出される。 - -\section{別Interfaceからの書き出しを取得する必要があるCodeGear} - -従来のMetaCodeGearの生成では、 別のInterfaceからの入力を受け取るCodeGearのStubの生成に問題があった。 -具体的なこの問題が発生する例題をソースコード\ref{src:insertTest1}に示す。 -\lstinputlisting[label=src:insertTest1, caption=別Interfaceからの書き出しを取得するCodeGearの例]{src/pop2test.cbc} -この例では\texttt{pop2Test}Code Gearから \texttt{stack->pop2}を呼び出し、 継続として\texttt{pop2Test1}を渡している。 -\texttt{pop2Test}自体はStackTest Interfaceであり、 \texttt{stack->pop2}の\texttt{stack}はStack Interfaceである。 -例題ではStack Interfaceの実装はSingleLinkedStackである。 -SingleLinkedStackの\texttt{pop2}の実装をソースコード\ref{src:pop2}に示す。 -\lstinputlisting[label=src:pop2, caption=SingleLinkedStackのpop2]{src/pop2.cbc} -pop2はスタックから値を2つ取得するAPIである。 -pop2の継続は\texttt{next}であり、 継続先に\texttt{data}と\texttt{data1}を渡している。 -data、 data1は引数で受けている\texttt{union Data*}型の変数であり、 それぞれstackの中の値のポインタを代入している。 -この操作でstackから値を2つ取得している。 +goto metaが発行されるとStub Code Gearに遷移するが、現在のシステムではInterfaceから値を取得しに行く。 -このコードをgenerate\_stub.pl経由でメタ計算を含むコードに変換する。 -変換した先のコードを\ref{src:pop2meta}に示す。 -\lstinputlisting[label=src:pop2meta, caption=SingleLinkedStackのpop2のメタ計算]{src/pop2meta.cbc} -実際は\texttt{next}は\texttt{goto meta}に変換されてしまう。 -data、data1は\texttt{goto meta}の前にポインタ変数\texttt{O\_data}が指す値にそれぞれ書き込まれる。 -\texttt{O\_data}はpop2のStub CodeGearである\texttt{pop2SingleLinkedStack\_stub}で作製している。 -つまり\texttt{O\_data}はcontext中に含まれているStack Interfaceのデータ保管場所にある変数dataのアドレスである。 -\texttt{pop2}のAPIを呼び出すと、 Stack Interface中の\texttt{data}にStackに保存されていたデータのアドレスが書き込まれる。 - - -当初Perlスクリプトが生成した\texttt{pop2Test1}のstub CodeGearはソースコード\ref{src:pop2stub-origin}のものである。 -CodeGear間で処理されるデータの流れの概要図を図\ref{fig:stackTest1}に示す。 -\lstinputlisting[label=src:pop2stub-origin, caption=生成されたStub]{src/pop2stub-origin.cbc} -\texttt{\_\_code pop2Test}で遷移する先のCodeGearはStackInterfaceであり、 呼び出しているAPIは\texttt{pop2}である。 -pop2で取り出したデータは、 上記で確認した通りContext中のStack Interfaceのデータ格納場所に書き込まれる。 -しかしソースコード\ref{src:pop2stub-origin}の例では\texttt{Gearef(context, StackTest)}でContext中の\texttt{StackTest} Interfaceのdataの置き場所から値を取得している。 -これはInterfaceのImplのCodeGearは、Interfaceから値を取得するというGearsOSのルールの為である。 -現状ではpop2でせっかく取り出した値をStubCodeGearで取得できない。 - -ここで必要となってくるのは、 実装しているInterface以外の呼び出し元のInterfaceからの値の取得である。 -今回の例ではStackTest InterfaceではなくStack Interfaceからdata、 data1を取得したい。 -どのInterfaceから呼び出されているかは、 コンパイルタイムには確定できるのでPerlのトランスコンパイラでStub Codeを生成したい。 +\section{GearsCbCのInterfaceの実装時の問題} -\begin{figure}[h] - \begin{center} - \includegraphics[width=130mm]{drawio/stackTest1.pdf} - \end{center} - \caption{stackTest1のstubの概要} - \label{fig:stackTest1} - \end{figure} - -別Interfaceから値を取得するには別の出力があるCodeGearの継続で渡されたCodeGearをまず確定させる。 -今回の例では\texttt{pop2Test1}が該当する。 -このCodeGearの入力の値と、 出力があるCodeGearの出力を見比べ、 出力をマッピングすれば良い。 -Stack Interfaceのpop2はdataとdata1に値を書き込む。 -pop2Test1の引数はdata, data1, stackであるので、前2つにpop2の出力を代入したい。 +Interfaceとそれを実装するImplの型が決定すると、最低限満たすべきCodeGearのAPIは一意に決定する。 +ここで満たすべきCodeGearは、Interfaceで定義したCodeGearと、 Impl側で定義した privateなCodeGearとなる。 +例えばStack Interfaceの実装を考えると、各Implで\texttt{pop}, push, shift, isEmptyなどを実装する必要がある。 -Contextから値を取り出すのはメタ計算であるStub CodeGearで行われる。 -別Interfaceから値を取り出そうとする場合、 すでにPerlトランスコンパイラが生成しているStubを書き換えてしまう方法も取れる。 -しかしStubCodeGearそのものを、 別Interfaceから値を取り出すように書き換えてはいけない。 -これは別Interfaceの継続として渡されるケースと、 次のgoto先として遷移するケースがあるためである。 -前者のみの場合は書き換えで問題ないが、 後者のケースで書き換えを行ってしまうとStubで値を取り出す先が異なってしまう。 -どのような呼び出し方をしても対応できるようにするには、 Stubを別に別ける必要がある。 - - -GearsOSでは継続として渡す場合や、 次のgoto文で遷移する先のCodeGearはノーマルレベルではenumの番号として表現されていた。 -enumが降られるCodeGearは、厳密にはCodeGearそのものではなくStub CodeGearに対して降られる。 -StubCodeGearを実装した分だけenumの番号が降られるため、 \texttt{goto meta}で遷移する際にenumの番号さえ合わせれば独自定義のStubに継続させることが可能である。 -別Interfaceから値を取り出したいケースの場合、 取り出してくる先のInterfaceと呼び出し元のCodeGearが確定したタイミングで別のStubCodeGearを生成する。 -呼び出し元のCodeGearが継続として渡すStubCodeGearのenumを、独自定義したenumに差し替えることでこの問題は解決する。 -この機能をPerlのトランスコンパイラである\texttt{generate\_stub.pl}に導入した。 - -\section{別Interfaceからの書き出しを取得するStubの生成} -別Interfaceからの書き出しを取得する場合、 generate\_stub.plでは次の点をサポートする機能をいれれば実現可能である。 +従来はプログラマが手作業でヘッダーファイルの定義を参照しながら\texttt{.cbc}ファイルを作成していた。 +手作業での実装のため、 コンパイル時に下記の問題点が多発した。 \begin{itemize} - \item goto先のCodeGearが出力を持つInterfaceでかつ継続で渡しているCodeGearが別Interfaceの場合の検知 - \begin{itemize} - \item この場合はgotoしている箇所で渡している継続のenumを、新たに作製したstubのenumに差し替える - \end{itemize} - \item 継続で実行された場合に別にInterfaceから値をとってこないといけないCodeGear自身 - \begin{itemize} - \item Stubを別のInterfaceから値をとる実装のものを別に作製する - \end{itemize} + \item CodeGearの入力のフォーマットの不一致 + \item Interfaceの実装のCodeGearの命名規則の不一致 + \item 実装を忘れているCodeGearの発生 \end{itemize} -\texttt{generate\_stub.pl}内では変換対象のCbCのソースコードを2度読み込む。 -最初の読み込み時に継続の状況を確認し、 2度目の読み込み時に状況を踏まえてコードを生成すれば良い。 -初回の読み込み時にInterface経由の\texttt{goto}文があった場合に、別Interfaceからの出力があるかなどの情報を確認したい。 +特にGearsOSの場合はPerlスクリプトによって純粋なCbCに一度変換されてからコンパイルが行われる。 +実装の状況とトランスコンパイラの組み合わせによっては、 CbCコンパイラレベルでコンパイルエラーを発生させないケースがある。 +この場合は実際に動作させながら、gdb, lldbなどのCデバッガを用いてデバッグをする必要がある。 +またCbCコンパイラレベルで検知できても、すでに変換されたコード側でエラーが出る。 +このため、 トランスコンパイラの挙動をトレースしながらデバッグをする必要がある。 +Interfaceの実装が不十分であることのエラーは、 GearsOSレベル、最低でもCbCコンパイラのレベルで完全に検知したい。 -\subsection{初回CbCファイル読み込み時の処理} +\section{Interfaceを満たすコード生成の他言語の対応状況} -Interface経由でのgoto文は\texttt{goto interface->method()}の形式で呼び出される。 -ソースコード\ref{src:parsedOutputStub.pl}はこの形式で来ていた行を読み込んだタイミングで実行される処理である。 -\lstinputlisting[label=src:parsedOutputStub.pl, caption=goto時に使用するinterfaceの解析]{src/parsedOutputStub.pl} +Interfaceを機能として所持している言語の場合、Interfaceを完全に見たいしているかどうかはコンパイルレベルか実行時レベルで検知される。 +例えばJavaの場合はInterfaceを満たしていない場合はコンパイルエラーになる。 + + +InterfaceのAPIを完全に実装するのを促す仕組みとして、Interfaceの定義からエディタやツールが満たすべき関数と引数の組を自動生成するツールがある。 -1行目の正規表現はInterface経由でのgoto文の正規表現パターンである。 -変数\texttt{\$instance}はInterfaceのインスタンスである。正規表現パターンでは\texttt{interface->method}の\texttt{->}の前に来ている変数名に紐づけられる。 -変数\texttt{\$method}はgoto先のInterfaceのAPIである。正規表現パターンでは\texttt{interface->method}の\texttt{->}の後に来ているAPI名である。 -ソースコード\ref{src:insertTest1}の\texttt{pop2Test}では、 \texttt{stack->pop2}の呼び出しをしているため、 \texttt{stack}がインスタンスであり、 \texttt{pop2}がAPIである。 -現在解析しているgoto文が含まれているCodeGearの名前は、変数\texttt{\$currentCodeGear}で別途保存している。 -連想配列である\texttt{\$codeGearInfo}の中には、 各CodeGearで使われている変数と変数の型などの情報が格納されている。 -ソースコード\ref{src:parsedOutputStub.pl}の9行目では、 \texttt{\$codeGearInfo}経由でInterfaceのインスタンスから、具体的にどの型が呼ばれているかを取得する。 -\texttt{pop2Test}では、 インスタンス\texttt{stack}に対応する型名は\texttt{Stack}と解析される。 +Javaでは様々な手法でこのツールを実装している。 +Microsoftが提唱しているIDEとプログラミング言語のコンパイラをつなぐプロトコルにLanguage Serverがある。 +Language Serverはコーディング中のソースコードをコンパイラ自身でパースし、 型推論やエラーの内容などをIDE側に通知するプロトコルである。 +主要なJavaのLanguage Serverの実装であるeclipse.jdt.ls\cite{eclipse.jdt.ls}では、 LanguageServerの機能として未実装のメソッドを検知する機能が実装されている。\cite{eclipse_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などのエディタからの呼び出しや、 シェル上で呼び出して標準出力の結果を利用することが可能である。 -ソースコード\ref{src:parsedOutputStub.pl}の10行目で実行されている\texttt{findExistsOutputDataGear}はgenerate\_stub.pl内の関数である。 -これはInterfaceの名前とメソッド名を与えると、 Interfaceの定義ファイルのパース結果から出力の有無を確認する動きをする。 -出力がある場合は出力している変数名の一覧を返す。 -ソースコード\ref{src:insertTest1}の例では\texttt{pop2}は\texttt{data}と\texttt{data1}を出力している為、 これらがリストとして関数から返される。 -出力がない場合は偽値を返すために13行目からのif文から先は動かない。 -出力があった場合はgenerate\_stub.plの内部変数に出力する変数名と、 Interfaceの名前の登録を行う。 -生成するStubは命名規則が、 \texttt{\_\_code CodeGearStub\_1}のように末尾に\texttt{\_}に続けて数値をいれる。 -この数値は変換した回数となるため、 この回数の計算を行う。 +\section{GearsOSでのInterfaceを満たすCbCの雛形生成} +GearsOSでも同様のInterfaceの定義から実装するCodeGearの雛形を生成したい。 +LanguageServerの導入も考えられるが、 今回の場合はC言語のLanguageServerをCbC用にまず改良し、 さらにGearsOS用に書き換える必要がある。 +現状のGearsOSが持つシンタックスはCbCのシンタックスを拡張しているものではあるが、これはCbCコンパイラ側には組み込まれていない。 +LanguageServerをGearsOSに対応する場合、 CbCコンパイラ側にGearsOSの拡張シンタックスを導入する必要がある。 +CbCコンパイラ側への機能の実装は、 比較的難易度が高いと考えらる。 +CbCコンパイラ側に手をつけず、 Interfaceの入出力の検査は既存のGearsOSのビルドシステム上に組み込みたい。 + +対してgolangの\texttt{impl}コマンドのように、 シェルから呼び出し標準出力に結果を書き込む形式も考えられる。 +この場合は実装が比較的容易かつ、 コマンドを呼び出して標準出力の結果を使えるシェルやエディタなどの各プラットフォームで使用可能となる。 +先行事例を参考に、コマンドを実行して雛形ファイルを生成するコマンド\texttt{impl2cbc.pl}をGearsOSに導入した。 +\texttt{impl2cbc.pl}の処理の概要を図\ref{fig:impl2cbc}に示す。 -27行目で\texttt{\$generateHaveOutputStub}のlist要素に現在のCodeGearの名前と、 出力に関する情報を代入している。 -現在のCodeGearの名前を保存しているのは、この後のコード生成部分でenumの番号を切り替える必要があるためである。 -ソースコード\ref{src:insertTest1}の例では\texttt{pop2Test}が使うenumを書き換える必要がある為、 ここの\texttt{\$currentCodeGear}はpop2Testとなる。 -ここで作製した\texttt{\$outputStubElem}は、返還後のCbCコードを生成しているフェーズで呼びされる。 - -\subsection{enumの差し替え処理} +\begin{figure}[hp] + \begin{center} + \includegraphics[width=130mm]{drawio/impl2cbc.pdf} + \end{center} + \caption{impl2cbcの処理の流れ} + \label{fig:impl2cbc} + \end{figure} -ソースコード\ref{src:generatePickNext.pl}の箇所は遷移先のenumをPerlスクリプトで生成し、 GearsOSが実行中にenumをcontextに書き込むコードを生成するフェーズである。 -\lstinputlisting[label=src:generatePickNext.pl, caption=Gearefのコード生成部分]{src/generatePickNext.pl} -if文で条件判定をしているが、前者は出力があるケースかどうかのチェックである。 -続く条件式はGearsOSのビルドルールとして静的に書いたstubの場合は変更を加えない為に、 静的に書いているかどうかの確認をしている。 -変数\texttt{\$pick\_next}で継続先のCodeGearの名前を作製している。 -CodeGearの名前は一度目の解析で確認した継続先に\texttt{\_}とカウント数をつけている。 -ここで作製したCodeGearの名前を、3行目でcontextに書き込むCbCコードとして生成している。 +\subsection{雛形生成の手法} + + +Interfaceでは入力の引数がImplと揃っている必要があるが、 第一引数は実装自身のインスタンスがくる制約となっている。 +実装自身の型は、Interface定義時には不定である。 +その為、 GearsOSではInterfaceのAPIの宣言時にデフォルト型変数\texttt{Impl}を実装の型として利用する。 +デフォルト型\texttt{Impl}を各実装の型に置換することで自動生成が可能となる。 -実際に生成された例題をソースコード\ref{src:replaceenum}に示す。 -\lstinputlisting[label=src:replaceenum, caption=enumの番号が差し替えられたCodeGear]{src/replaceenum.cbc} \ No newline at end of file +実装すべき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側を出力する。 + + +\subsection{コンストラクタの自動生成} +雛形生成では他にコンストラクタの生成も行う。 +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側での自動生成はせずに、 雛形生成されたコンストラクタを変更すれば良い。 +あくまで雛形生成スクリプトはプログラマ支援であるため、 いくつかの手動での実装は許容している。
--- a/paper/chapter/04-perl.tex Tue Feb 02 14:41:18 2021 +0900 +++ b/paper/chapter/04-perl.tex Tue Feb 02 15:48:04 2021 +0900 @@ -1,8 +1,9 @@ \chapter{トランスコンパイラによるメタ計算} GearsOSはCbCで実装を行う。 -CbCはC言語よりアセンブラに近い言語であるため、 すべてを純粋なCbCで記述しようとすると記述量が膨大になってしまう。 -またノーマルレベルの計算とメタレベルの計算を、全てプログラマが記述する必要が発生してしまう。 +CbCはC言語よりアセンブラに近い言語である。 +すべてを純粋なCbCで記述すると記述量が膨大になる。 +またノーマルレベルの計算とメタレベルの計算を、全てプログラマが記述する必要がでる。 メタ計算では値の取り出しなどを行うが、 これはノーマルレベルのCodeGearのAPIが決まれば一意に決定される。 したがってノーマルレベルのみ記述すれば、 機械的にメタ部分の処理は概ね生成可能となる。 また、メタレベルのみ切り替えたいなどの状況が存在する。 @@ -71,113 +72,200 @@ -\section{GearsCbCのInterfaceの実装時の問題} +\section{context.hの自動生成} +GearsOSのContextの定義はcontext.hにある。 +ContextはGearsOSの計算で使用されるすべてのCodeGear、 DataGearの情報を持っている。 +context.hではDataGearに対応する\texttt{union Data}型の定義も行っている。 +Data型はCの共用体であり、 Dataを構成する要素として各DataGearがある。 +各DataGearは構造体の形で表現されている。 +各DataGear自体の定義もcontext.hのunion Dataの定義の中で行われている。 + +DataGearの定義はInterfaceファイルで行っていた。 +InterfaceファイルはGearsOS用に拡張されたシンタックスのヘッダファイルを使っており、 直接CbCからロードすることができない。 +その為従来はプログラマが静的にInterfaceファイルをCbCの文脈に変換し、 context.hに構造体に変換したものを書いていた。 +この手法では手書きでの構築のために自由度は高かったが、 GearsOSの例題によっては使わないDataGearも、 context.hから削除しない限りcontextに含んでしまう問題があった。 +さらにInterfaceファイルで定義した型をcontext.hに転記し、それをもとにImplの型を考えてCbCファイルを作製する必要があった。 +これらをすべてユーザーが行うと、ファイルごとに微妙な差異が発生したりとかなり煩雑な実装を要求されてしまう。 +DataGearの定義はInterfaceファイルを作製した段階で決まり、 使用しているDataGear、CodeGearはコンパイル時に確定するはずである。 +使用している各Gearがコンパイル時に確定するならば、 コンパイルの直前に実行されるPerlトランスコンパイラでもGearの確定ができるはずである。 +ここからcontext.hをコンパイルタイミングでPerlスクリプト経由で生成する手法を考案した。 + +\subsection{context.hの作製フロー} +GearsCbCからメタ計算を含むCbCファイルに変換するgenerate\_stub.plは各CbCファイルを1つ1つ呼び出していた。 +context.hを生成しようとする場合、 プロジェクトで利用する全CbCファイルを扱う必要がある。 + +Contextの初期化ルーチンを作製するgenerate\_context.plは、その特性上すべてのCbCファイルをロードしていた。 +したがってcontext.hを作製する場合はこのスクリプトで行うのが良い。 + +Perlのモジュールとして\texttt{Gears::Template::Context}を作製した。 +xv6プロジェクトの場合は一部ヘッダファイルに含める情報が異なる。 + +派生モジュールとして\texttt{Gears::Template::Context::XV6}も実装している。 +これらのテンプレートモジュールはgenerate\_context.plの実行時のオプションで選択可能とした + +\section{メタ計算部分の入れ替え} +GearsOSでは次のCodeGearに移行する前のMetaCodeGearとして、 デフォルトでは\texttt{\_\_code meta}が使われている。 +\texttt{\_\_code meta}はcontextに含まれているCodeGearの関数ポインタを、 enumからディスパッチして次のStub CodeGearに継続するものである。 + +例えばモデル検査をGearsOSで実行する場合、 通常のStub CodeGearのほかに状態の保存などを行う必要がある。 +この状態の保存に関する一連の処理は明らかにメタ計算であるので、 ノーマルレベルのCodeGearではない箇所で行いたい。 +ノーマルレベル以外のCodeGearで実行する場合は、 通常のコード生成だとStubCodeGearの中で行うことになる。 +StubCodeGearは自動生成されてしまうため、 値の取り出し以外のことを行う場合は自分で実装する必要がある。 +しかしモデル検査に関する処理は様々なCodeGearの後に行う必要があるため、 すべてのCodeGearのStubを静的に実装するのは煩雑である。 + +ノーマルレベルのCodeGearの処理の後に、StubCodeGear以外のMeta Code Gearを実行したい。 +Stub Code Gearに直ちに遷移してしまう\texttt{\_\_code meta}以外のMeta CodeGearに、 特定のCodeGearの計算が終わったら遷移したい。 +このためには、特定のCodeGearの遷移先のMetaCodeGearをユーザーが定義できるAPIが必要となる。 +このAPIを実装すると、ユーザーが柔軟にメタ計算を選択することが可能となる。 + +GearsOSのビルドシステムのAPIとして\texttt{meta.pm}を作製した。 +これはPerlのモジュールファイルとして実装した。 +meta.pmはPerlで実装されたGearsOSのトランスコンパイラであるgenerate\_stub.plから呼び出される。 +meta.pmの中のサブルーチンである\texttt{replaceMeta}に変更対象のCodeGearと変更先のMetaCodeGearへのgotoを記述する。 +ユーザーはmeta.pmのPerlファイルをAPIとしてGearsOSのトランスコンパイラにアクセスすることが可能となる。 + +具体的な使用例をコード\ref{src:metapm}に示す。 +meta.pmはサブルーチン\texttt{replaceMeta}が返すリストの中に、特定のパターンで配列を設定する。 +各配列の0番目には、goto metaを置換したいCodeGearの名前を示すPerl正規表現リテラルを入れる。 +コード\ref{src:metapm}の例では、\texttt{PhilsImpl}が名前に含まれるCodeGearを指定している。 +すべてのCodeGearのgotoの先を切り替える場合は\texttt{qr/.*\//}などの正規表現を指定する。 + +\lstinputlisting[label=src:metapm, caption=meta.pm]{src/meta.pm} + +generate\_stub.plはGears CbCファイルの変換時に、 CbCファイルがあるディレクトリにmeta.pmがあるかを確認する。 +meta.pmがある場合はモジュールロードを行う。 +meta.pmがない場合はmeta Code Gearにgotoするものをデフォルト設定として使う。 +各Gode Gearが\texttt{goto文}を呼び出したタイミングでreplaceMetaを呼び出し、 ルールにしたがってgoto文を書き換える。 +変換するCodeGearがルールになかった場合は、 デフォルト設定が呼び出される。 + +\section{別Interfaceからの書き出しを取得する必要があるCodeGear} -Interfaceとそれを実装するImplの型が決定すると、最低限満たすべきCodeGearのAPIは一意に決定する。 -ここで満たすべきCodeGearは、Interfaceで定義したCodeGearと、 Impl側で定義した privateなCodeGearとなる。 -例えばStack Interfaceの実装を考えると、各Implで\texttt{pop}, push, shift, isEmptyなどを実装する必要がある。 +従来のMetaCodeGearの生成では、 別のInterfaceからの入力を受け取るCodeGearのStubの生成に問題があった。 +具体的なこの問題が発生する例題をソースコード\ref{src:insertTest1}に示す。 +\lstinputlisting[label=src:insertTest1, caption=別Interfaceからの書き出しを取得するCodeGearの例]{src/pop2test.cbc} +この例では\texttt{pop2Test}Code Gearから \texttt{stack->pop2}を呼び出し、 継続として\texttt{pop2Test1}を渡している。 +\texttt{pop2Test}自体はStackTest Interfaceであり、 \texttt{stack->pop2}の\texttt{stack}はStack Interfaceである。 +例題ではStack Interfaceの実装はSingleLinkedStackである。 +SingleLinkedStackの\texttt{pop2}の実装をソースコード\ref{src:pop2}に示す。 +\lstinputlisting[label=src:pop2, caption=SingleLinkedStackのpop2]{src/pop2.cbc} +pop2はスタックから値を2つ取得するAPIである。 +pop2の継続は\texttt{next}であり、 継続先に\texttt{data}と\texttt{data1}を渡している。 +data、 data1は引数で受けている\texttt{union Data*}型の変数であり、 それぞれstackの中の値のポインタを代入している。 +この操作でstackから値を2つ取得している。 + + +このコードをgenerate\_stub.pl経由でメタ計算を含むコードに変換する。 +変換した先のコードを\ref{src:pop2meta}に示す。 +\lstinputlisting[label=src:pop2meta, caption=SingleLinkedStackのpop2のメタ計算]{src/pop2meta.cbc} +実際は\texttt{next}は\texttt{goto meta}に変換されてしまう。 +data、data1は\texttt{goto meta}の前にポインタ変数\texttt{O\_data}が指す値にそれぞれ書き込まれる。 +\texttt{O\_data}はpop2のStub CodeGearである\texttt{pop2SingleLinkedStack\_stub}で作製している。 +つまり\texttt{O\_data}はcontext中に含まれているStack Interfaceのデータ保管場所にある変数dataのアドレスである。 +\texttt{pop2}のAPIを呼び出すと、 Stack Interface中の\texttt{data}にStackに保存されていたデータのアドレスが書き込まれる。 + + +当初Perlスクリプトが生成した\texttt{pop2Test1}のstub CodeGearはソースコード\ref{src:pop2stub-origin}のものである。 +CodeGear間で処理されるデータの流れの概要図を図\ref{fig:stackTest1}に示す。 +\lstinputlisting[label=src:pop2stub-origin, caption=生成されたStub]{src/pop2stub-origin.cbc} +\texttt{\_\_code pop2Test}で遷移する先のCodeGearはStackInterfaceであり、 呼び出しているAPIは\texttt{pop2}である。 +pop2で取り出したデータは、 上記で確認した通りContext中のStack Interfaceのデータ格納場所に書き込まれる。 +しかしソースコード\ref{src:pop2stub-origin}の例では\texttt{Gearef(context, StackTest)}でContext中の\texttt{StackTest} Interfaceのdataの置き場所から値を取得している。 +これはInterfaceのImplのCodeGearは、Interfaceから値を取得するというGearsOSのルールの為である。 +現状ではpop2でせっかく取り出した値をStubCodeGearで取得できない。 -従来はプログラマが手作業でヘッダーファイルの定義を参照しながら\texttt{.cbc}ファイルを作成していた。 -手作業での実装のため、 コンパイル時に次のような問題点が多発した。 +ここで必要となってくるのは、 実装しているInterface以外の呼び出し元のInterfaceからの値の取得である。 +今回の例ではStackTest InterfaceではなくStack Interfaceからdata、 data1を取得したい。 +どのInterfaceから呼び出されているかは、 コンパイルタイムには確定できるのでPerlのトランスコンパイラでStub Codeを生成したい。 + +\begin{figure}[h] + \begin{center} + \includegraphics[width=130mm]{drawio/stackTest1.pdf} + \end{center} + \caption{stackTest1のstubの概要} + \label{fig:stackTest1} + \end{figure} + +別Interfaceから値を取得するには別の出力があるCodeGearの継続で渡されたCodeGearをまず確定させる。 +今回の例では\texttt{pop2Test1}が該当する。 +このCodeGearの入力の値と、 出力があるCodeGearの出力を見比べ、 出力をマッピングすれば良い。 +Stack Interfaceのpop2はdataとdata1に値を書き込む。 +pop2Test1の引数はdata, data1, stackであるので、前2つにpop2の出力を代入したい。 + +Contextから値を取り出すのはメタ計算であるStub CodeGearで行われる。 +別Interfaceから値を取り出そうとする場合、 すでにPerlトランスコンパイラが生成しているStubを書き換える方法も取れる。 +しかしStubCodeGearそのものを、 別Interfaceから値を取り出すように書き換えてはいけない。 +これは別Interfaceの継続として渡されるケースと、 次のgoto先として遷移するケースがあるためである。 +前者のみの場合は書き換えで問題ないが、 後者のケースで書き換えを行ってしまうとStubで値を取り出す先が異なってしまう。 +どのような呼び出し方をしても対応できるようにするには、 Stubを別に別ける必要がある。 + + +GearsOSでは継続として渡す場合や、 次のgoto文で遷移する先のCodeGearはノーマルレベルではenumの番号として表現されていた。 +enumが降られるCodeGearは、厳密にはCodeGearそのものではなくStub CodeGearに対して降られる。 +StubCodeGearを実装した分だけenumの番号が降られるため、 \texttt{goto meta}で遷移する際にenumの番号さえ合わせれば独自定義のStubに継続させることが可能である。 +別Interfaceから値を取り出したいケースの場合、 取り出してくる先のInterfaceと呼び出し元のCodeGearが確定したタイミングで別のStubCodeGearを生成する。 +呼び出し元のCodeGearが継続として渡すStubCodeGearのenumを、独自定義したenumに差し替えることでこの問題は解決する。 +この機能をPerlのトランスコンパイラである\texttt{generate\_stub.pl}に導入した。 + +\section{別Interfaceからの書き出しを取得するStubの生成} +別Interfaceからの書き出しを取得する場合、 generate\_stub.plでは次の点をサポートする機能をいれれば実現可能である。 \begin{itemize} - \item CodeGearの入力のフォーマットの不一致 - \item Interfaceの実装のCodeGearの命名規則の不一致 - \item 実装を忘れているCodeGearの発生 + \item goto先のCodeGearが出力を持つInterfaceでかつ継続で渡しているCodeGearが別Interfaceの場合の検知 + \begin{itemize} + \item この場合はgotoしている箇所で渡している継続のenumを、新たに作製したstubのenumに差し替える + \end{itemize} + \item 継続で実行された場合に別にInterfaceから値をとってこないといけないCodeGear自身 + \begin{itemize} + \item Stubを別のInterfaceから値をとる実装のものを別に作製する + \end{itemize} \end{itemize} -特にGearsOSの場合はPerlスクリプトによって純粋なCbCに一度変換されてからコンパイルが行われる。 -実装の状況とトランスコンパイラの組み合わせによっては、 CbCコンパイラレベルでコンパイルエラーを発生させないケースがある。 -この場合は実際に動作させながら、gdb, lldbなどのCデバッガを用いてデバッグをする必要がある。 -またCbCコンパイラレベルで検知できても、すでに変換されたコード側でエラーが出てしまうので、トランスコンパイラの挙動をトレースしながらデバッグをする必要がある。 -Interfaceの実装が不十分であることのエラーは、 GearsOSレベル、最低でもCbCコンパイラのレベルで完全に検知したい。 +\texttt{generate\_stub.pl}内では変換対象のCbCのソースコードを2度読み込む。 +最初の読み込み時に継続の状況を確認し、 2度目の読み込み時に状況を踏まえてコードを生成すれば良い。 +初回の読み込み時にInterface経由の\texttt{goto}文があった場合に、別Interfaceからの出力があるかなどの情報を確認したい。 -\section{Interfaceを満たすコード生成の他言語の対応状況} +\subsection{初回CbCファイル読み込み時の処理} -Interfaceを機能として所持している言語の場合、Interfaceを完全に見たいしているかどうかはコンパイルレベルか実行時レベルで検知される。 -例えばJavaの場合はInterfaceを満たしていない場合はコンパイルエラーになる。 - - -InterfaceのAPIを完全に実装するのを促す仕組みとして、Interfaceの定義からエディタやツールが満たすべき関数と引数の組を自動生成するツールがある。 +Interface経由でのgoto文は\texttt{goto interface->method()}の形式で呼び出される。 +ソースコード\ref{src:parsedOutputStub.pl}はこの形式で来ていた行を読み込んだタイミングで実行される処理である。 +\lstinputlisting[label=src:parsedOutputStub.pl, caption=goto時に使用するinterfaceの解析]{src/parsedOutputStub.pl} -Javaでは様々な手法でこのツールを実装している。 -Microsoftが提唱しているIDEとプログラミング言語のコンパイラをつなぐプロトコルにLanguage Serverがある。 -Language Serverはコーディング中のソースコードをコンパイラ自身でパースし、 型推論やエラーの内容などをIDE側に通知するプロトコルである。 -主要なJavaのLanguage Serverの実装であるeclipse.jdt.ls\cite{eclipse.jdt.ls}では、 LanguageServerの機能として未実装のメソッドを検知する機能が実装されている。\cite{eclipse_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などのエディタから呼び出すことや、 シェル上で呼び出して標準出力の結果を利用することが可能である。 +1行目の正規表現はInterface経由でのgoto文の正規表現パターンである。 +変数\texttt{\$instance}はInterfaceのインスタンスである。正規表現パターンでは\texttt{interface->method}の\texttt{->}の前に来ている変数名に紐づけられる。 +変数\texttt{\$method}はgoto先のInterfaceのAPIである。正規表現パターンでは\texttt{interface->method}の\texttt{->}の後に来ているAPI名である。 +ソースコード\ref{src:insertTest1}の\texttt{pop2Test}では、 \texttt{stack->pop2}の呼び出しをしているため、 \texttt{stack}がインスタンスであり、 \texttt{pop2}がAPIである。 +現在解析しているgoto文が含まれているCodeGearの名前は、変数\texttt{\$currentCodeGear}で別途保存している。 +連想配列である\texttt{\$codeGearInfo}の中には、 各CodeGearで使われている変数と変数の型などの情報が格納されている。 +ソースコード\ref{src:parsedOutputStub.pl}の9行目では、 \texttt{\$codeGearInfo}経由でInterfaceのインスタンスから、具体的にどの型が呼ばれているかを取得する。 +\texttt{pop2Test}では、 インスタンス\texttt{stack}に対応する型名は\texttt{Stack}と解析される。 -\section{GearsOSでのInterfaceを満たすCbCの雛形生成} -GearsOSでも同様のInterfaceの定義から実装するCodeGearの雛形を生成したい。 -LanguageServerの導入も考えられるが、 今回の場合はC言語のLanguageServerをCbC用にまず改良し、 さらにGearsOS用に書き換える必要がある。 -現状のGearsOSが持つシンタックスはCbCのシンタックスを拡張しているものではあるが、これはCbCコンパイラ側には組み込まれていない。 -LanguageServerをGearsOSに対応する場合、 CbCコンパイラ側にGearsOSの拡張シンタックスを導入する必要がある。 -CbCコンパイラ側への機能の実装は、 比較的難易度が高いと考えらる。 -CbCコンパイラ側に手をつけず、 Interfaceの入出力の検査は既存のGearsOSのビルドシステム上に組み込みたい。 - -対してgolangの\texttt{impl}コマンドのように、 シェルから呼び出し標準出力に結果を書き込む形式も考えられる。 -この場合は実装が比較的容易かつ、 コマンドを呼び出して標準出力の結果を使えるシェルやエディタなどの各プラットフォームで使用可能となる。 -先行事例を参考に、コマンドを実行して雛形ファイルを生成するコマンド\texttt{impl2cbc.pl}をGearsOSに導入した。 -\texttt{impl2cbc.pl}の処理の概要を図\ref{fig:impl2cbc}に示す。 +ソースコード\ref{src:parsedOutputStub.pl}の10行目で実行されている\texttt{findExistsOutputDataGear}はgenerate\_stub.pl内の関数である。 +これはInterfaceの名前とメソッド名を与えると、 Interfaceの定義ファイルのパース結果から出力の有無を確認する動きをする。 +出力がある場合は出力している変数名の一覧を返す。 +ソースコード\ref{src:insertTest1}の例では\texttt{pop2}は\texttt{data}と\texttt{data1}を出力している為、 これらがリストとして関数から返される。 +出力がない場合は偽値を返すために13行目からのif文から先は動かない。 +出力があった場合はgenerate\_stub.plの内部変数に出力する変数名と、 Interfaceの名前の登録を行う。 +生成するStubは命名規則は、 Stubの本来のCodeGearの名前の末尾に\texttt{\_}に続けて数値をいれる。 +\texttt{\_\_code CodeGearStub}の場合は、 \texttt{\_\_code CodeGearStub\_1}となる。 +この数値は変換した回数となるため、 この回数の計算を行う。 -\begin{figure}[hp] - \begin{center} - \includegraphics[width=130mm]{drawio/impl2cbc.pdf} - \end{center} - \caption{impl2cbcの処理の流れ} - \label{fig:impl2cbc} - \end{figure} +27行目で\texttt{\$generateHaveOutputStub}のlist要素に現在のCodeGearの名前と、 出力に関する情報を代入している。 +現在のCodeGearの名前を保存しているのは、この後のコード生成部分でenumの番号を切り替える必要があるためである。 +ソースコード\ref{src:insertTest1}の例では\texttt{pop2Test}が使うenumを書き換える必要がある為、 ここの\texttt{\$currentCodeGear}はpop2Testとなる。 +ここで作製した\texttt{\$outputStubElem}は、返還後のCbCコードを生成しているフェーズで呼びされる。 + +\subsection{enumの差し替え処理} -\subsection{雛形生成の手法} - - -Interfaceでは入力の引数がImplと揃っている必要があるが、 第一引数は実装自身のインスタンスがくる制約となっている。 -実装自身の型は、Interface定義時には不定である。 -その為、 GearsOSではInterfaceのAPIの宣言時にデフォルト型変数\texttt{Impl}を実装の型として利用する。 -デフォルト型\texttt{Impl}を各実装の型に置換することで自動生成が可能となる。 +ソースコード\ref{src:generatePickNext.pl}の箇所は遷移先のenumをPerlスクリプトで生成し、 GearsOSが実行中にenumをcontextに書き込むコードを生成するフェーズである。 +\lstinputlisting[label=src:generatePickNext.pl, caption=Gearefのコード生成部分]{src/generatePickNext.pl} +if文で条件判定をしているが、前者は出力があるケースかどうかのチェックである。 +続く条件式はGearsOSのビルドルールとして静的に書いたstubの場合は変更を加えない為に、 静的に書いているかどうかの確認をしている。 +変数\texttt{\$pick\_next}で継続先のCodeGearの名前を作製している。 +CodeGearの名前は一度目の解析で確認した継続先に\texttt{\_}とカウント数をつけている。 +ここで作製したCodeGearの名前を、3行目でcontextに書き込むCbCコードとして生成している。 -実装すべき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側を出力する。 - - -\subsection{コンストラクタの自動生成} -雛形生成では他にコンストラクタの生成も行う。 -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側での自動生成はせずに、 雛形生成されたコンストラクタを変更すれば良い。 -あくまで雛形生成スクリプトはプログラマ支援であるため、 いくつかの手動での実装は許容している。 +実際に生成された例題をソースコード\ref{src:replaceenum}に示す。 +\lstinputlisting[label=src:replaceenum, caption=enumの番号が差し替えられたCodeGear]{src/replaceenum.cbc}
--- a/paper/chapter/abstract.tex Tue Feb 02 14:41:18 2021 +0900 +++ b/paper/chapter/abstract.tex Tue Feb 02 15:48:04 2021 +0900 @@ -8,7 +8,8 @@ 証明を利用して信頼性を保証する定理証明は、 AgdaやCoqなどの定理証明支援系を利用することになる。 支援系を利用する場合、各支援系でOSを実装しなければならない。 証明そのものは可能であるが、 支援系で証明されたソースコードがそのままOSとして動作する訳ではない。 -証明されたコードと、実際に動作するOSを記述するC言語などのプログラミング言語の間にはギャップが存在し、 Cでの実装時に入ってしまうバグを取り除くことはできない。 +証明されたコードと、実際に動作するOSを記述するC言語などのプログラミング言語の間にはギャップが存在する。 +Cでの実装時に入るバグは取り除けない。 このためには定理証明されたコードを等価なC言語などに変換する処理系が必要となる。 信頼性を保証するほかの方法として、プログラムの可能な実行をすべて数え上げて仕様を満たしているかを確認するモデル検査がある。
--- a/paper/master_paper.tex Tue Feb 02 14:41:18 2021 +0900 +++ b/paper/master_paper.tex Tue Feb 02 15:48:04 2021 +0900 @@ -97,8 +97,8 @@ \input{chapter/01-introduction.tex} \input{chapter/02-cbc.tex} \input{chapter/03-gears.tex} +\input{chapter/04-interface.tex} \input{chapter/04-perl.tex} -\input{chapter/04-interface.tex} \input{chapter/conclusion.tex}