Skip to content

[WIP] Associate messages with failures in debug info #26148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions include/swift/AST/Builtins.def
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,11 @@ BUILTIN_SIL_OPERATION(EndUnpairedAccess, "endUnpairedAccess", Special)
/// Triggers a runtime failure if the condition is true.
BUILTIN_SIL_OPERATION(CondFail, "condfail", Special)

/// condfail(Int1, RawPointer) -> ()
/// Triggers a runtime failure if the condition is true, with a
/// reason message to encode in debug info
BUILTIN_SIL_OPERATION(CondFailMessage, "condfail_message", Special)

/// fixLifetime(T) -> ()
/// Fixes the lifetime of any heap references in a value.
BUILTIN_SIL_OPERATION(FixLifetime, "fixLifetime", Special)
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsIRGen.def
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ ERROR(alignment_more_than_maximum,none,
"@_alignment cannot increase alignment above maximum alignment of %0",
(unsigned))

WARNING(irgen_condfail_non_literal_message,none,
"condfail message did not evaluate to a literal string", ())

#ifndef DIAG_NO_UNDEF
# if defined(DIAG)
# undef DIAG
Expand Down
19 changes: 12 additions & 7 deletions include/swift/SIL/SILBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -1774,16 +1774,21 @@ class SILBuilder {
// Runtime failure
//===--------------------------------------------------------------------===//

CondFailInst *createCondFail(SILLocation Loc, SILValue Operand,
bool Inverted = false) {
CondFailInst *createCondFail(SILLocation Loc,
SILValue Condition,
bool Inverted = false,
SILValue Message = SILValue(),
ArrayRef<SILValue> MessageArguments = {}) {
if (Inverted) {
SILType Ty = Operand->getType();
SILType Ty = Condition->getType();
SILValue True(createIntegerLiteral(Loc, Ty, 1));
Operand =
createBuiltinBinaryFunction(Loc, "xor", Ty, Ty, {Operand, True});
Condition =
createBuiltinBinaryFunction(Loc, "xor", Ty, Ty, {Condition, True});
}
return insert(new (getModule())
CondFailInst(getSILDebugLocation(Loc), Operand));

return insert(CondFailInst::create(getModule(),
getSILDebugLocation(Loc), Condition,
Message, MessageArguments));
}

BuiltinInst *createBuiltinTrap(SILLocation Loc) {
Expand Down
7 changes: 6 additions & 1 deletion include/swift/SIL/SILCloner.h
Original file line number Diff line number Diff line change
Expand Up @@ -2447,7 +2447,12 @@ SILCloner<ImplClass>::visitCondFailInst(CondFailInst *Inst) {
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
recordClonedInstruction(
Inst, getBuilder().createCondFail(getOpLocation(Inst->getLoc()),
getOpValue(Inst->getOperand())));
getOpValue(Inst->getCondition()),
/*inverted*/ false,
Inst->getMessage()
? getOpValue(Inst->getMessage())
: SILValue(),
getOpValueArray<8>(Inst->getMessageArguments())));
}

template<typename ImplClass>
Expand Down
53 changes: 48 additions & 5 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -6715,14 +6715,57 @@ class ProjectExistentialBoxInst
//===----------------------------------------------------------------------===//

/// Trigger a runtime failure if the given Int1 value is true.
class CondFailInst
: public UnaryInstructionBase<SILInstructionKind::CondFailInst,
class CondFailInst final
: public InstructionBaseWithTrailingOperands<
SILInstructionKind::CondFailInst,
CondFailInst,
NonValueInstruction>
{
friend SILBuilder;

CondFailInst(SILDebugLocation DebugLoc, SILValue Operand)
: UnaryInstructionBase(DebugLoc, Operand) {}

static SmallVector<SILValue, 8>
makeOperandsArray(SILValue Condition, SILValue Message,
ArrayRef<SILValue> MessageArguments) {
SmallVector<SILValue, 8> result;
result.push_back(Condition);
if (Message)
result.push_back(Message);
assert(Message || MessageArguments.empty()
&& "can only have message arguments with a message");
result.append(MessageArguments.begin(), MessageArguments.end());
return result;
}

CondFailInst(SILDebugLocation DebugLoc, SILValue Condition,
SILValue Message = SILValue(),
ArrayRef<SILValue> MessageArguments = {})
: InstructionBaseWithTrailingOperands(
makeOperandsArray(Condition, Message, MessageArguments),
DebugLoc) {}

enum { Condition, Message, FirstMessageArgument };

static CondFailInst *create(SILModule &M,
SILDebugLocation DebugLoc, SILValue Condition,
SILValue Message = SILValue(),
ArrayRef<SILValue> MessageArguments = {});

public:
SILValue getCondition() const { return getAllOperands()[Condition].get(); }

SILValue getMessage() const {
if (getAllOperands().size() > Message) {
return getAllOperands()[Message].get();
}
return SILValue();
}

OperandValueArrayRef getMessageArguments() const {
if (getAllOperands().size() > FirstMessageArgument) {
return OperandValueArrayRef(getAllOperands().slice(FirstMessageArgument));
}
return {};
}
};

//===----------------------------------------------------------------------===//
Expand Down
2 changes: 1 addition & 1 deletion include/swift/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 500; // dependency types for protocols
const uint16_t SWIFTMODULE_VERSION_MINOR = 501; // cond_fail message

using DeclIDField = BCFixed<31>;

Expand Down
13 changes: 12 additions & 1 deletion lib/AST/Builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1035,12 +1035,20 @@ static ValueDecl *getCanBeObjCClassOperation(ASTContext &Context,
}

static ValueDecl *getCondFailOperation(ASTContext &C, Identifier Id) {
// Int1 -> ()
// (Int1) -> ()
auto CondTy = BuiltinIntegerType::get(1, C);
auto VoidTy = TupleType::getEmpty(C);
return getBuiltinFunction(Id, {CondTy}, VoidTy);
}

static ValueDecl *getCondFailMessageOperation(ASTContext &C, Identifier Id) {
// (Int1, RawPointer) -> ()
auto CondTy = BuiltinIntegerType::get(1, C);
auto MsgTy = C.TheRawPointerType;
auto VoidTy = TupleType::getEmpty(C);
return getBuiltinFunction(Id, {CondTy, MsgTy}, VoidTy);
}

static ValueDecl *getAssertConfOperation(ASTContext &C, Identifier Id) {
// () -> Int32
auto Int32Ty = BuiltinIntegerType::get(32, C);
Expand Down Expand Up @@ -1895,6 +1903,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
case BuiltinValueKind::CondFail:
return getCondFailOperation(Context, Id);

case BuiltinValueKind::CondFailMessage:
return getCondFailMessageOperation(Context, Id);

case BuiltinValueKind::AssertConf:
return getAssertConfOperation(Context, Id);

Expand Down
85 changes: 60 additions & 25 deletions lib/IRGen/IRGenDebugInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ class IRGenDebugInfoImpl : public IRGenDebugInfo {
void clearLoc(IRBuilder &Builder);
void pushLoc();
void popLoc();
void setInlinedTrapLocation(IRBuilder &Builder, const SILDebugScope *Scope);
void setInlinedTrapLocation(IRBuilder &Builder, const SILDebugScope *Scope,
SILLocation loc,
StringRef message);
void setEntryPointLoc(IRBuilder &Builder);
llvm::DIScope *getEntryPointFn();
llvm::DIScope *getOrCreateScope(const SILDebugScope *DS);
Expand Down Expand Up @@ -1848,29 +1850,58 @@ void IRGenDebugInfoImpl::popLoc() {
std::tie(LastDebugLoc, LastScope) = LocationStack.pop_back_val();
}

/// This is done for WinDbg to avoid having two non-contiguous sets of
/// instructions because the ``@llvm.trap`` instruction gets placed at the end
/// of the function.
void IRGenDebugInfoImpl::setInlinedTrapLocation(IRBuilder &Builder,
const SILDebugScope *Scope) {
if (Opts.DebugInfoFormat != IRGenDebugInfoFormat::CodeView)
return;

// The @llvm.trap could be inlined into a chunk of code that was also inlined.
// If this is the case then simply using the LastScope's location would
// generate debug info that claimed Function A owned Block X and Block X
// thought it was owned by Function B. Therefore, we need to find the last
// inlined scope to point to.
const SILDebugScope *TheLastScope = LastScope;
while (TheLastScope->InlinedCallSite &&
TheLastScope->InlinedCallSite != TheLastScope) {
TheLastScope = TheLastScope->InlinedCallSite;
}
auto LastLocation = llvm::DebugLoc::get(
LastDebugLoc.Line, LastDebugLoc.Column, getOrCreateScope(TheLastScope));
// FIXME: This location should point to stdlib instead of being artificial.
auto DL = llvm::DebugLoc::get(0, 0, getOrCreateScope(Scope), LastLocation);
Builder.SetCurrentDebugLocation(DL);
const SILDebugScope *Scope,
SILLocation loc,
StringRef message) {
llvm::DebugLoc DL;
// If the trap came with a message, create an artificial inline scope inside
// the scope with the message.
if (!message.empty()) {
auto realScope = getOrCreateScope(LastScope);
auto trapScope = llvm::DISubprogram::getDistinct(IGM.LLVMContext,
realScope->getFile(),
message, "",
realScope->getFile(),
LastDebugLoc.Line,
/*type*/ nullptr,
LastDebugLoc.Line,
/*containingType*/ llvm::DITypeRef(),
/*virtualIndex*/0,
/*thisAdjustment*/0,
llvm::DINode::DIFlags(),
llvm::DISubprogram::DISPFlags::SPFlagDefinition,
TheCU);
DL = llvm::DebugLoc::get(LastDebugLoc.Line,
LastDebugLoc.Column,
trapScope,
llvm::DebugLoc::get(LastDebugLoc.Line,
LastDebugLoc.Column,
getOrCreateScope(LastScope)));
} else if (Opts.DebugInfoFormat == IRGenDebugInfoFormat::CodeView) {
// This is done for WinDbg to avoid having two non-contiguous sets of
// instructions because the ``@llvm.trap`` instruction gets placed at the end
// of the function.
//
// The @llvm.trap could be inlined into a chunk of code that was also inlined.
// If this is the case then simply using the LastScope's location would
// generate debug info that claimed Function A owned Block X and Block X
// thought it was owned by Function B. Therefore, we need to find the last
// inlined scope to point to.
const SILDebugScope *TheLastScope = LastScope;
while (TheLastScope->InlinedCallSite &&
TheLastScope->InlinedCallSite != TheLastScope) {
TheLastScope = TheLastScope->InlinedCallSite;
}
auto LastLocation = llvm::DebugLoc::get(
LastDebugLoc.Line, LastDebugLoc.Column, getOrCreateScope(TheLastScope));
// FIXME: This location should point to stdlib instead of being artificial.
DL = llvm::DebugLoc::get(0, 0, getOrCreateScope(Scope), LastLocation);
}

if (DL) {
Builder.SetCurrentDebugLocation(DL);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@compnerd It sounds like CodeView debug info has some additional invariants that aren't clear to me. I tried to preserve the existing logic here, but how would what we're trying to do here fit in with Windows debug info?

}

void IRGenDebugInfoImpl::setEntryPointLoc(IRBuilder &Builder) {
Expand Down Expand Up @@ -2333,9 +2364,13 @@ void IRGenDebugInfo::popLoc() {
}

void IRGenDebugInfo::setInlinedTrapLocation(IRBuilder &Builder,
const SILDebugScope *Scope) {
const SILDebugScope *Scope,
SILLocation loc,
StringRef message) {
static_cast<IRGenDebugInfoImpl *>(this)->setInlinedTrapLocation(Builder,
Scope);
Scope,
loc,
message);
}

void IRGenDebugInfo::setEntryPointLoc(IRBuilder &Builder) {
Expand Down
4 changes: 3 additions & 1 deletion lib/IRGen/IRGenDebugInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ class IRGenDebugInfo {
/// In CodeView, since zero is not an artificial location, we emit the
/// location of the unified trap block at the end of the fuction as an
/// artificial inline location pointing to the user's instruction.
void setInlinedTrapLocation(IRBuilder &Builder, const SILDebugScope *Scope);
void setInlinedTrapLocation(IRBuilder &Builder, const SILDebugScope *Scope,
SILLocation loc,
StringRef message);

/// Set the location for SWIFT_ENTRY_POINT_FUNCTION.
void setEntryPointLoc(IRBuilder &Builder);
Expand Down
31 changes: 24 additions & 7 deletions lib/IRGen/IRGenSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "swift/Basic/Range.h"
#include "swift/Basic/STLExtras.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/DiagnosticsIRGen.h"
#include "swift/AST/IRGenOptions.h"
#include "swift/AST/Pattern.h"
#include "swift/AST/ParameterList.h"
Expand Down Expand Up @@ -5426,7 +5427,7 @@ void IRGenSILFunction::visitDestroyAddrInst(swift::DestroyAddrInst *i) {
}

void IRGenSILFunction::visitCondFailInst(swift::CondFailInst *i) {
Explosion e = getLoweredExplosion(i->getOperand());
Explosion e = getLoweredExplosion(i->getCondition());
llvm::Value *cond = e.claimNext();

// Emit individual fail blocks so that we can map the failure back to a source
Expand All @@ -5435,12 +5436,28 @@ void IRGenSILFunction::visitCondFailInst(swift::CondFailInst *i) {
llvm::BasicBlock *contBB = llvm::BasicBlock::Create(IGM.getLLVMContext());
Builder.CreateCondBr(cond, failBB, contBB);
Builder.emitBlock(failBB);
if (IGM.DebugInfo)
// If we are emitting DWARF, this does nothing. Otherwise the ``llvm.trap``
// instruction emitted from ``Builtin.condfail`` should have an inlined
// debug location. This is because zero is not an artificial line location
// in CodeView.
IGM.DebugInfo->setInlinedTrapLocation(Builder, i->getDebugScope());
if (IGM.DebugInfo) {
// Give the ``Builtin.condfail`` an inlined debug location.

// If the failure has a literal string message associated, use it as the
// function name for the inlined location, so that backtraces show the
// message.
// TODO: Also lower the message arguments as frame variables in the inline
// scope.
SmallString<128> message;
if (auto messageValue = i->getMessage()) {
if (auto messageLiteral = dyn_cast<StringLiteralInst>(messageValue)) {
message = messageLiteral->getValue();
} else {
IGM.Context.Diags.diagnose(i->getLoc().getSourceLoc(),
diag::irgen_condfail_non_literal_message);
}
}

IGM.DebugInfo->setInlinedTrapLocation(Builder, i->getDebugScope(),
i->getLoc(), message);
}

emitTrap(/*EmitUnreachable=*/true);
Builder.emitBlock(contBB);
FailBBs.push_back(failBB);
Expand Down
34 changes: 33 additions & 1 deletion lib/ParseSIL/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2887,7 +2887,6 @@ bool SILParser::parseSILInstruction(SILBuilder &B) {
UNARY_INSTRUCTION(DestroyAddr)
UNARY_INSTRUCTION(CopyValue)
UNARY_INSTRUCTION(DestroyValue)
UNARY_INSTRUCTION(CondFail)
UNARY_INSTRUCTION(EndBorrow)
UNARY_INSTRUCTION(DestructureStruct)
UNARY_INSTRUCTION(DestructureTuple)
Expand All @@ -2911,6 +2910,39 @@ bool SILParser::parseSILInstruction(SILBuilder &B) {
#undef UNARY_INSTRUCTION
#undef REFCOUNTING_INSTRUCTION

case SILInstructionKind::CondFailInst: {
SILValue condition;
if (parseTypedValueRef(condition, B))
return true;
SILValue message;
SmallVector<SILValue, 4> messageArgs;
if (P.consumeIf(tok::comma)) {
if (parseTypedValueRef(message, B))
return true;

if (P.consumeIf(tok::l_paren)) {
while (true) {
SILValue arg;
if (parseTypedValueRef(arg, B))
return true;
messageArgs.push_back(arg);
if (!P.consumeIf(tok::comma))
break;
}
if (!P.consumeIf(tok::r_paren))
return true;
}
}

if (parseSILDebugLocation(InstLoc, B))
return true;

ResultVal = B.createCondFail(InstLoc, condition, /*inverted*/ false,
message, messageArgs);

break;
}

case SILInstructionKind::IsEscapingClosureInst: {
bool IsObjcVerifcationType = false;
if (parseSILOptional(IsObjcVerifcationType, *this, "objc"))
Expand Down
Loading