これも完全な答えではありませんが、いくつかのアイデアがあります。
私は、.NET JITチームの誰かが答えずに見つけるのと同じくらい良い説明を見つけたと思います。
更新
私はもう少し深く見て、問題の原因を見つけたと思います。これは、JITタイプ初期化ロジックのバグと、JITが意図したとおりに機能するという前提に依存するC#コンパイラの変更の組み合わせが原因で発生するようです。JITバグは.NET 4.0に存在していたと思いますが、.NET 4.5のコンパイラの変更によって明らかになりました。
beforefieldinit
ここで唯一の問題だとは思いません。それよりも簡単だと思います。
System.String
.NET 4.0のmscorlib.dll の型には、静的コンストラクタが含まれています。
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr ""
IL_0005: stsfld string System.String::Empty
IL_000a: ret
} // end of method String::.cctor
mscorlib.dllの.NET 4.5バージョンでは、String.cctor
(静的コンストラクタ)が著しく存在しません。
.....静的コンストラクタはありません:( .....
どちらのバージョンでも、String
タイプは次のように装飾されていbeforefieldinit
ます。
.class public auto ansi serializable sealed beforefieldinit System.String
同様にILにコンパイルされる型を作成しようとしましたが(静的フィールドはあるが静的コンストラクター.cctor
はないように)、できませんでした。これらすべてのタイプには、.cctor
ILにメソッドがあります。
public class MyString1 {
public static MyString1 Empty = new MyString1();
}
public class MyString2 {
public static MyString2 Empty = new MyString2();
static MyString2() {}
}
public class MyString3 {
public static MyString3 Empty;
static MyString3() { Empty = new MyString3(); }
}
私の推測では、.NET 4.0と4.5の間で2つの点が変更されたと思います。
最初にString.Empty
、アンマネージコードから自動的に初期化されるようにEEが変更されました。この変更は、おそらく.NET 4.0に対して行われたものです。
2番目:コンパイラーは、文字列の静的コンストラクターを発行しないように変更String.Empty
され、アンマネージドサイドから割り当てられることがわかっていました。この変更は.NET 4.5に対して行われたようです。
EE は、String.Empty
一部の最適化パスに沿ってすぐに割り当てを行わないようです。コンパイラーに加えられた変更(または非表示にするために変更されたものString.cctor
)は、EEがユーザーコードの実行前にこの割り当てを行うことを期待していましたが、EEはString.Empty
参照タイプ具体化ジェネリッククラスのメソッドで使用される前にこの割り当てを行わないようです。
最後に、このバグはJITタイプ初期化ロジックのより深い問題を示していると思います。コンパイラの変更はの特別なケースのようですSystem.String
が、JITがのためにここで特別なケースを作ったのではないかと思いSystem.String
ます。
元の
まず、WOW BCLの人々は、いくつかのパフォーマンスの最適化によって非常にクリエイティブになりました。 メソッドの多くは、String
スレッドの静的キャッシュStringBuilder
オブジェクトを使用して実行されるようになりました。
私はしばらくリードをたどりStringBuilder
ましたが、Trim
コードパスでは使用されていないため、スレッドの静的な問題ではない可能性があると判断しました。
私は同じバグの奇妙な症状を見つけたと思います。
このコードはアクセス違反で失敗します。
class A<T>
{
static A() { }
public A(out string s) {
s = string.Empty;
}
}
class B
{
static void Main() {
string s;
new A<object>(out s);
//new A<int>(out s);
System.Console.WriteLine(s.Length);
}
}
ただし、コメントを外す//new A<int>(out s);
とMain
、コードは正常に機能します。実際、A
が任意の参照タイプで具体化されている場合、プログラムは失敗しますが、A
値タイプで具体化されている場合、コードは失敗しません。また、A
の静的コンストラクターをコメントアウトしても、コードは失敗しません。掘り下げる後Trim
とFormat
、問題はそれがされていることは明らかであるLength
インライン化され、そして上記のこれらのサンプルでそのString
タイプが初期化されていません。具体的には、本体内部A
のコンストラクタ、string.Empty
正しくの体内が、割り当てられていないMain
、string.Empty
正しく割り当てられています。
String
なんとかして型の初期化がA
値型で具体化されているかどうかに依存しているのは私にとって驚くべきことです。私の唯一の理論は、すべてのタイプ間で共有される一般的なタイプ初期化のための最適化JITコードパスがあり、そのパスはBCL参照タイプ(「特殊タイプ?」)とその状態についての仮定を行うというものです。他のBCLクラスけれども簡単に見てpublic static
、基本的にはフィールドショーのすべてのそれらの静的コンストラクタ(でも、それらのような空のコンストラクタとデータがない、との実装System.DBNull
とSystem.Empty
している。BCL値の型public static
フィールドは静的コンストラクタを(実装していないようですSystem.IntPtr
)たとえば、 。これは、JITがBCL参照タイプの初期化についていくつかの仮定をしていることを示しているようです。
参考:2つのバージョンのJITコードは次のとおりです。
A<object>.ctor(out string)
:
public A(out string s) {
00000000 push rbx
00000001 sub rsp,20h
00000005 mov rbx,rdx
00000008 lea rdx,[FFEE38D0h]
0000000f mov rcx,qword ptr [rcx]
00000012 call 000000005F7AB4A0
s = string.Empty;
00000017 mov rdx,qword ptr [FFEE38D0h]
0000001e mov rcx,rbx
00000021 call 000000005F661180
00000026 nop
00000027 add rsp,20h
0000002b pop rbx
0000002c ret
}
A<int32>.ctor(out string)
:
public A(out string s) {
00000000 sub rsp,28h
00000004 mov rax,rdx
s = string.Empty;
00000007 mov rdx,12353250h
00000011 mov rdx,qword ptr [rdx]
00000014 mov rcx,rax
00000017 call 000000005F691160
0000001c nop
0000001d add rsp,28h
00000021 ret
}
残りのコード(Main
)は、2つのバージョンで同じです。
編集
さらに、2つのバージョンのILは、最初のバージョンのILに含まれるA.ctor
in の呼び出しを除いて、同じですB.Main()
。
newobj instance void class A`1<object>::.ctor(string&)
対
... A`1<int32>...
第二に。
注意すべきもう1つの点は、JITコードのA<int>.ctor(out string)
:は非ジェネリックバージョンと同じです。