foundationdb/SWIFT_GUIDE.md

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

311 lines
11 KiB
Markdown
Raw Normal View History

Introduce initial Swift support in fdbserver (#10156) * [fdbserver] workaround the FRT type layout issue to get Swfit getVersion working * MasterData.actor.h: fix comment typo * masterserver.swift: some tweaks * masterserver.swift: remove getVersion function, use the method * masterserver.swift: print replied version to output for tracing * [swift] add radar links for C++ interop issues found in getVersion bringup * Update fdbserver.actor.cpp * Migrate MasterData closer to full reference type This removes the workaround for the FRT type layout issue, and gets us closer to making MasterData a full reference type * [interop] require a new toolchain (>= Oct 19th) to build * [Swift] fix computation of toAdd for getVersion Swift implementation * add Swift to FDBClient and add async `atLeast` to NotifiedVersion * fix * use new atLeast API in master server * =build fixup link dependencies in swift fdbclient * clocks * +clock implement Clock using Flow's notion of time * [interop] workaround the immortal retain/release issue * [swift] add script to get latest centos toolchain * always install swift hooks; not only in "test" mode * simulator - first thing running WIP * cleanups * more cleanup * working snapshot * remove sim debug printlns * added convenience for whenAtLeast * try Alex's workaround * annotate nonnull * cleanup clock a little bit * fix missing impls after rebase * Undo the swift_lookup_Map_UID_CommitProxyVersionReplies workaround No longer needed - the issue was retain/release * [flow][swift] add Swift version of BUGGIFY * [swiftication] add CounterValue type to provide value semantics for Counter types on the Swift side * remove extraneous requestingProxyUID local * masterserver: initial Swift state prototype * [interop] make the Swiftied getVersion work * masterserver - remove the C++ implementation (it can't be supported as state is now missing) * Remove unnecessary SWIFT_CXX_REF_IMMORTAL annotations from Flow types * Remove C++ implementation of CommitProxyVersionReplies - it's in Swift now * [swift interop] remove more SWIFT_CXX_REF_IMMORTAL * [swift interop] add SWIFT_CXX_IMMORTAL_SINGLETON_TYPE annotation for semanticly meaningful immortal uses * rename SWIFT_CXX_REF_IMMORTAL -> UNSAFE_SWIFT_CXX_IMMORTAL_REF * Move master server waitForPrev to swift * =build fix linking swift in all modules * =build single link option * =cmake avoid manual math, just get "last" element from list * implement Streams support (#18) * [interop] update to new toolchain #6 * [interop] remove C++ vtable linking workarounds * [interop] make MasterData proper reference counted SWIFT_CXX_REF_MASTERDATA * [interop] use Swift array to pass UIDs to registerLastCommitProxyVersionReplies * [interop] expose MasterServer actor to C++ without wrapper struct * [interop] we no longer need expose on methods 🥳 * [interop] initial prototype of storing CheckedContinuation on the C++ side * Example of invoking a synchronous swift function from a C++ unit test. (#21) * move all "tests" we have in Swift, and priority support into real modules (#24) * Make set continuation functions inline * Split flow_swift into flow_swift and flow_swift_future to break circular dependency * rename SwiftContinuationCallbackStruct to FlowCallbackForSwiftContinuation * Future interop: use a method in a class template for continuation set call * Revert "Merge pull request #22 from FoundationDB/cpp-continuation" (#30) * Basic Swift Guide (#29) Co-authored-by: Alex Lorenz <arphaman@gmail.com> * Revert "Revert "Merge pull request #22 from FoundationDB/cpp-continuation" (#30)" This reverts commit c025fe6258c4c4904d5e70cd549d408bb61f2802. * Restore the C++ continuation, but it seems waitValue is broken for CInt somehow now * disable broken tests - waitValue not accessible * Streams can be async iterated over (#27) Co-authored-by: Alex Lorenz <arphaman@gmail.com> * remove work in progress things (#35) * remove some not used (yet) code * remove expose func for CInt, it's a primitive so we always have witness info (#37) * +masterdata implement provideVersions in Swift (#36) * serveLiveCommittedVersion in Swift (#38) * Port updateLiveCommittedVersion to swift (#33) Co-authored-by: Konrad `ktoso` Malawski <konrad_malawski@apple.com> * Implement updateRecoveryData in Swift (#39) Co-authored-by: Alex Lorenz <arphaman@gmail.com> * Simplify flow_swift to avoid multiple targets and generate separate CheckedContinuation header * Uncomment test which was blocked on extensions not being picked up (#31) * [interop] Use a separate target for Swift-to-C++ header generation * reduce boilerplate in future and stream support (#41) * [interop] require interop v8 - that will fix linker issue (https://github.com/apple/swift/issues/62448) * [interop] fix swift_stream_support.h Swift include * [interop] bump up requirement to version 9 * [interop] Generalize the Flow.Optional -> Swift.Optional conversion using generics * [WIP] masterServer func in Swift (#45) * [interop] Try conforms_to with a SWIFT_CONFORMS_TO macro for Optional conformance (#49) * [interop] include FlowOptionalProtocol source file when generating Flow_CheckedContinuation.h This header generation step depends on the import of the C++ Flow module, which requires the presence of FlowOptionalProtocol * conform Future to FlowFutureOps * some notes * move to value() so we can use discardable result for Flow.Void * make calling into Swift async funcs nicer by returning Flow Futures * [interop] hide initial use of FlowCheckedContinuation in flow.h to break dependency cycle * [fdbserver] fix an EncryptionOpsUtils.h modularization issue (showed up with modularized libc++) * Pass GCC toolchain using CMAKE_Swift_COMPILE_EXTERNAL_TOOLCHAIN to Swift's clang importer * [interop] drop the no longer needed libstdc++ include directories * [cmake] add a configuration check to ensure Swift can import C++ standard library * [swift] include msgpack from msgpack_DIR * [interop] make sure the FDB module maps have 'export' directive * add import 'flow_swift' to swift_fdbserver_cxx_swift_value_conformance.swift This is needed for CONFORMS_TO to work in imported modules * make sure the Swift -> C++ manually bridged function signature matches generated signature * [interop][workaround] force back use of @expose attribute before _Concurrency issue is fixed * [interop] make getResolutionBalancer return a pointer to allow Swift to use it We should revert back to a reference once compiler allows references again * [interop] add a workaround for 'pop' being marked as unsafe in Swift * masterserver.swift: MasterData returns the Swift actor pointer in an unsafe manner * Add a 'getCopy' method to AsyncVar to make it more Swift friendly * [interop] bump up the toolchain requirement * Revert "[interop][workaround] force back use of @expose attribute before _Concurrency issue is fixed" This reverts commit b01b271a76d1677bbb7c5c9c64cdad4b8b2b9612. * [interop] add FIXME comments highlighting new issue workarounds * [interop] adopt the new C++ interoperability compiler flag * [interop] generate swift compile commands * Do not deduplicate Swift compilation commands * [interop] generate swift compile commands * Do not deduplicate Swift compilation commands * flow actorcompiler.h: add a SWIFT_ACTOR empty macro definition This is needed to make the actor files parsable by clangd * [cmake] add missing dependencies * experimental cross compile * [cmake] fix triple in cross-compiled cmake flags * [interop] update to interop toolchain version 16 * [x-compile] add flags for cross-compiling boost * cleanup x-compile cmake changes * [cmake] fix typo in CMAKE_Swift_COMPILER_EXTERNAL_TOOLCHAIN config variable * [interop] pass MasterDataActor from Swift to C++ and back to Swift * [fdbserver] Swift->C++ header generation for FDBServer should use same module cache path * Update swift_get_latest_toolchain.sh to fetch 5.9 toochains * set HAVE_FLAG_SEARCH_PATHS_FIRST for cross compilation * Resolve conflicts in net2/sim2/actors, can't build yet * undo SWIFT_ACTOR changes, not necessary for merge * guard c++ compiler flags with is_cxx_compile * Update flow/actorcompiler/ActorParser.cs Co-authored-by: Evan Wilde <etceterawilde@gmail.com> * update the boost dependency * Include boost directory from the container for Swift * conform flow's Optional to FlowOptionalProtocol again * Guard entire RocksDBLogForwarder.h with SSD_ROCKSDB_EXPERIMENTAL to avoid failing on missing rocksdb APIs * remove extraneous merge marker * [swift] update swift_test_streams.swifto to use vars in more places * Add header guard to flow/include/flow/ThreadSafeQueue.h to fix moduralization issue * Update net and sim impls * [cmake] use prebuilt libc++ boost only when we're actually using libc++ * [fdbserver] Swift->C++ header generation for FDBServer should use same module cache path * fixups after merge * remove CustomStringConvertible conformance that would not be used * remove self-caused deprecation warnings in future_support * handle newly added task priority * reformatting * future: make value() not mutating * remove FIXME, not needed anymore * future: clarify why as functions * Support TraceEvent in Swift * Enable TraceEvent using a class wrapper in Swift * prearing WITH_SWIFT flag * wip disabled failing Go stuff * cleanup WITH_SWIFT_FLAG and reenable Go * wip disabled failing Go stuff * move setting flag before printing it * Add SWIFT_IDE_SETUP and cleanup guides and build a bit * Revert "Wipe packet buffers that held serialized WipedString (#10018)" This reverts commit e2df6e33029897360f8e11b3aea8fef97393a98c. * [Swift] Compile workaround in KeyBackedRangeMap; default init is incorrect * [interop] do not add FlowFutureOps conformance when building flow clang module for Flow checked continuation header pre-generation * make sure to show -DUSE_LIBCXX=OFF in readme * readme updates * do not print to stderr * Update Swift and C++ code to build with latest Swift 5.9 toolchain now that we no longer support universal references and bridge the methods that take in a constant reference template parameter correctly * Fix SERVER_KNOBS and enable use them for masterserver * Bump to C++20, Swift is now able to handle it as well * Put waitForPrev behind FLOW_WITH_SWIFT knob * Forward declare updateLiveCommittedVersion * Remove unused code * fix wrong condition set for updateLiveCommittedVersion * Revert "Revert "Wipe packet buffers that held serialized WipedString (#10018)"" This reverts commit 5ad8dce0525fb1844664ed2ccd7ba595db6913dd. * Enable go-bindings in cmake * Revert "Revert "Wipe packet buffers that held serialized WipedString (#10018)"" This reverts commit 5ad8dce0525fb1844664ed2ccd7ba595db6913dd. * USE_SWIFT flag so we "build without swift" until ready to by default * uncomment a few tests which were disabled during USE_SWIFT enablement * the option is WITH_SWIFT, not USE * formatting * Fix masterserver compile error * Fix some build errors. How did it not merge cleanly? :/ * remove initializer list from constructor * Expect Swift toolchain only if WITH_SWIFT is enabled * Don't require Flow_CheckedContinuation when Swift is disabled * Don't compile FlowCheckedContinuation when WITH_SWIFT=OFF * No-op Swift macros * More compile guards * fix typo * Run clang-format * Guard swift/bridging include in fdbrpc * Remove printf to pass the test * Remove some more printf to avoid potential issues TODO: Need to be TraceEvents instead * Remove __has_feature(nullability) as its only used in Swift * Don't use __FILENAME__ * Don't call generate_module_map outside WITH_SWIFT * Add some more cmake stuff under WITH_SWIFT guard * Some more guards * Bring back TLSTest.cpp * clang-format * fix comment formatting * Remove unused command line arg * fix cmake formatting in some files * Address some review comments * fix clang-format error --------- Co-authored-by: Alex Lorenz <arphaman@gmail.com> Co-authored-by: Russell Sears <russell_sears@apple.com> Co-authored-by: Evan Wilde <etceterawilde@gmail.com> Co-authored-by: Alex Lorenz <aleksei_lorenz@apple.com> Co-authored-by: Vishesh Yadav <vishesh_yadav@apple.com> Co-authored-by: Vishesh Yadav <vishesh3y@gmail.com>
2023-06-03 05:09:28 +08:00
# Swift in FoundationDB
The optional Swift support allows piecewise adoption of Swift in implementing FoundationDB.
Swift offers a unique modern type-safe low-ceremony approach taking the best of both worlds that scales from mobile
apps to high-performance systems where previously memory-unsafe languages would be used. It also interoperates
seamlessly with C and C++.
Since FoundationDB is largely implemented in C++ and Flow, large pieces of
## Building with Swift
Swift is built using the same CMake build as the rest of the project.
To configure the build such that `clang` and `swiftc` are used, use the following:
```swift
cd build
cmake -G 'Ninja' \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_Swift_COMPILER=swiftc \
-DWITH_SWIFT=ON \
-DUSE_LIBCXX=OFF \
-DCMAKE_Swift_COMPILER_EXTERNAL_TOOLCHAIN=/opt/rh/devtoolset-11/root/usr \
../src/foundationdb/
```
Then, build using `ninja` as usual.
## IDE Integration
A full guide on setting up IDEs with FoundationDB, including cross language autocomplete support and code navigation
is available here: [SWIFT_IDE_SETUP.md](SWIFT_IDE_SETUP.md)
## How Swift interoperates with C++
The CMake build has been prepared with Swift interop in the following modules:
- flow
- fdbrpc
- fdbclient
- fdbserver
The integration works "both ways", i.e. Swift can call into Flow/C++ code, as well as Flow/C++ can call into Swift.
Swift generates clang modules which can be consumed in C++. For example, the module `fdbserver_swift` contains all swift code in `fdbserver/`.
> Note: you can check, and add new files to the `_swift` targets by locating the command, e.g. `add_library(fdbserver_swft` in [fdbserver/CMakeLists.txt](fdbserver/CMakeLists.txt).
Then, you can then include the generated module in C++:
```cpp
// ...
#include "SwiftModules/FDBServer"
#include "flow/actorcompiler.h" // This must be the last #include.
```
## Swift Basics
When in doubt about Swift syntax, refer to https://docs.swift.org/swift-book/ or reach out to the team, we're here to help.
## Swift → C++
Swift can import clang modules, and does so using the `import` statement, like so:
```swift
import Flow
import flow_swift
import FDBServer
import FDBClient
import fdbclient_swift
import FlowFutureSupport
import flow_swift_future
// ...
```
The module has to have dependencies set up properly in `CMake` as well, check `CMakeLists.txt` for examples.
### Futures and other templates
Swift's C++ interop cannot currently instantiate class templates in Swift, but can use specialized templates if they are declared so in C++. For example, in order to use Flow futures and promises in Swift, we currently need to type alias them on the C++ side, and then use the type alias name in Swift, like so:
```swift
// flow/include/flow/swift_future_support.h
using PromiseCInt = Promise<int>;
using FutureCInt = Future<int>;
using PromiseVoid = Promise<Void>;
using FutureVoid = Future<Void>;
```
To use them in Swift make sure to use the type-aliases:
```swift
public func getVersion(cxxState: MasterData, req: GetCommitVersionRequest, result promise: PromiseVoid) {
// ...
}
```
### Sendable
Sendable is Swift's mechanism to ensure compile time thread-safety. It does so by marking types known to be safe to send across task/actor/thread boundaries with `Sendable` (i.e. `struct Something: Sendable {}` and checks related to it).
In order to declare a C++ type as Sendable you can use the @Sendable attribute:
```cpp
#define SWIFT_SENDABLE \
__attribute__((swift_attr("@Sendable")))
```
which is used like this:
```cpp
template <class T>
class SWIFT_SENDABLE Future {
// ...
};
```
### Importing types
Swift can import copyable C++ structs and classes as Swift value types. Non-copyable types with value semantics require special wrappers to make them available in Swift (see the "Non-copyable types" section).
Swift can also import some C++ types as reference types, if they're appropriately annotated. The sections "Bridging Singleton-like values with immortal reference types" and "Bridging reference types" describes how that can be done.
Swift will avoid importing "unsafe projections", which means a type which contains pointers to something "outside" the type, as they may end up pointing at unsafe memory.
#### Non-copyable types
Swift's C++ interop currently cannot import non-copyable C++ types. If you have a type that's non-copyable that you want to use as a value type in Swift (i.e. it's typically used as its own type in C++, not through a pointer type), you most likely want to wrap it in a copyable type which is then made available to Swift. For types that have reference semantics (i.e. you always pass it around using a raw pointer or a custom smart pointer type in C++), see the "Bridging reference types" section.
For example, Flow's `Counter` type is non-copyable. We can make it available to
Swift so that it can be used as a stored property in a Swift actor by creating a value
type wrapper for it in C++, that stores the counter in a shared pointer value:
```cpp
// A type with Swift value semantics for working with `Counter` types.
class CounterValue {
public:
using Value = Counter::Value;
CounterValue(std::string const& name, CounterCollection& collection);
void operator+=(Value delta);
void operator++();
void clear();
private:
std::shared_ptr<Counter> value;
};
```
We want to implement the required interface for this type that's needed from Swift.
#### Bridging Singleton-like values with immortal reference types
Certain C++ types act as singletons which have one value referenced throughout codebase. That value is expected to be alive throughout the lifetime of the program. Such types can be bridged to Swift using the `SWIFT_CXX_IMMORTAL_SINGLETON_TYPE` annotation. This annotation will instruct Swift to import such type as reference type that doesn't need any reference counting, i.e. it's assumed to be immortal.
For instance, the `INetwork` interface type:
```cpp
class SWIFT_CXX_IMMORTAL_SINGLETON_TYPE ServerKnobs : public KnobsImpl<ServerKnobs> {
public:
```
Gets bridged over to an immortal reference type in Swift:
```Swift
let knobs = getServerKnobs() // knobs type is `ServerKnobs` in Swift, identical to `ServerKnobs *` C++ type.
knobs.MAX_VERSION_RATE_MODIFIER
```
#### Bridging reference types
Some C++ types have reference counting and referencial semantics, i.e. they're passed around using raw or smart pointers that point to an instance. That instance typically has its own reference count, that keeps track of when the instance should be released. Such types can be bridged over to Swift reference types, and Swift's automatic reference counting (ARC) will automatically retain and release them using their C++ reference counting implementation.
You can use the `SWIFT_CXX_REF` annotation for that. Right now `SWIFT_CXX_REF` does not work (due to https://github.com/apple/swift/issues/61620), so you have to make a custom annotation for each class you want to bridge with reference semantics to Swift. For example, the `MasterData` class receives the following annotation:
```cpp
#define SWIFT_CXX_REF_MASTERDATA \
__attribute__((swift_attr("import_as_ref"))) \
__attribute__((swift_attr("retain:addrefMasterData"))) \
__attribute__((swift_attr("release:delrefMasterData")))
struct SWIFT_CXX_REF_MASTERDATA MasterData : NonCopyable, ReferenceCounted<MasterData> {
```
This annotation then makes Swift's' `MasterData` type behave like C++/Flow's `Reference<MasterData>` type, i.e. it is automatically reference counted in Swift.
### Awaiting Flow concurrency types
Flow **Futures** can be awaited on in Swift, like this:
```swift
var f: FutureCInt = ...
await f.value()
```
to avoid name clashes with `value` it's currently called `waitValue` though we should probably rename this.
You can also await a next value of a stream:
```swift
var ps = PromiseStream<CInt>()
var fs: FutureStream<CInt> = ps.getFuture()
// ...
let element = try? await fs.waitNext // suspends until value is sent into `ps`
```
It is also possible to use the `async for-loop` syntax on `FutureStream`s:
```swift
for try await num in fs {
// ...
}
```
This future will loop until an "end" is sent to the stream.
Sending an "end" element is currently done the same way as in Flow itself:
```swift
var i: CInt = 10
ps.send(&i)
ps.sendError(end_of_stream())
```
## C++ → Swift
Calling Swift from C++ is relatively simple, you can write new Swift code and `@_expose(Cxx)` them, like this free function in Swift:
```swift
@_expose(Cxx)
public func swiftyTestRunner(p: PromiseVoid) { }
```
This can be called from C++ as expected:
```swift
fdbserver_swift::swiftyTestRunner(p);
```
### Exposing actors and async functions
Actors in Swift have strong isolation properties and cannot be accessed synchronously, as such, every method declared on an actor is implicitly `async` if called from the outside. For example:
```swift
@_expose(Cxx)
actor Example {
func hi() -> String { "hi!" }
}
```
this `hi()` method is not imported into C++, so you'd get an error when trying to call it on an instance of `Example` from C++:
```
<<error not imported>>
```
This is because, calls "into" an actor are implicitly async, so the method is effectively async:
```swift
@_expose(Cxx)
actor Example {
func hi() async -> CInt { 42 }
}
```
Since C++ does not understand Swift's async calling convention, we don't import such methods today. This is why today we implement a `nonisolated` wrapper method in order to bridge Flow/C++ and the Swift actor method, like this:
```swift
@_expose(Cxx)
actor Example {
func hi() async -> CInt { 42 }
nonisolated func hi(/*other params*/ result: PromiseCInt) {
Task { // schedule a new Task
var i = await self.hi() // inside that task, await the hi() method
result.send(&i)
}
}
}
```
And since the `Example` was `_expose`-d the `nonisolated func hi(result:)` is as well, and that can be called from C++:
```swift
// C++
auto promise = Promise<int>();
actor.hi(promise);
wait(p.getFuture());
```
> Note: We hope to simplify this, so you could just `wait(actor.hi())`. And the nonisolated wrapper can also be implemented using a Swift macro once they land (Swift macros are work in progress, and operate on AST, so we're not too worried about using them -- i.e. they are not just textual macros).
## Swift tests
We have prepared a ver simple test suite runner since we cannot use the usual Swift's XCTest framework (maybe we could, didn't investigate yet). To run `swift` tests inside `fdbserver` run like this:
```bash
FDBSWIFTTEST=1 bin/fdbserver -p AUTO
```
you can also `--filter-test future` etc.