| // Copyright 2019 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "tools/debugger/debug_app.h" |
| |
| #include <GLES2/gl2.h> |
| |
| #include <algorithm> |
| #include <cstdio> |
| |
| #include "absl/flags/flag.h" |
| #include "absl/memory/memory.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/types/optional.h" |
| #include "base/memory.h" |
| #include "base/source_location.h" |
| #include "base/status.h" |
| #include "rt/debug/debug_client.h" |
| #include "schemas/debug_service_generated.h" |
| #include "third_party/dear_imgui/imgui.h" |
| #include "third_party/dear_imgui/imgui_internal.h" |
| #include "vm/bytecode_module.h" |
| #include "vm/bytecode_tables_sequencer.h" |
| |
| namespace iree { |
| namespace rt { |
| namespace debug { |
| namespace { |
| |
| void PushButtonHue(float hue) { |
| ImGui::PushStyleColor(ImGuiCol_Button, |
| (ImVec4)ImColor::HSV(hue / 7.0f, 0.6f, 0.6f)); |
| ImGui::PushStyleColor(ImGuiCol_ButtonHovered, |
| (ImVec4)ImColor::HSV(hue / 7.0f, 0.7f, 0.7f)); |
| ImGui::PushStyleColor(ImGuiCol_ButtonActive, |
| (ImVec4)ImColor::HSV(hue / 7.0f, 0.8f, 0.8f)); |
| } |
| |
| void PushButtonColor(const ImVec4& color) { |
| ImGui::PushStyleColor(ImGuiCol_Button, color); |
| ImGui::PushStyleColor(ImGuiCol_ButtonHovered, color); |
| ImGui::PushStyleColor(ImGuiCol_ButtonActive, color); |
| } |
| |
| void PopButtonStyle() { ImGui::PopStyleColor(3); } |
| |
| bool AreBreakpointsEqual(const RemoteBreakpoint& breakpoint, |
| const DebugApp::UserBreakpoint& user_breakpoint) { |
| if (user_breakpoint.active_breakpoint == &breakpoint) { |
| return true; |
| } else if (user_breakpoint.type != breakpoint.type()) { |
| return false; |
| } |
| switch (breakpoint.type()) { |
| case RemoteBreakpoint::Type::kBytecodeFunction: |
| if (user_breakpoint.function_ordinal != -1 && |
| user_breakpoint.function_ordinal != breakpoint.function_ordinal()) { |
| return false; |
| } |
| return breakpoint.module_name() == user_breakpoint.module_name && |
| breakpoint.function_name() == user_breakpoint.function_name && |
| breakpoint.bytecode_offset() == user_breakpoint.bytecode_offset; |
| case RemoteBreakpoint::Type::kNativeFunction: |
| return breakpoint.function_name() == user_breakpoint.native_function; |
| default: |
| return false; |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| void DebugApp::PumpMainLoopThunk(void* arg) { |
| auto status = reinterpret_cast<DebugApp*>(arg)->PumpMainLoop(); |
| if (IsCancelled(status)) { |
| return; |
| } else if (!status.ok()) { |
| CHECK_OK(status); |
| } |
| } |
| |
| DebugApp::DebugApp(SDL_Window* window, SDL_GLContext gl_context, |
| const char* glsl_version) |
| : window_(window), gl_context_(gl_context) { |
| VLOG(1) << "DebugApp initializing..."; |
| IMGUI_CHECKVERSION(); |
| ImGui::CreateContext(); |
| ImGuiIO& io = ImGui::GetIO(); |
| io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; |
| io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; |
| |
| // TODO(benvanik): ini file for settings. |
| io.IniFilename = nullptr; |
| // ImGui::LoadIniSettingsFromMemory() |
| // ImGui::SaveIniSettingsToMemory() |
| |
| // TODO(benvanik): theming. |
| ImGui::StyleColorsDark(); |
| |
| // Setup Platform/Renderer bindings |
| ImGui_ImplSDL2_InitForOpenGL(window_, gl_context_); |
| ImGui_ImplOpenGL3_Init(glsl_version); |
| SDL_GL_MakeCurrent(nullptr, nullptr); |
| VLOG(1) << "DebugApp initialized"; |
| } |
| |
| DebugApp::~DebugApp() { |
| VLOG(1) << "DebugApp shutting down..."; |
| ImGui_ImplOpenGL3_Shutdown(); |
| ImGui_ImplSDL2_Shutdown(); |
| ImGui::DestroyContext(); |
| |
| SDL_GL_DeleteContext(gl_context_); |
| SDL_GL_MakeCurrent(nullptr, nullptr); |
| SDL_DestroyWindow(window_); |
| SDL_Quit(); |
| VLOG(1) << "DebugApp shut down (SDL_Quit)"; |
| } |
| |
| Status DebugApp::Connect(absl::string_view service_address) { |
| VLOG(1) << "Connecting to debug service at " << service_address << "..."; |
| ASSIGN_OR_RETURN(debug_client_, DebugClient::Connect(service_address, this)); |
| |
| // TODO(benvanik): load breakpoints from file. |
| UserBreakpoint user_breakpoint; |
| user_breakpoint.module_name = "module"; |
| user_breakpoint.function_name = "main"; |
| user_breakpoint.bytecode_offset = 0; |
| user_breakpoint.wants_enabled = true; |
| user_breakpoint_list_.push_back(std::move(user_breakpoint)); |
| RETURN_IF_ERROR(RefreshActiveBreakpoints()); |
| |
| // Set paused so that we need to resume to continue execution. |
| is_paused_ = true; |
| return OkStatus(); |
| } |
| |
| Status DebugApp::Disconnect() { |
| VLOG(1) << "Disconnecting from debug service"; |
| debug_client_.reset(); |
| return OkStatus(); |
| } |
| |
| bool DebugApp::is_paused() const { |
| if (!debug_client_) { |
| return false; |
| } |
| if (!hit_breakpoints_.empty()) { |
| return true; // One or more breakpoints hit. |
| } |
| return is_paused_ || !is_stepping_; |
| } |
| |
| RemoteInvocation* DebugApp::GetSelectedInvocation() const { |
| if (!debug_client_ || !selected_invocation_id_.has_value()) { |
| return nullptr; |
| } |
| for (auto* invocation : debug_client_->invocations()) { |
| if (invocation->id() == selected_invocation_id_.value()) { |
| return invocation; |
| } |
| } |
| return nullptr; |
| } |
| |
| Status DebugApp::RefreshActiveBreakpoints() { |
| // Set all breakpoints to disabled. We'll re-enable them as we find them |
| // below. |
| for (auto& user_breakpoint : user_breakpoint_list_) { |
| user_breakpoint.active_breakpoint = nullptr; |
| } |
| |
| // If not connected then no breakpoints are active. |
| if (!debug_client_) { |
| return OkStatus(); |
| } |
| |
| // Reconcile the user breakpoint list with the breakpoints available on the |
| // server. |
| for (auto* breakpoint : debug_client_->breakpoints()) { |
| auto it = |
| std::find_if(user_breakpoint_list_.begin(), user_breakpoint_list_.end(), |
| [breakpoint](const UserBreakpoint& user_breakpoint) { |
| return AreBreakpointsEqual(*breakpoint, user_breakpoint); |
| }); |
| if (it == user_breakpoint_list_.end()) { |
| // Breakpoint not found - add to user list. |
| UserBreakpoint user_breakpoint; |
| user_breakpoint.type = breakpoint->type(); |
| user_breakpoint.active_breakpoint = breakpoint; |
| user_breakpoint.module_name = breakpoint->module_name(); |
| user_breakpoint.function_name = breakpoint->function_name(); |
| user_breakpoint.function_ordinal = breakpoint->function_ordinal(); |
| user_breakpoint.bytecode_offset = breakpoint->bytecode_offset(); |
| user_breakpoint_list_.push_back(std::move(user_breakpoint)); |
| } else { |
| // Breakpoint found - set the active pointer. |
| UserBreakpoint& user_breakpoint = *it; |
| user_breakpoint.active_breakpoint = breakpoint; |
| user_breakpoint.is_enabling = false; |
| user_breakpoint.module_name = breakpoint->module_name(); |
| user_breakpoint.function_name = breakpoint->function_name(); |
| user_breakpoint.function_ordinal = breakpoint->function_ordinal(); |
| user_breakpoint.bytecode_offset = breakpoint->bytecode_offset(); |
| } |
| } |
| |
| // Ensure any breakpoint the user wants enabled is active/otherwise. |
| for (auto& user_breakpoint : user_breakpoint_list_) { |
| if (user_breakpoint.wants_enabled && !user_breakpoint.is_enabling && |
| !user_breakpoint.active_breakpoint) { |
| // Add breakpoint on server. |
| switch (user_breakpoint.type) { |
| case RemoteBreakpoint::Type::kBytecodeFunction: |
| RETURN_IF_ERROR(debug_client_->AddFunctionBreakpoint( |
| user_breakpoint.module_name, user_breakpoint.function_name, |
| user_breakpoint.bytecode_offset, |
| [&user_breakpoint](const RemoteBreakpoint& breakpoint) { |
| user_breakpoint.function_ordinal = |
| breakpoint.function_ordinal(); |
| })); |
| break; |
| case RemoteBreakpoint::Type::kNativeFunction: |
| // TODO(benvanik): native breakpoint support. |
| return UnimplementedErrorBuilder(IREE_LOC) |
| << "Native function breakpoints are TODO"; |
| default: |
| return UnimplementedErrorBuilder(IREE_LOC) |
| << "Unimplemented breakpoint type"; |
| } |
| user_breakpoint.is_enabling = true; |
| } else if (!user_breakpoint.wants_enabled && |
| user_breakpoint.active_breakpoint) { |
| // Remove breakpoint from server. |
| RETURN_IF_ERROR( |
| debug_client_->RemoveBreakpoint(*user_breakpoint.active_breakpoint)); |
| |
| user_breakpoint.active_breakpoint = nullptr; |
| } |
| } |
| |
| return OkStatus(); |
| } |
| |
| bool DebugApp::IsStoppedAtBreakpoint( |
| const UserBreakpoint& user_breakpoint) const { |
| return std::find(hit_breakpoints_.begin(), hit_breakpoints_.end(), |
| user_breakpoint.active_breakpoint) != hit_breakpoints_.end(); |
| } |
| |
| int DebugApp::FindMatchingUserBreakpointIndex(absl::string_view module_name, |
| int function_ordinal, |
| int offset) { |
| for (int i = 0; i < user_breakpoint_list_.size(); ++i) { |
| auto& user_breakpoint = user_breakpoint_list_[i]; |
| if (user_breakpoint.module_name == module_name && |
| user_breakpoint.function_ordinal == function_ordinal && |
| user_breakpoint.bytecode_offset == offset) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| int DebugApp::FindMatchingUserBreakpointIndex(absl::string_view module_name, |
| absl::string_view function_name, |
| int offset) { |
| for (int i = 0; i < user_breakpoint_list_.size(); ++i) { |
| auto& user_breakpoint = user_breakpoint_list_[i]; |
| if (user_breakpoint.module_name == module_name && |
| user_breakpoint.function_name == function_name && |
| user_breakpoint.bytecode_offset == offset) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| Status DebugApp::ResumeFromBreakpoint(UserBreakpoint* user_breakpoint) { |
| if (!user_breakpoint->active_breakpoint) { |
| return FailedPreconditionErrorBuilder(IREE_LOC) << "Breakpoint not active"; |
| } |
| VLOG(1) << "Resuming from breakpoint " |
| << user_breakpoint->active_breakpoint->id() << "..."; |
| auto it = std::find(hit_breakpoints_.begin(), hit_breakpoints_.end(), |
| user_breakpoint->active_breakpoint); |
| if (it == hit_breakpoints_.end()) { |
| return NotFoundErrorBuilder(IREE_LOC) << "Breakpoint not found"; |
| } |
| hit_breakpoints_.erase(it); |
| return debug_client_->MakeReady(); |
| } |
| |
| Status DebugApp::OnContextRegistered(const RemoteContext& context) { |
| // Ack event. |
| return debug_client_->MakeReady(); |
| } |
| |
| Status DebugApp::OnContextUnregistered(const RemoteContext& context) { |
| // Close documents that may reference modules in the context. |
| std::vector<CodeViewDocument*> closing_documents; |
| for (auto& document : documents_) { |
| auto* module = document->function->module(); |
| if (module->context_id() != context.id()) { |
| // Document is not from this context so it's fine. |
| continue; |
| } |
| |
| // See if any other live context still has the module loaded. We can change |
| // the document over to that. |
| RemoteModule* replacement_module = nullptr; |
| for (auto* context : debug_client_->contexts()) { |
| for (auto* other_module : context->modules()) { |
| if (other_module->name() == module->name()) { |
| replacement_module = other_module; |
| break; |
| } |
| } |
| if (replacement_module) break; |
| } |
| if (replacement_module && replacement_module->is_loaded()) { |
| // Replace document module reference. |
| int function_ordinal = document->function->ordinal(); |
| auto functions = replacement_module->functions(); |
| if (function_ordinal < functions.size()) { |
| document->function = functions[function_ordinal]; |
| } else { |
| document->function = nullptr; |
| } |
| } else { |
| document->function = nullptr; |
| } |
| |
| if (!document->function) { |
| // Close the document if we don't have a valid function for it. |
| VLOG(1) |
| << "Closing document " << document->title |
| << " because the last context using the module is being unregistered"; |
| closing_documents.push_back(document.get()); |
| } |
| } |
| for (auto* document : closing_documents) { |
| auto it = std::find_if( |
| documents_.begin(), documents_.end(), |
| [document](const std::unique_ptr<CodeViewDocument>& open_document) { |
| return document == open_document.get(); |
| }); |
| documents_.erase(it); |
| } |
| |
| // Ack event. |
| return debug_client_->MakeReady(); |
| } |
| |
| Status DebugApp::OnModuleLoaded(const RemoteContext& context, |
| const RemoteModule& module) { |
| // Ack event. |
| return debug_client_->MakeReady(); |
| } |
| |
| Status DebugApp::OnInvocationRegistered(const RemoteInvocation& invocation) { |
| if (!selected_invocation_id_.has_value()) { |
| selected_invocation_id_ = invocation.id(); |
| selected_stack_frame_index_ = {}; |
| } |
| |
| // Ack event. |
| return debug_client_->MakeReady(); |
| } |
| |
| Status DebugApp::OnInvocationUnregistered(const RemoteInvocation& invocation) { |
| if (selected_invocation_id_.has_value() && |
| selected_invocation_id_.value() == invocation.id()) { |
| selected_invocation_id_ = {}; |
| selected_stack_frame_index_ = {}; |
| } |
| |
| // Ack event. |
| return debug_client_->MakeReady(); |
| } |
| |
| Status DebugApp::OnBreakpointHit(const RemoteBreakpoint& breakpoint, |
| const RemoteInvocation& invocation) { |
| // Keep track of where we are stopped. |
| hit_breakpoints_.push_back(&breakpoint); |
| return NavigateToCodeView(invocation, -1, NavigationMode::kMatchDocument); |
| } |
| |
| Status DebugApp::PumpMainLoop() { |
| ImGuiIO& io = ImGui::GetIO(); |
| |
| if (debug_client_) { |
| RETURN_IF_ERROR(debug_client_->Poll()); |
| } |
| RETURN_IF_ERROR(RefreshActiveBreakpoints()); |
| |
| SDL_GL_MakeCurrent(window_, gl_context_); |
| |
| SDL_Event event; |
| while (SDL_PollEvent(&event)) { |
| ImGui_ImplSDL2_ProcessEvent(&event); |
| if (event.type == SDL_QUIT) { |
| return CancelledErrorBuilder(IREE_LOC) << "Quit hotkey"; |
| } else if (event.type == SDL_WINDOWEVENT && |
| event.window.event == SDL_WINDOWEVENT_CLOSE && |
| event.window.windowID == SDL_GetWindowID(window_)) { |
| return CancelledErrorBuilder(IREE_LOC) << "Window closed"; |
| } |
| } |
| ImGui_ImplOpenGL3_NewFrame(); |
| ImGui_ImplSDL2_NewFrame(window_); |
| ImGui::NewFrame(); |
| |
| auto draw_status = DrawUI(); |
| if (!draw_status.ok()) { |
| // TODO(benvanik): show on screen? Probably all messed up. |
| LOG(ERROR) << draw_status; |
| } |
| |
| // Blit the entire ImGui UI. |
| ImGui::Render(); |
| SDL_GL_MakeCurrent(window_, gl_context_); |
| glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); |
| glClearColor(0.45f, 0.55f, 0.60f, 1.0f); |
| glClear(GL_COLOR_BUFFER_BIT); |
| // Workaround for terrible bad SDL/graphics driver leaks. |
| IREE_DISABLE_LEAK_CHECKS(); |
| ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); |
| IREE_ENABLE_LEAK_CHECKS(); |
| |
| // Render additional viewport windows (desktop only). |
| if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { |
| SDL_Window* backup_current_window = SDL_GL_GetCurrentWindow(); |
| SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext(); |
| ImGui::UpdatePlatformWindows(); |
| ImGui::RenderPlatformWindowsDefault(); |
| SDL_GL_MakeCurrent(backup_current_window, backup_current_context); |
| } |
| |
| SDL_GL_SwapWindow(window_); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::LayoutInitialDockSpace() { |
| dockspace_id_ = ImGui::GetID("MainDockSpace"); |
| if (ImGui::DockBuilderGetNode(dockspace_id_)) { |
| // Already configured. |
| return OkStatus(); |
| } |
| ImGui::DockBuilderAddNode(dockspace_id_, ImGuiDockNodeFlags_DockSpace); |
| |
| dock_content_id_ = dockspace_id_; |
| dock_top_id_ = ImGui::DockBuilderSplitNode(dock_content_id_, ImGuiDir_Up, |
| 0.05f, nullptr, &dock_content_id_); |
| dock_left_id_ = ImGui::DockBuilderSplitNode( |
| dock_content_id_, ImGuiDir_Left, 0.20f, nullptr, &dock_content_id_); |
| dock_bottom_id_ = ImGui::DockBuilderSplitNode( |
| dock_content_id_, ImGuiDir_Down, 0.20f, nullptr, &dock_content_id_); |
| dock_right_id_ = ImGui::DockBuilderSplitNode( |
| dock_content_id_, ImGuiDir_Right, 0.20f, nullptr, &dock_content_id_); |
| dock_bottom_left_id_ = ImGui::DockBuilderSplitNode( |
| dock_bottom_id_, ImGuiDir_Left, 0.50f, nullptr, &dock_bottom_right_id_); |
| |
| ImGui::DockBuilderDockWindow("Toolbar", dock_top_id_); |
| auto* dock_top_node = ImGui::DockBuilderGetNode(dock_top_id_); |
| dock_top_node->LocalFlags = ImGuiDockNodeFlags_NoSplit | |
| ImGuiDockNodeFlags_NoResize | |
| ImGuiDockNodeFlags_AutoHideTabBar; |
| |
| ImGui::DockBuilderDockWindow("Modules", dock_left_id_); |
| ImGui::DockBuilderDockWindow("Locals", dock_bottom_left_id_); |
| ImGui::DockBuilderDockWindow("Invocations", dock_bottom_right_id_); |
| ImGui::DockBuilderDockWindow("Breakpoints", dock_bottom_right_id_); |
| |
| ImGui::DockBuilderFinish(dockspace_id_); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawUI() { |
| ImGuiWindowFlags window_flags = |
| ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; |
| window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | |
| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | |
| ImGuiWindowFlags_NoNavFocus; |
| |
| ImGuiViewport* viewport = ImGui::GetMainViewport(); |
| ImGui::SetNextWindowPos(viewport->Pos); |
| ImGui::SetNextWindowSize(viewport->Size); |
| ImGui::SetNextWindowViewport(viewport->ID); |
| ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); |
| ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); |
| ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); |
| ImGui::Begin("IREEDebugRoot", nullptr, window_flags); |
| ImGui::PopStyleVar(3); |
| |
| RETURN_IF_ERROR(LayoutInitialDockSpace()); |
| ImGui::DockSpace(dockspace_id_, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); |
| |
| RETURN_IF_ERROR(DrawMainMenu()); |
| RETURN_IF_ERROR(DrawToolbar()); |
| |
| ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(2, 2)); |
| RETURN_IF_ERROR(DrawBreakpointListPanel()); |
| RETURN_IF_ERROR(DrawModuleListPanel()); |
| RETURN_IF_ERROR(DrawLocalListPanel()); |
| RETURN_IF_ERROR(DrawInvocationListPanel()); |
| ImGui::PopStyleVar(); |
| |
| RETURN_IF_ERROR(DrawCodeViewPanels()); |
| |
| ImGui::End(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawMainMenu() { |
| if (!ImGui::BeginMenuBar()) return OkStatus(); |
| |
| // TODO(benvanik): main menu. |
| if (ImGui::BeginMenu("File")) { |
| ImGui::EndMenu(); |
| } |
| |
| ImGui::EndMenuBar(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawToolbar() { |
| // TODO(benvanik): figure out how to make this not grow. |
| ImGui::Begin("Toolbar", nullptr, |
| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | |
| ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | |
| ImGuiWindowFlags_NoScrollbar); |
| ImGui::BeginGroup(); |
| |
| #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) |
| static bool show_demo_window = false; |
| if (ImGui::Button("Demo")) { |
| show_demo_window = !show_demo_window; |
| } |
| if (show_demo_window) { |
| ImGui::SetNextWindowDockID(dock_content_id_); |
| ImGui::ShowDemoWindow(&show_demo_window); |
| } |
| #endif // !IMGUI_DISABLE_DEMO_WINDOWS |
| |
| ImGui::SameLine(); |
| if (!debug_client_) { |
| if (ImGui::Button("Connect")) { |
| // TODO(benvanik): connection dialog and/or autoconnect. |
| } |
| } else { |
| if (ImGui::Button("Disconnect")) { |
| debug_client_.reset(); |
| } |
| } |
| |
| ImGui::SameLine(); |
| if (debug_client_) { |
| ImGui::Text("<status>"); |
| } else { |
| ImGui::TextDisabled("disconnected"); |
| } |
| |
| ImGui::SameLine(); |
| ImGui::Spacing(); |
| ImGui::SameLine(); |
| ImGui::Spacing(); |
| |
| ImGui::SameLine(); |
| ImGui::BeginGroup(); |
| ImGui::Text("Invocation: "); |
| ImGui::SameLine(); |
| ImGui::SetNextItemWidth(300); |
| auto* selected_invocation = GetSelectedInvocation(); |
| const std::string& active_invocation_name = |
| selected_invocation ? selected_invocation->name() : ""; |
| if (ImGui::BeginCombo("##active_invocation", active_invocation_name.c_str(), |
| ImGuiComboFlags_PopupAlignLeft)) { |
| if (debug_client_) { |
| for (auto* invocation : debug_client_->invocations()) { |
| ImGui::PushID(invocation->id()); |
| bool is_selected = invocation == selected_invocation; |
| if (ImGui::Selectable(invocation->name().c_str(), is_selected)) { |
| RETURN_IF_ERROR(NavigateToCodeView(*invocation, -1, |
| NavigationMode::kMatchDocument)); |
| } |
| if (is_selected) { |
| ImGui::SetItemDefaultFocus(); |
| } |
| ImGui::PopID(); |
| } |
| } |
| ImGui::EndCombo(); |
| } |
| ImGui::EndGroup(); |
| |
| ImGui::SameLine(); |
| ImGui::BeginGroup(); |
| static const float kPauseButtonHue = 0.0f; |
| static const float kResumeButtonHue = 2.0f; |
| static const float kStepButtonHue = 1.0f; |
| if (debug_client_ && !is_paused()) { |
| PushButtonHue(kPauseButtonHue); |
| if (ImGui::Button("Pause")) { |
| RETURN_IF_ERROR(debug_client_->SuspendAllInvocations()); |
| } |
| PopButtonStyle(); |
| } else if (debug_client_ && is_paused()) { |
| ImGui::PushStyleColor(ImGuiCol_Button, 0xFF666666); |
| ImGui::PushStyleColor(ImGuiCol_Text, 0xFFAAAAAA); |
| ImGui::ButtonEx("Pause", {}, ImGuiButtonFlags_Disabled); |
| ImGui::PopStyleColor(2); |
| } |
| if (debug_client_ && is_paused()) { |
| ImGui::SameLine(); |
| PushButtonHue(kResumeButtonHue); |
| if (ImGui::Button("Resume")) { |
| if (is_paused_) { |
| is_paused_ = false; |
| RETURN_IF_ERROR(debug_client_->MakeReady()); |
| } |
| while (!hit_breakpoints_.empty()) { |
| hit_breakpoints_.pop_back(); |
| RETURN_IF_ERROR(debug_client_->MakeReady()); |
| } |
| } |
| PopButtonStyle(); |
| } else { |
| ImGui::PushStyleColor(ImGuiCol_Button, 0xFF666666); |
| ImGui::PushStyleColor(ImGuiCol_Text, 0xFFAAAAAA); |
| ImGui::SameLine(); |
| ImGui::ButtonEx("Resume", {}, ImGuiButtonFlags_Disabled); |
| ImGui::PopStyleColor(2); |
| } |
| |
| if (debug_client_ && is_paused() && selected_invocation) { |
| ImGui::SameLine(); |
| PushButtonHue(kStepButtonHue); |
| if (ImGui::Button("Step Into")) { |
| RETURN_IF_ERROR( |
| debug_client_->StepInvocation(*selected_invocation, [this]() { |
| is_paused_ = true; |
| is_stepping_ = false; |
| })); |
| is_stepping_ = true; |
| } |
| PopButtonStyle(); |
| ImGui::SameLine(); |
| if (ImGui::Button("Step Over")) { |
| RETURN_IF_ERROR( |
| debug_client_->StepInvocationOver(*selected_invocation, [this]() { |
| is_paused_ = true; |
| is_stepping_ = false; |
| })); |
| is_stepping_ = true; |
| } |
| ImGui::SameLine(); |
| if (ImGui::Button("Step Out")) { |
| RETURN_IF_ERROR( |
| debug_client_->StepInvocationOut(*selected_invocation, [this]() { |
| is_paused_ = true; |
| is_stepping_ = false; |
| })); |
| is_stepping_ = true; |
| } |
| if (ImGui::BeginPopup("Step to...")) { |
| // TODO(benvanik): step to Invoke exit, next FFI call, etc |
| ImGui::MenuItem("(stuff)"); |
| ImGui::EndPopup(); |
| } |
| ImGui::SameLine(); |
| if (ImGui::Button("Step to...")) { |
| ImGui::OpenPopup("Step to..."); |
| } |
| } else { |
| ImGui::PushStyleColor(ImGuiCol_Button, 0xFF666666); |
| ImGui::PushStyleColor(ImGuiCol_Text, 0xFFAAAAAA); |
| ImGui::SameLine(); |
| ImGui::ButtonEx("Step Into", {}, ImGuiButtonFlags_Disabled); |
| ImGui::SameLine(); |
| ImGui::ButtonEx("Step Over", {}, ImGuiButtonFlags_Disabled); |
| ImGui::SameLine(); |
| ImGui::ButtonEx("Step Out", {}, ImGuiButtonFlags_Disabled); |
| ImGui::SameLine(); |
| ImGui::ButtonEx("Step to...", {}, ImGuiButtonFlags_Disabled); |
| ImGui::PopStyleColor(2); |
| } |
| ImGui::EndGroup(); |
| |
| ImGui::EndGroup(); |
| ImGui::End(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawBreakpointListPanel() { |
| static bool is_panel_visible = true; |
| if (!ImGui::Begin("Breakpoints", &is_panel_visible, ImGuiWindowFlags_None)) { |
| ImGui::End(); |
| return OkStatus(); |
| } |
| |
| ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); |
| absl::optional<RemoteBreakpoint::Type> add_breakpoint_type; |
| if (ImGui::BeginPopup("+ Function")) { |
| if (ImGui::MenuItem("Bytecode Function")) { |
| add_breakpoint_type = RemoteBreakpoint::Type::kBytecodeFunction; |
| } |
| if (ImGui::MenuItem("Native Function")) { |
| add_breakpoint_type = RemoteBreakpoint::Type::kNativeFunction; |
| } |
| ImGui::EndPopup(); |
| } |
| ImGui::PopStyleVar(); |
| if (ImGui::Button("+ Function")) { |
| ImGui::OpenPopup("+ Function"); |
| } |
| RETURN_IF_ERROR(DrawAddBreakpointDialogs(add_breakpoint_type)); |
| |
| ImGui::SameLine(); |
| if (ImGui::Button("Remove All")) { |
| // TODO(benvanik): removal all is broken - need removebreakpoints or a |
| // 'want_removal' flag so that RefreshActiveBreakpoints handles things. |
| // Right now if you have 2 breakpoints and hit remove all the second will |
| // come back during the next refresh (as the server hasn't removed it yet). |
| for (auto& user_breakpoint : user_breakpoint_list_) { |
| if (user_breakpoint.active_breakpoint) { |
| RETURN_IF_ERROR(debug_client_->RemoveBreakpoint( |
| *user_breakpoint.active_breakpoint)); |
| user_breakpoint.active_breakpoint = nullptr; |
| } |
| } |
| user_breakpoint_list_.clear(); |
| } |
| ImGui::Separator(); |
| |
| ImGui::BeginChild("BreakpointList", ImVec2(-1, -1), false, |
| ImGuiWindowFlags_AlwaysVerticalScrollbar); |
| std::vector<UserBreakpoint*> dead_breakpoints; |
| for (auto& user_breakpoint : user_breakpoint_list_) { |
| ASSIGN_OR_RETURN(bool should_keep, DrawBreakpoint(&user_breakpoint)); |
| if (!should_keep) { |
| dead_breakpoints.push_back(&user_breakpoint); |
| } |
| } |
| for (auto* user_breakpoint : dead_breakpoints) { |
| for (auto it = user_breakpoint_list_.begin(); |
| it != user_breakpoint_list_.end(); ++it) { |
| if (&*it == user_breakpoint) { |
| if (user_breakpoint->active_breakpoint) { |
| RETURN_IF_ERROR(debug_client_->RemoveBreakpoint( |
| *user_breakpoint->active_breakpoint)); |
| } |
| user_breakpoint_list_.erase(it); |
| break; |
| } |
| } |
| } |
| ImGui::EndChild(); |
| |
| ImGui::End(); |
| return OkStatus(); |
| } |
| |
| StatusOr<bool> DebugApp::DrawBreakpoint(UserBreakpoint* user_breakpoint) { |
| std::string breakpoint_name; |
| switch (user_breakpoint->type) { |
| case RemoteBreakpoint::Type::kBytecodeFunction: |
| breakpoint_name = |
| absl::StrCat("[bytecode] ", user_breakpoint->module_name, ":", |
| user_breakpoint->function_name, ":", |
| user_breakpoint->bytecode_offset); |
| if (user_breakpoint->function_ordinal != -1) { |
| absl::StrAppend(&breakpoint_name, " @", |
| user_breakpoint->function_ordinal); |
| } |
| break; |
| case RemoteBreakpoint::Type::kNativeFunction: |
| breakpoint_name = |
| absl::StrCat("[native ] ", user_breakpoint->native_function); |
| break; |
| } |
| ImGui::BeginGroup(); |
| bool is_closing = true; |
| bool is_expanded = ImGui::CollapsingHeader( |
| ("##" + breakpoint_name).c_str(), &is_closing, |
| ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | |
| ImGuiTreeNodeFlags_NoAutoOpenOnLog | ImGuiTreeNodeFlags_OpenOnArrow | |
| ImGuiTreeNodeFlags_OpenOnDoubleClick); |
| ImGui::SameLine(); |
| ImGui::Checkbox(breakpoint_name.c_str(), &user_breakpoint->wants_enabled); |
| ImGui::EndGroup(); |
| if (!is_expanded) { |
| return is_closing; |
| } |
| ImGui::PushID(breakpoint_name.c_str()); |
| |
| ImGui::Text("(breakpoint stats/etc)"); |
| |
| ImGui::PopID(); |
| return is_closing; |
| } |
| |
| Status DebugApp::DrawAddBreakpointDialogs( |
| absl::optional<RemoteBreakpoint::Type> add_breakpoint_type) { |
| if (add_breakpoint_type.has_value()) { |
| switch (add_breakpoint_type.value()) { |
| case RemoteBreakpoint::Type::kBytecodeFunction: |
| ImGui::OpenPopup("Add Bytecode Function Breakpoint"); |
| break; |
| case RemoteBreakpoint::Type::kNativeFunction: |
| ImGui::OpenPopup("Add Native Function Breakpoint"); |
| break; |
| } |
| } |
| ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8)); |
| RETURN_IF_ERROR(DrawAddBytecodeFunctionBreakpointDialog()); |
| RETURN_IF_ERROR(DrawAddNativeFunctionBreakpointDialog()); |
| ImGui::PopStyleVar(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawAddBytecodeFunctionBreakpointDialog() { |
| ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); |
| bool close_popup = true; |
| if (!ImGui::BeginPopupModal("Add Bytecode Function Breakpoint", &close_popup, |
| ImGuiWindowFlags_None)) { |
| return OkStatus(); |
| } |
| ImGui::BeginGroup(); |
| ImGui::BeginChild("##data_entry", |
| ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); |
| |
| ImGui::TextWrapped( |
| "Adds a breakpoint set on the entry of the function (offset=0)."); |
| ImGui::Separator(); |
| |
| // TODO(benvanik): fancy list, filtering, etc. |
| |
| static char module_name[256] = {0}; |
| ImGui::InputText("Module", module_name, sizeof(module_name)); |
| ImGui::SetItemDefaultFocus(); |
| |
| static char function_name[256] = {0}; |
| ImGui::InputText("Function", function_name, sizeof(function_name)); |
| |
| ImGui::EndChild(); |
| ImGui::Separator(); |
| |
| if (ImGui::Button("Add")) { |
| int offset = 0; |
| if (FindMatchingUserBreakpointIndex(module_name, function_name, offset) == |
| -1) { |
| UserBreakpoint user_breakpoint; |
| user_breakpoint.type = RemoteBreakpoint::Type::kBytecodeFunction; |
| user_breakpoint.module_name = module_name; |
| user_breakpoint.function_name = function_name; |
| user_breakpoint.bytecode_offset = offset; |
| user_breakpoint.wants_enabled = true; |
| user_breakpoint_list_.push_back(std::move(user_breakpoint)); |
| } |
| ImGui::CloseCurrentPopup(); |
| } |
| ImGui::SameLine(); |
| if (ImGui::Button("Cancel")) { |
| ImGui::CloseCurrentPopup(); |
| } |
| |
| ImGui::EndGroup(); |
| ImGui::EndPopup(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawAddNativeFunctionBreakpointDialog() { |
| ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); |
| bool close_popup = true; |
| if (!ImGui::BeginPopupModal("Add Native Function Breakpoint", &close_popup, |
| ImGuiWindowFlags_None)) { |
| return OkStatus(); |
| } |
| ImGui::BeginGroup(); |
| ImGui::BeginChild("##data_entry", |
| ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); |
| |
| ImGui::TextWrapped( |
| "Adds a breakpoint set on any call to the given FFI imported " |
| "function."); |
| ImGui::Separator(); |
| |
| static char function_name[256] = {0}; |
| ImGui::InputText("Function", function_name, sizeof(function_name)); |
| ImGui::SetItemDefaultFocus(); |
| |
| ImGui::EndChild(); |
| ImGui::Separator(); |
| |
| if (ImGui::Button("Add")) { |
| UserBreakpoint user_breakpoint; |
| user_breakpoint.type = RemoteBreakpoint::Type::kNativeFunction; |
| user_breakpoint.native_function = function_name; |
| user_breakpoint.wants_enabled = true; |
| user_breakpoint_list_.push_back(std::move(user_breakpoint)); |
| ImGui::CloseCurrentPopup(); |
| } |
| ImGui::SameLine(); |
| if (ImGui::Button("Cancel")) { |
| ImGui::CloseCurrentPopup(); |
| } |
| |
| ImGui::EndGroup(); |
| ImGui::EndPopup(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawModuleListPanel() { |
| static bool is_panel_visible = true; |
| if (!ImGui::Begin("Modules", &is_panel_visible, ImGuiWindowFlags_None)) { |
| ImGui::End(); |
| return OkStatus(); |
| } else if (!debug_client_) { |
| ImGui::TextDisabled("disconnected"); |
| ImGui::End(); |
| return OkStatus(); |
| } |
| ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4, 4)); |
| |
| ImGui::BeginGroup(); |
| ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth()); |
| static char function_name_filter_text[256] = {0}; |
| ImGui::InputTextWithHint( |
| "##function_name_filter", "Filter functions", function_name_filter_text, |
| sizeof(function_name_filter_text), ImGuiInputTextFlags_AutoSelectAll); |
| ImGuiTextFilter function_name_filter(function_name_filter_text); |
| ImGui::EndGroup(); |
| |
| ImGui::Separator(); |
| |
| ImGui::BeginGroup(); |
| ImGui::BeginChild("##context_list", ImVec2(0, -ImGui::GetFrameHeight())); |
| for (auto* context : debug_client_->contexts()) { |
| RETURN_IF_ERROR(DrawContext(*context, function_name_filter)); |
| } |
| ImGui::EndChild(); |
| ImGui::EndGroup(); |
| |
| ImGui::PopStyleVar(); |
| ImGui::End(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawContext(const RemoteContext& context, |
| const ImGuiTextFilter& filter) { |
| std::string context_name = absl::StrCat("Context ", context.id()); |
| if (!ImGui::CollapsingHeader(context_name.c_str(), nullptr, |
| ImGuiTreeNodeFlags_DefaultOpen | |
| ImGuiTreeNodeFlags_Framed | |
| ImGuiTreeNodeFlags_NoTreePushOnOpen | |
| ImGuiTreeNodeFlags_NoAutoOpenOnLog | |
| ImGuiTreeNodeFlags_OpenOnArrow | |
| ImGuiTreeNodeFlags_OpenOnDoubleClick)) { |
| return OkStatus(); |
| } |
| ImGui::PushID(context.id()); |
| for (auto* module : context.modules()) { |
| RETURN_IF_ERROR(DrawModule(module, filter)); |
| } |
| ImGui::PopID(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawModule(RemoteModule* module, |
| const ImGuiTextFilter& filter) { |
| ImGui::PushID(module->name().c_str()); |
| if (ImGui::TreeNodeEx(module->name().c_str(), |
| ImGuiTreeNodeFlags_Framed | |
| ImGuiTreeNodeFlags_DefaultOpen | |
| ImGuiTreeNodeFlags_OpenOnDoubleClick | |
| ImGuiTreeNodeFlags_OpenOnArrow)) { |
| if (module->CheckLoadedOrRequest()) { |
| for (auto* function : module->functions()) { |
| char function_name[128]; |
| if (function->name().empty()) { |
| std::snprintf(function_name, sizeof(function_name), "@%d", |
| function->ordinal()); |
| } else { |
| std::snprintf(function_name, sizeof(function_name), "@%d %s", |
| function->ordinal(), function->name().c_str()); |
| } |
| if (filter.IsActive() && !filter.PassFilter(function_name)) { |
| continue; |
| } |
| ImGui::PushID(function->ordinal()); |
| bool is_selected = false; |
| if (ImGui::Selectable("##selectable", &is_selected, |
| ImGuiSelectableFlags_AllowDoubleClick | |
| ImGuiSelectableFlags_DrawFillAvailWidth)) { |
| if (is_selected) { |
| RETURN_IF_ERROR(NavigateToCodeView(module->name(), |
| function->ordinal(), 0, |
| NavigationMode::kMatchDocument)); |
| } |
| } |
| ImGui::SameLine(); |
| // TODO(benvanik): detect if breakpoint active at offset 0. |
| ImGui::BulletText("%s", function_name); |
| ImGui::PopID(); |
| } |
| } else { |
| ImGui::TextDisabled("Loading..."); |
| } |
| ImGui::TreePop(); |
| } |
| ImGui::PopID(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawLocalListPanel() { |
| static bool is_panel_visible = true; |
| if (!ImGui::Begin("Locals", &is_panel_visible, ImGuiWindowFlags_None)) { |
| ImGui::End(); |
| return OkStatus(); |
| } else if (!debug_client_) { |
| ImGui::TextDisabled("disconnected"); |
| ImGui::End(); |
| return OkStatus(); |
| } |
| auto* invocation = GetSelectedInvocation(); |
| if (!invocation) { |
| ImGui::TextDisabled("select a invocation to view locals"); |
| ImGui::End(); |
| return OkStatus(); |
| } else if (invocation->def().frames.empty()) { |
| ImGui::TextDisabled("(invocation has no frames)"); |
| ImGui::End(); |
| return OkStatus(); |
| } |
| int stack_frame_index = selected_stack_frame_index_.value_or(-1); |
| if (stack_frame_index == -1) { |
| stack_frame_index = invocation->def().frames.size() - 1; |
| } |
| auto& stack_frame = invocation->def().frames[stack_frame_index]; |
| |
| // TODO(benvanik): toggle for IREE VM locals vs. source locals. |
| for (int i = 0; i < stack_frame->locals.size(); ++i) { |
| auto& local = stack_frame->locals[i]; |
| RETURN_IF_ERROR(DrawLocal(invocation, stack_frame_index, i, *local)); |
| } |
| |
| ImGui::End(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawLocal(RemoteInvocation* invocation, int stack_frame_index, |
| int local_index, const rpc::BufferViewDefT& local) { |
| // TODO(benvanik): columns and such in fancy table. |
| ImGui::Text("l%d", local_index); |
| ImGui::SameLine(50); |
| if (local.is_valid) { |
| auto shape_str = |
| absl::StrCat(absl::StrJoin(local.shape, "x"), "x", local.element_size); |
| ImGui::Text("%s", shape_str.c_str()); |
| } else { |
| ImGui::TextDisabled("∅"); |
| } |
| // TODO(benvanik): editing options (change shape, change contents, upload). |
| // TODO(benvanik): save/download/log options. |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawInvocationListPanel() { |
| static bool is_panel_visible = true; |
| if (!ImGui::Begin("Invocations", &is_panel_visible, ImGuiWindowFlags_None)) { |
| ImGui::End(); |
| return OkStatus(); |
| } else if (!debug_client_) { |
| ImGui::TextDisabled("disconnected"); |
| ImGui::End(); |
| return OkStatus(); |
| } |
| for (auto* invocation : debug_client_->invocations()) { |
| RETURN_IF_ERROR(DrawInvocation(*invocation)); |
| } |
| ImGui::End(); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawInvocation(const RemoteInvocation& invocation) { |
| // TODO(benvanik): expand if any breakpoints are stopped in invocation. |
| if (selected_invocation_id_.has_value() && |
| selected_invocation_id_.value() == invocation.id()) { |
| ImGui::SetNextTreeNodeOpen(true); |
| } |
| if (!ImGui::CollapsingHeader(invocation.name().c_str())) { |
| return OkStatus(); |
| } |
| ImGui::PushID(invocation.id()); |
| |
| for (int i = 0; i < invocation.def().frames.size(); ++i) { |
| const auto& stack_frame = invocation.def().frames[i]; |
| ImGui::PushID(i); |
| // TODO(benvanik): highlight frames with breakpoints in them. |
| bool is_selected = selected_invocation_id_.has_value() && |
| selected_invocation_id_.value() == invocation.id() && |
| selected_stack_frame_index_.has_value() && |
| selected_stack_frame_index_.value() == i; |
| if (ImGui::Selectable("##selectable", &is_selected, |
| ImGuiSelectableFlags_AllowDoubleClick | |
| ImGuiSelectableFlags_DrawFillAvailWidth)) { |
| // TODO(benvanik): detect when clicking but already selected. |
| if (is_selected) { |
| RETURN_IF_ERROR( |
| NavigateToCodeView(invocation, i, NavigationMode::kMatchDocument)); |
| } |
| } |
| ImGui::SameLine(); |
| ImGui::Bullet(); |
| ImGui::SameLine(); |
| // TODO(benvanik): better naming/etc (resolve function). |
| ImGui::Text("%s:%d:%d", stack_frame->module_name.c_str(), |
| stack_frame->function_ordinal, stack_frame->offset); |
| |
| ImGui::PopID(); |
| } |
| |
| ImGui::PopID(); |
| return OkStatus(); |
| } |
| |
| DebugApp::CodeViewDocument* DebugApp::FindMatchingDocument( |
| absl::string_view module_name, int function_ordinal) { |
| for (auto& document : documents_) { |
| if (document->function->module()->name() == module_name && |
| document->function->ordinal() == function_ordinal) { |
| return document.get(); |
| } |
| } |
| return nullptr; |
| } |
| |
| Status DebugApp::NavigateToCodeView(absl::string_view module_name, |
| int function_ordinal, int offset, |
| NavigationMode navigation_mode) { |
| if (!debug_client_) { |
| return UnavailableErrorBuilder(IREE_LOC) << "No connection established"; |
| } |
| VLOG(1) << "NavigateToCodeView(" << module_name << ", " << function_ordinal |
| << ", " << offset << ")"; |
| CodeViewDocument* existing_document = nullptr; |
| switch (navigation_mode) { |
| case NavigationMode::kNewDocument: |
| // Fall through and create below. |
| break; |
| case NavigationMode::kCurrentDocument: |
| // Not yet done - treat as a new document. |
| break; |
| case NavigationMode::kMatchDocument: |
| existing_document = FindMatchingDocument(module_name, function_ordinal); |
| break; |
| } |
| if (existing_document) { |
| ImGui::SetWindowFocus(existing_document->title.c_str()); |
| return OkStatus(); |
| } |
| |
| // TODO(benvanik): make this common code. |
| RETURN_IF_ERROR(debug_client_->GetFunction( |
| std::string(module_name), function_ordinal, |
| [this, offset](StatusOr<RemoteFunction*> function_or) { |
| if (!function_or.ok()) { |
| // TODO(benvanik): error dialog. |
| CHECK_OK(function_or.status()); |
| } |
| auto* function = function_or.ValueOrDie(); |
| auto document = absl::make_unique<CodeViewDocument>(); |
| document->title = |
| absl::StrCat(function->module()->name(), ":", function->name()); |
| document->function = function; |
| document->focus_offset = offset; |
| ImGui::SetWindowFocus(document->title.c_str()); |
| documents_.push_back(std::move(document)); |
| })); |
| return OkStatus(); |
| } |
| |
| Status DebugApp::NavigateToCodeView(absl::string_view module_name, |
| absl::string_view function_name, int offset, |
| NavigationMode navigation_mode) { |
| if (!debug_client_) { |
| return UnavailableErrorBuilder(IREE_LOC) << "No connection established"; |
| } |
| return debug_client_->ResolveFunction( |
| std::string(module_name), std::string(function_name), |
| [this, navigation_mode, module_name, |
| offset](StatusOr<int> function_ordinal) { |
| CHECK_OK(function_ordinal.status()); |
| CHECK_OK(NavigateToCodeView(module_name, function_ordinal.ValueOrDie(), |
| offset, navigation_mode)); |
| }); |
| } |
| |
| Status DebugApp::NavigateToCodeView(const RemoteInvocation& invocation, |
| int stack_frame_index, |
| NavigationMode navigation_mode) { |
| if (!debug_client_) { |
| return UnavailableErrorBuilder(IREE_LOC) << "No connection established"; |
| } |
| const auto& stack_frame = stack_frame_index == -1 |
| ? *invocation.def().frames.back() |
| : *invocation.def().frames[stack_frame_index]; |
| selected_invocation_id_ = invocation.id(); |
| selected_stack_frame_index_ = stack_frame_index; |
| return NavigateToCodeView(stack_frame.module_name, |
| stack_frame.function_ordinal, stack_frame.offset, |
| NavigationMode::kMatchDocument); |
| } |
| |
| Status DebugApp::NavigateToCodeView(const UserBreakpoint& user_breakpoint, |
| NavigationMode navigation_mode) { |
| if (!debug_client_) { |
| return UnavailableErrorBuilder(IREE_LOC) << "No connection established"; |
| } |
| switch (user_breakpoint.type) { |
| case RemoteBreakpoint::Type::kBytecodeFunction: |
| if (user_breakpoint.function_ordinal != -1) { |
| return NavigateToCodeView( |
| user_breakpoint.module_name, user_breakpoint.function_ordinal, |
| user_breakpoint.bytecode_offset, navigation_mode); |
| } else { |
| return NavigateToCodeView( |
| user_breakpoint.module_name, user_breakpoint.function_name, |
| user_breakpoint.bytecode_offset, navigation_mode); |
| } |
| case RemoteBreakpoint::Type::kNativeFunction: |
| return UnimplementedErrorBuilder(IREE_LOC) |
| << "Navigation to non-bytecode functions unimplemented"; |
| } |
| } |
| |
| Status DebugApp::DrawCodeViewPanels() { |
| // If we've disconnected then we need to clear bodies. |
| // TODO(benvanik): allow documents to persist by caching all required info. |
| if (!debug_client_) { |
| documents_.clear(); |
| return OkStatus(); |
| } |
| |
| std::vector<CodeViewDocument*> closing_documents; |
| for (auto& document : documents_) { |
| ASSIGN_OR_RETURN(bool is_open, DrawCodeViewDocument(document.get())); |
| if (!is_open) { |
| closing_documents.push_back(document.get()); |
| } |
| } |
| for (auto* closing_document : closing_documents) { |
| auto it = std::find_if( |
| documents_.begin(), documents_.end(), |
| [closing_document](const std::unique_ptr<CodeViewDocument>& document) { |
| return document.get() == closing_document; |
| }); |
| documents_.erase(it); |
| } |
| return OkStatus(); |
| } |
| |
| StatusOr<bool> DebugApp::DrawCodeViewDocument(CodeViewDocument* document) { |
| ImGui::SetNextWindowDockID(dockspace_id_, ImGuiCond_FirstUseEver); |
| ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); |
| bool is_open = true; |
| bool is_visible = |
| ImGui::Begin(document->title.c_str(), &is_open, ImGuiWindowFlags_None); |
| if (!is_open || !is_visible) { |
| ImGui::End(); |
| ImGui::PopStyleVar(); |
| return is_open; |
| } |
| ImGui::PopStyleVar(); |
| |
| auto* remote_module = document->function->module(); |
| auto* remote_function = document->function; |
| if (remote_module->CheckLoadedOrRequest() && |
| remote_function->CheckLoadedOrRequest()) { |
| // TODO(benvanik): draw function signature. |
| if (remote_function->bytecode()) { |
| RETURN_IF_ERROR(DrawBytecodeCodeView(document)); |
| } else { |
| // TODO(benvanik): display native registration info. |
| ImGui::TextDisabled("(native)"); |
| } |
| } else { |
| ImGui::TextDisabled("loading..."); |
| } |
| |
| ImGui::End(); |
| return true; |
| } |
| |
| Status DebugApp::PrepareBytecodeCodeView(CodeViewDocument* document) { |
| auto* remote_module = document->function->module(); |
| auto* remote_function = document->function; |
| |
| document->bytecode_info.lines = remote_function->name(); |
| |
| return OkStatus(); |
| } |
| |
| Status DebugApp::DrawBytecodeCodeView(CodeViewDocument* document) { |
| // Ensure we have cached our line information. |
| RETURN_IF_ERROR(PrepareBytecodeCodeView(document)); |
| |
| auto* remote_module = document->function->module(); |
| auto* remote_function = document->function; |
| |
| ImGui::BeginGroup(); |
| ImGui::BeginChild("##bytecode_view", ImVec2(0, 0), false, |
| ImGuiWindowFlags_AlwaysVerticalScrollbar); |
| ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); |
| |
| // TODO(benvanik): cache breakpoints for this function for faster lookup. |
| |
| auto& bytecode_info = document->bytecode_info; |
| ImGuiListClipper clipper(bytecode_info.lines.size(), |
| ImGui::GetTextLineHeightWithSpacing()); |
| while (clipper.Step()) { |
| for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) { |
| ImGui::PushID(i); |
| |
| // TODO(benvanik): lookup line info. |
| int bytecode_offset = 0; |
| int breakpoint_index = FindMatchingUserBreakpointIndex( |
| remote_module->name(), remote_function->ordinal(), bytecode_offset); |
| bool has_breakpoint = breakpoint_index != -1; |
| bool active_on_any_invocation = false; |
| bool active_on_selected_invocation = false; |
| |
| ImGui::Dummy(ImVec2(4, 0)); |
| |
| // Gutter breakpoint button. |
| ImGui::SameLine(); |
| if (has_breakpoint) { |
| PushButtonHue(0.0f); // Red |
| if (ImGui::Button(" ##toggle_breakpoint")) { |
| CHECK_GE(breakpoint_index, 0); |
| auto& user_breakpoint = user_breakpoint_list_[breakpoint_index]; |
| if (user_breakpoint.active_breakpoint) { |
| RETURN_IF_ERROR(debug_client_->RemoveBreakpoint( |
| *user_breakpoint.active_breakpoint)); |
| } |
| user_breakpoint_list_.erase(user_breakpoint_list_.begin() + |
| breakpoint_index); |
| } |
| PopButtonStyle(); |
| if (ImGui::IsItemHovered()) { |
| ImGui::SetTooltip("Remove the breakpoint at this offset."); |
| } |
| } else { |
| PushButtonColor(ImGui::GetStyleColorVec4(ImGuiCol_ChildBg)); |
| if (ImGui::Button(" ##toggle_breakpoint")) { |
| UserBreakpoint user_breakpoint; |
| user_breakpoint.type = RemoteBreakpoint::Type::kBytecodeFunction; |
| user_breakpoint.module_name = remote_module->name(); |
| user_breakpoint.function_name = remote_function->name(); |
| user_breakpoint.bytecode_offset = bytecode_offset; |
| user_breakpoint.wants_enabled = true; |
| user_breakpoint_list_.push_back(std::move(user_breakpoint)); |
| } |
| PopButtonStyle(); |
| if (ImGui::IsItemHovered()) { |
| ImGui::SetTooltip("Add a breakpoint at this offset."); |
| } |
| } |
| |
| // Active execution chevron (shows when active or any invocation is |
| // executing this region). |
| ImGui::SameLine(); |
| if (active_on_selected_invocation) { |
| // The selected invocation is active here. |
| ImGui::TextColored(ImGui::GetStyleColorVec4(ImGuiCol_SeparatorActive), |
| " > "); |
| } else if (active_on_any_invocation) { |
| // At least one other invocation is active here. |
| ImGui::TextColored(ImGui::GetStyleColorVec4(ImGuiCol_Separator), " > "); |
| } else { |
| // Not active. |
| ImGui::Text(" "); |
| } |
| |
| // Line contents. |
| ImGui::SameLine(); |
| ImGui::Text("%s", bytecode_info.lines[i].c_str()); |
| |
| if (document->focus_offset.has_value() && |
| bytecode_offset == document->focus_offset.value()) { |
| document->bytecode_offset = document->focus_offset.value(); |
| document->focus_offset = {}; |
| ImGui::SetScrollHereY(); |
| } |
| |
| ImGui::PopID(); |
| } |
| } |
| |
| ImGui::PopStyleVar(); |
| ImGui::EndChild(); |
| ImGui::EndGroup(); |
| |
| return OkStatus(); |
| } |
| |
| } // namespace debug |
| } // namespace rt |
| } // namespace iree |