diff --git a/gdb/ada-tasks.c b/gdb/ada-tasks.c index 0e5df8ff500..94fee81d81c 100644 --- a/gdb/ada-tasks.c +++ b/gdb/ada-tasks.c @@ -1583,8 +1583,8 @@ task_apply_all_command (const char *cmd, int from_tty) for (const auto &info : thr_list_cpy) if (switch_to_thread_if_alive (info.second.get ())) - thread_try_catch_cmd (info.second.get (), info.first, cmd, - from_tty, flags); + thr_lane_try_catch_cmd (false, info.second.get (), 0, info.first, cmd, + from_tty, flags); } /* Implementation of 'task apply'. */ @@ -1647,8 +1647,8 @@ task_apply_command (const char *tidlist, int from_tty) for (const auto &info : thr_list_cpy) if (switch_to_thread_if_alive (info.second.get ())) - thread_try_catch_cmd (info.second.get (), info.first, cmd, - from_tty, flags); + thr_lane_try_catch_cmd (false, info.second.get (), 0, info.first, cmd, + from_tty, flags); } void _initialize_tasks (); diff --git a/gdb/amd-dbgapi-target.c b/gdb/amd-dbgapi-target.c index f72627c1d00..3298792205d 100644 --- a/gdb/amd-dbgapi-target.c +++ b/gdb/amd-dbgapi-target.c @@ -309,6 +309,12 @@ struct amd_dbgapi_target final : public target_ops std::string pid_to_str (ptid_t ptid) override; + std::string lane_to_str (thread_info *thr, int lane) override; + + std::string dispatch_pos_str (thread_info *thr) override; + std::string thread_workgroup_pos_str (thread_info *thr) override; + std::string lane_workgroup_pos_str (thread_info *thr, int lane) override; + const char *thread_name (thread_info *tp) override; const char *extra_thread_info (thread_info *tp) override; @@ -507,6 +513,25 @@ dispatch_target_id_string (amd_dbgapi_dispatch_id_t dispatch_id) os_id); } +/* Return the dispatch position string for a given thread. */ + +static std::string +dispatch_pos_string (thread_info *tp) +{ + uint32_t group_ids[3]; + if (wave_get_info (tp, AMD_DBGAPI_WAVE_INFO_WORKGROUP_COORD, group_ids) + != AMD_DBGAPI_STATUS_SUCCESS) + return "(?,?,?)/?"; + + uint32_t wave_in_group; + wave_get_info_throw (tp, AMD_DBGAPI_WAVE_INFO_WAVE_NUMBER_IN_WORKGROUP, + wave_in_group); + + return string_printf ("(%d,%d,%d)/%d", + group_ids[0], group_ids[1], group_ids[2], + wave_in_group); +} + /* Return the target id string for a given queue. */ static std::string queue_target_id_string (amd_dbgapi_queue_id_t queue_id) @@ -548,6 +573,187 @@ agent_target_id_string (amd_dbgapi_agent_id_t agent_id) return string_printf ("AMDGPU Agent (GPUID %ld)", os_id); } +/* Return the thread/wave's workgroup position as a string. */ + +static std::string +thread_workgroup_pos_string (thread_info *tp) +{ + uint32_t wave_in_group; + if (wave_get_info (tp, AMD_DBGAPI_WAVE_INFO_WAVE_NUMBER_IN_WORKGROUP, + wave_in_group) != AMD_DBGAPI_STATUS_SUCCESS) + return "?"; + + return string_printf ("%u", wave_in_group); +} + +/* Convert flat ID FLATID to coordinates and store them in COORD_ID. + SIZES is the sizes of each axis. */ + +static void +flatid_to_id (uint32_t coord_id[3], size_t flatid, const size_t sizes[3]) +{ + coord_id[2] = flatid / (sizes[0] * sizes[1]); + + flatid -= (size_t) coord_id[2] * sizes[0] * sizes[1]; + + coord_id[1] = flatid / sizes[0]; + + flatid -= (size_t) coord_id[1] * sizes[0]; + + coord_id[0] = flatid; +} + +/* Object used to collect information about a work-item. Used to + compute work-item coordinates taking into account partial + work-groups. */ + +struct work_item_info +{ + amd_dbgapi_dispatch_id_t dispatch_id; + amd_dbgapi_queue_id_t queue_id; + amd_dbgapi_agent_id_t agent_id; + + /* Grid sizes in work-items. */ + uint32_t grid_sizes[3]; + + /* Grid's work-group sizes in work-items. */ + uint16_t work_group_sizes[3]; + + /* Grid work-group coordinates. */ + uint32_t work_group_ids[3]; + + /* Wave in work-group. */ + uint32_t wave_in_group; + + /* Lane count per wave. */ + size_t lane_count; + + /* Return the flat work-item id of the lane at index LANE_INDEX. */ + size_t flatid (int lane_index) const + { + return wave_in_group * lane_count + lane_index; + } + + /* Store in PARTIAL_WORKGROUP_SIZES the work-group item sizes for + each axis, taking into account the work-items that actually fit + in the grid. */ + void partial_work_group_sizes (size_t partial_work_group_sizes[3]) const + { + for (int i = 0; i < 3; i++) + { + size_t work_item_start = work_group_ids[i] * work_group_sizes[i]; + size_t work_item_end = work_item_start + work_group_sizes[i]; + if (work_item_end > grid_sizes[i]) + work_item_end = grid_sizes[i]; + partial_work_group_sizes[i] = work_item_end - work_item_start; + } + } +}; + +/* Populate WI, a work_item_info object describing lane LANE of wave + TP. Returns true on success, false if info is not available. */ + +static bool +make_work_item_info (thread_info *tp, int lane, work_item_info *wi) +{ + if (wave_get_info (tp, AMD_DBGAPI_WAVE_INFO_DISPATCH, wi->dispatch_id) + != AMD_DBGAPI_STATUS_SUCCESS) + { + /* The dispatch associated with a wave is not available. A wave + may not have an associated dispatch if attaching to a process + with already existing waves. */ + return false; + } + + wave_get_info_throw (tp, AMD_DBGAPI_WAVE_INFO_QUEUE, wi->queue_id); + wave_get_info_throw (tp, AMD_DBGAPI_WAVE_INFO_AGENT, wi->agent_id); + dispatch_get_info_throw (wi->dispatch_id, AMD_DBGAPI_DISPATCH_INFO_GRID_SIZES, + wi->grid_sizes); + + dispatch_get_info_throw (wi->dispatch_id, + AMD_DBGAPI_DISPATCH_INFO_WORKGROUP_SIZES, + wi->work_group_sizes); + + wave_get_info_throw (tp, AMD_DBGAPI_WAVE_INFO_WORKGROUP_COORD, + wi->work_group_ids); + + wave_get_info_throw (tp, AMD_DBGAPI_WAVE_INFO_WAVE_NUMBER_IN_WORKGROUP, + wi->wave_in_group); + + wave_get_info_throw (tp, AMD_DBGAPI_WAVE_INFO_LANE_COUNT, wi->lane_count); + + return true; +} + +/* Return the lane's work-group position as a string. */ + +static std::string +lane_workgroup_pos_string (thread_info *tp, int lane) +{ + work_item_info wi; + + if (make_work_item_info (tp, lane, &wi)) + { + size_t partial_work_group_sizes[3]; + + wi.partial_work_group_sizes (partial_work_group_sizes); + + size_t work_item_flatid = wi.flatid (lane); + + uint32_t work_item_ids[3]; + flatid_to_id (work_item_ids, work_item_flatid, partial_work_group_sizes); + + return string_printf ("[%d,%d,%d]", + work_item_ids[0], work_item_ids[1], work_item_ids[2]); + } + else + return "[?,?,?]"; +} + +/* Return the target id string for a given lane. */ + +static std::string +lane_target_id_string (thread_info *tp, int lane) +{ + amd_dbgapi_dispatch_id_t dispatch_id; + amd_dbgapi_queue_id_t queue_id; + amd_dbgapi_agent_id_t agent_id; + uint32_t group_ids[3]; + + std::string str = "AMDGPU Lane "; + + str += (wave_get_info (tp, AMD_DBGAPI_WAVE_INFO_AGENT, agent_id) + == AMD_DBGAPI_STATUS_SUCCESS) + ? string_printf ("%ld", agent_id.handle) + : " ?"; + + str += (wave_get_info (tp, AMD_DBGAPI_WAVE_INFO_QUEUE, queue_id) + == AMD_DBGAPI_STATUS_SUCCESS) + ? string_printf (":%ld", queue_id.handle) + : ":?"; + + str += (wave_get_info (tp, AMD_DBGAPI_WAVE_INFO_DISPATCH, + dispatch_id) + == AMD_DBGAPI_STATUS_SUCCESS) + ? string_printf (":%ld", dispatch_id.handle) + : ":?"; + + amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (tp->ptid); + + str += string_printf (":%ld/%d", wave_id.handle, lane); + + str += (wave_get_info (tp, AMD_DBGAPI_WAVE_INFO_WORKGROUP_COORD, + group_ids) + == AMD_DBGAPI_STATUS_SUCCESS + ? string_printf (" (%d,%d,%d)", group_ids[0], group_ids[1], + group_ids[2]) + : " (?,?,?)"); + + str += lane_workgroup_pos_string (tp, lane); + + return str; +} + /* Clear our async event handler. */ static void @@ -788,6 +994,46 @@ amd_dbgapi_target::pid_to_str (ptid_t ptid) return wave_coordinates (wave_id).to_string (); } +std::string +amd_dbgapi_target::lane_to_str (thread_info *thr, int lane) +{ + if (!ptid_is_gpu (thr->ptid)) + return beneath ()->lane_to_str (thr, lane); + + return lane_target_id_string (thr, lane); +} + +/* Implementation of target_workgroup_pos_str. */ + +std::string +amd_dbgapi_target::dispatch_pos_str (thread_info *thr) +{ + if (!ptid_is_gpu (thr->ptid)) + return beneath ()->dispatch_pos_str (thr); + + return dispatch_pos_string (thr); +} + +std::string +amd_dbgapi_target::thread_workgroup_pos_str (thread_info *thr) +{ + if (!ptid_is_gpu (thr->ptid)) + return beneath ()->thread_workgroup_pos_str (thr); + + return thread_workgroup_pos_string (thr); +} + +/* Implementation of target_lane_workgroup_pos_str. */ + +std::string +amd_dbgapi_target::lane_workgroup_pos_str (thread_info *thr, int lane) +{ + if (!ptid_is_gpu (thr->ptid)) + return beneath ()->lane_workgroup_pos_str (thr, lane); + + return lane_workgroup_pos_string (thr, lane); +} + const char * amd_dbgapi_target::extra_thread_info (thread_info *tp) { @@ -840,13 +1086,14 @@ amd_dbgapi_target::xfer_partial (enum target_object object, const char *annex, size_t len = requested_len; amd_dbgapi_status_t status; + int current_lane = inferior_thread ()->current_simd_lane (); if (readbuf != nullptr) - status = amd_dbgapi_read_memory (process_id, wave_id, 0, + status = amd_dbgapi_read_memory (process_id, wave_id, current_lane, address_space_id, segment_address, &len, readbuf); else - status = amd_dbgapi_write_memory (process_id, wave_id, 0, + status = amd_dbgapi_write_memory (process_id, wave_id, current_lane, address_space_id, segment_address, &len, writebuf); diff --git a/gdb/amd-dbgapi-target.h b/gdb/amd-dbgapi-target.h index e7cd4fed2b7..70fe85b321e 100644 --- a/gdb/amd-dbgapi-target.h +++ b/gdb/amd-dbgapi-target.h @@ -115,4 +115,48 @@ get_status_string (amd_dbgapi_status_t status) return ret; } +/* Convenience wrapper around amd_dbgapi_wave_get_info that avoids + manually specifying RES's size and accepts a thread pointer instead + of a wave id. */ + +template +static amd_dbgapi_status_t +wave_get_info (thread_info *tp, amd_dbgapi_wave_info_t query, Res &res) +{ + amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (tp->ptid); + + return amd_dbgapi_wave_get_info (wave_id, query, sizeof (res), &res); +} + +/* Like wave_get_info above, but throws an error if the dbgapi call + fails. */ + +template +static void +wave_get_info_throw (thread_info *tp, amd_dbgapi_wave_info_t query, Res &res) +{ + amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (tp->ptid); + amd_dbgapi_status_t status + = amd_dbgapi_wave_get_info (wave_id, query, sizeof (res), &res); + if (status != AMD_DBGAPI_STATUS_SUCCESS) + error (_("amd_dbgapi_wave_get_info for wave_%ld failed: %s"), + wave_id.handle, get_status_string (status)); +} + +/* Convenience wrapper around amd_dbgapi_dispatch_get_info that avoids + manually specifying RES's size and throws an error if the dbgapi + call fails. */ + +template +static void +dispatch_get_info_throw (amd_dbgapi_dispatch_id_t dispatch_id, + amd_dbgapi_dispatch_info_t query, Res &res) +{ + amd_dbgapi_status_t status + = amd_dbgapi_dispatch_get_info (dispatch_id, query, sizeof (res), &res); + if (status != AMD_DBGAPI_STATUS_SUCCESS) + error (_("amd_dbgapi_dispatch_get_info for dispatch_%ld failed: %s"), + dispatch_id.handle, get_status_string (status)); +} + #endif /* GDB_AMD_DBGAPI_TARGET_H */ diff --git a/gdb/amdgpu-tdep.c b/gdb/amdgpu-tdep.c index bc2e61003d6..b342bec90c9 100644 --- a/gdb/amdgpu-tdep.c +++ b/gdb/amdgpu-tdep.c @@ -433,12 +433,8 @@ amdgcn_return_value_load_store (gdbarch *gdbarch, regcache *regcache, const int base_regno = first_regnum_for_arg_or_return_value (gdbarch, regcache->ptid ()); const int lanenumber -#if 0 = current_inferior ()->find_thread (regcache->ptid ())->current_simd_lane (); -#else - = 0; -#endif for (const auto &piece : alloc.allocation ()) { @@ -1770,6 +1766,87 @@ amdgpu_get_watchable_aliases (struct gdbarch *gdbarch, return aliases; } +static simd_lanes_mask_t +amdgpu_active_lanes_mask (struct gdbarch *gdbarch, thread_info *tp) +{ + static_assert (sizeof (simd_lanes_mask_t) >= sizeof (uint64_t)); + + uint64_t exec_mask; + if (wave_get_info (tp, AMD_DBGAPI_WAVE_INFO_EXEC_MASK, exec_mask) + != AMD_DBGAPI_STATUS_SUCCESS) + return 0; + + return exec_mask; +} + +static int +amdgpu_supported_lanes_count (struct gdbarch *gdbarch, thread_info *tp) +{ + size_t count; + if (wave_get_info (tp, AMD_DBGAPI_WAVE_INFO_LANE_COUNT, count) + != AMD_DBGAPI_STATUS_SUCCESS) + return 0; + + return count; +} + +/* A client debugger can check if the lane is part of a valid + work-group by checking that the lane is in the range of the + associated work-group within the grid, accounting for partial + work-groups. */ + +static int +amdgpu_used_lanes_count (struct gdbarch *gdbarch, thread_info *tp) +{ + amd_dbgapi_dispatch_id_t dispatch_id; + if (wave_get_info (tp, AMD_DBGAPI_WAVE_INFO_DISPATCH, dispatch_id) + != AMD_DBGAPI_STATUS_SUCCESS) + { + /* The dispatch associated with a wave is not available. A wave + may not have an associated dispatch if attaching to a process + with already existing waves. In that case, all we can do is + claim that all lanes are used. */ + return amdgpu_supported_lanes_count (gdbarch, tp); + } + + uint32_t grid_sizes[3]; + dispatch_get_info_throw (dispatch_id, + AMD_DBGAPI_DISPATCH_INFO_GRID_SIZES, + grid_sizes); + + uint16_t work_group_sizes[3]; + dispatch_get_info_throw (dispatch_id, + AMD_DBGAPI_DISPATCH_INFO_WORKGROUP_SIZES, + work_group_sizes); + + uint32_t group_ids[3]; + wave_get_info_throw (tp, AMD_DBGAPI_WAVE_INFO_WORKGROUP_COORD, group_ids); + + uint32_t wave_in_group; + wave_get_info_throw (tp, AMD_DBGAPI_WAVE_INFO_WAVE_NUMBER_IN_WORKGROUP, + wave_in_group); + + size_t lane_count; + wave_get_info_throw (tp, AMD_DBGAPI_WAVE_INFO_LANE_COUNT, lane_count); + + size_t work_group_item_sizes[3]; + for (int i = 0; i < 3; i++) + { + size_t item_start = group_ids[i] * work_group_sizes[i]; + size_t item_end = item_start + work_group_sizes[i]; + if (item_end > grid_sizes[i]) + item_end = grid_sizes[i]; + work_group_item_sizes[i] = item_end - item_start; + } + + size_t work_items = (work_group_item_sizes[0] + * work_group_item_sizes[1] + * work_group_item_sizes[2]); + + size_t work_items_left = work_items - wave_in_group * lane_count; + return std::min (work_items_left, lane_count); +} + static bool amdgpu_supports_arch_info (const struct bfd_arch_info *info) { @@ -1997,6 +2074,11 @@ amdgpu_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) set_gdbarch_max_insn_length (gdbarch, max_insn_length); + /* Lane debugging. */ + set_gdbarch_active_lanes_mask (gdbarch, amdgpu_active_lanes_mask); + set_gdbarch_supported_lanes_count (gdbarch, amdgpu_supported_lanes_count); + set_gdbarch_used_lanes_count (gdbarch, amdgpu_used_lanes_count); + status = amd_dbgapi_architecture_get_info (architecture_id, AMD_DBGAPI_ARCHITECTURE_INFO_BREAKPOINT_INSTRUCTION_SIZE, sizeof (tdep->breakpoint_instruction_size), diff --git a/gdb/arch-utils.c b/gdb/arch-utils.c index 9a4ae6b4229..0627095da8a 100644 --- a/gdb/arch-utils.c +++ b/gdb/arch-utils.c @@ -1610,6 +1610,14 @@ core_file_exec_context::environment () const return e; } +/* See arch-utils.h. */ + +int +default_supported_lanes_count (struct gdbarch *gdbarch, thread_info *tp) +{ + return 0; +} + void _initialize_gdbarch_utils (); void _initialize_gdbarch_utils () diff --git a/gdb/arch-utils.h b/gdb/arch-utils.h index abd98a17ca0..6a754a751c1 100644 --- a/gdb/arch-utils.h +++ b/gdb/arch-utils.h @@ -452,4 +452,7 @@ extern location_scope default_address_scope (struct gdbarch *gdbarch, extern std::vector default_get_watchable_aliases (struct gdbarch *gdbarch, ptid_t ptid, int simd_lane, addr_range range); +extern int default_supported_lanes_count (struct gdbarch *gdbarch, + thread_info *tp); + #endif /* GDB_ARCH_UTILS_H */ diff --git a/gdb/break-cond-parse.c b/gdb/break-cond-parse.c index 65615af3526..54e68efb1f2 100644 --- a/gdb/break-cond-parse.c +++ b/gdb/break-cond-parse.c @@ -96,6 +96,7 @@ struct token THREAD, INFERIOR, TASK, + LANE, /* This is the token used when we find unknown content, the m_content for this token is the rest of the input string. */ @@ -130,6 +131,9 @@ struct token case type::TASK: return string_printf ("{ TASK: \"%s\" }", std::string (m_content).c_str ()); + case type::LANE: + return string_printf ("{ LANE: \"%s\" }", + std::string (m_content).c_str ()); case type::REST: return string_printf ("{ REST: \"%s\" }", std::string (m_content).c_str ()); @@ -282,6 +286,8 @@ parse_all_tokens (const char *str) curr_results->emplace_back (token::type::INFERIOR, v); else if (startswith ("task", t)) curr_results->emplace_back (token::type::TASK, v); + else if (startswith ("lane", t)) + curr_results->emplace_back (token::type::LANE, v); else { /* An unknown token. If we are scanning forward then reset TOK @@ -378,11 +384,16 @@ parse_all_tokens (const char *str) if (t.get_type () == token::type::FORCE) forward_results.back ().extend (std::move (t)); - else if (t.get_type () == token::type::THREAD) + else if (t.get_type () == token::type::THREAD + || t.get_type () == token::type::LANE) { const char *end; std::string v (t.get_value ()); - if (is_thread_id (v.c_str (), &end) && *end == '\0') + + auto is_my_id = (t.get_type () == token::type::THREAD + ? is_thread_id + : is_lane_id); + if (is_my_id (v.c_str (), &end) && *end == '\0') break; forward_results.back ().extend (std::move (t)); } @@ -446,18 +457,25 @@ dump_condition_tokens (const std::vector &vec) /* See break-cond-parse.h. */ +void +error_if_already_specific (const bp_specificity &spec) +{ + if (spec.is_specific ()) + error ("You can specify only one of thread, inferior, lane, or task."); +} + +/* See break-cond-parse.h. */ + void create_breakpoint_parse_arg_string (const char *str, gdb::unique_xmalloc_ptr *cond_string_ptr, - int *thread_ptr, int *inferior_ptr, int *task_ptr, + bp_specificity *specificity_ptr, gdb::unique_xmalloc_ptr *rest_ptr, bool *force_ptr) { /* Set up the defaults. */ cond_string_ptr->reset (); rest_ptr->reset (); - *thread_ptr = -1; - *inferior_ptr = -1; - *task_ptr = -1; + *specificity_ptr = {}; *force_ptr = false; if (str == nullptr) @@ -472,7 +490,7 @@ create_breakpoint_parse_arg_string as we parse TOKENS. If all of TOKENS is parsed successfully then the state from these variables is copied into the output arguments before the function returns. */ - int thread = -1, inferior = -1, task = -1; + bp_specificity spec; bool force = false; gdb::unique_xmalloc_ptr cond_string, rest; @@ -486,49 +504,58 @@ create_breakpoint_parse_arg_string break; case token::type::THREAD: { - if (thread != -1) + if (spec.thread != -1) error ("You can specify only one thread."); - if (task != -1 || inferior != -1) - error ("You can specify only one of thread, inferior, or task."); + error_if_already_specific (spec); const char *tmptok; thread_info *thr = parse_thread_id (tok_value.c_str (), &tmptok); gdb_assert (*tmptok == '\0'); - thread = thr->global_num; + spec.thread = thr->global_num; + } + break; + case token::type::LANE: + { + if (spec.lane != -1) + error ("You can specify only one lane."); + error_if_already_specific (spec); + const char *tmptok; + auto [thr, lane_num] = parse_lane_id (tok_value.c_str (), &tmptok); + gdb_assert (*tmptok == '\0'); + spec.thread = thr->global_num; + spec.lane = lane_num; } break; case token::type::INFERIOR: { - if (inferior != -1) + if (spec.inferior != -1) error ("You can specify only one inferior."); - if (task != -1 || thread != -1) - error ("You can specify only one of thread, inferior, or task."); + error_if_already_specific (spec); char *tmptok; long inferior_id = strtol (tok_value.c_str (), &tmptok, 0); if (*tmptok != '\0') error (_("Junk '%s' after inferior keyword."), tmptok); if (inferior_id > INT_MAX) error (_("No inferior number '%ld'"), inferior_id); - inferior = static_cast (inferior_id); - struct inferior *inf = find_inferior_id (inferior); + spec.inferior = static_cast (inferior_id); + inferior *inf = find_inferior_id (spec.inferior); if (inf == nullptr) - error (_("No inferior number '%d'"), inferior); + error (_("No inferior number '%d'"), spec.inferior); } break; case token::type::TASK: { - if (task != -1) + if (spec.task != -1) error ("You can specify only one task."); - if (inferior != -1 || thread != -1) - error ("You can specify only one of thread, inferior, or task."); + error_if_already_specific (spec); char *tmptok; long task_id = strtol (tok_value.c_str (), &tmptok, 0); if (*tmptok != '\0') error (_("Junk '%s' after task keyword."), tmptok); if (task_id > INT_MAX) error (_("Unknown task %ld"), task_id); - task = static_cast (task_id); - if (!valid_task_id (task)) - error (_("Unknown task %d."), task); + spec.task = static_cast (task_id); + if (!valid_task_id (spec.task)) + error (_("Unknown task %d."), spec.task); } break; case token::type::CONDITION: @@ -544,9 +571,7 @@ create_breakpoint_parse_arg_string /* Move results into the output locations. */ *force_ptr = force; - *thread_ptr = thread; - *inferior_ptr = inferior; - *task_ptr = task; + *specificity_ptr = spec; rest_ptr->reset (rest.release ()); cond_string_ptr->reset (cond_string.release ()); } @@ -561,13 +586,13 @@ namespace selftests { create_breakpoint_parse_arg_string. */ static void -test (const char *input, const char *condition, int thread = -1, - int inferior = -1, int task = -1, bool force = false, +test (const char *input, const char *condition, + const bp_specificity &specificity = {}, bool force = false, const char *rest = nullptr, const char *error_msg = nullptr) { gdb::unique_xmalloc_ptr extracted_condition; gdb::unique_xmalloc_ptr extracted_rest; - int extracted_thread, extracted_inferior, extracted_task; + bp_specificity extracted_specificity; bool extracted_force_condition; std::string exception_msg, error_str; @@ -577,9 +602,8 @@ test (const char *input, const char *condition, int thread = -1, try { create_breakpoint_parse_arg_string (input, &extracted_condition, - &extracted_thread, - &extracted_inferior, - &extracted_task, &extracted_rest, + &extracted_specificity, + &extracted_rest, &extracted_force_condition); } catch (const gdb_exception_error &ex) @@ -595,9 +619,7 @@ test (const char *input, const char *condition, int thread = -1, && strcmp (condition, extracted_condition.get ()) != 0) || (rest == nullptr) != (extracted_rest.get () == nullptr) || (rest != nullptr && strcmp (rest, extracted_rest.get ()) != 0) - || thread != extracted_thread - || inferior != extracted_inferior - || task != extracted_task + || specificity != extracted_specificity || force != extracted_force_condition || exception_msg != error_str) { @@ -606,9 +628,10 @@ test (const char *input, const char *condition, int thread = -1, debug_printf ("input: '%s'\n", input); debug_printf ("condition: '%s'\n", extracted_condition.get ()); debug_printf ("rest: '%s'\n", extracted_rest.get ()); - debug_printf ("thread: %d\n", extracted_thread); - debug_printf ("inferior: %d\n", extracted_inferior); - debug_printf ("task: %d\n", extracted_task); + debug_printf ("thread: %d\n", extracted_specificity.thread); + debug_printf ("inferior: %d\n", extracted_specificity.inferior); + debug_printf ("task: %d\n", extracted_specificity.task); + debug_printf ("lane: %d\n", extracted_specificity.lane); debug_printf ("forced: %s\n", extracted_force_condition ? "true" : "false"); debug_printf ("exception: '%s'\n", exception_msg.c_str ()); @@ -626,7 +649,7 @@ test (const char *input, const char *condition, int thread = -1, static void test_error (const char *input, const char *error_msg) { - test (input, nullptr, -1, -1, -1, false, nullptr, error_msg); + test (input, nullptr, {}, false, nullptr, error_msg); } /* Test the create_breakpoint_parse_arg_string function. Just wraps @@ -639,30 +662,29 @@ create_breakpoint_parse_arg_string_tests () scoped_restore_current_pspace_and_thread restore; scoped_mock_context mock_target (arch); - int global_thread_num = mock_target.mock_thread.global_num; + bp_specificity thread_specific; + thread_specific.thread = mock_target.mock_thread.global_num; + bp_specificity inferior_specific; + inferior_specific.inferior = 1; /* Test parsing valid breakpoint condition strings. */ test (" if blah ", "blah"); - test (" if blah thread 1", "blah", global_thread_num); - test (" if blah inferior 1", "blah", -1, 1); - test (" if blah thread 1 ", "blah", global_thread_num); - test ("thread 1 woof", nullptr, global_thread_num, -1, -1, false, "woof"); - test ("thread 1 X", nullptr, global_thread_num, -1, -1, false, "X"); - test (" if blah thread 1 -force-condition", "blah", global_thread_num, - -1, -1, true); - test (" -force-condition if blah thread 1", "blah", global_thread_num, - -1, -1, true); - test (" -force-condition if blah thread 1 ", "blah", global_thread_num, - -1, -1, true); - test ("thread 1 -force-condition if blah", "blah", global_thread_num, - -1, -1, true); + test (" if blah thread 1", "blah", thread_specific); + test (" if blah inferior 1", "blah", inferior_specific); + test (" if blah thread 1 ", "blah", thread_specific); + test ("thread 1 woof", nullptr, thread_specific, false, "woof"); + test ("thread 1 X", nullptr, thread_specific, false, "X"); + test (" if blah thread 1 -force-condition", "blah", thread_specific, true); + test (" -force-condition if blah thread 1", "blah", thread_specific, true); + test (" -force-condition if blah thread 1 ", "blah", thread_specific, true); + test ("thread 1 -force-condition if blah", "blah", thread_specific, true); test ("if (A::outer::func ())", "(A::outer::func ())"); test ("if ( foo == thread )", "( foo == thread )"); - test ("if ( foo == thread ) inferior 1", "( foo == thread )", -1, 1); - test ("if ( foo == thread ) thread 1", "( foo == thread )", - global_thread_num); + test ("if ( foo == thread ) inferior 1", "( foo == thread )", + inferior_specific); + test ("if ( foo == thread ) thread 1", "( foo == thread )", thread_specific); test ("if foo == thread", "foo == thread"); - test ("if foo == thread 1", "foo ==", global_thread_num); + test ("if foo == thread 1", "foo ==", thread_specific); /* Test parsing some invalid breakpoint condition strings. */ test_error ("thread 1 if foo == 123 thread 1", @@ -682,6 +704,8 @@ create_breakpoint_parse_arg_string_tests () test_error ("thread 1xxx", "Invalid thread ID: 1xxx"); test_error ("inferior 1xxx", "Junk 'xxx' after inferior keyword."); test_error ("task 1xxx", "Junk 'xxx' after task keyword."); + + /* XXX add lane tests */ } } /* namespace selftests */ diff --git a/gdb/break-cond-parse.h b/gdb/break-cond-parse.h index 33e60dd4ef7..46f6484e020 100644 --- a/gdb/break-cond-parse.h +++ b/gdb/break-cond-parse.h @@ -19,10 +19,9 @@ #define GDB_BREAK_COND_PARSE_H /* Given TOK, a string possibly containing a condition, thread, inferior, - task and force-condition flag, as accepted by the 'break' command, - extract the condition string, thread, inferior, task number, and the - force_condition flag, then set *COND_STRING, *THREAD, *INFERIOR, *TASK, - and *FORCE. + task, lane and force-condition flag, as accepted by the 'break' command, + extract the condition string, thread, inferior, lane, task number, and the + force_condition flag, then set *COND_STRING, *SPECIFICITY, and *FORCE. As TOK is parsed, if an unknown keyword is encountered before the 'if' keyword then everything starting from the unknown keyword is placed into @@ -32,9 +31,10 @@ found then *COND will be returned as nullptr. If no unknown content is found then *REST is returned as nullptr. - If no thread is found, *THREAD is set to -1. If no inferior is found, - *INFERIOR is set to -1. If no task is found, *TASK is set to -1. If - the -force-condition flag is not found then *FORCE is set to false. + *SPECIFICITY is cleared on entry, and then its fields are filled in + when the corresponding specificity keyword ('thread', 'inferior', + etc.) is found. If the -force-condition flag is not found then + *FORCE is set to false. Due to the free-form nature that the string TOK might take (a 'thread' keyword can appear before or after an 'if' condition) then we end up @@ -46,7 +46,11 @@ extern void create_breakpoint_parse_arg_string (const char *tok, gdb::unique_xmalloc_ptr *cond_string, - int *thread, int *inferior, int *task, + bp_specificity *specificity_ptr, gdb::unique_xmalloc_ptr *rest, bool *force); +/* Throw an error if SPEC is already specific. */ + +extern void error_if_already_specific (const bp_specificity &spec); + #endif /* GDB_BREAK_COND_PARSE_H */ diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 93410f42398..90244a36d9b 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -104,7 +104,8 @@ static void create_breakpoints_sal (struct gdbarch *, gdb::unique_xmalloc_ptr, gdb::unique_xmalloc_ptr, enum bptype, - enum bpdisp, int, int, int, + enum bpdisp, + const bp_specificity &, int, int, int, int, unsigned); @@ -385,10 +386,10 @@ struct momentary_breakpoint : public code_breakpoint enable_state = bp_enabled; disposition = disp_donttouch; frame_id = frame_id_; - thread = thread_; + specificity.thread = thread_; /* The inferior should have been set by the parent constructor. */ - gdb_assert (inferior == -1); + gdb_assert (specificity.inferior == -1); } void re_set (program_space *pspace) override; @@ -1574,10 +1575,11 @@ breakpoint_set_thread (struct breakpoint *b, int thread) /* It is not valid to set a thread restriction for a breakpoint that already has task or inferior restriction. */ - gdb_assert (thread == -1 || (b->task == -1 && b->inferior == -1)); + gdb_assert (thread == -1 + || (b->specificity.task == -1 && b->specificity.inferior == -1)); - int old_thread = b->thread; - b->thread = thread; + int old_thread = b->specificity.thread; + b->specificity.thread = thread; if (old_thread != thread) { /* If THREAD is in a different program_space than OLD_THREAD, or the @@ -1632,10 +1634,12 @@ breakpoint_set_inferior (struct breakpoint *b, int inferior) /* It is not valid to set an inferior restriction for a breakpoint that already has a task or thread restriction. */ - gdb_assert (inferior == -1 || (b->task == -1 && b->thread == -1)); + gdb_assert (inferior == -1 + || (b->specificity.task == -1 + && b->specificity.thread == -1)); - int old_inferior = b->inferior; - b->inferior = inferior; + int old_inferior = b->specificity.inferior; + b->specificity.inferior = inferior; if (old_inferior != inferior) { /* If INFERIOR is in a different program_space than OLD_INFERIOR, or @@ -1689,10 +1693,11 @@ breakpoint_set_task (struct breakpoint *b, int task) /* It is not valid to set a task restriction for a breakpoint that already has a thread or inferior restriction. */ - gdb_assert (task == -1 || (b->thread == -1 && b->inferior == -1)); + gdb_assert (task == -1 || (b->specificity.thread == -1 + && b->specificity.inferior == -1)); - int old_task = b->task; - b->task = task; + int old_task = b->specificity.task; + b->specificity.task = task; if (old_task != task) notify_breakpoint_modified (b); } @@ -2030,22 +2035,38 @@ is_watchpoint (const struct breakpoint *bpt) || bpt->type == bp_watchpoint); } -/* Returns true if the current thread and its running state are safe - to evaluate or update watchpoint B. Watchpoints on local - expressions need to be evaluated in the context of the thread that - was current when the watchpoint was created, and, that thread needs - to be stopped to be able to select the correct frame context. +/* Returns true if the current thread, its SIMD lane and its running + state are safe to evaluate or update watchpoint B. + + Watchpoints on local expressions need to be evaluated in the context + that they were bound to on their creation. This means that the + original thread needs to be stopped and in focus (together with the + original SIMD lane) for the needed evaluation context to be present. + Additional need to stop the thread is to be able to select the + correct frame context. + Watchpoints on global expressions can be evaluated on any thread, - and in any state. It is presently left to the target allowing - memory accesses when threads are running. */ + and in any state. + + It is presently left to the target allowing memory accesses when + threads are running. */ static bool -watchpoint_in_thread_scope (struct watchpoint *b) +watchpoint_in_thread_and_simd_lane_scope (struct watchpoint *b) { - return (b->pspace == current_program_space - && (b->watchpoint_thread == null_ptid - || (inferior_ptid == b->watchpoint_thread - && !inferior_thread ()->executing ()))); + if (b->pspace != current_program_space) + return false; + else if (b->watchpoint_thread == null_ptid) + return true; + else if (inferior_ptid != b->watchpoint_thread + || inferior_thread ()->executing ()) + return false; + else if (b->watchpoint_simd_lane == -1) + return true; + else if (b->watchpoint_simd_lane != inferior_thread ()->current_simd_lane ()) + return false; + + return true; } /* Set watchpoint B to disp_del_at_next_stop, even including its possible @@ -2163,7 +2184,7 @@ update_watchpoint (struct watchpoint *b, bool reparse) /* If this is a local watchpoint, we only want to check if the watchpoint frame is in scope if the current thread is the thread that was used to create the watchpoint. */ - if (!watchpoint_in_thread_scope (b)) + if (!watchpoint_in_thread_and_simd_lane_scope (b)) return; if (b->disposition == disp_del_at_next_stop) @@ -2399,7 +2420,7 @@ update_watchpoint (struct watchpoint *b, bool reparse) std::vector aliases = gdbarch_get_watchable_aliases (arch, b->watchpoint_thread, - 0, /* lane */ + b->watchpoint_simd_lane, {addr, range.size}); if (aliases.empty ()) { @@ -2626,7 +2647,8 @@ should_be_inserted (struct bp_location *bl) We can't fix it unless GDB is able to emulate the instruction or switch to displaced stepping. */ && !(bl->owner->type == bp_single_step - && thread_is_stepping_over_breakpoint (bl->owner->thread))) + && (thread_is_stepping_over_breakpoint + (bl->owner->specificity.thread)))) { infrun_debug_printf ("skipping breakpoint: stepping past insn at: %s", paddress (bl->gdbarch, bl->address)); @@ -2987,7 +3009,7 @@ breakpoint_kind (const struct bp_location *bl, CORE_ADDR *addr) { if (bl->owner->type == bp_single_step) { - struct thread_info *thr = find_thread_global_id (bl->owner->thread); + thread_info *thr = find_thread_global_id (bl->owner->specificity.thread); struct regcache *regcache; regcache = get_thread_regcache (thr); @@ -3474,14 +3496,14 @@ insert_breakpoint_locations (void) /* There is no point inserting thread-specific breakpoints if the thread no longer exists. ALL_BP_LOCATIONS bp_location has BL->OWNER always non-NULL. */ - if (bl->owner->thread != -1 - && !valid_global_thread_id (bl->owner->thread)) + if (bl->owner->specificity.thread != -1 + && !valid_global_thread_id (bl->owner->specificity.thread)) continue; /* Or inferior specific breakpoints if the inferior no longer exists. */ - if (bl->owner->inferior != -1 - && !valid_global_inferior_id (bl->owner->inferior)) + if (bl->owner->specificity.inferior != -1 + && !valid_global_inferior_id (bl->owner->specificity.inferior)) continue; switch_to_program_space_and_thread (bl->pspace); @@ -3576,7 +3598,7 @@ remove_threaded_breakpoints (thread_info *tp, { for (breakpoint &b : all_breakpoints_safe ()) { - if (b.thread == tp->global_num && user_breakpoint_p (&b)) + if (b.specificity.thread == tp->global_num && user_breakpoint_p (&b)) { gdb_printf (_("\ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\n"), @@ -3594,7 +3616,7 @@ remove_inferior_breakpoints (struct inferior *inf) { for (breakpoint &b : all_breakpoints_safe ()) { - if (b.inferior == inf->num && user_breakpoint_p (&b)) + if (b.specificity.inferior == inf->num && user_breakpoint_p (&b)) { /* Tell the user the breakpoint has been deleted. But only for breakpoints that would not normally have been deleted at the @@ -4752,7 +4774,8 @@ bpstat::bpstat (const bpstat &other) commands (other.commands), print (other.print), stop (other.stop), - print_it (other.print_it) + print_it (other.print_it), + simd_lane_mask (other.simd_lane_mask) { if (other.old_val != NULL) old_val = release_value (other.old_val->copy ()); @@ -4991,6 +5014,11 @@ bpstat_do_actions_1 (bpstat **bsp) int printed_hit_locno = -1; breakpoint_proceeded = 0; + + /* After all actions are done, restore the original SIMD lane. */ + thread_info *thread = inferior_thread (); + scoped_restore_current_simd_lane restore_lane (thread); + for (; bs != NULL; bs = bs->next) { struct command_line *cmd = NULL; @@ -5031,6 +5059,13 @@ bpstat_do_actions_1 (bpstat **bsp) cmd = cmd->next; } + if (cmd != nullptr && thread->has_simd_lanes ()) + { + /* Apply actions to the first hit lane. */ + int lane = find_first_active_simd_lane (bs->simd_lane_mask); + thread->set_current_simd_lane (lane); + } + while (cmd != NULL) { execute_control_command (cmd); @@ -5318,7 +5353,8 @@ bpstat::bpstat (struct bp_location *bl, bpstat ***bs_link_pointer) commands (NULL), print (0), stop (0), - print_it (print_it_normal) + print_it (print_it_normal), + simd_lane_mask (0) { **bs_link_pointer = this; *bs_link_pointer = &next; @@ -5330,7 +5366,8 @@ bpstat::bpstat () commands (NULL), print (0), stop (0), - print_it (print_it_normal) + print_it (print_it_normal), + simd_lane_mask (0) { } @@ -5445,7 +5482,7 @@ watchpoint_check (bpstat *bs) /* If this is a local watchpoint, we only want to check if the watchpoint frame is in scope if the current thread is the thread that was used to create the watchpoint. */ - if (!watchpoint_in_thread_scope (b)) + if (!watchpoint_in_thread_and_simd_lane_scope (b)) return WP_IGNORE; if (b->exp_valid_block == NULL) @@ -5752,6 +5789,21 @@ bpstat_check_watchpoint (bpstat *bs) } } +bool +bp_specificity::matches (thread_info *thr) const +{ + if (thread != -1 && thread != thr->global_num) + return false; + + if (inferior != -1 && inferior != thr->inf->num) + return false; + + if (task != -1 && task != ada_get_task_number (thr)) + return false; + + return true; +} + /* For breakpoints that are currently marked as telling gdb to stop, check conditions (condition proper, frame, thread and ignore count) of breakpoint referred to by BS. If we should not stop for this @@ -5794,12 +5846,30 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread) return; } + /* Remember the SIMD mask. */ + bs->simd_lane_mask = thread->active_simd_lanes_mask (); + + /* If the breakpoint is set for a specific lane, mask all other + lanes. */ + if (b->specificity.thread != -1 + && b->specificity.thread == thread->global_num + && b->specificity.lane >= 0) + bs->simd_lane_mask &= (simd_lanes_mask_t) 1 << b->specificity.lane; + + /* If we hit the breakpoint with all lanes inactive, don't stop. + This can happen in conditional/divergent code -- the compiler may + decide it's cheaper to execute a block of instructions unmasked + than to emit a jump over the instruction block. */ + if (maint_lane_divergence_support && bs->simd_lane_mask == 0) + { + bs->stop = 0; + return; + } + /* If this is a thread/task-specific breakpoint, don't waste cpu evaluating the condition if this isn't the specified thread/task. */ - if ((b->thread != -1 && b->thread != thread->global_num) - || (b->inferior != -1 && b->inferior != thread->inf->num) - || (b->task != -1 && b->task != ada_get_task_number (thread))) + if (!b->specificity.matches (thread)) { infrun_debug_printf ("incorrect thread or task, not stopping"); bs->stop = false; @@ -5871,7 +5941,27 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread) { scoped_restore reset_in_cond_eval = make_scoped_restore (&thread->control.in_cond_eval, true); - condition_result = breakpoint_cond_eval (cond); + scoped_restore_current_simd_lane restore_lane {thread}; + simd_lanes_mask_t condition_mask = 0; + + /* Evaluate the condition for all SIMD lanes which might have + caused the stop. */ + for_active_lanes (bs->simd_lane_mask, [&] (int lane) + { + thread->set_current_simd_lane (lane); + if (breakpoint_cond_eval (cond)) + { + /* Unmask the lane if the condition is true. */ + condition_mask |= (simd_lanes_mask_t) 1 << lane; + } + + return true; + }); + + /* If at least one lane is unmasked, then the condition + was held. Update the SIMD lanes mask. */ + condition_result = condition_mask != 0; + bs->simd_lane_mask = condition_mask; } catch (const gdb_exception_error &ex) { @@ -6835,12 +6925,14 @@ print_one_breakpoint_location (struct breakpoint *b, output below. */ if (part_of_multiple && uiout->is_mi_like_p ()) { - if (b->thread != -1) - uiout->field_signed ("thread", b->thread); - else if (b->task != -1) - uiout->field_signed ("task", b->task); - else if (b->inferior != -1) - uiout->field_signed ("inferior", b->inferior); + if (b->specificity.thread != -1) + uiout->field_signed ("thread", b->specificity.thread); + else if (b->specificity.task != -1) + uiout->field_signed ("task", b->specificity.task); + else if (b->specificity.inferior != -1) + uiout->field_signed ("inferior", b->specificity.inferior); + else if (b->specificity.lane != -1) + uiout->field_signed ("lane", b->specificity.inferior); } uiout->text ("\n"); @@ -6881,32 +6973,42 @@ print_one_breakpoint_location (struct breakpoint *b, uiout->text ("\n"); } - if (!part_of_multiple && b->thread != -1) + if (!part_of_multiple && b->specificity.thread != -1) { + const bp_specificity &s = b->specificity; + /* FIXME should make an annotation for this. */ - uiout->text ("\tstop only in thread "); if (uiout->is_mi_like_p ()) - uiout->field_signed ("thread", b->thread); + uiout->field_signed ("thread", s.thread); else { - struct thread_info *thr = find_thread_global_id (b->thread); + thread_info *thr = find_thread_global_id (s.thread); - uiout->field_string ("thread", print_thread_id (thr)); + if (s.lane >= 0) + { + uiout->text ("\tstop only in lane "); + uiout->field_fmt ("lane", "%s", print_lane_id (thr, s.lane)); + } + else + { + uiout->text ("\tstop only in thread "); + uiout->field_string ("thread", print_thread_id (thr)); + } } uiout->text ("\n"); } - if (!part_of_multiple && b->task != -1) + if (!part_of_multiple && b->specificity.task != -1) { uiout->text ("\tstop only in task "); - uiout->field_signed ("task", b->task); + uiout->field_signed ("task", b->specificity.task); uiout->text ("\n"); } - if (!part_of_multiple && b->inferior != -1) + if (!part_of_multiple && b->specificity.inferior != -1) { uiout->text ("\tstop only in inferior "); - uiout->field_signed ("inferior", b->inferior); + uiout->field_signed ("inferior", b->specificity.inferior); uiout->text ("\n"); } @@ -7386,12 +7488,15 @@ breakpoint_has_pc (struct breakpoint *b, return false; } -/* See breakpoint.h. */ +/* Print a message describing any user-breakpoints set at PC. This + concerns with logical breakpoints, so we match program spaces, not + address spaces. */ -void +static void describe_other_breakpoints (struct gdbarch *gdbarch, struct program_space *pspace, CORE_ADDR pc, - struct obj_section *section, int thread) + struct obj_section *section, + const bp_specificity &specificity) { int others = 0; @@ -7411,15 +7516,23 @@ describe_other_breakpoints (struct gdbarch *gdbarch, { others--; gdb_printf ("%d", b.number); - if (b.thread == -1 && thread != -1) - gdb_printf (" (all threads)"); - else if (b.thread != -1) + /* o => other breakpoint */ + const bp_specificity &o = b.specificity; + if (!o.is_specific () && specificity.is_specific ()) + gdb_printf (" (not specific)"); + else if (o.thread != -1) { - struct thread_info *thr = find_thread_global_id (b.thread); - gdb_printf (" (thread %s)", print_thread_id (thr)); + thread_info *thr = find_thread_global_id (o.thread); + if (o.lane >= 0) + gdb_printf (" (lane %s)", print_lane_id (thr, o.lane)); + else + gdb_printf (" (thread %s)", print_thread_id (thr)); } - else if (b.task != -1) - gdb_printf (" (task %d)", b.task); + else if (o.task != -1) + gdb_printf (" (task %d)", o.task); + else if (o.inferior != -1) + gdb_printf (" (inferior %d)", o.inferior); + gdb_printf ("%s%s ", ((b.enable_state == bp_disabled || b.enable_state == bp_call_disabled) @@ -7899,9 +8012,10 @@ delete_longjmp_breakpoint (int thread) for (breakpoint &b : all_breakpoints_safe ()) if (b.type == bp_longjmp || b.type == bp_exception) { - if (b.thread == thread) + const bp_specificity &s = b.specificity; + if (s.thread == thread) { - gdb_assert (b.inferior == -1); + gdb_assert (s.inferior == -1); delete_breakpoint (&b); } } @@ -7913,9 +8027,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread) for (breakpoint &b : all_breakpoints_safe ()) if (b.type == bp_longjmp || b.type == bp_exception) { - if (b.thread == thread) + const bp_specificity &s = b.specificity; + if (s.thread == thread) { - gdb_assert (b.inferior == -1); + gdb_assert (s.inferior == -1); b.disposition = disp_del_at_next_stop; } } @@ -7972,9 +8087,10 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp) for (struct breakpoint &b : all_breakpoints ()) { - if (b.type == bp_longjmp_call_dummy && b.thread == tp->global_num) + const bp_specificity &s = b.specificity; + if (b.type == bp_longjmp_call_dummy && s.thread == tp->global_num) { - gdb_assert (b.inferior == -1); + gdb_assert (s.inferior == -1); struct breakpoint *dummy_b = b.related_breakpoint; /* Find the bp_call_dummy breakpoint in the list of breakpoints @@ -8621,7 +8737,7 @@ clone_momentary_breakpoint (struct breakpoint *orig) return NULL; return momentary_breakpoint_from_master (orig, orig->type, 0, - orig->thread); + orig->specificity.thread); } breakpoint_up @@ -8871,7 +8987,7 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_, gdb::unique_xmalloc_ptr cond_string_, gdb::unique_xmalloc_ptr extra_string_, enum bpdisp disposition_, - int thread_, int task_, int inferior_, + const bp_specificity &specificity_, int ignore_count_, int from_tty, int enabled_, unsigned flags, @@ -8895,15 +9011,9 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_, } gdb_assert (!sals.empty ()); + specificity_.assert_valid (); - /* At most one of thread, task, or inferior can be set on any breakpoint. */ - gdb_assert (((thread == -1 ? 0 : 1) - + (task == -1 ? 0 : 1) - + (inferior == -1 ? 0 : 1)) <= 1); - - thread = thread_; - task = task_; - inferior = inferior_; + specificity = specificity_; cond_string = std::move (cond_string_); extra_string = std::move (extra_string_); @@ -8953,7 +9063,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_, loc_gdbarch = gdbarch; describe_other_breakpoints (loc_gdbarch, - sal.pspace, sal.pc, sal.section, thread); + sal.pspace, sal.pc, sal.section, + specificity); } bp_location *new_loc = add_location (sal); @@ -9000,7 +9111,8 @@ create_breakpoint_sal (struct gdbarch *gdbarch, gdb::unique_xmalloc_ptr cond_string, gdb::unique_xmalloc_ptr extra_string, enum bptype type, enum bpdisp disposition, - int thread, int task, int inferior, int ignore_count, + const bp_specificity &specificity, + int ignore_count, int from_tty, int enabled, int internal, unsigned flags, int display_canonical) @@ -9014,7 +9126,8 @@ create_breakpoint_sal (struct gdbarch *gdbarch, std::move (cond_string), std::move (extra_string), disposition, - thread, task, inferior, ignore_count, + specificity, + ignore_count, from_tty, enabled, flags, display_canonical); @@ -9043,7 +9156,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch, gdb::unique_xmalloc_ptr cond_string, gdb::unique_xmalloc_ptr extra_string, enum bptype type, enum bpdisp disposition, - int thread, int task, int inferior, + const bp_specificity &specificity, int ignore_count, int from_tty, int enabled, int internal, unsigned flags) @@ -9068,7 +9181,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch, std::move (cond_string), std::move (extra_string), type, disposition, - thread, task, inferior, ignore_count, + specificity, ignore_count, from_tty, enabled, internal, flags, canonical->special_display); } @@ -9307,7 +9420,7 @@ int create_breakpoint (struct gdbarch *gdbarch, location_spec *locspec, const char *cond_string, - int thread, int inferior, + bp_specificity specificity, const char *extra_string, bool force_condition, int parse_extra, int tempflag, enum bptype type_wanted, @@ -9319,18 +9432,14 @@ create_breakpoint (struct gdbarch *gdbarch, { struct linespec_result canonical; bool pending = false; - int task = -1; int prev_bkpt_count = breakpoint_count; - gdb_assert (thread == -1 || thread > 0); - gdb_assert (inferior == -1 || inferior > 0); - gdb_assert (thread == -1 || inferior == -1); + specificity.assert_valid (); - /* If PARSE_EXTRA is true then the thread and inferior details will be - parsed from the EXTRA_STRING, the THREAD and INFERIOR arguments - should be -1. */ - gdb_assert (!parse_extra || thread == -1); - gdb_assert (!parse_extra || inferior == -1); + /* If PARSE_EXTRA is true then the specificity details will be + parsed from the EXTRA_STRING, and the SPECIFICY argument should + be clear. */ + gdb_assert (!parse_extra || !specificity.is_specific ()); gdb_assert (ops != NULL); @@ -9358,7 +9467,7 @@ create_breakpoint (struct gdbarch *gdbarch, { /* Parse EXTRA_STRING splitting the parts out. */ create_breakpoint_parse_arg_string (extra_string, &cond_string_copy, - &thread, &inferior, &task, + &specificity, &extra_string_copy, &force_condition); @@ -9372,9 +9481,7 @@ create_breakpoint (struct gdbarch *gdbarch, We still do the EXTRA_STRING_COPY is empty check, just later in this function. */ - gdb_assert (thread == -1 || thread > 0); - gdb_assert (task == -1 || task > 0); - gdb_assert (inferior == -1 || inferior > 0); + specificity.assert_valid (); } else { @@ -9391,7 +9498,8 @@ create_breakpoint (struct gdbarch *gdbarch, try { struct program_space *search_pspace - = find_program_space_for_breakpoint (thread, inferior); + = find_program_space_for_breakpoint (specificity.thread, + specificity.inferior); ops->create_sals_from_location_spec (locspec, &canonical, search_pspace); } @@ -9488,7 +9596,7 @@ create_breakpoint (struct gdbarch *gdbarch, std::move (extra_string_copy), type_wanted, tempflag ? disp_del : disp_donttouch, - thread, task, inferior, ignore_count, + specificity, ignore_count, from_tty, enabled, internal, flags); } else @@ -9500,9 +9608,7 @@ create_breakpoint (struct gdbarch *gdbarch, /* Create a private copy of the condition string. */ b->cond_string = std::move (cond_string_copy); - b->thread = thread; - b->task = task; - b->inferior = inferior; + b->specificity = specificity; /* Create a private copy of any extra string. */ b->extra_string = std::move (extra_string_copy); @@ -9553,8 +9659,8 @@ break_command_1 (const char *arg, int flag, int from_tty) create_breakpoint (get_current_arch (), locspec.get (), - NULL, - -1 /* thread */, -1 /* inferior */, + nullptr, /* cond_string */ + {} /* specificity */, arg, false, 1 /* parse arg */, tempflag, type_wanted, 0 /* Ignore count */, @@ -9664,7 +9770,8 @@ dprintf_command (const char *arg, int from_tty) create_breakpoint (get_current_arch (), locspec.get (), - NULL, -1, -1, + nullptr, /* cond_string */ + {} /* specificity */, arg, false, 0 /* parse arg */, 0, bp_dprintf, 0 /* Ignore count */, @@ -10424,8 +10531,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty, const char *cond_start = NULL; const char *cond_end = NULL; enum bptype bp_type; - int thread = -1; - int inferior = -1; + bp_specificity specificity; /* Flag to indicate whether we are going to use masks for the hardware watchpoint. */ bool use_mask = false; @@ -10474,34 +10580,26 @@ watch_command_1 (const char *arg, int accessflag, int from_tty, only in a specific thread. */ const char *endp; - if (thread != -1) + if (specificity.thread != -1) error(_("You can specify only one thread.")); - if (task != -1) - error (_("You can specify only one of thread or task.")); - - if (inferior != -1) - error (_("You can specify only one of inferior or thread.")); + error_if_already_specific (specificity); /* Extract the thread ID from the next token. */ thr = parse_thread_id (value_start, &endp); if (value_start == endp) error (_("Junk after thread keyword.")); - thread = thr->global_num; + specificity.thread = thr->global_num; } else if (toklen == 4 && startswith (tok, "task")) { char *tmp; - if (task != -1) + if (specificity.task != -1) error(_("You can specify only one task.")); - if (thread != -1) - error (_("You can specify only one of thread or task.")); - - if (inferior != -1) - error (_("You can specify only one of inferior or task.")); + error_if_already_specific (specificity); task = strtol (value_start, &tmp, 0); if (tmp == value_start) @@ -10627,6 +10725,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty, } ptid_t watchpoint_thread_ptid = null_ptid; + int watchpoint_simd_lane = -1; frame_info_ptr wp_frame = nullptr; if (scope_matches (scope, LOCATION_SCOPE_THREAD)) { @@ -10635,6 +10734,16 @@ watch_command_1 (const char *arg, int accessflag, int from_tty, watchpoint_thread_ptid = inferior_ptid; + if (scope_matches (scope, LOCATION_SCOPE_LANE)) + { + int current_simd_lane = inferior_thread ()->current_simd_lane (); + + if (current_simd_lane == -1) + error (_("Location requires a selected SIMD lane")); + + watchpoint_simd_lane = current_simd_lane; + } + if (scope_matches (scope, LOCATION_SCOPE_FRAME)) wp_frame = get_selected_frame ("Location requires a selected frame"); else @@ -10683,10 +10792,8 @@ watch_command_1 (const char *arg, int accessflag, int from_tty, w = std::make_unique (nullptr, bp_type); /* At most one of thread or task can be set on a watchpoint. */ - gdb_assert (thread == -1 || task == -1); - w->thread = thread; - w->inferior = inferior; - w->task = task; + specificity.assert_valid (); + w->specificity = specificity; w->disposition = disp_donttouch; w->pspace = current_program_space; w->exp = std::move (exp); @@ -10725,6 +10832,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty, w->watchpoint_frame = watchpoint_frame_id; w->watchpoint_thread = watchpoint_thread_ptid; + w->watchpoint_simd_lane = watchpoint_simd_lane; if (!just_location) value_free_to_mark (mark); @@ -12152,6 +12260,30 @@ ordinary_breakpoint::print_it (const bpstat *bs) const print_num_locno (bs, uiout); uiout->text (", "); + if (bs->simd_lane_mask != 0) + { + if (inferior_thread ()->has_simd_lanes ()) + { + std::vector hit_lanes; + for_active_lanes (bs->simd_lane_mask, [&] (int lane) + { + hit_lanes.push_back (lane); + return true; + }); + + if (hit_lanes.size () > 1) + uiout->text ("with lanes "); + else + uiout->text ("with lane "); + + std::string lanes + = make_ranges_from_sorted_vector (hit_lanes, + !current_uiout->is_mi_like_p ()); + uiout->field_string ("hit-lanes", lanes.c_str ()); + uiout->text (", "); + } + } + return PRINT_SRC_AND_LOC; } @@ -12362,7 +12494,7 @@ momentary_breakpoint::print_mention () const longjmp_breakpoint::~longjmp_breakpoint () { - thread_info *tp = find_thread_global_id (this->thread); + thread_info *tp = find_thread_global_id (this->specificity.thread); if (tp != NULL) tp->initiating_frame = null_frame_id; @@ -12549,8 +12681,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch, gdb::unique_xmalloc_ptr extra_string, enum bptype type_wanted, enum bpdisp disposition, - int thread, - int task, int inferior, + const bp_specificity &specificity, int ignore_count, int from_tty, int enabled, int internal, unsigned flags) @@ -12577,7 +12708,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch, std::move (cond_string), std::move (extra_string), disposition, - thread, task, inferior, ignore_count, + specificity, ignore_count, from_tty, enabled, flags, canonical->special_display)); @@ -13223,7 +13354,8 @@ code_breakpoint::re_set_default (struct program_space *filter_pspace) breakpoints that are not thread- or inferior-specific, BP_PSPACE will be nullptr. */ program_space *bp_pspace - = find_program_space_for_breakpoint (this->thread, this->inferior); + = find_program_space_for_breakpoint (this->specificity.thread, + this->specificity.inferior); /* If this is not a thread or inferior specific breakpoint, or it is a thread or inferior specific breakpoint but we are looking for new @@ -13364,9 +13496,9 @@ breakpoint_re_set_one (breakpoint *b, program_space *filter_pspace) void breakpoint_re_set_thread (struct breakpoint *b) { - if (b->thread != -1) + if (b->specificity.thread != -1) { - b->thread = inferior_thread ()->global_num; + b->specificity.thread = inferior_thread ()->global_num; /* We're being called after following a fork. The new fork is selected as current, and unless this was a vfork will have a @@ -13537,17 +13669,18 @@ extract_bp_num (extract_bp_kind kind, const char *start, int trailer, const char **end_out = NULL) { const char *end = start; - int num = get_number_trailer (&end, trailer); + std::optional res = get_number_trailer (&end, trailer); + if (!res.has_value () || *res == 0) + error (kind == extract_bp_kind::bp + ? _("Bad breakpoint number '%.*s'") + : _("Bad breakpoint location number '%.*s'"), + int (end - start), start); + int num = *res; if (num < 0) error (kind == extract_bp_kind::bp ? _("Negative breakpoint number '%.*s'") : _("Negative breakpoint location number '%.*s'"), int (end - start), start); - if (num == 0) - error (kind == extract_bp_kind::bp - ? _("Bad breakpoint number '%.*s'") - : _("Bad breakpoint location number '%.*s'"), - int (end - start), start); if (end_out != NULL) *end_out = end; @@ -14102,7 +14235,9 @@ trace_command (const char *arg, int from_tty) create_breakpoint (get_current_arch (), locspec.get (), - NULL, -1, -1, arg, false, 1 /* parse arg */, + nullptr, /* cond_string */ + {} /* specificity */, + arg, false, 1 /* parse arg */, 0 /* tempflag */, bp_tracepoint /* type_wanted */, 0 /* Ignore count */, @@ -14120,7 +14255,9 @@ ftrace_command (const char *arg, int from_tty) current_language); create_breakpoint (get_current_arch (), locspec.get (), - NULL, -1, -1, arg, false, 1 /* parse arg */, + nullptr, /* cond_string */ + {} /* specificity */, + arg, false, 1 /* parse arg */, 0 /* tempflag */, bp_fast_tracepoint /* type_wanted */, 0 /* Ignore count */, @@ -14158,7 +14295,9 @@ strace_command (const char *arg, int from_tty) create_breakpoint (get_current_arch (), locspec.get (), - NULL, -1, -1, arg, false, 1 /* parse arg */, + nullptr, /* cond_string */ + {} /* specificity */, + arg, false, 1 /* parse arg */, 0 /* tempflag */, type /* type_wanted */, 0 /* Ignore count */, @@ -14233,7 +14372,9 @@ create_tracepoint_from_upload (struct uploaded_tp *utp) if (!create_breakpoint (get_current_arch (), locspec.get (), - utp->cond_string.get (), -1, -1, addr_str, + utp->cond_string.get () /* cond_string */, + {} /* specificity */, + addr_str, false /* force_condition */, 0 /* parse cond/thread */, 0 /* tempflag */, @@ -14494,14 +14635,20 @@ get_tracepoint_by_number (const char **arg, void breakpoint::print_recreate_thread (struct ui_file *fp) const { - if (thread != -1) + if (specificity.thread != -1) { - struct thread_info *thr = find_thread_global_id (thread); - gdb_printf (fp, " thread %s", print_full_thread_id (thr)); + thread_info *thr = find_thread_global_id (specificity.thread); + if (specificity.lane != -1) + gdb_printf (fp, " lane %s", print_full_lane_id (thr, specificity.lane)); + else + gdb_printf (fp, " thread %s", print_full_thread_id (thr)); } - if (task != -1) - gdb_printf (fp, " task %d", task); + if (specificity.task != -1) + gdb_printf (fp, " task %d", specificity.task); + + if (specificity.inferior != -1) + gdb_printf (fp, " inferior %d", specificity.inferior); gdb_printf (fp, "\n"); } diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h index 502080dbdd9..8e11a014fe1 100644 --- a/gdb/breakpoint.h +++ b/gdb/breakpoint.h @@ -259,6 +259,64 @@ enum condition_status condition_updated }; +struct bp_specificity +{ + /* Inferior number for inferior-specific breakpoint, or -1 if this + breakpoint is for all inferiors. */ + int inferior = -1; + + /* Global thread number for thread-specific breakpoint, or -1 if + don't care. */ + int thread = -1; + + /* Lane number for lane-specific breakpoint, or -1 if don't care. + It is taken into consideration iff THREAD is specified. */ + int lane = -1; + + /* Ada task number for task-specific breakpoint, or -1 if don't + care. */ + int task = -1; + + bool operator== (const bp_specificity &rhs) const + { + return (thread == rhs.thread + && inferior == rhs.inferior + && task == rhs.task + && lane == rhs.lane); + } + + bool operator!= (const bp_specificity &rhs) const + { + return !(*this == rhs); + } + + /* Do various validity assertion checks. Note this isn't a function + that returns true/false depending on validity, leaving the + gdb_assert to the caller, so that an eventual assertion failure + points more directly to the check that failed. */ + void assert_valid () const + { + gdb_assert (thread == -1 || thread > 0); + gdb_assert (inferior == -1 || inferior > 0); + gdb_assert (task == -1 || task > 0); + gdb_assert (lane == -1 || (lane >= 0 && thread > 0)); + + /* At most one of thread, task or inferior can be set on any + breakpoint. If lane is set, then thread must be set too, so we + don't count lane here. */ + gdb_assert (((thread == -1 ? 0 : 1) + + (task == -1 ? 0 : 1) + + (inferior == -1 ? 0 : 1)) <= 1); + } + + bool is_specific () const + { + return (*this != bp_specificity {}); + } + + bool matches (thread_info *thr) const; +}; + /* Information used by targets to insert and remove breakpoints. */ struct bp_target_info @@ -588,7 +646,8 @@ struct breakpoint_ops struct linespec_result *, gdb::unique_xmalloc_ptr, gdb::unique_xmalloc_ptr, - enum bptype, enum bpdisp, int, int, int, + enum bptype, enum bpdisp, + const bp_specificity &, int, int, int, int, unsigned); }; @@ -884,17 +943,7 @@ struct breakpoint : public intrusive_list_node watchpoint_scope breakpoint or something like that. FIXME). */ breakpoint *related_breakpoint; - /* Thread number for thread-specific breakpoint, or -1 if don't - care. */ - int thread = -1; - - /* Inferior number for inferior-specific breakpoint, or -1 if this - breakpoint is for all inferiors. */ - int inferior = -1; - - /* Ada task number for task-specific breakpoint, or -1 if don't - care. */ - int task = -1; + bp_specificity specificity; /* Count of the number of times this breakpoint was taken, dumped with the info, but not used for anything else. Useful for seeing @@ -950,7 +999,8 @@ struct code_breakpoint : public breakpoint gdb::unique_xmalloc_ptr cond_string, gdb::unique_xmalloc_ptr extra_string, enum bpdisp disposition, - int thread, int task, int inferior, int ignore_count, + const bp_specificity &specificity, + int ignore_count, int from_tty, int enabled, unsigned flags, int display_canonical); @@ -1068,6 +1118,11 @@ struct watchpoint : public breakpoint watchpoint should be evaluated in all threads. */ ptid_t watchpoint_thread; + /* Holds the SIMD lane that was in focus at the point when the + watchpoint was inserted, or `-1' if no SIMD lane was in focus + at that point or the WATCHPOINT_THREAD doesn't contain lanes. */ + int watchpoint_simd_lane; + /* For hardware watchpoints, the triggered status according to the hardware. */ enum watchpoint_triggered watchpoint_triggered; @@ -1440,6 +1495,12 @@ struct bpstat /* Tell bpstat_print and print_bp_stop_message how to print stuff associated with this element of the bpstat chain. */ enum bp_print_how print_it; + + /* Which SIMD lanes where active when the breakpoint was hit. If + the breakpoint is conditional, then this is further restricted + to which lanes the breakpoint conditional evaluated true + for. */ + simd_lanes_mask_t simd_lane_mask; }; enum inf_context @@ -1674,8 +1735,8 @@ enum breakpoint_create_flags extern int create_breakpoint (struct gdbarch *gdbarch, struct location_spec *locspec, - const char *cond_string, int thread, - int inferior, + const char *cond_string, + bp_specificity specificity, const char *extra_string, bool force_condition, int parse_extra, @@ -2093,14 +2154,6 @@ bool is_hardware_watchpoint (const struct breakpoint *bpt); extern void print_solib_event (bool is_catchpoint); -/* Print a message describing any user-breakpoints set at PC. This - concerns with logical breakpoints, so we match program spaces, not - address spaces. */ - -extern void describe_other_breakpoints (struct gdbarch *, - struct program_space *, CORE_ADDR, - struct obj_section *, int); - /* Enable or disable a breakpoint location LOC. ENABLE specifies whether to enable or disable. */ diff --git a/gdb/c-exp.y b/gdb/c-exp.y index 9b7e6991635..30e65f8d3b3 100644 --- a/gdb/c-exp.y +++ b/gdb/c-exp.y @@ -2947,7 +2947,8 @@ lex_one_token (struct parser_state *par_state, bool *is_quoted_name) similarly to breakpoint.c:find_condition_and_thread. */ if (namelen >= 1 && (strncmp (tokstart, "thread", namelen) == 0 - || strncmp (tokstart, "task", namelen) == 0) + || strncmp (tokstart, "task", namelen) == 0 + || strncmp (tokstart, "lane", namelen) == 0) && (tokstart[namelen] == ' ' || tokstart[namelen] == '\t') && ! scanning_macro_expansion ()) { diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c index 30c006851ba..320b59bafe1 100644 --- a/gdb/cli/cli-cmds.c +++ b/gdb/cli/cli-cmds.c @@ -2390,6 +2390,7 @@ value_from_setting (const setting &var, struct gdbarch *gdbarch) case var_string_noescape: case var_optional_filename: case var_filename: + case var_expression: case var_enum: { const char *value; @@ -2472,6 +2473,7 @@ str_value_from_setting (const setting &var, struct gdbarch *gdbarch) case var_string_noescape: case var_optional_filename: case var_filename: + case var_expression: case var_enum: /* For these cases, we do not use get_setshow_command_value_string, as this function handle some characters specially, e.g. by diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c index 5008b4881fb..c4dc9016f81 100644 --- a/gdb/cli/cli-decode.c +++ b/gdb/cli/cli-decode.c @@ -992,6 +992,55 @@ add_setshow_filename_cmd (const char *name, command_class theclass, return cmds; } +/* Add element named NAME to both the set and show command LISTs (the + list for set/show or some sublist thereof). */ + +set_show_commands +add_setshow_expression_cmd (const char *name, enum command_class theclass, + std::string *var, + const char *set_doc, const char *show_doc, + const char *help_doc, + cmd_func_ftype *set_func, + show_value_ftype *show_func, + struct cmd_list_element **set_list, + struct cmd_list_element **show_list) +{ + set_show_commands commands + = add_setshow_cmd_full (name, theclass, var_expression, var, + set_doc, show_doc, help_doc, + nullptr, nullptr, set_func, + show_func, set_list, show_list); + + set_cmd_completer (commands.set, expression_completer); + + return commands; +} + +/* Same as above but using a getter and a setter function instead of a pointer + to a global storage buffer. */ + +set_show_commands +add_setshow_expression_cmd (const char *name, command_class theclass, + const char *set_doc, const char *show_doc, + const char *help_doc, + setting_func_types::set set_func, + setting_func_types::get get_func, + show_value_ftype *show_func, + cmd_list_element **set_list, + cmd_list_element **show_list) +{ + auto cmds = add_setshow_cmd_full (name, theclass, var_expression, + nullptr, set_doc, show_doc, + help_doc, set_func, get_func, + nullptr, show_func, set_list, + show_list); + + set_cmd_completer (cmds.set, expression_completer); + + return cmds; +} + + /* Add element named NAME to both the set and show command LISTs (the list for set/show or some sublist thereof). */ diff --git a/gdb/cli/cli-option.c b/gdb/cli/cli-option.c index 34ac1642b84..e824b53c9ca 100644 --- a/gdb/cli/cli-option.c +++ b/gdb/cli/cli-option.c @@ -43,7 +43,8 @@ union option_value /* For var_enum options. */ const char *enumeration; - /* For var_string and var_filename options. This is allocated with new. */ + /* For var_string, var_filename and var_expression options. This is + allocated with new. */ std::string *string; /* For var_color options. */ @@ -88,7 +89,9 @@ struct option_def_and_value { if (value.has_value ()) { - if (option.type == var_string || option.type == var_filename) + if (option.type == var_string + || option.type == var_filename + || option.type == var_expression) delete value->string; } } @@ -105,7 +108,9 @@ struct option_def_and_value { if (value.has_value ()) { - if (option.type == var_string || option.type == var_filename) + if (option.type == var_string + || option.type == var_filename + || option.type == var_expression) value->string = nullptr; } } @@ -176,6 +181,24 @@ complete_on_all_options (completion_tracker &tracker, complete_on_options (options_group, tracker, opt + 1, opt); } +/* Wrapper around complete_expression, helper for parse_option. It + doesn't let exceptions escape (e.g., + MAX_COMPLETIONS_REACHED_ERROR), as we need parse_option's caller to + advance the word point past the "-expression " part. */ + +static void +safe_complete_expression (completion_tracker &tracker, + const char *text, const char *word) +{ + try + { + complete_expression (tracker, text, word); + } + catch (const gdb_exception_error &except) + { + } +} + /* Parse ARGS, guided by OPTIONS_GROUP. HAVE_DELIMITER is true if the whole ARGS line included the "--" options-terminator delimiter. */ @@ -483,6 +506,133 @@ parse_option (gdb::array_view options_group, val.string = new std::string (std::move (str)); return option_def_and_value {*match, match_ctx, val}; } + case var_expression: + { + if (check_for_argument (args, "--")) + { + /* Treat e.g., "maint test-options -expression --" as if there + was no argument after "-expression". */ + error (_("-%s requires an argument"), match->name); + } + + const char *arg_start = *args; + + std::string str; + + if (**args == '\0' && completion == nullptr) + error (_("-%s requires an argument"), match->name); + else if (**args == '\'' || **args == '"') + { + str = extract_string_maybe_quoted (args); + if (*args == arg_start) + error (_("-%s requires an argument"), match->name); + } + else if (**args == '(') + { + const char *p = *args + 1; + int depth = 1; + while (*p != '\0') + { + if (*p == '(') + ++depth; + else if (*p == ')') + { + if (--depth == 0) + { + ++p; + break; + } + } + ++p; + } + + if (completion == nullptr) + { + if (depth != 0) + error (_("-%s EXPR requires balanced parentheses"), + match->name); + if (depth == 0 && !(*p == 0 || isspace (*p))) + error (_("garbage after -%s EXPR"), match->name); + } + + /* Don't complete if there's garbage after (EXPR), like: + -expr (foo) bar[TAB] + */ + if (completion != nullptr && *p == '\0') + { + if (depth == 0) + { + /* If completing an expression with the cursor + right at the terminating parens, complete the + completion word without interpretation, so that + readline advances the cursor one whitespace + past the parens, like so: + + before: "-expr (foo + bar)" + after: "-expr (foo + bar) " + + Include the opening parens in the completion + too, because when the user presses TAB in the + middle of a line, like: + + "-expr (foo + bar) -other" + ^ cursor here + + readline prints the lcd, like so: + + (gdb) cmd -expr (foo + bar) -other + (foo + bar) + (gdb) cmd -expr (foo + bar) -other + */ + gdb::unique_xmalloc_ptr text_copy + = make_unique_xstrdup (arg_start); + completion->tracker.add_completion (std::move (text_copy)); + return {}; + } + else + { + completion->tracker.advance_custom_word_point_by (1); + + const char *word + = (advance_to_expression_complete_word_point + (completion->tracker, arg_start + 1)); + + safe_complete_expression (completion->tracker, + arg_start + 1, word); + + if (completion->tracker.have_completions ()) + return {}; + } + } + + str = std::string (*args + 1, (depth == 0) ? p - 1 : p); + *args = p; + } + else + { + const char *end = skip_to_space (*args); + + if (completion != nullptr && *end == '\0') + { + const char *word + = (advance_to_expression_complete_word_point + (completion->tracker, arg_start)); + + safe_complete_expression (completion->tracker, + arg_start, word); + + if (completion->tracker.have_completions ()) + return {}; + } + + str = std::string (*args, end); + *args = end; + } + + option_value val; + val.string = new std::string (std::move (str)); + return option_def_and_value {*match, match_ctx, val}; + } case var_filename: { @@ -512,7 +662,7 @@ parse_option (gdb::array_view options_group, completions. However, *ARGS will also have been updated, and the general - option completion code (which we will return too) also + option completion code (which we will return to) also updates the custom word point based on the adjustment made to *ARGS. @@ -721,6 +871,7 @@ save_option_value_in_ctx (std::optional &ov) break; case var_string: case var_filename: + case var_expression: *ov->option.var_address.string (ov->option, ov->ctx) = std::move (*ov->value->string); break; @@ -837,6 +988,9 @@ append_val_type_str (std::string &help, const option_def &opt, case var_filename: help += " FILENAME"; break; + case var_expression: + help += " EXPRESSION"; + break; default: break; } @@ -997,6 +1151,15 @@ add_setshow_cmds_for_options (command_class cmd_class, nullptr, option.show_cmd_cb, set_list, show_list); } + else if (option.type == var_expression) + { + add_setshow_expression_cmd (option.name, cmd_class, + option.var_address.string (option, data), + option.set_doc, option.show_doc, + option.help_doc, + nullptr, option.show_cmd_cb, + set_list, show_list); + } else gdb_assert_not_reached ("option type not handled"); } diff --git a/gdb/cli/cli-option.h b/gdb/cli/cli-option.h index 38dcd8219c5..69fd7c2ac58 100644 --- a/gdb/cli/cli-option.h +++ b/gdb/cli/cli-option.h @@ -349,6 +349,39 @@ struct color_option_def : option_def } }; +/* A var_expression command line option. */ + +template +struct expression_option_def : option_def +{ + expression_option_def (const char *long_option_, + std::string *(*get_var_address_cb_) (Context *), + show_value_ftype *show_cmd_cb_, + const char *set_doc_, + const char *show_doc_ = nullptr, + const char *help_doc_ = nullptr) + : option_def (long_option_, var_expression, nullptr, + (erased_get_var_address_ftype *) get_var_address_cb_, + show_cmd_cb_, + set_doc_, show_doc_, help_doc_) + { + var_address.string = detail::get_var_address; + } + + expression_option_def (const char *long_option_, + const char *set_doc_, + const char *show_doc_ = nullptr, + const char *help_doc_ = nullptr) + : option_def (long_option_, var_expression, nullptr, + ((erased_get_var_address_ftype *) + gdb::option::detail::return_self), + nullptr, + set_doc_, show_doc_, help_doc_) + { + var_address.string = detail::get_var_address; + } +}; + /* A group of options that all share the same context pointer to pass to the options' get-current-value callbacks. */ struct option_def_group diff --git a/gdb/cli/cli-setshow.c b/gdb/cli/cli-setshow.c index a6593d24bd7..c44df3db966 100644 --- a/gdb/cli/cli-setshow.c +++ b/gdb/cli/cli-setshow.c @@ -139,6 +139,7 @@ deprecated_show_value_hack (struct ui_file *file, { case var_string: case var_string_noescape: + case var_expression: case var_enum: gdb_printf (file, (" is \"%s\".\n"), value); break; @@ -379,6 +380,7 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c) } break; case var_string_noescape: + case var_expression: option_changed = c->var->set (std::string (arg)); break; case var_filename: @@ -524,6 +526,7 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c) { case var_string: case var_string_noescape: + case var_expression: case var_filename: case var_optional_filename: interps_notify_param_changed @@ -597,6 +600,7 @@ get_setshow_command_value_string (const setting &var) case var_string_noescape: case var_optional_filename: case var_filename: + case var_expression: stb.puts (var.get ().c_str ()); break; case var_enum: diff --git a/gdb/cli/cli-utils.c b/gdb/cli/cli-utils.c index ad72e53de87..bfa32e51f39 100644 --- a/gdb/cli/cli-utils.c +++ b/gdb/cli/cli-utils.c @@ -21,6 +21,7 @@ #include "cli/cli-utils.h" #include "value.h" +#include #include /* See documentation in cli-utils.h. */ @@ -77,10 +78,10 @@ get_ulongest (const char **pp, int trailer) /* See documentation in cli-utils.h. */ -int +std::optional get_number_trailer (const char **pp, int trailer) { - int retval = 0; /* default */ + std::optional retval; const char *p = *pp; bool negative = false; @@ -97,12 +98,9 @@ get_number_trailer (const char **pp, int trailer) if (val) /* Value history reference */ { if (val->type ()->code () == TYPE_CODE_INT) - retval = value_as_long (val); + retval.emplace (value_as_long (val)); else - { - gdb_printf (_("History value must have integer type.\n")); - retval = 0; - } + gdb_printf (_("History value must have integer type.\n")); } else /* Convenience variable */ { @@ -119,12 +117,11 @@ get_number_trailer (const char **pp, int trailer) varname[p - start] = '\0'; if (get_internalvar_integer (lookup_internalvar (varname), &longest_val)) - retval = (int) longest_val; + retval.emplace ((int) longest_val); else { gdb_printf (_("Convenience variable must " "have integer value.\n")); - retval = 0; } } } @@ -139,22 +136,25 @@ get_number_trailer (const char **pp, int trailer) /* Skip non-numeric token. */ while (*p && !isspace((int) *p)) ++p; - /* Return zero, which caller must interpret as error. */ - retval = 0; } else - retval = atoi (p1); + retval.emplace (atoi (p1)); } if (!(isspace (*p) || *p == '\0' || *p == trailer)) { - /* Trailing junk: return 0 and let caller print error msg. */ + /* Trailing junk: return empty and let caller print error + msg. */ while (!(isspace (*p) || *p == '\0' || *p == trailer)) ++p; - retval = 0; + retval.reset (); } p = skip_spaces (p); *pp = p; - return negative ? -retval : retval; + + if (negative && retval.has_value ()) + *retval = -*retval; + + return retval; } /* See documentation in cli-utils.h. */ @@ -162,7 +162,8 @@ get_number_trailer (const char **pp, int trailer) int get_number (const char **pp) { - return get_number_trailer (pp, '\0'); + std::optional res = get_number_trailer (pp, '\0'); + return res.value_or (0); } /* See documentation in cli-utils.h. */ @@ -170,12 +171,10 @@ get_number (const char **pp) int get_number (char **pp) { - int result; const char *p = *pp; - - result = get_number_trailer (&p, '\0'); + std::optional res = get_number_trailer (&p, '\0'); *pp = (char *) p; - return result; + return res.value_or (0); } /* See documentation in cli-utils.h. */ @@ -254,7 +253,11 @@ number_or_range_parser::get_number () { /* Default case: state->m_cur_tok is pointing either to a solo number, or to the first number of a range. */ - m_last_retval = get_number_trailer (&m_cur_tok, '-'); + const char *number = m_cur_tok; + std::optional res = get_number_trailer (&m_cur_tok, '-'); + if (!res.has_value ()) + error (_("invalid number: %s"), number); + m_last_retval = *res; /* If get_number_trailer has found a '-' preceded by a space, it might be the start of a command option. So, do not parse a range if the '-' is followed by an alpha or another '-'. We @@ -437,3 +440,59 @@ validate_flags_qcs (const char *which_command, qcs_flags *flags) error (_("%s: -c and -s are mutually exclusive"), which_command); } +/* See documentation in cli-utils.h. */ + +std::string +make_ranges_from_sorted_vector (const std::vector &numbers, + bool want_brackets) +{ + gdb_assert (std::is_sorted (numbers.begin (), numbers.end ())); + std::string result; + + if (numbers.empty ()) + return result; + + std::vector::const_iterator start = numbers.begin (); + result = std::to_string(*start); + + int previous_value = *start; + bool has_brackets = false; + + for (auto it = start + 1; it != numbers.end(); it++) + { + if ((previous_value + 1) < *it) + { + /* The current range ends. */ + has_brackets = true; + + if (*start != previous_value) + { + /* The previous value is the end of the current + range. */ + result += "-" + std::to_string (previous_value); + } + else + { + /* The range consists of only the starting number, which + is already included in the result. */ + } + + /* The current value is the beginning of a new range. */ + start = it; + result += " " + std::to_string (*start); + } + previous_value = *it; + } + + if (*start != previous_value) + { + /* Close the last range. */ + result += "-" + std::to_string (previous_value); + has_brackets = true; + } + + if (want_brackets && has_brackets) + result = "[" + result + "]"; + + return result; +} diff --git a/gdb/cli/cli-utils.h b/gdb/cli/cli-utils.h index af816975098..357d3530ebe 100644 --- a/gdb/cli/cli-utils.h +++ b/gdb/cli/cli-utils.h @@ -23,6 +23,8 @@ #include "completer.h" +#include + struct cmd_list_element; /* *PP is a string denoting a number. Get the number. Advance *PP @@ -34,7 +36,7 @@ struct cmd_list_element; TRAILER is a character which can be found after the number; most commonly this is `-'. If you don't want a trailer, use \0. */ -extern int get_number_trailer (const char **pp, int trailer); +extern std::optional get_number_trailer (const char **pp, int trailer); /* Convenience. Like get_number_trailer, but with no TRAILER. */ @@ -226,4 +228,19 @@ struct qcs_flags message. */ extern void validate_flags_qcs (const char *which_command, qcs_flags *flags); +/* Create a string of ranges out of a sorted vector of integers + NUMBERS. Duplicated values are ignored. If the result contains + more than one number, it is enclosed in square brackets, unless + WANT_BRACKETS is false. + + Examples: + For the vector {} the result is "". + For the vector {1} the result is "1". + For the vector {1,1,1,1} the result is "1". + For the vector {0,1,1,2,4,6,7,8} the result is "[0-2 4 6-8]". +*/ +extern std::string + make_ranges_from_sorted_vector (const std::vector &numbers, + bool want_brakets); + #endif /* GDB_CLI_CLI_UTILS_H */ diff --git a/gdb/command.h b/gdb/command.h index b670b4b4e30..ec4b1818572 100644 --- a/gdb/command.h +++ b/gdb/command.h @@ -108,6 +108,8 @@ enum var_types var_optional_filename, /* String which stores a filename. (*VAR) is a std::string. */ var_filename, + /* String which stores an expression. (*VAR) is a std::string. */ + var_expression, /* Enumerated type. Can only have one of the specified values. *VAR is a char pointer to the name of the element that we find. */ @@ -175,8 +177,11 @@ inline bool var_type_uses (var_types t) template<> inline bool var_type_uses (var_types t) { - return (t == var_string || t == var_string_noescape - || t == var_optional_filename || t == var_filename); + return (t == var_string + || t == var_string_noescape + || t == var_optional_filename + || t == var_filename + || t == var_expression); } /* Return true if a setting of type T is backed by a const char * variable. @@ -768,6 +773,19 @@ extern set_show_commands add_setshow_filename_cmd setting_func_types::get get_func, show_value_ftype *show_func, cmd_list_element **set_list, cmd_list_element **show_list); +extern set_show_commands add_setshow_expression_cmd + (const char *name, command_class theclass, std::string *var, const char *set_doc, + const char *show_doc, const char *help_doc, cmd_func_ftype *set_func, + show_value_ftype *show_func, cmd_list_element **set_list, + cmd_list_element **show_list); + +extern set_show_commands add_setshow_expression_cmd + (const char *name, command_class theclass, const char *set_doc, + const char *show_doc, const char *help_doc, + setting_func_types::set set_func, + setting_func_types::get get_func, show_value_ftype *show_func, + cmd_list_element **set_list, cmd_list_element **show_list); + extern set_show_commands add_setshow_string_cmd (const char *name, command_class theclass, std::string *var, const char *set_doc, const char *show_doc, const char *help_doc, cmd_func_ftype *set_func, diff --git a/gdb/dcache.c b/gdb/dcache.c index e0349092f3e..6cab733338f 100644 --- a/gdb/dcache.c +++ b/gdb/dcache.c @@ -114,9 +114,12 @@ struct dcache_struct int size; CORE_ADDR line_size; /* current line_size. */ - /* The ptid of last inferior to use cache or null_ptid. */ + /* The ptid of the last thread to use the cache or null_ptid. */ ptid_t ptid; + /* The selected lane of the last thread to use the cache or -1. */ + int lane; + /* The process target of last inferior to use the cache or nullptr. */ process_stratum_target *proc_target; @@ -253,6 +256,7 @@ dcache_invalidate (DCACHE *dcache) dcache->oldest = NULL; dcache->size = 0; dcache->ptid = null_ptid; + dcache->lane = -1; dcache->proc_target = nullptr; if (dcache->line_size != dcache_line_size) @@ -458,6 +462,7 @@ dcache_init (void) dcache->size = 0; dcache->line_size = dcache_line_size; dcache->ptid = null_ptid; + dcache->lane = -1; dcache->proc_target = nullptr; return dcache; @@ -476,14 +481,24 @@ dcache_read_memory_partial (struct target_ops *ops, DCACHE *dcache, { ULONGEST i; - /* If this is a different thread from what we've recorded, flush the - cache. */ + /* If this is a different thread or lane from what we've recorded, + flush the cache. */ + + /* When we're detaching breakpoints from a fork child before + detaching it ("set detach-on-fork on"), inferior_ptid points to + the child, and the current inferior points to the parent. We can + detect the situation because the pids don't match. */ + int current_lane = (inferior_ptid.pid () != current_inferior ()->pid + ? 0 + : inferior_thread ()->current_simd_lane ()); process_stratum_target *proc_target = current_inferior ()->process_target (); - if (proc_target != dcache->proc_target || inferior_ptid != dcache->ptid) + if (proc_target != dcache->proc_target || inferior_ptid != dcache->ptid + || current_lane != dcache->lane) { dcache_invalidate (dcache); dcache->ptid = inferior_ptid; + dcache->lane = current_lane; dcache->proc_target = proc_target; } diff --git a/gdb/defs.h b/gdb/defs.h index 3f876a45cba..c00b4647260 100644 --- a/gdb/defs.h +++ b/gdb/defs.h @@ -402,7 +402,10 @@ enum user_selected_what_flag USER_SELECTED_THREAD = 1 << 2, /* Frame selected. */ - USER_SELECTED_FRAME = 1 << 3 + USER_SELECTED_FRAME = 1 << 3, + + /* Lane selected. */ + USER_SELECTED_LANE = 1 << 4, }; DEF_ENUM_FLAGS_TYPE (enum user_selected_what_flag, user_selected_what); diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index d790e79632f..9990f1e94d8 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -24447,6 +24447,7 @@ supplied regular expression. @xref{Threads}. @cindex lane identifier (system) +@anchor{info lanes} @item info lanes @r{[}-all | -active | -inactived@r{]} @var{lane-id-list} Display information about one or more heterogeneous lanes of the current thread. With no arguments displays information about all used @@ -24743,6 +24744,7 @@ cause the source position to appear to move until execution reaches a point that makes the current heterogeneous lane active. However, other heterogeneous lanes of the same thread will advance. +@anchor{break commands} @item break @r{[}-lane @var{lane-index}@r{]} @r{[}location@r{]} @r{[}if @var{cond}@r{]} @itemx tbreak @r{[}-lane @var{lane-index}@r{]} @r{[}location@r{]} @r{[}if @var{cond}@r{]} @itemx hbreak @r{[}-lane @var{lane-index}@r{]} @r{[}location@r{]} @r{[}if @var{cond}@r{]} @@ -34169,40 +34171,43 @@ the user interface. @subsubsection Threads and Frames In most cases when @value{GDBN} accesses the target, this access is -done in context of a specific thread and frame (@pxref{Frames}). +done in context of a specific thread and frame (@pxref{Frames}). If +the thread has multiple lanes (@pxref{Heterogeneous Debugging}), then +the access is further done in the context of a specific lane of the +thread. Often, even when accessing global data, the target requires that a thread -be specified. The CLI interface maintains the selected thread and frame, +be specified. The CLI interface maintains the selected thread, lane, and frame, and supplies them to target on each command. This is convenient, because a command line user would not want to specify that information explicitly on each command, and because user interacts with @value{GDBN} via a single terminal, so no confusion is possible as -to what thread and frame are the current ones. +to what thread, lane, and frame are the current ones. -In the case of MI, the concept of selected thread and frame is less +In the case of MI, the concept of selected thread, lane, and frame is less useful. First, a frontend can easily remember this information itself. Second, a graphical frontend can have more than one window, each one used for debugging a different thread, and the frontend might want to access additional threads for internal purposes. This increases the risk that by relying on implicitly selected thread, the frontend may be operating on a wrong one. Therefore, each MI command -should explicitly specify which thread and frame to operate on. To -make it possible, each MI command accepts the @samp{--thread} and +should explicitly specify which thread, lane, and frame to operate on. To +make it possible, each MI command accepts the @samp{--thread}, @samp{--lane}, and @samp{--frame} options, the value to each is @value{GDBN} global -identifier for thread and frame to operate on. +identifier for thread, lane, and frame to operate on. Usually, each top-level window in a frontend allows the user to select -a thread and a frame, and remembers the user selection for further +a thread, lane, and a frame, and remembers the user selection for further operations. However, in some cases @value{GDBN} may suggest that the -current thread or frame be changed. For example, when stopping on a +current thread, lane, or frame be changed. For example, when stopping on a breakpoint it is reasonable to switch to the thread where breakpoint is -hit. For another example, if the user issues the CLI @samp{thread} or +hit. For another example, if the user issues the CLI @samp{thread}, @samp{lane} or @samp{frame} commands via the frontend, it is desirable to change the frontend's selection to the one specified by user. @value{GDBN} -communicates the suggestion to change current thread and frame using the +communicates the suggestion to change current thread, lane, and frame using the @samp{=thread-selected} notification. -Note that historically, MI shares the selected thread with CLI, so -frontends used the @code{-thread-select} to execute commands in the +Note that historically, MI shares the selected thread with the CLI, so +frontends used the @code{-thread-select} command to execute commands in the right context. However, getting this to work right is cumbersome. The simplest way is for frontend to emit @code{-thread-select} command before every command. This doubles the number of commands that need @@ -34215,8 +34220,8 @@ selected thread, then the behavior of subsequent commands will change. So, a frontend should either wait for response from such problematic commands, or explicitly add @code{-thread-select} for all subsequent commands. No frontend is known to do this exactly -right, so it is suggested to just always pass the @samp{--thread} and -@samp{--frame} options. +right, so it is suggested to just always pass the @samp{--thread}, +@samp{--lane}, and @samp{--frame} options. @subsubsection Language @@ -34845,7 +34850,7 @@ because it cannot resume all threads together, or even for a single thread, if the thread must be stepped though some code before letting it run freely. -@item *stopped,reason="@var{reason}",thread-id="@var{id}",stopped-threads="@var{stopped}",core="@var{core}" +@item *stopped,reason="@var{reason}",thread-id="@var{tid}",[lane-id="@var{lid}",]stopped-threads="@var{stopped}",core="@var{core}" The target has stopped. The @var{reason} field can have one of the following values: @@ -34899,8 +34904,15 @@ The inferior called @code{exec}. This is reported when @code{catch exec} There isn't enough history recorded to continue reverse execution. @end table -The @var{id} field identifies the global thread ID of the thread +The @var{tid} field identifies the global thread ID of the thread that directly caused the stop -- for example by hitting a breakpoint. +The @var{lane-id} field is present if the thread has multiple lanes, +in which case @var{lid} indentifies the lane ID of the lane that +@value{GDBN} is reporting the stop for, and thus gets the focus. This +lane is the one for which the arguments in the frame field are for. +No stop record is reported for the other lanes in the thread. Note +that the @var{lane-id} field always indicates one lane only, while +@var{hit-lanes} may indicate a set of lanes. Depending on whether all-stop mode is in effect (@pxref{All-Stop Mode}), @value{GDBN} may either stop all threads, or only the thread that directly triggered the stop. @@ -34940,14 +34952,14 @@ A thread either was created, or has exited. The @var{id} field contains the global @value{GDBN} identifier of the thread. The @var{gid} field identifies the thread group this thread belongs to. -@item =thread-selected,id="@var{id}"[,frame="@var{frame}"] -Informs that the selected thread or frame were changed. This notification +@item =thread-selected,id="@var{tid}"[,lane="@var{lid}"][,frame="@var{frame}"] +Informs that the selected thread, lane, or frame were changed. This notification is not emitted as result of the @code{-thread-select} or @code{-stack-select-frame} commands, but is emitted whenever an MI command -that is not documented to change the selected thread and frame actually +that is not documented to change the selected thread, lane, and frame actually changes them. In particular, invoking, directly or indirectly -(via user-defined command), the CLI @code{thread} or @code{frame} commands, -will generate this notification. Changing the thread or frame from another +(via user-defined command), the CLI @code{thread}, @code{lane}, or @code{frame} commands, +will generate this notification. Changing the thread, lane, or frame from another user interface (see @ref{Interpreters}) will also generate this notification. The @var{frame} field is only present if the newly selected thread is @@ -35087,6 +35099,30 @@ This is the breakpoint disposition---either @samp{del}, meaning that the breakpoint will be deleted at the next stop, or @samp{keep}, meaning that the breakpoint will not be deleted. +@item hit-lanes +Optional field that is present if the thread that caused the stop has +multiple lanes. It lists the lanes that were active when the +breakpoint was hit. The field's value is formatted as a lane ID list +(@pxref{lane ID list}). If the breakpoint is conditional, the list is +further reduced to include only lanes for which the breakpoint +condition evaluates true. I.e., the list only includes the lanes +which explain the stop. For example, here is what the field looks +like if lanes 0, 2, 3, 4, and 10 were active and for which the +breakpoint condition evaluates as true: + +@smallexample +*stopped,reason="breakpoint-hit",disp="keep",bkptno="1", + hit-lanes="0 2-4 10", + frame=@{level="0",addr="0x00007ffff580b8b4",func="kernel", + args=[]@}@}, + thread-id="3",lane-id="0",stopped-threads="all"@} +@end smallexample + +@xref{break commands, description of how break commands and +heterogeneous debugging interact}, for the description of how +@value{GDBN} behaves around breakpoints, breakpoint conditions, +breakpoint commands and lanes. + @item enabled This indicates whether the breakpoint is enabled, in which case the value is @samp{y}, or disabled, in which case the value is @samp{n}. @@ -36966,6 +37002,72 @@ current-thread-id="1" (gdb) @end smallexample +@subheading The @code{-lane-info} Command +@findex -lane-info + +@subsubheading Synopsis + +@smallexample + -lane-info [ @var{lane-id} ] +@end smallexample + +Reports information about either a specific lane, if the @var{lane-id} +parameter is present, or about all lanes of the current thread. + +@subsubheading @value{GDBN} Command + +The @samp{info lanes} command prints the same information about lanes. + +@subsubheading Result + +The result contains the following attributes: + +@table @samp +@item lanes + +A list of lanes. The format of each element of the list is a tuple +with the following fields. The fields are always present unless +stated otherwise. + +@table @code +@item id +The numeric id assigned to the lane by @value{GDBN}. + +@item state +The state of the lane, either @samp{A}, @samp{I}, or +@samp{U}. @xref{info lanes} for a description of what these states +mean. + +@item target-id +The target-specific string identifying the lane. + +@item frame +The stack frame currently executing in the lane. This field is only +present if the lane's thread is stopped. Its format is documented in +@ref{GDB/MI Frame Information}. +@end table + +@end table + +@subsubheading Example + +@smallexample + +-lane-info +^done,lanes=[ +@{id="0",state="A",target-id="AMDGPU Lane 1:2:1:1/0 (0,0,0)[0,0,0]", + frame=@{level="0",addr="0x00007ffff580b8b4",func="kernel", + args=[]@}@}, +@{id="1",state="I",target-id="AMDGPU Lane 1:2:1:1/1 (0,0,0)[1,0,0]", + frame=@{level="0",addr="0x00007ffff580b8b4",func="kernel", + args=[]@}@}, +... +@{id="31",state="A",target-id="AMDGPU Lane 1:2:1:1/31 (0,0,0)[1,0,0]", + frame=@{level="0",addr="0x00007ffff580b8b4",func="kernel", + args=[]@}@}] +(gdb) +@end smallexample + @findex -thread-list-ids @subheading The @code{-thread-list-ids} Command @@ -37003,19 +37105,21 @@ current-thread-id="1",number-of-threads="3" @subsubheading Synopsis @smallexample - -thread-select @var{thread-id} + -thread-select [ -l @var{lane-id} ] @var{thread-id} @end smallexample Make thread with global thread number @var{thread-id} the current -thread. It prints the number of the new current thread, and the -topmost frame for that thread. +thread, and if present, the lane with number @var{lane-id} the current +lane. It prints the number of the new current thread, and the +topmost frame for that thread. If the new current thread supports +lanes, the command prints the number of the new current lane as well. This command is deprecated in favor of explicitly using the -@samp{--thread} option to each command. +@samp{--thread} and @samp{--lane} options to each command. @subsubheading @value{GDBN} Command -The corresponding @value{GDBN} command is @samp{thread}. +The corresponding @value{GDBN} commands are @samp{thread} and @samp{lane}. @subsubheading Example @@ -37040,6 +37144,15 @@ args=[@{name="format",value="0x8048e9c \"%*s%c %d %c\\n\""@}, (gdb) @end smallexample +@smallexample +(gdb) +-thread-select -l 2 3 +^done,new-thread-id="3",lane-id="2", +frame=@{level="0",func="kernel", +args=[],file="kernel.cc",line="20",arch="amdgcn:gfx906"@} +(gdb) +@end smallexample + @c %%%%%%%%%%%%%%%%%%%%%%%%%%%% SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @node GDB/MI Ada Tasking Commands @section @sc{gdb/mi} Ada Tasking Commands @@ -38365,10 +38478,12 @@ object will report the value of @code{state} in the current frame. If an expression specified when creating a fixed variable object refers to a local variable, the variable object becomes bound to the -thread and frame in which the variable object is created. When such -variable object is updated, @value{GDBN} makes sure that the -thread/frame combination the variable object is bound to still exists, -and re-evaluates the variable object in context of that thread/frame. +thread, lane (if available, @pxref{heterogeneous lane}), and frame in +which the variable object is created. +When such variable object is updated, @value{GDBN} makes +sure that the thread/lane/frame combination the variable object is +bound to still exists, and re-evaluates the variable object in context +of that thread/lane/frame. The following is the complete set of @sc{gdb/mi} operations defined to access this functionality: @@ -38511,6 +38626,10 @@ would be printed by the @value{GDBN} CLI. If @samp{print object} If a variable object is bound to a specific thread, then this is the thread's global identifier. +@item lane-id +If a variable object is bound to a specific lane, then this is the +lane's identifier. + @item has_more For a dynamic varobj, this indicates whether there appear to be any children available. For a non-dynamic varobj, this will be 0. @@ -38693,6 +38812,10 @@ If values were requested, this is the value. If this variable object is associated with a thread, this is the thread's global thread id. Otherwise this result is not present. +@item lane-id +If this variable object is associated with a lane, this is the lane's +id. Otherwise this result is not present. + @item frozen If the variable object is frozen, this variable will be present with a value of 1. diff --git a/gdb/dummy-frame.c b/gdb/dummy-frame.c index 697cc11a184..d790289a13f 100644 --- a/gdb/dummy-frame.c +++ b/gdb/dummy-frame.c @@ -128,7 +128,7 @@ remove_dummy_frame (struct dummy_frame **dummy_ptr) static bool pop_dummy_frame_bpt (struct breakpoint *b, struct dummy_frame *dummy) { - if (b->thread == dummy->id.thread->global_num + if (b->specificity.thread == dummy->id.thread->global_num && b->disposition == disp_del && b->frame_id == dummy->id.id) { while (b->related_breakpoint != b) diff --git a/gdb/dwarf2/expr.c b/gdb/dwarf2/expr.c index 16a83b953b4..1b9af3bafaf 100644 --- a/gdb/dwarf2/expr.c +++ b/gdb/dwarf2/expr.c @@ -90,13 +90,15 @@ static void ensure_have_simd_lane (const char *op_name) { ensure_have_thread (op_name); -#if 0 - int current_simd_lane - = inferior_thread ()->current_simd_lane (); - if (current_simd_lane == -1) -#endif + thread_info *thr = inferior_thread (); + ULONGEST lane = thr->current_simd_lane (); + + if (lane == -1) error (_("%s evaluation requires a SIMD lane to be in focus."), op_name); + + if (!thr->is_simd_lane_active (lane)) + throw_error (LANE_INACTIVE_ERROR, _("lane inactive")); } /* Return the number of bytes overlapping a contiguous chunk of N_BITS @@ -3711,11 +3713,9 @@ dwarf_expr_context::execute_llvm_stack_op (dwarf_llvm_user op, case DW_OP_LLVM_USER_push_lane: { ensure_have_simd_lane ("DW_OP_LLVM_push_lane"); -#if 0 ULONGEST lane = inferior_thread ()->current_simd_lane (); result_entry = std::make_shared (lane, address_type); m_scope |= LOCATION_SCOPE_LANE; -#endif } break; diff --git a/gdb/elfread.c b/gdb/elfread.c index 4acd7755e21..87f8b075ee2 100644 --- a/gdb/elfread.c +++ b/gdb/elfread.c @@ -971,7 +971,7 @@ elf_gnu_ifunc_resolver_stop (code_breakpoint *b) gdb_assert (b_return->has_single_location ()); gdb_assert (frame_id_p (b_return->frame_id)); - if (b_return->thread == thread_id + if (b_return->specificity.thread == thread_id && b_return->first_loc ().requested_address == prev_pc && b_return->frame_id == prev_frame_id) break; diff --git a/gdb/frame.h b/gdb/frame.h index a5dfeb11ca3..bb579897903 100644 --- a/gdb/frame.h +++ b/gdb/frame.h @@ -945,9 +945,9 @@ struct frame_arg ERROR are NULL this parameter's value should not be printed. */ struct value *val = nullptr; - /* String containing the error message, it is more usually NULL indicating no - error occurred reading this parameter. */ - gdb::unique_xmalloc_ptr error; + /* If an error occured reading this parameter, this is the error. + Otherwise, this is GDB_NO_ERROR. */ + gdb_exception error; /* One of the print_entry_values_* entries as appropriate specifically for this frame_arg. It will be different from print_entry_values. With diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c index d2c38845900..dcf22eb57ad 100644 --- a/gdb/gdbarch-gen.c +++ b/gdb/gdbarch-gen.c @@ -96,6 +96,9 @@ struct gdbarch gdbarch_push_dummy_call_ftype *push_dummy_call = nullptr; enum call_dummy_location_type call_dummy_location = AT_ENTRY_POINT; gdbarch_push_dummy_code_ftype *push_dummy_code = nullptr; + gdbarch_active_lanes_mask_ftype *active_lanes_mask = nullptr; + gdbarch_supported_lanes_count_ftype *supported_lanes_count = default_supported_lanes_count; + gdbarch_used_lanes_count_ftype *used_lanes_count = gdbarch_supported_lanes_count; gdbarch_code_of_frame_writable_ftype *code_of_frame_writable = default_code_of_frame_writable; gdbarch_print_registers_info_ftype *print_registers_info = default_print_registers_info; gdbarch_print_float_info_ftype *print_float_info = default_print_float_info; @@ -368,6 +371,9 @@ verify_gdbarch (struct gdbarch *gdbarch) /* Skip verify of push_dummy_call, has predicate. */ /* Skip verify of call_dummy_location, invalid_p == 0. */ /* Skip verify of push_dummy_code, has predicate. */ + /* Skip verify of active_lanes_mask, has predicate. */ + /* Skip verify of supported_lanes_count, invalid_p == 0. */ + /* Skip verify of used_lanes_count, invalid_p == 0. */ /* Skip verify of code_of_frame_writable, invalid_p == 0. */ /* Skip verify of print_registers_info, invalid_p == 0. */ /* Skip verify of print_float_info, invalid_p == 0. */ @@ -772,6 +778,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file) gdb_printf (file, "gdbarch_dump: push_dummy_code = <%s>\n", host_address_to_string (gdbarch->push_dummy_code)); + gdb_printf (file, + "gdbarch_dump: gdbarch_active_lanes_mask_p() = %d\n", + gdbarch_active_lanes_mask_p (gdbarch)); + gdb_printf (file, + "gdbarch_dump: active_lanes_mask = <%s>\n", + host_address_to_string (gdbarch->active_lanes_mask)); + gdb_printf (file, + "gdbarch_dump: supported_lanes_count = <%s>\n", + host_address_to_string (gdbarch->supported_lanes_count)); + gdb_printf (file, + "gdbarch_dump: used_lanes_count = <%s>\n", + host_address_to_string (gdbarch->used_lanes_count)); gdb_printf (file, "gdbarch_dump: code_of_frame_writable = <%s>\n", host_address_to_string (gdbarch->code_of_frame_writable)); @@ -2414,6 +2432,64 @@ set_gdbarch_push_dummy_code (struct gdbarch *gdbarch, gdbarch->push_dummy_code = push_dummy_code; } +bool +gdbarch_active_lanes_mask_p (struct gdbarch *gdbarch) +{ + gdb_assert (gdbarch != NULL); + return gdbarch->active_lanes_mask != NULL; +} + +simd_lanes_mask_t +gdbarch_active_lanes_mask (struct gdbarch *gdbarch, thread_info *tp) +{ + gdb_assert (gdbarch != NULL); + gdb_assert (gdbarch->active_lanes_mask != NULL); + if (gdbarch_debug >= 2) + gdb_printf (gdb_stdlog, "gdbarch_active_lanes_mask called\n"); + return gdbarch->active_lanes_mask (gdbarch, tp); +} + +void +set_gdbarch_active_lanes_mask (struct gdbarch *gdbarch, + gdbarch_active_lanes_mask_ftype active_lanes_mask) +{ + gdbarch->active_lanes_mask = active_lanes_mask; +} + +int +gdbarch_supported_lanes_count (struct gdbarch *gdbarch, thread_info *tp) +{ + gdb_assert (gdbarch != NULL); + gdb_assert (gdbarch->supported_lanes_count != NULL); + if (gdbarch_debug >= 2) + gdb_printf (gdb_stdlog, "gdbarch_supported_lanes_count called\n"); + return gdbarch->supported_lanes_count (gdbarch, tp); +} + +void +set_gdbarch_supported_lanes_count (struct gdbarch *gdbarch, + gdbarch_supported_lanes_count_ftype supported_lanes_count) +{ + gdbarch->supported_lanes_count = supported_lanes_count; +} + +int +gdbarch_used_lanes_count (struct gdbarch *gdbarch, thread_info *tp) +{ + gdb_assert (gdbarch != NULL); + gdb_assert (gdbarch->used_lanes_count != NULL); + if (gdbarch_debug >= 2) + gdb_printf (gdb_stdlog, "gdbarch_used_lanes_count called\n"); + return gdbarch->used_lanes_count (gdbarch, tp); +} + +void +set_gdbarch_used_lanes_count (struct gdbarch *gdbarch, + gdbarch_used_lanes_count_ftype used_lanes_count) +{ + gdbarch->used_lanes_count = used_lanes_count; +} + int gdbarch_code_of_frame_writable (struct gdbarch *gdbarch, const frame_info_ptr &frame) { diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h index 93d8d0d5307..b0c1f21115f 100644 --- a/gdb/gdbarch-gen.h +++ b/gdb/gdbarch-gen.h @@ -360,6 +360,27 @@ typedef CORE_ADDR (gdbarch_push_dummy_code_ftype) (struct gdbarch *gdbarch, CORE extern CORE_ADDR gdbarch_push_dummy_code (struct gdbarch *gdbarch, CORE_ADDR sp, CORE_ADDR funaddr, struct value **args, int nargs, struct type *value_type, CORE_ADDR *real_pc, CORE_ADDR *bp_addr, struct regcache *regcache); extern void set_gdbarch_push_dummy_code (struct gdbarch *gdbarch, gdbarch_push_dummy_code_ftype *push_dummy_code); +/* Return the active SIMD lanes mask for a thread TP. */ + +extern bool gdbarch_active_lanes_mask_p (struct gdbarch *gdbarch); + +typedef simd_lanes_mask_t (gdbarch_active_lanes_mask_ftype) (struct gdbarch *gdbarch, thread_info *tp); +extern simd_lanes_mask_t gdbarch_active_lanes_mask (struct gdbarch *gdbarch, thread_info *tp); +extern void set_gdbarch_active_lanes_mask (struct gdbarch *gdbarch, gdbarch_active_lanes_mask_ftype *active_lanes_mask); + +/* Return the number of lanes supported by the thread. */ + +typedef int (gdbarch_supported_lanes_count_ftype) (struct gdbarch *gdbarch, thread_info *tp); +extern int gdbarch_supported_lanes_count (struct gdbarch *gdbarch, thread_info *tp); +extern void set_gdbarch_supported_lanes_count (struct gdbarch *gdbarch, gdbarch_supported_lanes_count_ftype *supported_lanes_count); + +/* Return the number of lanes used by the thread, accounting for + partial work-groups. Defaults to the number of supported lanes. */ + +typedef int (gdbarch_used_lanes_count_ftype) (struct gdbarch *gdbarch, thread_info *tp); +extern int gdbarch_used_lanes_count (struct gdbarch *gdbarch, thread_info *tp); +extern void set_gdbarch_used_lanes_count (struct gdbarch *gdbarch, gdbarch_used_lanes_count_ftype *used_lanes_count); + /* Return true if the code of FRAME is writable. */ typedef int (gdbarch_code_of_frame_writable_ftype) (struct gdbarch *gdbarch, const frame_info_ptr &frame); diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h index f1958e52614..13ba0cc8592 100644 --- a/gdb/gdbarch.h +++ b/gdb/gdbarch.h @@ -142,6 +142,9 @@ using read_core_file_mappings_loop_ftype = const char *filename, const bfd_build_id *build_id)>; +/* 64-bits is sufficient for all known architectures. */ +typedef uint64_t simd_lanes_mask_t; + /* Possible values for gdbarch_call_dummy_location. */ enum call_dummy_location_type { diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py index 34be3f62eb3..38fa3c10bcf 100644 --- a/gdb/gdbarch_components.py +++ b/gdb/gdbarch_components.py @@ -684,6 +684,40 @@ predicate=True, ) +Method( + comment=""" +Return the active SIMD lanes mask for a thread TP. +""", + type="simd_lanes_mask_t", + name="active_lanes_mask", + params=[("thread_info *", "tp")], + predicate=True, + invalid=True, +) + +Method( + comment=""" +Return the number of lanes supported by the thread. +""", + type="int", + name="supported_lanes_count", + params=[("thread_info *", "tp")], + predefault="default_supported_lanes_count", + invalid=False, +) + +Method( + comment=""" +Return the number of lanes used by the thread, accounting for +partial work-groups. Defaults to the number of supported lanes. +""", + type="int", + name="used_lanes_count", + params=[("thread_info *", "tp")], + predefault="gdbarch_supported_lanes_count", + invalid=False, +) + Method( comment=""" Return true if the code of FRAME is writable. diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index 229d228ff5e..e4f12f5e7ea 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -603,6 +603,40 @@ class thread_info : public intrusive_list_node, /* The thread options as last set with a call to set_thread_options. */ gdb_thread_options m_thread_options; + + /* Currently selected SIMD lane. + + If SIMD is supported by the architecture, changing this attribute + switches the focus between different SIMD lanes within a thread. + This field is tigthly bound to the active SIMD lanes mask, which + indicates lanes that are currently active. + + If SIMD is not supported, we pretend there's only one lane, with + index 0. + + In thread_info, SIMD_LANE_NUM should stay non-negative. Related + fields outside thread_info, such as the lane variable in + parse_thread_id or breakpoint::simd_lane_num could be negative, + with value '-1' meaning, that we do not care about SIMD lane + number or do not want to change the currently selected SIMD + lane. */ + int m_current_simd_lane = 0; + +public: + /* Return true if this thread has SIMD lanes. */ + bool has_simd_lanes (); + + /* Return active lanes mask for this thread. */ + simd_lanes_mask_t active_simd_lanes_mask (); + + /* Return the current simd lane. */ + int current_simd_lane (); + + /* Set the current simd lane. */ + void set_current_simd_lane (int lane); + + /* Return true if LANE is active in this thread. */ + bool is_simd_lane_active (int lane); }; using thread_info_resumed_with_pending_wait_status_node @@ -701,10 +735,20 @@ extern int show_inferior_qualified_tids (void); circular static buffer, NUMCELLS deep. */ const char *print_thread_id (struct thread_info *thr); +/* Return a string version of [THR/LANE]'s lane ID. If there are + multiple inferiors, then this prints the inferior-qualifier form of + the thread part. The result is stored in a circular static buffer, + NUMCELLS deep. */ +const char *print_lane_id (struct thread_info *thr, int lane); + /* Like print_thread_id, but always prints the inferior-qualified form, even when there is only a single inferior. */ const char *print_full_thread_id (struct thread_info *thr); +/* Return a string version of a fully-qualified lane ID for + printing. */ +const char *print_full_lane_id (struct thread_info *thr, int lane); + /* Boolean test for an already-known ptid. */ extern bool in_thread_list (process_stratum_target *targ, ptid_t ptid); @@ -813,6 +857,9 @@ extern void switch_to_no_thread (); /* Switch from one thread to another. Does not read registers. */ extern void switch_to_thread_no_regs (struct thread_info *thread); +/* Switch current thread to lane LANE. */ +extern void switch_to_lane (int lane); + /* Marks or clears thread(s) PTID of TARG as resumed. If PTID is MINUS_ONE_PTID, applies to all threads of TARG. If ptid_is_pid(PTID) is true, applies to all threads of the process @@ -883,6 +930,12 @@ extern void print_thread_info (struct ui_out *uiout, const char *requested_threads, int pid); +/* Prints the list of lanes of the current thread and their details on + UIOUT. If REQUESTED_LANES, a list of GDB ids/ranges, is not NULL, + only print lanes whose ID is included in the list. */ +extern void print_lane_info (struct ui_out *uiout, + const char *requested_lanes); + /* Save/restore current inferior/thread/frame. */ class scoped_restore_current_thread @@ -914,6 +967,19 @@ class scoped_restore_current_thread scoped_restore_current_language m_lang; }; +/* Save/restore current lane. */ +class scoped_restore_current_simd_lane +{ +public: + /* THR specifies the thread for which the current SIMD lane is + saved/restored. */ + explicit scoped_restore_current_simd_lane (thread_info *thr); + ~scoped_restore_current_simd_lane (); +private: + thread_info_ref m_thr; + int m_current_simd_lane; +}; + /* Returns a pointer into the thread_info corresponding to INFERIOR_PTID. INFERIOR_PTID *must* be in the thread list. */ extern struct thread_info* inferior_thread (void); @@ -1043,8 +1109,9 @@ extern void print_selected_thread_frame (struct ui_out *uiout, /* Helper for the CLI's "thread" command and for MI's -thread-select. Selects thread THR. TIDSTR is the original string the thread ID was parsed from. This is used in the error message if THR is not - alive anymore. */ -extern void thread_select (const char *tidstr, class thread_info *thr); + alive anymore. If LANE is not -1, select that lane. */ +extern void thread_select (const char *tidstr, class thread_info *thr, + int lane = -1); /* Return THREAD's name. @@ -1057,20 +1124,51 @@ extern const char *thread_name (thread_info *thread); extern bool switch_to_thread_if_alive (thread_info *thr); -/* Assuming that THR is the current thread, execute CMD. - If ADA_TASK is not empty, it is the Ada task ID, and will - be printed instead of the thread information. - FLAGS.QUIET controls the printing of the thread information. +/* Switch to lane LANE of thread THR and execute CMD. If ADA_TASK is not + empty, it is the Ada task ID, and will be printed instead of the thread + information. FLAGS.QUIET controls the printing of the thread information. FLAGS.CONT and FLAGS.SILENT control how to handle errors. Can throw an exception if !FLAGS.SILENT and !FLAGS.CONT and CMD fails. */ -extern void thread_try_catch_cmd (thread_info *thr, - std::optional ada_task, - const char *cmd, int from_tty, - const qcs_flags &flags); +extern void thr_lane_try_catch_cmd (bool lane_mode, thread_info *thr, int lane, + std::optional ada_task, + const char *cmd, int from_tty, + const qcs_flags &flags); /* Return a string representation of STATE. */ extern const char *thread_state_string (enum thread_state state); +/* Return the number of the first active lane in MASK or -1 if MASK is + 0. */ +extern int find_first_active_simd_lane (simd_lanes_mask_t mask); + +/* Return true if LANE is unmasked in MASK. */ +extern bool is_simd_lane_active (simd_lanes_mask_t mask, int lane); + +/* Execute function FUNC for all active lanes in MASK. FUNC should + have the following prototype: + + bool func (int lane_num, ...) + + LANE_NUM is the currently iterated lane. ARGS are passed to FUNC. + If FUNC returns false, the loop breaks. */ +template +void +for_active_lanes (simd_lanes_mask_t mask, Func func, Args &...args) +{ + int lane = 0; + + while (mask != 0) + { + if ((mask & 1) != 0) + { + if (!func (lane, args...)) + break; + } + ++lane; + mask >>= 1; + } +} + #endif /* GDB_GDBTHREAD_H */ diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c index b8a5d82afa6..a678710ff8b 100644 --- a/gdb/guile/scm-breakpoint.c +++ b/gdb/guile/scm-breakpoint.c @@ -464,7 +464,7 @@ gdbscm_register_breakpoint_x (SCM self) const breakpoint_ops *ops = breakpoint_ops_for_location_spec (locspec.get (), false); create_breakpoint (get_current_arch (), - locspec.get (), NULL, -1, -1, NULL, false, + locspec.get (), NULL, {}, NULL, false, 0, temporary, bp_breakpoint, 0, @@ -749,10 +749,10 @@ gdbscm_breakpoint_thread (SCM self) breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); - if (bp_smob->bp->thread == -1) + if (bp_smob->bp->specificity.thread == -1) return SCM_BOOL_F; - return scm_from_long (bp_smob->bp->thread); + return scm_from_long (bp_smob->bp->specificity.thread); } /* (set-breakpoint-thread! integer) -> unspecified */ @@ -773,7 +773,7 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue) _("invalid thread id")); } - if (bp_smob->bp->task != -1) + if (bp_smob->bp->specificity.task != -1) scm_misc_error (FUNC_NAME, _("cannot set both task and thread attributes"), SCM_EOL); @@ -783,7 +783,7 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue) else SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f")); - if (bp_smob->bp->inferior != -1 && id != -1) + if (bp_smob->bp->specificity.inferior != -1 && id != -1) scm_misc_error (FUNC_NAME, _("Cannot have both 'thread' and 'inferior' " "conditions on a breakpoint"), SCM_EOL); @@ -801,10 +801,10 @@ gdbscm_breakpoint_task (SCM self) breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); - if (bp_smob->bp->task == -1) + if (bp_smob->bp->specificity.task == -1) return SCM_BOOL_F; - return scm_from_long (bp_smob->bp->task); + return scm_from_long (bp_smob->bp->specificity.task); } /* (set-breakpoint-task! integer) -> unspecified */ @@ -838,7 +838,7 @@ gdbscm_set_breakpoint_task_x (SCM self, SCM newvalue) _("invalid task id")); } - if (bp_smob->bp->thread != -1) + if (bp_smob->bp->specificity.thread != -1) scm_misc_error (FUNC_NAME, _("cannot set both task and thread attributes"), SCM_EOL); diff --git a/gdb/guile/scm-param.c b/gdb/guile/scm-param.c index 749c5ea1f7d..712f476142b 100644 --- a/gdb/guile/scm-param.c +++ b/gdb/guile/scm-param.c @@ -132,6 +132,7 @@ enum scm_param_types param_string_noescape, param_optional_filename, param_filename, + param_expression, param_enum, param_color, }; @@ -159,6 +160,7 @@ param_to_var[] = { var_string_noescape }, { var_optional_filename }, { var_filename }, + { var_expression }, { var_enum }, { var_color } }; @@ -508,6 +510,13 @@ add_setshow_generic (enum var_types param_type, show_func, set_list, show_list); break; + case var_expression: + commands = add_setshow_expression_cmd (cmd_name, cmd_class, + self->value.stringval, set_doc, + show_doc, help_doc, set_func, + show_func, set_list, show_list); + break; + case var_enum: /* Initialize the value, just in case. */ make_setting (self).set (self->enumeration[0]); @@ -651,6 +660,7 @@ pascm_param_value (const setting &var, int arg_pos, const char *func_name) case var_string_noescape: case var_optional_filename: case var_filename: + case var_expression: { const std::string &str = var.get (); return gdbscm_scm_from_host_string (str.c_str (), str.length ()); @@ -739,6 +749,7 @@ pascm_set_param_value_x (param_smob *p_smob, case var_string_noescape: case var_optional_filename: case var_filename: + case var_expression: SCM_ASSERT_TYPE (scm_is_string (value) || (var.type () != var_filename && gdbscm_is_false (value)), diff --git a/gdb/infcmd.c b/gdb/infcmd.c index 17350f5edbb..5163a2cdefb 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -2448,7 +2448,7 @@ kill_command (const char *arg, int from_tty) target_kill (); - update_previous_thread (); + update_previous_thread_and_lane (); if (print_inferior_events) gdb_printf (_("[Inferior %d (%s) killed]\n"), @@ -2810,7 +2810,7 @@ detach_command (const char *args, int from_tty) target_detach (inf, from_tty); - update_previous_thread (); + update_previous_thread_and_lane (); /* The current inferior process was just detached successfully. Get rid of breakpoints that no longer make sense. Note we don't do @@ -2850,7 +2850,7 @@ disconnect_command (const char *args, int from_tty) target_disconnect (args, from_tty); no_shared_libraries (current_program_space); init_thread_list (); - update_previous_thread (); + update_previous_thread_and_lane (); if (deprecated_detach_hook) deprecated_detach_hook (); } diff --git a/gdb/inferior.c b/gdb/inferior.c index 343f31bb7c7..ee9892c2b23 100644 --- a/gdb/inferior.c +++ b/gdb/inferior.c @@ -783,6 +783,7 @@ inferior_command (const char *args, int from_tty) notify_user_selected_context_changed (USER_SELECTED_INFERIOR | USER_SELECTED_THREAD + | USER_SELECTED_LANE | USER_SELECTED_FRAME); } else diff --git a/gdb/infrun.c b/gdb/infrun.c index 23250c4a045..3a2f909e0f3 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -155,21 +155,28 @@ show_step_stop_if_no_debug (struct ui_file *file, int from_tty, gdb_printf (file, _("Mode of the step operation is %s.\n"), value); } -/* proceed and normal_stop use this to notify the user when the - inferior stopped in a different thread than it had been running in. - It can also be used to find for which thread normal_stop last - reported a stop. */ +/* proceed and normal_stop use these to notify the user when the + inferior stopped in a different thread/lane than it had been + running in. The thread can also be used to find for which thread + normal_stop last reported a stop. */ static thread_info_ref previous_thread; +static int previous_lane = -1; /* See infrun.h. */ void -update_previous_thread () +update_previous_thread_and_lane () { if (inferior_ptid == null_ptid) - previous_thread = nullptr; + { + previous_thread = nullptr; + previous_lane = -1; + } else - previous_thread = thread_info_ref::new_reference (inferior_thread ()); + { + previous_thread = thread_info_ref::new_reference (inferior_thread ()); + previous_lane = inferior_thread ()->current_simd_lane (); + } } /* See infrun.h. */ @@ -195,6 +202,18 @@ show_debug_infrun (struct ui_file *file, int from_tty, gdb_printf (file, _("Inferior debugging is %s.\n"), value); } +/* See infrun.h. */ +bool maint_lane_divergence_support = true; + +/* Implementation of "maint show lane-divergence-support". */ + +static void +maint_show_lane_divergence_support (ui_file *file, int from_tty, + cmd_list_element *c, const char *value) +{ + gdb_printf (file, _("Lane divergence debugging support is %s.\n"), value); +} + /* Support for disabling address space randomization. */ bool disable_randomization = true; @@ -3632,8 +3651,8 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal) return; } - /* We'll update this if & when we switch to a new thread. */ - update_previous_thread (); + /* We'll update these if & when we switch to a new thread/lane. */ + update_previous_thread_and_lane (); thread_info *cur_thr = inferior_thread (); infrun_debug_printf ("cur_thr = %s", cur_thr->ptid.to_string ().c_str ()); @@ -3878,7 +3897,7 @@ init_wait_for_inferior (void) nullify_last_target_wait_ptid (); - update_previous_thread (); + update_previous_thread_and_lane (); } @@ -7379,6 +7398,38 @@ handle_signal_stop (struct execution_control_state *ecs) process_event_stop_test (ecs); } +/* Switch to an active lane that caused the stop. If we stopped for a + breakpoint, use the breakpoint's lane mask to determine candidate + active lanes, as it may have been further restricted from the + thread's execution mask. Otherwise, use the thread's execution + mask. If the previous current lane is still active, do not change + lanes. Otherwise, select the first active lane. If all lanes are + masked out, leave the previous current lane selected. */ + +static void +switch_to_active_lane (thread_info *thr) +{ + simd_lanes_mask_t mask; + if (thr->control.stop_bpstat != nullptr) + mask = thr->control.stop_bpstat->simd_lane_mask; + else + mask = thr->active_simd_lanes_mask (); + + if (mask != 0) + { + int current_simd_lane = thr->current_simd_lane (); + /* If previous SIMD lane matches the SIMD lane mask, do not + change it. Otherwise, find a new one. */ + if (!is_simd_lane_active (mask, current_simd_lane)) + { + /* If a specific SIMD lane caused the stop, then switch the + thread to this lane. */ + int first_lane = find_first_active_simd_lane (mask); + thr->set_current_simd_lane (first_lane); + } + } +} + /* Return the address for the beginning of the line. */ CORE_ADDR @@ -7707,6 +7758,21 @@ process_event_stop_test (struct execution_control_state *ecs) return; } + /* If this lane is divergent, keep stepping until it becomes + active. */ + if (maint_lane_divergence_support + && (!ecs->event_thread->is_simd_lane_active + (ecs->event_thread->current_simd_lane ()))) + { + infrun_debug_printf + ("stepping divergent lane %s", + target_lane_to_str (ecs->event_thread, + ecs->event_thread->current_simd_lane ()).c_str ()); + + keep_going (ecs); + return; + } + fill_in_stop_func (gdbarch, ecs); /* If stepping through a line, keep going if still within it. @@ -9597,6 +9663,12 @@ normal_stop () instead of after. */ update_thread_list (); + if (target_has_execution () + && last.kind () != TARGET_WAITKIND_SIGNALLED + && last.kind () != TARGET_WAITKIND_EXITED + && last.kind () != TARGET_WAITKIND_NO_RESUMED) + switch_to_active_lane (inferior_thread ()); + if (last.kind () == TARGET_WAITKIND_STOPPED && stopped_by_random_signal) notify_signal_received (inferior_thread ()->stop_signal ()); @@ -9623,18 +9695,35 @@ normal_stop () && last.kind () != TARGET_WAITKIND_NO_RESUMED && last.kind () != TARGET_WAITKIND_THREAD_EXITED) && target_has_execution () - && previous_thread != inferior_thread ()) + && (previous_thread != inferior_thread () + || previous_lane != inferior_thread ()->current_simd_lane ())) { SWITCH_THRU_ALL_UIS () { target_terminal::ours_for_output (); - gdb_printf (_("[Switching to %s]\n"), - target_pid_to_str (inferior_ptid).c_str ()); + + thread_info *thr = inferior_thread (); + + if (thr->has_simd_lanes ()) + { + int lane = thr->current_simd_lane (); + + gdb_printf (_("[Switching to lane %s (%s)]\n"), + print_lane_id (thr, lane), + target_lane_to_str (thr, lane).c_str ()); + } + else + { + gdb_printf (_("[Switching to thread %s (%s)]\n"), + print_thread_id (thr), + target_pid_to_str (thr->ptid).c_str ()); + } + annotate_thread_changed (); } } - update_previous_thread (); + update_previous_thread_and_lane (); } if (last.kind () == TARGET_WAITKIND_NO_RESUMED @@ -10869,6 +10958,16 @@ or signalled."), &setlist, &showlist); + add_setshow_boolean_cmd ("lane-divergence-support", class_maintenance, + &maint_lane_divergence_support, _("\ +Set lane divergence debugging support."), _("\ +Show lane divergence debugging support."), _("\ +When non-zero, lane divergence debugging is enabled."), + nullptr, + maint_show_lane_divergence_support, + &maintenance_set_cmdlist, + &maintenance_show_cmdlist); + #if GDB_SELF_TEST selftests::register_test ("infrun_thread_ptid_changed", selftests::infrun_thread_ptid_changed); diff --git a/gdb/infrun.h b/gdb/infrun.h index 616fa7b44a3..3945a79fc8f 100644 --- a/gdb/infrun.h +++ b/gdb/infrun.h @@ -119,8 +119,11 @@ enum exec_direction_kind extern enum exec_direction_kind execution_direction; /* Call this to point 'previous_thread' at the thread returned by - inferior_thread, or at nullptr, if there's no selected thread. */ -extern void update_previous_thread (); + inferior_thread, or at nullptr, if there's no selected thread. + + Update 'previous_lane' to the value returned by + thread_info::current_simd_lane, or -1 if there's no selected thread. */ +extern void update_previous_thread_and_lane (); /* Get a weak reference to 'previous_thread'. */ extern thread_info *get_previous_thread (); @@ -427,5 +430,8 @@ struct scoped_enable_commit_resumed bool m_prev_enable_commit_resumed; }; +/* True if lane divergence debugging support is enabled. Controlled + with "maint set lane-divergence". */ +extern bool maint_lane_divergence_support; #endif /* GDB_INFRUN_H */ diff --git a/gdb/linespec.c b/gdb/linespec.c index 5eef96c44e7..4bf9636b205 100644 --- a/gdb/linespec.c +++ b/gdb/linespec.c @@ -280,7 +280,15 @@ enum linespec_token_type /* List of keywords. This is NULL-terminated so that it can be used as enum completer. */ -const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL }; +const char * const linespec_keywords[] = { + "if", + "thread", + "task", + "inferior", + "lane", + "-force-condition", + nullptr +}; #define IF_KEYWORD_INDEX 0 #define FORCE_KEYWORD_INDEX 4 diff --git a/gdb/linux-fork.c b/gdb/linux-fork.c index 52fd7d9ab8f..0c0daf6d954 100644 --- a/gdb/linux-fork.c +++ b/gdb/linux-fork.c @@ -297,13 +297,14 @@ parse_checkpoint_id (const char *ckptstr) if (dot != nullptr) { /* Parse number to the left of the dot. */ - int inf_num; p1 = number; - inf_num = get_number_trailer (&p1, '.'); - if (inf_num <= 0) + std::optional res = get_number_trailer (&p1, '.'); + if (!res.has_value () || *res <= 0) error (_("Inferior number must be a positive integer")); + int inf_num = *res; + inf = find_inferior_id (inf_num); if (inf == NULL) error (_("No inferior number '%d'"), inf_num); @@ -316,7 +317,7 @@ parse_checkpoint_id (const char *ckptstr) p1 = number; } - int fork_num = get_number_trailer (&p1, 0); + int fork_num = get_number (&p1); if (fork_num < 0) error (_("Checkpoint number must be a non-negative integer")); diff --git a/gdb/maint-test-options.c b/gdb/maint-test-options.c index da9765e0733..b3c39ce5c4e 100644 --- a/gdb/maint-test-options.c +++ b/gdb/maint-test-options.c @@ -57,13 +57,14 @@ readline, for proper testing of TAB completion. These maintenance commands support options of all the different - available kinds of commands (boolean, enum, flag, string, filename, - uinteger): + available kinds of commands (boolean, color, enum, flag, string, + filename, expression, uinteger): (gdb) maint test-options require-delimiter -[TAB] - -bool -flag -uinteger-unlimited - -enum -pinteger-unlimited -xx1 - -filename -string -xx2 + -bool -filename -uinteger-unlimited + -color -flag -xx1 + -enum -pinteger-unlimited -xx2 + -expression -string (gdb) maint test-options require-delimiter -bool o[TAB] off on @@ -137,6 +138,7 @@ struct test_options_opts int pint_unl_opt = 0; std::string string_opt; std::string filename_opt; + std::string expression_opt; ui_file_style::color color_opt { ui_file_style::MAGENTA }; test_options_opts () = default; @@ -150,7 +152,7 @@ struct test_options_opts gdb_printf (file, _("-flag %d -xx1 %d -xx2 %d -bool %d " "-enum %s -uint-unl %s -pint-unl %s -string '%s' " - "-filename '%s' -color %s -- %s\n"), + "-filename '%s' -expression (%s) -color %s -- %s\n"), flag_opt, xx1_opt, xx2_opt, @@ -164,6 +166,7 @@ struct test_options_opts : plongest (pint_unl_opt)), string_opt.c_str (), filename_opt.c_str (), + expression_opt.c_str (), color_opt.to_string ().c_str (), args); } @@ -248,6 +251,14 @@ static const gdb::option::option_def test_options_option_defs[] = { N_("A filename option."), }, + /* An expression option. */ + gdb::option::expression_option_def { + "expression", + [] (test_options_opts *opts) { return &opts->expression_opt; }, + nullptr, /* show_cmd_cb */ + N_("An expression option."), + }, + /* A color option. */ gdb::option::color_option_def { "color", diff --git a/gdb/mi/mi-cmd-break.c b/gdb/mi/mi-cmd-break.c index 49154694b7c..db613a4ac4f 100644 --- a/gdb/mi/mi-cmd-break.c +++ b/gdb/mi/mi-cmd-break.c @@ -364,8 +364,12 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, error (_("Garbage '%s' at end of location"), address); } + bp_specificity specificity; + specificity.thread = thread; + specificity.inferior = thread_group; + create_breakpoint (get_current_arch (), locspec.get (), condition, - thread, thread_group, + specificity, extra_string.c_str (), force_condition, 0 /* condition and thread are valid. */, diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c index 05bc0ffce27..46351ae75d8 100644 --- a/gdb/mi/mi-cmd-stack.c +++ b/gdb/mi/mi-cmd-stack.c @@ -496,15 +496,15 @@ list_arg_or_local (const struct frame_arg *arg, enum what_to_list what, { struct ui_out *uiout = current_uiout; - gdb_assert (!arg->val || !arg->error); + gdb_assert (!arg->val || arg->error.error == GDB_NO_ERROR); gdb_assert ((values == PRINT_NO_VALUES && arg->val == NULL - && arg->error == NULL) + && arg->error.error == GDB_NO_ERROR) || values == PRINT_SIMPLE_VALUES || (values == PRINT_ALL_VALUES - && (arg->val != NULL || arg->error != NULL))); + && (arg->val != NULL || arg->error.error != GDB_NO_ERROR))); gdb_assert (arg->entry_kind == print_entry_values_no || (arg->entry_kind == print_entry_values_only - && (arg->val || arg->error))); + && (arg->val || arg->error.error != GDB_NO_ERROR))); if (skip_unavailable && arg->val != NULL && (arg->val->entirely_unavailable () @@ -537,10 +537,13 @@ list_arg_or_local (const struct frame_arg *arg, enum what_to_list what, uiout->field_stream ("type", stb); } - if (arg->val || arg->error) + if (arg->val || arg->error.error != GDB_NO_ERROR) { - if (arg->error) - stb.printf (_(""), arg->error.get ()); + if (arg->error.error == LANE_INACTIVE_ERROR) + stb.printf (_("<%s>"), arg->error.message->c_str ()); + else if (arg->error.error != GDB_NO_ERROR) + stb.printf (_(""), + arg->error.message->c_str ()); else { try diff --git a/gdb/mi/mi-cmd-var.c b/gdb/mi/mi-cmd-var.c index 7e089470e87..afd98bce90b 100644 --- a/gdb/mi/mi-cmd-var.c +++ b/gdb/mi/mi-cmd-var.c @@ -72,7 +72,13 @@ print_varobj (struct varobj *var, enum print_values print_values, thread_id = varobj_get_thread_id (var); if (thread_id > 0) - uiout->field_signed ("thread-id", thread_id); + { + uiout->field_signed ("thread-id", thread_id); + + int lane = varobj_get_lane (var); + if (lane >= 0) + uiout->field_signed ("lane-id", lane); + } if (varobj_get_frozen (var)) uiout->field_signed ("frozen", 1); diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c index 69f19257b67..882dfe4a47e 100644 --- a/gdb/mi/mi-cmds.c +++ b/gdb/mi/mi-cmds.c @@ -330,6 +330,7 @@ add_builtin_mi_commands () add_mi_cmd_mi ("target-flash-erase", mi_cmd_target_flash_erase); add_mi_cmd_cli ("target-select", "target", 1); add_mi_cmd_mi ("thread-info", mi_cmd_thread_info); + add_mi_cmd_mi ("lane-info", mi_cmd_lane_info); add_mi_cmd_mi ("thread-list-ids", mi_cmd_thread_list_ids); add_mi_cmd_mi ("thread-select", mi_cmd_thread_select, &mi_suppress_notification.user_selected_context); diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h index 25e71729f70..a761d462bfb 100644 --- a/gdb/mi/mi-cmds.h +++ b/gdb/mi/mi-cmds.h @@ -90,6 +90,7 @@ extern mi_cmd_argv_ftype mi_cmd_info_ada_exceptions; extern mi_cmd_argv_ftype mi_cmd_info_gdb_mi_command; extern mi_cmd_argv_ftype mi_cmd_info_os; extern mi_cmd_argv_ftype mi_cmd_interpreter_exec; +extern mi_cmd_argv_ftype mi_cmd_lane_info; extern mi_cmd_argv_ftype mi_cmd_list_features; extern mi_cmd_argv_ftype mi_cmd_list_target_features; extern mi_cmd_argv_ftype mi_cmd_list_thread_groups; diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c index 71491ac663e..8dd7d1660c4 100644 --- a/gdb/mi/mi-interp.c +++ b/gdb/mi/mi-interp.c @@ -427,6 +427,8 @@ mi_interp::on_normal_stop (struct bpstat *bs, int print_frame) print_stop_event (this->cli_uiout); mi_uiout->field_signed ("thread-id", tp->global_num); + if (tp->has_simd_lanes ()) + mi_uiout->field_signed ("lane-id", tp->current_simd_lane ()); if (non_stop) { ui_out_emit_list list_emitter (mi_uiout, "stopped-threads"); @@ -829,7 +831,13 @@ mi_interp::on_memory_changed (CORE_ADDR memaddr, ssize_t len, ui_out_redirect_pop redir (mi_uiout, this->event_channel); - if (scope_matches (scope, LOCATION_SCOPE_THREAD)) + if (scope_matches (scope, LOCATION_SCOPE_LANE)) + { + mi_uiout->field_signed ("thread-id", inferior_thread ()->global_num); + mi_uiout->field_signed ("lane-id", + inferior_thread ()->current_simd_lane ()); + } + else if (scope_matches (scope, LOCATION_SCOPE_THREAD)) mi_uiout->field_signed ("thread-id", inferior_thread ()->global_num); else mi_uiout->field_fmt ("thread-group", "i%d", current_inferior ()->num); @@ -876,6 +884,10 @@ mi_interp::on_user_selected_context_changed (user_selected_what selection) gdb_printf (this->event_channel, "thread-selected,id=\"%d\"", tp->global_num); + if (tp->has_simd_lanes ()) + gdb_printf (this->event_channel, + ",lane-id=\"%d\"", tp->current_simd_lane ()); + if (tp->state != THREAD_RUNNING) { if (has_stack_frames ()) diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c index a8b829f44d1..bcf2c66e710 100644 --- a/gdb/mi/mi-main.c +++ b/gdb/mi/mi-main.c @@ -530,15 +530,52 @@ mi_cmd_target_flash_erase (const char *command, const char *const *argv, void mi_cmd_thread_select (const char *command, const char *const *argv, int argc) { + int lane = -1; + + /* Parse the command options. */ + enum opt + { + LANE_OPT, + }; + static const struct mi_opt opts[] = + { + /* Note: this can't be "--lane", because we have a global --lane + option parsed by mi_parse. "-thread-select --lane 3 4" would + select lane 3 of the current thread instead of the lane of + the passed-in thread, only to be then be "overwritten" when + thread 4 is selected. */ + {"l", LANE_OPT, 1}, + {NULL, 0, 0}, + }; + + int oind = 0; + const char *oarg; + + while (1) + { + int opt = mi_getopt ("-thread-select", argc, argv, opts, &oind, &oarg); + + if (opt < 0) + break; + switch ((enum opt) opt) + { + case LANE_OPT: + lane = atoi (oarg); + break; + } + } + argv += oind; + argc -= oind; + if (argc != 1) - error (_("-thread-select: USAGE: threadnum.")); + error (_("-thread-select: USAGE: [-l lanenum] threadnum.")); int num = value_as_long (parse_and_eval (argv[0])); thread_info *thr = find_thread_global_id (num); if (thr == NULL) error (_("Thread ID %d not known."), num); - thread_select (argv[0], thr); + thread_select (argv[0], thr, lane); print_selected_thread_frame (current_uiout, USER_SELECTED_THREAD | USER_SELECTED_FRAME); @@ -573,6 +610,15 @@ mi_cmd_thread_list_ids (const char *command, const char *const *argv, int argc) current_uiout->field_signed ("number-of-threads", num); } +void +mi_cmd_lane_info (const char *command, const char *const *argv, int argc) +{ + if (argc != 0 && argc != 1) + error (_("Invalid MI command")); + + print_lane_info (current_uiout, argv[0]); +} + void mi_cmd_thread_info (const char *command, const char *const *argv, int argc) { @@ -1966,7 +2012,10 @@ struct user_selected_context { /* Constructor. */ user_selected_context () - : m_previous_ptid (inferior_ptid) + : m_previous_ptid (inferior_ptid), + m_previous_lane (inferior_ptid != null_ptid + ? inferior_thread ()->current_simd_lane () + : -1) { save_selected_frame (&m_previous_frame_id, &m_previous_frame_level); } @@ -1975,9 +2024,10 @@ struct user_selected_context was created. */ bool has_changed () const { - /* Did the selected thread change? */ + /* Did the selected thread or lane change? */ if (m_previous_ptid != null_ptid && inferior_ptid != null_ptid - && m_previous_ptid != inferior_ptid) + && (m_previous_ptid != inferior_ptid + || inferior_thread ()->current_simd_lane () != m_previous_lane)) return true; /* Grab details of the currently selected frame, for comparison. */ @@ -2006,6 +2056,10 @@ struct user_selected_context no previously selected thread. */ ptid_t m_previous_ptid; + /* The previously selected lane. This will be -1 if there was no + previously selected lane. */ + int m_previous_lane; + /* The previously selected frame. If the innermost frame is selected, or no frame is selected, then the frame_id will be null_frame_id, and the level will be -1. */ @@ -2024,6 +2078,9 @@ mi_cmd_execute (struct mi_parse *parse) if (parse->all && parse->thread != -1) error (_("Cannot specify --thread together with --all")); + if (parse->all && parse->lane != -1) + error (_("Cannot specify --lane together with --all")); + if (parse->thread_group != -1 && parse->thread != -1) error (_("Cannot specify --thread together with --thread-group")); @@ -2072,6 +2129,18 @@ mi_cmd_execute (struct mi_parse *parse) switch_to_thread (tp); } + std::optional lane_saver; + if (parse->lane != -1) + { + if (inferior_ptid == null_ptid) + error (_("No selected thread.")); + + if (parse->cmd->preserve_user_selected_context ()) + lane_saver.emplace (inferior_thread ()); + + switch_to_lane (parse->lane); + } + std::optional frame_saver; if (parse->frame != -1) { diff --git a/gdb/mi/mi-parse.c b/gdb/mi/mi-parse.c index 1b8b5308b70..fd5eace4299 100644 --- a/gdb/mi/mi-parse.c +++ b/gdb/mi/mi-parse.c @@ -263,6 +263,16 @@ mi_parse::set_thread (const char *arg, char **endp) /* See mi-parse.h. */ +void +mi_parse::set_lane (const char *arg, char **endp) +{ + if (lane != -1) + error (_("Duplicate '--lane' option")); + lane = strtol (arg, endp, 10); +} + +/* See mi-parse.h. */ + void mi_parse::set_frame (const char *arg, char **endp) { @@ -342,8 +352,9 @@ mi_parse::mi_parse (const char *cmd, std::string *token) size_t as = sizeof ("--all ") - 1; size_t tgs = sizeof ("--thread-group ") - 1; size_t ts = sizeof ("--thread ") - 1; + size_t lane_s = sizeof ("--lane ") - 1; size_t fs = sizeof ("--frame ") - 1; - size_t ls = sizeof ("--language ") - 1; + size_t lang_s = sizeof ("--language ") - 1; if (strncmp (chp, "--all ", as) == 0) { @@ -374,6 +385,15 @@ mi_parse::mi_parse (const char *cmd, std::string *token) this->set_thread (chp, &endp); chp = endp; } + else if (strncmp (chp, "--lane ", lane_s) == 0) + { + char *endp; + + option = "--lane"; + chp += lane_s; + this->set_lane (chp, &endp); + chp = endp; + } else if (strncmp (chp, "--frame ", fs) == 0) { char *endp; @@ -383,10 +403,10 @@ mi_parse::mi_parse (const char *cmd, std::string *token) this->set_frame (chp, &endp); chp = endp; } - else if (strncmp (chp, "--language ", ls) == 0) + else if (strncmp (chp, "--language ", lang_s) == 0) { option = "--language"; - chp += ls; + chp += lang_s; this->set_language (chp, &chp); } else @@ -449,6 +469,13 @@ mi_parse::mi_parse (gdb::unique_xmalloc_ptr command, error ("No argument to '--thread'"); this->set_thread (args[i].get (), nullptr); } + else if (strcmp (chp, "--lane") == 0) + { + ++i; + if (i == args.size ()) + error ("No argument to '--lane'"); + this->set_lane (args[i].get (), nullptr); + } else if (strcmp (chp, "--frame") == 0) { ++i; diff --git a/gdb/mi/mi-parse.h b/gdb/mi/mi-parse.h index e6777ba97fe..2ebcd8edb06 100644 --- a/gdb/mi/mi-parse.h +++ b/gdb/mi/mi-parse.h @@ -80,6 +80,7 @@ struct mi_parse int all = 0; int thread_group = -1; /* At present, the same as inferior number. */ int thread = -1; + int lane = -1; int frame = -1; /* The language that should be used to evaluate the MI command. @@ -94,6 +95,7 @@ struct mi_parse will be updated to just after the argument text. */ void set_thread_group (const char *arg, char **endp); void set_thread (const char *arg, char **endp); + void set_lane (const char *arg, char **endp); void set_frame (const char *arg, char **endp); void set_language (const char *arg, const char **endp); diff --git a/gdb/printcmd.c b/gdb/printcmd.c index c6e69b6de81..8c6e0a98e23 100644 --- a/gdb/printcmd.c +++ b/gdb/printcmd.c @@ -2384,9 +2384,17 @@ print_variable_and_value (const char *name, struct symbol *var, } catch (const gdb_exception_error &except) { - fprintf_styled (stream, metadata_style.style (), - "", name, - except.what ()); + if (except.error == LANE_INACTIVE_ERROR) + { + fprintf_styled (stream, metadata_style.style (), "<%s>", + except.what ()); + } + else + { + fprintf_styled (stream, metadata_style.style (), + "", name, + except.what ()); + } } gdb_printf (stream, "\n"); diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c index 882b825f682..129e72fc382 100644 --- a/gdb/python/py-breakpoint.c +++ b/gdb/python/py-breakpoint.c @@ -272,7 +272,7 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure) return -1; } - if (self_bp->bp->task != -1) + if (self_bp->bp->specificity.task != -1) { PyErr_SetString (PyExc_RuntimeError, _("Cannot set both task and thread attributes.")); @@ -288,7 +288,7 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure) return -1; } - if (self_bp->bp->inferior != -1 && id != -1) + if (self_bp->bp->specificity.inferior != -1 && id != -1) { PyErr_SetString (PyExc_RuntimeError, _("Cannot have both 'thread' and 'inferior' " @@ -347,7 +347,7 @@ bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure) return -1; } - if (self_bp->bp->thread != -1 && id != -1) + if (self_bp->bp->specificity.thread != -1 && id != -1) { PyErr_SetString (PyExc_RuntimeError, _("Cannot have both 'thread' and 'inferior' conditions " @@ -355,7 +355,7 @@ bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure) return -1; } - if (self_bp->bp->task != -1 && id != -1) + if (self_bp->bp->specificity.task != -1 && id != -1) { PyErr_SetString (PyExc_RuntimeError, _("Cannot have both 'task' and 'inferior' conditions " @@ -405,7 +405,7 @@ bppy_set_task (PyObject *self, PyObject *newvalue, void *closure) return -1; } - if (self_bp->bp->thread != -1) + if (self_bp->bp->specificity.thread != -1) { PyErr_SetString (PyExc_RuntimeError, _("Cannot set both task and thread attributes.")); @@ -765,10 +765,12 @@ bppy_get_thread (PyObject *self, void *closure) BPPY_REQUIRE_VALID (self_bp); - if (self_bp->bp->thread == -1) + int thread = self_bp->bp->specificity.thread; + + if (thread == -1) Py_RETURN_NONE; - return gdb_py_object_from_longest (self_bp->bp->thread).release (); + return gdb_py_object_from_longest (thread).release (); } /* Python function to get the breakpoint's inferior ID. */ @@ -779,10 +781,12 @@ bppy_get_inferior (PyObject *self, void *closure) BPPY_REQUIRE_VALID (self_bp); - if (self_bp->bp->inferior == -1) + int inferior = self_bp->bp->specificity.inferior; + + if (inferior == -1) Py_RETURN_NONE; - return gdb_py_object_from_longest (self_bp->bp->inferior).release (); + return gdb_py_object_from_longest (inferior).release (); } /* Python function to get the breakpoint's task ID (in Ada). */ @@ -793,10 +797,12 @@ bppy_get_task (PyObject *self, void *closure) BPPY_REQUIRE_VALID (self_bp); - if (self_bp->bp->task == -1) + int task = self_bp->bp->specificity.task; + + if (task == -1) Py_RETURN_NONE; - return gdb_py_object_from_longest (self_bp->bp->task).release (); + return gdb_py_object_from_longest (task).release (); } /* Python function to get the breakpoint's hit count. */ @@ -1024,7 +1030,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs) = breakpoint_ops_for_location_spec (locspec.get (), false); create_breakpoint (gdbpy_enter::get_gdbarch (), - locspec.get (), NULL, -1, -1, NULL, false, + locspec.get (), NULL, {}, NULL, false, 0, temporary_bp, type, 0, @@ -1073,10 +1079,13 @@ bppy_repr (PyObject *self) return PyUnicode_FromFormat ("<%s (invalid)>", Py_TYPE (self)->tp_name); std::string str = " "; - if (bp->bp->thread != -1) - str += string_printf ("thread=%d ", bp->bp->thread); - if (bp->bp->task > 0) - str += string_printf ("task=%d ", bp->bp->task); + const bp_specificity &s = bp->bp->specificity; + if (s.inferior != -1) + str += string_printf ("inferior=%d ", s.inferior); + if (s.thread != -1) + str += string_printf ("thread=%d ", s.thread); + if (s.task > 0) + str += string_printf ("task=%d ", s.task); if (bp->bp->enable_count > 0) str += string_printf ("enable_count=%d ", bp->bp->enable_count); str.pop_back (); diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c index bc53d4ed13d..62d88a18832 100644 --- a/gdb/python/py-finishbreakpoint.c +++ b/gdb/python/py-finishbreakpoint.c @@ -305,8 +305,10 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs) /* Set a breakpoint on the return address. */ location_spec_up locspec = new_address_location_spec (get_frame_pc (prev_frame), NULL, 0); + bp_specificity specificity; + specificity.thread = thread; create_breakpoint (gdbpy_enter::get_gdbarch (), - locspec.get (), NULL, thread, -1, NULL, false, + locspec.get (), NULL, specificity, NULL, false, 0, 1 /*temp_flag*/, bp_breakpoint, diff --git a/gdb/python/py-framefilter.c b/gdb/python/py-framefilter.c index 88252b06144..632f1840057 100644 --- a/gdb/python/py-framefilter.c +++ b/gdb/python/py-framefilter.c @@ -313,7 +313,7 @@ py_print_single_arg (struct ui_out *out, if (fa != NULL) { - if (fa->val == NULL && fa->error == NULL) + if (fa->val == NULL && fa->error.error == GDB_NO_ERROR) return; language = language_def (fa->sym->language ()); val = fa->val; @@ -387,10 +387,10 @@ py_print_single_arg (struct ui_out *out, { if (val == NULL) { - gdb_assert (fa != NULL && fa->error != NULL); + gdb_assert (fa != NULL && fa->error.error != GDB_NO_ERROR); out->field_fmt ("value", metadata_style.style (), _(""), - fa->error.get ()); + fa->error.message->c_str ()); } else py_print_value (out, val, opts, 0, args_type, language); diff --git a/gdb/python/py-param.c b/gdb/python/py-param.c index 0e79d2199f6..027c3c895f2 100644 --- a/gdb/python/py-param.c +++ b/gdb/python/py-param.c @@ -40,6 +40,7 @@ enum py_param_types param_string_noescape, param_optional_filename, param_filename, + param_expression, param_zinteger, param_zuinteger, param_zuinteger_unlimited, @@ -68,6 +69,7 @@ param_to_var[] = { var_string_noescape }, { var_optional_filename }, { var_filename }, + { var_expression }, { var_integer }, { var_uinteger }, { var_pinteger, pinteger_unlimited_literals }, @@ -89,6 +91,7 @@ static struct { { "PARAM_STRING_NOESCAPE", param_string_noescape }, { "PARAM_OPTIONAL_FILENAME", param_optional_filename }, { "PARAM_FILENAME", param_filename }, + { "PARAM_EXPRESSION", param_expression }, { "PARAM_ZINTEGER", param_zinteger }, { "PARAM_ZUINTEGER", param_zuinteger }, { "PARAM_ZUINTEGER_UNLIMITED", param_zuinteger_unlimited }, @@ -719,6 +722,13 @@ add_setshow_generic (enum var_types type, const literal_def *extra_literals, get_show_value, set_list, show_list); break; + case var_expression: + commands = add_setshow_expression_cmd (cmd_name.get (), cmdclass, + self->value.stringval, set_doc, + show_doc, help_doc, get_set_value, + get_show_value, set_list, show_list); + break; + case var_enum: /* Initialize the value, just in case. */ self->value.cstringval = self->enumeration[0]; diff --git a/gdb/stack.c b/gdb/stack.c index 2c8ae0e48df..7683bcd36a2 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -416,7 +416,7 @@ print_frame_arg (const frame_print_options &fp_opts, string_file stb; - gdb_assert (!arg->val || !arg->error); + gdb_assert (!arg->val || arg->error.error == GDB_NO_ERROR); gdb_assert (arg->entry_kind == print_entry_values_no || arg->entry_kind == print_entry_values_only || (!uiout->is_mi_like_p () @@ -441,13 +441,19 @@ print_frame_arg (const frame_print_options &fp_opts, uiout->text ("="); ui_file_style style; - if (!arg->val && !arg->error) + if (!arg->val && arg->error.error == GDB_NO_ERROR) uiout->text ("..."); else { - if (arg->error) + if (arg->error.error == LANE_INACTIVE_ERROR) { - stb.printf (_(""), arg->error.get ()); + stb.printf (_("<%s>"), arg->error.message->c_str ()); + style = metadata_style.style (); + } + else if (arg->error.error != GDB_NO_ERROR) + { + stb.printf (_(""), + arg->error.message->c_str()); style = metadata_style.style (); } else @@ -504,7 +510,7 @@ read_frame_local (struct symbol *sym, const frame_info_ptr &frame, { argp->sym = sym; argp->val = NULL; - argp->error = NULL; + argp->error = {}; try { @@ -512,7 +518,7 @@ read_frame_local (struct symbol *sym, const frame_info_ptr &frame, } catch (const gdb_exception_error &except) { - argp->error.reset (xstrdup (except.what ())); + argp->error = except; } } @@ -525,7 +531,7 @@ read_frame_arg (const frame_print_options &fp_opts, struct frame_arg *argp, struct frame_arg *entryargp) { struct value *val = NULL, *entryval = NULL; - char *val_error = NULL, *entryval_error = NULL; + gdb_exception val_error, entryval_error; int val_equal = 0; if (fp_opts.print_entry_values != print_entry_values_only @@ -537,8 +543,7 @@ read_frame_arg (const frame_print_options &fp_opts, } catch (const gdb_exception_error &except) { - val_error = (char *) alloca (except.message->size () + 1); - strcpy (val_error, except.what ()); + val_error = except; } } @@ -556,10 +561,7 @@ read_frame_arg (const frame_print_options &fp_opts, catch (const gdb_exception_error &except) { if (except.error != NO_ENTRY_VALUE_ERROR) - { - entryval_error = (char *) alloca (except.message->size () + 1); - strcpy (entryval_error, except.what ()); - } + entryval_error = except; } if (entryval != NULL && entryval->optimized_out ()) @@ -616,11 +618,7 @@ read_frame_arg (const frame_print_options &fp_opts, if (except.error == NO_ENTRY_VALUE_ERROR) val_equal = 1; else if (except.message != NULL) - { - entryval_error - = (char *) alloca (except.message->size () + 1); - strcpy (entryval_error, except.what ()); - } + entryval_error = except; } /* Value was not a reference; and its content matches. */ @@ -635,10 +633,11 @@ read_frame_arg (const frame_print_options &fp_opts, /* Try to remove possibly duplicate error message for ENTRYARGP even in MI mode. */ - if (val_error && entryval_error - && strcmp (val_error, entryval_error) == 0) + if (val_error.error != GDB_NO_ERROR + && entryval_error.error != GDB_NO_ERROR + && *val_error.message == *entryval_error.message) { - entryval_error = NULL; + entryval_error = {}; /* Do not se VAL_EQUAL as the same error message may be shown for the entry value even if no entry values are present in the @@ -659,8 +658,7 @@ read_frame_arg (const frame_print_options &fp_opts, } catch (const gdb_exception_error &except) { - val_error = (char *) alloca (except.message->size () + 1); - strcpy (val_error, except.what ()); + val_error = except; } } if (fp_opts.print_entry_values == print_entry_values_only @@ -669,7 +667,7 @@ read_frame_arg (const frame_print_options &fp_opts, && (!val || val->optimized_out ()))) { entryval = value::allocate_optimized_out (sym->type ()); - entryval_error = NULL; + entryval_error = {}; } } if ((fp_opts.print_entry_values == print_entry_values_compact @@ -678,13 +676,13 @@ read_frame_arg (const frame_print_options &fp_opts, && (!val || val->optimized_out ()) && entryval != NULL) { val = NULL; - val_error = NULL; + val_error = {}; } argp->sym = sym; argp->val = val; - argp->error.reset (val_error ? xstrdup (val_error) : NULL); - if (!val && !val_error) + argp->error = val_error; + if (!val && val_error.error == GDB_NO_ERROR) argp->entry_kind = print_entry_values_only; else if ((fp_opts.print_entry_values == print_entry_values_compact || fp_opts.print_entry_values == print_entry_values_default) @@ -698,8 +696,8 @@ read_frame_arg (const frame_print_options &fp_opts, entryargp->sym = sym; entryargp->val = entryval; - entryargp->error.reset (entryval_error ? xstrdup (entryval_error) : NULL); - if (!entryval && !entryval_error) + entryargp->error = entryval_error; + if (!entryval && entryval_error.error == GDB_NO_ERROR) entryargp->entry_kind = print_entry_values_no; else entryargp->entry_kind = print_entry_values_only; @@ -3099,7 +3097,7 @@ frame_apply_cmd_completer (struct cmd_list_element *ignore, { const char *cmd = text; - int count = get_number_trailer (&cmd, 0); + int count = get_number (&cmd); if (count == 0) return; @@ -3187,7 +3185,7 @@ frame_apply_command (const char* cmd, int from_tty) if (cmd == NULL) error (_("Missing COUNT argument.")); - count = get_number_trailer (&cmd, 0); + count = get_number (&cmd); if (count == 0) error (_("Invalid COUNT argument.")); diff --git a/gdb/target-delegates-gen.c b/gdb/target-delegates-gen.c index 54b71ed3a4e..afc0e494783 100644 --- a/gdb/target-delegates-gen.c +++ b/gdb/target-delegates-gen.c @@ -90,6 +90,10 @@ struct dummy_target : public target_ops std::string pid_to_str (ptid_t arg0) override; const char *extra_thread_info (thread_info *arg0) override; const char *thread_name (thread_info *arg0) override; + std::string lane_to_str (thread_info *arg0, int arg1) override; + std::string dispatch_pos_str (thread_info *arg0) override; + std::string thread_workgroup_pos_str (thread_info *arg0) override; + std::string lane_workgroup_pos_str (thread_info *arg0, int arg1) override; thread_info *thread_handle_to_thread_info (const gdb_byte *arg0, int arg1, inferior *arg2) override; gdb::array_view thread_info_to_thread_handle (struct thread_info *arg0) override; void stop (ptid_t arg0) override; @@ -273,6 +277,10 @@ struct debug_target : public target_ops std::string pid_to_str (ptid_t arg0) override; const char *extra_thread_info (thread_info *arg0) override; const char *thread_name (thread_info *arg0) override; + std::string lane_to_str (thread_info *arg0, int arg1) override; + std::string dispatch_pos_str (thread_info *arg0) override; + std::string thread_workgroup_pos_str (thread_info *arg0) override; + std::string lane_workgroup_pos_str (thread_info *arg0, int arg1) override; thread_info *thread_handle_to_thread_info (const gdb_byte *arg0, int arg1, inferior *arg2) override; gdb::array_view thread_info_to_thread_handle (struct thread_info *arg0) override; void stop (ptid_t arg0) override; @@ -1824,6 +1832,108 @@ debug_target::thread_name (thread_info *arg0) return result; } +std::string +target_ops::lane_to_str (thread_info *arg0, int arg1) +{ + return this->beneath ()->lane_to_str (arg0, arg1); +} + +std::string +dummy_target::lane_to_str (thread_info *arg0, int arg1) +{ + return default_lane_to_str (this, arg0, arg1); +} + +std::string +debug_target::lane_to_str (thread_info *arg0, int arg1) +{ + target_debug_printf_nofunc ("-> %s->lane_to_str (...)", this->beneath ()->shortname ()); + std::string result + = this->beneath ()->lane_to_str (arg0, arg1); + target_debug_printf_nofunc ("<- %s->lane_to_str (%s, %s) = %s", + this->beneath ()->shortname (), + target_debug_print_thread_info_p (arg0).c_str (), + target_debug_print_int (arg1).c_str (), + target_debug_print_std_string (result).c_str ()); + return result; +} + +std::string +target_ops::dispatch_pos_str (thread_info *arg0) +{ + return this->beneath ()->dispatch_pos_str (arg0); +} + +std::string +dummy_target::dispatch_pos_str (thread_info *arg0) +{ + return default_dispatch_pos_str (this, arg0); +} + +std::string +debug_target::dispatch_pos_str (thread_info *arg0) +{ + target_debug_printf_nofunc ("-> %s->dispatch_pos_str (...)", this->beneath ()->shortname ()); + std::string result + = this->beneath ()->dispatch_pos_str (arg0); + target_debug_printf_nofunc ("<- %s->dispatch_pos_str (%s) = %s", + this->beneath ()->shortname (), + target_debug_print_thread_info_p (arg0).c_str (), + target_debug_print_std_string (result).c_str ()); + return result; +} + +std::string +target_ops::thread_workgroup_pos_str (thread_info *arg0) +{ + return this->beneath ()->thread_workgroup_pos_str (arg0); +} + +std::string +dummy_target::thread_workgroup_pos_str (thread_info *arg0) +{ + return default_thread_workgroup_pos_str (this, arg0); +} + +std::string +debug_target::thread_workgroup_pos_str (thread_info *arg0) +{ + target_debug_printf_nofunc ("-> %s->thread_workgroup_pos_str (...)", this->beneath ()->shortname ()); + std::string result + = this->beneath ()->thread_workgroup_pos_str (arg0); + target_debug_printf_nofunc ("<- %s->thread_workgroup_pos_str (%s) = %s", + this->beneath ()->shortname (), + target_debug_print_thread_info_p (arg0).c_str (), + target_debug_print_std_string (result).c_str ()); + return result; +} + +std::string +target_ops::lane_workgroup_pos_str (thread_info *arg0, int arg1) +{ + return this->beneath ()->lane_workgroup_pos_str (arg0, arg1); +} + +std::string +dummy_target::lane_workgroup_pos_str (thread_info *arg0, int arg1) +{ + return default_lane_workgroup_pos_str (this, arg0, arg1); +} + +std::string +debug_target::lane_workgroup_pos_str (thread_info *arg0, int arg1) +{ + target_debug_printf_nofunc ("-> %s->lane_workgroup_pos_str (...)", this->beneath ()->shortname ()); + std::string result + = this->beneath ()->lane_workgroup_pos_str (arg0, arg1); + target_debug_printf_nofunc ("<- %s->lane_workgroup_pos_str (%s, %s) = %s", + this->beneath ()->shortname (), + target_debug_print_thread_info_p (arg0).c_str (), + target_debug_print_int (arg1).c_str (), + target_debug_print_std_string (result).c_str ()); + return result; +} + thread_info * target_ops::thread_handle_to_thread_info (const gdb_byte *arg0, int arg1, inferior *arg2) { diff --git a/gdb/target.c b/gdb/target.c index 6c293814cef..ae3aecd645f 100644 --- a/gdb/target.c +++ b/gdb/target.c @@ -2469,7 +2469,7 @@ target_pre_inferior () current_inferior ()->highest_thread_num = 0; - update_previous_thread (); + update_previous_thread_and_lane (); agent_capability_invalidate (); } @@ -2500,7 +2500,7 @@ target_preopen (int from_tty) } /* Release reference to old previous thread. */ - update_previous_thread (); + update_previous_thread_and_lane (); /* Calling target_kill may remove the target from the stack. But if it doesn't (which seems like a win for UDI), remove it now. */ @@ -2604,6 +2604,30 @@ target_pid_to_str (ptid_t ptid) return current_inferior ()->top_target ()->pid_to_str (ptid); } +std::string +target_lane_to_str (thread_info *thr, int lane) +{ + return current_inferior ()->top_target ()->lane_to_str (thr, lane); +} + +std::string +target_dispatch_pos_str (thread_info *thr) +{ + return current_inferior ()->top_target ()->dispatch_pos_str (thr); +} + +std::string +target_thread_workgroup_pos_str (thread_info *thr) +{ + return current_inferior ()->top_target ()->thread_workgroup_pos_str (thr); +} + +std::string +target_lane_workgroup_pos_str (thread_info *thr, int lane) +{ + return current_inferior ()->top_target ()->lane_workgroup_pos_str (thr, lane); +} + const char * target_thread_name (struct thread_info *info) { @@ -3686,6 +3710,30 @@ default_pid_to_str (struct target_ops *ops, ptid_t ptid) return normal_pid_to_str (ptid); } +static std::string +default_lane_to_str (struct target_ops *ops, thread_info *thr, int lane) +{ + return target_pid_to_str (thr->ptid); +} + +static std::string +default_dispatch_pos_str (struct target_ops *ops, thread_info *thr) +{ + return {}; +} + +static std::string +default_thread_workgroup_pos_str (struct target_ops *ops, thread_info *thr) +{ + return {}; +} + +static std::string +default_lane_workgroup_pos_str (struct target_ops *ops, thread_info *thr, int lane) +{ + return {}; +} + /* Error-catcher for target_find_memory_regions. */ static int dummy_find_memory_regions (struct target_ops *self, diff --git a/gdb/target.h b/gdb/target.h index db62f3f03a6..7d62291e815 100644 --- a/gdb/target.h +++ b/gdb/target.h @@ -718,6 +718,14 @@ struct target_ops TARGET_DEFAULT_RETURN (NULL); virtual const char *thread_name (thread_info *) TARGET_DEFAULT_RETURN (NULL); + virtual std::string lane_to_str (thread_info *, int) + TARGET_DEFAULT_FUNC (default_lane_to_str); + virtual std::string dispatch_pos_str (thread_info *) + TARGET_DEFAULT_FUNC (default_dispatch_pos_str); + virtual std::string thread_workgroup_pos_str (thread_info *) + TARGET_DEFAULT_FUNC (default_thread_workgroup_pos_str); + virtual std::string lane_workgroup_pos_str (thread_info *, int) + TARGET_DEFAULT_FUNC (default_lane_workgroup_pos_str); virtual thread_info *thread_handle_to_thread_info (const gdb_byte *, int, inferior *inf) @@ -2006,6 +2014,16 @@ extern std::string target_pid_to_str (ptid_t ptid); extern std::string normal_pid_to_str (ptid_t ptid); +/* Convert lane LANE of THR to a string. */ +extern std::string target_lane_to_str (thread_info *thr, int lane); + +/* Get the thread's dispatch position as a string. */ +extern std::string target_dispatch_pos_str (thread_info *thr); +/* Get the thread's workgroup position as a string. */ +extern std::string target_thread_workgroup_pos_str (thread_info *thr); +/* Get the lane's workgroup position as a string. */ +extern std::string target_lane_workgroup_pos_str (thread_info *thr, int lane); + /* Return a short string describing extra information about PID, e.g. "sleeping", "runnable", "running on LWP 3". Null return value is okay. */ diff --git a/gdb/testsuite/gdb.base/default.exp b/gdb/testsuite/gdb.base/default.exp index e255712644d..82e6aefef68 100644 --- a/gdb/testsuite/gdb.base/default.exp +++ b/gdb/testsuite/gdb.base/default.exp @@ -669,6 +669,11 @@ set show_conv_list \ { \ {$_sdata = void} \ {$_siginfo = void} \ + {$_lane_workgroup_pos = ""} \ + {$_thread_workgroup_pos = ""} \ + {$_dispatch_pos = ""} \ + {$_lane_count = 0} \ + {$_lane = 0} \ {$_thread = 0} \ {$_gthread = 0} \ {$_inferior = 1} \ diff --git a/gdb/testsuite/gdb.mi/mi-pthreads.exp b/gdb/testsuite/gdb.mi/mi-pthreads.exp index c151d43cff9..5659a85888e 100644 --- a/gdb/testsuite/gdb.mi/mi-pthreads.exp +++ b/gdb/testsuite/gdb.mi/mi-pthreads.exp @@ -30,7 +30,7 @@ proc check_mi_thread_command_set {} { set thread_list [get_mi_thread_list "in check_mi_thread_command_set"] mi_gdb_test "-thread-select" \ - {\^error,msg="-thread-select: USAGE: threadnum."} \ + {\^error,msg="-thread-select: USAGE: \[-l lanenum\] threadnum."} \ "check_mi_thread_command_set: -thread-select" mi_gdb_test "-thread-select 123456789" \ diff --git a/gdb/testsuite/gdb.rocm/lane-execution.cpp b/gdb/testsuite/gdb.rocm/lane-execution.cpp new file mode 100644 index 00000000000..42fbcb9e9eb --- /dev/null +++ b/gdb/testsuite/gdb.rocm/lane-execution.cpp @@ -0,0 +1,143 @@ +/* Copyright 2021-2024 Free Software Foundation, Inc. + Copyright (C) 2021-2025 Advanced Micro Devices, Inc. All rights reserved. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#define CHECK(cmd) \ + do \ + { \ + hipError_t error = cmd; \ + if (error != hipSuccess) \ + { \ + fprintf(stderr, "error: '%s'(%d) at %s:%d\n", \ + hipGetErrorString (error), error, \ + __FILE__, __LINE__); \ + exit (EXIT_FAILURE); \ + } \ + } while (0) + +struct test_struct +{ + int int_elem; + char char_elem; + int array_elem[32]; +}; + +__device__ const struct test_struct const_struct = { + 32, '2', + { + 32, 31, 30, 29, 28, 27, 26, 25, + 24, 23, 22, 21, 20, 19, 18, 17, + 16, 15, 14, 13, 12, 11, 10, 9, + 8, 7, 6, 5, 4, 3, 2, 1 + } +}; + +__device__ const int const_array[32] = { + 1, 1, 1, 1, 1, 5, 5, 7, + 2, 2, 2, 2, 2, 5, 5, 10, + 3, 3, 3, 3, 3, 5, 5, 2, + 4, 4, 4, 4, 4, 5, 5, 3 +}; + +__device__ int +bar (int val) +{ + return val; +} + +__device__ int +foo (int val) +{ + return bar (val); +} + +__device__ void +lane_pc_test (unsigned gid, const int *in, struct test_struct *out) +{ + int elem; + + /* This first divergence is here so GDB can check which lane is the + 'then' and which is the 'else' lane, based on which lanes become + active/inactive. This if/then/else block is stepped with "maint + set lane-divergence-support off". */ + if (gid % 2) /* if_0_cond */ + elem = const_array[gid] + 1; /* if_0_then */ + else + elem = const_array[gid] + 3; /* if_0_else */ + + out->int_elem = const_struct.int_elem; + out->char_elem = const_struct.char_elem; + + if (gid % 2) /* if_1_cond */ + { + elem = const_array[gid] + 1; /* if_1_then */ + elem = const_array[gid] + 2; /* if_1_then_2 */ + } /* if_1_then_3 */ + else + { + elem = const_array[gid] + 3; /* if_1_else */ + elem = const_array[gid] + 4; /* if_1_else_2 */ + } + /* if_1_end */ + + if (gid % 2) /* if_2_cond */ + elem = foo (const_array[gid]); /* if_2_then */ + else + elem = const_array[gid]; /* if_2_else */ + /* if_2_end */ + + /* This condition is always false. */ + if (gid == -1) /* if_3_cond */ + elem = const_array[gid] + 1; /* if_3_then */ + else + elem = const_array[gid] + 2; /* if_3_else */ + /* if_3_end */ + + atomicAdd (&out->int_elem, elem); + out->array_elem[gid] = elem; +} + +__global__ void +kernel (const int *in, struct test_struct *out) +{ + unsigned gid0 = blockIdx.x * blockDim.x + threadIdx.x; + lane_pc_test (gid0, in, out); +} + +int +main () +{ + dim3 grid_dim (1); + dim3 block_dim (32); + + struct test_struct *sOutBuff; + int *sInBuff; + CHECK (hipMalloc (&sOutBuff, sizeof (struct test_struct))); + CHECK (hipMalloc (&sInBuff, sizeof (int) + * (grid_dim.x * grid_dim.y * grid_dim.z + * block_dim.x + * block_dim.y + * block_dim.z))); + + hipLaunchKernelGGL (kernel, grid_dim, block_dim, 0, 0, sInBuff, sOutBuff); + + CHECK (hipDeviceSynchronize ()); + + return 0; +} diff --git a/gdb/testsuite/gdb.rocm/lane-execution.exp b/gdb/testsuite/gdb.rocm/lane-execution.exp new file mode 100644 index 00000000000..f4c030629cc --- /dev/null +++ b/gdb/testsuite/gdb.rocm/lane-execution.exp @@ -0,0 +1,392 @@ +# Copyright 2021-2024 Free Software Foundation, Inc. +# Copyright (C) 2021-2025 Advanced Micro Devices, Inc. All rights reserved. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This testcase exercises execution-related scenarios involving lanes, +# and some related lane debugging features. Mainly: +# +# - Test that GDB automatically steps over code regions where the +# current lane is inactive. +# +# - Test breakpoint hits with some lanes inactive. Also test that GDB +# evaluates the breakpoint condition on each lane. +# +# - Test that GDB warns when you select an inactive lane. +# +# - Test $_lane and the "lane" command. +# +# Note the tests below assume we don't have DW_AT_LLVM_lane_pc info, +# meaning GDB has no idea what is the logical PC a divergent lane is +# stopped at. All it knows is the current wave/physical PC, and that +# the lane is inactive. + +load_lib rocm.exp + +require allow_hipcc_tests + +standard_testfile .cpp + +if {[build_executable "failed to prepare" $testfile $srcfile {debug hip}]} { + return -1 +} + +# Usage: +# my_lineno [TEXT]... +# +# For each TEXT, find line number of the first line containing TEXT. +# The found line number is stored in a global called TEXT_line. + +proc my_lineno {args} { + foreach text $args { + global ${text}_line + set ${text}_line [gdb_get_line_number $text] + } +} + +my_lineno \ + "if_1_cond" "if_1_then" "if_1_else" "if_1_end" \ + "if_2_cond" "if_2_then" "if_2_else" "if_2_end" \ + "if_3_cond" "if_3_then" "if_3_else" "if_3_end" + +# Issue a "next" command, expecting to step at a source line with a +# LABEL comment. + +proc do_next {label} { + gdb_test "next" \ + "$label.*" \ + "next to \"$label\"" +} + +# Issue a "step" command, expecting to step at a source line with a +# LABEL comment. + +proc do_step {label} { + gdb_test "step" \ + "$label.*" \ + "step to \"$label\"" +} + +# Switch to lane LANE, expecting that GDB does not print a warning +# about the lane being inactive. LABEL is the expected text comment +# present in the current source line. MESSAGE is the test message. + +proc lane_cmd_active {lane label message} { + gdb_test_multiple "lane $lane" $message { + -re -wrap "warning.*" { + fail $gdb_test_name + + } + -re -wrap " $label .*" { + pass $gdb_test_name + } + } +} + +# Switch to lane LANE, expecting that GDB prints a warning about the +# lane being inactive. LABEL is the expected text comment present in +# the current source line. MESSAGE is the test message. + +proc lane_cmd_inactive {lane label message} { + gdb_test "lane $lane" \ + "warning: Current lane is inactive.* $label .*"\ + $message +} + +# Setup for testing. Restarts GDB, and extracts which lane is the +# "then" lane, and which lane is the "else" lane out of lanes 0-2. +# The results are written to the LANE_THEN_NAME and LANE_ELSE_NAME +# variables in the caller. Returns true on success, false otherwise. + +proc setup {lane_then_name lane_else_name} { + upvar $lane_then_name lane_then + upvar $lane_else_name lane_else + + clean_restart $::binfile + + if ![runto "lane_pc_test" allow-pending] { + return 0 + } + + gdb_test_no_output "set scheduler-locking on" + + gdb_test_no_output "maint set lane-divergence-support off" + do_step " if_0_else " + + set lane_then -1 + set lane_else -1 + gdb_test_multiple "info lanes 0-2" "" { + -re "($::decimal) (A|I)" { + set lane $expect_out(1,string) + set state $expect_out(2,string) + + if {$state == "A"} { + set lane_else $lane + } else { + set lane_then $lane + } + exp_continue + } + -wrap -re "" { + gdb_assert {$lane_else != -1 && $lane_then != -1} \ + $gdb_test_name + } + } + verbose -log "lane_else=$lane_else, lane_then=$lane_then" + + if {$lane_then == -1 || $lane_else == -1} { + return 0 + } + + do_step " if_0_then " + do_step "const_struct.int_elem" + gdb_test_no_output "maint set lane-divergence-support on" + + return 1 +} + +# If BRANCH is "then", switch to LANE_THEN lane. Otherwise, switch to +# the LANE_ELSE lane. + +proc switch_to_lane_branch {branch lane_then lane_else} { + if {$branch == "then"} { + gdb_test "lane $lane_then" \ + "Switching to thread $::decimal, lane $lane_then.*" \ + "switch to \"then\" lane" + } else { + gdb_test "lane $lane_else" \ + "Switching to thread $::decimal, lane $lane_else.*" \ + "switch to \"else\" lane" + } +} + +# Test stepping lanes with "step". BRANCH indicates which set of lane +# we expect to be active -- it's either "then" or "else". + +proc test_step {branch} { + with_test_prefix "step: $branch" { + if ![setup lane_then lane_else] { + return + } + + switch_to_lane_branch $branch $lane_then $lane_else + + do_step "const_struct.char_elem" + do_step " if_1_cond " + + if {$branch == "then"} { + do_step " if_1_then " + + # Check that when we're stopped at the "then", the "else" + # lane is inactive and GDB warns about it. + lane_cmd_inactive $lane_else \ + "if_1_then" \ + "else lane inactive at if_1_then" + + # Switching back to the "then" lane, GDB should not warn. + lane_cmd_active $lane_then \ + "if_1_then" \ + "then lane active at if_1_then" + + do_step " if_1_then_2 " + do_step " if_1_then_3 " + + do_step " if_2_cond " + do_step " if_2_then " + + # The next step is interesting, because it steps into a + # function, while the else lanes are divergent. + + # Step, and check that lane LANE_ELSE is divergent. + proc do_step_check_div {label} { + upvar lane_else lane_else + + do_step $label + gdb_test "info lanes $lane_else" \ + "$lane_else \+I.*"\ + "lane else is inactive at '$label'" + } + + do_step_check_div "foo.*return bar \\(val\\);" + + gdb_test "bt" \ + [multi_line \ + "bt" \ + "#0 foo \\(val=.*\\) at .*/${::srcfile}:${::decimal}" \ + "#1 $::hex in lane_pc_test \\(.*\\) at .*:$::if_2_then_line" \ + "#2 $::hex in kernel .*"] \ + "bt on then lane" + + do_step_check_div "bar \\(val=.*\\) at " + } else { + do_step " if_1_else " + + # Check that when we're stopped at the "else", the "then" + # lane is inactive. + lane_cmd_inactive $lane_then \ + "if_1_else" \ + "then lane inactive at if_1_else" + + # Switching back to the then lane, GDB should not warn. + lane_cmd_active $lane_else \ + "if_1_else" \ + "else lane active at if_1_else" + + do_step " if_1_else_2 " + do_step " if_2_cond " + do_step " if_2_else " + } + + # The last "if" condition is always false for all lanes. + do_step " if_3_cond " + do_step " if_3_else " + do_step "atomicAdd " + } +} + +# Test issuing the "step" command while the current lane is divergent. + +proc test_step_divergent {branch} { + with_test_prefix "step divergent: $branch" { + if ![setup lane_then lane_else] { + return + } + + switch_to_lane_branch $branch $lane_then $lane_else + + do_step "const_struct.char_elem" + do_step " if_1_cond " + + if {$branch == "then"} { + do_step " if_1_then " + lane_cmd_inactive $lane_else \ + "if_1_then" \ + "else lane inactive at if_1_then" + do_step " if_2_cond " + } else { + do_step " if_1_else " + lane_cmd_inactive $lane_then \ + "if_1_else" \ + "then lane inactive at if_1_else" + do_step " if_1_then " + } + } +} + +# Test stepping lanes with "next". BRANCH is either "then" or "else". + +proc test_next {branch} { + with_test_prefix "next: $branch" { + if ![setup lane_then lane_else] { + return + } + + switch_to_lane_branch $branch $lane_then $lane_else + + do_next "const_struct.char_elem" + do_next " if_1_cond " + + if {$branch == "then"} { + do_next " if_1_then " + + # Check that when we're stopped at the "then", the "else" + # lane is inactive. + lane_cmd_inactive $lane_else \ + "if_1_then" \ + "else lane inactive at if_2_cond" + + lane_cmd_active $lane_then \ + "if_1_then" \ + "then lane active at if_1_then" + + do_next " if_1_then_2 " + do_next " if_1_then_3 " + do_next " if_2_cond " + do_next " if_2_then " + } else { + do_next " if_1_else " + + # Check that when we're stopped at the "else", the "then" + # lane is inactive. + lane_cmd_inactive $lane_then \ + "if_1_else" \ + "then lane inactive at if_1_else" + lane_cmd_active $lane_else \ + "if_1_else" \ + "else lane active at if_1_else" + + do_next " if_1_else_2 " + do_next " if_2_cond " + do_next " if_2_else " + } + + # The last "if" condition is always false for all lanes. + do_next " if_3_cond " + do_next " if_3_else " + do_next "atomicAdd " + } +} + +# Test conditional breakpoints with conditions that only eval true for +# some lane or lanes. + +proc_with_prefix test_breakpoint_lane_cond {} { + if ![setup lane_then lane_else] { + return + } + + # Set a breakpoint with a condition that only evals true on a + # given lane. + gdb_test "break $::if_1_else_line if \$_lane == $lane_else" ".*" + + # And a breakpoint with a breakpoint condition that we know should + # eval false. + gdb_test "break $::if_1_then_line if \$_lane == $lane_else" ".*" + + # There are multiple lanes active, but GDB should only mention the + # lanes where the condition was true. + gdb_test "continue" \ + "hit Breakpoint $::decimal, with lane $lane_else, lane_pc_test .*" \ + "continue to '\$lane_else' breakpoint" + + # Make sure GDB doesn't present a stop for lane 0 if the condition + # is to stop for all lanes but 0. GDB's breakpoint condition + # evaluation code used to have a left shift overflow bug (doing '1 + # << lane' with lane > 32) that would result in showing a stop for + # lanes that evaluated false. + gdb_test "break $::if_2_then_line if \$_lane > 0" \ + "Breakpoint $::decimal at .*" + + gdb_test_multiple "continue" "continue to '\$_lane > 0' breakpoint" { + -wrap -re "hit Breakpoint $::decimal, with lane(s)? \\\[0.*" { + fail $gdb_test_name + } + -wrap -re "hit Breakpoint $::decimal, with lanes \\\[$::decimal.*" { + pass $gdb_test_name + } + } +} + +with_rocm_gpu_lock { + test_step "then" + test_step "else" + + test_step_divergent "then" + test_step_divergent "else" + + test_next "then" + test_next "else" + + test_breakpoint_lane_cond +} diff --git a/gdb/testsuite/gdb.rocm/lane-info.cpp b/gdb/testsuite/gdb.rocm/lane-info.cpp new file mode 100644 index 00000000000..a7a3d541913 --- /dev/null +++ b/gdb/testsuite/gdb.rocm/lane-info.cpp @@ -0,0 +1,80 @@ +/* Copyright 2021-2024 Free Software Foundation, Inc. + Copyright (C) 2021-2025 Advanced Micro Devices, Inc. All rights reserved. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include +#include + +#define CHECK(cmd) \ + { \ + hipError_t error = cmd; \ + if (error != hipSuccess) \ + { \ + fprintf (stderr, "error: '%s'(%d) at %s:%d\n", \ + hipGetErrorString (error), error, __FILE__, __LINE__); \ + exit (EXIT_FAILURE); \ + } \ + } + +/* The kernel never returns, via this sleep, so that the .exp file can + test background execution (cont&). */ + +__device__ static void +sleep_forever () +{ + while (1) + __builtin_amdgcn_s_sleep (1); +} + +__device__ static void +foo () +{ +} + +__device__ static void +bar () +{ + sleep_forever (); +} + +__global__ void +kernel () +{ + int tid_x = blockIdx.x * blockDim.x + threadIdx.x; + + if (tid_x % 2) + foo (); + else + bar (); +} + +int +main () +{ + alarm (30); + + /* If the wavefront size is 64 lanes, then this results in 2 waves, 1 + with 64 lanes used, and 1 with 5 lanes used. If the wavefront + size is 32 lanes, then this results in 3 waves, 2 with 32 lanes + used each, and 1 with 5 lanes used. */ + hipLaunchKernelGGL (kernel, dim3 (1), dim3 (64 + 5), + 0 /*dynamicShared*/, 0 /*stream*/); + + CHECK (hipDeviceSynchronize ()); + + return 0; +} diff --git a/gdb/testsuite/gdb.rocm/lane-info.exp b/gdb/testsuite/gdb.rocm/lane-info.exp new file mode 100644 index 00000000000..bbbea30104b --- /dev/null +++ b/gdb/testsuite/gdb.rocm/lane-info.exp @@ -0,0 +1,534 @@ +# Copyright 2021-2024 Free Software Foundation, Inc. +# Copyright (C) 2021-2025 Advanced Micro Devices, Inc. All rights reserved. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Misc lane info debugging tests: +# +# Test the $_lane_count convenience variable, checking that it takes +# into account unused lanes. +# +# Test "info lanes" and its different arguments, checking that it +# takes into account unused lanes, running lanes, active/inactive +# lanes, etc. +# +# Test "lane apply" and its different arguments, likewise taking into +# account unused lanes, etc. + +load_lib rocm.exp + +require allow_hipcc_tests + +standard_testfile .cpp + +if {[build_executable "failed to prepare" $testfile $srcfile {debug hip}]} { + return -1 +} + +# Consume the prompt and issue a fail with message MESSAGE. + +proc prompt_and_fail {message} { + gdb_test_multiple "" $message { + -re "$::gdb_prompt " { + fail $gdb_test_name + } + } +} + +# Run an "info lanes"-like command specified in CMD, and check that +# the right set of lanes is listed. LANE_STATE_NAME is the name of +# the array variable that contains the lane states, as extracted with +# "info lanes -all". VALID_STATES is a list containing the set of +# lane states the "info lanes" command variant lists. + +proc info_lanes {cmd lane_state_name valid_states} { + set any "\[^\r\n\]*" + upvar $lane_state_name lane_state + + array unset lane_state2 + for {set i 0} {$i < [array size lane_state]} {incr i} { + set lane_state2($i) "" + } + + gdb_test_multiple "$cmd" "" { + -re "($::decimal) +(.) +AMDGPU Lane ${any}\r\n" { + set lane $expect_out(1,string) + set lane_state2($lane) $expect_out(2,string) + exp_continue + } + -re "$::gdb_prompt " { + set ok 1 + for {set i 0} {$i < [array size lane_state]} {incr i} { + if {[lsearch $valid_states $lane_state($i)] != -1} { + set should_see 1 + } else { + set should_see 0 + } + + set lane_seen [expr {$lane_state2($i) != ""}] + + if {$should_see != $lane_seen} { + verbose -log \ + "lane $i, $should_see, $lane_seen, $lane_state($i), $lane_state2($i)" + set ok 0 + } elseif {$lane_seen && $lane_state($i) != $lane_state2($i)} { + verbose -log \ + "lane $i, $should_see, $lane_seen, $lane_state($i), $lane_state2($i)" + set ok 0 + } + } + gdb_assert {$ok} $gdb_test_name + } + } +} + +# Run a "lane apply"-like command specified in CMD, and check that the +# right set of lanes is iterated. LANE_STATE_NAME is the name of the +# array variable that contains the lane states, as extracted with +# "info lanes -all". VALID_STATES is a list containing the set of +# lane states the "lane apply" command variant applies the command to. + +proc lane_apply {cmd lane_state_name valid_states} { + set any "\[^\r\n\]*" + upvar $lane_state_name lane_state + + array unset lane_seen + for {set i 0} {$i < [array size lane_state]} {incr i} { + set lane_seen($i) 0 + } + + gdb_test_multiple "$cmd print \$_lane" "" { + -re "Lane ($::decimal) \\\($any\\\):\r\n.$::decimal = ($::decimal)" { + set lane1 $expect_out(1,string) + set lane2 $expect_out(1,string) + + if {$lane1 != $lane2} { + prompt_and_fail "$gdb_test_name (inconsistent lane)" + } else { + set lane_seen($lane1) 1 + exp_continue + } + } + -re "$::gdb_prompt " { + set ok 1 + for {set i 0} {$i < [array size lane_state]} {incr i} { + if {[lsearch $valid_states $lane_state($i)] != -1} { + set should_see 1 + } else { + set should_see 0 + } + + if {$should_see != $lane_seen($i)} { + verbose -log \ + "lane $i, $should_see, $lane_seen($i), $lane_state($i)" + set ok 0 + } + } + gdb_assert {$ok} $gdb_test_name + } + } +} + +proc_with_prefix test {} { + clean_restart $::binfile + + if ![runto "bar" allow-pending message] { + return + } + + with_test_prefix "bad" { + gdb_test "info lanes -1" "Unrecognized option at: -1" + gdb_test "info lanes 64" "No lanes match '64'\." + + gdb_test "lane -1" "Lane -1 does not exist on this thread\." + gdb_test "lane 64" "Lane 64 does not exist on this thread\." + + gdb_test "lane apply -1 print 1" "negative value" + } + + set any "\[^\r\n\]*" + + # Which wave has unused lanes. + set partial_wave 0 + + set num_used_lanes 5 + + with_test_prefix "\$_lane_count" { + # The first and last waves. + set first_wave 0 + set last_wave 0 + + set saw_partial 0 + set saw_full 0 + set saw_cpu 0 + gdb_test_multiple "thread apply all -ascending print \$_lane_count" "" { + -re "Thread ($::decimal) \\\((AMDGPU Wave|Thread) $any\r\n\\$${::decimal} = ($::decimal)\r\n" { + set thread_num $expect_out(1,string) + set target_id $expect_out(2,string) + set lane_count $expect_out(3,string) + verbose -log "thread_num=$thread_num, target_id=\"$target_id\", lane_count=$lane_count" + + if {$target_id == "AMDGPU Wave"} { + # GPU. + if {$first_wave == 0} { + set first_wave $thread_num + } + set last_wave $thread_num + + if {$lane_count == $num_used_lanes} { + incr saw_partial + set partial_wave $thread_num + } elseif {$lane_count == 32 || $lane_count == 64} { + incr saw_full + } else { + prompt_and_fail $gdb_test_name + return + } + } elseif {$target_id == "Thread"} { + # CPU. + if {$lane_count != 0} { + prompt_and_fail $gdb_test_name + return + } + incr saw_cpu + } else { + error "unexpected" + } + exp_continue + } + -re "$::gdb_prompt $" { + gdb_assert \ + {$partial_wave != 0 \ + && $saw_partial == 1 \ + && $saw_full > 0 \ + && $saw_cpu > 0} \ + $gdb_test_name + } + } + } + + # Test "info lanes". + with_test_prefix "info lanes" { + # Show active and inactive lanes, but not unused lanes. + # Validate the info in the "target id" column. + with_test_prefix "default, target id" { + set work_item_pos 0 + for {set t $first_wave} {$t <= $last_wave} {incr t} { + with_test_prefix "thread $t" { + gdb_test "thread $t" "Switching .*" + + set lane 0 + gdb_test_multiple "info lanes" "" { + -re "($::decimal) +(?:A|I) +AMDGPU Lane ${::decimal}:${::decimal}:${::decimal}:${::decimal}/($::decimal) \\\(0,0,0\\\)\\\[($::decimal),0,0\\\] +" { + + set lane_m $expect_out(1,string) + set lane2_m $expect_out(2,string) + set wi_pos_m $expect_out(3,string) + + verbose -log "lane: lane_m=$lane_m, lane2_m=$lane2_m, wi_pos_m=$wi_pos_m" + verbose -log "lane: lane=$lane, work_item_pos=$work_item_pos" + + if {$lane != $lane_m + || $lane != $lane2_m + || $work_item_pos != $wi_pos_m} { + prompt_and_fail $gdb_test_name + } else { + incr lane + incr work_item_pos + exp_continue + } + } + -re "$::gdb_prompt" { + verbose -log "prompt: lane=$lane, work_item_pos=$work_item_pos" + + if {$t == $partial_wave} { + gdb_assert {$lane == $num_used_lanes} \ + $gdb_test_name + } else { + gdb_assert {$lane == 32 || $lane == 64} \ + $gdb_test_name + } + } + } + } + } + } + + # The state of each lane, per "info lanes -all". + array unset lane_state + + # Show all lanes including unused lanes & collect lane states + # for subsequent tests. + with_test_prefix "all" { + gdb_test "thread $partial_wave" "Switching .*" \ + "switch to partial wave" + + # The last seen lane. + set lane -1 + # The next lane we should see. + set next_lane 0 + + gdb_test_multiple "info lanes -all" "" { + -re "($::decimal) +(.) +AMDGPU Lane " { + set lane $expect_out(1,string) + set state $expect_out(2,string) + + verbose -log "got lane=$lane, $state" + + if {$lane != $next_lane} { + prompt_and_fail "$gdb_test_name (unexpected lane)" + } elseif {$lane >= 64} { + prompt_and_fail "$gdb_test_name (lane too high)" + } elseif {$state != "A" && $state != "I" && $state != "U"} { + prompt_and_fail "$gdb_test_name (unexpected state)" + } else { + incr next_lane + set lane_state($lane) $state + + # Expect the "Frame" column, which depends on + # state. + set f_ok 0 + if {$state == "A"} { + gdb_test_multiple "" "$gdb_test_name (frame column)" { + -re "bar \\\(\\\)${any}\r\n" { + set f_ok 1 + } + } + } elseif {$state == "I"} { + gdb_test_multiple "" "$gdb_test_name (frame column)" { + -re "\\\(inactive\\\)\r\n" { + set f_ok 1 + } + } + } elseif {$state == "U"} { + gdb_test_multiple "" "$gdb_test_name (frame column)" { + -re "\\\(unused\\\)\r\n" { + set f_ok 1 + } + } + } else { + error "unexpected state" + } + if {$f_ok} { + exp_continue + } + } + } + -re "$::gdb_prompt" { + verbose -log "lane=$lane" + + set ok 1 + + if {$lane != 31 && $lane != 63} { + set ok 0 + } else { + set used_count 0 + set unused_count 0 + for {set i 0} {$i < $lane} {incr i} { + if {$lane_state($i) == "A" + || $lane_state($i) == "I"} { + incr used_count + if {$i >= $num_used_lanes} { + set ok 0 + } + } elseif {$lane_state($i) == "U"} { + incr unused_count + if {$i < $num_used_lanes} { + set ok 0 + } + } else { + verbose -log "unexpected state" + set ok 0 + } + } + + if {$used_count != $num_used_lanes} { + set ok 0 + } + } + + gdb_assert {$ok} $gdb_test_name + } + } + } + + # The following tests all work with the wave that has unused + # lanes. + gdb_test "thread $partial_wave" "Switching .*" "switch to partial wave" + + with_test_prefix "state filter" { + # Test "info lanes" without options again, but this time check + # whether we only print active lanes, and whether we print + # them all, compared to the active lanes shown by "info lanes + # -all". + info_lanes "info lanes" lane_state {"A" "I"} + + # Check that -active and -inactive filter lanes correctly. + info_lanes "info lanes -active" lane_state {"A"} + info_lanes "info lanes -inactive" lane_state {"I"} + } + + # Test "info lanes" when the wave is running. + with_test_prefix "running" { + delete_breakpoints + gdb_test "cont&" "Continuing\." + + set count 0 + gdb_test_multiple "info lanes" "" { + -re "($::decimal) +R +AMDGPU Lane ${any}\\\(running\\\)\r\n" { + set lane $expect_out(1,string) + if {$lane >= $num_used_lanes} { + prompt_and_fail "wrong state" + } else { + incr count + exp_continue + } + } + -re "($::decimal) +${any}\r\n" { + prompt_and_fail "$gdb_test_name (unexpected state)" + } + -re "$::gdb_prompt " { + verbose -log "count=$count" + gdb_assert {$count == $num_used_lanes} $gdb_test_name + } + } + + set r_count 0 + set u_count 0 + gdb_test_multiple "info lanes -all" "" { + -re "($::decimal) +(.) +AMDGPU Lane " { + set lane $expect_out(1,string) + set state $expect_out(2,string) + if {$state != "R" && $state != "U"} { + prompt_and_fail "$gdb_test_name (unexpected state)" + } elseif {$state == "R" && $lane >= $num_used_lanes} { + prompt_and_fail "$gdb_test_name (wrong state)" + } elseif {$state == "U" && $lane < $num_used_lanes} { + prompt_and_fail "$gdb_test_name (wrong state)" + } else { + if {$state == "R"} { + incr r_count + } else { + incr u_count + } + + set f_ok 0 + gdb_test_multiple "" "$gdb_test_name (frame column)" { + -re "\\\(running\\\)\r\n" { + set f_ok 1 + } + } + if {$f_ok} { + exp_continue + } + } + } + -re "$::gdb_prompt " { + verbose -log "r_count=$r_count, u_count=$u_count" + gdb_assert \ + {$r_count == $num_used_lanes \ + && ($u_count == (32 - $num_used_lanes) \ + || $u_count == (64 - $num_used_lanes))} \ + $gdb_test_name + } + } + + # Interrupt threads/waves, to prepare for following tests. + with_test_prefix "stop waves" { + gdb_test_multiple "interrupt" "" { + -re "$::gdb_prompt " { + gdb_test_multiple "" $gdb_test_name { + -re "received signal SIGINT" { + pass $gdb_test_name + } + } + } + } + gdb_test "thread $partial_wave" "Switching .*" \ + "switch to partial wave" + } + } + } + + with_test_prefix "lane apply" { + + with_test_prefix "state filter" { + lane_apply "lane apply all" lane_state {"A" "I"} + lane_apply "lane apply all -all" lane_state {"A" "I" "U"} + lane_apply "lane apply all -active" lane_state {"A"} + lane_apply "lane apply all -inactive" lane_state {"I"} + } + + # Find an active lane and an inactive lane. + set active_lane -1 + set inactive_lane -1 + for {set i 0} {$i < [array size lane_state]} {incr i} { + if {$lane_state($i) == "A"} { + set active_lane $i + } elseif {$lane_state($i) == "I"} { + set inactive_lane $i + } + } + + # Test that "-active" when the lane list does not include any + # active lane ends up not printing anything. Same for + # "-inactive". + with_test_prefix "no match" { + gdb_test_no_output "lane apply $active_lane -inactive print 1" + gdb_test_no_output "lane apply $inactive_lane -active print 1" + + # Double check that -active really prints the active lane, + # and same for -inactive. + gdb_test "lane apply $active_lane -active print \$_lane" \ + "Lane $active_lane .*= $active_lane" + gdb_test "lane apply $inactive_lane -inactive print \$_lane" \ + "Lane $inactive_lane .*= $inactive_lane" + + } + } + + # Check that "thread find" hits lane target Ids too. + with_test_prefix "thread find" { + + # A regexp that hits all work-items. We should see 64 + 5 + # hits, which is the total number of used lanes. + set count 0 + gdb_test_multiple "thread find AMDGPU Lane" "" { + -re "Thread $::decimal, lane $::decimal has target id 'AMDGPU Lane $any'" { + incr count + exp_continue + } + -wrap -re "" { + gdb_assert {$count == 64 + 5} "thread find hit all used lanes" + } + } + + # A regexp that hits a specific work-item. We should see only + # 1 hit. + set count 0 + gdb_test_multiple "thread find AMDGPU Lane.*\\\[0,0,0\\\]" "" { + -re "Thread $::decimal, lane $::decimal has target id 'AMDGPU Lane $any'" { + incr count + exp_continue + } + -wrap -re "" { + gdb_assert {$count == 1} "thread find hit expected lane" + } + } + } +} + +with_rocm_gpu_lock { + test +} diff --git a/gdb/testsuite/gdb.rocm/meeting.cpp b/gdb/testsuite/gdb.rocm/meeting.cpp new file mode 100644 index 00000000000..893c505b7f4 --- /dev/null +++ b/gdb/testsuite/gdb.rocm/meeting.cpp @@ -0,0 +1,59 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2025 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include +#include + +struct S +{ + int x; + int y; + int z; +}; + +__device__ S my_s; + +__device__ void +foo (int arg1, int arg2, int x) +{ +} + +__device__ void +bar (int arg1, int arg2, int x) +{ +} + +__global__ void +my_kernel () +{ + while (1) + { + if (threadIdx.x & 1) + foo (1, 2, threadIdx.x); + else + bar (3, 4, threadIdx.x); + } +} + +int +main () +{ + my_kernel<<>> (); + if (hipDeviceSynchronize () != hipSuccess) + return 1; + return 0; +} diff --git a/gdb/testsuite/gdb.rocm/meeting.exp b/gdb/testsuite/gdb.rocm/meeting.exp new file mode 100644 index 00000000000..33b7832dfae --- /dev/null +++ b/gdb/testsuite/gdb.rocm/meeting.exp @@ -0,0 +1,45 @@ +# Copyright 2025 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# A simple AMD GPU debugging smoke test. Run to a breakpoint in device code, +# then continue until the end of the program. + +load_lib rocm.exp + +require allow_hipcc_tests + +standard_testfile .cpp + +if {[build_executable "failed to prepare" $testfile $srcfile {debug hip}]} { + return +} + +proc do_test {} { + clean_restart $::binfile + + with_rocm_gpu_lock { + if ![runto_main] { + return + } + + gdb_test "with breakpoint pending on -- break my_kernel" \ + "Breakpoint $::decimal \\(my_kernel\\) pending." + + gdb_test "continue" \ + "Thread $::decimal \"meeting\" hit Breakpoint $::decimal, .*my_kernel .*" + } +} + +do_test diff --git a/gdb/testsuite/gdb.rocm/mi-lanes.exp b/gdb/testsuite/gdb.rocm/mi-lanes.exp new file mode 100644 index 00000000000..88eee8e80f9 --- /dev/null +++ b/gdb/testsuite/gdb.rocm/mi-lanes.exp @@ -0,0 +1,266 @@ +# Copyright 2022-2024 Free Software Foundation, Inc. +# Copyright (C) 2022-2025 Advanced Micro Devices, Inc. All rights reserved. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This testcase exercises MI support for lane debugging features. +# +load_lib rocm.exp +load_lib mi-support.exp +set MIFLAGS "-i=mi" + +require allow_hipcc_tests + +standard_testfile lane-execution.cpp + +if {[build_executable "failed to prepare" $testfile $srcfile {debug hip}]} { + return -1 +} + +# Usage: +# my_lineno [TEXT]... +# +# For each TEXT, find line number of the first line containing TEXT. +# The found line number is stored in a global called TEXT_line. + +proc my_lineno {args} { + foreach text $args { + global ${text}_line + set ${text}_line [gdb_get_line_number $text] + } +} + +my_lineno \ + "if_1_cond" "if_1_then" "if_1_else" "if_1_end" \ + "if_2_cond" "if_2_then" "if_2_else" "if_2_end" \ + "if_3_cond" "if_3_then" "if_3_else" "if_3_end" + +# The GPU thread's id. +set gpu_thr "" + +# Test MI lanes support. + +proc_with_prefix test_mi_lanes {} { + global hex decimal mi_gdb_prompt + global gpu_thr + + set any "\[^\n\]*" + + set lane_re_A "\\\{id=\"$decimal\",state=\"A\",target-id=\"$any\",frame=\\\{level=\"$decimal\",addr=\"$hex\",func=\"$any\",$any\\\}" + set lane_re_I "\\\{id=\"$decimal\",state=\"I\",target-id=\"$any\"\\\}" + set lane_re "($lane_re_A|$lane_re_I)" + + mi_clean_restart $::binfile + + with_test_prefix "no execution" { + set no_thr_selected_err "\\^error,msg=\"No thread selected\\.\"" + mi_gdb_test "-lane-info" $no_thr_selected_err + mi_gdb_test "-lane-info 0" $no_thr_selected_err + } + + mi_runto "lane_pc_test" -pending + + with_test_prefix "breakpoint stop" { + mi_create_breakpoint "$::if_1_else_line" "break-insert lineno" \ + -number 2 -func "lane_pc_test\\(.*\\)" -file ".*lane-execution.cpp" \ + -line $::if_1_else_line + + gdb_test_multiple \ + "-data-evaluate-expression \$_thread" \ + "get gpu thread" \ + -prompt "$mi_gdb_prompt" { + + -re "\\^done,value=\"($decimal)\"\r\n${mi_gdb_prompt}" { + set gpu_thr $expect_out(1,string) + pass $gdb_test_name + } + } + + if {$gpu_thr == ""} { + untested "couldn't retrieve gpu thread" + return + } + + mi_send_resuming_command "exec-continue" "continuing execution" + + mi_expect_breakpoint_stop "breakpoint stop, hit-lanes" \ + -hit-lanes "0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30" \ + -func "lane_pc_test" \ + -file "lane-execution.cpp" \ + -thread-id $gpu_thr \ + -lane-id 0 + } + + # Check that end-stepping-range stops also include the "lane-id" + # attribute. + with_test_prefix "step" { + + # Switch to a lane other than 0 to check that GDB isn't always + # printing "lane-id" as 0. + mi_gdb_test "-thread-select -l 2 $gpu_thr" \ + ".*\\^done,new-thread-id=\"$gpu_thr\",lane-id=\"2\",frame={.*}" + + mi_send_resuming_command "exec-next" "next" + + mi_expect_step_stop "step stop" \ + -func "lane_pc_test" \ + -file "lane-execution.cpp" \ + -thread-id $gpu_thr \ + -lane-id 2 + } + + with_test_prefix "lane-info" { + mi_gdb_test "-lane-info -1" "\\^error,msg=\"negative value\"" + mi_gdb_test "-lane-info 0" "\\^done,lanes=\\\[$lane_re_A\\\]" + mi_gdb_test "-lane-info 1" "\\^done,lanes=\\\[$lane_re_I\\\]" + + mi_gdb_test "-lane-info 0-1" "\\^done,lanes=\\\[$lane_re\\\]" + + mi_gdb_test "-lane-info 0-1 4" "\\^error,msg=\"Invalid MI command\"" + mi_gdb_test "-lane-info \"0-1 4\"" "\\^done,lanes=\\\[$lane_re,$lane_re,$lane_re\\\]" + + mi_gdb_test "-lane-info" "\\^done,lanes=\\\[($lane_re)+\\\]" + } + + with_test_prefix "--lane" { + mi_gdb_test "-data-evaluate-expression \$_lane" \ + "\\^done,value=\"2\"" \ + "-data-evaluate-expression without --lane" + + mi_gdb_test "-data-evaluate-expression --lane 1 \$_lane" \ + "\\^done,value=\"1\"" \ + "-data-evaluate-expression with --lane" + + mi_gdb_test "-data-evaluate-expression --thread $gpu_thr --lane 4 \$_lane" \ + "\\^done,value=\"4\"" \ + "-data-evaluate-expression with --thread --lane" + } + + with_test_prefix "-thread-select -l" { + mi_gdb_test "-thread-select -l 4" \ + "\\^error,msg=\"-thread-select: USAGE: .*" \ + "missing thread" + + mi_gdb_test "-thread-select -l 5 $gpu_thr" \ + ".*\\^done,new-thread-id=\"$gpu_thr\",lane-id=\"5\",frame={.*}" \ + "with thread" + } + + with_test_prefix "=thread-selected" { + mi_gdb_test "thread 1" \ + ".*=thread-selected,id=\"1\",frame={.*}\r\n\\^done" \ + "thread cpu" + + mi_gdb_test "thread $gpu_thr" \ + ".*=thread-selected,id=\"$gpu_thr\",lane-id=\"5\",frame={.*}\r\n\\^done" \ + "thread gpu" + + mi_gdb_test "lane 5" \ + ".*=thread-selected,id=\"$gpu_thr\",lane-id=\"5\",frame={.*}\r\n\\^done" \ + "lane cmd" + } +} + +# Test varobjs. + +proc_with_prefix test_mi_varobjs {} { + global hex mi_gdb_prompt + global gpu_thr + + # Check that we're using a compiler that can emit debug info for + # locals. + gdb_test_multiple \ + "-data-evaluate-expression gid" \ + "probe for debug info" \ + -prompt "$mi_gdb_prompt" { + + -re "\\^done,value=\"5\"\r\n${mi_gdb_prompt}" { + pass $gdb_test_name + } + -re "\\^done,value=\"\"\r\n${mi_gdb_prompt}" { + pass "$gdb_test_name" + untested "MI varobjs, no debug info" + return + } + } + + mi_gdb_test "-var-create var * gid" \ + "\\^done,name=\"var\",numchild=\"0\",value=\"5\",type=\"unsigned int\",thread-id=\"$gpu_thr\",lane-id=\"5\",has_more=\"0\"" + + mi_gdb_test "-var-evaluate-expression var" \ + "\\^done,value=\"5\"" "eval varobj" + + mi_gdb_test "-data-evaluate-expression --thread $gpu_thr --lane 5 gid=555" \ + "=memory-changed,thread-id=\"$gpu_thr\",lane-id=\"5\",addr=\"private_lane#$hex\",len=\"0x4\"\r\n\\^done,value=\"555\"" \ + "frob value" + + # Test updating the varobj while a lane other than 3 is + # selected. + with_test_prefix "another lane" { + mi_gdb_test "-thread-select -l 0 $gpu_thr" \ + ".*\\^done,new-thread-id=\"$gpu_thr\",lane-id=\"0\",frame={.*}" \ + "-thread-select another lane" + + # The value of "gid" for the current lane should not be + # "555". This ensures that GDB correctly changes lane to + # update the varobj. + mi_gdb_test "-data-evaluate-expression gid" \ + "\\^done,value=\"0\"" \ + "value for current lane" + + mi_gdb_test "-var-update var" \ + "\\^done,changelist=\\\[\\{name=\"var\",in_scope=\"true\",type_changed=\"false\",has_more=\"0\"\\}\\\]" + + mi_gdb_test "524-var-evaluate-expression var" \ + "524\\^done,value=\"555\"" + + # Check GDB did not lose the selected thread/lane. + mi_gdb_test "-data-evaluate-expression \$_thread" \ + "\\^done,value=\"$gpu_thr\"" + mi_gdb_test "-data-evaluate-expression \$_lane" \ + "\\^done,value=\"0\"" + } + + # Test updating the varobj while a CPU thread is selected. + with_test_prefix "cpu thread" { + mi_gdb_test "-data-evaluate-expression --thread $gpu_thr --lane 5 gid=666" \ + "=memory-changed,thread-id=\"$gpu_thr\",lane-id=\"5\",addr=\"private_lane#$hex\",len=\"0x4\"\r\n\\^done,value=\"666\"" \ + "frob value" + + mi_gdb_test "-thread-select 1" \ + ".*\\^done,new-thread-id=\"1\",frame={.*}" \ + "-thread-select host thread" + + mi_gdb_test "-var-update var" \ + "\\^done,changelist=\\\[\\{name=\"var\",in_scope=\"true\",type_changed=\"false\",has_more=\"0\"\\}\\\]" + + mi_gdb_test "-var-evaluate-expression var" \ + "\\^done,value=\"666\"" + + # Check that GDB did not lose the selected thread/lane. + mi_gdb_test "-data-evaluate-expression \$_thread" \ + "\\^done,value=\"1\"" + mi_gdb_test "-data-evaluate-expression \$_lane" \ + "\\^done,value=\"0\"" + } + + mi_gdb_test "-data-evaluate-expression --thread $gpu_thr --lane 5 gid=5" \ + "=memory-changed,thread-id=\"$gpu_thr\",lane-id=\"5\",addr=\"private_lane#$hex\",len=\"0x4\"\r\n\\^done,value=\"5\"" \ + "restore value" +} + +with_rocm_gpu_lock { + test_mi_lanes + test_mi_varobjs +} diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp index 206971cdb70..8d83a73b998 100644 --- a/gdb/testsuite/lib/mi-support.exp +++ b/gdb/testsuite/lib/mi-support.exp @@ -1291,10 +1291,12 @@ proc mi_expect_stop { reason func args file line extra test } { set a $after_reason - verbose -log "mi_expect_stop: expecting: \\*stopped,${ebn}${r}${a}${bn}frame=\{addr=\"$hex\",func=\"$func\",args=$args,(?:file=\"$any$file\",fullname=\"${fullname_syntax}$file\",line=\"$line\",arch=\"$any\"|from=\"$file\")\}$after_stopped,thread-id=\"$decimal\",stopped-threads=$any\r\n($thread_selected_re|$breakpoint_re)*$prompt_re" + set hit_lanes "(?:hit-lanes=\"$any\",)?" + set lane_id "(?:lane-id=\"$any\",)?" + verbose -log "mi_expect_stop: expecting: \\*stopped,${ebn}${r}${a}${bn}${hit_lanes}frame=\{addr=\"$hex\",func=\"$func\",args=$args,(?:file=\"$any$file\",fullname=\"${fullname_syntax}$file\",line=\"$line\",arch=\"$any\"|from=\"$file\")\}$after_stopped,thread-id=\"$decimal\",${lane_id}stopped-threads=$any\r\n($thread_selected_re|$breakpoint_re)*$prompt_re" gdb_expect { - -re "\\*stopped,${ebn}${r}${a}${bn}frame=\{addr=\"$hex\",func=\"$func\",args=$args,(?:file=\"$any$file\",fullname=\"${fullname_syntax}$file\",line=\"($line)\",arch=\"$any\"|from=\"$file\")\}$after_stopped,thread-id=\"$decimal\",stopped-threads=$any\r\n($thread_selected_re|$breakpoint_re)*$prompt_re" { + -re "\\*stopped,${ebn}${r}${a}${bn}${hit_lanes}frame=\{addr=\"$hex\",func=\"$func\",args=$args,(?:file=\"$any$file\",fullname=\"${fullname_syntax}$file\",line=\"($line)\",arch=\"$any\"|from=\"$file\")\}$after_stopped,thread-id=\"$decimal\",${lane_id}stopped-threads=$any\r\n($thread_selected_re|$breakpoint_re)*$prompt_re" { pass "$test" if {[array names expect_out "2,string"] != ""} { return $expect_out(2,string) @@ -1302,7 +1304,7 @@ proc mi_expect_stop { reason func args file line extra test } { # No debug info available but $file does match. return 0 } - -re "\\*stopped,${ebn}${r}${a}${bn}frame=\{addr=\"$hex\",func=\"$any\",args=\[\\\[\{\]$any\[\\\]\}\],file=\"$any\",fullname=\"${fullname_syntax}$any\",line=\"\[0-9\]*\",arch=\"$any\"\}$after_stopped,thread-id=\"$decimal\",stopped-threads=$any\r\n($thread_selected_re|$breakpoint_re)*$prompt_re" { + -re "\\*stopped,${ebn}${r}${a}${bn}${hit_lanes}frame=\{addr=\"$hex\",func=\"$any\",args=\[\\\[\{\]$any\[\\\]\}\],file=\"$any\",fullname=\"${fullname_syntax}$any\",line=\"\[0-9\]*\",arch=\"$any\"\}$after_stopped,thread-id=\"$decimal\",${lane_id}stopped-threads=$any\r\n($thread_selected_re|$breakpoint_re)*$prompt_re" { verbose -log "got $expect_out(buffer)" fail "$test (stopped at wrong place)" return -1 @@ -2787,6 +2789,126 @@ proc mi_make_breakpoint_pending {args} { return $result } +# Construct an expected regexp string for a "*stopped" event. REASON +# is the value of "reason" attribute. Accepts options that map to +# expected MI attributes: +# +# Option => MI +# ======================== ============================= +# -addr ADDR => addr="ADDR" +# -func FUNC => func="FUNC" (frame attribute) +# -func-args ARGS => args="ARGS" (frame attribute) +# -file FILE => file="FILE" (frame attribute) +# -line LINE => line="LINE" (frame attribute) +# -thread-id TID => thread-id="TID" +# -lane-id LID => lane-id="LID" +# -stopped-threads STOPPED => stopped-threads="STOPPED" +# +# If reason is "breakpoint-hit", also handles the following options, +# with corresponding MI mapping: +# +# -disp DISP => disp="DISP" +# -bkptno BKPTNO => bkptno="BKPTNO" +# -hit-lanes LANES => hit-lanes="LANES" +# +# If some option is not specified, the option is expected, with a +# default value regexp that accepts any valid value. +# +# Note we have "-func-args" instead of the naturally expect -args, to +# match MI's "args", because $args is a special variable in TCL. +# +# Do not pass .* for any argument if you are expecting more than one +# stop. +proc mi_make_stopped {reason args} { + global hex decimal fullname_syntax + set any "\[^\n\]*" + + set options { + {addr $hex} {func $any} {func-args $any} + {file $any} {line $decimal} + {thread-id $decimal} {lane-id $decimal} + {stopped-threads "($decimal|all)"} + } + + if {$reason == "breakpoint-hit"} { + lappend options {disp $any} {bkptno $decimal} {hit-lanes $any} + } + + parse_list 1 args $options "-" true + + set attr_list {} + + if {$reason == "breakpoint-hit"} { + foreach attr [list disp bkptno hit-lanes] { + lappend attr_list $attr [set $attr] + } + } + + set f "" + append f "\\{" + append f "addr=\"$addr\"," + append f "func=\"$func\"," + append f "args=${func-args}," + append f "(?:file=\"$any$file\"," + append f "fullname=\"${fullname_syntax}$file\"," + append f "line=\"$line\"," + append f "arch=\"$any\"|from=\"$file\")" + append f "\\}" + + lappend attr_list "frame" $f + + foreach attr [list thread-id lane-id stopped-threads] { + lappend attr_list $attr [set $attr] + } + + return "\\*stopped,reason=\"$reason\",[mi_build_kv_pairs $attr_list]" +} + +# Expect a *stopped, with reason REASON. ARGS is passed to +# mi_make_stopped. See that function for description of supported +# options. +proc mi_expect_stopped_reason {test reason args} { + global async + global mi_gdb_prompt + + set re [mi_make_stopped $reason {*}$args] + + if {$async} { + set prompt_re "" + } else { + set prompt_re "$mi_gdb_prompt$" + } + + verbose -log "expecting: $re" + gdb_expect { + -re "$re.*${prompt_re}.*" { + pass "$test" + return 0 + } + -re ".*\r\n$mi_gdb_prompt$" { + verbose -log "got $expect_out(buffer)" + fail "$test (unknown output after running)" + return -1 + } + timeout { + fail "$test (timeout)" + return -1 + } + } +} + +# Expect a *stopped,reason="breakpoint-hit" stop. See mi_make_stopped +# for description of supported options. +proc mi_expect_breakpoint_stop {test args} { + mi_expect_stopped_reason $test "breakpoint-hit" {*}$args +} + +# Expect a *stopped,reason="end-stepping-range" stop. See +# mi_make_stopped for description of supported options. +proc mi_expect_step_stop {test args} { + mi_expect_stopped_reason $test "end-stepping-range" {*}$args +} + # Construct a breakpoint regexp. This may be used to test the output of # -break-insert, -dprintf-insert, or -break-info. # diff --git a/gdb/thread.c b/gdb/thread.c index ecaa6dbd3f7..ecaed28004b 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -52,6 +52,8 @@ #include "stack.h" #include "interps.h" #include "record-full.h" +#include "gdbarch.h" +#include "arch-utils.h" /* See gdbthread.h. */ @@ -75,6 +77,88 @@ static int highest_thread_num; /* The current/selected thread. */ static thread_info *current_thread_; +/* See gdbthread.h. */ + +bool +thread_info::has_simd_lanes () +{ + scoped_restore_current_thread restore_thread; + switch_to_thread (this); + gdbarch *arch = target_thread_architecture (this->ptid); + return gdbarch_active_lanes_mask_p (arch) != 0; +} + +/* See gdbthread.h. */ + +simd_lanes_mask_t +thread_info::active_simd_lanes_mask () +{ + gdb_assert (this->inf != nullptr); + + scoped_restore_current_thread restore_thread; + switch_to_inferior_no_thread (this->inf); + gdbarch *arch = target_thread_architecture (this->ptid); + + if (gdbarch_active_lanes_mask_p (arch)) + return gdbarch_active_lanes_mask (arch, this); + + /* Default: only one lane is active, lane 0. */ + return 1; +} + +/* See gdbthread.h. */ + +int +thread_info::current_simd_lane () +{ + return m_current_simd_lane; +} + +/* See gdbthread.h. */ + +void +thread_info::set_current_simd_lane (int lane) +{ + gdb_assert (lane >= 0); + + m_current_simd_lane = lane; +} + +/* See gdbthread.h. */ + +bool +thread_info::is_simd_lane_active (int lane) +{ + simd_lanes_mask_t mask = active_simd_lanes_mask (); + return (mask & ((simd_lanes_mask_t) 1 << lane)) != 0; +} + +/* See gdbthread.h. */ + +int +find_first_active_simd_lane (simd_lanes_mask_t mask) +{ + int result = -1; + + for_active_lanes (mask, [&] (int lane) + { + result = lane; + + /* We need to call this function only once. */ + return false; + }); + + return result; +} + +/* See gdbthread.h. */ + +bool +is_simd_lane_active (simd_lanes_mask_t mask, int lane) +{ + return ((mask >> lane) & 0x1) == 0x1; +} + /* Returns true if THR is the current thread. */ static bool @@ -978,6 +1062,12 @@ finish_thread_state (process_stratum_target *targ, ptid_t ptid) notify_target_resumed (ptid); } +static void +error_no_thread_selected () +{ + error (_("No thread selected.")); +} + /* See gdbthread.h. */ void @@ -985,7 +1075,7 @@ validate_registers_access (void) { /* No selected thread, no registers. */ if (inferior_ptid == null_ptid) - error (_("No thread selected.")); + error_no_thread_selected (); thread_info *tp = inferior_thread (); @@ -1039,10 +1129,73 @@ pc_in_thread_step_range (CORE_ADDR pc, struct thread_info *thread) && pc < thread->control.step_range_end); } +/* Returns true if either IF_EXPR evaluates to true in the current + context, or IF_EXPR is empty. */ + +static bool +if_expr_true (const std::string &if_expr) +{ + if (!if_expr.empty ()) + { + try + { + if (parse_and_eval_long (if_expr.c_str ()) == 0) + return false; + } + catch (const gdb_exception_error &except) + { + return false; + } + } + + return true; +} + +/* The options for the "info threads" command. */ + +struct info_threads_opts +{ + /* For "-gid". */ + bool show_global_ids = false; + + /* For "-if EXPR". */ + std::string if_expr; +}; + +/* The "-if" option, split out because it is shared by "info threads", + and "lane apply" commands. */ +static const gdb::option::expression_option_def<> thr_if_expr_option_def { + "if", + N_("Only threads for which EXPRESSION is true."), +}; + +static const gdb::option::option_def info_threads_option_defs[] = { + + gdb::option::flag_option_def { + "gid", + [] (info_threads_opts *opts) { return &opts->show_global_ids; }, + N_("Show global thread IDs."), + }, + +}; + +/* Create an option_def_group for the "info threads" options, with + IT_OPTS as context. */ + +static inline std::array +make_info_threads_options_def_group (info_threads_opts *it_opts) +{ + return {{ + { {info_threads_option_defs}, it_opts }, + { {thr_if_expr_option_def.def ()}, + it_opts != nullptr ? &it_opts->if_expr : nullptr }, + }}; +} + /* Helper for print_thread_info. Returns true if THR should be printed. If REQUESTED_THREADS, a list of GDB ids/ranges, is not NULL, only print THR if its ID is included in the list. GLOBAL_IDS - is true if REQUESTED_THREADS is list of global IDs, false if a list + is true if REQUESTED_THREADS is a list of global IDs, false if a list of per-inferior thread ids. If PID is not -1, only print THR if it is a thread from the process PID. Otherwise, threads from all attached PIDs are printed. If both REQUESTED_THREADS is not NULL @@ -1050,8 +1203,10 @@ pc_in_thread_step_range (CORE_ADDR pc, struct thread_info *thread) specified process. Otherwise, an error is raised. */ static bool -should_print_thread (const char *requested_threads, int default_inf_num, - int global_ids, int pid, struct thread_info *thr) +should_print_thread (const char *requested_threads, + const info_threads_opts &opts, + int default_inf_num, + bool global_ids, int pid, struct thread_info *thr) { if (requested_threads != NULL && *requested_threads != '\0') { @@ -1076,6 +1231,13 @@ should_print_thread (const char *requested_threads, int default_inf_num, if (thr->state == THREAD_EXITED) return false; + { + scoped_restore_current_thread restore_thread; + switch_to_thread (thr); + if (!if_expr_true (opts.if_expr)) + return false; + } + return true; } @@ -1083,9 +1245,13 @@ should_print_thread (const char *requested_threads, int default_inf_num, column, for TP. */ static std::string -thread_target_id_str (thread_info *tp) +thread_target_id_str (thread_info *tp, int lane = -1) { - std::string target_id = target_pid_to_str (tp->ptid); + std::string target_id; + if (lane != -1) + target_id = target_lane_to_str (tp, lane); + else + target_id = target_pid_to_str (tp->ptid); const char *extra_info = target_extra_thread_info (tp); const char *name = thread_name (tp); @@ -1105,7 +1271,9 @@ thread_target_id_str (thread_info *tp) static void do_print_thread (ui_out *uiout, const char *requested_threads, - int global_ids, int pid, int show_global_ids, + const info_threads_opts &opts, + bool global_ids, int pid, + bool show_current_lane, int default_inf_num, thread_info *tp, thread_info *current_thread) { @@ -1115,7 +1283,7 @@ do_print_thread (ui_out *uiout, const char *requested_threads, if (current_thread != nullptr) switch_to_thread (current_thread); - if (!should_print_thread (requested_threads, default_inf_num, + if (!should_print_thread (requested_threads, opts, default_inf_num, global_ids, pid, tp)) return; @@ -1131,7 +1299,7 @@ do_print_thread (ui_out *uiout, const char *requested_threads, uiout->field_string ("id-in-tg", print_thread_id (tp)); } - if (show_global_ids || uiout->is_mi_like_p ()) + if (opts.show_global_ids || uiout->is_mi_like_p ()) uiout->field_signed ("id", tp->global_num); /* Switch to the thread (and inferior / target). */ @@ -1161,6 +1329,14 @@ do_print_thread (ui_out *uiout, const char *requested_threads, uiout->field_string ("target-id", thread_target_id_str (tp)); } + if (show_current_lane) + { + if (tp->has_simd_lanes ()) + uiout->field_signed ("lane", tp->current_simd_lane ()); + else + uiout->field_skip ("lane"); + } + if (tp->state == THREAD_RUNNING) uiout->text ("(running)\n"); else @@ -1192,12 +1368,15 @@ do_print_thread (ui_out *uiout, const char *requested_threads, static void print_thread (ui_out *uiout, const char *requested_threads, - int global_ids, int pid, int show_global_ids, + const info_threads_opts &opts, + bool global_ids, int pid, + bool show_current_lane, int default_inf_num, thread_info *tp, thread_info *current_thread) { do_with_buffered_output (do_print_thread, uiout, requested_threads, - global_ids, pid, show_global_ids, + opts, + global_ids, pid, show_current_lane, default_inf_num, tp, current_thread); } @@ -1207,8 +1386,8 @@ print_thread (ui_out *uiout, const char *requested_threads, static void print_thread_info_1 (struct ui_out *uiout, const char *requested_threads, - int global_ids, int pid, - int show_global_ids) + const info_threads_opts &opts, + int global_ids, int pid) { int default_inf_num = current_inferior ()->num; @@ -1232,6 +1411,8 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads, /* We'll be switching threads temporarily below. */ scoped_restore_current_thread restore_thread; + bool show_current_lane = false; + if (uiout->is_mi_like_p ()) list_emitter.emplace (uiout, "threads"); else @@ -1247,7 +1428,8 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads, if (current_thread != nullptr) switch_to_thread (current_thread); - if (!should_print_thread (requested_threads, default_inf_num, + if (!should_print_thread (requested_threads, opts, + default_inf_num, global_ids, pid, tp)) continue; @@ -1260,6 +1442,9 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads, thread_target_id_str (tp).size ()); ++n_threads; + + if (!show_current_lane && tp->has_simd_lanes ()) + show_current_lane = true; } if (n_threads == 0) @@ -1272,15 +1457,21 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads, return; } - table_emitter.emplace (uiout, show_global_ids ? 5 : 4, - n_threads, "threads"); + int n_cols = 4; + if (opts.show_global_ids) + n_cols++; + if (show_current_lane) + n_cols++; + table_emitter.emplace (uiout, n_cols, n_threads, "threads"); uiout->table_header (1, ui_left, "current", ""); uiout->table_header (4, ui_left, "id-in-tg", "Id"); - if (show_global_ids) + if (opts.show_global_ids) uiout->table_header (4, ui_left, "id", "GId"); uiout->table_header (target_id_col_width, ui_left, "target-id", "Target Id"); + if (show_current_lane) + uiout->table_header (5, ui_left, "lane", "Lane"); uiout->table_header (1, ui_left, "frame", "Frame"); uiout->table_body (); } @@ -1293,8 +1484,9 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads, if (tp == current_thread && tp->state == THREAD_EXITED) current_exited = true; - print_thread (uiout, requested_threads, global_ids, pid, - show_global_ids, default_inf_num, tp, current_thread); + print_thread (uiout, requested_threads, opts, global_ids, pid, + show_current_lane, + default_inf_num, tp, current_thread); } /* This end scope restores the current thread and the frame @@ -1323,34 +1515,9 @@ void print_thread_info (struct ui_out *uiout, const char *requested_threads, int pid) { - print_thread_info_1 (uiout, requested_threads, 1, pid, 0); -} - -/* The options for the "info threads" command. */ - -struct info_threads_opts -{ - /* For "-gid". */ - bool show_global_ids = false; -}; - -static const gdb::option::option_def info_threads_option_defs[] = { - - gdb::option::flag_option_def { - "gid", - [] (info_threads_opts *opts) { return &opts->show_global_ids; }, - N_("Show global thread IDs."), - }, - -}; + info_threads_opts opts; -/* Create an option_def_group for the "info threads" options, with - IT_OPTS as context. */ - -static inline gdb::option::option_def_group -make_info_threads_options_def_group (info_threads_opts *it_opts) -{ - return {{info_threads_option_defs}, it_opts}; + print_thread_info_1 (uiout, requested_threads, opts, 1, pid); } /* Implementation of the "info threads" command. @@ -1368,7 +1535,7 @@ info_threads_command (const char *arg, int from_tty) gdb::option::process_options (&arg, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR, grp); - print_thread_info_1 (current_uiout, arg, 0, -1, it_opts.show_global_ids); + print_thread_info_1 (current_uiout, arg, it_opts, 0, -1); } /* Completer for the "info threads" command. */ @@ -1394,127 +1561,574 @@ info_threads_command_completer (struct cmd_list_element *ignore, } } -/* See gdbthread.h. */ +/* The options for the "info lanes" command. */ -void -switch_to_thread_no_regs (struct thread_info *thread) +struct info_lanes_opts { - gdb_assert (thread != nullptr); - threads_debug_printf ("thread = %s", thread->ptid.to_string ().c_str ()); - - struct inferior *inf = thread->inf; - - set_current_program_space (inf->pspace); - set_current_inferior (inf); + /* For "-all". */ + bool show_all = false; - current_thread_ = thread; - inferior_ptid = current_thread_->ptid; -} + /* For "-active". */ + bool only_active = false; -/* See gdbthread.h. */ + /* For "-inactive". */ + bool only_inactive = false; -void -switch_to_no_thread () -{ - if (current_thread_ == nullptr) - return; + /* For "-unused". */ + bool only_unused = false; - threads_debug_printf ("thread = NONE"); + /* For "-if EXPR". */ + std::string if_expr; +}; - current_thread_ = nullptr; - inferior_ptid = null_ptid; - reinit_frame_cache (); -} +static const gdb::option::option_def info_lanes_option_defs[] = { -/* See gdbthread.h. */ + gdb::option::flag_option_def { + "all", + [] (info_lanes_opts *opts) { return &opts->show_all; }, + N_("All lanes (active, inactive and unused)."), + }, -void -switch_to_thread (thread_info *thr) -{ - gdb_assert (thr != NULL); + gdb::option::flag_option_def { + "active", + [] (info_lanes_opts *opts) { return &opts->only_active; }, + N_("Only active lanes."), + }, - if (is_current_thread (thr)) - return; + gdb::option::flag_option_def { + "inactive", + [] (info_lanes_opts *opts) { return &opts->only_inactive; }, + N_("Only inactive lanes."), + }, - switch_to_thread_no_regs (thr); + gdb::option::flag_option_def { + "unused", + [] (info_lanes_opts *opts) { return &opts->only_unused; }, + N_("Only unused lanes."), + }, - reinit_frame_cache (); -} + gdb::option::expression_option_def { + "if", + [] (info_lanes_opts *opts) { return &opts->if_expr; }, + nullptr, + N_("Only lanes for which EXPRESSION is true."), + }, +}; -/* See gdbsupport/common-gdbthread.h. */ +/* Create an option_def_group for the "info lanes" options, with OPTS + as context. */ -void -switch_to_thread (process_stratum_target *proc_target, ptid_t ptid) +static inline gdb::option::option_def_group +make_info_lanes_options_def_group (info_lanes_opts *opts) { - thread_info *thr = proc_target->find_thread (ptid); - switch_to_thread (thr); + return {{info_lanes_option_defs}, opts}; } -/* See frame.h. */ +/* Helper for print_lane_info_1. Returns true if LANE should be + printed. */ -void -scoped_restore_current_thread::restore () +static bool +should_print_lane_state_flags (thread_info *thr, + const info_lanes_opts &opts, + int lane_used_count) { - /* If an entry of thread_info was previously selected, it won't be - deleted because we've increased its refcount. The thread represented - by this thread_info entry may have already exited (due to normal exit, - detach, etc), so the thread_info.state is THREAD_EXITED. */ - if (m_thread != NULL - /* If the previously selected thread belonged to a process that has - in the mean time exited (or killed, detached, etc.), then don't revert - back to it, but instead simply drop back to no thread selected. */ - && m_inf->pid != 0) - switch_to_thread (m_thread.get ()); - else - switch_to_inferior_no_thread (m_inf.get ()); + int lane = thr->current_simd_lane (); - /* The running state of the originally selected thread may have - changed, so we have to recheck it here. */ - if (inferior_ptid != null_ptid - && m_was_stopped - && m_thread->state == THREAD_STOPPED - && target_has_registers () - && target_has_stack () - && target_has_memory ()) - restore_selected_frame (m_selected_frame_id, m_selected_frame_level); + if (opts.show_all) + return true; + else if (!opts.only_active + && !opts.only_inactive + && !opts.only_unused + && lane < lane_used_count) + return true; + else if (opts.only_active && thr->is_simd_lane_active (lane)) + return true; + else if (opts.only_inactive + && !thr->is_simd_lane_active (lane) + && lane < lane_used_count) + return true; + else if (opts.only_unused && lane >= lane_used_count) + return true; + return false; } -scoped_restore_current_thread::~scoped_restore_current_thread () +static bool +should_print_lane (thread_info *thr, const info_lanes_opts &opts, + int lane_used_count) { - if (m_dont_restore) - m_lang.dont_restore (); - else - restore (); + if (!should_print_lane_state_flags (thr, opts, lane_used_count)) + return false; + + if (!if_expr_true (opts.if_expr)) + return false; + + return true; } -scoped_restore_current_thread::scoped_restore_current_thread () +/* Print one row in the "info lanes" table. TP is the thread related + to the printed row. LANE is the lane of the thread to be printed. + IS_CURRENT shows whether we print the current lane of the current + thread. */ + +static void +print_lane_row (ui_out *uiout, thread_info *tp, int lane, bool is_current) { - m_inf = inferior_ref::new_reference (current_inferior ()); + ui_out_emit_tuple tuple_emitter (uiout, NULL); - if (inferior_ptid != null_ptid) + if (!uiout->is_mi_like_p ()) { - m_thread = thread_info_ref::new_reference (inferior_thread ()); - - m_was_stopped = m_thread->state == THREAD_STOPPED; - save_selected_frame (&m_selected_frame_id, &m_selected_frame_level); + if (is_current) + uiout->field_string ("current", "*"); + else + uiout->field_skip ("current"); } -} -scoped_restore_current_thread::scoped_restore_current_thread - (scoped_restore_current_thread &&rhs) - : m_dont_restore (std::move (rhs.m_dont_restore)), - m_thread (std::move (rhs.m_thread)), - m_inf (std::move (rhs.m_inf)), - m_selected_frame_id (std::move (rhs.m_selected_frame_id)), - m_selected_frame_level (std::move (rhs.m_selected_frame_level)), - m_was_stopped (std::move (rhs.m_was_stopped)), - m_lang (std::move (rhs.m_lang)) -{ - /* Deactivate the rhs. */ - rhs.m_dont_restore = true; -} + uiout->field_string ("id", print_lane_id (tp, lane)); -/* See gdbthread.h. */ + gdbarch *arch = target_thread_architecture (tp->ptid); + int used_lanes_count = gdbarch_used_lanes_count (arch, tp); + + auto lane_state = [&] () + { + if (lane >= used_lanes_count) + { + /* Unused in workgroup. */ + return "U"; + } + + if (tp->state == THREAD_RUNNING) + { + /* Running. */ + return "R"; + } + + if (tp->is_simd_lane_active (lane)) + { + /* Active. */ + return "A"; + } + + /* Inactive. */ + return "I"; + }; + + uiout->field_string ("state", lane_state ()); + + uiout->field_string ("target-id", target_lane_to_str (tp, lane)); + + if (tp->state == THREAD_RUNNING) + uiout->text ("(running)\n"); + else + { + if (lane >= used_lanes_count) + { + /* This lane is unused. */ + uiout->text ("(unused)\n"); + } + else + { + frame_info_ptr curr_frame = get_current_frame (); + + print_stack_frame (curr_frame, + /* For MI output, print frame level. */ + uiout->is_mi_like_p (), + LOCATION, 0); + } + } +} + +/* These work with gdb::array_view with the expectation they will be + reused for Thread ID list parsing too. */ + +static bool +id_matches_inf (gdb::array_view id, + inferior *inf) +{ + gdb_assert (id.size () >= 1); + return (id[0] == lane_id_list_parser::star_part + || (id[0].first <= inf->num && inf->num <= id[0].second)); +} + +static bool +id_matches_thread (gdb::array_view id, + thread_info *thr) +{ + gdb_assert (id.size () >= 2); + return (id_matches_inf (id, thr->inf) + && (id[1] == lane_id_list_parser::star_part + || (id[1].first <= thr->per_inf_num + && thr->per_inf_num <= id[1].second))); +} + +static bool +id_matches_lane (gdb::array_view id, + thread_info *thr, int lane) +{ + gdb_assert (id.size () >= 3); + return (id_matches_thread (id, thr) + && (id[2] == lane_id_list_parser::star_part + || (id[2].first <= lane && lane <= id[2].second))); +} + +/* Get the (fully-qualified) lane ID representing the current + {inferior,thread,lane} context. Parts which don't exist in the + current context are set to -1. */ + +static lane_id +get_current_lane_id () +{ + int current_inferior_num = current_inferior ()->num; + int current_thread_num = (inferior_ptid != null_ptid + ? inferior_thread ()->per_inf_num + : -1); + int current_lane = (inferior_ptid != null_ptid + ? inferior_thread ()->current_simd_lane () + : -1); + + return { current_inferior_num, current_thread_num, current_lane }; +} + +/* Print info about the lanes specified in ID_LIST. */ + +static void +print_lane_info_1 (struct ui_out *uiout, const char *lane_id_list, + const info_lanes_opts &opts) +{ + /* For backward compatibility, we make a list for MI. A table is + preferable for the CLI, though, because it shows table headers. + XXX: Actually, there's no backward compatibility issue here, + this is copied verbatim from the thread printing code. */ + std::optional list_emitter; + std::optional table_emitter; + + /* We'll be switching threads temporarily below. */ + scoped_restore_current_thread restore_thread; + + lane_id current_lane_id = get_current_lane_id (); + + /* Parse the whole lane IDs list in one go. */ + std::vector lane_ids; + + if (lane_id_list == nullptr || *lane_id_list == '\0') + { + lane_ids.push_back ({ + lane_id_list_parser::star_part, + lane_id_list_parser::star_part, + lane_id_list_parser::star_part, + }); + } + else + { + lane_id_list_parser parser; + parser.init (lane_id_list); + while (!parser.finished ()) + { + auto elmt = parser.get_id_range (); + + /* Fill in missing parts from current context. */ + for (size_t i = 0; i < elmt.size (); i++) + if (elmt[i] == lane_id_list_parser::missing_part) + elmt[i].first = elmt[i].second = current_lane_id[i]; + + lane_ids.push_back (elmt); + } + } + + auto matches_inf = [&] (inferior *inf) + { + for (auto &elmt : lane_ids) + if (id_matches_inf (elmt, inf)) + return true; + return false; + }; + + auto matches_thread = [&] (thread_info *thr) + { + for (auto &elmt : lane_ids) + if (id_matches_thread (elmt, thr)) + return true; + return false; + }; + + auto matches_lane = [&] (thread_info *thr, int lane) + { + for (auto &elmt : lane_ids) + if (id_matches_lane (elmt, thr, lane)) + return true; + return false; + }; + + auto walk_lanes + = [&] (gdb::function_view cb) + { + for (inferior *inf : all_inferiors ()) + { + if (!matches_inf (inf)) + continue; + + for (thread_info *thr : inf->non_exited_threads ()) + { + if (!matches_thread (thr)) + continue; + + if (!thr->has_simd_lanes ()) + continue; + + /* Switch threads so we're looking at the right target + stack. */ + switch_to_thread (thr); + + gdbarch *arch = target_thread_architecture (thr->ptid); + int lane_count = gdbarch_supported_lanes_count (arch, thr); + int lane_used_count = gdbarch_used_lanes_count (arch, thr); + + scoped_restore_current_simd_lane restore_lane (thr); + + for (int lane = 0; lane < lane_count; lane++) + { + thr->set_current_simd_lane (lane); + + if (!matches_lane (thr, lane) + || !should_print_lane (thr, opts, lane_used_count)) + continue; + + cb (thr, lane); + } + } + } + }; + + int n_lanes = 0; + + /* The width of the "Target Id" column. Grown below to + accommodate the largest entry. */ + size_t target_id_col_width = 17; + + walk_lanes ([&] (thread_info *thr, int lane) + { + if (!uiout->is_mi_like_p ()) + { + target_id_col_width + = std::max (target_id_col_width, + target_lane_to_str (thr, lane).size ()); + } + + ++n_lanes; + }); + + if (n_lanes == 0) + { + if (lane_id_list == NULL || *lane_id_list == '\0') + uiout->message (_("No lanes.\n")); + else + uiout->message (_("No lanes match '%s'.\n"), + lane_id_list); + return; + } + + if (uiout->is_mi_like_p ()) + list_emitter.emplace (uiout, "lanes"); + else + { + table_emitter.emplace (uiout, 5, n_lanes, "lanes"); + + uiout->table_header (1, ui_left, "current", ""); + uiout->table_header (4, ui_left, "id", "Id"); + uiout->table_header (5, ui_left, "state", "State"); + uiout->table_header (target_id_col_width, ui_left, + "target-id", "Target Id"); + uiout->table_header (1, ui_left, "frame", "Frame"); + uiout->table_body (); + } + + walk_lanes ([&] (thread_info *thr, int lane) + { + lane_id lid = {thr->inf->num, thr->per_inf_num, lane}; + print_lane_row (uiout, thr, lane, lid == current_lane_id); + }); +} + +/* See gdbthread.h. */ + +void +print_lane_info (struct ui_out *uiout, const char *requested_lanes) +{ + info_lanes_opts opts; + + opts.show_all = true; + + print_lane_info_1 (current_uiout, requested_lanes, opts); +} + +/* Implementation of the "info lanes" command. */ + +static void +info_lanes_command (const char *arg, int from_tty) +{ + info_lanes_opts opts; + + auto grp = make_info_lanes_options_def_group (&opts); + gdb::option::process_options + (&arg, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR, grp); + + print_lane_info_1 (current_uiout, arg, opts); +} + +/* Completer for the "info lanes" command. */ + +static void +info_lanes_command_completer (struct cmd_list_element *ignore, + completion_tracker &tracker, + const char *text, const char *word_ignored) +{ + const auto grp = make_info_lanes_options_def_group (nullptr); + + if (gdb::option::complete_options + (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR, grp)) + return; + + /* Convenience to let the user know what the option can accept. */ + if (*text == '\0') + { + gdb::option::complete_on_all_options (tracker, grp); + /* Keep this "ID" in sync with what "help info lanes" says. */ + tracker.add_completion (make_unique_xstrdup ("ID")); + } +} + +/* See gdbthread.h. */ + +void +switch_to_thread_no_regs (struct thread_info *thread) +{ + gdb_assert (thread != nullptr); + threads_debug_printf ("thread = %s", thread->ptid.to_string ().c_str ()); + + struct inferior *inf = thread->inf; + + set_current_program_space (inf->pspace); + set_current_inferior (inf); + + current_thread_ = thread; + inferior_ptid = current_thread_->ptid; +} + +/* See gdbthread.h. */ + +void +switch_to_no_thread () +{ + if (current_thread_ == nullptr) + return; + + threads_debug_printf ("thread = NONE"); + + current_thread_ = nullptr; + inferior_ptid = null_ptid; + reinit_frame_cache (); +} + +/* See gdbthread.h. */ + +void +switch_to_thread (thread_info *thr) +{ + gdb_assert (thr != NULL); + + if (is_current_thread (thr)) + return; + + switch_to_thread_no_regs (thr); + + reinit_frame_cache (); +} + +/* See gdbsupport/common-gdbthread.h. */ + +void +switch_to_thread (process_stratum_target *proc_target, ptid_t ptid) +{ + thread_info *thr = proc_target->find_thread (ptid); + switch_to_thread (thr); +} + +/* See frame.h. */ + +void +scoped_restore_current_thread::restore () +{ + /* If an entry of thread_info was previously selected, it won't be + deleted because we've increased its refcount. The thread represented + by this thread_info entry may have already exited (due to normal exit, + detach, etc), so the thread_info.state is THREAD_EXITED. */ + if (m_thread != NULL + /* If the previously selected thread belonged to a process that has + in the mean time exited (or killed, detached, etc.), then don't revert + back to it, but instead simply drop back to no thread selected. */ + && m_inf->pid != 0) + switch_to_thread (m_thread.get ()); + else + switch_to_inferior_no_thread (m_inf.get ()); + + /* The running state of the originally selected thread may have + changed, so we have to recheck it here. */ + if (inferior_ptid != null_ptid + && m_was_stopped + && m_thread->state == THREAD_STOPPED + && target_has_registers () + && target_has_stack () + && target_has_memory ()) + restore_selected_frame (m_selected_frame_id, m_selected_frame_level); +} + +scoped_restore_current_thread::~scoped_restore_current_thread () +{ + if (m_dont_restore) + m_lang.dont_restore (); + else + restore (); +} + +scoped_restore_current_thread::scoped_restore_current_thread () +{ + m_inf = inferior_ref::new_reference (current_inferior ()); + + if (inferior_ptid != null_ptid) + { + m_thread = thread_info_ref::new_reference (inferior_thread ()); + + m_was_stopped = m_thread->state == THREAD_STOPPED; + save_selected_frame (&m_selected_frame_id, &m_selected_frame_level); + } +} + +scoped_restore_current_thread::scoped_restore_current_thread + (scoped_restore_current_thread &&rhs) + : m_dont_restore (std::move (rhs.m_dont_restore)), + m_thread (std::move (rhs.m_thread)), + m_inf (std::move (rhs.m_inf)), + m_selected_frame_id (std::move (rhs.m_selected_frame_id)), + m_selected_frame_level (std::move (rhs.m_selected_frame_level)), + m_was_stopped (std::move (rhs.m_was_stopped)), + m_lang (std::move (rhs.m_lang)) +{ + /* Deactivate the rhs. */ + rhs.m_dont_restore = true; +} + +scoped_restore_current_simd_lane::scoped_restore_current_simd_lane + (thread_info *thr) + : m_thr (thread_info_ref::new_reference (thr)), + m_current_simd_lane (thr->current_simd_lane ()) +{ +} + +scoped_restore_current_simd_lane::~scoped_restore_current_simd_lane () +{ + m_thr->set_current_simd_lane (m_current_simd_lane); +} + +/* See gdbthread.h. */ int show_thread_that_caused_stop (void) @@ -1551,6 +2165,18 @@ print_thread_id (struct thread_info *thr) /* See gdbthread.h. */ +const char * +print_lane_id (struct thread_info *thr, int lane) +{ + char *s = get_print_cell (); + + gdb_assert (thr != nullptr); + xsnprintf (s, PRINT_CELL_SIZE, "%s.%d", print_thread_id (thr), lane); + return s; +} + +/* See gdbthread.h. */ + const char * print_full_thread_id (struct thread_info *thr) { @@ -1561,6 +2187,17 @@ print_full_thread_id (struct thread_info *thr) return s; } +const char * +print_full_lane_id (struct thread_info *thr, int lane) +{ + char *s = get_print_cell (); + + gdb_assert (thr != nullptr); + xsnprintf (s, PRINT_CELL_SIZE, "%d.%d.%d", + thr->inf->num, thr->per_inf_num, lane); + return s; +} + /* Sort an array of struct thread_info pointers by thread ID (first by inferior number, and then by per-inferior thread number). Sorts in ascending order. */ @@ -1590,32 +2227,49 @@ tp_array_compar_descending (const thread_info_ref &a, const thread_info_ref &b) /* See gdbthread.h. */ void -thread_try_catch_cmd (thread_info *thr, std::optional ada_task, - const char *cmd, int from_tty, - const qcs_flags &flags) +thr_lane_try_catch_cmd (bool lane_mode, thread_info *thr, int lane, + std::optional ada_task, const char *cmd, + int from_tty, const qcs_flags &flags) { gdb_assert (is_current_thread (thr)); /* The thread header is computed before running the command since - the command can change the inferior, which is not permitted - by thread_target_id_str. */ - std::string thr_header; - if (ada_task.has_value ()) - thr_header = string_printf (_("\nTask ID %d:\n"), *ada_task); + the command can change the inferior, which is not permitted by + thread_target_id_str. */ + std::string header; + + if (lane_mode) + { + header + = string_printf (_("\nLane %s (%s):\n"), + print_lane_id (thr, lane), + target_lane_to_str (thr, lane).c_str ()); + } + else if (ada_task.has_value ()) + header = string_printf (_("\nTask ID %d:\n"), *ada_task); else - thr_header = string_printf (_("\nThread %s (%s):\n"), - print_thread_id (thr), - thread_target_id_str (thr).c_str ()); + header + = string_printf (_("\nThread %s (%s):\n"), print_thread_id (thr), + thread_target_id_str (thr).c_str ()); try { + /* Switch to the lane on which the command is executed. */ + std::optional restore_lane; + if (lane_mode) + { + restore_lane.emplace (thr); + thr->set_current_simd_lane (lane); + } + std::string cmd_result; execute_command_to_string (cmd_result, cmd, from_tty, gdb_stdout->term_out ()); + if (!flags.silent || cmd_result.length () > 0) { if (!flags.quiet) - gdb_printf ("%s", thr_header.c_str ()); + gdb_puts (header.c_str ()); gdb_printf ("%s", cmd_result.c_str ()); } } @@ -1624,7 +2278,8 @@ thread_try_catch_cmd (thread_info *thr, std::optional ada_task, if (!flags.silent) { if (!flags.quiet) - gdb_printf ("%s", thr_header.c_str ()); + gdb_puts (header.c_str ()); + if (flags.cont) gdb_printf ("%s\n", ex.what ()); else @@ -1651,7 +2306,7 @@ using qcs_flag_option_def static const gdb::option::option_def thr_qcs_flags_option_defs[] = { qcs_flag_option_def { "q", [] (qcs_flags *opt) { return &opt->quiet; }, - N_("Disables printing the thread information."), + N_("Disables printing the thread or lane information."), }, qcs_flag_option_def { @@ -1665,26 +2320,48 @@ static const gdb::option::option_def thr_qcs_flags_option_defs[] = { }, }; +/* The options for the "thread apply ID" and "thread apply all" + commands. */ + +struct thread_apply_opts +{ + /* Only used by "thread apply all". */ + bool ascending = false; + + qcs_flags flags; + + /* For "-if EXPR". */ + std::string if_expr; +}; + /* Create an option_def_group for the "thread apply all" options, with - ASCENDING and FLAGS as context. */ + OPTS as context. */ -static inline std::array -make_thread_apply_all_options_def_group (bool *ascending, - qcs_flags *flags) +static inline std::array +make_thread_apply_all_options_def_group (thread_apply_opts *opts) { return {{ - { {ascending_option_def.def ()}, ascending}, - { {thr_qcs_flags_option_defs}, flags }, + { {ascending_option_def.def ()}, + opts != nullptr ? &opts->ascending : nullptr}, + { {thr_qcs_flags_option_defs}, + opts != nullptr ? &opts->flags : nullptr }, + { {thr_if_expr_option_def.def ()}, + opts != nullptr ? &opts->if_expr : nullptr }, }}; } /* Create an option_def_group for the "thread apply" options, with - FLAGS as context. */ + OPTS as context. */ -static inline gdb::option::option_def_group -make_thread_apply_options_def_group (qcs_flags *flags) +static inline std::array +make_thread_apply_options_def_group (thread_apply_opts *opts) { - return {{thr_qcs_flags_option_defs}, flags}; + return {{ + { {thr_qcs_flags_option_defs}, + opts != nullptr ? &opts->flags : nullptr }, + { {thr_if_expr_option_def.def ()}, + opts != nullptr ? &opts->if_expr : nullptr }, + }}; } /* Apply a GDB command to a list of threads. List syntax is a whitespace @@ -1698,15 +2375,13 @@ make_thread_apply_options_def_group (qcs_flags *flags) static void thread_apply_all_command (const char *cmd, int from_tty) { - bool ascending = false; - qcs_flags flags; + thread_apply_opts opts; - auto group = make_thread_apply_all_options_def_group (&ascending, - &flags); + auto group = make_thread_apply_all_options_def_group (&opts); gdb::option::process_options (&cmd, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group); - validate_flags_qcs ("thread apply all", &flags); + validate_flags_qcs ("thread apply all", &opts.flags); if (cmd == NULL || *cmd == '\000') error (_("Please specify a command at the end of 'thread apply all'")); @@ -1728,7 +2403,7 @@ thread_apply_all_command (const char *cmd, int from_tty) thr_list_cpy.push_back (thread_info_ref::new_reference (tp)); gdb_assert (thr_list_cpy.size () == tc); - auto *sorter = (ascending + auto *sorter = (opts.ascending ? tp_array_compar_ascending : tp_array_compar_descending); std::sort (thr_list_cpy.begin (), thr_list_cpy.end (), sorter); @@ -1737,7 +2412,12 @@ thread_apply_all_command (const char *cmd, int from_tty) for (thread_info_ref &thr : thr_list_cpy) if (switch_to_thread_if_alive (thr.get ())) - thread_try_catch_cmd (thr.get (), {}, cmd, from_tty, flags); + { + if (!if_expr_true (opts.if_expr)) + continue; + thr_lane_try_catch_cmd (false, thr.get (), 0, {}, cmd, from_tty, + opts.flags); + } } } @@ -1764,13 +2444,256 @@ thread_apply_command_completer (cmd_list_element *ignore, if (!parser.get_tid_range (&inf_num, &thr_start, &thr_end)) break; - if (parser.in_star_range () || parser.in_thread_range ()) + if (parser.in_star_range () || parser.in_thread_range ()) + parser.skip_range (); + } + } + catch (const gdb_exception_error &ex) + { + /* get_tid_range throws if it parses a negative number, for + example. But a seemingly negative number may be the start of + an option instead. */ + } + + const char *cmd = parser.cur_tok (); + + if (cmd == text) + { + /* No thread ID list yet. */ + return; + } + + /* Check if we're past a valid thread ID list already. */ + if (parser.finished () + && cmd > text && !isspace (cmd[-1])) + return; + + /* We're past the thread ID list, advance word point. */ + tracker.advance_custom_word_point_by (cmd - text); + text = cmd; + + const auto group = make_thread_apply_options_def_group (nullptr); + if (gdb::option::complete_options + (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group)) + return; + + complete_nested_command_line (tracker, text); +} + +/* Completer for "thread apply all". */ + +static void +thread_apply_all_command_completer (cmd_list_element *ignore, + completion_tracker &tracker, + const char *text, const char *word) +{ + const auto group = make_thread_apply_all_options_def_group (nullptr); + if (gdb::option::complete_options + (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group)) + return; + + complete_nested_command_line (tracker, text); +} + +/* Implementation of the "thread apply" command. */ + +static void +thread_apply_command (const char *tidlist, int from_tty) +{ + thread_apply_opts opts; + const char *cmd = NULL; + tid_range_parser parser; + + if (tidlist == NULL || *tidlist == '\000') + error (_("Please specify a thread ID list")); + + parser.init (tidlist, current_inferior ()->num); + while (!parser.finished ()) + { + int inf_num, thr_start, thr_end; + + if (!parser.get_tid_range (&inf_num, &thr_start, &thr_end)) + break; + } + + cmd = parser.cur_tok (); + + auto group = make_thread_apply_options_def_group (&opts); + gdb::option::process_options + (&cmd, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group); + + validate_flags_qcs ("thread apply", &opts.flags); + + if (*cmd == '\0') + error (_("Please specify a command following the thread ID list")); + + if (tidlist == cmd || isdigit (cmd[0])) + invalid_thread_id_error (cmd); + + scoped_restore_current_thread restore_thread; + + parser.init (tidlist, current_inferior ()->num); + while (!parser.finished ()) + { + struct thread_info *tp = NULL; + struct inferior *inf; + int inf_num, thr_num; + + parser.get_tid (&inf_num, &thr_num); + inf = find_inferior_id (inf_num); + if (inf != NULL) + tp = find_thread_id (inf, thr_num); + + if (parser.in_star_range ()) + { + if (inf == NULL) + { + warning (_("Unknown inferior %d"), inf_num); + parser.skip_range (); + continue; + } + + /* No use looking for threads past the highest thread number + the inferior ever had. */ + if (thr_num >= inf->highest_thread_num) + parser.skip_range (); + + /* Be quiet about unknown threads numbers. */ + if (tp == NULL) + continue; + } + + if (tp == NULL) + { + if (show_inferior_qualified_tids () || parser.tid_is_qualified ()) + warning (_("Unknown thread %d.%d"), inf_num, thr_num); + else + warning (_("Unknown thread %d"), thr_num); + continue; + } + + if (!switch_to_thread_if_alive (tp)) + { + warning (_("Thread %s has terminated."), print_thread_id (tp)); + continue; + } + + if (!if_expr_true (opts.if_expr)) + continue; + + thr_lane_try_catch_cmd (false, tp, 0, {}, cmd, from_tty, opts.flags); + } +} + +/* Create an option_def_group for the "lane apply" / "lane apply all" + options, with FLAGS and IL as context. */ + +static inline std::array +make_lane_apply_options_def_group (qcs_flags *flags, + info_lanes_opts *il) +{ + return {{ + { {thr_qcs_flags_option_defs}, flags }, + { {info_lanes_option_defs}, il }, + }}; +} + +/* Apply a GDB command to a list of lanes of the current thread. List + syntax is a whitespace separated list of numbers, or ranges, or the + keyword `all'. Ranges consist of two numbers separated by a + hyphen. Examples: + + lane apply 1 2 7 4 backtrace Apply 'backtrace' cmd to lanes 1,2,7,4 + lane apply 2-7 9 p foo Apply 'p foo' cmd to lanes 2->7 & 9 + lane apply all x/i $lane_pc Apply 'x/i $lane_pc' cmd to all lanes. +*/ + +static void +lane_apply_all_command (const char *cmd, int from_tty) +{ + qcs_flags flags; + info_lanes_opts il_opts; + + auto group = make_lane_apply_options_def_group (&flags, &il_opts); + gdb::option::process_options + (&cmd, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group); + + validate_flags_qcs ("lane apply all", &flags); + + if (cmd == NULL || *cmd == '\000') + error (_("Please specify a command at the end of 'lane apply all'")); + + if (inferior_ptid == null_ptid) + return; + + scoped_restore_current_thread restore_thread; + + for (thread_info *thr : all_threads ()) + { + switch_to_thread (thr); + + scoped_restore_current_simd_lane restore_lane (thr); + scoped_restore_selected_frame restore_frame; + + gdbarch *arch = target_thread_architecture (thr->ptid); + int lane_count = gdbarch_supported_lanes_count (arch, thr); + int lane_used_count = gdbarch_used_lanes_count (arch, thr); + + for (int lane = 0; lane < lane_count; ++lane) + { + thr->set_current_simd_lane (lane); + + if (!should_print_lane (thr, il_opts, lane_used_count)) + continue; + + frame_info_ptr curr_frame = get_current_frame (); + select_frame (curr_frame); + + thr_lane_try_catch_cmd (true, thr, lane, {}, cmd, from_tty, flags); + } + } +} + +/* Completer for the "lane apply ..." commands. */ + +static void +lane_apply_completer (completion_tracker &tracker, const char *text) +{ + const auto group = make_lane_apply_options_def_group (nullptr, nullptr); + if (gdb::option::complete_options + (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group)) + return; + + complete_nested_command_line (tracker, text); +} + +/* Completer for "lane apply [ID list]". */ + +static void +lane_apply_command_completer (cmd_list_element *ignore, + completion_tracker &tracker, + const char *text, const char * /*word*/) +{ + /* Don't leave this to complete_options because there's an early + return below. */ + tracker.set_use_custom_word_point (true); + + number_or_range_parser parser (text); + + try + { + while (!parser.finished ()) + { + /* Call for effect. */ + parser.get_number (); + + if (parser.in_range ()) parser.skip_range (); } } catch (const gdb_exception_error &ex) { - /* get_tid_range throws if it parses a negative number, for + /* get_number throws if it parses a negative number, for example. But a seemingly negative number may be the start of an option instead. */ } @@ -1779,130 +2702,189 @@ thread_apply_command_completer (cmd_list_element *ignore, if (cmd == text) { - /* No thread ID list yet. */ + /* No lane ID list yet. */ return; } - /* Check if we're past a valid thread ID list already. */ + /* Check if we're past a valid lane ID already. */ if (parser.finished () && cmd > text && !isspace (cmd[-1])) return; - /* We're past the thread ID list, advance word point. */ + /* We're past the lane ID list, advance word point. */ tracker.advance_custom_word_point_by (cmd - text); text = cmd; - const auto group = make_thread_apply_options_def_group (nullptr); - if (gdb::option::complete_options - (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group)) - return; - - complete_nested_command_line (tracker, text); + lane_apply_completer (tracker, text); } -/* Completer for "thread apply all". */ +/* Completer for "lane apply all". */ static void -thread_apply_all_command_completer (cmd_list_element *ignore, - completion_tracker &tracker, - const char *text, const char *word) +lane_apply_all_command_completer (cmd_list_element *ignore, + completion_tracker &tracker, + const char *text, const char *word) { - const auto group = make_thread_apply_all_options_def_group (nullptr, - nullptr); - if (gdb::option::complete_options - (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group)) - return; - - complete_nested_command_line (tracker, text); + lane_apply_completer (tracker, text); } -/* Implementation of the "thread apply" command. */ +/* Implementation of the "lane apply" command. */ static void -thread_apply_command (const char *tidlist, int from_tty) +lane_apply_command (const char *id_list, int from_tty) { qcs_flags flags; - const char *cmd = NULL; - tid_range_parser parser; + info_lanes_opts il_opts; + const char *cmd = nullptr; + lane_id_list_parser parser; - if (tidlist == NULL || *tidlist == '\000') - error (_("Please specify a thread ID list")); + if (id_list == nullptr || *id_list == '\000') + error (_("Please specify a lane ID list")); - parser.init (tidlist, current_inferior ()->num); + parser.init (id_list); while (!parser.finished ()) - { - int inf_num, thr_start, thr_end; - - if (!parser.get_tid_range (&inf_num, &thr_start, &thr_end)) - break; - } + parser.get_id_range (); cmd = parser.cur_tok (); - auto group = make_thread_apply_options_def_group (&flags); + auto group = make_lane_apply_options_def_group (&flags, &il_opts); gdb::option::process_options (&cmd, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group); - validate_flags_qcs ("thread apply", &flags); + validate_flags_qcs ("lane apply", &flags); if (*cmd == '\0') - error (_("Please specify a command following the thread ID list")); + error (_("Please specify a command following the lane ID list")); - if (tidlist == cmd || isdigit (cmd[0])) - invalid_thread_id_error (cmd); + if (id_list == cmd || isdigit (cmd[0])) + invalid_lane_id_error (cmd); scoped_restore_current_thread restore_thread; + scoped_restore_selected_frame restore_frame; - parser.init (tidlist, current_inferior ()->num); + lane_id current_lane_id = get_current_lane_id (); + + parser.init (id_list); while (!parser.finished ()) { - struct thread_info *tp = NULL; - struct inferior *inf; - int inf_num, thr_num; + auto elmt = parser.get_id_range (); - parser.get_tid (&inf_num, &thr_num); - inf = find_inferior_id (inf_num); - if (inf != NULL) - tp = find_thread_id (inf, thr_num); + /* Fill in missing parts from current context. */ + for (size_t i = 0; i < elmt.size (); i++) + if (elmt[i] == lane_id_list_parser::missing_part) + elmt[i].first = elmt[i].second = current_lane_id[i]; - if (parser.in_star_range ()) + for (inferior *inf : all_inferiors ()) { - if (inf == NULL) + if (elmt[0] == lane_id_list_parser::star_part + || (elmt[0].first <= inf->num && inf->num <= elmt[0].second)) { - warning (_("Unknown inferior %d"), inf_num); - parser.skip_range (); - continue; + for (thread_info *thr : inf->non_exited_threads ()) + { + switch_to_thread (thr); + + gdbarch *arch = target_thread_architecture (thr->ptid); + int lane_count = gdbarch_supported_lanes_count (arch, thr); + int lane_used_count = gdbarch_used_lanes_count (arch, thr); + + scoped_restore_current_simd_lane restore_lane (thr); + + lane_id_list_parser::range lane_range = elmt[2]; + + if (lane_range == lane_id_list_parser::star_part) + { + if (lane_count == 0) + continue; + + lane_range.first = 0; + lane_range.second = lane_count - 1; + } + + if (elmt[1] == lane_id_list_parser::star_part + || (elmt[1].first <= thr->per_inf_num + && thr->per_inf_num <= elmt[1].second)) + { + for (int lane = lane_range.first; + lane <= lane_range.second; + lane++) + { + if (lane >= lane_count) + { + /* Be consistent with "thread apply" and warn. */ + warning + (_("Lane %d does not exist on thread %s."), + lane, print_thread_id (thr)); + continue; + } + + thr->set_current_simd_lane (lane); + + if (!should_print_lane (thr, il_opts, + lane_used_count)) + continue; + + frame_info_ptr curr_frame = get_current_frame (); + select_frame (curr_frame); + + thr_lane_try_catch_cmd (true, thr, lane, {}, + cmd, from_tty, flags); + } + } + } } + } + } +} - /* No use looking for threads past the highest thread number - the inferior ever had. */ - if (thr_num >= inf->highest_thread_num) - parser.skip_range (); +/* Find lane IDs with a target ID matching ARG. */ - /* Be quiet about unknown threads numbers. */ - if (tp == NULL) - continue; - } +static void +lane_find_command (const char *arg, int from_tty) +{ + if (arg == nullptr || *arg == '\0') + error (_("Command requires an argument.")); - if (tp == NULL) - { - if (show_inferior_qualified_tids () || parser.tid_is_qualified ()) - warning (_("Unknown thread %d.%d"), inf_num, thr_num); - else - warning (_("Unknown thread %d"), thr_num); - continue; - } + const char *tmp = re_comp (arg); + if (tmp != 0) + error (_("Invalid regexp (%s): %s"), tmp, arg); - if (!switch_to_thread_if_alive (tp)) + /* We're going to be switching threads. */ + scoped_restore_current_thread restore_thread; + + update_thread_list (); + + bool match = false; + + for (inferior *inf : all_inferiors ()) + { + switch_to_inferior_no_thread (inf); + + for (thread_info *tp : inf->non_exited_threads ()) { - warning (_("Thread %s has terminated."), print_thread_id (tp)); - continue; - } + if (!tp->has_simd_lanes ()) + continue; - thread_try_catch_cmd (tp, {}, cmd, from_tty, flags); + gdbarch *arch = target_thread_architecture (tp->ptid); + int lane_count = gdbarch_used_lanes_count (arch, tp); + + scoped_restore_current_simd_lane restore_lane (tp); + + for (int lane = 0; lane < lane_count; ++lane) + { + std::string name = target_lane_to_str (tp, lane); + if (!name.empty () && re_exec (name.c_str ())) + { + gdb_printf (_("Lane %s has target id '%s'\n"), + print_lane_id (tp, lane), name.c_str ()); + match = true; + } + } + } } -} + if (!match) + gdb_printf (_("No lanes match '%s'\n"), arg); +} /* Implementation of the "taas" command. */ @@ -1934,10 +2916,7 @@ thread_command (const char *tidstr, int from_tty) { if (tidstr == NULL) { - if (inferior_ptid == null_ptid) - error (_("No thread selected")); - - if (target_has_stack ()) + if (inferior_ptid != null_ptid) { struct thread_info *tp = inferior_thread (); @@ -1951,25 +2930,110 @@ thread_command (const char *tidstr, int from_tty) target_pid_to_str (inferior_ptid).c_str ()); } else - error (_("No stack.")); + error_no_thread_selected (); } else { + /* XXX: this is not multi-target safe... */ ptid_t previous_ptid = inferior_ptid; thread_select (tidstr, parse_thread_id (tidstr, NULL)); + user_selected_what what = (USER_SELECTED_THREAD + | USER_SELECTED_LANE + | USER_SELECTED_FRAME); + /* Print if the thread has not changed, otherwise an event will be sent. */ if (inferior_ptid == previous_ptid) + print_selected_thread_frame (current_uiout, what); + else + notify_user_selected_context_changed (what); + } +} + +static void +switch_to_lane (thread_info *tp, int lane) +{ + scoped_restore_current_thread restore_thread; + + switch_to_thread (tp); + gdbarch *arch = target_thread_architecture (tp->ptid); + int lane_count = gdbarch_supported_lanes_count (arch, tp); + if (lane < 0 || lane >= lane_count) + error (_("Lane %d does not exist on thread %s."), lane, + print_thread_id (tp)); + + tp->set_current_simd_lane (lane); + + restore_thread.dont_restore (); + + if (tp->state == THREAD_STOPPED) + select_frame (get_current_frame ()); +} + +/* See gdbthread.h. */ + +void +switch_to_lane (int lane) +{ + switch_to_lane (inferior_thread (), lane); +} + +/* Switch to the specified lane, or print the current lane. */ + +static void +lane_command (const char *tidstr, int from_tty) +{ + if (tidstr == NULL) + { + if (inferior_ptid != null_ptid) { - print_selected_thread_frame (current_uiout, - USER_SELECTED_THREAD - | USER_SELECTED_FRAME); + thread_info *tp = inferior_thread (); + + if (!tp->has_simd_lanes ()) + error (_("The current thread has no lanes.")); + + int lane = tp->current_simd_lane (); + + if (tp->state == THREAD_EXITED) + gdb_printf (_("[Current lane is %s (%s) (exited)]\n"), + print_lane_id (tp, lane), + target_lane_to_str (tp, lane).c_str ()); + else + gdb_printf (_("[Current lane is %s (%s)]\n"), + print_lane_id (tp, tp->current_simd_lane ()), + target_lane_to_str (tp, lane).c_str ()); } else - notify_user_selected_context_changed - (USER_SELECTED_THREAD | USER_SELECTED_FRAME); + error_no_thread_selected (); + } + else + { + auto current_thread_lane = [] () -> std::pair + { + if (inferior_ptid != null_ptid) + { + thread_info *thr = inferior_thread (); + return { thr, thr->current_simd_lane () }; + } + else + return { nullptr, -1 }; + }; + + auto [previous_thread, previous_lane] = current_thread_lane (); + + auto [thr, lane] = parse_lane_id (tidstr); + + switch_to_lane (thr, lane); + + /* Print if the lane has not changed, otherwise an event will be + sent. */ + user_selected_what what = USER_SELECTED_LANE | USER_SELECTED_FRAME; + if (previous_thread == thr && previous_lane == lane) + print_selected_thread_frame (current_uiout, what); + else + notify_user_selected_context_changed (what); } } @@ -1981,7 +3045,7 @@ thread_name_command (const char *arg, int from_tty) struct thread_info *info; if (inferior_ptid == null_ptid) - error (_("No thread selected")); + error_no_thread_selected (); arg = skip_spaces (arg); @@ -2062,11 +3126,14 @@ show_print_thread_events (struct ui_file *file, int from_tty, /* See gdbthread.h. */ void -thread_select (const char *tidstr, thread_info *tp) +thread_select (const char *tidstr, thread_info *tp, int lane) { if (!switch_to_thread_if_alive (tp)) error (_("Thread ID %s has terminated."), tidstr); + if (lane != -1) + switch_to_lane (lane); + annotate_thread_changed (); /* Since the current thread may have changed, see if there is any @@ -2086,30 +3153,46 @@ print_selected_thread_frame (struct ui_out *uiout, { if (uiout->is_mi_like_p ()) { - uiout->field_signed ("new-thread-id", - inferior_thread ()->global_num); + uiout->field_signed ("new-thread-id", tp->global_num); + if (tp->has_simd_lanes ()) + uiout->field_signed ("lane-id", tp->current_simd_lane ()); } else { uiout->text ("[Switching to thread "); - uiout->field_string ("new-thread-id", print_thread_id (tp)); + uiout->text (print_thread_id (tp)); uiout->text (" ("); uiout->text (target_pid_to_str (inferior_ptid)); - uiout->text (")]"); + uiout->text (")"); + uiout->text ("]"); + if (tp->state == THREAD_RUNNING) + uiout->text ("(running)"); + uiout->text ("\n"); } } - if (tp->state == THREAD_RUNNING) + if (selection & USER_SELECTED_LANE && tp->has_simd_lanes ()) { - if (selection & USER_SELECTED_THREAD) - uiout->text ("(running)\n"); + const int lane = tp->current_simd_lane (); + if (uiout->is_mi_like_p ()) + uiout->field_signed ("new-lane-id", lane); + else + { + uiout->text ("[Switching to lane "); + uiout->text (print_lane_id (tp, lane)); + uiout->text (" ("); + uiout->text (target_lane_to_str (tp, lane)); + uiout->text (")"); + uiout->text ("]"); + if (tp->state == THREAD_RUNNING) + uiout->text ("(running)"); + uiout->text ("\n"); + } } - else if (selection & USER_SELECTED_FRAME) - { - if (selection & USER_SELECTED_THREAD) - uiout->text ("\n"); - if (has_stack_frames ()) + if (selection & USER_SELECTED_FRAME) + { + if (tp->state == THREAD_STOPPED && has_stack_frames ()) print_stack_frame_to_uiout (uiout, get_selected_frame (NULL), 1, SRC_AND_LOC, 1); } @@ -2262,7 +3345,7 @@ inferior_thread_count_make_value (struct gdbarch *gdbarch, } /* Commands with a prefix of `thread'. */ -struct cmd_list_element *thread_cmd_list = NULL; +struct cmd_list_element *thread_cmd_list = nullptr; /* Implementation of `thread' variable. */ @@ -2288,11 +3371,161 @@ static const struct internalvar_funcs inferior_thread_count_funcs = NULL, }; +/* Commands with a prefix of `lane'. */ +struct cmd_list_element *lane_cmd_list = NULL; + +/* Return a new value for the selected lane's index. Return a value + of 0 if no thread is selected, or no threads exist. */ + +static struct value * +lane_make_value (struct gdbarch *gdbarch, internalvar *var, void *ignore) +{ + int int_val; + + if (inferior_ptid == null_ptid) + int_val = 0; + else + { + thread_info *thr = inferior_thread (); + int_val = thr->current_simd_lane (); + } + + return value_from_longest (builtin_type (gdbarch)->builtin_int, int_val); +} + +/* Implementation of the `lane' variable. */ + +static const struct internalvar_funcs lane_funcs = +{ + lane_make_value, + NULL, +}; + +/* Return a new value for the frame's used lanes count, taking partial + work-groups into account. Return a value of 0 if no thread is + selected or the frame's architecture does not support SIMD + lanes. */ + +static struct value * +lane_count_make_value (struct gdbarch *gdbarch, internalvar *var, void *ignore) +{ + int int_val; + + if (inferior_ptid == null_ptid) + int_val = 0; + else + { + thread_info *thr = inferior_thread (); + struct gdbarch *arch = get_frame_arch (get_selected_frame (nullptr)); + int_val = gdbarch_used_lanes_count (arch, thr); + } + + return value_from_longest (builtin_type (gdbarch)->builtin_int, int_val); +} + +/* Implementation of the `lane_count' variable. */ + +static const struct internalvar_funcs lane_count_funcs = +{ + lane_count_make_value, + NULL, +}; + +/* Helper for thread/lane string convenience variables. Return a new + string value as returned by GET_STR. Returns the empty string if + no thread is selected, or no threads exist. */ + +static struct value * +make_thread_string_value (struct gdbarch *gdbarch, internalvar *var, + gdb::function_view get_str) +{ + std::string pos; + + if (inferior_ptid != null_ptid) + pos = get_str (); + + value *val = value_cstring (pos.c_str (), pos.size (), + builtin_type (gdbarch)->builtin_char); + + /* Make sure GDB doesn't try to push the string to the inferior. */ + val->set_lval (lval_internalvar); + return val; +} + +/* Return a new string value for the current thread's workgroup + position. Returns the empty string if no thread is selected, or no + threads exist. */ + +static struct value * +thread_workgroup_pos_make_value (struct gdbarch *gdbarch, internalvar *var, + void *ignore) +{ + return make_thread_string_value (gdbarch, var, [] () + { + return target_thread_workgroup_pos_str (inferior_thread ()); + }); +} + +/* Implementation of the `$_thread_workgroup_pos' variable. */ + +static const struct internalvar_funcs thread_workgroup_pos_funcs = +{ + thread_workgroup_pos_make_value, + NULL, +}; + +/* Return a new string value for the current lane's workgroup + position. Returns the empty string if no thread is selected, or no + threads exist. */ + +static struct value * +dispatch_pos_make_value (struct gdbarch *gdbarch, internalvar *var, + void *ignore) +{ + return make_thread_string_value (gdbarch, var, [] () + { + return target_dispatch_pos_str (inferior_thread ()); + }); +} + +/* Implementation of the `$_thread_workgroup_pos' variable. */ + +static const struct internalvar_funcs dispatch_pos_funcs = +{ + dispatch_pos_make_value, + NULL, +}; + +/* Return a new string value for the current lane's workgroup + position. Returns the empty string if no thread is selected, or no + threads exist. */ + +static struct value * +lane_workgroup_pos_make_value (struct gdbarch *gdbarch, internalvar *var, + void *ignore) +{ + return make_thread_string_value (gdbarch, var, [] () + { + thread_info *thr = inferior_thread (); + return target_lane_workgroup_pos_str (thr, + thr->current_simd_lane ()); + }); +} + +/* Implementation of the `$_lane_workgroup_pos' variable. */ + +static const struct internalvar_funcs lane_workgroup_pos_funcs = +{ + lane_workgroup_pos_make_value, + NULL, +}; + void _initialize_thread (); void _initialize_thread () { static struct cmd_list_element *thread_apply_list = NULL; + static struct cmd_list_element *lane_apply_list = NULL; cmd_list_element *c; const auto info_threads_opts = make_info_threads_options_def_group (nullptr); @@ -2313,6 +3546,30 @@ Options:\n\ c = add_info ("threads", info_threads_command, info_threads_help.c_str ()); set_cmd_completer_handle_brkchars (c, info_threads_command_completer); + const auto info_lanes_opts = make_info_lanes_options_def_group (nullptr); + + /* Note: keep this "ID" in sync with what "info lanes [TAB]" + suggests. */ + static std::string info_lanes_help + = gdb::option::build_help (_("\ +Display currently known lanes.\n\ +Usage: info lanes [OPTION]... [ID]...\n\ +\n\ +Options:\n\ +%OPTIONS%\ +\n\n\ +If ID is given, it is a space-separated list of IDs of lanes to display.\n\ +Otherwise, all lanes are displayed."), + info_lanes_opts); + + c = add_info ("lanes", info_lanes_command, info_lanes_help.c_str ()); + set_cmd_completer_handle_brkchars (c, info_lanes_command_completer); + + add_prefix_cmd ("lane", class_run, lane_command, _("\ +Use this command to switch between lanes.\n\ +The new lane ID must be currently known."), + &lane_cmd_list, 1, &cmdlist); + cmd_list_element *thread_cmd = add_prefix_cmd ("thread", class_run, thread_command, _("\ Use this command to switch between threads.\n\ @@ -2347,7 +3604,7 @@ THREAD_APPLY_OPTION_HELP), set_cmd_completer_handle_brkchars (c, thread_apply_command_completer); const auto thread_apply_all_opts - = make_thread_apply_all_options_def_group (nullptr, nullptr); + = make_thread_apply_all_options_def_group (nullptr); static std::string thread_apply_all_help = gdb::option::build_help (_("\ Apply a command to all threads.\n\ @@ -2361,6 +3618,48 @@ THREAD_APPLY_OPTION_HELP), &thread_apply_list); set_cmd_completer_handle_brkchars (c, thread_apply_all_command_completer); + /* lane apply ... commands. */ + { +#define LANE_APPLY_OPTION_HELP "\ +Prints lane number and target system's lane id\n\ +followed by COMMAND output.\n\ +\n\ +By default, an error raised during the execution of COMMAND\n\ +aborts \"lane apply\".\n\ +\n\ +Options:\n\ +%OPTIONS%" + + const auto lane_apply_opts = make_lane_apply_options_def_group (nullptr, + nullptr); + + static std::string lane_apply_help + = gdb::option::build_help (_("\ +Apply a command to a list of lanes.\n\ +Usage: lane apply ID... [OPTION]... COMMAND\n\ +ID is a space-separated list of IDs of lanes to apply COMMAND on.\n" +LANE_APPLY_OPTION_HELP), + lane_apply_opts); + + c = add_prefix_cmd ("apply", class_run, lane_apply_command, + lane_apply_help.c_str (), + &lane_apply_list, 1, &lane_cmd_list); + set_cmd_completer_handle_brkchars (c, lane_apply_command_completer); + + static std::string lane_apply_all_help + = gdb::option::build_help (_("\ +Apply a command to all lanes.\n\ +\n\ +Usage: lane apply all [OPTION]... COMMAND\n" +LANE_APPLY_OPTION_HELP), + lane_apply_opts); + + c = add_cmd ("all", class_run, lane_apply_all_command, + lane_apply_all_help.c_str (), + &lane_apply_list); + set_cmd_completer_handle_brkchars (c, lane_apply_all_command_completer); + } + c = add_com ("taas", class_run, taas_command, _("\ Apply a command to all threads (ignoring errors and empty output).\n\ Usage: taas [OPTION]... COMMAND\n\ @@ -2386,6 +3685,12 @@ Usage: thread find REGEXP\n\ Will display thread ids whose name, target ID, or extra info matches REGEXP."), &thread_cmd_list); + add_cmd ("find", class_run, lane_find_command, _("\ +Find lanes that match a regular expression.\n\ +Usage: lane find REGEXP\n\ +Displays lane IDs whose target ID matches REGEXP."), + &lane_cmd_list); + add_setshow_boolean_cmd ("thread-events", no_class, &print_thread_events, _("\ Set printing of thread events (such as thread start and exit)."), _("\ @@ -2406,4 +3711,13 @@ When on messages about thread creation and deletion are printed."), create_internalvar_type_lazy ("_gthread", >hread_funcs, NULL); create_internalvar_type_lazy ("_inferior_thread_count", &inferior_thread_count_funcs, NULL); + + create_internalvar_type_lazy ("_lane", &lane_funcs, nullptr); + create_internalvar_type_lazy ("_lane_count", &lane_count_funcs, nullptr); + + create_internalvar_type_lazy ("_thread_workgroup_pos", + &thread_workgroup_pos_funcs, nullptr); + create_internalvar_type_lazy ("_lane_workgroup_pos", + &lane_workgroup_pos_funcs, nullptr); + create_internalvar_type_lazy ("_dispatch_pos", &dispatch_pos_funcs, nullptr); } diff --git a/gdb/tid-parse.c b/gdb/tid-parse.c index 442d5b33693..d346b85724b 100644 --- a/gdb/tid-parse.c +++ b/gdb/tid-parse.c @@ -30,21 +30,26 @@ invalid_thread_id_error (const char *string) error (_("Invalid thread ID: %s"), string); } +[[noreturn]] void +invalid_lane_id_error (const char *string) +{ + error (_("Invalid lane ID: %s"), string); +} + /* Wrapper for get_number_trailer that throws an error if we get back a negative number. We'll see a negative value if the number is stored in a negative convenience variable (e.g., $minus_one = -1). STRING is the parser string to be used in the error message if we do get back a negative number. */ -static int -get_positive_number_trailer (const char **pp, int trailer, const char *string) +static std::optional +get_non_negative_number_trailer (const char **pp, int trailer, + const char *string) { - int num; - - num = get_number_trailer (pp, trailer); - if (num < 0) + std::optional res = get_number_trailer (pp, trailer); + if (res.has_value () && *res < 0) error (_("negative value: %s"), string); - return num; + return res; } /* Parse TIDSTR as a per-inferior thread ID, in either INF_NUM.THR_NUM @@ -64,46 +69,45 @@ get_positive_number_trailer (const char **pp, int trailer, const char *string) exception. */ static std::pair -parse_thread_id_1 (const char *tidstr, const char **end) +parse_thread_id_1 (const char *tidstr, const char **end, + gdb::function_view invalid_id, + const char *tidstr_error) { - const char *number = tidstr; - const char *dot, *p1; - int thr_num, inf_num; - - dot = strchr (number, '.'); - - if (dot != NULL) + const char *p; + int inf_num; + const char *dot = strchr (tidstr, '.'); + if (dot != nullptr) { /* Parse number to the left of the dot. */ - p1 = number; - inf_num = get_positive_number_trailer (&p1, '.', number); - if (inf_num == 0) - invalid_thread_id_error (number); - p1 = dot + 1; + p = tidstr; + std::optional res + = get_non_negative_number_trailer (&p, '.', tidstr_error); + if (!res.has_value () || *res == 0) + invalid_id (tidstr_error); + inf_num = *res; + p = dot + 1; } else { inf_num = 0; - p1 = number; + p = tidstr; } - thr_num = get_positive_number_trailer (&p1, 0, number); - if (thr_num == 0) - invalid_thread_id_error (number); + std::optional res + = get_non_negative_number_trailer (&p, '.', tidstr_error); + if (!res.has_value () || *res == 0) + invalid_id (tidstr_error); + int thr_num = *res; if (end != nullptr) - *end = p1; + *end = p; return { inf_num, thr_num }; } -/* See tid-parse.h. */ - -struct thread_info * -parse_thread_id (const char *tidstr, const char **end) +static thread_info * +resolve_thread_id (int inf_num, int thr_num) { - const auto [inf_num, thr_num] = parse_thread_id_1 (tidstr, end); - inferior *inf; bool explicit_inf_id = false; @@ -138,12 +142,208 @@ parse_thread_id (const char *tidstr, const char **end) /* See tid-parse.h. */ +struct thread_info * +parse_thread_id (const char *tidstr, const char **end) +{ + const auto [inf_num, thr_num] + = parse_thread_id_1 (tidstr, end, invalid_thread_id_error, tidstr); + return resolve_thread_id (inf_num, thr_num); +} + +std::pair +split_lane_id (const char *lidstr) +{ + const char *last_dot = strrchr (lidstr, '.'); + if (last_dot != nullptr) + { + std::string tidstr (lidstr, last_dot); + std::string lanestr (last_dot + 1); + return {tidstr, lanestr}; + } + else + return {"", lidstr}; +} + +static std::array +split_lane_id_parts (const char *input, const char **endp) +{ + std::string_view sv (input); + + /* Check if there's a space in the input and adjust the + string_view accordingly. */ + size_t space_pos = sv.find (' '); + if (space_pos != std::string_view::npos) + { + sv = sv.substr (0, space_pos); + *endp = input + space_pos; + } + else + *endp = input + sv.size (); + + /* Hold the three parts (filled from right to left). */ + std::array parts; + + if (sv.empty ()) + return parts; + + size_t count = 0; + size_t end = sv.size (); + + /* Find dots from right to left. */ + for (;;) + { + size_t dot_pos = sv.rfind ('.', end - 1); + if (dot_pos == std::string_view::npos) + break; + + if (count == 2 || dot_pos == 0 || dot_pos == end - 1) + invalid_lane_id_error (input); + + parts[2 - count++] = sv.substr (dot_pos + 1, end - dot_pos - 1); + end = dot_pos; + } + /* Capture the last remaining part. */ + parts[2 - count++] = sv.substr (0, end); + + return parts; +} + +void +lane_id_list_parser::init (const char *input) +{ + m_cursor = skip_spaces (input); +} + +lane_id_list_parser::lane_id_range +lane_id_list_parser::get_id_range () +{ + lane_id_range res; + const char *end; + auto str_res = split_lane_id_parts (m_cursor, &end); + + for (size_t i = 0; i < 3; i++) + { + auto &part = str_res[i]; + if (part.empty ()) + res[i] = missing_part; + else if (part == "*") + res[i] = star_part; + else + { + std::string str (str_res[i]); + number_or_range_parser parser (str.c_str ()); + int range_start = parser.get_number (); + int range_end = (parser.in_range () + ? parser.end_value () + : range_start); + res[i] = {range_start, range_end}; + } + } + + m_cursor = skip_spaces (end); + + return res; +} + +bool +lane_id_list_parser::finished () +{ + /* Parsing is finished when at end of string or null string, or we + are not in a range and not in front of an integer, negative + integer, convenience var or negative convenience var. */ + return (*m_cursor == '\0' + || !(isdigit (*m_cursor) + || *m_cursor == '$' + || *m_cursor == '*')); +} + +static lane_id +parse_lane_id_1 (const char *lidstr, const char **end) +{ + const char *lanenum_str; + lane_id res; + + const char *last_dot = strrchr (lidstr, '.'); + if (last_dot != nullptr) + { + /* Parse [INF.]THR to the left of the dot. */ + std::string tidstr (lidstr, last_dot); + const char *tid_end; + auto pair = parse_thread_id_1 (tidstr.c_str (), &tid_end, + invalid_lane_id_error, lidstr); + if (*tid_end != '\0') + error (_("whoops")); + res[0] = std::get<0> (pair); + res[1] = std::get<1> (pair); + lanenum_str = last_dot + 1; + } + else + { + res[0] = 0; + res[1] = 0; + lanenum_str = lidstr; + } + + const char *p = lanenum_str; + std::optional lanenum = get_non_negative_number_trailer (&p, 0, lidstr); + /* Note that 0 is valid. */ + if (!lanenum.has_value ()) + invalid_lane_id_error (lidstr); + res[2] = *lanenum; + + if (end != nullptr) + *end = p; + + return res; +} + +std::pair +parse_lane_id (const char *lidstr, const char **end) +{ + auto [inf_num, thr_num, lane_num] = parse_lane_id_1 (lidstr, end); + + thread_info *thr; + if (thr_num == 0) + { + /* If the thread number is 0, it means the user didn't specify a + thread. Fill it from current context. */ + gdb_assert (inf_num == 0); + thr = inferior_thread (); + } + else + { + /* resolve_thread_id handles the case of the user not specifying + an inferior number either. */ + thr = resolve_thread_id (inf_num, thr_num); + } + + return { thr, lane_num }; +} + +/* See tid-parse.h. */ + bool is_thread_id (const char *tidstr, const char **end) { try { - (void) parse_thread_id_1 (tidstr, end); + (void) parse_thread_id_1 (tidstr, end, invalid_thread_id_error, tidstr); + return true; + } + catch (const gdb_exception_error &) + { + return false; + } +} + +/* See tid-parse.h. */ + +bool +is_lane_id (const char *lidstr, const char **end) +{ + try + { + (void) parse_lane_id_1 (lidstr, end); return true; } catch (const gdb_exception_error &) @@ -254,9 +454,11 @@ tid_range_parser::get_tid_or_range (int *inf_num, /* Parse number to the left of the dot. */ p = m_cur_tok; - m_inf_num = get_positive_number_trailer (&p, '.', m_cur_tok); - if (m_inf_num == 0) + std::optional res + = get_non_negative_number_trailer (&p, '.', m_cur_tok); + if (!res.has_value () || *res == 0) return 0; + m_inf_num = *res; m_qualified = true; p = dot + 1; diff --git a/gdb/tid-parse.h b/gdb/tid-parse.h index f971f91b4f6..9f9367c3d4a 100644 --- a/gdb/tid-parse.h +++ b/gdb/tid-parse.h @@ -22,12 +22,19 @@ #include "cli/cli-utils.h" +/* Fully-qualified lane ID. */ +using lane_id = std::array; + struct thread_info; /* Issue an invalid thread ID error, pointing at STRING, the invalid ID. */ [[noreturn]] extern void invalid_thread_id_error (const char *string); +/* Issue an invalid lane ID error, pointing at STRING, the invalid + ID. */ +[[noreturn]] extern void invalid_lane_id_error (const char *string); + /* Parse TIDSTR as a per-inferior thread ID, in either INF_NUM.THR_NUM or THR_NUM form. In the latter case, the missing INF_NUM is filled in from the current inferior. If ENDPTR is not NULL, @@ -36,6 +43,40 @@ struct thread_info; thrown. */ struct thread_info *parse_thread_id (const char *tidstr, const char **end); +std::pair parse_lane_id (const char *input, + const char **end = nullptr); + +std::pair split_lane_id (const char *lidstr); + +class lane_id_list_parser +{ +public: + /* Start and end (inclusive) of id-part range. See inline variables + representing special cases below. */ + using range = std::pair; + + /* The three parts of [[INF.]THR.]LANE. Note that each part is a + range. */ + using lane_id_range = std::array; + + /* Part was missing => current inferior/thread/lane. */ + inline static constexpr range missing_part = {-2, 0}; + + /* Part was '*' => all inferiors/threads/lanes. */ + inline static constexpr range star_part = {-1, 0}; + + void init (const char *input); + + lane_id_range get_id_range (); + + bool finished (); + + const char *cur_tok () { return m_cursor; } + +private: + const char *m_cursor; +}; + /* Return true if TIDSTR is pointing to a string that looks like a thread-id. This doesn't mean that TIDSTR identifies a valid thread, but the string does at least look like a valid thread-id. If END is not @@ -44,6 +85,14 @@ struct thread_info *parse_thread_id (const char *tidstr, const char **end); bool is_thread_id (const char *tidstr, const char **end); +/* Return true if LIDSTR is pointing to a string that looks like a + lane-id. This doesn't mean that LIDSTR identifies a valid lane, + but the string does at least look like a valid lane-id. If END is + not NULL, parse_lane_id stores the address of the first character + after the lane-id into *END. */ + +bool is_lane_id (const char *lidstr, const char **end); + /* Parse a thread ID or a thread range list. A range will be of the form diff --git a/gdb/varobj.c b/gdb/varobj.c index 7b625dc598a..b9d0bdf85f8 100644 --- a/gdb/varobj.c +++ b/gdb/varobj.c @@ -93,6 +93,10 @@ struct varobj_root was created. */ int thread_id = 0; + /* If not -1, indicates the currently selected lane. Valid iff + THREAD_ID is not -1. */ + int lane = -1; + /* If true, the -var-update always recomputes the value in the current thread and frame. Otherwise, variable object is always updated in the specific scope/thread/frame. */ @@ -354,7 +358,13 @@ varobj_create (const char *objname, error (_("Failed to find the specified frame")); var->root->frame = get_frame_id (fi); - var->root->thread_id = inferior_thread ()->global_num; + + thread_info *thr = inferior_thread (); + var->root->thread_id = thr->global_num; + + if (thr->has_simd_lanes ()) + var->root->lane = thr->current_simd_lane (); + old_id = get_frame_id (get_selected_frame (NULL)); select_frame (fi); } @@ -544,6 +554,21 @@ varobj_get_thread_id (const struct varobj *var) return -1; } +/* If the variable object is bound to a specific thread lane, return + the id of the lane -- which is always non-negative. Otherwise, + returns -1. */ + +int +varobj_get_lane (const struct varobj *var) +{ + if (var->root->valid_block + && var->root->thread_id > 0 + && var->root->lane >= 0) + return var->root->lane; + else + return -1; +} + void varobj_set_frozen (struct varobj *var, bool frozen) { @@ -1234,7 +1259,35 @@ install_new_value (struct varobj *var, struct value *value, bool initial) try { - value->fetch_lazy (); + /* If the value's address is of an address space that is + thread or lane specific, we need to fetch the value + in the context of the right thread/lane. */ + std::optional restore_thread; + std::optional restore_lane; + + if (var->root->thread_id > 0) + { + thread_info *thread = find_thread_global_id (var->root->thread_id); + if (thread != nullptr) + { + restore_thread.emplace (); + switch_to_thread (thread); + + if (var->root->lane != -1) + { + restore_lane.emplace (thread); + thread->set_current_simd_lane (var->root->lane); + } + } + else + { + /* See comment in the catch block. */ + value = nullptr; + } + } + + if (value != nullptr) + value->fetch_lazy (); } catch (const gdb_exception_error &except) @@ -1942,6 +1995,7 @@ value_of_root_1 (struct varobj **var_handle) return NULL; scoped_restore_current_thread restore_thread; + std::optional restore_lane; /* Determine whether the variable is still around. */ if (var->root->valid_block == NULL || var->root->floating) @@ -1961,6 +2015,12 @@ value_of_root_1 (struct varobj **var_handle) if (thread != NULL) { switch_to_thread (thread); + if (var->root->lane != -1) + { + restore_lane.emplace (thread); + thread->set_current_simd_lane (var->root->lane); + } + within_scope = check_scope (var); } } diff --git a/gdb/varobj.h b/gdb/varobj.h index 78f3ba02436..5983bb0f740 100644 --- a/gdb/varobj.h +++ b/gdb/varobj.h @@ -266,6 +266,8 @@ extern enum varobj_display_formats varobj_get_display_format ( extern int varobj_get_thread_id (const struct varobj *var); +extern int varobj_get_lane (const struct varobj *var); + extern void varobj_set_frozen (struct varobj *var, bool frozen); extern bool varobj_get_frozen (const struct varobj *var); diff --git a/gdbsupport/common-exceptions.h b/gdbsupport/common-exceptions.h index cde67da73b0..19773c6010e 100644 --- a/gdbsupport/common-exceptions.h +++ b/gdbsupport/common-exceptions.h @@ -120,6 +120,10 @@ enum errors { a thread context to be present, but it is missing. */ DWARF2_THREAD_CONTEXT_MISSING, + /* Operation requires an active lane, but current lane is + inactive. */ + LANE_INACTIVE_ERROR, + /* Add more errors here. */ NR_ERRORS };