[Mono-devel-list] mcs code generation

Ben Maurer bmaurer at users.sourceforge.net
Sun Mar 28 11:35:55 EST 2004


Hello,

You address two separate issues, as such, I will respond separately.

On Sun, 2004-03-28 at 08:57, Andre 'Ilu' Seidelt wrote:
> given the following simple pice of code:

>     try {
>       return new Object().ToString();
>     } catch(NullReferenceException e) {
> 
> why does mcs generate the leave twice for each block?
Lets look at a different code example to aid the explanation:

using System;
class T {
	static int Main () {
		try {
			return 0;
		} catch {
			Console.WriteLine ("uhoh!");
		}
		
		Console.WriteLine ("ok, so we failed the test :-(");
		return 1;
	}
}

The code generate for main (I have cleaned this up by hand):

	[header blah blah blah]
	.try {
		ldc.i4.0 
		stloc.0 
		leave RETURN_LABEL
		
		leave EXCEPTION_END
	
	}
	catch [mscorlib]System.Object {
		pop 
		ldstr "uhoh!"
		call void class [mscorlib]'System.Console'::'WriteLine'(string)
		leave EXCEPTION_END
	
	}
	ldstr "ok, so we failed the test :-("
	call void class [mscorlib]'System.Console'::'WriteLine'(string)
	ldc.i4.1 
	ret 
	
	RETURN_LABEL: ldloc.0 
	ret

So, now that we have a bit of a easier sample to explain, lets look at
the rationale for each leave statement:
> leave RETURN_LABEL
According to the CLI spec:
        The ret instruction cannot be used to transfer control out of a
        try, filter, catch, or finally block. From within a try or
        catch, use the leave instruction with a destination of a ret
        instruction that is outside all enclosing exception blocks.

So, we comply with this.

Now, for the less obvious one:

> leave EXCEPTION_END
The rationale behind this instruction is that you may never `fall out
of' an exception handling block. Therefore, it is necessary to provide a
leave statement at the end.

`Now wait!' you say, `You can't fall thru because of that other leave
instruction'. Well, the issue is that System.Reflection.Emit takes care
of this for us:


> 		private void InternalEndClause ()
> 		{
> 			switch (ex_handlers [cur_block].LastClauseType ()) {
> 			case ILExceptionBlock.CATCH:
> 				// how could we optimize code size here?
> 				Emit (OpCodes.Leave, ex_handlers [cur_block].end);
> 				break;
> 			case ILExceptionBlock.FAULT:
> 			case ILExceptionBlock.FINALLY:
> 				Emit (OpCodes.Endfinally);
> 				break;
> 			case ILExceptionBlock.FILTER:
> 				Emit (OpCodes.Endfilter);
> 				break;
> 			}
> 		}

(note that a `try' block goes under the Catch switch statement here).

So, we *always* get a leave instruction that makes it look like we are
falling thru to the end of the block, even if we dont ask for it.

I am not really sure there is a good way to fix this in the context of
Sys.Ref.Emit.

> they even generate a leave.s instead of leave.
That is because we do not do any second-pass optimizing on the IL, so we
can not know that the target of the leave instruction is actually within
the range of leave.s.

Please note that `fixing' this would only help the size of the compiled
IL code. The runtime can kill that extra leave instruction. Also, the
leave.s has no effect on code generation.

-- Ben




More information about the Mono-devel-list mailing list