Skip to content

[Interop][SwiftToCxx] add support for resilient enum #60451

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 1 commit into from
Aug 11, 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
8 changes: 7 additions & 1 deletion include/swift/IRGen/IRABIDetailsProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,15 @@ class IRABIDetailsProvider {
/// access function.
FunctionABISignature getTypeMetadataAccessFunctionSignature();

struct EnumElementInfo {
unsigned tag;
StringRef globalVariableName;
};

/// Returns EnumElementDecls (enum cases) in their declaration order with
/// their tag indices from the given EnumDecl
llvm::MapVector<EnumElementDecl *, unsigned> getEnumTagMapping(EnumDecl *ED);
llvm::MapVector<EnumElementDecl *, EnumElementInfo>
getEnumTagMapping(const EnumDecl *ED);

/// Returns the additional params if they exist after lowering the function.
SmallVector<ABIAdditionalParam, 1>
Expand Down
14 changes: 9 additions & 5 deletions lib/IRGen/IRABIDetailsProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,18 @@ class IRABIDetailsProviderImpl {
return {returnTy, {paramTy}};
}

llvm::MapVector<EnumElementDecl *, unsigned> getEnumTagMapping(EnumDecl *ED) {
llvm::MapVector<EnumElementDecl *, unsigned> elements;
llvm::MapVector<EnumElementDecl *, IRABIDetailsProvider::EnumElementInfo>
getEnumTagMapping(const EnumDecl *ED) {
llvm::MapVector<EnumElementDecl *, IRABIDetailsProvider::EnumElementInfo>
elements;
auto &enumImplStrat =
getEnumImplStrategy(IGM, ED->getDeclaredType()->getCanonicalType());

for (auto *element : ED->getAllElements()) {
auto tagIdx = enumImplStrat.getTagIndex(element);
elements.insert({element, tagIdx});
auto *global = cast<llvm::GlobalVariable>(
IGM.getAddrOfEnumCase(element, NotForDefinition).getAddress());
elements.insert({element, {tagIdx, global->getName()}});
}

return elements;
Expand Down Expand Up @@ -217,7 +221,7 @@ IRABIDetailsProvider::getTypeMetadataAccessFunctionSignature() {
return impl->getTypeMetadataAccessFunctionSignature();
}

llvm::MapVector<EnumElementDecl *, unsigned>
IRABIDetailsProvider::getEnumTagMapping(EnumDecl *ED) {
llvm::MapVector<EnumElementDecl *, IRABIDetailsProvider::EnumElementInfo>
IRABIDetailsProvider::getEnumTagMapping(const EnumDecl *ED) {
return impl->getEnumTagMapping(ED);
}
55 changes: 43 additions & 12 deletions lib/PrintAsClang/DeclAndTypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ class DeclAndTypePrinter::Implementation
owningPrinter.interopContext.getIrABIDetails().getEnumTagMapping(ED);
// Sort cases based on their assigned tag indices
llvm::stable_sort(elementTagMapping, [](const auto &p1, const auto &p2) {
return p1.second < p2.second;
return p1.second.tag < p2.second.tag;
});

if (elementTagMapping.empty()) {
Expand All @@ -429,18 +429,44 @@ class DeclAndTypePrinter::Implementation

// Printing operator cases()
os << " inline operator cases() const {\n";
os << " switch (_getEnumTag()) {\n";
for (const auto &pair : elementTagMapping) {
// TODO: have to use global variable for resilient enum
os << " case " << pair.second << ": return cases::";
syntaxPrinter.printIdentifier(pair.first->getNameStr());
os << ";\n";
if (ED->isResilient()) {
os << " auto tag = _getEnumTag();\n";
for (const auto &pair : elementTagMapping) {
os << " if (tag == " << cxx_synthesis::getCxxImplNamespaceName();
os << "::" << pair.second.globalVariableName << ") return cases::";
syntaxPrinter.printIdentifier(pair.first->getNameStr());
os << ";\n";
}
// TODO: change to Swift's fatalError when it's available in C++
os << " abort();\n";
} else { // non-resilient enum
os << " switch (_getEnumTag()) {\n";
for (const auto &pair : elementTagMapping) {
os << " case " << pair.second.tag << ": return cases::";
syntaxPrinter.printIdentifier(pair.first->getNameStr());
os << ";\n";
}
// TODO: change to Swift's fatalError when it's available in C++
os << " default: abort();\n";
os << " }\n"; // switch's closing bracket
}
// TODO: change to Swift's fatalError when it's available in C++
os << " default: abort();\n";
os << " }\n"; // switch's closing bracket
os << " }\n"; // operator cases()'s closing bracket

if (ED->isResilient()) {
os << " inline bool inResilientUnknownCase() const {\n";
os << " auto tag = _getEnumTag();\n";
os << " return";
llvm::interleave(
elementTagMapping, os,
[&](const auto &pair) {
os << "\n tag != " << cxx_synthesis::getCxxImplNamespaceName()
<< "::" << pair.second.globalVariableName;
},
" &&");
os << ";\n";
os << " }\n";
}

// Printing case-related functions
DeclAndTypeClangFunctionPrinter clangFuncPrinter(
os, owningPrinter.prologueOS, owningPrinter.typeMapping,
Expand All @@ -451,8 +477,13 @@ class DeclAndTypePrinter::Implementation
auto name = pair.first->getNameStr().str();
name[0] = std::toupper(name[0]);
os << name << "() const {\n";
os << " return *this == cases::";
syntaxPrinter.printIdentifier(pair.first->getNameStr());
os << " return _getEnumTag() == ";
if (ED->isResilient()) {
os << cxx_synthesis::getCxxImplNamespaceName()
<< "::" << pair.second.globalVariableName;
} else {
os << pair.second.tag;
}
os << ";\n }\n";

if (!pair.first->hasAssociatedValues()) {
Expand Down
34 changes: 23 additions & 11 deletions lib/PrintAsClang/PrintClangValueType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,22 @@ void ClangValueTypePrinter::printValueTypeDecl(
// access type metadata.
printer.printCTypeMetadataTypeFunction(
typeDecl, typeMetadataFuncName);
// Print out global variables for resilient enum
// cases
if (isa<EnumDecl>(typeDecl) && isOpaqueLayout) {
auto elementTagMapping =
interopContext.getIrABIDetails()
.getEnumTagMapping(
cast<EnumDecl>(typeDecl));
os << "// Tags for resilient enum ";
os << typeDecl->getName().str() << '\n';
os << "extern \"C\" {\n";
for (const auto &pair : elementTagMapping) {
os << "extern int "
<< pair.second.globalVariableName << ";\n";
}
os << "}\n";
}
});

auto printEnumVWTableVariable = [&](StringRef metadataName = "metadata",
Expand Down Expand Up @@ -265,17 +281,13 @@ void ClangValueTypePrinter::printValueTypeDecl(
os << " return result;\n";
os << " }\n";
// Print out helper function for initializeWithTake
// TODO: (tongjie) support opaque layout
if (!isOpaqueLayout) {
os << " static inline void initializeWithTake(char * _Nonnull "
"destStorage, char * _Nonnull srcStorage) {\n";
ClangValueTypePrinter::printValueWitnessTableAccessAsVariable(
os, typeMetadataFuncName);
os << " vwTable->initializeWithTake(destStorage, srcStorage, "
"metadata._0);\n";
os << " }\n";
}

os << " static inline void initializeWithTake(char * _Nonnull "
"destStorage, char * _Nonnull srcStorage) {\n";
ClangValueTypePrinter::printValueWitnessTableAccessAsVariable(
os, typeMetadataFuncName);
os << " vwTable->initializeWithTake(destStorage, srcStorage, "
"metadata._0);\n";
os << " }\n";
os << "};\n";
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// RUN: %empty-directory(%t)

// RUN: %target-swift-frontend %S/resilient-enum-in-cxx.swift -enable-library-evolution -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/resilient-enum-in-cxx.swift -enable-library-evolution -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 --check-prefixes=CHECK,OLD_CASE %s

// RUN: %target-interop-build-swift %S/resilient-enum-in-cxx.swift -enable-library-evolution -o %t//swift-enums-execution-new -Xlinker %t/swift-enums-execution.o -module-name Enums -Xfrontend -entry-point-function-name -Xfrontend swiftMain -D NEW_CASE
// RUN: %target-codesign %t/swift-enums-execution-new
// RUN: %target-run %t/swift-enums-execution-new | %FileCheck --check-prefixes=CHECK,NEW_CASE %s

// REQUIRES: executable_test

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

int main() {
using namespace Enums;
auto f1 = makeFoo(10);
auto f2 = makeFoo(-10);

printFoo(f1);
printFoo(f2);

assert(!f2.inResilientUnknownCase());
if (f1.inResilientUnknownCase()) {
std::cout << "f1.inResilientUnknownCase()\n";
assert(!f1.isA());
} else {
assert(f1.isA());
assert(f1.getA() == 10.0);
}

return 0;
}

// OLD_CASE: a(10.0)
// NEW_CASE: b(10)
// CHECK-NEXT: a(-10.0)

// NEW_CASE: f1.inResilientUnknownCase()
63 changes: 63 additions & 0 deletions test/Interop/SwiftToCxx/enums/resilient-enum-in-cxx.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// RUN: %empty-directory(%t)

// RUN: %target-swift-frontend %s -enable-library-evolution -typecheck -module-name Enums -clang-header-expose-public-decls -emit-clang-header-path %t/enums.h
// RUN: %FileCheck --check-prefixes=CHECK,OLD_CASE %s < %t/enums.h

// RUN: %target-swift-frontend %s -enable-library-evolution -D NEW_CASE -typecheck -module-name Enums -clang-header-expose-public-decls -emit-clang-header-path %t/enums_new_case.h
// RUN: %FileCheck --check-prefixes=CHECK,NEW_CASE %s < %t/enums_new_case.h

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

public enum Foo {
case a(Double)
#if NEW_CASE
case b(Int)
#endif
}

public func makeFoo(_ x: Int) -> Foo {
#if NEW_CASE
if x >= 0 {
return .b(x)
} else {
return .a(Double(x))
}
#else
return .a(Double(x))
#endif
}

public func printFoo(_ x: Foo) {
print(x)
}

// CHECK: // Tags for resilient enum Foo
// CHECK-NEXT: extern "C" {
// CHECK-NEXT: extern int $s5Enums3FooO1ayACSdcACmFWC;
// NEW_CASE-NEXT: extern int $s5Enums3FooO1byACSicACmFWC;
// CHECK-NEXT: }
// CHECK-EMPTY:
// CHECK-NEXT: } // namespace _impl
// CHECK-EMPTY:
// CHECK-NEXT: class Foo final {
// CHECK-NEXT: public:
// CHECK: inline operator cases() const {
// CHECK-NEXT: auto tag = _getEnumTag();
// CHECK-NEXT: if (tag == _impl::$s5Enums3FooO1ayACSdcACmFWC) return cases::a;
// NEW_CASE-NEXT: if (tag == _impl::$s5Enums3FooO1byACSicACmFWC) return cases::b;
// CHECK-NEXT: abort();
// CHECK-NEXT: }
// CHECK-NEXT: inline bool inResilientUnknownCase() const {
// CHECK-NEXT: auto tag = _getEnumTag();
// CHECK-NEXT: return
// OLD_CASE-NEXT: tag != _impl::$s5Enums3FooO1ayACSdcACmFWC;
// NEW_CASE-NEXT: tag != _impl::$s5Enums3FooO1ayACSdcACmFWC &&
// NEW_CASE-NEXT: tag != _impl::$s5Enums3FooO1byACSicACmFWC;
// CHECK-NEXT: }
// CHECK-NEXT: inline bool isA() const {
// CHECK-NEXT: return _getEnumTag() == _impl::$s5Enums3FooO1ayACSdcACmFWC;
// CHECK-NEXT: }
// NEW_CASE: inline bool isB() const {
// NEW_CASE-NEXT: return _getEnumTag() == _impl::$s5Enums3FooO1byACSicACmFWC;
// NEW_CASE-NEXT: }
34 changes: 17 additions & 17 deletions test/Interop/SwiftToCxx/enums/swift-enum-case-functions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,13 @@ public func checkIntDoubleOrBignum(_ x: IntDoubleOrBignum, tag: Int) -> Bool {
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: inline bool isSecond() const {
// CHECK-NEXT: return *this == cases::second;
// CHECK-NEXT: return _getEnumTag() == 0;
// CHECK-NEXT: }
// CHECK: inline bool isFirst() const {
// CHECK-NEXT: return *this == cases::first;
// CHECK-NEXT: return _getEnumTag() == 1;
// CHECK-NEXT: }
// CHECK-NEXT: inline bool isThird() const {
// CHECK-NEXT: return *this == cases::third;
// CHECK-NEXT: return _getEnumTag() == 2;
// CHECK-NEXT: }
// CHECK: inline int _getEnumTag() const {
// CHECK-NEXT: auto metadata = _impl::$s5Enums12BoolWithCaseOMa(0);
Expand All @@ -193,13 +193,13 @@ public func checkIntDoubleOrBignum(_ x: IntDoubleOrBignum, tag: Int) -> Bool {
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: inline bool isOne() const {
// CHECK-NEXT: return *this == cases::one;
// CHECK-NEXT: return _getEnumTag() == 0;
// CHECK-NEXT: }
// CHECK: inline bool isTwo() const {
// CHECK-NEXT: return *this == cases::two;
// CHECK-NEXT: return _getEnumTag() == 1;
// CHECK-NEXT: }
// CHECK: inline bool isThree() const {
// CHECK-NEXT: return *this == cases::three;
// CHECK-NEXT: return _getEnumTag() == 2;
// CHECK-NEXT: }
// CHECK: inline int _getEnumTag() const {
// CHECK-NEXT: auto metadata = _impl::$s5Enums9CLikeEnumOMa(0);
Expand All @@ -222,7 +222,7 @@ public func checkIntDoubleOrBignum(_ x: IntDoubleOrBignum, tag: Int) -> Bool {
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: inline bool isOne() const {
// CHECK-NEXT: return *this == cases::one;
// CHECK-NEXT: return _getEnumTag() == 0;
// CHECK-NEXT: }
// CHECK: inline int _getEnumTag() const {
// CHECK-NEXT: auto metadata = _impl::$s5Enums8DataCaseOMa(0);
Expand All @@ -247,13 +247,13 @@ public func checkIntDoubleOrBignum(_ x: IntDoubleOrBignum, tag: Int) -> Bool {
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: inline bool isInt() const {
// CHECK-NEXT: return *this == cases::Int;
// CHECK-NEXT: return _getEnumTag() == 0;
// CHECK-NEXT: }
// CHECK: inline bool isDouble() const {
// CHECK-NEXT: return *this == cases::Double;
// CHECK-NEXT: return _getEnumTag() == 1;
// CHECK-NEXT: }
// CHECK: inline bool isBignum() const {
// CHECK-NEXT: return *this == cases::Bignum;
// CHECK-NEXT: return _getEnumTag() == 2;
// CHECK-NEXT: }
// CHECK: inline int _getEnumTag() const {
// CHECK-NEXT: auto metadata = _impl::$s5Enums17IntDoubleOrBignumOMa(0);
Expand All @@ -278,13 +278,13 @@ public func checkIntDoubleOrBignum(_ x: IntDoubleOrBignum, tag: Int) -> Bool {
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: inline bool isInt() const {
// CHECK-NEXT: return *this == cases::Int;
// CHECK-NEXT: return _getEnumTag() == 0;
// CHECK-NEXT: }
// CHECK: inline bool isNegInfinity() const {
// CHECK-NEXT: return *this == cases::NegInfinity;
// CHECK-NEXT: return _getEnumTag() == 1;
// CHECK-NEXT: }
// CHECK: inline bool isPosInfinity() const {
// CHECK-NEXT: return *this == cases::PosInfinity;
// CHECK-NEXT: return _getEnumTag() == 2;
// CHECK-NEXT: }
// CHECK: inline int _getEnumTag() const {
// CHECK-NEXT: auto metadata = _impl::$s5Enums13IntOrInfinityOMa(0);
Expand All @@ -310,16 +310,16 @@ public func checkIntDoubleOrBignum(_ x: IntDoubleOrBignum, tag: Int) -> Bool {
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: inline bool isSecond() const {
// CHECK-NEXT: return *this == cases::second;
// CHECK-NEXT: return _getEnumTag() == 0;
// CHECK-NEXT: }
// CHECK: inline bool isThird() const {
// CHECK-NEXT: return *this == cases::third;
// CHECK-NEXT: return _getEnumTag() == 1;
// CHECK-NEXT: }
// CHECK: inline bool isFirst() const {
// CHECK-NEXT: return *this == cases::first;
// CHECK-NEXT: return _getEnumTag() == 2;
// CHECK-NEXT: }
// CHECK-NEXT: inline bool isFourth() const {
// CHECK-NEXT: return *this == cases::fourth;
// CHECK-NEXT: return _getEnumTag() == 3;
// CHECK-NEXT: }
// CHECK: inline int _getEnumTag() const {
// CHECK-NEXT: auto metadata = _impl::$s5Enums20MultipleBoolWithCaseOMa(0);
Expand Down