1/* Copyright (C) 2021 Free Software Foundation, Inc.
2   Contributed by Oracle.
3
4   This file is part of GNU Binutils.
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 3, or (at your option)
9   any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, 51 Franklin Street - Fifth Floor, Boston,
19   MA 02110-1301, USA.  */
20
21#include "config.h"
22#include <pthread.h>
23
24#include "collector.h"
25#include "libcol_util.h"
26#include "tsd.h"
27#include "memmgr.h"
28
29/* TprintfT(<level>,...) definitions.  Adjust per module as needed */
30#define DBG_LT0 0 // for high-level configuration, unexpected errors/warnings
31#define DBG_LT1 1 // for configuration details, warnings
32#define DBG_LT2 2
33#define DBG_LT3 3
34
35/*
36 * Build our thread-specific-data support on pthread interfaces.
37 */
38#define MAXNKEYS    64  /* hard-wired? really? well, it depends only on us and we have a sense for how many keys we will use */
39static pthread_key_t tsd_pkeys[MAXNKEYS];
40static size_t tsd_sizes[MAXNKEYS];
41static unsigned tsd_nkeys = 0;
42
43int
44__collector_tsd_init ()
45{
46  return 0;
47}
48
49void
50__collector_tsd_fini ()
51{
52  Tprintf (DBG_LT1, "tsd_fini()\n");
53  while (tsd_nkeys)
54    {
55      tsd_nkeys--;
56      pthread_key_delete (tsd_pkeys[tsd_nkeys]);
57      tsd_sizes[tsd_nkeys] = 0; // should be unneeded
58    }
59}
60
61int
62__collector_tsd_allocate ()
63{
64  return 0;
65}
66
67void
68__collector_tsd_release () { }
69
70static void
71tsd_destructor (void *p)
72{
73  if (p)
74    __collector_freeCSize (__collector_heap, p, *((size_t *) p));
75}
76
77unsigned
78__collector_tsd_create_key (size_t sz, void (*init)(void*), void (*fini)(void*))
79{
80  /*
81   * We no longer support init and fini arguments (and weren't using them anyhow).
82   * Our hard-wired MAXNKEYS presumably is considerably higher than the number of keys we use.
83   */
84  if (init || fini || (tsd_nkeys >= MAXNKEYS))
85    return COLLECTOR_TSD_INVALID_KEY;
86
87  /*
88   * A pthread key has a value that is (void *).
89   * We don't know where it is stored, and can access its value only through {get|set}specific.
90   * But libcollector expects a pointer to memory that it can modify.
91   * So we have to allocate that memory and store the pointer.
92   *
93   * For now, we just have to register a destructor that will free the memory
94   * when the thread finishes.
95   */
96  if (pthread_key_create (&tsd_pkeys[tsd_nkeys], &tsd_destructor))
97   return COLLECTOR_TSD_INVALID_KEY;
98  tsd_sizes[tsd_nkeys] = sz;
99  tsd_nkeys++;
100  return (tsd_nkeys - 1);
101}
102
103void *
104__collector_tsd_get_by_key (unsigned key_index)
105{
106  if (key_index == COLLECTOR_TSD_INVALID_KEY)
107    return NULL;
108  if (key_index < 0 || key_index >= tsd_nkeys)
109    return NULL;
110  pthread_key_t key = tsd_pkeys[key_index];
111  size_t sz = tsd_sizes[key_index];
112
113  /*
114   * When we use __collector_freeCSize(), we need to know the
115   * size that had been allocated.  So, stick a header to the
116   * front of the allocation to hold the size.  The header could
117   * just be sizeof(size_t), but pad it to preserve alignment for
118   * the usable area.
119   */
120  size_t header = 8;
121  void *value = pthread_getspecific (key);
122
123  // check whether we have allocated the memory
124  if (value == NULL)
125    {
126      // add room to record the size
127      value = __collector_allocCSize (__collector_heap, sz + header, 0);
128      if (value == NULL)
129	{
130	  // do we need to guard against trying to alloc each time?
131	  return NULL;
132	}
133      // write the size of the allocation
134      *((size_t *) value) = sz + header;
135      CALL_UTIL (memset)(((char *) value) + header, 0, sz);
136
137      // record the allocation for future retrieval
138      if (pthread_setspecific (key, value))
139	return NULL;
140    }
141  // return the pointer, skipping the header
142  return ((char *) value) +header;
143}
144
145void
146__collector_tsd_fork_child_cleanup ()
147{
148  __collector_tsd_fini ();
149}
150