Skip to content

[interop][SwiftToCxx] Add support for emitting resilient struct bindings #59482

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 6 commits into from
Jun 17, 2022
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
4 changes: 4 additions & 0 deletions lib/PrintAsClang/ClangSyntaxPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ using namespace cxx_synthesis;

StringRef cxx_synthesis::getCxxImplNamespaceName() { return "_impl"; }

StringRef cxx_synthesis::getCxxOpaqueStorageClassName() {
return "OpaqueStorage";
}

bool ClangSyntaxPrinter::isClangKeyword(StringRef name) {
static const llvm::DenseSet<StringRef> keywords = [] {
llvm::DenseSet<StringRef> set;
Expand Down
4 changes: 4 additions & 0 deletions lib/PrintAsClang/ClangSyntaxPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ namespace cxx_synthesis {
/// module in C++.
StringRef getCxxImplNamespaceName();

/// Return the name of the C++ class inside of `swift::_impl`
/// namespace that holds an opaque value, like a resilient struct.
StringRef getCxxOpaqueStorageClassName();

} // end namespace cxx_synthesis

class ClangSyntaxPrinter {
Expand Down
2 changes: 2 additions & 0 deletions lib/PrintAsClang/PrintAsClang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ static void writePrologue(raw_ostream &out, ASTContext &ctx,
"#include <cstddef>\n"
"#include <cstdbool>\n"
"#include <cstring>\n";
out << "#include <stdlib.h>\n";
out << "#if defined(_WIN32)\n#include <malloc.h>\n#endif\n";
},
[&] {
out << "#include <stdint.h>\n"
Expand Down
18 changes: 15 additions & 3 deletions lib/PrintAsClang/PrintClangFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ bool isKnownType(Type t, PrimitiveTypeMapping &typeMapping,
return getKnownTypeInfo(typeDecl, typeMapping, languageMode) != None;
}

bool isResilientType(Type t) {
if (auto *typeAliasType = dyn_cast<TypeAliasType>(t.getPointer()))
return isResilientType(typeAliasType->getSinglyDesugaredType());
else if (auto *nominalType = t->getNominalOrBoundGenericNominal())
return nominalType->isResilient();
return false;
}

bool isKnownCxxType(Type t, PrimitiveTypeMapping &typeMapping) {
return isKnownType(t, typeMapping, OutputLanguageMode::Cxx);
}
Expand Down Expand Up @@ -135,7 +143,8 @@ class CFunctionSignatureTypePrinter
// FIXME: Handle optional structures.
if (typeUseKind == FunctionSignatureTypeUse::ParamType) {
if (languageMode != OutputLanguageMode::Cxx &&
interopContext.getIrABIDetails().shouldPassIndirectly(ST)) {
(SD->isResilient() ||
interopContext.getIrABIDetails().shouldPassIndirectly(ST))) {
if (modifiersDelegate.prefixIndirectParamValueTypeInC)
(*modifiersDelegate.prefixIndirectParamValueTypeInC)(os);
// FIXME: it would be nice to print out the C struct type here.
Expand Down Expand Up @@ -198,7 +207,8 @@ void DeclAndTypeClangFunctionPrinter::printFunctionSignature(
bool isIndirectReturnType =
kind == FunctionSignatureKind::CFunctionProto &&
!isKnownCType(resultTy, typeMapping) &&
interopContext.getIrABIDetails().shouldReturnIndirectly(resultTy);
(isResilientType(resultTy) ||
interopContext.getIrABIDetails().shouldReturnIndirectly(resultTy));
if (!isIndirectReturnType) {
OptionalTypeKind retKind;
Type objTy;
Expand Down Expand Up @@ -285,7 +295,8 @@ void DeclAndTypeClangFunctionPrinter::printCxxToCFunctionParameterUse(
if (auto *structDecl = type->getStructOrBoundGenericStruct()) {
ClangValueTypePrinter(os, cPrologueOS, typeMapping, interopContext)
.printParameterCxxToCUseScaffold(
interopContext.getIrABIDetails().shouldPassIndirectly(type),
structDecl->isResilient() ||
interopContext.getIrABIDetails().shouldPassIndirectly(type),
structDecl, namePrinter, isInOut,
/*isSelf=*/paramRole &&
*paramRole == AdditionalParam::Role::Self);
Expand Down Expand Up @@ -352,6 +363,7 @@ void DeclAndTypeClangFunctionPrinter::printCxxThunkBody(
if (!isKnownCxxType(resultTy, typeMapping)) {
if (auto *structDecl = resultTy->getStructOrBoundGenericStruct()) {
bool isIndirect =
structDecl->isResilient() ||
interopContext.getIrABIDetails().shouldReturnIndirectly(resultTy);
ClangValueTypePrinter valueTypePrinter(os, cPrologueOS, typeMapping,
interopContext);
Expand Down
76 changes: 58 additions & 18 deletions lib/PrintAsClang/PrintClangValueType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,18 @@ void printCTypeMetadataTypeFunction(raw_ostream &os,
void ClangValueTypePrinter::printValueTypeDecl(
const NominalTypeDecl *typeDecl,
llvm::function_ref<void(void)> bodyPrinter) {
auto typeSizeAlign =
interopContext.getIrABIDetails().getTypeSizeAlignment(typeDecl);
if (!typeSizeAlign) {
// FIXME: handle non-fixed layout structs.
return;
}
if (typeSizeAlign->size == 0) {
// FIXME: How to represent 0 sized structs?
return;
llvm::Optional<IRABIDetailsProvider::SizeAndAlignment> typeSizeAlign;
if (!typeDecl->isResilient()) {

typeSizeAlign =
interopContext.getIrABIDetails().getTypeSizeAlignment(typeDecl);
assert(typeSizeAlign && "unknown layout for non-resilient type!");
if (typeSizeAlign->size == 0) {
// FIXME: How to represent 0 sized structs?
return;
}
}
bool isOpaqueLayout = !typeSizeAlign.hasValue();

ClangSyntaxPrinter printer(os);

Expand Down Expand Up @@ -138,6 +140,11 @@ void ClangValueTypePrinter::printValueTypeDecl(
os << " auto *vwTable = ";
printer.printValueWitnessTableAccessFromTypeMetadata("metadata");
os << ";\n";
if (isOpaqueLayout) {
os << " _storage = ";
printer.printSwiftImplQualifier();
os << cxx_synthesis::getCxxOpaqueStorageClassName() << "(vwTable);\n";
}
os << " vwTable->initializeWithCopy(_getOpaquePointer(), const_cast<char "
"*>(other._getOpaquePointer()), metadata._0);\n";
os << " }\n";
Expand All @@ -156,23 +163,55 @@ void ClangValueTypePrinter::printValueTypeDecl(
// Print out private default constructor.
os << " inline ";
printer.printBaseName(typeDecl);
os << "() {}\n";
if (isOpaqueLayout) {
os << "(";
printer.printSwiftImplQualifier();
os << "ValueWitnessTable * _Nonnull vwTable) : _storage(vwTable) {}\n";
} else {
os << "() {}\n";
}
// Print out '_make' function which returns an unitialized instance for
// passing to Swift.
os << " static inline ";
printer.printBaseName(typeDecl);
os << " _make() { return ";
printer.printBaseName(typeDecl);
os << "(); }\n";
os << " _make() {";
if (isOpaqueLayout) {
os << "\n";
os << " auto metadata = " << cxx_synthesis::getCxxImplNamespaceName()
<< "::";
printer.printSwiftTypeMetadataAccessFunctionCall(typeMetadataFuncName);
os << ";\n";
os << " return ";
printer.printBaseName(typeDecl);
os << "(";
printer.printValueWitnessTableAccessFromTypeMetadata("metadata");
os << ");\n }\n";
} else {
os << " return ";
printer.printBaseName(typeDecl);
os << "(); }\n";
}
// Print out the private accessors to the underlying Swift value storage.
os << " inline const char * _Nonnull _getOpaquePointer() const { return "
"_storage; }\n";
os << " inline char * _Nonnull _getOpaquePointer() { return _storage; }\n";
"_storage";
if (isOpaqueLayout)
os << ".getOpaquePointer()";
os << "; }\n";
os << " inline char * _Nonnull _getOpaquePointer() { return _storage";
if (isOpaqueLayout)
os << ".getOpaquePointer()";
os << "; }\n";
os << "\n";

// Print out the storage for the value type.
os << " alignas(" << typeSizeAlign->alignment << ") ";
os << "char _storage[" << typeSizeAlign->size << "];\n";
os << " ";
if (isOpaqueLayout) {
printer.printSwiftImplQualifier();
os << cxx_synthesis::getCxxOpaqueStorageClassName() << " _storage;\n";
} else {
os << "alignas(" << typeSizeAlign->alignment << ") ";
os << "char _storage[" << typeSizeAlign->size << "];\n";
}
// Wrap up the value type.
os << " friend class " << cxx_synthesis::getCxxImplNamespaceName() << "::";
printCxxImplClassName(os, typeDecl);
Expand Down Expand Up @@ -209,7 +248,8 @@ void ClangValueTypePrinter::printValueTypeDecl(
os << "};\n";
});

printCValueTypeStorageStruct(cPrologueOS, typeDecl, *typeSizeAlign);
if (!isOpaqueLayout)
printCValueTypeStorageStruct(cPrologueOS, typeDecl, *typeSizeAlign);
}

/// Print the name of the C stub struct for passing/returning a value type
Expand Down
61 changes: 61 additions & 0 deletions lib/PrintAsClang/PrintSwiftToClangCoreScaffold.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,63 @@ static void printTypeMetadataResponseType(SwiftToClangInteropContext &ctx,
funcSig.parameterTypes[0]);
}

static void printOpaqueAllocFee(raw_ostream &os) {
os << R"text(inline void * _Nonnull opaqueAlloc(size_t size, size_t align) {
#if defined(_WIN32)
void *r = _aligned_malloc(size, align);
#else
if (align < sizeof(void *)) align = sizeof(void *);
void *r = nullptr;
int res = posix_memalign(&r, align, size);
(void)res;
#endif
return r;
}
inline void opaqueFree(void * _Nonnull p) {
#if defined(_WIN32)
_aligned_free(p);
#else
free(p);
#endif
}
)text";
}

static void printSwiftResilientStorageClass(raw_ostream &os) {
auto name = cxx_synthesis::getCxxOpaqueStorageClassName();
static_assert(TargetValueWitnessFlags<uint64_t>::AlignmentMask ==
TargetValueWitnessFlags<uint32_t>::AlignmentMask,
"alignment mask doesn't match");
os << "/// Container for an opaque Swift value, like resilient struct.\n";
os << "class " << name << " {\n";
os << "public:\n";
os << " inline " << name << "() noexcept : storage(nullptr) { }\n";
os << " inline " << name
<< "(ValueWitnessTable * _Nonnull vwTable) noexcept : storage("
"reinterpret_cast<char *>(opaqueAlloc(vwTable->size, (vwTable->flags &"
<< TargetValueWitnessFlags<uint64_t>::AlignmentMask << ") + 1))) { }\n";
os << " inline " << name << "(" << name
<< "&& other) noexcept : storage(other.storage) { other.storage = "
"nullptr; }\n";
os << " inline " << name << "(const " << name << "&) noexcept = delete;\n";
os << " inline ~" << name
<< "() noexcept { if (storage) { opaqueFree(static_cast<char "
"* _Nonnull>(storage)); } }\n";
os << " void operator =(" << name
<< "&& other) noexcept { auto temp = storage; storage = other.storage; "
"other.storage = temp; }\n";
os << " void operator =(const " << name << "&) noexcept = delete;\n";
os << " inline char * _Nonnull getOpaquePointer() noexcept { return "
"static_cast<char "
"* _Nonnull>(storage); }\n";
os << " inline const char * _Nonnull getOpaquePointer() const noexcept { "
"return "
"static_cast<char * _Nonnull>(storage); }\n";
os << "private:\n";
os << " char * _Nullable storage;\n";
os << "};\n";
}

void swift::printSwiftToClangCoreScaffold(SwiftToClangInteropContext &ctx,
PrimitiveTypeMapping &typeMapping,
raw_ostream &os) {
Expand All @@ -130,6 +187,10 @@ void swift::printSwiftToClangCoreScaffold(SwiftToClangInteropContext &ctx,
os << "\n";
printValueWitnessTable(os);
});
os << "\n";
printOpaqueAllocFee(os);
os << "\n";
printSwiftResilientStorageClass(os);
});
});
}
19 changes: 19 additions & 0 deletions test/Inputs/clang-importer-sdk/usr/include/stdlib.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#if defined(_WIN32) || defined(WIN32)
#define SDK_STDLIB_H
#endif

#ifndef SDK_STDLIB_H
#define SDK_STDLIB_H

#include <stdint.h>

typedef long ldiv_t;
typedef long long lldiv_t;

int posix_memalign(void **, size_t, size_t);
void free(void *);

ldiv_t ldiv(long int, long int);
lldiv_t lldiv(long long int, long long int);

#endif // SDK_STDLIB_H
35 changes: 35 additions & 0 deletions test/Interop/SwiftToCxx/core/swift-impl-defs-in-cxx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,41 @@
// CHECK-NEXT: }
// CHECK-NEXT: #endif
// CHECK-EMPTY:
// CHECK-NEXT: inline void * _Nonnull opaqueAlloc(size_t size, size_t align) {
// CHECK-NEXT: #if defined(_WIN32)
// CHECK-NEXT: void *r = _aligned_malloc(size, align);
// CHECK-NEXT: #else
// CHECK-NEXT: if (align < sizeof(void *)) align = sizeof(void *);
// CHECK-NEXT: void *r = nullptr;
// CHECK-NEXT: int res = posix_memalign(&r, align, size);
// CHECK-NEXT: (void)res;
// CHECK-NEXT: #endif
// CHECK-NEXT: return r;
// CHECK-NEXT: }
// CHECK-NEXT: inline void opaqueFree(void * _Nonnull p) {
// CHECK-NEXT: #if defined(_WIN32)
// CHECK-NEXT: _aligned_free(p);
// CHECK-NEXT: #else
// CHECK-NEXT: free(p);
// CHECK-NEXT: #endif
// CHECK-NEXT: }
// CHECK-EMPTY:
// CHECK-NEXT: /// Container for an opaque Swift value, like resilient struct.
// CHECK-NEXT: class OpaqueStorage {
// CHECK-NEXT: public:
// CHECK-NEXT: inline OpaqueStorage() noexcept : storage(nullptr) { }
// CHECK-NEXT: inline OpaqueStorage(ValueWitnessTable * _Nonnull vwTable) noexcept : storage(reinterpret_cast<char *>(opaqueAlloc(vwTable->size, (vwTable->flags &255) + 1))) { }
// CHECK-NEXT: inline OpaqueStorage(OpaqueStorage&& other) noexcept : storage(other.storage) { other.storage = nullptr; }
// CHECK-NEXT: inline OpaqueStorage(const OpaqueStorage&) noexcept = delete;
// CHECK-NEXT: inline ~OpaqueStorage() noexcept { if (storage) { opaqueFree(static_cast<char * _Nonnull>(storage)); } }
// CHECK-NEXT: void operator =(OpaqueStorage&& other) noexcept { auto temp = storage; storage = other.storage; other.storage = temp; }
// CHECK-NEXT: void operator =(const OpaqueStorage&) noexcept = delete;
// CHECK-NEXT: inline char * _Nonnull getOpaquePointer() noexcept { return static_cast<char * _Nonnull>(storage); }
// CHECK-NEXT: inline const char * _Nonnull getOpaquePointer() const noexcept { return static_cast<char * _Nonnull>(storage); }
// CHECK-NEXT: private:
// CHECK-NEXT: char * _Nullable storage;
// CHECK-NEXT: };
// CHECK-EMPTY:
// CHECK-NEXT: } // namespace _impl
// CHECK-EMPTY:
// CHECK-EMPTY:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// RUN: %empty-directory(%t)

// RUN: %target-swift-frontend %S/resilient-struct-in-cxx.swift -enable-library-evolution -typecheck -module-name Structs -clang-header-expose-public-decls -emit-clang-header-path %t/structs.h

// RUN: %target-interop-build-clangxx -c %s -I %t -o %t/swift-structs-execution.o

// RUN: %target-interop-build-swift %S/resilient-struct-in-cxx.swift -enable-library-evolution -o %t/swift-structs-execution -Xlinker %t/swift-structs-execution.o -module-name Structs -Xfrontend -entry-point-function-name -Xfrontend swiftMain

// RUN: %target-codesign %t/swift-structs-execution
// RUN: %target-run %t/swift-structs-execution | %FileCheck --check-prefixes=CHECK,CURRENT %s

// RUN: %target-interop-build-swift %S/resilient-struct-in-cxx.swift -enable-library-evolution -o %t/swift-structs-execution-new -Xlinker %t/swift-structs-execution.o -module-name Structs -Xfrontend -entry-point-function-name -Xfrontend swiftMain -D CHANGE_LAYOUT

// RUN: %target-codesign %t/swift-structs-execution-new
// RUN: %target-run %t/swift-structs-execution-new | %FileCheck --check-prefixes=CHECK,CHANGE %s

// REQUIRES: executable_test

#include <assert.h>
#include "structs.h"

int main() {
using namespace Structs;

auto largeStruct = createLargeStruct(11);
assert(largeStruct.getX1() == 11);
largeStruct.dump();
// CHECK: x.1 = 11, .2 = -11, .3 = 22, .4 = 7, .5 = 0

auto smallStruct = largeStruct.getFirstSmallStruct();
assert(smallStruct.getX() == 65);
smallStruct.dump();
// CHECK: find - small dump
// CURRENT-NEXT: x = 65
// CHANGE-NEXT: x&y = 65&0

auto copySmallStruct = smallStruct;
mutateSmall(copySmallStruct);
copySmallStruct.dump();
// CHECK: find - small dump
// CURRENT-NEXT: x = 66
// CHANGE-NEXT: x&y = 0&65

printSmallAndLarge(smallStruct, largeStruct);
// CHECK: find - small dump
// CURRENT-NEXT: x = 65
// CHANGE-NEXT: x&y = 65&0
// CHECK-NEXT: x.1 = 11, .2 = -11, .3 = 22, .4 = 7, .5 = 0

{
auto structWithRefCountStoredProp =
createStructWithRefCountStoredProp();
structWithRefCountStoredProp.dump();
{
StructWithRefCountStoredProp copy(structWithRefCountStoredProp);
}
structWithRefCountStoredProp.dump();
}
// CHECK-NEXT: create RefCountedClass 0
// CHECK-NEXT: storedRef = 0
// CHECK-NEXT: storedRef = 0
// CHECK-NEXT: destroy RefCountedClass 0
return 0;
}
Loading