为什么 ILGenerator.Emit() 在动态汇编中插入 nop 操作码?
Why does ILGenerator.Emit() insert nop opcodes in dynamic assembly?
我正在用 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 [=11=]
IL_0028: nop
IL_0029: nop
IL_002a: ldloc [=11=]
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
根据要求,下面是我的代码的概要。缺少一些东西,因为代码分为单独的
模块,这些是按执行顺序排列的最相关的部分。
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 命名空间下。 class 将代码生成的所有内容组合在一起的名称为 CilVisitor。
如评论中所示,对于 Ldarg
、Stloc
和 Ldloc
操作码,您应该使用接受 [=14= 的 Emit
重载] 作为第二个参数,而您的 Index
可能是 int
,因此使用了错误的 Emit
重载。 IL 生成器不检查它,只是将值的所有 4 个字节输出到 IL 流。 2 个高位字节为零,在 IL 中为 nop
,因此反汇编中的 nop
s。
将 Index
的类型更改为 short
或在传递给 Emit
时强制转换它。
我正在用 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 [=11=]
IL_0028: nop
IL_0029: nop
IL_002a: ldloc [=11=]
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
根据要求,下面是我的代码的概要。缺少一些东西,因为代码分为单独的 模块,这些是按执行顺序排列的最相关的部分。
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 命名空间下。 class 将代码生成的所有内容组合在一起的名称为 CilVisitor。
如评论中所示,对于 Ldarg
、Stloc
和 Ldloc
操作码,您应该使用接受 [=14= 的 Emit
重载] 作为第二个参数,而您的 Index
可能是 int
,因此使用了错误的 Emit
重载。 IL 生成器不检查它,只是将值的所有 4 个字节输出到 IL 流。 2 个高位字节为零,在 IL 中为 nop
,因此反汇编中的 nop
s。
将 Index
的类型更改为 short
或在传递给 Emit
时强制转换它。