Skip to content

[6.2] Fix linking Embedded Swift concurrency for Wasm #83288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 19 commits into
base: release/6.2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
75593de
test: set `lit.py` args in separate `setup_lit_args` (#82878)
MaxDesiatov Jul 10, 2025
7c7d9cd
test/CMakeLists.txt: run Embedded Swift for Wasm tests
MaxDesiatov Jul 17, 2025
fa4c9aa
test/embedded/dependencies-random: use sed instead of ex
MaxDesiatov Jul 17, 2025
86b85f9
test: simpler `sed` expression in `dependencies-random.swift`
MaxDesiatov Jul 21, 2025
8e1adb6
Update for more tests
MaxDesiatov Jun 24, 2025
ba32d27
Fix library path in`concurrency-simple.swift`
MaxDesiatov Jul 1, 2025
ba59931
Disable one more ARM-specific IR/SIL test on WASI
MaxDesiatov Jun 27, 2025
46391f2
Enable `concurrency-deleted-method.swift` test for `wasip1`
MaxDesiatov Jul 22, 2025
b1dd110
Update `lit.cfg` with subs required for embedded `wasip1`
MaxDesiatov Jul 22, 2025
798acfd
Enable `MainActor` in embedded concurrency, add `ExecutorImpl.cpp`
MaxDesiatov Jun 18, 2025
ff4b6cb
Embedded: only include `ExecutorImpl.cpp` for Wasm
MaxDesiatov Jul 21, 2025
7678991
Embedded: exclude `ExecutorImpl.swift` for non-Wasm platforms
MaxDesiatov Jul 21, 2025
45c08e7
Enable `concurrency-deleted-method.swift` test for `wasip1` (#83236)
MaxDesiatov Jul 25, 2025
0b923c6
[Concurrency][Embedded] Make sure Embedded Swift links Impl functions.
al45tair Jun 16, 2025
d8a7b20
[Concurrency][Embedded] Add some extra Concurrency functions.
al45tair Jul 8, 2025
bc2b27d
Link.cpp: load `MainActor: Actor` WT for Embedded Swift
MaxDesiatov Jul 11, 2025
9437bb1
Add `linkWitnessTable` to `SILModule`, use in `Link.cpp`
MaxDesiatov Jul 15, 2025
8f4763e
Enable more `MainActor` code paths for Embedded Swift
MaxDesiatov Jul 17, 2025
518e490
Executor.swift: only enable `MainActor` in Embedded Swift for WASI
MaxDesiatov Jul 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions include/swift/SIL/SILModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,11 @@ class SILModule {
/// Returns true if linking succeeded, false otherwise.
bool linkFunction(SILFunction *F, LinkingMode LinkMode);

/// Attempt to deserialize witness table for protocol conformance \p PC.
///
/// Returns true if linking succeeded, false otherwise.
bool linkWitnessTable(ProtocolConformance *PC, LinkingMode LinkMode);

/// Check if a given function exists in any of the modules.
/// i.e. it can be linked by linkFunction.
bool hasFunction(StringRef Name);
Expand Down
4 changes: 4 additions & 0 deletions lib/SIL/IR/SILModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,10 @@ bool SILModule::linkFunction(SILFunction *F, SILModule::LinkingMode Mode) {
return SILLinkerVisitor(*this, Mode).processFunction(F);
}

bool SILModule::linkWitnessTable(ProtocolConformance *PC, SILModule::LinkingMode Mode) {
return SILLinkerVisitor(*this, Mode).processConformance(ProtocolConformanceRef(PC));
}

bool SILModule::hasFunction(StringRef Name) {
if (lookUpFunction(Name))
return true;
Expand Down
47 changes: 46 additions & 1 deletion lib/SILOptimizer/UtilityPasses/Link.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//

#include "swift/AST/ProtocolConformance.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SIL/SILModule.h"
Expand Down Expand Up @@ -48,6 +49,7 @@ class SILLinker : public SILModuleTransform {
// (swift_retain, etc.). Link them in so they can be referenced in IRGen.
if (M.getOptions().EmbeddedSwift && LinkEmbeddedRuntime) {
linkEmbeddedRuntimeFromStdlib();
linkEmbeddedConcurrency();
}

// In embedded Swift, we need to explicitly link any @_used globals and
Expand Down Expand Up @@ -80,6 +82,32 @@ class SILLinker : public SILModuleTransform {
linkEmbeddedRuntimeFunctionByName("swift_retainCount", { RefCounting });
}

void linkEmbeddedConcurrency() {
using namespace RuntimeConstants;

// Note: we ignore errors here, because, depending on the exact situation
//
// (a) We might not have Concurrency anyway, and
//
// (b) The Impl function might be implemented in C++.
//
// Also, the hook Impl functions are marked as internal, unlike the
// runtime functions, which are public.

#define SWIFT_CONCURRENCY_HOOK(RETURNS, NAME, ...) \
linkUsedFunctionByName(#NAME "Impl", SILLinkage::HiddenExternal)
#define SWIFT_CONCURRENCY_HOOK0(RETURNS, NAME) \
linkUsedFunctionByName(#NAME "Impl", SILLinkage::HiddenExternal)

#include "swift/Runtime/ConcurrencyHooks.def"

linkUsedFunctionByName("swift_task_asyncMainDrainQueueImpl",
SILLinkage::HiddenExternal);
linkUsedFunctionByName("swift_createDefaultExecutors",
SILLinkage::HiddenExternal);
linkEmbeddedRuntimeWitnessTables();
}

void linkEmbeddedRuntimeFunctionByName(StringRef name,
ArrayRef<RuntimeEffect> effects) {
SILModule &M = *getModule();
Expand All @@ -96,6 +124,23 @@ class SILLinker : public SILModuleTransform {
linkUsedFunctionByName(name, SILLinkage::PublicExternal);
}

void linkEmbeddedRuntimeWitnessTables() {
SILModule &M = *getModule();

auto *mainActor = M.getASTContext().getMainActorDecl();
if (mainActor) {
for (auto *PC : mainActor->getAllConformances()) {
auto *ProtoDecl = PC->getProtocol();
if (ProtoDecl->getName().str() == "Actor") {
M.linkWitnessTable(PC, SILModule::LinkingMode::LinkAll);
if (auto *WT = M.lookUpWitnessTable(PC)) {
WT->setLinkage(SILLinkage::Public);
}
}
}
}
}

SILFunction *linkUsedFunctionByName(StringRef name,
std::optional<SILLinkage> Linkage) {
SILModule &M = *getModule();
Expand Down Expand Up @@ -134,7 +179,7 @@ class SILLinker : public SILModuleTransform {
// linked global variable.
if (GV->isDefinition())
GV->setLinkage(SILLinkage::Public);

return GV;
}

Expand Down
20 changes: 15 additions & 5 deletions stdlib/public/Concurrency/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ set(SWIFT_RUNTIME_CONCURRENCY_C_SOURCES
Actor.cpp
AsyncLet.cpp
Clock.cpp
GlobalExecutor.cpp
ConcurrencyHooks.cpp
GlobalExecutor.cpp
EmbeddedSupport.cpp
Error.cpp
ExecutorBridge.cpp
Expand Down Expand Up @@ -196,10 +196,6 @@ else()
)
endif()

set(SWIFT_RUNTIME_CONCURRENCY_EMBEDDED_SWIFT_SOURCES
PlatformExecutorNone.swift
)

set(LLVM_OPTIONAL_SOURCES
DispatchGlobalExecutor.cpp
CooperativeGlobalExecutor.cpp
Expand Down Expand Up @@ -316,6 +312,20 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB AND SWIFT_SHOULD_BUILD_EMBEDDED_CONCURRENC
set(extra_swift_compile_flags)
endif()

if("${arch}" MATCHES "wasm32")
set(SWIFT_RUNTIME_CONCURRENCY_EMBEDDED_SWIFT_SOURCES
ExecutorImpl.swift
PlatformExecutorCooperative.swift
)
list(APPEND SWIFT_RUNTIME_CONCURRENCY_C_SOURCES
ExecutorImpl.cpp
)
else()
set(SWIFT_RUNTIME_CONCURRENCY_EMBEDDED_SWIFT_SOURCES
PlatformExecutorNone.swift
)
endif()

set(SWIFT_SDK_embedded_THREADING_PACKAGE none)
set(SWIFT_SDK_embedded_ARCH_${arch}_MODULE "${mod}")
set(SWIFT_SDK_embedded_ARCH_${mod}_MODULE "${mod}")
Expand Down
23 changes: 12 additions & 11 deletions stdlib/public/Concurrency/Executor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,12 @@ extension Executor where Self: Equatable {
}

extension Executor {

#if !$Embedded
#if os(WASI) || !$Embedded
// This defaults to `false` so that existing third-party Executor
// implementations will work as expected.
@available(StdlibDeploymentTarget 6.2, *)
var isMainExecutor: Bool { false }
#endif
#endif // os(WASI) || !$Embedded

}

Expand Down Expand Up @@ -346,10 +347,10 @@ public protocol SerialExecutor: Executor {
@available(StdlibDeploymentTarget 6.0, *)
extension SerialExecutor {

#if !$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
#if os(WASI) || (!$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY)
@available(StdlibDeploymentTarget 6.2, *)
var isMainExecutor: Bool { return MainActor.executor._isSameExecutor(self) }
#endif
#endif // os(WASI) || (!$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY)

@available(StdlibDeploymentTarget 6.0, *)
public func checkIsolated() {
Expand Down Expand Up @@ -552,11 +553,11 @@ protocol MainExecutor: RunLoopExecutor, SerialExecutor {
/// executors.
@available(StdlibDeploymentTarget 6.2, *)
protocol ExecutorFactory {
#if !$Embedded
#if os(WASI) || !$Embedded
/// Constructs and returns the main executor, which is started implicitly
/// by the `async main` entry point and owns the "main" thread.
static var mainExecutor: any MainExecutor { get }
#endif
#endif // os(WASI) || !$Embedded

/// Constructs and returns the default or global executor, which is the
/// default place in which we run tasks.
Expand All @@ -569,9 +570,9 @@ typealias DefaultExecutorFactory = PlatformExecutorFactory
@available(StdlibDeploymentTarget 6.2, *)
@_silgen_name("swift_createExecutors")
func _createExecutors<F: ExecutorFactory>(factory: F.Type) {
#if !$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
#if os(WASI) || (!$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY)
MainActor._executor = factory.mainExecutor
#endif
#endif // os(WASI) || (!$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY)
Task._defaultExecutor = factory.defaultExecutor
}

Expand All @@ -583,7 +584,7 @@ func _createDefaultExecutors() {
}
}

#if !$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
#if os(WASI) || (!$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY)
@available(StdlibDeploymentTarget 6.2, *)
extension MainActor {
static var _executor: (any MainExecutor)? = nil
Expand All @@ -599,7 +600,7 @@ extension MainActor {
return _executor!
}
}
#endif // !$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
#endif // os(WASI) || (!$Embedded && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY)

@available(StdlibDeploymentTarget 6.2, *)
extension Task where Success == Never, Failure == Never {
Expand Down
4 changes: 2 additions & 2 deletions stdlib/public/Concurrency/ExecutorBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ internal func _jobGetExecutorPrivateData(
_ job: Builtin.Job
) -> UnsafeMutableRawPointer

#if !$Embedded
#if os(WASI) || !$Embedded
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
@available(StdlibDeploymentTarget 6.2, *)
@_silgen_name("swift_getMainExecutor")
Expand All @@ -100,7 +100,7 @@ internal func _getMainExecutorAsSerialExecutor() -> (any SerialExecutor)? {
@_silgen_name("swift_getMainExecutor")
internal func _getMainExecutorAsSerialExecutor() -> (any SerialExecutor)?
#endif // SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
#endif // !$Embedded
#endif // os(WASI) || !$Embedded

@available(StdlibDeploymentTarget 6.2, *)
@_silgen_name("swift_dispatchMain")
Expand Down
4 changes: 2 additions & 2 deletions stdlib/public/Concurrency/MainActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import Swift

#if !$Embedded
#if os(WASI) || !$Embedded

#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
@available(SwiftStdlib 5.1, *)
Expand Down Expand Up @@ -197,4 +197,4 @@ public func _deinitOnExecutorMainActorBackDeploy(

#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY

#endif // !$Embedded
#endif // os(WASI) || !$Embedded
12 changes: 0 additions & 12 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -677,18 +677,6 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB_CROSS_COMPILING)
"${CMAKE_CURRENT_BINARY_DIR}${VARIANT_SUFFIX}/lit.site.cfg"
"test${VARIANT_SUFFIX}.lit.site.cfg")
swift_generate_lit_swift_features_cfg("${CMAKE_CURRENT_BINARY_DIR}${VARIANT_SUFFIX}/lit.swift-features.cfg")

set(VARIANT_SUFFIX "-embedded-wasi")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variant is already enabled above on L610, removing as unused

set(VARIANT_TRIPLE "wasm32-unknown-wasip1")
set(VARIANT_EXTERNAL_EMBEDDED_PLATFORM FALSE)
set(VARIANT_EXTERNAL_EMBEDDED_DEVICE)
set(SWIFT_TEST_RESULTS_DIR "${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/swift-test-results/${VARIANT_TRIPLE}")
swift_configure_lit_site_cfg(
"${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in"
"${CMAKE_CURRENT_BINARY_DIR}${VARIANT_SUFFIX}/lit.site.cfg"
"test${VARIANT_SUFFIX}.lit.site.cfg")
swift_generate_lit_swift_features_cfg("${CMAKE_CURRENT_BINARY_DIR}${VARIANT_SUFFIX}/lit.swift-features.cfg")
message(STATUS "SWIFT_LIT_ARGS is ${SWIFT_LIT_ARGS}")
endif()

# Add shortcuts for the default variant.
Expand Down
12 changes: 6 additions & 6 deletions test/embedded/concurrency-deleted-method.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -enable-experimental-feature Embedded -parse-as-library -module-name main %s -emit-ir | %FileCheck --check-prefix=CHECK-IR %s
// RUN: %target-swift-frontend -enable-experimental-feature Embedded -parse-as-library -module-name main %s -c -o %t/a.o
// RUN: %target-clang %t/a.o -o %t/a.out -L%swift_obj_root/lib/swift/embedded/%target-cpu-apple-macos -lswift_Concurrency -lswift_ConcurrencyDefaultExecutor -dead_strip
// RUN: %target-run %t/a.out | %FileCheck %s
// RUN: %target-clang %t/a.o -o %t/a.out -L%swift_obj_root/lib/swift/embedded/%module-target-triple %target-clang-resource-dir-opt -lswift_Concurrency %target-swift-default-executor-opt -dead_strip
// RUN: if [ %target-os != "wasip1" ]; then %target-run %t/a.out | %FileCheck %s; fi

// REQUIRES: executable_test
// REQUIRES: swift_in_compiler
// REQUIRES: optimized_stdlib
// REQUIRES: OS=macosx
// REQUIRES: OS=macosx || OS=wasip1
// REQUIRES: swift_feature_Embedded

import _Concurrency
Expand All @@ -31,7 +31,7 @@ actor MyActor {
}

// CHECK-IR: @swift_deletedAsyncMethodErrorTu =
// CHECK-IR: @"$e4main7MyActorCN" = global <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }> <{
// CHECK-IR: @"$e4main7MyActorCN" = global <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }> <{
// CHECK-IR-SAME: ptr null,
// CHECK-IR-SAME: ptr @"$e4main7MyActorCfD",
// CHECK-IR-SAME: ptr null,
Expand All @@ -41,10 +41,10 @@ actor MyActor {
// CHECK-IR-SAME: ptr @"$e4main7MyActorC3fooyyYaFTu",
// CHECK-IR-SAME: ptr @got.swift_deletedAsyncMethodErrorTu,
// CHECK-IR-SAME: ptr @"$e4main7MyActorCACycfC"
// CHECK-IR-SAME: }>, align 8
// CHECK-IR-SAME: }>, align {{[48]}}

// CHECK-IR-NOT: $e4main7MyActorC12thisIsUnusedyyYaF

// CHECK-IR: define swifttailcc void @swift_deletedAsyncMethodError(ptr swiftasync %0)
// CHECK-IR: define {{swifttailcc|swiftcc}} void @swift_deletedAsyncMethodError(ptr swiftasync %0)

// CHECK: value: 42
2 changes: 1 addition & 1 deletion test/embedded/concurrency-simple.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -enable-experimental-feature Embedded -parse-as-library %s -c -o %t/a.o
// RUN: %target-clang %t/a.o -o %t/a.out -L%swift_obj_root/lib/swift/embedded/%target-cpu-apple-macos -lswift_Concurrency -lswift_ConcurrencyDefaultExecutor -dead_strip
// RUN: %target-clang %target-clang-resource-dir-opt %t/a.o -o %t/a.out -L%swift_obj_root/lib/swift/embedded/%module-target-triple -lc++ -lswift_Concurrency -lswift_ConcurrencyDefaultExecutor -dead_strip
// RUN: %target-run %t/a.out | %FileCheck %s

// REQUIRES: executable_test
Expand Down
4 changes: 4 additions & 0 deletions test/lit.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,7 @@ config.target_static_library_suffix = '.a'
config.target_env_prefix = ''
config.target_sdk_libcxx_path = ''
config.target_clang_resource_dir_opt = ''
config.target_swift_default_executor_opt = '-lswift_ConcurrencyDefaultExecutor'

if run_vendor == 'apple':
target_specific_module_triple = '{}-apple-{}'.format(
Expand Down Expand Up @@ -2051,6 +2052,7 @@ elif kIsWASI:
config.target_runtime = "native"
config.target_not_crash = "not"
config.target_clang_resource_dir_opt = f"-resource-dir {test_resource_dir}/../../../wasi-sysroot/wasm32-wasip1"
config.target_swift_default_executor_opt = ''

config.target_swift_autolink_extract = inferSwiftBinary("swift-autolink-extract")

Expand Down Expand Up @@ -2222,6 +2224,7 @@ config.substitutions.append(('%module-target-future', target_future))

# Add 'target-sdk-name' as the name for platform-specific directories
config.substitutions.append(('%target-sdk-name', config.target_sdk_name))
config.substitutions.append(('%target-sdk', config.variant_sdk))

# Also add 'target-arch' so we can locate some things inside the build tree
config.substitutions.append(('%target-arch', target_arch))
Expand Down Expand Up @@ -3027,6 +3030,7 @@ config.substitutions.append(('%llvm-cov', config.llvm_cov))
config.substitutions.append(('%hmaptool', os.path.join(config.llvm_src_root, '..', 'clang', 'utils', 'hmaptool', 'hmaptool')))
config.substitutions.append(('%target-not-crash', config.target_not_crash))
config.substitutions.insert(0, ('%target-clang-resource-dir-opt', config.target_clang_resource_dir_opt))
config.substitutions.insert(0, ('%target-swift-default-executor-opt', config.target_swift_default_executor_opt))

# Set up the host library environment.
if hasattr(config, 'target_library_path_var'):
Expand Down