Почему 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
,