This is an archive of the discontinued LLVM Phabricator instance.

[Debug] [Coroutines] Add deref operator for non complex expression
ClosedPublic

Authored by ChuanqiXu on May 23 2022, 11:34 PM.

Details

Summary

Background:

When we construct coroutine frame, we would insert a dbg.declare intrinsic for it:

%hdl = call void @llvm.coro.begin() ; would return coroutine handle
call void @llvm.dbg.declare(metadata ptr %hdl, metadata ![[DEBUG_VARIABLE: __coro_frame]], metadata !DIExpression())

And in the splitted coroutine, it looks like:

define void @coro_func.resume(ptr *hdl) {
entry.resume:
    call void @llvm.dbg.declare(metadata ptr %hdl, metadata ![[DEBUG_VARIABLE: __coro_frame]], metadata !DIExpression())
}

And we would salvage the debug info by inserting a new alloca here:

define void @coro_func.resume(ptr %hdl) {
entry.resume:
    %frame.debug = alloca ptr
    call void @llvm.dbg.declare(metadata ptr %frame.debug, metadata ![[DEBUG_VARIABLE: __coro_frame]], metadata !DIExpression())
    store ptr %hdl, %frame.debug
}

But now, the problem comes since the dbg.declare refers to the address of that alloca instead of actual coroutine handle. I saw there are codes to solve the problem but it only applies to complex expression only. I feel if it is OK to relax the condition to make it work for __coro_frame.

Diff Detail

Event Timeline

ChuanqiXu created this revision.May 23 2022, 11:34 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 23 2022, 11:34 PM
Herald added a subscriber: hiraditya. · View Herald Transcript
ChuanqiXu requested review of this revision.May 23 2022, 11:34 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 23 2022, 11:34 PM

This seems fine to me -- something very similar happens with NRVO in C++, a dbg.declare(..., DW_OP_deref) is created for the implicit output argument. There's a test demonstrating this in clang/test/CodeGenCXX/debug-info-nrvo.cpp.

The whole interplay between levels of DW_OP_deref and whether there are already opcodes in the expression is pretty miserable; what scenarios is salvageDebugInfo called in? I'm trying to reason about when the isComplex test might be necessary. Also, IMO, you should be able to assert that Expr is nonzero. AFAIUI dbg.declare's need to have the expression argument non-null, so any null Expr found here is a problem with the input IR.

ChuanqiXu updated this revision to Diff 432449.May 26 2022, 7:35 PM

Address comments.

what scenarios is salvageDebugInfo called in? I'm trying to reason about when the isComplex test might be necessary.

Background: compiler would construct new structs called coroutine frames for the coroutines. It is necessary since a coroutine would be split into multiple functions in the middle end so we need a state to record the information. So the following one:

coro f(int a) {
   a++;
   co_await something();
   a++;
   co_return a;
}

would be translated to:

coro f(int a) {
    frame_ty *frame = malloc(size);
    store `a` to someplace of frame
    %a.tmp = load `a` from frame
    %a.tmp += 1
    store %a.tmp to frame
    ...
}

then it would be split into:

coro f(int a) {
    frame_ty *frame = malloc(size);
    store `a` to someplace of frame
    return frame
}

void f.resume(frame_ty* frame) {
    %a.tmp = load `a` from frame
    %a.tmp += 1
    store %a.tmp to frame

unreachable: // which would be removed finally
    frame_ty *frame = malloc(size);
    store `a` to someplace of frame
    ...
}

then if we add dbg intrinsics, it would look like:

coro f(int a) {
    frame_ty *frame = malloc(size);
    call @dbg.declare(frame, "frame_var", DIExpression())
    store `a` to someplace of frame
    call @dbg.declare(the address of `a` in frame, "a", DIExpression())
    return frame
}

void f.resume(frame_ty* frame) {
    %a.tmp = load `a` from frame
    %a.tmp += 1
    store %a.tmp to frame

unreachable:
    frame_ty *d_frame = malloc(size);
    call @dbg.declare(d_frame, "frame_var", DIExpression())
    store `a` to someplace of d_frame
    call @dbg.declare(the address of `a` in d_frame, "a", DIExpression())
    ...
}

Then we call salvageDebugInfo here to salvage the debug information from unreachable bb to the entry of the f.resume function. Then the main work it tries to do is to replace d_frame to the argument. And I remember the reason why we would create an additional alloca is that the argument couldn't be the address argument of @dbg.declares

jmorse accepted this revision.Jun 7 2022, 2:33 AM

(All away for the UK holiday week sorry),

Thanks for the detail on how salvaging happens with coroutines -- I was curious why it would depend on what was already in the DIExpression. The deref is indeed necessary because the pointer is in an alloca. I suspect the check for isComplex() wasn't completely necessary (but only because of DIExpression quirks, unfortunately).

LGTM, but could you put an extra CHECK in where I've marked to capture FramePtr_RESUME using the alloca instruction. When reading the test, it wasn't clear to me where the dbg.declare was going to point, this will make it easier for future readers to understand.

llvm/test/Transforms/Coroutines/coro-debug-coro-frame.ll
12

Could you put a CHECK in here for the FramePtr_RESUME variable that checks it's coming from an alloca?

This revision is now accepted and ready to land.Jun 7 2022, 2:33 AM

(All away for the UK holiday week sorry),

Thanks for the detail on how salvaging happens with coroutines -- I was curious why it would depend on what was already in the DIExpression. The deref is indeed necessary because the pointer is in an alloca. I suspect the check for isComplex() wasn't completely necessary (but only because of DIExpression quirks, unfortunately).

LGTM, but could you put an extra CHECK in where I've marked to capture FramePtr_RESUME using the alloca instruction. When reading the test, it wasn't clear to me where the dbg.declare was going to point, this will make it easier for future readers to understand.

Thanks for reviewing!

This revision was landed with ongoing or failed builds.Jun 7 2022, 7:54 PM
This revision was automatically updated to reflect the committed changes.