mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[cxx-interop] Fix memory layout for structs with out-of-order base types
In C++, a primary base class that is placed in the beginning of the type's memory layout isn't always the type that is the first in the list of bases – the base types might be laid out in memory in a different order. This makes sure that IRGen handles base types of C++ structs in the correct order. This fixes an assertion in asserts-enabled compilers, and an out-of-memory error in asserts-disabled compilers. The issue was happening for both value types and foreign reference types. This change also includes a small refactoring to reuse the logic between the two code paths. rdar://140848603
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
#include "clang/AST/Expr.h"
|
#include "clang/AST/Expr.h"
|
||||||
#include "clang/AST/ExprCXX.h"
|
#include "clang/AST/ExprCXX.h"
|
||||||
#include "clang/AST/GlobalDecl.h"
|
#include "clang/AST/GlobalDecl.h"
|
||||||
|
#include "clang/AST/RecordLayout.h"
|
||||||
#include "clang/AST/RecursiveASTVisitor.h"
|
#include "clang/AST/RecursiveASTVisitor.h"
|
||||||
#include "clang/CodeGen/ModuleBuilder.h"
|
#include "clang/CodeGen/ModuleBuilder.h"
|
||||||
#include "clang/Sema/Sema.h"
|
#include "clang/Sema/Sema.h"
|
||||||
@@ -366,3 +367,41 @@ void IRGenModule::ensureImplicitCXXDestructorBodyIsDefined(
|
|||||||
auto &sema = Context.getClangModuleLoader()->getClangSema();
|
auto &sema = Context.getClangModuleLoader()->getClangSema();
|
||||||
sema.DefineImplicitDestructor(clang::SourceLocation(), destructor);
|
sema.DefineImplicitDestructor(clang::SourceLocation(), destructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the base classes of a C++ struct/class ordered by their offset in
|
||||||
|
/// the derived type's memory layout.
|
||||||
|
SmallVector<CXXBaseRecordLayout, 1>
|
||||||
|
irgen::getBasesAndOffsets(const clang::CXXRecordDecl *decl) {
|
||||||
|
auto &layout = decl->getASTContext().getASTRecordLayout(decl);
|
||||||
|
|
||||||
|
// Collect the offsets and sizes of base classes within the memory layout
|
||||||
|
// of the derived class.
|
||||||
|
SmallVector<CXXBaseRecordLayout, 1> baseOffsetsAndSizes;
|
||||||
|
for (auto base : decl->bases()) {
|
||||||
|
if (base.isVirtual())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto baseType = base.getType().getCanonicalType();
|
||||||
|
auto baseRecordType = cast<clang::RecordType>(baseType);
|
||||||
|
auto baseRecord = baseRecordType->getAsCXXRecordDecl();
|
||||||
|
assert(baseRecord && "expected a base C++ record");
|
||||||
|
|
||||||
|
if (baseRecord->isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto offset = Size(layout.getBaseClassOffset(baseRecord).getQuantity());
|
||||||
|
auto size =
|
||||||
|
Size(decl->getASTContext().getTypeSizeInChars(baseType).getQuantity());
|
||||||
|
|
||||||
|
baseOffsetsAndSizes.push_back({baseRecord, offset, size});
|
||||||
|
}
|
||||||
|
|
||||||
|
// In C++, base classes might get reordered if the primary base was not
|
||||||
|
// the first base type on the declaration of the class.
|
||||||
|
llvm::sort(baseOffsetsAndSizes, [](const CXXBaseRecordLayout &lhs,
|
||||||
|
const CXXBaseRecordLayout &rhs) {
|
||||||
|
return lhs.offset < rhs.offset;
|
||||||
|
});
|
||||||
|
|
||||||
|
return baseOffsetsAndSizes;
|
||||||
|
}
|
||||||
|
|||||||
@@ -294,32 +294,18 @@ namespace {
|
|||||||
if (!cxxRecord)
|
if (!cxxRecord)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto &layout = cxxRecord->getASTContext().getASTRecordLayout(cxxRecord);
|
auto bases = getBasesAndOffsets(cxxRecord);
|
||||||
|
for (auto base : bases) {
|
||||||
for (auto base : cxxRecord->bases()) {
|
if (base.offset != CurSize) {
|
||||||
if (base.isVirtual())
|
assert(base.offset > CurSize);
|
||||||
continue;
|
auto paddingSize = base.offset - CurSize;
|
||||||
|
auto &opaqueTI =
|
||||||
auto baseType = base.getType().getCanonicalType();
|
IGM.getOpaqueStorageTypeInfo(paddingSize, Alignment(1));
|
||||||
|
|
||||||
auto baseRecord = cast<clang::RecordType>(baseType)->getDecl();
|
|
||||||
auto baseCxxRecord = cast<clang::CXXRecordDecl>(baseRecord);
|
|
||||||
|
|
||||||
if (baseCxxRecord->isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto offset = Size(layout.getBaseClassOffset(baseCxxRecord).getQuantity());
|
|
||||||
auto size = Size(cxxRecord->getASTContext().getTypeSizeInChars(baseType).getQuantity());
|
|
||||||
|
|
||||||
if (offset != CurSize) {
|
|
||||||
assert(offset > CurSize);
|
|
||||||
auto paddingSize = offset - CurSize;
|
|
||||||
auto &opaqueTI = IGM.getOpaqueStorageTypeInfo(paddingSize, Alignment(1));
|
|
||||||
auto element = ElementLayout::getIncomplete(opaqueTI);
|
auto element = ElementLayout::getIncomplete(opaqueTI);
|
||||||
addField(element, LayoutStrategy::Universal);
|
addField(element, LayoutStrategy::Universal);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &opaqueTI = IGM.getOpaqueStorageTypeInfo(size, Alignment(1));
|
auto &opaqueTI = IGM.getOpaqueStorageTypeInfo(base.size, Alignment(1));
|
||||||
auto element = ElementLayout::getIncomplete(opaqueTI);
|
auto element = ElementLayout::getIncomplete(opaqueTI);
|
||||||
addField(element, LayoutStrategy::Universal);
|
addField(element, LayoutStrategy::Universal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -368,28 +368,6 @@ namespace {
|
|||||||
ClangFieldInfo> {
|
ClangFieldInfo> {
|
||||||
const clang::RecordDecl *ClangDecl;
|
const clang::RecordDecl *ClangDecl;
|
||||||
|
|
||||||
template <class Fn>
|
|
||||||
void forEachNonEmptyBase(Fn fn) const {
|
|
||||||
auto &layout = ClangDecl->getASTContext().getASTRecordLayout(ClangDecl);
|
|
||||||
|
|
||||||
if (auto cxxRecord = dyn_cast<clang::CXXRecordDecl>(ClangDecl)) {
|
|
||||||
for (auto base : cxxRecord->bases()) {
|
|
||||||
auto baseType = base.getType().getCanonicalType();
|
|
||||||
|
|
||||||
auto baseRecord = cast<clang::RecordType>(baseType)->getDecl();
|
|
||||||
auto baseCxxRecord = cast<clang::CXXRecordDecl>(baseRecord);
|
|
||||||
|
|
||||||
if (baseCxxRecord->isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto offset = layout.getBaseClassOffset(baseCxxRecord);
|
|
||||||
auto size =
|
|
||||||
ClangDecl->getASTContext().getTypeSizeInChars(baseType);
|
|
||||||
fn(baseType, offset, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LoadableClangRecordTypeInfo(ArrayRef<ClangFieldInfo> fields,
|
LoadableClangRecordTypeInfo(ArrayRef<ClangFieldInfo> fields,
|
||||||
unsigned explosionSize, llvm::Type *storageType,
|
unsigned explosionSize, llvm::Type *storageType,
|
||||||
@@ -446,10 +424,11 @@ namespace {
|
|||||||
|
|
||||||
void addToAggLowering(IRGenModule &IGM, SwiftAggLowering &lowering,
|
void addToAggLowering(IRGenModule &IGM, SwiftAggLowering &lowering,
|
||||||
Size offset) const override {
|
Size offset) const override {
|
||||||
forEachNonEmptyBase([&](clang::QualType type, clang::CharUnits offset,
|
if (auto cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(ClangDecl)) {
|
||||||
clang::CharUnits) {
|
for (auto base : getBasesAndOffsets(cxxRecordDecl)) {
|
||||||
lowering.addTypedData(type, offset);
|
lowering.addTypedData(base.decl, base.offset.asCharUnits());
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lowering.addTypedData(ClangDecl, offset.asCharUnits());
|
lowering.addTypedData(ClangDecl, offset.asCharUnits());
|
||||||
}
|
}
|
||||||
@@ -1384,23 +1363,12 @@ private:
|
|||||||
void collectBases(const clang::RecordDecl *decl) {
|
void collectBases(const clang::RecordDecl *decl) {
|
||||||
auto &layout = decl->getASTContext().getASTRecordLayout(decl);
|
auto &layout = decl->getASTContext().getASTRecordLayout(decl);
|
||||||
if (auto cxxRecord = dyn_cast<clang::CXXRecordDecl>(decl)) {
|
if (auto cxxRecord = dyn_cast<clang::CXXRecordDecl>(decl)) {
|
||||||
for (auto base : cxxRecord->bases()) {
|
auto bases = getBasesAndOffsets(cxxRecord);
|
||||||
if (base.isVirtual())
|
for (auto base : bases) {
|
||||||
continue;
|
SubobjectAdjustment += base.offset;
|
||||||
|
collectBases(base.decl);
|
||||||
auto baseType = base.getType().getCanonicalType();
|
collectStructFields(base.decl);
|
||||||
|
SubobjectAdjustment -= base.offset;
|
||||||
auto baseRecord = cast<clang::RecordType>(baseType)->getDecl();
|
|
||||||
auto baseCxxRecord = cast<clang::CXXRecordDecl>(baseRecord);
|
|
||||||
|
|
||||||
if (baseCxxRecord->isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto baseOffset = Size(layout.getBaseClassOffset(baseCxxRecord).getQuantity());
|
|
||||||
SubobjectAdjustment += baseOffset;
|
|
||||||
collectBases(baseCxxRecord);
|
|
||||||
collectStructFields(baseCxxRecord);
|
|
||||||
SubobjectAdjustment -= baseOffset;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2029,6 +2029,17 @@ public:
|
|||||||
/// Workaround to disable thumb-mode until debugger support is there.
|
/// Workaround to disable thumb-mode until debugger support is there.
|
||||||
bool shouldRemoveTargetFeature(StringRef);
|
bool shouldRemoveTargetFeature(StringRef);
|
||||||
|
|
||||||
|
struct CXXBaseRecordLayout {
|
||||||
|
const clang::CXXRecordDecl *decl;
|
||||||
|
Size offset;
|
||||||
|
Size size;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Retrieves the base classes of a C++ struct/class ordered by their offset in
|
||||||
|
/// the derived type's memory layout.
|
||||||
|
SmallVector<CXXBaseRecordLayout, 1>
|
||||||
|
getBasesAndOffsets(const clang::CXXRecordDecl *decl);
|
||||||
|
|
||||||
} // end namespace irgen
|
} // end namespace irgen
|
||||||
} // end namespace swift
|
} // end namespace swift
|
||||||
|
|
||||||
|
|||||||
@@ -132,3 +132,27 @@ struct DerivedHasTailPadding : public BaseAlign8 {
|
|||||||
struct DerivedUsesBaseTailPadding : public DerivedHasTailPadding {
|
struct DerivedUsesBaseTailPadding : public DerivedHasTailPadding {
|
||||||
short field2 = 789;
|
short field2 = 789;
|
||||||
}; // sizeof=16, dsize=14, align=8
|
}; // sizeof=16, dsize=14, align=8
|
||||||
|
|
||||||
|
// MARK: Types with an out-of-order inheritance.
|
||||||
|
|
||||||
|
struct BaseWithVirtualDestructor {
|
||||||
|
int baseField = 123;
|
||||||
|
|
||||||
|
virtual ~BaseWithVirtualDestructor() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DerivedWithVirtualDestructor : public BaseWithVirtualDestructor {
|
||||||
|
int derivedField = 456;
|
||||||
|
|
||||||
|
~DerivedWithVirtualDestructor() override {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DerivedOutOfOrder : public HasOneField,
|
||||||
|
public DerivedWithVirtualDestructor {
|
||||||
|
// DerivedWithVirtualDestructor is the primary base class despite being the
|
||||||
|
// second one the list.
|
||||||
|
|
||||||
|
int leafField = 789;
|
||||||
|
|
||||||
|
~DerivedOutOfOrder() override {}
|
||||||
|
};
|
||||||
|
|||||||
@@ -124,4 +124,11 @@ FieldsTestSuite.test("Field in tail padding of base class") {
|
|||||||
expectEqual(usesBaseTailPadding.field8, 123)
|
expectEqual(usesBaseTailPadding.field8, 123)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FieldsTestSuite.test("Out-of-order inheritance") {
|
||||||
|
let d = DerivedOutOfOrder()
|
||||||
|
expectEqual(d.leafField, 789)
|
||||||
|
expectEqual(d.derivedField, 456)
|
||||||
|
expectEqual(d.baseField, 123)
|
||||||
|
}
|
||||||
|
|
||||||
runAllTests()
|
runAllTests()
|
||||||
|
|||||||
@@ -27,3 +27,44 @@ public:
|
|||||||
SubT(const SubT &) = delete;
|
SubT(const SubT &) = delete;
|
||||||
static SubT &getSubT() { static SubT singleton; return singleton; }
|
static SubT &getSubT() { static SubT singleton; return singleton; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct
|
||||||
|
__attribute__((swift_attr("import_reference")))
|
||||||
|
__attribute__((swift_attr("retain:immortal")))
|
||||||
|
__attribute__((swift_attr("release:immortal")))
|
||||||
|
BaseWithVirtualDestructor {
|
||||||
|
int baseField = 123;
|
||||||
|
|
||||||
|
BaseWithVirtualDestructor() {}
|
||||||
|
virtual ~BaseWithVirtualDestructor() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct
|
||||||
|
__attribute__((swift_attr("import_reference")))
|
||||||
|
__attribute__((swift_attr("retain:immortal")))
|
||||||
|
__attribute__((swift_attr("release:immortal")))
|
||||||
|
DerivedWithVirtualDestructor : public BaseWithVirtualDestructor {
|
||||||
|
int derivedField = 456;
|
||||||
|
|
||||||
|
DerivedWithVirtualDestructor() : BaseWithVirtualDestructor() {}
|
||||||
|
~DerivedWithVirtualDestructor() override {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct
|
||||||
|
__attribute__((swift_attr("import_reference")))
|
||||||
|
__attribute__((swift_attr("retain:immortal")))
|
||||||
|
__attribute__((swift_attr("release:immortal")))
|
||||||
|
DerivedOutOfOrder : public BaseT, public DerivedWithVirtualDestructor {
|
||||||
|
// DerivedWithVirtualDestructor is the primary base class despite being the
|
||||||
|
// second one the list.
|
||||||
|
|
||||||
|
int leafField = 789;
|
||||||
|
|
||||||
|
DerivedOutOfOrder() = default;
|
||||||
|
~DerivedOutOfOrder() override {}
|
||||||
|
|
||||||
|
static DerivedOutOfOrder& getInstance() {
|
||||||
|
static DerivedOutOfOrder singleton;
|
||||||
|
return singleton;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
14
test/Interop/Cxx/foreign-reference/inheritance-irgen.swift
Normal file
14
test/Interop/Cxx/foreign-reference/inheritance-irgen.swift
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// RUN: %target-swift-emit-ir %s -I %S/Inputs -enable-experimental-cxx-interop -Xcc -fignore-exceptions -disable-availability-checking
|
||||||
|
|
||||||
|
// This ensured we do not crash during IRGen for inherited C++ foreign reference types.
|
||||||
|
|
||||||
|
import Inheritance
|
||||||
|
|
||||||
|
@inline(never)
|
||||||
|
public func blackHole<T>(_ _: T) {}
|
||||||
|
|
||||||
|
let x = DerivedOutOfOrder.getInstance()
|
||||||
|
|
||||||
|
blackHole(x.baseField)
|
||||||
|
blackHole(x.derivedField)
|
||||||
|
blackHole(x.leafField)
|
||||||
@@ -21,3 +21,8 @@ let b: BaseT = BaseT.getBaseT()
|
|||||||
assert(b.isBase)
|
assert(b.isBase)
|
||||||
let bc: BaseT = cxxCast(b) // should instantiate I and O both to BaseT
|
let bc: BaseT = cxxCast(b) // should instantiate I and O both to BaseT
|
||||||
assert(bc.isBase)
|
assert(bc.isBase)
|
||||||
|
|
||||||
|
let d = DerivedOutOfOrder.getInstance()
|
||||||
|
let _ = d.baseField
|
||||||
|
let _ = d.derivedField
|
||||||
|
let _ = d.leafField
|
||||||
|
|||||||
@@ -30,4 +30,11 @@ TemplatingTestSuite.test("BaseT") {
|
|||||||
expectTrue(bc.isBase)
|
expectTrue(bc.isBase)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TemplatingTestSuite.test("DerivedOutOfOrder") {
|
||||||
|
let d = DerivedOutOfOrder.getInstance()
|
||||||
|
expectEqual(123, d.baseField)
|
||||||
|
expectEqual(456, d.derivedField)
|
||||||
|
expectEqual(789, d.leafField)
|
||||||
|
}
|
||||||
|
|
||||||
runAllTests()
|
runAllTests()
|
||||||
|
|||||||
Reference in New Issue
Block a user