mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[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:
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
237
lib/SILOptimizer/Transforms/AccessEnforcementDom.cpp
Normal file
237
lib/SILOptimizer/Transforms/AccessEnforcementDom.cpp
Normal 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();
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
335
test/SILOptimizer/access_dom.sil
Normal file
335
test/SILOptimizer/access_dom.sil
Normal 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 can’t 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 : $()
|
||||||
|
}
|
||||||
@@ -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]]
|
||||||
|
|||||||
Reference in New Issue
Block a user