[Mono-dev] Fixing Thread:Abort handling of finally clauses
Paolo Molaro
lupus at ximian.com
Wed Mar 10 06:57:01 EST 2010
On 03/09/10 Rodrigo Kumpera wrote:
> One is to patch the return address of a finally clause to jump into runtime
> code that will handle raising the ThreadAbortException.
> This is very tricky because we need to store precise unwind information to
> be able to figure out where that value is. The advantage is
> that is doesn't slow down the fast path.
Note that sooner or later we must implement the precise info anyway
since we need it for reliable exception handling, precise GC of stack
locations etc.
It willl also be considerably easier once we move the x86 jit to
allocate enough stack for the calls at method entry, so avoiding all the
overhead of adjusting esp all the time before and after the calls
(instead of push instructions we'll use store from the esp base reg).
At the end there is no difference in complexity between storing a flag
and storing to the return address.
> The other option is to change how finally clauses work so they make this
> easier. Zoltan mentioned that we should restore from EH context and use a
> variable to tell what to do next. The pseudo-code for the fast-path are
> something like:
>
> Currently:
> try_body:
> ...
> call finally
> jmp rest_of_function
> finally:
> ...
> ret
> rest_of_function:
> ...
>
> Zoltan's suggestion:
>
> try body:
> ...
> mov 0, [EBP + ?] //this is the variable that tells to resume unwinding or
> not
> jmp finally;
> finally:
> ...
> cmp 0, [EBP + ?]
> jmp_if_zero rest_of_function
> call resume_unwinding
> rest_of_function:
> ...
>
> Looking at the pseudo code, currently we do 3 branches (call, ret, jmp) with
> Zoltan's suggestion
> we would do only two (jmp, jz), thou one is conditional. The memory
> bandwidth is the same,
> both require one load and one store. Zoltan's suggestion should result in
> larger code thou.
If you want to go that route, the call/jmp pair could be optimized to
a push rest_of_function/jmp finally and still maintain correctness.
> >>From our irc log, you raised a few issues with this approach, first that is
> might cause
> issues with the ppc ABI. This approach is basically the same to how we
> handle catch
> clauses and the later doesn't seen to have issues.
As I said on irc, finally clauses and catch clauses are fundamentally
different.
> You also mentioned that a finally clause can be called in a different stack
> frame that
> of its original method. I fail to see how could that happen, specially if
> we'll be restoring to it.
Consider this code:
void method () {
try {
if (boolean)
throw ex;
} finally {
...
}
}
Now, the finally clause can be reached in two different ways, I will
list the stack frames involved:
boolean TRUE FALSE
method method
runtime EH finally
finally
Note the difference. In both cases we need to maintain correctness wrt
the ABI (as I explained for the PPC case, MIPS is similar and likely
other archs follow the same pattern) and also for our own ability to
handle stack walks from within the finally handler, both for exception
handling and for the precise GC support.
With a catch cluase, instead, you have the following:
void method () {
try {
if (boolean)
throw ex;
} catch {
...
}
}
boolean TRUE FALSE
method method
catch catch
So the two cases are different.
> I do support Zoltan that this approach is the way to go, but I rather hear
> your opinion first.
> Even if it ends up been marginally slower, it has the advantage of been much
> much simpler,
> which wins us in development time and reliability.
I don't think Zoltan's code is correct in this case, apart from the
above issues of stack frame consistency and additional code size.
There is an additional complication with finally clauses, in that
a leave instruction may leave multiple nested protected blocks and all
the finally clauses must be executed.
I attached a sample that should print just "1", from your pseudocode
above this case wouldn't be handled correctly, though I don't have an
llvmono uild to try it out. AFAIK, it should also verify correctly.
lupus
--
-----------------------------------------------------------------
lupus at debian.org debian/rules
lupus at ximian.com Monkeys do it better
-------------- next part --------------
.assembly extern mscorlib
{
.ver 2:0:0:0
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
}
.assembly 'finally'
{
.custom instance void class [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::'.ctor'() = (
01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module finally.exe // GUID = {5B003E11-5CB3-450F-A0E4-42E364A764C7}
.class private auto ansi beforefieldinit T
extends [mscorlib]System.Object
{
// method line 1
.method public hidebysig specialname rtspecialname
instance default void '.ctor' () cil managed
{
// Method begins at RVA 0x2050
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void object::'.ctor'()
IL_0006: ret
} // end of method T::.ctor
// method line 2
.method private static hidebysig
default void Main () cil managed
{
// Method begins at RVA 0x2058
.entrypoint
// Code size 39 (0x27)
.maxstack 3
.locals init (
int32 V_0)
IL_0000: ldc.i4.0
IL_0001: stloc.0
.try { // 1
.try { // 0
IL_0002: ldloc.0
IL_0003: ldc.i4.1
IL_0004: add
IL_0005: stloc.0
IL_0006: leave IL_0020
} // end .try 0
finally { // 0
IL_000b: ldloc.0
IL_000c: ldc.i4.1
IL_000d: sub
IL_000e: stloc.0
IL_000f: endfinally
} // end handler 0
IL_0010: ldloc.0
IL_0011: call void class [mscorlib]System.Console::WriteLine(int32)
IL_0016: leave IL_0020
} // end .try 1
finally { // 1
IL_001b: ldloc.0
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.0
IL_001f: endfinally
} // end handler 1
IL_0020: ldloc.0
IL_0021: call void class [mscorlib]System.Console::WriteLine(int32)
IL_0026: ret
} // end of method T::Main
} // end of class T
More information about the Mono-devel-list
mailing list