Merge pull request #78422 from swiftlang/gaborh/cxx-span-overload-importer

[cxx-interop] Generate safe overloads for non-escapable spans
This commit is contained in:
Gábor Horváth
2025-01-09 12:10:30 +00:00
committed by GitHub
7 changed files with 116 additions and 13 deletions

View File

@@ -566,7 +566,8 @@ void importer::getNormalInvocationArguments(
}
}
if (LangOpts.hasFeature(Feature::SafeInteropWrappers))
if (LangOpts.hasFeature(Feature::SafeInteropWrappers) &&
!LangOpts.EnableCXXInterop)
invocationArgStrs.push_back("-fexperimental-bounds-safety-attributes");
// Set C language options.

View File

@@ -60,6 +60,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjCCommon.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Basic/TargetInfo.h"
@@ -71,10 +72,12 @@
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/Path.h"
#include <algorithm>
#include <utility>
#define DEBUG_TYPE "Clang module importer"
@@ -130,6 +133,7 @@ createFuncOrAccessor(ClangImporter::Implementation &impl, SourceLoc funcLoc,
impl.importSwiftAttrAttributes(decl);
if (hasBoundsAnnotation)
impl.importBoundsAttributes(decl);
impl.importSpanAttributes(decl);
return decl;
}
@@ -8698,11 +8702,7 @@ public:
void printCountedBy(const clang::CountAttributedType *CAT,
size_t pointerIndex) {
if (!firstParam) {
out << ", ";
} else {
firstParam = false;
}
printSeparator();
clang::Expr *countExpr = CAT->getCountExpr();
bool isSizedBy = CAT->isCountInBytes();
out << ".";
@@ -8720,9 +8720,76 @@ public:
out, {}, {ctx.getLangOpts()}); // TODO: map clang::Expr to Swift Expr
out << "\")";
}
void printNonEscaping(int idx) {
printSeparator();
out << ".nonescaping(pointer: " << idx << ")";
}
void printTypeMapping(const llvm::StringMap<std::string> &mapping) {
printSeparator();
out << "typeMappings: [";
if (mapping.empty()) {
out << ":]";
return;
}
llvm::interleaveComma(mapping, out, [&](const auto &entry) {
out << '"' << entry.getKey() << "\" : \"" << entry.getValue() << '"';
});
out << "]";
}
private:
void printSeparator() {
if (!firstParam) {
out << ", ";
} else {
firstParam = false;
}
}
};
} // namespace
void ClangImporter::Implementation::importSpanAttributes(FuncDecl *MappedDecl) {
if (!SwiftContext.LangOpts.hasFeature(Feature::SafeInteropWrappers))
return;
auto ClangDecl =
dyn_cast_or_null<clang::FunctionDecl>(MappedDecl->getClangDecl());
if (!ClangDecl)
return;
llvm::SmallString<128> MacroString;
bool attachMacro = false;
{
llvm::raw_svector_ostream out(MacroString);
llvm::StringMap<std::string> typeMapping;
SwiftifyInfoPrinter printer(getClangASTContext(), out);
for (auto [index, param] : llvm::enumerate(ClangDecl->parameters())) {
auto paramTy = param->getType();
const auto *decl = paramTy->getAsTagDecl();
if (!decl || !decl->isInStdNamespace())
continue;
if (decl->getName() != "span")
continue;
if (param->hasAttr<clang::NoEscapeAttr>()) {
printer.printNonEscaping(index + 1);
clang::PrintingPolicy policy(param->getASTContext().getLangOpts());
policy.SuppressTagKeyword = true;
auto param = MappedDecl->getParameters()->get(index);
typeMapping.insert(std::make_pair(
paramTy.getAsString(policy),
param->getInterfaceType()->getDesugaredType()->getString()));
attachMacro = true;
}
}
printer.printTypeMapping(typeMapping);
}
if (attachMacro)
importNontrivialAttribute(MappedDecl, MacroString);
}
void ClangImporter::Implementation::importBoundsAttributes(
FuncDecl *MappedDecl) {
assert(SwiftContext.LangOpts.hasFeature(Feature::SafeInteropWrappers));

View File

@@ -1746,6 +1746,7 @@ public:
void importSwiftAttrAttributes(Decl *decl);
void importBoundsAttributes(FuncDecl *MappedDecl);
void importSpanAttributes(FuncDecl *MappedDecl);
/// Find the lookup table that corresponds to the given Clang module.
///

View File

@@ -167,6 +167,18 @@ func isRawPointerType(text: String) -> Bool {
}
}
// Remove std. or std.__1. prefix
func getUnqualifiedStdName(_ type: String) -> String? {
if (type.hasPrefix("std.")) {
var ty = type.dropFirst(4)
if (ty.hasPrefix("__1.")) {
ty = ty.dropFirst(4)
}
return String(ty)
}
return nil
}
func getSafePointerName(mut: Mutability, generateSpan: Bool, isRaw: Bool) -> TokenSyntax {
switch (mut, generateSpan, isRaw) {
case (.Immutable, true, true): return "RawSpan"
@@ -314,9 +326,10 @@ struct CxxSpanThunkBuilder: BoundsCheckedThunkBuilder {
"unable to desugar type with name '\(typeName)'", node: node)
}
let parsedDesugaredType = TypeSyntax("\(raw: desugaredType)")
types[index] = TypeSyntax(IdentifierTypeSyntax(name: "Span",
genericArgumentClause: parsedDesugaredType.as(IdentifierTypeSyntax.self)!.genericArgumentClause))
let parsedDesugaredType = TypeSyntax("\(raw: getUnqualifiedStdName(desugaredType)!)")
let genericArg = TypeSyntax(parsedDesugaredType.as(IdentifierTypeSyntax.self)!
.genericArgumentClause!.arguments.first!.argument)!
types[index] = TypeSyntax("Span<\(raw: try getTypeName(genericArg).text)>")
return try base.buildFunctionSignature(types, variant)
}
@@ -696,9 +709,11 @@ public struct SwiftifyImportMacro: PeerMacro {
for (idx, param) in signature.parameterClause.parameters.enumerated() {
let typeName = try getTypeName(param.type).text;
if let desugaredType = typeMappings[typeName] {
if desugaredType.starts(with: "span") {
result.append(CxxSpan(pointerIndex: idx + 1, nonescaping: false,
original: param, typeMappings: typeMappings))
if let unqualifiedDesugaredType = getUnqualifiedStdName(desugaredType) {
if unqualifiedDesugaredType.starts(with: "span<") {
result.append(CxxSpan(pointerIndex: idx + 1, nonescaping: false,
original: param, typeMappings: typeMappings))
}
}
}
}

View File

@@ -54,4 +54,6 @@ inline SpanOfInt initSpan(int arr[], size_t size) {
inline struct SpanBox getStructSpanBox() { return {iarray, iarray, sarray, sarray}; }
void funcWithSafeWrapper(SpanOfInt s [[clang::noescape]]) {}
#endif // TEST_INTEROP_CXX_STDLIB_INPUTS_STD_SPAN_H

View File

@@ -0,0 +1,17 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-ide-test -plugin-path %swift-plugin-dir -I %S/Inputs -enable-experimental-feature Span -enable-experimental-feature SafeInteropWrappers -print-module -module-to-print=StdSpan -source-filename=x -enable-experimental-cxx-interop -Xcc -std=c++20 -module-cache-path %t > %t/interface.swift
// RUN: %FileCheck %s < %t/interface.swift
// REQUIRES: swift_feature_SafeInteropWrappers
// REQUIRES: swift_feature_Span
// FIXME swift-ci linux tests do not support std::span
// UNSUPPORTED: OS=linux-gnu
#if !BRIDGING_HEADER
import StdSpan
#endif
import CxxStdlib
// CHECK: func funcWithSafeWrapper(_ s: SpanOfInt)
// CHECK-NEXT: @_alwaysEmitIntoClient public func funcWithSafeWrapper(_ s: Span<CInt>)

View File

@@ -7,7 +7,7 @@ public struct SpanOfInt {
init(_ x: Span<CInt>) {}
}
@_SwiftifyImport(.nonescaping(pointer: 1), typeMappings: ["SpanOfInt" : "span<CInt>"])
@_SwiftifyImport(.nonescaping(pointer: 1), typeMappings: ["SpanOfInt" : "std.span<CInt>"])
func myFunc(_ span: SpanOfInt, _ secondSpan: SpanOfInt) {
}