Slide 1

Slide 1 text

Generics on Xamarin products atsushieno

Slide 2

Slide 2 text

このセッションの目的 - Xamarinをダシにして素材としてreified genericsの世界で起こる問題を紹介する - XamarinはC#/Monoをベースにした製品だけど、 - .NET環境には実装がいろいろあって、プラットフォームによる実装の違いもある - その辺の事情を交通整理する - Xamarinにおけるジェネリクスは何が特別なのかを説明する - 主にランタイム - ついでにmaciosとAndroid - XamarinやC#/.NETのジェネリクス関連で今後の面白トピックを紹介する

Slide 3

Slide 3 text

Terms ● .NET: 雑にECMA 334 (C#)とECMA 335 (CLI)に基づく環境を表すことが多い ● Xamarin: .NETに基づく、モバイルプラットフォーム用の開発ツール ● Mono: Xamarinが使っている.NET標準の実装 ○ Mono Runtime: Monoのランタイム。.NETならCLR、JavaならJVM ○ Mono SDK: Monのランタイムと標準ライブラリと C#コンパイラのセット ○ Roslyn: MSのオープンソース版 C#コンパイラ ● Xamarin.iOS: Xamarin on iOS (, watchOS, tvOS) ○ Xamarin.Mac: Xamarin on MacOS ○ まとめてxamarin-macios ● Xamarin.Android: Xamarin on Android

Slide 4

Slide 4 text

.NET Platforms and Runtimes 今日はXamarin/Monoの実装の詳細の話 Framework Runtime Platform .NET Framework (,WinRT,UWP) CLR* Windows .NET Core (Core)CLR Win, Mac, Linux, Tizen Mono Mono(新) Linux, Mac, Win Xamarin Mono(新) iOS*, Android Unity Mono(古→新) almost everywhere

Slide 5

Slide 5 text

.NET Compilers 今後はRoslynに一本化する方向(ジェネリクスの実装の詳細は気にしなくても良い) XamarinはVSからも使えて、その場合はWindows上のcscが使われる .NET < 4.6 / Visual Studio < 2015 ~ C# 5.0 classic csc .NET >= 4.6 / Visual Studio >= 2015 C# 6.0 ~ Roslyn csc .NET Core C# 6.0 ~ Roslyn csc Mono < 5.0 / Xamarin Studio ~ C# 6.0 mcs Mono >= 5.0 / Visual Studio for Mac C# 7.0 ~ Roslyn csc Unity * mcs → Roslyn?

Slide 6

Slide 6 text

Runtime and generics

Slide 7

Slide 7 text

C#/.NET Generics ● キーワードはreified generics ○ 実行時にも実体が存在するジェネリクス ○ Object(など)への変換が発生しないのでパフォーマンスが良い  e.g. ArrayList vs. List ■ C#ではboxingのコストが高い ● Java ○ 実行コード上にジェネリック型情報は無い ※実行コードとして存在しないだけで、ジェネリック型情報が全部消えているわけではない ● C++ templates ○ 実行時には(今日話題にするような)「型情報」が無い ● Swift, Kotlinなど - 識者の解説待ち(!?)

Slide 8

Slide 8 text

Terms ● CLI: .NETのランタイム ● CIL(MSIL): CLIのバイトコード ○ CILメタデータ ○ アセンブリ: CILを保持するファイル( DLLまたはEXE) ● マネージドコード: CILで書かれたプログラム ● アンマネージドコード、ネイティブコード: CPUネイティブ命令に基づくプログラム ● 実行エンジン: CILを実行するためのエンジン

Slide 9

Slide 9 text

Mono Runtime main()が呼び出される ↓ AppDomainを作成しアセンブリをロードし 型情報を解決(vtableなど) ↓ エントリポイントのCLI型を探索し、 メソッドの内容(コード、CIL)を取得 ↓ CILを{解釈して | ネイティブコードに変換して}実行

Slide 10

Slide 10 text

Mono Runtime main()が呼び出される ↓ AppDomainを作成しアセンブリをロードし 型情報を解決(vtableなど) ↓ エントリポイントのCLI型を探索し、 メソッドの内容(コード、CIL)を取得 ↓ CILを{解釈して | ネイティブコードに変換して}実行 monoランタイムのEE ● インタープリター ● コードジェネレーター (mini) ○ JIT(ジャストインタイム)コンパイル ○ AOT(事前)コンパイル

Slide 11

Slide 11 text

More on Mono EEs ● インタープリター (mint) ○ 実装は仮想命令をシミュレートするだけなので簡単 ○ 実行速度は(きわめて)悪い ○ 2002年までの実装で、もう無い ● コードジェネレーター (mini) ○ 対象アーキテクチャごとに実装する必要がある ○ JITでは変換速度がそれなりに重要 ○ AOTでは変換処理はみっちり行える。静的なコードは ROMからでも読み出せる ■ embedded mono - 組み込み環境で有用( RAMが少ない) ■ mono --aot で変換できる ■ .NET Coreならcorert

Slide 12

Slide 12 text

Strongly-typed code generation ● オブジェクトのネイティブ メモリレイアウトは型によって固定できる ○ それがメソッド呼び出しの引数や戻り値などで参照される ● EEは最適化されたネイティブコードを一度生成し、以降の呼び出しで何度も使う ○ 同じメソッドを何度も JITしない ○ 強く型付けされているので可能 ● この辺たぶんLL処理系のJITとは違う部分が多い

Slide 13

Slide 13 text

Genrating code for generics... ● ジェネリクスがあると生成されるコードがジェネリック型引数で変わってしまう ● ただし、値型の型引数についてのみ問題 参照型については、1つのコードをどの型引数についても使いまわせる ● CLIの型の種類 ○ 参照型: GCでメモリ管理されるオブジェクトの型 ○ 値型: GCでメモリ管理されないオブジェクトの型 ● 参照型のランタイム上の表現はただのポインタ→生成コードの統一は容易 ● 値型は内容による = 可変サイズ→生成コードがどうしても変わる(!)

Slide 14

Slide 14 text

Generating code for generics... 例: メソッド呼び出しの引数確保部分 class G { void M ( int X, T Y, double Z ) {...} } G.M() Offset Size 0 4 3 4 7 8 G.M() Offset Size 0 4 3 8 11 8 G.M() Offset Size 0 4 3 4 7 8 G.M() Offset Size 0 4 3 4 7 8 Uri FooBar BF7F5 31D4 A2E9 C6B BF7F5 31D4 A2E9 C6B BF7F5 31D BF7F5 31D

Slide 15

Slide 15 text

Generic code sharing ● 対応策1: ジェネリック インスタンスごとにネイティブコードを生成する → 使われた型引数(の組み合わせ)の数だけメモリ消費が増える  Dictionary, Dictionary, ... ● 対応策2: ジェネリック型引数の部分だけ動的に生成するコードにする → 他の部分は動的に生成 → 実装が複雑になる ● monoは200x年代後半に対応策1から対応策2に切り替え 「ジェネリックコード共有」

Slide 16

Slide 16 text

Generic code sharing ● Mono以外ではどうなのか? ● erased genericsの世界では問題にならない 例: 全部java.lang.Objectになる ○ C#で同じことをやるとboxingによるメモリ確保の嵐になって死ぬ ○ Varhallaってこの辺どうするんだろう? ● clang/llvmはどうなの? → わかりません ● 参考資料 ○ http://www.mono-project.com/docs/advanced/runtime/docs/generic-sharing/ とその先にリンクされている開発者ブログ ○ mono/mini/mini-generic-sharing.c

Slide 17

Slide 17 text

iPhone: AOT iPhoneSDK「アプリは事前審査。事前審査できない動的コード生成は認めない。」 monoランタイムならAOTで対応可能…? 動的な型の生成と実行さえしなければOK…?

Slide 18

Slide 18 text

AOT + generic sharing A) generic sharing: 共有できない部分は実行時にコード生成している(!) B) iPhone: 動的コード生成は禁止 A + B = □※$”#%¥!!!!11 →monoランタイムでは「完全AOT」モードを実装 $ mono --aot=full ちなみに、元々はUnityのmonoランタイムで実現していた

Slide 19

Slide 19 text

Generic sharing for value type ● 問題になるコード: generic value sharing (gsharedvt) ○ 事前に使われるとわかっていない値型のジェネリック インスタンス ● やっつけ対応策 ○ ある程度の大きさのスペースを固定サイズで確保しておいて、 その範囲内のサイズに収まるオブジェクトであれば問題ない ● 本格的な対応策 ○ gsharedvt な引数については、実行時ジェネリック情報 rgctx を もとに、実際の値型に必要なメモリスペースを localloc 命令で スタック領域に確保し、その後 CPUアーキテクチャ別のトランポリン を 使って、実際に必要なメモリレイアウトを構築してから呼び出す GM() Offset Size 0 4 3 X 3+X 8

Slide 20

Slide 20 text

Generic sharing for value type ● 参考資料 ○ https://www.mono-project.com/docs/advanced/runtime/docs/gsharedvt/ ○ sources: mono/mini:mini-[arch]-gsharedvt.c,tramp-[arch]-gsharedvt.c

Slide 21

Slide 21 text

shared generics on NGen Professional .NET 2.0 Generics: "With NGen, there can't be anything "lazy" about how generics are instantiated. Instead, NGen must create representations of every generics instance it could encounter. The CLR won't be around to JIT anything. So, after all the discussion of avoiding code bloat and taking advantage of CLR's run-time optimizations, NGen takes all those values and turns them on their collective heads." "For value types, where you can't share code, there's more motivation to avoid precompiling those types that are not needed. To achieve this, NGen will compute the set types that are required for a given assembly (using transitive closure) and precompile just those instances. This will minimize the amount of bloat you'll take on for instances that will never get created at run-time." ※NGenではJITできないから事前に生成するよ

Slide 22

Slide 22 text

Platform API Interop

Slide 23

Slide 23 text

xamarin-macios ● Xamarin.iOSとXamarin.Mac - まとめてxamarin-macios ● Objective-C APIとの相互運用の世界: ObjCRuntimeをP/Invokeする ● 任意のObjective-Cライブラリのバインディングも作成可能 ○ Xamarin.iOS.dll - iOS SDK APIのバインディング ○ 型やメンバーに[Register] 属性を付けるとバインディングになる ● Objective-Cにはreified genericsが存在しないので ○ Objective-Cランタイム側からCLIのジェネリック型をインスタンス化することができない ○ ジェネリック型をバインディング APIの定義に含めることはできない ○ ジェネリック型から派生した非ジェネリック型を使うことはできる ● 公式サイトのドキュメントが親切で詳しい

Slide 24

Slide 24 text

xamarin-android ● Java APIとの相互運用の世界 - JNIEnvをP/Invokeする ● 任意のJava/Androidライブラリのバインディングも作成可能 ○ Mono.Android.dll - Android APIのバインディング ○ 型やメンバーに[Register] 属性を付けるとバインディングになる(通常は IDEで生成) ● ジェネリクスの制約はだいたいiOSと同じ ○ generic value sharing関連の、AOTを理由とする制約は無い ○ オーバーライドしたメンバーを JNIで呼び出せるようにJava glueコードを生成していて、 実行時に動的にこれを生成することはできないので、リフレクションの制約は似ている ● 公式ドキュメントで言及されている(あまり親切ではない…)

Slide 25

Slide 25 text

details: TBD ● この勉強会でXamarinの「使い方」を詳しく話してもしょうがない → https://www.monkeyfest.io/ で詳しく話します(予定 / Androidだけ) シンガポールだけど

Slide 26

Slide 26 text

Xamarin Future and generics

Slide 27

Slide 27 text

C#: default interface methods ● Javaのデフォルトインターフェースメソッドと同等のものがC#にも導入される(予定) ● 仕様について詳しくは ○ インターフェースを「契約」として見たときの問題点 ― C#への「インターフェースのデフォルト実装」の導入(前編) ○ デフォルト実装の導入がもたらす影響 ― C#への「インターフェースのデフォルト実装」の導入(中編) ○ インターフェースを拡張する2つの手段 ― C#への「インターフェースのデフォルト実装」の導入(後編) ● 要は ○ インターフェースに後から破壊的変更を引き起こさずにメンバーを追加できる ○ ランタイムのサポートが必要( C# 3.5〜C#7.0では不要だった) ● 今日は「ここから先」の話だけをします public interface IX { int X (); int Y (); int Z () { return 0; } }

Slide 28

Slide 28 text

default interface methods in Roslyn/.NET Core ● "d15.6" branch (15はVSのバージョン, 15 = 2017。 .n はサービスリリース) ● Roslynの仕様 ○ https://github.com/dotnet/roslyn/tree/features/DefaultInterfaceImplementation ● Roslynの実装 ○ https://github.com/dotnet/roslyn/compare/dev15.6...features/DefaultInterfaceImplementation ● Roslynの残っている課題 ○ https://github.com/dotnet/roslyn/issues/17952 ● CoreCLRの進捗状況 ○ https://github.com/dotnet/coreclr/issues/10504 ● CoreCLRの実装(PR) ○ https://github.com/dotnet/coreclr/pull/10505 (dev/defaultintf)

Slide 29

Slide 29 text

default interface methods in Mono ● コンパイラはRoslynの実装待ち ● mono runtimeには試験的に(?)実装済み https://github.com/mono/mono/pull/4625 ● まとめ ○ Roslynの特別なブランチのビルドが必要 ○ coreclrも特別なブランチのビルドが必要 ○ monoはmasterで試せそう(ただし試すには特別なブランチの roslynビルドが必要)

Slide 30

Slide 30 text

Why does it matter? は何を返す は何を返す

Slide 31

Slide 31 text

How should shared generics process this IL...  ①  ③  ④  ② は  ②② は  ①③④①③④ IL_0003: constrained. !!T IL_0009: callvirt instance int32 class IX`1::M() このメソッドは引数のboxingを起こすかもしれないし、起こさないかもしれない でもそんな呼び出し方はCLRに存在しないので困る(!)

Slide 32

Slide 32 text

Default interface methods in Xamarin ● Xamarin.iOS ○ Objective-Cのプロトコルは「実装しても良いし、しなくても良い」存在なので ちょうど平仄が合っている? ○ Swiftバインディングツール(SwiftNetifier)でサポートされるか? ● Xamarin.Android ○ Android N API以降のAPIが徐々に利用し始めているので、 そのバインディングに必要(現在はバインドしていない)

Slide 33

Slide 33 text

Wrap up ● reified genericsはたいへん ● 値型が特にたいへん ● full AOTが特にたいへん ● そのうち来るデフォルトインターフェースメソッドと値型のコンボもたいへん ● というのを他の言語で実装するときに思い出すといいかも?

Slide 34

Slide 34 text

Thanks