Skip to content

Commit 652d60e

Browse files
committed
Merge pull request #1405 from TheSobkiewicz/thesobkiewicz/nifs/ets/refactor_insert
Add option to insert list in ets:insert, ets:lookup refactor - Enabled ets:insert/2 to accept lists for bulk insertion. - Extracted helper functions for ets:lookup/2 and ets:insert/2 that do not apply table locks. The new helper functions can be utilized in the following ETS operations to reduce code duplication: - ets:update_element/3 - ets:insert_new/2 - ets:update_counter/3 - ets:update_counter/4 - ets:take/2 - ets:delete_object/2 Every mentioned function will be implemented after merging of this PR. These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 312f7c6 + 7640eaa commit 652d60e

File tree

7 files changed

+171
-71
lines changed

7 files changed

+171
-71
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Added `socket:getopt/2`
1717
- Added `supervisor:terminate_child/2`, `supervisor:restart_child/2` and `supervisor:delete_child/2`
1818
- Added `esp:partition_read/3`, and documentation for `esp:partition_erase_range/2/3` and `esp:partition_write/3`
19+
- Added support for list insertion in 'ets:insert/2'.
1920

2021
### Fixed
2122
- ESP32: improved sntp sync speed from a cold boot.

libs/estdlib/src/ets.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ new(_Name, _Options) ->
6363
%% @doc Insert an entry into an ets table.
6464
%% @end
6565
%%-----------------------------------------------------------------------------
66-
-spec insert(Table :: table(), Entry :: tuple()) -> true.
66+
-spec insert(Table :: table(), Entry :: tuple() | [tuple()]) -> true.
6767
insert(_Table, _Entry) ->
6868
erlang:nif_error(undefined).
6969

src/libAtomVM/ets.c

Lines changed: 90 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -252,58 +252,104 @@ static void ets_delete_all_tables(struct Ets *ets, GlobalContext *global)
252252
ets_delete_tables_internal(ets, true_pred, NULL, global);
253253
}
254254

255-
EtsErrorCode ets_insert(term ref, term entry, Context *ctx)
255+
static EtsErrorCode ets_table_insert(struct EtsTable *ets_table, term entry, Context *ctx)
256256
{
257-
struct EtsTable *ets_table = term_is_atom(ref) ? ets_get_table_by_name(&ctx->global->ets, ref, TableAccessWrite) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(ref), TableAccessWrite);
258-
if (ets_table == NULL) {
259-
return EtsTableNotFound;
260-
}
261-
262257
if (ets_table->access_type != EtsAccessPublic && ets_table->owner_process_id != ctx->process_id) {
263-
SMP_UNLOCK(ets_table);
264258
return EtsPermissionDenied;
265259
}
266260

267-
if ((size_t) term_get_tuple_arity(entry) < (ets_table->keypos + 1)) {
268-
SMP_UNLOCK(ets_table);
261+
size_t keypos = ets_table->keypos;
262+
263+
if ((size_t) term_get_tuple_arity(entry) < keypos + 1) {
269264
return EtsBadEntry;
270265
}
271266

272-
Heap *heap = malloc(sizeof(Heap));
273-
if (IS_NULL_PTR(heap)) {
274-
SMP_UNLOCK(ets_table);
267+
struct HNode *new_node = ets_hashtable_new_node(entry, keypos);
268+
if (IS_NULL_PTR(new_node)) {
275269
return EtsAllocationFailure;
276270
}
277-
size_t size = (size_t) memory_estimate_usage(entry);
278-
if (memory_init_heap(heap, size) != MEMORY_GC_OK) {
279-
free(heap);
280-
SMP_UNLOCK(ets_table);
271+
272+
EtsHashtableErrorCode res = ets_hashtable_insert(ets_table->hashtable, new_node, EtsHashtableAllowOverwrite, ctx->global);
273+
if (UNLIKELY(res != EtsHashtableOk)) {
281274
return EtsAllocationFailure;
282275
}
283276

284-
term new_entry = memory_copy_term_tree(heap, entry);
285-
term key = term_get_tuple_element(new_entry, (int) ets_table->keypos);
277+
return EtsOk;
278+
}
286279

287-
EtsErrorCode ret = EtsOk;
288-
EtsHashtableErrorCode res = ets_hashtable_insert(ets_table->hashtable, key, new_entry, EtsHashtableAllowOverwrite, heap, ctx->global);
289-
if (UNLIKELY(res != EtsHashtableOk)) {
290-
ret = EtsAllocationFailure;
280+
static EtsErrorCode ets_table_insert_list(struct EtsTable *ets_table, term list, Context *ctx)
281+
{
282+
if (ets_table->access_type != EtsAccessPublic && ets_table->owner_process_id != ctx->process_id) {
283+
return EtsPermissionDenied;
291284
}
292285

293-
SMP_UNLOCK(ets_table);
286+
term iter = list;
287+
size_t size = 0;
294288

295-
return ret;
289+
while (term_is_nonempty_list(iter)) {
290+
term tuple = term_get_list_head(iter);
291+
iter = term_get_list_tail(iter);
292+
if (!term_is_tuple(tuple) || (size_t) term_get_tuple_arity(tuple) < (ets_table->keypos + 1)) {
293+
return EtsBadEntry;
294+
}
295+
++size;
296+
}
297+
if (!term_is_nil(iter)) {
298+
return EtsBadEntry;
299+
}
300+
301+
struct HNode **nodes = malloc(size * sizeof(struct HNode *));
302+
if (IS_NULL_PTR(nodes)) {
303+
return EtsAllocationFailure;
304+
}
305+
306+
size_t i = 0;
307+
while (term_is_nonempty_list(list)) {
308+
term tuple = term_get_list_head(list);
309+
nodes[i] = ets_hashtable_new_node(tuple, ets_table->keypos);
310+
if (IS_NULL_PTR(nodes[i])) {
311+
ets_hashtable_free_node_array(nodes, i, ctx->global);
312+
free(nodes);
313+
return EtsAllocationFailure;
314+
}
315+
++i;
316+
list = term_get_list_tail(list);
317+
}
318+
319+
for (size_t i = 0; i < size; ++i) {
320+
321+
EtsHashtableErrorCode res = ets_hashtable_insert(ets_table->hashtable, nodes[i], EtsHashtableAllowOverwrite, ctx->global);
322+
assert(res == EtsHashtableOk);
323+
}
324+
325+
free(nodes);
326+
return EtsOk;
296327
}
297328

298-
EtsErrorCode ets_lookup(term ref, term key, term *ret, Context *ctx)
329+
EtsErrorCode ets_insert(term name_or_ref, term entry, Context *ctx)
299330
{
300-
struct EtsTable *ets_table = term_is_atom(ref) ? ets_get_table_by_name(&ctx->global->ets, ref, TableAccessRead) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(ref), TableAccessRead);
331+
struct EtsTable *ets_table = term_is_atom(name_or_ref) ? ets_get_table_by_name(&ctx->global->ets, name_or_ref, TableAccessWrite) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(name_or_ref), TableAccessWrite);
301332
if (ets_table == NULL) {
302333
return EtsTableNotFound;
303334
}
304335

336+
EtsErrorCode result;
337+
if (term_is_tuple(entry)) {
338+
result = ets_table_insert(ets_table, entry, ctx);
339+
} else if (term_is_list(entry)) {
340+
result = ets_table_insert_list(ets_table, entry, ctx);
341+
} else {
342+
result = EtsBadEntry;
343+
}
344+
345+
SMP_UNLOCK(ets_table);
346+
347+
return result;
348+
}
349+
350+
static EtsErrorCode ets_table_lookup(struct EtsTable *ets_table, term key, term *ret, Context *ctx)
351+
{
305352
if (ets_table->access_type == EtsAccessPrivate && ets_table->owner_process_id != ctx->process_id) {
306-
SMP_UNLOCK(ets_table);
307353
return EtsPermissionDenied;
308354
}
309355

@@ -316,24 +362,35 @@ EtsErrorCode ets_lookup(term ref, term key, term *ret, Context *ctx)
316362
size_t size = (size_t) memory_estimate_usage(res);
317363
// allocate [object]
318364
if (UNLIKELY(memory_ensure_free_opt(ctx, size + CONS_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
319-
SMP_UNLOCK(ets_table);
320365
return EtsAllocationFailure;
321366
}
322367
term new_res = memory_copy_term_tree(&ctx->heap, res);
323368
*ret = term_list_prepend(new_res, term_nil(), &ctx->heap);
324369
}
325-
SMP_UNLOCK(ets_table);
326370

327371
return EtsOk;
328372
}
329373

330-
EtsErrorCode ets_lookup_element(term ref, term key, size_t pos, term *ret, Context *ctx)
374+
EtsErrorCode ets_lookup(term name_or_ref, term key, term *ret, Context *ctx)
375+
{
376+
struct EtsTable *ets_table = term_is_atom(name_or_ref) ? ets_get_table_by_name(&ctx->global->ets, name_or_ref, TableAccessRead) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(name_or_ref), TableAccessRead);
377+
if (ets_table == NULL) {
378+
return EtsTableNotFound;
379+
}
380+
381+
EtsErrorCode result = ets_table_lookup(ets_table, key, ret, ctx);
382+
SMP_UNLOCK(ets_table);
383+
384+
return result;
385+
}
386+
387+
EtsErrorCode ets_lookup_element(term name_or_ref, term key, size_t pos, term *ret, Context *ctx)
331388
{
332389
if (UNLIKELY(pos == 0)) {
333390
return EtsBadPosition;
334391
}
335392

336-
struct EtsTable *ets_table = term_is_atom(ref) ? ets_get_table_by_name(&ctx->global->ets, ref, TableAccessRead) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(ref), TableAccessRead);
393+
struct EtsTable *ets_table = term_is_atom(name_or_ref) ? ets_get_table_by_name(&ctx->global->ets, name_or_ref, TableAccessRead) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(name_or_ref), TableAccessRead);
337394
if (ets_table == NULL) {
338395
return EtsTableNotFound;
339396
}
@@ -368,9 +425,9 @@ EtsErrorCode ets_lookup_element(term ref, term key, size_t pos, term *ret, Conte
368425
return EtsOk;
369426
}
370427

371-
EtsErrorCode ets_delete(term ref, term key, term *ret, Context *ctx)
428+
EtsErrorCode ets_delete(term name_or_ref, term key, term *ret, Context *ctx)
372429
{
373-
struct EtsTable *ets_table = term_is_atom(ref) ? ets_get_table_by_name(&ctx->global->ets, ref, TableAccessRead) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(ref), TableAccessRead);
430+
struct EtsTable *ets_table = term_is_atom(name_or_ref) ? ets_get_table_by_name(&ctx->global->ets, name_or_ref, TableAccessRead) : ets_get_table_by_ref(&ctx->global->ets, term_to_ref_ticks(name_or_ref), TableAccessRead);
374431
if (ets_table == NULL) {
375432
return EtsTableNotFound;
376433
}

src/libAtomVM/ets_hashtable.c

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct HNode
3535
struct HNode *next;
3636
term key;
3737
term entry;
38-
Heap *heap;
38+
Heap heap;
3939
};
4040

4141
static uint32_t hash_term(term t, GlobalContext *global);
@@ -53,14 +53,26 @@ struct EtsHashTable *ets_hashtable_new()
5353
return htable;
5454
}
5555

56+
static void ets_hashtable_free_node(struct HNode *node, GlobalContext *global)
57+
{
58+
memory_destroy_heap(&node->heap, global);
59+
free(node);
60+
}
61+
62+
void ets_hashtable_free_node_array(struct HNode **allocated, size_t size, GlobalContext *global)
63+
{
64+
for (size_t i = 0; i < size; ++i) {
65+
ets_hashtable_free_node(allocated[i], global);
66+
}
67+
}
68+
5669
void ets_hashtable_destroy(struct EtsHashTable *hash_table, GlobalContext *global)
5770
{
5871
for (size_t i = 0; i < hash_table->capacity; ++i) {
5972
struct HNode *node = hash_table->buckets[i];
60-
while (node != 0) {
61-
memory_destroy_heap(node->heap, global);
73+
while (node != NULL) {
6274
struct HNode *next_node = node->next;
63-
free(node);
75+
ets_hashtable_free_node(node, global);
6476
node = next_node;
6577
}
6678
}
@@ -82,8 +94,33 @@ static void print_info(struct EtsHashTable *hash_table)
8294
}
8395
#endif
8496

85-
EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, term key, term entry, EtsHashtableOptions opts, Heap *heap, GlobalContext *global)
97+
struct HNode *ets_hashtable_new_node(term entry, int keypos)
8698
{
99+
100+
struct HNode *new_node = malloc(sizeof(struct HNode));
101+
if (IS_NULL_PTR(new_node)) {
102+
return NULL;
103+
}
104+
105+
size_t size = (size_t) memory_estimate_usage(entry);
106+
if (memory_init_heap(&new_node->heap, size) != MEMORY_GC_OK) {
107+
free(new_node);
108+
return NULL;
109+
}
110+
111+
term new_entry = memory_copy_term_tree(&new_node->heap, entry);
112+
term key = term_get_tuple_element(new_entry, keypos);
113+
114+
new_node->next = NULL;
115+
new_node->key = key;
116+
new_node->entry = new_entry;
117+
118+
return new_node;
119+
}
120+
121+
EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, struct HNode *new_node, EtsHashtableOptions opts, GlobalContext *global)
122+
{
123+
term key = new_node->key;
87124
uint32_t hash = hash_term(key, global);
88125
uint32_t index = hash % hash_table->capacity;
89126

@@ -94,38 +131,30 @@ EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, term
94131
#endif
95132

96133
struct HNode *node = hash_table->buckets[index];
97-
if (node) {
98-
while (1) {
99-
if (term_compare(key, node->key, TermCompareExact, global) == TermEquals) {
100-
if (opts & EtsHashtableAllowOverwrite) {
101-
node->entry = entry;
102-
memory_destroy_heap(node->heap, global);
103-
node->heap = heap;
104-
return EtsHashtableOk;
134+
struct HNode *last_node = NULL;
135+
while (node) {
136+
if (term_compare(key, node->key, TermCompareExact, global) == TermEquals) {
137+
if (opts & EtsHashtableAllowOverwrite) {
138+
if (IS_NULL_PTR(last_node)) {
139+
new_node->next = node->next;
140+
hash_table->buckets[index] = new_node;
105141
} else {
106-
return EtsHashtableFailure;
142+
last_node->next = new_node;
143+
new_node->next = node->next;
107144
}
108-
}
109-
110-
if (node->next) {
111-
node = node->next;
145+
ets_hashtable_free_node(node, global);
146+
return EtsHashtableOk;
112147
} else {
113-
break;
148+
ets_hashtable_free_node(new_node, global);
149+
return EtsHashtableFailure;
114150
}
115151
}
152+
last_node = node;
153+
node = node->next;
116154
}
117155

118-
struct HNode *new_node = malloc(sizeof(struct HNode));
119-
if (IS_NULL_PTR(new_node)) {
120-
return EtsHashtableError;
121-
}
122-
new_node->next = NULL;
123-
new_node->key = key;
124-
new_node->entry = entry;
125-
new_node->heap = heap;
126-
127-
if (node) {
128-
node->next = new_node;
156+
if (last_node) {
157+
last_node->next = new_node;
129158
} else {
130159
hash_table->buckets[index] = new_node;
131160
}
@@ -165,7 +194,7 @@ bool ets_hashtable_remove(struct EtsHashTable *hash_table, term key, size_t keyp
165194
term key_to_compare = term_get_tuple_element(node->entry, keypos);
166195
if (term_compare(key, key_to_compare, TermCompareExact, global) == TermEquals) {
167196

168-
memory_destroy_heap(node->heap, global);
197+
memory_destroy_heap(&node->heap, global);
169198
struct HNode *next_node = node->next;
170199
free(node);
171200

src/libAtomVM/ets_hashtable.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@ typedef enum EtsHashtableErrorCode
5252
struct EtsHashTable *ets_hashtable_new();
5353
void ets_hashtable_destroy(struct EtsHashTable *hash_table, GlobalContext *global);
5454

55-
EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, term key, term entry, EtsHashtableOptions opts, Heap *heap, GlobalContext *global);
55+
EtsHashtableErrorCode ets_hashtable_insert(struct EtsHashTable *hash_table, struct HNode *new_node, EtsHashtableOptions opts, GlobalContext *global);
5656
term ets_hashtable_lookup(struct EtsHashTable *hash_table, term key, size_t keypos, GlobalContext *global);
5757
bool ets_hashtable_remove(struct EtsHashTable *hash_table, term key, size_t keypos, GlobalContext *global);
58+
struct HNode *ets_hashtable_new_node(term entry, int keypos);
59+
void ets_hashtable_free_node_array(struct HNode **allocated, size_t len, GlobalContext *global);
5860

5961
#ifdef __cplusplus
6062
}

src/libAtomVM/nifs.c

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3327,10 +3327,6 @@ static term nif_ets_insert(Context *ctx, int argc, term argv[])
33273327
VALIDATE_VALUE(ref, is_ets_table_id);
33283328

33293329
term entry = argv[1];
3330-
VALIDATE_VALUE(entry, term_is_tuple);
3331-
if (term_get_tuple_arity(entry) < 1) {
3332-
RAISE_ERROR(BADARG_ATOM);
3333-
}
33343330

33353331
EtsErrorCode result = ets_insert(ref, entry, ctx);
33363332
switch (result) {

tests/erlang_tests/test_ets.erl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ start() ->
3131
ok = test_protected_access(),
3232
ok = test_public_access(),
3333
ok = test_lookup_element(),
34-
34+
ok = test_insert_list(),
3535
0.
3636

3737
test_basic() ->
@@ -352,3 +352,18 @@ test_lookup_element() ->
352352
expect_failure(fun() -> ets:lookup_element(Tid, foo, 3) end),
353353
expect_failure(fun() -> ets:lookup_element(Tid, foo, 0) end),
354354
ok.
355+
356+
test_insert_list() ->
357+
Tid = ets:new(test_insert_list, []),
358+
true = ets:insert(Tid, [{foo, tapas}, {batat, batat}, {patat, patat}]),
359+
true = ets:insert(Tid, [{foo, tapas}, {batat, batat}, {patat, patat}]),
360+
[{patat, patat}] = ets:lookup(Tid, patat),
361+
[{batat, batat}] = ets:lookup(Tid, batat),
362+
true = ets:insert(Tid, []),
363+
expect_failure(fun() -> ets:insert(Tid, [{foo, tapas} | {patat, patat}]) end),
364+
expect_failure(fun() -> ets:insert(Tid, [{foo, tapas}, {batat, batat}, {patat, patat}, {}]) end),
365+
expect_failure(fun() ->
366+
ets:insert(Tid, [{foo, tapas}, pararara, {batat, batat}, {patat, patat}])
367+
end),
368+
expect_failure(fun() -> ets:insert(Tid, [{}]) end),
369+
ok.

0 commit comments

Comments
 (0)