以下のサンプルコードは自然に発生しました。突然、私のコードは非常に厄介なFatalExecutionEngineError
例外になりました。犯人のサンプルを分離して最小化するために、私は30分を費やしました。コンソールアプリとしてVisual Studio 2012を使用してこれをコンパイルします。
class A<T>
{
static A() { }
public A() { string.Format("{0}", string.Empty); }
}
class B
{
static void Main() { new A<object>(); }
}
.NET framework 4および4.5でこのエラーが発生するはずです。
これは既知のバグですか、原因は何ですか、それを軽減するために何ができますか?私の現在の回避策はstring.Empty
を使用しないことですが、間違ったツリーを起動していますか?そのコードについて何かを変更すると、期待どおりに機能します。たとえば、A
の空の静的コンストラクターを削除したり、型パラメーターをobject
からint
に変更したりします。
私はラップトップでこのコードを試しましたが、文句は言いませんでした。ただし、メインアプリを試してみたところ、ラップトップでもクラッシュしました。問題を軽減するときに何かを壊してしまったに違いありません。それが何であるかを理解できるかどうかを確認します。
私のラップトップは上記と同じコード、フレームワーク4.0でクラッシュしましたが、4.5でもメインクラッシュします。両方のシステムは、最新の更新(7月?)でVS'12を使用しています。
詳細情報:
これも完全な答えではありませんが、いくつかのアイデアがあります。
。NET JITチームの誰かが答えることなく見つけるのと同じくらい良い説明を見つけたと思います。
[〜#〜] update [〜#〜]
私はもう少し深く見て、問題の原因を見つけたと思います。これは、JIT型初期化ロジックのバグと、JITが意図したとおりに動作するという前提に依存するC#コンパイラの変更の組み合わせによって引き起こされるようです。 JITバグは.NET 4.0に存在していたと思いますが、.NET 4.5のコンパイラの変更により発見されました。
ここではbeforefieldinit
だけが問題だとは思わない。それよりも簡単だと思います。
.NET 4.0のmscorlib.dllの_System.String
_型には、静的コンストラクターが含まれています。
_.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
_
.NET 4.5バージョンのmscorlib.dllでは、_String.cctor
_(静的コンストラクター)が著しく欠落しています。
.....静的コンストラクターなし:( .....
どちらのバージョンでも、String
タイプはbeforefieldinit
で装飾されています。
_.class public auto ansi serializable sealed beforefieldinit System.String
_
ILに同様にコンパイルする型を作成しようとしました(静的フィールドはあるが、静的コンストラクター_.cctor
_はありません)が、できませんでした。これらのすべての型には、ILに_.cctor
_メソッドがあります。
_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つのことが変わったと思います:
最初:EEは、アンマネージコードから_String.Empty
_を自動的に初期化するように変更されました。この変更は、おそらく.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 The 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);
}
}
_
ただし、Main
の//new A<int>(out s);
のコメントを外すと、コードは正常に機能します。実際、A
が任意の参照型で具体化されると、プログラムは失敗しますが、A
が任意の値型で具体化されると、コードは失敗しません。また、A
の静的コンストラクターをコメントアウトした場合、コードは失敗しません。 Trim
とFormat
を掘り下げた後、問題はLength
がインライン化されていることと、これらのサンプルではString
型が初期化されていません。特に、A
のコンストラクターの本体内では、Main
の本体内では_string.Empty
_が正しく割り当てられていますが、_string.Empty
_は正しく割り当てられていません。
String
の型の初期化が、何らかの形でA
が値型で具体化されるかどうかに依存することは驚くべきことです。私の唯一の理論は、すべての型で共有される汎用型初期化のための最適化JITコードパスがあり、そのパスはBCL参照型(「特殊型?」)とその状態について仮定するということです。 _public static
_フィールドを持つ他のBCLクラスをざっと見てみると、基本的にallが静的コンストラクター(空のコンストラクターと_System.DBNull
_や_System.Empty
_などのデータ。_public static
_フィールドを持つBCL値型は、静的コンストラクター(たとえば、_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つのバージョン間で同一です。
[〜#〜] edit [〜#〜]
さらに、2つのバージョンのILは、B.Main()
の_A.ctor
_の呼び出しを除いて同一です。最初のバージョンのILには次が含まれます。
_newobj instance void class A`1<object>::.ctor(string&)
_
versus
_... A`1<int32>...
_
第二に。
もう1つ注意すべきことは、A<int>.ctor(out string)
:のJITedコードは、非ジェネリックバージョンと同じであるということです。
BeforeFieldInit
に関連) が原因だと強く思います。静的コンストラクターを明示的に宣言すると、beforefieldinit
が出力され、静的メンバーが静的メンバーアクセスの前に実行される必要があることをランタイムに伝えます。
私は彼らが何らかの形でx64 JITerでこの事実を台無しにして、adifferenttype's静的メンバーは、own静的コンストラクターが既に実行されているクラスからアクセスされ、何らかの形でskips実行中(または間違った順序)静的コンストラクター-したがって、クラッシュを引き起こします。 (nullで初期化されていないため、nullポインター例外、おそらくを取得しません。)
私はnotあなたのコードを実行しているので、この部分は間違っているかもしれませんstring.Format
(または同様のConsole.WriteLine
)は、おそらく明示的なstaticを必要とするlocale関連クラスなど、クラッシュを引き起こす内部にアクセスする必要があります建設。
繰り返しますが、私はそれをテストしていませんが、データを推測するのが最善です。
私の仮説をテストして、それがどうなるか教えてください。
観察ですが、DotPeekは逆コンパイルされたstring.Emptyを示します。
/// <summary>
/// Represents the empty string. This field is read-only.
/// </summary>
/// <filterpriority>1</filterpriority>
[__DynamicallyInvokable]
public static readonly string Empty;
internal sealed class __DynamicallyInvokableAttribute : Attribute
{
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public __DynamicallyInvokableAttribute()
{
}
}
属性がない場合を除いて、独自のEmpty
を同じ方法で宣言すると、MDAを取得できなくなります。
class A<T>
{
static readonly string Empty;
static A() { }
public A()
{
string.Format("{0}", Empty);
}
}