Skip to content

Instantly share code, notes, and snippets.

@aiverson
Last active February 19, 2019 09:11
Show Gist options
  • Save aiverson/ad185d24773751259d65909d4b72b25d to your computer and use it in GitHub Desktop.
Save aiverson/ad185d24773751259d65909d4b72b25d to your computer and use it in GitHub Desktop.
Theoretical proposed semantics, syntax, and implementation of exceptions in the language Terra

Desiderata

Exception raising and handling must require no heap-allocated memory for core functionality. Exception handling should minimize the amount of information on the stack. Exception handling must be able to propagate across external code. Exception handling should gracefully extend into multithreading. Exception handling should be statically checkable. Exception handling should be efficient at runtime.

Basics

Exception handling will be split into three parts. First is the code that raises an exception. Second is the code that handles an exception. Third is the code that recovers from an exception.

Exceptions are statically typed, and what exceptions a piece of code can raise are statically determined, either by type inference from exception statements or by explicit declaration.

Exceptions have slots for information to be added and for restarts to be bound. Exceptions also have a code to allow discriminating the exception type.

Usage

First an exception type must be created using an appropriate function.

An exception can be raised using the raise function. It cannot have any constructor logic besides setting fields to arguments.

A block of code or an expression can be wrapped in a catch construct. A catch construct can handle one or more exception types with an appropriate catch handler. Control flow passing off the end of a catch handler or off the end of the body proceeds after the catch construct.

A block of code or an expression can be wrapped in a restart construct. A restart construct binds restarts to an exception. Control flow passing off the end of a restart or off the end of the body proceeds after the restart construct.

A catch that does not invoke a restart is essentially equivalent to Java's try-catch-throw mechanism.

A restart allows using high level context to advise a low level operation. This permits graceful error recovery without needing a bunch of extra code paths and data through the entire call hierarchy.

Possible Syntax

fooException = terralib.types.newexception("fooException", {{"val", int}}, {{"useValue", {int}}})

exception barException {
    val: int
    restart useValue(int)
}

terra high(): int
    var a: int
    try
        a = mid()
        a = a + a
    catch e: fooException
        e:useValue(2)
    end

    return a
end

terra mid(): int
    var b: int
    retry -- previous keyword was restartblock. Could still be improved probably.
        b = low()
        b = b + b
    restart useValue(x: int)
        b = x
    end
    return b
end

terra low(): int
    raise(fooException {val = 1})
    return 3
end

high() -- returns 4

Implementation

struct HandlerLink {
    prev: &HandlerLink
    frame: &opaque -- frame pointer onto the stack for the local variable
    code: &opaque -- A strange type of function pointer.
}

struct Exception {
    id: int
    data...: any
    restarts...: pair(&opaque, &opaque) -- A frame pointer and a strange function pointer
}

There is a single thread local or global memory location holding a handler-link. When a handler is installed, it allocates one handler link on the stack, copies the global handler-link to the stack cell and installs itself into the global handler link, linking the global handler-link to the cell on the stack. When a handler is uninstalled, the link can be copied from the stack cell to the global cell.

Restart Installation

Restarts get placed into the restart slots of an exception type by a special type of handler.

Only the deepest restart in the stack is acceptable, so these handlers check if the restart has already been set before continuing.

As the exception propagates up the stack, restarts get bound until it reaches a catch handler that catches the exception.

Catch Handling

A catch handler is provided the exception info on the top of the stack. The stack does not get unwound prior to the invocation of the catch handler. This means that if the exception is unhandled, a debugger can inspect the exception, inspect the stack, and invoke a restart.

A catch handler is invoked with the frame pointer of its installation context so that it has access to the local variables from outside its installation scope. It is invoked on the top of the stack when the exception is raised, leaving the entire stack beneath the Exception intact.

A catch handler can do any of invoking a restart, eating the exception and transfering flow of control to directly after the protected block, continuing to propogate the same exception up the stack, or producing a new exception to raise.

Restart Invocation

When a restart is invoked, the stack is unwound to the position of the installation of the restart. If the restart terminates normally, flow of control passes to the line immediately following the restart-protected block. The function that bound the restart is now on the top of the stack.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment