This is an archive of the discontinued LLVM Phabricator instance.

[CodeGen] Emit MSVC funclet IR for Obj-C exceptions
AbandonedPublic

Authored by smeenai on Jun 9 2018, 2:54 PM.

Details

Summary

We're implementing funclet-compatible code generation for Obj-C
exceptions when using the MSVC ABI. The idea is that the Obj-C runtime
will wrap Obj-C exceptions inside C++ exceptions, which allows for
interoperability with C++ exceptions (for Obj-C++) and zero-cost
exceptions. This is the approach taken by e.g. WinObjC, and I believe it
to be the best approach for Obj-C exceptions in the MSVC ABI.

This change implements emitting the actual funclet-compatible IR. The
generic exceptions codegen already takes care of emitting the proper
catch dispatch structures, but we need to ensure proper handling of
catch parameters and scoping (emitting the catchret). Finally blocks are
handled quite differently from Itanium; they're expected to be outlined
by the frontend, which limits some control flow possibilities but also
greatly simplifies their codegen. See r334251 for further discussion of
why frontend outlining is used.

Worked on with Saleem Abdulrasool <compnerd@compnerd.org>.

Diff Detail

Event Timeline

smeenai created this revision.Jun 9 2018, 2:54 PM

@rnk remember how I was asking you about inlining into catchpads on IRC a few days back? It was in relation to this change.

If I have a file finally1.m:

void f(void);
void g() {
  @try {
    f();
  } @finally {
    f();
  }
}

and compile it with:

clang -cc1 -triple i686-windows-msvc -fobjc-runtime=ios-6.0 -fexceptions -fobjc-exceptions -Os -emit-llvm -o - finally1.m

the @finally calls out to the captured statement instead of inlining it:

finally.catchall:                                 ; preds = %catch.dispatch
  %1 = catchpad within %0 [i8* null, i32 64, i8* null]
  call fastcc void @__captured_stmt() [ "funclet"(token %1) ]
  call void @_CxxThrowException(i8* null, %eh.ThrowInfo* null) #3 [ "funclet"(token %1) ]
  unreachable

If I add an outer try-catch scope, as in finally2.m:

void f(void);
void g() {
  @try {
    @try {
      f();
    } @finally {
      f();
    }
  } @catch (...) {
  }
}

and run the same compilation command:

clang -cc1 -triple i686-windows-msvc -fobjc-runtime=ios-6.0 -fexceptions -fobjc-exceptions -Os -emit-llvm -o - finally2.m

the @finally inlines the captured statement:

finally.catchall:                                 ; preds = %catch.dispatch
  %2 = catchpad within %0 [i8* null, i32 64, i8* null]
  invoke void @f() #2 [ "funclet"(token %2) ]
          to label %invoke.cont1 unwind label %catch.dispatch4

invoke.cont1:                                     ; preds = %finally.catchall
  invoke void @_CxxThrowException(i8* null, %eh.ThrowInfo* null) #3 [ "funclet"(token %2) ]
          to label %unreachable unwind label %catch.dispatch4

Any idea why we would see inlining in one case and not the other? i686 vs. x86-64 doesn't make any difference, and neither does -Os vs. -O1 vs. -O2.

rnk added a comment.Jun 12 2018, 2:16 PM

Any idea why we would see inlining in one case and not the other? i686 vs. x86-64 doesn't make any difference, and neither does -Os vs. -O1 vs. -O2.

My theory is that the inliner is attempting to avoid inlining on cold codepaths that are post-dominated by unreachable. When you put an outer try around it, the function may continue by unwinding, so the inline cost analysis gives different results. Of course, that's all just a wild guess.

In general, it's unfortunate that this has to leave so many C++-runtime-specific tendrils in the ObjC code. Unlike the EH type patch, though, I'm not sure I can see a great alternative here, especially because of the semantic restrictions required by outlining.

lib/CodeGen/CGCXXABI.h
248

Should you just generalize the existing method to only take a VarDecl* so it can be used for either kind of catch?

rnk added a comment.Jun 18 2018, 4:38 PM

In general, it's unfortunate that this has to leave so many C++-runtime-specific tendrils in the ObjC code. Unlike the EH type patch, though, I'm not sure I can see a great alternative here, especially because of the semantic restrictions required by outlining.

It's technically possible to lift those restrictions by returning an i32 from the outlined function and switching on it. Right? The question is, is it worth it? The catch funclet would effectively store the i32 to the stack frame, then "catchret" via the runtime, and then we'd switch out to the jump target.

In D47988#1135929, @rnk wrote:

In general, it's unfortunate that this has to leave so many C++-runtime-specific tendrils in the ObjC code. Unlike the EH type patch, though, I'm not sure I can see a great alternative here, especially because of the semantic restrictions required by outlining.

It's technically possible to lift those restrictions by returning an i32 from the outlined function and switching on it. Right? The question is, is it worth it? The catch funclet would effectively store the i32 to the stack frame, then "catchret" via the runtime, and then we'd switch out to the jump target.

I don't think it's important. Uses of control flow out of @finally are rare, and we could probably forbid it entirely without significant loss.

smeenai added inline comments.Jun 22 2018, 2:37 PM
lib/CodeGen/CGCXXABI.h
248

The Itanium version of emitBeginCatch actually uses the statement for location info (it calls getLocStart on it). I suppose I could generalize the existing method to take both a VarDecl* and a SourceLocation though.

smeenai abandoned this revision.Apr 28 2022, 1:24 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 28 2022, 1:24 PM