1--  Copyright 1994 Grady Booch
2--  Copyright 1999 Pat Rogers
3--  Copyright 1999-2014 Simon Wright <simon@pushface.org>
4--  Modifications November 2006 by Christopher J. Henrich
5
6--  This package is free software; you can redistribute it and/or
7--  modify it under terms of the GNU General Public License as
8--  published by the Free Software Foundation; either version 2, or
9--  (at your option) any later version. This package is distributed in
10--  the hope that it will be useful, but WITHOUT ANY WARRANTY; without
11--  even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12--  PARTICULAR PURPOSE. See the GNU General Public License for more
13--  details. You should have received a copy of the GNU General Public
14--  License distributed with this package; see file COPYING.  If not,
15--  write to the Free Software Foundation, 59 Temple Place - Suite
16--  330, Boston, MA 02111-1307, USA.
17
18--  As a special exception, if other files instantiate generics from
19--  this unit, or you link this unit with other files to produce an
20--  executable, this unit does not by itself cause the resulting
21--  executable to be covered by the GNU General Public License.  This
22--  exception does not however invalidate any other reasons why the
23--  executable file might be covered by the GNU Public License.
24
25with Ada.Unchecked_Deallocation;
26with System.Address_To_Access_Conversions;
27
28package body BC.Support.Managed_Storage is
29
30
31   --  Manage chaining through the allocated elements in chunks.
32
33   function Value_At (Location : System.Address) return System.Address;
34   pragma Inline (Value_At);
35
36   procedure Put (This : System.Address; At_Location : System.Address);
37   pragma Inline (Put);
38
39
40   --  Utilities.
41
42   procedure Get_Chunk (Result : out Chunk_Pointer;
43                        From : in out Pool;
44                        Requested_Element_Size : SSE.Storage_Count;
45                        Requested_Alignment : SSE.Storage_Count);
46
47   function Within_Range (Target : System.Address;
48                          Base : Chunk_Pointer) return Boolean;
49   pragma Inline (Within_Range);
50
51   procedure Usable_Size_And_Alignment
52     (For_Size : SSE.Storage_Count;
53      For_Alignment : SSE.Storage_Count;
54      Is_Size : out SSE.Storage_Count;
55      Is_Alignment : out SSE.Storage_Count);
56
57
58   --  Constants.
59
60   use type SSE.Storage_Count;
61
62   Address_Size_I : constant Integer
63     := System.Address'Max_Size_In_Storage_Elements;
64   Address_Size_SC : constant SSE.Storage_Count
65     := System.Address'Max_Size_In_Storage_Elements;
66
67
68   --  Instantiations.
69
70   procedure Dispose is
71      new Ada.Unchecked_Deallocation (Chunk_List, Chunk_List_Pointer);
72   procedure Dispose is
73      new Ada.Unchecked_Deallocation (Chunk, Chunk_Pointer);
74
75   package PeekPoke is
76      new System.Address_To_Access_Conversions (System.Address);
77
78
79   --  Bodies.
80
81   procedure Allocate (The_Pool : in out Pool;
82                       Storage_Address : out System.Address;
83                       Size_In_Storage_Elements : SSE.Storage_Count;
84                       Alignment : SSE.Storage_Count)
85   is
86
87      Usable_Size : SSE.Storage_Count;
88      Usable_Alignment : SSE.Storage_Count;
89
90      List : Chunk_List_Pointer;
91      Previous_List : Chunk_List_Pointer;
92      Chunk : Chunk_Pointer;
93
94      use type System.Address;
95
96   begin
97
98      --  Calculate the usable size and alignment.
99      Usable_Size_And_Alignment (Size_In_Storage_Elements,
100                                 Alignment,
101                                 Usable_Size,
102                                 Usable_Alignment);
103
104      --  Look for a chunk list with the right element size and
105      --  alignment, stopping when no point in continuing
106      List := The_Pool.Head;
107      while List /= null and then
108        (List.Element_Size > Usable_Size
109           or else List.Alignment > Usable_Alignment)
110      loop
111         Previous_List := List;
112         List := List.Next_List;
113      end loop;
114
115      if List = null
116        or else List.Element_Size /= Usable_Size
117        or else List.Alignment /= Usable_Alignment
118      then
119
120         --  Need to create a new list.
121         --
122         --  The new list is inserted before the next list of the
123         --  previous list, if any, and may become the new head.
124
125         List := new Chunk_List;
126
127         --  Chain the new list in
128         if Previous_List /= null then
129
130            --  There is a previous member, insert
131            List.Next_List := Previous_List.Next_List;
132            Previous_List.Next_List := List;
133
134         else
135
136            --  There was no previous member, add as head (before
137            --  previous head)
138            List.Next_List := The_Pool.Head;
139            The_Pool.Head := List;
140
141         end if;
142
143         --  Store the sizing attributes
144         List.Element_Size := Usable_Size;
145         List.Alignment := Usable_Alignment;
146
147      end if;
148
149      --  List designates the correct chunk list.
150      --  Find a chunk with a free element.
151      Chunk := List.Head;
152      while Chunk /= null
153        and then Chunk.Next_Element = System.Null_Address loop
154         Chunk := Chunk.Next_Chunk;
155      end loop;
156
157      if Chunk = null then
158
159         --  There was no chunk with free elements; allocate a new one
160         --  (at the head, for efficiency in future allocations).
161         --
162         --  Note that if Get_Chunk fails (alignment > alignment of
163         --  System.Address => this request just fails to fit) we may
164         --  be left with an empty List.
165         begin
166            Chunk := List.Head;
167            Get_Chunk (List.Head, The_Pool, Usable_Size, Usable_Alignment);
168            List.Head.Next_Chunk := Chunk;
169            Chunk := List.Head;
170            Chunk.Parent := List;
171         exception
172            when BC.Storage_Error =>
173               if List.Head = null then
174                  if The_Pool.Head = List then
175                     The_Pool.Head := List.Next_List;
176                  else
177                     Previous_List.Next_List := List.Next_List;
178                  end if;
179                  Dispose (List);
180               end if;
181               raise;
182         end;
183
184      end if;
185
186      Storage_Address := Chunk.Next_Element;
187      Chunk.Next_Element := Value_At (Chunk.Next_Element);
188
189   end Allocate;
190
191
192   procedure Deallocate
193     (The_Pool : in out Pool;
194      Storage_Address : System.Address;
195      Size_In_Storage_Elements : SSE.Storage_Count;
196      Alignment : SSE.Storage_Count)
197   is
198
199      Usable_Size : SSE.Storage_Count;
200      Usable_Alignment : SSE.Storage_Count;
201
202      List : Chunk_List_Pointer;
203
204   begin
205
206      --  Calculate the usable size and alignment.
207      Usable_Size_And_Alignment (Size_In_Storage_Elements,
208                                 Alignment,
209                                 Usable_Size,
210                                 Usable_Alignment);
211
212      --  Look for the right list
213      List := The_Pool.Head;
214      while List /= null and then
215        (List.Element_Size /= Usable_Size
216           or List.Alignment /= Usable_Alignment)
217      loop
218         List := List.Next_List;
219      end loop;
220      if List = null then
221         raise Pool_Error;
222      end if;
223
224      Put (List.Head.Next_Element, At_Location => Storage_Address);
225      List.Head.Next_Element := Storage_Address;
226      --  Note that the effect of the above is that the "linked list"
227      --  of elements will span chunks. This is necessary since
228      --  Deallocate is given an address of the element, not a pointer
229      --  to the containing chunk, and we don't want the overhead of
230      --  the search at this time. The user should call
231      --  Reclaim_Unused_Chunks at an appropriate moment.
232
233   end Deallocate;
234
235
236   function Dirty_Chunks (This : Pool) return Natural
237   is
238      Result : Natural := 0;
239      List : Chunk_List_Pointer;
240      Chunk : Chunk_Pointer;
241   begin
242      List := This.Head;
243      while List /= null loop
244         Chunk := List.Head;
245         while Chunk /= null loop
246            Result := Result + 1;
247            Chunk := Chunk.Next_Chunk;
248         end loop;
249         List := List.Next_List;
250      end loop;
251      return Result;
252   end Dirty_Chunks;
253
254
255   procedure Finalize (This : in out Pool)
256   is
257      List, Previous_List : Chunk_List_Pointer;
258      Chunk, Previous_Chunk : Chunk_Pointer;
259   begin
260      List := This.Head;
261      while List /= null loop
262         Chunk := List.Head;
263         while Chunk /= null loop
264            Previous_Chunk := Chunk;
265            Chunk := Chunk.Next_Chunk;
266            Dispose (Previous_Chunk);
267         end loop;
268         Previous_List := List;
269         List := List.Next_List;
270         Dispose (Previous_List);
271      end loop;
272   end Finalize;
273
274
275   procedure Get_Chunk (Result : out Chunk_Pointer;
276                        From : in out Pool;
277                        Requested_Element_Size : SSE.Storage_Count;
278                        Requested_Alignment : SSE.Storage_Count)
279   is
280
281      --  There are some tricky problems around the question of
282      --  alignment, especially when the requested alignment is
283      --  sufficiently large to impact the number of elements that can
284      --  live in a chunk (the chunk's payload's alignment is the
285      --  alignment of a System.Address).
286      --
287      --  This is normally not of any great significance: on i386
288      --  hardware, the maximum alignment is 8, while on PowerPC it is
289      --  4 (sometimes 8, depending on OS).
290      --
291      --  However, we can't calculate the number of elements that can
292      --  be held in the chunk until we've got the chunk.
293
294      --  The maximum that can be held if we turn out to be aligned
295      --  correctly; there may in fact turn out to be room for one less
296      --  element.
297      Usable_Chunk_Size : constant SSE.Storage_Count :=
298        (SSE.Storage_Count (From.Address_Array_Size) * Address_Size_SC
299           / Requested_Alignment)
300        * Requested_Alignment;
301
302      Next, Start, Stop : System.Address;
303
304      use type System.Address;
305      use type SSE.Integer_Address;
306
307   begin
308
309      if Requested_Element_Size > Usable_Chunk_Size then
310         --  We have no chance of meeting the requirement.
311         raise BC.Storage_Error;
312      end if;
313
314      if From.Unused /= null then
315         Result := From.Unused;
316         From.Unused := From.Unused.Next_Chunk;
317      else
318         Result := new Chunk (Address_Array_Size => From.Address_Array_Size);
319      end if;
320
321      declare
322         First : Positive := Result.Payload'First;
323      begin
324         --  Probably should be able to do this without a loop!
325         loop
326            exit when SSE.To_Integer (Result.Payload (First)'Address)
327              mod SSE.Integer_Address (Requested_Alignment) = 0;
328            First := First + 1;
329         end loop;
330         Start := Result.Payload (First)'Address;
331         Result.Usable_Chunk_Size :=
332           Usable_Chunk_Size
333           - Address_Size_SC * SSE.Storage_Count (First
334                                                    - Result.Payload'First);
335      end;
336
337      Result.Number_Elements :=
338        Result.Usable_Chunk_Size / Requested_Element_Size;
339
340      if Result.Number_Elements < 1 then
341         --  We have failed. Put the chunk back.
342         Result.Next_Chunk := From.Unused;
343         From.Unused := Result;
344         raise BC.Storage_Error;
345      end if;
346
347      Stop := Start + ((Result.Number_Elements - 1) * Requested_Element_Size);
348      Next := Start;
349      while Next < Stop loop
350         Put (Next + Requested_Element_Size, At_Location => Next);
351         Next := Next + Requested_Element_Size;
352      end loop;
353      Put (System.Null_Address, At_Location => Stop);
354      Result.Next_Element := Start;
355
356   end Get_Chunk;
357
358
359   procedure Initialize (This : in out Pool)
360   is
361   begin
362      This.Address_Array_Size :=
363        (Integer (This.Chunk_Size) + Address_Size_I - 1) / Address_Size_I;
364   end Initialize;
365
366
367   procedure Preallocate_Chunks (This : in out Pool; Count : Positive)
368   is
369      Ch : Chunk_Pointer;
370   begin
371      for K in 1 .. Count loop
372         Ch := new Chunk (Address_Array_Size => This.Address_Array_Size);
373         Ch.Next_Chunk := This.Unused;
374         This.Unused := Ch;
375      end loop;
376   end Preallocate_Chunks;
377
378
379   procedure Purge_Unused_Chunks (This : in out Pool)
380   is
381      Chunk : Chunk_Pointer;
382   begin
383      while This.Unused /= null loop
384         Chunk := This.Unused;
385         This.Unused := This.Unused.Next_Chunk;
386         Dispose (Chunk);
387      end loop;
388   end Purge_Unused_Chunks;
389
390
391   procedure Put (This : System.Address;
392                  At_Location : System.Address)
393   is
394   begin
395      PeekPoke.To_Pointer (At_Location).all := This;
396   end Put;
397
398
399   procedure Reclaim_Unused_Chunks (This : in out Pool)
400   is
401
402      List : Chunk_List_Pointer;
403      Previous_List : Chunk_List_Pointer;
404      Chunk : Chunk_Pointer;
405      Previous_Chunk : Chunk_Pointer;
406      Element : System.Address;
407      Previous_Element : System.Address; -- cjh
408
409      use SSE;
410      use type System.Address;
411
412   begin
413
414      pragma Style_Checks (Off); -- GNAT and GLIDE disagree about layout here
415
416      List := This.Head;
417      Previous_List := null;
418
419      while List /= null loop
420
421         Chunk := List.Head;
422
423         --  Compute the maximum number of elements possible, per chunk,
424         --  within this sized sublist.
425     Compute_Max :
426         while Chunk /= null loop
427            Chunk.Number_Elements :=
428              Chunk.Usable_Chunk_Size / Chunk.Parent.Element_Size;
429            Chunk := Chunk.Next_Chunk;
430         end loop Compute_Max;
431
432         --  Now we traverse the "linked list" of free elements that
433         --  span chunks, determining the containing chunk per element
434         --  and decrementing the corresponding count (computed as the
435         --  max, above).
436         Element := List.Head.Next_Element;
437
438     Decrement_Counts :
439         while Element /= System.Null_Address loop
440            Chunk := List.Head;
441
442        This_Chunk :
443            while Chunk /= null loop
444               if Within_Range (Element, Chunk) then
445
446                  Chunk.Number_Elements := Chunk.Number_Elements - 1;
447                  exit This_Chunk;
448
449               end if;
450               Chunk := Chunk.Next_Chunk;
451            end loop This_Chunk;
452            if Chunk = null then
453               raise Pool_Error;
454            end if;
455
456            Element := Value_At (Element); -- get next element
457
458         end loop Decrement_Counts;
459
460         --  Now walk each sized sublist and remove those chunks no
461         --  longer used.
462         Previous_Chunk := null;
463         Chunk := List.Head;
464
465     Reclaiming :
466         while Chunk /= null loop
467
468            if Chunk.Number_Elements = 0 then
469
470               --  Remove this chunk to the Unused list.
471
472               -- cjh: Elements on the "Next_Element" list and lying
473               --  within this chunk must be removed from the list.
474               Element := List.Head.Next_Element;
475               Previous_Element := System.Null_Address;
476
477               while Element /= System.Null_Address loop
478                  if Within_Range (Element, Chunk) then
479                     if Previous_Element = System.Null_Address then
480                        List.Head.Next_Element := Value_At (Element);
481                     else
482                        Put (Value_At (Element),
483                             At_Location => Previous_Element);
484                     end if;
485                  else
486                     Previous_Element := Element;
487                  end if;
488                  Element := Value_At (Element); -- get next element
489               end loop;
490               -- end cjh
491
492               if Previous_Chunk /= null then
493
494                  --  This isn't the first chunk in this list.
495                  Previous_Chunk.Next_Chunk := Chunk.Next_Chunk;
496                  Chunk.Next_Chunk := This.Unused;
497                  This.Unused := Chunk;
498                  Chunk := Previous_Chunk.Next_Chunk;
499
500               else
501
502                  --  This is the first chunk in this list.
503                  List.Head := Chunk.Next_Chunk;
504                  Chunk.Next_Chunk := This.Unused;
505                  This.Unused := Chunk;
506                  Chunk := List.Head;
507
508               end if;
509
510            else
511
512               --  Chunk isn't empty.
513               Previous_Chunk := Chunk;
514               Chunk := Chunk.Next_Chunk;
515
516            end if;
517
518         end loop Reclaiming;
519
520         --  If this list has no chunks, delete it.
521         if List.Head = null then
522
523            declare
524               Next_List : constant Chunk_List_Pointer := List.Next_List;
525            begin
526
527               if This.Head = List then
528
529                  --  This is the head list of the pool; make the next
530                  --  list the new head.
531                  This.Head := Next_List;
532
533               else
534
535                  --  This isn't the head list of the pool, so there
536                  --  is a previous list; make its next list this
537                  --  list's next list.
538                  if Previous_List = null then
539                     raise Pool_Error;
540                  end if;
541                  Previous_List.Next_List := Next_List;
542
543               end if;
544
545               Dispose (List);
546
547               List := Next_List;
548
549            end;
550
551         else
552
553            --  List wasn't empty
554            Previous_List := List;
555            List := List.Next_List;
556
557         end if;
558
559      end loop;
560      pragma Style_Checks (On);
561
562   end Reclaim_Unused_Chunks;
563
564
565   function Storage_Size (This : Pool) return SSE.Storage_Count
566   is
567      pragma Warnings (Off, This);
568   begin
569      return SSE.Storage_Count'Last; -- well, what else can we say!?
570   end Storage_Size;
571
572
573   function Total_Chunks (This : Pool) return Natural
574   is
575   begin
576      return Dirty_Chunks (This) + Unused_Chunks (This);
577   end Total_Chunks;
578
579
580   function Unused_Chunks (This : Pool) return Natural
581   is
582      Chunk : Chunk_Pointer;
583      Result : Natural := 0;
584   begin
585      Chunk := This.Unused;
586      while Chunk /= null loop
587         Result := Result + 1;
588         Chunk := Chunk.Next_Chunk;
589      end loop;
590      return Result;
591   end Unused_Chunks;
592
593
594   procedure Usable_Size_And_Alignment
595     (For_Size : SSE.Storage_Count;
596      For_Alignment : SSE.Storage_Count;
597      Is_Size : out SSE.Storage_Count;
598      Is_Alignment : out SSE.Storage_Count)
599   is
600      --  The usable alignment is at least the alignment of a
601      --  System.Address, because of the way that elements within a
602      --  chunk are chained.
603      --  The usable size must be a multiple of the size of a
604      --  System.Address, likewise.
605      Minimum_Size : constant SSE.Storage_Count :=
606        SSE.Storage_Count'Max (For_Size, Address_Size_SC);
607   begin
608      Is_Size :=
609        ((Minimum_Size + Address_Size_SC - 1) / Address_Size_SC)
610        * Address_Size_SC;
611      Is_Alignment :=
612        SSE.Storage_Count'Max (For_Alignment,
613                               System.Address'Alignment);
614   end Usable_Size_And_Alignment;
615
616
617   function Value_At (Location : System.Address) return System.Address
618   is
619   begin
620      return PeekPoke.To_Pointer (Location).all;
621   end Value_At;
622
623
624   function Within_Range (Target : System.Address;
625                          Base : Chunk_Pointer) return Boolean
626   is
627      use type System.Address;
628   begin
629
630      --  Element is within this chunk (NB, we check <= the last
631      --  address because this is a legal position, at least for
632      --  elements no larger than a System.Address).
633      return Base.Payload (Base.Payload'First)'Address <= Target
634        and Target <= Base.Payload (Base.Payload'Last)'Address;
635
636   end Within_Range;
637
638
639end BC.Support.Managed_Storage;
640