Skip to content

[interop][SwiftToCxx] Add initial support for passing/returning enums #59584

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 4 commits into from
Jun 23, 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
13 changes: 13 additions & 0 deletions lib/PrintAsClang/DeclAndTypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,19 @@ class DeclAndTypePrinter::Implementation

void visitEnumDecl(EnumDecl *ED) {
printDocumentationComment(ED);

if (outputLang == OutputLanguageMode::Cxx) {
// FIXME: Print enum's availability
ClangValueTypePrinter printer(os, owningPrinter.prologueOS,
owningPrinter.typeMapping,
owningPrinter.interopContext);
printer.printValueTypeDecl(
ED, /*bodyPrinter=*/[&]() {}); // TODO: (tongjie) print cases
os << outOfLineDefinitions;
outOfLineDefinitions.clear();
return;
}

os << "typedef ";
StringRef customName = getNameForObjC(ED, CustomNamesOnly);
if (customName.empty()) {
Expand Down
12 changes: 9 additions & 3 deletions lib/PrintAsClang/ModuleContentsWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@ class ModuleWriter {
}

void forwardDeclare(const EnumDecl *ED) {
// TODO: skip for now; will overhaul the forward decals for c++ in the
// future
if (outputLangMode == swift::OutputLanguageMode::Cxx) {
return;
}

assert(ED->isObjC() || ED->hasClangNode());

forwardDeclare(ED, [&]{
Expand Down Expand Up @@ -595,7 +601,9 @@ class ModuleWriter {
const Decl *D = declsToWrite.back();
bool success = true;

if (outputLangMode == OutputLanguageMode::Cxx) {
if (auto ED = dyn_cast<EnumDecl>(D)) {
success = writeEnum(ED);
} else if (outputLangMode == OutputLanguageMode::Cxx) {
if (auto FD = dyn_cast<FuncDecl>(D))
success = writeFunc(FD);
if (auto SD = dyn_cast<StructDecl>(D))
Expand All @@ -606,8 +614,6 @@ class ModuleWriter {
success = writeClass(CD);
else if (auto PD = dyn_cast<ProtocolDecl>(D))
success = writeProtocol(PD);
else if (auto ED = dyn_cast<EnumDecl>(D))
success = writeEnum(ED);
else if (auto ED = dyn_cast<FuncDecl>(D))
success = writeFunc(ED);
else
Expand Down
93 changes: 61 additions & 32 deletions lib/PrintAsClang/PrintClangFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,30 @@ class CFunctionSignatureTypePrinter
visitPart(aliasTy->getSinglyDesugaredType(), optionalKind, isInOutParam);
}

void visitEnumType(EnumType *ET, Optional<OptionalTypeKind> optionalKind,
bool isInOutParam) {
visitValueType(ET, optionalKind, isInOutParam);
}

void visitStructType(StructType *ST, Optional<OptionalTypeKind> optionalKind,
bool isInOutParam) {
const StructDecl *SD = ST->getStructOrBoundGenericStruct();
visitValueType(ST, optionalKind, isInOutParam);
}

void visitValueType(NominalType *NT, Optional<OptionalTypeKind> optionalKind,
bool isInOutParam) {
assert(isa<StructType>(NT) || isa<EnumType>(NT));
const auto *decl = NT->getNominalOrBoundGenericNominal();
assert(isa<StructDecl>(decl) || isa<EnumDecl>(decl));

// Handle known type names.
if (printIfKnownSimpleType(SD, optionalKind, isInOutParam))
if (printIfKnownSimpleType(decl, optionalKind, isInOutParam))
return;
// FIXME: Handle optional structures.
if (typeUseKind == FunctionSignatureTypeUse::ParamType) {
if (languageMode != OutputLanguageMode::Cxx &&
(SD->isResilient() ||
interopContext.getIrABIDetails().shouldPassIndirectly(ST))) {
(decl->isResilient() ||
interopContext.getIrABIDetails().shouldPassIndirectly(NT))) {
if (modifiersDelegate.prefixIndirectParamValueTypeInC)
(*modifiersDelegate.prefixIndirectParamValueTypeInC)(os);
// FIXME: it would be nice to print out the C struct type here.
Expand All @@ -156,11 +168,11 @@ class CFunctionSignatureTypePrinter

} else {
ClangValueTypePrinter(os, cPrologueOS, typeMapping, interopContext)
.printValueTypeParameterType(SD, languageMode, isInOutParam);
.printValueTypeParameterType(decl, languageMode, isInOutParam);
}
} else
ClangValueTypePrinter(os, cPrologueOS, typeMapping, interopContext)
.printValueTypeReturnType(SD, languageMode);
.printValueTypeReturnType(decl, languageMode);
}

void visitPart(Type Ty, Optional<OptionalTypeKind> optionalKind,
Expand Down Expand Up @@ -297,16 +309,19 @@ void DeclAndTypeClangFunctionPrinter::printCxxToCFunctionParameterUse(
Type type, StringRef name, bool isInOut, bool isIndirect,
llvm::Optional<AdditionalParam::Role> paramRole) {
auto namePrinter = [&]() { ClangSyntaxPrinter(os).printIdentifier(name); };
if (!isKnownCxxType(type, typeMapping)) {
if (auto *structDecl = type->getStructOrBoundGenericStruct()) {
ClangValueTypePrinter(os, cPrologueOS, typeMapping, interopContext)
.printParameterCxxToCUseScaffold(
isIndirect || structDecl->isResilient() ||
interopContext.getIrABIDetails().shouldPassIndirectly(type),
structDecl, namePrinter, isInOut,
/*isSelf=*/paramRole &&
*paramRole == AdditionalParam::Role::Self);
return;
if (!isKnownCxxType(type, typeMapping) &&
!hasKnownOptionalNullableCxxMapping(type)) {
if (auto *decl = type->getNominalOrBoundGenericNominal()) {
if ((isa<StructDecl>(decl) || isa<EnumDecl>(decl))) {
ClangValueTypePrinter(os, cPrologueOS, typeMapping, interopContext)
.printParameterCxxToCUseScaffold(
isIndirect || decl->isResilient() ||
interopContext.getIrABIDetails().shouldPassIndirectly(type),
decl, namePrinter, isInOut,
/*isSelf=*/paramRole &&
*paramRole == AdditionalParam::Role::Self);
return;
}
}
}
// Primitive types are passed directly without any conversions.
Expand Down Expand Up @@ -368,23 +383,26 @@ void DeclAndTypeClangFunctionPrinter::printCxxThunkBody(

// Values types are returned either direcly in their C representation, or
// indirectly by a pointer.
if (!isKnownCxxType(resultTy, typeMapping)) {
if (auto *structDecl = resultTy->getStructOrBoundGenericStruct()) {
bool isIndirect =
structDecl->isResilient() ||
interopContext.getIrABIDetails().shouldReturnIndirectly(resultTy);
ClangValueTypePrinter valueTypePrinter(os, cPrologueOS, typeMapping,
interopContext);
if (isIndirect) {
valueTypePrinter.printValueTypeIndirectReturnScaffold(
structDecl, [&](StringRef returnParam) {
printCallToCFunc(/*additionalParam=*/returnParam);
});
} else {
valueTypePrinter.printValueTypeDirectReturnScaffold(
structDecl, [&]() { printCallToCFunc(/*additionalParam=*/None); });
if (!isKnownCxxType(resultTy, typeMapping) &&
!hasKnownOptionalNullableCxxMapping(resultTy)) {
if (auto *decl = resultTy->getNominalOrBoundGenericNominal()) {
if ((isa<StructDecl>(decl) || isa<EnumDecl>(decl))) {
bool isIndirect =
decl->isResilient() ||
interopContext.getIrABIDetails().shouldReturnIndirectly(resultTy);
ClangValueTypePrinter valueTypePrinter(os, cPrologueOS, typeMapping,
interopContext);
if (isIndirect) {
valueTypePrinter.printValueTypeIndirectReturnScaffold(
decl, [&](StringRef returnParam) {
printCallToCFunc(/*additionalParam=*/returnParam);
});
} else {
valueTypePrinter.printValueTypeDirectReturnScaffold(
decl, [&]() { printCallToCFunc(/*additionalParam=*/None); });
}
return;
}
return;
}
}

Expand Down Expand Up @@ -476,3 +494,14 @@ void DeclAndTypeClangFunctionPrinter::printCxxPropertyAccessorMethod(
/*isIndirect=*/false}});
os << " }\n";
}

bool DeclAndTypeClangFunctionPrinter::hasKnownOptionalNullableCxxMapping(
Type type) {
if (auto optionalObjectType = type->getOptionalObjectType()) {
if (auto typeInfo = typeMapping.getKnownCxxTypeInfo(
optionalObjectType->getNominalOrBoundGenericNominal())) {
return typeInfo->canBeNullable;
}
}
return false;
}
2 changes: 2 additions & 0 deletions lib/PrintAsClang/PrintClangFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class DeclAndTypeClangFunctionPrinter {
Type type, StringRef name, bool isInOut, bool isIndirect = false,
llvm::Optional<AdditionalParam::Role> paramRole = None);

bool hasKnownOptionalNullableCxxMapping(Type type);

raw_ostream &os;
raw_ostream &cPrologueOS;
PrimitiveTypeMapping &typeMapping;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// RUN: %empty-directory(%t)

// RUN: %target-swift-frontend %S/large-enums-pass-return-in-cxx.swift -typecheck -module-name Enums -clang-header-expose-public-decls -emit-clang-header-path %t/enums.h

// RUN: %target-interop-build-clangxx -c %s -I %t -o %t/swift-enums-execution.o
// RUN: %target-interop-build-swift %S/large-enums-pass-return-in-cxx.swift -o %t/swift-enums-execution -Xlinker %t/swift-enums-execution.o -module-name Enums -Xfrontend -entry-point-function-name -Xfrontend swiftMain

// RUN: %target-codesign %t/swift-enums-execution
// RUN: %target-run %t/swift-enums-execution | %FileCheck %s

// REQUIRES: executable_test
// UNSUPPORTED: CPU=arm64e

#include <cassert>
#include <cstdint>
#include "enums.h"

int main() {
using namespace Enums;

// sizeof(generated cxx class) = 1 + max(sizeof(case) for all cases) + padding
static_assert(sizeof(Large) == 56, "MemoryLayout<Large>.stride == 56");

auto large = makeLarge(-1);
printLarge(large);
// CHECK: Large.second
inoutLarge(large, 10);
printLarge(large);
// CHECK: Large.first(-1, -2, -3, -4, -5, -6)
printLarge(passThroughLarge(large));
// CHECK: Large.first(-1, -2, -3, -4, -5, -6)

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend %s -typecheck -module-name Enums -clang-header-expose-public-decls -emit-clang-header-path %t/enums.h
// RUN: %FileCheck %s < %t/enums.h

// RUN: %check-interop-cxx-header-in-clang(%t/enums.h -Wno-unused-private-field -Wno-unused-function)

public enum Large {
case first(Int64, Int64, Int64, Int64, Int64, Int64)
case second
}

public func makeLarge(_ x: Int) -> Large {
return x >= 0 ? .first(0, 1, 2, 3, 4, 5) : .second
}

public func printLarge(_ en: Large) {
switch en {
case let .first(a, b, c, d, e, f):
let x = (a, b, c, d, e, f)
print("Large.first\(x)")
case .second:
print("Large.second")
}
}

public func passThroughLarge(_ en: Large) -> Large {
return en
}

public func inoutLarge(_ en: inout Large, _ x: Int) {
if x >= 0 {
en = .first(-1, -2, -3, -4, -5, -6)
} else {
en = .second
}
}

// CHECK: SWIFT_EXTERN void $s5Enums10inoutLargeyyAA0C0Oz_SitF(void * _Nonnull en, ptrdiff_t x) SWIFT_NOEXCEPT SWIFT_CALL; // inoutLarge(_:_:)
// CHECK: SWIFT_EXTERN void $s5Enums9makeLargeyAA0C0OSiF(SWIFT_INDIRECT_RESULT void * _Nonnull, ptrdiff_t x) SWIFT_NOEXCEPT SWIFT_CALL; // makeLarge(_:)
// CHECK: SWIFT_EXTERN void $s5Enums16passThroughLargeyAA0D0OADF(SWIFT_INDIRECT_RESULT void * _Nonnull, const void * _Nonnull en) SWIFT_NOEXCEPT SWIFT_CALL; // passThroughLarge(_:)
// CHECK: SWIFT_EXTERN void $s5Enums10printLargeyyAA0C0OF(const void * _Nonnull en) SWIFT_NOEXCEPT SWIFT_CALL; // printLarge(_:)
// CHECK: class Large final {

// CHECK: inline void inoutLarge(Large& en, swift::Int x) noexcept {
// CHECK-NEXT: return _impl::$s5Enums10inoutLargeyyAA0C0Oz_SitF(_impl::_impl_Large::getOpaquePointer(en), x);
// CHECK-NEXT: }

// CHECK: inline Large makeLarge(swift::Int x) noexcept SWIFT_WARN_UNUSED_RESULT {
// CHECK-NEXT: return _impl::_impl_Large::returnNewValue([&](void * _Nonnull result) {
// CHECK-NEXT: _impl::$s5Enums9makeLargeyAA0C0OSiF(result, x);
// CHECK-NEXT: });
// CHECK-NEXT: }

// CHECK: inline Large passThroughLarge(const Large& en) noexcept SWIFT_WARN_UNUSED_RESULT {
// CHECK-NEXT: return _impl::_impl_Large::returnNewValue([&](void * _Nonnull result) {
// CHECK-NEXT: _impl::$s5Enums16passThroughLargeyAA0D0OADF(result, _impl::_impl_Large::getOpaquePointer(en));
// CHECK-NEXT: });
// CHECK-NEXT: }

// CHECK: inline void printLarge(const Large& en) noexcept {
// CHECK-NEXT: return _impl::$s5Enums10printLargeyyAA0C0OF(_impl::_impl_Large::getOpaquePointer(en));
// CHECK-NEXT: }
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// RUN: %empty-directory(%t)

// RUN: %target-swift-frontend %S/small-enums-pass-return-in-cxx.swift -typecheck -module-name Enums -clang-header-expose-public-decls -emit-clang-header-path %t/enums.h

// RUN: %target-interop-build-clangxx -c %s -I %t -o %t/swift-enums-execution.o
// RUN: %target-interop-build-swift %S/small-enums-pass-return-in-cxx.swift -o %t/swift-enums-execution -Xlinker %t/swift-enums-execution.o -module-name Enums -Xfrontend -entry-point-function-name -Xfrontend swiftMain

// RUN: %target-codesign %t/swift-enums-execution
// RUN: %target-run %t/swift-enums-execution | %FileCheck %s

// REQUIRES: executable_test
// UNSUPPORTED: CPU=arm64e

#include <cassert>
#include <cstddef>
#include "enums.h"

#define MAX(a, b) ((a) >= (b) ? (a) : (b))

int main() {
using namespace Enums;

// sizeof(generated cxx class) = 1 + max(sizeof(case) for all cases) + padding
static_assert(sizeof(Tiny) == 1, "MemoryLayout<Tiny>.stride == 1");
static_assert(sizeof(Small) == 16, "MemoryLayout<Small>.stride == 16");

auto tiny = makeTiny(10);
printTiny(tiny);
// CHECK: Tiny.first
inoutTiny(tiny, -1);
printTiny(tiny);
// CHECK: Tiny.second
printTiny(passThroughTiny(tiny));
// CHECK: Tiny.second

auto small = makeSmall(-3);
printSmall(small);
// CHECK: Small.second(3.0)
inoutSmall(small, 100);
printSmall(small);
// CHECK: Small.first(100)
printSmall(passThroughSmall(small));
// CHECK: Small.first(100)

return 0;
}
Loading