// This file is part of khash released under the MIT license. // See the LICENSE file for more information. // Copyright 2013 Cloudant, Inc #include #include #include #include "erl_nif.h" #include "hash.h" #ifdef _WIN32 #define INLINE __inline #else #define INLINE inline #endif #define KHASH_VERSION 0 typedef struct { ERL_NIF_TERM atom_ok; ERL_NIF_TERM atom_error; ERL_NIF_TERM atom_value; ERL_NIF_TERM atom_not_found; ERL_NIF_TERM atom_end_of_table; ERL_NIF_TERM atom_expired_iterator; ErlNifResourceType* res_hash; ErlNifResourceType* res_iter; } khash_priv; typedef struct { unsigned int hval; ErlNifEnv* env; ERL_NIF_TERM key; ERL_NIF_TERM val; } khnode_t; typedef struct { int version; unsigned int gen; hash_t* h; ErlNifPid p; } khash_t; typedef struct { int version; unsigned int gen; khash_t* khash; hscan_t scan; } khash_iter_t; static INLINE ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name) { ERL_NIF_TERM ret; if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) { return ret; } return enif_make_atom(env, name); } static INLINE ERL_NIF_TERM make_ok(ErlNifEnv* env, khash_priv* priv, ERL_NIF_TERM value) { return enif_make_tuple2(env, priv->atom_ok, value); } static INLINE ERL_NIF_TERM make_error(ErlNifEnv* env, khash_priv* priv, ERL_NIF_TERM reason) { return enif_make_tuple2(env, priv->atom_error, reason); } static INLINE int check_pid(ErlNifEnv* env, khash_t* khash) { ErlNifPid pid; enif_self(env, &pid); if(enif_compare(pid.pid, khash->p.pid) == 0) { return 1; } return 0; } hnode_t* khnode_alloc(void* ctx) { hnode_t* ret = (hnode_t*) enif_alloc(sizeof(hnode_t)); khnode_t* node = (khnode_t*) enif_alloc(sizeof(khnode_t)); memset(ret, '\0', sizeof(hnode_t)); memset(node, '\0', sizeof(khnode_t)); node->env = enif_alloc_env(); ret->hash_key = node; return ret; } void khnode_free(hnode_t* obj, void* ctx) { khnode_t* node = (khnode_t*) kl_hnode_getkey(obj); enif_free_env(node->env); enif_free(node); enif_free(obj); return; } int khash_cmp_fun(const void* l, const void* r) { khnode_t* left = (khnode_t*) l; khnode_t* right = (khnode_t*) r; int cmp = enif_compare(left->key, right->key); if(cmp < 0) { return -1; } else if(cmp == 0) { return 0; } else { return 1; } } hash_val_t khash_hash_fun(const void* obj) { khnode_t* node = (khnode_t*) obj; return (hash_val_t) node->hval; } static INLINE khash_t* khash_create_int(ErlNifEnv* env, khash_priv* priv, ERL_NIF_TERM opts) { khash_t* khash = NULL; assert(priv != NULL && "missing private data member"); khash = (khash_t*) enif_alloc_resource(priv->res_hash, sizeof(khash_t)); memset(khash, '\0', sizeof(khash_t)); khash->version = KHASH_VERSION; khash->gen = 0; khash->h = kl_hash_create(HASHCOUNT_T_MAX, khash_cmp_fun, khash_hash_fun); if(khash->h == NULL ) { enif_release_resource(khash); return NULL; } kl_hash_set_allocator(khash->h, khnode_alloc, khnode_free, NULL); enif_self(env, &(khash->p)); return khash; } static ERL_NIF_TERM khash_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { khash_priv* priv = enif_priv_data(env); khash_t* khash; ERL_NIF_TERM ret; if(argc != 1) { return enif_make_badarg(env); } khash = khash_create_int(env, priv, argv[0]); if(khash == NULL) { return enif_make_badarg(env); } ret = enif_make_resource(env, khash); enif_release_resource(khash); return make_ok(env, priv, ret); } static void khash_free(ErlNifEnv* env, void* obj) { khash_t* khash = (khash_t*) obj; if(khash->h != NULL) { kl_hash_free_nodes(khash->h); kl_hash_destroy(khash->h); } return; } static ERL_NIF_TERM khash_to_list(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { khash_priv* priv = (khash_priv*) enif_priv_data(env); ERL_NIF_TERM ret = enif_make_list(env, 0); khash_t* khash = NULL; void* res = NULL; hscan_t scan; hnode_t* entry; khnode_t* node; ERL_NIF_TERM key; ERL_NIF_TERM val; ERL_NIF_TERM tuple; if(argc != 1) { return enif_make_badarg(env); } if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { return enif_make_badarg(env); } khash = (khash_t*) res; if(!check_pid(env, khash)) { return enif_make_badarg(env); } kl_hash_scan_begin(&scan, khash->h); while((entry = kl_hash_scan_next(&scan)) != NULL) { node = (khnode_t*) kl_hnode_getkey(entry); key = enif_make_copy(env, node->key); val = enif_make_copy(env, node->val); tuple = enif_make_tuple2(env, key, val); ret = enif_make_list_cell(env, tuple, ret); } return ret; } static ERL_NIF_TERM khash_clear(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { khash_priv* priv = enif_priv_data(env); khash_t* khash = NULL; void* res = NULL; if(argc != 1) { return enif_make_badarg(env); } if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { return enif_make_badarg(env); } khash = (khash_t*) res; if(!check_pid(env, khash)) { return enif_make_badarg(env); } kl_hash_free_nodes(khash->h); khash->gen += 1; return priv->atom_ok; } static INLINE hnode_t* khash_lookup_int(ErlNifEnv* env, uint32_t hv, ERL_NIF_TERM key, khash_t* khash) { khnode_t node; node.hval = hv; node.env = env; node.key = key; return kl_hash_lookup(khash->h, &node); } static ERL_NIF_TERM khash_lookup(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { khash_priv* priv = enif_priv_data(env); khash_t* khash = NULL; void* res = NULL; uint32_t hval; hnode_t* entry; khnode_t* node; ERL_NIF_TERM ret; if(argc != 3) { return enif_make_badarg(env); } if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { return enif_make_badarg(env); } khash = (khash_t*) res; if(!check_pid(env, khash)) { return enif_make_badarg(env); } if(!enif_get_uint(env, argv[1], &hval)) { return enif_make_badarg(env); } entry = khash_lookup_int(env, hval, argv[2], khash); if(entry == NULL) { ret = priv->atom_not_found; } else { node = (khnode_t*) kl_hnode_getkey(entry); ret = enif_make_copy(env, node->val); ret = enif_make_tuple2(env, priv->atom_value, ret); } return ret; } static ERL_NIF_TERM khash_get(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { khash_priv* priv = enif_priv_data(env); khash_t* khash = NULL; void* res = NULL; uint32_t hval; hnode_t* entry; khnode_t* node; ERL_NIF_TERM ret; if(argc != 4) { return enif_make_badarg(env); } if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { return enif_make_badarg(env); } khash = (khash_t*) res; if(!check_pid(env, khash)) { return enif_make_badarg(env); } if(!enif_get_uint(env, argv[1], &hval)) { return enif_make_badarg(env); } entry = khash_lookup_int(env, hval, argv[2], khash); if(entry == NULL) { ret = argv[3]; } else { node = (khnode_t*) kl_hnode_getkey(entry); ret = enif_make_copy(env, node->val); } return ret; } static ERL_NIF_TERM khash_put(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { khash_priv* priv = enif_priv_data(env); khash_t* khash = NULL; void* res = NULL; uint32_t hval; hnode_t* entry; khnode_t* node; if(argc != 4) { return enif_make_badarg(env); } if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { return enif_make_badarg(env); } khash = (khash_t*) res; if(!check_pid(env, khash)) { return enif_make_badarg(env); } if(!enif_get_uint(env, argv[1], &hval)) { return enif_make_badarg(env); } entry = khash_lookup_int(env, hval, argv[2], khash); if(entry == NULL) { entry = khnode_alloc(NULL); node = (khnode_t*) kl_hnode_getkey(entry); node->hval = hval; node->key = enif_make_copy(node->env, argv[2]); node->val = enif_make_copy(node->env, argv[3]); kl_hash_insert(khash->h, entry, node); } else { node = (khnode_t*) kl_hnode_getkey(entry); enif_clear_env(node->env); node->key = enif_make_copy(node->env, argv[2]); node->val = enif_make_copy(node->env, argv[3]); } khash->gen += 1; return priv->atom_ok; } static ERL_NIF_TERM khash_del(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { khash_priv* priv = enif_priv_data(env); khash_t* khash = NULL; void* res = NULL; uint32_t hval; hnode_t* entry; ERL_NIF_TERM ret; if(argc != 3) { return enif_make_badarg(env); } if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { return enif_make_badarg(env); } khash = (khash_t*) res; if(!check_pid(env, khash)) { return enif_make_badarg(env); } if(!enif_get_uint(env, argv[1], &hval)) { return enif_make_badarg(env); } entry = khash_lookup_int(env, hval, argv[2], khash); if(entry == NULL) { ret = priv->atom_not_found; } else { kl_hash_delete_free(khash->h, entry); ret = priv->atom_ok; } khash->gen += 1; return ret; } static ERL_NIF_TERM khash_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { khash_priv* priv = enif_priv_data(env); khash_t* khash; if(argc != 1) { return enif_make_badarg(env); } if(!enif_get_resource(env, argv[0], priv->res_hash, (void*) &khash)) { return enif_make_badarg(env); } if(!check_pid(env, khash)) { return enif_make_badarg(env); } return enif_make_uint64(env, kl_hash_count(khash->h)); } static ERL_NIF_TERM khash_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { khash_priv* priv = enif_priv_data(env); khash_t* khash = NULL; void* res = NULL; khash_iter_t* iter; ERL_NIF_TERM ret; if(argc != 1) { return enif_make_badarg(env); } if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { return enif_make_badarg(env); } khash = (khash_t*) res; if(!check_pid(env, khash)) { return enif_make_badarg(env); } iter = (khash_iter_t*) enif_alloc_resource( priv->res_iter, sizeof(khash_iter_t)); memset(iter, '\0', sizeof(khash_iter_t)); iter->version = KHASH_VERSION; iter->gen = khash->gen; iter->khash = khash; kl_hash_scan_begin(&(iter->scan), iter->khash->h); // The iterator needs to guarantee that the khash // remains alive for the life of the iterator. enif_keep_resource(khash); ret = enif_make_resource(env, iter); enif_release_resource(iter); return make_ok(env, priv, ret); } static void khash_iter_free(ErlNifEnv* env, void* obj) { khash_iter_t* iter = (khash_iter_t*) obj; enif_release_resource(iter->khash); } static ERL_NIF_TERM khash_iter_next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { khash_priv* priv = enif_priv_data(env); khash_iter_t* iter = NULL; void* res = NULL; hnode_t* entry; khnode_t* node; ERL_NIF_TERM key; ERL_NIF_TERM val; if(argc != 1) { return enif_make_badarg(env); } if(!enif_get_resource(env, argv[0], priv->res_iter, &res)) { return enif_make_badarg(env); } iter = (khash_iter_t*) res; if(!check_pid(env, iter->khash)) { return enif_make_badarg(env); } if(iter->gen != iter->khash->gen) { return make_error(env, priv, priv->atom_expired_iterator); } entry = kl_hash_scan_next(&(iter->scan)); if(entry == NULL) { return priv->atom_end_of_table; } node = (khnode_t*) kl_hnode_getkey(entry); key = enif_make_copy(env, node->key); val = enif_make_copy(env, node->val); return enif_make_tuple2(env, key, val); } static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) { int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; ErlNifResourceType* res; khash_priv* new_priv = (khash_priv*) enif_alloc(sizeof(khash_priv)); if(new_priv == NULL) { return 1; } res = enif_open_resource_type( env, NULL, "khash", khash_free, flags, NULL); if(res == NULL) { return 1; } new_priv->res_hash = res; res = enif_open_resource_type( env, NULL, "khash_iter", khash_iter_free, flags, NULL); if(res == NULL) { return 1; } new_priv->res_iter = res; new_priv->atom_ok = make_atom(env, "ok"); new_priv->atom_error = make_atom(env, "error"); new_priv->atom_value = make_atom(env, "value"); new_priv->atom_not_found = make_atom(env, "not_found"); new_priv->atom_end_of_table = make_atom(env, "end_of_table"); new_priv->atom_expired_iterator = make_atom(env, "expired_iterator"); *priv = (void*) new_priv; return 0; } static int reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) { return 0; } static int upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) { return load(env, priv, info); } static void unload(ErlNifEnv* env, void* priv) { enif_free(priv); return; } static ErlNifFunc funcs[] = { {"new", 1, khash_new}, {"to_list", 1, khash_to_list}, {"clear", 1, khash_clear}, {"lookup_int", 3, khash_lookup}, {"get_int", 4, khash_get}, {"put_int", 4, khash_put}, {"del_int", 3, khash_del}, {"size", 1, khash_size}, {"iter", 1, khash_iter}, {"iter_next", 1, khash_iter_next} }; ERL_NIF_INIT(khash, funcs, &load, &reload, &upgrade, &unload);