2015-05-12 Robert Dewar <dewar@adacore.com>
[official-gcc.git] / gcc / ada / a-chtgop.adb
blobdda5f2cccf7db4fa493dc0f604123ef074e9a9e7
1 ------------------------------------------------------------------------------
2 -- --
3 -- GNAT LIBRARY COMPONENTS --
4 -- --
5 -- ADA.CONTAINERS.HASH_TABLES.GENERIC_OPERATIONS --
6 -- --
7 -- B o d y --
8 -- --
9 -- Copyright (C) 2004-2014, Free Software Foundation, Inc. --
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 -- This unit was originally developed by Matthew J Heaney. --
28 ------------------------------------------------------------------------------
30 with Ada.Containers.Prime_Numbers;
31 with Ada.Unchecked_Deallocation;
33 with System; use type System.Address;
35 package body Ada.Containers.Hash_Tables.Generic_Operations is
37 type Buckets_Allocation is access all Buckets_Type;
38 -- Used for allocation and deallocation (see New_Buckets and Free_Buckets).
39 -- This is necessary because Buckets_Access has an empty storage pool.
41 ------------
42 -- Adjust --
43 ------------
45 procedure Adjust (HT : in out Hash_Table_Type) is
46 Src_Buckets : constant Buckets_Access := HT.Buckets;
47 N : constant Count_Type := HT.Length;
48 Src_Node : Node_Access;
49 Dst_Prev : Node_Access;
51 begin
52 HT.Buckets := null;
53 HT.Length := 0;
55 if N = 0 then
56 return;
57 end if;
59 -- Technically it isn't necessary to allocate the exact same length
60 -- buckets array, because our only requirement is that following
61 -- assignment the source and target containers compare equal (that is,
62 -- operator "=" returns True). We can satisfy this requirement with any
63 -- hash table length, but we decide here to match the length of the
64 -- source table. This has the benefit that when iterating, elements of
65 -- the target are delivered in the exact same order as for the source.
67 HT.Buckets := New_Buckets (Length => Src_Buckets'Length);
69 for Src_Index in Src_Buckets'Range loop
70 Src_Node := Src_Buckets (Src_Index);
72 if Src_Node /= null then
73 declare
74 Dst_Node : constant Node_Access := Copy_Node (Src_Node);
76 -- See note above
78 pragma Assert (Checked_Index (HT, Dst_Node) = Src_Index);
80 begin
81 HT.Buckets (Src_Index) := Dst_Node;
82 HT.Length := HT.Length + 1;
84 Dst_Prev := Dst_Node;
85 end;
87 Src_Node := Next (Src_Node);
88 while Src_Node /= null loop
89 declare
90 Dst_Node : constant Node_Access := Copy_Node (Src_Node);
92 -- See note above
94 pragma Assert (Checked_Index (HT, Dst_Node) = Src_Index);
96 begin
97 Set_Next (Node => Dst_Prev, Next => Dst_Node);
98 HT.Length := HT.Length + 1;
100 Dst_Prev := Dst_Node;
101 end;
103 Src_Node := Next (Src_Node);
104 end loop;
105 end if;
106 end loop;
108 pragma Assert (HT.Length = N);
109 end Adjust;
111 --------------
112 -- Capacity --
113 --------------
115 function Capacity (HT : Hash_Table_Type) return Count_Type is
116 begin
117 if HT.Buckets = null then
118 return 0;
119 end if;
121 return HT.Buckets'Length;
122 end Capacity;
124 -------------------
125 -- Checked_Index --
126 -------------------
128 function Checked_Index
129 (Hash_Table : aliased in out Hash_Table_Type;
130 Buckets : Buckets_Type;
131 Node : Node_Access) return Hash_Type
133 Result : Hash_Type;
135 B : Natural renames Hash_Table.Busy;
136 L : Natural renames Hash_Table.Lock;
138 begin
139 B := B + 1;
140 L := L + 1;
142 Result := Index (Buckets, Node);
144 B := B - 1;
145 L := L - 1;
147 return Result;
149 exception
150 when others =>
151 B := B - 1;
152 L := L - 1;
154 raise;
155 end Checked_Index;
157 function Checked_Index
158 (Hash_Table : aliased in out Hash_Table_Type;
159 Node : Node_Access) return Hash_Type
161 begin
162 return Checked_Index (Hash_Table, Hash_Table.Buckets.all, Node);
163 end Checked_Index;
165 -----------
166 -- Clear --
167 -----------
169 procedure Clear (HT : in out Hash_Table_Type) is
170 Index : Hash_Type := 0;
171 Node : Node_Access;
173 begin
174 if HT.Busy > 0 then
175 raise Program_Error with
176 "attempt to tamper with cursors (container is busy)";
177 end if;
179 while HT.Length > 0 loop
180 while HT.Buckets (Index) = null loop
181 Index := Index + 1;
182 end loop;
184 declare
185 Bucket : Node_Access renames HT.Buckets (Index);
186 begin
187 loop
188 Node := Bucket;
189 Bucket := Next (Bucket);
190 HT.Length := HT.Length - 1;
191 Free (Node);
192 exit when Bucket = null;
193 end loop;
194 end;
195 end loop;
196 end Clear;
198 --------------------------
199 -- Delete_Node_At_Index --
200 --------------------------
202 procedure Delete_Node_At_Index
203 (HT : in out Hash_Table_Type;
204 Indx : Hash_Type;
205 X : in out Node_Access)
207 Prev : Node_Access;
208 Curr : Node_Access;
210 begin
211 Prev := HT.Buckets (Indx);
213 if Prev = X then
214 HT.Buckets (Indx) := Next (Prev);
215 HT.Length := HT.Length - 1;
216 Free (X);
217 return;
218 end if;
220 if HT.Length = 1 then
221 raise Program_Error with
222 "attempt to delete node not in its proper hash bucket";
223 end if;
225 loop
226 Curr := Next (Prev);
228 if Curr = null then
229 raise Program_Error with
230 "attempt to delete node not in its proper hash bucket";
231 end if;
233 if Curr = X then
234 Set_Next (Node => Prev, Next => Next (Curr));
235 HT.Length := HT.Length - 1;
236 Free (X);
237 return;
238 end if;
240 Prev := Curr;
241 end loop;
242 end Delete_Node_At_Index;
244 ---------------------------
245 -- Delete_Node_Sans_Free --
246 ---------------------------
248 procedure Delete_Node_Sans_Free
249 (HT : in out Hash_Table_Type;
250 X : Node_Access)
252 pragma Assert (X /= null);
254 Indx : Hash_Type;
255 Prev : Node_Access;
256 Curr : Node_Access;
258 begin
259 if HT.Length = 0 then
260 raise Program_Error with
261 "attempt to delete node from empty hashed container";
262 end if;
264 Indx := Checked_Index (HT, X);
265 Prev := HT.Buckets (Indx);
267 if Prev = null then
268 raise Program_Error with
269 "attempt to delete node from empty hash bucket";
270 end if;
272 if Prev = X then
273 HT.Buckets (Indx) := Next (Prev);
274 HT.Length := HT.Length - 1;
275 return;
276 end if;
278 if HT.Length = 1 then
279 raise Program_Error with
280 "attempt to delete node not in its proper hash bucket";
281 end if;
283 loop
284 Curr := Next (Prev);
286 if Curr = null then
287 raise Program_Error with
288 "attempt to delete node not in its proper hash bucket";
289 end if;
291 if Curr = X then
292 Set_Next (Node => Prev, Next => Next (Curr));
293 HT.Length := HT.Length - 1;
294 return;
295 end if;
297 Prev := Curr;
298 end loop;
299 end Delete_Node_Sans_Free;
301 --------------
302 -- Finalize --
303 --------------
305 procedure Finalize (HT : in out Hash_Table_Type) is
306 begin
307 Clear (HT);
308 Free_Buckets (HT.Buckets);
309 end Finalize;
311 -----------
312 -- First --
313 -----------
315 function First (HT : Hash_Table_Type) return Node_Access is
316 Indx : Hash_Type;
318 begin
319 if HT.Length = 0 then
320 return null;
321 end if;
323 Indx := HT.Buckets'First;
324 loop
325 if HT.Buckets (Indx) /= null then
326 return HT.Buckets (Indx);
327 end if;
329 Indx := Indx + 1;
330 end loop;
331 end First;
333 ------------------
334 -- Free_Buckets --
335 ------------------
337 procedure Free_Buckets (Buckets : in out Buckets_Access) is
338 procedure Free is
339 new Ada.Unchecked_Deallocation (Buckets_Type, Buckets_Allocation);
341 begin
342 -- Buckets must have been created by New_Buckets. Here, we convert back
343 -- to the Buckets_Allocation type, and do the free on that.
345 Free (Buckets_Allocation (Buckets));
346 end Free_Buckets;
348 ---------------------
349 -- Free_Hash_Table --
350 ---------------------
352 procedure Free_Hash_Table (Buckets : in out Buckets_Access) is
353 Node : Node_Access;
355 begin
356 if Buckets = null then
357 return;
358 end if;
360 for J in Buckets'Range loop
361 while Buckets (J) /= null loop
362 Node := Buckets (J);
363 Buckets (J) := Next (Node);
364 Free (Node);
365 end loop;
366 end loop;
368 Free_Buckets (Buckets);
369 end Free_Hash_Table;
371 -------------------
372 -- Generic_Equal --
373 -------------------
375 function Generic_Equal
376 (L, R : Hash_Table_Type) return Boolean
378 BL : Natural renames L'Unrestricted_Access.Busy;
379 LL : Natural renames L'Unrestricted_Access.Lock;
381 BR : Natural renames R'Unrestricted_Access.Busy;
382 LR : Natural renames R'Unrestricted_Access.Lock;
384 Result : Boolean;
386 L_Index : Hash_Type;
387 L_Node : Node_Access;
389 N : Count_Type;
391 begin
392 if L'Address = R'Address then
393 return True;
394 end if;
396 if L.Length /= R.Length then
397 return False;
398 end if;
400 if L.Length = 0 then
401 return True;
402 end if;
404 -- Find the first node of hash table L
406 L_Index := 0;
407 loop
408 L_Node := L.Buckets (L_Index);
409 exit when L_Node /= null;
410 L_Index := L_Index + 1;
411 end loop;
413 -- Per AI05-0022, the container implementation is required to detect
414 -- element tampering by a generic actual subprogram.
416 BL := BL + 1;
417 LL := LL + 1;
419 BR := BR + 1;
420 LR := LR + 1;
422 -- For each node of hash table L, search for an equivalent node in hash
423 -- table R.
425 N := L.Length;
426 loop
427 if not Find (HT => R, Key => L_Node) then
428 Result := False;
429 exit;
430 end if;
432 N := N - 1;
434 L_Node := Next (L_Node);
436 if L_Node = null then
437 -- We have exhausted the nodes in this bucket
439 if N = 0 then
440 Result := True;
441 exit;
442 end if;
444 -- Find the next bucket
446 loop
447 L_Index := L_Index + 1;
448 L_Node := L.Buckets (L_Index);
449 exit when L_Node /= null;
450 end loop;
451 end if;
452 end loop;
454 BL := BL - 1;
455 LL := LL - 1;
457 BR := BR - 1;
458 LR := LR - 1;
460 return Result;
462 exception
463 when others =>
464 BL := BL - 1;
465 LL := LL - 1;
467 BR := BR - 1;
468 LR := LR - 1;
470 raise;
471 end Generic_Equal;
473 -----------------------
474 -- Generic_Iteration --
475 -----------------------
477 procedure Generic_Iteration (HT : Hash_Table_Type) is
478 Node : Node_Access;
480 begin
481 if HT.Length = 0 then
482 return;
483 end if;
485 for Indx in HT.Buckets'Range loop
486 Node := HT.Buckets (Indx);
487 while Node /= null loop
488 Process (Node);
489 Node := Next (Node);
490 end loop;
491 end loop;
492 end Generic_Iteration;
494 ------------------
495 -- Generic_Read --
496 ------------------
498 procedure Generic_Read
499 (Stream : not null access Root_Stream_Type'Class;
500 HT : out Hash_Table_Type)
502 N : Count_Type'Base;
503 NN : Hash_Type;
505 begin
506 Clear (HT);
508 Count_Type'Base'Read (Stream, N);
510 if N < 0 then
511 raise Program_Error with "stream appears to be corrupt";
512 end if;
514 if N = 0 then
515 return;
516 end if;
518 -- The RM does not specify whether or how the capacity changes when a
519 -- hash table is streamed in. Therefore we decide here to allocate a new
520 -- buckets array only when it's necessary to preserve representation
521 -- invariants.
523 if HT.Buckets = null
524 or else HT.Buckets'Length < N
525 then
526 Free_Buckets (HT.Buckets);
527 NN := Prime_Numbers.To_Prime (N);
528 HT.Buckets := New_Buckets (Length => NN);
529 end if;
531 for J in 1 .. N loop
532 declare
533 Node : constant Node_Access := New_Node (Stream);
534 Indx : constant Hash_Type := Checked_Index (HT, Node);
535 B : Node_Access renames HT.Buckets (Indx);
536 begin
537 Set_Next (Node => Node, Next => B);
538 B := Node;
539 end;
541 HT.Length := HT.Length + 1;
542 end loop;
543 end Generic_Read;
545 -------------------
546 -- Generic_Write --
547 -------------------
549 procedure Generic_Write
550 (Stream : not null access Root_Stream_Type'Class;
551 HT : Hash_Table_Type)
553 procedure Write (Node : Node_Access);
554 pragma Inline (Write);
556 procedure Write is new Generic_Iteration (Write);
558 -----------
559 -- Write --
560 -----------
562 procedure Write (Node : Node_Access) is
563 begin
564 Write (Stream, Node);
565 end Write;
567 begin
568 -- See Generic_Read for an explanation of why we do not stream out the
569 -- buckets array length too.
571 Count_Type'Base'Write (Stream, HT.Length);
572 Write (HT);
573 end Generic_Write;
575 -----------
576 -- Index --
577 -----------
579 function Index
580 (Buckets : Buckets_Type;
581 Node : Node_Access) return Hash_Type is
582 begin
583 return Hash_Node (Node) mod Buckets'Length;
584 end Index;
586 function Index
587 (Hash_Table : Hash_Table_Type;
588 Node : Node_Access) return Hash_Type is
589 begin
590 return Index (Hash_Table.Buckets.all, Node);
591 end Index;
593 ----------
594 -- Move --
595 ----------
597 procedure Move (Target, Source : in out Hash_Table_Type) is
598 begin
599 if Target'Address = Source'Address then
600 return;
601 end if;
603 if Source.Busy > 0 then
604 raise Program_Error with
605 "attempt to tamper with cursors (container is busy)";
606 end if;
608 Clear (Target);
610 declare
611 Buckets : constant Buckets_Access := Target.Buckets;
612 begin
613 Target.Buckets := Source.Buckets;
614 Source.Buckets := Buckets;
615 end;
617 Target.Length := Source.Length;
618 Source.Length := 0;
619 end Move;
621 -----------------
622 -- New_Buckets --
623 -----------------
625 function New_Buckets (Length : Hash_Type) return Buckets_Access is
626 subtype Rng is Hash_Type range 0 .. Length - 1;
628 begin
629 -- Allocate in Buckets_Allocation'Storage_Pool, then convert to
630 -- Buckets_Access.
632 return Buckets_Access (Buckets_Allocation'(new Buckets_Type (Rng)));
633 end New_Buckets;
635 ----------
636 -- Next --
637 ----------
639 function Next
640 (HT : aliased in out Hash_Table_Type;
641 Node : Node_Access) return Node_Access
643 Result : Node_Access;
644 First : Hash_Type;
646 begin
647 Result := Next (Node);
649 if Result /= null then
650 return Result;
651 end if;
653 First := Checked_Index (HT, Node) + 1;
654 for Indx in First .. HT.Buckets'Last loop
655 Result := HT.Buckets (Indx);
657 if Result /= null then
658 return Result;
659 end if;
660 end loop;
662 return null;
663 end Next;
665 ----------------------
666 -- Reserve_Capacity --
667 ----------------------
669 procedure Reserve_Capacity
670 (HT : in out Hash_Table_Type;
671 N : Count_Type)
673 NN : Hash_Type;
675 begin
676 if HT.Buckets = null then
677 if N > 0 then
678 NN := Prime_Numbers.To_Prime (N);
679 HT.Buckets := New_Buckets (Length => NN);
680 end if;
682 return;
683 end if;
685 if HT.Length = 0 then
687 -- This is the easy case. There are no nodes, so no rehashing is
688 -- necessary. All we need to do is allocate a new buckets array
689 -- having a length implied by the specified capacity. (We say
690 -- "implied by" because bucket arrays are always allocated with a
691 -- length that corresponds to a prime number.)
693 if N = 0 then
694 Free_Buckets (HT.Buckets);
695 return;
696 end if;
698 if N = HT.Buckets'Length then
699 return;
700 end if;
702 NN := Prime_Numbers.To_Prime (N);
704 if NN = HT.Buckets'Length then
705 return;
706 end if;
708 declare
709 X : Buckets_Access := HT.Buckets;
710 pragma Warnings (Off, X);
711 begin
712 HT.Buckets := New_Buckets (Length => NN);
713 Free_Buckets (X);
714 end;
716 return;
717 end if;
719 if N = HT.Buckets'Length then
720 return;
721 end if;
723 if N < HT.Buckets'Length then
725 -- This is a request to contract the buckets array. The amount of
726 -- contraction is bounded in order to preserve the invariant that the
727 -- buckets array length is never smaller than the number of elements
728 -- (the load factor is 1).
730 if HT.Length >= HT.Buckets'Length then
731 return;
732 end if;
734 NN := Prime_Numbers.To_Prime (HT.Length);
736 if NN >= HT.Buckets'Length then
737 return;
738 end if;
740 else
741 NN := Prime_Numbers.To_Prime (Count_Type'Max (N, HT.Length));
743 if NN = HT.Buckets'Length then -- can't expand any more
744 return;
745 end if;
746 end if;
748 if HT.Busy > 0 then
749 raise Program_Error with
750 "attempt to tamper with cursors (container is busy)";
751 end if;
753 Rehash : declare
754 Dst_Buckets : Buckets_Access := New_Buckets (Length => NN);
755 Src_Buckets : Buckets_Access := HT.Buckets;
756 pragma Warnings (Off, Src_Buckets);
758 L : Count_Type renames HT.Length;
759 LL : constant Count_Type := L;
761 Src_Index : Hash_Type := Src_Buckets'First;
763 begin
764 while L > 0 loop
765 declare
766 Src_Bucket : Node_Access renames Src_Buckets (Src_Index);
768 begin
769 while Src_Bucket /= null loop
770 declare
771 Src_Node : constant Node_Access := Src_Bucket;
773 Dst_Index : constant Hash_Type :=
774 Checked_Index (HT, Dst_Buckets.all, Src_Node);
776 Dst_Bucket : Node_Access renames Dst_Buckets (Dst_Index);
778 begin
779 Src_Bucket := Next (Src_Node);
781 Set_Next (Src_Node, Dst_Bucket);
783 Dst_Bucket := Src_Node;
784 end;
786 pragma Assert (L > 0);
787 L := L - 1;
788 end loop;
790 exception
791 when others =>
793 -- If there's an error computing a hash value during a
794 -- rehash, then AI-302 says the nodes "become lost." The
795 -- issue is whether to actually deallocate these lost nodes,
796 -- since they might be designated by extant cursors. Here
797 -- we decide to deallocate the nodes, since it's better to
798 -- solve real problems (storage consumption) rather than
799 -- imaginary ones (the user might, or might not, dereference
800 -- a cursor designating a node that has been deallocated),
801 -- and because we have a way to vet a dangling cursor
802 -- reference anyway, and hence can actually detect the
803 -- problem.
805 for Dst_Index in Dst_Buckets'Range loop
806 declare
807 B : Node_Access renames Dst_Buckets (Dst_Index);
808 X : Node_Access;
809 begin
810 while B /= null loop
811 X := B;
812 B := Next (X);
813 Free (X);
814 end loop;
815 end;
816 end loop;
818 Free_Buckets (Dst_Buckets);
819 raise Program_Error with
820 "hash function raised exception during rehash";
821 end;
823 Src_Index := Src_Index + 1;
824 end loop;
826 HT.Buckets := Dst_Buckets;
827 HT.Length := LL;
829 Free_Buckets (Src_Buckets);
830 end Rehash;
831 end Reserve_Capacity;
833 end Ada.Containers.Hash_Tables.Generic_Operations;