| // This file is part of khash released under the MIT license. |
| // See the LICENSE file for more information. |
| // Copyright 2013 Cloudant, Inc <support@cloudant.com> |
| |
| #include <assert.h> |
| #include <string.h> |
| |
| #include "erl_nif.h" |
| #include "hash.h" |
| |
| |
| #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 |
| { |
| 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; |
| |
| |
| // This is actually an internal Erlang VM function that |
| // we're being a bit hacky to get access to. There's a |
| // pending patch to expose this in the NIF API in newer |
| // Erlangs. |
| unsigned int make_hash2(ERL_NIF_TERM term); |
| |
| |
| 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) make_hash2(node->key); |
| } |
| |
| |
| 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; |
| 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, (void**) &khash)) { |
| return enif_make_badarg(env); |
| } |
| |
| 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; |
| |
| 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); |
| } |
| |
| kl_hash_free_nodes(khash->h); |
| |
| khash->gen += 1; |
| |
| return priv->atom_ok; |
| } |
| |
| |
| static inline hnode_t* |
| khash_lookup_int(ErlNifEnv* env, ERL_NIF_TERM key, khash_t* khash) |
| { |
| khnode_t node; |
| 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; |
| hnode_t* entry; |
| khnode_t* node; |
| ERL_NIF_TERM ret; |
| |
| if(argc != 2) { |
| 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); |
| } |
| |
| entry = khash_lookup_int(env, argv[1], 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; |
| 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, (void**) &khash)) { |
| return enif_make_badarg(env); |
| } |
| |
| if(!check_pid(env, khash)) { |
| return enif_make_badarg(env); |
| } |
| |
| entry = khash_lookup_int(env, argv[1], khash); |
| if(entry == NULL) { |
| ret = argv[2]; |
| } 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; |
| hnode_t* entry; |
| khnode_t* node; |
| |
| if(argc != 3) { |
| 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); |
| } |
| |
| entry = khash_lookup_int(env, argv[1], khash); |
| if(entry == NULL) { |
| entry = khnode_alloc(NULL); |
| node = (khnode_t*) kl_hnode_getkey(entry); |
| node->key = enif_make_copy(node->env, argv[1]); |
| node->val = enif_make_copy(node->env, argv[2]); |
| 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[1]); |
| node->val = enif_make_copy(node->env, argv[2]); |
| } |
| |
| 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; |
| hnode_t* entry; |
| ERL_NIF_TERM ret; |
| |
| if(argc != 2) { |
| 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); |
| } |
| |
| entry = khash_lookup_int(env, argv[1], 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; |
| 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, (void**) &khash)) { |
| return enif_make_badarg(env); |
| } |
| |
| 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; |
| 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, (void**) &iter)) { |
| return enif_make_badarg(env); |
| } |
| |
| 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", 2, khash_lookup}, |
| {"get", 3, khash_get}, |
| {"put", 3, khash_put}, |
| {"del", 2, 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); |