@@ -1528,6 +1528,190 @@ class BlockPartitionState {
15281528
15291529} // namespace
15301530
1531+ // ===----------------------------------------------------------------------===//
1532+ // MARK: Require Liveness
1533+ // ===----------------------------------------------------------------------===//
1534+
1535+ namespace {
1536+
1537+ class BlockLivenessInfo {
1538+ // Generation counter so we do not need to reallocate.
1539+ unsigned generation = 0 ;
1540+ SILInstruction *firstRequireInst = nullptr ;
1541+
1542+ void resetIfNew (unsigned newGeneration) {
1543+ if (generation == newGeneration)
1544+ return ;
1545+ generation = newGeneration;
1546+ firstRequireInst = nullptr ;
1547+ }
1548+
1549+ public:
1550+ SILInstruction *getInst (unsigned callerGeneration) {
1551+ resetIfNew (callerGeneration);
1552+ return firstRequireInst;
1553+ }
1554+
1555+ void setInst (unsigned callerGeneration, SILInstruction *newValue) {
1556+ resetIfNew (callerGeneration);
1557+ firstRequireInst = newValue;
1558+ }
1559+ };
1560+
1561+ // / We only want to emit errors for the first requires along a path from a
1562+ // / transfer instruction. We discover this by walking from user blocks to
1563+ struct RequireLiveness {
1564+ unsigned generation;
1565+ SILInstruction *transferInst;
1566+ BasicBlockData<BlockLivenessInfo> &blockLivenessInfo;
1567+ InstructionSet allRequires;
1568+ InstructionSetWithSize finalRequires;
1569+
1570+ // / If we have requires in the def block before our transfer, this is the
1571+ // / first require.
1572+ SILInstruction *firstRequireBeforeTransferInDefBlock = nullptr ;
1573+
1574+ RequireLiveness (unsigned generation, Operand *transferOp,
1575+ BasicBlockData<BlockLivenessInfo> &blockLivenessInfo)
1576+ : generation(generation), transferInst(transferOp->getUser ()),
1577+ blockLivenessInfo(blockLivenessInfo),
1578+ allRequires(transferOp->getParentFunction ()),
1579+ finalRequires(transferOp->getParentFunction ()) {}
1580+
1581+ template <typename Collection>
1582+ void process (Collection collection);
1583+
1584+ // / Attempt to process requireInst for our def block. Returns false if
1585+ // / requireInst was before our def and we need to do interprocedural
1586+ // / processing. Returns true if requireInst was after our transferInst and we
1587+ // / were able to appropriately determine if we should emit it or not.
1588+ void processDefBlock ();
1589+
1590+ // / Process all requires in block, updating blockLivenessInfo.
1591+ void processNonDefBlock (SILBasicBlock *block);
1592+ };
1593+
1594+ } // namespace
1595+
1596+ void RequireLiveness::processDefBlock () {
1597+ LLVM_DEBUG (llvm::dbgs () << " Processing def block!\n " );
1598+ // First walk from the beginning of the block to the transfer instruction to
1599+ // see if we have any requires before our def. Once we find one, we can skip
1600+ // the traversal and jump straight to the transfer.
1601+ for (auto ii = transferInst->getParent ()->begin (),
1602+ ie = transferInst->getIterator ();
1603+ ii != ie; ++ii) {
1604+ if (allRequires.contains (&*ii) && !firstRequireBeforeTransferInDefBlock) {
1605+ firstRequireBeforeTransferInDefBlock = &*ii;
1606+ LLVM_DEBUG (llvm::dbgs () << " Found transfer before def: "
1607+ << *firstRequireBeforeTransferInDefBlock);
1608+ break ;
1609+ }
1610+ }
1611+
1612+ // Then walk from our transferInst to the end of the block looking for the
1613+ // first require inst. Once we find it... return.
1614+ for (auto ii = std::next (transferInst->getIterator ()),
1615+ ie = transferInst->getParent ()->end ();
1616+ ii != ie; ++ii) {
1617+ if (!allRequires.contains (&*ii))
1618+ continue ;
1619+
1620+ finalRequires.insert (&*ii);
1621+ LLVM_DEBUG (llvm::dbgs () << " Found transfer after def: " << *ii);
1622+ return ;
1623+ }
1624+ }
1625+
1626+ void RequireLiveness::processNonDefBlock (SILBasicBlock *block) {
1627+ // Walk from the bottom to the top... assigning to our block state.
1628+ auto blockState = blockLivenessInfo.get (block);
1629+ for (auto &inst : llvm::make_range (block->rbegin (), block->rend ())) {
1630+ if (!finalRequires.contains (&inst))
1631+ continue ;
1632+ blockState.get ()->setInst (generation, &inst);
1633+ }
1634+ }
1635+
1636+ template <typename Collection>
1637+ void RequireLiveness::process (Collection requireInstList) {
1638+ LLVM_DEBUG (llvm::dbgs () << " ==> Performing Require Liveness for: "
1639+ << *transferInst);
1640+
1641+ // Then put all of our requires into our allRequires set.
1642+ BasicBlockWorklist initializingWorklist (transferInst->getFunction ());
1643+ for (auto *require : requireInstList) {
1644+ LLVM_DEBUG (llvm::dbgs () << " Require Inst: " << *require);
1645+ allRequires.insert (require);
1646+ initializingWorklist.pushIfNotVisited (require->getParent ());
1647+ }
1648+
1649+ // Then process our def block to see if we have any requires before and after
1650+ // the transferInst...
1651+ processDefBlock ();
1652+
1653+ // If we found /any/ requries after the transferInst, we can bail early since
1654+ // that is guaranteed to dominate all further requires.
1655+ if (!finalRequires.empty ()) {
1656+ LLVM_DEBUG (
1657+ llvm::dbgs ()
1658+ << " Found transfer after def in def block! Exiting early!\n " );
1659+ return ;
1660+ }
1661+
1662+ LLVM_DEBUG (llvm::dbgs () << " Did not find transfer after def in def "
1663+ " block! Walking blocks!\n " );
1664+
1665+ // If we found a transfer in the def block before our def, add it to the block
1666+ // state for the def.
1667+ if (firstRequireBeforeTransferInDefBlock) {
1668+ LLVM_DEBUG (
1669+ llvm::dbgs ()
1670+ << " Found a require before transfer! Adding to block state!\n " );
1671+ auto blockState = blockLivenessInfo.get (transferInst->getParent ());
1672+ blockState.get ()->setInst (generation, firstRequireBeforeTransferInDefBlock);
1673+ }
1674+
1675+ // Then for each require block that isn't a def block transfer, find the
1676+ // earliest transfer inst.
1677+ while (auto *requireBlock = initializingWorklist.pop ()) {
1678+ auto blockState = blockLivenessInfo.get (requireBlock);
1679+ for (auto &inst : *requireBlock) {
1680+ if (!allRequires.contains (&inst))
1681+ continue ;
1682+ LLVM_DEBUG (llvm::dbgs () << " Mapping Block bb"
1683+ << requireBlock->getDebugID () << " to: " << inst);
1684+ blockState.get ()->setInst (generation, &inst);
1685+ break ;
1686+ }
1687+ }
1688+
1689+ // Then walk from our def block looking for setInst blocks.
1690+ auto *transferBlock = transferInst->getParent ();
1691+ BasicBlockWorklist worklist (transferInst->getFunction ());
1692+ for (auto *succBlock : transferBlock->getSuccessorBlocks ())
1693+ worklist.pushIfNotVisited (succBlock);
1694+
1695+ while (auto *next = worklist.pop ()) {
1696+ // Check if we found an earliest requires... if so, add that to final
1697+ // requires and continue. We don't want to visit successors.
1698+ auto blockState = blockLivenessInfo.get (next);
1699+ if (auto *inst = blockState.get ()->getInst (generation)) {
1700+ finalRequires.insert (inst);
1701+ continue ;
1702+ }
1703+
1704+ // Do not look at successors of the transfer block.
1705+ if (next == transferBlock)
1706+ continue ;
1707+
1708+ // Otherwise, we did not find a requires and need to search further
1709+ // successors.
1710+ for (auto *succBlock : next->getSuccessorBlocks ())
1711+ worklist.pushIfNotVisited (succBlock);
1712+ }
1713+ }
1714+
15311715// ===----------------------------------------------------------------------===//
15321716// MARK: Inferring Transferred Instruction from violating Require
15331717// ===----------------------------------------------------------------------===//
@@ -2035,15 +2219,10 @@ class PartitionAnalysis {
20352219
20362220 BasicBlockData<BlockPartitionState> blockStates;
20372221
2038- RaceTracer raceTracer;
2039-
20402222 SILFunction *function;
20412223
20422224 bool solved;
20432225
2044- // TODO: make this configurable in a better way
2045- const static int NUM_REQUIREMENTS_TO_DIAGNOSE = 50 ;
2046-
20472226 // / The constructor initializes each block in the function by compiling it to
20482227 // / PartitionOps, then seeds the solve method by setting `needsUpdate` to true
20492228 // / for the entry block
@@ -2053,7 +2232,7 @@ class PartitionAnalysis {
20532232 [this ](SILBasicBlock *block) {
20542233 return BlockPartitionState (block, translator);
20552234 }),
2056- raceTracer (fn, blockStates), function(fn), solved(false ) {
2235+ function (fn), solved(false ) {
20572236 // Initialize the entry block as needing an update, and having a partition
20582237 // that places all its non-sendable args in a single region
20592238 blockStates[fn->getEntryBlock ()].needsUpdate = true ;
@@ -2151,17 +2330,6 @@ class PartitionAnalysis {
21512330 translator.sortUniqueNeverTransferredValues ();
21522331 }
21532332
2154- // / Track the transfer insts that have already had diagnostics emitted about.
2155- llvm::DenseSet<SILInstruction *> emittedTransferInsts;
2156-
2157- // / Returns true if a diagnostic has already been emitted for the transferred
2158- // / instruction \p transferredInst.
2159- // /
2160- // / If we return false, we insert \p transferredInst into emittedTransferInst.
2161- bool hasBeenEmitted (SILInstruction *transferredInst) {
2162- return !emittedTransferInsts.insert (transferredInst).second ;
2163- }
2164-
21652333 // / Once we have reached a fixpoint, this routine runs over all blocks again
21662334 // / reporting any failures by applying our ops to the converged dataflow
21672335 // / state.
@@ -2170,7 +2338,9 @@ class PartitionAnalysis {
21702338
21712339 LLVM_DEBUG (llvm::dbgs () << " Emitting diagnostics for function "
21722340 << function->getName () << " \n " );
2173- RaceTracer tracer (function, blockStates);
2341+
2342+ SmallFrozenMultiMap<Operand *, SILInstruction *, 8 >
2343+ transferOpToRequireInstMultiMap;
21742344
21752345 // Then for each block...
21762346 for (auto [block, blockState] : blockStates) {
@@ -2182,27 +2352,25 @@ class PartitionAnalysis {
21822352 PartitionOpEvaluator eval (workingPartition);
21832353 eval.failureCallback = /* handleFailure=*/
21842354 [&](const PartitionOp &partitionOp, TrackableValueID transferredVal,
2185- Operand *transferringInst) {
2186- // Ensure that multiple transfers at the same AST node are only
2187- // entered once into the race tracer
2188- if (hasBeenEmitted (partitionOp.getSourceInst ()))
2189- return ;
2190-
2191- LLVM_DEBUG (llvm::dbgs ()
2192- << " Emitting Use After Transfer Error!\n "
2193- << " ID: %%" << transferredVal << " \n "
2194- << " Rep: "
2195- << *translator.getValueForId (transferredVal)
2196- ->getRepresentative ());
2197-
2198- raceTracer.traceUseOfTransferredValue (partitionOp, transferredVal);
2355+ Operand *transferringOp) {
2356+ auto rep =
2357+ translator.getValueForId (transferredVal)->getRepresentative ();
2358+ LLVM_DEBUG (
2359+ llvm::dbgs ()
2360+ << " Emitting Use After Transfer Error!\n "
2361+ << " ID: %%" << transferredVal << " \n "
2362+ << " Rep: " << *rep
2363+ << " Require Inst: " << *partitionOp.getSourceInst ()
2364+ << " Transferring Inst: " << *transferringOp->getUser ());
2365+ transferOpToRequireInstMultiMap.insert (transferringOp,
2366+ partitionOp.getSourceInst ());
21992367 };
22002368 eval.transferredNonTransferrableCallback =
22012369 [&](const PartitionOp &partitionOp, TrackableValueID transferredVal) {
22022370 LLVM_DEBUG (llvm::dbgs ()
2203- << " Emitting TransferNonTransferrable Error!\n "
2204- << " ID: %%" << transferredVal << " \n "
2205- << " Rep: "
2371+ << " Emitting TransferNonTransferrable Error!\n "
2372+ << " ID: %%" << transferredVal << " \n "
2373+ << " Rep: "
22062374 << *translator.getValueForId (transferredVal)
22072375 ->getRepresentative ());
22082376 function->getASTContext ().Diags .diagnose (
@@ -2223,43 +2391,60 @@ class PartitionAnalysis {
22232391 }
22242392 }
22252393
2226- // Once we have run over all backs, dump the accumulator and ask the
2227- // raceTracer to report diagnostics at the transfer sites for all the racy
2228- // requirement sites entered into it above.
2229- LLVM_DEBUG (llvm::dbgs () << " Accumulator Complete:\n " ;
2230- raceTracer.getAccumulator ().print (llvm::dbgs ()););
2231- raceTracer.getAccumulator ().emitErrorsForTransferRequire (
2232- NUM_REQUIREMENTS_TO_DIAGNOSE);
2233- }
2394+ // Now that we have found all of our transferInsts/Requires emit errors.
2395+ transferOpToRequireInstMultiMap.setFrozen ();
2396+
2397+ BasicBlockData<BlockLivenessInfo> blockLivenessInfo (function);
2398+ // We use a generation counter so we can lazily reset blockLivenessInfo
2399+ // since we cannot clear it without iterating over it.
2400+ unsigned blockLivenessInfoGeneration = 0 ;
2401+
2402+ for (auto [transferOp, requireInsts] :
2403+ transferOpToRequireInstMultiMap.getRange ()) {
2404+
2405+ LLVM_DEBUG (llvm::dbgs () << " Transfer Inst: " << *transferOp->getUser ());
2406+
2407+ RequireLiveness liveness (blockLivenessInfoGeneration, transferOp,
2408+ blockLivenessInfo);
2409+ ++blockLivenessInfoGeneration;
2410+ liveness.process (requireInsts);
2411+
2412+ InferredCallerArgumentTypeInfo argTypeInfo (transferOp);
2413+ auto loc = transferOp->getUser ()->getLoc ();
2414+
2415+ function->getASTContext ()
2416+ .Diags
2417+ .diagnose (loc.getSourceLoc (), diag::call_site_transfer_yields_race,
2418+ argTypeInfo.inferredType ,
2419+ argTypeInfo.isolationCrossing .getCallerIsolation (),
2420+ argTypeInfo.isolationCrossing .getCalleeIsolation ())
2421+ .highlight (transferOp->get ().getLoc ().getSourceRange ());
2422+
2423+ // Ok, we now have our requires... emit the errors.
2424+ bool didEmitRequire = false ;
2425+
2426+ InstructionSet requireInstsUnique (function);
2427+ for (auto *require : requireInsts) {
2428+ // We can have multiple of the same require insts if we had a require
2429+ // and an assign from the same instruction. Our liveness checking above
2430+ // doesn't care about that, but we still need to make sure we do not
2431+ // emit twice.
2432+ if (!requireInstsUnique.insert (require))
2433+ continue ;
22342434
2235- bool tryDiagnoseAsCallSite (const PartitionOp &transferOp,
2236- unsigned numDisplayed, unsigned numHidden) {
2237- SILInstruction *sourceInst = transferOp.getSourceInst ();
2238- ApplyExpr *apply = sourceInst->getLoc ().getAsASTNode <ApplyExpr>();
2435+ // If this was not a last require, do not emit an error.
2436+ if (!liveness.finalRequires .contains (require))
2437+ continue ;
22392438
2240- // If the transfer does not correspond to an apply expression... return
2241- // early.
2242- if (!apply)
2243- return false ;
2439+ auto loc = require->getLoc ();
2440+ function->getASTContext ()
2441+ .Diags .diagnose (loc.getSourceLoc (), diag::possible_racy_access_site)
2442+ .highlight (loc.getSourceRange ());
2443+ didEmitRequire = true ;
2444+ }
22442445
2245- auto isolationCrossing = apply->getIsolationCrossing ();
2246- if (!isolationCrossing) {
2247- assert (false && " ApplyExprs should be transferring only if"
2248- " they are isolation crossing" );
2249- return false ;
2446+ assert (didEmitRequire && " Should have a require for all errors?!" );
22502447 }
2251- auto argExpr = transferOp.getSourceLoc ();
2252- if (!argExpr)
2253- assert (false && " sourceExpr should be populated for ApplyExpr transfers" );
2254-
2255- function->getASTContext ()
2256- .Diags
2257- .diagnose (argExpr.getSourceLoc (), diag::call_site_transfer_yields_race,
2258- transferOp.getSourceOp ()->get ()->getType ().getASTType (),
2259- isolationCrossing.value ().getCallerIsolation (),
2260- isolationCrossing.value ().getCalleeIsolation ())
2261- .highlight (argExpr.getSourceRange ());
2262- return true ;
22632448 }
22642449
22652450public:
0 commit comments