Skip to content

Commit cfe2728

Browse files
authored
Merge pull request #72903 from apple/egorzhdan/zero-init
[cxx-interop] Zero-initialize C++ structs when calling their default constructors
2 parents 2dfbceb + bac5d0e commit cfe2728

8 files changed

+62
-8
lines changed

lib/IRGen/GenDecl.cpp

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3476,7 +3476,12 @@ llvm::Constant *swift::irgen::emitCXXConstructorThunkIfNeeded(
34763476
llvm::FunctionType *ctorFnType =
34773477
cast<llvm::FunctionType>(clangFunc->getValueType());
34783478

3479-
if (assumedFnType == ctorFnType) {
3479+
// Only need a thunk if either:
3480+
// 1. The calling conventions do not match, and we need to pass arguments
3481+
// differently.
3482+
// 2. This is a default constructor, and we need to zero the backing memory of
3483+
// the struct.
3484+
if (assumedFnType == ctorFnType && !ctor->isDefaultConstructor()) {
34803485
return ctorAddress;
34813486
}
34823487

@@ -3510,14 +3515,34 @@ llvm::Constant *swift::irgen::emitCXXConstructorThunkIfNeeded(
35103515
Args.push_back(i);
35113516
}
35123517

3513-
clang::CodeGen::ImplicitCXXConstructorArgs implicitArgs =
3514-
clang::CodeGen::getImplicitCXXConstructorArgs(IGM.ClangCodeGen->CGM(),
3515-
ctor);
3516-
for (size_t i = 0; i < implicitArgs.Prefix.size(); ++i) {
3517-
Args.insert(Args.begin() + 1 + i, implicitArgs.Prefix[i]);
3518+
if (assumedFnType != ctorFnType) {
3519+
clang::CodeGen::ImplicitCXXConstructorArgs implicitArgs =
3520+
clang::CodeGen::getImplicitCXXConstructorArgs(IGM.ClangCodeGen->CGM(),
3521+
ctor);
3522+
for (size_t i = 0; i < implicitArgs.Prefix.size(); ++i) {
3523+
Args.insert(Args.begin() + 1 + i, implicitArgs.Prefix[i]);
3524+
}
3525+
for (const auto &arg : implicitArgs.Suffix) {
3526+
Args.push_back(arg);
3527+
}
35183528
}
3519-
for (const auto &arg : implicitArgs.Suffix) {
3520-
Args.push_back(arg);
3529+
3530+
if (ctor->isDefaultConstructor()) {
3531+
assert(Args.size() > 0 && "expected at least 1 argument (result address) "
3532+
"for default constructor");
3533+
3534+
// Zero out the backing memory of the struct.
3535+
// This makes default initializers for C++ structs behave consistently with
3536+
// the synthesized empty initializers for C structs. When C++ interop is
3537+
// enabled in a project, all imported C structs are treated as C++ structs,
3538+
// which sometimes means that Clang will synthesize a default constructor
3539+
// for the C++ struct that does not zero out trivial fields of a struct.
3540+
auto cxxRecord = ctor->getParent();
3541+
clang::ASTContext &ctx = cxxRecord->getASTContext();
3542+
auto typeSize = ctx.getTypeSizeInChars(ctx.getRecordType(cxxRecord));
3543+
subIGF.Builder.CreateMemSet(Args[0],
3544+
llvm::ConstantInt::get(subIGF.IGM.Int8Ty, 0),
3545+
typeSize.getQuantity(), llvm::MaybeAlign());
35213546
}
35223547

35233548
auto *call =

test/Interop/Cxx/class/Inputs/constructors.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ struct ConstructorWithParam {
3232
struct CopyAndMoveConstructor {
3333
CopyAndMoveConstructor(const CopyAndMoveConstructor &) = default;
3434
CopyAndMoveConstructor(CopyAndMoveConstructor &&) = default;
35+
36+
int value = 123;
37+
int *ptr = nullptr;
3538
};
3639

3740
struct Base {};

test/Interop/Cxx/class/constructors-executable.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,23 @@ CxxConstructorTestSuite.test("TemplatedConstructor") {
4444
expectEqual(2, instance.value.i)
4545
}
4646

47+
CxxConstructorTestSuite.test("implicit default ctor") {
48+
// Make sure that fields of C++ structs are zeroed out.
49+
50+
let instance1 = ConstructorWithParam()
51+
expectEqual(0, instance1.x)
52+
53+
let instance2 = IntWrapper()
54+
expectEqual(0, instance2.x)
55+
56+
// CopyAndMoveConstructor is not default-initializable in C++, however, Swift
57+
// generates an implicit deprecated default constructor for C++ structs for
58+
// compatibility with C. This constructor will zero out the entire backing
59+
// memory of the struct, including fields that have an init expression.
60+
// See `SwiftDeclSynthesizer::createDefaultConstructor`.
61+
let instance3 = CopyAndMoveConstructor()
62+
expectEqual(0, instance3.value)
63+
expectNil(instance3.ptr)
64+
}
65+
4766
runAllTests()

test/Interop/Cxx/class/constructors-irgen-android.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public func createImplicitDefaultConstructor() -> ImplicitDefaultConstructor {
2323
// ITANIUM_ARM: define protected swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0deF0VyF"()
2424
// ITANIUM_ARM-NOT: define
2525
// Note `this` return type.
26+
// ITANIUM_ARM: call void @llvm.memset.p0.i64
2627
// ITANIUM_ARM: call void @_ZN26ImplicitDefaultConstructorC2Ev(ptr %{{[0-9]+}})
2728
return ImplicitDefaultConstructor()
2829
}

test/Interop/Cxx/class/constructors-irgen-macosx.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public func createHasVirtualBase() -> HasVirtualBase {
1818
public func createImplicitDefaultConstructor() -> ImplicitDefaultConstructor {
1919
// ITANIUM_X64: define swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0deF0VyF"()
2020
// ITANIUM_X64-NOT: define
21+
// ITANIUM_X64: call void @llvm.memset.p0.i64
2122
// ITANIUM_X64: call void @_ZN26ImplicitDefaultConstructorC1Ev(ptr %{{[0-9]+}})
2223
return ImplicitDefaultConstructor()
2324
}

test/Interop/Cxx/class/constructors-irgen-windows.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public func createImplicitDefaultConstructor() -> ImplicitDefaultConstructor {
2323
// MICROSOFT_X64: define dllexport swiftcc i32 @"$s7MySwift32createImplicitDefaultConstructorSo0{{bcD0VyF|deF0VyF}}"()
2424
// MICROSOFT_X64-NOT: define
2525
// Note `this` return type but no implicit "most derived" argument.
26+
// MICROSOFT_X64: call void @llvm.memset.p0.i64
2627
// MICROSOFT_X64: call ptr @"??0ImplicitDefaultConstructor@@QEAA@XZ"(ptr %{{[0-9]+}})
2728
return ImplicitDefaultConstructor()
2829
}

test/Interop/Cxx/class/constructors-module-interface.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
// CHECK-NEXT: struct CopyAndMoveConstructor {
3333
// CHECK-NEXT: @available(*, deprecated, message
3434
// CHECK-NEXT: init()
35+
// CHECK-NEXT: init(value: Int32, ptr: UnsafeMutablePointer<Int32>!)
36+
// CHECK-NEXT: var value: Int32
37+
// CHECK-NEXT: var ptr: UnsafeMutablePointer<Int32>!
3538
// CHECK-NEXT: }
3639
// CHECK-NEXT: struct Base {
3740
// CHECK-NEXT: init()

test/Interop/Cxx/class/constructors-typechecker.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ let explicit = ExplicitDefaultConstructor()
77
let implicit = ImplicitDefaultConstructor()
88

99
let deletedImplicitly = ConstructorWithParam() // expected-warning {{'init()' is deprecated}}
10+
let onlyCopyAndMove = CopyAndMoveConstructor() // expected-warning {{'init()' is deprecated}}
1011

1112
let deletedExplicitly = DefaultConstructorDeleted() // expected-error {{missing argument for parameter 'a' in call}}
1213

0 commit comments

Comments
 (0)