この作品 (index.html
) は
クリエイティブ・コモンズ
表示 - 継承 4.0 国際
ライセンスの下に提供されています.
表示される画像等はそれぞれのライセンスに従います.
今回はVRoid Studioのはなし.
たくさんある.
みんなコンパイラ作りたい!
DSLは一般的な技法
なぜかしらないけどみんな型をつけたがる
性能が要求される場面では型がついたターゲットが候補になる.
LLVMなら強力な最適化を施した上で機械語が出力できる.
Monoならインタプリタ, JIT, AOTの多様な選択肢がある.
一方で, コンパイラ技術をもつ人間は少ない.
行き当たりばったりで開発すると死ぬ.
型はやばい.
VRoid Studioでは独自のUIフレームワークを開発している.
これらをXAMLによって宣言的に記述できる.
実質Vue.js
Xamarin.Formsによって支えられている.
<c:ArrayList>
<n:Target P="hello" />
<DataTemplate>
<n:Target x:DataType="n:Source" P="{Binding Q}" />
</DataTemplate>
</c:ArrayList>
new ArrayList {
new Target { P = "hello" },
new DataTemplate(() => {
var target = new Target();
target.SetBinding(Target.PProperty, new TypedBinding<Source, string>(
source => source.Q,
(source, value) => source.Q = value,
new[] {
new Tuple<Func<TSource, object>, string>.Create(
source => source.Q, "Q") }));
return k;
})
};
テンプレートやバインディングを簡潔に記述できる.
データとビューを分離したいというUI制作の要求に応える.
Mono
Common Language Infrastructure (CLI)
Xamarin.FormsにはXAMLコンパイラとインタプリタが存在する.
インタプリタはC#コードを参照するためにリフレクションが多発して遅い.
リフレクションはC#コードをAOTコンパイル (IL2CPP) した場合問題になる.
リフレクションを避けることでパフォーマンスが上がる.
静的に型を検証することで誤りを早く発見できる.
Xamarin.Formsでは極めて簡易ではあるがその両方を用いている.
一部の "Markup Extension" は入力する値の型と出力する値の方が決まっている.
例: 型を表す x:Type
{x:Type n:Target}
"n:Target"
(System.String
)typeof(Namespace.Target)
(System.Type
)データバインディング対象の型をx:DataType
属性で指定できる.
<n:Target x:DataType="n:Source" P="{Binding Q}" />
target.SetBinding(Target.PProperty, new TypedBinding<Source, string>(
source => source.Q,
(source, value) => source.Q = value,
new[] { new Tuple<Func<TSource, object>, string>.Create(source => source.Q, "Q") }));
CLIの場合, 型情報はmetadataとして出力され, VESによって検証される.
コードの信頼性を実行直前に保証できる.
これを活用してユーザーモードとカーネルモードの分離をソフトウェアで実現するSingularityというプロジェクトもあった.
VESはあくまで検証のみを行う.
ありがちな型変換を自動でしてくれるようなことはない.
そのまま出力するとランタイムが型検証を行った際に例外が発生する.
(System.InvalidProgramException
)
Monoの場合, ランタイムの型検証が不完全だったため, 「間違ってるコードは確実に実行されない」という保証もなかった.
ターゲットの型システム (CTS) に合わせなければならない.
Inheritance, generics, value type, object type, managed/unmanaged pointer type, …
つらい.
DSLを自分で設計している場合, 「暗黙の型変換」はなくすことができる.
「暗黙の型変換」がなければ検証さえ確実に行なえば良い.
実際には汎用プログラミング言語と相互運用性を確保する必要がある.
特にプログラマにとって似たような書き心地であることは重要.
つらい.
System.ValueType
とSystem.Object
(boxing/unboxing)System.Object
が全ての基底クラスのように扱えるが, 実際は異なる.System.ValueType
と&System.ValueType
(C#のref
)System.ValueType
として扱っているものはthis
であるとき&System.ValueType
になるので呼び出し前に変換が必要.「既存のコンパイラの出力を逆アセンブルしてまねすればいい」
しかしコンパイラの出力は指定された型に特化している.
異なる型の値を入力すると失敗する.
型を検証する必要がある.
変換するコードを出力する必要がある.
Xamarin.FormsのXAMLコンパイラではこれらの処理について一貫性がなく多くの不具合をはらんでいた.
1つ1つ対応する必要がある.
しかし型の検証や変換は冗長な操作になる.
local variableを指定した型に変換するLoadAs
メソッドを導入.
public static IEnumerable<Instruction> LoadAs(
this VariableDefinition self,
TypeReference type,
ModuleDefinition module)
CILはスタックマシンだからレジスタ割り付けを考える必要なしにコード生成を他のメソッドに委譲して, 受け取った命令列を挿入できる.
ldloc.0 // LoadAsが出力
box // LoadAsが出力
call instance void Type::Method(System.Object) // LoadAsを呼び出したメソッドが出力
完
今度は型をいちいち指定しなければならなくなった.
つらい.
経験則として, 型に制約が発生する場面はメソッド呼び出しであることが多い.
メソッドを呼び出すときに引数の型を見て, 自動的に変換すればよい.
public static IEnumerable<Instruction> Call(
this MethodReference self,
param VariableDefinition[])
さらにこれはlocal variable以外を受け付けるように拡張できる.
public static IEnumerable<Instruction> Call(
this MethodReference self,
param object[])
メソッド呼び出しは値を生成する. じゃあメソッド呼び出しを Call
メソッドに渡して…
「メソッド呼び出しを渡す」?
引数で指定された情報をもとに命令列を返すという手続きを単位とした重複の除去はもはや不可能.
Call
をメソッドではなくオブジェクトに.
interface ICall
{
IEnumerable Arguments;
MethodReference Method;
new ();
IEnumerable<Instruction> GenerateIL();
}
(new Call(ImportInstanceMethodReference(
result: module.TypeSystem.String,
type: module.TypeSystem.Object,
name: "ToString",
arguments: new[] { module.TypeSytem.Object }),
{
new Call(ImportInstanceMethodReference(
result: module.TypeSystem.Double,
type: module.ImportTypeReference("System", "Math"),
name: "Max"
result: module.TypeSystem.Double,
arguments: new[] { new ArrayType(module.TypeSystem.Double) })
{ new[] { 0d, 1d } }
}).GenerateIL()
Call: instance System.Object System.Object::ToString()
+-Call: System.Double System.Math::Max(System.Double[])
+-0d
+-1d
型をつけることで値に強い依存関係が生まれる.
木をつくることで依存関係を簡潔に表せる.
スタックマシンなら簡単にコードを生成できる.
先行例: Expression Tree
Dragon.svg
cts.svg
iTunesArtwork@2x
mono-gorilla.svg
unity-logo-white.svg