| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
There was a problem hiding this comment.
I need to learn what changes are needed to make this work.
adding the Package.swift and module.modulemap does not seem problematic.
Would it be implementable by adding a SIMDUTF_COUNTED_BY() macro that expands to the needed attributes?
Even if that is the case, making the header files even more unreadable is unattractive to me.
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?
Sorry, something went wrong.
| #include <type_traits> | ||
| #include <span> | ||
| #include <tuple> | ||
| #include <lifetimebound.h> |
There was a problem hiding this comment.
this is not a standard header, where is it defined? I can't find it on my system using clang 21 and gcc 15.
Sorry, something went wrong.
There was a problem hiding this comment.
From Tim, in the forums post:
It’s in {swift toolchain location}/usr/lib/clang/21/include/ so not available if not running from Swift
Sorry, something went wrong.
There was a problem hiding this comment.
cat swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-24-a.xctoolchain/usr/lib/clang/21/include/lifetimebound.h
/*===---- lifetimebound.h - Lifetime attributes -----------------------------===
*
* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
* See https://llvm.org/LICENSE.txt for license information.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*
*===------------------------------------------------------------------------===
*/
#ifndef __LIFETIMEBOUND_H
#define __LIFETIMEBOUND_H
#if defined(__cplusplus) && defined(__has_cpp_attribute)
#define __use_cpp_spelling(x) __has_cpp_attribute(x)
#else
#define __use_cpp_spelling(x) 0
#endif
#if __use_cpp_spelling(clang::lifetimebound)
#define __lifetimebound [[clang::lifetimebound]]
#else
#define __lifetimebound __attribute__((lifetimebound))
#endif
#if __use_cpp_spelling(clang::lifetime_capture_by)
#define __lifetime_capture_by(X) [[clang::lifetime_capture_by(X)]]
#else
#define __lifetime_capture_by(X) __attribute__((lifetime_capture_by(X)))
#endif
#if __use_cpp_spelling(clang::noescape)
#define __noescape [[clang::noescape]]
#else
#define __noescape __attribute__((noescape))
#endif
#endif /* __LIFETIMEBOUND_H */
Sorry, something went wrong.
| * @return true if and only if the string is valid ASCII. | ||
| */ | ||
| simdutf_warn_unused bool validate_utf16_as_ascii(const char16_t *buf, | ||
| simdutf_warn_unused bool validate_utf16_as_ascii(const char16_t *__counted_by(len) buf __noescape, |
There was a problem hiding this comment.
looks like the __noescape could be expressed as a C++ attribute instead, can you use that instead?
https://clang.llvm.org/docs/AttributeReference.html#noescape
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?
Sorry, something went wrong.
|
@pauldreik Thank you for the quick review. |
Sorry, something went wrong.
|
Related discussion: https://forums.swift.org/t/making-simdutf-c-c-seamlessly-interoperate-with-swift/83940 |
Sorry, something went wrong.
|
@MahdiBM This seems like a great idea !!! |
Sorry, something went wrong.
|
The relevant document appears to be here: https://www.swift.org/documentation/cxx-interop/safe-interop/#annotating-c-apis |
Sorry, something went wrong.
|
A C API might be simpler, no ? |
Sorry, something went wrong.
|
@MahdiBM Here is what I am thinking. If we have a C API, then we can surely generate a Swift wrapper with relatively little work. It can be done in a largely automated manner. |
Sorry, something went wrong.
|
Scratch that. Swift can read a C header file and call C functions, can't it ? https://github.com/lemire/SwiftCallingCHeader/blob/master/Sources/SomeSwift/main.swift So it should be pretty much automated ? |
Sorry, something went wrong.
|
Yes Swift interop with C/C++ is pretty seamless. I think we'd only need the modulemap and the Package.swift file. I think optimally we'd have those, and if/when we add __noescape to the std::span arguments, then Swift should be able to synthesize those functions as Swift Spans as well, for free. I did try to get std::span working with the Swift compiler but there must be a quirk of some sort which got in the way and failed the attempt. |
Sorry, something went wrong.
Reading the documentation, we might need to annotate all of our std::span. This can be done in a portable way... see |
Sorry, something went wrong.
|
@lemire yes I did notice the PR, I think that's exactly what we need 🙂 assuming the Swift compiler is also cooperative, it should be the only extra change we need. |
Sorry, something went wrong.
|
seems like the c api is the way to move forward on this! |
Sorry, something went wrong.
|
@MahdiBM Are you able to test the C API and see if it works ? If it solved a problem for Swift user, I think we could start officially supporting the C API. |
Sorry, something went wrong.
|
@lemire I can imagine the C API being useful for some users, but it's not really needed for Swift users. Swift has been supporting C++ interop since Swift 5.9 which was released 2-3 years ago. In the Swift OpenSource community we usually support past 3 minor versions of Swift (worth of 1.5 years), so 6.0, 6.1, 6.2, which all have the C++ interoperability. Code-wise Swift only needs these:
After that, Swift will synthesize functions via its family of UnsafePointer types. Now here's the issue that I'm struggling to solve: For that, the Swift team have introduced 2 ways to synthesize Swift Span types from C/C++ code. 1- If in C/C++ you have pointer+len parameters, annotate the pointer parameter with __noescape and __counted_by(the-len-param-name). 2- If in C++20 you have std::span, annotate it with __noescape. In both of these 2 cases, Swift will automatically synthesize code that accepts Swift's Span type instead of unsafe pointers. You show that you like the __noescape attribute anyway. The attribute makes sense and is not Swift specific. So that's good. But the issue with "1" is that it requires __counted_by which is pretty Swift specific and as @pauldreik mentioned, will make the files even less readable. The issue with "2" is ... well, I can't seem to get it working. I even tried to replicate a situation as close as possible to Swift's compiler tests for Span synthesis, verified the compiler flags are correctly passed, but with no luck. So I'm still trying to ask around, and I expect I'll have an answer from the Swift team at worst until the end of the week hopefully. I'll let you know if there is some kind of not-work-around-able bug there and we need to choose another way, but for now, I'm hoping that I can get "2" working. |
Sorry, something went wrong.
|
Ok @lemire @pauldreik . I found the culprit. Let's just show the code upfront: This works for accepting a Swift Span for the function call in Swift: using char16_span = std::span<const char16_t>;
simdutf_really_inline simdutf_warn_unused simdutf_constexpr23 bool
validate_utf16_as_ascii(char16_span input __noescape) noexcept;But this doesn't: simdutf_really_inline simdutf_warn_unused simdutf_constexpr23 bool
validate_utf16_as_ascii(std::span<const char16_t> input __noescape) noexcept;So ... yeah. For some reason Swift REQUIRES you to declare a "type alias" via using, and use that instead in the function parameter. How does this change sound to you?
(excluding CI and docs works) I think the first 2 sound fine, and the 3rd one is meh/ok? what about the 4th one? The name of the "type alias" doesn't matter. You can have any of these or anything else, but we'll need to do these everywhere std::span is used. using char16_span = std::span<const char16_t>;
using const_char16_span = std::span<const char16_t>;
using std_span_of_const_char16_t = std::span<const char16_t>;
using span_const_char16 = std::span<const char16_t>;Optionally we can first just don't do spans and just do point 1 and 2, + CI and some docs, and in another PR I can make the std::span changes. |
Sorry, something went wrong.
|
See this as an example of how this works with my specific simdutf branch (not this branch, mmbm-swift-take-2): |
Sorry, something went wrong.
|
@MahdiBM But your demo does not build, right? At least, not portably. $ cmake --build build
[ 1%] Building CXX object src/CMakeFiles/simdutf.dir/simdutf.cpp.o
In file included from /Users/dlemire/tmp/simdutf/include/simdutf.h:15,
from /Users/dlemire/tmp/simdutf/src/simdutf.cpp:1:
/Users/dlemire/tmp/simdutf/include/simdutf/implementation.h:16:10: fatal error: ptrcheck.h: No such file or directory
16 | #include <ptrcheck.h>
| ^~~~~~~~~~~~
compilation terminated.
gmake[2]: *** [src/CMakeFiles/simdutf.dir/build.make:79: src/CMakeFiles/simdutf.dir/simdutf.cpp.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:1385: src/CMakeFiles/simdutf.dir/all] Error 2
gmake: *** [Makefile:146: all] Error 2
It seems that #include <ptrcheck.h> is an LLVM specific header. We target at least GCC, LLVM and MSVC C++ compilers.
I am somewhat puzzled, what is wrong with the C API ? Doesn't it solve the issue without any need for intrusive changes in our library?
By your own analysis, this statement does not appear to be correct. If I trust what you are writing and demoing, one needs to make substantial changes to the C++ itself, and these changes appear to be invasive. I am not sure how the C++/Swift interop works but it seems to require substantial annotation, new headers, and we can't use std::span<char> and the like. Consider that we need to maintain this on the long run and it seems that neither myself nor @pauldreik is well versed in this Swift/C++ interop. Don't get me wrong, we very much want simdutf to be callable from Swift, but we also want it to be done in a way that requires little additional maintenance. Modifying significant our code in ways that are not obvious does not bode well. I believe that the C API is much more desirable. |
Sorry, something went wrong.
Emm yeah I only tried the build via Swift, so I probably just missed that. In anycase I don't think that's a blocking issue; we can just put it behind some compiler flags or such even if it's needed (<ptrcheck> specifically is probably not even needed).
Nothing really. Just that we already had C++.
I am also confused. Why do you think it does solve the issue? What were the issues? If we can add __noescape and __counted_by annotations to the C API, then yes. Otherwise I don't see a difference between using the C++ API or the C API.
No worries, I understand. That's why I'm pushing for Swift to be more smart about synthesizing Swift Spans out of C++ std::span so it requires less changes on the C++ side: swiftlang/swift#86339 (comment)
That's a legitimate concern, and It'll be one more thing to take care of I guess. I guess as a Swift developer what I can say is that there is not really that much to the C++ interop. It's simply just compiler magic. I think I can set up some proper Swift CI together, and that should cover a lot of the concerns. If there is an issue, you can:
|
Sorry, something went wrong.
|
The C API should solve the issue because it makes it easy to write a Swift library that acts as a wrapper. Calling C from Swift is easy and has no performance overhead so it is ideal. This is no difficult work. One can probably get it done in an hour or so. One just needs some Swift boilerplate code. |
Sorry, something went wrong.
Ok but I'm struggling to see which part of this is not true about C++ 🤔. IIUC its the same for C++ modulo the Swift versions required. Or am I missing something? |
Sorry, something went wrong.
The C API is available now and it did not require any change to the library. One can use it now to write a wrapper. It works. What you are proposing is that we start maintaining invasive Swift-specific code changes inside simdutf, it is undesirable. Let be clear: this is not happening. I explained why above in clear terms. It would be poor engineering. I consider that adding the C interface resolves the issue in a satisfactory manner. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Short title (summary):
Enable seamless Swift interoperability with this package.
Description
Let's preface this with saying that I'm not a C++ developer, and I'll be happy to adjust the code based on your feedback or even nitpicks.
The idea of making simdutf swift-compatible came from this discussion in apple/swift-nio.
This PR is to open up the discussion with a POC, and see if these changes are acceptable or not.
If you prefer to have the discussion elsewhere, we can discuss this in that place, but I wanted to have something to show, and not just discuss theories.
What I've done
What remains to be done in my view:
Other notes
When we're close to be done with this PR, I can ask some Apple folks to review the PR as well, considering C++ interoperability (unlike C) is new in Swift.
They might not be able to directly comment in this PR/repository, but I can open up a discussion in Swift forums.
Swift's ecosystem also tries to follow Sem Ver 2.0, which I think is the same thing that this library aims for. So we should be good on that front as well.
Type of change
How to verify / test
Adding tests remains to be done.
Watch this WWDC25 video for some info about C++ interoperability in Swift, if you're curious:
https://developer.apple.com/videos/play/wwdc2025/311/
You can also just read the transcript and the 2 articles linked in "Resources".
I'm currently testing these in a package of mine on macOS.
swift-idna - branch mmbm-simdutf
See the branch's small diff compared to main.
Build via swift build.
If you don't have Swift installed, install Swift 6.2.3 via swiftly, or another way of your choice.
If you'd like, open swift-idna in VSCode (or any of the forks) and install the official Swift extension.
I've yet to get things building on Linux, looks like there are some C++ interop issues there? I personally need to be able to use simdutf on Linux as well, so I'll get that working one way or another.
Please read before contributing:
If you can, we recommend running our tests with the sanitizers turned on.
For non-Visual Studio users, it is as easy as doing:
Our CI checks, among other things, for trailing whitespace.
Checklist before submitting
Final notes
Thanks for the contribution!