Files
binutils-gdb/gdbsupport/gdb_obstack.h
Jan Vrany 42373e5864 gdb: use std::vector<> to hold on blocks in struct blockvector
This commit changes internal implementation of struct blockvector to use
std::vector<> rather than flexible array.

The main motivation for this change is to simplify adding blocks to
existing symtab. This feature will be used later by Python API to build
objfiles, compunits and symtabs dynamically (similarly to JIT reader
API).

To do so, this commit

 1. introduces obstack_allocator, an implementation of Allocator
    concept that allocates memory on obstack.
 2. uses std::vector<> with the above allocator to hold on blocks
 3. updates users.

As a side-effect of this change, blockvectors allocated in mdebugread.c
are obstack-allocated rather than xzalloc()ated which seems to be the
correct thing to do. Also, code got simpler.

The downside is higher memory consumption. The size of std::vector with
obstack allocator is 32 bytes (GCC 14) compared to 8 bytes used
currently to store the number of blocks (m_num_blocks). Stopping gdb at
its main(), followed by "maint expand-symtabs" results in 4593
compunit symtabs so in this case the overhead is 24*4593 = 110232 bytes
which I hope is acceptable.

Maybe more concerning is the fact that one may waste obstack memory when
excessively adding blocks. However, blockvectors are not added blocks
after initial allocation at the moment (except in mdebugread.c) so this
is not a problem for existing code. To to mitigate this issue code
allocating may capacity - a number of blocks the blockvector may hold
without reallocating.
2024-11-21 13:49:11 +00:00

213 lines
6.0 KiB
C++

/* Obstack wrapper for GDB.
Copyright (C) 2002-2024 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/>. */
#if !defined (GDB_OBSTACK_H)
#define GDB_OBSTACK_H 1
#include <limits>
#include <type_traits>
#include "obstack.h"
/* Utility macros - wrap obstack alloc into something more robust. */
template <typename T>
static inline T*
obstack_zalloc (struct obstack *ob)
{
static_assert (IsMallocable<T>::value, "Trying to use OBSTACK_ZALLOC with a \
non-POD data type. Use obstack_new instead.");
return ((T *) memset (obstack_alloc (ob, sizeof (T)), 0, sizeof (T)));
}
#define OBSTACK_ZALLOC(OBSTACK,TYPE) obstack_zalloc<TYPE> ((OBSTACK))
template <typename T>
static inline T *
obstack_calloc (struct obstack *ob, size_t number)
{
static_assert (IsMallocable<T>::value, "Trying to use OBSTACK_CALLOC with a \
non-POD data type. Use obstack_new instead.");
return ((T *) memset (obstack_alloc (ob, number * sizeof (T)), 0,
number * sizeof (T)));
}
#define OBSTACK_CALLOC(OBSTACK,NUMBER,TYPE) \
obstack_calloc<TYPE> ((OBSTACK), (NUMBER))
/* Allocate an object on OB and call its constructor. */
template <typename T, typename... Args>
static inline T*
obstack_new (struct obstack *ob, Args&&... args)
{
T* object = (T *) obstack_alloc (ob, sizeof (T));
object = new (object) T (std::forward<Args> (args)...);
return object;
}
/* Unless explicitly specified, GDB obstacks always use xmalloc() and
xfree(). */
/* Note: ezannoni 2004-02-09: One could also specify the allocation
functions using a special init function for each obstack,
obstack_specify_allocation. However we just use obstack_init and
let these defines here do the job. While one could argue the
superiority of one approach over the other, we just chose one
throughout. */
#define obstack_chunk_alloc xmalloc
#define obstack_chunk_free xfree
#define obstack_grow_str(OBSTACK,STRING) \
obstack_grow (OBSTACK, STRING, strlen (STRING))
#define obstack_grow_str0(OBSTACK,STRING) \
obstack_grow0 (OBSTACK, STRING, strlen (STRING))
#define obstack_grow_wstr(OBSTACK, WSTRING) \
obstack_grow (OBSTACK, WSTRING, sizeof (gdb_wchar_t) * gdb_wcslen (WSTRING))
/* Concatenate NULL terminated variable argument list of `const char
*' strings; return the new string. Space is found in the OBSTACKP.
Argument list must be terminated by a sentinel expression `(char *)
NULL'. */
extern char *obconcat (struct obstack *obstackp, ...) ATTRIBUTE_SENTINEL;
/* Duplicate STRING, returning an equivalent string that's allocated on the
obstack OBSTACKP. */
static inline char *
obstack_strdup (struct obstack *obstackp, const char *string)
{
return (char *) obstack_copy0 (obstackp, string, strlen (string));
}
/* Duplicate STRING, returning an equivalent string that's allocated on the
obstack OBSTACKP. */
static inline char *
obstack_strdup (struct obstack *obstackp, const std::string &string)
{
return (char *) obstack_copy0 (obstackp, string.c_str (),
string.size ());
}
/* Duplicate the first N characters of STRING, returning a
\0-terminated string that's allocated on the obstack OBSTACKP.
Note that exactly N characters are copied, even if STRING is
shorter. */
static inline char *
obstack_strndup (struct obstack *obstackp, const char *string, size_t n)
{
return (char *) obstack_copy0 (obstackp, string, n);
}
/* An obstack that frees itself on scope exit. */
struct auto_obstack : obstack
{
auto_obstack ()
{ obstack_init (this); }
~auto_obstack ()
{ obstack_free (this, NULL); }
DISABLE_COPY_AND_ASSIGN (auto_obstack);
/* Free all memory in the obstack but leave it valid for further
allocation. */
void clear ()
{ obstack_free (this, obstack_base (this)); }
};
/* Objects are allocated on obstack instead of heap. This is a mixin
that uses CRTP to ensure that the type in question is trivially
destructible. */
template<typename T>
struct allocate_on_obstack
{
allocate_on_obstack () = default;
void* operator new (size_t size, struct obstack *obstack)
{
static_assert (IsFreeable<T>::value);
return obstack_alloc (obstack, size);
}
void* operator new[] (size_t size, struct obstack *obstack)
{
return obstack_alloc (obstack, size);
}
void operator delete (void *memory) {}
void operator delete[] (void *memory) {}
};
/* Implementation of Allocator concept using obstack to
allocate memory. This allows standard containers to be
used with obstack. */
template <typename T>
class obstack_allocator
{
public:
typedef T value_type;
obstack_allocator (struct obstack *obstack)
: m_obstack(obstack)
{}
template <typename U> constexpr obstack_allocator (const obstack_allocator<U>& allocator) noexcept
: m_obstack(allocator.m_obstack)
{}
T* allocate (std::size_t n)
{
if (n > std::numeric_limits<std::size_t>::max () / sizeof (T))
throw std::bad_array_new_length ();
if (auto p = static_cast<T*> (obstack_alloc (m_obstack, n * sizeof (T))))
{
return p;
}
throw std::bad_alloc ();
}
void deallocate(T* p, std::size_t n) noexcept
{}
private:
struct obstack *m_obstack;
};
template <class T, class U>
bool operator==(const obstack_allocator<T> &t, const obstack_allocator<U> &u)
{
return (std::is_same<T, U>::value_type) && (t.m_obstack == u.m_obstack);
}
template <class T, class U>
bool operator!=(const obstack_allocator<T> &t, const obstack_allocator<U> &u)
{
return ! (t == u);
}
#endif