[Exclusivity] Remove dominated access checks with no nested conflict.

General case:

—
begin_access A (may or may not have no_nested_conflict)
load/store
end_access

apply // may have a scoped access that conflicts with A

begin_access A [no_nested_conflict]
load/store
end_access A
—

The second access scope does not need to be emitted.

NOTE: KeyPath access must be identified at the top-level, non-inlinable stdlib entry point.
As such, The sodlib entry pointed is annotated by a new @_semantics that is equivalent to inline(never)
This commit is contained in:
Joe Shajrawi
2018-10-05 09:21:40 -07:00
parent b0389af92e
commit 63b50f65a4
11 changed files with 608 additions and 12 deletions

View File

@@ -379,6 +379,10 @@ SIMPLE_DECL_ATTR(_nonoverride, NonOverride,
OnFunc | OnAccessor | OnVar | OnSubscript | OnConstructor | OnAssociatedType | OnFunc | OnAccessor | OnVar | OnSubscript | OnConstructor | OnAssociatedType |
UserInaccessible | NotSerialized, UserInaccessible | NotSerialized,
79) 79)
SIMPLE_DECL_ATTR(_keyPathEntryPoint, KeyPathEntryPoint,
OnFunc |
UserInaccessible,
80)
#undef TYPE_ATTR #undef TYPE_ATTR
#undef DECL_ATTR_ALIAS #undef DECL_ATTR_ALIAS

View File

@@ -54,6 +54,8 @@ PASS(AADumper, "aa-dump",
"Dump Alias Analysis over all Pairs") "Dump Alias Analysis over all Pairs")
PASS(ABCOpt, "abcopts", PASS(ABCOpt, "abcopts",
"Array Bounds Check Optimization") "Array Bounds Check Optimization")
PASS(AccessEnforcementDom, "access-enforcement-dom",
"Remove dominated access checks with no nested conflict")
PASS(AccessEnforcementOpts, "access-enforcement-opts", PASS(AccessEnforcementOpts, "access-enforcement-opts",
"Access Enforcement Optimization") "Access Enforcement Optimization")
PASS(AccessEnforcementSelection, "access-enforcement-selection", PASS(AccessEnforcementSelection, "access-enforcement-selection",

View File

@@ -530,6 +530,8 @@ IsSerialized_t SILDeclRef::isSerialized() const {
bool SILDeclRef::isNoinline() const { bool SILDeclRef::isNoinline() const {
if (!hasDecl()) if (!hasDecl())
return false; return false;
if (getDecl()->getAttrs().hasAttribute<KeyPathEntryPointAttr>())
return true;
if (auto InlineA = getDecl()->getAttrs().getAttribute<InlineAttr>()) if (auto InlineA = getDecl()->getAttrs().getAttribute<InlineAttr>())
if (InlineA->getKind() == InlineKind::Never) if (InlineA->getKind() == InlineKind::Never)
return true; return true;

View File

@@ -496,6 +496,10 @@ static void addLastChanceOptPassPipeline(SILPassPipelinePlan &P) {
// Optimize access markers for improved IRGen after all other optimizations. // Optimize access markers for improved IRGen after all other optimizations.
P.addAccessEnforcementOpts(); P.addAccessEnforcementOpts();
P.addAccessEnforcementWMO(); P.addAccessEnforcementWMO();
P.addAccessEnforcementDom();
// addAccessEnforcementDom might provide potential for LICM:
// A loop might have only one dynamic access now, i.e. hoistable
P.addLICM();
// Only has an effect if the -assume-single-thread option is specified. // Only has an effect if the -assume-single-thread option is specified.
P.addAssumeSingleThreaded(); P.addAssumeSingleThreaded();

View File

@@ -0,0 +1,237 @@
//===--- AccessEnforcementDom.cpp - dominated access removal opt ---===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 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 function pass removes dynamic access enforcement based on dominance.
///
/// General case:
/// begin_access A (may or may not have no_nested_conflict)
/// load/store
/// end_access
/// ...
/// begin_access A [no_nested_conflict] // dominated by the first access
/// load/store
/// end_access A
/// The second access scope does not need to be emitted.
///
/// Note: This optimization must be aware of all possible access to a Class or
/// Global address. This includes unpaired access instructions and keypath
/// entry points. Ignoring any access pattern would weaken enforcement.
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "access-enforcement-dom"
#include "swift/SIL/DebugUtils.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/Local.h"
using namespace swift;
namespace {
class DominatedAccessRemoval {
public:
using AccessedStoragePair = std::pair<BeginAccessInst *, AccessedStorage>;
using AccessedStorageInfo = llvm::SmallVector<AccessedStoragePair, 32>;
using DominatorToDominatedPair =
std::pair<BeginAccessInst *, BeginAccessInst *>;
using DomPairSet = llvm::SmallVector<DominatorToDominatedPair, 32>;
using KeyPathEntryPointsSet = llvm::SmallSet<SILInstruction *, 8>;
using UnpairedAccessToStoragePair =
std::pair<BeginUnpairedAccessInst *, AccessedStorage>;
using UnpairedAccessToStorageInfo =
llvm::SmallVector<UnpairedAccessToStoragePair, 8>;
public:
DominatedAccessRemoval(SILFunction &func, DominanceInfo *domInfo)
: func(func), domInfo(domInfo) {}
void perform();
protected:
void visitInstruction(SILInstruction *instr);
void visitBeginAccess(BeginAccessInst *beginAccess, AccessedStorage storage);
bool domByKeyPath(BeginAccessInst *dominatedInstr);
bool domByRelevantUnpairedAccess(DominatorToDominatedPair pair);
void analyze();
void optimize();
private:
SILFunction &func;
DominanceInfo *domInfo;
AccessedStorageInfo accessInfo;
DomPairSet domPairs;
KeyPathEntryPointsSet keypathEntries;
UnpairedAccessToStorageInfo unpairedEntries;
};
} // namespace
bool DominatedAccessRemoval::domByKeyPath(BeginAccessInst *dominatedInstr) {
for (SILInstruction *keyPathEntry : keypathEntries) {
if (domInfo->properlyDominates(keyPathEntry, dominatedInstr)) {
return true;
}
}
return false;
}
bool DominatedAccessRemoval::domByRelevantUnpairedAccess(
DominatorToDominatedPair pair) {
BeginAccessInst *parentBegin = pair.first;
BeginAccessInst *dominatedInstr = pair.second;
auto predEqual = [&](AccessedStoragePair it) {
auto currInstr = it.first;
return currInstr == parentBegin;
};
auto currStorageIt =
std::find_if(accessInfo.begin(), accessInfo.end(), predEqual);
assert(currStorageIt != accessInfo.end() && "Expected storage in accessInfo");
AccessedStorage currStorage = currStorageIt->second;
for (UnpairedAccessToStoragePair unpairedEntry : unpairedEntries) {
auto *instr = unpairedEntry.first;
if (!domInfo->properlyDominates(instr, dominatedInstr)) {
continue;
}
auto entryStorage = unpairedEntry.second;
if (!currStorage.isDistinctFrom(entryStorage)) {
return true;
}
}
return false;
}
void DominatedAccessRemoval::visitInstruction(SILInstruction *instr) {
if (auto *BAI = dyn_cast<BeginAccessInst>(instr)) {
if (BAI->getEnforcement() != SILAccessEnforcement::Dynamic) {
return;
}
AccessedStorage storage = findAccessedStorageNonNested(BAI->getSource());
if (!storage) {
return;
}
visitBeginAccess(BAI, storage);
} else if (auto fullApply = FullApplySite::isa(instr)) {
SILFunction *callee = fullApply.getReferencedFunction();
if (!callee)
return;
if (!callee->hasSemanticsAttr("_keyPathEntryPoint"))
return;
// we can't eliminate dominated checks even when we can prove that
// the dominated scope has no internal nested conflicts.
keypathEntries.insert(fullApply.getInstruction());
} else if (auto *BUAI = dyn_cast<BeginUnpairedAccessInst>(instr)) {
AccessedStorage storage = findAccessedStorageNonNested(BUAI->getSource());
unpairedEntries.push_back(std::make_pair(BUAI, storage));
}
}
void DominatedAccessRemoval::visitBeginAccess(BeginAccessInst *beginAccess,
AccessedStorage storage) {
auto predEqual = [&](AccessedStoragePair it) {
auto currStorage = it.second;
return currStorage.hasIdenticalBase(storage);
};
// If the currnet access has nested conflict, just add it to map
// we can't remove it by finding a dominating access
if (!beginAccess->hasNoNestedConflict()) {
accessInfo.push_back(std::make_pair(beginAccess, storage));
return;
}
auto it = std::find_if(accessInfo.begin(), accessInfo.end(), predEqual);
while (it != accessInfo.end()) {
BeginAccessInst *parentBeginAccess = it->first;
if (!domInfo->properlyDominates(parentBeginAccess, beginAccess)) {
++it;
it = std::find_if(it, accessInfo.end(), predEqual);
continue;
}
// Found a pair that can potentially be optimized
domPairs.push_back(std::make_pair(parentBeginAccess, beginAccess));
return;
}
// Did not find a dominating access to same storage
accessInfo.push_back(std::make_pair(beginAccess, storage));
}
// Finds domPairs for which we can change the dominated instruction to static
// NOTE: We might not be able to optimize some the pairs due to other
// restrictions Such as key-path or unpaired begin access We only traverse the
// function once, if we find a pattern that *might* prevent optimization, we
// just add it to appropriate data structures which will be analyzed later.
void DominatedAccessRemoval::analyze() {
SILBasicBlock *entry = &func.front();
DominanceOrder domOrder(entry, domInfo, func.size());
while (SILBasicBlock *block = domOrder.getNext()) {
for (auto &instr : *block) {
visitInstruction(&instr);
}
domOrder.pushChildren(block);
}
}
// Sets the dominated instruction to static.
// Goes through the data structures initialized by the analysis method
// and makes sure we are not Weakening enforcement
void DominatedAccessRemoval::optimize() {
for (DominatorToDominatedPair pair : domPairs) {
LLVM_DEBUG(llvm::dbgs()
<< "Processing optimizable pair - Dominator: " << *pair.first
<< " , Dominated: " << *pair.second << "\n");
BeginAccessInst *dominatedInstr = pair.second;
// look through keypathEntries to see if dominatedInstr
// can no longer be optimized
if (domByKeyPath(dominatedInstr)) {
LLVM_DEBUG(llvm::dbgs()
<< "Can not set " << *dominatedInstr
<< " access enforcement to static - it is properly dominated "
"by a key-path entry point\n");
continue;
}
if (domByRelevantUnpairedAccess(pair)) {
LLVM_DEBUG(llvm::dbgs()
<< "Can not set " << *dominatedInstr
<< " access enforcement to static - there's an unpaired "
"access that is not distinct from it in the way\n");
continue;
}
LLVM_DEBUG(llvm::dbgs() << "Setting " << *dominatedInstr
<< " access enforcement to static\n");
dominatedInstr->setEnforcement(SILAccessEnforcement::Static);
}
}
void DominatedAccessRemoval::perform() {
if (func.empty())
return;
analyze();
optimize();
}
namespace {
struct AccessEnforcementDom : public SILFunctionTransform {
void run() override {
DominanceAnalysis *domAnalysis = getAnalysis<DominanceAnalysis>();
DominanceInfo *domInfo = domAnalysis->get(getFunction());
DominatedAccessRemoval eliminationPass(*getFunction(), domInfo);
eliminationPass.perform();
}
};
} // namespace
SILTransform *swift::createAccessEnforcementDom() {
return new AccessEnforcementDom();
}

View File

@@ -1,5 +1,6 @@
silopt_register_sources( silopt_register_sources(
ARCCodeMotion.cpp ARCCodeMotion.cpp
AccessEnforcementDom.cpp
AccessEnforcementOpts.cpp AccessEnforcementOpts.cpp
AccessEnforcementWMO.cpp AccessEnforcementWMO.cpp
AllocBoxToStack.cpp AllocBoxToStack.cpp

View File

@@ -117,6 +117,7 @@ public:
IGNORED_ATTR(UnsafeNoObjCTaggedPointer) IGNORED_ATTR(UnsafeNoObjCTaggedPointer)
IGNORED_ATTR(UsableFromInline) IGNORED_ATTR(UsableFromInline)
IGNORED_ATTR(WeakLinked) IGNORED_ATTR(WeakLinked)
IGNORED_ATTR(KeyPathEntryPoint)
#undef IGNORED_ATTR #undef IGNORED_ATTR
// @noreturn has been replaced with a 'Never' return type. // @noreturn has been replaced with a 'Never' return type.
@@ -818,6 +819,7 @@ public:
IGNORED_ATTR(Transparent) IGNORED_ATTR(Transparent)
IGNORED_ATTR(WarnUnqualifiedAccess) IGNORED_ATTR(WarnUnqualifiedAccess)
IGNORED_ATTR(WeakLinked) IGNORED_ATTR(WeakLinked)
IGNORED_ATTR(KeyPathEntryPoint)
#undef IGNORED_ATTR #undef IGNORED_ATTR
void visitAvailableAttr(AvailableAttr *attr); void visitAvailableAttr(AvailableAttr *attr);

View File

@@ -1236,6 +1236,7 @@ namespace {
UNINTERESTING_ATTR(WeakLinked) UNINTERESTING_ATTR(WeakLinked)
UNINTERESTING_ATTR(Frozen) UNINTERESTING_ATTR(Frozen)
UNINTERESTING_ATTR(HasInitialValue) UNINTERESTING_ATTR(HasInitialValue)
UNINTERESTING_ATTR(KeyPathEntryPoint)
#undef UNINTERESTING_ATTR #undef UNINTERESTING_ATTR
void visitAvailableAttr(AvailableAttr *attr) { void visitAvailableAttr(AvailableAttr *attr) {

View File

@@ -1656,7 +1656,15 @@ func _projectKeyPathReadOnly<Root, Value>(
return keyPath._projectReadOnly(from: root) return keyPath._projectReadOnly(from: root)
} }
@inlinable // The compiler can't tell which calls might begin an access.
// That means it can't eliminate dominated checks even when it can prove
// that the dominated scope has no internal nested conflicts.
// We use the @_keyPathEntryPoint annotation:
// This doesn't solve the deinit ending a scope problem,
// but it solves the much more important half of the problem:
// identifying the beginning of an access scope -
// would allow dominance based optimization:
@_keyPathEntryPoint
public // COMPILER_INTRINSIC public // COMPILER_INTRINSIC
func _projectKeyPathWritable<Root, Value>( func _projectKeyPathWritable<Root, Value>(
root: UnsafeMutablePointer<Root>, root: UnsafeMutablePointer<Root>,
@@ -1665,7 +1673,7 @@ func _projectKeyPathWritable<Root, Value>(
return keyPath._projectMutableAddress(from: root) return keyPath._projectMutableAddress(from: root)
} }
@inlinable @_keyPathEntryPoint
public // COMPILER_INTRINSIC public // COMPILER_INTRINSIC
func _projectKeyPathReferenceWritable<Root, Value>( func _projectKeyPathReferenceWritable<Root, Value>(
root: Root, root: Root,

View File

@@ -0,0 +1,335 @@
// RUN: %target-sil-opt -access-enforcement-dom -assume-parsing-unqualified-ownership-sil %s -enable-sil-verify-all | %FileCheck %s
//
// Test the AccessEnforcementDom pass in isolation. This ensures that
// no upstream passes have removed SIL-level access markers that are
// required to ensure the pass is not overly optimistic.
sil_stage canonical
import Builtin
import Swift
import SwiftShims
struct X {
@sil_stored var i: Int64 { get set }
init(i: Int64)
init()
}
var globalX: X
var globalOtherX: X
sil_global hidden @globalX : $X
sil_global hidden @globalOtherX : $X
sil hidden @Xinit : $@convention(method) (@thin X.Type) -> X {
bb0(%0 : $@thin X.Type):
%1 = alloc_stack $X, var, name "self"
%2 = integer_literal $Builtin.Int64, 7
%3 = struct $Int64 (%2 : $Builtin.Int64)
%4 = struct_element_addr %1 : $*X, #X.i
store %3 to %4 : $*Int64
%6 = struct $X (%3 : $Int64)
dealloc_stack %1 : $*X
return %6 : $X
}
// public func testDomSimpleRead() {
// Checks 3 scopes, two of which are dominated and access the same storage
//
// CHECK-LABEL: sil @testDomSimpleRead : $@convention(thin) () -> () {
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NOT: begin_access
// CHECK-LABEL: } // end sil function 'testDomSimpleRead'
sil @testDomSimpleRead : $@convention(thin) () -> () {
bb0:
%0 = global_addr @globalX: $*X
%1 = begin_access [read] [dynamic] %0 : $*X
%2 = load %1 : $*X
end_access %1 : $*X
%4 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%5 = load %4 : $*X
end_access %4 : $*X
%7 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%8 = load %7 : $*X
end_access %7 : $*X
%10 = tuple ()
return %10 : $()
}
// public func testDomSimpleWrite() {
// Checks 3 scopes, two of which are dominated and access the same storage
//
// CHECK-LABEL: sil @testDomSimpleWrite : $@convention(thin) () -> () {
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK: [[BEGIN:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK: store {{.*}} to [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NOT: begin_access
// CHECK-LABEL: } // end sil function 'testDomSimpleWrite'
sil @testDomSimpleWrite : $@convention(thin) () -> () {
bb0:
%0 = global_addr @globalX: $*X
%1 = begin_access [modify] [dynamic] %0 : $*X
%2 = load %1 : $*X
end_access %1 : $*X
%4 = metatype $@thin X.Type
// function_ref X.init()
%5 = function_ref @Xinit : $@convention(method) (@thin X.Type) -> X
%6 = apply %5(%4) : $@convention(method) (@thin X.Type) -> X
%7 = begin_access [modify] [dynamic] [no_nested_conflict] %0 : $*X
store %6 to %7 : $*X
end_access %7 : $*X
%10 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%11 = load %10 : $*X
end_access %10 : $*X
%12 = tuple ()
return %12 : $()
}
// public func testDomAcrossBBs() {
// Checks static-setting of scopes across basic blocks
//
// CHECK-LABEL: sil @testDomAcrossBBs : $@convention(thin) () -> () {
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NEXT: br bb1
// CHECK: br bb2
// CHECK: bb2:
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NOT: begin_access
// CHECK-LABEL: } // end sil function 'testDomAcrossBBs'
sil @testDomAcrossBBs : $@convention(thin) () -> () {
bb0:
%0 = global_addr @globalX: $*X
%1 = begin_access [modify] [dynamic] %0 : $*X
%2 = load %1 : $*X
end_access %1 : $*X
br bb1
bb1:
br bb2
bb2:
%4 = begin_access [modify] [dynamic] [no_nested_conflict] %0 : $*X
%5 = load %4 : $*X
end_access %4 : $*X
%7 = tuple ()
return %7 : $()
}
// public func testDomAcrossInnerLoop() {
// Checksstatic-setting of scopes across an inner loop
//
// CHECK-LABEL: sil @testDomAcrossInnerLoop : $@convention(thin) () -> () {
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NEXT: br bb1
// CHECK: cond_br {{.*}}, bb1, bb2
// CHECK: bb2:
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NOT: begin_access
// CHECK-LABEL: } // end sil function 'testDomAcrossInnerLoop'
sil @testDomAcrossInnerLoop : $@convention(thin) () -> () {
bb0:
%0 = global_addr @globalX: $*X
%1 = begin_access [modify] [dynamic] %0 : $*X
%2 = load %1 : $*X
end_access %1 : $*X
br bb1
bb1:
%cond = integer_literal $Builtin.Int1, 1
cond_br %cond, bb1, bb2
bb2:
%4 = begin_access [modify] [dynamic] [no_nested_conflict] %0 : $*X
%5 = load %4 : $*X
end_access %4 : $*X
%7 = tuple ()
return %7 : $()
}
// public func testIrreducibleGraph() {
// Checks domination in an irreducible control flow
//
// CHECK-LABEL: sil @testIrreducibleGraph : $@convention(thin) () -> () {
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NEXT: br bb1
// CHECK: [[BEGIN2:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN2]] : $*X
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
// CHECK: cond_br {{.*}}, bb2, bb3
// CHECK: [[BEGIN3:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN3]] : $*X
// CHECK-NEXT: end_access [[BEGIN3]] : $*X
// CHECK: cond_br {{.*}}, bb3, bb4
// CHECK: [[BEGIN4:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN4]] : $*X
// CHECK-NEXT: end_access [[BEGIN4]] : $*X
// CHECK: cond_br {{.*}}, bb2, bb1
// CHECK: [[BEGIN5:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN5]] : $*X
// CHECK-NEXT: end_access [[BEGIN5]] : $*X
// CHECK-NOT: begin_access
// CHECK-LABEL: } // end sil function 'testIrreducibleGraph'
sil @testIrreducibleGraph : $@convention(thin) () -> () {
bb0:
%0 = global_addr @globalX: $*X
%1 = begin_access [read] [dynamic] %0 : $*X
%2 = load %1 : $*X
end_access %1 : $*X
br bb1
bb1:
%4 = begin_access [modify] [dynamic] [no_nested_conflict] %0 : $*X
%5 = load %4 : $*X
end_access %4 : $*X
%cond1 = integer_literal $Builtin.Int1, 1
cond_br %cond1, bb2, bb3
bb2:
%6 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%7 = load %6 : $*X
end_access %6 : $*X
%cond2 = integer_literal $Builtin.Int1, 1
cond_br %cond2, bb3, bb4
bb3:
%8 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%9 = load %8 : $*X
end_access %8 : $*X
%cond3 = integer_literal $Builtin.Int1, 1
cond_br %cond3, bb2, bb1
bb4:
%10 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%11 = load %10 : $*X
end_access %10 : $*X
%12 = tuple ()
return %12 : $()
}
// public func testIrreducibleGraph2() {
// Checks detection of irreducible control flow / bail for *some* of them
//
// CHECK-LABEL: sil @testIrreducibleGraph2 : $@convention(thin) () -> () {
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
// CHECK-NEXT: br bb1
// CHECK: cond_br {{.*}}, bb2, bb3
// CHECK: bb2:
// CHECK: [[BEGIN2:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN2]] : $*X
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
// CHECK-NEXT: br bb3
// CHECK: bb3:
// CHECK: [[BEGIN3:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN3]] : $*X
// CHECK-NEXT: end_access [[BEGIN3]] : $*X
// CHECK: br bb4
// CHECK: [[BEGIN4:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN4]] : $*X
// CHECK-NEXT: end_access [[BEGIN4]] : $*X
// CHECK: cond_br {{.*}}, bb2, bb5
// CHECK: [[BEGIN5:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN5]] : $*X
// CHECK-NEXT: end_access [[BEGIN5]] : $*X
// CHECK-NOT: begin_access
// CHECK-LABEL: } // end sil function 'testIrreducibleGraph2'
sil @testIrreducibleGraph2 : $@convention(thin) () -> () {
bb0:
%0 = global_addr @globalX: $*X
br bb1
bb1:
%cond1 = integer_literal $Builtin.Int1, 1
cond_br %cond1, bb2, bb3
bb2:
%6 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%7 = load %6 : $*X
end_access %6 : $*X
br bb3
bb3:
%8 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%9 = load %8 : $*X
end_access %8 : $*X
br bb4
bb4:
%10 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%11 = load %10 : $*X
end_access %10 : $*X
%cond2 = integer_literal $Builtin.Int1, 1
cond_br %cond2, bb2, bb5
bb5:
%13 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%14 = load %13 : $*X
end_access %13 : $*X
%16 = tuple ()
return %16 : $()
}
// public func testDomUnpaired() {
// Checks 3 scopes, two of which are dominated and access the same storage
// However, The second access is unpaired - we cant optimized
//
// CHECK-LABEL: sil @testDomUnpaired : $@convention(thin) () -> () {
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NEXT: [[ALLOC:%.*]] = alloc_stack $Builtin.UnsafeValueBuffer
// CHECK-NEXT: begin_unpaired_access [read] [dynamic] [[GLOBAL]] : $*X, [[ALLOC]] : $*Builtin.UnsafeValueBuffer
// CHECK-NEXT: end_unpaired_access [dynamic] [[ALLOC]] : $*Builtin.UnsafeValueBuffer
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
// CHECK-NEXT: load [[BEGIN]] : $*X
// CHECK-NEXT: end_access [[BEGIN]] : $*X
// CHECK-NOT: begin_access
// CHECK-LABEL: } // end sil function 'testDomUnpaired'
sil @testDomUnpaired : $@convention(thin) () -> () {
bb0:
%0 = global_addr @globalX: $*X
%1 = begin_access [read] [dynamic] %0 : $*X
%2 = load %1 : $*X
end_access %1 : $*X
%buffer = alloc_stack $Builtin.UnsafeValueBuffer
begin_unpaired_access [read] [dynamic] %0 : $*X, %buffer : $*Builtin.UnsafeValueBuffer
end_unpaired_access [dynamic] %buffer : $*Builtin.UnsafeValueBuffer
%7 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
%8 = load %7 : $*X
end_access %7 : $*X
dealloc_stack %buffer : $*Builtin.UnsafeValueBuffer
%10 = tuple ()
return %10 : $()
}

View File

@@ -15,17 +15,17 @@ func sum(_ x: UInt64, _ y: UInt64) -> UInt64 {
// TESTSIL: [[B1:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B1:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL: end_access [[B1]] // TESTSIL: end_access [[B1]]
// TESTSIL: bb5 // TESTSIL: bb5
// TESTSIL: [[B2:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B2:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL-NEXT: load [[B2]] // TESTSIL-NEXT: load [[B2]]
// TESTSIL: store {{.*}} to [[B2]] // TESTSIL: store {{.*}} to [[B2]]
// TESTSIL: end_access [[B2]] // TESTSIL: end_access [[B2]]
// TESTSIL: bb6 // TESTSIL: bb6
// TESTSIL: [[B3:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B3:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL-NEXT: load [[B3]] // TESTSIL-NEXT: load [[B3]]
// TESTSIL: store {{.*}} to [[B3]] // TESTSIL: store {{.*}} to [[B3]]
// TESTSIL: end_access [[B3]] // TESTSIL: end_access [[B3]]
// TESTSIL: bb7 // TESTSIL: bb7
// TESTSIL: [[B4:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B4:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL-NEXT: load [[B4]] // TESTSIL-NEXT: load [[B4]]
// TESTSIL: store {{.*}} to [[B4]] // TESTSIL: store {{.*}} to [[B4]]
// TESTSIL: end_access [[B4]] // TESTSIL: end_access [[B4]]
@@ -54,12 +54,12 @@ public func MergeTest1(_ N: Int) {
// TESTSIL: [[B1:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B1:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL: end_access [[B1]] // TESTSIL: end_access [[B1]]
// TESTSIL: bb6 // TESTSIL: bb6
// TESTSIL: [[B2:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B2:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL-NEXT: load [[B2]] // TESTSIL-NEXT: load [[B2]]
// TESTSIL: store {{.*}} to [[B2]] // TESTSIL: store {{.*}} to [[B2]]
// TESTSIL: end_access [[B2]] // TESTSIL: end_access [[B2]]
// TESTSIL: bb7 // TESTSIL: bb7
// TESTSIL: [[B3:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B3:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL-NEXT: load [[B3]] // TESTSIL-NEXT: load [[B3]]
// TESTSIL: store {{.*}} to [[B3]] // TESTSIL: store {{.*}} to [[B3]]
// TESTSIL: end_access [[B3]] // TESTSIL: end_access [[B3]]
@@ -105,12 +105,12 @@ public func MergeTest3(_ N: Int) {
// TESTSIL: [[B1:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B1:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL: end_access [[B1]] // TESTSIL: end_access [[B1]]
// TESTSIL: bb7 // TESTSIL: bb7
// TESTSIL: [[B2:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B2:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL-NEXT: load [[B2]] // TESTSIL-NEXT: load [[B2]]
// TESTSIL: store {{.*}} to [[B2]] // TESTSIL: store {{.*}} to [[B2]]
// TESTSIL: end_access [[B2]] // TESTSIL: end_access [[B2]]
// TESTSIL: bb8 // TESTSIL: bb8
// TESTSIL: [[B3:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B3:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL-NEXT: load [[B3]] // TESTSIL-NEXT: load [[B3]]
// TESTSIL: store {{.*}} to [[B3]] // TESTSIL: store {{.*}} to [[B3]]
// TESTSIL: end_access [[B3]] // TESTSIL: end_access [[B3]]
@@ -136,17 +136,17 @@ public func MergeTest4(_ N: Int) {
// TESTSIL: [[B1:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B1:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL: end_access [[B1]] // TESTSIL: end_access [[B1]]
// TESTSIL: bb6 // TESTSIL: bb6
// TESTSIL: [[B2:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B2:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL-NEXT: load [[B2]] // TESTSIL-NEXT: load [[B2]]
// TESTSIL: store {{.*}} to [[B2]] // TESTSIL: store {{.*}} to [[B2]]
// TESTSIL: end_access [[B2]] // TESTSIL: end_access [[B2]]
// TESTSIL: bb7 // TESTSIL: bb7
// TESTSIL: [[B3:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B3:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL-NEXT: load [[B3]] // TESTSIL-NEXT: load [[B3]]
// TESTSIL: store {{.*}} to [[B3]] // TESTSIL: store {{.*}} to [[B3]]
// TESTSIL: end_access [[B3]] // TESTSIL: end_access [[B3]]
// TESTSIL: bb8 // TESTSIL: bb8
// TESTSIL: [[B4:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBALVAR]] // TESTSIL: [[B4:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBALVAR]]
// TESTSIL-NEXT: load [[B4]] // TESTSIL-NEXT: load [[B4]]
// TESTSIL: store {{.*}} to [[B4]] // TESTSIL: store {{.*}} to [[B4]]
// TESTSIL: end_access [[B4]] // TESTSIL: end_access [[B4]]