Skip to content

[sil-dead-funciton-elimination] Eliminate unused default methods impementations from default witness tables #7148

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

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
21 changes: 20 additions & 1 deletion include/swift/SIL/SILDefaultWitnessTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class SILDefaultWitnessTable : public llvm::ilist_node<SILDefaultWitnessTable>,
: Requirement(Requirement), Witness(Witness) {}

bool isValid() const {
return !Requirement.isNull();
return !Requirement.isNull() && Witness;
}

const SILDeclRef &getRequirement() const {
Expand All @@ -68,6 +68,12 @@ class SILDefaultWitnessTable : public llvm::ilist_node<SILDefaultWitnessTable>,
assert(Witness != nullptr);
return Witness;
}
void removeWitnessMethod() {
if (Witness) {
Witness->decrementRefCount();
}
Witness = nullptr;
}
};

private:
Expand Down Expand Up @@ -131,6 +137,19 @@ class SILDefaultWitnessTable : public llvm::ilist_node<SILDefaultWitnessTable>,
/// Return the AST ProtocolDecl this default witness table is associated with.
const ProtocolDecl *getProtocol() const { return Protocol; }

/// Clears methods in witness entries.
/// \p predicate Returns true if the passed entry should be set to null.
template <typename Predicate> void clearMethods_if(Predicate predicate) {
for (Entry &entry : Entries) {
if (!entry.isValid())
continue;
auto *MW = entry.getWitness();
if (MW && predicate(MW)) {
entry.removeWitnessMethod();
}
}
}

/// Return the minimum witness table size, in words.
///
/// This will not change if requirements with default implementations are
Expand Down
44 changes: 37 additions & 7 deletions lib/SILOptimizer/IPO/DeadFunctionElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ class FunctionLivenessComputation {
}

/// Retrieve the visibility information from the AST.
bool isVisibleExternally(ValueDecl *decl) {
bool isVisibleExternally(const ValueDecl *decl) {
Accessibility accessibility = decl->getEffectiveAccess();
SILLinkage linkage;
switch (accessibility) {
Expand Down Expand Up @@ -558,15 +558,27 @@ class DeadFunctionElimination : FunctionLivenessComputation {
makeAlive(&WT);
}
}

// Check default witness methods.
for (SILDefaultWitnessTable &WT : Module->getDefaultWitnessTableList()) {
for (const SILDefaultWitnessTable::Entry &entry : WT.getEntries()) {
if (!entry.isValid())
continue;
if (isVisibleExternally(WT.getProtocol())) {
// The default witness table is visible from "outside". Therefore all
// methods might be called and we mark all methods as alive.
for (const SILDefaultWitnessTable::Entry &entry : WT.getEntries()) {
if (!entry.isValid())
continue;

auto *fd = cast<AbstractFunctionDecl>(entry.getRequirement().getDecl());
MethodInfo *mi = getMethodInfo(fd, /*isWitnessTable*/ true);
ensureAliveProtocolMethod(mi);
auto *fd =
cast<AbstractFunctionDecl>(entry.getRequirement().getDecl());
assert(fd == getBase(fd) &&
"key in default witness table is overridden");
SILFunction *F = entry.getWitness();
if (!F)
continue;

MethodInfo *mi = getMethodInfo(fd, /*isWitnessTable*/ true);
ensureAliveProtocolMethod(mi);
}
}
}
}
Expand Down Expand Up @@ -597,6 +609,24 @@ class DeadFunctionElimination : FunctionLivenessComputation {
return false;
});
}

auto DefaultWitnessTables = Module->getDefaultWitnessTables();
for (auto WI = DefaultWitnessTables.begin(),
EI = DefaultWitnessTables.end();
WI != EI;) {
SILDefaultWitnessTable *WT = &*WI;
WI++;
WT->clearMethods_if([this](SILFunction *MW) -> bool {
if (!MW)
return false;
if (!isAlive(MW)) {
DEBUG(llvm::dbgs() << " erase dead default witness method "
<< MW->getName() << "\n");
return true;
}
return false;
});
}
}

public:
Expand Down
34 changes: 32 additions & 2 deletions test/SILOptimizer/dead_function_elimination.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,23 @@ public class PublicCl {
// Check if unused witness table methods are removed.

protocol Prot {
func aliveWitness()
func aliveWitness()

func DeadWitness()
func DeadWitness()

func aliveDefaultWitness()

func DeadDefaultWitness()
}

extension Prot {
@inline(never)
func aliveDefaultWitness() {
}

@inline(never)
func DeadDefaultWitness() {
}
}

struct Adopt : Prot {
Expand All @@ -134,6 +148,11 @@ func testProtocols(_ p: Prot) {
p.aliveWitness()
}

@inline(never)
@_semantics("optimize.sil.never") // avoid devirtualization
func testDefaultWitnessMethods(_ p: Prot) {
p.aliveDefaultWitness()
}

@_semantics("optimize.sil.never") // avoid devirtualization
public func callTest() {
Expand Down Expand Up @@ -198,3 +217,14 @@ internal func donotEliminate() {
// CHECK-TESTING-LABEL: sil_witness_table [fragile] Adopt: Prot
// CHECK-TESTING: DeadWitness{{.*}}: @{{.*}}DeadWitness

// CHECK-LABEL: sil_default_witness_table hidden Prot
// CHECK: no_default
// CHECK: no_default
// CHECK: method #Prot.aliveDefaultWitness!1: {{.*}} : @{{.*}}aliveDefaultWitness
// CHECK: no_default

// CHECK-TESTING-LABEL: sil_default_witness_table Prot
// CHECK-TESTING: no_default
// CHECK-TESTING: no_default
// CHECK-TESTING: method #Prot.aliveDefaultWitness!1: {{.*}} : @{{.*}}aliveDefaultWitness
// CHECK-TESTING: method #Prot.DeadDefaultWitness!1: {{.*}} : @{{.*}}DeadDefaultWitness