[PATCH] D99179: [RFC] [Coroutines] Enable printing coroutine frame in debugger if program is compiled with -g

Chuanqi Xu via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Tue Mar 23 05:11:05 PDT 2021


ChuanqiXu created this revision.
ChuanqiXu added reviewers: lxfind, junparser, aprantl, rjmccall, bruno, dongAxis1944.
Herald added a subscriber: hiraditya.
ChuanqiXu requested review of this revision.
Herald added a project: LLVM.
Herald added a subscriber: llvm-commits.

Although the coroutine frame is constructed and maintained by the compiler and the programmer shouldn't care about the coroutine frame by the design of C++20 coroutine, 
a lot of programmers told me that they want to see the layout of the coroutine frame strongly. Although C++ is designed as an abstract layer so that the programmers shouldn't care about the actual memory in bits, many experienced C++ programmers  are familiar with assembler and debugger to see the memory layout in fact, After I was been told they want to see the coroutine frame about 3 times, I think it is an actual and desired demand.

However, the debug information is constructed in the front end and coroutine frame is constructed in the middle end. This is a natural and clear gap. So I could only try to construct the debug information in the middle end after coroutine frame constructed. I am not clear whether this violates the guide principles of LLVM.

Here is an example:

  struct test {
  int i;
  int j;
  double k;
  };
  
  struct coro {
    struct promise_type {
      struct test str2;
      promise_type() {
          str2.i = 1;
          str2.j = 2;
          str2.k = 3;
      }
      coro get_return_object() {
        auto handle = coroutine_handle<promise_type>::from_promise(*this);
        return coro(handle);
      }
      suspend_always initial_suspend() { return {}; }
      suspend_always final_suspend() noexcept { return {}; }
      void return_void() __attribute__((noinline)) {}
      void unhandled_exception() {}
    };
  
    coroutine_handle<promise_type> handle;
    coro(coroutine_handle<promise_type> handle)
        : handle(handle) {}
  };
  
  class Obj {
  public:
    Obj(int a) : a(a) {}
  
      coro foo(struct test t, std::shared_ptr<struct test> SP) noexcept {
        int localStr = t.i;
        int j = t.i;
        struct test t1 = {1, ++t.i, t.k};
        printf("str");
  
        co_await suspend_always();
        j = t.j;
        t1.i++;
        t1.j++;
        localStr += t.i + t.j;
        a = 10;
        if (t.i > 1000) {
          foo(t, std::move(SP));
        } else {
          printf("%d, %d, %d\n", SP->i, t.i, t1.j); // 2, 1
        }
  
        co_await suspend_always();
        j = t.k;
        ++localStr;
        printf("%d,\n", localStr); // 3, 2
  
        t1.i++;
      }
  
  private:
      int a;
  };

After we added simple main function and compiled it with:

  clang++ -g -O2 -fcoroutines-ts -mllvm -enhance-debug-with-coroutine ToSeeCoroFrame.cpp -o a.out

Then we can stop the program in gdb/lldb in the middle of the coroutine, we can get the coroutine frame layout by `p __coro_frame`:

  $1 = {__resume_fn = 0x4013b0 <Obj::foo(test, std::shared_ptr<test>)>, __destroy_fn = 0x401530 <Obj::foo(test, std::shared_ptr<test>)>, __promise = {str2 = {i = 1, j = 2, k = 3}}, class_std__shared_ptr_0 = {class_std____shared_ptr = {
        struct_test_Ptr = 0x0, class_std____shared_count = {class_std___Sp_counted_base_Ptr = 0x0}}}, class_Obj_Ptr_1 = 0x7fffffffe250, int64_2 = 1095216660718, double_3 = 1, struct_test_Ptr_4 = 0x416ec0,
    class_std___Sp_counted_base_Ptr_5 = 0x416eb0, class_std___Sp_counted_base_Ptr_6 = 0x0, __coro_index = 0 '\000'}

One hard part is we need construct the name for variables since there isn't a map from llvm variables to DIVar. Then here is the strategy this patch uses:

- The name `__resume_fn `, `__destroy_fn` and `__coro_index ` are constructed by the patch.
- Then the name `__promise` comes from the dbg.variable of corresponding dbg.declare of PromiseAlloca, which shows highest priority to construct the debug information for the member of coroutine frame.
- Then if the member is struct, we would try to get the name of the llvm struct directly. Then replace ':' and '.' with '_' to make it printable for debugger.
- If the member is a basic type like integer or double, we would try to emit the corresponding name.
- Then if the member is a Pointer Type, we would add `Ptr` after corresponding pointee type.
- Otherwise, we would name it with 'UnknownType'.

The ability to construct coroutine is controlled by an option so this patch won't affect other parts. Some parts of this patch is duplicated with D97673 <https://reviews.llvm.org/D97673> and D96938 <https://reviews.llvm.org/D96938>. So we can't apply them all without conflicts.

It is a little unnatural to construct coroutine frame debug information. The implementation and design for the patch may have many problems. Any opinion is well come.


https://reviews.llvm.org/D99179

Files:
  llvm/lib/Transforms/Coroutines/CoroFrame.cpp
  llvm/lib/Transforms/Coroutines/CoroInternal.h
  llvm/test/Transforms/Coroutines/coro-debug-coro-frame.ll

-------------- next part --------------
A non-text attachment was scrubbed...
Name: D99179.332546.patch
Type: text/x-patch
Size: 27580 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20210323/6a5381ac/attachment.bin>


More information about the llvm-commits mailing list