Может кто-нибудь объяснить эти несколько строк MSIL? - PullRequest
5 голосов
/ 19 мая 2009

Может кто-нибудь объяснить эти несколько строк MSIL? Почему он перемещает значение из стека оценки в локальную переменную, только чтобы сразу же переместить его назад и вернуть?

Следующий код MSIL загружает один аргумент (строку), вызывает метод, который возвращает bool, а затем возвращает это значение bool. Я не понимаю, почему он вызывает stloc.0, чтобы сохранить возвращаемое значение метода в локальной переменной, а затем выполняет явный безусловный перенос управления на следующую помеченную строку (кажется ненужным), только чтобы переместить значение обратно на стек оценки перед возвратом.

.maxstack 1
.locals init ([0] bool CS$1$0000)
L_0000: nop
L_0001: ldarg.0
L_0002: call bool FuncNameNotImporant::MethodNameNotImporant(string)
L_0007: stloc.0 
L_0008: br.s L_000a
L_000a: ldloc.0 
L_000b: ret 

Мое лучшее предположение о том, почему это происходит, состоит в том, чтобы выполнить какую-то проверку типа, чтобы убедиться, что значение в стеке оценки на самом деле является логическим значением перед его возвратом. Но я ничего не понимаю о явном переходе к следующей строке; Я имею в виду, разве это не пойдет туда в любом случае? Исходный код C # для метода - это всего одна строка, которая возвращает результат метода.

Ответы [ 2 ]

7 голосов
/ 20 мая 2009

Если вы откроете эту функцию в отладчике с кодом, скомпилированным в режиме отладки:

bool foo(string arg)
{
    return bar(arg);
}

Вы можете установить 3 точки останова:

  1. На открывающей скобке функции.
  2. В строке «возврат».
  3. На закрывающей скобке функции.

Установка точки останова на открывающей скобке означает «разрыв, когда вызывается эта функция». Вот почему в начале метода есть инструкция no-op. Когда точка останова установлена ​​на открывающей скобке, отладчик фактически устанавливает ее в нерабочем состоянии.

Установка точки останова на закрывающей скобке означает «разрыв, когда эта функция завершается». Чтобы это произошло, функция должна иметь одну инструкцию возврата в своем IL, где можно установить точку останова. Компилятор позволяет это, используя временную переменную для хранения возвращаемого значения и конвертируя

return retVal;

в

$retTmp = retVal;
goto exit;

, а затем вставьте следующий код в конец метода:

exit:
return $ret;

Кроме того, когда в режиме отладки компиляторы глупы в отношении кода, который они генерируют. Они в основном делают что-то вроде:

GenerateProlog();
foreach (var statement in statements)
{
    Generate(statement);
}
GenerateEpilog();

В вашем случае вы видите:

return foo(arg);

переводится на:

; //this is a no-op
bool retTemp = false;
retTemp = foo(arg);
goto exit;
exit:
return retTemp;

Если бы компилятор выполнял «оптимизацию скользящего окна», он мог бы взглянуть на этот код и понять, что существует некоторая избыточность. Однако компиляторы обычно не делают этого в режиме отладки. Оптимизация компилятора может выполнять такие вещи, как устранение переменных и изменение порядка команд, что затрудняет отладку. Поскольку целью отладочной сборки является включение отладки, было бы нецелесообразно включать оптимизацию.

В сборке релиза код не будет выглядеть так. Это связано с тем, что компилятор не вводит специальный код для включения точек останова на открывающей и закрывающей скобках, в результате чего компилируется следующее:

return bar(arg);

Все выглядит довольно просто.

Следует отметить, однако, что я не думаю, что компилятор C # делает много оптимизаций скользящего окна, даже в розничных сборках. Это потому, что большинство этих оптимизаций зависят от базовой архитектуры процессора и выполняются компилятором JIT. Выполнение оптимизаций, даже тех, которые не зависят от процессора, в компиляторе C # может препятствовать способности JIT оптимизировать код (он ищет шаблоны, сгенерированные неоптимизированной генерацией кода, и если он видит сильно оптимизированный IL, он может получить смущенный). Поэтому обычно компиляторы управляемого кода не делают их. Он делает некоторые «дорогие вещи» (которые JIT не хочет делать во время выполнения), такие как обнаружение мертвого кода и анализ переменных в реальном времени, но они не решают проблемы, решаемые путем оптимизации скользящего окна.

4 голосов
/ 19 мая 2009

Вы компилируете в режиме отладки или выпуска? В режиме релиза я получаю:

.method private hidebysig static bool Test1(string arg) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: call bool FuncNameNotImportant::MethodNameNotImportant(string)
    L_0006: ret 
}

Ответвление, которое вы видите, вероятно, для поддержки отладчика.

...