Beautiful Code [26]
The actual branch instruction code in Intermediate Language contains a numeric value indicating the address of the destination instruction as an offset to the address of the current instruction. Figuring out these offsets would be too painful for programmers, so a labeling system is provided to make things easier. All that is required is that you define where labels should be inserted in the instruction stream, so that when you indicate a branch to that label, the code generator can calculate the proper numeric offset.
Two method calls are required for using a label. The DefineLabel call defines a label that you can then refer to in branching instructions. The MarkLabel call actually inserts the label into the Intermediate Language instruction stream. This two-step process allows you to define a label and then emit an op code that branches to that label, even though the label won't actually appear until later in the instruction stream. The following lines call both DefineLabel and MarkLabel to put a Label object named labelTop at the top of the iDst loop:
Label labelTop = generator.DefineLabel();
generator.MarkLabel(labelTop);
This label is equivalent to the location of the for statement in line 6 of the C# code listing in Example 8-2. A label is required here because code at the end of the loop has to branch to the top of the loop.
We're now generating code within the iDst loop. This is the per-pixel processing. The first step is to initialize pixelsAccum and filterAccum to 0. The first Emit call shown in the following code snippet has an op code of Ldc_R8, which will load an 8-bit real (that is, a floating-point number) on the stack. The second argument of Emit is the actual number. The type of this number must match the type implied by the op code. If you just use a 0 with no decimal point, the C# compiler will interpret it as an integer, and you won't know you blundered until a runtime exception indicates an invalid program:
generator.Emit(OpCodes.Ldc_R8, 0.0);
generator.Emit(OpCodes.Dup);
generator.Emit(OpCodes.Stloc_1);
generator.Emit(OpCodes.Stloc_2);
The Dup instruction duplicates the 0 value on the stack, and the Stloc_1 and Stloc_2 op codes store the value in the local variables representing pixelsAccum and filterAccum. Again, you must make sure that all the types agree here; otherwise, a runtime exception will be raised indicating that the just-in-time compiler detected an invalid program.
At this point, we're ready to generate code for each of the elements in the filter array. However, we don't want the Intermediate Language to loop through the filter array and access each element. Instead, we want all the filter elements to be hardcoded in Intermediate Language. If the filter has nine elements, we want nine similar sections of Intermediate Language. For that reason, we use a C# for statement to loop through the filter elements:
for (int iFilter = 0; iFilter < filter.Length; iFilter++)
{
If a particular element of the filter is 0, that element can be entirely ignored—no Intermediate Language code has to be generated, so we can just skip to the next element of the filter array:
if (filter[iFilter] == 0)
continue;
For each filter element, the index of the src array will be a particular offset from the iDst index. The following C# code calculates that offset. The offset value can be calculated in C# code because it only has to be represented as a constant in Intermediate Language:
int xFilter = iFilter % cxFilter;
int yFilter = iFilter / cxFilter;
int offset = stride * (yFilter - cyFilter / 2) +
bytesPerPixel * (xFilter - cxFilter / 2);
Accessing or setting an element of an array is a three-step process. First, you need to put a reference to the array on the stack. Then you need to put an index of the array on the stack. Finally, if you're accessing that element, you need a load instruction, and if you're setting the array element, you need a store instruction. The src array must be accessed for each nonzero element