В настоящее время я проверяю глубокие объекты в CLR с помощью Profiler API. У меня есть конкретная проблема, связанная с анализом аргумента «this» для итераторов / асинхронных методов (генерируемых компилятором в виде <name>d__123::MoveNext
).
Исследуя это, я обнаружил, что действительно существует особое поведение. Во-первых, компилятор C # компилирует эти сгенерированные методы как структуры (только в режиме Release). ECMA-334 (спецификация языка C #, 5-е издание: https://www.ecma -international.org / публикации / файлы / ECMA-ST / ECMA-334.pdf ) сообщает (12.7.8 этот доступ):
"... Если метод или метод доступа являются итератором или асинхронной функцией, переменная this представляет копию
структура, для которой был вызван метод или метод доступа, .... "
Это означает, что в отличие от других аргументов "this", в этом случае слово "this" отправляется по значению, а не по ссылке. Я действительно вижу, что копия не изменена снаружи. Я пытаюсь понять, как именно отправляется структура.
Я позволил себе разобрать сложный случай и воспроизвести его с помощью небольшой структуры. Посмотрите на следующий код:
struct Struct
{
public static void mainFoo()
{
Struct st = new Struct();
st.a = "String";
st.p = new Program();
System.Console.WriteLine("foo: " + st.foo1());
System.Console.WriteLine("static foo: " + Struct.foo(st));
}
int i;
String a;
Program p;
[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static int foo(Struct st)
{
return st.i;
}
[MethodImplAttribute(MethodImplOptions.NoInlining)]
public int foo1()
{
return i;
}
}
NoInlining
только для того, чтобы мы могли правильно проверить JITted-код. Я смотрю на три разные вещи: как mainFoo вызывает foo / foo1, как foo компилируется и как foo1 компилируется.
Ниже приводится сгенерированный код IL (с использованием ildasm):
.method public hidebysig static int32 foo(valuetype nitzan_multi_tester.Struct st) cil managed noinlining
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld int32 nitzan_multi_tester.Struct::i
IL_0006: ret
} // end of method Struct::foo
.method public hidebysig instance int32 foo1() cil managed noinlining
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld int32 nitzan_multi_tester.Struct::i
IL_0006: ret
} // end of method Struct::foo1
.method public hidebysig static void mainFoo() cil managed
{
// Code size 86 (0x56)
.maxstack 2
.locals init ([0] valuetype nitzan_multi_tester.Struct st)
IL_0000: ldloca.s st
IL_0002: initobj nitzan_multi_tester.Struct
IL_0008: ldloca.s st
IL_000a: ldstr "String"
IL_000f: stfld string nitzan_multi_tester.Struct::a
IL_0014: ldloca.s st
IL_0016: newobj instance void nitzan_multi_tester.Program::.ctor()
IL_001b: stfld class nitzan_multi_tester.Program nitzan_multi_tester.Struct::p
IL_0020: ldstr "foo: "
IL_0025: ldloca.s st
IL_0027: call instance int32 nitzan_multi_tester.Struct::foo1()
IL_002c: box [mscorlib]System.Int32
IL_0031: call string [mscorlib]System.String::Concat(object,
object)
IL_0036: call void [mscorlib]System.Console::WriteLine(string)
IL_003b: ldstr "static foo: "
IL_0040: ldloc.0
IL_0041: call int32 nitzan_multi_tester.Struct::foo(valuetype nitzan_multi_tester.Struct)
IL_0046: box [mscorlib]System.Int32
IL_004b: call string [mscorlib]System.String::Concat(object,
object)
IL_0050: call void [mscorlib]System.Console::WriteLine(string)
IL_0055: ret
} // end of method Struct::mainFoo
Сгенерированный код сборки (только для соответствующих деталей):
foo/foo1:
mov eax,dword ptr [rcx+10h]
ret
fooMain (line 18):
mov rcx,offset mscorlib_ni+0x8aaf8 (00007ffc`37d6aaf8) (MT: System.Int32)
call clr+0x2510 (00007ffc`392f2510) (JitHelp: CORINFO_HELP_NEWSFAST)
mov rsi,rax
lea rcx,[rsp+40h]
call 00007ffb`d9db04e0 (nitzan_multi_tester.Struct.foo1(), mdToken: 000000000600000b)
mov dword ptr [rsi+8],eax
mov rdx,rsi
mov rcx,1DBCE383690h
mov rcx,qword ptr [rcx]
call mscorlib_ni+0x635bd0 (00007ffc`38315bd0) (System.String.Concat(System.Object, System.Object), mdToken: 000000000600054f)
mov rcx,rax
call mscorlib_ni+0x56d290 (00007ffc`3824d290) (System.Console.WriteLine(System.String), mdToken: 0000000006000b78)
fooMain (line 19):
mov rcx,offset mscorlib_ni+0x8aaf8 (00007ffc`37d6aaf8) (MT: System.Int32)
call clr+0x2510 (00007ffc`392f2510) (JitHelp: CORINFO_HELP_NEWSFAST)
mov rsi,rax
lea rcx,[rsp+28h]
mov rax,qword ptr [rsp+40h]
mov qword ptr [rcx],rax
mov rax,qword ptr [rsp+48h]
mov qword ptr [rcx+8],rax
mov eax,dword ptr [rsp+50h]
mov dword ptr [rcx+10h],eax
lea rcx,[rsp+28h]
call 00007ffb`d9db04d8 (nitzan_multi_tester.Struct.foo(nitzan_multi_tester.Struct), mdToken: 000000000600000a)
mov dword ptr [rsi+8],eax
mov rdx,rsi
mov rcx,1DBCE383698h
mov rcx,qword ptr [rcx]
call mscorlib_ni+0x635bd0 (00007ffc`38315bd0) (System.String.Concat(System.Object, System.Object), mdToken: 000000000600054f)
mov rcx,rax
call mscorlib_ni+0x56d290 (00007ffc`3824d290) (System.Console.WriteLine(System.String), mdToken: 0000000006000b78)
Первое, что мы можем видеть, это то, что и foo, и foo1 генерируют один и тот же код IL (и один и тот же код сборки JITted). Это имеет смысл, поскольку в конечном итоге мы просто используем первый аргумент. Второе, что мы видим, это то, что mainFoo вызывает два метода по-разному (ldloc vs ldloca). Поскольку и foo, и foo1 ожидают одинакового ввода, я ожидаю, что mainFoo будет отправлять одинаковые аргументы. Это подняло 3 вопроса
1) Что именно означает загрузка структуры в стек по сравнению с загрузкой адреса структуры в этом стеке? Я имею в виду, структура размером более 8 байт (64 бита) не может «сидеть» в стеке.
2) Генерирует ли CLR копию структуры, прежде чем просто использовать ее как «это» (мы знаем, что это правда, согласно спецификации C #)? Где хранится эта копия? Сборка fooMain показывает, что вызывающий метод генерирует копию в своем стеке.
3) Кажется, что загрузка структуры по значению и адресу (ldarg / ldloc vs ldarga / ldloca) фактически загружает адрес - для второго набора он просто создает копию ранее. Зачем? Я что-то здесь упускаю?
4) Возвращаясь к Iterators / async - повторяет ли пример foo / foo1 разницу между аргументом "this" для структур итераторов и не-итераторов? Почему это поведение требуется? Создание копии кажется пустой тратой времени. Какая мотивация?
(Этот пример взят с использованием .Net framework 4.5, но то же поведение наблюдается и с использованием .Net framework 2 и CoreCLR)