aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVitaly Buka <vitalybuka@google.com>2024-10-10 13:11:22 -0700
committerVitaly Buka <vitalybuka@google.com>2024-10-10 13:11:22 -0700
commit4ca4c25b6eb5fb4827c5874c17a54a5c7ab3ad3e (patch)
treed2296ffd2fdedea61f769d4e168daa108b052877
parentd146f5a231e6b274281a8d09f2ee2bb06029562f (diff)
parent942fefe74112acb68fa43dde44abe3ae125457e1 (diff)
downloadllvm-users/vitalybuka/spr/main.sanitizer-vreport-beforeforkafterfork.zip
llvm-users/vitalybuka/spr/main.sanitizer-vreport-beforeforkafterfork.tar.gz
llvm-users/vitalybuka/spr/main.sanitizer-vreport-beforeforkafterfork.tar.bz2
[𝘀𝗽𝗿] changes introduced through rebaseusers/vitalybuka/spr/main.sanitizer-vreport-beforeforkafterfork
Created using spr 1.3.4 [skip ci]
-rw-r--r--compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp4
-rw-r--r--lldb/include/lldb/API/SBProcess.h1
-rw-r--r--lldb/include/lldb/Target/Process.h21
-rw-r--r--lldb/include/lldb/Target/StopInfo.h6
-rw-r--r--lldb/include/lldb/lldb-enumerations.h6
-rw-r--r--lldb/packages/Python/lldbsuite/test/gdbclientutils.py5
-rw-r--r--lldb/packages/Python/lldbsuite/test/lldbgdbproxy.py175
-rw-r--r--lldb/packages/Python/lldbsuite/test/lldbreverse.py418
-rw-r--r--lldb/packages/Python/lldbsuite/test/lldbtest.py2
-rw-r--r--lldb/source/API/SBProcess.cpp8
-rw-r--r--lldb/source/API/SBThread.cpp2
-rw-r--r--lldb/source/Interpreter/CommandInterpreter.cpp3
-rw-r--r--lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp3
-rw-r--r--lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp9
-rw-r--r--lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h2
-rw-r--r--lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp8
-rw-r--r--lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h2
-rw-r--r--lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp22
-rw-r--r--lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h6
-rw-r--r--lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp1
-rw-r--r--lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp77
-rw-r--r--lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h2
-rw-r--r--lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp9
-rw-r--r--lldb/source/Plugins/Process/scripted/ScriptedProcess.h2
-rw-r--r--lldb/source/Target/Process.cpp29
-rw-r--r--lldb/source/Target/StopInfo.cpp29
-rw-r--r--lldb/source/Target/Thread.cpp8
-rw-r--r--lldb/test/API/functionalities/reverse-execution/Makefile3
-rw-r--r--lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py115
-rw-r--r--lldb/test/API/functionalities/reverse-execution/TestReverseContinueNotSupported.py30
-rw-r--r--lldb/test/API/functionalities/reverse-execution/main.c14
-rw-r--r--lldb/tools/lldb-dap/JSONUtils.cpp3
-rw-r--r--lldb/tools/lldb-dap/LLDBUtils.cpp1
-rw-r--r--llvm/docs/Coroutines.rst90
-rw-r--r--llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h36
-rw-r--r--llvm/lib/Target/NVPTX/NVPTXInstrInfo.td5
-rw-r--r--llvm/lib/Target/NVPTX/NVPTXSubtarget.h8
-rw-r--r--llvm/lib/Target/NVPTX/NVPTXTargetMachine.cpp10
-rw-r--r--llvm/lib/Transforms/Instrumentation/PGOCtxProfLowering.cpp23
-rw-r--r--llvm/lib/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.cpp20
-rw-r--r--llvm/test/CodeGen/NVPTX/unreachable.ll103
-rw-r--r--llvm/unittests/Transforms/Vectorize/SandboxVectorizer/DependencyGraphTest.cpp73
42 files changed, 1252 insertions, 142 deletions
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp
index 671546c..70fd940 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp
@@ -1037,9 +1037,7 @@ ThreadLister::Result ThreadLister::ListThreads(
Report("Can't open %s for reading.\n", task_path_.data());
return Error;
}
- auto acts_cleanup = at_scope_exit([&] {
- internal_close(descriptor);
- });
+ auto acts_cleanup = at_scope_exit([&] { internal_close(descriptor); });
threads->clear();
Result result = Ok;
diff --git a/lldb/include/lldb/API/SBProcess.h b/lldb/include/lldb/API/SBProcess.h
index 1624e02..8b8ed83 100644
--- a/lldb/include/lldb/API/SBProcess.h
+++ b/lldb/include/lldb/API/SBProcess.h
@@ -159,6 +159,7 @@ public:
lldb::SBError Destroy();
lldb::SBError Continue();
+ lldb::SBError Continue(RunDirection direction);
lldb::SBError Stop();
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index b8c53a4..fe7fbc5 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -857,10 +857,10 @@ public:
/// \see Thread:Resume()
/// \see Thread:Step()
/// \see Thread:Suspend()
- Status Resume();
+ Status Resume(lldb::RunDirection direction = lldb::eRunForward);
/// Resume a process, and wait for it to stop.
- Status ResumeSynchronous(Stream *stream);
+ Status ResumeSynchronous(Stream *stream, lldb::RunDirection direction = lldb::eRunForward);
/// Halts a running process.
///
@@ -1104,9 +1104,14 @@ public:
/// \see Thread:Resume()
/// \see Thread:Step()
/// \see Thread:Suspend()
- virtual Status DoResume() {
- return Status::FromErrorStringWithFormatv(
- "error: {0} does not support resuming processes", GetPluginName());
+ virtual Status DoResume(lldb::RunDirection direction) {
+ if (direction == lldb::RunDirection::eRunForward) {
+ return Status::FromErrorStringWithFormatv(
+ "error: {0} does not support resuming processes", GetPluginName());
+ } else {
+ return Status::FromErrorStringWithFormatv(
+ "error: {0} does not support reverse execution of processes", GetPluginName());
+ }
}
/// Called after resuming a process.
@@ -2332,6 +2337,8 @@ public:
bool IsRunning() const;
+ lldb::RunDirection GetLastRunDirection() { return m_last_run_direction; }
+
DynamicCheckerFunctions *GetDynamicCheckers() {
return m_dynamic_checkers_up.get();
}
@@ -2851,7 +2858,7 @@ protected:
///
/// \return
/// An Status object describing the success or failure of the resume.
- Status PrivateResume();
+ Status PrivateResume(lldb::RunDirection direction = lldb::eRunForward);
// Called internally
void CompleteAttach();
@@ -3127,6 +3134,8 @@ protected:
// m_currently_handling_do_on_removals are true,
// Resume will only request a resume, using this
// flag to check.
+ // The direction of execution from the last time this process was resumed.
+ lldb::RunDirection m_last_run_direction;
lldb::tid_t m_interrupt_tid; /// The tid of the thread that issued the async
/// interrupt, used by thread plan timeout. It
diff --git a/lldb/include/lldb/Target/StopInfo.h b/lldb/include/lldb/Target/StopInfo.h
index fae9036..072f71f 100644
--- a/lldb/include/lldb/Target/StopInfo.h
+++ b/lldb/include/lldb/Target/StopInfo.h
@@ -142,6 +142,12 @@ public:
static lldb::StopInfoSP
CreateStopReasonProcessorTrace(Thread &thread, const char *description);
+ // This creates a StopInfo indicating that execution stopped because
+ // it was replaying some recorded execution history, and execution reached
+ // the end of that recorded history.
+ static lldb::StopInfoSP
+ CreateStopReasonHistoryBoundary(Thread &thread, const char *description);
+
static lldb::StopInfoSP CreateStopReasonFork(Thread &thread,
lldb::pid_t child_pid,
lldb::tid_t child_tid);
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index 938f6e3..232d1df 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -135,6 +135,9 @@ FLAGS_ENUM(LaunchFlags){
/// Thread Run Modes.
enum RunMode { eOnlyThisThread, eAllThreads, eOnlyDuringStepping };
+/// Execution directions
+enum RunDirection { eRunForward, eRunReverse };
+
/// Byte ordering definitions.
enum ByteOrder {
eByteOrderInvalid = 0,
@@ -254,6 +257,9 @@ enum StopReason {
eStopReasonVFork,
eStopReasonVForkDone,
eStopReasonInterrupt, ///< Thread requested interrupt
+ // Indicates that execution stopped because the debugger backend relies
+ // on recorded data and we reached the end of that data.
+ eStopReasonHistoryBoundary,
};
/// Command Return Status Types.
diff --git a/lldb/packages/Python/lldbsuite/test/gdbclientutils.py b/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
index 1784487..732d617 100644
--- a/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
+++ b/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
@@ -510,8 +510,9 @@ class MockGDBServer:
self._thread.start()
def stop(self):
- self._thread.join()
- self._thread = None
+ if self._thread is not None:
+ self._thread.join()
+ self._thread = None
def get_connect_address(self):
return self._socket.get_connect_address()
diff --git a/lldb/packages/Python/lldbsuite/test/lldbgdbproxy.py b/lldb/packages/Python/lldbsuite/test/lldbgdbproxy.py
new file mode 100644
index 0000000..2a9592b
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/lldbgdbproxy.py
@@ -0,0 +1,175 @@
+import logging
+import os
+import os.path
+import random
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.gdbclientutils import *
+import lldbgdbserverutils
+from lldbsuite.support import seven
+
+
+class GDBProxyTestBase(TestBase):
+ """
+ Base class for gdbserver proxy tests.
+
+ This class will setup and start a mock GDB server for the test to use.
+ It pases through requests to a regular lldb-server/debugserver and
+ forwards replies back to the LLDB under test.
+ """
+
+ """The gdbserver that we implement."""
+ server = None
+ """The inner lldb-server/debugserver process that we proxy requests into."""
+ monitor_server = None
+ monitor_sock = None
+
+ server_socket_class = TCPServerSocket
+
+ DEFAULT_TIMEOUT = 20 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)
+
+ _verbose_log_handler = None
+ _log_formatter = logging.Formatter(fmt="%(asctime)-15s %(levelname)-8s %(message)s")
+
+ def setUpBaseLogging(self):
+ self.logger = logging.getLogger(__name__)
+
+ if len(self.logger.handlers) > 0:
+ return # We have set up this handler already
+
+ self.logger.propagate = False
+ self.logger.setLevel(logging.DEBUG)
+
+ # log all warnings to stderr
+ handler = logging.StreamHandler()
+ handler.setLevel(logging.WARNING)
+ handler.setFormatter(self._log_formatter)
+ self.logger.addHandler(handler)
+
+ def setUp(self):
+ TestBase.setUp(self)
+
+ self.setUpBaseLogging()
+
+ if self.isVerboseLoggingRequested():
+ # If requested, full logs go to a log file
+ log_file_name = self.getLogBasenameForCurrentTest() + "-proxy.log"
+ self._verbose_log_handler = logging.FileHandler(
+ log_file_name
+ )
+ self._verbose_log_handler.setFormatter(self._log_formatter)
+ self._verbose_log_handler.setLevel(logging.DEBUG)
+ self.logger.addHandler(self._verbose_log_handler)
+
+ lldb_server_exe = lldbgdbserverutils.get_lldb_server_exe()
+ if lldb_server_exe is None:
+ self.debug_monitor_exe = lldbgdbserverutils.get_debugserver_exe()
+ self.assertTrue(self.debug_monitor_exe is not None)
+ self.debug_monitor_extra_args = []
+ else:
+ self.debug_monitor_exe = lldb_server_exe
+ self.debug_monitor_extra_args = ["gdbserver"]
+
+ self.server = MockGDBServer(self.server_socket_class())
+ self.server.responder = self
+
+ def tearDown(self):
+ # TestBase.tearDown will kill the process, but we need to kill it early
+ # so its client connection closes and we can stop the server before
+ # finally calling the base tearDown.
+ if self.process() is not None:
+ self.process().Kill()
+ self.server.stop()
+
+ self.logger.removeHandler(self._verbose_log_handler)
+ self._verbose_log_handler = None
+
+ TestBase.tearDown(self)
+
+ def isVerboseLoggingRequested(self):
+ # We will report our detailed logs if the user requested that the "gdb-remote" channel is
+ # logged.
+ return any(("gdb-remote" in channel) for channel in lldbtest_config.channels)
+
+ def connect(self, target):
+ """
+ Create a process by connecting to the mock GDB server.
+ """
+ self.prep_debug_monitor_and_inferior()
+ self.server.start()
+
+ listener = self.dbg.GetListener()
+ error = lldb.SBError()
+ process = target.ConnectRemote(
+ listener, self.server.get_connect_url(), "gdb-remote", error
+ )
+ self.assertTrue(error.Success(), error.description)
+ self.assertTrue(process, PROCESS_IS_VALID)
+ return process
+
+ def get_next_port(self):
+ return 12000 + random.randint(0, 3999)
+
+ def prep_debug_monitor_and_inferior(self):
+ inferior_exe_path = self.getBuildArtifact("a.out")
+ self.connect_to_debug_monitor([inferior_exe_path])
+ self.assertIsNotNone(self.monitor_server)
+ self.initial_handshake()
+
+ def initial_handshake(self):
+ self.monitor_server.send_packet(seven.bitcast_to_bytes("+"))
+ reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
+ self.assertEqual(reply, "+")
+ self.monitor_server.send_packet(seven.bitcast_to_bytes("QStartNoAckMode"))
+ reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
+ self.assertEqual(reply, "+")
+ reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
+ self.assertEqual(reply, "OK")
+ self.monitor_server.send_packet(seven.bitcast_to_bytes("+"))
+ reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
+ self.assertEqual(reply, "+")
+
+ def get_debug_monitor_command_line_args(self, connect_address, launch_args):
+ return self.debug_monitor_extra_args + ["--reverse-connect", connect_address] + launch_args
+
+ def launch_debug_monitor(self, launch_args):
+ family, type, proto, _, addr = socket.getaddrinfo(
+ "localhost", 0, proto=socket.IPPROTO_TCP
+ )[0]
+ sock = socket.socket(family, type, proto)
+ sock.settimeout(self.DEFAULT_TIMEOUT)
+ sock.bind(addr)
+ sock.listen(1)
+ addr = sock.getsockname()
+ connect_address = "[{}]:{}".format(*addr)
+
+ commandline_args = self.get_debug_monitor_command_line_args(
+ connect_address, launch_args
+ )
+
+ # Start the server.
+ self.logger.info(f"Spawning monitor {commandline_args}")
+ monitor_process = self.spawnSubprocess(
+ self.debug_monitor_exe, commandline_args, install_remote=False
+ )
+ self.assertIsNotNone(monitor_process)
+
+ self.monitor_sock = sock.accept()[0]
+ self.monitor_sock.settimeout(self.DEFAULT_TIMEOUT)
+ return monitor_process
+
+ def connect_to_debug_monitor(self, launch_args):
+ monitor_process = self.launch_debug_monitor(launch_args)
+ self.monitor_server = lldbgdbserverutils.Server(self.monitor_sock, monitor_process)
+
+ def respond(self, packet):
+ """Subclasses can override this to change how packets are handled."""
+ return self.pass_through(packet)
+
+ def pass_through(self, packet):
+ self.logger.info(f"Sending packet {packet}")
+ self.monitor_server.send_packet(seven.bitcast_to_bytes(packet))
+ reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
+ self.logger.info(f"Received reply {reply}")
+ return reply
diff --git a/lldb/packages/Python/lldbsuite/test/lldbreverse.py b/lldb/packages/Python/lldbsuite/test/lldbreverse.py
new file mode 100644
index 0000000..0f02fdf
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/lldbreverse.py
@@ -0,0 +1,418 @@
+import os
+import os.path
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.gdbclientutils import *
+from lldbsuite.test.lldbgdbproxy import *
+import lldbgdbserverutils
+import re
+
+
+class ThreadSnapshot:
+ def __init__(self, thread_id, registers):
+ self.thread_id = thread_id
+ self.registers = registers
+
+
+class MemoryBlockSnapshot:
+ def __init__(self, address, data):
+ self.address = address
+ self.data = data
+
+
+class StateSnapshot:
+ def __init__(self, thread_snapshots, memory):
+ self.thread_snapshots = thread_snapshots
+ self.memory = memory
+ self.thread_id = None
+
+
+class RegisterInfo:
+ def __init__(self, lldb_index, bitsize, little_endian):
+ self.lldb_index = lldb_index
+ self.bitsize = bitsize
+ self.little_endian = little_endian
+
+
+BELOW_STACK_POINTER = 16384
+ABOVE_STACK_POINTER = 4096
+
+BLOCK_SIZE = 1024
+
+SOFTWARE_BREAKPOINTS = 0
+HARDWARE_BREAKPOINTS = 1
+WRITE_WATCHPOINTS = 2
+
+
+class ReverseTestBase(GDBProxyTestBase):
+ """
+ Base class for tests that need reverse execution.
+
+ This class uses a gdbserver proxy to add very limited reverse-
+ execution capability to lldb-server/debugserver for testing
+ purposes only.
+
+ To use this class, run the inferior forward until some stopping point.
+ Then call `start_recording()` and execute forward again until reaching
+ a software breakpoint; this class records the state before each execution executes.
+ At that point, the server will accept "bc" and "bs" packets to step
+ backwards through the state.
+ When executing during recording, we only allow single-step and continue without
+ delivering a signal, and only software breakpoint stops are allowed.
+
+ We assume that while recording is enabled, the only effects of instructions
+ are on general-purpose registers (read/written by the 'g' and 'G' packets)
+ and on memory bytes between [SP - BELOW_STACK_POINTER, SP + ABOVE_STACK_POINTER).
+ """
+
+ """
+ A list of StateSnapshots in time order.
+
+ There is one snapshot per single-stepped instruction,
+ representing the state before that instruction was
+ executed. The last snapshot in the list is the
+ snapshot before the last instruction was executed.
+ This is an undo log; we snapshot a superset of the state that may have
+ been changed by the instruction's execution.
+ """
+ snapshots = None
+ recording_enabled = False
+
+ breakpoints = None
+
+ pid = None
+
+ pc_register_info = None
+ sp_register_info = None
+ general_purpose_register_info = None
+
+ def __init__(self, *args, **kwargs):
+ GDBProxyTestBase.__init__(self, *args, **kwargs)
+ self.breakpoints = [set(), set(), set(), set(), set()]
+
+ def respond(self, packet):
+ if not packet:
+ raise ValueError("Invalid empty packet")
+ if packet == self.server.PACKET_INTERRUPT:
+ # Don't send a response. We'll just run to completion.
+ return []
+ if self.is_command(packet, "qSupported", ":"):
+ reply = self.pass_through(packet)
+ return reply + ";ReverseStep+;ReverseContinue+"
+ if self.is_command(packet, "vCont", ";"):
+ if self.recording_enabled:
+ return self.continue_with_recording(packet)
+ snapshots = []
+ if packet[0] == "c" or packet[0] == "s" or packet[0] == "C" or packet[0] == "S":
+ raise ValueError("LLDB should not be sending old-style continuation packets")
+ if packet == "bc":
+ return self.reverse_continue()
+ if packet == "bs":
+ return self.reverse_step()
+ if packet == 'jThreadsInfo':
+ # Suppress this because it contains thread stop reasons which we might
+ # need to modify, and we don't want to have to implement that.
+ return ""
+ if packet[0] == "z" or packet[0] == "Z":
+ reply = self.pass_through(packet)
+ if reply == "OK":
+ self.update_breakpoints(packet)
+ return reply
+ return GDBProxyTestBase.respond(self, packet)
+
+ def start_recording(self):
+ self.recording_enabled = True
+ self.snapshots = []
+
+ def stop_recording(self):
+ """
+ Don't record when executing foward.
+
+ Reverse execution is still supported until the next forward continue.
+ """
+ self.recording_enabled = False
+
+ def is_command(self, packet, cmd, follow_token):
+ return packet == cmd or packet[0:len(cmd) + 1] == cmd + follow_token
+
+ def update_breakpoints(self, packet):
+ m = re.match("([zZ])([01234]),([0-9a-f]+),([0-9a-f]+)", packet)
+ if m is None:
+ raise ValueError("Invalid breakpoint packet: " + packet)
+ t = int(m.group(2))
+ addr = int(m.group(3), 16)
+ kind = int(m.group(4), 16)
+ if m.group(1) == 'Z':
+ self.breakpoints[t].add((addr, kind))
+ else:
+ self.breakpoints[t].discard((addr, kind))
+
+ def breakpoint_triggered_at(self, pc):
+ if any(addr == pc for addr, kind in self.breakpoints[SOFTWARE_BREAKPOINTS]):
+ return True
+ if any(addr == pc for addr, kind in self.breakpoints[HARDWARE_BREAKPOINTS]):
+ return True
+ return False
+
+ def watchpoint_triggered(self, new_value_block, current_contents):
+ """Returns the address or None."""
+ for watch_addr, kind in breakpoints[WRITE_WATCHPOINTS]:
+ for offset in range(0, kind):
+ addr = watch_addr + offset
+ if (addr >= new_value_block.address and
+ addr < new_value_block.address + len(new_value_block.data)):
+ index = addr - new_value_block.address
+ if new_value_block.data[index*2:(index + 1)*2] != current_contents[index*2:(index + 1)*2]:
+ return watch_addr
+ return None
+
+ def continue_with_recording(self, packet):
+ self.logger.debug("Continue with recording enabled")
+
+ step_packet = "vCont;s"
+ if packet == "vCont":
+ requested_step = False
+ else:
+ m = re.match("vCont;(c|s)(.*)", packet)
+ if m is None:
+ raise ValueError("Unsupported vCont packet: " + packet)
+ requested_step = m.group(1) == 's'
+ step_packet += m.group(2)
+
+ while True:
+ snapshot = self.capture_snapshot()
+ reply = self.pass_through(step_packet)
+ (stop_signal, stop_pairs) = self.parse_stop(reply)
+ if stop_signal != 5:
+ raise ValueError("Unexpected stop signal: " + reply)
+ is_swbreak = False
+ thread_id = None
+ for key, value in stop_pairs.items():
+ if key == "thread":
+ thread_id = self.parse_thread_id(value)
+ continue
+ if re.match('[0-9a-f]+', key):
+ continue
+ if key == "swbreak" or (key == "reason" and value == "breakpoint"):
+ is_swbreak = True
+ continue
+ if key in ["name", "threads", "thread-pcs", "reason"]:
+ continue
+ raise ValueError(f"Unknown stop key '{key}' in {reply}")
+ if is_swbreak:
+ self.logger.debug("Recording stopped")
+ return reply
+ if thread_id is None:
+ return ValueError("Expected thread ID: " + reply)
+ snapshot.thread_id = thread_id
+ self.snapshots.append(snapshot)
+ if requested_step:
+ self.logger.debug("Recording stopped for step")
+ return reply
+
+ def parse_stop(self, reply):
+ result = {}
+ if not reply:
+ raise ValueError("Invalid empty packet")
+ if reply[0] == "T" and len(reply) >= 3:
+ result = {k:v for k, v in self.parse_pairs(reply[3:])}
+ return (int(reply[1:3], 16), result)
+ raise "Unsupported stop reply: " + reply
+
+ def parse_pairs(self, text):
+ for pair in text.split(";"):
+ if not pair:
+ continue
+ m = re.match("([^:]+):(.*)", pair)
+ if m is None:
+ raise ValueError("Invalid pair text: " + text)
+ yield (m.group(1), m.group(2))
+
+ def capture_snapshot(self):
+ """Snapshot all threads and their stack memories."""
+ self.ensure_register_info()
+ current_thread = self.get_current_thread()
+ thread_snapshots = []
+ memory = []
+ for thread_id in self.get_thread_list():
+ registers = {}
+ for index in sorted(self.general_purpose_register_info.keys()):
+ reply = self.pass_through(f"p{index:x};thread:{thread_id:x};")
+ if reply == "" or reply[0] == 'E':
+ raise ValueError("Can't read register")
+ registers[index] = reply
+ thread_snapshot = ThreadSnapshot(thread_id, registers)
+ thread_sp = self.get_register(self.sp_register_info, thread_snapshot.registers)
+ memory += self.read_memory(thread_sp - BELOW_STACK_POINTER, thread_sp + ABOVE_STACK_POINTER)
+ thread_snapshots.append(thread_snapshot)
+ self.set_current_thread(current_thread)
+ return StateSnapshot(thread_snapshots, memory)
+
+ def restore_snapshot(self, snapshot):
+ """
+ Restore the snapshot during reverse execution.
+
+ If this triggers a breakpoint or watchpoint, return the stop reply,
+ otherwise None.
+ """
+ current_thread = self.get_current_thread()
+ stop_reasons = []
+ for thread_snapshot in snapshot.thread_snapshots:
+ thread_id = thread_snapshot.thread_id
+ for lldb_index in sorted(thread_snapshot.registers.keys()):
+ data = thread_snapshot.registers[lldb_index]
+ reply = self.pass_through(f"P{lldb_index:x}={data};thread:{thread_id:x};")
+ if reply != "OK":
+ raise ValueError("Can't restore thread register")
+ if thread_id == snapshot.thread_id:
+ new_pc = self.get_register(self.pc_register_info, thread_snapshot.registers)
+ if self.breakpoint_triggered_at(new_pc):
+ stop_reasons.append([("reason", "breakpoint")])
+ self.set_current_thread(current_thread)
+ for block in snapshot.memory:
+ current_memory = self.pass_through(f"m{block.address:x},{(len(block.data)/2):x}")
+ if not current_memory or current_memory[0] == 'E':
+ raise ValueError("Can't read back memory")
+ reply = self.pass_through(f"M{block.address:x},{len(block.data)/2:x}:" + block.data)
+ if reply != "OK":
+ raise ValueError("Can't restore memory")
+ watch_addr = self.watchpoint_triggered(block, current_memory[1:])
+ if watch_addr is not None:
+ stop_reasons.append([("reason", "watchpoint"), ("watch", f"{watch_addr:x}")])
+ if stop_reasons:
+ pairs = ";".join(f"{key}:{value}" for key, value in stop_reasons[0])
+ return f"T05thread:{self.pid:x}.{snapshot.thread_id:x};{pairs};"
+ return None
+
+ def reverse_step(self):
+ if not self.snapshots:
+ self.logger.debug("Reverse-step at history boundary")
+ return self.history_boundary_reply(self.get_current_thread())
+ self.logger.debug("Reverse-step started")
+ snapshot = self.snapshots.pop()
+ stop_reply = self.restore_snapshot(snapshot)
+ self.set_current_thread(snapshot.thread_id)
+ self.logger.debug("Reverse-step stopped")
+ if stop_reply is None:
+ return self.singlestep_stop_reply(snapshot.thread_id)
+ return stop_reply
+
+ def reverse_continue(self):
+ self.logger.debug("Reverse-continue started")
+ thread_id = None
+ while self.snapshots:
+ snapshot = self.snapshots.pop()
+ stop_reply = self.restore_snapshot(snapshot)
+ thread_id = snapshot.thread_id
+ if stop_reply is not None:
+ self.set_current_thread(thread_id)
+ self.logger.debug("Reverse-continue stopped")
+ return stop_reply
+ if thread_id is None:
+ thread_id = self.get_current_thread()
+ else:
+ self.set_current_thread(snapshot.thread_id)
+ self.logger.debug("Reverse-continue stopped at history boundary")
+ return self.history_boundary_reply(thread_id)
+
+ def get_current_thread(self):
+ reply = self.pass_through("qC")
+ return self.parse_thread_id(reply[2:])
+
+ def parse_thread_id(self, thread_id):
+ m = re.match("(p([0-9a-f]+)[.])?([0-9a-f]+)$", thread_id)
+ if m is None:
+ raise ValueError("Invalid thread ID: " + thread_id)
+ if self.pid is None:
+ self.pid = int(m.group(2), 16)
+ return int(m.group(3), 16)
+
+ def history_boundary_reply(self, thread_id):
+ return f"T00thread:{self.pid:x}.{thread_id:x};replaylog:begin;"
+
+ def singlestep_stop_reply(self, thread_id):
+ return f"T05thread:{self.pid:x}.{thread_id:x};"
+
+ def set_current_thread(self, thread_id):
+ """
+ Set current thread in inner gdbserver.
+ """
+ if thread_id >= 0:
+ self.pass_through(f"Hg{self.pid:x}.{thread_id:x}")
+ self.pass_through(f"Hc{self.pid:x}.{thread_id:x}")
+ else:
+ self.pass_through(f"Hc-1.-1")
+ self.pass_through(f"Hg-1.-1")
+
+ def get_register(self, register_info, registers):
+ if register_info.bitsize % 8 != 0:
+ raise ValueError("Register size must be a multiple of 8 bits")
+ if register_info.lldb_index not in registers:
+ raise ValueError("Register value not captured")
+ data = registers[register_info.lldb_index]
+ num_bytes = register_info.bitsize//8
+ bytes = []
+ for i in range(0, num_bytes):
+ bytes.append(int(data[i*2:(i + 1)*2], 16))
+ if register_info.little_endian:
+ bytes.reverse()
+ result = 0
+ for byte in bytes:
+ result = (result << 8) + byte
+ return result
+
+ def read_memory(self, start_addr, end_addr):
+ """
+ Read a region of memory from the target.
+
+ Some of the addresses may extend into invalid virtual memory;
+ skip those areas.
+ Return a list of blocks containing the valid area(s) in the
+ requested range.
+ """
+ regions = []
+ start_addr = start_addr & (BLOCK_SIZE - 1)
+ end_addr = (end_addr + BLOCK_SIZE - 1) & (BLOCK_SIZE - 1)
+ for addr in range(start_addr, end_addr, BLOCK_SIZE):
+ reply = self.pass_through(f"m{addr:x},{(BLOCK_SIZE - 1):x}")
+ if reply and reply[0] != 'E':
+ block = MemoryBlockSnapshot(addr, reply[1:])
+ regions.append(block)
+ return regions
+
+ def ensure_register_info(self):
+ if self.general_purpose_register_info is not None:
+ return
+ reply = self.pass_through("qHostInfo")
+ little_endian = any(kv == ("endian", "little") for kv in self.parse_pairs(reply))
+ self.general_purpose_register_info = {}
+ lldb_index = 0
+ while True:
+ reply = self.pass_through(f"qRegisterInfo{lldb_index:x}")
+ if not reply or reply[0] == 'E':
+ break
+ info = {k:v for k, v in self.parse_pairs(reply)}
+ reg_info = RegisterInfo(lldb_index, int(info["bitsize"]), little_endian)
+ if info["set"] == "General Purpose Registers" and not "container-regs" in info:
+ self.general_purpose_register_info[lldb_index] = reg_info
+ if "generic" in info:
+ if info["generic"] == "pc":
+ self.pc_register_info = reg_info
+ elif info["generic"] == "sp":
+ self.sp_register_info = reg_info
+ lldb_index += 1
+ if self.pc_register_info is None or self.sp_register_info is None:
+ raise ValueError("Can't find generic pc or sp register")
+
+ def get_thread_list(self):
+ threads = []
+ reply = self.pass_through("qfThreadInfo")
+ while True:
+ if not reply:
+ raise ValueError("Missing reply packet")
+ if reply[0] == 'm':
+ for id in reply[1:].split(","):
+ threads.append(self.parse_thread_id(id))
+ elif reply[0] == 'l':
+ return threads
+ reply = self.pass_through("qsThreadInfo")
diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index 8884ef5..7cc1ac9 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -143,6 +143,8 @@ STOPPED_DUE_TO_STEP_IN = "Process state is stopped due to step in"
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"
diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp
index 9773144..07780f9 100644
--- a/lldb/source/API/SBProcess.cpp
+++ b/lldb/source/API/SBProcess.cpp
@@ -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;
@@ -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");
diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp
index a99456e..aca8a03 100644
--- a/lldb/source/API/SBThread.cpp
+++ b/lldb/source/API/SBThread.cpp
@@ -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;
@@ -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;
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index 8d3a82e..ea60492 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -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) {
diff --git a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp
index de047ee..b0aa664 100644
--- a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp
@@ -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));
diff --git a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
index 9b2907c..116c43343 100644
--- a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
+++ b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
@@ -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();
diff --git a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h
index e5ec591..1b71d83 100644
--- a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h
+++ b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h
@@ -90,7 +90,7 @@ public:
// 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;
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 703aa08..76b7095 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -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...",
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
index e97cfb7..97284b7 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
@@ -52,7 +52,7 @@ public:
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;
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index e42526c..fc792a4 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -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;
@@ -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;
@@ -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
@@ -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
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
index 898d176..116b47c 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -331,6 +331,10 @@ public:
bool GetMultiprocessSupported();
+ bool GetReverseContinueSupported();
+
+ bool GetReverseStepSupported();
+
LazyBool SupportsAllocDeallocMemory() // const
{
// Uncomment this to have lldb pretend the debug server doesn't respond to
@@ -561,6 +565,8 @@ protected:
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,
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index 35fa93e..4016cde 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -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:
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 3e09c31..3fc03bd 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -169,6 +169,10 @@ public:
}
};
+std::chrono::seconds ResumeTimeout() {
+ return std::chrono::seconds(5);
+}
+
} // namespace
static PluginProperties &GetGlobalPluginProperties() {
@@ -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"));
@@ -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
@@ -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...
@@ -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()) {
@@ -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)) {
@@ -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(
@@ -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) {
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index 2492795..fa3e1ce 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -111,7 +111,7 @@ public:
// Process Control
Status WillResume() override;
- Status DoResume() override;
+ Status DoResume(lldb::RunDirection direction) override;
Status DoHalt(bool &caused_stop) override;
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
index d2111ce..304c121 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
@@ -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) {
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedProcess.h b/lldb/source/Plugins/Process/scripted/ScriptedProcess.h
index 0335364..8ebe4ca 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedProcess.h
+++ b/lldb/source/Plugins/Process/scripted/ScriptedProcess.h
@@ -52,7 +52,7 @@ public:
void DidResume() override;
- Status DoResume() override;
+ Status DoResume(lldb::RunDirection direction) override;
Status DoAttachToProcessWithID(lldb::pid_t pid,
const ProcessAttachInfo &attach_info) override;
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index aca0897..ff6a2f5 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -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),
@@ -845,6 +846,7 @@ bool Process::HandleProcessStateChangedEvent(
switch (thread_stop_reason) {
case eStopReasonInvalid:
case eStopReasonNone:
+ case eStopReasonHistoryBoundary:
break;
case eStopReasonSignal: {
@@ -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()) {
@@ -1361,7 +1363,7 @@ 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();
@@ -1369,7 +1371,7 @@ Status Process::Resume() {
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()) {
@@ -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,
@@ -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 "
@@ -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()) {
@@ -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();
@@ -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;
@@ -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();
diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp
index bd7032b..08e9a7c 100644
--- a/lldb/source/Target/StopInfo.cpp
+++ b/lldb/source/Target/StopInfo.cpp
@@ -1212,6 +1212,30 @@ public:
}
};
+// 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 {
@@ -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));
}
diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp
index 902fbb2..bbb586f 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -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.
@@ -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);
diff --git a/lldb/test/API/functionalities/reverse-execution/Makefile b/lldb/test/API/functionalities/reverse-execution/Makefile
new file mode 100644
index 0000000..1049594
--- /dev/null
+++ b/lldb/test/API/functionalities/reverse-execution/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py b/lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py
new file mode 100644
index 0000000..b37578f
--- /dev/null
+++ b/lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py
@@ -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)
diff --git a/lldb/test/API/functionalities/reverse-execution/TestReverseContinueNotSupported.py b/lldb/test/API/functionalities/reverse-execution/TestReverseContinueNotSupported.py
new file mode 100644
index 0000000..d610761
--- /dev/null
+++ b/lldb/test/API/functionalities/reverse-execution/TestReverseContinueNotSupported.py
@@ -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)
diff --git a/lldb/test/API/functionalities/reverse-execution/main.c b/lldb/test/API/functionalities/reverse-execution/main.c
new file mode 100644
index 0000000..40e45dc
--- /dev/null
+++ b/lldb/test/API/functionalities/reverse-execution/main.c
@@ -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;
+}
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 558f889..211fd34 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -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");
diff --git a/lldb/tools/lldb-dap/LLDBUtils.cpp b/lldb/tools/lldb-dap/LLDBUtils.cpp
index b38833c..1c5e3ac 100644
--- a/lldb/tools/lldb-dap/LLDBUtils.cpp
+++ b/lldb/tools/lldb-dap/LLDBUtils.cpp
@@ -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:
diff --git a/llvm/docs/Coroutines.rst b/llvm/docs/Coroutines.rst
index 5679aef..8794df6 100644
--- a/llvm/docs/Coroutines.rst
+++ b/llvm/docs/Coroutines.rst
@@ -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.
@@ -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
==========
@@ -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
diff --git a/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h b/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h
index eba6d75..da50e53 100644
--- a/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h
+++ b/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h
@@ -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.
@@ -96,9 +96,6 @@ protected:
// 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.
@@ -170,17 +167,6 @@ public:
}
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;
@@ -198,6 +184,9 @@ public:
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; }
@@ -222,6 +211,21 @@ public:
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.
@@ -266,7 +270,7 @@ private:
/// 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)
diff --git a/llvm/lib/Target/NVPTX/NVPTXInstrInfo.td b/llvm/lib/Target/NVPTX/NVPTXInstrInfo.td
index 8f4eddb..8b34ce4 100644
--- a/llvm/lib/Target/NVPTX/NVPTXInstrInfo.td
+++ b/llvm/lib/Target/NVPTX/NVPTXInstrInfo.td
@@ -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()">;
@@ -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)]>;
diff --git a/llvm/lib/Target/NVPTX/NVPTXSubtarget.h b/llvm/lib/Target/NVPTX/NVPTXSubtarget.h
index 8b9059b..e785bbf 100644
--- a/llvm/lib/Target/NVPTX/NVPTXSubtarget.h
+++ b/llvm/lib/Target/NVPTX/NVPTXSubtarget.h
@@ -95,6 +95,14 @@ public:
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; }
diff --git a/llvm/lib/Target/NVPTX/NVPTXTargetMachine.cpp b/llvm/lib/Target/NVPTX/NVPTXTargetMachine.cpp
index 8e6e439..2eb8b17 100644
--- a/llvm/lib/Target/NVPTX/NVPTXTargetMachine.cpp
+++ b/llvm/lib/Target/NVPTX/NVPTXTargetMachine.cpp
@@ -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() {
diff --git a/llvm/lib/Transforms/Instrumentation/PGOCtxProfLowering.cpp b/llvm/lib/Transforms/Instrumentation/PGOCtxProfLowering.cpp
index b620306..e7b7c26 100644
--- a/llvm/lib/Transforms/Instrumentation/PGOCtxProfLowering.cpp
+++ b/llvm/lib/Transforms/Instrumentation/PGOCtxProfLowering.cpp
@@ -154,15 +154,15 @@ 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*/
@@ -170,13 +170,12 @@ CtxInstrumentationLowerer::CtxInstrumentationLowerer(Module &M,
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.
@@ -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(),
@@ -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;
}
diff --git a/llvm/lib/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.cpp b/llvm/lib/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.cpp
index 7aea466..7084381 100644
--- a/llvm/lib/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.cpp
+++ b/llvm/lib/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.cpp
@@ -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;
}
@@ -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;
}
@@ -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) {
@@ -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>
@@ -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");
diff --git a/llvm/test/CodeGen/NVPTX/unreachable.ll b/llvm/test/CodeGen/NVPTX/unreachable.ll
index f911890..6bd583c 100644
--- a/llvm/test/CodeGen/NVPTX/unreachable.ll
+++ b/llvm/test/CodeGen/NVPTX/unreachable.ll
@@ -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}
diff --git a/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/DependencyGraphTest.cpp b/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/DependencyGraphTest.cpp
index 6b3d9cc..5a9c981 100644
--- a/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/DependencyGraphTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/SandboxVectorizer/DependencyGraphTest.cpp
@@ -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;
}
};
@@ -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);
@@ -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) {
@@ -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) {
@@ -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) {
@@ -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) {
@@ -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) {
@@ -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));
}
@@ -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.
@@ -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.
@@ -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
@@ -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
@@ -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));
}