rider.お品書きサクッと書いて公開して、後でちまちま修正する事がママあります。なんじゃこりゃ?と思ったら、数日してから見て貰えると嬉しいです。「スクラムの実践方法を分かりやすくマンガも交えて解説。2017」の5日目です。いうか、今のところこのカテゴリに私しかエントリーしてないので、漏れた日を埋めていこうかな、ぐらいの感じです。IL2Cのダイジェストをやっていこうかと思います。(イメージをクリックするとYouTubeが開きます)#6-13これは、いわゆる型推論のような何かとか、CPUのレジスタリネーミングのような何かでは?みたいな事を考えながら、理論的なことはわからないので実コードとして実装可能なように問題を整理していました。ここで、スタックスロットをシンボルとして抽象化した時に、単にリネーミングするとC言語に落としたときに型が合わなくてコードが成立しないので、その場合はそれぞれの型に合わせたコードを出力する必要があります。問題の核心にたどり着きました。#6-14ILConverterの構造に手を入れて、全体的に修正したコードの解説を行いました。前回わかった問題を、どうやって対処するのか、ですが...ブランチ命令によって分割される「実行パス」を認識して、それぞれのパスをキューに入れ、実行パス分岐後の流入箇所で想定される評価スタックのスロット(ここではインデックスと読んでいる)の型が一致しているかどうかを確認し、一致していればC言語上でもコードが一致するので単にgotoで飛ばし、そうでないなら、新たな型を使うが同じようなコードとして出力し直す、ということをします。この部分、スタックスロットのコンシューマーが、ブランチ直前と遷移先で、どこまでと規定されるのかを機械的に判断するのが難しく、現在(#50)での実装も少し手抜きになっています(複雑というだけで不可能ではない)。緊張しましたが、正しく期待通りに変換されました。Cコンパイラはこの複雑に見えるコードをちゃんと見抜いています。:)変換中の情報をDecodeContextクラスで保持するスタックとスタックの各スロットに格納されることを想定している型の情報を保持するそれぞれのスタックスロットに対して、個別のシンボル名を割り当てる(型の異なるローカル変数を区別可能にする)という登場人物を図示して、実際のコードに落としていきます。あと、goto文を成立させるためにラベルをどうやって付けるか、という問題を考えています。今考えると、関数で遅延評価すれば良いよなと思うんですが、C#で書くと(ちょっとしたことではあるんですが)モヤモヤして、結局小さい入れ物を作って対処しました。2017」の4日目です。いうか、今のところこのカテゴリに私しかエントリーしてないので、漏れた日を埋めていこうかな、ぐらいの感じです。IL2Cのダイジェストをやっていこうかと思います。(イメージをクリックするとYouTubeが開きます)#6-10前回から引き続き、フロー解析(実行パス解析)の検討です。ここら辺で、評価スタックの同じインデックスに異なる型が使用される可能性について気がついた感じです。:)#6-11フローについて本格的に解析が必要になり、今まではILConverterで変換した結果を一方的に変換処理に垂れ流ししていたのですが、実行パス(解析開始位置からブランチ先までの一連のopcodeシーケンス)毎に「フローの束」として順次解析を行う事が出来るように、地味ですが重要なリファクタリングを行っていきます。#6-12引き続き、フロー解析のコア部分を検討しながら実装しています。評価スタックの同一インデックスが異なる型で使われる場合のCソースコード上の表現方法について、ホワイトボードにあるように異なるローカル変数として宣言して使い分ける、という方針で実装することになりました。2017」の3日目です。いうか、今のところこのカテゴリに私しかエントリーしてないので、漏れた日を埋めていこうかな、ぐらいの感じです。IL2Cのダイジェストをやっていこうかと思います。world的な最初のトライがInt32の計算だったので、まずはInt64を扱うことで、どのような差異が生まれるのかを確認しました。オペレータになる)なのですが、リテラルの数値に”LL”を付けたり、関数引数の型に応じてローカル変数の型を変える必要があり、変換用コンテキストとして保持している情報にパラメータ群も保持するように改造しました。このあたりではまだ評価スタックは、直前にPushされた値に対応する式を直接文字列で格納していたりして、ある意味のどかな感じです。:)前回までで評価スタックに式を文字列で格納するのは問題がありそうだ、ということは分かったのですが、具体的に何が問題になりそうなのかはまだグレーだったので、簡単なILから順に考えて問題の焦点を考察しました。デコーダー(とILConverterの前後)で、ブランチ命令の分析が必要ではないかという事が臭ってきます。評価スタックにPushする値(の型)は自由であり型の制約がないため、フロー解析(実行パス解析)を行わないと、どの型に対応するのかが分からないからです。#6-9前回から引き続き、フロー解析のアルゴリズムの検討です。この頃はまだ、文字列で表現された式でどうやってうまくやるかという事に執着していたような気がします。細かいパターンを詳細に検討します。2017」の二日目です。いうか、今のところこのカテゴリに私しかエントリーしてないので、漏れた日を埋めていこうかな、ぐらいの感じです。IL2Cのダイジェストをやっていこうかと思います。(イメージをクリックするとYouTubeが開きます)#6-4ILConverterという抽象クラスを導入して、OpCodeに対応する変換の実装をクラスで表現できるようにしました。ILConverterを動的に収集して、ILConverterを実装した具象クラスを定義するだけで、OpCodeのデコーダーへの紐付けを動的に処理できるようにしました。現在の実装(#6-48)でもそうですが、どうにかして「OpCodeと対応する変換処理」と「オペランドの扱い」を分離したいと言うバックグラウンドが垣間見えます。理由は、分離したほうがテストがしやすいのではないかとか、そういう事を考えていたと思います。今は...?そう言えば、まだ録画機材とかで色々トラブルとか試行錯誤とかしていた頃で、(YouTubeに上げて正常公開されたにもかかわらず)ちょっと変な気がします。H264のストリームが壊れてるのかもしれません。生ファイルは持っているので再エンコードして入れ替えたいのですが、今のYouTubeの仕様では後から動画の差し替えが出来ないんですよね。machine」という、Androidのゲームを紹介しています。このゲームは、ILやバイトコードのような低レベル言語がどのように動作するのかを、パズルのような遊び感覚で知ることが出来るものです。開発をやらない人によっては、このゲームの由来がそういう所から来ている事すら気が付かない人もいると思います。面白いのでおすすめです。後半は、ILConverterの実装が増えてくることを考えて、ILConverterの変換結果をテストできるように、ユニットテストを実装する方法と実際の実装をやってみました。テストにはNUnitを使っています。ユニットテストはしばらくこのインフラを使っていきますが、現在の実装においては機能していません。途中での大幅に構造に手を入れた事があり、今後もまだ構造に変更が加わらないかどうかがまだ見えていないため、この修正は保留しています。world的な変換が出来て、テストコードも書けたので、今後の大まかな方針をまとめました。その上で、Int32を扱ったので、Int64にしたらどうなるのかを検証しなから対応しました。題材として丁度良かったので、徐々にTDDでやるようにしていきました。:)そして、いよいよVC++のプロジェクトを作り、そこに変換されたCソースコードを突っ込んで、正しく動作する事を確認しました。ここで、生成された機械語コードが「想定通り」全部静的解決されて、計算が直値に置き換わり、しかも関数呼び出しもインライン化されて、たった1命令に短縮されたことを確認しました。2017」の一日目です。いうか、今のところこのカテゴリに私しかエントリーしてないので、漏れた日を埋めていこうかな、ぐらいの感じです。IL2Cのダイジェストをやっていこうかと思います。world的なコードがどのようなILにコンパイルされるのかを解説しています。一応、メタプログラミング初心者とか、IL初心者とかを想定して、割りと細かいところまで解説しています。#6-2前回ILSpyで確認したILを、リフレクションを使用してILの情報をプログラマブルに取得するためには、どのような事をすれば良いのかを解説しています。ライブ配信ではありませんが、目の前で一から書いてデバッグして見せることで、感触をつかみやすくしました。この回で、ILのバイトコードデコーダーの雛形を作りました。#6-3画像の通り、極初期の、main関数っぽい何か(全然C言語コードではありませんが)を、デコーダーを使って出力する所までを実装しました。これがIL2Cの内部骨格となって、あとはOpCodeをどう変換するかという、要するにスタート地点に立ったことになります。1;のようなコードに変換されて欲しい、というOpCodeとC言語の対応付けを確認しました。このまとめを書いたことで思い出したのですが、既に#6-3の時点で評価スタックの型の追跡が必要なのではないかという事に気がついていましたね。milestone3IL2Cの短信です。milestone3となる、ValueTypeのサポートを(限定的ですが)行いました。UNOのCコンパイラを使って実際に極小環境で動かしました。TokyoのUnplugged枠で登壇。この時点までのfeatureの解説などをしました。ValueType(構造体)のフィールドとメソッドをサポート。スタティックとインスタンスメンバの両方共に変換出来ます。スタティックメンバはCソースコード上、グローバル変数とグローバル関数として変換し、インスタンスフィールドは構造体、インスタンスメソッドはグローバル関数として変換。名前空間と型名についてのマングリングの導入。Xamarin”に、IL2Cを寄稿させて頂きました。Confの発表分です。ビデオシリーズではすべて網羅していますが、その中でも印象深かったトピックについて、書籍化で解説しやすい部分を抜粋してあります。締切がギリギリでしたが掲載を快諾していただいたXamaritansのメンバーの方々に感謝します。milestone2IL2Cの短信です。milestoneを決めてやっているわけではないのですが、一区切りついたのでmilestone2ということにしておこう。(#6-16)プリミティブ型のプラットフォーム依存性を回避したいので、stdint.hを使うようにする。64ビット整数の足し算のサポート(今後の方向性検証のため)。スタックの高度な再利用を含むコードの変換が出来る。ILの実行パス解析に型推論が必要(知識ないのでもっと直接的な手法で実装した)とか。今の方法にも積み残しがあるけど。無条件・条件ジャンプに対応。上が出来たので、必然的にこれも出来るようになった。IL2CIL2Cと言うのは、Unityで言うところのIL2CPPのC言語版です。これをフルスクラッチで作ってみるというビデオシリーズです。C#(というか、それをコンパイルしたILを含むDLLやEXE)を入力に、C言語のソースコードを生成するツール、というわけです。local3;}というC言語のソースコードに変換できる所まで出来ました。int64_t)も出来たところです。テストまで含めて、全体を一からフルスクラッチでやってるところを、ほぼカット無しで収録しています。メタプログラミングに興味のある方は視聴してみてください。Machineというゲームをちょっとだけ紹介しています。IL(というかアセンブリ言語)をゲーム感覚で学べるアプリでお薦めです。勉強会でやってきました!前回色々進行がダメだったので仕切り直しと言う事で、最初に少し解説を加えたりしてみました。各自で解いてみるという形式で進行しました。その前に導入として:「System.Reflection.Emit」名前空間を覚えておくこと。System.Reflection.Emit.OpCodesクラスがあり、ここにILのオプコード(OpCode)が定義されているよ。OpCodeとはCLRが理解できる中間言語(バイトコード)OpCodeにはいくつか種類がある事。スタック操作・スタックマシンとは:CLRがバイトコード由来で動作するための基礎となる構造で、JVMのような類似技術もある。リアルなCPUにはあまり採用されていない事。実践して覚える今回も、前回と同様GitHubのテンプレートとなるコードを元に課題にチャレンジします。Part5では、インスタンスメソッドを扱い、その後のPartでクラス内のフィールドへのアクセス等を扱う予定でしたが、時間足らず。スライドは上げておくので、残りのPartを「忘れないうちに」取り組んでみる事をお勧めします。Languageを覚えることの意義ILの基本と言う事で2回も長時間セッションを行ったわけですが、ILを覚えたところで一体何の役に立つのか?と言う疑問を持つ方もいるかも知れません。ILをやっていて重要だなと思うポイントを2点挙げておきます。動的コード生成技術(式木・IL等)でもやりましたが、CLRには大別して「参照型」と「値型」という2つの型の相違があります。これらはCLR内でのメモリの扱われ方が違う上、IL上でも異なる扱われ方をされます。参照型の値と、値型の値と、値型が参照される場合、など、IL上でも正しく区別が必要です。これらの区別がC#やVB.netでも正しく出来ていない・苦手という人は多いと思いますが、ILでどのように扱うのかがしっかり理解できれば(しかもこれは単純な法則でもあるので、抽象的な概念などは不要で実は覚えやすい)、C#などでコードを書く場合でも、自信をもって書けるはずです。これが分かると、ボクシング(Boxing)・アンボクシング(Unboxing)のコストと、これらがいつ生じるのか、と言う事が正しく理解できます。気が付かないうちにこれらを発生させてしまい、パフォーマンスの低下を招くという事を回避できるようになるはずです。普段はコンパイラが自動的によしなにやってくれる、アセンブリメタデータの構造についての直観的な理解が得られます。C#の言語構造はこうなっているのか、の(すべてではありませんが)ILから見た姿が分かります。記述した型がどこでどのように使われるのか、あるいはメソッドの定義はどこからやってくるのか、ILとどのように関連付けされるのかが分かります。これが分かると、どんな場合にアセンブリを分割すべきなのか、あるいはリフレクションを使用して解決すべき課題なのかそうではないのか、と言ったことを判断できます。いった判断は、最終的にシステム全体の構造にも影響を与える可能性があるので、システム設計を行う上では無視できない要素の一つだと思います。学問で言えば本当に基礎的な部分に相当するので、これを習得するメリットが見えにくいのは事実ですが、覚えておいて損はないと思います。大体、ILなんて「単純」なので、難しそうで実は簡単なんですよ!(きわめて抽象度の高い概念を覚える事に比べたら、どんな人でも覚えれる可能性があります)。IL!!それではまた。CLR年末会で登壇してきました。CLRも第五回で、毎回参加の方と新しく参加の方で程よく盛り上がってうれしい感じです。ChalkTalkもそうですが、遠方からわざわざ参加して頂いた方もありがとうございます。来年も継続して行きたいと思います。今回のネタは結構前から決めていたものの、年末の忙しさの為に十分な時間が取れず、直前に方針を決めてバタバタしてしまいました。猛省...と言う事だけではないんですが、ちょっとセッションの進行を変えてみました。動的コード生成技術(式木・IL等)」と題してディスカッションを行ったのですが、今回は本会でその展開をしようと思ったのです。ILを説明したのでは、実際にILを使う具体的なシチュエーションでも提起できない限り、右から左へ流れていってしまうだけだと考えて、ハンズオンのような形式で進行してみました。Emitが可能なコードを一から書いていると、なかなか本題にたどり着けないため、GitHubに元ネタとなるコードを用意しておき、Emitコードから書き始めれられるようにしました。コードをILで動的に生成するヘルパークラスです。Disposeメソッドです。引数と戻り値を指定可能なメソッドを定義します。結果Console.WriteLine(result);}}}Emitterクラスは、System.Reflection.Emitの煩雑なコードを隠ぺいするために定義しています。課題の多くはMainメソッド内のラムダブロックのEmitメソッドをいじります。このコードはnetcore5向けでも実行可能にしてあります。課題はPart1~Part10まで用意し、はじめに少しだけ「スタックマシン」についての解説を行いました。引数の値をToStringして返すようにするGitHubのコードを軽く説明して、Emit本体のコードを課題に従って書き換えてもらいました。coder!”と言う文字列を返すだけのものですが、引数で与えられたintの値を文字列に変換して返すのが課題です。実はこれ、大変な罠が...やるべきことは、スライド通り大きく3つあります。引数の値はどうやって入手するかヒントに書いておいたのですが、「OpCodesクラス」を見ると、IL命令の一覧が確認できます。スタックに値を積む(プッシュ)するのは、「Ld~」で始まる命令であることも説明しました。OpCodesの一覧を見ていると、「Ldarg_0」命令が見つかります。これを使うと、引数の最初の値をスタックに積みます。「Ldarg」や「Ldarg_S」命令でもOKです。その場合は、Emitメソッドの引数にインデックス(0ですが)を指定します。staticメソッドの呼び出しメソッドを呼び出すには、「Call」命令を使います。その時、Emitする引数に、呼び出すメソッドの「メソッド情報(MethodInfo)」が必要です。これには、リフレクションを使います。Type.EmptyTypes);ToStringのオーバーロードは複数あるので、正しいメソッド情報を選択するために、Type.EmptyTypesフィールドを使って、「引数0」のオーバーロードを選択させています(このフィールドを使わなくても、0個の配列でもOKです)。typeofを使うことに対して、一部の方がモヤモヤしていた(チートっぽい)ようです。厳密に0からInt32のTypeを取得したことにならないのではないかとの事で、確かにその通り。typeofを使うと、C#コンパイラがコンパイル時にInt32を解決しようとします。実行時に0から取得する事も不可能ではないのですが、その話をするとまた話がずれていくので、今は諦めてもらう事に。そこで、「ILSpy」を使って、C#で書いた等価コードを逆アセンブルした所、引数をスタックに積む際に、「Ldarga」命令を使っていました。orzLdarga命令は、対象の値そのものではなく、その値を示す参照(ポインタ)をスタックに積みます。これは、対象の値が「値型(ValueType)」であり、Callでそのメソッドに遷移した先ではthis参照として扱われなければならないため、Ldarg命令ではなく、Ldarga命令を使う必要があったのです。Int32の場合は値型と言っても、なぜ参照化しなければならないのか、ピンと来ないかもしれません。hoge.Cは、123か3か?Console.WriteLine(hoge.C);Computeメソッドはインスタンスメソッドなので、内部でthis参照が使えます。this参照を使った場合、そのインスタンスは、呼び出し元のhogeとなるはずです。Computeメソッドから呼び出し元が保持するインスタンスにアクセスさせるには、文字通り「参照」が必要なのです。従って、ここではLdarga命令を使って、インスタンスへの参照を渡す必要があります。対象が参照型(クラス)であれば、Ldarg命令で問題ありません。NullReferenceExceptionがスローされたのは、本当に偶然のようで、真の原因は分かりません。足りない!!そんなわけで、このPart1を30分ぐらいでやるつもりだったのが、これだけで時間使い切ってしまいました。値型と参照型の違いは、実際にはPart6ぐらいでやってもらう予定だったのが、こんな所でやる事になったのが敗因...時間を延長して続けるかどうかという話もあったのですが、他セッションもあったため、次回に持ち越しとなりました。次回に同じ題目で再チャレンジします。スライドはその時まで正式公開はしません。次回も同様に参加する場合は、Part1の内容の復習をしておいてくだしあ。́Д`)でも楽しかったです。Fifteen.%d人のブロガーが「いいね」をつけました。