[v3,1/2] gdb: dwarf2 generic implementation for caching function data

Message ID 20230119102948.3069226-1-torbjorn.svensson@foss.st.com
State New
Headers
Series [v3,1/2] gdb: dwarf2 generic implementation for caching function data |

Commit Message

Torbjörn SVENSSON Jan. 19, 2023, 10:29 a.m. UTC
  v2 -> v3:

Addressed comments from Tom in
https://sourceware.org/pipermail/gdb-patches/2023-January/195882.html


---

When there is no dwarf2 data for a register, a function can be called
to provide the value of this register.  In some situations, it might
not be trivial to determine the value to return and it would cause a
performance bottleneck to do the computation each time.

This patch allows the called function to have a "cache" object that it
can use to store some metadata between calls to reduce the performance
impact of the complex logic.

The cache object is unique for each function and frame, so if there are
more than one function pointer stored in the dwarf2_frame_cache->reg
array, then the appropriate pointer will be supplied (the type is not
known by the dwarf2 implementation).

dwarf2_frame_get_fn_data can be used to retrieve the function unique
cache object.
dwarf2_frame_allocate_fn_data can be used to allocate and retrieve the
function unique cache object.

Signed-off-by: Torbjörn SVENSSON <torbjorn.svensson@foss.st.com>
Signed-off-by: Yvan Roux <yvan.roux@foss.st.com>
---
 gdb/dwarf2/frame.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++
 gdb/dwarf2/frame.h | 37 ++++++++++++++++++++++++++--
 2 files changed, 96 insertions(+), 2 deletions(-)
  

Comments

Simon Marchi Jan. 19, 2023, 4:53 p.m. UTC | #1
On 1/19/23 05:29, Torbjörn SVENSSON via Gdb-patches wrote:
> v2 -> v3:
> 
> Addressed comments from Tom in
> https://sourceware.org/pipermail/gdb-patches/2023-January/195882.html
> 
> 
> ---
> 
> When there is no dwarf2 data for a register, a function can be called
> to provide the value of this register.  In some situations, it might
> not be trivial to determine the value to return and it would cause a
> performance bottleneck to do the computation each time.
> 
> This patch allows the called function to have a "cache" object that it
> can use to store some metadata between calls to reduce the performance
> impact of the complex logic.
> 
> The cache object is unique for each function and frame, so if there are
> more than one function pointer stored in the dwarf2_frame_cache->reg
> array, then the appropriate pointer will be supplied (the type is not
> known by the dwarf2 implementation).
> 
> dwarf2_frame_get_fn_data can be used to retrieve the function unique
> cache object.
> dwarf2_frame_allocate_fn_data can be used to allocate and retrieve the
> function unique cache object.
> 
> Signed-off-by: Torbjörn SVENSSON <torbjorn.svensson@foss.st.com>
> Signed-off-by: Yvan Roux <yvan.roux@foss.st.com>

Hi,

My main question is: this approach requires each custom prev_register
function to set up and manage its own cache.  Did you consider caching
values at a higher level?

Perhaps:

 1. In dwarf2_frame_prev_register, just for DWARF2_FRAME_REG_FN
 2. In dwarf2_frame_prev_register, but for all kinds of registers
 3. In frame_unwind_register_value, which would cache all register values
    for all frames regardless of the unwinder

I don't know if other ways of unwinding would benefit that much from
caching, but I guess it wouldn't hurt.  For instance, evaluating DWARF
expressions is probably generally not super expensive, but it might
still benefit from getting cached.  And it would be nice to write the
caching code just once and have it work transparently for everything.

> @@ -262,4 +264,35 @@ extern int dwarf2_fetch_cfa_info (struct gdbarch *gdbarch, CORE_ADDR pc,
>  				  const gdb_byte **cfa_start_out,
>  				  const gdb_byte **cfa_end_out);
>  
> +
> +/* Allocate a new instance of the function unique data.
> +
> +   The main purpose of this custom function data object is to allow caching the
> +   value of expensive lookups in the prev_register implementation.

I would replace "in the prev_register implementation" with "in
prev_register implementations".  There isn't only one prev_register
implementation AFAIK.

Simon
  
Torbjörn SVENSSON Jan. 20, 2023, 2:12 p.m. UTC | #2
On 2023-01-19 17:53, Simon Marchi wrote:
> 
> 
> On 1/19/23 05:29, Torbjörn SVENSSON via Gdb-patches wrote:
>> v2 -> v3:
>>
>> Addressed comments from Tom in
>> https://sourceware.org/pipermail/gdb-patches/2023-January/195882.html
>>
>>
>> ---
>>
>> When there is no dwarf2 data for a register, a function can be called
>> to provide the value of this register.  In some situations, it might
>> not be trivial to determine the value to return and it would cause a
>> performance bottleneck to do the computation each time.
>>
>> This patch allows the called function to have a "cache" object that it
>> can use to store some metadata between calls to reduce the performance
>> impact of the complex logic.
>>
>> The cache object is unique for each function and frame, so if there are
>> more than one function pointer stored in the dwarf2_frame_cache->reg
>> array, then the appropriate pointer will be supplied (the type is not
>> known by the dwarf2 implementation).
>>
>> dwarf2_frame_get_fn_data can be used to retrieve the function unique
>> cache object.
>> dwarf2_frame_allocate_fn_data can be used to allocate and retrieve the
>> function unique cache object.
>>
>> Signed-off-by: Torbjörn SVENSSON <torbjorn.svensson@foss.st.com>
>> Signed-off-by: Yvan Roux <yvan.roux@foss.st.com>
> 
> Hi,
> 
> My main question is: this approach requires each custom prev_register
> function to set up and manage its own cache.  Did you consider caching
> values at a higher level?
> 
> Perhaps:
> 
>   1. In dwarf2_frame_prev_register, just for DWARF2_FRAME_REG_FN
>   2. In dwarf2_frame_prev_register, but for all kinds of registers
>   3. In frame_unwind_register_value, which would cache all register values
>      for all frames regardless of the unwinder
> 
> I don't know if other ways of unwinding would benefit that much from
> caching, but I guess it wouldn't hurt.  For instance, evaluating DWARF
> expressions is probably generally not super expensive, but it might
> still benefit from getting cached.  And it would be nice to write the
> caching code just once and have it work transparently for everything.

I suppose a generic cache might be useful in the long run, but it would 
have an impact on other code paths and I'm not comfortable of doing this 
major change now. Doing this change would imply that almost everything 
related to unwinding is impacted in one way or another and I suppose we 
would need to test that. With the solution I proposed, only Arm Cortex 
would be impacted and the scope for testing would be rather small.

I'm also aiming to resolve this for GDB13, so doing this major change is 
a bit late in the sprint, right?

If you have a better idea on how to cache everything, like in your 3 
ideas above, please don't hesitate do share the implementation. :)

> 
>> @@ -262,4 +264,35 @@ extern int dwarf2_fetch_cfa_info (struct gdbarch *gdbarch, CORE_ADDR pc,
>>   				  const gdb_byte **cfa_start_out,
>>   				  const gdb_byte **cfa_end_out);
>>   
>> +
>> +/* Allocate a new instance of the function unique data.
>> +
>> +   The main purpose of this custom function data object is to allow caching the
>> +   value of expensive lookups in the prev_register implementation.
> 
> I would replace "in the prev_register implementation" with "in
> prev_register implementations".  There isn't only one prev_register
> implementation AFAIK.

Will do that for v4 or when merging if no v4 is needed.

> 
> Simon


Kind regards,
Torbjörn
  
Simon Marchi Jan. 20, 2023, 5:28 p.m. UTC | #3
> I suppose a generic cache might be useful in the long run, but it
> would have an impact on other code paths and I'm not comfortable of
> doing this major change now. Doing this change would imply that almost
> everything related to unwinding is impacted in one way or another and
> I suppose we would need to test that. With the solution I proposed,
> only Arm Cortex would be impacted and the scope for testing would be
> rather small.

I understand the feeling.  It's impossible to test every change on every
configuration.  We do our best to understand the code and how the change
will impact different configurations, but we also have to accept a
certain level of risk that we will break something, otherwise we will
never change / improve anything.  More eyes on the code, more people
testing regularly with different configurations and more configurations
on the Buildbot can help mitigate that.

> I'm also aiming to resolve this for GDB13, so doing this major change
> is a bit late in the sprint, right?

Indeed.  Then I think it's fine to introduce what you have, and then we
could replace it with a more general solution later.

> If you have a better idea on how to cache everything, like in your 3
> ideas above, please don't hesitate do share the implementation. :)

I imagine something simple.  A hash table in frame_info or somewhere
else, where frame_unwind_register_value would put values returned by
`next_frame->unwind->prev_register`, which would be returned directly on
subsequent calls.  This makes the assumption that a given frame info
object has the same unwinder throughout its lifetime, and that it will
return the same register value throughout its lifetime.  I don't know of
any situation where that is not true.

To keep the allocation / deallocation cost down, the hash tables could
be allocated on the frame obstack.

Simon
  
Luis Machado Jan. 20, 2023, 5:33 p.m. UTC | #4
On 1/20/23 17:28, Simon Marchi wrote:
> 
>> I suppose a generic cache might be useful in the long run, but it
>> would have an impact on other code paths and I'm not comfortable of
>> doing this major change now. Doing this change would imply that almost
>> everything related to unwinding is impacted in one way or another and
>> I suppose we would need to test that. With the solution I proposed,
>> only Arm Cortex would be impacted and the scope for testing would be
>> rather small.
> 
> I understand the feeling.  It's impossible to test every change on every
> configuration.  We do our best to understand the code and how the change
> will impact different configurations, but we also have to accept a
> certain level of risk that we will break something, otherwise we will
> never change / improve anything.  More eyes on the code, more people
> testing regularly with different configurations and more configurations
> on the Buildbot can help mitigate that.
> 
>> I'm also aiming to resolve this for GDB13, so doing this major change
>> is a bit late in the sprint, right?
> 
> Indeed.  Then I think it's fine to introduce what you have, and then we
> could replace it with a more general solution later.
> 
>> If you have a better idea on how to cache everything, like in your 3
>> ideas above, please don't hesitate do share the implementation. :)
> 
> I imagine something simple.  A hash table in frame_info or somewhere
> else, where frame_unwind_register_value would put values returned by
> `next_frame->unwind->prev_register`, which would be returned directly on
> subsequent calls.  This makes the assumption that a given frame info
> object has the same unwinder throughout its lifetime, and that it will
> return the same register value throughout its lifetime.  I don't know of
> any situation where that is not true.
> 
> To keep the allocation / deallocation cost down, the hash tables could
> be allocated on the frame obstack.

Maybe not a solution for now, but can't we use something like the trad-frame structs to cache
values/locations of registers for a given frame?
  
Simon Marchi Jan. 20, 2023, 5:43 p.m. UTC | #5
> Maybe not a solution for now, but can't we use something like the trad-frame structs to cache
> values/locations of registers for a given frame?

It's true that it's very similar, but I don't immediately see how I
would use it in this case.  The trad-frame stuff saves the info in its
own format, whereas here it would be nice to cache struct values
directly.  And it wouldn't be optimal for trad-frame to cache values,
because we would end up allocating values that might not be needed in
the end.

However, I see that trad-frame uses an array of size
gdbarch_num_cooked_regs.  It would make sense to use that here too,
instead of a hash table, it makes the lookups O(1).

Simon
  
Tom Tromey Jan. 20, 2023, 7:59 p.m. UTC | #6
>>>>> Torbjörn SVENSSON via Gdb-patches <gdb-patches@sourceware.org> writes:

> v2 -> v3:
> Addressed comments from Tom in
> https://sourceware.org/pipermail/gdb-patches/2023-January/195882.html

Sorry, I'm afraid you missed a few.

> +struct dwarf2_frame_fn_data
> +{
> +  /* The cookie to identify the custom function data by.  */
> +  fn_prev_register cookie;

The previous review mentioned changing the type of this, but honestly I
don't really care about that one, this is as good as anything now that
it's documented.

> +/* See frame.h.  */
> +
> +void *dwarf2_frame_get_fn_data (frame_info_ptr this_frame, void **this_cache,
> +				fn_prev_register cookie)

gdb style puts the function name at the start of the line.
There are many examples in the source.

> +void *dwarf2_frame_allocate_fn_data (frame_info_ptr this_frame,
> +				     void **this_cache,
> +				     fn_prev_register cookie,
> +				     unsigned long size)

Here too.

> +{
> +  struct dwarf2_frame_fn_data *fn_data = nullptr;
> +  struct dwarf2_frame_cache *cache
> +    = dwarf2_frame_cache (this_frame, this_cache);
> +
> +  /* First try to find an existing object.  */
> +  void *data = dwarf2_frame_get_fn_data (this_frame, this_cache, cookie);
> +  if (data)
> +    return data;

It seems to me that there is no need to do this check.
IMO it's fine to just assert that it isn't found.

thanks,
Tom
  
Torbjörn SVENSSON Jan. 25, 2023, 9:34 a.m. UTC | #7
On 2023-01-20 18:43, Simon Marchi wrote:
>> Maybe not a solution for now, but can't we use something like the trad-frame structs to cache
>> values/locations of registers for a given frame?
> 
> It's true that it's very similar, but I don't immediately see how I
> would use it in this case.  The trad-frame stuff saves the info in its
> own format, whereas here it would be nice to cache struct values
> directly.  And it wouldn't be optimal for trad-frame to cache values,
> because we would end up allocating values that might not be needed in
> the end.
> 
> However, I see that trad-frame uses an array of size
> gdbarch_num_cooked_regs.  It would make sense to use that here too,
> instead of a hash table, it makes the lookups O(1).
> 
> Simon

I think this is something that can be looked at later (GDB14?).

My first impression is that an array of pointers to struct value might 
work. If the pointer is nullptr, then there is no cached value, 
otherwise the pointer could be returned directly. But lest think of this 
after GDB13.
  
Torbjörn SVENSSON Jan. 25, 2023, 9:39 a.m. UTC | #8
On 2023-01-20 20:59, Tom Tromey wrote:
>>>>>> Torbjörn SVENSSON via Gdb-patches <gdb-patches@sourceware.org> writes:
> 
>> v2 -> v3:
>> Addressed comments from Tom in
>> https://sourceware.org/pipermail/gdb-patches/2023-January/195882.html
> 
> Sorry, I'm afraid you missed a few.
> 
>> +struct dwarf2_frame_fn_data
>> +{
>> +  /* The cookie to identify the custom function data by.  */
>> +  fn_prev_register cookie;
> 
> The previous review mentioned changing the type of this, but honestly I
> don't really care about that one, this is as good as anything now that
> it's documented.
> 
>> +/* See frame.h.  */
>> +
>> +void *dwarf2_frame_get_fn_data (frame_info_ptr this_frame, void **this_cache,
>> +				fn_prev_register cookie)
> 
> gdb style puts the function name at the start of the line.
> There are many examples in the source.
> 
>> +void *dwarf2_frame_allocate_fn_data (frame_info_ptr this_frame,
>> +				     void **this_cache,
>> +				     fn_prev_register cookie,
>> +				     unsigned long size)
> 
> Here too.
> 
>> +{
>> +  struct dwarf2_frame_fn_data *fn_data = nullptr;
>> +  struct dwarf2_frame_cache *cache
>> +    = dwarf2_frame_cache (this_frame, this_cache);
>> +
>> +  /* First try to find an existing object.  */
>> +  void *data = dwarf2_frame_get_fn_data (this_frame, this_cache, cookie);
>> +  if (data)
>> +    return data;
> 
> It seems to me that there is no need to do this check.
> IMO it's fine to just assert that it isn't found.
> 
> thanks,
> Tom

Thanks for the review.

I've corrected the points noted above. Still not sure why the 
check_GNU_style.py script in GCC contrib does not complain about this..

Anyway, is it okay for trunk with these changes or should I send a v4?

Kind regards,
Torbjörn
  
Tom Tromey Jan. 25, 2023, 4:11 p.m. UTC | #9
>>>>> "Torbjorn" == Torbjorn SVENSSON via Gdb-patches <gdb-patches@sourceware.org> writes:

Torbjorn> Anyway, is it okay for trunk with these changes or should I send a v4?

Yeah, it's ok.  Thank you.

Tom
  

Patch

diff --git a/gdb/dwarf2/frame.c b/gdb/dwarf2/frame.c
index bd36a6c7fb4..a81b2500cb2 100644
--- a/gdb/dwarf2/frame.c
+++ b/gdb/dwarf2/frame.c
@@ -831,6 +831,22 @@  dwarf2_fetch_cfa_info (struct gdbarch *gdbarch, CORE_ADDR pc,
 }
 
 
+/* Custom function data object for architecture specific prev_register
+   implementation.  Main purpose of this object is to allow caching of
+   expensive data lookups in the prev_register handling.  */
+
+struct dwarf2_frame_fn_data
+{
+  /* The cookie to identify the custom function data by.  */
+  fn_prev_register cookie;
+
+  /* The custom function data.  */
+  void *data;
+
+  /* Pointer to the next custom function data object for this frame.  */
+  struct dwarf2_frame_fn_data *next;
+};
+
 struct dwarf2_frame_cache
 {
   /* DWARF Call Frame Address.  */
@@ -862,6 +878,8 @@  struct dwarf2_frame_cache
      dwarf2_tailcall_frame_unwind unwinder so this field does not apply for
      them.  */
   void *tailcall_cache;
+
+  struct dwarf2_frame_fn_data *fn_data;
 };
 
 static struct dwarf2_frame_cache *
@@ -1221,6 +1239,49 @@  dwarf2_frame_prev_register (frame_info_ptr this_frame, void **this_cache,
     }
 }
 
+/* See frame.h.  */
+
+void *dwarf2_frame_get_fn_data (frame_info_ptr this_frame, void **this_cache,
+				fn_prev_register cookie)
+{
+  struct dwarf2_frame_fn_data *fn_data = nullptr;
+  struct dwarf2_frame_cache *cache
+    = dwarf2_frame_cache (this_frame, this_cache);
+
+  /* Find the object for the function.  */
+  for (fn_data = cache->fn_data; fn_data; fn_data = fn_data->next)
+    if (fn_data->cookie == cookie)
+      return fn_data->data;
+
+  return nullptr;
+}
+
+/* See frame.h.  */
+
+void *dwarf2_frame_allocate_fn_data (frame_info_ptr this_frame,
+				     void **this_cache,
+				     fn_prev_register cookie,
+				     unsigned long size)
+{
+  struct dwarf2_frame_fn_data *fn_data = nullptr;
+  struct dwarf2_frame_cache *cache
+    = dwarf2_frame_cache (this_frame, this_cache);
+
+  /* First try to find an existing object.  */
+  void *data = dwarf2_frame_get_fn_data (this_frame, this_cache, cookie);
+  if (data)
+    return data;
+
+  /* No object found, lets create a new instance.  */
+  fn_data = FRAME_OBSTACK_ZALLOC (struct dwarf2_frame_fn_data);
+  fn_data->cookie = cookie;
+  fn_data->data = frame_obstack_zalloc (size);
+  fn_data->next = cache->fn_data;
+  cache->fn_data = fn_data;
+
+  return fn_data->data;
+}
+
 /* Proxy for tailcall_frame_dealloc_cache for bottom frame of a virtual tail
    call frames chain.  */
 
diff --git a/gdb/dwarf2/frame.h b/gdb/dwarf2/frame.h
index 4444c153741..5643e557513 100644
--- a/gdb/dwarf2/frame.h
+++ b/gdb/dwarf2/frame.h
@@ -66,6 +66,9 @@  enum dwarf2_frame_reg_rule
 
 /* Register state.  */
 
+typedef struct value *(*fn_prev_register) (frame_info_ptr this_frame,
+					   void **this_cache, int regnum);
+
 struct dwarf2_frame_state_reg
 {
   /* Each register save state can be described in terms of a CFA slot,
@@ -78,8 +81,7 @@  struct dwarf2_frame_state_reg
       const gdb_byte *start;
       ULONGEST len;
     } exp;
-    struct value *(*fn) (frame_info_ptr this_frame, void **this_cache,
-			 int regnum);
+    fn_prev_register fn;
   } loc;
   enum dwarf2_frame_reg_rule how;
 };
@@ -262,4 +264,35 @@  extern int dwarf2_fetch_cfa_info (struct gdbarch *gdbarch, CORE_ADDR pc,
 				  const gdb_byte **cfa_start_out,
 				  const gdb_byte **cfa_end_out);
 
+
+/* Allocate a new instance of the function unique data.
+
+   The main purpose of this custom function data object is to allow caching the
+   value of expensive lookups in the prev_register implementation.
+
+   THIS_FRAME is the frame that the custom data object should be associated
+   with.
+   THIS_CACHE is the dwarf2 cache object to store the pointer on.
+   COOKIE is the key for the prev_function implementation.
+   SIZE is the size of the custom data object to allocate.  */
+
+extern void *dwarf2_frame_allocate_fn_data (frame_info_ptr this_frame,
+					    void **this_cache,
+					    fn_prev_register cookie,
+					    unsigned long size);
+
+/* Retrieve the function unique data for this frame or NULL if none exists.
+
+   The main purpose of this custom function data object is to allow caching the
+   value of expensive lookups in the prev_register implementation.
+
+   THIS_FRAME is the frame that the custom data object should be associated
+   with.
+   THIS_CACHE is the dwarf2 cache object to store the pointer on.
+   COOKIE is the key for the prev_function implementation.  */
+
+extern void *dwarf2_frame_get_fn_data (frame_info_ptr this_frame,
+				       void **this_cache,
+				       fn_prev_register cookie);
+
 #endif /* dwarf2-frame.h */