IL2C短信: milestone3

IL2Cの短信です。milestone3となる、ValueTypeのサポートを(限定的ですが)行いました。

YouTube: Playlist: Making archive IL2C
GitHub: IL2C step-by-step design project


IL2Cをmicro:bitで動かす (#6-24・#6-25)

  • #6-24・#6-25の時点のIL2Cを使用して、micro:bitArduino UNOのCコンパイラを使って実際に極小環境で動かしました。

Making Archive IL2C at .NET Conf 2017 Tokyo (#6-28)

namespace il2c_test_target
{
    public class Hoge1
    {
        public static int Add1(int a, bool isTwo)
        {
            return a + (isTwo ? 2 : 1);
        }
    }
}
int32_t il2c_test_target_Hoge1_Add1(int32_t a, bool isTwo)
{
    int32_t local0;

    int32_t __stack0_int32_t;
    int32_t __stack1_int32_t;

    __stack0_int32_t = a;
    __stack1_int32_t = isTwo ? 1 : 0;
    if (__stack1_int32_t != 0) goto L_0000;
    __stack1_int32_t = 1;
    goto L_0001;
L_0000:
    __stack1_int32_t = 2;
L_0001:
    __stack0_int32_t = __stack0_int32_t + __stack1_int32_t;
    local0 = __stack0_int32_t;
    goto L_0002;
L_0002:
    __stack0_int32_t = local0;
    return __stack0_int32_t;
}

milestone3 (#6-37)

  • byte, sbyte, short, ushort, boolのサポート。
  • ValueType(構造体)のフィールドとメソッドをサポート。スタティックとインスタンスメンバの両方共に変換出来ます。
    スタティックメンバはCソースコード上、グローバル変数とグローバル関数として変換し、インスタンスフィールドは構造体、インスタンスメソッドはグローバル関数として変換。
  • 名前空間と型名についてのマングリングの導入。
  • 値を返さないメソッド(void)のret OpCodeのサポート
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace il2c_test_target
{
    public struct Hoge3
    {
        public static int Value1 = 123;
        public int Value2;

        public int GetValue2(int a, int b)
        {
            return this.Value2 + a + b;
        }
    }

    public class Hoge4
    {
        public static int Test4()
        {
            var hoge3 = new Hoge3();
            hoge3.Value2 = 456;

            return hoge3.Value2;
        }

        public static int Test5()
        {
            var hoge3 = new Hoge3();
            hoge3.Value2 = 789;

            var result = hoge3.GetValue2(123, 456);
            return result;
        }
    }
}
#ifndef __MODULE_il2c_test_target__
#define __MODULE_il2c_test_target__

#include <stdint.h>
#include <string.h>
#include <stdbool.h>

typedef struct il2c_test_target_Hoge3
{
    int32_t Value2;
} il2c_test_target_Hoge3;

extern int32_t il2c_test_target_Hoge3_Value1;

extern int32_t il2c_test_target_Hoge3_GetValue2(il2c_test_target_Hoge3* __this, int32_t a, int32_t b);
extern int32_t il2c_test_target_Hoge4_Test4(void);
extern int32_t il2c_test_target_Hoge4_Test5(void);

#endif
#include "il2c_test_target.h"

int32_t il2c_test_target_Hoge3_Value1 = 123;

int32_t il2c_test_target_Hoge3_GetValue2(il2c_test_target_Hoge3* __this, int32_t a, int32_t b)
{
    int32_t local0;

    il2c_test_target_Hoge3* __stack0_il2c_test_target_Hoge3_reference;
    int32_t __stack0_int32_t;
    int32_t __stack1_int32_t;

    __stack0_il2c_test_target_Hoge3_reference = __this;
    __stack0_int32_t = __stack0_il2c_test_target_Hoge3_reference->Value2;
    __stack1_int32_t = a;
    __stack0_int32_t = __stack0_int32_t + __stack1_int32_t;
    __stack1_int32_t = b;
    __stack0_int32_t = __stack0_int32_t + __stack1_int32_t;
    local0 = __stack0_int32_t;
    goto L_0000;
L_0000:
    __stack0_int32_t = local0;
    return __stack0_int32_t;
}

int32_t il2c_test_target_Hoge4_Test4(void)
{
    il2c_test_target_Hoge3 local0;
    int32_t local1;

    il2c_test_target_Hoge3* __stack0_il2c_test_target_Hoge3_reference;
    il2c_test_target_Hoge3 __stack0_il2c_test_target_Hoge3;
    int32_t __stack0_int32_t;
    int32_t __stack1_int32_t;

    __stack0_il2c_test_target_Hoge3_reference = &local0;
    memset(__stack0_il2c_test_target_Hoge3_reference, 0x00, sizeof(il2c_test_target_Hoge3));
    __stack0_il2c_test_target_Hoge3_reference = &local0;
    __stack1_int32_t = 456;
    __stack0_il2c_test_target_Hoge3_reference->Value2 = __stack1_int32_t;
    __stack0_il2c_test_target_Hoge3 = local0;
    __stack0_int32_t = __stack0_il2c_test_target_Hoge3.Value2;
    local1 = __stack0_int32_t;
    goto L_0000;
L_0000:
    __stack0_int32_t = local1;
    return __stack0_int32_t;
}

int32_t il2c_test_target_Hoge4_Test5(void)
{
    il2c_test_target_Hoge3 local0;
    int32_t local1;
    int32_t local2;

    il2c_test_target_Hoge3* __stack0_il2c_test_target_Hoge3_reference;
    int32_t __stack0_int32_t;
    int32_t __stack1_int32_t;
    int32_t __stack2_int32_t;

    __stack0_il2c_test_target_Hoge3_reference = &local0;
    memset(__stack0_il2c_test_target_Hoge3_reference, 0x00, sizeof(il2c_test_target_Hoge3));
    __stack0_il2c_test_target_Hoge3_reference = &local0;
    __stack1_int32_t = 789;
    __stack0_il2c_test_target_Hoge3_reference->Value2 = __stack1_int32_t;
    __stack0_il2c_test_target_Hoge3_reference = &local0;
    __stack1_int32_t = 123;
    __stack2_int32_t = 456;
    __stack0_int32_t = il2c_test_target_Hoge3_GetValue2(__stack0_il2c_test_target_Hoge3_reference, __stack1_int32_t, __stack2_int32_t);
    local1 = __stack0_int32_t;
    __stack0_int32_t = local1;
    local2 = __stack0_int32_t;
    goto L_0000;
L_0000:
    __stack0_int32_t = local2;
    return __stack0_int32_t;
}

IL2Cプロジェクト – Extensive Xamarin

技術書典3で配布予定の”Extensive Xamarin”に、IL2Cを寄稿させて頂きました。
是非、皆さん入手して読んでください :)

範囲はおおよそ.NET Confの発表分です。ビデオシリーズではすべて網羅していますが、その中でも印象深かったトピックについて、書籍化で解説しやすい部分を抜粋してあります。
締切がギリギリでしたが掲載を快諾していただいたXamaritansのメンバーの方々に感謝します。

何故Xamarin本に寄稿したのかですが、#6-38で喋っています:

IL2C短信: milestone2

IL2Cの短信です。milestoneを決めてやっているわけではないのですが、一区切りついたのでmilestone2ということにしておこう。

YouTube: Playlist: Making archive IL2C
GitHub: IL2C step-by-step design project


milestone1 (#6-4)

Hello world的なシンプルコードの変換が出来る:

public static int main()
{
  var a = 1;
  var b = 2;
  var c = a + b;
  return c;
}
int main(void)
{
  int local0;
  int local1;
  int local2;
  int local3;

  local0 = 1;
  local1 = 2;
  local2 = local1 + local0;
  local3 = local2;
  return local3;
}

milestone2 (#6-16)

  • プリミティブ型のプラットフォーム依存性を回避したいので、stdint.hを使うようにする。
  • 64ビット整数の足し算のサポート(今後の方向性検証のため)。
  • スタックの高度な再利用を含むコードの変換が出来る。
    これが一番大変だった… ILの実行パス解析に型推論が必要(知識ないのでもっと直接的な手法で実装した)とか。今の方法にも積み残しがあるけど。
  • 無条件・条件ジャンプに対応。
    上が出来たので、必然的にこれも出来るようになった。
public static long main()
{
  var a = 1L;
  var b = 2L;
  var c = a + b;
  return c;
}
#include <stdint.h>

int64_t main(void)
{
  int64_t local0;
  int64_t local1;
  int64_t local2;
  int64_t local3;

  int32_t __stack0_int32_t;
  int64_t __stack0_int64_t;
  int64_t __stack1_int64_t;

  __stack0_int32_t = 1;
  __stack0_int64_t = (int64_t)__stack0_int32_t;
  local0 = __stack0_int64_t;
  __stack0_int32_t = 2;
  __stack0_int64_t = (int64_t)__stack0_int32_t;
  local1 = __stack0_int64_t;
  __stack0_int64_t = local0;
  __stack1_int64_t = local1;
  __stack0_int64_t = __stack0_int64_t + __stack1_int64_t;
  local2 = __stack0_int64_t;
  __stack0_int64_t = local2;
  local3 = __stack0_int64_t;
  goto L_000b;
L_000b:
  __stack0_int64_t = local3;
  return __stack0_int64_t;
}

この冗長なコードは、VC++のReleaseビルドで2命令に短縮されます:

ビデオネタ: IL2C

IL2Cと言うのは、Unityで言うところのIL2CPPのC言語版です。これをフルスクラッチで作ってみるというビデオシリーズです。
つまり、C#(というか、それをコンパイルしたILを含むDLLやEXE)を入力に、C言語のソースコードを生成するツール、というわけです。

YouTube: Playlist: Making archive IL2C
GitHub: IL2C step-by-step design project

現在第6回までやったところですが、以下のようなC#のコード:

public static int main()
{
  var a = 1;
  var b = 2;
  var c = a + b;
  return c;
}

が、

#include <stdint.h>
int32_t main(void)
{
  int32_t local0;
  int32_t local1;
  int32_t local2;
  int32_t local3;

  local0 = 1;
  local1 = 2;
  local2 = local1 + local0;
  local3 = local2;
  return local3;
}

というC言語のソースコードに変換できる所まで出来ました。また、これの64ビット(long, int64_t)も出来たところです。

開発の方法の検討・設計・実装・テストまで含めて、全体を一からフルスクラッチでやってるところを、ほぼカット無しで収録しています。メタプログラミングに興味のある方は視聴してみてください。

この調子で続けると、40回~50回ぐらいで完成かな… 先は長そうだ :)

あと、途中でHuman Resource Machineというゲームをちょっとだけ紹介しています。IL(というかアセンブリ言語)をゲーム感覚で学べるアプリでお薦めです。

真・ILのキホン – 第六回 Center CLR 勉強会

WP_20160319_11_10_36_Pro_LIILについての勉強会を、第六回 Center CLR 勉強会でやってきました!

前回色々進行がダメだったので仕切り直しと言う事で、最初に少し解説を加えたりしてみました。


導入

il1扱っているネタが極めて低レベルと言う事もあり、前回同様出題内容を各自で解いてみるという形式で進行しました。
その前に導入として:

  • 「System.Reflection.Emit」名前空間を覚えておくこと。
    System.Reflection.Emit.OpCodesクラスがあり、ここにILのオプコード(OpCode)が定義されているよ。
  • OpCodeとは
    CLRが理解できる中間言語(バイトコード)
  • OpCodeにはいくつか種類がある事。
  • スタック操作・スタックマシンとは:
    CLRがバイトコード由来で動作するための基礎となる構造で、JVMのような類似技術もある。リアルなCPUにはあまり採用されていない事。

を説明しました。


実践して覚える

今回も、前回と同様GitHubのテンプレートとなるコードを元に課題にチャレンジします。

果たして…今回はPart5まで到達出来ました!! Part5では、インスタンスメソッドを扱い、その後のPartでクラス内のフィールドへのアクセス等を扱う予定でしたが、時間足らず。スライドは上げておくので、残りのPartを「忘れないうちに」取り組んでみる事をお勧めします。

# 多分しばらくはILネタのセッションは無いですww


Intermediate Languageを覚えることの意義

ILの基本と言う事で2回も長時間セッションを行ったわけですが、ILを覚えたところで一体何の役に立つのか?と言う疑問を持つ方もいるかも知れません。ILをやっていて重要だなと思うポイントを2点挙げておきます。

  • 参照型と値型の明確な区別
    ChalkTalk CLR – 動的コード生成技術(式木・IL等)でもやりましたが、CLRには大別して「参照型」と「値型」という2つの型の相違があります。これらはCLR内でのメモリの扱われ方が違う上、IL上でも異なる扱われ方をされます。特に参照型の値と、値型の値と、値型が参照される場合、など、IL上でも正しく区別が必要です。これらの区別がC#やVB.netでも正しく出来ていない・苦手という人は多いと思いますが、ILでどのように扱うのかがしっかり理解できれば(しかもこれは単純な法則でもあるので、抽象的な概念などは不要で実は覚えやすい)、C#などでコードを書く場合でも、自信をもって書けるはずです。また、これが分かると、ボクシング(Boxing)・アンボクシング(Unboxing)のコストと、これらがいつ生じるのか、と言う事が正しく理解できます。暗に気が付かないうちにこれらを発生させてしまい、パフォーマンスの低下を招くという事を回避できるようになるはずです。
  • アセンブリメタデータの構造観
    普段はコンパイラが自動的によしなにやってくれる、アセンブリメタデータの構造についての直観的な理解が得られます。なぜC#の言語構造はこうなっているのか、の(すべてではありませんが)ILから見た姿が分かります。記述した型がどこでどのように使われるのか、あるいはメソッドの定義はどこからやってくるのか、ILとどのように関連付けされるのかが分かります。これが分かると、どんな場合にアセンブリを分割すべきなのか、あるいはリフレクションを使用して解決すべき課題なのかそうではないのか、と言ったことを判断できます。こういった判断は、最終的にシステム全体の構造にも影響を与える可能性があるので、システム設計を行う上では無視できない要素の一つだと思います。

学問で言えば本当に基礎的な部分に相当するので、これを習得するメリットが見えにくいのは事実ですが、覚えておいて損はないと思います。大体、ILなんて「単純」なので、難しそうで実は簡単なんですよ!(きわめて抽象度の高い概念を覚える事に比べたら、どんな人でも覚えれる可能性があります)。

Let’s IL!!

それではまた。

ILのキホン – 第五回Center CLR年末会

WP_20151226_14_13_25_Pro_LI「ILのキホン」と言う題目で、Center CLR年末会で登壇してきました。

Center CLRも第五回で、毎回参加の方と新しく参加の方で程よく盛り上がってうれしい感じです。ChalkTalkもそうですが、遠方からわざわざ参加して頂いた方もありがとうございます。来年も継続して行きたいと思います。

さて、今回のネタは結構前から決めていたものの、年末の忙しさの為に十分な時間が取れず、直前に方針を決めてバタバタしてしまいました。猛省…

と言う事だけではないんですが、ちょっとセッションの進行を変えてみました。


Intermediate Languageのキホン

ILの事については、第一回のChalkTalk CLRで「ChalkTalk CLR – 動的コード生成技術(式木・IL等)」と題してディスカッションを行ったのですが、今回は本会でその展開をしようと思ったのです。

briefingしかし、ただILを説明したのでは、実際にILを使う具体的なシチュエーションでも提起できない限り、右から左へ流れていってしまうだけだと考えて、ハンズオンのような形式で進行してみました。

Emitが可能なコードを一から書いていると、なかなか本題にたどり着けないため、GitHubに元ネタとなるコードを用意しておき、Emitコードから書き始めれられるようにしました。

/// <summary>
/// コードをILで動的に生成するヘルパークラスです。
/// </summary>
internal sealed class Emitter : IDisposable
{
	private readonly AssemblyBuilder assemblyBuilder_;
	private readonly ModuleBuilder moduleBuilder_;

	/// <summary>
	/// コンストラクタです。
	/// </summary>
	/// <param name="name">アセンブリ名</param>
	public Emitter(string name)
	{
		var assemblyName = new AssemblyName(name);
#if NET45
		assemblyBuilder_ = AssemblyBuilder.DefineDynamicAssembly(
			assemblyName,
			AssemblyBuilderAccess.RunAndSave);
#else
		assemblyBuilder_ = AssemblyBuilder.DefineDynamicAssembly(
			assemblyName,
			AssemblyBuilderAccess.Run);
#endif
		moduleBuilder_ = assemblyBuilder_.DefineDynamicModule(name + ".dll");
	}

	/// <summary>
	/// Disposeメソッドです。
	/// </summary>
	public void Dispose()
	{
#if NET45
		// デバッグ用に出力
		assemblyBuilder_.Save(moduleBuilder_.ScopeName);
#endif
	}

	/// <summary>
	/// 引数と戻り値を指定可能なメソッドを定義します。
	/// </summary>
	/// <typeparam name="TArgument">引数の型</typeparam>
	/// <typeparam name="TReturn">戻り値の型</typeparam>
	/// <param name="typeName">クラス名</param>
	/// <param name="methodName">メソッド名</param>
	/// <param name="emitter">Emitを実行するデリゲート</param>
	/// <returns>デリゲート</returns>
	public Func<TArgument, TReturn> EmitMethod<TArgument, TReturn>(
		string typeName,
		string methodName,
		Action<ILGenerator> emitter)
	{
		// クラス定義
		var typeAttribute = TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract;
		var typeBuilder = moduleBuilder_.DefineType(
			typeName,
			typeAttribute,
			typeof(object));

		// メソッド定義
		var methodAttribute = MethodAttributes.Public | MethodAttributes.Static;
		var methodBuilder = typeBuilder.DefineMethod(
			methodName,
			methodAttribute,
			typeof(TReturn),
			new [] { typeof(TArgument) });

		// ILGenerator
		var ilGenerator = methodBuilder.GetILGenerator();

		emitter(ilGenerator);

		// 定義を完了して型を得る
		var typeInfo = typeBuilder.CreateTypeInfo();
		var type = typeInfo.AsType();

		// メソッドを取得する
		var bindingFlags = BindingFlags.Public | BindingFlags.Static;
		var method = type.GetMethod(methodName, bindingFlags);

		// デリゲートを生成する
		return (Func<TArgument, TReturn>)method.CreateDelegate(
			typeof(Func<TArgument, TReturn>));
	}
}

public static class Program
{
	public static void Main(string[] args)
	{
		// 動的アセンブリを生成
		using (var emitter = new Emitter("TestAssembly"))
		{
			// メソッドを動的に生成
			var func = emitter.EmitMethod<int, string>(
				"TestNamespace.TestType",
				"TestMethod",
				ilGenerator =>
				{
					ilGenerator.Emit(OpCodes.Ldstr, "Hello IL coder!");
					ilGenerator.Emit(OpCodes.Ret);
				});

			// 実行
			var result = func(123);

			// 結果
			Console.WriteLine(result);
		}
	}
}

Emitterクラスは、System.Reflection.Emitの煩雑なコードを隠ぺいするために定義しています。課題の多くはMainメソッド内のラムダブロックのEmitメソッドをいじります。

# なお、このコードはnetcore5向けでも実行可能にしてあります。

課題はPart1~Part10まで用意し、はじめに少しだけ「スタックマシン」についての解説を行いました。


Part 1: 引数の値をToStringして返すようにする

part1GitHubのコードを軽く説明して、Emit本体のコードを課題に従って書き換えてもらいました。

テンプレートのコードは、”Hello IL coder!”と言う文字列を返すだけのものですが、引数で与えられたintの値を文字列に変換して返すのが課題です。

最初なので、こんなものだろうと思っていたのですが… 実はこれ、大変な罠が…

やるべきことは、スライド通り大きく3つあります。

引数の値はどうやって入手するか

ヒントに書いておいたのですが、「OpCodesクラス」を見ると、IL命令の一覧が確認できます。

そして、スタックに値を積む(プッシュ)するのは、「Ld~」で始まる命令であることも説明しました。OpCodesの一覧を見ていると、「Ldarg_0」命令が見つかります。これを使うと、引数の最初の値をスタックに積みます。「Ldarg」や「Ldarg_S」命令でもOKです。その場合は、Emitメソッドの引数にインデックス(0ですが)を指定します。

Int32.ToStringのMethodInfoはどうやって入手するか / staticメソッドの呼び出し

メソッドを呼び出すには、「Call」命令を使います。その時、Emitする引数に、呼び出すメソッドの「メソッド情報(MethodInfo)」が必要です。これには、リフレクションを使います。

// Int32のTypeクラスのインスタンスを得る
Type int32Type = typeof(System.Int32);

// Int32.ToStringメソッドのメソッド情報を得る
MethodInfo toStringMethod = int32Type.GetMethod("ToString", Type.EmptyTypes);

ToStringのオーバーロードは複数あるので、正しいメソッド情報を選択するために、Type.EmptyTypesフィールドを使って、「引数0」のオーバーロードを選択させています(このフィールドを使わなくても、0個の配列でもOKです)。

typeofを使うことに対して、一部の方がモヤモヤしていた(チートっぽい)ようです。厳密に0からInt32のTypeを取得したことにならないのではないかとの事で、確かにその通り。typeofを使うと、C#コンパイラがコンパイル時にInt32を解決しようとします。実行時に0から取得する事も不可能ではないのですが、その話をするとまた話がずれていくので、今は諦めてもらう事に。

# Call命令を使うかどうかの更なる議論がありますが、それは後のPartで…


「動かない!」

複数のチームから「動かない!」とか、「NullReferenceExceptionがスローされる!」とか騒ぎが…

ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Call, toStringMethod);
ilGenerator.Emit(OpCodes.Ret);

どう見ても間違っていないように見える… そこで、「ILSpy」を使って、C#で書いた等価コードを逆アセンブルした所、引数をスタックに積む際に、「Ldarga」命令を使っていました。

やられた orz

Ldarga命令は、対象の値そのものではなく、その値を示す参照(ポインタ)をスタックに積みます。これは、対象の値が「値型(ValueType)」であり、Callでそのメソッドに遷移した先ではthis参照として扱われなければならないため、Ldarg命令ではなく、Ldarga命令を使う必要があったのです。

Int32の場合は値型と言っても、なぜ参照化しなければならないのか、ピンと来ないかもしれません。以下に構造体(構造体も値型)を使った例を示します:

// 構造体
public struct Hoge
{
	public int A;
	public int B;
	public int C;

	// 計算し、結果をCに代入する
	public void Compute()
	{
		// 計算結果を代入するには、this(自分自身への参照)が必要
		this.C = this.A + this.B;
	}
}

// メソッドを呼び出して計算させる
Hoge hoge = new Hoge();
hoge.A = 1;
hoge.B = 2;
hoge.C = 123;
hoge.Compute();

// hoge.Cは、123か3か?
Console.WriteLine(hoge.C);

Computeメソッドはインスタンスメソッドなので、内部でthis参照が使えます。this参照を使った場合、そのインスタンスは、呼び出し元のhogeとなるはずです。Computeメソッドから呼び出し元が保持するインスタンスにアクセスさせるには、文字通り「参照」が必要なのです。従って、ここではLdarga命令を使って、インスタンスへの参照を渡す必要があります。勿論、対象が参照型(クラス)であれば、Ldarg命令で問題ありません。

# NullReferenceExceptionがスローされたのは、本当に偶然のようで、真の原因は分かりません。


全然足りない!!

そんなわけで、このPart1を30分ぐらいでやるつもりだったのが、これだけで時間使い切ってしまいました。値型と参照型の違いは、実際にはPart6ぐらいでやってもらう予定だったのが、こんな所でやる事になったのが敗因…

時間を延長して続けるかどうかという話もあったのですが、他セッションもあったため、次回に持ち越しとなりました。次回に同じ題目で再チャレンジします。ごめんなさい。スライドはその時まで正式公開はしません。次回も同様に参加する場合は、Part1の内容の復習をしておいてくだしあ。


詰め込みすぎは良くない

年末は… ダメですね。この記事も31日に書いてるし (;´Д`)
でも楽しかったです。来年もよろしくお願いします!

ChalkTalk CLR – 動的コード生成技術(式木・IL等)

ChalkTalkCLR12122636_1505045119820235_1955812025993410087_n「ChalkTalk CLR – 動的コード生成技術(式木・IL等) 」というお題で、小規模で濃い話が出来る場を設けてディスカッションしてきました。

今回の会場は、「Maker Lab NAGOYA」さんをお借りすることが出来ました。モノづくり主題のワークスペースで、色んな加工装置とかあって、IoTネタとか常時面白そうなことをやっています。案内頂いたMatsuokaさんは、.NET Micro Framework (GR-PEACH) のバグフィックスとかやっていて、わぉーと言う感じです。

WP_20151017_12_58_31_Pro__highres


開催の背景

「ChalkTalk」という名称は、チョークと黒板を使って、密なディスカッションをする、のような意(のはず)です。普段のCenter CLR勉強会でもディスカッションと言う事は意識しているのですが、もっと密にやりたかったという動機があります。

  • 今回のテーマが、ILや式木なので、いきなり展開するにはディープ過ぎる。
  • でも、個人的にはやってみたい。なので、小規模でやってみて、このネタをどうやって本会でやるのかを練ってみたい。
  • そもそも、Center CLRに近しいメンバーが、この辺りの技術にどれだけ長けているか・追従できるか、と言う事を知りたい。
  • そして、ChalkTalkは面白い(知ったのはde:code 2015)ので、自分でもやりたい。

と言う事が背景にあって、オーガナイザーなんだから、やってみればいいよね、ダメなら今回で終わりにすればいいし、と思って開きました。

WP_20151017_17_49_59_Pro__highres告知通り、事前に資料の準備なし(以前にやった勉強会の資料とかはありますが)としたのですが、あまりにフリーダムだと議論が発散する可能性もあったので、KPTを使って、参加者がどのような課題を持っているのかを視覚化してみようと思いつき、このような感じで5分で書き出してもらいました。

一部の参加者が「COMが~COMが~」と言っていたのですが、COMの基礎をやり始めるとそれだけで潰れてしまうので、やむなく我慢してもらうことに (;´Д`) 何故COM?と聞いてみたところ、「WinRTのバックグラウンドにCOMがあるらしいけど、もうググっても情報が得られなくて云々…」との事。この、某学生MVPには、やたら熱くアピールされたので、COMについてはまたChalkTalkでリベンジすることにしました。

結局、KPTでは各参加者のレベル確認的な所となり、順当にILとかCLSの説明からスタートする事に。KPTの導入は、私のアジャイル方面への試みの一つでもあるんですが、全然生かせていないのは今後の課題だなと感じました。
題目が「IL・式木」と言うように、初めから技術にフォーカスしていて、これらの技術を使う動機というか、バックグラウンドと言うか、そういうところに焦点が当たりにくかったのも敗因かなと思っています(技術フォーカスだとわかりやすいし、もともとは自分がやりたかった話なので、仕方ないんですが)。


ILとCLSの話

makerlab1x86とかx64のような物理CPUアーキテクチャの話と、それをつなぐCLSやILのコードの関係・実行時に何が起こっているのかという概要の話をしました。参加者には経験が浅くてILとかそもそも中間言語や仮想CPUとかわからない方も居たので、概要レベルでの導入を試みました(どんなエキスパートでも、最初に通る道なので、知らなくても恥じる必要はないです)

特に、仮想的なCPUが「スタックマシン」であり、スタックの出し入れで処理がおこなわれる、という所が焦点だと思ったので、ILSpyで逆コンパイルした結果を見せ、仮想CPUのスタックにどのように値が出し入れされるのかを、詳しく説明しました。この部分は、以前にMGK2015(三重合同懇親会)で、LTでILを説明するという無謀を試みたことがあり :)、その話も交えながら。写真のように、白板にスタックの図を描いて、ILコードからどのようにスタックが操作されるのかを詳しく説明しました。

ILの仮想CPUがスタックマシンであることと、もう一つ重要な概念として「box化(boxing)」とその解除の話をしました。.NETにはValueType型があり、この型を継承した型が構造体(struct)となって、ILのレベルでは扱いが異なると言う事を話しました。マサカリ来ましたねー (;´Д`) ValueType型自身は構造体ではなく、クラスでしたね(今、ちゃんとリファレンスで再確認しました)。

構造体のインスタンスは、ローカル変数領域(.locals)や、フィールド変数などで、直接そこに値が格納されます(物理CPUでどう実装されるのかは、ひょっとしたら違いがあるかもしれません)。それに対して、「参照型(クラス)」は、不透明なポインタ値がそこに格納され、実際の値(インスタンス)は、ヒープ領域に格納されます。object型のフィールドや変数にインスタンスを格納する場合、構造体のインスタンスであれば「box命令」「unbox.any命令」で、ヒープ領域への格納と取り出しが行われる、というのを図示しました。この操作にコストがかかることについても理解してもらえたと思います。

トリビアとして、box化を忘れたり、やらなくてもよい所でbox化や解除したりすると、おかしな現象が起きたり起きなかったり、InvalidProgramExceptionで落ちるよ、みたいな話もしました。CLRがロード時にILに対してどこまでの検査をしている・していないのか、というのは興味深いところです。bleisさんからは、PeVerifyツールを紹介して頂きました。自分でEmitで生成したコードに問題がないかどうかを、静的に解析できます。ILSpyは、ILの使用方法が間違っていても、一見それっぽく逆コンパイルコードを表示してくる(クラッシュする場合もある)ので、初めのうちは特に注意した方が良いでしょう。

ILについては、あとは命令のバリエーションを理解すれば、少なくともILSpyで逆コンパイルしながらコードを調整していくことで、そこそこILが書けるようになると思います。と、ここで、「IL Support」という拡張機能の事についても教えて貰えました。これを導入しておくと、il用のソースを別に用意しておき、C#側からそれを参照する定義をexternで書いておくだけで、動的にEmitしまくる事無く静的に自分のコードにILを導入できます。更にILソースファイルは、pdbにデバッグ情報も出力されるので、デバッガでILにステップイン出来るという、正に神ツールです。すばらすぃ….

ILを書いたソースコードファイルをhoge.ilのようなファイルで保存し:

.namespace CenterCLR
{
	.class public ILSupportSampleClass
	{
		.method public static int32
			Compute(int32 a, int32 b) cil managed 
		{
			ldarg.0
			ldarg.1
			add
			ret        
		}
	}
}

C#側からはMethodImpl属性で参照可能にします。

namespace CenterCLR
{
	public class ILSupportSampleClass
	{
		[MethodImpl(MethodImplOptions.ForwardRef)]
		public static extern int Compute(int a, int b);
	}
}

式木の話

ILの話は低レベルでなじみが薄いため、一般的にはILの方が難しいような印象がありますが、上記の話を押さえておけば、結構誰でも掛けたります。間違ってると簡単にクラッシュもしますが。しかし、式木については概念からわかってないと扱いづらい(≒解説が長期化してしまう)と言う事もあって、どうやってディスカッションするか、考えあぐねていました。

第一回~第三回のCenterCLR勉強会でも度々取り上げてきたので、参加者には「ぼんやりと」式木の概念は伝わっていたようですが… ここでもbleisさんに、式木についての説明をしてもらいました。王道的に計算式的な定義から、ノードに分解される様、そしてノードの木構造があれば、プログラマブルに探索が可能・変形も可能、という感じでの解説でした。簡潔で分かりやすい。ちゃんと伝わったかな?

式木は:

  • (C#において)ラムダ式からExpressionクラスのインスタンスを取得して、式を動的に解釈する(Entity FrameworkのようなO/Rマッパーが行っている手法)
  • Expressionクラスのインスタンスを動的に組み立て、Compileメソッドを呼び出して、実際に実行可能なデリゲートを入手する
  • 式木ノードを探索して、メタデータ参照に役立てる

というような代表的な用途があることを説明しました。

最初の例では、Entity Frameowrkの話だけでは終わってしまうので :)、Final LINQ Extensions IIIで解説した、即席O/Rマッパーについて軽く復習して、式木の内容をどうやってサーバーサイドで実行するか、のような話をしました。

次に、がりっち=サンに話を振って、「某ronia」というツイッタークライアントのフィルタ条件を動的に構築するために、入力されたフィルタ式をパースして(ここは手動)、Expressionを生成して式木を組み立てて、フィルタ処理を実現した、という話をして貰いました。

最後に、EXCEL仕様書の駆逐ネタでやった、メタデータの参照利用としての式木の話をしました。

そのほか、貴重なお話として、bleisさんの業務での式木の利用経験として、F#の式木をどうやって解釈するのかを考えたという話をしてもらいました。F#の式木は、概念的にはC#(というより.NET FrameworkのExpression)と同じようなものですが実際は異なり、高速化のためにExpressionにマップしてCompileするか、またはF#式木を直接解析してILを生成するか、のような事で、いくつか障害があって頭を悩ましたという話を聞くことが出来ました。この話、以前に何かで見た記憶があるなーと思っていたのですが、今になって繋がった!みたいな、妙な感動がありました。

# 関数型言語にまつわるドタバタで徘徊していた時に見た気がする。
# (えーと、私は面白い技術であればおk的な立場です)

ちなみに、このF#の解析器、手伝ってくれる人募集中とのこと。私が手伝うには、F#をモノにする必要があるな….


総括

WP_20151017_17_54_15_Pro__highres結局、結構時間もおしてしまって、いつも通り(?)最後にバタバタしてしまったのは申し訳ないです。クロージングでKPTを再度書いてもらったのですが、振り返りの時間がまともに取れなかったのでダメですねー。でも、ディスカッションとしては楽しい時間を過ごせました。参加者の皆さんありがとうございます。

課題的にはやはりというか、参加者に想定する知識レベルの差をどうやって埋めるか、と言う事が課題だなと思います。長い目で見た場合、Center CLRへの参加者のレベルが全体的に底上げされて来れば、だんだん解消されるかなと思っています。気の長い話ですが、コミュニティベースなので、このぐらいが良いかなと。ChalkTalkについても、レベル差で細分化した方が良いのかもしれません。私のマンパワーにも限界があるので、中々難しい所ですが、あまり小難しく考えず、トライして方針を修正していく、という感じでやりたいと思っています。

それにしても、白板に書きまくったのに、写真全然撮ってなかったのも残念。だれかカメラ担当お願いすれば良かったです orz

抽象太郎ものがたり そして伝説へ – NGK2014B 名古屋合同懇親会

今年も残りわずか、NGK2014B 名古屋合同懇親会にて、「抽象太郎ものがたり そして伝説へ」というタイトルでLT登壇してきました。

WP_20141206_14_24_43_Pro

会場の様子: 今年は名工大が会場でした。会場はほぼ満席、いつもながら(去年しか知らないけど)凄いです。

NGK2014Bは大LT大会で、LT持ち時間は5分、5分を過ぎると強制終了、5分未満だと放置プレイですww


WP_20141206_16_12_42_Pro

今年の一番の驚きは、ふ”れいすさんの「FCell」でした。うーむ、EXCEL侮りがたし… F#/C#/VB.netのコードを直接EXCEL上で書けて、しかもセルの計算式に指定出来て、しかも再計算も勿論自動で、しかも非同期処理出来て結果も非同期で反映できるという… ええ、私もVBA「大嫌い」ですよ。妄想でこういうの考えた事はあった。これマジ欲しい!!!

「札束でEXCELを殴る」で、Ust見れます。今、ようやくタイトルの意味が分かったw ちなみに、その次の「Play emacs」もデモンストレーションとして凄い良かった・面白かったです。


で、私の登壇した内容ですが… (;´Д`) ええと、忘年会なので、完全にネタです。ただ、ILやりたい人には、サンプルコードは参考になるかと思います。例によってGitHubにアップするので、遊んで下さい。

オリジナルプレゼンはこちら:抽象太郎ものがたり_e
GitHub: CenterCLR.StaticMethodInInterface
Ust: [6/6] NGK2014B 名古屋合同懇親会 昼の部 19:52から

ちなみに、突っ込まれました、その通りです、ハイ。

Youtubeで動画が公開されています。20分ぐらいからです。

去年もネタで笑いが取れるかどうかが勝負だったんですが、今年も何とか笑いが取れたので満足です。
来年も頑張ります。


WP_20141206_18_00_44_Pro

昼の部のLT大会の後は、夜の部で、しゃぶしゃぶ&しーすー食べ放題&飲み放題!!! とっても楽しかったです。

それでは、また。