Files
swift-mirror/include/swift/SIL/SILLocation.h
Erik Eckstein ce7358d35d SIL: create hop_to_executor instructions with "auto-generated" debug locations.
Those instructions should not be "visible" for debugging and for diagnostic messages.

Fixes a wrong "unreachable code" warning.

https://bugs.swift.org/browse/SR-14873
rdar://80237870
2021-08-26 19:14:16 +02:00

642 lines
22 KiB
C++

//===--- SILLocation.h - Location information for SIL nodes -----*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_SIL_LOCATION_H
#define SWIFT_SIL_LOCATION_H
#include "llvm/ADT/PointerUnion.h"
#include "swift/Basic/SourceLoc.h"
#include "swift/SIL/SILAllocated.h"
#include "swift/AST/TypeAlignments.h"
#include <cstddef>
#include <type_traits>
namespace swift {
class ReturnStmt;
class BraceStmt;
class AbstractClosureExpr;
class AbstractFunctionDecl;
class SILModule;
class SourceManager;
/// Represents a source location.
///
/// In most cases this is a pointer to an AST node from which the source
/// location can be retrieved from. In case the location refers to a parsed
/// or deserialized SIL file, the source location is represented differently.
/// For details see SILLocation::Storage.
class SILLocation {
public:
enum LocationKind : uint8_t {
RegularKind = 1,
ReturnKind = 2,
ImplicitReturnKind = 3,
InlinedKind = 4,
MandatoryInlinedKind = 5,
CleanupKind = 6,
ArtificialUnreachableKind = 7
};
/// Describes a position in a source file by explicitly storing the file name,
/// line and column.
///
/// This is used for parsed locations from a SIL file, for
/// "-sil-based-debuginfo" (see SILDebugInfoGenerator) and for the
/// "compiler-generated" singleton location.
/// In future we might also use it for de-serialized locations from a
/// swiftmodule file.
struct FilenameAndLocation : public SILAllocated<FilenameAndLocation> {
unsigned line;
uint16_t column;
StringRef filename;
FilenameAndLocation(unsigned line = 0, uint16_t column = 0,
StringRef filename = StringRef())
: line(line), column(column), filename(filename) {}
static FilenameAndLocation *alloc(unsigned line, unsigned column,
StringRef filename, SILModule &module);
inline bool operator==(const FilenameAndLocation &rhs) const {
return line == rhs.line && column == rhs.column &&
filename.equals(rhs.filename);
}
void dump() const;
void print(raw_ostream &OS) const;
};
protected:
template <class T, class Enable = void>
struct base_type;
template <class T>
struct base_type<T,
typename std::enable_if<std::is_base_of<Decl, T>::value>::type> {
using type = Decl;
};
template <class T>
struct base_type<T,
typename std::enable_if<std::is_base_of<Expr, T>::value>::type> {
using type = Expr;
};
template <class T>
struct base_type<T,
typename std::enable_if<std::is_base_of<Stmt, T>::value>::type> {
using type = Stmt;
};
template <class T>
struct base_type<T,
typename std::enable_if<std::is_base_of<Pattern, T>::value>::type> {
using type = Pattern;
};
using ASTNodeTy = llvm::PointerUnion<Stmt *, Expr *, Decl *, Pattern *>;
/// Used in case the location for diagnostics does not match the location for
/// debugging.
struct ExtendedASTNodeLoc : public SILAllocated<ExtendedASTNodeLoc> {
/// Primary AST location, always used for diagnostics.
ASTNodeTy primary;
/// Sometimes the location for diagnostics needs to be different than the
/// one used to emit the line table for debugging.
ASTNodeTy forDebugging;
ExtendedASTNodeLoc(ASTNodeTy primary, ASTNodeTy forDebugging) :
primary(primary), forDebugging(forDebugging) {}
};
private:
friend class SILInstruction;
/// Each kind corresponds to a union member in `Storage`.
enum StorageKind : uint8_t {
/// For usages see the struct `FilenameAndLocation`.
FilenameAndLocationKind,
/// The most common location kind: a pointer to the AST from which the
/// source location can be retrieved.
/// This is the "normal" case when compiling a swift file.
ASTNodeKind,
/// Use for a few locations in pattern-code. See ExtendedASTNodeLoc.
ExtendedASTNodeKind,
/// This is used when parsing a SIL file.
/// Note: this is only used for functions/instruction for which there is no
/// location specified in the SIL file.
/// In case a SIL instruction is annotated with a parsable location in
/// the SIL file, the instruction gets a FilenameAndLocation.
SourceLocKind
};
/// The storage which actually points to the source location. Each union
/// member corresponds to a StorageKind and is basically a pointer.
/// Note that a referenced FilenameAndLocation or ExtendedASTNodeLoc is not
/// owned by SILLocation, thus not freed on destruction. Those data structures
/// are allocated with the SILModule's bump pointer allocator.
union Storage {
Storage() : filePositionLoc(nullptr) {}
Storage(FilenameAndLocation *F) : filePositionLoc(F) {}
Storage(ASTNodeTy N) : ASTNodeLoc(N) {}
Storage(ExtendedASTNodeLoc *E) : extendedASTNodeLoc(E) {}
Storage(SourceLoc L) : sourceLoc(L) {}
const FilenameAndLocation *filePositionLoc;
ASTNodeTy ASTNodeLoc;
const ExtendedASTNodeLoc *extendedASTNodeLoc;
SourceLoc sourceLoc;
};
static_assert(sizeof(SILLocation::Storage) == sizeof(void *),
"SILLocation::Storage must stay small");
/// Contains the LocationKind, StorageKind and some extra flags.
/// This fits nicely in a single byte. If for some reason we need more flags
/// it's possible to extend this to e.g. a uint16_t.
/// But SILNode::Bits.LocationKindAndFlags must be updated accordingly.
union KindAndFlags {
KindAndFlags() : packedKindAndFlags(0) {}
KindAndFlags(LocationKind kind, StorageKind storageKind)
: fields({kind, storageKind, 0, 0, 0 }) {
assert(fields.kind == kind && "LocationKind overflow");
assert(fields.storageKind == storageKind && "StorageKind overflow");
}
KindAndFlags(uint8_t packed) : packedKindAndFlags(packed) {}
uint8_t packedKindAndFlags;
struct Fields {
uint8_t kind: 3;
uint8_t storageKind: 2;
uint8_t autoGenerated: 1;
uint8_t pointsToEnd: 1;
uint8_t inPrologue: 1;
} fields;
};
static_assert(sizeof(SILLocation::KindAndFlags) == sizeof(uint8_t),
"SILLocation::KindAndFlags should be 1 byte");
// Data fields:
Storage storage;
KindAndFlags kindAndFlags;
StorageKind getStorageKind() const {
return (StorageKind)kindAndFlags.fields.storageKind;
}
ASTNodeTy getPrimaryASTNode() const {
switch (getStorageKind()) {
case ASTNodeKind:
return storage.ASTNodeLoc;
case ExtendedASTNodeKind:
return storage.extendedASTNodeLoc->primary;
case SourceLocKind:
case FilenameAndLocationKind:
llvm_unreachable("location type has no AST node");
}
llvm_unreachable("covered switch");
}
/// Returns true if the location has a separate AST node for debugging.
/// See ExtendedASTNodeLoc.
bool hasASTNodeForDebugging() const {
return getStorageKind() == ExtendedASTNodeKind;
}
template <typename T>
T *getNodeAs(ASTNodeTy Node) const {
using base = typename base_type<T>::type*;
return dyn_cast_or_null<T>(Node.dyn_cast<base>());
}
template <typename T>
bool isNode(ASTNodeTy Node) const {
assert(isASTNode());
ASTNodeTy primaryNd = getPrimaryASTNode();
if (primaryNd.is<typename base_type<T>::type*>())
return isa<T>(Node.get<typename base_type<T>::type*>());
return false;
}
template <typename T>
T *castNodeTo(ASTNodeTy Node) const {
return cast<T>(Node.get<typename base_type<T>::type*>());
}
SourceLoc getSourceLoc(ASTNodeTy N) const;
static SourceLoc getStartSourceLoc(ASTNodeTy N);
static SourceLoc getEndSourceLoc(ASTNodeTy N);
protected:
SILLocation(LocationKind K) : kindAndFlags(K, FilenameAndLocationKind) {}
/// Constructs a null location.
SILLocation() : SILLocation(RegularKind) { assert(isNull()); }
/// Constructs a location like \p l, but with a different kind.
SILLocation(SILLocation l, LocationKind K)
: storage(l.storage), kindAndFlags(l.kindAndFlags.packedKindAndFlags) {
kindAndFlags.fields.kind = K;
}
SILLocation(FilenameAndLocation *filePos, LocationKind K)
: storage(filePos), kindAndFlags(K, FilenameAndLocationKind) {
assert(filePos && !filePos->filename.empty());
}
// It is okay to pass a nullptr, but preferably, a null-location should be
// created with `invalid()`.
SILLocation(ASTNodeTy Node, LocationKind K)
: storage(Node), kindAndFlags(K, ASTNodeKind) {}
SILLocation(ExtendedASTNodeLoc *ext, LocationKind K)
: storage(ext), kindAndFlags(K, ExtendedASTNodeKind) {
assert(ext && !ext->primary.isNull() && !ext->forDebugging.isNull());
}
SILLocation(SourceLoc L, LocationKind K)
: storage(L), kindAndFlags(K, SourceLocKind) {}
// Used by SILInstruction.
SILLocation(Storage storage, uint8_t packedKindAndFlags) :
storage(storage), kindAndFlags(packedKindAndFlags) {}
public:
// When an ASTNode gets implicitly converted into a SILLocation we
// construct a RegularLocation. Since RegularLocations represent the majority
// of locations, this greatly simplifies the user code.
//
// It is okay to pass a nullptr to these constructors, but preferably, a
// null-location should be created with `invalid()`.
SILLocation(Stmt *S) : SILLocation(ASTNodeTy(S), RegularKind) {}
SILLocation(Expr *E) : SILLocation(ASTNodeTy(E), RegularKind) {}
SILLocation(Decl *D) : SILLocation(ASTNodeTy(D), RegularKind) {}
SILLocation(Pattern *P) : SILLocation(ASTNodeTy(P), RegularKind) {}
/// Returns a null location.
static SILLocation invalid() { return SILLocation(); }
LocationKind getKind() const {
return (LocationKind)kindAndFlags.fields.kind;
}
/// Artificial locations and the top-level module locations will be null.
bool isNull() const {
switch (getStorageKind()) {
case ASTNodeKind: return storage.ASTNodeLoc.isNull();
case ExtendedASTNodeKind: return false;
case FilenameAndLocationKind: return storage.filePositionLoc == nullptr;;
case SourceLocKind: return storage.sourceLoc.isInvalid();
}
llvm_unreachable("covered switch");
}
explicit operator bool() const { return !isNull(); }
/// Return true if this location is backed by an AST node.
bool isASTNode() const {
switch (getStorageKind()) {
case ASTNodeKind:
case ExtendedASTNodeKind:
return true;
case SourceLocKind:
case FilenameAndLocationKind:
return false;
}
llvm_unreachable("covered switch");
}
/// Returns true if this location came from a SIL file.
bool isSILFile() const { return getStorageKind() == SourceLocKind; }
bool isFilenameAndLocation() const {
return getStorageKind() == FilenameAndLocationKind && !isNull();
}
const FilenameAndLocation *getFilenameAndLocation() const {
assert(isFilenameAndLocation());
return storage.filePositionLoc;
}
/// Marks the location as coming from auto-generated body.
void markAutoGenerated() { kindAndFlags.fields.autoGenerated = true; }
/// Returns this location with the auto-generated flag set.
SILLocation asAutoGenerated() const {
SILLocation loc = *this;
loc.markAutoGenerated();
return loc;
}
/// Returns true if the location represents an artificially generated
/// body, such as thunks or default destructors.
///
/// These locations should not be included in the debug line table.
/// These might also need special handling by the debugger since they might
/// contain calls, which the debugger could be able to step into.
bool isAutoGenerated() const { return kindAndFlags.fields.autoGenerated; }
/// Returns true if the line number of this location is zero.
bool isLineZero(const SourceManager &SM) const {
return decodeForDebugging(SM).line == 0;
}
/// Changes the default source location position to point to the end of
/// the AST node.
void pointToEnd() { kindAndFlags.fields.pointsToEnd = true; }
/// Mark this location as being part of the function
/// prologue, which means that it deals with setting up the stack
/// frame. The first breakpoint location in a function is at the end
/// of the prologue.
void markAsPrologue() { kindAndFlags.fields.inPrologue = true; }
/// Check is this location is part of a function's implicit prologue.
bool isInPrologue() const { return kindAndFlags.fields.inPrologue; }
/// Check if the corresponding source code location definitely points
/// to the end of the AST node.
bool alwaysPointsToEnd() const { return kindAndFlags.fields.pointsToEnd; }
template <typename T> bool is() const { return T::isKind(*this); }
/// If the current value is of the specified AST unit type T,
/// return it, otherwise return null.
template <typename T> T *getAsASTNode() const {
return isASTNode() ? getNodeAs<T>(getPrimaryASTNode()) : nullptr;
}
/// Returns true if the Location currently points to the AST node
/// matching type T.
template <typename T> bool isASTNode() const {
return isASTNode() && isNode<T>(getPrimaryASTNode());
}
/// Returns the primary value as the specified AST node type. If the
/// specified type is incorrect, asserts.
template <typename T> T *castToASTNode() const {
return castNodeTo<T>(getPrimaryASTNode());
}
/// Return the location as a DeclContext or null.
DeclContext *getAsDeclContext() const;
/// Returns the source location for diagnoses.
SourceLoc getSourceLoc() const;
/// Returns the source location for debugging.
/// See ExtendedASTNodeLoc.
SourceLoc getSourceLocForDebugging() const;
SourceLoc getStartSourceLoc() const;
SourceLoc getEndSourceLoc() const;
SourceRange getSourceRange() const {
return {getStartSourceLoc(), getEndSourceLoc()};
}
/// Extract the line, column, and filename from \p Loc.
static FilenameAndLocation decode(SourceLoc Loc, const SourceManager &SM);
/// Return the decoded FilenameAndLocation.
/// In case the location has a separate AST node for debugging, this node is
/// used. See ExtendedASTNodeLoc.
FilenameAndLocation decodeForDebugging(const SourceManager &SM) const {
return isFilenameAndLocation() ? *getFilenameAndLocation()
: decode(getSourceLocForDebugging(), SM);
}
/// Compiler-generated locations may be applied to instructions without any
/// clear correspondence to an AST node in an otherwise normal function.
static FilenameAndLocation *getCompilerGeneratedLoc();
/// Pretty-print the value.
void dump() const;
void print(raw_ostream &OS, const SourceManager &SM) const;
inline bool operator==(const SILLocation& R) const {
return kindAndFlags.packedKindAndFlags == R.kindAndFlags.packedKindAndFlags
&& storage.filePositionLoc == R.storage.filePositionLoc;
}
inline bool operator!=(const SILLocation &R) const { return !(*this == R); }
};
/// Allowed on any instruction.
class RegularLocation : public SILLocation {
public:
RegularLocation(Stmt *S) : SILLocation(ASTNodeTy(S), RegularKind) {}
RegularLocation(Expr *E) : SILLocation(ASTNodeTy(E), RegularKind) {}
RegularLocation(Decl *D) : SILLocation(ASTNodeTy(D), RegularKind) {}
RegularLocation(Pattern *P) : SILLocation(ASTNodeTy(P), RegularKind) {}
RegularLocation(Stmt *S, Pattern *P, SILModule &Module);
RegularLocation(SourceLoc L) : SILLocation(L, RegularKind) {}
RegularLocation(FilenameAndLocation *filePos)
: SILLocation(filePos, RegularKind) {}
/// Convert \p loc to a RegularLocation.
explicit RegularLocation(SILLocation loc) : SILLocation(loc, RegularKind) {}
/// Returns a location representing the module.
/// This is just a null location.
static RegularLocation getModuleLocation() { return RegularLocation(); }
/// Compiler-generated locations may be applied to instructions without any
/// clear correspondence to an AST node in an otherwise normal function.
/// The auto-generated bit also turns off certain diagnostics passes such.
static RegularLocation getAutoGeneratedLocation() {
RegularLocation AL(getCompilerGeneratedLoc());
AL.markAutoGenerated();
return AL;
}
/// Returns a location that is compiler-generated, but with a hint as to where
/// it may have been generated from. These locations will have an artificial
/// line location of zero in DWARF, but in CodeView we want to use the given
/// line since line zero does not represent an artificial line in CodeView.
template <typename InputLocTy>
static RegularLocation getAutoGeneratedLocation(InputLocTy L) {
RegularLocation AL(L);
AL.markAutoGenerated();
return AL;
}
static bool isKind(const SILLocation& L) {
return L.getKind() == RegularKind;
}
private:
RegularLocation() : SILLocation(RegularKind) {}
};
/// Used to represent a return instruction in user code.
///
/// Allowed on an BranchInst, ReturnInst.
class ReturnLocation : public SILLocation {
public:
ReturnLocation(ReturnStmt *RS);
/// Construct the return location for a constructor or a destructor.
ReturnLocation(BraceStmt *BS);
ReturnLocation(FilenameAndLocation *filePos)
: SILLocation(filePos, ReturnKind) {}
static bool isKind(const SILLocation& L) {
return L.getKind() == ReturnKind;
}
};
/// Used on the instruction that was generated to represent an implicit
/// return from a function.
///
/// Allowed on an BranchInst, ReturnInst.
class ImplicitReturnLocation : public SILLocation {
public:
ImplicitReturnLocation(AbstractClosureExpr *E);
ImplicitReturnLocation(ReturnStmt *S);
ImplicitReturnLocation(AbstractFunctionDecl *AFD);
ImplicitReturnLocation(FilenameAndLocation *filePos)
: SILLocation(filePos, ImplicitReturnKind) {}
/// Convert \p loc to an ImplicitReturnLocation.
explicit ImplicitReturnLocation(SILLocation Loc);
static bool isKind(const SILLocation& L) {
return L.getKind() == ImplicitReturnKind;
}
};
/// Marks instructions that correspond to inlined function body and
/// setup code. This location should not be used for inlined transparent
/// bodies, see MandatoryInlinedLocation.
///
/// This location wraps the call site ASTNode.
///
/// Allowed on any instruction except for ReturnInst.
class InlinedLocation : public SILLocation {
public:
/// Convert \p loc to an InlinedLocation.
InlinedLocation(SILLocation L) : SILLocation(L, InlinedKind) {}
static bool isKind(const SILLocation& L) {
return L.getKind() == InlinedKind;
}
};
/// Marks instructions that correspond to inlined function body and
/// setup code for transparent functions, inlined as part of mandatory inlining
/// pass.
///
/// This location wraps the call site ASTNode.
///
/// Allowed on any instruction except for ReturnInst.
class MandatoryInlinedLocation : public SILLocation {
public:
MandatoryInlinedLocation() : SILLocation(MandatoryInlinedKind) {}
/// Convert \p loc to a MandatoryInlinedLocation.
explicit MandatoryInlinedLocation(SILLocation L)
: SILLocation(L, MandatoryInlinedKind) {}
static MandatoryInlinedLocation getAutoGeneratedLocation() {
return MandatoryInlinedLocation(
RegularLocation::getAutoGeneratedLocation());
}
static bool isKind(const SILLocation& L) {
return L.getKind() == MandatoryInlinedKind;
}
};
/// Used on the instruction performing auto-generated cleanup such as
/// deallocs, destructor calls.
///
/// The cleanups are performed after completing the evaluation of the AST Node
/// wrapped inside the SILLocation. This location wraps the statement
/// representing the enclosing scope, for example, FuncDecl, ParenExpr. The
/// scope's end location points to the SourceLoc that shows when the operation
/// is performed at runtime.
///
/// Allowed on any instruction except for ReturnInst.
/// Locations of an inlined destructor should also be represented by this.
class CleanupLocation : public SILLocation {
public:
CleanupLocation(Expr *E) : SILLocation(ASTNodeTy(E), CleanupKind) {}
CleanupLocation(Stmt *S) : SILLocation(ASTNodeTy(S), CleanupKind) {}
CleanupLocation(Pattern *P) : SILLocation(ASTNodeTy(P), CleanupKind) {}
CleanupLocation(Decl *D) : SILLocation(ASTNodeTy(D), CleanupKind) {}
/// Convert \p loc to a CleanupLocation.
explicit CleanupLocation(SILLocation L) : SILLocation(L, CleanupKind) {}
/// Returns a null location.
static CleanupLocation invalid() { return CleanupLocation(); }
/// Returns a location representing a cleanup on the module level.
/// This is just a null location.
static CleanupLocation getModuleCleanupLocation() {
return CleanupLocation();
}
static bool isKind(const SILLocation& L) {
return L.getKind() == CleanupKind;
}
private:
CleanupLocation() : SILLocation(CleanupKind) {}
};
/// Used to represent an unreachable location that was
/// auto-generated and has no correspondence to user code. It should
/// not be used in diagnostics or for debugging.
///
/// Differentiates an unreachable instruction, which is generated by
/// DCE, from an unreachable instruction in user code (output of SILGen).
/// Allowed on an unreachable instruction.
class ArtificialUnreachableLocation : public SILLocation {
public:
ArtificialUnreachableLocation() : SILLocation(ArtificialUnreachableKind) {}
static bool isKind(const SILLocation& L) {
return (L.getKind() == ArtificialUnreachableKind);
}
};
class SILDebugScope;
/// A SILLocation paired with a SILDebugScope.
class SILDebugLocation {
const SILDebugScope *debugScope;
SILLocation location;
public:
SILDebugLocation()
: debugScope(nullptr),
location(RegularLocation::getAutoGeneratedLocation()) {}
SILDebugLocation(SILLocation location, const SILDebugScope *debugScope)
: debugScope(debugScope), location(location) {}
SILLocation getLocation() const { return location; }
const SILDebugScope *getScope() const { return debugScope; }
operator bool() const { return bool(location) && debugScope; }
};
} // end swift namespace
#endif