diff --git a/lib/IRGen/GenDecl.cpp b/lib/IRGen/GenDecl.cpp index ce9aad03af81b..8ac4d0a312cb9 100644 --- a/lib/IRGen/GenDecl.cpp +++ b/lib/IRGen/GenDecl.cpp @@ -3476,7 +3476,12 @@ llvm::Constant *swift::irgen::emitCXXConstructorThunkIfNeeded( llvm::FunctionType *ctorFnType = cast(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; } @@ -3510,14 +3515,34 @@ llvm::Constant *swift::irgen::emitCXXConstructorThunkIfNeeded( Args.push_back(i); } - clang::CodeGen::ImplicitCXXConstructorArgs implicitArgs = - clang::CodeGen::getImplicitCXXConstructorArgs(IGM.ClangCodeGen->CGM(), - ctor); - for (size_t i = 0; i < implicitArgs.Prefix.size(); ++i) { - Args.insert(Args.begin() + 1 + i, implicitArgs.Prefix[i]); + if (assumedFnType != ctorFnType) { + clang::CodeGen::ImplicitCXXConstructorArgs implicitArgs = + clang::CodeGen::getImplicitCXXConstructorArgs(IGM.ClangCodeGen->CGM(), + ctor); + for (size_t i = 0; i < implicitArgs.Prefix.size(); ++i) { + Args.insert(Args.begin() + 1 + i, implicitArgs.Prefix[i]); + } + for (const auto &arg : implicitArgs.Suffix) { + Args.push_back(arg); + } } - for (const auto &arg : implicitArgs.Suffix) { - 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 = diff --git a/test/Interop/Cxx/class/Inputs/constructors.h b/test/Interop/Cxx/class/Inputs/constructors.h index 533db77d44f98..dafb869c49bef 100644 --- a/test/Interop/Cxx/class/Inputs/constructors.h +++ b/test/Interop/Cxx/class/Inputs/constructors.h @@ -32,6 +32,9 @@ struct ConstructorWithParam { struct CopyAndMoveConstructor { CopyAndMoveConstructor(const CopyAndMoveConstructor &) = default; CopyAndMoveConstructor(CopyAndMoveConstructor &&) = default; + + int value = 123; + int *ptr = nullptr; }; struct Base {}; diff --git a/test/Interop/Cxx/class/constructors-executable.swift b/test/Interop/Cxx/class/constructors-executable.swift index 1efbb2556b6cc..b706ebbd9f8e9 100644 --- a/test/Interop/Cxx/class/constructors-executable.swift +++ b/test/Interop/Cxx/class/constructors-executable.swift @@ -44,4 +44,23 @@ CxxConstructorTestSuite.test("TemplatedConstructor") { 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() diff --git a/test/Interop/Cxx/class/constructors-irgen-android.swift b/test/Interop/Cxx/class/constructors-irgen-android.swift index 3ee0e369826c9..ee8b5da8f773a 100644 --- a/test/Interop/Cxx/class/constructors-irgen-android.swift +++ b/test/Interop/Cxx/class/constructors-irgen-android.swift @@ -23,6 +23,7 @@ public func createImplicitDefaultConstructor() -> ImplicitDefaultConstructor { // ITANIUM_ARM: define protected swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0deF0VyF"() // ITANIUM_ARM-NOT: define // Note `this` return type. + // ITANIUM_ARM: call void @llvm.memset.p0.i64 // ITANIUM_ARM: call void @_ZN26ImplicitDefaultConstructorC2Ev(ptr %{{[0-9]+}}) return ImplicitDefaultConstructor() } diff --git a/test/Interop/Cxx/class/constructors-irgen-macosx.swift b/test/Interop/Cxx/class/constructors-irgen-macosx.swift index 120d681030cb6..0f58d21ffd162 100644 --- a/test/Interop/Cxx/class/constructors-irgen-macosx.swift +++ b/test/Interop/Cxx/class/constructors-irgen-macosx.swift @@ -18,6 +18,7 @@ public func createHasVirtualBase() -> HasVirtualBase { public func createImplicitDefaultConstructor() -> ImplicitDefaultConstructor { // ITANIUM_X64: define swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0deF0VyF"() // ITANIUM_X64-NOT: define + // ITANIUM_X64: call void @llvm.memset.p0.i64 // ITANIUM_X64: call void @_ZN26ImplicitDefaultConstructorC1Ev(ptr %{{[0-9]+}}) return ImplicitDefaultConstructor() } diff --git a/test/Interop/Cxx/class/constructors-irgen-windows.swift b/test/Interop/Cxx/class/constructors-irgen-windows.swift index 4239aedce8ad6..3038babc9d3d9 100644 --- a/test/Interop/Cxx/class/constructors-irgen-windows.swift +++ b/test/Interop/Cxx/class/constructors-irgen-windows.swift @@ -23,6 +23,7 @@ public func createImplicitDefaultConstructor() -> ImplicitDefaultConstructor { // MICROSOFT_X64: define dllexport swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0{{bcD0VyF|deF0VyF}}"() // MICROSOFT_X64-NOT: define // 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]+}}) return ImplicitDefaultConstructor() } diff --git a/test/Interop/Cxx/class/constructors-module-interface.swift b/test/Interop/Cxx/class/constructors-module-interface.swift index 5f39ea6e8c1aa..bf189b6e3024b 100644 --- a/test/Interop/Cxx/class/constructors-module-interface.swift +++ b/test/Interop/Cxx/class/constructors-module-interface.swift @@ -32,6 +32,9 @@ // CHECK-NEXT: struct CopyAndMoveConstructor { // CHECK-NEXT: @available(*, deprecated, message // CHECK-NEXT: init() +// CHECK-NEXT: init(value: Int32, ptr: UnsafeMutablePointer!) +// CHECK-NEXT: var value: Int32 +// CHECK-NEXT: var ptr: UnsafeMutablePointer! // CHECK-NEXT: } // CHECK-NEXT: struct Base { // CHECK-NEXT: init() diff --git a/test/Interop/Cxx/class/constructors-typechecker.swift b/test/Interop/Cxx/class/constructors-typechecker.swift index 770763aaaeae8..16509750a322d 100644 --- a/test/Interop/Cxx/class/constructors-typechecker.swift +++ b/test/Interop/Cxx/class/constructors-typechecker.swift @@ -7,6 +7,7 @@ let explicit = ExplicitDefaultConstructor() let implicit = ImplicitDefaultConstructor() 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}}