From 52287f872eb8c887abe3342e5e303ff5b888dad4 Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Wed, 18 Nov 2015 12:00:26 -0800 Subject: [PATCH] Initial implementation of a @_cdecl attribute to export top-level functions to C. There's an immediate need for this in the core libs, and we have most of the necessary pieces on hand to make it easy to implement. This is an unpolished initial implementation, with the following limitations, among others: - It doesn't support bridging error conventions, - It relies on ObjC interop, - It doesn't check for symbol name collisions, - It has an underscored name with required symbol name `@cdecl("symbol_name")`, awaiting official bikeshed painting. --- include/swift/AST/Attr.def | 4 + include/swift/AST/Attr.h | 18 +++ include/swift/AST/DiagnosticsSema.def | 10 +- include/swift/Serialization/ModuleFormat.h | 12 +- lib/AST/ASTContext.cpp | 5 +- lib/AST/ASTVerifier.cpp | 5 +- lib/AST/Attr.cpp | 7 + lib/IRGen/Linking.cpp | 2 +- lib/Parse/ParseDecl.cpp | 12 +- lib/PrintAsObjC/PrintAsObjC.cpp | 173 +++++++++++++++------ lib/SIL/SILDeclRef.cpp | 20 ++- lib/SILGen/SILGen.cpp | 8 + lib/Sema/TypeCheckAttr.cpp | 15 ++ lib/Sema/TypeCheckDecl.cpp | 13 ++ lib/Sema/TypeCheckType.cpp | 8 +- lib/Sema/TypeChecker.h | 1 + lib/Serialization/Deserialization.cpp | 8 + lib/Serialization/Serialization.cpp | 11 +- test/PrintAsObjC/cdecl.swift | 30 ++++ test/SILGen/cdecl.swift | 43 +++++ test/attr/attr_cdecl.swift | 55 +++++++ 21 files changed, 397 insertions(+), 63 deletions(-) create mode 100644 test/PrintAsObjC/cdecl.swift create mode 100644 test/SILGen/cdecl.swift create mode 100644 test/attr/attr_cdecl.swift diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index 38bad9b1f2504..d7a15f985eb78 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -245,6 +245,10 @@ SIMPLE_DECL_ATTR(warn_unqualified_access, WarnUnqualifiedAccess, SIMPLE_DECL_ATTR(_show_in_interface, ShowInInterface, OnProtocol | UserInaccessible, 62) +DECL_ATTR(_cdecl, CDecl, + OnFunc | LongAttribute | UserInaccessible, 63) + + #undef TYPE_ATTR #undef DECL_ATTR_ALIAS #undef SIMPLE_DECL_ATTR diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index bfcf7ab97b811..6d5267268d8a2 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -516,6 +516,24 @@ class SILGenNameAttr : public DeclAttribute { } }; +/// Defines the @_cdecl attribute. +class CDeclAttr : public DeclAttribute { +public: + CDeclAttr(StringRef Name, SourceLoc AtLoc, SourceRange Range, bool Implicit) + : DeclAttribute(DAK_CDecl, AtLoc, Range, Implicit), + Name(Name) {} + + CDeclAttr(StringRef Name, bool Implicit) + : CDeclAttr(Name, SourceLoc(), SourceRange(), /*Implicit=*/true) {} + + /// The symbol name. + const StringRef Name; + + static bool classof(const DeclAttribute *DA) { + return DA->getKind() == DAK_CDecl; + } +}; + /// Defines the @_semantics attribute. class SemanticsAttr : public DeclAttribute { public: diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index e17896565197c..f7ad542daa9a1 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -836,6 +836,14 @@ ERROR(no_objc_tagged_pointer_not_class_protocol,none, ERROR(swift_native_objc_runtime_base_not_on_root_class,none, "@_swift_native_objc_runtime_base_not_on_root_class can only be applied " "to root classes", ()) + +ERROR(cdecl_not_at_top_level,none, + "@_cdecl can only be applied to global functions", ()) +ERROR(cdecl_empty_name,none, + "@_cdecl symbol name cannot be empty", ()) +ERROR(cdecl_throws,none, + "raising errors from @_cdecl functions is not supported", ()) + ERROR(attr_methods_only,none, "only methods can be declared %0", (DeclAttribute)) ERROR(access_control_in_protocol,none, @@ -2487,7 +2495,7 @@ ERROR(objc_enum_case_multi,none, "'@objc' enum case declaration defines multiple enum cases with the same Objective-C name", ()) // If you change this, also change enum ObjCReason -#define OBJC_ATTR_SELECT "select{marked dynamic|marked @objc|marked @IBOutlet|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override}" +#define OBJC_ATTR_SELECT "select{marked @_cdecl|marked dynamic|marked @objc|marked @IBOutlet|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override}" ERROR(objc_invalid_on_var,none, "property cannot be %" OBJC_ATTR_SELECT "0 " diff --git a/include/swift/Serialization/ModuleFormat.h b/include/swift/Serialization/ModuleFormat.h index af745d21bec29..fe36cae428ef3 100644 --- a/include/swift/Serialization/ModuleFormat.h +++ b/include/swift/Serialization/ModuleFormat.h @@ -43,7 +43,7 @@ const unsigned char MODULE_DOC_SIGNATURE[] = { 0xE2, 0x9C, 0xA8, 0x07 }; /// Serialized module format major version number. /// -/// Always 0 for Swift 1.0. +/// Always 0 for Swift 1.x and 2.x. const uint16_t VERSION_MAJOR = 0; /// Serialized module format minor version number. @@ -53,7 +53,8 @@ const uint16_t VERSION_MAJOR = 0; /// in source control, you should also update the comment to briefly /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. -const uint16_t VERSION_MINOR = 239; // multiple results for SILFunctionType +/// describe what change you made. +const uint16_t VERSION_MINOR = 240; // Last change: @_cdecl using DeclID = PointerEmbeddedInt; using DeclIDField = BCFixed<31>; @@ -1223,6 +1224,13 @@ namespace decls_block { BCFixed<1>, // implicit flag BCBlob // _silgen_name >; + + using CDeclDeclAttrLayout = BCRecordLayout< + CDecl_DECL_ATTR, + BCFixed<1>, // implicit flag + BCBlob // _silgen_name + >; + using AlignmentDeclAttrLayout = BCRecordLayout< Alignment_DECL_ATTR, diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index f0d33d3460ad2..6db9fe927ba65 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -1908,7 +1908,10 @@ void AbstractFunctionDecl::setForeignErrorConvention( Optional AbstractFunctionDecl::getForeignErrorConvention() const { - if (!isObjC() || !isBodyThrowing()) return None; + if (!isObjC() && !getAttrs().hasAttribute()) + return None; + if (!isBodyThrowing()) + return None; auto &conventionsMap = getASTContext().Impl.ForeignErrorConventions; auto it = conventionsMap.find(this); if (it == conventionsMap.end()) return None; diff --git a/lib/AST/ASTVerifier.cpp b/lib/AST/ASTVerifier.cpp index 8a1db86400a10..c5ed2bba76c98 100644 --- a/lib/AST/ASTVerifier.cpp +++ b/lib/AST/ASTVerifier.cpp @@ -2200,8 +2200,9 @@ struct ASTNodeBase {}; abort(); } - if (AFD->getForeignErrorConvention() && !AFD->isObjC()) { - Out << "foreign error convention on non-@objc function\n"; + if (AFD->getForeignErrorConvention() + && !AFD->isObjC() && !AFD->getAttrs().hasAttribute()) { + Out << "foreign error convention on non-@objc, non-@_cdecl function\n"; AFD->dump(Out); abort(); } diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index cb470ce69bff4..7d69f2e53d632 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -372,6 +372,11 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options) if (cast(this)->isEscaping()) Printer << "(escaping)"; break; + + case DAK_CDecl: + Printer << "@_cdecl(\"" << cast(this)->Name << "\")"; + break; + case DAK_ObjC: { Printer.printAttrName("@objc"); llvm::SmallString<32> scratch; @@ -487,6 +492,8 @@ StringRef DeclAttribute::getAttrName() const { return "_silgen_name"; case DAK_Alignment: return "_alignment"; + case DAK_CDecl: + return "_cdecl"; case DAK_SwiftNativeObjCRuntimeBase: return "_swift_native_objc_runtime_base"; case DAK_Semantics: diff --git a/lib/IRGen/Linking.cpp b/lib/IRGen/Linking.cpp index 93b51864b8dd3..4e67190f66625 100644 --- a/lib/IRGen/Linking.cpp +++ b/lib/IRGen/Linking.cpp @@ -232,7 +232,7 @@ void LinkEntity::mangle(raw_ostream &buffer) const { // entity ::= declaration // other declaration case Kind::Function: - // As a special case, functions can have external asm names. + // As a special case, functions can have manually mangled names. if (auto AsmA = getDecl()->getAttrs().getAttribute()) { mangler.append(AsmA->Name); return mangler.finalize(buffer); diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 7b9a3030e9d23..408a385fc750c 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -565,6 +565,7 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc, break; } + case DAK_CDecl: case DAK_SILGenName: { if (!consumeIf(tok::l_paren)) { diagnose(Loc, diag::attr_expected_lparen, AttrName, @@ -601,9 +602,16 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc, diagnose(Loc, diag::attr_only_at_non_local_scope, AttrName); } - if (!DiscardAttribute) - Attributes.add(new (Context) SILGenNameAttr(AsmName.getValue(), AtLoc, + if (!DiscardAttribute) { + if (DK == DAK_SILGenName) + Attributes.add(new (Context) SILGenNameAttr(AsmName.getValue(), AtLoc, + AttrRange, /*Implicit=*/false)); + else if (DK == DAK_CDecl) + Attributes.add(new (Context) CDeclAttr(AsmName.getValue(), AtLoc, AttrRange, /*Implicit=*/false)); + else + llvm_unreachable("out of sync with switch"); + } break; } diff --git a/lib/PrintAsObjC/PrintAsObjC.cpp b/lib/PrintAsObjC/PrintAsObjC.cpp index dcd1178e050de..56df3cf48f8f5 100644 --- a/lib/PrintAsObjC/PrintAsObjC.cpp +++ b/lib/PrintAsObjC/PrintAsObjC.cpp @@ -91,7 +91,8 @@ static Identifier getNameForObjC(const ValueDecl *VD, namespace { class ObjCPrinter : private DeclVisitor, private TypeVisitor> { + Optional> +{ friend ASTVisitor; friend TypeVisitor; @@ -120,7 +121,8 @@ class ObjCPrinter : private DeclVisitor, } bool shouldInclude(const ValueDecl *VD) { - return VD->isObjC() && VD->getFormalAccess() >= minRequiredAccess && + return (VD->isObjC() || VD->getAttrs().hasAttribute()) && + VD->getFormalAccess() >= minRequiredAccess && !(isa(VD) && cast(VD)->hasStubImplementation()); } @@ -333,48 +335,52 @@ class ObjCPrinter : private DeclVisitor, return true; } - void printAbstractFunction(AbstractFunctionDecl *AFD, bool isClassMethod, - bool isNSUIntegerSubscript = false) { - printDocumentationComment(AFD); - if (isClassMethod) - os << "+ ("; - else - os << "- ("; - - const clang::ObjCMethodDecl *clangMethod = nullptr; - if (!isNSUIntegerSubscript) { - if (const AbstractFunctionDecl *clangBase = findClangBase(AFD)) { - clangMethod = - dyn_cast_or_null(clangBase->getClangDecl()); - } - } - - Type rawMethodTy = AFD->getType()->castTo()->getResult(); - auto methodTy = rawMethodTy->castTo(); - + Type getForeignResultType(AbstractFunctionDecl *AFD, + FunctionType *methodTy, + Optional errorConvention) { // A foreign error convention can affect the result type as seen in // Objective-C. - Optional errorConvention - = AFD->getForeignErrorConvention(); - Type resultTy = methodTy->getResult(); if (errorConvention) { switch (errorConvention->getKind()) { case ForeignErrorConvention::ZeroResult: case ForeignErrorConvention::NonZeroResult: // The error convention provides the result type. - resultTy = errorConvention->getResultType(); - break; + return errorConvention->getResultType(); case ForeignErrorConvention::NilResult: // Errors are propagated via 'nil' returns. - resultTy = OptionalType::get(resultTy); - break; + return OptionalType::get(methodTy->getResult()); case ForeignErrorConvention::NonNilError: case ForeignErrorConvention::ZeroPreservedResult: break; } } + return methodTy->getResult(); + } + + void printAbstractFunctionAsMethod(AbstractFunctionDecl *AFD, + bool isClassMethod, + bool isNSUIntegerSubscript = false) { + printDocumentationComment(AFD); + if (isClassMethod) + os << "+ ("; + else + os << "- ("; + + const clang::ObjCMethodDecl *clangMethod = nullptr; + if (!isNSUIntegerSubscript) { + if (const AbstractFunctionDecl *clangBase = findClangBase(AFD)) { + clangMethod = + dyn_cast_or_null(clangBase->getClangDecl()); + } + } + + Optional errorConvention + = AFD->getForeignErrorConvention(); + Type rawMethodTy = AFD->getType()->castTo()->getResult(); + auto methodTy = rawMethodTy->castTo(); + auto resultTy = getForeignResultType(AFD, methodTy, errorConvention); // Constructors and methods returning DynamicSelf return // instancetype. @@ -464,15 +470,63 @@ class ObjCPrinter : private DeclVisitor, os << ";\n"; } + + void printSingleFunctionParam(const ParamDecl *param) { + // The type name may be multi-part. + PrintMultiPartType multiPart(*this); + visitPart(param->getType(), OTK_None); + auto name = param->getName(); + if (name.empty()) + return; + + os << ' ' << name; + if (isClangKeyword(name)) + os << "_"; + } + void printAbstractFunctionAsFunction(FuncDecl *FD) { + printDocumentationComment(FD); + Optional errorConvention + = FD->getForeignErrorConvention(); + auto resultTy = getForeignResultType(FD, + FD->getType()->castTo(), + errorConvention); + + // The result type may be a partial function type we need to close + // up later. + PrintMultiPartType multiPart(*this); + visitPart(resultTy, OTK_None); + + assert(FD->getAttrs().hasAttribute() + && "not a cdecl function"); + + os << ' ' << FD->getAttrs().getAttribute()->Name << '('; + + assert(FD->getParameterLists().size() == 1 && "not a C-compatible func"); + auto params = FD->getParameterLists().back(); + interleave(*params, + [&](const ParamDecl *param) { + printSingleFunctionParam(param); + }, + [&]{ os << ", "; }); + + os << ')'; + + // Finish the result type. + multiPart.finish(); + + os << ';'; + } + void visitFuncDecl(FuncDecl *FD) { - assert(FD->getDeclContext()->isTypeContext() && - "cannot handle free functions right now"); - printAbstractFunction(FD, FD->isStatic()); + if (FD->getDeclContext()->isTypeContext()) + printAbstractFunctionAsMethod(FD, FD->isStatic()); + else + printAbstractFunctionAsFunction(FD); } void visitConstructorDecl(ConstructorDecl *CD) { - printAbstractFunction(CD, false); + printAbstractFunctionAsMethod(CD, false); } bool maybePrintIBOutletCollection(Type ty) { @@ -514,9 +568,9 @@ class ObjCPrinter : private DeclVisitor, if (VD->isStatic()) { // Objective-C doesn't have class properties. Just print the accessors. - printAbstractFunction(VD->getGetter(), true); + printAbstractFunctionAsMethod(VD->getGetter(), true); if (auto setter = VD->getSetter()) - printAbstractFunction(setter, true); + printAbstractFunctionAsMethod(setter, true); return; } @@ -635,9 +689,9 @@ class ObjCPrinter : private DeclVisitor, isNSUIntegerSubscript = isNSUInteger(indexParam->getType()); } - printAbstractFunction(SD->getGetter(), false, isNSUIntegerSubscript); + printAbstractFunctionAsMethod(SD->getGetter(), false, isNSUIntegerSubscript); if (auto setter = SD->getSetter()) - printAbstractFunction(setter, false, isNSUIntegerSubscript); + printAbstractFunctionAsMethod(setter, false, isNSUIntegerSubscript); } /// Visit part of a type, such as the base of a pointer type. @@ -1169,6 +1223,33 @@ class ObjCPrinter : private DeclVisitor, Optional optionalKind) { visitPart(RST->getReferentType(), optionalKind); } + + /// RAII class for printing multi-part C types, such as functions and arrays. + class PrintMultiPartType { + ObjCPrinter &Printer; + decltype(ObjCPrinter::openFunctionTypes) savedFunctionTypes; + + PrintMultiPartType(const PrintMultiPartType &) = delete; + public: + PrintMultiPartType(ObjCPrinter &Printer) + : Printer(Printer) { + savedFunctionTypes.swap(Printer.openFunctionTypes); + } + + void finish() { + auto &openFunctionTypes = Printer.openFunctionTypes; + while (!openFunctionTypes.empty()) { + const FunctionType *openFunctionTy = openFunctionTypes.pop_back_val(); + Printer.finishFunctionType(openFunctionTy); + } + openFunctionTypes = std::move(savedFunctionTypes); + savedFunctionTypes.clear(); + } + + ~PrintMultiPartType() { + finish(); + } + }; /// Print a full type, optionally declaring the given \p name. /// @@ -1180,9 +1261,7 @@ class ObjCPrinter : private DeclVisitor, Identifier name = Identifier()) { PrettyStackTraceType trace(M.getASTContext(), "printing", ty); - decltype(openFunctionTypes) savedFunctionTypes; - savedFunctionTypes.swap(openFunctionTypes); - + PrintMultiPartType multiPart(*this); visitPart(ty, optionalKind); if (!name.empty()) { os << ' ' << name; @@ -1190,12 +1269,6 @@ class ObjCPrinter : private DeclVisitor, os << '_'; } } - while (!openFunctionTypes.empty()) { - const FunctionType *openFunctionTy = openFunctionTypes.pop_back_val(); - finishFunctionType(openFunctionTy); - } - - openFunctionTypes = std::move(savedFunctionTypes); } }; @@ -1485,6 +1558,14 @@ class ModuleWriter { printer.print(CD); return true; } + + bool writeFunc(const FuncDecl *FD) { + if (addImport(FD)) + return true; + + printer.print(FD); + return true; + } bool writeProtocol(const ProtocolDecl *PD) { if (addImport(PD)) @@ -1834,6 +1915,8 @@ class ModuleWriter { success = writeProtocol(PD); else if (auto ED = dyn_cast(D)) success = writeEnum(ED); + else if (auto ED = dyn_cast(D)) + success = writeFunc(ED); else llvm_unreachable("unknown top-level ObjC value decl"); diff --git a/lib/SIL/SILDeclRef.cpp b/lib/SIL/SILDeclRef.cpp index 23a8c3c7d2792..3239c3667dbce 100644 --- a/lib/SIL/SILDeclRef.cpp +++ b/lib/SIL/SILDeclRef.cpp @@ -325,7 +325,10 @@ SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const { // Currying and calling convention thunks have shared linkage. if (isThunk()) - return SILLinkage::Shared; + // If a function declares a @_cdecl name, its native-to-foreign thunk is + // exported with the visibility of the function. + if (!isNativeToForeignThunk() || !d->getAttrs().hasAttribute()) + return SILLinkage::Shared; // Enum constructors are essentially the same as thunks, they are // emitted by need and have shared linkage. @@ -483,13 +486,20 @@ static std::string mangleConstant(SILDeclRef c, StringRef prefix) { return mangler.finalize(); } - // As a special case, functions can have external asm names. - // Use the asm name only for the original non-thunked, non-curried entry + // As a special case, functions can have manually mangled names. + // Use the SILGen name only for the original non-thunked, non-curried entry // point. - if (auto AsmA = c.getDecl()->getAttrs().getAttribute()) + if (auto NameA = c.getDecl()->getAttrs().getAttribute()) if (!c.isForeignToNativeThunk() && !c.isNativeToForeignThunk() && !c.isCurried) { - mangler.append(AsmA->Name); + mangler.append(NameA->Name); + return mangler.finalize(); + } + + // Use a given cdecl name for native-to-foreign thunks. + if (auto CDeclA = c.getDecl()->getAttrs().getAttribute()) + if (c.isNativeToForeignThunk()) { + mangler.append(CDeclA->Name); return mangler.finalize(); } diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index f3978dd92e674..278609f1926be 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -435,6 +435,14 @@ void SILGenModule::emitAbstractFuncDecl(AbstractFunctionDecl *AFD) { if (!Captures.empty()) TopLevelSGF->B.createMarkFunctionEscape(AFD, Captures); } + + // If the declaration is exported as a C function, emit its native-to-foreign + // thunk too, if it wasn't already forced. + if (AFD->getAttrs().hasAttribute()) { + auto thunk = SILDeclRef(AFD).asForeign(); + if (!hasFunction(thunk)) + emitNativeToForeignThunk(thunk); + } } static bool hasSILBody(FuncDecl *fd) { diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index f0b9778a5502e..c2716604b7ba8 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -47,6 +47,7 @@ class AttributeEarlyChecker : public AttributeVisitor { bool visitDeclAttribute(DeclAttribute *A) = delete; #define IGNORED_ATTR(X) void visit##X##Attr(X##Attr *) {} + IGNORED_ATTR(CDecl) IGNORED_ATTR(SILGenName) IGNORED_ATTR(Available) IGNORED_ATTR(Convenience) @@ -652,6 +653,8 @@ class AttributeChecker : public AttributeVisitor { #undef IGNORED_ATTR void visitAvailableAttr(AvailableAttr *attr); + + void visitCDeclAttr(CDeclAttr *attr); void visitFinalAttr(FinalAttr *attr); void visitIBActionAttr(IBActionAttr *attr); @@ -853,6 +856,18 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *attr) { } } +void AttributeChecker::visitCDeclAttr(CDeclAttr *attr) { + // Only top-level func decls are currently supported. + if (D->getDeclContext()->isTypeContext()) + TC.diagnose(attr->getLocation(), + diag::cdecl_not_at_top_level); + + // The name must not be empty. + if (attr->Name.empty()) + TC.diagnose(attr->getLocation(), + diag::cdecl_empty_name); +} + void AttributeChecker::visitUnsafeNoObjCTaggedPointerAttr( UnsafeNoObjCTaggedPointerAttr *attr) { // Only class protocols can have the attribute. diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index 0fc9248c47eab..bc2340f57c9e5 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -4414,6 +4414,18 @@ class DeclChecker : public DeclVisitor { markAsObjC(TC, FD, isObjC, errorConvention); } + // If the function is exported to C, it must be representable in (Obj-)C. + if (auto CDeclAttr = FD->getAttrs().getAttribute()) { + Optional errorConvention; + if (TC.isRepresentableInObjC(FD, ObjCReason::ExplicitlyCDecl, + errorConvention)) { + if (FD->isBodyThrowing()) { + FD->setForeignErrorConvention(*errorConvention); + TC.diagnose(CDeclAttr->getLocation(), diag::cdecl_throws); + } + } + } + inferDynamic(TC.Context, FD); TC.checkDeclAttributes(FD); @@ -5056,6 +5068,7 @@ class DeclChecker : public DeclVisitor { UNINTERESTING_ATTR(Accessibility) UNINTERESTING_ATTR(Alignment) + UNINTERESTING_ATTR(CDecl) UNINTERESTING_ATTR(SILGenName) UNINTERESTING_ATTR(Exported) UNINTERESTING_ATTR(IBAction) diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 3f9686330e7a4..54516250d0ba0 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -2633,9 +2633,10 @@ bool TypeChecker::isRepresentableInObjC( if (auto *FD = dyn_cast(AFD)) { if (FD->isAccessor()) { // Accessors can only be @objc if the storage declaration is. + // Global computed properties may however @_cdecl their accessors. auto storage = FD->getAccessorStorageDecl(); validateDecl(storage); - if (!storage->isObjC()) { + if (!storage->isObjC() && Reason != ObjCReason::ExplicitlyCDecl) { if (Diagnose) { auto error = FD->isGetter() ? (isa(storage) @@ -2690,7 +2691,8 @@ bool TypeChecker::isRepresentableInObjC( isSpecialInit = init->isObjCZeroParameterWithLongSelector(); if (!isSpecialInit && - !isParamListRepresentableInObjC(*this, AFD, AFD->getParameterList(1), + !isParamListRepresentableInObjC(*this, AFD, + AFD->getParameterLists().back(), Reason)) { if (!Diagnose) { // Return as soon as possible if we are not producing diagnostics. @@ -2826,7 +2828,7 @@ bool TypeChecker::isRepresentableInObjC( // If the selector did not provide an index for the error, find // the last parameter that is not a trailing closure. if (!foundErrorParameterIndex) { - auto *paramList = AFD->getParameterList(1); + auto *paramList = AFD->getParameterLists().back(); errorParameterIndex = paramList->size(); while (errorParameterIndex > 0) { // Skip over trailing closures. diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index 048ddd9380fe9..e01122b4de2ec 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -399,6 +399,7 @@ withoutContext(TypeResolutionOptions options) { /// the OBJC_ATTR_SELECT macro in DiagnosticsSema.def. enum class ObjCReason { DoNotDiagnose, + ExplicitlyCDecl, ExplicitlyDynamic, ExplicitlyObjC, ExplicitlyIBOutlet, diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 4269e8e2a46e2..7a06cda69622e 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -1884,6 +1884,14 @@ Decl *ModuleFile::getDecl(DeclID DID, Optional ForcedContext) { break; } + case decls_block::CDecl_DECL_ATTR: { + bool isImplicit; + serialization::decls_block::CDeclDeclAttrLayout::readRecord( + scratch, isImplicit); + Attr = new (ctx) CDeclAttr(blobData, isImplicit); + break; + } + case decls_block::Alignment_DECL_ATTR: { bool isImplicit; unsigned alignment; diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index 94d2e16244d2f..efbba010b4efb 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -1621,7 +1621,16 @@ void Serializer::writeDeclAttribute(const DeclAttribute *DA) { theAttr->Name); return; } - + + case DAK_CDecl: { + auto *theAttr = cast(DA); + auto abbrCode = DeclTypeAbbrCodes[CDeclDeclAttrLayout::Code]; + CDeclDeclAttrLayout::emitRecord(Out, ScratchRecord, abbrCode, + theAttr->isImplicit(), + theAttr->Name); + return; + } + case DAK_Alignment: { auto *theAlignment = cast(DA); auto abbrCode = DeclTypeAbbrCodes[AlignmentDeclAttrLayout::Code]; diff --git a/test/PrintAsObjC/cdecl.swift b/test/PrintAsObjC/cdecl.swift new file mode 100644 index 0000000000000..83b313e625750 --- /dev/null +++ b/test/PrintAsObjC/cdecl.swift @@ -0,0 +1,30 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -enable-source-import -emit-module -emit-module-doc -o %t %s -disable-objc-attr-requires-foundation-module +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -parse-as-library %t/cdecl.swiftmodule -parse -emit-objc-header-path %t/cdecl.h -import-objc-header %S/../Inputs/empty.h -disable-objc-attr-requires-foundation-module +// RUN: FileCheck %s < %t/cdecl.h +// RUN: %check-in-clang %t/cdecl.h +// RUN: %check-in-clang -fno-modules %t/cdecl.h -include Foundation.h -include ctypes.h -include CoreFoundation.h + +// REQUIRES: objc_interop + +// CHECK-LABEL: /// What a nightmare! +// CHECK-LABEL: double (^ _Nonnull block_nightmare(float (^ _Nonnull x)(NSInteger)))(char); + +/// What a nightmare! +@_cdecl("block_nightmare") +func block_nightmare(x: @convention(block) Int -> Float) + -> @convention(block) CChar -> Double { return { _ in 0 } } + +// CHECK-LABEL: void foo_bar(NSInteger x, NSInteger y); +@_cdecl("foo_bar") +func foo(x: Int, bar y: Int) {} + +// CHECK-LABEL: double (* _Nonnull function_pointer_nightmare(float (* _Nonnull x)(NSInteger)))(char); +@_cdecl("function_pointer_nightmare") +func function_pointer_nightmare(x: @convention(c) Int -> Float) + -> @convention(c) CChar -> Double { return { _ in 0 } } + +// CHECK-LABEL: void has_keyword_arg_names(NSInteger auto_, NSInteger union_); +@_cdecl("has_keyword_arg_names") +func keywordArgNames(auto: Int, union: Int) {} diff --git a/test/SILGen/cdecl.swift b/test/SILGen/cdecl.swift new file mode 100644 index 0000000000000..c5344bb04e315 --- /dev/null +++ b/test/SILGen/cdecl.swift @@ -0,0 +1,43 @@ +// RUN: %target-swift-frontend -emit-silgen %s | FileCheck %s + +// CHECK-LABEL: sil hidden @pear : $@convention(c) +// CHECK: function_ref @_TF5cdecl5apple +// CHECK-LABEL: sil hidden @_TF5cdecl5apple +@_cdecl("pear") +func apple(f: @convention(c) Int -> Int) { +} + +// CHECK-LABEL: sil hidden @_TF5cdecl16forceCEntryPoint +// CHECK: function_ref @grapefruit +func forceCEntryPoint() { + apple(orange) +} + +// CHECK-LABEL: sil hidden @grapefruit : $@convention(c) +// CHECK: function_ref @_TF5cdecl6orange +// CHECK-LABEL: sil hidden @_TF5cdecl6orange +@_cdecl("grapefruit") +func orange(x: Int) -> Int { + return x +} + +// CHECK-LABEL: sil @cauliflower : $@convention(c) +// CHECK: function_ref @_TF5cdecl8broccoli +// CHECK-LABEL: sil @_TF5cdecl8broccoli +@_cdecl("cauliflower") +public func broccoli(x: Int) -> Int { + return x +} + +// CHECK-LABEL: sil private @collard_greens : $@convention(c) +// CHECK: function_ref @_TF5cdeclP[[PRIVATE:.*]]4kale +// CHECK: sil private @_TF5cdeclP[[PRIVATE:.*]]4kale +@_cdecl("collard_greens") +private func kale(x: Int) -> Int { + return x +} + +/* TODO: Handle error conventions +@_cdecl("vomits") +func barfs() throws {} + */ diff --git a/test/attr/attr_cdecl.swift b/test/attr/attr_cdecl.swift new file mode 100644 index 0000000000000..f10f69679607e --- /dev/null +++ b/test/attr/attr_cdecl.swift @@ -0,0 +1,55 @@ +// RUN: %target-parse-verify-swift + +@_cdecl("cdecl_foo") func foo(x: Int) -> Int { return x } + +@_cdecl("") // expected-error{{symbol name cannot be empty}} +func emptyName(x: Int) -> Int { return x } + +@_cdecl("noBody") +func noBody(x: Int) -> Int // expected-error{{expected '{' in body of function}} + +@_cdecl("property") // expected-error{{may only be used on 'func' declarations}} +var property: Int + +var computed: Int { + @_cdecl("get_computed") get { return 0 } + @_cdecl("set_computed") set { return } +} + +struct SwiftStruct { var x, y: Int } +enum SwiftEnum { case A, B } +@objc enum CEnum: Int { case A, B } + +@_cdecl("swiftStruct") +func swiftStruct(x: SwiftStruct) {} // expected-error{{cannot be represented}} expected-note{{Swift struct}} + +@_cdecl("swiftEnum") +func swiftEnum(x: SwiftEnum) {} // expected-error{{cannot be represented}} expected-note{{non-'@objc' enum}} + +@_cdecl("cEnum") +func cEnum(x: CEnum) {} + +class Foo { + @_cdecl("Foo_foo") // expected-error{{can only be applied to global functions}} + func foo(x: Int) -> Int { return x } + + @_cdecl("Foo_foo_2") // expected-error{{can only be applied to global functions}} + static func foo(x: Int) -> Int { return x } + + @_cdecl("Foo_init") // expected-error{{may only be used on 'func'}} + init() {} + + @_cdecl("Foo_deinit") // expected-error{{may only be used on 'func'}} + deinit {} +} + +func hasNested() { + @_cdecl("nested") // expected-error{{can only be used in a non-local scope}} + func nested() { } +} + +// TODO: Handle error conventions in SILGen for toplevel functions. +@_cdecl("throwing") // expected-error{{raising errors from @_cdecl functions is not supported}} +func throwing() throws { } + +// TODO: cdecl name collisions