Merge pull request #72903 from apple/egorzhdan/zero-init

[cxx-interop] Zero-initialize C++ structs when calling their default constructors
This commit is contained in:
Egor Zhdan
2024-04-17 13:39:21 +01:00
committed by GitHub
8 changed files with 62 additions and 8 deletions

View File

@@ -3476,7 +3476,12 @@ llvm::Constant *swift::irgen::emitCXXConstructorThunkIfNeeded(
llvm::FunctionType *ctorFnType = llvm::FunctionType *ctorFnType =
cast<llvm::FunctionType>(clangFunc->getValueType()); cast<llvm::FunctionType>(clangFunc->getValueType());
if (assumedFnType == ctorFnType) { // Only need a thunk if either:
// 1. The calling conventions do not match, and we need to pass arguments
// differently.
// 2. This is a default constructor, and we need to zero the backing memory of
// the struct.
if (assumedFnType == ctorFnType && !ctor->isDefaultConstructor()) {
return ctorAddress; return ctorAddress;
} }
@@ -3510,6 +3515,7 @@ llvm::Constant *swift::irgen::emitCXXConstructorThunkIfNeeded(
Args.push_back(i); Args.push_back(i);
} }
if (assumedFnType != ctorFnType) {
clang::CodeGen::ImplicitCXXConstructorArgs implicitArgs = clang::CodeGen::ImplicitCXXConstructorArgs implicitArgs =
clang::CodeGen::getImplicitCXXConstructorArgs(IGM.ClangCodeGen->CGM(), clang::CodeGen::getImplicitCXXConstructorArgs(IGM.ClangCodeGen->CGM(),
ctor); ctor);
@@ -3519,6 +3525,25 @@ llvm::Constant *swift::irgen::emitCXXConstructorThunkIfNeeded(
for (const auto &arg : implicitArgs.Suffix) { for (const auto &arg : implicitArgs.Suffix) {
Args.push_back(arg); Args.push_back(arg);
} }
}
if (ctor->isDefaultConstructor()) {
assert(Args.size() > 0 && "expected at least 1 argument (result address) "
"for default constructor");
// Zero out the backing memory of the struct.
// This makes default initializers for C++ structs behave consistently with
// the synthesized empty initializers for C structs. When C++ interop is
// enabled in a project, all imported C structs are treated as C++ structs,
// which sometimes means that Clang will synthesize a default constructor
// for the C++ struct that does not zero out trivial fields of a struct.
auto cxxRecord = ctor->getParent();
clang::ASTContext &ctx = cxxRecord->getASTContext();
auto typeSize = ctx.getTypeSizeInChars(ctx.getRecordType(cxxRecord));
subIGF.Builder.CreateMemSet(Args[0],
llvm::ConstantInt::get(subIGF.IGM.Int8Ty, 0),
typeSize.getQuantity(), llvm::MaybeAlign());
}
auto *call = auto *call =
emitCXXConstructorCall(subIGF, ctor, ctorFnType, ctorAddress, Args); emitCXXConstructorCall(subIGF, ctor, ctorFnType, ctorAddress, Args);

View File

@@ -32,6 +32,9 @@ struct ConstructorWithParam {
struct CopyAndMoveConstructor { struct CopyAndMoveConstructor {
CopyAndMoveConstructor(const CopyAndMoveConstructor &) = default; CopyAndMoveConstructor(const CopyAndMoveConstructor &) = default;
CopyAndMoveConstructor(CopyAndMoveConstructor &&) = default; CopyAndMoveConstructor(CopyAndMoveConstructor &&) = default;
int value = 123;
int *ptr = nullptr;
}; };
struct Base {}; struct Base {};

View File

@@ -44,4 +44,23 @@ CxxConstructorTestSuite.test("TemplatedConstructor") {
expectEqual(2, instance.value.i) expectEqual(2, instance.value.i)
} }
CxxConstructorTestSuite.test("implicit default ctor") {
// Make sure that fields of C++ structs are zeroed out.
let instance1 = ConstructorWithParam()
expectEqual(0, instance1.x)
let instance2 = IntWrapper()
expectEqual(0, instance2.x)
// CopyAndMoveConstructor is not default-initializable in C++, however, Swift
// generates an implicit deprecated default constructor for C++ structs for
// compatibility with C. This constructor will zero out the entire backing
// memory of the struct, including fields that have an init expression.
// See `SwiftDeclSynthesizer::createDefaultConstructor`.
let instance3 = CopyAndMoveConstructor()
expectEqual(0, instance3.value)
expectNil(instance3.ptr)
}
runAllTests() runAllTests()

View File

@@ -23,6 +23,7 @@ public func createImplicitDefaultConstructor() -> ImplicitDefaultConstructor {
// ITANIUM_ARM: define protected swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0deF0VyF"() // ITANIUM_ARM: define protected swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0deF0VyF"()
// ITANIUM_ARM-NOT: define // ITANIUM_ARM-NOT: define
// Note `this` return type. // Note `this` return type.
// ITANIUM_ARM: call void @llvm.memset.p0.i64
// ITANIUM_ARM: call void @_ZN26ImplicitDefaultConstructorC2Ev(ptr %{{[0-9]+}}) // ITANIUM_ARM: call void @_ZN26ImplicitDefaultConstructorC2Ev(ptr %{{[0-9]+}})
return ImplicitDefaultConstructor() return ImplicitDefaultConstructor()
} }

View File

@@ -18,6 +18,7 @@ public func createHasVirtualBase() -> HasVirtualBase {
public func createImplicitDefaultConstructor() -> ImplicitDefaultConstructor { public func createImplicitDefaultConstructor() -> ImplicitDefaultConstructor {
// ITANIUM_X64: define swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0deF0VyF"() // ITANIUM_X64: define swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0deF0VyF"()
// ITANIUM_X64-NOT: define // ITANIUM_X64-NOT: define
// ITANIUM_X64: call void @llvm.memset.p0.i64
// ITANIUM_X64: call void @_ZN26ImplicitDefaultConstructorC1Ev(ptr %{{[0-9]+}}) // ITANIUM_X64: call void @_ZN26ImplicitDefaultConstructorC1Ev(ptr %{{[0-9]+}})
return ImplicitDefaultConstructor() return ImplicitDefaultConstructor()
} }

View File

@@ -23,6 +23,7 @@ public func createImplicitDefaultConstructor() -> ImplicitDefaultConstructor {
// MICROSOFT_X64: define dllexport swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0{{bcD0VyF|deF0VyF}}"() // MICROSOFT_X64: define dllexport swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0{{bcD0VyF|deF0VyF}}"()
// MICROSOFT_X64-NOT: define // MICROSOFT_X64-NOT: define
// Note `this` return type but no implicit "most derived" argument. // Note `this` return type but no implicit "most derived" argument.
// MICROSOFT_X64: call void @llvm.memset.p0.i64
// MICROSOFT_X64: call ptr @"??0ImplicitDefaultConstructor@@QEAA@XZ"(ptr %{{[0-9]+}}) // MICROSOFT_X64: call ptr @"??0ImplicitDefaultConstructor@@QEAA@XZ"(ptr %{{[0-9]+}})
return ImplicitDefaultConstructor() return ImplicitDefaultConstructor()
} }

View File

@@ -32,6 +32,9 @@
// CHECK-NEXT: struct CopyAndMoveConstructor { // CHECK-NEXT: struct CopyAndMoveConstructor {
// CHECK-NEXT: @available(*, deprecated, message // CHECK-NEXT: @available(*, deprecated, message
// CHECK-NEXT: init() // CHECK-NEXT: init()
// CHECK-NEXT: init(value: Int32, ptr: UnsafeMutablePointer<Int32>!)
// CHECK-NEXT: var value: Int32
// CHECK-NEXT: var ptr: UnsafeMutablePointer<Int32>!
// CHECK-NEXT: } // CHECK-NEXT: }
// CHECK-NEXT: struct Base { // CHECK-NEXT: struct Base {
// CHECK-NEXT: init() // CHECK-NEXT: init()

View File

@@ -7,6 +7,7 @@ let explicit = ExplicitDefaultConstructor()
let implicit = ImplicitDefaultConstructor() let implicit = ImplicitDefaultConstructor()
let deletedImplicitly = ConstructorWithParam() // expected-warning {{'init()' is deprecated}} let deletedImplicitly = ConstructorWithParam() // expected-warning {{'init()' is deprecated}}
let onlyCopyAndMove = CopyAndMoveConstructor() // expected-warning {{'init()' is deprecated}}
let deletedExplicitly = DefaultConstructorDeleted() // expected-error {{missing argument for parameter 'a' in call}} let deletedExplicitly = DefaultConstructorDeleted() // expected-error {{missing argument for parameter 'a' in call}}