Skip to content

Commit 2e32a7c

Browse files
authored
Merge pull request #36632 from gottesmm/opt_remark_runtime_cast_remark
[opt-remark] Add support for emitting remarks about runtime casts.
2 parents cb6b1ea + 753f7ab commit 2e32a7c

14 files changed

+858
-92
lines changed

include/swift/SIL/Consumption.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
#ifndef SWIFT_SIL_CONSUMPTION_H
1919
#define SWIFT_SIL_CONSUMPTION_H
2020

21+
#include "llvm/Support/ErrorHandling.h"
22+
#include <cstdint>
23+
2124
namespace swift {
2225

2326
/// Is an operation a "take"? A take consumes the original value,

include/swift/SIL/DebugUtils.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,96 @@ inline SILBasicBlock::iterator eraseFromParentWithDebugInsts(
218218
bool hasNonTrivialNonDebugTransitiveUsers(
219219
PointerUnion<SILInstruction *, SILArgument *> V);
220220

221+
/// A light weight abstraction on top of an instruction that carries within it
222+
/// information about a debug variable. This allows one to write high level code
223+
/// over the set of such instructions with greater correctness by using
224+
/// exhaustive switches, methods, and keeping it light weight by using *, ->
225+
/// operators to access functionality from the underlying instruction when
226+
/// needed.
227+
struct DebugVarCarryingInst {
228+
enum class Kind {
229+
Invalid = 0,
230+
DebugValue,
231+
DebugValueAddr,
232+
AllocStack,
233+
AllocBox,
234+
};
235+
236+
Kind kind;
237+
SILInstruction *inst;
238+
239+
DebugVarCarryingInst() : kind(Kind::Invalid), inst(nullptr) {}
240+
DebugVarCarryingInst(DebugValueInst *dvi)
241+
: kind(Kind::DebugValue), inst(dvi) {}
242+
DebugVarCarryingInst(DebugValueAddrInst *dvai)
243+
: kind(Kind::DebugValueAddr), inst(dvai) {}
244+
DebugVarCarryingInst(AllocStackInst *asi)
245+
: kind(Kind::AllocStack), inst(asi) {}
246+
DebugVarCarryingInst(AllocBoxInst *abi) : kind(Kind::AllocBox), inst(abi) {}
247+
DebugVarCarryingInst(SILInstruction *newInst)
248+
: kind(Kind::Invalid), inst(nullptr) {
249+
switch (newInst->getKind()) {
250+
default:
251+
return;
252+
case SILInstructionKind::DebugValueInst:
253+
kind = Kind::DebugValue;
254+
break;
255+
case SILInstructionKind::DebugValueAddrInst:
256+
kind = Kind::DebugValueAddr;
257+
break;
258+
case SILInstructionKind::AllocStackInst:
259+
kind = Kind::AllocStack;
260+
break;
261+
case SILInstructionKind::AllocBoxInst:
262+
kind = Kind::AllocBox;
263+
break;
264+
}
265+
inst = newInst;
266+
}
267+
268+
/// Enable the composition struct to be used as an instruction easily. We use
269+
/// a '*' so that in the source it is easily visible to the eye that something
270+
/// is happening here.
271+
SILInstruction *operator*() const { return inst; }
272+
273+
/// Enable one to access the methods of the wrapped instruction using
274+
/// '->'. This keeps the wrapper light weight.
275+
SILInstruction *operator->() const { return inst; }
276+
277+
/// Add support for this struct in `if` statement.
278+
explicit operator bool() const { return bool(kind); }
279+
280+
VarDecl *getDecl() const {
281+
switch (kind) {
282+
case Kind::Invalid:
283+
llvm_unreachable("Invalid?!");
284+
case Kind::DebugValue:
285+
return cast<DebugValueInst>(inst)->getDecl();
286+
case Kind::DebugValueAddr:
287+
return cast<DebugValueAddrInst>(inst)->getDecl();
288+
case Kind::AllocStack:
289+
return cast<AllocStackInst>(inst)->getDecl();
290+
case Kind::AllocBox:
291+
return cast<AllocBoxInst>(inst)->getDecl();
292+
}
293+
}
294+
295+
Optional<SILDebugVariable> getVarInfo() const {
296+
switch (kind) {
297+
case Kind::Invalid:
298+
llvm_unreachable("Invalid?!");
299+
case Kind::DebugValue:
300+
return cast<DebugValueInst>(inst)->getVarInfo();
301+
case Kind::DebugValueAddr:
302+
return cast<DebugValueAddrInst>(inst)->getVarInfo();
303+
case Kind::AllocStack:
304+
return cast<AllocStackInst>(inst)->getVarInfo();
305+
case Kind::AllocBox:
306+
return cast<AllocBoxInst>(inst)->getVarInfo();
307+
}
308+
}
309+
};
310+
221311
} // end namespace swift
222312

223313
#endif // SWIFT_SIL_DEBUGUTILS_H

include/swift/SIL/DynamicCasts.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class SILBuilder;
3232
class SILLocation;
3333
class SILModule;
3434
class SILType;
35-
enum class CastConsumptionKind : unsigned char;
35+
enum class CastConsumptionKind : uint8_t;
3636
struct SILDynamicCastInst;
3737

3838
enum class DynamicCastFeasibility {

include/swift/SIL/MemAccessUtils.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,16 +1164,25 @@ namespace swift {
11641164
/// uninitialized.
11651165
bool memInstMustInitialize(Operand *memOper);
11661166

1167-
/// Is this an alloc_stack instruction that is:
1167+
/// Is this an alloc_stack instruction that we can prove is:
11681168
///
11691169
/// 1. Only initialized once in its own def block.
11701170
/// 2. Never written to again except by destroy_addr.
11711171
///
1172-
/// On return, destroyingUsers contains the list of users that destroy the
1173-
/// alloc_stack. If the alloc_stack is destroyed in pieces, we do not guarantee
1174-
/// that the list of destroying users is a minimal jointly post-dominating set.
1175-
bool isSingleInitAllocStack(AllocStackInst *asi,
1176-
SmallVectorImpl<Operand *> &destroyingUses);
1172+
/// Then we return the single initializing use and if \p destroyingUsers was
1173+
/// non-null, On return, if non-null, \p destroyingUsers contains the list of
1174+
/// users that destroy the alloc_stack. If the alloc_stack is destroyed in
1175+
/// pieces, we do not guarantee that the list of destroying users is a minimal
1176+
/// jointly post-dominating set.
1177+
Operand *getSingleInitAllocStackUse(
1178+
AllocStackInst *asi, SmallVectorImpl<Operand *> *destroyingUses = nullptr);
1179+
1180+
/// Same as getSingleInitAllocStack except we throw away the single use and just
1181+
/// provide a bool.
1182+
inline bool isSingleInitAllocStack(AllocStackInst *asi,
1183+
SmallVectorImpl<Operand *> &destroyingUses) {
1184+
return getSingleInitAllocStackUse(asi, &destroyingUses);
1185+
}
11771186

11781187
/// Return true if the given address value is produced by a special address
11791188
/// producer that is only used for local initialization, not formal access.

lib/SIL/Utils/MemAccessUtils.cpp

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include "swift/SIL/SILModule.h"
1717
#include "swift/SIL/SILUndef.h"
1818
#include "swift/SIL/DynamicCasts.h"
19+
#include "swift/SIL/Consumption.h"
20+
#include "swift/SIL/SILInstruction.h"
1921
#include "llvm/Support/Debug.h"
2022

2123
using namespace swift;
@@ -1545,12 +1547,13 @@ bool swift::memInstMustInitialize(Operand *memOper) {
15451547
}
15461548
}
15471549

1548-
bool swift::isSingleInitAllocStack(AllocStackInst *asi,
1549-
SmallVectorImpl<Operand *> &destroyingUses) {
1550+
Operand *
1551+
swift::getSingleInitAllocStackUse(AllocStackInst *asi,
1552+
SmallVectorImpl<Operand *> *destroyingUses) {
15501553
// For now, we just look through projections and rely on memInstMustInitialize
15511554
// to classify all other uses as init or not.
15521555
SmallVector<Operand *, 32> worklist(asi->getUses());
1553-
bool foundInit = false;
1556+
Operand *singleInit = nullptr;
15541557

15551558
while (!worklist.empty()) {
15561559
auto *use = worklist.pop_back_val();
@@ -1571,14 +1574,42 @@ bool swift::isSingleInitAllocStack(AllocStackInst *asi,
15711574
continue;
15721575
}
15731576
// Treat load [take] as a write.
1574-
return false;
1577+
return nullptr;
15751578
}
15761579

15771580
switch (user->getKind()) {
15781581
default:
15791582
break;
1583+
case SILInstructionKind::UnconditionalCheckedCastAddrInst: {
1584+
auto *uccai = cast<UnconditionalCheckedCastAddrInst>(user);
1585+
// Only handle the case where we are doing a take of our alloc_stack as a
1586+
// source value. If we are the dest, then something else is happening!
1587+
// Break!
1588+
if (use->get() == uccai->getDest())
1589+
break;
1590+
// Ok, we are the Src and are performing a take. Treat it as a destroy!
1591+
if (destroyingUses)
1592+
destroyingUses->push_back(use);
1593+
continue;
1594+
}
1595+
case SILInstructionKind::CheckedCastAddrBranchInst: {
1596+
auto *ccabi = cast<CheckedCastAddrBranchInst>(user);
1597+
// We only handle the case where we are doing a take of our alloc_stack as
1598+
// a source.
1599+
//
1600+
// TODO: Can we expand this?
1601+
if (use->get() == ccabi->getDest())
1602+
break;
1603+
if (ccabi->getConsumptionKind() != CastConsumptionKind::TakeAlways)
1604+
break;
1605+
// Ok, we are the Src and are performing a take. Treat it as a destroy!
1606+
if (destroyingUses)
1607+
destroyingUses->push_back(use);
1608+
continue;
1609+
}
15801610
case SILInstructionKind::DestroyAddrInst:
1581-
destroyingUses.push_back(use);
1611+
if (destroyingUses)
1612+
destroyingUses->push_back(use);
15821613
continue;
15831614
case SILInstructionKind::DeallocStackInst:
15841615
case SILInstructionKind::LoadBorrowInst:
@@ -1589,21 +1620,21 @@ bool swift::isSingleInitAllocStack(AllocStackInst *asi,
15891620
// See if we have an initializer and that such initializer is in the same
15901621
// block.
15911622
if (memInstMustInitialize(use)) {
1592-
if (user->getParent() != asi->getParent() || foundInit) {
1593-
return false;
1623+
if (user->getParent() != asi->getParent() || singleInit) {
1624+
return nullptr;
15941625
}
15951626

1596-
foundInit = true;
1627+
singleInit = use;
15971628
continue;
15981629
}
15991630

16001631
// Otherwise, if we have found something not in our allowlist, return false.
1601-
return false;
1632+
return nullptr;
16021633
}
16031634

16041635
// We did not find any users that we did not understand. So we can
1605-
// conservatively return true here.
1606-
return true;
1636+
// conservatively return the single initializing write that we found.
1637+
return singleInit;
16071638
}
16081639

16091640
/// Return true if the given address value is produced by a special address

lib/SILOptimizer/SemanticARC/LoadCopyToLoadBorrowOpt.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,10 @@ class StorageGuaranteesLoadVisitor
281281
/// See if we have an alloc_stack that is only written to once by an
282282
/// initializing instruction.
283283
void visitStackAccess(AllocStackInst *stack) {
284-
SmallVector<Operand *, 8> destroyAddrOperands;
285-
bool initialAnswer = isSingleInitAllocStack(stack, destroyAddrOperands);
284+
// These will contain all of the address destroying operands that form the
285+
// lifetime of the object. They may not be destroy_addr!
286+
SmallVector<Operand *, 8> addrDestroyingOperands;
287+
bool initialAnswer = isSingleInitAllocStack(stack, addrDestroyingOperands);
286288
if (!initialAnswer)
287289
return answer(true);
288290

@@ -291,7 +293,7 @@ class StorageGuaranteesLoadVisitor
291293
LinearLifetimeChecker checker(ctx.getDeadEndBlocks());
292294
// Returns true on success. So we invert.
293295
bool foundError = !checker.validateLifetime(
294-
stack, destroyAddrOperands /*consuming users*/,
296+
stack, addrDestroyingOperands /*consuming users*/,
295297
liveRange.getAllConsumingUses() /*non consuming users*/);
296298
return answer(foundError);
297299
}

0 commit comments

Comments
 (0)