1------------------------------------------------------------------------------
2--                                                                          --
3--                         GNAT COMPILER COMPONENTS                         --
4--                                                                          --
5--                   G N A T . D Y N A M I C _ T A B L E S                  --
6--                                                                          --
7--                                 B o d y                                  --
8--                                                                          --
9--                     Copyright (C) 2000-2018, AdaCore                     --
10--                                                                          --
11-- GNAT is free software;  you can  redistribute it  and/or modify it under --
12-- terms of the  GNU General Public License as published  by the Free Soft- --
13-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
14-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
15-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
16-- or FITNESS FOR A PARTICULAR PURPOSE.                                     --
17--                                                                          --
18-- As a special exception under Section 7 of GPL version 3, you are granted --
19-- additional permissions described in the GCC Runtime Library Exception,   --
20-- version 3.1, as published by the Free Software Foundation.               --
21--                                                                          --
22-- You should have received a copy of the GNU General Public License and    --
23-- a copy of the GCC Runtime Library Exception along with this program;     --
24-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
25-- <http://www.gnu.org/licenses/>.                                          --
26--                                                                          --
27-- GNAT was originally developed  by the GNAT team at  New York University. --
28-- Extensive contributions were provided by Ada Core Technologies Inc.      --
29--                                                                          --
30------------------------------------------------------------------------------
31
32pragma Compiler_Unit_Warning;
33
34with GNAT.Heap_Sort_G;
35
36with Ada.Unchecked_Deallocation;
37with System;
38
39package body GNAT.Dynamic_Tables is
40
41   -----------------------
42   -- Local Subprograms --
43   -----------------------
44
45   function Last_Allocated (T : Instance) return Table_Last_Type;
46   pragma Inline (Last_Allocated);
47   --  Return the index of the last allocated element
48
49   procedure Grow (T : in out Instance; New_Last : Table_Last_Type);
50   --  This is called when we are about to set the value of Last to a value
51   --  that is larger than Last_Allocated. This reallocates the table to the
52   --  larger size, as indicated by New_Last. At the time this is called,
53   --  Last (T) is still the old value, and this does not modify it.
54
55   --------------
56   -- Allocate --
57   --------------
58
59   procedure Allocate (T : in out Instance; Num : Integer := 1) is
60   begin
61      --  Note that Num can be negative
62
63      pragma Assert (not T.Locked);
64      Set_Last (T, Last (T) + Table_Index_Type'Base (Num));
65   end Allocate;
66
67   ------------
68   -- Append --
69   ------------
70
71   procedure Append (T : in out Instance; New_Val : Table_Component_Type) is
72      pragma Assert (not T.Locked);
73      New_Last : constant Table_Last_Type := Last (T) + 1;
74
75   begin
76      if New_Last <= Last_Allocated (T) then
77
78         --  Fast path
79
80         T.P.Last := New_Last;
81         T.Table (New_Last) := New_Val;
82
83      else
84         Set_Item (T, New_Last, New_Val);
85      end if;
86   end Append;
87
88   ----------------
89   -- Append_All --
90   ----------------
91
92   procedure Append_All (T : in out Instance; New_Vals : Table_Type) is
93   begin
94      for J in New_Vals'Range loop
95         Append (T, New_Vals (J));
96      end loop;
97   end Append_All;
98
99   --------------------
100   -- Decrement_Last --
101   --------------------
102
103   procedure Decrement_Last (T : in out Instance) is
104   begin
105      pragma Assert (not T.Locked);
106      Allocate (T, -1);
107   end Decrement_Last;
108
109   -----------
110   -- First --
111   -----------
112
113   function First return Table_Index_Type is
114   begin
115      return Table_Low_Bound;
116   end First;
117
118   --------------
119   -- For_Each --
120   --------------
121
122   procedure For_Each (Table : Instance) is
123      Quit : Boolean := False;
124   begin
125      for Index in First .. Last (Table) loop
126         Action (Index, Table.Table (Index), Quit);
127         exit when Quit;
128      end loop;
129   end For_Each;
130
131   ----------
132   -- Grow --
133   ----------
134
135   procedure Grow (T : in out Instance; New_Last : Table_Last_Type) is
136
137      --  Note: Type Alloc_Ptr below needs to be declared locally so we know
138      --  the bounds. That means that the collection is local, so is finalized
139      --  when leaving Grow. That's why this package doesn't support controlled
140      --  types; the table elements would be finalized prematurely. An Ada
141      --  implementation would also be within its rights to reclaim the
142      --  storage. Fortunately, GNAT doesn't do that.
143
144      pragma Assert (not T.Locked);
145      pragma Assert (New_Last > Last_Allocated (T));
146
147      subtype Table_Length_Type is Table_Index_Type'Base
148        range 0 .. Table_Index_Type'Base'Last;
149
150      Old_Last_Allocated   : constant Table_Last_Type := Last_Allocated (T);
151      Old_Allocated_Length : constant Table_Length_Type :=
152                               Old_Last_Allocated - First + 1;
153
154      New_Length : constant Table_Length_Type := New_Last - First + 1;
155      New_Allocated_Length : Table_Length_Type;
156
157   begin
158      if T.Table = Empty_Table_Ptr then
159         New_Allocated_Length := Table_Length_Type (Table_Initial);
160      else
161         New_Allocated_Length :=
162           Table_Length_Type
163             (Long_Long_Integer (Old_Allocated_Length) *
164               (100 + Long_Long_Integer (Table_Increment)) / 100);
165      end if;
166
167      --  Make sure it really did grow
168
169      if New_Allocated_Length <= Old_Allocated_Length then
170         New_Allocated_Length := Old_Allocated_Length + 10;
171      end if;
172
173      if New_Allocated_Length <= New_Length then
174         New_Allocated_Length := New_Length + 10;
175      end if;
176
177      pragma Assert (New_Allocated_Length > Old_Allocated_Length);
178      pragma Assert (New_Allocated_Length > New_Length);
179
180      T.P.Last_Allocated := First + New_Allocated_Length - 1;
181
182      declare
183         subtype Old_Alloc_Type is Table_Type (First .. Old_Last_Allocated);
184         type Old_Alloc_Ptr is access all Old_Alloc_Type;
185
186         procedure Free is
187           new Ada.Unchecked_Deallocation (Old_Alloc_Type, Old_Alloc_Ptr);
188         function To_Old_Alloc_Ptr is
189           new Ada.Unchecked_Conversion (Table_Ptr, Old_Alloc_Ptr);
190
191         subtype Alloc_Type is
192           Table_Type (First .. First + New_Allocated_Length - 1);
193         type Alloc_Ptr is access all Alloc_Type;
194
195         function To_Table_Ptr is
196           new Ada.Unchecked_Conversion (Alloc_Ptr, Table_Ptr);
197
198         Old_Table : Old_Alloc_Ptr := To_Old_Alloc_Ptr (T.Table);
199         New_Table : constant Alloc_Ptr := new Alloc_Type;
200
201      begin
202         if T.Table /= Empty_Table_Ptr then
203            New_Table (First .. Last (T)) := Old_Table (First .. Last (T));
204            Free (Old_Table);
205         end if;
206
207         T.Table := To_Table_Ptr (New_Table);
208      end;
209
210      pragma Assert (New_Last <= Last_Allocated (T));
211      pragma Assert (T.Table /= null);
212      pragma Assert (T.Table /= Empty_Table_Ptr);
213   end Grow;
214
215   --------------------
216   -- Increment_Last --
217   --------------------
218
219   procedure Increment_Last (T : in out Instance) is
220   begin
221      pragma Assert (not T.Locked);
222      Allocate (T, 1);
223   end Increment_Last;
224
225   ----------
226   -- Init --
227   ----------
228
229   procedure Init (T : in out Instance) is
230      pragma Assert (not T.Locked);
231      subtype Alloc_Type is Table_Type (First .. Last_Allocated (T));
232      type Alloc_Ptr is access all Alloc_Type;
233
234      procedure Free is new Ada.Unchecked_Deallocation (Alloc_Type, Alloc_Ptr);
235      function To_Alloc_Ptr is
236        new Ada.Unchecked_Conversion (Table_Ptr, Alloc_Ptr);
237
238      Temp : Alloc_Ptr := To_Alloc_Ptr (T.Table);
239
240   begin
241      if T.Table = Empty_Table_Ptr then
242         pragma Assert (T.P = (Last_Allocated | Last => First - 1));
243         null;
244      else
245         Free (Temp);
246         T.Table := Empty_Table_Ptr;
247         T.P := (Last_Allocated | Last => First - 1);
248      end if;
249   end Init;
250
251   --------------
252   -- Is_Empty --
253   --------------
254
255   function Is_Empty (T : Instance) return Boolean is
256   begin
257      return Last (T) = First - 1;
258   end Is_Empty;
259
260   ----------
261   -- Last --
262   ----------
263
264   function Last (T : Instance) return Table_Last_Type is
265   begin
266      return T.P.Last;
267   end Last;
268
269   --------------------
270   -- Last_Allocated --
271   --------------------
272
273   function Last_Allocated (T : Instance) return Table_Last_Type is
274   begin
275      return T.P.Last_Allocated;
276   end Last_Allocated;
277
278   ----------
279   -- Move --
280   ----------
281
282   procedure Move (From, To : in out Instance) is
283   begin
284      pragma Assert (not From.Locked);
285      pragma Assert (not To.Locked);
286      pragma Assert (Is_Empty (To));
287      To := From;
288
289      From.Table            := Empty_Table_Ptr;
290      From.Locked           := False;
291      From.P.Last_Allocated := First - 1;
292      From.P.Last           := First - 1;
293      pragma Assert (Is_Empty (From));
294   end Move;
295
296   -------------
297   -- Release --
298   -------------
299
300   procedure Release (T : in out Instance) is
301      pragma Assert (not T.Locked);
302      Old_Last_Allocated : constant Table_Last_Type := Last_Allocated (T);
303
304      function New_Last_Allocated return Table_Last_Type;
305      --  Compute the new value of Last_Allocated. This is normally equal to
306      --  Last, but if Release_Threshold /= 0, then we need to take that into
307      --  account.
308
309      ------------------------
310      -- New_Last_Allocated --
311      ------------------------
312
313      function New_Last_Allocated return Table_Last_Type is
314         subtype Table_Length_Type is Table_Index_Type'Base
315           range 0 .. Table_Index_Type'Base'Last;
316
317         Length : constant Table_Length_Type := Last (T) - First + 1;
318
319         Comp_Size_In_Bytes : constant Table_Length_Type :=
320           Table_Type'Component_Size / System.Storage_Unit;
321
322         Length_Threshold : constant Table_Length_Type :=
323           Table_Length_Type (Release_Threshold) / Comp_Size_In_Bytes;
324
325      begin
326         if Release_Threshold = 0 or else Length < Length_Threshold then
327            return Last (T);
328         else
329            declare
330               Extra_Length : constant Table_Length_Type := Length / 1000;
331            begin
332               return (Length + Extra_Length) - 1 + First;
333            end;
334         end if;
335      end New_Last_Allocated;
336
337      --  Local variables
338
339      New_Last_Alloc : constant Table_Last_Type := New_Last_Allocated;
340
341   --  Start of processing for Release
342
343   begin
344      if New_Last_Alloc < Last_Allocated (T) then
345         pragma Assert (Last (T) < Last_Allocated (T));
346         pragma Assert (T.Table /= Empty_Table_Ptr);
347
348         declare
349            subtype Old_Alloc_Type is Table_Type (First .. Old_Last_Allocated);
350            type Old_Alloc_Ptr is access all Old_Alloc_Type;
351
352            procedure Free is
353              new Ada.Unchecked_Deallocation (Old_Alloc_Type, Old_Alloc_Ptr);
354            function To_Old_Alloc_Ptr is
355              new Ada.Unchecked_Conversion (Table_Ptr, Old_Alloc_Ptr);
356
357            subtype Alloc_Type is Table_Type (First .. New_Last_Alloc);
358            type Alloc_Ptr is access all Alloc_Type;
359
360            function To_Table_Ptr is
361              new Ada.Unchecked_Conversion (Alloc_Ptr, Table_Ptr);
362
363            Old_Table : Old_Alloc_Ptr := To_Old_Alloc_Ptr (T.Table);
364            New_Table : constant Alloc_Ptr := new Alloc_Type;
365
366         begin
367            New_Table (First .. Last (T)) := Old_Table (First .. Last (T));
368            T.P.Last_Allocated := New_Last_Alloc;
369            Free (Old_Table);
370            T.Table := To_Table_Ptr (New_Table);
371         end;
372      end if;
373   end Release;
374
375   --------------
376   -- Set_Item --
377   --------------
378
379   procedure Set_Item
380     (T     : in out Instance;
381      Index : Valid_Table_Index_Type;
382      Item  : Table_Component_Type)
383   is
384   begin
385      pragma Assert (not T.Locked);
386
387      --  If Set_Last is going to reallocate the table, we make a copy of Item,
388      --  in case the call was "Set_Item (T, X, T.Table (Y));", and Item is
389      --  passed by reference. Without the copy, we would deallocate the array
390      --  containing Item, leaving a dangling pointer.
391
392      if Index > Last_Allocated (T) then
393         declare
394            Item_Copy : constant Table_Component_Type := Item;
395         begin
396            Set_Last (T, Index);
397            T.Table (Index) := Item_Copy;
398         end;
399
400      else
401         if Index > Last (T) then
402            Set_Last (T, Index);
403         end if;
404
405         T.Table (Index) := Item;
406      end if;
407   end Set_Item;
408
409   --------------
410   -- Set_Last --
411   --------------
412
413   procedure Set_Last (T : in out Instance; New_Val : Table_Last_Type) is
414   begin
415      pragma Assert (not T.Locked);
416      if New_Val > Last_Allocated (T) then
417         Grow (T, New_Val);
418      end if;
419
420      T.P.Last := New_Val;
421   end Set_Last;
422
423   ----------------
424   -- Sort_Table --
425   ----------------
426
427   procedure Sort_Table (Table : in out Instance) is
428      Temp : Table_Component_Type;
429      --  A temporary position to simulate index 0
430
431      --  Local subprograms
432
433      function Index_Of (Idx : Natural) return Table_Index_Type'Base;
434      --  Return index of Idx'th element of table
435
436      function Lower_Than (Op1, Op2 : Natural) return Boolean;
437      --  Compare two components
438
439      procedure Move (From : Natural; To : Natural);
440      --  Move one component
441
442      package Heap_Sort is new GNAT.Heap_Sort_G (Move, Lower_Than);
443
444      --------------
445      -- Index_Of --
446      --------------
447
448      function Index_Of (Idx : Natural) return Table_Index_Type'Base is
449         J : constant Integer'Base :=
450               Table_Index_Type'Base'Pos (First) + Idx - 1;
451      begin
452         return Table_Index_Type'Base'Val (J);
453      end Index_Of;
454
455      ----------
456      -- Move --
457      ----------
458
459      procedure Move (From : Natural; To : Natural) is
460      begin
461         if From = 0 then
462            Table.Table (Index_Of (To)) := Temp;
463
464         elsif To = 0 then
465            Temp := Table.Table (Index_Of (From));
466
467         else
468            Table.Table (Index_Of (To)) :=
469              Table.Table (Index_Of (From));
470         end if;
471      end Move;
472
473      ----------------
474      -- Lower_Than --
475      ----------------
476
477      function Lower_Than (Op1, Op2 : Natural) return Boolean is
478      begin
479         if Op1 = 0 then
480            return Lt (Temp, Table.Table (Index_Of (Op2)));
481
482         elsif Op2 = 0 then
483            return Lt (Table.Table (Index_Of (Op1)), Temp);
484
485         else
486            return
487              Lt (Table.Table (Index_Of (Op1)), Table.Table (Index_Of (Op2)));
488         end if;
489      end Lower_Than;
490
491   --  Start of processing for Sort_Table
492
493   begin
494      Heap_Sort.Sort (Natural (Last (Table) - First) + 1);
495   end Sort_Table;
496
497end GNAT.Dynamic_Tables;
498