Branching

Branching

The branch instruction is available in various permutations, but in all circumstances it is essentially a goto statement. The target of a branch instruction is a label. Most branch instructions are conditional and based on a comparative expression or a Boolean condition. For an unconditional goto, use the br instruction. Loop and other transfer-of-control statements in C# are implemented as some combination of branch instructions.

A conditional branch can be made with the brtrue and brfalse statements. The brtrue instruction branches on a true condition, whereas the brfalse branches on a false condition. These instructions consume a Boolean value, which should be on the evaluation stack.

Compare instructions perform a comparison on the top two values of the evaluation stack. The two values are replaced on the evaluation stack with the result of the comparison, which is either true or false. The comparison should be between related types.

Table 11-7 lists the compare instructions. In the table, assume that t2 is top value on the evaluation stack, and t1 is the second value. t2 is the last item placed on the evaluation stack and the next to be removed.

Table 11-7: Compare Instructions

Instruction

Comparison

ceq

t1 equal to t2

cgt

t1 greater than t2

clt

t1 less than t2

cgt.un and clt.un

Unsigned or unordered version of the comparison operations

This sample code shows unconditional and conditional branching. It also contains an example of a comparison instruction:

.assembly extern mscorlib {}
.assembly application {}

.namespace Donis.CSharpBook {

    .class Starter {

        .method static public void Main() il managed {
            .entrypoint
            ldc.i4.3
            ldc.i4.1
            cgt
            brtrue greater
            ldstr "{0} is less than or equal {1}"
            br end
greater:    ldstr "{0} is greater than {1}"
end:        ldc.i4.3
            box int32
            ldc.i4.m1
            box int32
            call void [mscorlib] System.Console::WriteLine(
                string, object, object)
            ret
        }
    }
}

As a convenience, branch and compare instructions are combinable. The combined instruction compares the top two values of the evaluation stack and branches on the result. These are called comparative branching instructions. Instead of requiring two instructions to perform the test, only one is needed.

Table 11-8 lists comparative branching instructions.

Table 11-8: Comparative Branching

Instruction

Description

Beq

Branch on equal

Bne

Branch on not equal

Bge

Branch on greater than or equal

Bgt

Branch on greater than

Ble

Branch on less than or equal

Blt

Branch on less than

bgt.un, blt.un, and bne.un

The unsigned version of these instructions

Here is an example of a for loop in MSIL code. The loop increments the count from zero to five. The current count is displayed in iterations of the loop:

.assembly extern mscorlib {}
.assembly application {}

.namespace Donis.CSharpBook {

    .class Starter {

        .method static public void Main() il managed {
            .entrypoint
            .locals (int32 count)
            ldc.i4.0
            stloc.0
            br.s loop
for:        ldloc count
            ldc.i4.1
            add
            dup
            stloc count
            call void [mscorlib] System.Console::WriteLine(int32)
loop:       ldloc count
            ldc.i4.5
            clt
            brtrue.s for
            ret
        }
        }
}

The sample code uses the short form of the branch instruction. Be careful when branching in the short form. As with all short-form instructions, the operand is limited to a single byte. The short form of branch instructions cannot jump to a label further than 1 byte away. The distance from the branch site to the label must be describable in a single byte. If not, the application might branch to the wrong location.

Calling Methods

There are a variety of ways to call methods. So far, only the call and callvirt methods have been shown in sample code. Some instructions or actions, such as newobj, call a method implicitly. The newobj instruction calls a constructor, whereas the static constructor is called implicitly on the first access to the class or object.

The call methods have the same general syntax:

  • callsuffix returntype [assembly] signature

The returntype is placed on the evaluation stack; assembly is the location of the method. If in the current assembly, the assembly element can be ignored. The complete signature of the method is represented by signature.

Table 11-9 lists the call instructions in MSIL.

Table 11-9: Call Instructions

Instruction

Description

call

Intended for calling nonvirtual methods. However, the instruction can be applied to virtual methods. The invocation is still interpreted as a nonvirtual call.

callvirt

Calls a virtual method. For nonvirtual methods, a nonvirtual call is conducted.

calli

Calls a function indirectly through a function pointer. Place each function parameter and then the function pointer on the evaluation stack. The calli syntax is slightly different from other call instructions. It does not include the target assembly or method name in the syntax. Use the ldftn instruction to place a function pointer on the stack for a particular method.

This sample code includes both the calli and ldftn instructions:

.assembly extern mscorlib {}
.assembly application {}

.namespace Donis.CSharpBook {

  .class Starter {

    .method static public void Main() il managed {
      .entrypoint
      ldstr "Donis"
      ldftn void
Donis.CSharpBook.Starter::Name(string)
      calli void(string)
      ret
    }

    .method static public void Name(string)
      il managed {
      ldstr "Hello, {0}!"
      ldarg.0
      call void [mscorlib]
      System.Console::WriteLine(string, object)
      ret
    }
 }
}

jmp

Jumps from the current method to the target method and transfers the arguments. The caller and callee must have matching signatures. Code after the jmp site in the calling function is abandoned.

 

Here is sample code of the jmp instruction:

.assembly extern mscorlib {}
.assembly application {}
.namespace Donis.CSharpBook {
    .class Starter {
      .method static public void Main() il managed {
        .entrypoint
        ldstr "Aloha!"
        call void
 Donis.CSharpBook.Starter::MethodA(string)
        ret
      }
      .method static public void
        MethodA(string) il managed {
        ldstr "Before jump"
        call void [mscorlib]
        System.Console::WriteLine(string)
        ldstr "In MethodA: {0}"
        ldarg.0
        call void [mscorlib]
        System.Console::WriteLine(string, object)
        jmp void
 Donis.CSharpBook.Starter::MethodB(string)
        ldstr "After jump"
        call void [mscorlib]
        System.Console::WriteLine(string)
        ret
      }
      .method static public void
        MethodB(string) il managed {
        ldstr "In MethodB: {0}"
        ldarg.0
        call void [mscorlib]
        System.Console::WriteLine(string, object)
        ret
     }
   }
}

This program jumps from MethodA to MethodB. In MethodA, the instruction after the jmp instruction is orphaned. Therefore, the message "After jump" is not displayed. MethodB returns directly to Main, not MethodA.

tail

The tail command is a prefix instruction and similar to the jmp command. However, the arguments must be loaded explicitly on the evaluation stack and the method signatures can be different. Otherwise, the functions are operationally equivalent.

This is the syntax of the tail instruction:

  • tail.

  • callsuffix returntype [assembly] signature