Как запретить компилятору генерировать сравнение вместо инструкций ветвления?

#c# #bytecode #cil #opcode

#c# #байт-код #cil #код операции

Вопрос:

Учитывая следующие идентичные свойства инструкций ветвления (от Microsoft):

  1. blt: эффект идентичен выполнению инструкции clt, за которой следует переход brtrue к конкретной целевой инструкции.
  2. bgt: эффект идентичен выполнению инструкции cgt, за которой следует переход brtrue к конкретной целевой инструкции
  3. bge: эффект идентичен выполнению инструкции clt (clt.un для чисел с плавающей запятой), за которой следует переход brfalse к конкретной целевой инструкции.
  4. beq: эффект такой же, как при выполнении инструкции ceq, за которой следует переход brtrue к конкретной целевой инструкции.

Оказывается, что компилятор обычно оптимизирует поток управления, переводя логический оператор типа ‘<‘ в свою инструкцию ветвления дополнения IL (clt). Поэтому может быть так, что на разных компьютерах IL-код для сравнения может отличаться. Мой компилятор всегда будет генерировать оператор сравнения, однако на другом компьютере я увидел, что он скомпилировал тот же код в вариант ветвления.

Мне нужны конкретные примеры C #, генерирующие операторы всегда ветвления (blt / bgt / bge / beq) или всегда операторы сравнения (clt / cgt / clt / ceq), за которыми следует значение ветвления true. Мои тесты моего приложения должны иметь возможность утверждать этот IL-код.

Мои попытки:

  • Добавить [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
  • Операции If/while/for/do-while / switch, но все они приводят к операторам сравнения для меня.
  • Поиграл со многими примерами, но безуспешно при генерации простого if, что привело к использованию операторов ветвления.

Usecase

Я использую модульные тесты, которые компилируют файл C # в сборку, затем эти тесты ищут определенный код операции (clt) и находят возможный обратный (cgt). Это требуется для тестирования инструмента тестирования мутаций на C #, который я написал. Этот инструмент мутации мутирует (инвертирует операторы) в коде dll на байтовом уровне. На уровне исходного кода C # мы используем ‘<‘, однако на уровне IL-байт-кода это может быть либо clt, либо blt.

Ресурсы

Комментарии:

1. Если вам нужны определенные последовательности IL, почему вы пытаетесь заставить компилятор C # генерировать их, а не генерировать их самостоятельно явно?

2. Не беспокойтесь об IL — беспокойтесь о ASM, который генерирует JIT

3. Я использую модульные тесты, которые компилируют файл C # в сборку, затем они ищут определенный код операции (clt) и находят возможный обратный (cgt). Это требуется для тестирования инструмента тестирования мутаций на C #, который я написал. Этот инструмент мутации изменяет dll-код на байтовом уровне.

4. Итак, вы проводите модульное тестирование инструмента, который работает на IL, и хотите убедить компилятор выводить определенные последовательности IL? Не делайте этого — это невероятно зависит от версии компилятора. Создайте последовательности IL, которые вы хотите протестировать самостоятельно

5. @canton7 Может быть, это то, что я могу сделать здесь. Я мог бы написать некоторую логику, которая гарантирует наличие правильного IL-кода. Так что, если у меня нет возможности определить выходной IL-код при генерации, я думаю, это было бы неплохо сделать.

Ответ №1:

Я написал генератор, который может убедиться, что определение метода содержит вариант ветвления вместо варианта сравнения. Это достигается путем создания полного тела метода для варианта ветвления. Этот генератор работает только с методом, как показано в документации. Такой метод я использую для своих тестов.

     /// <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);
        }
    }