418 changes: 418 additions & 0 deletions lldb/packages/Python/lldbsuite/test/lldbreverse.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions lldb/packages/Python/lldbsuite/test/lldbtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@

STOPPED_DUE_TO_WATCHPOINT = "Process should be stopped due to watchpoint"

STOPPED_DUE_TO_HISTORY_BOUNDARY = "Process should be stopped due to history boundary"

DATA_TYPES_DISPLAYED_CORRECTLY = "Data type(s) displayed correctly"

VALID_BREAKPOINT = "Got a valid breakpoint"
Expand Down
8 changes: 6 additions & 2 deletions lldb/source/API/SBProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,10 @@ uint32_t SBProcess::GetAddressByteSize() const {
}

SBError SBProcess::Continue() {
return Continue(RunDirection::eRunForward);
}

SBError SBProcess::Continue(RunDirection direction) {
LLDB_INSTRUMENT_VA(this);

SBError sb_error;
Expand All @@ -574,9 +578,9 @@ SBError SBProcess::Continue() {
process_sp->GetTarget().GetAPIMutex());

if (process_sp->GetTarget().GetDebugger().GetAsyncExecution())
sb_error.ref() = process_sp->Resume();
sb_error.ref() = process_sp->Resume(direction);
else
sb_error.ref() = process_sp->ResumeSynchronous(nullptr);
sb_error.ref() = process_sp->ResumeSynchronous(nullptr, direction);
} else
sb_error = Status::FromErrorString("SBProcess is invalid");

Expand Down
2 changes: 2 additions & 0 deletions lldb/source/API/SBThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ size_t SBThread::GetStopReasonDataCount() {
case eStopReasonInstrumentation:
case eStopReasonProcessorTrace:
case eStopReasonVForkDone:
case eStopReasonHistoryBoundary:
// There is no data for these stop reasons.
return 0;

Expand Down Expand Up @@ -233,6 +234,7 @@ uint64_t SBThread::GetStopReasonDataAtIndex(uint32_t idx) {
case eStopReasonInstrumentation:
case eStopReasonProcessorTrace:
case eStopReasonVForkDone:
case eStopReasonHistoryBoundary:
// There is no data for these stop reasons.
return 0;

Expand Down
3 changes: 2 additions & 1 deletion lldb/source/Interpreter/CommandInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2553,7 +2553,8 @@ bool CommandInterpreter::DidProcessStopAbnormally() const {
const StopReason reason = stop_info->GetStopReason();
if (reason == eStopReasonException ||
reason == eStopReasonInstrumentation ||
reason == eStopReasonProcessorTrace || reason == eStopReasonInterrupt)
reason == eStopReasonProcessorTrace || reason == eStopReasonInterrupt ||
reason == eStopReasonHistoryBoundary)
return true;

if (reason == eStopReasonSignal) {
Expand Down
3 changes: 3 additions & 0 deletions lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ void LogThreadStopInfo(Log &log, const ThreadStopInfo &stop_info,
case eStopReasonProcessorTrace:
log.Printf("%s: %s processor trace", __FUNCTION__, header);
return;
case eStopReasonHistoryBoundary:
log.Printf("%s: %s history boundary", __FUNCTION__, header);
return;
default:
log.Printf("%s: %s invalid stop reason %" PRIu32, __FUNCTION__, header,
static_cast<uint32_t>(stop_info.reason));
Expand Down
9 changes: 8 additions & 1 deletion lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,16 @@ lldb_private::DynamicLoader *ProcessKDP::GetDynamicLoader() {

Status ProcessKDP::WillResume() { return Status(); }

Status ProcessKDP::DoResume() {
Status ProcessKDP::DoResume(RunDirection direction) {
Status error;
Log *log = GetLog(KDPLog::Process);

if (direction == RunDirection::eRunReverse) {
error.SetErrorStringWithFormatv(
"error: {0} does not support reverse execution of processes", GetPluginName());
return error;
}

// Only start the async thread if we try to do any process control
if (!m_async_thread.IsJoinable())
StartAsyncThread();
Expand Down
2 changes: 1 addition & 1 deletion lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class ProcessKDP : public lldb_private::Process {
// Process Control
lldb_private::Status WillResume() override;

lldb_private::Status DoResume() override;
lldb_private::Status DoResume(lldb::RunDirection direction) override;

lldb_private::Status DoHalt(bool &caused_stop) override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,17 @@ ProcessWindows::DoAttachToProcessWithID(lldb::pid_t pid,
return error;
}

Status ProcessWindows::DoResume() {
Status ProcessWindows::DoResume(RunDirection direction) {
Log *log = GetLog(WindowsLog::Process);
llvm::sys::ScopedLock lock(m_mutex);
Status error;

if (direction == RunDirection::eRunReverse) {
error.SetErrorStringWithFormatv(
"error: {0} does not support reverse execution of processes", GetPluginName());
return error;
}

StateType private_state = GetPrivateState();
if (private_state == eStateStopped || private_state == eStateCrashed) {
LLDB_LOG(log, "process {0} is in state {1}. Resuming...",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class ProcessWindows : public Process, public ProcessDebugger {
Status DoAttachToProcessWithID(
lldb::pid_t pid,
const lldb_private::ProcessAttachInfo &attach_info) override;
Status DoResume() override;
Status DoResume(lldb::RunDirection direction) override;
Status DoDestroy() override;
Status DoHalt(bool &caused_stop) override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,20 @@ uint64_t GDBRemoteCommunicationClient::GetRemoteMaxPacketSize() {
return m_max_packet_size;
}

bool GDBRemoteCommunicationClient::GetReverseContinueSupported() {
if (m_supports_reverse_continue == eLazyBoolCalculate) {
GetRemoteQSupported();
}
return m_supports_reverse_continue == eLazyBoolYes;
}

bool GDBRemoteCommunicationClient::GetReverseStepSupported() {
if (m_supports_reverse_step == eLazyBoolCalculate) {
GetRemoteQSupported();
}
return m_supports_reverse_step == eLazyBoolYes;
}

bool GDBRemoteCommunicationClient::QueryNoAckModeSupported() {
if (m_supports_not_sending_acks == eLazyBoolCalculate) {
m_send_acks = true;
Expand Down Expand Up @@ -295,6 +309,8 @@ void GDBRemoteCommunicationClient::ResetDiscoverableSettings(bool did_exec) {
m_supports_qXfer_siginfo_read = eLazyBoolCalculate;
m_supports_augmented_libraries_svr4_read = eLazyBoolCalculate;
m_uses_native_signals = eLazyBoolCalculate;
m_supports_reverse_continue = eLazyBoolCalculate;
m_supports_reverse_step = eLazyBoolCalculate;
m_supports_qProcessInfoPID = true;
m_supports_qfProcessInfo = true;
m_supports_qUserName = true;
Expand Down Expand Up @@ -348,6 +364,8 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
m_supports_memory_tagging = eLazyBoolNo;
m_supports_qSaveCore = eLazyBoolNo;
m_uses_native_signals = eLazyBoolNo;
m_supports_reverse_continue = eLazyBoolNo;
m_supports_reverse_step = eLazyBoolNo;

m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if
// not, we assume no limit
Expand Down Expand Up @@ -401,6 +419,10 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
m_supports_qSaveCore = eLazyBoolYes;
else if (x == "native-signals+")
m_uses_native_signals = eLazyBoolYes;
else if (x == "ReverseContinue+")
m_supports_reverse_continue = eLazyBoolYes;
else if (x == "ReverseStep+")
m_supports_reverse_step = eLazyBoolYes;
// Look for a list of compressions in the features list e.g.
// qXfer:features:read+;PacketSize=20000;qEcho+;SupportedCompressions=zlib-
// deflate,lzma
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,10 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {

bool GetMultiprocessSupported();

bool GetReverseContinueSupported();

bool GetReverseStepSupported();

LazyBool SupportsAllocDeallocMemory() // const
{
// Uncomment this to have lldb pretend the debug server doesn't respond to
Expand Down Expand Up @@ -561,6 +565,8 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
LazyBool m_supports_memory_tagging = eLazyBoolCalculate;
LazyBool m_supports_qSaveCore = eLazyBoolCalculate;
LazyBool m_uses_native_signals = eLazyBoolCalculate;
LazyBool m_supports_reverse_continue = eLazyBoolCalculate;
LazyBool m_supports_reverse_step = eLazyBoolCalculate;

bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1,
m_supports_qUserName : 1, m_supports_qGroupName : 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,7 @@ static const char *GetStopReasonString(StopReason stop_reason) {
return "vforkdone";
case eStopReasonInterrupt:
return "async interrupt";
case eStopReasonHistoryBoundary:
case eStopReasonInstrumentation:
case eStopReasonInvalid:
case eStopReasonPlanComplete:
Expand Down
77 changes: 63 additions & 14 deletions lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ class PluginProperties : public Properties {
}
};

std::chrono::seconds ResumeTimeout() {
return std::chrono::seconds(5);
}

} // namespace

static PluginProperties &GetGlobalPluginProperties() {
Expand Down Expand Up @@ -1180,10 +1184,11 @@ Status ProcessGDBRemote::WillResume() {
return Status();
}
Status ProcessGDBRemote::DoResume() {
Status ProcessGDBRemote::DoResume(RunDirection direction) {
Status error;
Log *log = GetLog(GDBRLog::Process);
LLDB_LOGF(log, "ProcessGDBRemote::Resume()");
LLDB_LOGF(log, "ProcessGDBRemote::Resume(%s)",
direction == RunDirection::eRunForward ? "" : "reverse");
ListenerSP listener_sp(
Listener::MakeListener("gdb-remote.resume-packet-sent"));
Expand All @@ -1197,12 +1202,21 @@ Status ProcessGDBRemote::DoResume() {
StreamString continue_packet;
bool continue_packet_error = false;
if (m_gdb_comm.HasAnyVContSupport()) {
// Number of threads continuing with "c", i.e. continuing without a signal to deliver.
const size_t num_continue_c_tids = m_continue_c_tids.size();
// Number of threads continuing with "C", i.e. continuing with a signal to deliver.
const size_t num_continue_C_tids = m_continue_C_tids.size();
// Number of threads continuing with "s", i.e. single-stepping.
const size_t num_continue_s_tids = m_continue_s_tids.size();
// Number of threads continuing with "S", i.e. single-stepping with a signal to deliver.
const size_t num_continue_S_tids = m_continue_S_tids.size();
if (direction == RunDirection::eRunForward &&
m_gdb_comm.HasAnyVContSupport()) {
std::string pid_prefix;
if (m_gdb_comm.GetMultiprocessSupported())
pid_prefix = llvm::formatv("p{0:x-}.", GetID());
if (m_continue_c_tids.size() == num_threads ||
if (num_continue_c_tids == num_threads ||
(m_continue_c_tids.empty() && m_continue_C_tids.empty() &&
m_continue_s_tids.empty() && m_continue_S_tids.empty())) {
// All threads are continuing
Expand Down Expand Up @@ -1265,14 +1279,11 @@ Status ProcessGDBRemote::DoResume() {
} else
continue_packet_error = true;
if (continue_packet_error) {
if (direction == RunDirection::eRunForward && continue_packet_error) {
// Either no vCont support, or we tried to use part of the vCont packet
// that wasn't supported by the remote GDB server. We need to try and
// make a simple packet that can do our continue
const size_t num_continue_c_tids = m_continue_c_tids.size();
const size_t num_continue_C_tids = m_continue_C_tids.size();
const size_t num_continue_s_tids = m_continue_s_tids.size();
const size_t num_continue_S_tids = m_continue_S_tids.size();
// that wasn't supported by the remote GDB server, or it's the reverse
// direction. We need to try and make a simple packet that can do our
// continue.
if (num_continue_c_tids > 0) {
if (num_continue_c_tids == num_threads) {
// All threads are resuming...
Expand Down Expand Up @@ -1363,9 +1374,41 @@ Status ProcessGDBRemote::DoResume() {
}
}
if (direction == RunDirection::eRunReverse && continue_packet_error) {
if (num_continue_C_tids > 0 || num_continue_S_tids > 0) {
LLDB_LOGF(log, "ProcessGDBRemote::DoResumeReverse: Signals not supported");
return Status::FromErrorString("can't deliver signals while running in reverse");
}
if (num_continue_s_tids > 0) {
if (num_continue_s_tids > 1) {
LLDB_LOGF(log, "ProcessGDBRemote::DoResumeReverse: can't step multiple threads");
return Status::FromErrorString("can't step multiple threads while reverse-stepping");
}
if (!m_gdb_comm.GetReverseStepSupported()) {
LLDB_LOGF(log, "ProcessGDBRemote::DoResumeReverse: target does not support reverse-stepping");
return Status::FromErrorString("target does not support reverse-stepping");
}
m_gdb_comm.SetCurrentThreadForRun(m_continue_s_tids.front());
continue_packet.PutCString("bs");
} else {
if (!m_gdb_comm.GetReverseContinueSupported()) {
LLDB_LOGF(log, "ProcessGDBRemote::DoResumeReverse: target does not support reverse-continue");
return Status::FromErrorString("target does not support reverse-continue");
}
// All threads continue whether requested or not ---
// we can't change how threads ran in the past.
continue_packet.PutCString("bc");
}

continue_packet_error = false;
}

if (continue_packet_error) {
error =
Status::FromErrorString("can't make continue packet for this resume");
return Status::FromErrorString("can't make continue packet for this resume");
} else {
EventSP event_sp;
if (!m_async_thread.IsJoinable()) {
Expand All @@ -1380,7 +1423,7 @@ Status ProcessGDBRemote::DoResume() {
std::make_shared<EventDataBytes>(continue_packet.GetString());
m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue, data_sp);

if (!listener_sp->GetEvent(event_sp, std::chrono::seconds(5))) {
if (!listener_sp->GetEvent(event_sp, ResumeTimeout())) {
error = Status::FromErrorString("Resume timed out.");
LLDB_LOGF(log, "ProcessGDBRemote::DoResume: Resume timed out.");
} else if (event_sp->BroadcasterIs(&m_async_broadcaster)) {
Expand Down Expand Up @@ -1863,6 +1906,10 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(
thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithException(
*thread_sp, description.c_str()));
handled = true;
} else if (reason == "replaylog") {
thread_sp->SetStopInfo(StopInfo::CreateStopReasonHistoryBoundary(
*thread_sp, description.c_str()));
handled = true;
} else if (reason == "exec") {
did_exec = true;
thread_sp->SetStopInfo(
Expand Down Expand Up @@ -2318,6 +2365,8 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
description = std::string(ostr.GetString());
} else if (key.compare("swbreak") == 0 || key.compare("hwbreak") == 0) {
reason = "breakpoint";
} else if (key.compare("replaylog") == 0) {
reason = "replaylog";
} else if (key.compare("library") == 0) {
auto error = LoadModules();
if (error) {
Expand Down
2 changes: 1 addition & 1 deletion lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class ProcessGDBRemote : public Process,
// Process Control
Status WillResume() override;

Status DoResume() override;
Status DoResume(lldb::RunDirection direction) override;

Status DoHalt(bool &caused_stop) override;

Expand Down
9 changes: 7 additions & 2 deletions lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,15 @@ void ScriptedProcess::DidResume() {
m_pid = GetInterface().GetProcessID();
}

Status ScriptedProcess::DoResume() {
Status ScriptedProcess::DoResume(RunDirection direction) {
LLDB_LOGF(GetLog(LLDBLog::Process), "ScriptedProcess::%s resuming process", __FUNCTION__);

return GetInterface().Resume();
if (direction == RunDirection::eRunForward) {
return GetInterface().Resume();
} else {
return Status::FromErrorStringWithFormatv(
"error: {0} does not support reverse execution of processes", GetPluginName());
}
}

Status ScriptedProcess::DoAttach(const ProcessAttachInfo &attach_info) {
Expand Down
2 changes: 1 addition & 1 deletion lldb/source/Plugins/Process/scripted/ScriptedProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class ScriptedProcess : public Process {

void DidResume() override;

Status DoResume() override;
Status DoResume(lldb::RunDirection direction) override;

Status DoAttachToProcessWithID(lldb::pid_t pid,
const ProcessAttachInfo &attach_info) override;
Expand Down
29 changes: 20 additions & 9 deletions lldb/source/Target/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,8 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp,
m_memory_cache(*this), m_allocated_memory_cache(*this),
m_should_detach(false), m_next_event_action_up(), m_public_run_lock(),
m_private_run_lock(), m_currently_handling_do_on_removals(false),
m_resume_requested(false), m_interrupt_tid(LLDB_INVALID_THREAD_ID),
m_resume_requested(false), m_last_run_direction(eRunForward),
m_interrupt_tid(LLDB_INVALID_THREAD_ID),
m_finalizing(false), m_destructing(false),
m_clear_thread_plans_on_stop(false), m_force_next_event_delivery(false),
m_last_broadcast_state(eStateInvalid), m_destroy_in_process(false),
Expand Down Expand Up @@ -845,6 +846,7 @@ bool Process::HandleProcessStateChangedEvent(
switch (thread_stop_reason) {
case eStopReasonInvalid:
case eStopReasonNone:
case eStopReasonHistoryBoundary:
break;

case eStopReasonSignal: {
Expand Down Expand Up @@ -1352,7 +1354,7 @@ void Process::SetPublicState(StateType new_state, bool restarted) {
}
}

Status Process::Resume() {
Status Process::Resume(RunDirection direction) {
Log *log(GetLog(LLDBLog::State | LLDBLog::Process));
LLDB_LOGF(log, "(plugin = %s) -- locking run lock", GetPluginName().data());
if (!m_public_run_lock.TrySetRunning()) {
Expand All @@ -1361,15 +1363,15 @@ Status Process::Resume() {
return Status::FromErrorString(
"Resume request failed - process still running.");
}
Status error = PrivateResume();
Status error = PrivateResume(direction);
if (!error.Success()) {
// Undo running state change
m_public_run_lock.SetStopped();
}
return error;
}

Status Process::ResumeSynchronous(Stream *stream) {
Status Process::ResumeSynchronous(Stream *stream, RunDirection direction) {
Log *log(GetLog(LLDBLog::State | LLDBLog::Process));
LLDB_LOGF(log, "Process::ResumeSynchronous -- locking run lock");
if (!m_public_run_lock.TrySetRunning()) {
Expand All @@ -1382,7 +1384,7 @@ Status Process::ResumeSynchronous(Stream *stream) {
Listener::MakeListener(ResumeSynchronousHijackListenerName.data()));
HijackProcessEvents(listener_sp);

Status error = PrivateResume();
Status error = PrivateResume(direction);
if (error.Success()) {
StateType state =
WaitForProcessToStop(std::nullopt, nullptr, true, listener_sp, stream,
Expand Down Expand Up @@ -3239,7 +3241,7 @@ Status Process::ConnectRemote(llvm::StringRef remote_url) {
return error;
}

Status Process::PrivateResume() {
Status Process::PrivateResume(RunDirection direction) {
Log *log(GetLog(LLDBLog::Process | LLDBLog::Step));
LLDB_LOGF(log,
"Process::PrivateResume() m_stop_id = %u, public state: %s "
Expand All @@ -3255,6 +3257,15 @@ Status Process::PrivateResume() {
if (!GetModID().IsLastResumeForUserExpression())
ResetExtendedCrashInfoDict();

if (m_last_run_direction != direction) {
// In the future we might want to support mixed-direction plans,
// e.g. a forward step-over stops at a breakpoint, the user does
// a reverse-step, then disables the breakpoint and continues forward.
// This code will need to be changed to support that.
m_thread_list.DiscardThreadPlans();
m_last_run_direction = direction;
}

Status error(WillResume());
// Tell the process it is about to resume before the thread list
if (error.Success()) {
Expand All @@ -3272,7 +3283,7 @@ Status Process::PrivateResume() {
"Process::PrivateResume PreResumeActions failed, not resuming.");
} else {
m_mod_id.BumpResumeID();
error = DoResume();
error = DoResume(direction);
if (error.Success()) {
DidResume();
m_thread_list.DidResume();
Expand Down Expand Up @@ -3735,7 +3746,7 @@ bool Process::ShouldBroadcastEvent(Event *event_ptr) {
"from state: %s",
static_cast<void *>(event_ptr), StateAsCString(state));
ProcessEventData::SetRestartedInEvent(event_ptr, true);
PrivateResume();
PrivateResume(m_last_run_direction);
}
} else {
return_value = true;
Expand Down Expand Up @@ -4346,7 +4357,7 @@ void Process::ProcessEventData::DoOnRemoval(Event *event_ptr) {
SetRestarted(true);
// Use the private resume method here, since we aren't changing the run
// lock state.
process_sp->PrivateResume();
process_sp->PrivateResume(process_sp->m_last_run_direction);
} else {
bool hijacked = process_sp->IsHijackedForEvent(eBroadcastBitStateChanged) &&
!process_sp->StateChangedIsHijackedForSynchronousResume();
Expand Down
29 changes: 29 additions & 0 deletions lldb/source/Target/StopInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,30 @@ class StopInfoProcessorTrace : public StopInfo {
}
};

// StopInfoHistoryBoundary

class StopInfoHistoryBoundary : public StopInfo {
public:
StopInfoHistoryBoundary(Thread &thread, const char *description)
: StopInfo(thread, LLDB_INVALID_UID) {
if (description)
SetDescription(description);
}

~StopInfoHistoryBoundary() override = default;

StopReason GetStopReason() const override {
return eStopReasonHistoryBoundary;
}

const char *GetDescription() override {
if (m_description.empty())
return "history boundary";
else
return m_description.c_str();
}
};

// StopInfoThreadPlan

class StopInfoThreadPlan : public StopInfo {
Expand Down Expand Up @@ -1439,6 +1463,11 @@ StopInfoSP StopInfo::CreateStopReasonProcessorTrace(Thread &thread,
return StopInfoSP(new StopInfoProcessorTrace(thread, description));
}

StopInfoSP StopInfo::CreateStopReasonHistoryBoundary(Thread &thread,
const char *description) {
return StopInfoSP(new StopInfoHistoryBoundary(thread, description));
}

StopInfoSP StopInfo::CreateStopReasonWithExec(Thread &thread) {
return StopInfoSP(new StopInfoExec(thread));
}
Expand Down
8 changes: 6 additions & 2 deletions lldb/source/Target/Thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -624,10 +624,12 @@ void Thread::SetupForResume() {
// what the current plan is.

lldb::RegisterContextSP reg_ctx_sp(GetRegisterContext());
if (reg_ctx_sp) {
ProcessSP process_sp(GetProcess());
if (reg_ctx_sp && process_sp &&
process_sp->GetLastRunDirection() == eRunForward) {
const addr_t thread_pc = reg_ctx_sp->GetPC();
BreakpointSiteSP bp_site_sp =
GetProcess()->GetBreakpointSiteList().FindByAddress(thread_pc);
process_sp->GetBreakpointSiteList().FindByAddress(thread_pc);
if (bp_site_sp) {
// Note, don't assume there's a ThreadPlanStepOverBreakpoint, the
// target may not require anything special to step over a breakpoint.
Expand Down Expand Up @@ -1732,6 +1734,8 @@ std::string Thread::StopReasonAsString(lldb::StopReason reason) {
return "processor trace";
case eStopReasonInterrupt:
return "async interrupt";
case eStopReasonHistoryBoundary:
return "history boundary";
}

return "StopReason = " + std::to_string(reason);
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/functionalities/reverse-execution/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
C_SOURCES := main.c

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import lldb
import time
import unittest
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from lldbsuite.test.gdbclientutils import *
from lldbsuite.test.lldbreverse import ReverseTestBase
from lldbsuite.test import lldbutil


class TestReverseContinueBreakpoints(ReverseTestBase):
NO_DEBUG_INFO_TESTCASE = True

def test_reverse_continue(self):
self.reverse_continue_internal(async_mode=False)

def test_reverse_continue_async(self):
self.reverse_continue_internal(async_mode=True)

def reverse_continue_internal(self, async_mode):
target, process, initial_threads = self.setup_recording(async_mode)

# Reverse-continue. We'll stop at the point where we started recording.
status = process.Continue(lldb.eRunReverse)
self.assertSuccess(status)
self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateStopped])
self.expect(
"thread list",
STOPPED_DUE_TO_HISTORY_BOUNDARY,
substrs=["stopped", "stop reason = history boundary"],
)

# Continue forward normally until the target exits.
status = process.Continue()
self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateExited])
self.assertSuccess(status)
self.assertState(process.GetState(), lldb.eStateExited)
self.assertEqual(process.GetExitStatus(), 0)

def test_reverse_continue_breakpoint(self):
self.reverse_continue_breakpoint_internal(async_mode=False)

def test_reverse_continue_breakpoint_async(self):
self.reverse_continue_breakpoint_internal(async_mode=True)

def reverse_continue_breakpoint_internal(self, async_mode):
target, process, initial_threads = self.setup_recording(async_mode)

# Reverse-continue to the function "trigger_breakpoint".
trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", None)
status = process.Continue(lldb.eRunReverse)
self.assertSuccess(status)
self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateStopped])
threads_now = lldbutil.get_threads_stopped_at_breakpoint(process, trigger_bkpt)
self.assertEqual(threads_now, initial_threads)

def test_reverse_continue_skip_breakpoint(self):
self.reverse_continue_skip_breakpoint_internal(async_mode=False)

def test_reverse_continue_skip_breakpoint_async(self):
self.reverse_continue_skip_breakpoint_internal(async_mode=True)

def reverse_continue_skip_breakpoint_internal(self, async_mode):
target, process, initial_threads = self.setup_recording(async_mode)

# Reverse-continue over a breakpoint at "trigger_breakpoint" whose
# condition is false.
# This tests that we continue in the correct direction after hitting
# the breakpoint.
trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", None)
trigger_bkpt.SetCondition("false_condition")
status = process.Continue(lldb.eRunReverse)
self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateStopped])
self.assertSuccess(status)
self.expect(
"thread list",
STOPPED_DUE_TO_HISTORY_BOUNDARY,
substrs=["stopped", "stop reason = history boundary"],
)

def setup_recording(self, async_mode):
"""
Record execution of code between "start_recording" and "stop_recording" breakpoints.
Returns with the target stopped at "stop_recording", with recording disabled,
ready to reverse-execute.
"""
self.build()
target = self.dbg.CreateTarget("")
process = self.connect(target)

# Record execution from the start of the function "start_recording"
# to the start of the function "stop_recording". We want to keep the
# interval that we record as small as possible to minimize the run-time
# of our single-stepping recorder.
start_recording_bkpt = target.BreakpointCreateByName("start_recording", None)
initial_threads = lldbutil.continue_to_breakpoint(process, start_recording_bkpt)
self.assertEqual(len(initial_threads), 1)
target.BreakpointDelete(start_recording_bkpt.GetID())
self.start_recording()
stop_recording_bkpt = target.BreakpointCreateByName("stop_recording", None)
lldbutil.continue_to_breakpoint(process, stop_recording_bkpt)
target.BreakpointDelete(stop_recording_bkpt.GetID())
self.stop_recording()

self.dbg.SetAsync(async_mode)
self.expect_async_state_changes(async_mode, process, [lldb.eStateStopped])

return target, process, initial_threads

def expect_async_state_changes(self, async_mode, process, states):
if not async_mode:
return
listener = self.dbg.GetListener()
lldbutil.expect_state_changes(self, listener, process, states)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import lldb
import unittest
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from lldbsuite.test import lldbutil


class TestReverseContinueNotSupported(TestBase):
NO_DEBUG_INFO_TESTCASE = True

def test_reverse_continue_not_supported(self):
self.build()
exe = self.getBuildArtifact("a.out")
target = self.dbg.CreateTarget(exe)
self.assertTrue(target, VALID_TARGET)

main_bkpt = target.BreakpointCreateByName("main", None)
self.assertTrue(main_bkpt, VALID_BREAKPOINT)

process = target.LaunchSimple(None, None, self.get_process_working_directory())
self.assertTrue(process, PROCESS_IS_VALID)

# This will fail gracefully.
status = process.Continue(lldb.eRunReverse)
self.assertFailure(status, "target does not support reverse-continue")

status = process.Continue()
self.assertSuccess(status)
self.assertState(process.GetState(), lldb.eStateExited)
self.assertEqual(process.GetExitStatus(), 0)
14 changes: 14 additions & 0 deletions lldb/test/API/functionalities/reverse-execution/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
volatile int false_condition = 0;

static void start_recording() {}

static void trigger_breakpoint() {}

static void stop_recording() {}

int main() {
start_recording();
trigger_breakpoint();
stop_recording();
return 0;
}
3 changes: 3 additions & 0 deletions lldb/tools/lldb-dap/JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,9 @@ llvm::json::Value CreateThreadStopped(lldb::SBThread &thread,
case lldb::eStopReasonProcessorTrace:
body.try_emplace("reason", "processor trace");
break;
case lldb::eStopReasonHistoryBoundary:
body.try_emplace("reason", "history boundary");
break;
case lldb::eStopReasonSignal:
case lldb::eStopReasonException:
body.try_emplace("reason", "exception");
Expand Down
1 change: 1 addition & 0 deletions lldb/tools/lldb-dap/LLDBUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ bool ThreadHasStopReason(lldb::SBThread &thread) {
case lldb::eStopReasonVFork:
case lldb::eStopReasonVForkDone:
case lldb::eStopReasonInterrupt:
case lldb::eStopReasonHistoryBoundary:
return true;
case lldb::eStopReasonThreadExiting:
case lldb::eStopReasonInvalid:
Expand Down
90 changes: 90 additions & 0 deletions llvm/docs/Coroutines.rst
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ lowered to a constant representing the size required for the coroutine frame.
The `coro.begin`_ intrinsic initializes the coroutine frame and returns the
coroutine handle. The second parameter of `coro.begin` is given a block of memory
to be used if the coroutine frame needs to be allocated dynamically.

The `coro.id`_ intrinsic serves as coroutine identity useful in cases when the
`coro.begin`_ intrinsic get duplicated by optimization passes such as
jump-threading.
Expand Down Expand Up @@ -749,6 +750,65 @@ and python iterator `__next__` would look like:
return *(int*)coro.promise(hdl, 4, false);
}

Custom ABIs and Plugin Libraries
--------------------------------

Plugin libraries can extend coroutine lowering enabling a wide variety of users
to utilize the coroutine transformation passes. An existing coroutine lowering
is extended by:

#. defining custom ABIs that inherit from the existing ABIs,
#. give a list of generators for the custom ABIs when constructing the `CoroSplit`_ pass, and
#. use `coro.begin.custom.abi`_ in place of `coro.begin`_ that has an additional parameter for the index of the generator/ABI to be used for the coroutine.

A custom ABI overriding the SwitchABI's materialization looks like:

.. code-block:: c++

class CustomSwitchABI : public coro::SwitchABI {
public:
CustomSwitchABI(Function &F, coro::Shape &S)
: coro::SwitchABI(F, S, ExtraMaterializable) {}
};

Giving a list of custom ABI generators while constructing the `CoroSplit`
pass looks like:

.. code-block:: c++

CoroSplitPass::BaseABITy GenCustomABI = [](Function &F, coro::Shape &S) {
return std::make_unique<CustomSwitchABI>(F, S);
};

CGSCCPassManager CGPM;
CGPM.addPass(CoroSplitPass({GenCustomABI}));

The LLVM IR for a coroutine using a Coroutine with a custom ABI looks like:

.. code-block:: llvm
define ptr @f(i32 %n) presplitcoroutine_custom_abi {
entry:
%id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
%size = call i32 @llvm.coro.size.i32()
%alloc = call ptr @malloc(i32 %size)
%hdl = call noalias ptr @llvm.coro.begin.custom.abi(token %id, ptr %alloc, i32 0)
br label %loop
loop:
%n.val = phi i32 [ %n, %entry ], [ %inc, %loop ]
%inc = add nsw i32 %n.val, 1
call void @print(i32 %n.val)
%0 = call i8 @llvm.coro.suspend(token none, i1 false)
switch i8 %0, label %suspend [i8 0, label %loop
i8 1, label %cleanup]
cleanup:
%mem = call ptr @llvm.coro.free(token %id, ptr %hdl)
call void @free(ptr %mem)
br label %suspend
suspend:
%unused = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none)
ret ptr %hdl
}
Intrinsics
==========
Expand Down Expand Up @@ -1007,6 +1067,36 @@ with small positive and negative offsets).

A frontend should emit exactly one `coro.begin` intrinsic per coroutine.

.. _coro.begin.custom.abi:

'llvm.coro.begin.custom.abi' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
::

declare ptr @llvm.coro.begin.custom.abi(token <id>, ptr <mem>, i32)

Overview:
"""""""""

The '``llvm.coro.begin.custom.abi``' intrinsic is used in place of the
`coro.begin` intrinsic that has an additional parameter to specify the custom
ABI for the coroutine. The return is identical to that of the `coro.begin`
intrinsic.

Arguments:
""""""""""

The first and second arguments are identical to those of the `coro.begin`
intrinsic.

The third argument is an i32 index of the generator list given to the
`CoroSplit` pass specifying the custom ABI generator lor this coroutine.

Semantics:
""""""""""

The semantics are identical to those of the `coro.begin` intrinsic.

.. _coro.free:

'llvm.coro.free' Intrinsic
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//===- DependencyGraph.h ----------------------------------*- C++ -*-===//
//===- DependencyGraph.h ----------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
Expand Down Expand Up @@ -96,9 +96,6 @@ class DGNode {
// TODO: Use a PointerIntPair for SubclassID and I.
/// For isa/dyn_cast etc.
DGNodeID SubclassID;
// TODO: Move MemPreds to MemDGNode.
/// Memory predecessors.
DenseSet<MemDGNode *> MemPreds;

DGNode(Instruction *I, DGNodeID ID) : I(I), SubclassID(ID) {}
friend class MemDGNode; // For constructor.
Expand Down Expand Up @@ -170,17 +167,6 @@ class DGNode {
}

Instruction *getInstruction() const { return I; }
void addMemPred(MemDGNode *PredN) { MemPreds.insert(PredN); }
/// \Returns all memory dependency predecessors.
iterator_range<DenseSet<MemDGNode *>::const_iterator> memPreds() const {
return make_range(MemPreds.begin(), MemPreds.end());
}
/// \Returns true if there is a memory dependency N->this.
bool hasMemPred(DGNode *N) const {
if (auto *MN = dyn_cast<MemDGNode>(N))
return MemPreds.count(MN);
return false;
}

#ifndef NDEBUG
virtual void print(raw_ostream &OS, bool PrintDeps = true) const;
Expand All @@ -198,6 +184,9 @@ class DGNode {
class MemDGNode final : public DGNode {
MemDGNode *PrevMemN = nullptr;
MemDGNode *NextMemN = nullptr;
/// Memory predecessors.
DenseSet<MemDGNode *> MemPreds;
friend class PredIterator; // For MemPreds.

void setNextNode(MemDGNode *N) { NextMemN = N; }
void setPrevNode(MemDGNode *N) { PrevMemN = N; }
Expand All @@ -222,6 +211,21 @@ class MemDGNode final : public DGNode {
MemDGNode *getPrevNode() const { return PrevMemN; }
/// \Returns the next Mem DGNode in instruction order.
MemDGNode *getNextNode() const { return NextMemN; }
/// Adds the mem dependency edge PredN->this.
void addMemPred(MemDGNode *PredN) { MemPreds.insert(PredN); }
/// \Returns true if there is a memory dependency N->this.
bool hasMemPred(DGNode *N) const {
if (auto *MN = dyn_cast<MemDGNode>(N))
return MemPreds.count(MN);
return false;
}
/// \Returns all memory dependency predecessors. Used by tests.
iterator_range<DenseSet<MemDGNode *>::const_iterator> memPreds() const {
return make_range(MemPreds.begin(), MemPreds.end());
}
#ifndef NDEBUG
virtual void print(raw_ostream &OS, bool PrintDeps = true) const override;
#endif // NDEBUG
};

/// Convenience builders for a MemDGNode interval.
Expand Down Expand Up @@ -266,7 +270,7 @@ class DependencyGraph {

/// Go through all mem nodes in \p SrcScanRange and try to add dependencies to
/// \p DstN.
void scanAndAddDeps(DGNode &DstN, const Interval<MemDGNode> &SrcScanRange);
void scanAndAddDeps(MemDGNode &DstN, const Interval<MemDGNode> &SrcScanRange);

public:
DependencyGraph(AAResults &AA)
Expand Down
5 changes: 4 additions & 1 deletion llvm/lib/Target/NVPTX/NVPTXInstrInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ def hasVote : Predicate<"Subtarget->hasVote()">;
def hasDouble : Predicate<"Subtarget->hasDouble()">;
def hasLDG : Predicate<"Subtarget->hasLDG()">;
def hasLDU : Predicate<"Subtarget->hasLDU()">;
def hasPTXASUnreachableBug : Predicate<"Subtarget->hasPTXASUnreachableBug()">;
def noPTXASUnreachableBug : Predicate<"!Subtarget->hasPTXASUnreachableBug()">;

def doF32FTZ : Predicate<"useF32FTZ()">;
def doNoF32FTZ : Predicate<"!useF32FTZ()">;
Expand Down Expand Up @@ -3736,9 +3738,10 @@ def Callseq_End :
[(callseq_end timm:$amt1, timm:$amt2)]>;

// trap instruction
def trapinst : NVPTXInst<(outs), (ins), "trap;", [(trap)]>, Requires<[noPTXASUnreachableBug]>;
// Emit an `exit` as well to convey to ptxas that `trap` exits the CFG.
// This won't be necessary in a future version of ptxas.
def trapinst : NVPTXInst<(outs), (ins), "trap; exit;", [(trap)]>;
def trapexitinst : NVPTXInst<(outs), (ins), "trap; exit;", [(trap)]>, Requires<[hasPTXASUnreachableBug]>;
// brkpt instruction
def debugtrapinst : NVPTXInst<(outs), (ins), "brkpt;", [(debugtrap)]>;

Expand Down
8 changes: 8 additions & 0 deletions llvm/lib/Target/NVPTX/NVPTXSubtarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ class NVPTXSubtarget : public NVPTXGenSubtargetInfo {
bool hasDotInstructions() const {
return SmVersion >= 61 && PTXVersion >= 50;
}
// Prior to CUDA 12.3 ptxas did not recognize that the trap instruction
// terminates a basic block. Instead, it would assume that control flow
// continued to the next instruction. The next instruction could be in the
// block that's lexically below it. This would lead to a phantom CFG edges
// being created within ptxas. This issue was fixed in CUDA 12.3. Thus, when
// PTX ISA versions 8.3+ we can confidently say that the bug will not be
// present.
bool hasPTXASUnreachableBug() const { return PTXVersion < 83; }
bool hasCvtaParam() const { return SmVersion >= 70 && PTXVersion >= 77; }
unsigned int getFullSmVersion() const { return FullSmVersion; }
unsigned int getSmVersion() const { return getFullSmVersion() / 10; }
Expand Down
10 changes: 7 additions & 3 deletions llvm/lib/Target/NVPTX/NVPTXTargetMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,13 @@ void NVPTXPassConfig::addIRPasses() {
addPass(createSROAPass());
}

const auto &Options = getNVPTXTargetMachine().Options;
addPass(createNVPTXLowerUnreachablePass(Options.TrapUnreachable,
Options.NoTrapAfterNoreturn));
if (ST.hasPTXASUnreachableBug()) {
// Run LowerUnreachable to WAR a ptxas bug. See the commit description of
// 1ee4d880e8760256c606fe55b7af85a4f70d006d for more details.
const auto &Options = getNVPTXTargetMachine().Options;
addPass(createNVPTXLowerUnreachablePass(Options.TrapUnreachable,
Options.NoTrapAfterNoreturn));
}
}

bool NVPTXPassConfig::addInstSelector() {
Expand Down
23 changes: 11 additions & 12 deletions llvm/lib/Transforms/Instrumentation/PGOCtxProfLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,29 +154,28 @@ CtxInstrumentationLowerer::CtxInstrumentationLowerer(Module &M,
StartCtx = cast<Function>(
M.getOrInsertFunction(
CompilerRtAPINames::StartCtx,
FunctionType::get(ContextNodeTy->getPointerTo(),
{ContextRootTy->getPointerTo(), /*ContextRoot*/
FunctionType::get(PointerTy,
{PointerTy, /*ContextRoot*/
I64Ty, /*Guid*/ I32Ty,
/*NumCounters*/ I32Ty /*NumCallsites*/},
false))
.getCallee());
GetCtx = cast<Function>(
M.getOrInsertFunction(CompilerRtAPINames::GetCtx,
FunctionType::get(ContextNodeTy->getPointerTo(),
FunctionType::get(PointerTy,
{PointerTy, /*Callee*/
I64Ty, /*Guid*/
I32Ty, /*NumCounters*/
I32Ty}, /*NumCallsites*/
false))
.getCallee());
ReleaseCtx = cast<Function>(
M.getOrInsertFunction(
CompilerRtAPINames::ReleaseCtx,
FunctionType::get(Type::getVoidTy(M.getContext()),
{
ContextRootTy->getPointerTo(), /*ContextRoot*/
},
false))
M.getOrInsertFunction(CompilerRtAPINames::ReleaseCtx,
FunctionType::get(Type::getVoidTy(M.getContext()),
{
PointerTy, /*ContextRoot*/
},
false))
.getCallee());

// Declare the TLSes we will need to use.
Expand Down Expand Up @@ -264,7 +263,7 @@ bool CtxInstrumentationLowerer::lowerFunction(Function &F) {
auto *Index = Builder.CreateAnd(CtxAsInt, Builder.getInt64(1));
// The GEPs corresponding to that index, in the respective TLS.
ExpectedCalleeTLSAddr = Builder.CreateGEP(
Builder.getInt8Ty()->getPointerTo(),
PointerType::getUnqual(F.getContext()),
Builder.CreateThreadLocalAddress(ExpectedCalleeTLS), {Index});
CallsiteInfoTLSAddr = Builder.CreateGEP(
Builder.getInt32Ty(),
Expand All @@ -277,7 +276,7 @@ bool CtxInstrumentationLowerer::lowerFunction(Function &F) {
// with counters) stays the same.
RealContext = Builder.CreateIntToPtr(
Builder.CreateAnd(CtxAsInt, Builder.getInt64(-2)),
ThisContextType->getPointerTo());
PointerType::getUnqual(F.getContext()));
I.eraseFromParent();
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ PredIterator::value_type PredIterator::operator*() {
// or a mem predecessor.
if (OpIt != OpItE)
return DAG->getNode(cast<Instruction>((Value *)*OpIt));
assert(MemIt != cast<MemDGNode>(N)->memPreds().end() &&
// It's a MemDGNode with OpIt == end, so we need to use MemIt.
assert(MemIt != cast<MemDGNode>(N)->MemPreds.end() &&
"Cant' dereference end iterator!");
return *MemIt;
}
Expand All @@ -45,7 +46,8 @@ PredIterator &PredIterator::operator++() {
OpIt = skipNonInstr(OpIt, OpItE);
return *this;
}
assert(MemIt != cast<MemDGNode>(N)->memPreds().end() && "Already at end!");
// It's a MemDGNode with OpIt == end, so we need to increment MemIt.
assert(MemIt != cast<MemDGNode>(N)->MemPreds.end() && "Already at end!");
++MemIt;
return *this;
}
Expand All @@ -57,10 +59,14 @@ bool PredIterator::operator==(const PredIterator &Other) const {
}

#ifndef NDEBUG
void DGNode::print(raw_ostream &OS, bool PrintDeps) const {
void DGNode::print(raw_ostream &OS, bool PrintDeps) const { I->dumpOS(OS); }
void DGNode::dump() const {
print(dbgs());
dbgs() << "\n";
}
void MemDGNode::print(raw_ostream &OS, bool PrintDeps) const {
I->dumpOS(OS);
if (PrintDeps) {
OS << "\n";
// Print memory preds.
static constexpr const unsigned Indent = 4;
for (auto *Pred : MemPreds) {
Expand All @@ -70,10 +76,6 @@ void DGNode::print(raw_ostream &OS, bool PrintDeps) const {
}
}
}
void DGNode::dump() const {
print(dbgs());
dbgs() << "\n";
}
#endif // NDEBUG

Interval<MemDGNode>
Expand Down Expand Up @@ -179,7 +181,7 @@ bool DependencyGraph::hasDep(Instruction *SrcI, Instruction *DstI) {
llvm_unreachable("Unknown DependencyType enum");
}

void DependencyGraph::scanAndAddDeps(DGNode &DstN,
void DependencyGraph::scanAndAddDeps(MemDGNode &DstN,
const Interval<MemDGNode> &SrcScanRange) {
assert(isa<MemDGNode>(DstN) &&
"DstN is the mem dep destination, so it must be mem");
Expand Down
103 changes: 81 additions & 22 deletions llvm/test/CodeGen/NVPTX/unreachable.ll
Original file line number Diff line number Diff line change
@@ -1,48 +1,107 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
; RUN: llc < %s -march=nvptx -mcpu=sm_20 -verify-machineinstrs -trap-unreachable=false \
; RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NOTRAP
; RUN: | FileCheck %s --check-prefixes=CHECK,NO-TRAP-UNREACHABLE
; RUN: llc < %s -march=nvptx64 -mcpu=sm_20 -verify-machineinstrs -trap-unreachable=false \
; RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NOTRAP
; RUN: | FileCheck %s --check-prefixes=CHECK,NO-TRAP-UNREACHABLE
; RUN: llc < %s -march=nvptx -mcpu=sm_20 -verify-machineinstrs -trap-unreachable -no-trap-after-noreturn \
; RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NOTRAP
; RUN: | FileCheck %s --check-prefixes=CHECK,NO-TRAP-AFTER-NORETURN
; RUN: llc < %s -march=nvptx64 -mcpu=sm_20 -verify-machineinstrs -trap-unreachable -no-trap-after-noreturn \
; RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NOTRAP
; RUN: | FileCheck %s --check-prefixes=CHECK,NO-TRAP-AFTER-NORETURN
; RUN: llc < %s -march=nvptx -mcpu=sm_20 -verify-machineinstrs -trap-unreachable -no-trap-after-noreturn=false \
; RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-TRAP
; RUN: | FileCheck %s --check-prefixes=CHECK,TRAP
; RUN: llc < %s -march=nvptx64 -mcpu=sm_20 -verify-machineinstrs -trap-unreachable -no-trap-after-noreturn=false \
; RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-TRAP
; RUN: | FileCheck %s --check-prefixes=CHECK,TRAP
; RUN: llc < %s -march=nvptx64 -mcpu=sm_20 -verify-machineinstrs -trap-unreachable -mattr=+ptx83 \
; RUN: | FileCheck %s --check-prefixes=BUG-FIXED
; RUN: %if ptxas && !ptxas-12.0 %{ llc < %s -march=nvptx -mcpu=sm_20 -verify-machineinstrs | %ptxas-verify %}
; RUN: %if ptxas %{ llc < %s -march=nvptx64 -mcpu=sm_20 -verify-machineinstrs | %ptxas-verify %}

; CHECK: .extern .func throw
target triple = "nvptx-unknown-cuda"

declare void @throw() #0
declare void @llvm.trap() #0

; CHECK-LABEL: .entry kernel_func
define void @kernel_func() {
; CHECK: call.uni
; CHECK: throw,
; NO-TRAP-UNREACHABLE-LABEL: kernel_func(
; NO-TRAP-UNREACHABLE: {
; NO-TRAP-UNREACHABLE-EMPTY:
; NO-TRAP-UNREACHABLE-EMPTY:
; NO-TRAP-UNREACHABLE-NEXT: // %bb.0:
; NO-TRAP-UNREACHABLE-NEXT: { // callseq 0, 0
; NO-TRAP-UNREACHABLE-NEXT: call.uni
; NO-TRAP-UNREACHABLE-NEXT: throw,
; NO-TRAP-UNREACHABLE-NEXT: (
; NO-TRAP-UNREACHABLE-NEXT: );
; NO-TRAP-UNREACHABLE-NEXT: } // callseq 0
; NO-TRAP-UNREACHABLE-NEXT: // begin inline asm
; NO-TRAP-UNREACHABLE-NEXT: exit;
; NO-TRAP-UNREACHABLE-NEXT: // end inline asm
;
; NO-TRAP-AFTER-NORETURN-LABEL: kernel_func(
; NO-TRAP-AFTER-NORETURN: {
; NO-TRAP-AFTER-NORETURN-EMPTY:
; NO-TRAP-AFTER-NORETURN-EMPTY:
; NO-TRAP-AFTER-NORETURN-NEXT: // %bb.0:
; NO-TRAP-AFTER-NORETURN-NEXT: { // callseq 0, 0
; NO-TRAP-AFTER-NORETURN-NEXT: call.uni
; NO-TRAP-AFTER-NORETURN-NEXT: throw,
; NO-TRAP-AFTER-NORETURN-NEXT: (
; NO-TRAP-AFTER-NORETURN-NEXT: );
; NO-TRAP-AFTER-NORETURN-NEXT: } // callseq 0
; NO-TRAP-AFTER-NORETURN-NEXT: // begin inline asm
; NO-TRAP-AFTER-NORETURN-NEXT: exit;
; NO-TRAP-AFTER-NORETURN-NEXT: // end inline asm
; NO-TRAP-AFTER-NORETURN-NEXT: trap; exit;
;
; TRAP-LABEL: kernel_func(
; TRAP: {
; TRAP-EMPTY:
; TRAP-EMPTY:
; TRAP-NEXT: // %bb.0:
; TRAP-NEXT: { // callseq 0, 0
; TRAP-NEXT: call.uni
; TRAP-NEXT: throw,
; TRAP-NEXT: (
; TRAP-NEXT: );
; TRAP-NEXT: } // callseq 0
; TRAP-NEXT: trap; exit;
;
; BUG-FIXED-LABEL: kernel_func(
; BUG-FIXED: {
; BUG-FIXED-EMPTY:
; BUG-FIXED-EMPTY:
; BUG-FIXED-NEXT: // %bb.0:
; BUG-FIXED-NEXT: { // callseq 0, 0
; BUG-FIXED-NEXT: call.uni
; BUG-FIXED-NEXT: throw,
; BUG-FIXED-NEXT: (
; BUG-FIXED-NEXT: );
; BUG-FIXED-NEXT: } // callseq 0
; BUG-FIXED-NEXT: trap;
call void @throw()
; CHECK-TRAP-NOT: exit;
; CHECK-TRAP: trap;
; CHECK-NOTRAP-NOT: trap;
; CHECK: exit;
unreachable
}

; CHECK-LABEL: kernel_func_2
define void @kernel_func_2() {
; CHECK: trap; exit;
; CHECK-LABEL: kernel_func_2(
; CHECK: {
; CHECK-EMPTY:
; CHECK-EMPTY:
; CHECK-NEXT: // %bb.0:
; CHECK-NEXT: trap; exit;
;
; BUG-FIXED-LABEL: kernel_func_2(
; BUG-FIXED: {
; BUG-FIXED-EMPTY:
; BUG-FIXED-EMPTY:
; BUG-FIXED-NEXT: // %bb.0:
; BUG-FIXED-NEXT: trap;
call void @llvm.trap()

;; Make sure we avoid emitting two trap instructions.
; CHECK-NOT: trap;
; CHECK-NOT: exit;
; Make sure we avoid emitting two trap instructions.
unreachable
}

attributes #0 = { noreturn }


!nvvm.annotations = !{!1}

!1 = !{ptr @kernel_func, !"kernel", i32 1}
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ struct DependencyGraphTest : public testing::Test {
return *AA;
}
/// \Returns true if there is a dependency: SrcN->DstN.
bool dependency(sandboxir::DGNode *SrcN, sandboxir::DGNode *DstN) {
const auto &Preds = DstN->memPreds();
auto It = find(Preds, SrcN);
return It != Preds.end();
bool memDependency(sandboxir::DGNode *SrcN, sandboxir::DGNode *DstN) {
if (auto *MemDstN = dyn_cast<sandboxir::MemDGNode>(DstN))
return MemDstN->hasMemPred(SrcN);
return false;
}
};

Expand Down Expand Up @@ -230,9 +230,10 @@ define void @foo(ptr %ptr, i8 %v0, i8 %v1) {
EXPECT_EQ(Span.top(), &*BB->begin());
EXPECT_EQ(Span.bottom(), BB->getTerminator());

sandboxir::DGNode *N0 = DAG.getNode(S0);
sandboxir::DGNode *N1 = DAG.getNode(S1);
sandboxir::DGNode *N2 = DAG.getNode(Ret);
auto *N0 = cast<sandboxir::MemDGNode>(DAG.getNode(S0));
auto *N1 = cast<sandboxir::MemDGNode>(DAG.getNode(S1));
auto *N2 = DAG.getNode(Ret);

// Check getInstruction().
EXPECT_EQ(N0->getInstruction(), S0);
EXPECT_EQ(N1->getInstruction(), S1);
Expand All @@ -247,7 +248,7 @@ define void @foo(ptr %ptr, i8 %v0, i8 %v1) {
// Check memPreds().
EXPECT_TRUE(N0->memPreds().empty());
EXPECT_THAT(N1->memPreds(), testing::ElementsAre(N0));
EXPECT_TRUE(N2->memPreds().empty());
EXPECT_TRUE(N2->preds(DAG).empty());
}

TEST_F(DependencyGraphTest, Preds) {
Expand Down Expand Up @@ -399,12 +400,14 @@ define void @foo(ptr %ptr, i8 %v0, i8 %v1) {
sandboxir::DependencyGraph DAG(getAA(*LLVMF));
DAG.extend({&*BB->begin(), BB->getTerminator()});
auto It = BB->begin();
auto *Store0N = DAG.getNode(cast<sandboxir::StoreInst>(&*It++));
auto *Store1N = DAG.getNode(cast<sandboxir::StoreInst>(&*It++));
auto *Store0N = cast<sandboxir::MemDGNode>(
DAG.getNode(cast<sandboxir::StoreInst>(&*It++)));
auto *Store1N = cast<sandboxir::MemDGNode>(
DAG.getNode(cast<sandboxir::StoreInst>(&*It++)));
auto *RetN = DAG.getNode(cast<sandboxir::ReturnInst>(&*It++));
EXPECT_TRUE(Store0N->memPreds().empty());
EXPECT_THAT(Store1N->memPreds(), testing::ElementsAre(Store0N));
EXPECT_TRUE(RetN->memPreds().empty());
EXPECT_TRUE(RetN->preds(DAG).empty());
}

TEST_F(DependencyGraphTest, NonAliasingStores) {
Expand All @@ -422,13 +425,15 @@ define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, i8 %v0, i8 %v1) {
sandboxir::DependencyGraph DAG(getAA(*LLVMF));
DAG.extend({&*BB->begin(), BB->getTerminator()});
auto It = BB->begin();
auto *Store0N = DAG.getNode(cast<sandboxir::StoreInst>(&*It++));
auto *Store1N = DAG.getNode(cast<sandboxir::StoreInst>(&*It++));
auto *Store0N = cast<sandboxir::MemDGNode>(
DAG.getNode(cast<sandboxir::StoreInst>(&*It++)));
auto *Store1N = cast<sandboxir::MemDGNode>(
DAG.getNode(cast<sandboxir::StoreInst>(&*It++)));
auto *RetN = DAG.getNode(cast<sandboxir::ReturnInst>(&*It++));
// We expect no dependencies because the stores don't alias.
EXPECT_TRUE(Store0N->memPreds().empty());
EXPECT_TRUE(Store1N->memPreds().empty());
EXPECT_TRUE(RetN->memPreds().empty());
EXPECT_TRUE(RetN->preds(DAG).empty());
}

TEST_F(DependencyGraphTest, VolatileLoads) {
Expand All @@ -446,12 +451,14 @@ define void @foo(ptr noalias %ptr0, ptr noalias %ptr1) {
sandboxir::DependencyGraph DAG(getAA(*LLVMF));
DAG.extend({&*BB->begin(), BB->getTerminator()});
auto It = BB->begin();
auto *Ld0N = DAG.getNode(cast<sandboxir::LoadInst>(&*It++));
auto *Ld1N = DAG.getNode(cast<sandboxir::LoadInst>(&*It++));
auto *Ld0N = cast<sandboxir::MemDGNode>(
DAG.getNode(cast<sandboxir::LoadInst>(&*It++)));
auto *Ld1N = cast<sandboxir::MemDGNode>(
DAG.getNode(cast<sandboxir::LoadInst>(&*It++)));
auto *RetN = DAG.getNode(cast<sandboxir::ReturnInst>(&*It++));
EXPECT_TRUE(Ld0N->memPreds().empty());
EXPECT_THAT(Ld1N->memPreds(), testing::ElementsAre(Ld0N));
EXPECT_TRUE(RetN->memPreds().empty());
EXPECT_TRUE(RetN->preds(DAG).empty());
}

TEST_F(DependencyGraphTest, VolatileSotres) {
Expand All @@ -469,12 +476,14 @@ define void @foo(ptr noalias %ptr0, ptr noalias %ptr1, i8 %v) {
sandboxir::DependencyGraph DAG(getAA(*LLVMF));
DAG.extend({&*BB->begin(), BB->getTerminator()});
auto It = BB->begin();
auto *Store0N = DAG.getNode(cast<sandboxir::StoreInst>(&*It++));
auto *Store1N = DAG.getNode(cast<sandboxir::StoreInst>(&*It++));
auto *Store0N = cast<sandboxir::MemDGNode>(
DAG.getNode(cast<sandboxir::StoreInst>(&*It++)));
auto *Store1N = cast<sandboxir::MemDGNode>(
DAG.getNode(cast<sandboxir::StoreInst>(&*It++)));
auto *RetN = DAG.getNode(cast<sandboxir::ReturnInst>(&*It++));
EXPECT_TRUE(Store0N->memPreds().empty());
EXPECT_THAT(Store1N->memPreds(), testing::ElementsAre(Store0N));
EXPECT_TRUE(RetN->memPreds().empty());
EXPECT_TRUE(RetN->preds(DAG).empty());
}

TEST_F(DependencyGraphTest, Call) {
Expand All @@ -498,12 +507,12 @@ define void @foo(float %v1, float %v2) {
DAG.extend({&*BB->begin(), BB->getTerminator()->getPrevNode()});

auto It = BB->begin();
auto *Call1N = DAG.getNode(&*It++);
auto *Call1N = cast<sandboxir::MemDGNode>(DAG.getNode(&*It++));
auto *AddN = DAG.getNode(&*It++);
auto *Call2N = DAG.getNode(&*It++);
auto *Call2N = cast<sandboxir::MemDGNode>(DAG.getNode(&*It++));

EXPECT_THAT(Call1N->memPreds(), testing::ElementsAre());
EXPECT_THAT(AddN->memPreds(), testing::ElementsAre());
EXPECT_THAT(AddN->preds(DAG), testing::ElementsAre());
EXPECT_THAT(Call2N->memPreds(), testing::ElementsAre(Call1N));
}

Expand Down Expand Up @@ -534,8 +543,8 @@ define void @foo() {
auto *AllocaN = DAG.getNode(&*It++);
auto *StackRestoreN = DAG.getNode(&*It++);

EXPECT_TRUE(dependency(AllocaN, StackRestoreN));
EXPECT_TRUE(dependency(StackSaveN, AllocaN));
EXPECT_TRUE(memDependency(AllocaN, StackRestoreN));
EXPECT_TRUE(memDependency(StackSaveN, AllocaN));
}

// Checks that stacksave and stackrestore depend on other mem instrs.
Expand Down Expand Up @@ -567,9 +576,9 @@ define void @foo(i8 %v0, i8 %v1, ptr %ptr) {
auto *StackRestoreN = DAG.getNode(&*It++);
auto *Store1N = DAG.getNode(&*It++);

EXPECT_TRUE(dependency(Store0N, StackSaveN));
EXPECT_TRUE(dependency(StackSaveN, StackRestoreN));
EXPECT_TRUE(dependency(StackRestoreN, Store1N));
EXPECT_TRUE(memDependency(Store0N, StackSaveN));
EXPECT_TRUE(memDependency(StackSaveN, StackRestoreN));
EXPECT_TRUE(memDependency(StackRestoreN, Store1N));
}

// Make sure there is a dependency between a stackrestore and an alloca.
Expand All @@ -596,7 +605,7 @@ define void @foo(ptr %ptr) {
auto *StackRestoreN = DAG.getNode(&*It++);
auto *AllocaN = DAG.getNode(&*It++);

EXPECT_TRUE(dependency(StackRestoreN, AllocaN));
EXPECT_TRUE(memDependency(StackRestoreN, AllocaN));
}

// Make sure there is a dependency between the alloca and stacksave
Expand All @@ -623,7 +632,7 @@ define void @foo(ptr %ptr) {
auto *AllocaN = DAG.getNode(&*It++);
auto *StackSaveN = DAG.getNode(&*It++);

EXPECT_TRUE(dependency(AllocaN, StackSaveN));
EXPECT_TRUE(memDependency(AllocaN, StackSaveN));
}

// A non-InAlloca in a stacksave-stackrestore region does not need extra
Expand Down Expand Up @@ -655,6 +664,6 @@ define void @foo() {
auto *AllocaN = DAG.getNode(&*It++);
auto *StackRestoreN = DAG.getNode(&*It++);

EXPECT_FALSE(dependency(StackSaveN, AllocaN));
EXPECT_FALSE(dependency(AllocaN, StackRestoreN));
EXPECT_FALSE(memDependency(StackSaveN, AllocaN));
EXPECT_FALSE(memDependency(AllocaN, StackRestoreN));
}