From: Lancelot SIX <lsix@lancelotsix.com>
To: Simon Marchi <simon.marchi@polymtl.ca>, gdb-patches@sourceware.org
Subject: Re: [PATCH 15/16] gdb: make cmd_list_element var an optional union
Date: Wed, 14 Jul 2021 17:12:38 +0000 [thread overview]
Message-ID: <20210714171238.vzccwpurh2izbkps@ubuntu.lan> (raw)
In-Reply-To: <20210714120851.3pfew5pgcdp6ezn6@ubuntu.lan>
On Wed, Jul 14, 2021 at 12:08:51PM +0000, Lancelot SIX via Gdb-patches wrote:
> On Wed, Jul 14, 2021 at 12:55:19AM -0400, Simon Marchi via Gdb-patches wrote:
> > The field cmd_list_element::var is used by set and show commands and
> > points to the variable affected by the set command and shown by the show
> > command. It is of type `void *` and cast to a pointer of the
> > appriopriate type on each use depending on the type of the setting
> > (cmd_list_element::var_type).
> >
> > I propose changing cmd_list_element to be a union of all the possible
> > pointer types. Fundamentally, this doesn't change much what is
> > happening. But I think this helps understand better how the field is
> > used and get a bit of type safety (while it is still possible to use the
> > wrong member at some point, we can't cast to something completely
> > unrealted).
>
> Hi,
>
> I happen to have been working on a different approach to the same
> problem. This was triggered when I was reading one of your previous
> patches where the surrounding codes had a lot of C style casts all
> around. My approach is arguably too complicated for the task, but it
> is meant to address (at least partly) the last point you discuss.
>
> Here is the gist of my approach: I add templated getter / setter to the
> var member, the template parameter being the var_type you expect. The
> usage looks like this, if you want to access the value of a
> cmd_list_element of var_types var_zuinteger:
>
> cmd_list_element *el;
> unsigned int val = el->get_var<var_zuinteger> (); // This type checks.
>
> The implementation for that requires a bit of boiler plate, but is not
> too complex to follow. I add a helper struct I use to describe the
> storage type used or each var_types:
>
> template<var_types T> struct var_types_storage { };
> template<> struct var_types_storage<var_integer> { using t = int; } ;
> template<> struct var_types_storage<var_uinteger> { using t = unsigned int; };
> template<> struct var_types_storage<var_string> { using t = char *; };
> template<> struct var_types_storage<var_enum> { using t = const char *; };
> // And so on for all the possible var type
>
> With that available, the getter / setter is quite easy to write:
>
> template<var_types T>
> typename var_types_storage<T>::t get_var () const
> {
> gdb_assert (T == this->var_type);
> return *static_cast<typename var_types_storage<T>::t *> (this->var);
> }
>
> template<var_types T>
> void set_var (typename var_types_storage<T>::t v)
> {
> gdb_assert (T == this->var_type);
> *static_cast<typename var_types_storage<T>::t *> (this->var> = v;
> }
>
> template<var_types T>
> void set_var_p (typename var_types_storage<T>::t v)
> {
> this->var_type = T;
> this->var = static_cast<void *> v;
> }
>
> add_set_or_show_cmd can also be updated in a similar way:
>
> template<var_types T> static struct cmd_list_element *
> add_set_or_show_cmd (const char *name,
> enum cmd_types type,
> enum command_class theclass,
> typename var_types_storage<T>::t *var,
> const char *doc,
> struct cmd_list_element **list)
> {
> struct cmd_list_element *c = add_cmd (name, theclass, doc, list);
> c->set_var_p<T> (var);
> // rest is unchanged
> return c;
> }
>
> // similar stuff for add_setshow_cmd_full
>
> Obviously there are a few differences with your approach.
>
> First, with my approach, it is possible to add the gdb_assert to check
> that when one accesses a value assuming it is of a given type, it
> actually is. However, there are few limitations with that, most
> notably in gdb/cli/cli-setshow.c, where some processing is common
> between cases (var_uinteger and var_zuinteger have quite a lot in
> common for example). At some point, the caller wants to call
> set_var<one of var_uinteger or var_zuinteger>, but obviously the
> assertion will fail half of the time. To go around this, I also use less
> type-safe setters / getters:
>
> template<typename T>
> const T get_var_as() { return *static_cast<T *> (this->var); }
> template<typename T>
> void set_var_as (T v) { *static_cast<T *> (this->var) = v; }
>
> Second, with the templated approach the caller does not need to know
> which member of the union he should access for a given var_types, the
> compiler will tel him if an incompatible types are used on the call
> site.
>
> On the other hand, my approach might be more difficult to follow / more
> surprising for someone not familiar with this kind of use of template
> specialization. It also requires a bit more boiler plate code (i.e. the
> var_types_storage struct).
>
> I still rely on type punning and a underlying void * pointer, which is
> not ideal. But at least, this becomes a detail of the implementation
> and can be ignored by the caller with all the casting done in just one
> place. The void * pointer should (in my approach) eventually be turned
> into a private member, which I have not done yet.
>
> Anyway, your approach is quite an improvement compared to the current
> type casting all over the place. I guess that we could, if you want,
> have a solution which which is a hybrid of both (i.e. a union instead of
> a void*, and templated access functions).
>
> Obviously I am a bit biased toward my approach. Any thoughts? I’ll
> pause my work on my patch for the moment.
>
> I still need to give a proper read to your patch series, I just skimmed
> through to compare our approaches for the moment.
>
> Best,
> Lancelot.
Actually, I have been playing around with it a bit more today, and with
extra boiler plate, I can remove the get_var_as accessor, and have a
more all-rounded get_var.
It becomes possible to write something like:
// No matter if we are looking at a var_uinteger or a
// var_zuinteger, the underlying type we are interested in is
// a unsigned int.
unsigned int *foo = c->get_var<var_uinteger, var_zuinteger> ();
In this case, get_var will even figure the common ground for all
possibilities the user asks for. The intended use case is when printing
the value of a string (ish) variable, and we want to cover multiple
scenarios including var_enum and var_string, we can write something
like:
// c is a const char *, but would be char * if var_enum was not
// in the list.
auto c = v.get_var<var_enum, var_string, var_filename> ();
The code could look like something like this (I am sure it is possible
to have a simpler solution to create a similar result):
template<var_types... Ts>
struct var_types_storage_helper;
template<var_types T>
struct var_types_storage_helper<T>
{
static constexpr bool all_same = true;
using type = typename var_types_storage<T>::t;
static constexpr bool covers_var_type (var_types t)
{
return t == T;
}
};
template<var_types T, var_types U>
struct var_types_storage_helper<T, U>
{
static constexpr bool all_same = std::is_same<typename var_types_storage<T>::t, typename var_types_storage<U>::t>::value;
using type = typename var_types_storage<T>::t;
static constexpr bool covers_var_type (var_types t)
{
return var_types_storage_helper<T>::covers_var_type (t) || var_types_storage_helper<U>::covers_var_type (t);
}
};
template<var_types T, var_types U, var_types... Us>
struct var_types_storage_helper<T, U, Us...>
{
static constexpr bool all_same = var_types_storage_helper<T, U>::all_same && var_types_storage_helper<T, Us...>::all_same;
using type = typename var_types_storage<T>::t;
static constexpr bool covers_var_type (var_types t)
{
return var_types_storage_helper<T> (t) || var_types_storage_helper<U, Us...>::covers_var_type (t);
}
};
and the accessors become:
template<var_types... T>
typename std::common_type<typename var_types_storage<T>::t...>::type get_var() const
{
assert (var_types_storage_helper<T...>::covers_var_type (this->var_type));
return *static_cast<typename std::common_type<typename var_types_storage<T>::t...>::type *>(this->var);
}
template<var_types... T>
void set_var(typename var_types_storage_helper<T...>::type v)
{
assert (var_types_storage_helper<T...>::covers_var_type (this->var_type));
static_assert (var_types_storage_helper<T...>::all_same);
*static_cast<typename var_types_storage_helper<T...>::type *> (this->var) = v;
}
template<var_types T>
void set_var_p (typename var_types_storage<T>::t *v)
{
this->var_type = T;
this->var = static_cast<void *> (v);
}
I guess improves the type safety side of things, but the
implementation definitely gets more complicated. I am not sure the
added checks come at a reasonable price.
This should all be valid c++11.
Any thoughts?
Best,
Lancelot.
>
>
> >
> > I wrapped the union in an optional, because we need to check in some
> > spots whether var is set or not. I think that conceptually, an optional
> > makes the most sense. Another option would be to pick an arbitrary
> > member of the union and compare it to nullptr, whenever we want to know
> > whether var is set, but that seems a bit more hack-ish.
> >
> > A note on the Guile situation: something that makes our life a little
> > bit complicated is that is possible to assign and read the value of a
> > Guile-created parameter that is not yet registered (see previous patch
> > that adds a test for this). It would have been much more simple to say
> > that this is simply not supported anymore, but that could break existing
> > scripts, so I don't think it is a good idea. In some cases, for example
> > when printing the value of a built-in parameter, we have access to a
> > show command and its union setting_variable. When we have an
> > un-registered Guile param, we don't have a show command associated to
> > it, but we can pass the parameter's pascm_variable union, stored in
> > param_smob. Because we have these two kinds of incompatible parameters,
> > we need two versions of the pascm_param_value function.
> >
> > Change-Id: I74a6be2a9188520dbdac4cb123c08e00ebb40a91
> > ---
> > gdb/cli/cli-cmds.c | 48 +++++++++-----
> > gdb/cli/cli-decode.c | 113 +++++++++++++++++++++++--------
> > gdb/cli/cli-decode.h | 31 ++++++++-
> > gdb/cli/cli-setshow.c | 87 +++++++++++++-----------
> > gdb/guile/scm-param.c | 125 +++++++++++++++++++++++++++++++----
> > gdb/python/py-param.c | 8 ++-
> > gdb/python/python-internal.h | 2 +-
> > gdb/python/python.c | 35 +++++++---
> > gdb/remote.c | 2 +-
> > 9 files changed, 340 insertions(+), 111 deletions(-)
next prev parent reply other threads:[~2021-07-14 17:12 UTC|newest]
Thread overview: 50+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-07-14 4:55 [PATCH 00/16] Bunch of commands related cleanups Simon Marchi
2021-07-14 4:55 ` [PATCH 01/16] gdb/testsuite: split gdb.python/py-parameter.exp in procs Simon Marchi
2021-07-14 4:55 ` [PATCH 02/16] gdb.base/setshow.exp: use save_vars to save/restore gdb_prompt Simon Marchi
2021-07-14 4:55 ` [PATCH 03/16] gdb.base/setshow.exp: split in procs Simon Marchi
2021-07-14 4:55 ` [PATCH 04/16] gdb.base/setshow.exp: fix duplicate test name Simon Marchi
2021-07-14 4:55 ` [PATCH 05/16] gdb: un-share set_inferior_cwd declaration Simon Marchi
2021-07-14 4:55 ` [PATCH 06/16] gdb: remove inferior::{argc,argv} Simon Marchi
2021-07-14 4:55 ` [PATCH 07/16] gdb: add setter/getter for inferior arguments Simon Marchi
2021-07-14 4:55 ` [PATCH 08/16] gdb: add setter/getter for inferior cwd Simon Marchi
2021-07-14 4:55 ` [PATCH 09/16] gdb: make inferior::m_args an std::string Simon Marchi
2021-07-14 4:55 ` [PATCH 10/16] gdb: make inferior::m_cwd " Simon Marchi
2021-07-14 4:55 ` [PATCH 11/16] gdb: make inferior::m_terminal " Simon Marchi
2021-07-14 4:55 ` [PATCH 12/16] gdb: rename cfunc to simple_func Simon Marchi
2021-07-14 4:55 ` [PATCH 13/16] gdb: remove cmd_list_element::function::sfunc Simon Marchi
2021-07-28 19:10 ` Tom Tromey
2021-07-28 21:17 ` Simon Marchi
2021-07-29 17:33 ` Tom Tromey
2021-07-14 4:55 ` [PATCH 14/16] gdb/testsuite: test get/set value of unregistered Guile parameter Simon Marchi
2021-07-23 19:42 ` Simon Marchi
2021-07-14 4:55 ` [PATCH 15/16] gdb: make cmd_list_element var an optional union Simon Marchi
2021-07-14 12:08 ` Lancelot SIX
2021-07-14 17:12 ` Lancelot SIX [this message]
2021-07-14 19:22 ` Simon Marchi
2021-07-14 23:22 ` Lancelot SIX
2021-07-19 14:32 ` Simon Marchi
2021-07-19 19:52 ` Simon Marchi
2021-07-20 23:03 ` Lancelot SIX
2021-07-23 19:56 ` Simon Marchi
2021-07-23 20:46 ` Lancelot SIX
2021-07-23 21:15 ` Simon Marchi
2021-07-23 22:55 ` Lancelot SIX
2021-07-24 2:04 ` Simon Marchi
2021-07-28 20:13 ` Tom Tromey
2021-07-28 20:45 ` Lancelot SIX
2021-07-29 17:47 ` Tom Tromey
2021-07-29 20:12 ` Lancelot SIX
2021-07-30 2:09 ` Simon Marchi
2021-07-30 17:47 ` Lancelot SIX
2021-07-18 15:44 ` Lancelot SIX
2021-07-19 14:19 ` Simon Marchi
2021-07-19 20:58 ` Lancelot SIX
2021-07-28 19:47 ` Tom Tromey
2021-07-28 20:59 ` Simon Marchi
2021-07-29 17:41 ` Tom Tromey
2021-07-29 17:44 ` Simon Marchi
2021-07-29 17:49 ` Tom Tromey
2021-07-29 18:00 ` Simon Marchi
2021-07-14 4:55 ` [PATCH 16/16] gdb: make string-like set show commands use std::string variable Simon Marchi
2021-07-28 20:27 ` Tom Tromey
2021-07-28 21:03 ` Simon Marchi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210714171238.vzccwpurh2izbkps@ubuntu.lan \
--to=lsix@lancelotsix.com \
--cc=gdb-patches@sourceware.org \
--cc=simon.marchi@polymtl.ca \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).