Giter VIP home page Giter VIP logo

Comments (28)

tgiphil avatar tgiphil commented on June 22, 2024

Hi -

The new register allocator does not fully support try/except/finally regions yet. It's very high on the priority list. At the moment, avoid the use of exceptions.

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

So I've been thinking about this for a while and how to implement it and came up with the following solution:

Change the calling convention so that the address of the method Metadata gets pushed on to the stack.

Convert throw instructions to call instructions which call a processing function which will walk the stack and at every step it will fetch the return pointer that is pushed on to the stack by the call instruction.

Using this pointer address a look up will be performed on the Metadata table that contains all the try definitions in the method and will test to see if the pointer is within the range of the try definition.

if the pointer is outside the range then the process finds the next pointer and so on. If a range is never found then a kernel fault is raised.

If the pointer is inside the range and matches a catch clause then return the address of the start of the clause.

Unwind the stack and perform a jump (not a call) to the specified clause address which will deal with the exception.

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

Lookup the current method using the EIP and the Method Lookup Table, instead of modifying the calling convention. To determine the previous caller method, look at the stack at [EBP -4].

For exception/finally handlers calling, store the return address on the local stack as a temp variable. That way the stack layout is compile time constant and would help simplify stack unwinding in some cases. The end of the exception/finally handlers code can simply JMP to the caller rather than RET to return to the caller (whatever that might be).

Note: The exception type must be stored on the stack as a temp (and not a virtual register). Due to the non-normal flow control caused by exception processing. And the metadata "Exception Handler Table" needs to store this temp location (offset from EBP) since the stack unwind know where to store the exception type. Or, an alternative implementation is to always store the exception type in a specific virtual register, and add IR.Gen {register} instruction to the top all the exception/finally handlers.

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

Can't use EIP as that would be pointing inside the exception engine.

Another way to determine the current method we're stack walking on would be to use the return pointer [EBP-4] and the Method Lookup Table as you suggested instead of modify the calling convention.

Exceptions are objects so the type can be deduced from the object.

Exception handler address, and exception object address will be stored in registers then a stack unwind (not a stack rewind) will be performed modifying the ESP and EBP registers until we reach the Method that contains the handler then we will perform a jump to the handler and continue normal execution.

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

Correction: exception type should have been exception object.

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

Wouldn't it be best to have an object on the heap as it follows the correct code structure and would be fairly simple to pass along to the handler as the handlers can be forced to use a certain register for holding the exception object address?

from mosa-project.

kiootic avatar kiootic commented on June 22, 2024

IIRC, Exception handling is commonly implemented using interrupts. In interrupt handlers, the EIP is saved in stack and restored after the execution of handler. You can read the EIP and modify it to point to the exception handler in the interrupt handler.

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

What would be stored in this object?

The specific register for the exception object can be fixed at compiler time. That's fine when calling into a exception/finally clause because all virtual register are reloaded anyway. (Exceptions can be generated at almost any point and the exact state of the register would be unknown.)

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

To follow up on @kiootic comment, exceptions can also be generated by the CPU (divide by zero), or OS (out of memory, or thread-abort). And thread-abort being a special case as control never returns to the non-exception flow control and instead the application and/or thread is terminated.

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

The problem I see is that the stack needs to be destroyed all the way down until it reaches the Method that the contains the try/catch, otherwise we'll have an improperly aligned stack of we'll have to modify the catch so that it destroys the stack.

The exception object can contain anything as it is an object, at the moment it contains error messages.

Please note I am talking about Runtime Exceptions.
CPU exceptions that will be allowed to be handled by the Runtime and not special code will need to be wrapped and thrown just like a normal Runtime Exception.

from mosa-project.

kiootic avatar kiootic commented on June 22, 2024

Destroying stack frames should not be a hard problem, since it involves only poping EBP out of the stack, and it can be done along with stack walking. The exception handler expects the current stack frame to be its own method's, so it should be done by the exception engine.

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

@kiootic basically described the way I wanted to do it, with the exception of doing a second stack walk to destroy the stack so that we have the entire stack still available while we are processing the throwing of the exception should the need arise to use information from the stack, then destroy the stack just before jumping to the catch.

Also this would allow stopping the stack destruction when we reach a certain point in the stack, such as the OS Boot method, so that we can maybe issue some sort of kernel fault that the OS can process without need to wrap the entire OS Boot method in a try/catch.

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

Let's walk thru a typical exception flow for a throw and rough out a plan:

  1. IR throw {object} places the exception object on the stack and generates a CALL to the platform specific ExceptionHandler Throw({object}) method.
  2. Throw() method retrieves the calling method address by looking at [EBP-4] and the exception object. It then determines the exception type of the object.
  3. Next, the method is looked up by the method address using the "Method Lookup Table".
  4. Next, the exception clause is looked up using the "Exception Handler Table". The innermost exception handler is retrieved first.
  5. If the exception handler type matches the exception object's type, then goto step 9.
  6. If not, look for another exception handler (next outer most).
  7. If out of exception handlers, unwind the stack, restart at step 4.
  8. If none are found, terminate the thread.
  9. The exception object is placed in a register, for simplicity, let's use EAX. If there is no exception object required (i.e.., for finally clause), the register is unaffected. (Use IR.GEN EAX at the top of the exception handler to inform the register allocator that EAX is alive/used on entry).
  10. The return address (*) is placed in a register or stack, again for simplicity, let's use EDX.
  11. Jump to the exception handler.

TODO: (*) Determining the return address.

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

I have modified the flow to how I think it should occur:

  1. IR throw {object} generates a CALL to the platforms specific ExceptionHandler Throw({object}) method. (the {object} parameter is an address to the already generated exception object that is on the heap, there may be some confusion as c# generates a newobj instruction then a throw instruction, check the generate CIL for clarification).
  2. Throw() method walks the stack and retrieves the calling method address by looking at [EBP-4].
  3. Next, the method is looked up by method address using the "Method Lookup Table".
  4. Next, the exception clause is looked up using the "Exception Handler Table". The innermost exception handler is retrieved first.
  5. If the exception handler type matches the exception object's type, then goto 9.
  6. If not, look for another exception handler (next catch, or next outer most).
  7. If out of exception handler, restart at (2).
  8. If none are found, terminate the thread. If the thread is a main thread throw an interrupt to give the OS a last chance to recover.
  9. The exception object address is placed in a register, for simplistic, let's use EDX. (Use IR.GEN EDX at the top of the exception handler to inform the register allocator that EDX is alive/used on entry).
  10. Jump to the exception handler.
  11. It is the job of exception handler to clear EDX.
  12. If we fall into a finally then it must end by checking if EDX is set, if it is set then it must restart at (1), otherwise continue normal execution. (finally handlers do not stop exceptions from bubbling if uncaught by a catch handler).

Please note that finally handlers are finicky in that they must always be executed before normal execution is resumed.

Take a look at this for some examples of when they execute http://csharp.2000things.com/tag/finally/

from mosa-project.

grover avatar grover commented on June 22, 2024

Hi,

I would propose a different alternative to this:

Every method that has exception handlers should push the address of its exception handler table an exception handler stack right after setting up the stack frame. This way we can optimize handler lookups by not having to traverse the stack and looking up the methods in the method lookup table. It is a small penalty at runtime in general, but should lead to much better exception processing times compared to the way Stefan described. This is an approach as used by the vectored exception handling in Win32 and I believe that the .NET exception handling would fit well with that. An additional advantage of this is that stack corruption would not interfere with exception handling, e.g. handlers are still looked up properly even though the callstack was corrupted for any reason.

The exception handler stack would have to be thread specific like the normal call stack.

Regards,
Michael

Am 10.08.2014 um 19:06 schrieb Stefan Andres Charsley [email protected]:

I have modified the flow to how I think it should occur:

IR throw {object} generates a CALL to the platforms specific ExceptionHandler Throw({object}) method. (the {object} parameter is an address to the already generated exception object that is on the heap, there may be some confusion as c# generates a newobj instruction then a throw instruction, check the generate CIL for clarification).

Throw() method walks the stack and retrieves the calling method address by looking at [EBP-4].

Next, the method is looked up by method address using the "Method Lookup Table".

Next, the exception clause is looked up using the "Exception Handler Table". The innermost exception handler is retrieved first.

If the exception handler type matches the exception object's type, then goto 9.

If not, look for another exception handler (next catch, or next outer most).

If out of exception handler, restart at (2).

If none are found, terminate the thread. If the thread is a main thread throw an interrupt to give the OS a last chance to recover.

The exception object address is placed in a register, for simplistic, let's use EDX. (Use IR.GEN EDX at the top of the exception handler to inform the register allocator that EDX is alive/used on entry).

Jump to the exception handler.

It is the job of exception handler to clear EDX.

If we fall into a finally then it must end by checking if EDX is set, if it is set then it must restart at (1), otherwise continue normal execution. (finally handlers do not stop exceptions from bubbling if uncaught by a catch handler).

Please note that finally handlers are finicky in that they must always be executed before normal execution is resumed.

Take a look at this for some examples of when they execute http://csharp.2000things.com/tag/finally/


Reply to this email directly or view it on GitHub.

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

Hi @grover, that would require modifying the calling convention which is preferred to stay the same.

Are you proposing some sort of dual stack or have I misread your comment?

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

I believe @grover is proposing a separate stack for exception handlers. Unfortunately, that'll either use up a register to track the exception handler stack location, or a query the kernel to determine the location of the exception handler stack. The kernel can lookup the thread via the current stack pointer, and return the exception handler stack for that thread.

IHMO - I'd still lean towards lookup via the method table --- as doesn't add any cost to the normal flow control. And performance costs associated with exceptions exist only when exceptions are used.

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

Note: "Use IR.GEN EDX at the top of the exception handler to inform the register allocator ".

Immediately after the IR.GEN EDX, add a move instruction to copy EDX to a virtual register. Otherwise, EDX will not be available for use. The register allocator attempt to keep the virtual register in a physical register and favor keeping it in the source register as well (EDX, in this case).

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

The part that we are missing so far in our discussion is determining to set the return address of exception handlers. Ideas?

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

Which return address? Why do they need one?

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

Here's are two scenarios: 1) at the end of a try block which has a finally handler, the finally handler is called. In this case, the return address for the finally hander is simply the next block after the whole try/finally block. 2) However, the finally block can also be called as part of a deeper exception that is processed up thru the call stack. In this case, the return address is a bit more difficult to determine. It's probably back to an ExceptionHandler method so it can continue to propagate the exception further up the stack. However, this is the part that I find difficult to resolve; specially, how to get a reference to the original exception object? Maybe it's passed to the finally block in another register, even if the finally block never uses it, and placed back in that register prior to JMP to the return address.

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

I probably didn't make it very clear in my exception flow plan but I did have a solution for that in (12) which was to check EDX to see if it was null.

If null continue normal execution, if not null then return to (1) and restart the process.

The only time EDX would be null is if it had been caught by a catch handler and been set to null during the CIL.leave instruction.

So both finally handlers and catch handlers would both receive the excepti9n object address in EDX.

As you mentioned above, EDX can be substituted for a virtual register once the handler has been entered.

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

I see how that can work. However, I'd still pass in the return address to the finally handler to avoid the comparison with object address in EDX.

I'll update the IR/X86 generation to generate the appropriate enter/exit code for the finally handler, and the leave instruction. Can someone else work on the method table generation and runtime lookup code? Afterwards, we can then tackle ExceptionHandler.

Note: There already is a native GetEBP instruction.

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

@tgiphil I can do it but it won't be possibly until December, depending on how I progress with my industry project.

A lot of the code exists but it's out of date so it needs to be updated and needs to be placed in IR stages wherever possible.

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

Quick update - the "ExceptionHanding" branch in my repo now supports nested try/finally situations. New IR instructions were added to model the try/finally/exception flow.

Next phase is to implement the exception processing.

This incomplete branch will merge into main repo shortly since it's fixes a few finally issues, doesn't break anything and introduces a few new compiler concepts. Also, this is some very preliminary code to support branch targets as operands.

from mosa-project.

charsleysa avatar charsleysa commented on June 22, 2024

@tgiphil looking good!

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

I hit a setback as well. Exceptions within finally or exception blocks cause the stack to become misaligned. I'm working out a solution that does not use the stack to store the return address.

from mosa-project.

tgiphil avatar tgiphil commented on June 22, 2024

Just a quick note: Exception handling is steps discussed above are slightly obsolete. I'll re-write them once the implementation is completed.

from mosa-project.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.