@@ -7,9 +7,11 @@ struct hash_pair {
77 R_xlen_t value ;
88};
99struct hash_tab {
10+ hashtab public ; // must be the first member
1011 size_t size , free ;
1112 int shift ;
1213 struct hash_pair * table ;
14+ struct hash_tab * next ;
1315};
1416
1517static const double default_load_factor = .75 ;
@@ -45,38 +47,65 @@ static R_INLINE size_t get_full_size(size_t n_elements, double load_factor) {
4547 return pow2 ;
4648}
4749
48- static hashtab * hash_create_ (size_t n , double load_factor ) {
50+ static struct hash_tab * hash_create_ (size_t n , double load_factor ) {
4951 size_t n_full = get_full_size (n , load_factor );
50- hashtab * ret = (hashtab * )R_alloc (sizeof (hashtab ), 1 );
52+
53+ struct hash_tab * ret = malloc (sizeof * ret );
54+ if (!ret )
55+ return NULL ; // #nocov
56+
57+ // Not strictly portable but faster than an explicit loop setting keys to NULL
58+ struct hash_pair * table = calloc (n_full , sizeof (* table ));
59+ if (!table ) {
60+ // #nocov start
61+ free (ret );
62+ return NULL ;
63+ // #nocov end
64+ }
65+
5166 ret -> size = n_full ;
5267 ret -> free = (size_t )(n_full * load_factor );
5368
5469 int k = 0 ;
5570 while ((1ULL << k ) < n_full ) k ++ ;
5671 ret -> shift = HASH_BITS - k ;
57- ret -> table = ( struct hash_pair * ) R_alloc ( n_full , sizeof ( * ret -> table )) ;
58- // No valid SEXP is a null pointer, so it's a safe marker for empty cells.
59- memset ( ret -> table , 0 , n_full * sizeof ( struct hash_pair ));
72+ ret -> table = table ;
73+ ret -> next = NULL ;
74+
6075 return ret ;
6176}
6277
63- hashtab * hash_create (size_t n ) { return hash_create_ (n , default_load_factor ); }
64-
65- static R_INLINE size_t hash_index (SEXP key , int shift ) {
66- return (size_t )((uintptr_t )key * HASH_MULTIPLIER ) >> shift ;
78+ static void hash_finalizer (SEXP prot ) {
79+ struct hash_tab * h = R_ExternalPtrAddr (prot );
80+ R_ClearExternalPtr (prot );
81+ while (h ) {
82+ struct hash_tab * next = h -> next ;
83+ free (h -> table );
84+ free (h );
85+ h = next ;
86+ }
6787}
6888
69- static R_INLINE hashtab * hash_rehash ( const hashtab * h ) {
70- size_t new_size = h -> size * 2 ;
71- hashtab * new_h = hash_create_ ( new_size , default_load_factor );
89+ hashtab * hash_create ( size_t n ) {
90+ SEXP prot = R_MakeExternalPtr ( NULL , R_NilValue , R_NilValue ) ;
91+ R_RegisterCFinalizer ( prot , hash_finalizer );
7292
73- for (size_t i = 0 ; i < h -> size ; ++ i ) {
74- if (h -> table [i ].key ) hash_set (new_h , h -> table [i ].key , h -> table [i ].value );
75- }
76- return new_h ;
93+ struct hash_tab * ret = hash_create_ (n , default_load_factor );
94+ if (!ret ) internal_error ( // #nocov
95+ __func__ , "failed to allocate a hash table for %zu entries" , n
96+ );
97+
98+ ret -> public .prot = prot ;
99+ R_SetExternalPtrAddr (prot , ret );
100+
101+ return & ret -> public ;
77102}
78103
79- static bool hash_set_ (hashtab * h , SEXP key , R_xlen_t value ) {
104+ static R_INLINE size_t hash_index (SEXP key , int shift ) {
105+ return (size_t )((uintptr_t )key * HASH_MULTIPLIER ) >> shift ;
106+ }
107+
108+ static bool hash_set_ (struct hash_tab * h , SEXP key , R_xlen_t value ) {
80109 size_t mask = h -> size - 1 ;
81110 size_t idx = hash_index (key , h -> shift );
82111 while (true) {
@@ -96,22 +125,54 @@ static bool hash_set_(hashtab *h, SEXP key, R_xlen_t value) {
96125 }
97126}
98127
99- void hash_set (hashtab * h , SEXP key , R_xlen_t value ) {
128+ static R_INLINE struct hash_tab * hash_rehash (const struct hash_tab * h ) {
129+ if (h -> size > SIZE_MAX / 2 ) return NULL ; // #nocov
130+
131+ size_t new_size = h -> size * 2 ;
132+ struct hash_tab * new_h = hash_create_ (new_size , default_load_factor );
133+ if (!new_h ) return NULL ;
134+
135+ new_h -> public = h -> public ;
136+
137+ for (size_t i = 0 ; i < h -> size ; ++ i ) {
138+ if (h -> table [i ].key )
139+ (void )hash_set_ (new_h , h -> table [i ].key , h -> table [i ].value );
140+ }
141+
142+ return new_h ;
143+ }
144+
145+ void hash_set (hashtab * self , SEXP key , R_xlen_t value ) {
146+ struct hash_tab * h = (struct hash_tab * )self ;
100147 if (!hash_set_ (h , key , value )) {
101- * h = * hash_rehash (h );
148+ struct hash_tab * new_h = hash_rehash (h );
149+ if (!new_h ) internal_error ( // # nocov
150+ __func__ , "hash table full at n_full=%zu and failed to rehash" , h -> size
151+ );
152+ // overwrite the existing table, keeping the external pointer
153+ free (h -> table );
154+ * h = * new_h ;
155+ free (new_h );
102156 (void )hash_set_ (h , key , value ); // must succeed on the second try
103157 }
104158}
105159
106- hashtab * hash_set_shared (hashtab * h , SEXP key , R_xlen_t value ) {
160+ hashtab * hash_set_shared (hashtab * self , SEXP key , R_xlen_t value ) {
161+ struct hash_tab * h = (struct hash_tab * )self ;
107162 if (!hash_set_ (h , key , value )) {
108- h = hash_rehash (h );
163+ struct hash_tab * new_h = hash_rehash (h );
164+ if (!new_h ) return NULL ; // # nocov
165+ // existing 'h' may still be in use by another thread
166+ h -> next = new_h ;
167+ h = new_h ;
109168 (void )hash_set_ (h , key , value );
110169 }
111- return h ;
170+ return & h -> public ;
112171}
113172
114- R_xlen_t hash_lookup (const hashtab * h , SEXP key , R_xlen_t ifnotfound ) {
173+ R_xlen_t hash_lookup (const hashtab * self , SEXP key , R_xlen_t ifnotfound ) {
174+ const struct hash_tab * h = (const struct hash_tab * )self ;
175+
115176 size_t mask = h -> size - 1 ;
116177 size_t idx = hash_index (key , h -> shift );
117178 while (true) {
0 commit comments