Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple Exception Support #424

Open
18 of 28 tasks
drcjt opened this issue Dec 13, 2023 · 9 comments
Open
18 of 28 tasks

Simple Exception Support #424

drcjt opened this issue Dec 13, 2023 · 9 comments
Assignees
Labels
enhancement New feature or request

Comments

@drcjt
Copy link
Owner

drcjt commented Dec 13, 2023

Look at exception handling in nativeaot - most of logic is in System.Runtime.ExceptionHandling.cs
https://github.com/dotnet/runtime/blob/acc3f0cea4057f8bde7ffebdabd1dc62c4ebef0b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/ExceptionHandling.cs#L4

Still uses native code for actual stack unwinding though.

  1. Stub Exception handling support
  • Add new compiler option to enable support for exception handling
  • Importer discards catch/filter/fault handlers
  • Explicit throw becomes helper call
    • Create managed code helper with placeholder for proper dispatch
    • Add native assembly to capture exception ip, sp/frame pointer information
  • Implicit exceptions expanded to explicit test/throw code
    • Null deference
    • Array bounds check
    • Array store checks
    • Divide by zero
    • Arithmetic overflow
    • Convert with overflow
  1. Handler bring-up
  • Catch handler support
    • Importer to include catch IL in basic blocks
    • Generate EH Clauses info per method
    • Enhance ExceptionHandling.Dispatch to search EH Clauses in current method
    • Add StackFrameIterator to virtually unwind frames and use in ExceptionHandling.Dispatch
    • Match catch handlers based on exception type
    • Deal with SP - as can't reliably unwind parameters.
  • Support for rethrow
  • Fix SSA to deal with EH properly.
  1. No handler optimization
  • Translate throw into call to failfast if no exception handlers
  1. Out of memory
  • Preallocate an out of memory exception
  • Use preallocation out of memory exception when oom detected
@drcjt drcjt added the enhancement New feature or request label Dec 13, 2023
@drcjt drcjt self-assigned this Dec 14, 2023
@drcjt
Copy link
Owner Author

drcjt commented Dec 14, 2023

Example of output from unhandled exception with exception having message of "Testing 1 2 3"

image

@drcjt
Copy link
Owner Author

drcjt commented Dec 17, 2023

Will need to store EH clauses for methods with try/catch/finally blocks.
Could store this in new data structure emitted as part of ILCompiler codegen.
The data structure would need to be indexed by method code address range - as when stack walking we start with current program counter and then walk the stack frames to find the return address of the calling method and so on. The program counter values/return addresses are the starting point for determine if the method the PC/Ret address is part of has any exception handling clauses.

IP Start
IP End
Number Of EH Clauses
EH Clause 1
..
EH Clause n

Is it worth extending above so that arbitrary info can be stored per method. In effect we have a map from instruction pointer address via IP range to a MethodTable data structure that can hold the EH Clauses and other info in the future too?
Problem is that the overhead might be too much.

@drcjt
Copy link
Owner Author

drcjt commented Dec 17, 2023

Currently not all methods include normal stack frame setup transferring the original SP into IX:

PUSH IX
LD HL, 0
ADD IX, SP

However, doesn't look like this happens on many methods e.g. for Snake there are only 2 methods without stack frame setup. Also these methods look like they'd be good candidates for inlining - so perhaps that is the long term solution.

For Exception handling - will need to add new config option and will use this to ensure that stack frame setup is always emitted for all methods.

@drcjt
Copy link
Owner Author

drcjt commented Dec 17, 2023

Real dotnet runtime maps instruction pointer to method info via the EEJitManager::FindMethodCode(..) method. This makes use of interesting data structure, the nibble map.

@drcjt
Copy link
Owner Author

drcjt commented Dec 18, 2023

Instead of throw opcode codegen call to ExceptionHandling.ThrowException(..) it needs to call an assembly code routine to capture the true exceptions IP address and framepointer. These need to be packaged into a struct and then passed on to ExceptionHandling.ThrowException.

The assembly code routine e.g. ThrowEx, will be something like this:

ThrowEx:
    pop de   ;  the exception object
    pop hl    ; get the return address which is the exception IP

    push de   ; exception object
    push hl    ; exception ip
    push ix    ; exception sp

    jp ThrowException

Note there is no need to call ThrowException as we don't expect it to ever return directly - it's going to call handlers/unwind the stack or ultimately call the unhandled exception handler.

@drcjt
Copy link
Owner Author

drcjt commented Jan 8, 2024

There is a problem with unwinding as we can determine SP at point call was made to current frame but we don't know the number of parameters on the stack. This is only an issue due to the stack under/overflow validation logic emitted in the Ret code generator - having the unwound SP effectively such that the calling parameters are left on the stack is probably okay as none of the codegen for subsequent basic blocks will be making any assumptions about what is on the actual stack.

Alternatively need a way to determine the number of parameters on the stack to virtually remove. Could use a loop table for the method call we are unwinding but this would require at least 3 bytes per method e.g. address of method being called, and number of parameters (could just be a single byte). Stack frame iterator would need to lookup in the table and use entry to work out exact SP prior to method call.

@drcjt
Copy link
Owner Author

drcjt commented Jan 11, 2024

Sample code that illustrates problem with incorrect unwinding due to not removing parameters from calls. This code will eventually overflow the stack as each call to N will push 4 bytes on the stack for the int, but this is not unwound.

public static Exception MyException = new Exception();

public void M()
{
	while (true)
	{
		try { N(123); }
		catch { }
	}
}

public void N(int a) => throw MyException;

Option for solving this is to generate unwind table used during the unwind process to apply appropriate delta to SP to remove the method parameters from the stack. Note that we don't need to include entries in this table for every method in the program - only those that are called within try blocks.

The unwind table could be represented as a range of PC addresses and the delta SP. However, it might be possible to just use the address of the start of the method - as when unwinding we will have the return address, which is directly after the CALL instruction to the method, so can extract from the CALL instruction the address of the start of the method.

@drcjt
Copy link
Owner Author

drcjt commented Jan 11, 2024

SSA may need revisiting as some test programs have been resulting in errors in the SSA analysis.

@drcjt
Copy link
Owner Author

drcjt commented Jan 18, 2024

Current overhead for exception handling is roughly 2k - so definitely large enough to keep exceptions behind feature flag for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant