@@ -1528,6 +1528,190 @@ class BlockPartitionState {
1528
1528
1529
1529
} // namespace
1530
1530
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
+
1531
1715
// ===----------------------------------------------------------------------===//
1532
1716
// MARK: Inferring Transferred Instruction from violating Require
1533
1717
// ===----------------------------------------------------------------------===//
@@ -2035,15 +2219,10 @@ class PartitionAnalysis {
2035
2219
2036
2220
BasicBlockData<BlockPartitionState> blockStates;
2037
2221
2038
- RaceTracer raceTracer;
2039
-
2040
2222
SILFunction *function;
2041
2223
2042
2224
bool solved;
2043
2225
2044
- // TODO: make this configurable in a better way
2045
- const static int NUM_REQUIREMENTS_TO_DIAGNOSE = 50 ;
2046
-
2047
2226
// / The constructor initializes each block in the function by compiling it to
2048
2227
// / PartitionOps, then seeds the solve method by setting `needsUpdate` to true
2049
2228
// / for the entry block
@@ -2053,7 +2232,7 @@ class PartitionAnalysis {
2053
2232
[this ](SILBasicBlock *block) {
2054
2233
return BlockPartitionState (block, translator);
2055
2234
}),
2056
- raceTracer (fn, blockStates), function(fn), solved(false ) {
2235
+ function (fn), solved(false ) {
2057
2236
// Initialize the entry block as needing an update, and having a partition
2058
2237
// that places all its non-sendable args in a single region
2059
2238
blockStates[fn->getEntryBlock ()].needsUpdate = true ;
@@ -2151,17 +2330,6 @@ class PartitionAnalysis {
2151
2330
translator.sortUniqueNeverTransferredValues ();
2152
2331
}
2153
2332
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
-
2165
2333
// / Once we have reached a fixpoint, this routine runs over all blocks again
2166
2334
// / reporting any failures by applying our ops to the converged dataflow
2167
2335
// / state.
@@ -2170,7 +2338,9 @@ class PartitionAnalysis {
2170
2338
2171
2339
LLVM_DEBUG (llvm::dbgs () << " Emitting diagnostics for function "
2172
2340
<< function->getName () << " \n " );
2173
- RaceTracer tracer (function, blockStates);
2341
+
2342
+ SmallFrozenMultiMap<Operand *, SILInstruction *, 8 >
2343
+ transferOpToRequireInstMultiMap;
2174
2344
2175
2345
// Then for each block...
2176
2346
for (auto [block, blockState] : blockStates) {
@@ -2182,27 +2352,25 @@ class PartitionAnalysis {
2182
2352
PartitionOpEvaluator eval (workingPartition);
2183
2353
eval.failureCallback = /* handleFailure=*/
2184
2354
[&](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 ());
2199
2367
};
2200
2368
eval.transferredNonTransferrableCallback =
2201
2369
[&](const PartitionOp &partitionOp, TrackableValueID transferredVal) {
2202
2370
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: "
2206
2374
<< *translator.getValueForId (transferredVal)
2207
2375
->getRepresentative ());
2208
2376
function->getASTContext ().Diags .diagnose (
@@ -2223,43 +2391,60 @@ class PartitionAnalysis {
2223
2391
}
2224
2392
}
2225
2393
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 ;
2234
2434
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 ;
2239
2438
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
+ }
2244
2445
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?!" );
2250
2447
}
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 ;
2263
2448
}
2264
2449
2265
2450
public:
0 commit comments