mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Sema: Type checking for C function pointers.
Since a function pointer doesn't carry any context, we can only form a C function pointer from a static reference to a global function or a context-free local function or closure. (Or maybe a static function applied to its metatype, but that's not handled here yet.) As a placeholder for a to-be-bikeshedded surface syntax, let the existing @cc(cdecl) attribute appear in source text when the -enable-c-function-pointers frontend flag is passed. Swift SVN r25308
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include "swift/AST/Decl.h"
|
||||
#include "swift/AST/Expr.h"
|
||||
#include "swift/AST/Types.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/PointerUnion.h"
|
||||
|
||||
namespace swift {
|
||||
@@ -27,6 +28,11 @@ class CaptureInfo;
|
||||
class AnyFunctionRef {
|
||||
PointerUnion<AbstractFunctionDecl *, AbstractClosureExpr *> TheFunction;
|
||||
|
||||
friend struct llvm::DenseMapInfo<AnyFunctionRef>;
|
||||
|
||||
AnyFunctionRef(decltype(TheFunction) TheFunction)
|
||||
: TheFunction(TheFunction) {}
|
||||
|
||||
public:
|
||||
AnyFunctionRef(AbstractFunctionDecl *AFD) : TheFunction(AFD) {
|
||||
assert(AFD && "should have a function");
|
||||
@@ -117,5 +123,29 @@ public:
|
||||
|
||||
} // namespace swift
|
||||
|
||||
namespace llvm {
|
||||
|
||||
template<>
|
||||
struct DenseMapInfo<swift::AnyFunctionRef> {
|
||||
using PointerUnion = decltype(swift::AnyFunctionRef::TheFunction);
|
||||
using PointerUnionTraits = DenseMapInfo<PointerUnion>;
|
||||
using AnyFunctionRef = swift::AnyFunctionRef;
|
||||
|
||||
static inline AnyFunctionRef getEmptyKey() {
|
||||
return AnyFunctionRef(PointerUnionTraits::getEmptyKey());
|
||||
}
|
||||
static inline AnyFunctionRef getTombstoneKey() {
|
||||
return AnyFunctionRef(PointerUnionTraits::getTombstoneKey());
|
||||
}
|
||||
static inline unsigned getHashValue(AnyFunctionRef ref) {
|
||||
return PointerUnionTraits::getHashValue(ref.TheFunction);
|
||||
}
|
||||
static bool isEqual(AnyFunctionRef a, AnyFunctionRef b) {
|
||||
return a.TheFunction == b.TheFunction;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // LLVM_SWIFT_AST_ANY_FUNCTION_REF_H
|
||||
|
||||
|
||||
@@ -404,6 +404,17 @@ ERROR(invalid_function_conversion,sema_tcc,none,
|
||||
NOTE(invalid_function_conversion_closure,sema_tcc,none,
|
||||
"use a closure to safely wrap calls to the function", ())
|
||||
|
||||
ERROR(invalid_c_function_pointer_conversion_expr,sema_tcc,none,
|
||||
"a C function pointer can only be formed from a reference to a 'func' or "
|
||||
"a literal closure", ())
|
||||
ERROR(c_function_pointer_from_method,sema_tcc,none,
|
||||
"a C function pointer cannot be formed from a method", ())
|
||||
ERROR(c_function_pointer_from_function_with_context,sema_tcc,none,
|
||||
"a C function pointer cannot be formed from a "
|
||||
"%select{local function|closure}0 that captures context", (bool))
|
||||
NOTE(c_function_pointer_captures_here,sema_tcc,none,
|
||||
"%0 captured here", (Identifier))
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Type Check Declarations
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -77,6 +77,9 @@ namespace swift {
|
||||
/// symbols as optionals.
|
||||
bool EnableExperimentalUnavailableAsOptional = false;
|
||||
|
||||
/// \brief Enable support for native C function pointer types.
|
||||
bool EnableCFunctionPointers = false;
|
||||
|
||||
/// \brief Enable features useful for running in the debugger.
|
||||
bool DebuggerSupport = false;
|
||||
|
||||
|
||||
@@ -166,6 +166,10 @@ def enable_experimental_unavailable_as_optional : Flag<["-"],
|
||||
"enable-experimental-unavailable-as-optional">,
|
||||
HelpText<"Enable experimental treat unavailable symbols as optional">;
|
||||
|
||||
def enable_c_function_pointers : Flag<["-"],
|
||||
"enable-c-function-pointers">,
|
||||
HelpText<"Enable native C function pointer types">;
|
||||
|
||||
def enable_character_literals : Flag<["-"], "enable-character-literals">,
|
||||
HelpText<"Enable legacy character literals">;
|
||||
|
||||
|
||||
@@ -560,6 +560,9 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
|
||||
Opts.EnableExperimentalUnavailableAsOptional |=
|
||||
Args.hasArg(OPT_enable_experimental_unavailable_as_optional);
|
||||
|
||||
Opts.EnableCFunctionPointers |=
|
||||
Args.hasArg(OPT_enable_c_function_pointers);
|
||||
|
||||
Opts.EnableCharacterLiterals |= Args.hasArg(OPT_enable_character_literals);
|
||||
Opts.UsePrivateDiscriminators |=
|
||||
Args.hasArg(OPT_enable_private_discriminators);
|
||||
|
||||
@@ -4212,6 +4212,77 @@ static void
|
||||
maybeDiagnoseUnsupportedFunctionConversion(TypeChecker &tc, Expr *expr,
|
||||
AnyFunctionType *toType) {
|
||||
Type fromType = expr->getType();
|
||||
auto fromFnType = fromType->getAs<AnyFunctionType>();
|
||||
|
||||
// Conversions to C function pointer type are limited. Since a C function
|
||||
// pointer captures no context, we can only do the necessary thunking or
|
||||
// codegen if the original function is a direct reference to a global function
|
||||
// or context-free closure or local function.
|
||||
if (toType->getExtInfo().getCC() == AbstractCC::C) {
|
||||
// Can convert from an ABI-compatible C function pointer.
|
||||
if (fromFnType && fromFnType->getExtInfo().getCC() == AbstractCC::C
|
||||
&& areConvertibleTypesABICompatible(fromType->getCanonicalType(),
|
||||
toType->getCanonicalType()))
|
||||
return;
|
||||
|
||||
// Can convert a decl ref to a global or local function that doesn't
|
||||
// capture context. Look through ignored bases too.
|
||||
// TODO: Look through static method applications to the type.
|
||||
auto semanticExpr = expr->getSemanticsProvidingExpr();
|
||||
while (auto ignoredBase = dyn_cast<DotSyntaxBaseIgnoredExpr>(semanticExpr)){
|
||||
semanticExpr = ignoredBase->getRHS()->getSemanticsProvidingExpr();
|
||||
}
|
||||
|
||||
auto maybeDiagnoseFunctionRef = [&](FuncDecl *fn) {
|
||||
// TODO: We could allow static (or class final) functions too by
|
||||
// "capturing" the metatype in a thunk.
|
||||
if (fn->getDeclContext()->isTypeContext()) {
|
||||
tc.diagnose(expr->getLoc(),
|
||||
diag::c_function_pointer_from_method);
|
||||
} else if (fn->getCaptureInfo().hasLocalCaptures()) {
|
||||
tc.diagnose(expr->getLoc(),
|
||||
diag::c_function_pointer_from_function_with_context,
|
||||
/*closure*/ false);
|
||||
} else if (fn->getCaptureInfo().empty()) {
|
||||
// The capture list is not always initialized by the point we reference
|
||||
// it. Remember we formed a C function pointer so we can diagnose later
|
||||
// if necessary.
|
||||
tc.LocalCFunctionPointers[fn].push_back(expr);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
if (auto declRef = dyn_cast<DeclRefExpr>(semanticExpr)) {
|
||||
if (auto fn = dyn_cast<FuncDecl>(declRef->getDecl())) {
|
||||
return maybeDiagnoseFunctionRef(fn);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto memberRef = dyn_cast<MemberRefExpr>(semanticExpr)) {
|
||||
if (auto fn = dyn_cast<FuncDecl>(memberRef->getMember().getDecl())) {
|
||||
return maybeDiagnoseFunctionRef(fn);
|
||||
}
|
||||
}
|
||||
|
||||
// Can convert a literal closure that doesn't capture context.
|
||||
if (auto closure = dyn_cast<ClosureExpr>(semanticExpr)) {
|
||||
if (closure->getCaptureInfo().hasLocalCaptures()) {
|
||||
tc.diagnose(expr->getLoc(),
|
||||
diag::c_function_pointer_from_function_with_context,
|
||||
/*closure*/ true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
tc.diagnose(expr->getLoc(),
|
||||
diag::invalid_c_function_pointer_conversion_expr);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check ABI compatibility of thick functions. TODO: In the fullness of time
|
||||
// these ought to be implementable by thunking.
|
||||
|
||||
if (areConvertibleTypesABICompatible(fromType->getCanonicalType(),
|
||||
toType->getCanonicalType())) {
|
||||
return;
|
||||
@@ -4220,7 +4291,7 @@ maybeDiagnoseUnsupportedFunctionConversion(TypeChecker &tc, Expr *expr,
|
||||
// Strip @noescape from the types. It's not relevant: any function type with
|
||||
// otherwise matching flags can be converted to a noescape function type.
|
||||
{
|
||||
auto fromFnType = fromType->castTo<AnyFunctionType>();
|
||||
assert(fromFnType);
|
||||
auto prettyFromExtInfo = fromFnType->getExtInfo().withNoEscape(false);
|
||||
fromType = fromFnType->withExtInfo(prettyFromExtInfo);
|
||||
|
||||
|
||||
@@ -740,5 +740,17 @@ void TypeChecker::computeCaptures(AnyFunctionRef AFR) {
|
||||
Context.AllocateCopy<ValueDecl *>(Captures.begin(), Captures.end());
|
||||
AFR.getCaptureInfo().setCaptures(
|
||||
llvm::makeArrayRef(CaptureCopy, Captures.size()));
|
||||
|
||||
// Diagnose if we have local captures and there were C pointers formed to
|
||||
// this function before we computed captures.
|
||||
auto cFunctionPointers = LocalCFunctionPointers.find(AFR);
|
||||
if (cFunctionPointers != LocalCFunctionPointers.end()
|
||||
&& AFR.getCaptureInfo().hasLocalCaptures()) {
|
||||
for (auto *expr : cFunctionPointers->second) {
|
||||
diagnose(expr->getLoc(),
|
||||
diag::c_function_pointer_from_function_with_context,
|
||||
/*closure*/ AFR.getAbstractClosureExpr() != nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1249,9 +1249,13 @@ Type TypeResolver::resolveAttributedType(TypeAttributes &attrs,
|
||||
// source level, but we want to support them there in SIL files.
|
||||
auto SF = DC->getParentSourceFile();
|
||||
if (!SF || SF->Kind != SourceFileKind::SIL) {
|
||||
for (auto silOnlyAttr : {TAK_cc, TAK_thin, TAK_thick}) {
|
||||
for (auto silOnlyAttr : {TAK_thin, TAK_thick}) {
|
||||
checkUnsupportedAttr(silOnlyAttr);
|
||||
}
|
||||
// TODO: Pick a real syntax for C function pointers.
|
||||
// For now admit @cc(cdecl) as a syntax for C function pointers.
|
||||
if (!Context.LangOpts.EnableCFunctionPointers)
|
||||
checkUnsupportedAttr(TAK_cc);
|
||||
}
|
||||
|
||||
bool hasFunctionAttr = false;
|
||||
|
||||
@@ -306,6 +306,13 @@ public:
|
||||
/// check wouldn't even be needed if we could handle captures within SILGen.
|
||||
std::vector<LocalFunctionCapture> LocalFunctionCaptures;
|
||||
|
||||
/// A set of local functions from which C function pointers are derived.
|
||||
///
|
||||
/// This is used to diagnose the use of local functions with captured context
|
||||
/// as C function pointers when the function's captures have not yet been
|
||||
/// computed.
|
||||
llvm::DenseMap<AnyFunctionRef, std::vector<Expr*>> LocalCFunctionPointers;
|
||||
|
||||
private:
|
||||
Type IntLiteralType;
|
||||
Type FloatLiteralType;
|
||||
|
||||
44
test/Parse/c_function_pointers.swift
Normal file
44
test/Parse/c_function_pointers.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
// RUN: %target-swift-frontend -parse -verify -module-name main -enable-c-function-pointers %s
|
||||
func global() -> Int { return 0 }
|
||||
|
||||
struct S {
|
||||
static func staticMethod() -> Int { return 0 }
|
||||
}
|
||||
|
||||
class C {
|
||||
static func staticMethod() -> Int { return 0 }
|
||||
class func classMethod() -> Int { return 0 }
|
||||
}
|
||||
|
||||
if true {
|
||||
var x = 0
|
||||
func local() -> Int { return 0 }
|
||||
func localWithContext() -> Int { return x }
|
||||
|
||||
let a: @cc(cdecl) () -> Int = global
|
||||
let a2: @cc(cdecl) () -> Int = main.global
|
||||
let b: @cc(cdecl) () -> Int = { 0 }
|
||||
let c: @cc(cdecl) () -> Int = local
|
||||
|
||||
// Can't convert a closure with context to a C function pointer
|
||||
let d: @cc(cdecl) () -> Int = { x } // expected-error{{cannot be formed from a closure that captures context}}
|
||||
let e: @cc(cdecl) () -> Int = localWithContext // expected-error{{cannot be formed from a local function that captures context}}
|
||||
|
||||
// Can't convert a closure value to a C function pointer
|
||||
let global2 = global
|
||||
let f: @cc(cdecl) () -> Int = global2 // expected-error{{can only be formed from a reference to a 'func' or a literal closure}}
|
||||
let globalBlock: @objc_block () -> Int = global
|
||||
let g: @cc(cdecl) () -> Int = globalBlock // expected-error{{can only be formed from a reference to a 'func' or a literal closure}}
|
||||
|
||||
// Can convert a function pointer to a block or closure, or assign to another
|
||||
// C function pointer
|
||||
let h: @cc(cdecl) () -> Int = a
|
||||
let i: @objc_block () -> Int = a
|
||||
let j: () -> Int = a
|
||||
|
||||
// Can't convert a C function pointer from a method.
|
||||
// TODO: Could handle static methods.
|
||||
let k: @cc(cdecl) () -> Int = S.staticMethod // expected-error{{}}
|
||||
let m: @cc(cdecl) () -> Int = C.staticMethod // expected-error{{}}
|
||||
let n: @cc(cdecl) () -> Int = C.classMethod // expected-error{{}}
|
||||
}
|
||||
Reference in New Issue
Block a user