Skip to content

Commit 01be666

Browse files
committed
[concurrency] Change #isolated to mask out 2 TBI bits of the witness pointer of the implicit isolated any Actor pointer so we can do optimizations on TBI supporting platforms in the future.
The specific bits that we mask are the bottom two bits of the top nibble of the TBI. This will let us perform an optimization in the future where a caller can set bits in the TBI byte to signal information dynamically to a nonisolated(nonsending) callee. The intent is that this will let us signal to a nonisolated(nonsending) callee that the caller isn't performing any isolated work before it hops (or potentially a caller of the caller) hops meaning that the nonisolated(nonsending) function can avoid a tail hop that restores isolation after calling some sort of actor isolated call. E.x.: ```swift nonisolated(nonsending) func callsActor(_ a: MyActor) async { await a.doSomething() } @Concurrent func runOnBackground() async {} actor A { func doSomething() async { ... } func caller() async { await callsActor(self) await runOnBackgroud() } } ``` In the above case, the hop back in callsActor is not needed dynamically in caller since we are going to immediately await ontot he background. But we do not know that. An optimization can be written that can recognize this case and dynamically tell via the tbi bits of the protocol witness table of the any Actor call that the hop to executor call can avoid the hop. Since this only applies to the implicit isolated any Actor parameter which is only used for the purpose of hopping, it is safe to do this since when we hop we just call the Actor unowned executor dispatch thunk which directly accesses the witness table by loading from that pointer... meaning that anything set in the TBI bit is irrelevent. The only other way to grab this implicit bit is to use `#isolation` and that is where this commit comes into play. Since we can access the actor via `#isolation`, we need to ensure that we mask out the TBI bits so that the rest of the runtime/compiled code never see the TBI bits. This is because we do not want the fact that we are using these TBI bits to ever escape into other code (e.x.: consider things that may cast). So we have basically created a hermetically sealed world where we can pass information from callee to caller using the protocol witness TBI bits. I also provided a similar implementation for platforms without TBI that use the tagged pointer bits of the protocol witness function. This is behind an experimental feature flag called NonisolatedNonsendingDynamicHopElim that is only available in asserts builds. rdar://156525771
1 parent e2ad4a9 commit 01be666

18 files changed

+437
-31
lines changed

include/swift/Basic/Features.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,10 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(Lifetimes, true)
541541
/// Allow macro based aliases to be imported into Swift
542542
EXPERIMENTAL_FEATURE(ImportMacroAliases, true)
543543

544+
/// Allow support for the nonisolated(nonsending) dynamic hop elim off of
545+
/// on platforms that do not support aarch64 tbi.
546+
EXPERIMENTAL_FEATURE(NonisolatedNonsendingDynamicHopElim, false)
547+
544548
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
545549
#undef EXPERIMENTAL_FEATURE
546550
#undef UPCOMING_FEATURE

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,9 @@ namespace swift {
670670
bool RestrictNonProductionExperimentalFeatures = false;
671671
#endif
672672

673+
/// Set to true if we support AArch64TBI.
674+
bool HasAArch64TBI = false;
675+
673676
bool isConcurrencyModelTaskToThread() const {
674677
return ActiveConcurrencyModel == ConcurrencyModel::TaskToThread;
675678
}

include/swift/Frontend/Frontend.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ class CompilerInvocation {
274274
/// C++ stdlib is the default for the specified target.
275275
void computeCXXStdlibOptions();
276276

277+
/// Compute whether or not we support aarch64TBI
278+
void computeAArch64TBIOptions();
279+
277280
/// Computes the runtime resource path relative to the given Swift
278281
/// executable.
279282
static void computeRuntimeResourcePathFromExecutablePath(

include/swift/SIL/SILType.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,10 @@ class SILType {
10251025
/// Return '()'
10261026
static SILType getEmptyTupleType(const ASTContext &C);
10271027

1028+
/// Return (elementTypes).
1029+
static SILType getTupleType(const ASTContext &ctx,
1030+
ArrayRef<SILType> elementTypes);
1031+
10281032
/// Get the type for opaque actor isolation values.
10291033
static SILType getOpaqueIsolationType(const ASTContext &C);
10301034

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ UNINTERESTING_FEATURE(SuppressedAssociatedTypes)
123123
UNINTERESTING_FEATURE(StructLetDestructuring)
124124
UNINTERESTING_FEATURE(MacrosOnImports)
125125
UNINTERESTING_FEATURE(NonisolatedNonsendingByDefault)
126+
UNINTERESTING_FEATURE(NonisolatedNonsendingDynamicHopElim)
126127
UNINTERESTING_FEATURE(KeyPathWithMethodMembers)
127128
UNINTERESTING_FEATURE(ImportMacroAliases)
128129
UNINTERESTING_FEATURE(NoExplicitNonIsolated)

lib/DriverTool/sil_opt_main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,8 @@ int sil_opt_main(ArrayRef<const char *> argv, void *MainAddr) {
814814
Invocation.getLangOptions().UnavailableDeclOptimizationMode =
815815
options.UnavailableDeclOptimization;
816816

817+
Invocation.computeAArch64TBIOptions();
818+
817819
// Enable strict concurrency if we have the feature specified or if it was
818820
// specified via a command line option to sil-opt.
819821
if (Invocation.getLangOptions().hasFeature(Feature::StrictConcurrency)) {

lib/Frontend/CompilerInvocation.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,13 @@ setBridgingHeaderFromFrontendOptions(ClangImporterOptions &ImporterOpts,
382382
FrontendOpts.InputsAndOutputs.getFilenameOfFirstInput();
383383
}
384384

385+
void CompilerInvocation::computeAArch64TBIOptions() {
386+
auto &LLVMArgs = getFrontendOptions().LLVMArgs;
387+
auto aarch64_use_tbi =
388+
std::find(LLVMArgs.begin(), LLVMArgs.end(), "-aarch64-use-tbi");
389+
LangOpts.HasAArch64TBI = aarch64_use_tbi != LLVMArgs.end();
390+
}
391+
385392
void CompilerInvocation::computeCXXStdlibOptions() {
386393
// The MSVC driver in Clang is not aware of the C++ stdlib, and currently
387394
// always assumes libstdc++, which is incorrect: the Microsoft stdlib is
@@ -4108,6 +4115,8 @@ bool CompilerInvocation::parseArgs(
41084115
setIRGenOutputOptsFromFrontendOptions(IRGenOpts, FrontendOpts);
41094116
setBridgingHeaderFromFrontendOptions(ClangImporterOpts, FrontendOpts);
41104117
computeCXXStdlibOptions();
4118+
computeAArch64TBIOptions();
4119+
41114120
if (LangOpts.hasFeature(Feature::Embedded)) {
41124121
IRGenOpts.InternalizeAtLink = true;
41134122
IRGenOpts.DisableLegacyTypeInfo = true;

lib/IRGen/GenThunk.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,23 @@ void IRGenThunk::prepareArguments() {
137137

138138
if (isWitnessMethod) {
139139
witnessMetadata.SelfWitnessTable = original.takeLast();
140+
141+
// Mask out the bottom bits when we dispatch specifically to
142+
// Actor.unownedExecutor if we NonisolatedNonsendingDynamicHopElim is
143+
// enabled and we are not on a platform that supports TBI.
144+
auto &langOpts = IGF.getSILModule().getASTContext().LangOpts;
145+
if (langOpts.hasFeature(Feature::NonisolatedNonsendingDynamicHopElim) &&
146+
!langOpts.HasAArch64TBI &&
147+
IGF.CurFn->getName() == "$sScA15unownedExecutorScevgTj") {
148+
auto type = llvm::Type::getInt64Ty(IGF.CurFn->getContext());
149+
auto pointerCast = IGF.Builder.CreateBitOrPointerCast(
150+
witnessMetadata.SelfWitnessTable, type);
151+
witnessMetadata.SelfWitnessTable = IGF.Builder.CreateBitOrPointerCast(
152+
IGF.Builder.CreateBinOp(llvm::Instruction::BinaryOps::And,
153+
pointerCast,
154+
llvm::ConstantInt::get(IGF.IGM.IntPtrTy, -4)),
155+
witnessMetadata.SelfWitnessTable->getType());
156+
}
140157
witnessMetadata.SelfMetadata = original.takeLast();
141158
}
142159

lib/SIL/IR/SILType.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,21 @@ SILType SILType::getEmptyTupleType(const ASTContext &C) {
103103
return getPrimitiveObjectType(C.TheEmptyTupleType);
104104
}
105105

106+
SILType SILType::getTupleType(const ASTContext &ctx,
107+
ArrayRef<SILType> elementTypes) {
108+
if (elementTypes.empty())
109+
return SILType::getEmptyTupleType(ctx);
110+
111+
SmallVector<TupleTypeElt, 8> tupleTypeElts;
112+
auto category = elementTypes[0].getCategory();
113+
for (auto type : elementTypes) {
114+
assert(type.getCategory() == category && "Mismatched tuple elt categories");
115+
tupleTypeElts.push_back(TupleTypeElt(type.getRawASTType()));
116+
}
117+
auto tupleType = TupleType::get(tupleTypeElts, ctx);
118+
return SILType::getPrimitiveType(tupleType->getCanonicalType(), category);
119+
}
120+
106121
SILType SILType::getSILTokenType(const ASTContext &C) {
107122
return getPrimitiveObjectType(C.TheSILTokenType);
108123
}

lib/SILGen/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ add_swift_host_library(swiftSILGen STATIC
33
ArgumentSource.cpp
44
Cleanup.cpp
55
Condition.cpp
6+
ConcurrencyUtils.cpp
67
FormalEvaluation.cpp
78
ManagedValue.cpp
89
ResultPlan.cpp

0 commit comments

Comments
 (0)