Issue 221105.1: Add a mechanism for specifying subprogram return value locations

Author: Kyle Huey
Champion: Andrew Cagney
Date submitted: 2022-11-05
Date revised: 2023-11-27
Date closed:
Type: Enhancement
Status: Open
DWARF version: 6

Comparing original with 2023-11-27. [ View this version ] [ Return to the latest version ]

Section 3.3.2, pg 78

## Background
DWARF allows a `DW_TAG_subprogram/DW_TAG_inlined_subroutine` (the latter 
via `DW_AT_abstract_origin`) to note their return type with a `DW_AT_type`, 
as detailed in Section 3.3.2. It does not, however, provide any 
information about where the return type is in the program at the 
subprogram boundary. Debuggers that support printing return values 
at function exit (e.g. gdb) currently infer this from the platform ABI 
(e.g. `amd64_return_value` in `gdb/amd64-tdep.c`). There is no requirement 
that arbitrary functions actually follow the platform's standard ABI 
though, and when this inference fails, it fails silently, presenting 
the wrong value to users.

There are, in my opinion, two interesting cases:
A debug feature is to print the result of a subroutine at exit.

One way this is implemented is:
1.  Cases where the function follows some sort of ABI, just not the 
    platform's standard ABI.

- run the called subroutine until it exits
    The Rust compiler, for instance, doesn't always follow the standard 
    SYSV AMD64 ABI when compiling for that platform. 
    See <https://github.com/rust-lang/rust/issues/85641> for one real world 
    example that silently breaks gdb.

  i.e., the return instruction has been completed and the program is
  in the calling subprogram
    In theory this case could be covered by adding a `DW_CC_rust` value for 
    the `DW_AT_calling_convention` attribute to the spec, and downstream 
    tools could be taught what that means and how to process it accordingly. 
    I think this is more complicated than having the compiler directly emit 
    the location information, and it wouldn't cover the second case.

2.  Inline functions.
- extract the result from the calling subprogram's frame

While DWARF makes available the type of the return value (`DW_AT_type`
attached to `DW_TAG_subprogram`, as detailed in Section 3.3.2.) it
does not provide any information about the return value's location.
Debuggers instead use the platform's calling convention to determine
the return value's location.
    Inline functions don't necessarily follow any ABI. Depending on the 
    optimizations performed after inlining, they may not even have proper 
    bounds to determine what constitutes a single invocation of the function 
    (imagine an inline function whose instructions have been intermingled 
    by the optimizer with the instructions of its containing function). 
    But there are common cases where a inlined function does have a 
    meaningful and easy-to-determine return value.

Consider the simple C++ program
However, for subprograms that do not follow the standard calling
convention (i.e., (hopefully) have `DW_AT_calling_convention`
`DW_CC_nocall` attached to `DW_TAG_subprogram`), locating the return
value may not be possible.

    #include <iostream>
Inline functions are similar (using `DW_TAG_inlined_subroutine` via
`DW_AT_abstract_origin`).  Run the code block until the inlined
function has "exited", and then extract the returned value.  However,
here things are even more challenging.  It is very unlikely that the
inlined and optimized code ever follows anything approaching an ABI.

    using namespace std;
## Proposal

    inline bool greater_than(int x, int y) {
        return x > y;
    }
Attach a location description to the subprogram's debug information
describing the location of the return value immediately after the
subprogram has returned.

### Why not describe the value at the return instruction?
    int main(int argc) {
        if (greater_than(argc, 4)) {
            cout << "I have more than 3 arguments\n";
        } else {
            cout << "I have 3 or fewer arguments\n";
        }
        return 0;
    }

- this is addressing the above existing pratice
An optimizing compiler (e.g. gcc 12.2 with `-O2`) can convert the 
`greater_than` function into a single comparison instruction inlined 
into main. The corresponding bit in the flags register is clearly 
not the ABI-specified location for the return value.

- informally, the completion of the return instruction acts as a
  synchronization point

- for delayed branch and VLIW architectures, it is often only after
  the completion of the return instruction (bundle) that the
  subprogram result is in a known location
I propose to add language to the spec allowing `DW_AT_location` to be 
present on `DW_TAG_subprogram`/`DW_TAG_inlined_subroutine`. When present, 
it would contain a location expression specifying the location of the 
function's return value. (In the two examples above, on x86-64, the 
expressions

    DW_OP_reg0 DW_OP_piece 4 DW_OP_reg1 DW_OP_piece 4
## Further work

and 
I suspect Call Site Entries may provide a better way of describing
inlined function call return values.  I recommend investigating that
separately.

## Change Details
    DW_OP_regx 49 DW_OP_dup DW_OP_const1u 64 DW_OP_and DW_OP_lit6 
    DW_OP_shr DW_OP_lit0 DW_OP_eq DW_OP_swap DW_OP_dup DW_OP_const1u 128 
    DW_OP_and DW_OP_lit7 DW_OP_shr DW_OP_swap DW_OP_lit1 DW_OP_and 
    DW_OP_eq DW_OP_eq

respectively are capable of encoding the return 
values). If it's not present, debuggers and other tools can fall back 
to their current behavior.
### In 3.3.2 Subroutine and Entry Point Return Types

Change the section heading to:

> 3.3.2 Subroutine and Entry Point Return Type and Value

At the end of the section, which currently reads:

> If the subroutine or entry point is a function that returns a
> value, then its debugging information entry has a `DW_AT_type`
> attribute to denote the type returned by that function.
> 
> [non-normative text]

append the text:

> A subroutine or entry point that is a function that returns a
> value and has a `DW_AT_type` attribute may also have a
> `DW_AT_location` attribute.  This attribute describes the
> location of the return value after the called function has
> returned and the calling subprogram is about to be resumed.  If
> a producer emits no machine code for this subprogram then this
> attribute is is not specified.
> 
> [begin non-normative]
> 
> Since the called subprogram has returned, the location
> description is relative to the calling subprogram.  For
> instance, for a sliding window architecture such as SPARC, the
> location description will refer to the output registers of the
> caller, and not the input registers of the callee.
> 
> [end non-normative]

**Questions:**

- "If a producer emits no machine code for this subprogram then this
  attribute is is not specified" is based on `DW_AT_high_pc`?

- should this attribute get a new name other than `DW_AT_location` or
  stick with that since it goes with `DW_AT_type`?

- `DW_AT_location` is useless with out `DW_AT_type`; I made
  `DW_AT_type` a predicate should it instead be non-normative text?

### In 3.3.8.1 Abstract Instances

In the non-normative text:

> For example, the `DW_AT_low_pc`, [...] attributes typically
> should be omitted;

add `DW_AT_location`.

### In 3.3.8.2 Concrete Instances

Following the paragraph:

> An inlined subroutine entry may have a `DW_AT_const_expr`
> attribute, [...], represented as it would be on the target
> architecture.

Add the paragraph:

> An inlined subroutine entry that is a function that returns a
> value may have a `DW_AT_location` attribute.  This attribute
> describes the location of the return value immediately after
> the concrete instance has completed execution of the contiguous
> or non-contiguous machine instructions generated for the
> inlined subroutine.

**Questions:**

- see comment above about `DW_AT_type`

### In Appendix A. Attributes by Tag (Informative)

Under TAG name `DW_TAG_subprogram` add `DW_AT_location` to the
Applicable attributes.

### In Appendix D.

**Questions:**

- do we want to attempt an example?

---

2023-11-05: Rewrote.
2023-07-24: Mark as incomplete. What "two examples above"?
When is the location valid? What about tail calls?
Second expression in example is not a location description.

Possible new proposal: How to determine end of an inlined function.