blob: 5136b04691ecf412e2c309adf35f79e343ad5a73 [file] [log] [blame]
// 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