为什么 IL.Emit 方法添加额外的 nop 指令?
Why is IL.Emit method adding additional nop instructions?
我有这段代码发出一些 IL
指令,在 null
对象上调用 string.IndexOf
:
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
指令?
ILGenerator
不是很高级,如果你使用 Emit(OpCode, Int32)
重载,它会将整个 int32
放入指令流中,无论操作码是否为 Ldc_I4
(实际上需要 4 个字节的立即数)或 Ldc_I4_S
(不需要)。
因此请确保使用正确的重载:
ilGenerator.Emit(OpCodes.Ldc_I4_S, (byte)120);
文档中的 lemmas for the opcodes 指定 Emit
的哪个重载是正确的。
在 reference source 中,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
指令,但这只是在它们实际上为零的情况下。如果传递的值是 "more wrong"(高字节不为零),那么效果可能会更糟,从无效的操作码到巧妙地破坏结果的操作。
IlGenerator.Emit的documentation提到了这个:
Remarks If the opcode parameter requires an argument, the caller must
ensure that the argument length matches the length of the declared
parameter. Otherwise, results will be unpredictable. For example, if
the Emit instruction requires a 2-byte operand and the caller supplies
a 4-byte operand, the runtime will emit two additional bytes to the
instruction stream. These extra bytes will be Nop instructions.
The instruction values are defined in OpCodes.
文档中提到了您的说明
Ldc_I4_S
将提供的 int8 值作为 int32 的缩写形式推入计算堆栈。
这三个额外的 nop 似乎来自 int8 而不是 int32。
我有这段代码发出一些 IL
指令,在 null
对象上调用 string.IndexOf
:
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
指令?
ILGenerator
不是很高级,如果你使用 Emit(OpCode, Int32)
重载,它会将整个 int32
放入指令流中,无论操作码是否为 Ldc_I4
(实际上需要 4 个字节的立即数)或 Ldc_I4_S
(不需要)。
因此请确保使用正确的重载:
ilGenerator.Emit(OpCodes.Ldc_I4_S, (byte)120);
文档中的 lemmas for the opcodes 指定 Emit
的哪个重载是正确的。
在 reference source 中,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
指令,但这只是在它们实际上为零的情况下。如果传递的值是 "more wrong"(高字节不为零),那么效果可能会更糟,从无效的操作码到巧妙地破坏结果的操作。
IlGenerator.Emit的documentation提到了这个:
Remarks If the opcode parameter requires an argument, the caller must ensure that the argument length matches the length of the declared parameter. Otherwise, results will be unpredictable. For example, if the Emit instruction requires a 2-byte operand and the caller supplies a 4-byte operand, the runtime will emit two additional bytes to the instruction stream. These extra bytes will be Nop instructions.
The instruction values are defined in OpCodes.
文档中提到了您的说明
Ldc_I4_S
将提供的 int8 值作为 int32 的缩写形式推入计算堆栈。
这三个额外的 nop 似乎来自 int8 而不是 int32。