This is an archive of the discontinued LLVM Phabricator instance.

Debug Info: Add support for named constants
AbandonedPublic

Authored by aprantl on Sep 13 2019, 10:31 AM.

Details

Summary

DWARF defines DW_TAG_constant for "languages that have true named constants" (DWARF v5 section 4.1). This patch adds LLVM IR metadata and DWARF AsmPrinter support for this feature. The primary motivation behind this addition is for representing "let"-bindings in Swift, to communicate to the debugger that these may not be modified. It is very likely also useful for many other programming languages that have named constants.

rdar://problem/16042546

Diff Detail

Event Timeline

aprantl created this revision.Sep 13 2019, 10:31 AM
Herald added a project: Restricted Project. · View Herald TranscriptSep 13 2019, 10:31 AM
Herald added a subscriber: hiraditya. · View Herald Transcript

What's a "true named constant" as opposed to, say, C++ (or C's) "const int X = 3;" (well, I guess const int X could be defined by a runtime expression, - OK, what about "constexpr int X = 3;" then?)

But seems fine in any case (:

vsk added a subscriber: jingham.Sep 13 2019, 11:24 AM

IIUC @jingham makes the argument here that DW_TAG_constant isn't appropriate for swift's 'let', and instead suggests attaching DW_AT_const_value to the DW_TAG_variable (which lldb seems to already understand). Wdyt of that as an alternative?

In D67563#1669828, @vsk wrote:

IIUC @jingham makes the argument here that DW_TAG_constant isn't appropriate for swift's 'let', and instead suggests attaching DW_AT_const_value to the DW_TAG_variable (which lldb seems to already understand). Wdyt of that as an alternative?

Yeah, sounds like "let" variables can be runtime values like const int in C (eg: "void f(int g) { const int h = g; ... }") - which, like Jim said, I don't think is what's meant by DWARF's "true constants" (though it'd be nice if DWARF were a bit more descriptive there).

That said, inversely - since Swift doesn't use DWARF tags to describe types, you don't have the option of describing these in the same way as "const" variables in C - because you can't put a DW_TAG_const_type around the type (yeah, that wouldn't quite match the source even if you could do it - but I think it'd be a pretty good analogy, really). So in that sense, I could see how using "DW_TAG_constant" might be a reasonable workaround - if not for the parameter issue. If you can have a "let" be a parameter, then that needs to be a DW_TAG_formal_parameter and can't be DW_TAG_constant (as Jim says) - so you'll need some other representation to cope with this anyway.

DW_AT_const_value probably isn't that representation, though - because that's for, even moreso (perhaps, only due to the ambiguity of DW_TAG_constant's specification) than DW_TAG_constant - DW_AT_const_value stores a compile-time, storage-less things which not all "let" values seem to be.

Before creating this patch I had considered 4 options:

  1. Use DW_TAG_constant + DW_AT_location
    • Con: DWARF spec doesn't actually explicitly allow combining these two.
    • Con: There is no way to apply this to a DW_TAG_formal_parameter or DW_TAG_member
  2. Use DW_TAG_variable + DW_AT_const_value
    • Big con: The DWARF spec doesn't allow DWARF exprs or loclists in DW_AT_const_value
  3. Use DW_TAG_const_type or DW_TAG_immutable_type
    • We do use a DW_AT_type to point to a stub type that holds the mangled name, so this would work.
    • Con: Semantically odd since the constness is a property of the variable not of the type.
    • Pro: also works with DW_TAG_formal_parameter and member types.
  4. We can invent our own DW_AT_LLVM_let_binding (true)
    • Con: Seems fairly Swift-specific (for example Rust has let mut which is like Swift's var).
    • Pro: also works with DW_TAG_formal_parameter and member types.
    • Pro: since most bindings are let, this would be more space-efficient.

Note that the parameter issue isn't quite such a bug deal since all parameters in Swift are constant unless they are specifically marked as inout, in which case the inout does become part of the type.
I'm starting to think we really should do either (3) or (4). Are there any concerns about approach (4), perhaps with a more generic name of DW_AT_LLVM_immutable?

aprantl updated this revision to Diff 220190.Sep 13 2019, 4:03 PM
aprantl added a reviewer: jingham.

Okay. Here is an alternative patch that implements a flag DW_AT_LLVM_constant. "The Swift Programming Language" calls let-bindings "constants" and constant seems to be a generic enough term to be generally useful.

Out of curiosity does swift have const in the type system like C and C++? (I guess so) - and what would be the difference between a normal (non-let) variable of a const type, versus a let variable of a non-const type?

Out of curiosity does swift have const in the type system like C and C++? (I guess so) - and what would be the difference between a normal (non-let) variable of a const type, versus a let variable of a non-const type?

Swift does not have const in the type system. There are no real pointers in the language; the closest equivalent are probably constant struct members, such as:

struct MyStruct {
  let i_am_a_constant : Int = 42
  var i_am_mutable : String
}

Out of curiosity does swift have const in the type system like C and C++? (I guess so) - and what would be the difference between a normal (non-let) variable of a const type, versus a let variable of a non-const type?

Swift does not have const in the type system. There are no real pointers in the language; the closest equivalent are probably constant struct members, such as:

struct MyStruct {
  let i_am_a_constant : Int = 42
  var i_am_mutable : String
}

Would seem to me (with an obvious C++ bias in my view of the world) that it maps best to i_am_a_constant being a DW_TAG_variable (or function_parameter) with a DW_TAG_const_type that has a base type of "Int" (however that's represented).

But I realize that might not be a good fit for Swift/its debuggers, etc, and certainly tend to leave this up to whatever you folks want to do with your language. Just my 2c :)

Would seem to me (with an obvious C++ bias in my view of the world) that it maps best to i_am_a_constant being a DW_TAG_variable (or function_parameter) with a DW_TAG_const_type that has a base type of "Int" (however that's represented).

But I realize that might not be a good fit for Swift/its debuggers, etc, and certainly tend to leave this up to whatever you folks want to do with your language. Just my 2c :)

It's not elegant in LLDB to peel the constness out of the type and attach it to the variable (the code that takes a DWARF type and returns a Swift type would need to ignore it and the code that builds an lldb_private::Variable needs to look into the DW_AT_type), but certainly doable, and implementation details of one specific debugger shouldn't really affect this decision. The prevalence of let-bindings may cause debug info to blow up a bit with all the extra DW_TAG_const_types, but that's not going to be a showstopper either.

vsk accepted this revision.Sep 18 2019, 10:14 AM

FTR, I like this approach (introducing DW_AT_LLVM_constant). A side-benefit of not attaching AT_location to TAG_constant is that there's no need to update the dwarf location statistics code. So, +1/lgtm on my end -- would be great to have another +1 on this change though.

This revision is now accepted and ready to land.Sep 18 2019, 10:14 AM
aprantl abandoned this revision.Sep 18 2019, 2:39 PM

After some consideration I concluded that this isn't the best approach either. [There's no plan to do that, but] If we ever want to serialize Swift types into DWARF then an attribute on (local) variables isn't enough to describe let-members of structs. So instead I'm currently preparing a patch (no LLVM involved) that will wrap let-bound types in DW_TAG_const_type (like the const qualifier in C).

What's a "true named constant" as opposed to, say, C++ (or C's) "const int X = 3;" (well, I guess const int X could be defined by a runtime expression, - OK, what about "constexpr int X = 3;" then?)

My take is, const and constexpr distinguish the set of possible uses of a variable; they don't turn the item into something that is not a variable. You can still take its address, for example. By that criterion, C and C++ don't have true named constants.

DW_TAG_constant is aimed more at other languages where constants are considered a different kind of language entity. The DWARF doc has examples from Fortran; I know Pascal and COBOL have them.

Would seem to me (with an obvious C++ bias in my view of the world) that it maps best to i_am_a_constant being a DW_TAG_variable (or function_parameter) with a DW_TAG_const_type that has a base type of "Int" (however that's represented).

But I realize that might not be a good fit for Swift/its debuggers, etc, and certainly tend to leave this up to whatever you folks want to do with your language. Just my 2c :)

It's not elegant in LLDB to peel the constness out of the type and attach it to the variable (the code that takes a DWARF type and returns a Swift type would need to ignore it and the code that builds an lldb_private::Variable needs to look into the DW_AT_type), but certainly doable, and implementation details of one specific debugger shouldn't really affect this decision. The prevalence of let-bindings may cause debug info to blow up a bit with all the extra DW_TAG_const_types, but that's not going to be a showstopper either.

Fair - I don't have so much experience with those sort of languages so perhaps haven't ever dealt with the concept that's trying to be described here.

I wonder, then, about a "constexpr T&" - maybe that's closer to a "true constant" - you can't take its address? (references have no address, as far as the language is concerned - maybe not-even-constexpr T& is a "true constant" (but only allows "address constants" not other things like integers to have similar handling)).

& maybe 'let' is more like a const T than it is one of these "true constant" things? (sounds like that's where Adrian's going with it now anyway)

Swift's let is indeed closer to C's const than to a named constant in Pascal (https://wiki.freepascal.org/Constants). The Swift language spec call them constants, and they are effectively SSA variables (with storage). But (and this is where it differs from Pascal constants) let bindings can also appear inside of composite types (there are let struct and class members) and as function arguments (unless a parameter is explicitly marked as inout, similar to Fortran). I have found that DW_TAG_const is the only mechanism that works for all of these use-cases.

How do Fortran compilers represent regular versus inout arguments in DWARF?

Swift's let is indeed closer to C's const than to a named constant in Pascal (https://wiki.freepascal.org/Constants). The Swift language spec call them constants, and they are effectively SSA variables (with storage). But (and this is where it differs from Pascal constants) let bindings can also appear inside of composite types (there are let struct and class members) and as function arguments (unless a parameter is explicitly marked as inout, similar to Fortran). I have found that DW_TAG_const is the only mechanism that works for all of these use-cases.

Adding an attribute for "I'm a 'let'" to DW_TAG_variable, DW_TAG_formal_parameter and DW_TAG_member I don't think would've been the end of the world/worst thing, but DW_TAG_const does seem like a closer general model as far as DWARF is concerned, yeah.

How do Fortran compilers represent regular versus inout arguments in DWARF?

According to godbolt.org, gfortran 8.2 emits the same DWARF for in and inout parameters. That doesn't help :-)

I wonder, then, about a "constexpr T&" - maybe that's closer to a "true constant" - you can't take its address? (references have no address, as far as the language is concerned - maybe not-even-constexpr T& is a "true constant" (but only allows "address constants" not other things like integers to have similar handling)).

I admire your gaming my off-the-cuff example. But no sensible producer would try to coerce that into a DW_TAG_constant.

How do Fortran compilers represent regular versus inout arguments in DWARF?

Can't help you there; last time I used Fortran, there was only one kind of argument. (Which can be entertaining when you pass "2" to a formal parameter that then gets modified by the callee. Redefining the value of "2" made for a very weird debugging experience.)

SouraVX added a subscriber: SouraVX.EditedFeb 9 2021, 11:23 PM

How do Fortran compilers represent regular versus inout arguments in DWARF?

According to godbolt.org, gfortran 8.2 emits the same DWARF for in and inout parameters. That doesn't help :-)

For Fortran snippet:

module foo
integer, parameter :: bar = 200
end module foo

$gfortran -g -c module.f90
DWARF:

0x00000055:     DW_TAG_constant
                  DW_AT_name    ("bar")
                  DW_AT_decl_file       ("mod.f90")
                  DW_AT_decl_line       (3)
                  DW_AT_decl_column     (0x27)
                  DW_AT_type    (0x0000006a "const integer(kind=4)")
                  DW_AT_external        (true)
                  DW_AT_const_value     (0xc8)