[llvm] [BPF] Handle C++ record elements in BTF and CO-RE (PR #226717)
Maxim Skokov via llvm-commits
llvm-commits at lists.llvm.org
Sat Sep 26 10:05:00 PDT 2026
https://github.com/maxskokov created https://github.com/llvm/llvm-project/pull/226717
Since #155783 (LLVM 22.1), compiling C++ for BPF with `-g` hits `llvm_unreachable()` in BTF emission as soon as a record has a base class, a static data member (DWARF 5) or a method; #205396 moved the crash into the offset sort:
```cpp
struct S { void *p; ~S(); };
long f(S *s) { return (long)s->p; }
```
A C++ record lists these among its debug info elements next to its data members. The CO-RE pass and `visitMapDefType()` walk the same elements and crash too.
**BTF.** A non-virtual base becomes an anonymous member of the base type at its offset, as pahole encodes inheritance; virtual bases, static data members and methods are skipped.
**CO-RE.** Fixing BTF alone would turn the crash into a silent miscompile, so both are fixed together: clang numbers the *fields* of a record for a CO-RE access, while the backend indexed its *elements*, so for `struct S : Base { int x, y; }` the access `&s->y` got the offset of `x`. The index is now mapped to the field, and the access string comes from the same ordered member list that BTF is built from. For an inherited field, only its position within the base is relocated. A record the debug info only declares is reported at the access.
**Testing**
BTF with single and multiple inheritance, an empty base, a method and a static data member, loaded with `BPF_BTF_LOAD` on Linux 6.12:
| compiler | base class in BTF | kernel |
|---|---|---|
| clang 19 | member with `type_id=0` | rejected: "Invalid type_id" |
| 22.1–main (from the source) | — | `llvm_unreachable()`: crash with assertions, UB without |
| this PR | anonymous member of the base type | accepted |
CO-RE:
| access | clang 19 | main | this PR |
|---|---|---|---|
| `struct S : Base { int x, y; }`, `&s->y` | offset 4, `0:1` (that is `x`) | crash | offset 8, `0:2` |
| `struct V : virtual Base { int v; }`, `&v->v`, `-fstandalone-debug` | offset 3, `0:0` | crash | offset 8, `0:1` |
| the same, `V` only declared | segfault | crash | error at the access |
A CO-RE access and field info into a declared-only record are reported as errors instead of crashing. The kernel BPF selftests (999 C programs) compile to byte-identical code, `.BTF` and `.BTF.ext`.
**Known limitations**
- `bpftool btf dump format c` prints an anonymous base member as `struct Base;`, which only keeps the layout with `-fms-extensions` and cannot express an empty base overlapping a field. BTF from pahole has the same shape.
- Classes with a vtable are still rejected by the kernel, as `_vptr$X` is not a valid BTF name; a separate PR escapes such names.
- Records declared with `class` (`DW_TAG_class_type`) still crash BTF since #155783 and get a wrong CO-RE offset; a separate PR handles them.
This fixes a crash that is a regression since 22.1 (#155783); once this lands I'd like to request a backport to 23.x.
Assisted-by: Claude Code (Claude Opus 5.5)
>From 5bd8f90035280c008a648bfe4264e91de7fb9034 Mon Sep 17 00:00:00 2001
From: Maxim Skokov <skokovmaksimevg at gmail.com>
Date: Sat, 26 Sep 2026 14:59:38 +0300
Subject: [PATCH] [BPF] Handle C++ record elements in BTF and CO-RE
A C++ record lists base classes, static data members and methods among
its debug info elements. Since #155783, BTF emission hits
llvm_unreachable() on any record element other than a member or a
variant part, and #205396 moved the crash into the offset sort; the
CO-RE pass and visitMapDefType() crash on them too.
BTF: emit a non-virtual base as an anonymous member of the base type at
its offset, as pahole does, and skip the other elements. Virtual bases
are skipped, as their offset is only known at run time.
CO-RE: fixing BTF alone would turn the crash into a silent miscompile,
since clang numbers the fields of a record while the backend indexed its
elements: for "struct S : Base { int x, y; }", &s->y got the offset of
x. Map the access index to the field and take the access string index
from the same member list BTF is built from. For an inherited field only
its position within the base is relocated; the offset of the base stays
a constant, or is loaded from the vtable for a virtual base. Report an
access into a record the debug info only declares as an error at the
access, and replace a field info call on it with 0.
Assisted-by: Claude Code (Claude Opus 5.5)
---
.../Target/BPF/BPFAbstractMemberAccess.cpp | 58 ++++--
llvm/lib/Target/BPF/BPFCORE.h | 54 ++++++
llvm/lib/Target/BPF/BTFDebug.cpp | 29 ++-
.../CodeGen/BPF/BTF/struct-cxx-elements.ll | 142 +++++++++++++++
.../CodeGen/BPF/CORE/struct-cxx-fields.ll | 169 ++++++++++++++++++
.../CodeGen/BPF/CORE/struct-cxx-fwd-decl.ll | 59 ++++++
6 files changed, 478 insertions(+), 33 deletions(-)
create mode 100644 llvm/test/CodeGen/BPF/BTF/struct-cxx-elements.ll
create mode 100644 llvm/test/CodeGen/BPF/CORE/struct-cxx-fields.ll
create mode 100644 llvm/test/CodeGen/BPF/CORE/struct-cxx-fwd-decl.ll
diff --git a/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp b/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp
index f5410820bfb49..003459f56a1f6 100644
--- a/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp
+++ b/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp
@@ -82,6 +82,7 @@
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/DebugInfo/BTF/BTF.h"
#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
@@ -298,6 +299,13 @@ static const DIType * stripQualifiers(const DIType *Ty) {
return Ty;
}
+/// Return the position of Field among the BTF members of record CTy.
+static uint64_t getBTFMemberIndex(const DICompositeType *CTy,
+ const DINode *Field) {
+ SmallVector<const DINode *, 8> Elements = getBTFRecordElements(CTy);
+ return llvm::find(Elements, Field) - Elements.begin();
+}
+
static uint32_t calcArraySize(const DICompositeType *CTy, uint32_t StartDim) {
DINodeArray Elements = CTy->getElements();
uint32_t DimSize = 1;
@@ -528,7 +536,9 @@ bool BPFAbstractMemberAccess::IsValidAIChain(const MDNode *ParentType,
if (PTyTag == dwarf::DW_TAG_array_type)
Ty = PTy->getBaseType();
else
- Ty = dyn_cast<DIType>(PTy->getElements()[ParentAI]);
+ Ty = dyn_cast_or_null<DIType>(getDIRecordField(PTy, ParentAI));
+ if (!Ty)
+ return false;
return dyn_cast<DICompositeType>(stripQualifiers(Ty)) == CTy;
}
@@ -685,7 +695,7 @@ uint32_t BPFAbstractMemberAccess::GetFieldInfo(uint32_t InfoKind,
PatchImm += AccessIndex * calcArraySize(CTy, 1) *
(EltTy->getSizeInBits() >> 3);
} else if (Tag == dwarf::DW_TAG_structure_type) {
- auto *MemberTy = cast<DIDerivedType>(CTy->getElements()[AccessIndex]);
+ auto *MemberTy = cast<DIDerivedType>(getDIRecordField(CTy, AccessIndex));
if (!MemberTy->isBitField()) {
PatchImm += MemberTy->getOffsetInBits() >> 3;
} else {
@@ -703,7 +713,7 @@ uint32_t BPFAbstractMemberAccess::GetFieldInfo(uint32_t InfoKind,
auto *EltTy = stripQualifiers(CTy->getBaseType());
return calcArraySize(CTy, 1) * (EltTy->getSizeInBits() >> 3);
} else {
- auto *MemberTy = cast<DIDerivedType>(CTy->getElements()[AccessIndex]);
+ auto *MemberTy = cast<DIDerivedType>(getDIRecordField(CTy, AccessIndex));
uint32_t SizeInBits = MemberTy->getSizeInBits();
if (!MemberTy->isBitField())
return SizeInBits >> 3;
@@ -726,7 +736,7 @@ uint32_t BPFAbstractMemberAccess::GetFieldInfo(uint32_t InfoKind,
report_fatal_error("Invalid array expression for llvm.bpf.preserve.field.info");
BaseTy = stripQualifiers(CTy->getBaseType());
} else {
- auto *MemberTy = cast<DIDerivedType>(CTy->getElements()[AccessIndex]);
+ auto *MemberTy = cast<DIDerivedType>(getDIRecordField(CTy, AccessIndex));
BaseTy = stripQualifiers(MemberTy->getBaseType());
}
@@ -758,7 +768,7 @@ uint32_t BPFAbstractMemberAccess::GetFieldInfo(uint32_t InfoKind,
auto *EltTy = stripQualifiers(CTy->getBaseType());
SizeInBits = calcArraySize(CTy, 1) * EltTy->getSizeInBits();
} else {
- MemberTy = cast<DIDerivedType>(CTy->getElements()[AccessIndex]);
+ MemberTy = cast<DIDerivedType>(getDIRecordField(CTy, AccessIndex));
SizeInBits = MemberTy->getSizeInBits();
IsBitField = MemberTy->isBitField();
}
@@ -789,7 +799,7 @@ uint32_t BPFAbstractMemberAccess::GetFieldInfo(uint32_t InfoKind,
auto *EltTy = stripQualifiers(CTy->getBaseType());
SizeInBits = calcArraySize(CTy, 1) * EltTy->getSizeInBits();
} else {
- MemberTy = cast<DIDerivedType>(CTy->getElements()[AccessIndex]);
+ MemberTy = cast<DIDerivedType>(getDIRecordField(CTy, AccessIndex));
SizeInBits = MemberTy->getSizeInBits();
IsBitField = MemberTy->isBitField();
}
@@ -965,19 +975,23 @@ Value *BPFAbstractMemberAccess::computeBaseAndAccessKey(CallInst *Call,
// At this stage, it cannot be pointer type.
auto *CTy = cast<DICompositeType>(stripQualifiers(cast<DIType>(MDN)));
+ // For a record, use the position of the field among its BTF members, and
+ // use an array index as is.
uint64_t BTFIndex = AccessIndex;
- if (CTy->getTag() == dwarf::DW_TAG_structure_type) {
- DINodeArray Elements = CTy->getElements();
- uint64_t Offset = getBTFRecordElementOffset(Elements[AccessIndex]);
- // Find this element's position in the stable offset order without
- // sorting the whole record for every CO-RE access.
- BTFIndex = 0;
- for (unsigned I = 0; I < Elements.size(); ++I) {
- uint64_t ElementOffset = getBTFRecordElementOffset(Elements[I]);
- if (ElementOffset < Offset ||
- (ElementOffset == Offset && I < AccessIndex))
- ++BTFIndex;
+ if (CTy->getTag() == dwarf::DW_TAG_structure_type ||
+ CTy->getTag() == dwarf::DW_TAG_union_type) {
+ const DINode *Field = getDIRecordField(CTy, AccessIndex);
+ if (!Field) {
+ Call->getContext().diagnose(DiagnosticInfoUnsupported(
+ *Call->getFunction(),
+ "CO-RE access to a field of '" + CTy->getName() +
+ "', which the debug info does not describe; the type may only "
+ "be declared there (e.g. clang's -fstandalone-debug emits it "
+ "in full)",
+ Call->getDebugLoc()));
+ return nullptr;
}
+ BTFIndex = getBTFMemberIndex(CTy, Field);
}
AccessKey += ":" + std::to_string(BTFIndex);
@@ -1074,8 +1088,16 @@ bool BPFAbstractMemberAccess::transformGEPChain(CallInst *Call,
TypeMeta = computeAccessKey(Call, CInfo, AccessKey, IsInt32Ret);
} else {
Base = computeBaseAndAccessKey(Call, CInfo, AccessKey, TypeMeta);
- if (!Base)
+ if (!Base) {
+ // For a field info call, computeBaseAndAccessKey() only gives up after
+ // reporting an error, so the value of the call does not matter.
+ if (IsInt32Ret) {
+ Call->replaceAllUsesWith(ConstantInt::get(Call->getType(), 0));
+ Call->eraseFromParent();
+ return true;
+ }
return false;
+ }
}
BasicBlock *BB = Call->getParent();
diff --git a/llvm/lib/Target/BPF/BPFCORE.h b/llvm/lib/Target/BPF/BPFCORE.h
index 5aa7695c0ecad..1a4d65b98e70b 100644
--- a/llvm/lib/Target/BPF/BPFCORE.h
+++ b/llvm/lib/Target/BPF/BPFCORE.h
@@ -9,6 +9,8 @@
#ifndef LLVM_LIB_TARGET_BPF_BPFCORE_H
#define LLVM_LIB_TARGET_BPF_BPFCORE_H
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/IR/DebugInfoMetadata.h"
@@ -21,10 +23,47 @@ class BasicBlock;
class Instruction;
class Module;
+/// Whether a record element becomes a BTF member: data members, variant parts
+/// and non-virtual C++ bases, the latter as anonymous members. Virtual bases
+/// have no fixed offset.
+inline bool isBTFRecordElement(const DINode *Element) {
+ switch (Element->getTag()) {
+ case dwarf::DW_TAG_member:
+ return !cast<DIDerivedType>(Element)->isStaticMember();
+ case dwarf::DW_TAG_inheritance:
+ return !cast<DIDerivedType>(Element)->isVirtual();
+ case dwarf::DW_TAG_variant_part:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/// Whether a record element is a field that a CO-RE access index counts.
+/// Clang does not count bases or its vtable pointer member ("_vptr$<class>").
+inline bool isDIRecordField(const DINode *Element) {
+ if (Element->getTag() == dwarf::DW_TAG_inheritance ||
+ !isBTFRecordElement(Element))
+ return false;
+ const auto *DTy = dyn_cast<DIDerivedType>(Element);
+ return !DTy || !(DTy->isArtificial() && DTy->getName().starts_with("_vptr$"));
+}
+
+/// Return the field a CO-RE access index refers to, or null if the debug info
+/// does not describe the record's fields.
+inline DINode *getDIRecordField(const DICompositeType *CTy,
+ uint64_t AccessIndex) {
+ for (DINode *Element : CTy->getElements())
+ if (isDIRecordField(Element) && AccessIndex-- == 0)
+ return Element;
+ return nullptr;
+}
+
/// Return the bit offset used to order an element of a BTF structure record.
inline uint64_t getBTFRecordElementOffset(const DINode *Element) {
switch (Element->getTag()) {
case dwarf::DW_TAG_member:
+ case dwarf::DW_TAG_inheritance:
return cast<DIDerivedType>(Element)->getOffsetInBits();
case dwarf::DW_TAG_variant_part:
return cast<DICompositeType>(Element)->getOffsetInBits();
@@ -33,6 +72,21 @@ inline uint64_t getBTFRecordElementOffset(const DINode *Element) {
}
}
+/// Return the BTF members of a record in BTF order: structure members by
+/// offset (BTF requires nondecreasing offsets), stable for equal offsets.
+inline SmallVector<const DINode *, 8>
+getBTFRecordElements(const DICompositeType *CTy) {
+ SmallVector<const DINode *, 8> Elements;
+ for (const DINode *Element : CTy->getElements())
+ if (isBTFRecordElement(Element))
+ Elements.push_back(Element);
+ if (CTy->getTag() == dwarf::DW_TAG_structure_type)
+ llvm::stable_sort(Elements, [](const DINode *LHS, const DINode *RHS) {
+ return getBTFRecordElementOffset(LHS) < getBTFRecordElementOffset(RHS);
+ });
+ return Elements;
+}
+
class BPFCoreSharedInfo {
public:
enum BTFTypeIdFlag : uint32_t {
diff --git a/llvm/lib/Target/BPF/BTFDebug.cpp b/llvm/lib/Target/BPF/BTFDebug.cpp
index 7ddeedb838e87..303196db64496 100644
--- a/llvm/lib/Target/BPF/BTFDebug.cpp
+++ b/llvm/lib/Target/BPF/BTFDebug.cpp
@@ -566,7 +566,8 @@ void BTFTypeStruct::completeType(BTFDebug &BDebug) {
struct BTF::BTFMember BTFMember;
switch (Element->getTag()) {
- case dwarf::DW_TAG_member: {
+ case dwarf::DW_TAG_member:
+ case dwarf::DW_TAG_inheritance: {
const auto *DDTy = cast<DIDerivedType>(Element);
BTFMember.NameOff = BDebug.addString(DDTy->getName());
@@ -976,14 +977,7 @@ int BTFDebug::genBTFTypeTags(const DIDerivedType *DTy, int BaseTypeId) {
/// Handle structure/union types.
void BTFDebug::visitStructType(const DICompositeType *CTy, bool IsStruct,
uint32_t &TypeId) {
- DINodeArray DIElements = CTy->getElements();
- SmallVector<const DINode *, 8> Elements(DIElements.begin(), DIElements.end());
- // Structure elements must have nondecreasing offsets in BTF. Preserve DI
- // order for union and variant-part records.
- if (CTy->getTag() == dwarf::DW_TAG_structure_type)
- llvm::stable_sort(Elements, [](const DINode *LHS, const DINode *RHS) {
- return getBTFRecordElementOffset(LHS) < getBTFRecordElementOffset(RHS);
- });
+ SmallVector<const DINode *, 8> Elements = getBTFRecordElements(CTy);
uint32_t VLen = Elements.size();
// Variant parts might have a discriminator. LLVM DI doesn't consider it as
// an element and instead keeps it as a separate reference. But we represent
@@ -1022,7 +1016,8 @@ void BTFDebug::visitStructType(const DICompositeType *CTy, bool IsStruct,
int FieldNo = 0;
for (const auto *Element : Elements) {
switch (Element->getTag()) {
- case dwarf::DW_TAG_member: {
+ case dwarf::DW_TAG_member:
+ case dwarf::DW_TAG_inheritance: {
const auto Elem = cast<DIDerivedType>(Element);
visitTypeEntry(Elem);
processDeclAnnotations(Elem->getAnnotations(), TypeId, FieldNo);
@@ -1205,14 +1200,14 @@ void BTFDebug::visitDerivedType(const DIDerivedType *DTy, uint32_t &TypeId,
Tag == dwarf::DW_TAG_restrict_type) {
auto TypeEntry = std::make_unique<BTFTypeDerived>(DTy, Tag, false);
TypeId = addType(std::move(TypeEntry), DTy);
- } else if (Tag != dwarf::DW_TAG_member) {
+ } else if (Tag != dwarf::DW_TAG_member && Tag != dwarf::DW_TAG_inheritance) {
return;
}
- // Visit base type of pointer, typedef, const, volatile, restrict or
- // struct/union member.
+ // Visit base type of pointer, typedef, const, volatile, restrict, or
+ // struct/union member or base class.
uint32_t TempTypeId = 0;
- if (Tag == dwarf::DW_TAG_member)
+ if (Tag == dwarf::DW_TAG_member || Tag == dwarf::DW_TAG_inheritance)
visitTypeEntry(DTy->getBaseType(), TempTypeId, true, false);
else
visitTypeEntry(DTy->getBaseType(), TempTypeId, CheckPointer, SeenPointer);
@@ -1328,7 +1323,11 @@ void BTFDebug::visitMapDefType(const DIType *Ty, uint32_t &TypeId) {
const auto *CTy = cast<DICompositeType>(Ty);
const DINodeArray Elements = CTy->getElements();
for (const auto *Element : Elements) {
- const auto *MemberType = cast<DIDerivedType>(Element);
+ // Static data members, methods and other elements of a C++ record are
+ // not part of the map definition.
+ const auto *MemberType = dyn_cast<DIDerivedType>(Element);
+ if (!MemberType || !isBTFRecordElement(Element))
+ continue;
const DIType *MemberBaseType = MemberType->getBaseType();
// If the member is a composite type, that may indicate the currently
// visited composite type is a wrapper, and the member represents the
diff --git a/llvm/test/CodeGen/BPF/BTF/struct-cxx-elements.ll b/llvm/test/CodeGen/BPF/BTF/struct-cxx-elements.ll
new file mode 100644
index 0000000000000..9f67dd3575ccf
--- /dev/null
+++ b/llvm/test/CodeGen/BPF/BTF/struct-cxx-elements.ll
@@ -0,0 +1,142 @@
+; RUN: llc -mtriple=bpfel -filetype=obj -o %t1 %s
+; RUN: llvm-objcopy --dump-section='.BTF'=%t2 %t1
+; RUN: %python %p/print_btf.py %t2 | FileCheck %s
+; RUN: llc -mtriple=bpfeb -filetype=obj -o %t1 %s
+; RUN: llvm-objcopy --dump-section='.BTF'=%t2 %t1
+; RUN: %python %p/print_btf.py %t2 | FileCheck %s
+
+; C++ records list base classes, static data members and methods among their
+; elements. A non-virtual base becomes an anonymous member of the base type at
+; its offset, so the layout stays complete. Static data members and methods
+; are dropped, and so is a virtual base: its offset is only known at run time,
+; and the DI offset of a virtual base is the position of its offset in the
+; vtable. The declaration tag index counts the emitted members. An empty base
+; shares the offset of the first field and comes first, a base before
+; bit-fields gets bitfield_size=0, and map definitions skip the same elements.
+;
+; struct Base { int b; };
+; struct Base2 { int c; };
+; struct S : Base, Base2 {
+; static int s1; // DW_TAG_variable
+; int x;
+; void m();
+; static int s2; // DW_TAG_member with DIFlagStaticMember
+; int y __attribute__((btf_decl_tag("y_tag")));
+; };
+; struct V : virtual Base { int v; }; // vptr at 0, v at 8, Base at 12
+; struct Empty {};
+; struct E : Empty { int e; }; // Empty and e both at offset 0
+; struct BF : Base { int x : 3; int y : 5; };
+; struct Map { int *key; void lookup(); static int instances; int *value; };
+; S value;
+; V vvalue;
+; E evalue;
+; BF bfvalue;
+; Map map __attribute__((section(".maps")));
+;
+; The vptr member of V ('_vptr$V') is left out of the debug info below on
+; purpose: its name is not valid in BTF, which is dealt with separately.
+
+; CHECK: [1] PTR '(anon)' type_id=2
+; CHECK-NEXT: [2] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
+; CHECK-NEXT: [3] STRUCT 'Map' size=16 vlen=2
+; CHECK-NEXT: 'key' type_id=1 bits_offset=0
+; CHECK-NEXT: 'value' type_id=1 bits_offset=64
+; CHECK-NEXT: [4] VAR 'map' type_id=3, linkage=global
+; CHECK-NEXT: [5] STRUCT 'S' size=16 vlen=4
+; CHECK-NEXT: '(anon)' type_id=6 bits_offset=0
+; CHECK-NEXT: '(anon)' type_id=7 bits_offset=32
+; CHECK-NEXT: 'x' type_id=2 bits_offset=64
+; CHECK-NEXT: 'y' type_id=2 bits_offset=96
+; CHECK-NEXT: [6] STRUCT 'Base' size=4 vlen=1
+; CHECK-NEXT: 'b' type_id=2 bits_offset=0
+; CHECK-NEXT: [7] STRUCT 'Base2' size=4 vlen=1
+; CHECK-NEXT: 'c' type_id=2 bits_offset=0
+; CHECK-NEXT: [8] DECL_TAG 'y_tag' type_id=5 component_idx=3
+; CHECK-NEXT: [9] VAR 'value' type_id=5, linkage=global
+; CHECK-NEXT: [10] STRUCT 'V' size=16 vlen=1
+; CHECK-NEXT: 'v' type_id=2 bits_offset=64
+; CHECK-NEXT: [11] VAR 'vvalue' type_id=10, linkage=global
+; CHECK-NEXT: [12] STRUCT 'E' size=4 vlen=2
+; CHECK-NEXT: '(anon)' type_id=13 bits_offset=0
+; CHECK-NEXT: 'e' type_id=2 bits_offset=0
+; CHECK-NEXT: [13] STRUCT 'Empty' size=1 vlen=0
+; CHECK-NEXT: [14] VAR 'evalue' type_id=12, linkage=global
+; CHECK-NEXT: [15] STRUCT 'BF' size=8 vlen=3
+; CHECK-NEXT: '(anon)' type_id=6 bits_offset=0 bitfield_size=0
+; CHECK-NEXT: 'x' type_id=2 bits_offset=32 bitfield_size=3
+; CHECK-NEXT: 'y' type_id=2 bits_offset=35 bitfield_size=5
+; CHECK-NEXT: [16] VAR 'bfvalue' type_id=15, linkage=global
+
+%struct.S = type { i32, i32, i32, i32 }
+%struct.V = type { i32, i32, i32, i32 }
+
+ at value = global %struct.S zeroinitializer, align 4, !dbg !0
+ at vvalue = global %struct.V zeroinitializer, align 4, !dbg !40
+ at evalue = global i32 0, align 4, !dbg !100
+ at bfvalue = global i64 0, align 4, !dbg !110
+ at map = global [2 x ptr] zeroinitializer, section ".maps", align 8, !dbg !120
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!30, !31}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "value", scope: !2, file: !3, line: 12, type: !5, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !4)
+!3 = !DIFile(filename: "test.cpp", directory: "/")
+!4 = !{!0, !40, !100, !110, !120}
+!5 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "S", file: !3, line: 3, size: 128, elements: !6)
+!6 = !{!7, !23, !11, !12, !13, !17, !18}
+!7 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !5, baseType: !8, extraData: i32 0)
+!8 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Base", file: !3, line: 1, size: 32, elements: !9)
+!9 = !{!10}
+!10 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !8, file: !3, line: 1, baseType: !20, size: 32)
+!11 = !DIDerivedType(tag: DW_TAG_variable, name: "s1", scope: !5, file: !3, line: 4, baseType: !20, flags: DIFlagStaticMember)
+!12 = !DIDerivedType(tag: DW_TAG_member, name: "x", scope: !5, file: !3, line: 5, baseType: !20, size: 32, offset: 64)
+!13 = !DISubprogram(name: "m", linkageName: "_ZN1S1mEv", scope: !5, file: !3, line: 6, type: !14, scopeLine: 6, flags: DIFlagPrototyped, spFlags: 0)
+!14 = !DISubroutineType(types: !15)
+!15 = !{null, !16}
+!16 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !5, size: 64, flags: DIFlagArtificial | DIFlagObjectPointer)
+!17 = !DIDerivedType(tag: DW_TAG_member, name: "s2", scope: !5, file: !3, line: 7, baseType: !20, flags: DIFlagStaticMember)
+!18 = !DIDerivedType(tag: DW_TAG_member, name: "y", scope: !5, file: !3, line: 8, baseType: !20, size: 32, offset: 96, annotations: !21)
+!20 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!21 = !{!22}
+!22 = !{!"btf_decl_tag", !"y_tag"}
+!23 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !5, baseType: !24, offset: 32, extraData: i32 0)
+!24 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Base2", file: !3, line: 2, size: 32, elements: !25)
+!25 = !{!26}
+!26 = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: !24, file: !3, line: 2, baseType: !20, size: 32)
+!30 = !{i32 2, !"Debug Info Version", i32 3}
+!31 = !{i32 2, !"Dwarf Version", i32 5}
+!40 = !DIGlobalVariableExpression(var: !41, expr: !DIExpression())
+!41 = distinct !DIGlobalVariable(name: "vvalue", scope: !2, file: !3, line: 13, type: !42, isLocal: false, isDefinition: true)
+!42 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "V", file: !3, line: 10, size: 128, elements: !43)
+!43 = !{!44, !45}
+!44 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !42, baseType: !8, offset: 24, flags: DIFlagVirtual, extraData: i32 0)
+!45 = !DIDerivedType(tag: DW_TAG_member, name: "v", scope: !42, file: !3, line: 10, baseType: !20, size: 32, offset: 64)
+!100 = !DIGlobalVariableExpression(var: !101, expr: !DIExpression())
+!101 = distinct !DIGlobalVariable(name: "evalue", scope: !2, file: !3, line: 16, type: !102, isLocal: false, isDefinition: true)
+!102 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "E", file: !3, line: 15, size: 32, elements: !103)
+!103 = !{!104, !106}
+!104 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !102, baseType: !105, extraData: i32 0)
+!105 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Empty", file: !3, line: 14, size: 8, elements: !107)
+!106 = !DIDerivedType(tag: DW_TAG_member, name: "e", scope: !102, file: !3, line: 15, baseType: !20, size: 32)
+!107 = !{}
+!110 = !DIGlobalVariableExpression(var: !111, expr: !DIExpression())
+!111 = distinct !DIGlobalVariable(name: "bfvalue", scope: !2, file: !3, line: 18, type: !112, isLocal: false, isDefinition: true)
+!112 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "BF", file: !3, line: 17, size: 64, elements: !113)
+!113 = !{!114, !115, !116}
+!114 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !112, baseType: !8, extraData: i32 0)
+!115 = !DIDerivedType(tag: DW_TAG_member, name: "x", scope: !112, file: !3, line: 17, baseType: !20, size: 3, offset: 32, flags: DIFlagBitField, extraData: i64 32)
+!116 = !DIDerivedType(tag: DW_TAG_member, name: "y", scope: !112, file: !3, line: 17, baseType: !20, size: 5, offset: 35, flags: DIFlagBitField, extraData: i64 32)
+!120 = !DIGlobalVariableExpression(var: !121, expr: !DIExpression())
+!121 = distinct !DIGlobalVariable(name: "map", scope: !2, file: !3, line: 20, type: !122, isLocal: false, isDefinition: true)
+!122 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Map", file: !3, line: 19, size: 128, elements: !123)
+!123 = !{!124, !125, !126, !127}
+!124 = !DIDerivedType(tag: DW_TAG_member, name: "key", scope: !122, file: !3, line: 19, baseType: !128, size: 64)
+!125 = !DISubprogram(name: "lookup", scope: !122, file: !3, line: 19, type: !129, spFlags: 0)
+!126 = !DIDerivedType(tag: DW_TAG_variable, name: "instances", scope: !122, file: !3, line: 19, baseType: !20, flags: DIFlagStaticMember)
+!127 = !DIDerivedType(tag: DW_TAG_member, name: "value", scope: !122, file: !3, line: 19, baseType: !128, size: 64, offset: 64)
+!128 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !20, size: 64)
+!129 = !DISubroutineType(types: !130)
+!130 = !{null}
diff --git a/llvm/test/CodeGen/BPF/CORE/struct-cxx-fields.ll b/llvm/test/CodeGen/BPF/CORE/struct-cxx-fields.ll
new file mode 100644
index 0000000000000..de26896113b10
--- /dev/null
+++ b/llvm/test/CodeGen/BPF/CORE/struct-cxx-fields.ll
@@ -0,0 +1,169 @@
+; RUN: opt -O2 %s -S | FileCheck %s
+; RUN: opt -O2 %s | llc -mtriple=bpfel -filetype=asm -o - | FileCheck %s --check-prefix=BTF
+
+; Clang numbers the fields of a record for a CO-RE access, while the debug
+; info of a C++ record also lists base classes, static data members, methods
+; and the vtable pointer among its elements. The access index is mapped to the
+; field it counts, and the access string index is the position of that field
+; among the BTF members, where non-virtual bases come first as anonymous
+; members and the vtable pointer is a member too. The name of each relocation
+; global encodes <kind>:<patched value>$<access string>.
+;
+; struct Base { int b; };
+; struct Base2 { int c; };
+; struct S : Base, Base2 {
+; static int s1; int x; void m(); static int s2; int y;
+; };
+; struct V : virtual Base { int v; }; // vptr at 0, v at 8, Base at 12
+; struct Empty {};
+; struct E : Empty { int e; }; // Empty and e both at offset 0
+; union U { int a; static int s; long b; void m(); };
+; struct BF : Base {
+; void m(); unsigned x : 3; int y : 5; static int s; int z;
+; };
+;
+; &s->x, &s->y, &v->v, &e->e, &u->b, and field info of bf->x, bf->y, bf->z
+
+; CHECK-DAG: @"llvm.S:0:8$0:2" =
+; CHECK-DAG: @"llvm.S:0:12$0:3" =
+; CHECK-DAG: @"llvm.V:0:8$0:1" =
+; CHECK-DAG: @"llvm.E:0:0$0:1" =
+; CHECK-DAG: @"llvm.U:0:0$0:1" =
+; CHECK-DAG: @"llvm.BF:1:4$0:2" =
+; CHECK-DAG: @"llvm.BF:3:1$0:2" =
+; CHECK-DAG: @"llvm.BF:3:0$0:1" =
+; CHECK-DAG: @"llvm.BF:4:56$0:2" =
+; CHECK-DAG: @"llvm.BF:0:8$0:3" =
+
+; The relocation for &s->y names member 3 of the BTF struct S, which is y.
+; S is the struct whose name is at string offset 1, and 83 is 'S'.
+; BTF: .long 1 # BTF_KIND_STRUCT(id = [[S_ID:[0-9]+]])
+; BTF: .byte 83 # string offset=1
+; BTF: .ascii "0:3" # string offset=[[Y_ACCESS:[0-9]+]]
+; BTF: .section .BTF.ext
+; BTF: .long [[S_ID]]{{\n}}{{[[:space:]]+}}.long [[Y_ACCESS]]{{\n}}{{[[:space:]]+}}.long 0
+
+target triple = "bpf"
+
+%struct.Base = type { i32 }
+%struct.Base2 = type { i32 }
+%struct.S = type { %struct.Base, %struct.Base2, i32, i32 }
+%struct.V = type <{ ptr, i32, %struct.Base, [4 x i8] }>
+%struct.E = type { i32 }
+%struct.BF = type { %struct.Base, i8, i32 }
+
+define ptr @s_x(ptr %p) {
+ %r = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.S) %p, i32 2, i32 0), !llvm.preserve.access.index !10
+ ret ptr %r
+}
+
+define ptr @s_y(ptr %p) !dbg !61 {
+ %r = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.S) %p, i32 3, i32 1), !dbg !64, !llvm.preserve.access.index !10
+ ret ptr %r
+}
+
+define ptr @v_v(ptr %p) {
+ %r = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.V) %p, i32 1, i32 0), !llvm.preserve.access.index !20
+ ret ptr %r
+}
+
+define ptr @e_e(ptr %p) {
+ %r = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.E) %p, i32 0, i32 0), !llvm.preserve.access.index !30
+ ret ptr %r
+}
+
+define ptr @u_b(ptr %p) {
+ %r = call ptr @llvm.preserve.union.access.index.p0.p0(ptr %p, i32 1), !llvm.preserve.access.index !40
+ ret ptr %r
+}
+
+define i32 @bf_y_size(ptr %p) {
+ %f = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.BF) %p, i32 1, i32 1), !llvm.preserve.access.index !50
+ %r = call i32 @llvm.bpf.preserve.field.info.p0(ptr %f, i64 1)
+ ret i32 %r
+}
+
+define i32 @bf_y_signed(ptr %p) {
+ %f = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.BF) %p, i32 1, i32 1), !llvm.preserve.access.index !50
+ %r = call i32 @llvm.bpf.preserve.field.info.p0(ptr %f, i64 3)
+ ret i32 %r
+}
+
+define i32 @bf_x_signed(ptr %p) {
+ %f = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.BF) %p, i32 1, i32 0), !llvm.preserve.access.index !50
+ %r = call i32 @llvm.bpf.preserve.field.info.p0(ptr %f, i64 3)
+ ret i32 %r
+}
+
+define i32 @bf_y_lshift(ptr %p) {
+ %f = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.BF) %p, i32 1, i32 1), !llvm.preserve.access.index !50
+ %r = call i32 @llvm.bpf.preserve.field.info.p0(ptr %f, i64 4)
+ ret i32 %r
+}
+
+define i32 @bf_z_offset(ptr %p) {
+ %f = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.BF) %p, i32 2, i32 2), !llvm.preserve.access.index !50
+ %r = call i32 @llvm.bpf.preserve.field.info.p0(ptr %f, i64 0)
+ ret i32 %r
+}
+
+declare ptr @llvm.preserve.struct.access.index.p0.p0(ptr, i32 immarg, i32 immarg)
+declare ptr @llvm.preserve.union.access.index.p0.p0(ptr, i32 immarg)
+declare i32 @llvm.bpf.preserve.field.info.p0(ptr, i64 immarg)
+
+!llvm.dbg.cu = !{!60}
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 2, !"Debug Info Version", i32 3}
+!1 = !DIFile(filename: "test.cpp", directory: "/")
+!2 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!3 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned)
+!4 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+!5 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Base", file: !1, size: 32, elements: !6)
+!6 = !{!7}
+!7 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !5, file: !1, baseType: !2, size: 32)
+!8 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Base2", file: !1, size: 32, elements: !9)
+!9 = !{!17}
+!10 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "S", file: !1, size: 128, elements: !11)
+!11 = !{!12, !13, !14, !15, !16, !18, !19}
+!12 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !10, baseType: !5, extraData: i32 0)
+!13 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !10, baseType: !8, offset: 32, extraData: i32 0)
+!14 = !DIDerivedType(tag: DW_TAG_variable, name: "s1", scope: !10, file: !1, baseType: !2, flags: DIFlagStaticMember)
+!15 = !DIDerivedType(tag: DW_TAG_member, name: "x", scope: !10, file: !1, baseType: !2, size: 32, offset: 64)
+!16 = !DIDerivedType(tag: DW_TAG_variable, name: "s2", scope: !10, file: !1, baseType: !2, flags: DIFlagStaticMember)
+!17 = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: !8, file: !1, baseType: !2, size: 32)
+!18 = !DIDerivedType(tag: DW_TAG_member, name: "y", scope: !10, file: !1, baseType: !2, size: 32, offset: 96)
+!19 = !DISubprogram(name: "m", scope: !10, file: !1, type: !28, spFlags: 0)
+!20 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "V", file: !1, size: 192, elements: !21)
+!21 = !{!22, !23, !25}
+!22 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !20, baseType: !5, offset: 24, flags: DIFlagVirtual, extraData: i32 0)
+!23 = !DIDerivedType(tag: DW_TAG_member, name: "_vptr$V", scope: !1, file: !1, baseType: !24, size: 64, flags: DIFlagArtificial)
+!24 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+!25 = !DIDerivedType(tag: DW_TAG_member, name: "v", scope: !20, file: !1, baseType: !2, size: 32, offset: 64)
+!26 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Empty", file: !1, size: 8, elements: !27)
+!27 = !{}
+!28 = !DISubroutineType(types: !29)
+!29 = !{null}
+!30 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "E", file: !1, size: 32, elements: !31)
+!31 = !{!32, !33}
+!32 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !30, baseType: !26, extraData: i32 0)
+!33 = !DIDerivedType(tag: DW_TAG_member, name: "e", scope: !30, file: !1, baseType: !2, size: 32)
+!40 = distinct !DICompositeType(tag: DW_TAG_union_type, name: "U", file: !1, size: 64, elements: !41)
+!41 = !{!42, !43, !44, !45}
+!42 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !40, file: !1, baseType: !2, size: 32)
+!43 = !DIDerivedType(tag: DW_TAG_variable, name: "s", scope: !40, file: !1, baseType: !2, flags: DIFlagStaticMember)
+!44 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !40, file: !1, baseType: !4, size: 64)
+!45 = !DISubprogram(name: "m", scope: !40, file: !1, type: !28, spFlags: 0)
+!50 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "BF", file: !1, size: 96, elements: !51)
+!51 = !{!52, !53, !54, !55, !56, !57}
+!52 = !DIDerivedType(tag: DW_TAG_inheritance, scope: !50, baseType: !5, extraData: i32 0)
+!53 = !DISubprogram(name: "m", scope: !50, file: !1, type: !28, spFlags: 0)
+!54 = !DIDerivedType(tag: DW_TAG_member, name: "x", scope: !50, file: !1, baseType: !3, size: 3, offset: 32, flags: DIFlagBitField, extraData: i64 32)
+!55 = !DIDerivedType(tag: DW_TAG_member, name: "y", scope: !50, file: !1, baseType: !2, size: 5, offset: 35, flags: DIFlagBitField, extraData: i64 32)
+!56 = !DIDerivedType(tag: DW_TAG_variable, name: "s", scope: !50, file: !1, baseType: !2, flags: DIFlagStaticMember)
+!57 = !DIDerivedType(tag: DW_TAG_member, name: "z", scope: !50, file: !1, baseType: !2, size: 32, offset: 64)
+!60 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!61 = distinct !DISubprogram(name: "s_y", scope: !1, file: !1, type: !62, spFlags: DISPFlagDefinition, unit: !60)
+!62 = !DISubroutineType(types: !63)
+!63 = !{null}
+!64 = !DILocation(line: 1, scope: !61)
diff --git a/llvm/test/CodeGen/BPF/CORE/struct-cxx-fwd-decl.ll b/llvm/test/CodeGen/BPF/CORE/struct-cxx-fwd-decl.ll
new file mode 100644
index 0000000000000..b63b2495f1c3f
--- /dev/null
+++ b/llvm/test/CodeGen/BPF/CORE/struct-cxx-fwd-decl.ll
@@ -0,0 +1,59 @@
+; RUN: not opt -O2 %s -o /dev/null 2>&1 | FileCheck %s
+
+; Clang often only declares a C++ record in the debug info of a translation
+; unit, e.g. a class whose vtable or constructor is emitted elsewhere. A CO-RE
+; access to one of its fields cannot be described and is reported as an error
+; at the access instead of crashing, and a field info call on it is dropped.
+;
+; struct Base { int pad; int b; };
+; struct V : virtual Base { int v; };
+; unsigned long get_v(V *v) {
+; return (unsigned long)__builtin_preserve_access_index(&v->v);
+; }
+; unsigned info_v(V *v) { return __builtin_preserve_field_info(v->v, 0); }
+
+; CHECK: error: test.cpp:3:1: in function get_v i64 (ptr): CO-RE access to a field of 'V', which the debug info does not describe; the type may only be declared there (e.g. clang's -fstandalone-debug emits it in full)
+; CHECK: error: test.cpp:4:1: in function info_v i32 (ptr): CO-RE access to a field of 'V', which the debug info does not describe
+
+target triple = "bpf"
+
+%struct.V = type <{ ptr, i32, %struct.Base, [4 x i8] }>
+%struct.Base = type { i32, i32 }
+
+define dso_local i64 @get_v(ptr %v) !dbg !10 {
+entry:
+ %0 = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.V) %v, i32 1, i32 0), !dbg !14, !llvm.preserve.access.index !15
+ %1 = ptrtoint ptr %0 to i64, !dbg !14
+ ret i64 %1, !dbg !14
+}
+
+define dso_local i32 @info_v(ptr %v) !dbg !20 {
+entry:
+ %0 = call ptr @llvm.preserve.struct.access.index.p0.p0(ptr elementtype(%struct.V) %v, i32 1, i32 0), !dbg !21, !llvm.preserve.access.index !15
+ %1 = call i32 @llvm.bpf.preserve.field.info.p0(ptr %0, i64 0), !dbg !21
+ ret i32 %1, !dbg !21
+}
+
+declare ptr @llvm.preserve.struct.access.index.p0.p0(ptr, i32 immarg, i32 immarg)
+declare i32 @llvm.bpf.preserve.field.info.p0(ptr, i64 immarg)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
+!1 = !DIFile(filename: "test.cpp", directory: "/")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 5}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!10 = distinct !DISubprogram(name: "get_v", scope: !1, file: !1, line: 3, type: !11, scopeLine: 3, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!11 = !DISubroutineType(types: !12)
+!12 = !{!13, !16}
+!13 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned)
+!14 = !DILocation(line: 3, column: 1, scope: !10)
+!15 = !DICompositeType(tag: DW_TAG_structure_type, name: "V", file: !1, line: 2, flags: DIFlagFwdDecl | DIFlagNonTrivial, identifier: "_ZTS1V")
+!16 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !15, size: 64)
+!20 = distinct !DISubprogram(name: "info_v", scope: !1, file: !1, line: 4, type: !22, scopeLine: 4, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!21 = !DILocation(line: 4, column: 1, scope: !20)
+!22 = !DISubroutineType(types: !23)
+!23 = !{!24, !16}
+!24 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned)
More information about the llvm-commits
mailing list