[concurrency] Implement bit masking for TBI when available or in tagged pointer bits otherwise.

Specifically, when TBI is available we use the bottom two bits of the top nibble
(bits 60,61). On platforms without TBI, we use the bottom two tagged pointer
bits (bits 0, 1).

rdar://156525771
This commit is contained in:
Michael Gottesman
2025-08-06 17:01:52 -07:00
parent dc193063cf
commit 390afe3e7d
11 changed files with 125 additions and 162 deletions

View File

@@ -1,38 +0,0 @@
//===--- ConcurrencyUtils.h -----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_SIL_CONCURRENCYUTILS_H
#define SWIFT_SIL_CONCURRENCYUTILS_H
#include "swift/SIL/SILType.h"
namespace swift {
class SILValue;
class SILBuilder;
class SILLocation;
/// Clear the implicit isolated bits of value.
///
/// \p value must be Builtin.ImplicitActor
///
/// \p finalType if empty, we always return
/// Builtin.ImplicitActor. Otherwise we bitcast to finalType after
/// tieing the lifetime of the result to \p value.
SILValue clearImplicitActorBits(SILBuilder &b, SILLocation loc, SILValue value,
SILType finalType = {});
SILValue setImplicitActorBits(SILBuilder &b, SILLocation loc, SILValue value);
} // namespace swift
#endif

View File

@@ -312,7 +312,9 @@ public:
bool isBoxWithAddress() const {
return kind == Kind::OwnedAddress;
}
bool isExplosionVector() const { return kind == Kind::ExplosionVector; }
const StackAddress &getStackAddress() const {
return Storage.get<StackAddress>(kind);
}
@@ -3377,12 +3379,10 @@ void IRGenSILFunction::visitExistentialMetatypeInst(
setLoweredExplosion(i, result);
}
static void emitApplyArgument(IRGenSILFunction &IGF,
SILValue arg,
SILType paramType,
Explosion &out,
SILInstruction *apply = nullptr,
unsigned idx = 0) {
static void emitApplyArgument(IRGenSILFunction &IGF, SILValue arg,
SILType paramType, Explosion &out,
SILInstruction *apply = nullptr, unsigned idx = 0,
bool isImplicitIsolatedParameter = false) {
bool isSubstituted = (arg->getType() != paramType);
// For indirect arguments, we just need to pass a pointer.
@@ -3424,7 +3424,20 @@ static void emitApplyArgument(IRGenSILFunction &IGF,
}
canForwardLoadToIndirect = true;
}();
IGF.getLoweredExplosion(arg, out);
// If we are emitting a parameter for an implicit isolated parameter, then
// we need to clear the implicit isolated actor bits.
if (isImplicitIsolatedParameter) {
auto &loweredValue = IGF.getLoweredValue(arg);
assert(loweredValue.isExplosionVector() &&
"Should be an explosion of two pointers");
auto explosionVector = loweredValue.getKnownExplosionVector();
assert(explosionVector.size() == 2 && "We should have two values");
out.add(explosionVector[0]);
out.add(clearImplicitIsolatedActorBits(IGF, explosionVector[1]));
} else {
IGF.getLoweredExplosion(arg, out);
}
if (canForwardLoadToIndirect) {
IGF.setForwardableArgument(idx);
}
@@ -3842,6 +3855,20 @@ void IRGenSILFunction::visitFullApplySite(FullApplySite site) {
}
}
// Extract the implicit isolated parameter so that we can mask it as
// appropriate.
//
// NOTE: We cannot just drop_front since we could be between the indirect
// results and the parameters.
std::optional<unsigned> implicitIsolatedParameterIndex;
if (auto actorIsolation = site.getFunction()->getActorIsolation();
actorIsolation && actorIsolation->isCallerIsolationInheriting() &&
site.isCallerIsolationInheriting()) {
auto *iso = site.getIsolatedArgumentOperandOrNullPtr();
assert(iso);
implicitIsolatedParameterIndex = site.getAppliedArgIndex(*iso);
}
// Lower the arguments and return value in the callee's generic context.
GenericContextScope scope(IGM,
origCalleeType->getInvocationGenericSignature());
@@ -3902,8 +3929,11 @@ void IRGenSILFunction::visitFullApplySite(FullApplySite site) {
emission->setIndirectTypedErrorResultSlot(addr.getAddress());
continue;
}
emitApplyArgument(*this, args[index], emission->getParameterType(index),
llArgs, site.getInstruction(), index);
llArgs, site.getInstruction(), index,
implicitIsolatedParameterIndex &&
*implicitIsolatedParameterIndex == index);
}
// Pass the generic arguments.

View File

@@ -2,7 +2,6 @@ target_sources(swiftSIL PRIVATE
BasicBlockUtils.cpp
BitDataflow.cpp
CalleeCache.cpp
ConcurrencyUtils.cpp
DebugUtils.cpp
Dominance.cpp
DynamicCasts.cpp

View File

@@ -1,32 +0,0 @@
//===--- ConcurrencyUtils.cpp ---------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "swift/SIL/ConcurrencyUtils.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILLocation.h"
using namespace swift;
SILValue swift::clearImplicitActorBits(SILBuilder &b, SILLocation loc,
SILValue value, SILType finalType) {
if (!finalType)
finalType = SILType::getBuiltinImplicitActorType(b.getASTContext());
if (value->getType() == finalType)
return value;
return b.emitUncheckedValueCast(loc, value, finalType);
}
SILValue swift::setImplicitActorBits(SILBuilder &b, SILLocation loc,
SILValue value) {
return value;
}

View File

@@ -1,70 +0,0 @@
//===--- ConcurrencyUtils.h -----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_SILGEN_CONCURRENCYUTILS_H
#define SWIFT_SILGEN_CONCURRENCYUTILS_H
#include "RValue.h"
#include "SILGenFunction.h"
#include "swift/SIL/ConcurrencyUtils.h"
namespace swift {
class SILLocation;
class Expr;
namespace Lowering {
class SILGenFunction;
class RValue;
class ManagedValue;
inline ManagedValue clearImplicitActorBits(SILGenFunction &SGF, SILLocation loc,
ManagedValue implicitIsolatedActor,
SILType type = {}) {
return ManagedValue::forBorrowedRValue(clearImplicitActorBits(
SGF.B, loc, implicitIsolatedActor.getUnmanagedValue(), type));
}
/// Clear the TBI bits if AArch64HasTBI is set. Otherwise clear the low tagged
/// bits.
///
/// \param expr - the expression which yielded this r-value; its type
/// will become the substituted formal type of this r-value
/// \param implicitIsolatedActor should be an Optional<any Actor>.
inline RValue clearImplicitActorBits(SILGenFunction &SGF, Expr *expr,
ManagedValue implicitIsolatedActor,
SILType type = {}) {
return RValue(SGF, expr,
clearImplicitActorBits(SGF, SILLocation(expr),
implicitIsolatedActor, type));
}
inline ManagedValue setImplicitActorBits(SILGenFunction &SGF, SILLocation loc,
ManagedValue implicitIsolatedActor) {
return ManagedValue::forBorrowedRValue(setImplicitActorBits(
SGF.B, loc, implicitIsolatedActor.getUnmanagedValue()));
}
inline RValue setImplicitActorBits(SILGenFunction &SGF, Expr *expr,
ManagedValue implicitIsolatedActor) {
return RValue(
SGF, expr,
setImplicitActorBits(SGF, SILLocation(expr), implicitIsolatedActor));
}
} // namespace Lowering
} // namespace swift
#endif

View File

@@ -13,7 +13,6 @@
#include "ArgumentScope.h"
#include "ArgumentSource.h"
#include "Callee.h"
#include "ConcurrencyUtils.h"
#include "Conversion.h"
#include "ExecutorBreadcrumb.h"
#include "FormalEvaluation.h"

View File

@@ -562,6 +562,13 @@ public:
convertToImplicitActor(loc, value.borrow(SGF, loc).getValue());
return ManagedValue::forBorrowedRValue(result);
}
using SILBuilder::createImplicitActorToOpaqueIsolationCast;
ManagedValue createImplicitActorToOpaqueIsolationCast(SILLocation loc,
ManagedValue mv) {
return ManagedValue::forBorrowedRValue(
createImplicitActorToOpaqueIsolationCast(loc, mv.getUnmanagedValue()));
}
};
} // namespace Lowering

View File

@@ -13,7 +13,6 @@
#include "ArgumentScope.h"
#include "ArgumentSource.h"
#include "Callee.h"
#include "ConcurrencyUtils.h"
#include "Condition.h"
#include "Conversion.h"
#include "Initialization.h"
@@ -7491,9 +7490,8 @@ RValue RValueEmitter::visitCurrentContextIsolationExpr(
assert(isolatedArg &&
"Caller Isolation Inheriting without isolated parameter");
auto isolatedMV = ManagedValue::forBorrowedRValue(isolatedArg);
return clearImplicitActorBits(
SGF, E, isolatedMV,
SILType::getOpaqueIsolationType(SGF.getASTContext()));
return RValue(
SGF, E, SGF.B.createImplicitActorToOpaqueIsolationCast(E, isolatedMV));
}
if (isolation == ActorIsolation::ActorInstance) {

View File

@@ -15,7 +15,6 @@
#include "swift/AST/ConformanceLookup.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/FrozenMultiMap.h"
#include "swift/SIL/ConcurrencyUtils.h"
#include "swift/SIL/Dominance.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILFunction.h"
@@ -353,8 +352,8 @@ static SILValue getExecutorForImplicitActor(SILOptFunctionBuilder &funcBuilder,
auto *front = newFunc->createBasicBlock();
SILBuilder builder(front);
auto *fArg = front->createFunctionArgument(implicitIsolatedActorType);
auto value = clearImplicitActorBits(builder, autoGenLoc, fArg,
SILType::getOpaqueIsolationType(ctx));
auto value = SILValue(
builder.createImplicitActorToOpaqueIsolationCast(autoGenLoc, fArg));
value = getExecutorForOptionalActor(builder, autoGenLoc, value);
builder.createReturn(autoGenLoc, value);
}

View File

@@ -0,0 +1,65 @@
// RUN: %target-swift-frontend -parse-as-library -emit-ir -disable-llvm-merge-functions-pass %s | %FileCheck --check-prefix=NO-TBI %s
// RUN: %target-swift-frontend -parse-as-library -Xllvm -aarch64-use-tbi -emit-ir -disable-llvm-merge-functions-pass %s | %FileCheck --check-prefix=TBI %s
// This test makes sure that we can properly fold the mask for the witness table
// when we have a #isolation.
// REQUIRES: concurrency
// REQUIRES: CODEGENERATOR=AArch64
// REQUIRES: PTRSIZE=64
// REQUIRES: OS=macosx || OS=ios
// REQUIRES: CPU=arm64
@inline(never)
func useActor(iso: (any Actor)?) {
print(iso!.unownedExecutor)
}
@inline(never)
func implicitParam(_ x: (any Actor)? = #isolation) {
print(x!.unownedExecutor)
}
// #isolation via direct usage
//
// TBI: define swifttailcc void @"$s18isolation_macro_ir46nonisolatedNonsendingUsePoundIsolationDirectlyyyYaF"(ptr swiftasync %0, i64 %1, i64 [[SECOND_WORD:%.*]])
// TBI: [[MASKED_SECOND_WORD:%.*]] = and i64 [[SECOND_WORD]], -3458764513820540929
// TBI: call swiftcc void @"$s18isolation_macro_ir8useActor3isoyScA_pSg_tF"(i64 %1, i64 [[MASKED_SECOND_WORD]])
// NO-TBI: define swifttailcc void @"$s18isolation_macro_ir46nonisolatedNonsendingUsePoundIsolationDirectlyyyYaF"(ptr swiftasync %0, i64 [[FIRST_WORD:%.*]], i64 [[SECOND_WORD:%.*]])
// NO-TBI: [[MASKED_SECOND_WORD:%.*]] = and i64 [[SECOND_WORD]], -4
// NO-TBI: call swiftcc void @"$s18isolation_macro_ir8useActor3isoyScA_pSg_tF"(i64 [[FIRST_WORD]], i64 [[MASKED_SECOND_WORD]])
public nonisolated(nonsending) func nonisolatedNonsendingUsePoundIsolationDirectly() async {
let iso = #isolation
useActor(iso: iso)
}
// #isolation via default arg
//
// TBI: define swifttailcc void @"$s18isolation_macro_ir45nonisolatedNonsendingPoundIsolationDefaultArgyyYaF"(ptr swiftasync {{%.*}}, i64 {{%.*}}, i64 [[WORD_2:%.*]])
// TBI: [[MASKED_WORD_2:%.*]] = and i64 [[WORD_2]], -3458764513820540929
// TBI: call swiftcc void @"$s18isolation_macro_ir13implicitParamyyScA_pSgF"(i64 {{%.*}}, i64 [[MASKED_WORD_2]])
// NO-TBI: define swifttailcc void @"$s18isolation_macro_ir45nonisolatedNonsendingPoundIsolationDefaultArgyyYaF"(ptr swiftasync %0, i64 [[WORD_1:%.*]], i64 [[WORD_2:%.*]])
// NO-TBI: [[MASKED_WORD_2:%.*]] = and i64 [[WORD_2]], -4
// NO-TBI: call swiftcc void @"$s18isolation_macro_ir13implicitParamyyScA_pSgF"(i64 {{%.*}}, i64 [[MASKED_WORD_2]])
public nonisolated(nonsending) func nonisolatedNonsendingPoundIsolationDefaultArg() async {
implicitParam()
}
@inline(never)
public nonisolated(nonsending) func calleeFunction() async {
}
// TBI: define swifttailcc void @"$s18isolation_macro_ir14callerFunctionyyYaF"(ptr swiftasync %0, i64 %1, i64 [[WORD_2:%.*]])
// TBI: [[MASKED_WORD_2:%.*]] = and i64 [[WORD_2]], -3458764513820540929
// TBI: musttail call swifttailcc void @"$s18isolation_macro_ir14calleeFunctionyyYaF"(ptr swiftasync {{%.*}}, i64 {{%.*}}, i64 [[MASKED_WORD_2]])
// NO-TBI: define swifttailcc void @"$s18isolation_macro_ir14callerFunctionyyYaF"(ptr swiftasync %0, i64 %1, i64 [[WORD:%.*]])
// NO-TBI: [[MASKED_WORD:%.*]] = and i64 [[WORD]], -4
// NO-TBI: musttail call swifttailcc void @"$s18isolation_macro_ir14calleeFunctionyyYaF"(ptr swiftasync {{%.*}}, i64 {{%.*}}, i64 [[MASKED_WORD]])
@inline(never)
public nonisolated(nonsending) func callerFunction() async {
await calleeFunction()
}

View File

@@ -15,7 +15,7 @@ func takeDefaulted(iso: isolated (any Actor)? = #isolation) {}
// CHECK-NEXT: // Isolation: caller_isolation_inheriting
// CHECK-NEXT: sil hidden @$s4test21nonisolatedNonsendingyyYaF : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor) -> () {
// CHECK: bb0([[IMPLICIT_ACTOR:%.*]] : $Builtin.ImplicitActor):
// CHECK: [[ACTOR:%.*]] = unchecked_bitwise_cast [[IMPLICIT_ACTOR]] to $Optional<any Actor>
// CHECK: [[ACTOR:%.*]] = implicitactor_to_opaqueisolation_cast [[IMPLICIT_ACTOR]]
// CHECK: retain_value [[ACTOR]]
// CHECK: debug_value [[ACTOR]], let, name "iso"
// CHECK: [[FUNC:%.*]] = function_ref @$s4test4take3isoyScA_pSg_tF : $@convention(thin) (@guaranteed Optional<any Actor>) -> ()
@@ -28,7 +28,7 @@ func takeDefaulted(iso: isolated (any Actor)? = #isolation) {}
// CHECK-NEXT: // Isolation: caller_isolation_inheriting
// CHECK-LABEL: sil private @$s4test15containsClosureyyFyyYaYCcfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor) -> () {
// CHECK: bb0(%0 : $Builtin.ImplicitActor):
// CHECK: [[ACTOR:%.*]] = unchecked_bitwise_cast %0 to $Optional<any Actor>
// CHECK: [[ACTOR:%.*]] = implicitactor_to_opaqueisolation_cast %0
// CHECK-NEXT: // function_ref take(iso:)
// CHECK-NEXT: [[FN:%.*]] = function_ref @
// CHECK-NEXT: apply [[FN]]([[ACTOR]])
@@ -51,12 +51,14 @@ func deferWithIsolatedParam(_ iso: isolated (any Actor)) {
// CHECK: bb0(%0 : $any Actor)
// CHECK: [[DEFER:%.*]] = function_ref @$s4test22deferWithIsolatedParamyyScA_pYiF6$deferL_yyF :
// CHECK-NEXT: apply [[DEFER]](%0)
// CHECK: } // end sil function '$s4test22deferWithIsolatedParamyyScA_pYiF'
// CHECK-LABEL: sil private @$s4test22deferWithIsolatedParamyyScA_pYiF6$deferL_yyF :
// CHECK: bb0(%0 : @closureCapture $any Actor):
// CHECK: [[T0:%.*]] = enum $Optional<any Actor>, #Optional.some!enumelt, %0
// CHECK: [[FN:%.*]] = function_ref @$s4test4take3isoyScA_pSg_tF :
// CHECK-NEXT: apply [[FN]]([[T0]])
// CHECK: } // end sil function '$s4test22deferWithIsolatedParamyyScA_pYiF6$deferL_yyF'
// Check that that happens even with uses in caller-side default
// arguments, which capture analysis was not previously walking into.
@@ -71,12 +73,14 @@ func deferWithIsolatedParam_defaultedUse(_ iso: isolated (any Actor)) {
// CHECK: bb0(%0 : $any Actor):
// CHECK: [[DEFER:%.*]] = function_ref @$s4test35deferWithIsolatedParam_defaultedUseyyScA_pYiF6$deferL_yyF :
// CHECK-NEXT: apply [[DEFER]](%0)
// CHECK: } // end sil function '$s4test35deferWithIsolatedParam_defaultedUseyyScA_pYiF'
// CHECK-LABEL: sil private @$s4test35deferWithIsolatedParam_defaultedUseyyScA_pYiF6$deferL_yyF :
// CHECK: bb0(%0 : @closureCapture $any Actor):
// CHECK: [[T0:%.*]] = enum $Optional<any Actor>, #Optional.some!enumelt, %0
// CHECK: [[FN:%.*]] = function_ref @$s4test13takeDefaulted3isoyScA_pSgYi_tF :
// CHECK-NEXT: apply [[FN]]([[T0]])
// CHECK: } // end sil function '$s4test35deferWithIsolatedParam_defaultedUseyyScA_pYiF6$deferL_yyF'
// TODO: we can't currently call nonisolated(nonsending) functions in
// defer bodies because they have to be async, but that should be
@@ -100,7 +104,7 @@ func hasDefer() async {
// CHECK-LABEL: // $defer #1 () in hasDefer()
// CHECK-NEXT: // Isolation: caller_isolation_inheriting
// CHECK: bb0(%0 : $Builtin.ImplicitActor):
// CHECK-NEXT: [[ACTOR:%.*]] = unchecked_bitwise_cast %0 to $Optional<any Actor>
// CHECK-NEXT: [[ACTOR:%.*]] = implicitactor_to_opaqueisolation_cast %0
// CHECK-NEXT: // function_ref take(iso:)
// CHECK-NEXT: [[FN:%.*]] = function_ref @
// CHECK-NEXT: apply [[FN]]([[ACTOR]])
@@ -122,6 +126,7 @@ func hasNestedDefer() async {
// CHECK: // function_ref $defer #1 () in hasNestedDefer()
// CHECK-NEXT: [[DEFER:%.*]] = function_ref
// CHECK-NEXT: apply [[DEFER]](%0)
// CHECK: } // end sil function '$s4test14hasNestedDeferyyYaF'
// CHECK-LABEL: // $defer #1 () in hasNestedDefer()
// CHECK-NEXT: // Isolation: caller_isolation_inheriting
@@ -129,12 +134,13 @@ func hasNestedDefer() async {
// CHECK: // function_ref $defer #1 () in $defer #1 () in hasNestedDefer()
// CHECK-NEXT: [[DEFER:%.*]] = function_ref
// CHECK-NEXT: apply [[DEFER]](%0)
// CHECK: } // end sil function '$s4test14hasNestedDeferyyYaF6$deferL_yyF'
// CHECK-LABEL: // $defer #1 () in $defer #1 () in hasNestedDefer()
// CHECK-NEXT: // Isolation: caller_isolation_inheriting
// CHECK-NEXT: sil private @$s4test14hasNestedDeferyyYaF6$deferL_yyFACL_yyF : $@convention(thin) (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor) -> () {
// CHECK: bb0(%0 : $Builtin.ImplicitActor):
// CHECK-NEXT: [[ACTOR:%.*]] = unchecked_bitwise_cast %0 to $Optional<any Actor>
// CHECK-NEXT: [[ACTOR:%.*]] = implicitactor_to_opaqueisolation_cast %0
// CHECK-NEXT: // function_ref take(iso:)
// CHECK-NEXT: [[FN:%.*]] = function_ref @
// CHECK-NEXT: apply [[FN]]([[ACTOR]])