Avoid LLVM setjmp bug

Currently, we can’t reliably run LLVM optimizations on any function with a try/catch since it mis-optimizes it. This is captured in the following issue:

Codegen bugs are always really nasty, since they can be so unpredictable. Today I realized we may be able to change our codegen representation to avoid this case with minimal effort! If codegen always out-lined the body of try/catch code, it should no longer be able to generate bad code. I think this may even let it perform better optimizations than currently. The key here is that the return path from setjmp needs to avoid referencing any state.

In pseudo-C syntax, this would mean codegen would take the Julia function:

function f()
  setup
  try
    try-body
  catch
    catch-body
  end
  rest-of-function
end

And emit something of the form:

jl_value_t *f(args, …) { /* the actual function */
  alloca /* local state */
  <setup> /* user code */
  switch (f_trycatch(&alloca)) {
  case 0: /* normal fall-through */
    break;
  case 1: /* user code */
    <catch-body>
    break;
  default:
    abort(); /* corrupted codegen */
  }
  <rest-of-function> /* user code */
}
int f_trycatch(struct* alloca) {
  /* return value describes control flow continuation path */
  /* all other local state (including a gc-frame)
  /* is packaged into the alloca struct that is passed as a pointer argument */
  if (int control-flow = setjmp(&alloca->jmpbuf)) /* Expr(:enter) */
    return control-flow;
  <try-body> /* user code */
  return 0; /* Expr(:leave) */
}

Since there’s already a couple function calls on this code path, I don’t think the addition of the extra local jmp statement will impact performance. While the removal of volatile load / store may actually permit improved codegen optimizations (so, net benefit).

I think this should work, but having a second person review this concept is always good, so I’m posting here for review and other suggestions. :slight_smile:

3 Likes