Почему метод IL.Emit добавляет дополнительные инструкции nop? - PullRequest
0 голосов
/ 16 сентября 2018

У меня есть этот код, который выдает некоторые IL инструкции, которые вызывают string.IndexOf для null объекта:

MethodBuilder methodBuilder = typeBuilder.DefineMethod(
                                             "Foo",
                                             MethodAttributes.Public,
                                             typeof(void), Array.Empty<Type>());
var methodInfo = typeof(string).GetMethod("IndexOf", new[] {typeof(char)});
ILGenerator ilGenerator = methodBuilder.GetILGenerator();

ilGenerator.Emit(OpCodes.Ldnull);
ilGenerator.Emit(OpCodes.Ldc_I4_S, 120);
ilGenerator.Emit(OpCodes.Call, methodInfo);
ilGenerator.Emit(OpCodes.Ret);

Это сгенерированный IL код:

.method public instance int32  Foo() cil managed
{
  // Code size       12 (0xc)
  .maxstack  2
  IL_0000:  ldnull
  IL_0001:  ldc.i4.s   120
  IL_0003:  nop
  IL_0004:  nop
  IL_0005:  nop
  IL_0006:  call       instance int32 [mscorlib]System.String::IndexOf(char)
  IL_000b:  ret
} // end of method MyDynamicType::Foo

Как видите, перед инструкцией call есть три nop инструкции.

Сначала я подумал о сборке Debug / Release, но это не сгенерированный компилятором код, я испускаю сырой код IL и ожидаю увидеть его как есть.

Итак, мой вопрос: почему есть три nop инструкции, когда я их не выпустил?

Ответы [ 2 ]

0 голосов
/ 16 сентября 2018

Документация от IlGenerator. Эмит упоминает это:

Замечания Если параметр opcode требует аргумент, вызывающая сторона должна убедитесь, что длина аргумента совпадает с длиной объявленного параметр. В противном случае результаты будут непредсказуемыми. Например, если инструкция Emit требует 2-байтового операнда и вызывающего 4-байтовый операнд, среда выполнения испустит два дополнительных байта поток инструкций. Эти дополнительные байты будут инструкциями Nop.

Значения команд определены в кодах операций.

И в документации упоминается о вашей инструкции

Ldc_I4_S
Помещает предоставленное значение int8 в стек оценки в виде краткой формы int32 .

Кажется, что три дополнительных nops приходят от int8 вместо int32.

0 голосов
/ 16 сентября 2018

ILGenerator не очень продвинутый, если вы используете перегрузку Emit(OpCode, Int32), это поместит весь int32 в поток команд, независимо от того, является ли код операции Ldc_I4 (который на самом деле требует 4 байта сразу) или Ldc_I4_S (чего нет).

Поэтому обязательно используйте правильную перегрузку:

ilGenerator.Emit(OpCodes.Ldc_I4_S, (byte)120);

Леммы для кодов операций в документации указывают, какую перегрузку Emit следует использовать.


В справочном источнике , Emit с аргументом int делает это:

public virtual void Emit(OpCode opcode, int arg) 
{
    // Puts opcode onto the stream of instructions followed by arg
    EnsureCapacity(7);
    InternalEmit(opcode);
    PutInteger4(arg);
}

Где PutInteger4 записывает четыре байта в байтовый массив, в котором построен IL.

В документации Emit говорится, что дополнительные байты будут Nop инструкциями, но это только если они фактически равны нулю. Если передаваемое значение «более неправильное» (с старшими байтами, отличными от нуля), то последствия могут быть хуже, от недопустимых кодов операций до операций, которые слегка искажают результаты.

...