Making `simdutf` (C/C++) seamlessly interoperate with Swift

I'm trying to make GitHub - simdutf/simdutf: Unicode routines (UTF8, UTF16, UTF32) and Base64: billions of characters per second using SSE2, AVX2, NEON, AVX-512, RISC-V Vector Extension, LoongArch64, POWER. Part of Node.js, WebKit/Safari, Ladybird, Chromium, Cloudflare Workers and Bun. seamlessly interoperate with Swift, and considering my insufficient knowledge of C/C++ and the fact that C++ interop is new, I need your help :slightly_smiling_face:.

The related Pull Request is this one:

I also tried to look around in apple / swiftlang orgs to find some examples of C++ Span interop, with no luck.
I'll be happy if you know and let me know of some good-quality projects that are similar to this situation, so I can take a look at them.

The maintainer is rightfully asking some questions, and I myself also have some questions:

    1. Is a modulemap required for things to work, or can I reasonably get rid of it?
    • The maintainer is fine with the modulemap so I'd just let it be if it's too problematic to remove.
    1. Swift wasn't synthesizing Swift Span types from std::span even after the __noescape attributes I applied. Are there known issues/quirks around this?
    • The library guards the Span support behind SIMDUTF_SPAN and claims it'll be enabled if on C++ 20, and I have also declared cxxLanguageStandard: .gnucxx20 in my Package.swifts.
    • Could it be some kind of issue with the library correctly detecting and enabling its Span support? The related code is here.
    1. Is it required to use Swift 6.2?
    • Optimally we can have Swift synthesize function using Span on Swift 6.2, and for Swift 6.1 and 6.0 it can just only synthesize the functions via the UnsafePointer types.
    1. How is the Package.swift file that I put together looking?
    • Any issues/sub-optimalities there?

The following questions from a maintainer of the library are viewable in the PR as well:

    1. The maintainer asks (link):
    • Would it be implementable by adding a SIMDUTF_COUNTED_BY() macro that expands to the needed attributes?

  1. The maintainer asks (link):
    • Can this be solved in an easier way by instead providing a separate auto generated header file, where you can use all the attributes and swift specific code you wish?

    • I think not? Because like in the WWDC 2025 video C++ interop, we need source file changes as well, other than header files changes?
    1. About #include <lifetimebound.h>, the maintainer says (link):
    • this is not a standard header, where is it defined? I can find it on my system using clang 21 and gcc 15.

    • I suspect he meant to write "I can_'t_ find it on my system".
    1. The maintainer asks (link):

looks like the __noescape could be expressed as a C++ attribute instead, can you use that instead?
Attributes in Clang — Clang 22.0.0git documentation

would that be useful outside of swift compatibility?

regarding the __counted_by, the only info I can find is that it is used on struct members. the gcc manual https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc.pdf says:

In C++ this attribute is ignored

are there alternative ways of specifying the "counted by" property?

I realize these might be one too many questions. I still appreciate any answers to any of them.

  • You don’t need a module map if the header files are in the right format/location that SwiftPM expects (so SwiftPM will synthesise one for you) but usually for adding Swift to existing projects it’s just easier to add a module map rather than reorganising the whole project.

  • Span types only work with lifetime and bounds annotations that line up with the expected types. You also need the right imports and features enabled. So it can be a bit tricky to make it all work. I don’t know the C++ 20 limitations but did you try with .cxx20 instead?

  • You need 6.2 to make it work, but it’s not a requirement to have the annotations I don't think. The new C imports might be problematic however. You need at least

    .enableExperimentalFeature("SafeInteropWrappers"),
    
    .unsafeFlags(["-Xcc", "-fexperimental-bounds-safety-attributes"]),
    

    You might also want to include:
    .strictMemorySafety()
    at the package level and then

    cSettings: [.define("ENABLE_C_BOUNDS_SAFETY")]
    

    at the C level

  • I don’t know enough about C++ macros to say, but if the generated code is the same it should work fine

  • A separate header file will work (you can point to that in the module map) but then the maintainer has the issue of having to maintain 2

  • You don’t need source code changes for most stuff (unless you enable bounds safety in your C code) so that should be ok

  • It’s in {toolchain location}/usr/lib/clang/21/include/ so not available if not running from Swift

  • I believe __counted_by is the only way to make this work currently. AFAIK it’s a Swift/Clang concept, so not generally applicable. The no escape stuff should be pretty general as seen by add lifetime and no escape macros by lemire · Pull Request #896 · simdutf/simdutf · GitHub

Given the project is used by WebKit and WebKit has started to add these kind of annotations they may want to offer some advice/help out with this since it benefits them as well

1 Like

So I'm now trying again to get Span-synthesis work from std::span with no luck.

What I've tried / done:

  • Read compiler tests and ensure the all relevant flags such as SafeInteropWrappers or c++20, experimental feature are available in the SwiftPM build output.
  • Span-synthesis from pointers marked with __counted_by and __noescape does work.
  • Import the world in case an import is missing. Such as lifetimebound.h, ptrcheck, etc...

Some of the stuff I have in my Package.swift:

//swift-tools-version: 6.2

...

cxxLanguageStandard: .cxx20

...

var settings: [SwiftSetting] {
    [
        .swiftLanguageMode(.v6),

        .interoperabilityMode(.Cxx),
        .enableExperimentalFeature("SafeInteropWrappers"),
        .unsafeFlags(["-Xcc", "-fexperimental-bounds-safety-attributes"]),
        .strictMemorySafety(),
        .enableExperimentalFeature("LifetimeDependence"),

        .enableUpcomingFeature("MemberImportVisibility"),
        .enableUpcomingFeature("InternalImportsByDefault"),
        .enableUpcomingFeature("ExistentialAny"),
        .enableExperimentalFeature("Lifetimes"),
        .enableExperimentalFeature(
            "AvailabilityMacro=swiftIDNAApplePlatforms 26:macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26"
        ),
        .enableExperimentalFeature(
            "AvailabilityMacro=swiftIDNAApplePlatforms 11:macOS 11, iOS 14, tvOS 14, watchOS 7"
        ),
        .enableExperimentalFeature(
            "AvailabilityMacro=swiftIDNAApplePlatforms 10.15:macOS 10.15, iOS 13, tvOS 13, watchOS 6"
        ),
    ]
}

The C++ library's Package.swift:

// swift-tools-version:6.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "simdutf",
    products: [
        .library(
            name: "simdutf",
            targets: ["simdutf"]
        )
    ],
    targets: [
        .target(
            name: "simdutf",
            path: ".",
            sources: [
                "src/simdutf.cpp"
            ],
            publicHeadersPath: "include",
            cxxSettings: [
                .headerSearchPath("src"),
                .headerSearchPath("include"),
            ]
        )
    ],
    cxxLanguageStandard: .cxx20
)

Any compilers engineer have any ideas what is going wrong here?

Sorry @Douglas_Gregor for the ping, could you or one of your colleagues who work on C++ interop take a look?

TL;DR, I'm struggling to make Swift synthesize Span from std::span<...> __noescape.

This all should be reproducible for you as well. What I'd do to reproduce:

    1. Clone swift-idna:
    • https://github.com/swift-dns/swift-idna
    • branch: mmbm-simdutf
    1. Clone my fork of simdutf:
    • https://github.com/MahdiBM/simdutf
    • branch: mmbm-swift-take-2
    1. In swift-idna's Package.swift (or via swift package edit) edit:
    • .package(url: "https://github.com/mahdibm/simdutf.git", branch: "mmbm-swiftpm-integration"),
    • To .package(path: "../simdutf"), or another valid path to the cloned simdutf.
    • Just so you can mess around with simdutf later if you want, to get things working.
    1. Open Sources/SwiftIDNA/+Span.swift, at the top of the file is an example of using simdutf.
    1. Observe Swift doesn't accept passing an Span<UInt16> as a std::span<const char16_t> __noescape.

As a reminder from the previous post: