123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154(*
* Copyright (c) 2021 Craig Ferguson <me@craigfe.io>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)let(=):int->int->bool=(=)let(<>):int->int->bool=(<>)typeptr=intletslot_taken=-1letfree_list_nil=-2(* [extra_data] is for keeping pointers passed to C alive. *)type'aentry=|Empty:'aentry|Entry:{data:'a;extra_data:'b;mutableptr:int}->'aentry(* Free-list allocator *)type'at={mutabledata:'aentryarray(* Pool of potentially-empty data slots. Invariant: an unfreed pointer [p]
into this array is valid iff [free_tail_relation.(p) = slot_taken]. *);mutablefree_head:ptr;mutablefree_tail_relation:ptrarray(* A linked list of pointers to free slots, with [free_head] being the first
element and [free_tail_relation] mapping each free slot to the next one.
Each entry [x] signals a state of the corresponding [data.(x)] slot:
- [x = slot_taken]: the data slot is taken;
- [x = free_list_nil]: the data slot is free, and is last to be allocated;
- [0 <= x < length data]: the data slot is free, and will be allocated before
[free_tail_relation.(x)].
The user is given only pointers [p] such that [free_tail_relation.(p) =
slot_taken]. *);mutablein_use:int(* Negative after release *)}letptr=function|Entry{ptr=-1;_}->invalid_arg"Entry has already been freed!"|Entry{ptr;_}->ptr|Empty->assertfalseletcreate:typea.int->at=funn->ifn<0||n>Sys.max_array_lengththeninvalid_arg"Heap.create";(* Every slot is free, and all but the last have a free successor. *)letfree_head=ifn=0thenfree_list_nilelse0inletfree_tail_relation=Array.initnsuccinifn>0thenfree_tail_relation.(n-1)<-free_list_nil;letdata=(* No slot in [free_tail_relation] is [slot_taken], so initial data is
inaccessible. *)Array.makenEmptyin{data;free_head;free_tail_relation;in_use=0}letin_uset=t.in_useletis_releasedt=t.in_use<0letmaybe_already_releasedt=ifis_releasedttheninvalid_arg"Heap already released!"letreleaset=ift.in_use>0theninvalid_arg"Heap still in use!";maybe_already_releasedt;t.in_use<--100;t.free_head<-free_list_nil(* Note: t must be full *)letgrowt=maybe_already_releasedt;ift.free_head<>free_list_niltheninvalid_arg"Heap is not full";letold_len=Array.lengtht.free_tail_relationinifold_len=Sys.max_array_lengththeninvalid_arg"Heap at Sys.max_array_length already";letnew_len=min(max64(old_len*2))Sys.max_array_lengthin(* Build new t.free_tail_relation, keep in sync with create() *)letnew_free_tail_relation=Array.initnew_len(funi->ifi<old_lenthent.free_tail_relation.(i)elsesucci)innew_free_tail_relation.(new_len-1)<-free_list_nil;(* First element of enlarged array *)letnew_free_head=old_lenin(* Note: Keep in sync with create() *)letnew_data=Array.initnew_len(funi->ifi<old_lenthent.data.(i)elseEmpty)in(* Commit *)t.free_tail_relation<-new_free_tail_relation;t.free_head<-new_free_head;t.data<-new_dataletalloctdata~extra_data=ift.free_head=free_list_nilthengrowt;letptr=t.free_headinletentry=Entry{data;extra_data;ptr}int.data.(ptr)<-entry;(* Drop [ptr] from the free list. *)lettail=t.free_tail_relation.(ptr)int.free_tail_relation.(ptr)<-slot_taken;t.free_head<-tail;t.in_use<-t.in_use+1;entryletfreetptr=assert(ptr>=0)(* [alloc] returns only valid pointers. *);ifptr>=Array.lengtht.datathenFmt.invalid_arg"Heap.free: invalid pointer %d"ptr;letslot_state=t.free_tail_relation.(ptr)inifslot_state<>slot_takentheninvalid_arg"Heap.free: pointer already freed";(* [t.free_tail_relation.(ptr) = slot_taken], so [t.data.(ptr)] is valid. *)letdatum=matcht.data.(ptr)with|Empty->assertfalse|Entryp->p.ptr<--1;p.datain(* Cons [ptr] to the free-list. *)t.free_tail_relation.(ptr)<-t.free_head;t.free_head<-ptr;(* We've marked this slot as free, so [t.data.(ptr)] is inaccessible. We zero
it to allow it to be GC-ed. *)assert(t.free_tail_relation.(ptr)<>slot_taken);t.data.(ptr)<-Empty;(* Extra-data can be GC'd here *)t.in_use<-t.in_use-1;datum