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-07-24
Date closed:
Type: Enhancement
Status: Open
DWARF version: 6

This is the original proposal. [ Return to the latest version ]

Section 3.3.2, pg 78

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:

  1. Cases where the function follows some sort of ABI, just not the platform's standard ABI.

    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.

    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.

    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

#include <iostream>

using namespace std;

inline bool greater_than(int x, int y) {
    return x > y;
}

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;
}

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.

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

and

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.


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.