Skip to content

Add experimental feature to defer Sendable checking to SILGen #66929

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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
88 changes: 88 additions & 0 deletions include/swift/AST/ActorIsolation.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,94 @@ bool safeToDropGlobalActor(

void simple_display(llvm::raw_ostream &out, const ActorIsolation &state);

/// A DeferredSendableDiagnostic wraps a list of closures that emit
/// Diagnostics when called. It is used to allow the logic for forming
/// those diagnostics to take place ahead of time, while delaying the
/// actual emission until several passes later. In particular, diagnostics
/// that identify NonSendable types being sent between isolation domains
/// are deferred thus so that a later flow-sensitive SIL pass can eliminate
/// diagnostics for sends that are provably safe.
struct DeferredSendableDiagnostic {
private:

// This field indicates whether any errors (as opposed to just warnings
// and notes) are produced by this DeferredSendableDiagnostic instance.
// This exists to allow existing control flow through the call stack in
// ActorIsolationChecker's walk methods. Because that control flow wasn't
// entirely principled, sometime the use of this field doesn't exactly
// align with the presence of errors vs warnings, for example in
// diagnoseReferenceToUnsafeGlobal and diagnoseInOutArg.
bool ProducesErrors;

// This field stores a vector, each entry of which is a closure that can be
// called, in order, to emit diagnostics.
std::vector<std::function<void()>> Diagnostics;

public:
DeferredSendableDiagnostic()
: ProducesErrors(false){}

// In general, an empty no-op closure should not be passed to
// Diagnostic here, or ProducesDiagnostics will contain an
// imprecise value.
DeferredSendableDiagnostic(
bool ProducesErrors, std::function<void()> Diagnostic)
: ProducesErrors(ProducesErrors),
Diagnostics({Diagnostic}) {
assert(Diagnostic && "Empty diagnostics function");
}

bool producesErrors() const {
return ProducesErrors;
}

bool producesDiagnostics() const {
return Diagnostics.size() != 0;
}

// Idempotent operation: call the contained closures in Diagnostics in order,
// and clear out the list so subsequent invocations are a no-op
void produceDiagnostics() {
for (auto Diagnostic : Diagnostics) {
Diagnostic();
}
ProducesErrors = false;
Diagnostics = {};
}

void setProducesErrors(bool producesErrors) {
ProducesErrors = producesErrors;
}

// In general, an empty no-op closure should not be passed to
// Diagnostic here, or ProducesDiagnostics will contain an
// imprecise value.
void addDiagnostic(std::function<void()> Diagnostic) {
assert(Diagnostic && "Empty diagnostics function");

Diagnostics.push_back(Diagnostic);
}

/// This variation on addErrorProducingDiagnostic should be called
/// when the passed lambda will definitely through a diagnostic
/// for the sake of maintaining existing control flow paths, it
/// is not used everywhere.
void addErrorProducingDiagnostic(std::function<void()> produceMoreDiagnostics) {
addDiagnostic(produceMoreDiagnostics);
setProducesErrors(true);
}

// compose this DeferredSendableDiagnostic with another - calling their
// wrapped Diagnostics closure in sequence and disjuncting their
// respective ProducesErrors flags
void followWith(DeferredSendableDiagnostic other) {
for (auto Diagnostic : other.Diagnostics) {
Diagnostics.push_back(Diagnostic);
}
ProducesErrors = ProducesErrors || other.ProducesErrors;
}
};

} // end namespace swift

#endif /* SWIFT_AST_ACTORISOLATIONSTATE_H */
14 changes: 14 additions & 0 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -3082,6 +3082,20 @@ class LinearToDifferentiableFunctionExpr : public ImplicitConversionExpr {
}
};

class SendNonSendableExpr : public ImplicitConversionExpr {
DeferredSendableDiagnostic *Diagnostic;
public:
SendNonSendableExpr(
ASTContext &ctx, DeferredSendableDiagnostic diagnostic, Expr *sub,
Type type = Type());

void produceDiagnostics() { Diagnostic->produceDiagnostics(); }

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::SendNonSendable;
}
};

/// Use an opaque type to abstract a value of the underlying concrete type,
/// possibly nested inside other types. E.g. can perform conversions "T --->
/// (opaque type)" and "S<T> ---> S<(opaque type)>".
Expand Down
3 changes: 2 additions & 1 deletion include/swift/AST/ExprNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ ABSTRACT_EXPR(ImplicitConversion, Expr)
EXPR(DifferentiableFunctionExtractOriginal, ImplicitConversionExpr)
EXPR(LinearFunctionExtractOriginal, ImplicitConversionExpr)
EXPR(LinearToDifferentiableFunction, ImplicitConversionExpr)
EXPR_RANGE(ImplicitConversion, Load, LinearToDifferentiableFunction)
EXPR(SendNonSendable, ImplicitConversionExpr)
EXPR_RANGE(ImplicitConversion, Load, SendNonSendable)
ABSTRACT_EXPR(ExplicitCast, Expr)
ABSTRACT_EXPR(CheckedCast, ExplicitCastExpr)
EXPR(ForcedCheckedCast, CheckedCastExpr)
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ EXPERIMENTAL_FEATURE(ReferenceBindings, false)
/// Enable the explicit 'import Builtin' and allow Builtin usage.
EXPERIMENTAL_FEATURE(BuiltinModule, true)

/// Defer Sendable checking to SIL diagnostic phase.
EXPERIMENTAL_FEATURE(DeferredSendableChecking, false)

#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE
Expand Down
6 changes: 6 additions & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2205,6 +2205,12 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
printRec(E->getSubExpr());
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}
void visitSendNonSendableExpr(SendNonSendableExpr *E) {
printCommon(E, "send_non_sendable_expr");
OS << '\n';
printRec(E->getSubExpr());
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}
void visitConsumeExpr(ConsumeExpr *E) {
printCommon(E, "consume_expr");
OS << '\n';
Expand Down
9 changes: 9 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3425,6 +3425,10 @@ static bool usesFeatureParameterPacks(Decl *decl) {
return false;
}

static bool usesFeatureDeferredSendableChecking(Decl *decl) {
return false;
}

/// Suppress the printing of a particular feature.
static void suppressingFeature(PrintOptions &options, Feature feature,
llvm::function_ref<void()> action) {
Expand Down Expand Up @@ -5010,6 +5014,11 @@ void PrintAST::visitAwaitExpr(AwaitExpr *expr) {
visit(expr->getSubExpr());
}

void PrintAST::visitSendNonSendableExpr(SendNonSendableExpr *expr) {
Printer << "sendNonSendable ";
visit(expr->getSubExpr());
}

void PrintAST::visitConsumeExpr(ConsumeExpr *expr) {
Printer << "consume ";
visit(expr->getSubExpr());
Expand Down
10 changes: 9 additions & 1 deletion lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ ConcreteDeclRef Expr::getReferencedDecl(bool stopAtParenExpr) const {
PASS_THROUGH_REFERENCE(UnresolvedMemberChainResult, getSubExpr);
PASS_THROUGH_REFERENCE(DotSelf, getSubExpr);
PASS_THROUGH_REFERENCE(Await, getSubExpr);
PASS_THROUGH_REFERENCE(SendNonSendable, getSubExpr);
PASS_THROUGH_REFERENCE(Consume, getSubExpr);
PASS_THROUGH_REFERENCE(Copy, getSubExpr);
PASS_THROUGH_REFERENCE(Borrow, getSubExpr);
Expand Down Expand Up @@ -750,6 +751,7 @@ bool Expr::canAppendPostfixExpression(bool appendingPostfixOperator) const {
case ExprKind::ForceTry:
case ExprKind::OptionalTry:
case ExprKind::InOut:
case ExprKind::SendNonSendable:
return false;

case ExprKind::RebindSelfInConstructor:
Expand Down Expand Up @@ -939,6 +941,7 @@ bool Expr::isValidParentOfTypeExpr(Expr *typeExpr) const {
case ExprKind::Consume:
case ExprKind::Copy:
case ExprKind::Borrow:
case ExprKind::SendNonSendable:
case ExprKind::UnresolvedMemberChainResult:
case ExprKind::Try:
case ExprKind::ForceTry:
Expand Down Expand Up @@ -2784,4 +2787,9 @@ const UnifiedStatsReporter::TraceFormatter*
FrontendStatsTracer::getTraceFormatter<const Expr *>() {
return &TF;
}

SendNonSendableExpr::SendNonSendableExpr(ASTContext &ctx, DeferredSendableDiagnostic diagnostic, Expr *sub, Type type)
: ImplicitConversionExpr(ExprKind::SendNonSendable, sub, type) {
void *mem = ctx.Allocate(sizeof(DeferredSendableDiagnostic), alignof(DeferredSendableDiagnostic));
Diagnostic = new (mem) DeferredSendableDiagnostic(std::move(diagnostic));
ctx.addDestructorCleanup(*Diagnostic);
}
6 changes: 6 additions & 0 deletions lib/SILGen/SILGenApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,12 @@ class SILGenApply : public Lowering::ExprVisitor<SILGenApply> {
}

SelfApplyExpr *getAsMethodSelfApply(Expr *e) {
// we need to look through any SendNonSendableExpr's that could
// wrap the SelfApplyExpr we're looking for
if (auto *SNS = dyn_cast<SendNonSendableExpr>(e)) {
SNS->produceDiagnostics();
return getAsMethodSelfApply(SNS->getSubExpr());
}
auto *SAE = dyn_cast<SelfApplyExpr>(e);
if (!SAE)
return nullptr;
Expand Down
10 changes: 10 additions & 0 deletions lib/SILGen/SILGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ namespace {
RValue visitConsumeExpr(ConsumeExpr *E, SGFContext C);
RValue visitCopyExpr(CopyExpr *E, SGFContext C);
RValue visitMacroExpansionExpr(MacroExpansionExpr *E, SGFContext C);
RValue visitSendNonSendableExpr(SendNonSendableExpr *E, SGFContext C);
};
} // end anonymous namespace

Expand Down Expand Up @@ -6322,6 +6323,15 @@ RValue RValueEmitter::visitMacroExpansionExpr(MacroExpansionExpr *E,
return RValue();
}

RValue RValueEmitter::visitSendNonSendableExpr(
SendNonSendableExpr *E, SGFContext C) {

// produce deferred diagnostics
E->produceDiagnostics();

return visit(E->getSubExpr(), C);
}

RValue SILGenFunction::emitRValue(Expr *E, SGFContext C) {
assert(!E->getType()->hasLValueType() &&
"l-values must be emitted with emitLValue");
Expand Down
Loading