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.