Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

Problem of rethrow instruction #30

@aheejin

Description

@aheejin

Problem

The current rethrow instruction has some problems that make its use very
limited. For example, suppose there are two try/catch blocks and in both catch
they jump to some common code, which is followed by a rethrow instruction.

block $label0
  try
    ...
  catch i
    br $label$0
  end
  ...
  try
    ...
  catch i
    br $label$0
  end
end

some common code
rethrow

This cannot be supported in the current spec because rethrow can occur only in
the scope of a catch block. But this code pattern is very common especially
when there is some cleanup code (calling destructors) to run before rethrowing
an exception up to a caller. If you compile the code below,

MyClass obj;
try {
  foo(); // might throw
} catch (int n) {
  ...
}
...
try {
  foo(); // might throw
} catch (int n) {
  ...
}

The generated code will look like this:

block $label0
  try
    call $foo
  catch i
    br $label$0
  end
  ...
  try
    ...
  catch i
    br $label$0
  end
end

call MyClass's destructor to destruct obj
rethrow

In this case, cleanup action consists of only a single destructor call, but it
can take more code space if there are many objects to destroy, so I think
duplicating this cleanup code into possibly n catch blocks is not a viable
idea. This is a classic case in which we need a rethrow, but it cannot be
executed at the end of this code because it's not in the scope of a catch.

This is one example of code sharing that's very common, but code sharing between
catch blocks can occur in other cases as well. You can use goto within catch
clauses to jump to a common block. Or any middle-IR-level compiler optimization
pass can factor out some common code in catch blocks.

While this problem can be worked around using not a rethrow but just a normal
throw, throw is considered as throwing a completely new exception, and the
VM wouldn't be able to carry an attached backtrace with it, which can be useful
when we later support backtrace debugging in the future. And more importantly,
this problem effectively makes rethrow unusable at all, because the most
common usecase of it is, as illustrated above, when it occurs after some common
cleanup code which is shared between many catch blocks. It can also occur when
there is shared cleanup code between catch i and catch_all clauses, which
will be very common case as well, but I'm actually planning on proposing
something else for catch clauses... but anyway.

Idea?

This is a rough idea and not a complete spec yet. And it's not I'm proposing
this as a single concrete alternative and I appreciate comments and suggestions.

I think it is necessary to make it possible to access some kind of handle to an
exception object outside a catch block. (The reason it is not a i32 value but a
handle is it can be opaque if it is for a foreign exception) There can be
multiple ways to do it. We can make catch instruction to return a handle, save
it to a local or something, and then use it after we exit a catch block. In this
case, rethrow should take an handle as an argument now.

try
  ...
catch i
  set_local 0, handle
end

get_local 0
use handle

In this case, I think when the VM can destroy an exception object is unclear,
and it can be an issue, maybe? Maybe the VM should maintain a map of handle to
an exception object until the program ends.

Or, to make the VM can destroy exception object when they are not necessary
anymore, we can add some reference count to exception objects, and make a way to
capture an exception handle within a catch block. Suppose capture_exception
instruction captures the current exception handle.

try
  ...
catch_all
  set_local 0, capture_exception
end

get_local 0
use handle

The reference count for an exception starts as 1 when any catch block is
entered.

  • It will be decremented when
    • It hits an try_end instruction.
    • When the function execution ends
  • It will be incremented when
    • When capture_exception instruction is executed within a catch block
    • When rethrow instruction is executed on the handle.
      This approach is in a way similar to the newly added library functions to the
      C++11 spec:
      std::current_exception, and std::rethrow_exception.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions