[RFA,1/5] Remove some ui_out-related cleanups from Python

Message ID 20170115134253.24018-2-tom@tromey.com
State New, archived
Headers

Commit Message

Tom Tromey Jan. 15, 2017, 1:42 p.m. UTC
  This patch introduces a bit of infrastructure -- namely, a minimal
std::optional analogue called gdb::optional, and an RAII template
class that works like make_cleanup_ui_out_tuple_begin_end or
make_cleanup_ui_out_list_begin_end -- and then uses these in the
Python code.  This removes a number of cleanups and generally
simplifies this code.

std::optional is only available in C++17.  Normally I would have had
this code check __cplusplus, but my gcc apparently isn't new enough to
find <optional>, even with -std=c++1z; so, because I could not test
it, the patch does not do this.

2017-01-15  Tom Tromey  <tom@tromey.com>

	* ui-out.h (ui_out_emit_type): New class.
	(ui_out_emit_tuple, ui_out_emit_list): New typedefs.
	* python/py-framefilter.c (py_print_single_arg): Use gdb::optional
	and ui_out_emit_tuple.
	(enumerate_locals): Likewise.
	(py_mi_print_variables, py_print_locals, py_print_args): Use
	ui_out_emit_list.
	(py_print_frame): Use gdb::optional, ui_out_emit_tuple,
	ui_out_emit_list.
	* common/gdb_option.h: New file.
---
 gdb/ChangeLog               |  13 +++
 gdb/common/gdb_option.h     |  87 ++++++++++++++++++
 gdb/python/py-framefilter.c | 213 ++++++++++++--------------------------------
 gdb/ui-out.h                |  33 +++++++
 4 files changed, 189 insertions(+), 157 deletions(-)
 create mode 100644 gdb/common/gdb_option.h
  

Comments

Simon Marchi Jan. 15, 2017, 9:52 p.m. UTC | #1
On 2017-01-15 08:42, Tom Tromey wrote:
> This patch introduces a bit of infrastructure -- namely, a minimal
> std::optional analogue called gdb::optional, and an RAII template
> class that works like make_cleanup_ui_out_tuple_begin_end or
> make_cleanup_ui_out_list_begin_end -- and then uses these in the
> Python code.  This removes a number of cleanups and generally
> simplifies this code.
> 
> std::optional is only available in C++17.  Normally I would have had
> this code check __cplusplus, but my gcc apparently isn't new enough to
> find <optional>, even with -std=c++1z; so, because I could not test
> it, the patch does not do this.
> 
> 2017-01-15  Tom Tromey  <tom@tromey.com>
> 
> 	* ui-out.h (ui_out_emit_type): New class.
> 	(ui_out_emit_tuple, ui_out_emit_list): New typedefs.
> 	* python/py-framefilter.c (py_print_single_arg): Use gdb::optional
> 	and ui_out_emit_tuple.
> 	(enumerate_locals): Likewise.
> 	(py_mi_print_variables, py_print_locals, py_print_args): Use
> 	ui_out_emit_list.
> 	(py_print_frame): Use gdb::optional, ui_out_emit_tuple,
> 	ui_out_emit_list.
> 	* common/gdb_option.h: New file.
> ---
>  gdb/ChangeLog               |  13 +++
>  gdb/common/gdb_option.h     |  87 ++++++++++++++++++

Any reason you did not name this gdb_optional.h?

Otherwise, it looks really nice.
  
Trevor Saunders Jan. 16, 2017, 11:30 a.m. UTC | #2
> +++ b/gdb/common/gdb_option.h

might be nice to put it in include/ but fine to do that later when
something else actually wants it.

> +/* This class attempts to be a compatible subset of std::optional,
> +   which is slated to be available in C++17.  This class optionally
> +   holds an object of some type -- by default it is constructed not
> +   holding an object, but later the object can be "emplaced".  This is
> +   similar to using std::unique_ptr, but stack allocation is
> +   guaranteed.  */

 wording nit, but stack isn't quiet what you want there, I can imagine
 putting an optional<T> in some object that lives on the heap.

> +template<typename T>
> +class optional
> +{
> +public:
> +
> +  optional ()
> +    : m_instantiated (false)
> +  {
> +  }
> +
> +  ~optional ()
> +  {
> +    if (m_instantiated)
> +      destroy ();
> +  }
> +
> +  /* These aren't deleted in std::optional, but it was simpler to
> +     delete them here, because currently the users of this class don't
> +     need them, and making them depend on the definition of T is
> +     somewhat complicated.  */

I think you can make <type_traits> do most of it, but fair enough.

> +  /* True if the object was ever emplaced.  */
> +  bool m_instantiated;
> +
> +  /* The object.  */
> +  union
> +  {
> +    struct { } m_dummy;
> +    T m_item;
> +  };

It doesn't matter yet, but space wise it would be better to put the bool
last right? For example if sizeof(T) is 6, or if the optional is in some
larger structure with a bool next.

Trev
  
Tom Tromey Jan. 16, 2017, 4:12 p.m. UTC | #3
>>>>> "Simon" == Simon Marchi <simon.marchi@polymtl.ca> writes:

Simon> Any reason you did not name this gdb_optional.h?

No, no good reason.  I'll rename it.

Tom
  
Pedro Alves Feb. 8, 2017, 5:28 p.m. UTC | #4
On 01/16/2017 11:30 AM, Trevor Saunders wrote:
>> +++ b/gdb/common/gdb_option.h
> 
> might be nice to put it in include/ but fine to do that later when
> something else actually wants it.

I've been thinking about putting all these utilities and
later-standards replacements we're coming up with in its own
directory/namespace.  I had thought of "gtl", for "gdb template
library", or "gnu template library" if other projects want to
reuse it.  (I think "gnu" is kind of taken by gnulib / libgnu.a)
One difficulty with putting such a thing at the top level is that
gdb relies on gnulib headers (that exist under gdb/) while
other toolchain components don't, yet, at least.

>> > +  /* These aren't deleted in std::optional, but it was simpler to
>> > +     delete them here, because currently the users of this class don't
>> > +     need them, and making them depend on the definition of T is
>> > +     somewhat complicated.  */
> I think you can make <type_traits> do most of it, but fair enough.
> 

Back around <https://sourceware.org/ml/gdb-patches/2016-11/msg00368.html>
I backported C++17's optional to C++11.  I still have that branch
somewhere locally...  /me finds it and pushes to github.  Here:

 https://github.com/palves/gdb/commits/palves/gdb-import-gcc-optional

This <https://github.com/palves/gdb/commit/880ab485c5873eb0a8ab1dca75e624ec2a064fe8>
contains the local changes I had made to to port it to C++11.  I was surprised
that other than portable type traits stuff that's missing in C++11, the
only thing that C++11 loses is constexpr-ness in some cases.  Now,
the result is of course much more code than what Tromey is proposing.
It probably does make sense to start with something simpler and
upgrade when/if we find a need.  OTOH, looks like the current patch
doesn't have accessors for the wrapped value, so it seems like we'll
at least need to be extend it in that direction in no time.

>> +  /* True if the object was ever emplaced.  */
>> +  bool m_instantiated;
>> +
>> +  /* The object.  */
>> +  union
>> +  {
>> +    struct { } m_dummy;
>> +    T m_item;
>> +  };
> 
> It doesn't matter yet, but space wise it would be better to put the bool
> last right? For example if sizeof(T) is 6, or if the optional is in some
> larger structure with a bool next.

Agreed.  Seems like that's what gcc's optional does.

I also agree with Simon's suggestion for renaming the file.

Patch LGTM with Trevor's and Simon's nits  addressed.

I wondered about making m_instantiated a char, so that optional<T> would
pack even better when sizeof or alignof T is small, and thus ends up being
no cost in those cases space-wise.  Though maybe that ends up being
worse / not so efficient generated code -wise, or GCC would do it too?
In any case, since GCC doesn't do that, if/when we ever move to C++17,
we'd lose that again anyway.

Thanks,
Pedro Alves
  
Pedro Alves Feb. 8, 2017, 10:26 p.m. UTC | #5
On 02/08/2017 05:28 PM, Pedro Alves wrote:

> I wondered about making m_instantiated a char, so that optional<T> would
> pack even better when sizeof or alignof T is small, and thus ends up being
> no cost in those cases space-wise.  Though maybe that ends up being
> worse / not so efficient generated code -wise, or GCC would do it too?
> In any case, since GCC doesn't do that, if/when we ever move to C++17,
> we'd lose that again anyway.

Eh, now that I check I see that sizeof bool == 1, and looking at GCC's sources,
I see it's like that for almost all ports.  Somehow I misremembered
this and thought it was sizeof(int).

Thanks,
Pedro Alves
  
Tom Tromey Feb. 8, 2017, 11:04 p.m. UTC | #6
>>>>> "Pedro" == Pedro Alves <palves@redhat.com> writes:

Pedro> One difficulty with putting such a thing at the top level is that
Pedro> gdb relies on gnulib headers (that exist under gdb/) while
Pedro> other toolchain components don't, yet, at least.

gnulib should just be at top-level, but I realize that's a pain.

Pedro> It probably does make sense to start with something simpler and
Pedro> upgrade when/if we find a need.  OTOH, looks like the current patch
Pedro> doesn't have accessors for the wrapped value, so it seems like we'll
Pedro> at least need to be extend it in that direction in no time.

I had forgotten about this code.  I don't mind using it instead.

Pedro> Patch LGTM with Trevor's and Simon's nits  addressed.

I have that done locally, but I've just been waiting until your series
lands, so I can rebase.  And then maybe the option stuff wouldn't be
needed anyway?  I don't remember the details from your branch, I was
just waiting for the big rebase to find out.

Tom
  
Pedro Alves Feb. 8, 2017, 11:51 p.m. UTC | #7
On 02/08/2017 11:04 PM, Tom Tromey wrote:
>>>>>> "Pedro" == Pedro Alves <palves@redhat.com> writes:
> 
> Pedro> One difficulty with putting such a thing at the top level is that
> Pedro> gdb relies on gnulib headers (that exist under gdb/) while
> Pedro> other toolchain components don't, yet, at least.
> 
> gnulib should just be at top-level, but I realize that's a pain.
> 
> Pedro> It probably does make sense to start with something simpler and
> Pedro> upgrade when/if we find a need.  OTOH, looks like the current patch
> Pedro> doesn't have accessors for the wrapped value, so it seems like we'll
> Pedro> at least need to be extend it in that direction in no time.
> 
> I had forgotten about this code.  I don't mind using it instead.

Let's start with your version, and leave the door open.  The version I have
would need a bit of cleaning, re-checking if GCC's implementation changed
meanwhile (ISTR there have been recent patches to address some DRs), check
that it compiles against clang and older gcc's (4.8, etc.).  I already have too
many balls in the air ATM.

> Pedro> Patch LGTM with Trevor's and Simon's nits  addressed.
> 
> I have that done locally, but I've just been waiting until your series
> lands, so I can rebase.  And then maybe the option stuff wouldn't be
> needed anyway?  I don't remember the details from your branch, I was
> just waiting for the big rebase to find out.

My series is all in master.  I think we'll still need your patch, since I only
touched ui_files.  I haven't done anything with ui_out list/tuple building.

Thanks,
Pedro Alves
  
Matt Rice Feb. 9, 2017, 4:34 a.m. UTC | #8
On Wed, Feb 8, 2017 at 3:51 PM, Pedro Alves <palves@redhat.com> wrote:

>> Pedro> Patch LGTM with Trevor's and Simon's nits  addressed.
>>
>> I have that done locally, but I've just been waiting until your series
>> lands, so I can rebase.  And then maybe the option stuff wouldn't be
>> needed anyway?  I don't remember the details from your branch, I was
>> just waiting for the big rebase to find out.
>
> My series is all in master.  I think we'll still need your patch, since I only
> touched ui_files.  I haven't done anything with ui_out list/tuple building.

I haven't been following much lately due to programming mostly in
languages unsupported by gdb apologies if this has been hashed out
before.

Wondering if this would be a good opportunity to try and transition
ui_out_list construction into a more type safe manner, e.g.

result ==> variable "=" value
list ==> "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]"

currently when building a list, it didn't specify whether you were
declaring a list of results or a list of values, unless/until the
first value or result was added, and IIRC this | property wasn't
really explicitly enforced.

it'd be nice to migrate this to something like:
result_list, value_list, deprecated_unspecified_list, It would be nice
to know if/when you guys think it would be a convenient time to
introduce such a change so I could get back up to speed...
  
Pedro Alves Feb. 9, 2017, 12:48 p.m. UTC | #9
Hi Matt,

On 02/09/2017 04:34 AM, Matt Rice wrote:

> Wondering if this would be a good opportunity to try and transition
> ui_out_list construction into a more type safe manner, e.g.
> result ==> variable "=" value
> list ==> "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]"
> 
> currently when building a list, it didn't specify whether you were
> declaring a list of results or a list of values, unless/until the
> first value or result was added, and IIRC this | property wasn't
> really explicitly enforced.

More type-safety sounds good to me.  :-)

Can you give an example of a command that outputs a result list,
and an example of a command that outputs a value list?

> it'd be nice to migrate this to something like:
> result_list, value_list, deprecated_unspecified_list, It would be nice
> to know if/when you guys think it would be a convenient time to
> introduce such a change so I could get back up to speed...

Simon has already C++-fyed ui_out in master, so this seems like
a good time to me.

Thanks,
Pedro Alves
  
Pedro Alves Feb. 9, 2017, 12:51 p.m. UTC | #10
On 02/09/2017 12:48 PM, Pedro Alves wrote:
> Hi Matt,
> 
> On 02/09/2017 04:34 AM, Matt Rice wrote:
> 
>> Wondering if this would be a good opportunity to try and transition
>> ui_out_list construction into a more type safe manner, e.g.
>> result ==> variable "=" value
>> list ==> "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]"
>>
>> currently when building a list, it didn't specify whether you were
>> declaring a list of results or a list of values, unless/until the
>> first value or result was added, and IIRC this | property wasn't
>> really explicitly enforced.
> 
> More type-safety sounds good to me.  :-)
> 
> Can you give an example of a command that outputs a result list,
> and an example of a command that outputs a value list?
> 
>> it'd be nice to migrate this to something like:
>> result_list, value_list, deprecated_unspecified_list, It would be nice
>> to know if/when you guys think it would be a convenient time to
>> introduce such a change so I could get back up to speed...
> 
> Simon has already C++-fyed ui_out in master, so this seems like
> a good time to me.

Though, to be clear, IIUC, you're talking about changing
GDB internals without affecting the resulting MI output, right?

Thanks,
Pedro Alves
  
Matt Rice Feb. 9, 2017, 3:46 p.m. UTC | #11
On Thu, Feb 9, 2017 at 4:51 AM, Pedro Alves <palves@redhat.com> wrote:
> On 02/09/2017 12:48 PM, Pedro Alves wrote:
>> Hi Matt,
>>
>> On 02/09/2017 04:34 AM, Matt Rice wrote:
>>
>>> Wondering if this would be a good opportunity to try and transition
>>> ui_out_list construction into a more type safe manner, e.g.
>>> result ==> variable "=" value
>>> list ==> "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]"
>>>
>>> currently when building a list, it didn't specify whether you were
>>> declaring a list of results or a list of values, unless/until the
>>> first value or result was added, and IIRC this | property wasn't
>>> really explicitly enforced.
>>
>> More type-safety sounds good to me.  :-)
>>
>> Can you give an example of a command that outputs a result list,
>> and an example of a command that outputs a value list?
>>

There is a good example of a command which outputs both in the
documentation (in the example below "The -break-insert command")

interpreter-exec mi -break-insert main
interpreter-exec mi -break-insert -t foo
interpreter-exec mi -break-list

in the output of the -break-list command there is a list of results of the form:
body=[bkpt=... , bkpt=...]

https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI-Breakpoint-Commands.html#g_t_002dbreak_002dinsert

I picked that one specifically because it shows the duplicate "bkpt" keys.
inside those bkpt={... thread-groups=["i1"] ... } should be a list of values.

interpreter-exec mi -list-thread-groups is a slightly easier to read
one that returns a list of values (tuples)
^done,groups=[tuple, ...]

Additionally of note is:
https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI-Output-Syntax.html#GDB_002fMI-Output-Syntax
* New gdb/mi commands should only output lists containing values.

So, i suppose result_list should be some variation on
compat_only_result_list or some such...

>>> it'd be nice to migrate this to something like:
>>> result_list, value_list, deprecated_unspecified_list, It would be nice
>>> to know if/when you guys think it would be a convenient time to
>>> introduce such a change so I could get back up to speed...
>>
>> Simon has already C++-fyed ui_out in master, so this seems like
>> a good time to me.
>
> Though, to be clear, IIUC, you're talking about changing
> GDB internals without affecting the resulting MI output, right?

Yes, internals change only,
  
Simon Marchi Feb. 9, 2017, 4:03 p.m. UTC | #12
On 2017-02-09 10:46, Matt Rice wrote:
> There is a good example of a command which outputs both in the
> documentation (in the example below "The -break-insert command")
> 
> interpreter-exec mi -break-insert main
> interpreter-exec mi -break-insert -t foo
> interpreter-exec mi -break-list
> 
> in the output of the -break-list command there is a list of results of 
> the form:
> body=[bkpt=... , bkpt=...]
> 
> https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI-Breakpoint-Commands.html#g_t_002dbreak_002dinsert
> 
> I picked that one specifically because it shows the duplicate "bkpt" 
> keys.
> inside those bkpt={... thread-groups=["i1"] ... } should be a list of 
> values.
> 
> interpreter-exec mi -list-thread-groups is a slightly easier to read
> one that returns a list of values (tuples)
> ^done,groups=[tuple, ...]
> 
> Additionally of note is:
> https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI-Output-Syntax.html#GDB_002fMI-Output-Syntax
> * New gdb/mi commands should only output lists containing values.
> 
> So, i suppose result_list should be some variation on
> compat_only_result_list or some such...

I like the idea of enforcing proper MI output through code.  One 
situation I know GDB outputs broken MI is with multiple locations 
breakpoints.  See:

   https://sourceware.org/bugzilla/show_bug.cgi?id=14733

It'd be nice if the MI building API didn't even let us do that.

>>>> it'd be nice to migrate this to something like:
>>>> result_list, value_list, deprecated_unspecified_list, It would be 
>>>> nice
>>>> to know if/when you guys think it would be a convenient time to
>>>> introduce such a change so I could get back up to speed...

Yes that would be great.  I would say that the most convenient time is 
when you have time to contribute to the project :).

About the naming, I think that "result_list" and "value_list" is 
confusing, since a list containing results is exactly what is 
deprecated.  What about just "tuple" and "list"?  According to the 
syntax and the note you mentioned, a tuple ({...}) will only accept 
results (foo=bar) and a list ([...]) will only accept values.  And we'd 
have deprecated_result_list to build lists containing results.

Simon
  
Trevor Saunders Feb. 10, 2017, 6:58 a.m. UTC | #13
On Wed, Feb 08, 2017 at 05:28:11PM +0000, Pedro Alves wrote:
> On 01/16/2017 11:30 AM, Trevor Saunders wrote:
> >> +++ b/gdb/common/gdb_option.h
> > 
> > might be nice to put it in include/ but fine to do that later when
> > something else actually wants it.
> 
> I've been thinking about putting all these utilities and
> later-standards replacements we're coming up with in its own
> directory/namespace.  I had thought of "gtl", for "gdb template
> library", or "gnu template library" if other projects want to
> reuse it.  (I think "gnu" is kind of taken by gnulib / libgnu.a)
> One difficulty with putting such a thing at the top level is that
> gdb relies on gnulib headers (that exist under gdb/) while
> other toolchain components don't, yet, at least.

yeah. I've been meaning to get to that too.  I think for some of these
things that may not matter, but for some I'm sure it does.

> >> > +  /* These aren't deleted in std::optional, but it was simpler to
> >> > +     delete them here, because currently the users of this class don't
> >> > +     need them, and making them depend on the definition of T is
> >> > +     somewhat complicated.  */
> > I think you can make <type_traits> do most of it, but fair enough.
> > 
> 
> Back around <https://sourceware.org/ml/gdb-patches/2016-11/msg00368.html>
> I backported C++17's optional to C++11.  I still have that branch
> somewhere locally...  /me finds it and pushes to github.  Here:
> 
>  https://github.com/palves/gdb/commits/palves/gdb-import-gcc-optional
> 
> This <https://github.com/palves/gdb/commit/880ab485c5873eb0a8ab1dca75e624ec2a064fe8>
> contains the local changes I had made to to port it to C++11.  I was surprised
> that other than portable type traits stuff that's missing in C++11, the
> only thing that C++11 loses is constexpr-ness in some cases.  Now,
> the result is of course much more code than what Tromey is proposing.
> It probably does make sense to start with something simpler and
> upgrade when/if we find a need.  OTOH, looks like the current patch
> doesn't have accessors for the wrapped value, so it seems like we'll
> at least need to be extend it in that direction in no time.

yeah, I think start small is a very reasonable approach, though I'd
agree you'll want to get at the wrapped value pretty soon.

Trev
  

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 39d66b8..8a2848f 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,16 @@ 
+2017-01-15  Tom Tromey  <tom@tromey.com>
+
+	* ui-out.h (ui_out_emit_type): New class.
+	(ui_out_emit_tuple, ui_out_emit_list): New typedefs.
+	* python/py-framefilter.c (py_print_single_arg): Use gdb::optional
+	and ui_out_emit_tuple.
+	(enumerate_locals): Likewise.
+	(py_mi_print_variables, py_print_locals, py_print_args): Use
+	ui_out_emit_list.
+	(py_print_frame): Use gdb::optional, ui_out_emit_tuple,
+	ui_out_emit_list.
+	* common/gdb_option.h: New file.
+
 2017-01-13  Yao Qi  <yao.qi@linaro.org>
 
 	* remote.c (REMOTE_DEBUG_MAX_CHAR): New macro.
diff --git a/gdb/common/gdb_option.h b/gdb/common/gdb_option.h
new file mode 100644
index 0000000..e2ac107
--- /dev/null
+++ b/gdb/common/gdb_option.h
@@ -0,0 +1,87 @@ 
+/* An optional object.
+
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   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 <http://www.gnu.org/licenses/>.  */
+
+#ifndef GDB_OPTIONAL_H
+#define GDB_OPTIONAL_H
+
+namespace gdb
+{
+
+/* This class attempts to be a compatible subset of std::optional,
+   which is slated to be available in C++17.  This class optionally
+   holds an object of some type -- by default it is constructed not
+   holding an object, but later the object can be "emplaced".  This is
+   similar to using std::unique_ptr, but stack allocation is
+   guaranteed.  */
+template<typename T>
+class optional
+{
+public:
+
+  optional ()
+    : m_instantiated (false)
+  {
+  }
+
+  ~optional ()
+  {
+    if (m_instantiated)
+      destroy ();
+  }
+
+  /* These aren't deleted in std::optional, but it was simpler to
+     delete them here, because currently the users of this class don't
+     need them, and making them depend on the definition of T is
+     somewhat complicated.  */
+  optional (const optional &other) = delete;
+  optional<T> &operator= (const optional &other) = delete;
+
+  template<typename... Args>
+  void emplace (Args &&... args)
+  {
+    if (m_instantiated)
+      destroy ();
+    new (&m_item) T (std::forward<Args>(args)...);
+    m_instantiated = true;
+  }
+
+private:
+
+  /* Destroy the object.  */
+  void destroy ()
+  {
+    gdb_assert (m_instantiated);
+    m_instantiated = false;
+    m_item.~T ();
+  }
+
+  /* True if the object was ever emplaced.  */
+  bool m_instantiated;
+
+  /* The object.  */
+  union
+  {
+    struct { } m_dummy;
+    T m_item;
+  };
+};
+
+}
+
+#endif /* GDB_OPTIONAL_H */
diff --git a/gdb/python/py-framefilter.c b/gdb/python/py-framefilter.c
index bdd9911..ae5197a 100644
--- a/gdb/python/py-framefilter.c
+++ b/gdb/python/py-framefilter.c
@@ -31,6 +31,7 @@ 
 #include "mi/mi-cmds.h"
 #include "python-internal.h"
 #include "py-ref.h"
+#include "common/gdb_option.h"
 
 enum mi_print_types
 {
@@ -373,7 +374,7 @@  py_print_single_arg (struct ui_out *out,
 
   TRY
     {
-      struct cleanup *cleanups = make_cleanup (null_cleanup, NULL);
+      gdb::optional<ui_out_emit_tuple> maybe_tuple;
 
       /*  MI has varying rules for tuples, but generally if there is only
       one element in each item in the list, do not start a tuple.  The
@@ -384,7 +385,7 @@  py_print_single_arg (struct ui_out *out,
       if (out->is_mi_like_p ())
 	{
 	  if (print_args_field || args_type != NO_VALUES)
-	    make_cleanup_ui_out_tuple_begin_end (out, NULL);
+	    maybe_tuple.emplace (out, nullptr);
 	}
 
       annotate_arg_begin ();
@@ -394,9 +395,10 @@  py_print_single_arg (struct ui_out *out,
       if (fa != NULL)
 	{
 	  struct ui_file *stb;
+	  struct cleanup *cleanup;
 
 	  stb = mem_fileopen ();
-	  make_cleanup_ui_file_delete (stb);
+	  cleanup = make_cleanup_ui_file_delete (stb);
 	  fprintf_symbol_filtered (stb, SYMBOL_PRINT_NAME (fa->sym),
 				   SYMBOL_LANGUAGE (fa->sym),
 				   DMGL_PARAMS | DMGL_ANSI);
@@ -414,6 +416,7 @@  py_print_single_arg (struct ui_out *out,
 	      fputs_filtered ("@entry", stb);
 	    }
 	  out->field_stream ("name", stb);
+	  do_cleanups (cleanup);
 	}
       else
 	/* Otherwise, just output the name.  */
@@ -433,10 +436,7 @@  py_print_single_arg (struct ui_out *out,
       if (args_type == MI_PRINT_SIMPLE_VALUES && val != NULL)
 	{
 	  if (py_print_type (out, val) == EXT_LANG_BT_ERROR)
-	    {
-	      retval = EXT_LANG_BT_ERROR;
-	      do_cleanups (cleanups);
-	    }
+	    retval = EXT_LANG_BT_ERROR;
 	}
 
       if (retval != EXT_LANG_BT_ERROR)
@@ -466,8 +466,6 @@  py_print_single_arg (struct ui_out *out,
 		    retval = EXT_LANG_BT_ERROR;
 		}
 	    }
-
-	  do_cleanups (cleanups);
 	}
     }
   CATCH (except, RETURN_MASK_ERROR)
@@ -707,35 +705,24 @@  enumerate_locals (PyObject *iter,
       struct symbol *sym;
       struct block *sym_block;
       int local_indent = 8 + (8 * indent);
-      struct cleanup *locals_cleanups;
+      gdb::optional<ui_out_emit_tuple> tuple;
 
       gdbpy_ref item (PyIter_Next (iter));
       if (item == NULL)
 	break;
 
-      locals_cleanups = make_cleanup (null_cleanup, NULL);
-
       success = extract_sym (item.get (), &sym_name, &sym, &sym_block,
 			     &language);
       if (success == EXT_LANG_BT_ERROR)
-	{
-	  do_cleanups (locals_cleanups);
-	  goto error;
-	}
+	return EXT_LANG_BT_ERROR;
 
       success = extract_value (item.get (), &val);
       if (success == EXT_LANG_BT_ERROR)
-	{
-	  do_cleanups (locals_cleanups);
-	  goto error;
-	}
+	return EXT_LANG_BT_ERROR;
 
       if (sym != NULL && out->is_mi_like_p ()
 	  && ! mi_should_print (sym, MI_PRINT_LOCALS))
-	{
-	  do_cleanups (locals_cleanups);
-	  continue;
-	}
+	continue;
 
       /* If the object did not provide a value, read it.  */
       if (val == NULL)
@@ -747,8 +734,7 @@  enumerate_locals (PyObject *iter,
 	  CATCH (except, RETURN_MASK_ERROR)
 	    {
 	      gdbpy_convert_exception (except);
-	      do_cleanups (locals_cleanups);
-	      goto error;
+	      return EXT_LANG_BT_ERROR;
 	    }
 	  END_CATCH
 	}
@@ -759,7 +745,7 @@  enumerate_locals (PyObject *iter,
       if (out->is_mi_like_p ())
 	{
 	  if (print_args_field || args_type != NO_VALUES)
-	    make_cleanup_ui_out_tuple_begin_end (out, NULL);
+	    tuple.emplace (out, nullptr);
 	}
       TRY
 	{
@@ -777,18 +763,14 @@  enumerate_locals (PyObject *iter,
       CATCH (except, RETURN_MASK_ERROR)
 	{
 	  gdbpy_convert_exception (except);
-	  do_cleanups (locals_cleanups);
-	  goto error;
+	  return EXT_LANG_BT_ERROR;
 	}
       END_CATCH
 
       if (args_type == MI_PRINT_SIMPLE_VALUES)
 	{
 	  if (py_print_type (out, val) == EXT_LANG_BT_ERROR)
-	    {
-	      do_cleanups (locals_cleanups);
-	      goto error;
-	    }
+	    return EXT_LANG_BT_ERROR;
 	}
 
       /* CLI always prints values for locals.  MI uses the
@@ -799,10 +781,7 @@  enumerate_locals (PyObject *iter,
 
 	  if (py_print_value (out, val, &opts, val_indent, args_type,
 			      language) == EXT_LANG_BT_ERROR)
-	    {
-	      do_cleanups (locals_cleanups);
-	      goto error;
-	    }
+	    return EXT_LANG_BT_ERROR;
 	}
       else
 	{
@@ -810,15 +789,10 @@  enumerate_locals (PyObject *iter,
 	    {
 	      if (py_print_value (out, val, &opts, 0, args_type,
 				  language) == EXT_LANG_BT_ERROR)
-		{
-		  do_cleanups (locals_cleanups);
-		  goto error;
-		}
+		return EXT_LANG_BT_ERROR;
 	    }
 	}
 
-      do_cleanups (locals_cleanups);
-
       TRY
 	{
 	  out->text ("\n");
@@ -826,7 +800,7 @@  enumerate_locals (PyObject *iter,
       CATCH (except, RETURN_MASK_ERROR)
 	{
 	  gdbpy_convert_exception (except);
-	  goto error;
+	  return EXT_LANG_BT_ERROR;
 	}
       END_CATCH
     }
@@ -834,7 +808,6 @@  enumerate_locals (PyObject *iter,
   if (!PyErr_Occurred ())
     return EXT_LANG_BT_OK;
 
- error:
   return EXT_LANG_BT_ERROR;
 }
 
@@ -847,8 +820,6 @@  py_mi_print_variables (PyObject *filter, struct ui_out *out,
 		       enum ext_lang_frame_args args_type,
 		       struct frame_info *frame)
 {
-  struct cleanup *old_chain;
-
   gdbpy_ref args_iter (get_py_iter_from_func (filter, "frame_args"));
   if (args_iter == NULL)
     return EXT_LANG_BT_ERROR;
@@ -857,24 +828,19 @@  py_mi_print_variables (PyObject *filter, struct ui_out *out,
   if (locals_iter == NULL)
     return EXT_LANG_BT_ERROR;
 
-  old_chain = make_cleanup_ui_out_list_begin_end (out, "variables");
+  ui_out_emit_list list_emitter (out, "variables");
 
-  if (args_iter != Py_None)
-    if (enumerate_args (args_iter.get (), out, args_type, 1, frame)
-	== EXT_LANG_BT_ERROR)
-      goto error;
+  if (args_iter != Py_None
+      && (enumerate_args (args_iter.get (), out, args_type, 1, frame)
+	  == EXT_LANG_BT_ERROR))
+    return EXT_LANG_BT_ERROR;
 
-  if (locals_iter != Py_None)
-    if (enumerate_locals (locals_iter.get (), out, 1, args_type, 1, frame)
-	== EXT_LANG_BT_ERROR)
-      goto error;
+  if (locals_iter != Py_None
+      && (enumerate_locals (locals_iter.get (), out, 1, args_type, 1, frame)
+	  == EXT_LANG_BT_ERROR))
+    return EXT_LANG_BT_ERROR;
 
-  do_cleanups (old_chain);
   return EXT_LANG_BT_OK;
-
- error:
-  do_cleanups (old_chain);
-  return EXT_LANG_BT_ERROR;
 }
 
 /* Helper function for printing locals.  This function largely just
@@ -888,25 +854,18 @@  py_print_locals (PyObject *filter,
 		 int indent,
 		 struct frame_info *frame)
 {
-  struct cleanup *old_chain;
-
   gdbpy_ref locals_iter (get_py_iter_from_func (filter, "frame_locals"));
   if (locals_iter == NULL)
     return EXT_LANG_BT_ERROR;
 
-  old_chain = make_cleanup_ui_out_list_begin_end (out, "locals");
+  ui_out_emit_list list_emitter (out, "locals");
 
-  if (locals_iter != Py_None)
-    if (enumerate_locals (locals_iter.get (), out, indent, args_type,
-			  0, frame) == EXT_LANG_BT_ERROR)
-      goto locals_error;
+  if (locals_iter != Py_None
+      && (enumerate_locals (locals_iter.get (), out, indent, args_type,
+			    0, frame) == EXT_LANG_BT_ERROR))
+    return EXT_LANG_BT_ERROR;
 
-  do_cleanups (old_chain);
   return EXT_LANG_BT_OK;
-
- locals_error:
-  do_cleanups (old_chain);
-  return EXT_LANG_BT_ERROR;
 }
 
 /* Helper function for printing frame arguments.  This function
@@ -920,13 +879,11 @@  py_print_args (PyObject *filter,
 	       enum ext_lang_frame_args args_type,
 	       struct frame_info *frame)
 {
-  struct cleanup *old_chain;
-
   gdbpy_ref args_iter (get_py_iter_from_func (filter, "frame_args"));
   if (args_iter == NULL)
     return EXT_LANG_BT_ERROR;
 
-  old_chain = make_cleanup_ui_out_list_begin_end (out, "args");
+  ui_out_emit_list list_emitter (out, "args");
 
   TRY
     {
@@ -937,14 +894,14 @@  py_print_args (PyObject *filter,
   CATCH (except, RETURN_MASK_ALL)
     {
       gdbpy_convert_exception (except);
-      goto args_error;
+      return EXT_LANG_BT_ERROR;
     }
   END_CATCH
 
-  if (args_iter != Py_None)
-    if (enumerate_args (args_iter.get (), out, args_type, 0, frame)
-	== EXT_LANG_BT_ERROR)
-      goto args_error;
+  if (args_iter != Py_None
+      && (enumerate_args (args_iter.get (), out, args_type, 0, frame)
+	  == EXT_LANG_BT_ERROR))
+    return EXT_LANG_BT_ERROR;
 
   TRY
     {
@@ -954,16 +911,11 @@  py_print_args (PyObject *filter,
   CATCH (except, RETURN_MASK_ALL)
     {
       gdbpy_convert_exception (except);
-      goto args_error;
+      return EXT_LANG_BT_ERROR;
     }
   END_CATCH
 
-  do_cleanups (old_chain);
   return EXT_LANG_BT_OK;
-
- args_error:
-  do_cleanups (old_chain);
-  return EXT_LANG_BT_ERROR;
 }
 
 /*  Print a single frame to the designated output stream, detecting
@@ -990,7 +942,6 @@  py_print_frame (PyObject *filter, int flags,
   CORE_ADDR address = 0;
   struct gdbarch *gdbarch = NULL;
   struct frame_info *frame = NULL;
-  struct cleanup *cleanup_stack;
   struct value_print_options opts;
   int print_level, print_frame_info, print_args, print_locals;
   gdb::unique_xmalloc_ptr<char> function_to_free;
@@ -1034,12 +985,12 @@  py_print_frame (PyObject *filter, int flags,
       return EXT_LANG_BT_COMPLETED;
     }
 
-  cleanup_stack = make_cleanup (null_cleanup, NULL);
+  gdb::optional<ui_out_emit_tuple> tuple;
 
   /* -stack-list-locals does not require a
      wrapping frame attribute.  */
   if (print_frame_info || (print_args && ! print_locals))
-    make_cleanup_ui_out_tuple_begin_end (out, "frame");
+    tuple.emplace (out, "frame");
 
   if (print_frame_info)
     {
@@ -1054,7 +1005,6 @@  py_print_frame (PyObject *filter, int flags,
 	  CATCH (except, RETURN_MASK_ERROR)
 	    {
 	      gdbpy_convert_exception (except);
-	      do_cleanups (cleanup_stack);
 	      return EXT_LANG_BT_ERROR;
 	    }
 	  END_CATCH
@@ -1067,18 +1017,12 @@  py_print_frame (PyObject *filter, int flags,
 	  gdbpy_ref paddr (PyObject_CallMethod (filter, "address", NULL));
 
 	  if (paddr == NULL)
-	    {
-	      do_cleanups (cleanup_stack);
-	      return EXT_LANG_BT_ERROR;
-	    }
+	    return EXT_LANG_BT_ERROR;
 
 	  if (paddr != Py_None)
 	    {
 	      if (get_addr_from_python (paddr.get (), &address) < 0)
-		{
-		  do_cleanups (cleanup_stack);
-		  return EXT_LANG_BT_ERROR;
-		}
+		return EXT_LANG_BT_ERROR;
 
 	      has_addr = 1;
 	    }
@@ -1117,7 +1061,6 @@  py_print_frame (PyObject *filter, int flags,
       CATCH (except, RETURN_MASK_ERROR)
 	{
 	  gdbpy_convert_exception (except);
-	  do_cleanups (cleanup_stack);
 	  return EXT_LANG_BT_ERROR;
 	}
       END_CATCH
@@ -1139,7 +1082,6 @@  py_print_frame (PyObject *filter, int flags,
 	  CATCH (except, RETURN_MASK_ERROR)
 	    {
 	      gdbpy_convert_exception (except);
-	      do_cleanups (cleanup_stack);
 	      return EXT_LANG_BT_ERROR;
 	    }
 	  END_CATCH
@@ -1152,20 +1094,14 @@  py_print_frame (PyObject *filter, int flags,
 	  const char *function = NULL;
 
 	  if (py_func == NULL)
-	    {
-	      do_cleanups (cleanup_stack);
-	      return EXT_LANG_BT_ERROR;
-	    }
+	    return EXT_LANG_BT_ERROR;
 
 	  if (gdbpy_is_string (py_func.get ()))
 	    {
 	      function_to_free = python_string_to_host_string (py_func.get ());
 
 	      if (function_to_free == NULL)
-		{
-		  do_cleanups (cleanup_stack);
-		  return EXT_LANG_BT_ERROR;
-		}
+		return EXT_LANG_BT_ERROR;
 
 	      function = function_to_free.get ();
 	    }
@@ -1175,10 +1111,7 @@  py_print_frame (PyObject *filter, int flags,
 	      struct bound_minimal_symbol msymbol;
 
 	      if (get_addr_from_python (py_func.get (), &addr) < 0)
-		{
-		  do_cleanups (cleanup_stack);
-		  return EXT_LANG_BT_ERROR;
-		}
+		return EXT_LANG_BT_ERROR;
 
 	      msymbol = lookup_minimal_symbol_by_pc (addr);
 	      if (msymbol.minsym != NULL)
@@ -1189,7 +1122,6 @@  py_print_frame (PyObject *filter, int flags,
 	      PyErr_SetString (PyExc_RuntimeError,
 			       _("FrameDecorator.function: expecting a " \
 				 "String, integer or None."));
-	      do_cleanups (cleanup_stack);
 	      return EXT_LANG_BT_ERROR;
 	    }
 
@@ -1204,7 +1136,6 @@  py_print_frame (PyObject *filter, int flags,
 	  CATCH (except, RETURN_MASK_ERROR)
 	    {
 	      gdbpy_convert_exception (except);
-	      do_cleanups (cleanup_stack);
 	      return EXT_LANG_BT_ERROR;
 	    }
 	  END_CATCH
@@ -1217,10 +1148,7 @@  py_print_frame (PyObject *filter, int flags,
   if (print_args)
     {
       if (py_print_args (filter, out, args_type, frame) == EXT_LANG_BT_ERROR)
-	{
-	  do_cleanups (cleanup_stack);
-	  return EXT_LANG_BT_ERROR;
-	}
+	return EXT_LANG_BT_ERROR;
     }
 
   /* File name/source/line number information.  */
@@ -1233,7 +1161,6 @@  py_print_frame (PyObject *filter, int flags,
       CATCH (except, RETURN_MASK_ERROR)
 	{
 	  gdbpy_convert_exception (except);
-	  do_cleanups (cleanup_stack);
 	  return EXT_LANG_BT_ERROR;
 	}
       END_CATCH
@@ -1243,10 +1170,7 @@  py_print_frame (PyObject *filter, int flags,
 	  gdbpy_ref py_fn (PyObject_CallMethod (filter, "filename", NULL));
 
 	  if (py_fn == NULL)
-	    {
-	      do_cleanups (cleanup_stack);
-	      return EXT_LANG_BT_ERROR;
-	    }
+	    return EXT_LANG_BT_ERROR;
 
 	  if (py_fn != Py_None)
 	    {
@@ -1254,10 +1178,7 @@  py_print_frame (PyObject *filter, int flags,
 		filename (python_string_to_host_string (py_fn.get ()));
 
 	      if (filename == NULL)
-		{
-		  do_cleanups (cleanup_stack);
-		  return EXT_LANG_BT_ERROR;
-		}
+		return EXT_LANG_BT_ERROR;
 
 	      TRY
 		{
@@ -1270,7 +1191,6 @@  py_print_frame (PyObject *filter, int flags,
 	      CATCH (except, RETURN_MASK_ERROR)
 		{
 		  gdbpy_convert_exception (except);
-		  do_cleanups (cleanup_stack);
 		  return EXT_LANG_BT_ERROR;
 		}
 	      END_CATCH
@@ -1283,19 +1203,13 @@  py_print_frame (PyObject *filter, int flags,
 	  int line;
 
 	  if (py_line == NULL)
-	    {
-	      do_cleanups (cleanup_stack);
-	      return EXT_LANG_BT_ERROR;
-	    }
+	    return EXT_LANG_BT_ERROR;
 
 	  if (py_line != Py_None)
 	    {
 	      line = PyLong_AsLong (py_line.get ());
 	      if (PyErr_Occurred ())
-		{
-		  do_cleanups (cleanup_stack);
-		  return EXT_LANG_BT_ERROR;
-		}
+		return EXT_LANG_BT_ERROR;
 
 	      TRY
 		{
@@ -1306,7 +1220,6 @@  py_print_frame (PyObject *filter, int flags,
 	      CATCH (except, RETURN_MASK_ERROR)
 		{
 		  gdbpy_convert_exception (except);
-		  do_cleanups (cleanup_stack);
 		  return EXT_LANG_BT_ERROR;
 		}
 	      END_CATCH
@@ -1326,7 +1239,6 @@  py_print_frame (PyObject *filter, int flags,
       CATCH (except, RETURN_MASK_ERROR)
 	{
 	  gdbpy_convert_exception (except);
-	  do_cleanups (cleanup_stack);
 	  return EXT_LANG_BT_ERROR;
 	}
       END_CATCH
@@ -1336,26 +1248,20 @@  py_print_frame (PyObject *filter, int flags,
     {
       if (py_print_locals (filter, out, args_type, indent,
 			   frame) == EXT_LANG_BT_ERROR)
-	{
-	  do_cleanups (cleanup_stack);
-	  return EXT_LANG_BT_ERROR;
-	}
+	return EXT_LANG_BT_ERROR;
     }
 
   {
     /* Finally recursively print elided frames, if any.  */
     gdbpy_ref elided (get_py_iter_from_func (filter, "elided"));
     if (elided == NULL)
-      {
-	do_cleanups (cleanup_stack);
-	return EXT_LANG_BT_ERROR;
-      }
+      return EXT_LANG_BT_ERROR;
 
     if (elided != Py_None)
       {
 	PyObject *item;
 
-	make_cleanup_ui_out_list_begin_end (out, "children");
+	ui_out_emit_list inner_list_emiter (out, "children");
 
 	if (! out->is_mi_like_p ())
 	  indent++;
@@ -1370,20 +1276,13 @@  py_print_frame (PyObject *filter, int flags,
 							      levels_printed);
 
 	    if (success == EXT_LANG_BT_ERROR)
-	      {
-		do_cleanups (cleanup_stack);
-		return EXT_LANG_BT_ERROR;
-	      }
+	      return EXT_LANG_BT_ERROR;
 	  }
 	if (item == NULL && PyErr_Occurred ())
-	  {
-	    do_cleanups (cleanup_stack);
-	    return EXT_LANG_BT_ERROR;
-	  }
+	  return EXT_LANG_BT_ERROR;
       }
   }
 
-  do_cleanups (cleanup_stack);
   return EXT_LANG_BT_COMPLETED;
 }
 
diff --git a/gdb/ui-out.h b/gdb/ui-out.h
index b8bea97..7d5a1fc 100644
--- a/gdb/ui-out.h
+++ b/gdb/ui-out.h
@@ -187,4 +187,37 @@  class ui_out
   ui_out_level *current_level () const;
 };
 
+/* This is similar to make_cleanup_ui_out_tuple_begin_end and
+   make_cleanup_ui_out_list_begin_end, but written as an RAII template
+   class.  It takes the ui_out_type as a template parameter.  Normally
+   this is used via the typedefs ui_out_emit_tuple and
+   ui_out_emit_list.  */
+template<ui_out_type Type>
+class ui_out_emit_type
+{
+public:
+
+  ui_out_emit_type (struct ui_out *uiout, const char *id)
+    : m_uiout (uiout)
+  {
+    uiout->begin (Type, id);
+  }
+
+  ~ui_out_emit_type ()
+  {
+    m_uiout->end (Type);
+  }
+
+  ui_out_emit_type (const ui_out_emit_type<Type> &) = delete;
+  ui_out_emit_type<Type> &operator= (const ui_out_emit_type<Type> &)
+      = delete;
+
+private:
+
+  struct ui_out *m_uiout;
+};
+
+typedef ui_out_emit_type<ui_out_type_tuple> ui_out_emit_tuple;
+typedef ui_out_emit_type<ui_out_type_list> ui_out_emit_list;
+
 #endif /* UI_OUT_H */