Issue 230529.2: Outlined Subroutines

Author: Jakub Jelinek
Champion: Jakub Jelinek
Date submitted: 2023-05-29
Date revised: 2024-03-18
Date closed:
Type: Enhancement
Status: Open
DWARF Version: 6

Background

Some subroutines are created artificially from just a part of user code. It is still useful to use DW_AT_abstract_origin on such subroutines to express from which user subroutine they are created, but it is desirable to tell the debugger about that, so that e.g. for break function it doesn't actually put a breakpoint at the start of such subroutine. Also, it can be useful to understand the purpose of the outlining.

One example is function splitting for partial inlining.

int make_me_big (void);
void do_work (void);

static int
split_me (int a)
{
  if (a < 10)
    {
      do_work ();
      return 42;
    }
  else
    {
      make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big ();
      make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big ();
      make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big ();
      make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big ();
      make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big ();
      make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big (); make_me_big ();
      return a + 1;
    }
}

int
test(void)
{
  return split_me (0)+split_me(1)+split_me(2)+split_me(36)+split_me(82);
}

The whole function might be too long to be inlined, so some compilers choose to only only inline some inexpensive part of the function and outline the rest into an artificial function.

That will typically result in DW_TAG_subprogram for the abstract instance with DW_AT_inline, then DW_TAG_inlined_subroutine referencing through DW_AT_abstract_origin that abstract instance (many times, for each inlined copy) and finally the large/expensive part is outlined into an artificial subprogram (in assembly GCC calls that split_me.part.0). That one gets DW_TAG_subprogram mentioning the DW_AT_abstract_origin. But, break split_me shouldn't stop at the start of this artificial subroutine, it is not the entry of the function. It can have completely different arguments/return value from the abstract origin, etc.

Another example are OpenMP/OpenACC regions, e.g.

void bar (void) { int a = 42, b, c = 0;
#pragma omp parallel shared(a) private (b)
{
  b = omp_get_thread_num ();
  printf ("Hello world from thread %d sees a = %d\n", b, a);
}
}

Here, various compilers outline the OpenMP parallel region into a separate function with some custom calling convention and invoke it multiple times from each thread. Similarly here it is desirable to tell the debugger that the artificial function has been created on behalf of user function bar, but it is useful to tell the debugger the purpose of it, that it is OpenMP parallel region, such that it can use e.g. OMPD library if any to query various details like how to find the frame of the containing function. While the a and b variables often will have DW_AT_location inside of such an artificial function such that print a or print b etc. works fine in the debugger, it would be useful to be able to print there also the variables which weren't privatized nor shared in the region because they weren't referenced there. For this purpose e.g. GCC uses DW_AT_static_link, which makes it work in the outlined region run by the same thread as the outer part of the function, but it doesn't work in other threads.

The scope of this proposal is just to determine which DW_TAG_subprogram DIEs represent just portions of original user functions not including the entry to those functions and the purpose why it has been created, not solving all other related problems what the debug information consumer can and should be doing with that information. So, the presence of the new attribute can hint to the debug info consumer it shouldn't put a breakpoint at the start when user requests putting a breakpoint at the start of the corresponding function, or it can hint it sholuld use OMPD or some other special code to find out which OpenMP thread created certain parallel region, but for other tasks there should be separate new extensions if needed.

Proposed Changes

In Table 2.2 add:

DW_AT_outlined  Purpose of outlined subroutine

In Table 7.5 add:

DW_AT_outlined ‡        0x93    constant

Insert new paragraph before 3.3.9 and renumber 3.3.9 Trampolines to 3.3.10:

3.3.9 Outlined regions

Some compilers split user subroutines into multiple parts and outline some of those into compiler-generated subroutines. This happens e.g. during function splitting for partial inlining, where some cheap part of a function including its entry is inlined into callers, but some more expensive/larger part is kept as a separate subroutine, or for various OpenMP or OpenACC constructs where the body of those constructs should be run by multiple threads or on different devices. Outlined subroutines never contain an entry of the user subroutine.

An outlined subroutine is represented by a debugging information entry with the tag DW_TAG_subprogram, DW_AT_abstract_origin attribute pointing to the abstract instance debugging information entry and a DW_AT_outlined attribute whose value is an integer constant explaining the purpose for which the outlined subroutine has been created.

The set of outlining purpose codes for subroutines is given in Table 3.XX.

Table 3.XX Types of outlined subroutines

    DW_OL_none
    DW_OL_partial_inline
    DW_OL_omp_teams
    DW_OL_omp_parallel
    DW_OL_omp_task
    DW_OL_omp_taskloop
    DW_OL_omp_target
    DW_OL_omp_target_teams
    DW_OL_oacc_parallel
    DW_OL_oacc_kernels
    DW_OL_lo_user
    DW_OL_hi_user

If this attribute is not present, or its value is the constant DW_OL_none, then the subroutine is not outlined subroutine and contains the subroutine's entry. If the value of the attribute is the constant DW_OL_partial_inline, then a part of a subroutine including its entry is represented using DW_TAG_inlined_subroutine and DW_TAG_subprogram with DW_AT_outlined DW_OL_partial_inline contains the other part of the function.

If the value of the attribute is DW_OL_omp_teams, DW_OL_omp_parallel, DW_OL_omp_task, DW_OL_omp_taskloop, DW_OL_omp_target, or DW_OL_omp_target_teams, then it is an outlined subroutine created for a body of an OpenMP teams (not nested inside of target construct), parallel, task, taskloop, target or teams (nested inside of target construct) construct. If the value of the attribute is DW_OL_oacc_parallel or DW_OL_oacc_kernels, then it is an outlined subroutine created for a body of an OpenACC parallel or kernel construct.

If the value of the attribute is in the DW_OL_lo_user to DW_OL_hi_user range, it is an outlined subroutine created for vendor specific reasons.

In section 7.1 Vendor Extensibility, add DW_OL, before DW_OP in second paragraph.

Add section before 7.17 Array Ordering, renumber 7.17 and later sections;

7.17 Types of outlined subroutines

The encodings of the constants used in the DW_AT_outlined attribute are given in Table 7.XX.

Table 7.XX Types of outlined subroutines

    Outline purpose         Value
    -----------------------------
    DW_OL_none              0x00
    DW_OL_partial_inline    0x01
    DW_OL_omp_teams         0x10
    DW_OL_omp_parallel      0x11
    DW_OL_omp_task          0x12
    DW_OL_omp_taskloop      0x13
    DW_OL_omp_target        0x14
    DW_OL_omp_target_teams  0x15
    DW_OL_oacc_parallel     0x20
    DW_OL_oacc_kernels      0x21
    DW_OL_lo_user           0x20000
    DW_OL_hi_user           0x3ffff

In Appendix A., add DW_AT_outlined as allowed for DW_TAG_subprogram.


2023-11-27: Need to revise to clarify what problems this proposal intends to solve and what problems it does not.

2024-03-18: Attempted to clarify the above, plus add DW_OL_omp_target_teams and vendor extensibility.