Skip to content

[cxx-interop] Fix metadata mismatch regarding fields of structs #81035

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
10 changes: 10 additions & 0 deletions lib/IRGen/Field.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,22 @@ struct Field {
bool operator!=(Field other) const { return declOrKind != other.declOrKind; }
};

bool isPrivateField(VarDecl *field);

// Don't export private C++ fields that were imported as private Swift fields.
// The type of a private field might not have all the type witness operations
// that Swift requires, for instance, `std::unique_ptr<IncompleteType>` would
// not have a destructor.
bool isExportableField(Field field);

/// Iterate all the fields of the given struct or class type, including
/// any implicit fields that might be accounted for in
/// getFieldVectorLength.
void forEachField(IRGenModule &IGM, const NominalTypeDecl *typeDecl,
llvm::function_ref<void(Field field)> fn);

unsigned countExportableFields(IRGenModule &IGM, const NominalTypeDecl *type);

} // end namespace irgen
} // end namespace swift

Expand Down
2 changes: 1 addition & 1 deletion lib/IRGen/GenMeta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1840,7 +1840,7 @@ namespace {

void addLayoutInfo() {
// uint32_t NumFields;
B.addInt32(getNumFields(getType()));
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we need to do something similar for ClassContextDescriptorBuilder too.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am less sure about emitInitializeFieldOffsetVector and co. Maybe those are good as is but maybe we want to ask someone familiar with type metadata.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wonder if we need to do something similar for ClassContextDescriptorBuilder too.

Yes I think you're right. I intend to check if we need a similar fix for classes and add those changes in a future PR

Regarding emitInitializeFieldOffsetVector, I'm also not sure. I'll investigate this further

B.addInt32(countExportableFields(IGM, getType()));

// uint32_t FieldOffsetVectorOffset;
B.addInt32(FieldVectorOffset / IGM.getPointerSize());
Expand Down
30 changes: 4 additions & 26 deletions lib/IRGen/GenReflection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -946,35 +946,13 @@ class FieldTypeMetadataBuilder : public ReflectionMetadataBuilder {
B.addInt16(uint16_t(kind));
B.addInt16(FieldRecordSize);

// Filter to select which fields we'll export FieldDescriptors for.
auto exportable_field =
[](Field field) {
// Don't export private C++ fields that were imported as private Swift fields.
// The type of a private field might not have all the type witness
// operations that Swift requires, for instance,
// `std::unique_ptr<IncompleteType>` would not have a destructor.
if (field.getKind() == Field::Kind::Var &&
field.getVarDecl()->getClangDecl() &&
field.getVarDecl()->getFormalAccess() == AccessLevel::Private)
return false;
// All other fields are exportable
return true;
};

// Count exportable fields
int exportableFieldCount = 0;
forEachField(IGM, NTD, [&](Field field) {
if (exportable_field(field)) {
++exportableFieldCount;
}
});

// Emit exportable fields, prefixed with a count
B.addInt32(exportableFieldCount);
B.addInt32(countExportableFields(IGM, NTD));

// Filter to select which fields we'll export FieldDescriptor for.
forEachField(IGM, NTD, [&](Field field) {
if (exportable_field(field)) {
if (isExportableField(field))
addField(field);
}
});
}

Expand Down
23 changes: 23 additions & 0 deletions lib/IRGen/StructLayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,18 @@ unsigned irgen::getNumFields(const NominalTypeDecl *target) {
return numFields;
}

bool irgen::isPrivateField(VarDecl *decl) {
return (decl->getClangDecl() &&
decl->getFormalAccess() == AccessLevel::Private);
}

bool irgen::isExportableField(Field field) {
if (field.getKind() == Field::Kind::Var && isPrivateField(field.getVarDecl()))
return false;
// All other fields are exportable
return true;
}

void irgen::forEachField(IRGenModule &IGM, const NominalTypeDecl *typeDecl,
llvm::function_ref<void(Field field)> fn) {
auto classDecl = dyn_cast<ClassDecl>(typeDecl);
Expand All @@ -610,6 +622,17 @@ void irgen::forEachField(IRGenModule &IGM, const NominalTypeDecl *typeDecl,
}
}

unsigned irgen::countExportableFields(IRGenModule &IGM,
const NominalTypeDecl *type) {
// Don't count private C++ fields that were imported as private Swift fields
int exportableFieldCount = 0;
forEachField(IGM, type, [&](Field field) {
if (isExportableField(field))
++exportableFieldCount;
});
return exportableFieldCount;
}

SILType Field::getType(IRGenModule &IGM, SILType baseType) const {
switch (getKind()) {
case Field::Var:
Expand Down
4 changes: 3 additions & 1 deletion lib/IRGen/StructMetadataVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#ifndef SWIFT_IRGEN_STRUCTMETADATALAYOUT_H
#define SWIFT_IRGEN_STRUCTMETADATALAYOUT_H

#include "Field.h"
#include "NominalMetadataVisitor.h"
#include "swift/AST/IRGenOptions.h"

Expand Down Expand Up @@ -64,7 +65,8 @@ template <class Impl> class StructMetadataVisitor
// Struct field offsets.
asImpl().noteStartOfFieldOffsets();
for (VarDecl *prop : Target->getStoredProperties())
asImpl().addFieldOffset(prop);
if (!isPrivateField(prop))
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we can get out of sync with emitInitializeFieldOffsetVector here.

asImpl().addFieldOffset(prop);

asImpl().noteEndOfFieldOffsets();

Expand Down
6 changes: 6 additions & 0 deletions test/Interop/Cxx/class/Inputs/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,9 @@ module PIMPL {
requires cplusplus
export *
}

module SimpleStructs {
header "simple-structs.h"
requires cplusplus
export *
}
53 changes: 53 additions & 0 deletions test/Interop/Cxx/class/Inputs/simple-structs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#ifndef TEST_INTEROP_CXX_CLASS_INPUTS_SIMPLE_STRUCTS_H
#define TEST_INTEROP_CXX_CLASS_INPUTS_SIMPLE_STRUCTS_H

#include <string>

struct HasPrivateFieldsOnly {
private:
int privInt;
std::string privStr;

public:
HasPrivateFieldsOnly(int i, std::string s) : privInt(i), privStr(s) {}
};

struct HasPublicFieldsOnly {
int pubInt;
std::string pubStr;

HasPublicFieldsOnly(int i, std::string s) : pubInt(i), pubStr(s) {}
};

struct HasIntFieldsOnly {
private:
int a = 1;

public:
int b = 2;

private:
int c = 3;

public:
int d = 4;
};

struct HasPrivateAndPublicFields {
private:
std::string privStr;

public:
int pubInt;

private:
int privInt;

public:
std::string pubStr;

HasPrivateAndPublicFields(int i1, int i2, std::string s1, std::string s2)
: privStr(s2), pubInt(i2), privInt(i1), pubStr(s1) {}
};

#endif
44 changes: 44 additions & 0 deletions test/Interop/Cxx/class/print.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -o %t/a.out -Xfrontend -enable-experimental-cxx-interop -I %S/Inputs
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-os

// REQUIRES: executable_test

import SimpleStructs

func printCxxStructPrivateFields() {
let s = HasPrivateFieldsOnly(42, "Hello")
print(s)
}

func printCxxStructPublicFields() {
let s = HasPublicFieldsOnly(42, "Hello")
print(s)
}

func printCxxStructIntFields() {
let s = HasIntFieldsOnly()
print(s)
}

func printCxxStructPrivateAndPublicFields() {
let s = HasPrivateAndPublicFields(24, 42, "Hello", "World")
print(s)
}

printCxxStructPrivateFields()
// CHECK: HasPrivateFieldsOnly()

printCxxStructIntFields()
// CHECK: HasIntFieldsOnly(b: 2, d: 4)

printCxxStructPublicFields()
// CHECK-macosx: HasPublicFieldsOnly(pubInt: 42, pubStr: __C.std.std::__1.basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>())
// CHECK-linux-gnu: HasPublicFieldsOnly(pubInt: 42, pubStr: __C.std.std::__cxx11.basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>())
// CHECK-windows-msvc: HasPublicFieldsOnly(pubInt: 42, pubStr: __C.std.basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>())

printCxxStructPrivateAndPublicFields()
// CHECK-macosx: HasPrivateAndPublicFields(pubInt: 42, pubStr: __C.std.std::__1.basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>())
// CHECK-linux-gnu: HasPrivateAndPublicFields(pubInt: 42, pubStr: __C.std.std::__cxx11.basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>())
// CHECK-windows-msvc: HasPrivateAndPublicFields(pubInt: 42, pubStr: __C.std.basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>())
6 changes: 6 additions & 0 deletions test/Interop/Cxx/stdlib/Inputs/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ module MsvcUseVecIt {
export *
}

module StdlibTypes {
header "stdlib-types.h"
requires cplusplus
export *
}

module StdUniquePtr {
header "std-unique-ptr.h"
requires cplusplus
Expand Down
24 changes: 24 additions & 0 deletions test/Interop/Cxx/stdlib/Inputs/stdlib-types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef TEST_INTEROP_CXX_STDLIB_INPUTS_STD_TYPES_H
#define TEST_INTEROP_CXX_STDLIB_INPUTS_STD_TYPES_H

#include <memory>
#include <string>
#include <vector>

std::unique_ptr<int> makeInt() { return std::make_unique<int>(42); }

std::unique_ptr<std::string> makeString() {
return std::make_unique<std::string>("Unique string");
}

std::shared_ptr<int> makeIntShared() { return std::make_unique<int>(42); }

std::shared_ptr<std::string> makeStringShared() {
return std::make_unique<std::string>("Shared string");
}

std::vector<int> makeVecOfInt() { return {1, 2, 3}; }

std::vector<std::string> makeVecOfString() { return {"Hello", "World"}; }

#endif
91 changes: 91 additions & 0 deletions test/Interop/Cxx/stdlib/print-stdlib-types.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -o %t/a.out -Xfrontend -enable-experimental-cxx-interop -I %S/Inputs
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-os

// REQUIRES: executable_test

import StdlibTypes

func printStdString(s: std.string) {
print(s)
let swiftString = String(s)
print(swiftString)
}

#if !os(Windows)
// FIXME: We can't import std::unique_ptr or std::shared_ptr properly on Windows (https://github.com/apple/swift/issues/70226)
func printStdUniquePtrOfInt() {
let uint = makeInt()
print(uint.pointee)
}

func printStdUniquePtrOfString() {
let ustring = makeString()
print(ustring.pointee)
}

func printStdSharedPtrOfInt() {
let sint = makeIntShared()
print(sint.pointee)
print(sint)
}

func printStdSharedPtrOfString() {
let sstring = makeStringShared()
print(sstring.pointee)
print(sstring)
}
#endif

func printStdVectorOfInt() {
let vecOfInt = makeVecOfInt()
print(vecOfInt[0])
print(vecOfInt)
}

func printStdVectorOfString() {
let vecOfString = makeVecOfString()
print(vecOfString[0])
print(vecOfString)
}

printStdString(s: "Hello")
// CHECK-macosx: basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>()
// CHECK-linux-gnu: basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>()
// CHECK-windows-msvc: basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>()
// CHECK: Hello

#if !os(Windows)
printStdUniquePtrOfInt()
// CHECK-macosx: 42
// CHECK-linux-gnu: 42
printStdUniquePtrOfString()
// CHECK-macosx: basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>()
// CHECK-linux-gnu: basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>()
printStdSharedPtrOfInt()
// CHECK-macosx: 42
// CHECK-macosx: shared_ptr<CInt>()
// CHECK-linux-gnu: 42
// CHECK-linux-gnu: shared_ptr<CInt>()
printStdSharedPtrOfString()
// CHECK-macosx: basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>()
Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively, you could add regexes that match on all platforms. I don't have a preference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion!
Hopefully we will soon fix the conformance of std::string to CustomStringConvertible. Then we can remove all these os options, since the value of the string will be printed instead of the type name.
That said, I don't mind switching to regex

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point, the string one is temporary (maybe not the shared_ptr one though). Let's leave it as is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh right!

// CHECK-macosx: shared_ptr<std.__1.basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>>()
// CHECK-linux-gnu: basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>()
// CHECK-linux-gnu: shared_ptr<std.__cxx11.basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>>()

#endif

printStdVectorOfInt()
// CHECK: 1
// CHECK-macosx: vector<CInt, std.__1.allocator<CInt>>()
// CHECK-linux-gnu: vector<CInt, std.allocator<CInt>>()
// CHECK-windows-msvc: vector<CInt, std.allocator<CInt>>()
printStdVectorOfString()
// CHECK-macosx: basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>()
// CHECK-macosx: vector<std.__1.basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>, std.__1.allocator<std.__1.basic_string<CChar, std.__1.char_traits<CChar>, std.__1.allocator<CChar>>>>()
// CHECK-linux-gnu: basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>()
// CHECK-linux-gnu: vector<std.__cxx11.basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>, std.allocator<std.__cxx11.basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>>>()
// CHECK-windows-msvc: basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>()
// CHECK-windows-msvc: vector<std.basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>, std.allocator<std.basic_string<CChar, std.char_traits<CChar>, std.allocator<CChar>>>>()