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:
Joe Groff
2015-02-16 00:41:53 +00:00
parent 33c9457930
commit 94be8424ff
10 changed files with 193 additions and 4 deletions

View File

@@ -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

View File

@@ -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
//------------------------------------------------------------------------------

View File

@@ -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;

View File

@@ -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">;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;

View 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{{}}
}