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