diff options
author | Vitaly Buka <vitalybuka@google.com> | 2024-10-10 13:11:22 -0700 |
---|---|---|
committer | Vitaly Buka <vitalybuka@google.com> | 2024-10-10 13:11:22 -0700 |
commit | 4ca4c25b6eb5fb4827c5874c17a54a5c7ab3ad3e (patch) | |
tree | d2296ffd2fdedea61f769d4e168daa108b052877 | |
parent | d146f5a231e6b274281a8d09f2ee2bb06029562f (diff) | |
parent | 942fefe74112acb68fa43dde44abe3ae125457e1 (diff) | |
download | llvm-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]
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)); } |