Skip to content

Add a SIL pass to select an access enforcement for allocated boxes. #8686

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
25 changes: 25 additions & 0 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1907,6 +1907,17 @@ class BeginAccessInst
SILValue getSource() const {
return getOperand();
}

private:
/// Predicate used to filter EndAccessRange.
struct UseToEndAccess;

public:
using EndAccessRange =
OptionalTransformRange<use_range, UseToEndAccess, use_iterator>;

/// Find all the associated end_access instructions for this begin_access.
EndAccessRange getEndAccesses() const;
};

/// Represents the end of an access scope.
Expand Down Expand Up @@ -1944,6 +1955,20 @@ class EndAccessInst : public UnaryInstructionBase<ValueKind::EndAccessInst> {
}
};

struct BeginAccessInst::UseToEndAccess {
Optional<EndAccessInst *> operator()(Operand *use) const {
if (auto access = dyn_cast<EndAccessInst>(use->getUser())) {
return access;
} else {
return None;
}
}
};

inline auto BeginAccessInst::getEndAccesses() const -> EndAccessRange {
return EndAccessRange(getUses(), UseToEndAccess());
}

/// AssignInst - Represents an abstract assignment to a memory location, which
/// may either be an initialization or a store sequence. This is only valid in
/// Raw SIL.
Expand Down
2 changes: 2 additions & 0 deletions include/swift/SILOptimizer/PassManager/Passes.def
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ PASS(AADumper, "aa-dump",
"Dump Alias Analysis over all Pairs")
PASS(ABCOpt, "abcopts",
"Array Bounds Check Optimization")
PASS(AccessEnforcementSelection, "access-enforcement-selection",
"Access Enforcement Selection")
PASS(AccessMarkerElimination, "access-marker-elim",
"Access Marker Elimination.")
PASS(AddressLowering, "address-lowering",
Expand Down
334 changes: 334 additions & 0 deletions lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
//===--- AccessEnforcementSelection.cpp - Select access enforcement -------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//
///
/// This pass eliminates 'unknown' access enforcement by selecting either
/// static or dynamic enforcement.
///
//===----------------------------------------------------------------------===//

#define DEBUG_TYPE "access-enforcement-selection"
#include "swift/SIL/SILFunction.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"

using namespace swift;

static void setStaticEnforcement(BeginAccessInst *access) {
// TODO: delete if we're not using static enforcement?
access->setEnforcement(SILAccessEnforcement::Static);
}

static void setDynamicEnforcement(BeginAccessInst *access) {
// TODO: delete if we're not using dynamic enforcement?
access->setEnforcement(SILAccessEnforcement::Dynamic);
}

namespace {
class SelectEnforcement {
AllocBoxInst *Box;

/// A state for tracking escape information about a variable.
/// StateMap only has entries for blocks for which the variable
/// has potentially escaped at exit.
struct State {
bool IsInWorklist = false;

// At least one of the following must be true.
bool HasEscape = false;
bool HasPotentiallyEscapedAtEntry = false;

// In a more advanced problem, this could easily be passed a State.
bool adjustForEscapeInPredecessor() {
bool updateSuccessors = false;

if (!HasPotentiallyEscapedAtEntry) {
HasPotentiallyEscapedAtEntry = true;
updateSuccessors = !HasEscape;
}

return updateSuccessors;
}
};
llvm::DenseMap<SILBasicBlock*, State> StateMap;

/// All the accesses in the function.
SmallVector<BeginAccessInst*, 8> Accesses;

/// All the escapes in the function.
SmallPtrSet<SILInstruction*, 8> Escapes;

/// A worklist we use for various purposes.
SmallVector<SILBasicBlock*, 8> Worklist;

public:
SelectEnforcement(AllocBoxInst *box) : Box(box) {}

void run();

private:
void analyzeUsesOfBox();
void analyzeProjection(ProjectBoxInst *projection);

void propagateEscapes();
void propagateEscapesFrom(SILBasicBlock *bb);

bool hasPotentiallyEscapedAt(SILInstruction *inst);
bool hasPotentiallyEscapedAtAnyReachableBlock(BeginAccessInst *access);

void updateAccesses();
void updateAccess(BeginAccessInst *access);
};
} // end anonymous namespace

void SelectEnforcement::run() {
// Set up the data-flow problem.
analyzeUsesOfBox();

// Run the data-flow problem.
propagateEscapes();

// Update all the accesses.
updateAccesses();
}

void SelectEnforcement::analyzeUsesOfBox() {
// Collect accesses rooted off of projections.
for (auto use : Box->getUses()) {
auto user = use->getUser();

if (auto projection = dyn_cast<ProjectBoxInst>(user)) {
analyzeProjection(projection);
continue;
}

// Ignore certain other uses that do not capture the value.
if (isa<StrongRetainInst>(user) ||
isa<StrongReleaseInst>(user) ||
isa<DestroyValueInst>(user) ||
isa<DeallocBoxInst>(user))
continue;

// Treat everything else as an escape:

// Add it to the escapes set.
Escapes.insert(user);

//
auto userBB = user->getParent();
auto &state = StateMap[userBB];
if (!state.IsInWorklist) {
state.HasEscape = true;
state.IsInWorklist = true;
Worklist.push_back(userBB);
}
assert(state.HasEscape);
assert(state.IsInWorklist);
}

assert(!Accesses.empty() && "didn't find original access!");
}

void SelectEnforcement::analyzeProjection(ProjectBoxInst *projection) {
for (auto use : projection->getUses()) {
auto user = use->getUser();

// Collect accesses.
if (auto access = dyn_cast<BeginAccessInst>(user)) {
if (access->getEnforcement() == SILAccessEnforcement::Unknown)
Accesses.push_back(access);
}
}
}

void SelectEnforcement::propagateEscapes() {
while (!Worklist.empty()) {
auto bb = Worklist.pop_back_val();
auto it = StateMap.find(bb);
assert(it != StateMap.end() &&
"block was in worklist but doesn't have a tracking state");
auto &state = it->second;
assert(state.HasEscape || state.HasPotentiallyEscapedAtEntry);
state.IsInWorklist = false;
propagateEscapesFrom(bb);
}
}

/// Given that the box potentially escaped before we exited the
/// given block, propagate that information to all of its successors.
void SelectEnforcement::propagateEscapesFrom(SILBasicBlock *bb) {
assert(StateMap.count(bb));

// Iterate over the successors of the block.
for (SILBasicBlock *succ : bb->getSuccessors()) {
auto &succState = StateMap[succ];

// If updating the successor changes it in a way that will
// require us to update its successors, add it to the worklist.
if (succState.adjustForEscapeInPredecessor()) {
if (!succState.IsInWorklist) {
succState.IsInWorklist = true;
Worklist.push_back(succ);
}
}
}
}

bool SelectEnforcement::hasPotentiallyEscapedAt(SILInstruction *point) {
auto bb = point->getParent();

// If we're not tracking anything for the whole block containing
// the instruction, we're done; it hasn't escaped here.
auto it = StateMap.find(bb);
if (it == StateMap.end())
return false;

// If the tracking information says there are escapes before entry,
// we're done; it has potentially escaped.
const auto &state = it->second;
if (state.HasPotentiallyEscapedAtEntry)
return true;

// Okay, there must be an escape within this block.
assert(state.HasEscape);
for (auto ii = point->getIterator(), ie = bb->begin(); ii != ie; ) {
auto inst = &*--ii;

// Maybe just record the first escape in the block and see if we
// come after it?
if (Escapes.count(inst))
return true;
}

return false;
}

bool SelectEnforcement::hasPotentiallyEscapedAtAnyReachableBlock(
BeginAccessInst *access) {
// Fast path: we're not tracking any escapes. (But the box should
// probably have been promoted to the stack in this case.)
if (StateMap.empty())
return false;

auto bb = access->getParent();

assert(Worklist.empty());
SmallPtrSet<SILBasicBlock*, 8> visited;
visited.insert(bb);
Worklist.push_back(bb);

while (!Worklist.empty()) {
bb = Worklist.pop_back_val();
assert(visited.count(bb));

// If we're tracking information for this block, there's an escape.
if (StateMap.count(bb))
return true;

// Add all reachable successors.
for (SILBasicBlock *succ : bb->getSuccessors()) {
if (visited.insert(succ).second)
Worklist.push_back(succ);
}
}

// No reachable block has an escape.
return false;
}

void SelectEnforcement::updateAccesses() {
for (auto access : Accesses) {
updateAccess(access);
}
}

void SelectEnforcement::updateAccess(BeginAccessInst *access) {
assert(access->getEnforcement() == SILAccessEnforcement::Unknown);

// Check whether the variable escaped before any of the end_accesses.
bool anyDynamic = false;
bool hasEndAccess = false;
for (auto endAccess : access->getEndAccesses()) {
hasEndAccess = true;
if (hasPotentiallyEscapedAt(endAccess)) {
anyDynamic = true;
break;
}
}

// If so, make the access dynamic.
if (anyDynamic)
return setDynamicEnforcement(access);

// Otherwise, if there are no end_access instructions,
// check the terminators of every reachable block.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure what "check the terminators" means" This code just checks if there are any uses reachable from this begin_access.

if (!hasEndAccess) {
if (hasPotentiallyEscapedAtAnyReachableBlock(access))
return setDynamicEnforcement(access);
}

// Otherwise, use static enforcement.
setStaticEnforcement(access);
}

namespace {

/// The pass.
struct AccessEnforcementSelection : SILFunctionTransform {
void run() override {
for (auto &bb : *getFunction()) {
for (auto ii = bb.begin(), ie = bb.end(); ii != ie; ) {
SILInstruction *inst = &*ii;
++ii;

if (auto access = dyn_cast<BeginAccessInst>(inst)) {
handleAccess(access);
}
}
}
}

void handleAccess(BeginAccessInst *access) {
if (access->getEnforcement() != SILAccessEnforcement::Unknown)
return;

auto address = access->getOperand();
assert(!isa<MarkUninitializedInst>(address) &&
"pass should be run after definite initialization");

if (auto box = dyn_cast<ProjectBoxInst>(address)) {
return handleAccessToBox(access, box);
}

// If we're not accessing a box, we must've lowered to
// a stack element.
assert(isa<AllocStackInst>(address));
setStaticEnforcement(access);
}

void handleAccessToBox(BeginAccessInst *access, ProjectBoxInst *projection) {
// If we didn't allocate the box, assume that we need to use
// dynamic enforcement.
// TODO: use static enforcement in certain provable cases.
auto box = dyn_cast<AllocBoxInst>(projection->getOperand());
if (!box) {
setDynamicEnforcement(access);
return;
}

SelectEnforcement(box).run();
}
};

} // end anonymous namespace

SILTransform *swift::createAccessEnforcementSelection() {
return new AccessEnforcementSelection();
}
1 change: 1 addition & 0 deletions lib/SILOptimizer/Mandatory/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
set(MANDATORY_SOURCES
Mandatory/AccessEnforcementSelection.cpp
Mandatory/AccessMarkerElimination.cpp
Mandatory/AddressLowering.cpp
Mandatory/DefiniteInitialization.cpp
Expand Down
Loading