NativeAOT(元corert)のrd.xmlについて

# 始めに

dotnet coreでAOTでネイティブシングルバイナリを実現するための一つの仕組みとして、.NET 7より[NativeAOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/) ([元corert](https://github.com/dotnet/corert))というものが追加された

この中でリフレクション関連の処理を行うための補助設定ファイルとしてrd.xmlというファイルがあるが、それについて現時点でどうすれば良いかという具体的な文書が見当たらなかったので、ここに書いておく。

なお、この記事では.NET 7のものに関して記述していくので、将来的に何か変更があった場合は注意。

# NativeAOTにおけるリフレクション

NativeAOTはコンパイル時にネイティブバイナリを作成する関係上、リフレクション関連の処理に大きな制限がかかる。
しかし、昨今のC#の状況をみると、リフレクション関連の処理を全てNGとするのは非現実的(enum関連とか)なため、API呼び出しやフロー解析を用いて、コンパイル時にメタ情報を静的に作成して使用することで、一定の範囲でリフレクションAPIを利用できるようにしている。

しかし、自動的な解析だけではどうしても限界が出てくるため、予めメタ情報を保持しておくものを指定しておくファイルとして、rd(Runtime Directive).xmlが使用される。

この辺りの、API呼び出し+フロー解析+補助設定ファイルというのは[GraalVMでも似たようなことをしており](https://github.com/oracle/graal/blob/master/substratevm/REFLECTION.md)、AOTでは結局同じような手法に行きつくという事だと思う。

* [rd.xml解析部分のソース](https://github.com/dotnet/runtime/blob/v7.0.0/src/coreclr/tools/aot/ILCompiler/RdXmlRootProvider.cs)
    * [corert時代のソース](https://github.com/dotnet/corert/blob/180a175405d76c83e48499a3eebabb952f7133d2/src/ILCompiler/src/RdXmlRootProvider.cs)

# rd.xmlの出自

rd.xml自体はNativeAOTのオリジナルではなく、[.NET Native](https://docs.microsoft.com/ja-jp/dotnet/framework/net-native/runtime-directives-rd-xml-configuration-file-reference)からのもの。
しかし、NativeAOTで使えるのはそのサブセットであり、全ての記法をサポートしているわけではない。

# いつ書くべきか

`Type.GetType()`直呼び出し程度ならば、コンパイルの段階で自動検出してくれるので、記述の必要は多くの場合は無い。
しかし、自動検出されなかった場合、**ネイティブバイナリ実行時に`MissingMetadataException`や`TypeLoadException`が発生する**。
例えば、[McMaster.Extensions.CommandLineUtils](https://github.com/natemcmaster/CommandLineUtils)では属性ベースでサブコマンドクラスを指定できるが、NativeAOTするとどこからも参照されてないクラスとしてサブコマンドクラスが刈り取られ、実行時に例外が発生する。
この時にrd.xmlを書くことを検討する。

## 意図通りに動作しない例

下記コードを実行すると、通常は"MyClass"という出力が得られるが、NativeAOTしたものを実行すると何も表示されなくなる。

```csharp
foreach(var x in CollectMy()){
    Console.WriteLine(x);
}

IEnumerable<string> CollectMy()
{
    foreach(var item in typeof(Program).Assembly.GetTypes())
    {
        var attr = item.GetCustomAttributes(typeof(MyAttribute), true);
        if(attr != null && attr.Length != 0)
        {
            yield return item.Name;
        }
    }
}

class MyAttribute: Attribute
{

}

[My]
class MyClass
{

}
```

# 書き方

* `Directives`を頂点として、その下に`Application`か`Library`要素を追加する
    * 現時点ではどちらでも入れてOK
* その配下に`Assembly`要素を追加し、属性としてそのアセンブリ名(`System.Reflection.AssemblyName.Name`)を指定
* 配下に`Type`要素を追加し、属性`Name`に名前空間を含めたタイプ名と、属性`Dynamic`に`Required All"を指定する
    * corertでは今の所`Dynamic`には`Required All`以外の記述は認められていない
    * メソッド指定のみ必要な場合は、`Dynamic`属性は付けなくてもOK
        * どのような効用があるかは今の所不明なので、とりあえずつけるという感じで良いかもしれない
    * ジェネリッククラスの場合、```TypeName`1[[Type1,AssemblyName]]```のような記述になる
    * インナークラスの場合、```OuterClass+InnerClass```のような記述になる
* メソッドの指定をしたい場合は、`Type`配下に`Method`要素を追加する
    * ジェネリックメソッドの場合は、更に配下に`GenericArgument`を追加する

```xml
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
    <Application>
        <Assembly Name="My.Assembly.Name">
            <Type Name="Full.Typename" Dynamic="Required All" />
            <Type Name="Full.Typename2">
                <Method Name="Method1"/>
                <Method Name="Method2>
                    <GenericArgument Name="System.Int32"/>
                </Method>
            </Type>
        </Assembly>
    </Application>
</Directives>
```

より実践的な書き方を見たい場合は、よくあるパッケージのrd.xmlの記述を予め書いておこうという [kant2002/RdXmlLibrary](https://github.com/kant2002/RdXmlLibrary) というプロジェクトがあるので、それを参考にしてもいいと思う。

# プロジェクト設定

rd.xmlはItemGroupに`RdXmlFile`を追加することで読み込んでくれる。
rd.xmlが不正なフォーマットだったり、クラス指定等が間違っていると、コンパイル時に例外として捕捉される。

# 終りに

簡単ではあるが、NativeAOTのrd.xmlについて書いた。
NativeAOTは、ネイティブバイナリ実行時にしか出ないエラーがあったり、何も言わずにSEGVで落ちる場合等があるので、癖が強く使いやすいとは言えないが、PublishSingleFileよりもコンパクトにまとまったり(1-10MB程度)、初回起動が非常に早かったり(ほぼCのネイティブバイナリと変わらない)するので、将来的にもっと使いやすいものになると良いと思う。