-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Non constant size and offset in DWARF #141106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@llvm/pr-subscribers-clang @llvm/pr-subscribers-llvm-ir Author: Tom Tromey (tromey) ChangesIn Ada, a record type can have a non-constant size, and a field can appear at a non-constant bit offset in a record. To support this, this patch changes DIType to record the size and offset using metadata, rather than plain integers. In addition to a constant offset, both DIVariable and DIExpression are now supported here. One thing of note in this patch is the choice of how exactly to represent a non-constant bit offset, with the difficulty being that DWARF 5 does not support this. DWARF 3 did have a way to support a non-constant byte offset, combined with a constant bit offset within the byte, but this was deprecated in DWARF 4 and removed from DWARF 5. This patch takes a simple approach: a DWARF extension allowing the use of an expression with DW_AT_data_bit_offset. There is a corresponding DWARF issue, see https://dwarfstd.org/issues/250501.1.html. The main reason for this approach is that it keeps API simplicity: just a single value is needed, rather than having separate data describing the byte offset and the bit within the byte. Patch is 124.35 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/141106.diff 13 Files Affected:
diff --git a/llvm/include/llvm/IR/DIBuilder.h b/llvm/include/llvm/IR/DIBuilder.h
index 4ce71bd3dad58..1e1a4cb0af9ba 100644
--- a/llvm/include/llvm/IR/DIBuilder.h
+++ b/llvm/include/llvm/IR/DIBuilder.h
@@ -365,6 +365,24 @@ namespace llvm {
uint64_t BaseOffset, uint32_t VBPtrOffset,
DINode::DIFlags Flags);
+ /// Create debugging information entry for a member.
+ /// \param Scope Member scope.
+ /// \param Name Member name.
+ /// \param File File where this member is defined.
+ /// \param LineNo Line number.
+ /// \param SizeInBits Member size.
+ /// \param AlignInBits Member alignment.
+ /// \param OffsetInBits Member offset.
+ /// \param Flags Flags to encode member attribute, e.g. private
+ /// \param Ty Parent type.
+ /// \param Annotations Member annotations.
+ DIDerivedType *createMemberType(DIScope *Scope, StringRef Name,
+ DIFile *File, unsigned LineNo,
+ Metadata *SizeInBits, uint32_t AlignInBits,
+ Metadata *OffsetInBits,
+ DINode::DIFlags Flags, DIType *Ty,
+ DINodeArray Annotations = nullptr);
+
/// Create debugging information entry for a member.
/// \param Scope Member scope.
/// \param Name Member name.
@@ -419,6 +437,25 @@ namespace llvm {
DIDerivedType *createVariantMemberType(DIScope *Scope, DINodeArray Elements,
Constant *Discriminant, DIType *Ty);
+ /// Create debugging information entry for a bit field member.
+ /// \param Scope Member scope.
+ /// \param Name Member name.
+ /// \param File File where this member is defined.
+ /// \param LineNo Line number.
+ /// \param SizeInBits Member size.
+ /// \param OffsetInBits Member offset.
+ /// \param StorageOffsetInBits Member storage offset.
+ /// \param Flags Flags to encode member attribute.
+ /// \param Ty Parent type.
+ /// \param Annotations Member annotations.
+ DIDerivedType *createBitFieldMemberType(DIScope *Scope, StringRef Name,
+ DIFile *File, unsigned LineNo,
+ Metadata *SizeInBits,
+ Metadata *OffsetInBits,
+ uint64_t StorageOffsetInBits,
+ DINode::DIFlags Flags, DIType *Ty,
+ DINodeArray Annotations = nullptr);
+
/// Create debugging information entry for a bit field member.
/// \param Scope Member scope.
/// \param Name Member name.
@@ -510,6 +547,29 @@ namespace llvm {
unsigned RunTimeLang = 0, DIType *VTableHolder = nullptr,
MDNode *TemplateParms = nullptr, StringRef UniqueIdentifier = "");
+ /// Create debugging information entry for a struct.
+ /// \param Scope Scope in which this struct is defined.
+ /// \param Name Struct name.
+ /// \param File File where this member is defined.
+ /// \param LineNumber Line number.
+ /// \param SizeInBits Member size.
+ /// \param AlignInBits Member alignment.
+ /// \param Flags Flags to encode member attribute, e.g. private
+ /// \param Elements Struct elements.
+ /// \param RunTimeLang Optional parameter, Objective-C runtime version.
+ /// \param UniqueIdentifier A unique identifier for the struct.
+ /// \param Specification The type that this type completes. This is used by
+ /// Swift to represent generic types.
+ /// \param NumExtraInhabitants The number of extra inhabitants of the type.
+ /// An extra inhabitant is a bit pattern that does not represent a valid
+ /// value for instances of a given type. This is used by the Swift language.
+ DICompositeType *createStructType(
+ DIScope *Scope, StringRef Name, DIFile *File, unsigned LineNumber,
+ Metadata *SizeInBits, uint32_t AlignInBits, DINode::DIFlags Flags,
+ DIType *DerivedFrom, DINodeArray Elements, unsigned RunTimeLang = 0,
+ DIType *VTableHolder = nullptr, StringRef UniqueIdentifier = "",
+ DIType *Specification = nullptr, uint32_t NumExtraInhabitants = 0);
+
/// Create debugging information entry for a struct.
/// \param Scope Scope in which this struct is defined.
/// \param Name Struct name.
diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h
index d82c69aebb026..2ab2da60b1b6e 100644
--- a/llvm/include/llvm/IR/DebugInfoMetadata.h
+++ b/llvm/include/llvm/IR/DebugInfoMetadata.h
@@ -711,40 +711,33 @@ std::optional<StringRef> DIScope::getSource() const {
class DIType : public DIScope {
unsigned Line;
DIFlags Flags;
- uint64_t SizeInBits;
- uint64_t OffsetInBits;
uint32_t NumExtraInhabitants;
protected:
+ static constexpr unsigned N_OPERANDS = 5;
+
DIType(LLVMContext &C, unsigned ID, StorageType Storage, unsigned Tag,
- unsigned Line, uint64_t SizeInBits, uint32_t AlignInBits,
- uint64_t OffsetInBits, uint32_t NumExtraInhabitants, DIFlags Flags,
- ArrayRef<Metadata *> Ops)
+ unsigned Line, uint32_t AlignInBits, uint32_t NumExtraInhabitants,
+ DIFlags Flags, ArrayRef<Metadata *> Ops)
: DIScope(C, ID, Storage, Tag, Ops) {
- init(Line, SizeInBits, AlignInBits, OffsetInBits, NumExtraInhabitants,
- Flags);
+ init(Line, AlignInBits, NumExtraInhabitants, Flags);
}
~DIType() = default;
- void init(unsigned Line, uint64_t SizeInBits, uint32_t AlignInBits,
- uint64_t OffsetInBits, uint32_t NumExtraInhabitants,
+ void init(unsigned Line, uint32_t AlignInBits, uint32_t NumExtraInhabitants,
DIFlags Flags) {
this->Line = Line;
this->Flags = Flags;
- this->SizeInBits = SizeInBits;
this->SubclassData32 = AlignInBits;
- this->OffsetInBits = OffsetInBits;
this->NumExtraInhabitants = NumExtraInhabitants;
}
/// Change fields in place.
- void mutate(unsigned Tag, unsigned Line, uint64_t SizeInBits,
- uint32_t AlignInBits, uint64_t OffsetInBits,
+ void mutate(unsigned Tag, unsigned Line, uint32_t AlignInBits,
uint32_t NumExtraInhabitants, DIFlags Flags) {
assert(isDistinct() && "Only distinct nodes can mutate");
setTag(Tag);
- init(Line, SizeInBits, AlignInBits, OffsetInBits, NumExtraInhabitants,
- Flags);
+ init(Line, AlignInBits, NumExtraInhabitants, Flags);
}
public:
@@ -753,10 +746,8 @@ class DIType : public DIScope {
}
unsigned getLine() const { return Line; }
- uint64_t getSizeInBits() const { return SizeInBits; }
uint32_t getAlignInBits() const;
uint32_t getAlignInBytes() const { return getAlignInBits() / CHAR_BIT; }
- uint64_t getOffsetInBits() const { return OffsetInBits; }
uint32_t getNumExtraInhabitants() const { return NumExtraInhabitants; }
DIFlags getFlags() const { return Flags; }
@@ -766,6 +757,26 @@ class DIType : public DIScope {
Metadata *getRawScope() const { return getOperand(1); }
MDString *getRawName() const { return getOperandAs<MDString>(2); }
+ Metadata *getRawSizeInBits() const { return getOperand(3); }
+ uint64_t getSizeInBits() const {
+ if (auto *MD = dyn_cast_or_null<ConstantAsMetadata>(getRawSizeInBits())) {
+ if (ConstantInt *CI = dyn_cast_or_null<ConstantInt>(MD->getValue())) {
+ return CI->getZExtValue();
+ }
+ }
+ return 0;
+ }
+
+ Metadata *getRawOffsetInBits() const { return getOperand(4); }
+ uint64_t getOffsetInBits() const {
+ if (auto *MD = dyn_cast_or_null<ConstantAsMetadata>(getRawOffsetInBits())) {
+ if (ConstantInt *CI = dyn_cast_or_null<ConstantInt>(MD->getValue())) {
+ return CI->getZExtValue();
+ }
+ }
+ return 0;
+ }
+
/// Returns a new temporary DIType with updated Flags
TempDIType cloneWithFlags(DIFlags NewFlags) const {
auto NewTy = clone();
@@ -831,18 +842,18 @@ class DIBasicType : public DIType {
protected:
DIBasicType(LLVMContext &C, StorageType Storage, unsigned Tag,
- uint64_t SizeInBits, uint32_t AlignInBits, unsigned Encoding,
+ uint32_t AlignInBits, unsigned Encoding,
uint32_t NumExtraInhabitants, DIFlags Flags,
ArrayRef<Metadata *> Ops)
- : DIType(C, DIBasicTypeKind, Storage, Tag, 0, SizeInBits, AlignInBits, 0,
+ : DIType(C, DIBasicTypeKind, Storage, Tag, 0, AlignInBits,
NumExtraInhabitants, Flags, Ops),
Encoding(Encoding) {}
DIBasicType(LLVMContext &C, unsigned ID, StorageType Storage, unsigned Tag,
- uint64_t SizeInBits, uint32_t AlignInBits, unsigned Encoding,
+ uint32_t AlignInBits, unsigned Encoding,
uint32_t NumExtraInhabitants, DIFlags Flags,
ArrayRef<Metadata *> Ops)
- : DIType(C, ID, Storage, Tag, 0, SizeInBits, AlignInBits, 0,
- NumExtraInhabitants, Flags, Ops),
+ : DIType(C, ID, Storage, Tag, 0, AlignInBits, NumExtraInhabitants, Flags,
+ Ops),
Encoding(Encoding) {}
~DIBasicType() = default;
@@ -859,11 +870,21 @@ class DIBasicType : public DIType {
MDString *Name, uint64_t SizeInBits,
uint32_t AlignInBits, unsigned Encoding,
uint32_t NumExtraInhabitants, DIFlags Flags,
+ StorageType Storage, bool ShouldCreate = true) {
+ auto *SizeInBitsNode = ConstantAsMetadata::get(
+ ConstantInt::get(Type::getInt64Ty(Context), SizeInBits));
+ return getImpl(Context, Tag, Name, SizeInBitsNode, AlignInBits, Encoding,
+ NumExtraInhabitants, Flags, Storage, ShouldCreate);
+ }
+ static DIBasicType *getImpl(LLVMContext &Context, unsigned Tag,
+ MDString *Name, Metadata *SizeInBits,
+ uint32_t AlignInBits, unsigned Encoding,
+ uint32_t NumExtraInhabitants, DIFlags Flags,
StorageType Storage, bool ShouldCreate = true);
TempDIBasicType cloneImpl() const {
- return getTemporary(getContext(), getTag(), getName(), getSizeInBits(),
- getAlignInBits(), getEncoding(),
+ return getTemporary(getContext(), getTag(), getRawName(),
+ getRawSizeInBits(), getAlignInBits(), getEncoding(),
getNumExtraInhabitants(), getFlags());
}
@@ -896,6 +917,12 @@ class DIBasicType : public DIType {
uint32_t NumExtraInhabitants, DIFlags Flags),
(Tag, Name, SizeInBits, AlignInBits, Encoding,
NumExtraInhabitants, Flags))
+ DEFINE_MDNODE_GET(DIBasicType,
+ (unsigned Tag, MDString *Name, Metadata *SizeInBits,
+ uint32_t AlignInBits, unsigned Encoding,
+ uint32_t NumExtraInhabitants, DIFlags Flags),
+ (Tag, Name, SizeInBits, AlignInBits, Encoding,
+ NumExtraInhabitants, Flags))
TempDIBasicType clone() const { return cloneImpl(); }
@@ -927,29 +954,28 @@ class DIFixedPointType : public DIBasicType {
APInt Denominator;
DIFixedPointType(LLVMContext &C, StorageType Storage, unsigned Tag,
- uint64_t SizeInBits, uint32_t AlignInBits, unsigned Encoding,
- DIFlags Flags, unsigned Kind, int Factor,
- ArrayRef<Metadata *> Ops)
- : DIBasicType(C, DIFixedPointTypeKind, Storage, Tag, SizeInBits,
- AlignInBits, Encoding, 0, Flags, Ops),
+ uint32_t AlignInBits, unsigned Encoding, DIFlags Flags,
+ unsigned Kind, int Factor, ArrayRef<Metadata *> Ops)
+ : DIBasicType(C, DIFixedPointTypeKind, Storage, Tag, AlignInBits,
+ Encoding, 0, Flags, Ops),
Kind(Kind), Factor(Factor) {
assert(Kind == FixedPointBinary || Kind == FixedPointDecimal);
}
DIFixedPointType(LLVMContext &C, StorageType Storage, unsigned Tag,
- uint64_t SizeInBits, uint32_t AlignInBits, unsigned Encoding,
- DIFlags Flags, unsigned Kind, APInt Numerator,
- APInt Denominator, ArrayRef<Metadata *> Ops)
- : DIBasicType(C, DIFixedPointTypeKind, Storage, Tag, SizeInBits,
- AlignInBits, Encoding, 0, Flags, Ops),
+ uint32_t AlignInBits, unsigned Encoding, DIFlags Flags,
+ unsigned Kind, APInt Numerator, APInt Denominator,
+ ArrayRef<Metadata *> Ops)
+ : DIBasicType(C, DIFixedPointTypeKind, Storage, Tag, AlignInBits,
+ Encoding, 0, Flags, Ops),
Kind(Kind), Factor(0), Numerator(Numerator), Denominator(Denominator) {
assert(Kind == FixedPointRational);
}
DIFixedPointType(LLVMContext &C, StorageType Storage, unsigned Tag,
- uint64_t SizeInBits, uint32_t AlignInBits, unsigned Encoding,
- DIFlags Flags, unsigned Kind, int Factor, APInt Numerator,
+ uint32_t AlignInBits, unsigned Encoding, DIFlags Flags,
+ unsigned Kind, int Factor, APInt Numerator,
APInt Denominator, ArrayRef<Metadata *> Ops)
- : DIBasicType(C, DIFixedPointTypeKind, Storage, Tag, SizeInBits,
- AlignInBits, Encoding, 0, Flags, Ops),
+ : DIBasicType(C, DIFixedPointTypeKind, Storage, Tag, AlignInBits,
+ Encoding, 0, Flags, Ops),
Kind(Kind), Factor(Factor), Numerator(Numerator),
Denominator(Denominator) {}
~DIFixedPointType() = default;
@@ -959,20 +985,42 @@ class DIFixedPointType : public DIBasicType {
uint64_t SizeInBits, uint32_t AlignInBits, unsigned Encoding,
DIFlags Flags, unsigned Kind, int Factor, APInt Numerator,
APInt Denominator, StorageType Storage, bool ShouldCreate = true) {
+ auto *SizeInBitsNode = ConstantAsMetadata::get(
+ ConstantInt::get(Type::getInt64Ty(Context), SizeInBits));
+ return getImpl(Context, Tag, getCanonicalMDString(Context, Name),
+ SizeInBitsNode, AlignInBits, Encoding, Flags, Kind, Factor,
+ Numerator, Denominator, Storage, ShouldCreate);
+ }
+ static DIFixedPointType *
+ getImpl(LLVMContext &Context, unsigned Tag, StringRef Name,
+ Metadata *SizeInBits, uint32_t AlignInBits, unsigned Encoding,
+ DIFlags Flags, unsigned Kind, int Factor, APInt Numerator,
+ APInt Denominator, StorageType Storage, bool ShouldCreate = true) {
return getImpl(Context, Tag, getCanonicalMDString(Context, Name),
SizeInBits, AlignInBits, Encoding, Flags, Kind, Factor,
Numerator, Denominator, Storage, ShouldCreate);
}
static DIFixedPointType *
getImpl(LLVMContext &Context, unsigned Tag, MDString *Name,
- uint64_t SizeInBits, uint32_t AlignInBits, unsigned Encoding,
+ uint32_t SizeInBits, uint32_t AlignInBits, unsigned Encoding,
+ DIFlags Flags, unsigned Kind, int Factor, APInt Numerator,
+ APInt Denominator, StorageType Storage, bool ShouldCreate = true) {
+ auto *SizeInBitsNode = ConstantAsMetadata::get(
+ ConstantInt::get(Type::getInt64Ty(Context), SizeInBits));
+ return getImpl(Context, Tag, Name, SizeInBitsNode, AlignInBits, Encoding,
+ Flags, Kind, Factor, Numerator, Denominator, Storage,
+ ShouldCreate);
+ }
+ static DIFixedPointType *
+ getImpl(LLVMContext &Context, unsigned Tag, MDString *Name,
+ Metadata *SizeInBits, uint32_t AlignInBits, unsigned Encoding,
DIFlags Flags, unsigned Kind, int Factor, APInt Numerator,
APInt Denominator, StorageType Storage, bool ShouldCreate = true);
TempDIFixedPointType cloneImpl() const {
- return getTemporary(getContext(), getTag(), getName(), getSizeInBits(),
- getAlignInBits(), getEncoding(), getFlags(), Kind,
- Factor, Numerator, Denominator);
+ return getTemporary(getContext(), getTag(), getRawName(),
+ getRawSizeInBits(), getAlignInBits(), getEncoding(),
+ getFlags(), Kind, Factor, Numerator, Denominator);
}
public:
@@ -1003,6 +1051,13 @@ class DIFixedPointType : public DIBasicType {
APInt Denominator),
(Tag, Name, SizeInBits, AlignInBits, Encoding, Flags, Kind,
Factor, Numerator, Denominator))
+ DEFINE_MDNODE_GET(DIFixedPointType,
+ (unsigned Tag, MDString *Name, Metadata *SizeInBits,
+ uint32_t AlignInBits, unsigned Encoding, DIFlags Flags,
+ unsigned Kind, int Factor, APInt Numerator,
+ APInt Denominator),
+ (Tag, Name, SizeInBits, AlignInBits, Encoding, Flags, Kind,
+ Factor, Numerator, Denominator))
TempDIFixedPointType clone() const { return cloneImpl(); }
@@ -1042,13 +1097,15 @@ class DIStringType : public DIType {
friend class LLVMContextImpl;
friend class MDNode;
+ static constexpr unsigned MY_FIRST_OPERAND = DIType::N_OPERANDS;
+
unsigned Encoding;
DIStringType(LLVMContext &C, StorageType Storage, unsigned Tag,
- uint64_t SizeInBits, uint32_t AlignInBits, unsigned Encoding,
+ uint32_t AlignInBits, unsigned Encoding,
ArrayRef<Metadata *> Ops)
- : DIType(C, DIStringTypeKind, Storage, Tag, 0, SizeInBits, AlignInBits, 0,
- 0, FlagZero, Ops),
+ : DIType(C, DIStringTypeKind, Storage, Tag, 0, AlignInBits, 0, FlagZero,
+ Ops),
Encoding(Encoding) {}
~DIStringType() = default;
@@ -1058,8 +1115,10 @@ class DIStringType : public DIType {
uint64_t SizeInBits, uint32_t AlignInBits,
unsigned Encoding, StorageType Storage,
bool ShouldCreate = true) {
+ auto *SizeInBitsNode = ConstantAsMetadata::get(
+ ConstantInt::get(Type::getInt64Ty(Context), SizeInBits));
return getImpl(Context, Tag, getCanonicalMDString(Context, Name),
- StringLength, StrLenExp, StrLocationExp, SizeInBits,
+ StringLength, StrLenExp, StrLocationExp, SizeInBitsNode,
AlignInBits, Encoding, Storage, ShouldCreate);
}
static DIStringType *getImpl(LLVMContext &Context, unsigned Tag,
@@ -1067,12 +1126,24 @@ class DIStringType : public DIType {
Metadata *StrLenExp, Metadata *StrLocationExp,
uint64_t SizeInBits, uint32_t AlignInBits,
unsigned Encoding, StorageType Storage,
+ bool ShouldCreate = true) {
+ auto *SizeInBitsNode = ConstantAsMetadata::get(
+ ConstantInt::get(Type::getInt64Ty(Context), SizeInBits));
+ return getImpl(Context, Tag, Name, StringLength, StrLenExp, StrLocationExp,
+ SizeInBitsNode, AlignInBits, Encoding, Storage,
+ ShouldCreate);
+ }
+ static DIStringType *getImpl(LLVMContext &Context, unsigned Tag,
+ MDString *Name, Metadata *StringLength,
+ Metadata *StrLenExp, Metadata *StrLocationExp,
+ Metadata *SizeInBits, uint32_t AlignInBits,
+ unsigned Encoding, StorageType Storage,
bool ShouldCreate = true);
TempDIStringType cloneImpl() const {
return getTemporary(getContext(), getTag(), getRawName(),
getRawStringLength(), getRawStringLengthExp(),
- getRawStringLocationExp(), getSizeInBits(),
+ getRawStringLocationExp(), getRawSizeInBits(),
...
[truncated]
|
Note that I've written this patch as a series of smaller patches. I know it'll all be squashed if it lands, but it may be simpler to review the pieces separately. |
I'm fixing up the failures. Sorry about that. Also as a follow-up I may implement the suggestion here: https://discourse.llvm.org/t/rfc-dynamic-sizes-and-field-offsets-in-dwarf/85992/3?u=tromey |
This patch changes code in DebugInfoMetadata.h to use symbolic constants to refer to metadata slots. This makes it a little simpler (but still not entirely seamless) to add metadata to DIType.
This changes DIType to use metadata for the size and offset information. This patch doesn't have any functional changes yet; the test is in the next patch.
This updates the DWARF writer to emit dynamic offsets and sizes for members.
This adds some new DIBuilder methods to expose the new dynamic size and offset functionality.
2698846
to
90a46a7
Compare
I rebased this and fixed up the mlir problem. I also changed how the overload resolution was done, so that a later patch that treats |
From what I can tell, those failures don't have anything to do with this patch. |
In Ada, a field can have a dynamic bit offset in its enclosing record. In DWARF 3, this was handled using a dynamic DW_AT_data_member_location, combined with a DW_AT_bit_offset -- this combination worked out ok because in practice GNAT only needs a dynamic byte offset with a fixed offset within the byte. However, this approach was deprecated in DWARF 4 and then removed in DWARF 5. No replacement approach was given, meaning that in strict mode there is no way to express this. This is a DWARF bug, see https://dwarfstd.org/issues/250501.1.html In a discussion on the DWARF mailing list, a couple people mentioned that compilers could use the obvious extension of a dynamic DW_AT_data_bit_offset. I've implemented this for LLVM: llvm/llvm-project#141106 In preparation for that landing, this patch implements support for this construct in gdb.
In Ada, a record type can have a non-constant size, and a field can appear at a non-constant bit offset in a record.
To support this, this patch changes DIType to record the size and offset using metadata, rather than plain integers. In addition to a constant offset, both DIVariable and DIExpression are now supported here.
One thing of note in this patch is the choice of how exactly to represent a non-constant bit offset, with the difficulty being that DWARF 5 does not support this. DWARF 3 did have a way to support a non-constant byte offset, combined with a constant bit offset within the byte, but this was deprecated in DWARF 4 and removed from DWARF 5.
This patch takes a simple approach: a DWARF extension allowing the use of an expression with DW_AT_data_bit_offset. There is a corresponding DWARF issue, see https://dwarfstd.org/issues/250501.1.html. The main reason for this approach is that it keeps API simplicity: just a single value is needed, rather than having separate data describing the byte offset and the bit within the byte.