1/* Copyright (C) 2020-2023 Free Software Foundation, Inc.
2
3   This file is part of GDB.
4
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 3 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18/* Support classes to wrap up the process of iterating over a
19   multi-dimensional Fortran array.  */
20
21#ifndef F_ARRAY_WALKER_H
22#define F_ARRAY_WALKER_H
23
24#include "defs.h"
25#include "gdbtypes.h"
26#include "f-lang.h"
27
28/* Class for calculating the byte offset for elements within a single
29   dimension of a Fortran array.  */
30class fortran_array_offset_calculator
31{
32public:
33  /* Create a new offset calculator for TYPE, which is either an array or a
34     string.  */
35  explicit fortran_array_offset_calculator (struct type *type)
36  {
37    /* Validate the type.  */
38    type = check_typedef (type);
39    if (type->code () != TYPE_CODE_ARRAY
40	&& (type->code () != TYPE_CODE_STRING))
41      error (_("can only compute offsets for arrays and strings"));
42
43    /* Get the range, and extract the bounds.  */
44    struct type *range_type = type->index_type ();
45    if (!get_discrete_bounds (range_type, &m_lowerbound, &m_upperbound))
46      error ("unable to read array bounds");
47
48    /* Figure out the stride for this array.  */
49    struct type *elt_type = check_typedef (type->target_type ());
50    m_stride = type->index_type ()->bounds ()->bit_stride ();
51    if (m_stride == 0)
52      m_stride = type_length_units (elt_type);
53    else
54      {
55	int unit_size
56	  = gdbarch_addressable_memory_unit_size (elt_type->arch ());
57	m_stride /= (unit_size * 8);
58      }
59  };
60
61  /* Get the byte offset for element INDEX within the type we are working
62     on.  There is no bounds checking done on INDEX.  If the stride is
63     negative then we still assume that the base address (for the array
64     object) points to the element with the lowest memory address, we then
65     calculate an offset assuming that index 0 will be the element at the
66     highest address, index 1 the next highest, and so on.  This is not
67     quite how Fortran works in reality; in reality the base address of
68     the object would point at the element with the highest address, and
69     we would index backwards from there in the "normal" way, however,
70     GDB's current value contents model doesn't support having the base
71     address be near to the end of the value contents, so we currently
72     adjust the base address of Fortran arrays with negative strides so
73     their base address points at the lowest memory address.  This code
74     here is part of working around this weirdness.  */
75  LONGEST index_offset (LONGEST index)
76  {
77    LONGEST offset;
78    if (m_stride < 0)
79      offset = std::abs (m_stride) * (m_upperbound - index);
80    else
81      offset = std::abs (m_stride) * (index - m_lowerbound);
82    return offset;
83  }
84
85private:
86
87  /* The stride for the type we are working with.  */
88  LONGEST m_stride;
89
90  /* The upper bound for the type we are working with.  */
91  LONGEST m_upperbound;
92
93  /* The lower bound for the type we are working with.  */
94  LONGEST m_lowerbound;
95};
96
97/* A base class used by fortran_array_walker.  There's no virtual methods
98   here, sub-classes should just override the functions they want in order
99   to specialise the behaviour to their needs.  The functionality
100   provided in these default implementations will visit every array
101   element, but do nothing for each element.  */
102
103struct fortran_array_walker_base_impl
104{
105  /* Called when iterating between the lower and upper bounds of each
106     dimension of the array.  Return true if GDB should continue iterating,
107     otherwise, return false.
108
109     SHOULD_CONTINUE indicates if GDB is going to stop anyway, and should
110     be taken into consideration when deciding what to return.  If
111     SHOULD_CONTINUE is false then this function must also return false,
112     the function is still called though in case extra work needs to be
113     done as part of the stopping process.  */
114  bool continue_walking (bool should_continue)
115  { return should_continue; }
116
117  /* Called when GDB starts iterating over a dimension of the array.  The
118     argument INDEX_TYPE is the type of the index used to address elements
119     in the dimension, NELTS holds the number of the elements there, and
120     INNER_P is true for the inner most dimension (the dimension containing
121     the actual elements of the array), and false for more outer dimensions.
122     For a concrete example of how this function is called see the comment
123     on process_element below.  */
124  void start_dimension (struct type *index_type, LONGEST nelts, bool inner_p)
125  { /* Nothing.  */ }
126
127  /* Called when GDB finishes iterating over a dimension of the array.  The
128     argument INNER_P is true for the inner most dimension (the dimension
129     containing the actual elements of the array), and false for more outer
130     dimensions.  LAST_P is true for the last call at a particular
131     dimension.  For a concrete example of how this function is called
132     see the comment on process_element below.  */
133  void finish_dimension (bool inner_p, bool last_p)
134  { /* Nothing.  */ }
135
136  /* Called when processing dimensions of the array other than the
137     innermost one.  WALK_1 is the walker to normally call, ELT_TYPE is
138     the type of the element being extracted, and ELT_OFF is the offset
139     of the element from the start of array being walked.  INDEX is the
140     value of the index the current element is at in the upper dimension.
141     Finally LAST_P is true only when this is the last element that will
142     be processed in this dimension.  */
143  void process_dimension (gdb::function_view<void (struct type *,
144						   int, bool)> walk_1,
145			  struct type *elt_type, LONGEST elt_off,
146			  LONGEST index, bool last_p)
147  {
148    walk_1 (elt_type, elt_off, last_p);
149  }
150
151  /* Called when processing the inner most dimension of the array, for
152     every element in the array.  ELT_TYPE is the type of the element being
153     extracted, and ELT_OFF is the offset of the element from the start of
154     array being walked.  INDEX is the value of the index the current
155     element is at in the upper dimension.  Finally LAST_P is true only
156     when this is the last element that will be processed in this dimension.
157
158     Given this two dimensional array ((1, 2) (3, 4) (5, 6)), the calls to
159     start_dimension, process_element, and finish_dimension look like this:
160
161     start_dimension (INDEX_TYPE, 3, false);
162       start_dimension (INDEX_TYPE, 2, true);
163         process_element (TYPE, OFFSET, false);
164         process_element (TYPE, OFFSET, true);
165       finish_dimension (true, false);
166       start_dimension (INDEX_TYPE, 2, true);
167         process_element (TYPE, OFFSET, false);
168         process_element (TYPE, OFFSET, true);
169       finish_dimension (true, true);
170       start_dimension (INDEX_TYPE, 2, true);
171         process_element (TYPE, OFFSET, false);
172         process_element (TYPE, OFFSET, true);
173       finish_dimension (true, true);
174     finish_dimension (false, true);  */
175  void process_element (struct type *elt_type, LONGEST elt_off,
176			LONGEST index, bool last_p)
177  { /* Nothing.  */ }
178};
179
180/* A class to wrap up the process of iterating over a multi-dimensional
181   Fortran array.  IMPL is used to specialise what happens as we walk over
182   the array.  See class FORTRAN_ARRAY_WALKER_BASE_IMPL (above) for the
183   methods than can be used to customise the array walk.  */
184template<typename Impl>
185class fortran_array_walker
186{
187  /* Ensure that Impl is derived from the required base class.  This just
188     ensures that all of the required API methods are available and have a
189     sensible default implementation.  */
190  gdb_static_assert ((std::is_base_of<fortran_array_walker_base_impl,Impl>::value));
191
192public:
193  /* Create a new array walker.  TYPE is the type of the array being walked
194     over, and ADDRESS is the base address for the object of TYPE in
195     memory.  All other arguments are forwarded to the constructor of the
196     template parameter class IMPL.  */
197  template <typename ...Args>
198  fortran_array_walker (struct type *type, CORE_ADDR address,
199			Args... args)
200    : m_type (type),
201      m_address (address),
202      m_impl (type, address, args...),
203      m_ndimensions (calc_f77_array_dims (m_type)),
204      m_nss (0)
205  { /* Nothing.  */ }
206
207  /* Walk the array.  */
208  void
209  walk ()
210  {
211    walk_1 (m_type, 0, false);
212  }
213
214private:
215  /* The core of the array walking algorithm.  TYPE is the type of
216     the current dimension being processed and OFFSET is the offset
217     (in bytes) for the start of this dimension.  */
218  void
219  walk_1 (struct type *type, int offset, bool last_p)
220  {
221    /* Extract the range, and get lower and upper bounds.  */
222    struct type *range_type = check_typedef (type)->index_type ();
223    LONGEST lowerbound, upperbound;
224    if (!get_discrete_bounds (range_type, &lowerbound, &upperbound))
225      error ("failed to get range bounds");
226
227    /* CALC is used to calculate the offsets for each element in this
228       dimension.  */
229    fortran_array_offset_calculator calc (type);
230
231    m_nss++;
232    gdb_assert (range_type->code () == TYPE_CODE_RANGE);
233    m_impl.start_dimension (range_type->target_type (),
234			    upperbound - lowerbound + 1,
235			    m_nss == m_ndimensions);
236
237    if (m_nss != m_ndimensions)
238      {
239	struct type *subarray_type = check_typedef (type)->target_type ();
240
241	/* For dimensions other than the inner most, walk each element and
242	   recurse while peeling off one more dimension of the array.  */
243	for (LONGEST i = lowerbound;
244	     m_impl.continue_walking (i < upperbound + 1);
245	     i++)
246	  {
247	    /* Use the index and the stride to work out a new offset.  */
248	    LONGEST new_offset = offset + calc.index_offset (i);
249
250	    /* Now print the lower dimension.  */
251	    m_impl.process_dimension
252	      ([this] (struct type *w_type, int w_offset, bool w_last_p) -> void
253		{
254		  this->walk_1 (w_type, w_offset, w_last_p);
255		},
256	       subarray_type, new_offset, i, i == upperbound);
257	  }
258      }
259    else
260      {
261	struct type *elt_type = check_typedef (type)->target_type ();
262
263	/* For the inner most dimension of the array, process each element
264	   within this dimension.  */
265	for (LONGEST i = lowerbound;
266	     m_impl.continue_walking (i < upperbound + 1);
267	     i++)
268	  {
269	    LONGEST elt_off = offset + calc.index_offset (i);
270
271	    if (is_dynamic_type (elt_type))
272	      {
273		CORE_ADDR e_address = m_address + elt_off;
274		elt_type = resolve_dynamic_type (elt_type, {}, e_address);
275	      }
276
277	    m_impl.process_element (elt_type, elt_off, i, i == upperbound);
278	  }
279      }
280
281    m_impl.finish_dimension (m_nss == m_ndimensions, last_p || m_nss == 1);
282    m_nss--;
283  }
284
285  /* The array type being processed.  */
286  struct type *m_type;
287
288  /* The address in target memory for the object of M_TYPE being
289     processed.  This is required in order to resolve dynamic types.  */
290  CORE_ADDR m_address;
291
292  /* An instance of the template specialisation class.  */
293  Impl m_impl;
294
295  /* The total number of dimensions in M_TYPE.  */
296  int m_ndimensions;
297
298  /* The current dimension number being processed.  */
299  int m_nss;
300};
301
302#endif /* F_ARRAY_WALKER_H */
303