Skip to content
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

Add an effects(releasenone) function effects attribute #14924

Merged
Merged
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
43 changes: 43 additions & 0 deletions docs/HighLevelSILOptimizations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,49 @@ readonly
eliminated, but cannot be reordered or folded in a way that would
move calls to the readnone function across side effects.

releasenone

function has side effects, it can read or write global state, or state
reachable from its arguments. It can however be assumed that no externally
visible release has happened (i.e it is allowed for a ``releasenone``
function to allocate and destruct an object in its implementation as long as
this is does not cause an release of an object visible outside of the
implementation). Here are some examples::

class SomeObject {
final var x: Int = 3
}

var global = SomeObject()

class SomeOtherObject {
var x: Int = 2
deinit {
global = SomeObject()
}
}

@effects(releasenone)
func validReleaseNoneFunction(x: Int) -> Int {
global.x = 5
return x + 2
}

@effects(releasenone)
func validReleaseNoneFunction(x: Int) -> Int {
var notExternallyVisibleObject = SomeObject()
return x + notExternallyVisibleObject.x
}

func notAReleaseNoneFunction(x: Int, y: SomeObject) -> Int {
return x + y.x
}

func notAReleaseNoneFunction(x: Int) -> Int {
var releaseExternallyVisible = SomeOtherObject()
return x + releaseExternallyVisible.x
}

readwrite

function has side effects and the optimizer can't assume anything.
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/AttrKind.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ enum class InlineKind : uint8_t {
enum class EffectsKind : uint8_t {
ReadNone,
ReadOnly,
ReleaseNone,
ReadWrite,
Unspecified
};
Expand Down
2 changes: 1 addition & 1 deletion include/swift/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const uint16_t VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t VERSION_MINOR = 401; // Last change: ValueOwnership
const uint16_t VERSION_MINOR = 402; // Last change: effects(releasenone)

using DeclIDField = BCFixed<31>;

Expand Down
2 changes: 2 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,8 @@ StringRef DeclAttribute::getAttrName() const {
return "effects(readnone)";
case EffectsKind::ReadOnly:
return "effects(readonly)";
case EffectsKind::ReleaseNone:
return "effects(releasenone)";
case EffectsKind::ReadWrite:
return "effects(readwrite)";
case EffectsKind::Unspecified:
Expand Down
2 changes: 2 additions & 0 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,8 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
kind = EffectsKind::ReadNone;
else if (Tok.getText() == "readwrite")
kind = EffectsKind::ReadWrite;
else if (Tok.getText() == "releasenone")
kind = EffectsKind::ReleaseNone;
else {
diagnose(Loc, diag::effects_attribute_unknown_option,
Tok.getText(), AttrName);
Expand Down
2 changes: 2 additions & 0 deletions lib/ParseSIL/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,8 @@ static bool parseDeclSILOptional(bool *isTransparent,
*MRK = EffectsKind::ReadOnly;
else if (MRK && SP.P.Tok.getText() == "readwrite")
*MRK = EffectsKind::ReadWrite;
else if (MRK && SP.P.Tok.getText() == "releasenone")
*MRK = EffectsKind::ReleaseNone;
else if (Semantics && SP.P.Tok.getText() == "_semantics") {
SP.P.consumeToken(tok::identifier);
if (SP.P.Tok.getKind() != tok::string_literal) {
Expand Down
4 changes: 3 additions & 1 deletion lib/SIL/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2307,8 +2307,10 @@ void SILFunction::print(SILPrintContext &PrintCtx) const {
OS << "[readonly] ";
else if (getEffectsKind() == EffectsKind::ReadNone)
OS << "[readnone] ";
if (getEffectsKind() == EffectsKind::ReadWrite)
else if (getEffectsKind() == EffectsKind::ReadWrite)
OS << "[readwrite] ";
else if (getEffectsKind() == EffectsKind::ReleaseNone)
OS << "[releasenone] ";

for (auto &Attr : getSemanticsAttrs())
OS << "[_semantics \"" << Attr << "\"] ";
Expand Down
5 changes: 5 additions & 0 deletions lib/SILOptimizer/Analysis/SideEffectAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ bool SideEffectAnalysis::getDefinedEffects(FunctionEffects &Effects,
return true;
}
switch (F->getEffectsKind()) {
case EffectsKind::ReleaseNone:
Effects.GlobalEffects.Reads = true;
Effects.GlobalEffects.Writes = true;
Effects.GlobalEffects.Releases = false;
return true;
case EffectsKind::ReadNone:
return true;
case EffectsKind::ReadOnly:
Expand Down
6 changes: 3 additions & 3 deletions lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1317,7 +1317,7 @@ SILInstruction *SILCombiner::visitApplyInst(ApplyInst *AI) {

// Optimize readonly functions with no meaningful users.
SILFunction *SF = AI->getReferencedFunction();
if (SF && SF->getEffectsKind() < EffectsKind::ReadWrite) {
if (SF && SF->getEffectsKind() < EffectsKind::ReleaseNone) {
UserListTy Users;
if (recursivelyCollectARCUsers(Users, AI)) {
if (eraseApply(AI, Users))
Expand All @@ -1327,7 +1327,7 @@ SILInstruction *SILCombiner::visitApplyInst(ApplyInst *AI) {
}

if (SF) {
if (SF->getEffectsKind() < EffectsKind::ReadWrite) {
if (SF->getEffectsKind() < EffectsKind::ReleaseNone) {
// Try to optimize string concatenation.
if (auto I = optimizeConcatenationOfStringLiterals(AI)) {
return I;
Expand Down Expand Up @@ -1450,7 +1450,7 @@ SILInstruction *SILCombiner::visitTryApplyInst(TryApplyInst *AI) {

// Optimize readonly functions with no meaningful users.
SILFunction *Fn = AI->getReferencedFunction();
if (Fn && Fn->getEffectsKind() < EffectsKind::ReadWrite) {
if (Fn && Fn->getEffectsKind() < EffectsKind::ReleaseNone) {
UserListTy Users;
if (isTryApplyResultNotUsed(Users, AI)) {
SILBasicBlock *BB = AI->getParent();
Expand Down
4 changes: 2 additions & 2 deletions lib/SILOptimizer/Utils/Local.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -693,8 +693,8 @@ bool StringConcatenationOptimizer::extractStringConcatOperands() {
auto *FRILeftFun = FRILeft->getReferencedFunction();
auto *FRIRightFun = FRIRight->getReferencedFunction();

if (FRILeftFun->getEffectsKind() >= EffectsKind::ReadWrite ||
FRIRightFun->getEffectsKind() >= EffectsKind::ReadWrite)
if (FRILeftFun->getEffectsKind() >= EffectsKind::ReleaseNone ||
FRIRightFun->getEffectsKind() >= EffectsKind::ReleaseNone)
return false;

if (!FRILeftFun->hasSemanticsAttrs() || !FRIRightFun->hasSemanticsAttrs())
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/SILFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ namespace sil_block {
BCFixed<1>, // global_init
BCFixed<2>, // inlineStrategy
BCFixed<2>, // optimizationMode
BCFixed<2>, // side effect info.
BCFixed<3>, // side effect info.
BCVBR<8>, // number of specialize attributes
BCFixed<1>, // has qualified ownership
BCFixed<1>, // must be weakly referenced
Expand Down
6 changes: 6 additions & 0 deletions test/SIL/Serialization/effectsattr.sil
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ bb0:
return %0 : $()
}

//CHECK: [releasenone] @function4
sil [releasenone] @function4 : $@convention(thin) () -> () {
bb0:
%0 = tuple ()
return %0 : $()
}
2 changes: 2 additions & 0 deletions test/SILGen/effectsattr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
//CHECK: [readwrite] @func3
@effects(readwrite) @_silgen_name("func3") func func3() { }

//CHECK: [releasenone] @func4
@effects(releasenone) @_silgen_name("func4") func func4() { }
28 changes: 28 additions & 0 deletions test/SILOptimizer/arcsequenceopts.sil
Original file line number Diff line number Diff line change
Expand Up @@ -2163,3 +2163,31 @@ bb0(%0 : $@callee_guaranteed () -> ()):
%9999 = tuple()
return %9999 : $()
}

// Make sure we can move a release across a releasenone functions.
//
// CHECK-LABEL: sil @testReleaseNoneAttribute
// CHECK-NOT: strong_retain
// CHECK: [[RELEASENONE:%.*]] = function_ref @releaseNone
// CHECK-NOT: strong_retain
// CHECK: apply [[RELEASENONE]]
// CHECK-NOT: strong_retain
// CHECK: [[MAYRELEASE:%.*]] = function_ref @mayRelease
// CHECK-NOT: strong_retain
// CHECK: apply [[MAYRELEASE]]
// CHECK-NOT: strong_retain
// CHECK: return
sil [releasenone] @releaseNone : $@convention(thin) () -> ()
sil @mayRelease : $@convention(thin) () -> ()

sil @testReleaseNoneAttribute : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
bb0(%0 : $Builtin.NativeObject):
%1 = function_ref @releaseNone : $@convention(thin) () -> ()
strong_retain %0 : $Builtin.NativeObject
%2 = apply %1() : $@convention(thin) () -> ()
%3 = function_ref @mayRelease : $@convention(thin) () -> ()
%4 = apply %3() : $@convention(thin) () -> ()
strong_release %0 : $Builtin.NativeObject
%10 = tuple()
return %10 : $()
}
Copy link
Member

Choose a reason for hiding this comment

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

👍

12 changes: 12 additions & 0 deletions test/SILOptimizer/side-effect.sil
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class X {

sil [_semantics "arc.programtermination_point"] @exitfunc : $@convention(thin) () -> Never
sil [readnone] @pure_func : $@convention(thin) () -> ()
sil [releasenone] @releasenone_func : $@convention(thin) () -> ()
sil [readonly] @readonly_owned : $@convention(thin) (@owned X) -> ()
sil [readonly] @readonly_guaranteed : $@convention(thin) (@guaranteed X) -> ()

Expand Down Expand Up @@ -449,6 +450,17 @@ bb0:
return %r : $()
}

// CHECK-LABEL: sil @call_releasenone
// CHECK: <func=rw>
sil @call_releasenone : $@convention(thin) () -> () {
bb0:
%u = function_ref @releasenone_func : $@convention(thin) () -> ()
%a = apply %u() : $@convention(thin) () -> ()
%r = tuple ()
return %r : $()
}


// CHECK-LABEL: sil @call_readonly_owned
// CHECK: <func=rw+-,param0=;alloc;trap;readrc>
sil @call_readonly_owned : $@convention(thin) (@owned X) -> () {
Expand Down