如何防止编译器生成比较而不是分支指令?
How to prevent the compiler from generating comparison instead of branching instructions?
鉴于分支指令的以下相同属性(来自 microsoft):
- blt:效果 与执行 clt 指令后跟 brtrue 相同分支到特定的目标指令。
- bgt:效果与执行 cgt 指令后跟 brtrue 相同分支到特定目标指令
- bge:效果 与执行 clt 指令相同(clt.un 用于浮点数)后跟指向特定目标指令的 brfalse 分支。
- beq:效果与执行 ceq 指令后跟到特定目标指令的 brtrue 分支相同。
事实证明,编译器通常会通过将诸如“<”之类的布尔运算符转换为其 IL 补码分支指令 (clt) 来优化控制流。因此,在不同的计算机上,用于比较的 IL 代码可能会有所不同。我的编译器总是会生成比较运算符,但是在另一台 PC 上我看到它将相同的代码编译成分支变体。
我需要特定的 C# 示例生成始终分支运算符 (blt/bgt/bge/beq) 或始终生成比较运算符 (clt/cgt/clt/ceq),后跟分支 true。我的应用程序测试需要能够断言此 IL 代码。
我的尝试:
- 添加
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
- If/while/for/do-while/switch 操作,但对我来说都是比较运算符。
- 尝试了多种示例,但未能生成简单的 if 导致使用分支运算符。
用例
我使用将 C# 文件编译成程序集的单元测试,然后这些测试搜索某个操作码 (clt) 并找到可能的逆操作码 (cgt)。这是测试我编写的 C#-mutation 测试工具所必需的。此变异工具在字节级的 dll 代码上变异(反转运算符)。在 C# 源代码级别,我们使用“<”,但在 IL 字节码级别,它可以是 clt 或 blt。
资源
- 微软操作码documentation
- 操作码含义列表documentation
- 使用 IL-spy/Dn-spy 之类的工具检查 IL 生成的指令。
我编写了一个生成器,它能够确保方法定义包含分支变体而不是比较变体。它通过为分支变体建立一个完整的方法体来做到这一点。此生成器仅适用于文档中所示的方法。我正在使用这种方法进行测试。
/// <summary>
/// Generator for a conditional branch statement.
/// This generator will only work for the following method structure:
/// * Returns true if condition is met
/// * Returns false if condition is not met
/// * Two int parameters.
/// * One if check with the condition.
/// ```
/// public bool ConditionCheck(int lhs, int rhs)
/// {
/// if (lhs (ANY CONDITION) rhs)
/// {
/// return true;
/// }
/// return false;
/// }
/// ```
/// In some cases the compiler can generate 'comparison' like clt/cgt instead of the branching variants blt/bgt.
/// This generator is able to override a method body were an comparison is occurring with the branching variant.
/// </summary>
internal class ConditionalBranchGenerator
{
private readonly List<Instruction> _instructions;
/// <summary>
/// Generates a new instance by passing in the branching operator that will be used in the generation process.
/// Accepts 'only' a branching operators like bgt/blt/beq etc..
/// </summary>
/// <param name="comparison"></param>
public ConditionalBranchGenerator(OpCode comparison)
{
var ia = Instruction.Create(OpCodes.Ldloc_1);
var ib = Instruction.Create(OpCodes.Ret);
var i7 = Instruction.Create(OpCodes.Ldc_I4_1);
// load the two parameter booleans
var i1 = Instruction.Create(OpCodes.Ldarg_1);
var i2 = Instruction.Create(OpCodes.Ldarg_2);
// if comparison true branch to i7.
var i3 = Instruction.Create(comparison, i7);
// if comparison false load '0' (false) and branch to return.
var i4 = Instruction.Create(OpCodes.Ldc_I4_0);
var i5 = Instruction.Create(OpCodes.Stloc_1);
var i6 = Instruction.Create(OpCodes.Br_S, ia);
var i8 = Instruction.Create(OpCodes.Stloc_1);
var i9 = Instruction.Create(OpCodes.Br_S, ia);
i1.Next = i2;
i2.Next = i3;
i3.Next = i4;
i4.Next = i5;
i5.Next = i6;
i6.Next = i7;
i7.Next = i8;
i8.Next = i9;
i9.Next = ia;
ia.Next = ib;
i2.Previous = i1;
i3.Previous = i2;
i4.Previous = i3;
i5.Previous = i4;
i6.Previous = i5;
i7.Previous = i6;
i8.Previous = i7;
i9.Previous = i8;
ia.Previous = i9;
ib.Previous = ia;
i1.Offset = 0;
i2.Offset = 1;
i3.Offset = 2;
i4.Offset = 3;
i5.Offset = 4;
i6.Offset = 5;
i7.Offset = 6;
i8.Offset = 7;
i9.Offset = 8;
ia.Offset = 9;
ib.Offset = 10;
_instructions = new List<Instruction>
{
i1, i2, i3, i4, i5, i6, i7, i8, i9, ia, ib
};
}
/// <summary>
/// Replace a method body were a comparison is occurring with the branching variant.
/// Note that the method definition must ad-hear to strict rules noted in the class-level comments.
/// </summary>
/// <param name="method"></param>
public void ReplaceMethodInstructions(MethodDefinition method)
{
method.Body.Instructions.Clear();
foreach (var instruction in _instructions)
method.Body.Instructions.Add(instruction);
}
}
鉴于分支指令的以下相同属性(来自 microsoft):
- blt:效果 与执行 clt 指令后跟 brtrue 相同分支到特定的目标指令。
- bgt:效果与执行 cgt 指令后跟 brtrue 相同分支到特定目标指令
- bge:效果 与执行 clt 指令相同(clt.un 用于浮点数)后跟指向特定目标指令的 brfalse 分支。
- beq:效果与执行 ceq 指令后跟到特定目标指令的 brtrue 分支相同。
事实证明,编译器通常会通过将诸如“<”之类的布尔运算符转换为其 IL 补码分支指令 (clt) 来优化控制流。因此,在不同的计算机上,用于比较的 IL 代码可能会有所不同。我的编译器总是会生成比较运算符,但是在另一台 PC 上我看到它将相同的代码编译成分支变体。
我需要特定的 C# 示例生成始终分支运算符 (blt/bgt/bge/beq) 或始终生成比较运算符 (clt/cgt/clt/ceq),后跟分支 true。我的应用程序测试需要能够断言此 IL 代码。
我的尝试:
- 添加
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
- If/while/for/do-while/switch 操作,但对我来说都是比较运算符。
- 尝试了多种示例,但未能生成简单的 if 导致使用分支运算符。
用例
我使用将 C# 文件编译成程序集的单元测试,然后这些测试搜索某个操作码 (clt) 并找到可能的逆操作码 (cgt)。这是测试我编写的 C#-mutation 测试工具所必需的。此变异工具在字节级的 dll 代码上变异(反转运算符)。在 C# 源代码级别,我们使用“<”,但在 IL 字节码级别,它可以是 clt 或 blt。
资源
- 微软操作码documentation
- 操作码含义列表documentation
- 使用 IL-spy/Dn-spy 之类的工具检查 IL 生成的指令。
我编写了一个生成器,它能够确保方法定义包含分支变体而不是比较变体。它通过为分支变体建立一个完整的方法体来做到这一点。此生成器仅适用于文档中所示的方法。我正在使用这种方法进行测试。
/// <summary>
/// Generator for a conditional branch statement.
/// This generator will only work for the following method structure:
/// * Returns true if condition is met
/// * Returns false if condition is not met
/// * Two int parameters.
/// * One if check with the condition.
/// ```
/// public bool ConditionCheck(int lhs, int rhs)
/// {
/// if (lhs (ANY CONDITION) rhs)
/// {
/// return true;
/// }
/// return false;
/// }
/// ```
/// In some cases the compiler can generate 'comparison' like clt/cgt instead of the branching variants blt/bgt.
/// This generator is able to override a method body were an comparison is occurring with the branching variant.
/// </summary>
internal class ConditionalBranchGenerator
{
private readonly List<Instruction> _instructions;
/// <summary>
/// Generates a new instance by passing in the branching operator that will be used in the generation process.
/// Accepts 'only' a branching operators like bgt/blt/beq etc..
/// </summary>
/// <param name="comparison"></param>
public ConditionalBranchGenerator(OpCode comparison)
{
var ia = Instruction.Create(OpCodes.Ldloc_1);
var ib = Instruction.Create(OpCodes.Ret);
var i7 = Instruction.Create(OpCodes.Ldc_I4_1);
// load the two parameter booleans
var i1 = Instruction.Create(OpCodes.Ldarg_1);
var i2 = Instruction.Create(OpCodes.Ldarg_2);
// if comparison true branch to i7.
var i3 = Instruction.Create(comparison, i7);
// if comparison false load '0' (false) and branch to return.
var i4 = Instruction.Create(OpCodes.Ldc_I4_0);
var i5 = Instruction.Create(OpCodes.Stloc_1);
var i6 = Instruction.Create(OpCodes.Br_S, ia);
var i8 = Instruction.Create(OpCodes.Stloc_1);
var i9 = Instruction.Create(OpCodes.Br_S, ia);
i1.Next = i2;
i2.Next = i3;
i3.Next = i4;
i4.Next = i5;
i5.Next = i6;
i6.Next = i7;
i7.Next = i8;
i8.Next = i9;
i9.Next = ia;
ia.Next = ib;
i2.Previous = i1;
i3.Previous = i2;
i4.Previous = i3;
i5.Previous = i4;
i6.Previous = i5;
i7.Previous = i6;
i8.Previous = i7;
i9.Previous = i8;
ia.Previous = i9;
ib.Previous = ia;
i1.Offset = 0;
i2.Offset = 1;
i3.Offset = 2;
i4.Offset = 3;
i5.Offset = 4;
i6.Offset = 5;
i7.Offset = 6;
i8.Offset = 7;
i9.Offset = 8;
ia.Offset = 9;
ib.Offset = 10;
_instructions = new List<Instruction>
{
i1, i2, i3, i4, i5, i6, i7, i8, i9, ia, ib
};
}
/// <summary>
/// Replace a method body were a comparison is occurring with the branching variant.
/// Note that the method definition must ad-hear to strict rules noted in the class-level comments.
/// </summary>
/// <param name="method"></param>
public void ReplaceMethodInstructions(MethodDefinition method)
{
method.Body.Instructions.Clear();
foreach (var instruction in _instructions)
method.Body.Instructions.Add(instruction);
}
}