Skip to content

introduce @_manualOwnership performance attribute #81858

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 1 commit into
base: main
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Function.swift
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash
case noRuntime
case noExistentials
case noObjCRuntime
case manualOwnership
}

public var performanceConstraints: PerformanceConstraints {
Expand All @@ -260,6 +261,7 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash
case .NoRuntime: return .noRuntime
case .NoExistentials: return .noExistentials
case .NoObjCBridging: return .noObjCRuntime
case .ManualOwnership: return .manualOwnership
default: fatalError("unknown performance constraint")
}
}
Expand Down
5 changes: 4 additions & 1 deletion include/swift/AST/DeclAttr.def
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,10 @@ SIMPLE_DECL_ATTR(_noObjCBridging, NoObjCBridging,
UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | ForbiddenInABIAttr,
155)

// Unused '156': Used to be `_distributedThunkTarget` but completed implementation in Swift 6.0 does not need it after all
SIMPLE_DECL_ATTR(_manualOwnership, ManualOwnership,
OnAbstractFunction | OnSubscript,
UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | ForbiddenInABIAttr,
156)

DECL_ATTR(_allowFeatureSuppression, AllowFeatureSuppression,
OnAnyDecl,
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSIL.def
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ ERROR(wrong_linkage_for_serialized_function,none,
"function has wrong linkage to be called from %0", (StringRef))
NOTE(performance_called_from,none,
"called from here", ())
ERROR(manualownership_copy,none,
"explicit 'copy' required here", ())

// 'transparent' diagnostics
ERROR(circular_transparent,none,
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2100,6 +2100,10 @@ ERROR(attr_static_exclusive_only_mutating,none,
ERROR(attr_static_exclusive_no_setters,none,
"variable of type %0 must not have a setter", (Type))

// @_manualOwnership
ERROR(attr_manual_ownership_experimental,none,
"'@_manualOwnership' requires '-enable-experimental-feature ManualOwnership'", ())

// @extractConstantsFromMembers
ERROR(attr_extractConstantsFromMembers_experimental,none,
"'@extractConstantsFromMembers' requires "
Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -2292,7 +2292,7 @@ class CopyExpr final : public Expr {
/// getSemanticsProvidingExpr() looks through this because it doesn't
/// provide the value and only very specific clients care where the
/// 'borrow' was written.
class BorrowExpr final : public IdentityExpr {
class BorrowExpr final : public IdentityExpr { // FIXME: this should be a ImplicitConversionExpr like LoadExpr
SourceLoc BorrowLoc;

public:
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 @@ -426,6 +426,9 @@ EXPERIMENTAL_FEATURE(LifetimeDependenceMutableAccessors, true)
/// Enable the `@_staticExclusiveOnly` attribute.
EXPERIMENTAL_FEATURE(StaticExclusiveOnly, true)

/// Enable the `@_manualOwnership` attribute.
EXPERIMENTAL_FEATURE(ManualOwnership, false)

/// Enable the @extractConstantsFromMembers attribute.
EXPERIMENTAL_FEATURE(ExtractConstantsFromMembers, false)

Expand Down
3 changes: 2 additions & 1 deletion include/swift/SIL/SILBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,8 @@ struct BridgedFunction {
NoLocks = 2,
NoRuntime = 3,
NoExistentials = 4,
NoObjCBridging = 5
NoObjCBridging = 5,
ManualOwnership = 6,
};

enum class InlineStrategy {
Expand Down
8 changes: 8 additions & 0 deletions include/swift/SIL/SILBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ class SILBuilder {
return false;
}

/// If we have a SILFunction, return true if it has a ManualOwnership
/// PerformanceConstraint, which corresponds to an attribute on the FuncDecl.
bool hasManualOwnershipAttr() const {
if (F)
return F->getPerfConstraints() == PerformanceConstraints::ManualOwnership;
return false;
}

//===--------------------------------------------------------------------===//
// Insertion Point Management
//===--------------------------------------------------------------------===//
Expand Down
19 changes: 19 additions & 0 deletions include/swift/SIL/SILCloner.h
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,16 @@ template<typename ImplClass>
void
SILCloner<ImplClass>::visitCopyAddrInst(CopyAddrInst *Inst) {
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
// When cloning into a function using manual ownership, convert to explicit
// copies, in order to preserve the local nature of the perf constraint.
if (getBuilder().hasManualOwnershipAttr() && getBuilder().hasOwnership()) {
recordClonedInstruction(
Inst, getBuilder().createExplicitCopyAddr(
getOpLocation(Inst->getLoc()), getOpValue(Inst->getSrc()),
getOpValue(Inst->getDest()), Inst->isTakeOfSrc(),
Inst->isInitializationOfDest()));
return;
}
recordClonedInstruction(
Inst, getBuilder().createCopyAddr(
getOpLocation(Inst->getLoc()), getOpValue(Inst->getSrc()),
Expand Down Expand Up @@ -2095,6 +2105,15 @@ void SILCloner<ImplClass>::visitCopyValueInst(CopyValueInst *Inst) {
return recordFoldedValue(Inst, newValue);
}

// When cloning into a function using manual ownership, convert to explicit
// copies, in order to preserve the local nature of the perf constraint.
if (getBuilder().hasManualOwnershipAttr() && getBuilder().hasOwnership()) {
recordClonedInstruction(
Inst, getBuilder().createExplicitCopyValue(getOpLocation(Inst->getLoc()),
getOpValue(Inst->getOperand())));
return;
}

recordClonedInstruction(
Inst, getBuilder().createCopyValue(getOpLocation(Inst->getLoc()),
getOpValue(Inst->getOperand())));
Expand Down
3 changes: 2 additions & 1 deletion include/swift/SIL/SILFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ enum class PerformanceConstraints : uint8_t {
NoLocks = 2,
NoRuntime = 3,
NoExistentials = 4,
NoObjCBridging = 5
NoObjCBridging = 5,
ManualOwnership = 6,
};

class SILSpecializeAttr final {
Expand Down
1 change: 1 addition & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5000,6 +5000,7 @@ class PrintAttribute : public AttributeVisitor<PrintAttribute, void, Label>,
TRIVIAL_ATTR_PRINTER(NoLocks, no_locks)
TRIVIAL_ATTR_PRINTER(NoMetadata, no_metadata)
TRIVIAL_ATTR_PRINTER(NoObjCBridging, no_objc_bridging)
TRIVIAL_ATTR_PRINTER(ManualOwnership, manual_ownership)
TRIVIAL_ATTR_PRINTER(NoRuntime, no_runtime)
TRIVIAL_ATTR_PRINTER(NonEphemeral, non_ephemeral)
TRIVIAL_ATTR_PRINTER(NonEscapable, non_escapable)
Expand Down
13 changes: 13 additions & 0 deletions lib/AST/ASTVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2487,6 +2487,19 @@ class Verifier : public ASTWalker {
verifyCheckedBase(E);
}

void verifyChecked(BorrowExpr *E) {
PrettyStackTraceExpr debugStack(Ctx, "verifying BorrowExpr", E);

auto toType = E->getType();
auto fromType = E->getSubExpr()->getType();

if (!fromType->hasLValueType())
error("borrow source must be an l-value", E);

if (toType->hasLValueType())
error("borrow result must be an r-value", E);
}

void verifyChecked(ABISafeConversionExpr *E) {
PrettyStackTraceExpr debugStack(Ctx, "verify ABISafeConversionExpr", E);

Expand Down
1 change: 1 addition & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ static bool usesFeatureInlineArrayTypeSugar(Decl *D) {
}

UNINTERESTING_FEATURE(StaticExclusiveOnly)
UNINTERESTING_FEATURE(ManualOwnership)
UNINTERESTING_FEATURE(ExtractConstantsFromMembers)
UNINTERESTING_FEATURE(GroupActorErrors)
UNINTERESTING_FEATURE(SameElementRequirements)
Expand Down
1 change: 1 addition & 0 deletions lib/ASTGen/Sources/ASTGen/DeclAttrs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ extension ASTGenVisitor {
.noLocks,
.noMetadata,
.noObjCBridging,
.manualOwnership,
.nonEphemeral,
.nonEscapable,
.nonObjC,
Expand Down
2 changes: 2 additions & 0 deletions lib/SIL/IR/SILFunctionBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ void SILFunctionBuilder::addFunctionAttributes(
F->setPerfConstraints(PerformanceConstraints::NoExistentials);
} else if (Attrs.hasAttribute<NoObjCBridgingAttr>()) {
F->setPerfConstraints(PerformanceConstraints::NoObjCBridging);
} else if (Attrs.hasAttribute<ManualOwnershipAttr>()) {
F->setPerfConstraints(PerformanceConstraints::ManualOwnership);
}

if (Attrs.hasAttribute<LexicalLifetimesAttr>()) {
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3635,6 +3635,7 @@ void SILFunction::print(SILPrintContext &PrintCtx) const {
case PerformanceConstraints::NoRuntime: OS << "[no_runtime] "; break;
case PerformanceConstraints::NoExistentials: OS << "[no_existentials] "; break;
case PerformanceConstraints::NoObjCBridging: OS << "[no_objc_bridging] "; break;
case PerformanceConstraints::ManualOwnership: OS << "[manual_ownership] "; break;
}

if (isPerformanceConstraint())
Expand Down
2 changes: 2 additions & 0 deletions lib/SIL/Parser/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,8 @@ static bool parseDeclSILOptional(
*perfConstraints = PerformanceConstraints::NoExistentials;
else if (perfConstraints && SP.P.Tok.getText() == "no_objc_bridging")
*perfConstraints = PerformanceConstraints::NoObjCBridging;
else if (perfConstraints && SP.P.Tok.getText() == "manual_ownership")
*perfConstraints = PerformanceConstraints::ManualOwnership;
else if (isPerformanceConstraint && SP.P.Tok.getText() == "perf_constraint")
*isPerformanceConstraint = true;
else if (markedAsUsed && SP.P.Tok.getText() == "used")
Expand Down
8 changes: 7 additions & 1 deletion lib/SIL/Utils/InstructionUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,6 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
case SILInstructionKind::CopyableToMoveOnlyWrapperValueInst:
case SILInstructionKind::MoveOnlyWrapperToCopyableValueInst:
case SILInstructionKind::UncheckedOwnershipConversionInst:
case SILInstructionKind::LoadInst:
case SILInstructionKind::LoadBorrowInst:
case SILInstructionKind::BeginBorrowInst:
case SILInstructionKind::BorrowedFromInst:
Expand Down Expand Up @@ -642,6 +641,13 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
case SILInstructionKind::TypeValueInst:
case SILInstructionKind::IgnoredUseInst:
return RuntimeEffect::NoEffect;

case SILInstructionKind::LoadInst:
// FIXME: this needs to be merged in a separate PR as it can affect other performance constraints
if (cast<LoadInst>(inst)->getOwnershipQualifier() == LoadOwnershipQualifier::Copy)
return RuntimeEffect::RefCounting;
else
return RuntimeEffect::NoEffect;

case SILInstructionKind::OpenExistentialMetatypeInst:
case SILInstructionKind::OpenExistentialBoxInst:
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/Utils/SILBridging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ static_assert((int)BridgedFunction::PerformanceConstraints::NoLocks == (int)swif
static_assert((int)BridgedFunction::PerformanceConstraints::NoRuntime == (int)swift::PerformanceConstraints::NoRuntime);
static_assert((int)BridgedFunction::PerformanceConstraints::NoExistentials == (int)swift::PerformanceConstraints::NoExistentials);
static_assert((int)BridgedFunction::PerformanceConstraints::NoObjCBridging == (int)swift::PerformanceConstraints::NoObjCBridging);
static_assert((int)BridgedFunction::PerformanceConstraints::ManualOwnership == (int)swift::PerformanceConstraints::ManualOwnership);

static_assert((int)BridgedFunction::InlineStrategy::InlineDefault == (int)swift::InlineDefault);
static_assert((int)BridgedFunction::InlineStrategy::NoInline == (int)swift::NoInline);
Expand Down
2 changes: 2 additions & 0 deletions lib/SILGen/SILGenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,8 @@ void SILGenFunction::emitPatternBinding(PatternBindingDecl *PBD, unsigned idx,
}
}

// Form an evaluation scope for the initializing expression
FormalEvaluationScope writeback(*this);
emitExprInto(initExpr, initialization.get());
}

Expand Down
20 changes: 19 additions & 1 deletion lib/SILGen/SILGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ namespace {

RValue visitStringLiteralExpr(StringLiteralExpr *E, SGFContext C);
RValue visitLoadExpr(LoadExpr *E, SGFContext C);
RValue visitBorrowExpr(BorrowExpr *E, SGFContext C);
RValue visitDerivedToBaseExpr(DerivedToBaseExpr *E, SGFContext C);
RValue visitMetatypeConversionExpr(MetatypeConversionExpr *E,
SGFContext C);
Expand Down Expand Up @@ -1108,6 +1109,20 @@ RValue RValueEmitter::visitLoadExpr(LoadExpr *E, SGFContext C) {
C.withFollowingSideEffects());
}

RValue RValueEmitter::visitBorrowExpr(BorrowExpr *E, SGFContext C_Ignored) {
// FIXME: how about address-only types? Maybe BorrowedAddressRead?

// NOTE: You should NOT add an evaluation scope here!
// The callers of this visitor should have established a scope already that
// encompasses the use of the borrowed RValue that we return.
assert(SGF.isInFormalEvaluationScope() && "emit borrow_expr without scope?");

LValue lv = SGF.emitLValue(E->getSubExpr(), SGFAccessKind::BorrowedObjectRead);
auto substFormalType = lv.getSubstFormalType();
ManagedValue mv = SGF.emitBorrowedLValue(E, std::move(lv));
return RValue(SGF, E, substFormalType, mv);
}

SILValue SILGenFunction::emitTemporaryAllocation(SILLocation loc, SILType ty,
HasDynamicLifetime_t dynamic,
IsLexical_t isLexical,
Expand Down Expand Up @@ -7222,7 +7237,10 @@ RValue RValueEmitter::visitCopyExpr(CopyExpr *E, SGFContext C) {
}

if (subType.isLoadable(SGF.F)) {
ManagedValue mv = SGF.emitRValue(subExpr).getAsSingleValue(SGF, subExpr);
// FIXME: this is a general improvement that can land in a separate PR
ManagedValue mv =
SGF.emitRValue(subExpr, SGFContext::AllowImmediatePlusZero)
.getAsSingleValue(SGF, subExpr);
if (mv.getType().isTrivial(SGF.F))
return RValue(SGF, {mv}, subType.getASTType());
{
Expand Down
65 changes: 65 additions & 0 deletions lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,11 @@ bool PerformanceDiagnostics::visitCallee(SILInstruction *callInst,
CalleeList callees,
PerformanceConstraints perfConstr,
LocWithParent *parentLoc) {
// Manual Ownership is not checked recursively within callees of the function,
// it's a local, non-viral performance annotation.
if (perfConstr == PerformanceConstraints::ManualOwnership)
return false;

LocWithParent asLoc(callInst->getLoc().getSourceLoc(), parentLoc);
LocWithParent *loc = &asLoc;
if (parentLoc && asLoc.loc == callInst->getFunction()->getLocation().getSourceLoc())
Expand Down Expand Up @@ -370,6 +375,66 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
RuntimeEffect impact = getRuntimeEffect(inst, impactType);
LocWithParent loc(inst->getLoc().getSourceLoc(), parentLoc);

if (perfConstr == PerformanceConstraints::ManualOwnership) {
if (impact == RuntimeEffect::RefCounting) {
bool shouldDiagnose = false;
switch (inst->getKind()) {
case SILInstructionKind::ExplicitCopyAddrInst:
case SILInstructionKind::ExplicitCopyValueInst:
break;
case SILInstructionKind::LoadInst: {
// FIXME: we don't have an `explicit_load` and transparent functions can
// end up bringing in a `load [copy]`. A better approach is needed to
// handle transparent functions, in general, as rewriting them during
// inlining of transparent functions is also not so great, as it
// may hide a copy that semantically is there, but we happened to
// tuck away in a transparent synthesized function, like those
// synthesized getters!
//
// For now look to see if the load copy's only non-destroying users are
// explicit_copy's, as that would indicate the user has acknowledged it:
//
// %y = load [copy] %x
// %z = explicit_copy_value %y
// destroy_value %y
//
// In all other cases, it's safer to diagnose.
//
auto load = cast<LoadInst>(inst);
if (load->getOwnershipQualifier() != LoadOwnershipQualifier::Copy)
break;

for (auto *use : load->getUsers()) {
if (!isa<ExplicitCopyAddrInst, ExplicitCopyValueInst,
DestroyAddrInst, DestroyValueInst>(use)) {
shouldDiagnose = true;
break;
}
}
break;
}
default:
shouldDiagnose = true;
break;
}
if (shouldDiagnose) {
diagnose(loc, diag::manualownership_copy);
llvm::dbgs() << "function " << inst->getFunction()->getName();
llvm::dbgs() << "\n has unexpected copying instruction: ";
inst->dump();
return false; // Don't bail-out early; diagnose more issues in the func.
}
} else if (impact == RuntimeEffect::ExclusivityChecking) {
// TODO: diagnose only the nested exclusivity; perhaps as a warning
// since we don't yet have reference bindings?

// diagnose(loc, diag::performance_arc);
// inst->dump();
// return true;
}
return false;
}

if (perfConstr == PerformanceConstraints::NoExistentials &&
((impact & RuntimeEffect::Existential) ||
(impact & RuntimeEffect::ExistentialClassBound))) {
Expand Down
9 changes: 9 additions & 0 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7125,6 +7125,15 @@ Expr *ExprRewriter::coerceExistential(Expr *expr, Type toType,
}

Expr *ConstraintSystem::addImplicitLoadExpr(Expr *expr) {
/// Avoid copying if we're in a manual ownership context; borrow instead.
if (auto *decl = DC->getAsDecl()) {
if (decl->getAttrs().hasAttribute<ManualOwnershipAttr>()) {
return TypeChecker::addImplicitBorrowExpr(
getASTContext(), expr, [this](Expr *expr) { return getType(expr); },
[this](Expr *expr, Type type) { setType(expr, type); });
}
}

return TypeChecker::addImplicitLoadExpr(
getASTContext(), expr, [this](Expr *expr) { return getType(expr); },
[this](Expr *expr, Type type) { setType(expr, type); });
Expand Down
Loading