diff --git a/CHANGELOG.md b/CHANGELOG.md index 6db6dd9ca..de15c9b72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +- Added support for list insertion in 'ets:insert/2'. ### Added - Added a limited implementation of the OTP `ets` interface @@ -25,7 +26,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the ability to run beams from the CLI for Generic Unix platform (it was already possible with nodejs and emscripten). - Added support for 'erlang:--/2'. -- Added support for list insertion in 'ets:insert/2'. ### Fixed diff --git a/src/libAtomVM/ets.c b/src/libAtomVM/ets.c index ecba07e29..e2cc16358 100644 --- a/src/libAtomVM/ets.c +++ b/src/libAtomVM/ets.c @@ -262,21 +262,15 @@ static EtsErrorCode ets_table_insert(struct EtsTable *ets_table, term entry, Con return EtsBadEntry; } - Heap *heap = malloc(sizeof(Heap)); - if (IS_NULL_PTR(heap)) { - return EtsAllocationFailure; - } - size_t size = (size_t) memory_estimate_usage(entry); - if (memory_init_heap(heap, size) != MEMORY_GC_OK) { - free(heap); + int keypos = (int) ets_table->keypos; + + struct HNode *new_node = ets_hashtable_new_node(entry, keypos, ctx->global); + if (IS_NULL_PTR(new_node)) { return EtsAllocationFailure; } - term new_entry = memory_copy_term_tree(heap, entry); - term key = term_get_tuple_element(new_entry, (int) ets_table->keypos); - EtsErrorCode result = EtsOk; - EtsHashtableErrorCode res = ets_hashtable_insert(ets_table->hashtable, key, new_entry, EtsHashtableAllowOverwrite, heap, ctx->global); + EtsHashtableErrorCode res = ets_hashtable_insert(ets_table->hashtable, new_node, EtsHashtableAllowOverwrite, ctx->global); if (UNLIKELY(res != EtsHashtableOk)) { result = EtsAllocationFailure; } @@ -286,7 +280,11 @@ static EtsErrorCode ets_table_insert(struct EtsTable *ets_table, term entry, Con static EtsErrorCode ets_table_insert_list(struct EtsTable *ets_table, term list, Context *ctx) { + if (ets_table->access_type != EtsAccessPublic && ets_table->owner_process_id != ctx->process_id) { + return EtsPermissionDenied; + } term iter = list; + size_t size = 0; while (term_is_nonempty_list(iter)) { term tuple = term_get_list_head(iter); @@ -294,21 +292,35 @@ static EtsErrorCode ets_table_insert_list(struct EtsTable *ets_table, term list, if (!term_is_tuple(tuple) || (size_t) term_get_tuple_arity(tuple) < (ets_table->keypos + 1)) { return EtsBadEntry; } + ++size; } if (!term_is_nil(iter)) { return EtsBadEntry; } + struct HNode **nodes = malloc(size * sizeof(struct HNode *)); + if (IS_NULL_PTR(nodes)) { + return EtsAllocationFailure; + } + + int i = 0; while (term_is_nonempty_list(list)) { term tuple = term_get_list_head(list); - EtsErrorCode result = ets_table_insert(ets_table, tuple, ctx); - if (UNLIKELY(result != EtsOk)) { - AVM_ABORT(); // Abort because operation might not be atomic. + nodes[i] = ets_hashtable_new_node(tuple, ets_table->keypos, ctx->global); + if (IS_NULL_PTR(nodes[i])) { + ets_hashtable_free_node_array(nodes, i, ctx->global); + free(nodes); + return EtsAllocationFailure; } - + ++i; list = term_get_list_tail(list); } + for (size_t i = 0; i < size; ++i) { + ets_hashtable_insert(ets_table->hashtable, nodes[i], EtsHashtableAllowOverwrite, ctx->global); // EtsHashtableAllowOverwrite cannot be changed here because it will result in data inconsistency. + } + + free(nodes); return EtsOk; } diff --git a/src/libAtomVM/ets_hashtable.c b/src/libAtomVM/ets_hashtable.c index 77e2d2f9b..800c1601b 100644 --- a/src/libAtomVM/ets_hashtable.c +++ b/src/libAtomVM/ets_hashtable.c @@ -82,8 +82,45 @@ static void print_info(struct EtsHashTable *hash_table) } #endif -EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, term key, term entry, EtsHashtableOptions opts, Heap *heap, GlobalContext *global) +struct HNode *ets_hashtable_new_node(term entry, int keypos, GlobalContext *global) { + Heap *heap = malloc(sizeof(Heap)); + if (IS_NULL_PTR(heap)) { + return NULL; + } + size_t size = (size_t) memory_estimate_usage(entry); + if (memory_init_heap(heap, size) != MEMORY_GC_OK) { + free(heap); + return NULL; + } + + term new_entry = memory_copy_term_tree(heap, entry); + struct HNode *new_node = malloc(sizeof(struct HNode)); + if (IS_NULL_PTR(new_node)) { + memory_destroy_heap(heap, global); + return NULL; + } + term key = term_get_tuple_element(new_entry, keypos); + + new_node->next = NULL; + new_node->key = key; + new_node->entry = new_entry; + new_node->heap = heap; + + return new_node; +} + +void ets_hashtable_free_node_array(struct HNode **allocated, size_t size, GlobalContext *global) +{ + for (size_t i = 0; i < size; ++i) { + memory_destroy_heap(allocated[i]->heap, global); + free(allocated[i]); + } +} + +EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, struct HNode *new_node, EtsHashtableOptions opts, GlobalContext *global) +{ + term key = new_node->key; uint32_t hash = hash_term(key, global); uint32_t index = hash % hash_table->capacity; @@ -94,38 +131,34 @@ EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, term #endif struct HNode *node = hash_table->buckets[index]; + struct HNode *last_node = NULL; if (node) { - while (1) { + while (node) { if (term_compare(key, node->key, TermCompareExact, global) == TermEquals) { if (opts & EtsHashtableAllowOverwrite) { - node->entry = entry; + if (IS_NULL_PTR(last_node)) { + new_node->next = node->next; + hash_table->buckets[index] = new_node; + } else { + last_node->next = new_node; + new_node->next = node->next; + } memory_destroy_heap(node->heap, global); - node->heap = heap; + free(node); return EtsHashtableOk; } else { + memory_destroy_heap(new_node->heap, global); + free(new_node); return EtsHashtableFailure; } } - - if (node->next) { - node = node->next; - } else { - break; - } + last_node = node; + node = node->next; } } - struct HNode *new_node = malloc(sizeof(struct HNode)); - if (IS_NULL_PTR(new_node)) { - return EtsHashtableError; - } - new_node->next = NULL; - new_node->key = key; - new_node->entry = entry; - new_node->heap = heap; - - if (node) { - node->next = new_node; + if (last_node) { + last_node->next = new_node; } else { hash_table->buckets[index] = new_node; } diff --git a/src/libAtomVM/ets_hashtable.h b/src/libAtomVM/ets_hashtable.h index f8900b519..05068ee09 100644 --- a/src/libAtomVM/ets_hashtable.h +++ b/src/libAtomVM/ets_hashtable.h @@ -52,9 +52,11 @@ typedef enum EtsHashtableErrorCode struct EtsHashTable *ets_hashtable_new(); void ets_hashtable_destroy(struct EtsHashTable *hash_table, GlobalContext *global); -EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, term key, term entry, EtsHashtableOptions opts, Heap *heap, GlobalContext *global); +EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, struct HNode *new_node, EtsHashtableOptions opts, GlobalContext *global); term ets_hashtable_lookup(struct EtsHashTable *hash_table, term key, size_t keypos, GlobalContext *global); bool ets_hashtable_remove(struct EtsHashTable *hash_table, term key, size_t keypos, GlobalContext *global); +struct HNode *ets_hashtable_new_node(term entry, int keypos, GlobalContext *global); +void ets_hashtable_free_node_array(struct HNode **allocated, size_t len, GlobalContext *global); #ifdef __cplusplus } diff --git a/tests/erlang_tests/test_ets.erl b/tests/erlang_tests/test_ets.erl index eafc2b65b..f1663cbde 100644 --- a/tests/erlang_tests/test_ets.erl +++ b/tests/erlang_tests/test_ets.erl @@ -356,6 +356,7 @@ test_lookup_element() -> test_insert_list() -> Tid = ets:new(test_insert_list, []), true = ets:insert(Tid, [{foo, tapas}, {batat, batat}, {patat, patat}]), + true = ets:insert(Tid, [{foo, tapas}, {batat, batat}, {patat, patat}]), [{patat, patat}] = ets:lookup(Tid, patat), [{batat, batat}] = ets:lookup(Tid, batat), true = ets:insert(Tid, []),