Feature #10096 » khash.patch
| common.mk | ||
|---|---|---|
| 		     {$(VPATH)}node.h {$(VPATH)}method.h {$(VPATH)}ruby_atomic.h \ | ||
| 	             {$(VPATH)}vm_debug.h {$(VPATH)}id.h {$(VPATH)}thread_native.h \ | ||
| 	             $(CCAN_LIST_INCLUDES) | ||
| RUBY_KHASH_INCLUDES = {$(VPATH)}ruby_khash.h {$(VPATH)}klib/khash.h | ||
| ### | ||
| ... | ... | |
| strftime.$(OBJEXT): {$(VPATH)}strftime.c $(RUBY_H_INCLUDES) \ | ||
|   {$(VPATH)}timev.h $(ENCODING_H_INCLUDES) | ||
| string.$(OBJEXT): {$(VPATH)}string.c $(RUBY_H_INCLUDES) {$(VPATH)}re.h {$(VPATH)}gc.h \ | ||
|   {$(VPATH)}regex.h $(ENCODING_H_INCLUDES) {$(VPATH)}internal.h $(PROBES_H_INCLUDES) | ||
|   {$(VPATH)}regex.h $(ENCODING_H_INCLUDES) {$(VPATH)}internal.h $(PROBES_H_INCLUDES) \ | ||
|   $(RUBY_KHASH_INCLUDES) | ||
| struct.$(OBJEXT): {$(VPATH)}struct.c $(RUBY_H_INCLUDES) {$(VPATH)}internal.h | ||
| symbol.$(OBJEXT): {$(VPATH)}symbol.c $(RUBY_H_INCLUDES) $(ENCODING_H_INCLUDES) \ | ||
|   {$(VPATH)}internal.h {$(VPATH)}node.h {$(VPATH)}id.h {$(VPATH)}symbol.h \ | ||
| klib/khash.h | ||
|---|---|---|
| /* The MIT License | ||
|    Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk> | ||
|    Permission is hereby granted, free of charge, to any person obtaining | ||
|    a copy of this software and associated documentation files (the | ||
|    "Software"), to deal in the Software without restriction, including | ||
|    without limitation the rights to use, copy, modify, merge, publish, | ||
|    distribute, sublicense, and/or sell copies of the Software, and to | ||
|    permit persons to whom the Software is furnished to do so, subject to | ||
|    the following conditions: | ||
|    The above copyright notice and this permission notice shall be | ||
|    included in all copies or substantial portions of the Software. | ||
|    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
|    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
|    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
|    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||
|    BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||
|    ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
|    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
|    SOFTWARE. | ||
| */ | ||
| /* | ||
|   An example: | ||
| #include "khash.h" | ||
| KHASH_MAP_INIT_INT(32, char) | ||
| int main() { | ||
| 	int ret, is_missing; | ||
| 	khiter_t k; | ||
| 	khash_t(32) *h = kh_init(32); | ||
| 	k = kh_put(32, h, 5, &ret); | ||
| 	kh_value(h, k) = 10; | ||
| 	k = kh_get(32, h, 10); | ||
| 	is_missing = (k == kh_end(h)); | ||
| 	k = kh_get(32, h, 5); | ||
| 	kh_del(32, h, k); | ||
| 	for (k = kh_begin(h); k != kh_end(h); ++k) | ||
| 		if (kh_exist(h, k)) kh_value(h, k) = 1; | ||
| 	kh_destroy(32, h); | ||
| 	return 0; | ||
| } | ||
| */ | ||
| /* | ||
|   2013-05-02 (0.2.8): | ||
| 	* Use quadratic probing. When the capacity is power of 2, stepping function | ||
| 	  i*(i+1)/2 guarantees to traverse each bucket. It is better than double | ||
| 	  hashing on cache performance and is more robust than linear probing. | ||
| 	  In theory, double hashing should be more robust than quadratic probing. | ||
| 	  However, my implementation is probably not for large hash tables, because | ||
| 	  the second hash function is closely tied to the first hash function, | ||
| 	  which reduce the effectiveness of double hashing. | ||
| 	Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php | ||
|   2011-12-29 (0.2.7): | ||
|     * Minor code clean up; no actual effect. | ||
|   2011-09-16 (0.2.6): | ||
| 	* The capacity is a power of 2. This seems to dramatically improve the | ||
| 	  speed for simple keys. Thank Zilong Tan for the suggestion. Reference: | ||
| 	   - http://code.google.com/p/ulib/ | ||
| 	   - http://nothings.org/computer/judy/ | ||
| 	* Allow to optionally use linear probing which usually has better | ||
| 	  performance for random input. Double hashing is still the default as it | ||
| 	  is more robust to certain non-random input. | ||
| 	* Added Wang's integer hash function (not used by default). This hash | ||
| 	  function is more robust to certain non-random input. | ||
|   2011-02-14 (0.2.5): | ||
|     * Allow to declare global functions. | ||
|   2009-09-26 (0.2.4): | ||
|     * Improve portability | ||
|   2008-09-19 (0.2.3): | ||
| 	* Corrected the example | ||
| 	* Improved interfaces | ||
|   2008-09-11 (0.2.2): | ||
| 	* Improved speed a little in kh_put() | ||
|   2008-09-10 (0.2.1): | ||
| 	* Added kh_clear() | ||
| 	* Fixed a compiling error | ||
|   2008-09-02 (0.2.0): | ||
| 	* Changed to token concatenation which increases flexibility. | ||
|   2008-08-31 (0.1.2): | ||
| 	* Fixed a bug in kh_get(), which has not been tested previously. | ||
|   2008-08-31 (0.1.1): | ||
| 	* Added destructor | ||
| */ | ||
| #ifndef __AC_KHASH_H | ||
| #define __AC_KHASH_H | ||
| /*! | ||
|   @header | ||
|   Generic hash table library. | ||
|  */ | ||
| #define AC_VERSION_KHASH_H "0.2.8" | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <limits.h> | ||
| /* compiler specific configuration */ | ||
| #if UINT_MAX == 0xffffffffu | ||
| typedef unsigned int khint32_t; | ||
| #elif ULONG_MAX == 0xffffffffu | ||
| typedef unsigned long khint32_t; | ||
| #endif | ||
| #if ULONG_MAX == ULLONG_MAX | ||
| typedef unsigned long khint64_t; | ||
| #else | ||
| typedef unsigned long long khint64_t; | ||
| #endif | ||
| #ifdef _MSC_VER | ||
| #define kh_inline __inline | ||
| #else | ||
| #define kh_inline inline | ||
| #endif | ||
| typedef khint32_t khint_t; | ||
| typedef khint_t khiter_t; | ||
| #define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) | ||
| #define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) | ||
| #define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) | ||
| #define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) | ||
| #define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) | ||
| #define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) | ||
| #define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) | ||
| #define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) | ||
| #ifndef kroundup32 | ||
| #define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) | ||
| #endif | ||
| #ifndef kcalloc | ||
| #define kcalloc(N,Z) calloc(N,Z) | ||
| #endif | ||
| #ifndef kmalloc | ||
| #define kmalloc(Z) malloc(Z) | ||
| #endif | ||
| #ifndef krealloc | ||
| #define krealloc(P,Z) realloc(P,Z) | ||
| #endif | ||
| #ifndef kfree | ||
| #define kfree(P) free(P) | ||
| #endif | ||
| static const double __ac_HASH_UPPER = 0.77; | ||
| #define __KHASH_TYPE(name, khkey_t, khval_t) \ | ||
| 	typedef struct { \ | ||
| 		khint_t n_buckets, size, n_occupied, upper_bound; \ | ||
| 		khint32_t *flags; \ | ||
| 		khkey_t *keys; \ | ||
| 		khval_t *vals; \ | ||
| 	} kh_##name##_t; | ||
| #define __KHASH_PROTOTYPES(name, khkey_t, khval_t)						\ | ||
| 	extern kh_##name##_t *kh_init_##name(void);							\ | ||
| 	extern void kh_destroy_##name(kh_##name##_t *h);					\ | ||
| 	extern void kh_clear_##name(kh_##name##_t *h);						\ | ||
| 	extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key);	\ | ||
| 	extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ | ||
| 	extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ | ||
| 	extern void kh_del_##name(kh_##name##_t *h, khint_t x); | ||
| #define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ | ||
| 	SCOPE kh_##name##_t *kh_init_##name(void) {							\ | ||
| 		return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t));		\ | ||
| 	}																	\ | ||
| 	SCOPE void kh_destroy_##name(kh_##name##_t *h)						\ | ||
| 	{																	\ | ||
| 		if (h) {														\ | ||
| 			kfree((void *)h->keys); kfree(h->flags);					\ | ||
| 			kfree((void *)h->vals);										\ | ||
| 			kfree(h);													\ | ||
| 		}																\ | ||
| 	}																	\ | ||
| 	SCOPE void kh_clear_##name(kh_##name##_t *h)						\ | ||
| 	{																	\ | ||
| 		if (h && h->flags) {											\ | ||
| 			memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ | ||
| 			h->size = h->n_occupied = 0;								\ | ||
| 		}																\ | ||
| 	}																	\ | ||
| 	SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key)	\ | ||
| 	{																	\ | ||
| 		if (h->n_buckets) {												\ | ||
| 			khint_t k, i, last, mask, step = 0; \ | ||
| 			mask = h->n_buckets - 1;									\ | ||
| 			k = __hash_func(key); i = k & mask;							\ | ||
| 			last = i; \ | ||
| 			while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ | ||
| 				i = (i + (++step)) & mask; \ | ||
| 				if (i == last) return h->n_buckets;						\ | ||
| 			}															\ | ||
| 			return __ac_iseither(h->flags, i)? h->n_buckets : i;		\ | ||
| 		} else return 0;												\ | ||
| 	}																	\ | ||
| 	SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ | ||
| 	{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ | ||
| 		khint32_t *new_flags = 0;										\ | ||
| 		khint_t j = 1;													\ | ||
| 		{																\ | ||
| 			kroundup32(new_n_buckets);									\ | ||
| 			if (new_n_buckets < 4) new_n_buckets = 4;					\ | ||
| 			if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0;	/* requested size is too small */ \ | ||
| 			else { /* hash table size to be changed (shrink or expand); rehash */ \ | ||
| 				new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t));	\ | ||
| 				if (!new_flags) return -1;								\ | ||
| 				memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ | ||
| 				if (h->n_buckets < new_n_buckets) {	/* expand */		\ | ||
| 					khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ | ||
| 					if (!new_keys) return -1;							\ | ||
| 					h->keys = new_keys;									\ | ||
| 					if (kh_is_map) {									\ | ||
| 						khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ | ||
| 						if (!new_vals) return -1;						\ | ||
| 						h->vals = new_vals;								\ | ||
| 					}													\ | ||
| 				} /* otherwise shrink */								\ | ||
| 			}															\ | ||
| 		}																\ | ||
| 		if (j) { /* rehashing is needed */								\ | ||
| 			for (j = 0; j != h->n_buckets; ++j) {						\ | ||
| 				if (__ac_iseither(h->flags, j) == 0) {					\ | ||
| 					khkey_t key = h->keys[j];							\ | ||
| 					khval_t val;										\ | ||
| 					khint_t new_mask;									\ | ||
| 					new_mask = new_n_buckets - 1;						\ | ||
| 					if (kh_is_map) val = h->vals[j];					\ | ||
| 					__ac_set_isdel_true(h->flags, j);					\ | ||
| 					while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ | ||
| 						khint_t k, i, step = 0; \ | ||
| 						k = __hash_func(key);							\ | ||
| 						i = k & new_mask;								\ | ||
| 						while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ | ||
| 						__ac_set_isempty_false(new_flags, i);			\ | ||
| 						if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ | ||
| 							{ khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ | ||
| 							if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ | ||
| 							__ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ | ||
| 						} else { /* write the element and jump out of the loop */ \ | ||
| 							h->keys[i] = key;							\ | ||
| 							if (kh_is_map) h->vals[i] = val;			\ | ||
| 							break;										\ | ||
| 						}												\ | ||
| 					}													\ | ||
| 				}														\ | ||
| 			}															\ | ||
| 			if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ | ||
| 				h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ | ||
| 				if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ | ||
| 			}															\ | ||
| 			kfree(h->flags); /* free the working space */				\ | ||
| 			h->flags = new_flags;										\ | ||
| 			h->n_buckets = new_n_buckets;								\ | ||
| 			h->n_occupied = h->size;									\ | ||
| 			h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ | ||
| 		}																\ | ||
| 		return 0;														\ | ||
| 	}																	\ | ||
| 	SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ | ||
| 	{																	\ | ||
| 		khint_t x;														\ | ||
| 		if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ | ||
| 			if (h->n_buckets > (h->size<<1)) {							\ | ||
| 				if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ | ||
| 					*ret = -1; return h->n_buckets;						\ | ||
| 				}														\ | ||
| 			} else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ | ||
| 				*ret = -1; return h->n_buckets;							\ | ||
| 			}															\ | ||
| 		} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ | ||
| 		{																\ | ||
| 			khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ | ||
| 			x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ | ||
| 			if (__ac_isempty(h->flags, i)) x = i; /* for speed up */	\ | ||
| 			else {														\ | ||
| 				last = i; \ | ||
| 				while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ | ||
| 					if (__ac_isdel(h->flags, i)) site = i;				\ | ||
| 					i = (i + (++step)) & mask; \ | ||
| 					if (i == last) { x = site; break; }					\ | ||
| 				}														\ | ||
| 				if (x == h->n_buckets) {								\ | ||
| 					if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ | ||
| 					else x = i;											\ | ||
| 				}														\ | ||
| 			}															\ | ||
| 		}																\ | ||
| 		if (__ac_isempty(h->flags, x)) { /* not present at all */		\ | ||
| 			h->keys[x] = key;											\ | ||
| 			__ac_set_isboth_false(h->flags, x);							\ | ||
| 			++h->size; ++h->n_occupied;									\ | ||
| 			*ret = 1;													\ | ||
| 		} else if (__ac_isdel(h->flags, x)) { /* deleted */				\ | ||
| 			h->keys[x] = key;											\ | ||
| 			__ac_set_isboth_false(h->flags, x);							\ | ||
| 			++h->size;													\ | ||
| 			*ret = 2;													\ | ||
| 		} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ | ||
| 		return x;														\ | ||
| 	}																	\ | ||
| 	SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x)				\ | ||
| 	{																	\ | ||
| 		if (x != h->n_buckets && !__ac_iseither(h->flags, x)) {			\ | ||
| 			__ac_set_isdel_true(h->flags, x);							\ | ||
| 			--h->size;													\ | ||
| 		}																\ | ||
| 	} | ||
| #define KHASH_DECLARE(name, khkey_t, khval_t)							\ | ||
| 	__KHASH_TYPE(name, khkey_t, khval_t)								\ | ||
| 	__KHASH_PROTOTYPES(name, khkey_t, khval_t) | ||
| #define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ | ||
| 	__KHASH_TYPE(name, khkey_t, khval_t)								\ | ||
| 	__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) | ||
| #define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ | ||
| 	KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) | ||
| /* --- BEGIN OF HASH FUNCTIONS --- */ | ||
| /*! @function | ||
|   @abstract     Integer hash function | ||
|   @param  key   The integer [khint32_t] | ||
|   @return       The hash value [khint_t] | ||
|  */ | ||
| #define kh_int_hash_func(key) (khint32_t)(key) | ||
| /*! @function | ||
|   @abstract     Integer comparison function | ||
|  */ | ||
| #define kh_int_hash_equal(a, b) ((a) == (b)) | ||
| /*! @function | ||
|   @abstract     64-bit integer hash function | ||
|   @param  key   The integer [khint64_t] | ||
|   @return       The hash value [khint_t] | ||
|  */ | ||
| #define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) | ||
| /*! @function | ||
|   @abstract     64-bit integer comparison function | ||
|  */ | ||
| #define kh_int64_hash_equal(a, b) ((a) == (b)) | ||
| /*! @function | ||
|   @abstract     const char* hash function | ||
|   @param  s     Pointer to a null terminated string | ||
|   @return       The hash value | ||
|  */ | ||
| static kh_inline khint_t __ac_X31_hash_string(const char *s) | ||
| { | ||
| 	khint_t h = (khint_t)*s; | ||
| 	if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; | ||
| 	return h; | ||
| } | ||
| /*! @function | ||
|   @abstract     Another interface to const char* hash function | ||
|   @param  key   Pointer to a null terminated string [const char*] | ||
|   @return       The hash value [khint_t] | ||
|  */ | ||
| #define kh_str_hash_func(key) __ac_X31_hash_string(key) | ||
| /*! @function | ||
|   @abstract     Const char* comparison function | ||
|  */ | ||
| #define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) | ||
| static kh_inline khint_t __ac_Wang_hash(khint_t key) | ||
| { | ||
|     key += ~(key << 15); | ||
|     key ^=  (key >> 10); | ||
|     key +=  (key << 3); | ||
|     key ^=  (key >> 6); | ||
|     key += ~(key << 11); | ||
|     key ^=  (key >> 16); | ||
|     return key; | ||
| } | ||
| #define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) | ||
| /* --- END OF HASH FUNCTIONS --- */ | ||
| /* Other convenient macros... */ | ||
| /*! | ||
|   @abstract Type of the hash table. | ||
|   @param  name  Name of the hash table [symbol] | ||
|  */ | ||
| #define khash_t(name) kh_##name##_t | ||
| /*! @function | ||
|   @abstract     Initiate a hash table. | ||
|   @param  name  Name of the hash table [symbol] | ||
|   @return       Pointer to the hash table [khash_t(name)*] | ||
|  */ | ||
| #define kh_init(name) kh_init_##name() | ||
| /*! @function | ||
|   @abstract     Destroy a hash table. | ||
|   @param  name  Name of the hash table [symbol] | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|  */ | ||
| #define kh_destroy(name, h) kh_destroy_##name(h) | ||
| /*! @function | ||
|   @abstract     Reset a hash table without deallocating memory. | ||
|   @param  name  Name of the hash table [symbol] | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|  */ | ||
| #define kh_clear(name, h) kh_clear_##name(h) | ||
| /*! @function | ||
|   @abstract     Resize a hash table. | ||
|   @param  name  Name of the hash table [symbol] | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @param  s     New size [khint_t] | ||
|  */ | ||
| #define kh_resize(name, h, s) kh_resize_##name(h, s) | ||
| /*! @function | ||
|   @abstract     Insert a key to the hash table. | ||
|   @param  name  Name of the hash table [symbol] | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @param  k     Key [type of keys] | ||
|   @param  r     Extra return code: -1 if the operation failed; | ||
|                 0 if the key is present in the hash table; | ||
|                 1 if the bucket is empty (never used); 2 if the element in | ||
| 				the bucket has been deleted [int*] | ||
|   @return       Iterator to the inserted element [khint_t] | ||
|  */ | ||
| #define kh_put(name, h, k, r) kh_put_##name(h, k, r) | ||
| /*! @function | ||
|   @abstract     Retrieve a key from the hash table. | ||
|   @param  name  Name of the hash table [symbol] | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @param  k     Key [type of keys] | ||
|   @return       Iterator to the found element, or kh_end(h) if the element is absent [khint_t] | ||
|  */ | ||
| #define kh_get(name, h, k) kh_get_##name(h, k) | ||
| /*! @function | ||
|   @abstract     Remove a key from the hash table. | ||
|   @param  name  Name of the hash table [symbol] | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @param  k     Iterator to the element to be deleted [khint_t] | ||
|  */ | ||
| #define kh_del(name, h, k) kh_del_##name(h, k) | ||
| /*! @function | ||
|   @abstract     Test whether a bucket contains data. | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @param  x     Iterator to the bucket [khint_t] | ||
|   @return       1 if containing data; 0 otherwise [int] | ||
|  */ | ||
| #define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) | ||
| /*! @function | ||
|   @abstract     Get key given an iterator | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @param  x     Iterator to the bucket [khint_t] | ||
|   @return       Key [type of keys] | ||
|  */ | ||
| #define kh_key(h, x) ((h)->keys[x]) | ||
| /*! @function | ||
|   @abstract     Get value given an iterator | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @param  x     Iterator to the bucket [khint_t] | ||
|   @return       Value [type of values] | ||
|   @discussion   For hash sets, calling this results in segfault. | ||
|  */ | ||
| #define kh_val(h, x) ((h)->vals[x]) | ||
| /*! @function | ||
|   @abstract     Alias of kh_val() | ||
|  */ | ||
| #define kh_value(h, x) ((h)->vals[x]) | ||
| /*! @function | ||
|   @abstract     Get the start iterator | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @return       The start iterator [khint_t] | ||
|  */ | ||
| #define kh_begin(h) (khint_t)(0) | ||
| /*! @function | ||
|   @abstract     Get the end iterator | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @return       The end iterator [khint_t] | ||
|  */ | ||
| #define kh_end(h) ((h)->n_buckets) | ||
| /*! @function | ||
|   @abstract     Get the number of elements in the hash table | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @return       Number of elements in the hash table [khint_t] | ||
|  */ | ||
| #define kh_size(h) ((h)->size) | ||
| /*! @function | ||
|   @abstract     Get the number of buckets in the hash table | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @return       Number of buckets in the hash table [khint_t] | ||
|  */ | ||
| #define kh_n_buckets(h) ((h)->n_buckets) | ||
| /*! @function | ||
|   @abstract     Iterate over the entries in the hash table | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @param  kvar  Variable to which key will be assigned | ||
|   @param  vvar  Variable to which value will be assigned | ||
|   @param  code  Block of code to execute | ||
|  */ | ||
| #define kh_foreach(h, kvar, vvar, code) { khint_t __i;		\ | ||
| 	for (__i = kh_begin(h); __i != kh_end(h); ++__i) {		\ | ||
| 		if (!kh_exist(h,__i)) continue;						\ | ||
| 		(kvar) = kh_key(h,__i);								\ | ||
| 		(vvar) = kh_val(h,__i);								\ | ||
| 		code;												\ | ||
| 	} } | ||
| /*! @function | ||
|   @abstract     Iterate over the values in the hash table | ||
|   @param  h     Pointer to the hash table [khash_t(name)*] | ||
|   @param  vvar  Variable to which value will be assigned | ||
|   @param  code  Block of code to execute | ||
|  */ | ||
| #define kh_foreach_value(h, vvar, code) { khint_t __i;		\ | ||
| 	for (__i = kh_begin(h); __i != kh_end(h); ++__i) {		\ | ||
| 		if (!kh_exist(h,__i)) continue;						\ | ||
| 		(vvar) = kh_val(h,__i);								\ | ||
| 		code;												\ | ||
| 	} } | ||
| /* More conenient interfaces */ | ||
| /*! @function | ||
|   @abstract     Instantiate a hash set containing integer keys | ||
|   @param  name  Name of the hash table [symbol] | ||
|  */ | ||
| #define KHASH_SET_INIT_INT(name)										\ | ||
| 	KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) | ||
| /*! @function | ||
|   @abstract     Instantiate a hash map containing integer keys | ||
|   @param  name  Name of the hash table [symbol] | ||
|   @param  khval_t  Type of values [type] | ||
|  */ | ||
| #define KHASH_MAP_INIT_INT(name, khval_t)								\ | ||
| 	KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) | ||
| /*! @function | ||
|   @abstract     Instantiate a hash map containing 64-bit integer keys | ||
|   @param  name  Name of the hash table [symbol] | ||
|  */ | ||
| #define KHASH_SET_INIT_INT64(name)										\ | ||
| 	KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) | ||
| /*! @function | ||
|   @abstract     Instantiate a hash map containing 64-bit integer keys | ||
|   @param  name  Name of the hash table [symbol] | ||
|   @param  khval_t  Type of values [type] | ||
|  */ | ||
| #define KHASH_MAP_INIT_INT64(name, khval_t)								\ | ||
| 	KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) | ||
| typedef const char *kh_cstr_t; | ||
| /*! @function | ||
|   @abstract     Instantiate a hash map containing const char* keys | ||
|   @param  name  Name of the hash table [symbol] | ||
|  */ | ||
| #define KHASH_SET_INIT_STR(name)										\ | ||
| 	KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) | ||
| /*! @function | ||
|   @abstract     Instantiate a hash map containing const char* keys | ||
|   @param  name  Name of the hash table [symbol] | ||
|   @param  khval_t  Type of values [type] | ||
|  */ | ||
| #define KHASH_MAP_INIT_STR(name, khval_t)								\ | ||
| 	KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) | ||
| #endif /* __AC_KHASH_H */ | ||
| ruby_khash.h | ||
|---|---|---|
| #ifndef RUBY_KHASH_H | ||
| #define RUBY_KHASH_H | ||
| #include "ruby/ruby.h" | ||
| #include "id.h" | ||
| /* | ||
|  * Avoid modifying klib/khash directly since that is imported from | ||
|  * https://github.com/attractivechaos/klib.git | ||
|  */ | ||
| /* override klib defaults to do GC accounting */ | ||
| #define kcalloc(N,Z) ruby_xcalloc((N),(Z)) | ||
| #define kmalloc(N) ruby_xmalloc((N)) | ||
| #define krealloc(P,Z) ruby_xrealloc((P),(Z)) | ||
| #define kfree(P) ruby_xfree((P)) | ||
| #include "klib/khash.h" | ||
| static inline khint_t | ||
| rb_khash_str(VALUE str) | ||
| { | ||
|     st_index_t h = rb_str_hash(str); | ||
|     if (sizeof(st_index_t) == (sizeof(khint_t) * 2)) { | ||
| 	union { st_index_t in; khint_t out[2]; } tmp; | ||
| 	tmp.in = h; | ||
| 	return tmp.out[0] ^ tmp.out[1]; | ||
|     } | ||
|     return (khint_t)h; | ||
| } | ||
| static inline khint_t | ||
| rb_khash_id(ID id) | ||
| { | ||
|     return (khint_t)(id >> RUBY_ID_SCOPE_SHIFT); | ||
| } | ||
| /* | ||
|  * TODO: send this to khash upstream if it works well | ||
|  * This is based on kh_foreach_value, but for keys. | ||
|  */ | ||
| #define rb_kh_foreach_key(h, kvar, code) do { khint_t __i; \ | ||
|     for (__i = kh_begin(h); __i != kh_end(h); ++__i) {	\ | ||
| 	if (!kh_exist(h,__i)) continue;	\ | ||
| 	(kvar) = kh_key(h,__i); \ | ||
| 	code; \ | ||
|     } } while (0) | ||
| #endif /* RUBY_KHASH_H */ | ||
| string.c | ||
|---|---|---|
| #include "probes.h" | ||
| #include "gc.h" | ||
| #include <assert.h> | ||
| #include "ruby_khash.h" | ||
| #define BEG(no) (regs->beg[(no)]) | ||
| #define END(no) (regs->end[(no)]) | ||
| ... | ... | |
|     } | ||
| } | ||
| static int fstring_cmp(VALUE a, VALUE b); | ||
| static st_table* frozen_strings; | ||
| static const struct st_hash_type fstring_hash_type = { | ||
|     fstring_cmp, | ||
|     rb_str_hash, | ||
| }; | ||
| static int fstring_eq(VALUE a, VALUE b); | ||
| KHASH_INIT(fstring, VALUE, char, 0, rb_khash_str, fstring_eq); | ||
| static khash_t(fstring) *frozen_strings; /* TODO: make mvm friendly */ | ||
| static int | ||
| fstr_update_callback(st_data_t *key, st_data_t *value, st_data_t arg, int existing) | ||
| fstring_eq(VALUE a, VALUE b) | ||
| { | ||
|     int cmp = rb_str_hash_cmp(a, b); | ||
|     if (cmp == 0) { | ||
| 	return (ENCODING_GET(b) - ENCODING_GET(a)) == 0; | ||
|     } | ||
|     return 0; | ||
| } | ||
| VALUE | ||
| rb_fstring(VALUE str) | ||
| { | ||
|     VALUE *fstr = (VALUE *)arg; | ||
|     VALUE str = (VALUE)*key; | ||
|     VALUE fstr; | ||
|     khint_t k; | ||
|     int ret; | ||
|     Check_Type(str, T_STRING); | ||
|     if (!frozen_strings) | ||
| 	frozen_strings = kh_init(fstring); | ||
|     if (FL_TEST(str, RSTRING_FSTR)) | ||
| 	return str; | ||
|     k = kh_get(fstring, frozen_strings, str); | ||
|     if (k != kh_end(frozen_strings)) { /* existing */ | ||
| 	fstr = kh_key(frozen_strings, k); | ||
|     if (existing) { | ||
| 	/* because of lazy sweep, str may be unmarked already and swept | ||
| 	 * at next time */ | ||
| 	if (rb_objspace_garbage_object_p(str)) { | ||
| 	    str = *fstr; | ||
| 	if (rb_objspace_garbage_object_p(fstr)) { | ||
| 	    goto create_new_fstr; | ||
| 	} | ||
| 	*fstr = str; | ||
| 	return ST_STOP; | ||
|     } | ||
|     else { | ||
| 	if (STR_SHARED_P(str)) { /* str should not be shared */ | ||
| 	  create_new_fstr: | ||
| 	    str = rb_enc_str_new(RSTRING_PTR(str), RSTRING_LEN(str), STR_ENC_GET(str)); | ||
| 	    OBJ_FREEZE(str); | ||
| 	    fstr = rb_enc_str_new(RSTRING_PTR(str), RSTRING_LEN(str), STR_ENC_GET(str)); | ||
| 	    OBJ_FREEZE(fstr); | ||
| 	} | ||
| 	else { | ||
| 	    str = rb_str_new_frozen(str); | ||
| 	    fstr = rb_str_new_frozen(str); | ||
| 	} | ||
| 	RBASIC(str)->flags |= RSTRING_FSTR; | ||
| 	*key = *value = *fstr = str; | ||
| 	return ST_CONTINUE; | ||
| 	RBASIC(fstr)->flags |= RSTRING_FSTR; | ||
| 	kh_put(fstring, frozen_strings, fstr, &ret); | ||
| 	if (ret < 0) rb_memerror(); | ||
|     } | ||
| } | ||
| VALUE | ||
| rb_fstring(VALUE str) | ||
| { | ||
|     Check_Type(str, T_STRING); | ||
|     if (!frozen_strings) | ||
| 	frozen_strings = st_init_table(&fstring_hash_type); | ||
|     if (FL_TEST(str, RSTRING_FSTR)) | ||
| 	return str; | ||
|     st_update(frozen_strings, (st_data_t)str, fstr_update_callback, (st_data_t)&str); | ||
|     return str; | ||
|     return fstr; | ||
| } | ||
| static VALUE | ||
| ... | ... | |
|     return rb_fstring(setup_fake_str(&fake_str, ptr, len, ENCINDEX_US_ASCII)); | ||
| } | ||
| static int | ||
| fstring_set_class_i(st_data_t key, st_data_t val, st_data_t arg) | ||
| { | ||
|     RBASIC_SET_CLASS((VALUE)key, (VALUE)arg); | ||
|     return ST_CONTINUE; | ||
| } | ||
| static int | ||
| fstring_cmp(VALUE a, VALUE b) | ||
| { | ||
|     int cmp = rb_str_hash_cmp(a, b); | ||
|     if (cmp != 0) { | ||
| 	return cmp; | ||
|     } | ||
|     return ENCODING_GET(b) - ENCODING_GET(a); | ||
| } | ||
| static inline int | ||
| single_byte_optimizable(VALUE str) | ||
| { | ||
| ... | ... | |
| rb_str_free(VALUE str) | ||
| { | ||
|     if (FL_TEST(str, RSTRING_FSTR)) { | ||
| 	st_data_t fstr = (st_data_t)str; | ||
| 	st_delete(frozen_strings, &fstr, NULL); | ||
| 	khint_t k = kh_get(fstring, frozen_strings, str); | ||
| 	if (k != kh_end(frozen_strings)) { | ||
| 	    kh_del(fstring, frozen_strings, k); | ||
| 	} | ||
|     } | ||
|     if (!STR_EMBED_P(str) && !FL_TEST(str, STR_SHARED)) { | ||
| ... | ... | |
|     rb_define_method(rb_cSymbol, "encoding", sym_encoding, 0); | ||
|     if (frozen_strings) | ||
| 	st_foreach(frozen_strings, fstring_set_class_i, rb_cString); | ||
|     if (frozen_strings) { | ||
| 	VALUE str; | ||
| 	rb_kh_foreach_key(frozen_strings, str, do { | ||
| 	    RBASIC_SET_CLASS(str, rb_cString); | ||
| 	} while (0)); | ||
|     } | ||
| } | ||
| symbol.c | ||
|---|---|---|
| #include "symbol.h" | ||
| #include "gc.h" | ||
| #include "probes.h" | ||
| #include "ruby_khash.h" | ||
| static ID register_static_symid(ID, const char *, long, rb_encoding *); | ||
| static ID register_static_symid_str(ID, VALUE); | ||
| ... | ... | |
| STATIC_ASSERT(op_tbl_name_size, sizeof(op_tbl[0].name) == 3); | ||
| #define op_tbl_len(i) (!op_tbl[i].name[1] ? 1 : !op_tbl[i].name[2] ? 2 : 3) | ||
| static int | ||
| id_str_eq(ID a, ID b) | ||
| { | ||
|     return a == b; | ||
| } | ||
| KHASH_INIT(id_str, ID, VALUE, 1, rb_khash_id, id_str_eq); | ||
| static struct symbols { | ||
|     ID last_id; | ||
|     st_table *str_id; | ||
|     st_table *id_str; | ||
|     st_table *str_id; /* ordered for now in case of compatibility */ | ||
|     khash_t(id_str) *id_str; | ||
|     VALUE dsymbol_fstr_hash; | ||
| } global_symbols = {tNEXT_ID-1}; | ||
| ... | ... | |
|     rb_str_hash, | ||
| }; | ||
| static int | ||
| lookup_static_id_str(ID id, VALUE *str) | ||
| { | ||
|     khint_t k = kh_get(id_str, global_symbols.id_str, id); | ||
|     if (k != kh_end(global_symbols.id_str)) { | ||
| 	*str = kh_val(global_symbols.id_str, k); | ||
| 	return TRUE; | ||
|     } | ||
|     return FALSE; | ||
| } | ||
| void | ||
| Init_sym(void) | ||
| { | ||
| ... | ... | |
|     rb_gc_register_mark_object(dsym_fstrs); | ||
|     rb_obj_hide(dsym_fstrs); | ||
|     /* load factor in a chained hash table is often > 1 */ | ||
|     global_symbols.str_id = st_init_table_with_size(&symhash, 1000); | ||
|     global_symbols.id_str = st_init_numtable_with_size(1000); | ||
|     /* load factor in an open-addressed hash table is always < 1 */ | ||
|     global_symbols.id_str = kh_init(id_str); | ||
|     kh_resize(id_str, global_symbols.id_str, 4096); | ||
|     Init_id(); | ||
| } | ||
| static ID attrsetname_to_attr(VALUE name); | ||
| static int lookup_id_str(ID id, st_data_t *data); | ||
| static int lookup_id_str(ID id, VALUE *str); | ||
| ID | ||
| rb_id_attrset(ID id) | ||
| ... | ... | |
| 	    return id; | ||
| 	  default: | ||
| 	    { | ||
| 		st_data_t data; | ||
| 		if (lookup_id_str(id, &data)) { | ||
| 		VALUE str; | ||
| 		if (lookup_id_str(id, &str)) { | ||
| 		    rb_name_error(id, "cannot make unknown type ID %d:%"PRIsVALUE" attrset", | ||
| 				  scope, (VALUE)data); | ||
| 				  scope, str); | ||
| 		} | ||
| 		else { | ||
| 		    rb_name_error_str(Qnil, "cannot make unknown type anonymous ID %d:%"PRIxVALUE" attrset", | ||
| ... | ... | |
|     return register_static_symid_str(id, str); | ||
| } | ||
| static void | ||
| register_direct(ID id, VALUE str) | ||
| { | ||
|     int ret; | ||
|     khint_t k; | ||
|     st_add_direct(global_symbols.str_id, (st_data_t)str, id); | ||
|     k = kh_put(id_str, global_symbols.id_str, id, &ret); | ||
|     if (ret == 0) { | ||
| 	rb_bug("duplicate element (id_str): %s", RSTRING_PTR(str)); | ||
|     } else if (ret < 0) { | ||
| 	rb_memerror(); | ||
|     } else { | ||
| 	kh_val(global_symbols.id_str, k) = str; | ||
|     } | ||
| } | ||
| static ID | ||
| register_static_symid_str(ID id, VALUE str) | ||
| { | ||
| ... | ... | |
| 	RUBY_DTRACE_SYMBOL_CREATE(RSTRING_PTR(str), rb_sourcefile(), rb_sourceline()); | ||
|     } | ||
|     st_add_direct(global_symbols.str_id, (st_data_t)str, id); | ||
|     st_add_direct(global_symbols.id_str, id, (st_data_t)str); | ||
|     register_direct(id, str); | ||
|     rb_gc_register_mark_object(str); | ||
|     return id; | ||
| ... | ... | |
| { | ||
|     if (UNLIKELY(!DYNAMIC_SYM_P(x))) { | ||
| 	if (STATIC_SYM_P(x)) { | ||
| 	    st_data_t str; | ||
| 	    VALUE str; | ||
| 	    if (lookup_id_str(RSHIFT((unsigned long)(x),RUBY_SPECIAL_SHIFT), &str)) { | ||
| 		rb_bug("wrong argument: %s (inappropriate Symbol)", RSTRING_PTR((VALUE)str)); | ||
| 		rb_bug("wrong argument: %s (inappropriate Symbol)", RSTRING_PTR(str)); | ||
| 	    } | ||
| 	    else { | ||
| 		rb_bug("wrong argument: inappropriate Symbol (%p)", (void *)x); | ||
| ... | ... | |
|     RB_OBJ_WRITE(dsym, &RSYMBOL(dsym)->fstr, str); | ||
|     RSYMBOL(dsym)->type = type; | ||
|     st_add_direct(global_symbols.str_id, (st_data_t)str, (st_data_t)dsym); | ||
|     st_add_direct(global_symbols.id_str, (ID)dsym, (st_data_t)str); | ||
|     register_direct((ID)dsym, str); | ||
|     rb_hash_aset(global_symbols.dsymbol_fstr_hash, str, Qtrue); | ||
|     if (RUBY_DTRACE_SYMBOL_CREATE_ENABLED()) { | ||
| ... | ... | |
|     return dsym; | ||
| } | ||
| static inline int | ||
| id_str_delete(ID id) | ||
| { | ||
|     khint_t k = kh_get(id_str, global_symbols.id_str, id); | ||
|     if (k != kh_end(global_symbols.id_str)) { | ||
| 	kh_del(id_str, global_symbols.id_str, k); | ||
| 	return TRUE; | ||
|     } | ||
|     return FALSE; | ||
| } | ||
| static inline VALUE | ||
| dsymbol_check(const VALUE sym) | ||
| { | ||
| ... | ... | |
| 	if (st_delete(global_symbols.str_id, (st_data_t *)&fstr, NULL) == 0) { | ||
| 	    rb_bug("can't remove fstr from str_id (%s)", RSTRING_PTR(fstr)); | ||
| 	} | ||
| 	if (st_delete(global_symbols.id_str, (st_data_t *)&sym, NULL) == 0) { | ||
| 	if (!id_str_delete(sym)) { | ||
| 	    rb_bug("can't remove sym from id_sym (%s)", RSTRING_PTR(fstr)); | ||
| 	} | ||
| 	return dsymbol_alloc(rb_cSymbol, fstr, rb_enc_get(fstr)); | ||
| ... | ... | |
| } | ||
| static int | ||
| lookup_id_str(ID id, st_data_t *data) | ||
| lookup_id_str(ID id, VALUE *str) | ||
| { | ||
|     if (ID_DYNAMIC_SYM_P(id)) { | ||
| 	*data = RSYMBOL(id)->fstr; | ||
| 	*str = RSYMBOL(id)->fstr; | ||
| 	return TRUE; | ||
|     } | ||
|     if (st_lookup(global_symbols.id_str, id, data)) { | ||
| 	return TRUE; | ||
|     } | ||
|     return FALSE; | ||
|     return lookup_static_id_str(id, str); | ||
| } | ||
| ID | ||
| ... | ... | |
| rb_gc_free_dsymbol(VALUE sym) | ||
| { | ||
|     st_data_t str_data = (st_data_t)RSYMBOL(sym)->fstr; | ||
|     st_data_t sym_data = (st_data_t)sym; | ||
|     if (str_data) { | ||
| 	if (st_delete(global_symbols.str_id, &str_data, 0) == 0) { | ||
| 	    rb_bug("rb_gc_free_dsymbol: %p can't remove str from str_id (%s)", (void *)sym, RSTRING_PTR(RSYMBOL(sym)->fstr)); | ||
| 	} | ||
| 	if (st_delete(global_symbols.id_str, &sym_data, 0) == 0) { | ||
| 	if (!id_str_delete((ID)sym)) { | ||
| 	    rb_bug("rb_gc_free_dsymbol: %p can't remove sym from id_str (%s)", (void *)sym, RSTRING_PTR(RSYMBOL(sym)->fstr)); | ||
| 	} | ||
| 	RSYMBOL(sym)->fstr = (VALUE)NULL; | ||
| ... | ... | |
|     VALUE sym = ID2SYM((ID)value); | ||
|     if (DYNAMIC_SYM_P(sym) && !SYMBOL_PINNED_P(sym) && rb_objspace_garbage_object_p(sym)) { | ||
| 	st_data_t sym_data = (st_data_t)sym; | ||
| 	st_delete(global_symbols.id_str, &sym_data, NULL); | ||
| 	id_str_delete((ID)sym); | ||
| 	RSYMBOL(sym)->fstr = 0; | ||
| 	return ST_DELETE; | ||
|     } | ||