|
14 | 14 |
|
15 | 15 | #include "swift/AST/ASTWalker.h"
|
16 | 16 | #include "swift/AST/Expr.h"
|
| 17 | +#include "swift/SIL/AddressWalker.h" |
17 | 18 | #include "swift/SIL/ApplySite.h"
|
18 | 19 | #include "swift/SIL/InstructionUtils.h"
|
19 | 20 | #include "swift/SIL/PatternMatch.h"
|
20 | 21 | #include "swift/SIL/SILGlobalVariable.h"
|
| 22 | +#include "swift/SIL/Test.h" |
21 | 23 | #include "swift/SILOptimizer/Utils/VariableNameUtils.h"
|
22 | 24 |
|
23 | 25 | using namespace swift;
|
@@ -141,6 +143,204 @@ bool DeclRefExprAnalysis::compute(Expr *expr) {
|
141 | 143 | return result;
|
142 | 144 | }
|
143 | 145 |
|
| 146 | +static SILIsolationInfo |
| 147 | +inferIsolationInfoForTempAllocStack(AllocStackInst *asi) { |
| 148 | + // We want to search for an alloc_stack that is not from a VarDecl and that is |
| 149 | + // initially isolated along all paths to the same actor isolation. If they |
| 150 | + // differ, then we emit a we do not understand error. |
| 151 | + struct AddressWalkerState { |
| 152 | + AllocStackInst *asi = nullptr; |
| 153 | + SmallVector<Operand *, 8> indirectResultUses; |
| 154 | + llvm::SmallSetVector<SILInstruction *, 8> writes; |
| 155 | + Operand *sameBlockIndirectResultUses = nullptr; |
| 156 | + }; |
| 157 | + |
| 158 | + struct AddressWalker final : TransitiveAddressWalker<AddressWalker> { |
| 159 | + AddressWalkerState &state; |
| 160 | + |
| 161 | + AddressWalker(AddressWalkerState &state) : state(state) { |
| 162 | + assert(state.asi); |
| 163 | + } |
| 164 | + |
| 165 | + bool visitUse(Operand *use) { |
| 166 | + // If we do not write to memory, then it is harmless. |
| 167 | + if (!use->getUser()->mayWriteToMemory()) |
| 168 | + return true; |
| 169 | + |
| 170 | + if (auto fas = FullApplySite::isa(use->getUser())) { |
| 171 | + if (fas.isIndirectResultOperand(*use)) { |
| 172 | + // If our indirect result use is in the same block... |
| 173 | + auto *parentBlock = state.asi->getParent(); |
| 174 | + if (fas.getParent() == parentBlock) { |
| 175 | + // If we haven't seen any indirect result use yet... just cache it |
| 176 | + // and return true. |
| 177 | + if (!state.sameBlockIndirectResultUses) { |
| 178 | + state.sameBlockIndirectResultUses = use; |
| 179 | + return true; |
| 180 | + } |
| 181 | + |
| 182 | + // If by walking from the alloc stack to the full apply site, we do |
| 183 | + // not see the current sameBlockIndirectResultUses, we have a new |
| 184 | + // newest use. |
| 185 | + if (llvm::none_of( |
| 186 | + llvm::make_range(state.asi->getIterator(), |
| 187 | + fas->getIterator()), |
| 188 | + [&](const SILInstruction &inst) { |
| 189 | + return &inst == |
| 190 | + state.sameBlockIndirectResultUses->getUser(); |
| 191 | + })) { |
| 192 | + state.sameBlockIndirectResultUses = use; |
| 193 | + } |
| 194 | + return true; |
| 195 | + } |
| 196 | + |
| 197 | + // If not, just stash it into the non-same block indirect result use |
| 198 | + // array. |
| 199 | + state.indirectResultUses.push_back(use); |
| 200 | + return true; |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + state.writes.insert(use->getUser()); |
| 205 | + return true; |
| 206 | + } |
| 207 | + }; |
| 208 | + |
| 209 | + AddressWalkerState state; |
| 210 | + state.asi = asi; |
| 211 | + AddressWalker walker(state); |
| 212 | + |
| 213 | + // If we fail to walk, emit an unknown patten error. |
| 214 | + if (AddressUseKind::Unknown == std::move(walker).walk(asi)) { |
| 215 | + return SILIsolationInfo(); |
| 216 | + } |
| 217 | + |
| 218 | + // If we do not have any indirect result uses... we can just assign fresh. |
| 219 | + if (!state.sameBlockIndirectResultUses && state.indirectResultUses.empty()) |
| 220 | + return SILIsolationInfo::getDisconnected(false /*isUnsafeNonIsolated*/); |
| 221 | + |
| 222 | + // Otherwise, lets see if we had a same block indirect result. |
| 223 | + if (state.sameBlockIndirectResultUses) { |
| 224 | + // If we do not have any writes in between the alloc stack and the |
| 225 | + // initializer, then we have a good target. Otherwise, we just return |
| 226 | + // AssignFresh. |
| 227 | + if (llvm::none_of( |
| 228 | + llvm::make_range( |
| 229 | + asi->getIterator(), |
| 230 | + state.sameBlockIndirectResultUses->getUser()->getIterator()), |
| 231 | + [&](SILInstruction &inst) { return state.writes.count(&inst); })) { |
| 232 | + auto isolationInfo = |
| 233 | + SILIsolationInfo::get(state.sameBlockIndirectResultUses->getUser()); |
| 234 | + if (isolationInfo) { |
| 235 | + return isolationInfo; |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + // If we did not find an isolation info, just do a normal assign fresh. |
| 240 | + return SILIsolationInfo::getDisconnected(false /*is unsafe non isolated*/); |
| 241 | + } |
| 242 | + |
| 243 | + // Check if any of our writes are within the first block. This would |
| 244 | + // automatically stop our search and we should assign fresh. Since we are |
| 245 | + // going over the writes here, also setup a writeBlocks set. |
| 246 | + auto *defBlock = asi->getParent(); |
| 247 | + BasicBlockSet writeBlocks(defBlock->getParent()); |
| 248 | + for (auto *write : state.writes) { |
| 249 | + if (write->getParent() == defBlock) |
| 250 | + return SILIsolationInfo::getDisconnected(false /*unsafe non isolated*/); |
| 251 | + writeBlocks.insert(write->getParent()); |
| 252 | + } |
| 253 | + |
| 254 | + // Ok, at this point we know that we do not have any indirect result uses in |
| 255 | + // the def block and also we do not have any writes in that initial |
| 256 | + // block. This sets us up for our global analysis. Our plan is as follows: |
| 257 | + // |
| 258 | + // 1. We are going to create a set of writeBlocks and a map from SILBasicBlock |
| 259 | + // -> first indirect result block if there isn't a write before it. |
| 260 | + // |
| 261 | + // 2. We walk from our def block until we reach the first indirect result |
| 262 | + // block. We stop processing successor if we find a write block successor that |
| 263 | + // is not also an indirect result block. This makes sense since we earlier |
| 264 | + // required that any notates indirect result block do not have any writes in |
| 265 | + // between the indirect result and the beginning of the block. |
| 266 | + llvm::SmallDenseMap<SILBasicBlock *, Operand *, 2> blockToOperandMap; |
| 267 | + for (auto *use : state.indirectResultUses) { |
| 268 | + // If our indirect result use has a write before it in the block, do not |
| 269 | + // store it. It cannot be our indirect result initializer. |
| 270 | + if (writeBlocks.contains(use->getParentBlock()) && |
| 271 | + llvm::any_of( |
| 272 | + use->getParentBlock()->getRangeEndingAtInst(use->getUser()), |
| 273 | + [&](SILInstruction &inst) { return state.writes.contains(&inst); })) |
| 274 | + continue; |
| 275 | + |
| 276 | + // Ok, we now know that there aren't any writes before us in the block. Now |
| 277 | + // try to insert. |
| 278 | + auto iter = blockToOperandMap.try_emplace(use->getParentBlock(), use); |
| 279 | + |
| 280 | + // If we actually inserted, then we are done. |
| 281 | + if (iter.second) { |
| 282 | + continue; |
| 283 | + } |
| 284 | + |
| 285 | + // Otherwise, if we are before the current value, set us to be the value |
| 286 | + // instead. |
| 287 | + if (llvm::none_of( |
| 288 | + use->getParentBlock()->getRangeEndingAtInst(use->getUser()), |
| 289 | + [&](const SILInstruction &inst) { |
| 290 | + return &inst == iter.first->second->getUser(); |
| 291 | + })) { |
| 292 | + iter.first->getSecond() = use; |
| 293 | + } |
| 294 | + } |
| 295 | + |
| 296 | + // Ok, we now have our data all setup. |
| 297 | + BasicBlockWorklist worklist(asi->getFunction()); |
| 298 | + for (auto *succBlock : asi->getParentBlock()->getSuccessorBlocks()) { |
| 299 | + worklist.pushIfNotVisited(succBlock); |
| 300 | + } |
| 301 | + |
| 302 | + Operand *targetOperand = nullptr; |
| 303 | + while (auto *next = worklist.pop()) { |
| 304 | + // First check if this is one of our target blocks. |
| 305 | + auto iter = blockToOperandMap.find(next); |
| 306 | + |
| 307 | + // If this is our target blocks... |
| 308 | + if (iter != blockToOperandMap.end()) { |
| 309 | + // If we already have an assigned target block, make sure this is the same |
| 310 | + // one. If it is, just continue. Otherwise, something happened we do not |
| 311 | + // understand... assign fresh. |
| 312 | + if (!targetOperand) { |
| 313 | + targetOperand = iter->second; |
| 314 | + continue; |
| 315 | + } |
| 316 | + |
| 317 | + if (targetOperand->getParentBlock() == iter->first) { |
| 318 | + continue; |
| 319 | + } |
| 320 | + |
| 321 | + return SILIsolationInfo::getDisconnected( |
| 322 | + false /*is unsafe non isolated*/); |
| 323 | + } |
| 324 | + |
| 325 | + // Otherwise, see if this block is a write block. If so, we have a path to a |
| 326 | + // write block that does not go through one of our blockToOperandMap |
| 327 | + // blocks... return assign fresh. |
| 328 | + if (writeBlocks.contains(next)) |
| 329 | + return SILIsolationInfo::getDisconnected( |
| 330 | + false /*is unsafe non isolated*/); |
| 331 | + |
| 332 | + // Otherwise, visit this blocks successors if we have not yet visited them. |
| 333 | + for (auto *succBlock : next->getSuccessorBlocks()) { |
| 334 | + worklist.pushIfNotVisited(succBlock); |
| 335 | + } |
| 336 | + } |
| 337 | + |
| 338 | + // At this point, we know that we have a single indirect result use that |
| 339 | + // dominates all writes and other indirect result uses. We can say that our |
| 340 | + // alloc_stack temporary is that indirect result use's isolation. |
| 341 | + return SILIsolationInfo::get(targetOperand->getUser()); |
| 342 | +} |
| 343 | + |
144 | 344 | SILIsolationInfo SILIsolationInfo::get(SILInstruction *inst) {
|
145 | 345 | if (auto fas = FullApplySite::isa(inst)) {
|
146 | 346 | if (auto crossing = fas.getIsolationCrossing()) {
|
@@ -475,6 +675,12 @@ SILIsolationInfo SILIsolationInfo::get(SILInstruction *inst) {
|
475 | 675 | true /*is nonisolated(unsafe)*/);
|
476 | 676 | }
|
477 | 677 | }
|
| 678 | + } else { |
| 679 | + // Ok, we have a temporary. If it is non-Sendable... |
| 680 | + if (SILIsolationInfo::isNonSendableType(asi)) { |
| 681 | + if (auto isolation = inferIsolationInfoForTempAllocStack(asi)) |
| 682 | + return isolation; |
| 683 | + } |
478 | 684 | }
|
479 | 685 | }
|
480 | 686 |
|
@@ -842,3 +1048,28 @@ SILDynamicMergedIsolationInfo::merge(SILIsolationInfo other) const {
|
842 | 1048 | // Otherwise, just return other.
|
843 | 1049 | return other;
|
844 | 1050 | }
|
| 1051 | + |
| 1052 | +//===----------------------------------------------------------------------===// |
| 1053 | +// MARK: Tests |
| 1054 | +//===----------------------------------------------------------------------===// |
| 1055 | + |
| 1056 | +namespace swift::test { |
| 1057 | + |
| 1058 | +// Arguments: |
| 1059 | +// - SILValue: value to emit a name for. |
| 1060 | +// Dumps: |
| 1061 | +// - The inferred isolation. |
| 1062 | +static FunctionTest |
| 1063 | + IsolationInfoInferrence("sil-isolation-info-inference", |
| 1064 | + [](auto &function, auto &arguments, auto &test) { |
| 1065 | + auto value = arguments.takeValue(); |
| 1066 | + |
| 1067 | + SILIsolationInfo info = |
| 1068 | + SILIsolationInfo::get(value); |
| 1069 | + llvm::outs() << "Input Value: " << *value; |
| 1070 | + llvm::outs() << "Isolation: "; |
| 1071 | + info.printForOneLineLogging(llvm::outs()); |
| 1072 | + llvm::outs() << "\n"; |
| 1073 | + }); |
| 1074 | + |
| 1075 | +} // namespace swift::test |
0 commit comments