Почему ILGenerator.Emit() вставляет nop коды операций в динамическую сборку?

Я строю небольшой компилятор в C#, поэтому неизбежно мне пришлось вмешиваться в динамические сборки и испускать коды операций. Теперь странным является то, что мои вызовы Emit() создают дополнительные коды операций nop в сгенерированном модуле. В моем случае это не так важно, так как производительность не очень важна, но, честно говоря, сбивает с толку, почему это происходит. Кажется, что это происходит после загрузки или сохранения локальным или аргументам. Любой эксперт по C#/ динамической сборке, который мог бы указать мне на вещи, которые я мог проверить? Я приложил образец сгенерированного кода, если требуется дополнительная информация, пожалуйста, дайте мне знать. Благодарю.

IL_0000:  ldc.i4     0x0
IL_0005:  stloc      c
IL_0009:  nop
IL_000a:  nop
IL_000b:  ldloc      c
IL_000f:  nop
IL_0010:  nop
IL_0011:  stloc      i
IL_0015:  nop
IL_0016:  nop
IL_0017:  ldarg      s
IL_001b:  nop
IL_001c:  nop
IL_001d:  ldloc      i
IL_0021:  nop
IL_0022:  nop
IL_0023:  add
IL_0024:  stloc      $0
IL_0028:  nop
IL_0029:  nop
IL_002a:  ldloc      $0
IL_002e:  nop
IL_002f:  nop
IL_0030:  ldind.i1
IL_0031:  ldc.i4     0x0
IL_0036:  bne.un     IL_0040

IL_003b:  br         IL_008e

IL_0040:  ldloc      c
IL_0044:  nop
IL_0045:  nop
IL_0046:  stloc      $1

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

string programName = "myprogram";

AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName(programName), AssemblyBuilderAccess.RunAndSave);

ModuleBuilder module = n.AssemblyBuilder.DefineDynamicModule(programName, string.Format("{0}.exe", programName), true);

string contextName = string.Format("{0}.{1}", programName, "context");


MethodAttributes attributes = MethodAttributes.Private | MethodAttributes.Static;

MethodBuilder methodBuilder = typeBuilder.DefineMethod(method, attributes, returnType, paramTypes);

foreach (string name in paramNames)
    methodBuilder.DefineParameter(i++, ParameterAttributes.None, name);

ILGenerator Cil = methodBuilder.GetILGenerator();

...

foreach (var g in qLocals)
{
    LocalBuilder localBuilder = Cil.DeclareLocal(type);

    localBuilder.SetLocalSymInfo(g.Name);
}

foreach (var s in strings)
{
    LocalBuilder localBuilder = Cil.DeclareLocal(typeIndexed.DotNetElementType. MakePointerType());

    localBuilder.SetLocalSymInfo(string.Format("_{0}", index));
}

IEnumerable<Quad> jumpTargets =
    (from q in n.Tac
    select q.Addrs.OfType<AddrQuad>()).
    SelectMany(x => x).Select(a => a.Quad).Distinct();

    foreach (Quad q in jumpTargets)
        q.DefineLabel(Cil);
}

Для каждого узла в моем абстрактном синтаксическом дереве (украшенном трехадресным кодом) я просто делаю:

public override void DefaultPost(NodeBase n)
{
    foreach (Quad q in n.Tac)
        q.Emit(Cil);
}

Это последовательность вызовов, которые производит эта функция:

cil.Emit(OpCodes.Ldloc, Index);

cil.Emit(OpCodes.Stloc, Index);

cil.Emit(OpCodes.Ldc_I4, (int)this.i);

cil.Emit(OpCodes.Stloc, Index);

cil.Emit(OpCodes.Ldloc, Index);

cil.Emit(OpCodes.Ldc_I4, (int)this.i);

cil.Emit(OpCodes.Br, res.Quad.Label.Value);

cil.Emit(OpCodes.Ldloc, Index);

cil.Emit(OpCodes.Ldc_I4, (int)this.i);

cil.Emit(OpCodes.Stloc, Index);

cil.Emit(OpCodes.Ldloc, Index);

cil.Emit(OpCodes.Stloc, Index);

cil.Emit(OpCodes.Ldloc, Index);

cil.Emit(OpCodes.Ldc_I4, (int)this.i);

cil.Emit(OpCodes.Bge, quad.Label.Value);

cil.Emit(OpCodes.Br, res.Quad.Label.Value);

...

Я не знаю, поможет ли это, если вы хотите проверить мой полный проект, он находится по адресу:

http://github.com/yannikab/grc

Все, что связано с генерацией целевого кода, находится в пространстве имен Cil. Класс, объединяющий все для генерации кода, называется CilVisitor.

1 ответ

Решение

Как указано в комментариях, для Ldarg, Stloc а также Ldloc коды операций, вы должны использовать Emit перегрузка, которая принимает short в качестве второго параметра, тогда как ваш Index предположительно intтак неправильно Emit перегрузка используется. Генератор IL не проверяет это, а просто выводит все 4 байта значения в поток IL. 2 старших байта равны нулю, что nop в IL, следовательно, nopв вашей разборки.

Либо изменить тип Index к short или бросить его при переходе к Emit,

Другие вопросы по тегам