Skip to content

[cxx-interop] [IRGen] TypeInfo for address-only types. #32973

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

Merged
merged 1 commit into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 89 additions & 24 deletions lib/IRGen/GenStruct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ enum class StructTypeInfoKind {
LoadableStructTypeInfo,
FixedStructTypeInfo,
LoadableClangRecordTypeInfo,
AddressOnlyClangRecordTypeInfo,
NonFixedStructTypeInfo,
ResilientStructTypeInfo
};
Expand Down Expand Up @@ -83,6 +84,12 @@ namespace {
/// A field-info implementation for fields of Clang types.
class ClangFieldInfo : public RecordField<ClangFieldInfo> {
public:
ClangFieldInfo(VarDecl *swiftField, const ElementLayout &layout,
const TypeInfo &typeInfo)
: RecordField(typeInfo), Field(swiftField) {
completeFrom(layout);
}

ClangFieldInfo(VarDecl *swiftField, const ElementLayout &layout,
unsigned explosionBegin, unsigned explosionEnd)
: RecordField(layout, explosionBegin, explosionEnd),
Expand Down Expand Up @@ -290,7 +297,7 @@ namespace {
}
}
};

/// A type implementation for loadable record types imported from Clang.
class LoadableClangRecordTypeInfo final :
public StructTypeInfoBase<LoadableClangRecordTypeInfo, LoadableTypeInfo,
Expand Down Expand Up @@ -339,6 +346,50 @@ namespace {
}
};

class AddressOnlyClangRecordTypeInfo final
: public StructTypeInfoBase<AddressOnlyClangRecordTypeInfo, FixedTypeInfo,
ClangFieldInfo> {
const clang::RecordDecl *ClangDecl;

public:
AddressOnlyClangRecordTypeInfo(ArrayRef<ClangFieldInfo> fields,
llvm::Type *storageType, Size size,
Alignment align,
const clang::RecordDecl *clangDecl)
: StructTypeInfoBase(StructTypeInfoKind::AddressOnlyClangRecordTypeInfo,
fields, storageType, size,
// We can't assume any spare bits in a C++ type
// with user-defined special member functions.
SpareBitVector(llvm::Optional<APInt>{
llvm::APInt(size.getValueInBits(), 0)}),
align, IsPOD, IsNotBitwiseTakable, IsFixedSize),
ClangDecl(clangDecl) {
(void)ClangDecl;
}

TypeLayoutEntry *buildTypeLayoutEntry(IRGenModule &IGM,
SILType T) const override {
return IGM.typeLayoutCache.getOrCreateScalarEntry(*this, T);
}

void initializeFromParams(IRGenFunction &IGF, Explosion &params,
Address addr, SILType T,
bool isOutlined) const override {
llvm_unreachable("Address-only C++ types must be created by C++ special "
"member functions.");
}

llvm::NoneType getNonFixedOffsets(IRGenFunction &IGF) const { return None; }
llvm::NoneType getNonFixedOffsets(IRGenFunction &IGF, SILType T) const {
return None;
}
MemberAccessStrategy
getNonFixedFieldAccessStrategy(IRGenModule &IGM, SILType T,
const ClangFieldInfo &field) const {
llvm_unreachable("non-fixed field in Clang type?");
}
};

/// A type implementation for loadable struct types.
class LoadableStructTypeInfo final
: public StructTypeInfoBase<LoadableStructTypeInfo, LoadableTypeInfo> {
Expand Down Expand Up @@ -680,6 +731,10 @@ class ClangRecordLowering {

const TypeInfo *createTypeInfo(llvm::StructType *llvmType) {
llvmType->setBody(LLVMFields, /*packed*/ true);
if (SwiftType.getStructOrBoundGenericStruct()->isCxxNonTrivial()) {
return AddressOnlyClangRecordTypeInfo::create(
FieldInfos, llvmType, TotalStride, TotalAlignment, ClangDecl);
}
return LoadableClangRecordTypeInfo::create(FieldInfos, NextExplosionIndex,
llvmType, TotalStride,
std::move(SpareBits), TotalAlignment,
Expand Down Expand Up @@ -773,7 +828,7 @@ class ClangRecordLowering {

// If we have a Swift import of this type, use our lowered information.
if (swiftField) {
auto &fieldTI = cast<LoadableTypeInfo>(IGM.getTypeInfo(
auto &fieldTI = cast<FixedTypeInfo>(IGM.getTypeInfo(
SwiftType.getFieldType(swiftField, IGM.getSILModule(),
IGM.getMaximalTypeExpansionContext())));
addField(swiftField, offset, fieldTI);
Expand Down Expand Up @@ -812,7 +867,7 @@ class ClangRecordLowering {

/// Add storage for an (optional) Swift field at the given offset.
void addField(VarDecl *swiftField, Size offset,
const LoadableTypeInfo &fieldType) {
const FixedTypeInfo &fieldType) {
assert(offset >= NextOffset && "adding fields out of order");

// Add a padding field if required.
Expand All @@ -823,8 +878,11 @@ class ClangRecordLowering {
}

/// Add information to track a value field at the current offset.
void addFieldInfo(VarDecl *swiftField, const LoadableTypeInfo &fieldType) {
unsigned explosionSize = fieldType.getExplosionSize();
void addFieldInfo(VarDecl *swiftField, const FixedTypeInfo &fieldType) {
bool isLoadableField = isa<LoadableTypeInfo>(fieldType);
unsigned explosionSize = 0;
if (isLoadableField)
explosionSize = cast<LoadableTypeInfo>(fieldType).getExplosionSize();
unsigned explosionBegin = NextExplosionIndex;
NextExplosionIndex += explosionSize;
unsigned explosionEnd = NextExplosionIndex;
Expand All @@ -838,9 +896,12 @@ class ClangRecordLowering {
layout.completeFixed(fieldType.isPOD(ResilienceExpansion::Maximal),
NextOffset, LLVMFields.size());

FieldInfos.push_back(
ClangFieldInfo(swiftField, layout, explosionBegin, explosionEnd));

if (isLoadableField)
FieldInfos.push_back(
ClangFieldInfo(swiftField, layout, explosionBegin, explosionEnd));
else
FieldInfos.push_back(ClangFieldInfo(swiftField, layout, fieldType));

if (!isEmpty) {
LLVMFields.push_back(fieldType.getStorageType());
NextOffset += fieldType.getFixedSize();
Expand All @@ -862,22 +923,26 @@ class ClangRecordLowering {

/// A convenient macro for delegating an operation to all of the
/// various struct implementations.
#define FOR_STRUCT_IMPL(IGF, type, op, ...) do { \
auto &structTI = IGF.getTypeInfo(type); \
switch (getStructTypeInfoKind(structTI)) { \
case StructTypeInfoKind::LoadableClangRecordTypeInfo: \
return structTI.as<LoadableClangRecordTypeInfo>().op(IGF, __VA_ARGS__); \
case StructTypeInfoKind::LoadableStructTypeInfo: \
return structTI.as<LoadableStructTypeInfo>().op(IGF, __VA_ARGS__); \
case StructTypeInfoKind::FixedStructTypeInfo: \
return structTI.as<FixedStructTypeInfo>().op(IGF, __VA_ARGS__); \
case StructTypeInfoKind::NonFixedStructTypeInfo: \
return structTI.as<NonFixedStructTypeInfo>().op(IGF, __VA_ARGS__); \
case StructTypeInfoKind::ResilientStructTypeInfo: \
llvm_unreachable("resilient structs are opaque"); \
} \
llvm_unreachable("bad struct type info kind!"); \
} while (0)
#define FOR_STRUCT_IMPL(IGF, type, op, ...) \
do { \
auto &structTI = IGF.getTypeInfo(type); \
switch (getStructTypeInfoKind(structTI)) { \
case StructTypeInfoKind::LoadableClangRecordTypeInfo: \
return structTI.as<LoadableClangRecordTypeInfo>().op(IGF, __VA_ARGS__); \
case StructTypeInfoKind::AddressOnlyClangRecordTypeInfo: \
return structTI.as<AddressOnlyClangRecordTypeInfo>().op(IGF, \
__VA_ARGS__); \
case StructTypeInfoKind::LoadableStructTypeInfo: \
return structTI.as<LoadableStructTypeInfo>().op(IGF, __VA_ARGS__); \
case StructTypeInfoKind::FixedStructTypeInfo: \
return structTI.as<FixedStructTypeInfo>().op(IGF, __VA_ARGS__); \
case StructTypeInfoKind::NonFixedStructTypeInfo: \
return structTI.as<NonFixedStructTypeInfo>().op(IGF, __VA_ARGS__); \
case StructTypeInfoKind::ResilientStructTypeInfo: \
llvm_unreachable("resilient structs are opaque"); \
} \
llvm_unreachable("bad struct type info kind!"); \
} while (0)

Address irgen::projectPhysicalStructMemberAddress(IRGenFunction &IGF,
Address base,
Expand Down
18 changes: 18 additions & 0 deletions test/Interop/Cxx/class/Inputs/type-classification.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,22 @@ struct StructDeletedDestructor {
~StructDeletedDestructor() = delete;
};

struct StructWithCopyConstructorAndValue {
int value;
StructWithCopyConstructorAndValue(
const StructWithCopyConstructorAndValue &other)
: value(other.value) {}
};

struct StructWithSubobjectCopyConstructorAndValue {
StructWithCopyConstructorAndValue member;
};

struct StructWithCopyConstructorAndSubobjectCopyConstructorAndValue {
StructWithCopyConstructorAndValue member;
StructWithCopyConstructorAndSubobjectCopyConstructorAndValue(
const StructWithCopyConstructorAndSubobjectCopyConstructorAndValue &other)
: member(other.member) {}
};

#endif
99 changes: 99 additions & 0 deletions test/Interop/Cxx/class/type-classification-non-trivial-irgen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// RUN: %target-swift-frontend -enable-cxx-interop -I %S/Inputs %s -emit-ir | %FileCheck %s

// Verify that non-trival/address-only C++ classes are constructed and accessed
// correctly. Make sure that we correctly IRGen functions that construct
// non-trivial C++ classes, take those classes as a parameter, and access those
// classes members.

import TypeClassification

// TODO: C++ objects with destructors should be tested here once we fully
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unfortunate (but also unrelated to this change). I was meaning to submit a fix for this and forgot. Maybe I'll have time this weekend. Otherwise, I'll do it next week.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #32591.

// support them.

// CHECK-LABEL: define {{.*}}i1 @"$s4main37testStructWithCopyConstructorAndValueSbyF"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add an explanation of what these tests are verifying, so that future when IRGen patterns change people can update the CHECK lines appropriately, and not accidentally eliminate the part that we're thinking we are testing here.

// CHECK: [[OBJ:%.*]] = alloca %TSo33StructWithCopyConstructorAndValueV
// CHECK: [[VAL_ELEMENT:%.*]] = getelementptr inbounds %TSo33StructWithCopyConstructorAndValueV, %TSo33StructWithCopyConstructorAndValueV* [[OBJ]], i32 0, i32 0
// CHECK: [[VAL_INT:%.*]] = getelementptr inbounds %Ts5Int32V, %Ts5Int32V* [[VAL_ELEMENT]], i32 0, i32 0
// CHECK: store i32 42, i32* [[VAL_INT]]
// CHECK: ret i1 true
public func testStructWithCopyConstructorAndValue() -> Bool {
let obj = StructWithCopyConstructorAndValue(value: 42)
return obj.value == 42
}

// CHECK-LABEL: define {{.*}}i1 @"$s4main46testStructWithSubobjectCopyConstructorAndValueSbyF"()
// CHECK: [[MEMBER:%.*]] = alloca %TSo33StructWithCopyConstructorAndValueV
// CHECK: [[OBJ:%.*]] = alloca %TSo42StructWithSubobjectCopyConstructorAndValueV
// CHECK: alloca %TSo33StructWithCopyConstructorAndValueV
// CHECK: [[TMP:%.*]] = alloca %TSo33StructWithCopyConstructorAndValueV
// CHECK: [[MEMBER_ELEMENT:%.*]] = getelementptr inbounds %TSo33StructWithCopyConstructorAndValueV, %TSo33StructWithCopyConstructorAndValueV* [[MEMBER]], i32 0, i32 0
// CHECK: [[MEMBER_VALUE:%.*]] = getelementptr inbounds %Ts5Int32V, %Ts5Int32V* [[MEMBER_ELEMENT]], i32 0, i32 0
// CHECK: store i32 42, i32* [[MEMBER_VALUE]]
// CHECK: %obj.member = getelementptr inbounds %TSo42StructWithSubobjectCopyConstructorAndValueV, %TSo42StructWithSubobjectCopyConstructorAndValueV* [[OBJ]], i32 0, i32 0
// CHECK: [[TEMP_MEMBER:%.*]] = getelementptr inbounds %TSo33StructWithCopyConstructorAndValueV, %TSo33StructWithCopyConstructorAndValueV* [[TMP]], i32 0, i32 0
// CHECK: [[TEMP_MEMBER_VALUE:%.*]] = getelementptr inbounds %Ts5Int32V, %Ts5Int32V* [[TEMP_MEMBER]], i32 0, i32 0
// CHECK: [[LHS:%.*]] = load i32, i32* [[TEMP_MEMBER_VALUE]]
// CHECK: [[OUT:%.*]] = icmp eq i32 [[LHS]], 42
// CHECK: ret i1 [[OUT]]
public func testStructWithSubobjectCopyConstructorAndValue() -> Bool {
let member = StructWithCopyConstructorAndValue(value: 42)
let obj = StructWithSubobjectCopyConstructorAndValue(member: member)
return obj.member.value == 42
}

// CHECK-LABEL: define {{.*}}i1 @"$s4main041testStructWithCopyConstructorAndSubobjectefG5ValueSbyF"()
// CHECK: [[MEMBER:%.*]] = alloca %TSo33StructWithCopyConstructorAndValueV
// CHECK: alloca %TSo037StructWithCopyConstructorAndSubobjectcdE5ValueV
// CHECK: alloca %TSo33StructWithCopyConstructorAndValueV
// CHECK: [[TEMP:%.*]] = alloca %TSo33StructWithCopyConstructorAndValueV
// CHECK: [[MEMBER_VAL:%.*]] = getelementptr inbounds %TSo33StructWithCopyConstructorAndValueV, %TSo33StructWithCopyConstructorAndValueV* [[MEMBER]], i32 0, i32 0
// CHECK: [[MEMBER_VAL_VAL:%.*]] = getelementptr inbounds %Ts5Int32V, %Ts5Int32V* [[MEMBER_VAL]], i32 0, i32 0
// CHECK: store i32 42, i32* [[MEMBER_VAL_VAL]]
// CHECK: [[TEMP_MEMBER:%.*]] = getelementptr inbounds %TSo33StructWithCopyConstructorAndValueV, %TSo33StructWithCopyConstructorAndValueV* [[TEMP]], i32 0, i32 0
// CHECK: [[TEMP_MEMBER_VAL:%.*]] = getelementptr inbounds %Ts5Int32V, %Ts5Int32V* [[TEMP_MEMBER]], i32 0, i32 0
// CHECK: [[LHS:%.*]] = load i32, i32* [[TEMP_MEMBER_VAL]]
// CHECK: [[OUT:%.*]] = icmp eq i32 [[LHS]], 42
// CHECK: ret i1 [[OUT]]
public func testStructWithCopyConstructorAndSubobjectCopyConstructorAndValue()
-> Bool {
let member = StructWithCopyConstructorAndValue(value: 42)
let obj = StructWithCopyConstructorAndSubobjectCopyConstructorAndValue(
member: member
)
return obj.member.value == 42
}

// CHECK-LABEL: define {{.*}}i1 @"$s4main4test3objSbSo33StructWithCopyConstructorAndValueV_tF"(%TSo33StructWithCopyConstructorAndValueV* noalias nocapture dereferenceable(4) %0)
// CHECK: [[VAL:%.*]] = getelementptr inbounds %TSo33StructWithCopyConstructorAndValueV, %TSo33StructWithCopyConstructorAndValueV* %0, i32 0, i32 0
// CHECK: [[VAL_VAL:%.*]] = getelementptr inbounds %Ts5Int32V, %Ts5Int32V* [[VAL]], i32 0, i32 0
// CHECK: [[LHS:%.*]] = load i32, i32* [[VAL_VAL]]
// CHECK: [[OUT:%.*]] = icmp eq i32 %1, 42
// CHECK: ret i1 [[OUT]]
public func test(obj: StructWithCopyConstructorAndValue) -> Bool {
return obj.value == 42
}

// CHECK-LABEL: define {{.*}}i1 @"$s4main4test3objSbSo42StructWithSubobjectCopyConstructorAndValueV_tF"(%TSo42StructWithSubobjectCopyConstructorAndValueV* noalias nocapture dereferenceable(4) %0)
// CHECK: [[TMP:%.*]] = alloca %TSo33StructWithCopyConstructorAndValueV
// CHECK: [[MEMBER:%.*]] = getelementptr inbounds %TSo42StructWithSubobjectCopyConstructorAndValueV, %TSo42StructWithSubobjectCopyConstructorAndValueV* %0, i32 0, i32 0
// CHECK: [[VAL:%.*]] = getelementptr inbounds %TSo33StructWithCopyConstructorAndValueV, %TSo33StructWithCopyConstructorAndValueV* [[TMP]], i32 0, i32 0
// CHECK: [[VAL_VAL:%.*]] = getelementptr inbounds %Ts5Int32V, %Ts5Int32V* [[VAL]], i32 0, i32 0
// CHECK: [[LHS:%.*]] = load i32, i32* [[VAL_VAL]]
// CHECK: [[OUT:%.*]] = icmp eq i32 [[LHS]], 42
// CHECK: ret i1 [[OUT]]
public func test(obj: StructWithSubobjectCopyConstructorAndValue) -> Bool {
return obj.member.value == 42
}

// CHECK-LABEL: define {{.*}}i1 @"$s4main4test3objSbSo037StructWithCopyConstructorAndSubobjectfgH5ValueV_tF"(%TSo037StructWithCopyConstructorAndSubobjectcdE5ValueV* noalias nocapture dereferenceable(4) %0)
// CHECK:[[TEMP:%.*]] = alloca %TSo33StructWithCopyConstructorAndValueV
// CHECK:[[VAL:%.*]] = getelementptr inbounds %TSo33StructWithCopyConstructorAndValueV, %TSo33StructWithCopyConstructorAndValueV* [[TEMP]], i32 0, i32 0
// CHECK:[[VAL_VAL:%.*]] = getelementptr inbounds %Ts5Int32V, %Ts5Int32V* [[VAL]], i32 0, i32 0
// CHECK:[[LHS:%.*]] = load i32, i32* [[VAL_VAL]]
// CHECK:[[OUT:%.*]] = icmp eq i32 [[LHS]], 42
// CHECK:ret i1 [[OUT]]
public func test(
obj: StructWithCopyConstructorAndSubobjectCopyConstructorAndValue
) -> Bool {
return obj.member.value == 42
}
Loading