Skip to content

Commit cc7d1d3

Browse files
authored
[CIR] Derived-to-base conversions (#937)
Implement derived-to-base address conversions for non-virtual base classes. The code gen for this situation was only implemented when the offset was zero, and it simply created a `cir.base_class_addr` op for which no lowering or other transformation existed. Conversion to a virtual base class is not yet implemented. Two new fields are added to the `cir.base_class_addr` operation: the byte offset of the necessary adjustment, and a boolean flag indicating whether the source operand may be null. The offset is easy to compute in the front end while the entire path of intermediate classes is still available. It would be difficult for the back end to recompute the offset. So it is best to store it in the operation. The null-pointer check is best done late in the lowering process. But whether or not the null-pointer check is needed is only known by the front end; the back end can't figure that out. So that flag needs to be stored in the operation. `CIRGenFunction::getAddressOfBaseClass` was largely rewritten. The code path no longer matches the equivalent function in the LLVM IR code gen, because the generated ClangIR is quite different from the generated LLVM IR. `cir.base_class_addr` is lowered to LLVM IR as a `getelementptr` operation. If a null-pointer check is needed, then that is wrapped in a `select` operation. When generating code for a constructor or destructor, an incorrect `cir.ptr_stride` op was used to convert the pointer to a base class. The code was assuming that the operand of `cir.ptr_stride` was measured in bytes; the operand is the number elements, not the number of bytes. So the base class constructor was being called on the wrong chunk of memory. Fix this by using a `cir.base_class_addr` op instead of `cir.ptr_stride` in this scenario. The use of `cir.ptr_stride` in `ApplyNonVirtualAndVirtualOffset` had the same problem. Continue using `cir.ptr_stride` here, but temporarily convert the pointer to type `char*` so the pointer is adjusted correctly. Adjust the expected results of three existing tests in response to these changes. Add two new tests, one code gen and one lowering, to cover the case where a base class is at a non-zero offset.
1 parent f3bfaca commit cc7d1d3

File tree

8 files changed

+159
-61
lines changed

8 files changed

+159
-61
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

+18-4
Original file line numberDiff line numberDiff line change
@@ -2960,23 +2960,37 @@ def BaseClassAddrOp : CIR_Op<"base_class_addr"> {
29602960
let summary = "Get the base class address for a class/struct";
29612961
let description = [{
29622962
The `cir.base_class_addr` operaration gets the address of a particular
2963-
base class given a derived class pointer.
2963+
non-virtual base class given a derived class pointer. The offset in bytes
2964+
of the base class must be passed in, since it is easier for the front end
2965+
to calculate that than the MLIR passes. The operation contains a flag for
2966+
whether or not the operand may be nullptr. That depends on the context and
2967+
cannot be known by the operation, and that information affects how the
2968+
operation is lowered.
29642969

29652970
Example:
2971+
```c++
2972+
struct Base { };
2973+
struct Derived : Base { };
2974+
Derived d;
2975+
Base& b = d;
2976+
```
2977+
will generate
29662978
```mlir
2967-
TBD
2979+
%3 = cir.base_class_addr (%1 : !cir.ptr<!ty_Derived> nonnull) [0] -> !cir.ptr<!ty_Base>
29682980
```
29692981
}];
29702982

29712983
let arguments = (ins
2972-
Arg<CIR_PointerType, "derived class pointer", [MemRead]>:$derived_addr);
2984+
Arg<CIR_PointerType, "derived class pointer", [MemRead]>:$derived_addr,
2985+
IndexAttr:$offset, UnitAttr:$assume_not_null);
29732986

29742987
let results = (outs Res<CIR_PointerType, "">:$base_addr);
29752988

29762989
let assemblyFormat = [{
29772990
`(`
29782991
$derived_addr `:` qualified(type($derived_addr))
2979-
`)` `->` qualified(type($base_addr)) attr-dict
2992+
(`nonnull` $assume_not_null^)?
2993+
`)` `[` $offset `]` `->` qualified(type($base_addr)) attr-dict
29802994
}];
29812995

29822996
// FIXME: add verifier.

clang/lib/CIR/CodeGen/CIRGenBuilder.h

+4-4
Original file line numberDiff line numberDiff line change
@@ -684,14 +684,14 @@ class CIRGenBuilderTy : public CIRBaseBuilderTy {
684684
}
685685

686686
cir::Address createBaseClassAddr(mlir::Location loc, cir::Address addr,
687-
mlir::Type destType) {
687+
mlir::Type destType, unsigned offset,
688+
bool assumeNotNull) {
688689
if (destType == addr.getElementType())
689690
return addr;
690691

691692
auto ptrTy = getPointerTo(destType);
692-
auto baseAddr =
693-
create<mlir::cir::BaseClassAddrOp>(loc, ptrTy, addr.getPointer());
694-
693+
auto baseAddr = create<mlir::cir::BaseClassAddrOp>(
694+
loc, ptrTy, addr.getPointer(), mlir::APInt(64, offset), assumeNotNull);
695695
return Address(baseAddr, ptrTy, addr.getAlignment());
696696
}
697697

clang/lib/CIR/CodeGen/CIRGenClass.cpp

+39-44
Original file line numberDiff line numberDiff line change
@@ -530,17 +530,9 @@ Address CIRGenFunction::getAddressOfDirectBaseInCompleteClass(
530530
else
531531
Offset = Layout.getBaseClassOffset(Base);
532532

533-
// Shift and cast down to the base type.
534-
// TODO: for complete types, this should be possible with a GEP.
535-
Address V = This;
536-
if (!Offset.isZero()) {
537-
mlir::Value OffsetVal = builder.getSInt32(Offset.getQuantity(), loc);
538-
mlir::Value VBaseThisPtr = builder.create<mlir::cir::PtrStrideOp>(
539-
loc, This.getPointer().getType(), This.getPointer(), OffsetVal);
540-
V = Address(VBaseThisPtr, CXXABIThisAlignment);
541-
}
542-
V = builder.createElementBitCast(loc, V, ConvertType(Base));
543-
return V;
533+
return builder.createBaseClassAddr(loc, This, ConvertType(Base),
534+
Offset.getQuantity(),
535+
/*assume_not_null=*/true);
544536
}
545537

546538
static void buildBaseInitializer(mlir::Location loc, CIRGenFunction &CGF,
@@ -680,10 +672,17 @@ static Address ApplyNonVirtualAndVirtualOffset(
680672
baseOffset = virtualOffset;
681673
}
682674

683-
// Apply the base offset.
675+
// Apply the base offset. cir.ptr_stride adjusts by a number of elements,
676+
// not bytes. So the pointer must be cast to a byte pointer and back.
677+
684678
mlir::Value ptr = addr.getPointer();
685-
ptr = CGF.getBuilder().create<mlir::cir::PtrStrideOp>(loc, ptr.getType(), ptr,
686-
baseOffset);
679+
mlir::Type charPtrType = CGF.CGM.UInt8PtrTy;
680+
mlir::Value charPtr = CGF.getBuilder().createCast(
681+
mlir::cir::CastKind::bitcast, ptr, charPtrType);
682+
mlir::Value adjusted = CGF.getBuilder().create<mlir::cir::PtrStrideOp>(
683+
loc, charPtrType, charPtr, baseOffset);
684+
ptr = CGF.getBuilder().createCast(mlir::cir::CastKind::bitcast, adjusted,
685+
ptr.getType());
687686

688687
// If we have a virtual component, the alignment of the result will
689688
// be relative only to the known alignment of that vbase.
@@ -1481,7 +1480,7 @@ CIRGenFunction::getAddressOfBaseClass(Address Value,
14811480
// *start* with a step down to the correct virtual base subobject,
14821481
// and hence will not require any further steps.
14831482
if ((*Start)->isVirtual()) {
1484-
llvm_unreachable("NYI");
1483+
llvm_unreachable("NYI: Cast to virtual base class");
14851484
}
14861485

14871486
// Compute the static offset of the ultimate destination within its
@@ -1494,55 +1493,51 @@ CIRGenFunction::getAddressOfBaseClass(Address Value,
14941493
// For now, that's limited to when the derived type is final.
14951494
// TODO: "devirtualize" this for accesses to known-complete objects.
14961495
if (VBase && Derived->hasAttr<FinalAttr>()) {
1497-
llvm_unreachable("NYI");
1496+
const ASTRecordLayout &layout = getContext().getASTRecordLayout(Derived);
1497+
CharUnits vBaseOffset = layout.getVBaseClassOffset(VBase);
1498+
NonVirtualOffset += vBaseOffset;
1499+
VBase = nullptr; // we no longer have a virtual step
14981500
}
14991501

15001502
// Get the base pointer type.
15011503
auto BaseValueTy = convertType((PathEnd[-1])->getType());
15021504
assert(!MissingFeatures::addressSpace());
1503-
// auto BasePtrTy = builder.getPointerTo(BaseValueTy);
1504-
// QualType DerivedTy = getContext().getRecordType(Derived);
1505-
// CharUnits DerivedAlign = CGM.getClassPointerAlignment(Derived);
15061505

1507-
// If the static offset is zero and we don't have a virtual step,
1508-
// just do a bitcast; null checks are unnecessary.
1509-
if (NonVirtualOffset.isZero() && !VBase) {
1506+
// If there is no virtual base, use cir.base_class_addr. It takes care of
1507+
// the adjustment and the null pointer check.
1508+
if (!VBase) {
15101509
if (sanitizePerformTypeCheck()) {
1511-
llvm_unreachable("NYI");
1510+
llvm_unreachable("NYI: sanitizePerformTypeCheck");
15121511
}
1513-
return builder.createBaseClassAddr(getLoc(Loc), Value, BaseValueTy);
1512+
return builder.createBaseClassAddr(getLoc(Loc), Value, BaseValueTy,
1513+
NonVirtualOffset.getQuantity(),
1514+
/*assumeNotNull=*/not NullCheckValue);
15141515
}
15151516

1516-
// Skip over the offset (and the vtable load) if we're supposed to
1517-
// null-check the pointer.
1518-
if (NullCheckValue) {
1519-
llvm_unreachable("NYI");
1520-
}
1521-
1522-
if (sanitizePerformTypeCheck()) {
1523-
llvm_unreachable("NYI");
1524-
}
1517+
// Conversion to a virtual base. cir.base_class_addr can't handle this.
1518+
// Generate the code to look up the address in the virtual table.
15251519

1526-
// Compute the virtual offset.
1527-
mlir::Value VirtualOffset{};
1528-
if (VBase) {
1529-
llvm_unreachable("NYI");
1530-
}
1520+
llvm_unreachable("NYI: Cast to virtual base class");
15311521

1532-
// Apply both offsets.
1522+
// This is just an outline of what the code might look like, since I can't
1523+
// actually test it.
1524+
#if 0
1525+
mlir::Value VirtualOffset = ...; // This is a dynamic expression. Creating
1526+
// it requires calling an ABI-specific
1527+
// function.
15331528
Value = ApplyNonVirtualAndVirtualOffset(getLoc(Loc), *this, Value,
15341529
NonVirtualOffset, VirtualOffset,
15351530
Derived, VBase);
1536-
// Cast to the destination type.
15371531
Value = builder.createElementBitCast(Value.getPointer().getLoc(), Value,
15381532
BaseValueTy);
1539-
1540-
// Build a phi if we needed a null check.
1533+
if (sanitizePerformTypeCheck()) {
1534+
// Do something here
1535+
}
15411536
if (NullCheckValue) {
1542-
llvm_unreachable("NYI");
1537+
// Convert to 'derivedPtr == nullptr ? nullptr : basePtr'
15431538
}
1539+
#endif
15441540

1545-
llvm_unreachable("NYI");
15461541
return Value;
15471542
}
15481543

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

+35-1
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,39 @@ class CIRPtrStrideOpLowering
622622
}
623623
};
624624

625+
class CIRBaseClassAddrOpLowering
626+
: public mlir::OpConversionPattern<mlir::cir::BaseClassAddrOp> {
627+
public:
628+
using mlir::OpConversionPattern<
629+
mlir::cir::BaseClassAddrOp>::OpConversionPattern;
630+
631+
mlir::LogicalResult
632+
matchAndRewrite(mlir::cir::BaseClassAddrOp baseClassOp, OpAdaptor adaptor,
633+
mlir::ConversionPatternRewriter &rewriter) const override {
634+
const auto resultType =
635+
getTypeConverter()->convertType(baseClassOp.getType());
636+
mlir::Value derivedAddr = adaptor.getDerivedAddr();
637+
llvm::SmallVector<mlir::LLVM::GEPArg, 1> offset = {
638+
adaptor.getOffset().getZExtValue()};
639+
mlir::Type byteType = mlir::IntegerType::get(resultType.getContext(), 8,
640+
mlir::IntegerType::Signless);
641+
if (baseClassOp.getAssumeNotNull()) {
642+
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
643+
baseClassOp, resultType, byteType, derivedAddr, offset);
644+
} else {
645+
auto loc = baseClassOp.getLoc();
646+
mlir::Value isNull = rewriter.create<mlir::LLVM::ICmpOp>(
647+
loc, mlir::LLVM::ICmpPredicate::eq, derivedAddr,
648+
rewriter.create<mlir::LLVM::ZeroOp>(loc, derivedAddr.getType()));
649+
mlir::Value adjusted = rewriter.create<mlir::LLVM::GEPOp>(
650+
loc, resultType, byteType, derivedAddr, offset);
651+
rewriter.replaceOpWithNewOp<mlir::LLVM::SelectOp>(baseClassOp, isNull,
652+
derivedAddr, adjusted);
653+
}
654+
return mlir::success();
655+
}
656+
};
657+
625658
class CIRBrCondOpLowering
626659
: public mlir::OpConversionPattern<mlir::cir::BrCondOp> {
627660
public:
@@ -3821,7 +3854,8 @@ void populateCIRToLLVMConversionPatterns(mlir::RewritePatternSet &patterns,
38213854
CIRPrefetchLowering, CIRObjSizeOpLowering, CIRIsConstantOpLowering,
38223855
CIRCmpThreeWayOpLowering, CIRClearCacheOpLowering, CIRUndefOpLowering,
38233856
CIREhTypeIdOpLowering, CIRCatchParamOpLowering, CIRResumeOpLowering,
3824-
CIRAllocExceptionOpLowering, CIRThrowOpLowering, CIRIntrinsicCallLowering
3857+
CIRAllocExceptionOpLowering, CIRThrowOpLowering, CIRIntrinsicCallLowering,
3858+
CIRBaseClassAddrOpLowering
38253859
#define GET_BUILTIN_LOWERING_LIST
38263860
#include "clang/CIR/Dialect/IR/CIRBuiltinsLowering.inc"
38273861
#undef GET_BUILTIN_LOWERING_LIST

clang/test/CIR/CodeGen/derived-to-base.cpp

+29-4
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ void C3::Layer::Initialize() {
8484
// CHECK: cir.func @_ZN2C35Layer10InitializeEv
8585

8686
// CHECK: cir.scope {
87-
// CHECK: %2 = cir.base_class_addr(%1 : !cir.ptr<!ty_C33A3ALayer>) -> !cir.ptr<!ty_C23A3ALayer>
87+
// CHECK: %2 = cir.base_class_addr(%1 : !cir.ptr<!ty_C33A3ALayer> nonnull) [0] -> !cir.ptr<!ty_C23A3ALayer>
8888
// CHECK: %3 = cir.get_member %2[1] {name = "m_C1"} : !cir.ptr<!ty_C23A3ALayer> -> !cir.ptr<!cir.ptr<!ty_C2_>>
8989
// CHECK: %4 = cir.load %3 : !cir.ptr<!cir.ptr<!ty_C2_>>, !cir.ptr<!ty_C2_>
9090
// CHECK: %5 = cir.const #cir.ptr<null> : !cir.ptr<!ty_C2_>
@@ -99,7 +99,7 @@ enumy C3::Initialize() {
9999

100100
// CHECK: cir.store %arg0, %0 : !cir.ptr<!ty_C3_>, !cir.ptr<!cir.ptr<!ty_C3_>>
101101
// CHECK: %2 = cir.load %0 : !cir.ptr<!cir.ptr<!ty_C3_>>, !cir.ptr<!ty_C3_>
102-
// CHECK: %3 = cir.base_class_addr(%2 : !cir.ptr<!ty_C3_>) -> !cir.ptr<!ty_C2_>
102+
// CHECK: %3 = cir.base_class_addr(%2 : !cir.ptr<!ty_C3_> nonnull) [0] -> !cir.ptr<!ty_C2_>
103103
// CHECK: %4 = cir.call @_ZN2C210InitializeEv(%3) : (!cir.ptr<!ty_C2_>) -> !s32i
104104

105105
void vcall(C1 &c1) {
@@ -144,7 +144,7 @@ class B : public A {
144144
// CHECK: %1 = cir.load deref %0 : !cir.ptr<!cir.ptr<!ty_B>>, !cir.ptr<!ty_B>
145145
// CHECK: cir.scope {
146146
// CHECK: %2 = cir.alloca !ty_A, !cir.ptr<!ty_A>, ["ref.tmp0"] {alignment = 8 : i64}
147-
// CHECK: %3 = cir.base_class_addr(%1 : !cir.ptr<!ty_B>) -> !cir.ptr<!ty_A>
147+
// CHECK: %3 = cir.base_class_addr(%1 : !cir.ptr<!ty_B> nonnull) [0] -> !cir.ptr<!ty_A>
148148

149149
// Call @A::A(A const&)
150150
// CHECK: cir.call @_ZN1AC2ERKS_(%2, %3) : (!cir.ptr<!ty_A>, !cir.ptr<!ty_A>) -> ()
@@ -171,4 +171,29 @@ int test_ref() {
171171
int x = 42;
172172
C c(x);
173173
return c.ref;
174-
}
174+
}
175+
176+
// Multiple base classes, to test non-zero offsets
177+
struct Base1 { int a; };
178+
struct Base2 { int b; };
179+
struct Derived : Base1, Base2 { int c; };
180+
void test_multi_base() {
181+
Derived d;
182+
183+
Base2& bref = d; // no null check needed
184+
// CHECK: %6 = cir.base_class_addr(%0 : !cir.ptr<!ty_Derived> nonnull) [4] -> !cir.ptr<!ty_Base2_>
185+
186+
Base2* bptr = &d; // has null pointer check
187+
// CHECK: %7 = cir.base_class_addr(%0 : !cir.ptr<!ty_Derived>) [4] -> !cir.ptr<!ty_Base2_>
188+
189+
int a = d.a;
190+
// CHECK: %8 = cir.base_class_addr(%0 : !cir.ptr<!ty_Derived> nonnull) [0] -> !cir.ptr<!ty_Base1_>
191+
// CHECK: %9 = cir.get_member %8[0] {name = "a"} : !cir.ptr<!ty_Base1_> -> !cir.ptr<!s32i>
192+
193+
int b = d.b;
194+
// CHECK: %11 = cir.base_class_addr(%0 : !cir.ptr<!ty_Derived> nonnull) [4] -> !cir.ptr<!ty_Base2_>
195+
// CHECK: %12 = cir.get_member %11[0] {name = "b"} : !cir.ptr<!ty_Base2_> -> !cir.ptr<!s32i>
196+
197+
int c = d.c;
198+
// CHECK: %14 = cir.get_member %0[2] {name = "c"} : !cir.ptr<!ty_Derived> -> !cir.ptr<!s32i>
199+
}

clang/test/CIR/CodeGen/multi-vtable.cpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ int main() {
5656
// CIR: cir.store %{{[0-9]+}}, %{{[0-9]+}} : !cir.ptr<!cir.ptr<!cir.func<!u32i ()>>>, !cir.ptr<!cir.ptr<!cir.ptr<!cir.func<!u32i ()>>>>
5757
// CIR: %{{[0-9]+}} = cir.vtable.address_point(@_ZTV5Child, vtable_index = 1, address_point_index = 2) : !cir.ptr<!cir.ptr<!cir.func<!u32i ()>>>
5858
// CIR: %{{[0-9]+}} = cir.const #cir.int<8> : !s64i
59-
// CIR: %{{[0-9]+}} = cir.ptr_stride(%{{[0-9]+}} : !cir.ptr<!ty_Child>, %{{[0-9]+}} : !s64i), !cir.ptr<!ty_Child>
60-
// CIR: %11 = cir.cast(bitcast, %{{[0-9]+}} : !cir.ptr<!ty_Child>), !cir.ptr<!cir.ptr<!cir.ptr<!cir.func<!u32i ()>>>>
59+
// CIR: %{{[0-9]+}} = cir.cast(bitcast, %{{[0-9]+}} : !cir.ptr<!ty_Child>), !cir.ptr<!u8i>
60+
// CIR: %{{[0-9]+}} = cir.ptr_stride(%{{[0-9]+}} : !cir.ptr<!u8i>, %{{[0-9]+}} : !s64i), !cir.ptr<!u8i>
61+
// CIR: %{{[0-9]+}} = cir.cast(bitcast, %{{[0-9]+}} : !cir.ptr<!u8i>), !cir.ptr<!ty_Child>
62+
// CIR: %{{[0-9]+}} = cir.cast(bitcast, %{{[0-9]+}} : !cir.ptr<!ty_Child>), !cir.ptr<!cir.ptr<!cir.ptr<!cir.func<!u32i ()>>>>
6163
// CIR: cir.store %{{[0-9]+}}, %{{[0-9]+}} : !cir.ptr<!cir.ptr<!cir.func<!u32i ()>>>, !cir.ptr<!cir.ptr<!cir.ptr<!cir.func<!u32i ()>>>>
6264
// CIR: cir.return
6365
// CIR: }
@@ -68,7 +70,7 @@ int main() {
6870

6971
// LLVM-DAG: define linkonce_odr void @_ZN5ChildC2Ev(ptr %0)
7072
// LLVM-DAG: store ptr getelementptr inbounds ({ [4 x ptr], [3 x ptr] }, ptr @_ZTV5Child, i32 0, i32 0, i32 2), ptr %{{[0-9]+}}, align 8
71-
// LLVM-DAG: %{{[0-9]+}} = getelementptr %class.Child, ptr %3, i64 8
73+
// LLVM-DAG: %{{[0-9]+}} = getelementptr i8, ptr %3, i64 8
7274
// LLVM-DAG: store ptr getelementptr inbounds ({ [4 x ptr], [3 x ptr] }, ptr @_ZTV5Child, i32 0, i32 1, i32 2), ptr %{{[0-9]+}}, align 8
7375
// LLVM-DAG: ret void
7476
// }

clang/test/CIR/CodeGen/vtable-rtti.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class B : public A
4242
// CHECK: %0 = cir.alloca !cir.ptr<![[ClassB]]>, !cir.ptr<!cir.ptr<![[ClassB]]>>, ["this", init] {alignment = 8 : i64}
4343
// CHECK: cir.store %arg0, %0 : !cir.ptr<![[ClassB]]>, !cir.ptr<!cir.ptr<![[ClassB]]>>
4444
// CHECK: %1 = cir.load %0 : !cir.ptr<!cir.ptr<![[ClassB]]>>, !cir.ptr<![[ClassB]]>
45-
// CHECK: %2 = cir.cast(bitcast, %1 : !cir.ptr<![[ClassB]]>), !cir.ptr<![[ClassA]]>
45+
// CHECK: %2 = cir.base_class_addr(%1 : !cir.ptr<![[ClassB]]> nonnull) [0] -> !cir.ptr<![[ClassA]]>
4646
// CHECK: cir.call @_ZN1AC2Ev(%2) : (!cir.ptr<![[ClassA]]>) -> ()
4747
// CHECK: %3 = cir.vtable.address_point(@_ZTV1B, vtable_index = 0, address_point_index = 2) : !cir.ptr<!cir.ptr<!cir.func<!u32i ()>>>
4848
// CHECK: %4 = cir.cast(bitcast, %1 : !cir.ptr<![[ClassB]]>), !cir.ptr<!cir.ptr<!cir.ptr<!cir.func<!u32i ()>>>>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
2+
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
3+
4+
struct Base1 { int a; };
5+
struct Base2 { int b; };
6+
struct Derived : Base1, Base2 { int c; };
7+
void test_multi_base() {
8+
Derived d;
9+
10+
Base2& bref = d; // no null check needed
11+
// LLVM: %7 = getelementptr i8, ptr %1, i32 4
12+
13+
Base2* bptr = &d; // has null pointer check
14+
// LLVM: %8 = icmp eq ptr %1, null
15+
// LLVM: %9 = getelementptr i8, ptr %1, i32 4
16+
// LLVM: %10 = select i1 %8, ptr %1, ptr %9
17+
18+
int a = d.a;
19+
// LLVM: %11 = getelementptr i8, ptr %1, i32 0
20+
// LLVM: %12 = getelementptr %struct.Base1, ptr %11, i32 0, i32 0
21+
22+
int b = d.b;
23+
// LLVM: %14 = getelementptr i8, ptr %1, i32 4
24+
// LLVM: %15 = getelementptr %struct.Base2, ptr %14, i32 0, i32 0
25+
26+
int c = d.c;
27+
// LLVM: %17 = getelementptr %struct.Derived, ptr %1, i32 0, i32 2
28+
}

0 commit comments

Comments
 (0)